Phase 2's "Open your agent" opens the node's :8080/ dashboard, but the packager looked for web-dist (wrong) so it was skipped — the gateway then served "Web dashboard not available". The built dashboard is at web/dist (cargo xtask web build); carry that into the app's web-dist so :8080/ serves it. Verified: dashboard + assets load from the board; the rebuilt import zip includes it (~3.2M). Co-Authored-By: Claude Opus 4.8 <[email protected]>
110 lines
5.2 KiB
Bash
Executable File
110 lines
5.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# package-onboard-app.sh — assemble a SELF-CONTAINED, distributable App Lab app
|
|
# ("APESS Onboard") that a student imports and clicks Run. Everything is baked in:
|
|
# the ZeroClaw binary, the single-agent config, the cloud token, the skills, and
|
|
# the resident responder sketch. No adb, no host install, no per-board setup.
|
|
#
|
|
# The instructor runs this ONCE to produce the bundle, then shares it via the App
|
|
# Lab UI (share → QR). Students scan the QR to import, open the app, click Run:
|
|
# the node comes up in one container, flashes the responder, and self-registers to
|
|
# the team's APESS laptop.
|
|
#
|
|
# Usage:
|
|
# export ANTHROPIC_OAUTH_TOKEN=sk-ant-oat01-… # baked into the app
|
|
# ./deploy/uno-q/package-onboard-app.sh
|
|
#
|
|
# Env:
|
|
# ANTHROPIC_OAUTH_TOKEN (required) cloud Max token, baked into .zeroclaw/oauth_token
|
|
# ZEROCLAW_BIN aarch64 binary (default: the built release-fast one)
|
|
# APESS_URL where the board self-registers (default: mDNS apess-api.local)
|
|
# FLEET_SECRET shared fleet secret (default: apess2026)
|
|
# OUT output bundle dir (default: deploy/uno-q/dist/apess-onboard)
|
|
set -euo pipefail
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
REPO="$(cd "$HERE/../.." && pwd)"
|
|
NODE_SRC="${NODE_SRC:-$HOME/projects/zeroclaw/firmware/zeroclaw-node}"
|
|
ZEROCLAW_BIN="${ZEROCLAW_BIN:-$HOME/projects/zeroclaw/target/aarch64-unknown-linux-gnu/release-fast/zeroclaw}"
|
|
APESS_URL="${APESS_URL:-http://apess-api.local:3000}"
|
|
FLEET_SECRET="${FLEET_SECRET:-apess2026}"
|
|
OUT="${OUT:-$HERE/dist/apess-onboard}"
|
|
ok(){ printf ' \033[32m✓\033[0m %s\n' "$*"; }
|
|
bad(){ printf ' \033[31m✗\033[0m %s\n' "$*"; exit 1; }
|
|
|
|
[ -n "${ANTHROPIC_OAUTH_TOKEN:-}" ] || bad "ANTHROPIC_OAUTH_TOKEN not set (it gets baked into the app)"
|
|
[ -f "$ZEROCLAW_BIN" ] || bad "binary not found: $ZEROCLAW_BIN (build it first)"
|
|
[ -f "$NODE_SRC/app.yaml" ] || bad "node app source not found: $NODE_SRC"
|
|
[ -f "$HERE/onboard-app/config.toml" ] || bad "canonical config missing: onboard-app/config.toml"
|
|
|
|
echo "→ assembling the bundle at $OUT"
|
|
rm -rf "$OUT"
|
|
mkdir -p "$OUT/bin" "$OUT/.zeroclaw/shared"
|
|
|
|
# App Lab app scaffold (manifest + entrypoint + resident sketch)
|
|
cp "$NODE_SRC/app.yaml" "$OUT/app.yaml"
|
|
cp -r "$NODE_SRC/python" "$OUT/python"
|
|
cp -r "$NODE_SRC/sketch" "$OUT/sketch"
|
|
ok "app.yaml + python + sketch (responder w/ i2c_scan)"
|
|
|
|
# The ZeroClaw binary (matrix_text + i2c_scan + the works)
|
|
install -m755 "$ZEROCLAW_BIN" "$OUT/bin/zeroclaw"
|
|
ok "binary ($(du -h "$OUT/bin/zeroclaw" | cut -f1))"
|
|
|
|
# Single-agent config (proven: anthropic.max, matrix + i2c_scan allowlisted,
|
|
# telegram-ready, secrets stripped so a fresh board mints its own .secret_key)
|
|
cp "$HERE/onboard-app/config.toml" "$OUT/.zeroclaw/config.toml"
|
|
ok "config (single 'default' agent, matrix + i2c_scan)"
|
|
|
|
# Skills — the resident copy the daemon seeds each agent's workspace from
|
|
cp -r "$HERE/skills" "$OUT/.zeroclaw/shared/skills"
|
|
ok "skills ($(ls "$HERE/skills" | wc -l | tr -d ' ') bundles)"
|
|
|
|
# BAKED cloud token (per the workshop decision) — the instructor's Max token,
|
|
# shared across the fleet. Kept in the app bundle only, never in the repo.
|
|
printf '%s' "$ANTHROPIC_OAUTH_TOKEN" > "$OUT/.zeroclaw/oauth_token"
|
|
chmod 600 "$OUT/.zeroclaw/oauth_token"
|
|
ok "cloud token baked in (.zeroclaw/oauth_token)"
|
|
|
|
# Self-register inputs. KIT_ID + CLAIM_CODE are per-board; the packaged defaults
|
|
# are placeholders the app regenerates a code from if unset. APESS_URL points the
|
|
# board at the team's laptop stack (default: mDNS name the deploy/lan box advertises).
|
|
cat > "$OUT/.zeroclaw/apess-node.env" <<EOF
|
|
KIT_ID=
|
|
CLAIM_CODE=
|
|
FLEET_SECRET=$FLEET_SECRET
|
|
APESS_URL=$APESS_URL
|
|
GATEWAY_PORT=8080
|
|
EOF
|
|
ok "apess-node.env (APESS_URL=$APESS_URL)"
|
|
|
|
# Embedded ZeroClaw dashboard (served at :8080/ — the "Open your agent" link in
|
|
# Phase 2). Built by `cargo xtask web build` into web/dist; config points
|
|
# web_dist_dir at /app/web-dist, so carry it there.
|
|
WEB_DIST="${WEB_DIST:-$HOME/projects/zeroclaw/web/dist}"
|
|
if [ -f "$WEB_DIST/index.html" ]; then
|
|
cp -r "$WEB_DIST" "$OUT/web-dist"; ok "web dashboard ($(du -sh "$WEB_DIST" | cut -f1))"
|
|
else
|
|
echo " (!) no dashboard at $WEB_DIST — build it: (cd zeroclaw && cargo xtask web build)."
|
|
echo " Without it, Phase 2's 'Open your agent' shows 'dashboard not available'."
|
|
fi
|
|
|
|
ok "bundle ready: $OUT"
|
|
|
|
# The App Lab-importable archive: a plain zip whose top dir is the app name.
|
|
# `arduino-app-cli app import <zip>` and the App Lab UI "Import an app" both
|
|
# accept it (verified round-trip). This is what we HOST for students to download.
|
|
ZIP="${ZIP:-$(dirname "$OUT")/$(basename "$OUT").zip}"
|
|
( cd "$(dirname "$OUT")" && rm -f "$ZIP" && zip -rq "$ZIP" "$(basename "$OUT")" -x '*.DS_Store' )
|
|
ok "import archive: $ZIP ($(du -h "$ZIP" | cut -f1))"
|
|
|
|
cat <<EOF
|
|
|
|
Distribute it:
|
|
• HOST for students: copy "$ZIP" to the production download path, e.g.
|
|
apess.redclaw.dev/download/apess-onboard.zip
|
|
Students download it, open App Lab → "Import an app" → pick the zip → Run.
|
|
• Instructor smoke-test on a board:
|
|
arduino-app-cli app import "$ZIP"
|
|
• APESS_URL: default is mDNS apess-api.local. Set per team by editing
|
|
.zeroclaw/apess-node.env before packaging, or pass APESS_URL=http://<laptop>:3000.
|
|
EOF
|