Files
apress/deploy/uno-q/package-onboard-app.sh
T
Omar SobhandClaude Opus 4.8 d3011130a3 fix(onboard): bake the ZeroClaw dashboard into the app (correct web/dist path)
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]>
2026-07-22 18:15:17 -07:00

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