feat(setup): three-level output (clack UI / progression log / raw per-step)
Documents and implements the output contract from docs/setup-flow.md:
Level 1: clack UI — branded, concise, product content
Level 2: logs/setup.log — append-only, linear, structured entries for
humans + AI agents reviewing a run
Level 3: logs/setup-steps/NN-name.log — full raw stdout+stderr per step
Every scripted sub-step, including bootstrap, emits at all three levels.
Bootstrap now runs under a bash-side clack-alike spinner with live elapsed
time; its apt/pnpm output is captured to 01-bootstrap.log and summarised
as a progression entry. setup.sh's legacy log() routes to the raw log
instead of contaminating the progression log.
Telegram install becomes fully branded: setup/auto.ts owns the BotFather
instructions (clack note), token paste (clack password with format
validation), and getMe check (clack spinner). add-telegram.sh drops to a
non-interactive installer that reads TELEGRAM_BOT_TOKEN from env, logs to
stderr, and emits a single ADD_TELEGRAM status block on stdout.
The Anthropic credential flow is the one intentional break — register-
claude-token.sh still inherits the TTY for claude setup-token's browser
dance; it logs as an 'interactive' progression entry with the method.
setup/logs.ts centralises the level 2/3 formatting: reset, header, step,
userInput, complete, abort, stepRawLog. User answers (display name, agent
name, channel choice, telegram_token preview) log as their own entries so
the setup path is reconstructable from the progression log alone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
162
nanoclaw.sh
162
nanoclaw.sh
@@ -2,12 +2,15 @@
|
||||
#
|
||||
# NanoClaw — scripted end-to-end install.
|
||||
#
|
||||
# Runs `bash setup.sh` (bootstrap: Node check, pnpm install, native module
|
||||
# verify), then `pnpm run setup:auto` (environment → container → onecli →
|
||||
# auth → mounts → service → cli-agent → channel → verify).
|
||||
# Phase 1: bootstrap (Node + pnpm + native module verify). Runs bash-side
|
||||
# since tsx isn't available until pnpm install completes.
|
||||
# Phase 2: setup:auto (all remaining steps under clack).
|
||||
#
|
||||
# Everything that can be scripted runs unattended; the one interactive pause
|
||||
# is the auth step (browser sign-in or paste token/API key).
|
||||
# Both phases obey the same three-level output contract (see
|
||||
# docs/setup-flow.md):
|
||||
# 1. User-facing — concise status line with elapsed time
|
||||
# 2. Progression log — logs/setup.log (header + one entry per phase/step)
|
||||
# 3. Raw per-step log — logs/setup-steps/NN-name.log (full verbatim output)
|
||||
#
|
||||
# Config via env — passed through unchanged:
|
||||
# NANOCLAW_SKIP comma-separated setup:auto step names to skip
|
||||
@@ -19,28 +22,163 @@ set -euo pipefail
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
LOGS_DIR="$PROJECT_ROOT/logs"
|
||||
STEPS_DIR="$LOGS_DIR/setup-steps"
|
||||
PROGRESS_LOG="$LOGS_DIR/setup.log"
|
||||
|
||||
# ─── log helpers ────────────────────────────────────────────────────────
|
||||
|
||||
ts_utc() { date -u +%Y-%m-%dT%H:%M:%SZ; }
|
||||
|
||||
write_header() {
|
||||
local ts
|
||||
ts=$(ts_utc)
|
||||
local branch commit
|
||||
branch=$(git branch --show-current 2>/dev/null || echo unknown)
|
||||
commit=$(git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||
{
|
||||
echo "## ${ts} · setup:auto started"
|
||||
echo " invocation: nanoclaw.sh"
|
||||
echo " user: $(whoami)"
|
||||
echo " cwd: ${PROJECT_ROOT}"
|
||||
echo " branch: ${branch}"
|
||||
echo " commit: ${commit}"
|
||||
echo ""
|
||||
} > "$PROGRESS_LOG"
|
||||
}
|
||||
|
||||
# grep_field FIELD FILE — first value of FIELD: from a status block.
|
||||
grep_field() {
|
||||
grep "^$1:" "$2" 2>/dev/null | head -1 | sed "s/^$1: *//" || true
|
||||
}
|
||||
|
||||
write_bootstrap_entry() {
|
||||
local status=$1 dur=$2 raw=$3
|
||||
local ts
|
||||
ts=$(ts_utc)
|
||||
local platform is_wsl node_version deps_ok native_ok has_build_tools
|
||||
platform=$(grep_field PLATFORM "$raw")
|
||||
is_wsl=$(grep_field IS_WSL "$raw")
|
||||
node_version=$(grep_field NODE_VERSION "$raw" | head -1)
|
||||
deps_ok=$(grep_field DEPS_OK "$raw")
|
||||
native_ok=$(grep_field NATIVE_OK "$raw")
|
||||
has_build_tools=$(grep_field HAS_BUILD_TOOLS "$raw")
|
||||
{
|
||||
echo "=== [${ts}] bootstrap [${dur}s] → ${status} ==="
|
||||
[ -n "$platform" ] && echo " platform: ${platform}"
|
||||
[ -n "$is_wsl" ] && echo " is_wsl: ${is_wsl}"
|
||||
[ -n "$node_version" ] && echo " node_version: ${node_version}"
|
||||
[ -n "$deps_ok" ] && echo " deps_ok: ${deps_ok}"
|
||||
[ -n "$native_ok" ] && echo " native_ok: ${native_ok}"
|
||||
[ -n "$has_build_tools" ] && echo " has_build_tools: ${has_build_tools}"
|
||||
# Emit the raw path relative to PROJECT_ROOT so the progression log
|
||||
# is portable and matches the TS-side format (logs/setup-steps/NN-…).
|
||||
echo " raw: ${raw#${PROJECT_ROOT}/}"
|
||||
echo ""
|
||||
} >> "$PROGRESS_LOG"
|
||||
}
|
||||
|
||||
write_abort_entry() {
|
||||
local step=$1 error=$2
|
||||
local ts
|
||||
ts=$(ts_utc)
|
||||
echo "## ${ts} · aborted at ${step} (${error})" >> "$PROGRESS_LOG"
|
||||
}
|
||||
|
||||
# ─── bash-side "clack-alike" status line ────────────────────────────────
|
||||
|
||||
use_ansi() { [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; }
|
||||
dim() { use_ansi && printf '\033[2m%s\033[0m' "$1" || printf '%s' "$1"; }
|
||||
gray() { use_ansi && printf '\033[90m%s\033[0m' "$1" || printf '%s' "$1"; }
|
||||
red() { use_ansi && printf '\033[31m%s\033[0m' "$1" || printf '%s' "$1"; }
|
||||
clear_line() { use_ansi && printf '\r\033[2K' || printf '\n'; }
|
||||
|
||||
spinner_start() { printf '%s %s…' "$(gray '◒')" "$1"; }
|
||||
spinner_update() { clear_line; printf '%s %s… %s' "$(gray '◒')" "$1" "$(dim "(${2}s)")"; }
|
||||
spinner_success() { clear_line; printf '%s %s %s\n' "$(gray '◇')" "$1" "$(dim "(${2}s)")"; }
|
||||
spinner_failure() { clear_line; printf '%s %s %s\n' "$(red '✗')" "$1" "$(dim "(${2}s)")"; }
|
||||
|
||||
# ─── fresh-run setup ────────────────────────────────────────────────────
|
||||
|
||||
rm -rf "$STEPS_DIR"
|
||||
rm -f "$PROGRESS_LOG"
|
||||
mkdir -p "$STEPS_DIR" "$LOGS_DIR"
|
||||
write_header
|
||||
|
||||
cat <<'EOF'
|
||||
═══════════════════════════════════════════════════════════════
|
||||
NanoClaw scripted setup
|
||||
═══════════════════════════════════════════════════════════════
|
||||
|
||||
Phase 1: bootstrap (Node + pnpm + native modules)
|
||||
Phase 1 · bootstrap
|
||||
|
||||
EOF
|
||||
|
||||
if ! bash setup.sh; then
|
||||
# ─── phase 1: bootstrap ─────────────────────────────────────────────────
|
||||
|
||||
BOOTSTRAP_RAW="${STEPS_DIR}/01-bootstrap.log"
|
||||
BOOTSTRAP_LABEL="Bootstrapping Node, pnpm, native modules"
|
||||
BOOTSTRAP_START=$(date +%s)
|
||||
|
||||
spinner_start "$BOOTSTRAP_LABEL"
|
||||
|
||||
# Run in the background so we can tick elapsed time. Capture exit code via
|
||||
# a tmpfile (subshell $? is lost after the while loop finishes).
|
||||
BOOTSTRAP_EXIT_FILE=$(mktemp -t nanoclaw-bootstrap-exit.XXXXXX)
|
||||
(
|
||||
# setup.sh's legacy `log()` writes to a file; point it at the raw log
|
||||
# so its verbose entries land alongside the stdout we're capturing.
|
||||
export NANOCLAW_BOOTSTRAP_LOG="$BOOTSTRAP_RAW"
|
||||
if bash setup.sh > "$BOOTSTRAP_RAW" 2>&1; then
|
||||
echo 0 > "$BOOTSTRAP_EXIT_FILE"
|
||||
else
|
||||
echo $? > "$BOOTSTRAP_EXIT_FILE"
|
||||
fi
|
||||
) &
|
||||
BOOTSTRAP_PID=$!
|
||||
|
||||
while kill -0 "$BOOTSTRAP_PID" 2>/dev/null; do
|
||||
sleep 1
|
||||
if kill -0 "$BOOTSTRAP_PID" 2>/dev/null; then
|
||||
spinner_update "$BOOTSTRAP_LABEL" "$(( $(date +%s) - BOOTSTRAP_START ))"
|
||||
fi
|
||||
done
|
||||
# `wait` surfaces the child's exit code; we've already captured it.
|
||||
wait "$BOOTSTRAP_PID" 2>/dev/null || true
|
||||
|
||||
BOOTSTRAP_RC=$(cat "$BOOTSTRAP_EXIT_FILE")
|
||||
rm -f "$BOOTSTRAP_EXIT_FILE"
|
||||
BOOTSTRAP_DUR=$(( $(date +%s) - BOOTSTRAP_START ))
|
||||
|
||||
if [ "$BOOTSTRAP_RC" -eq 0 ]; then
|
||||
spinner_success "Bootstrap complete" "$BOOTSTRAP_DUR"
|
||||
write_bootstrap_entry success "$BOOTSTRAP_DUR" "$BOOTSTRAP_RAW"
|
||||
else
|
||||
spinner_failure "Bootstrap failed" "$BOOTSTRAP_DUR"
|
||||
write_bootstrap_entry failed "$BOOTSTRAP_DUR" "$BOOTSTRAP_RAW"
|
||||
write_abort_entry bootstrap "exit-${BOOTSTRAP_RC}"
|
||||
|
||||
echo
|
||||
echo "[nanoclaw.sh] Bootstrap failed. Inspect logs/setup.log and retry." >&2
|
||||
echo "$(dim '── last 40 lines of ')$(dim "$BOOTSTRAP_RAW")$(dim ' ──')"
|
||||
tail -40 "$BOOTSTRAP_RAW"
|
||||
echo
|
||||
echo "Full raw log: $BOOTSTRAP_RAW"
|
||||
echo "Progression: $PROGRESS_LOG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
cat <<'EOF'
|
||||
|
||||
═══════════════════════════════════════════════════════════════
|
||||
Phase 2: setup:auto
|
||||
═══════════════════════════════════════════════════════════════
|
||||
Phase 2 · setup:auto
|
||||
|
||||
EOF
|
||||
|
||||
# ─── phase 2: clack driver ──────────────────────────────────────────────
|
||||
|
||||
# NANOCLAW_BOOTSTRAPPED=1 tells setup/auto.ts that the progression log has
|
||||
# already been initialized (header + bootstrap entry), so it should append
|
||||
# rather than wipe.
|
||||
export NANOCLAW_BOOTSTRAPPED=1
|
||||
|
||||
# exec so signals (Ctrl-C) propagate directly to the child.
|
||||
exec pnpm run setup:auto
|
||||
|
||||
Reference in New Issue
Block a user