Claude/review project brief r6 p xw#4
Merged
Conversation
bin/clawcontrol.js (530 lines, replaces the Phase 1 stub)
• Node >= 18 gate; ASCII banner; tiny ANSI colorize that respects
NO_COLOR and non-TTY pipes.
• Interactive onboarding wizard via @inquirer/prompts when
~/.clawcontrol/config.json is absent:
gateway URL (auto-detects ws://localhost:3002 if `openclaw` is on
PATH or ~/.openclaw exists) · admin password (optional; argon2id
via @node-rs/argon2 stored as authPasswordHash; bearer authToken
generated independently and printed once) · backup storage
(Local / S3 with bucket+region+keys captured to a separate 0600
s3-credentials.json / Disabled) · default LLM provider (anthropic
/ openai / gemini / openrouter / ollama / skip) · API key
collected once and persisted to ~/.clawcontrol/pending-keys.json
0600, the server picks it up on first boot · 32-byte master
secret.key (0600).
• All subcommands from the brief:
start detached spawn of packages/server/dist/index.js, write
PID file, wait up to 15s on /api/health, open browser
(suppressed by CLAWCONTROL_NO_OPEN=1)
stop SIGTERM with 8s grace then SIGKILL
status read PID + check alive + GET /api/health
backup POST /api/backups (manual), print path
doctor GET /api/doctor, colored PASS/WARN/FAIL table, exits
non-zero on any FAIL — CI-friendly
update GET /check + confirm + POST /install
reset delete config.json (keep DB, secrets, backups)
reset --hard rm -rf ~/.clawcontrol/
export <p> tar -czf, excluding PID + server.log
import <p> tar -xzf with overwrite confirm
logs tail -F server.log
--version / --help
• Resolves the server bundle via repo path → node_modules fallback so
the same script works in dev, after npm install, and inside Docker.
• Top-level error handler treats @InQuirer ExitPromptError (Ctrl+C) as
exit 130 instead of a stack trace.
install.sh
• Detects macOS / Linux / WSL / Windows-shell / unknown.
• Verifies node >= 20; auto-installs via nvm if available, Homebrew
on macOS, or surfaces clear platform-specific guidance otherwise.
• Honors CLAWCONTROL_PACKAGE override and --no-start flag.
• Falls through to sudo only when npm prefix isn't writable and
we're not already root.
Dockerfile (multi-stage)
• deps — pnpm install with the build toolchain present so
better-sqlite3 + @node-rs/argon2 native fallbacks work on
arm64 Alpine.
• build — pnpm build (esbuild server + vite UI).
• runtime — slim image with tini for proper signal handling, only
the bundles + their runtime node_modules + bin/.
CLAWCONTROL_NO_OPEN=1 so the entrypoint doesn't try to
spawn a browser inside the container.
docker-compose.yml
• Default profile boots only clawcontrol with a persistent volume
and an /api/health healthcheck.
• Optional profiles: `--profile ollama` (ollama/ollama at :11434) and
`--profile openclaw` (overridable via CLAWCONTROL_OPENCLAW_IMAGE)
each with their own volume.
Publish metadata
• Root package.json: bin entry retained, files allow-list for
bin/, packages/{server,ui}/dist + their package.json, install.sh,
README.md, LICENSE; publishConfig: {"access":"public"};
prepublishOnly runs pnpm build; license MIT; repository + keywords
populated.
• .npmignore strips src/, lockfiles, design/, PROJECT_BRIEF.md,
*.tsbuildinfo so the tarball is just the bundles + CLI.
• Note: the npm name `clawcontrol` is already taken on the public
registry by an unrelated package (v0.2.4 at the time of writing).
Final publish will use a scoped name (e.g. @clawcontrol/cli) —
install.sh respects $CLAWCONTROL_PACKAGE for that override.
Bug fix in packages/server/build.mjs
• The esbuild ESM bundle imploded at runtime because node-cron
references `__dirname` to locate its daemon.js, and ESM modules
don't get __dirname/__filename for free. Added them (+ require)
to the banner so the bundled output runs as a plain `node bundle.js`.
Verified:
• node bin/clawcontrol.js --help / --version / status / unknown-cmd
all behave correctly with no server running.
• CLAWCONTROL_NO_OPEN=1 node bin/clawcontrol.js start now boots the
bundled server, waits on /api/health, prints "ClawControl is up".
• status, backup (12.6 KB tar.gz on disk), doctor (4 pass / 3 warn /
3 fail with colored output), stop (graceful SIGTERM), export
(tar.gz containing config.json + secret.key + db + backups dir,
PID/log excluded), reset (confirmation prompt fires) — all
round-trip cleanly against a freshly-built bundle.
UX polish
• ToastBus subscribes to /ws and emits the brief's exact taxonomy:
green: agent started · task done · backup completed ·
update installed · OpenClaw recovered
amber: budget at 80% · OpenClaw reconnecting
red: budget limit hit · Doctor critical · OpenClaw crashed ·
backup failed · update failed
Stable toast IDs mean repeat events dedupe rather than stack.
• ShortcutsProvider — keyboard surfaces wired at the layout level:
Cmd/Ctrl + K fuzzy palette across agents/tasks/goals/nav/actions
(label + hint substring scoring, top 30 hits)
Cmd/Ctrl + D /doctor
Cmd/Ctrl + B POST /api/backups (loading toast, then result)
Cmd/Ctrl + / keyboard reference modal
G then A/M/O chord with 1.2s window → /agents · /mission-board
· /org-chart. Skipped while typing in inputs.
Esc closes any overlay.
• MobileTabBar: at <768px the sidebar hides and a 5-tab bottom bar
(Dash · Agents · Board · Doctor · Settings) takes over with proper
safe-area-inset-bottom respect. Full nav surface still reachable
via Cmd+K.
• Layout.tsx wraps Outlet in the existing per-section ErrorBoundary
so a render crash in one page never takes down the rest of the app.
Documentation
• README.md — quickstart for npm + Docker + source, full CLI
reference, "vs Paperclip" comparison table, config
example, layout, requirements.
• CONTRIBUTING.md — adapter-pattern walkthrough (worked example:
adding Mistral in one file), Doctor check guide
(with the load-bearing "no openclaw-client" rule
spelled out), route-add recipe, code style.
• docs/architecture.md — four-layer ASCII diagram, process
boundaries, the typed WS EventMap quoted in full,
schema versioning notes, build outputs.
• docs/doctor-checks.md — table for every check (signals + pass/
warn/fail rules + auto-fix) plus the rationale for
keeping Doctor out-of-process.
• CHANGELOG.md — phase-by-phase release history.
• LICENSE — MIT.
Server tests (vitest + supertest, 28 passing)
• encryption.test.ts (5) — AES-256-GCM roundtrip with
multi-byte payloads, IV randomness,
GCM tamper detection, malformed
ciphertext, secret.key 0600 perms.
• agents.test.ts (6) — list/get/create+validation/
pause+resume+restart audit chain/
clone/delete (with FK SET NULL
behaviour for audit_log).
• budget.test.ts (3) — auto-pause when spent >= budget,
audit row asserted, override+resume
lifts back to idle, summary
projection numerics.
• doctor.test.ts (7) — runs all 10 checks offline,
process+gateway FAIL with fix
metadata, summary counters, run
history persists, single-check
route + 404, /fix/:check 400 when
no auto-fix is registered.
• backup.test.ts (3) — tar.gz on disk + non-zero size,
delete removes both row and file,
restore round-trip completes with
schema_version validation.
• anthropic-adapter.test.ts (4) — listModels has the brief's set,
estimateCostCents matches the
static table exactly (opus 4-6:
$90 for 1M+1M; sonnet 100k/50k =
105¢; unknown model = 0), test-
Connection ok=true on success and
ok=false WITHOUT throwing on SDK
failure (mocked SDK).
Each test file gets a fresh tmp DB via tests/_helpers.ts; no live
network calls.
UI tests (vitest + RTL + jsdom, 15 passing)
• ErrorBoundary.test.tsx (3) — catches a render error and
surfaces Retry; renders
children when nothing throws;
Retry recovers when the
underlying child stops
throwing.
• primitives.test.tsx (6) — Card / Button / Chip /
Avatar (initials) /
StatusDot / ProgressBar
(clamped at 100%) /
ProgressRing label /
Skeleton / EmptyState /
ErrorPanel.
• sections.test.tsx (4) — Dashboard, Agents, Doctor,
and Heartbeats render with
mocked hook data; mocks the
hooks barrel + websocket
module so no fetch fires.
• websocket-reconnect.test.tsx (2) — typed event dispatch +
connection state stream,
plus exponential backoff
(500ms → 1s → 2s timer
ladder verified with fake
timers).
GitHub Actions
• .github/workflows/ci.yml — typecheck + sequential pnpm test on
Node 20 and 22 for every push/PR; production build + bundle smoke
+ artifact upload on push to main only.
• .github/workflows/release.yml — on tag v*: typecheck + test gate,
pnpm build, npm publish (NPM_TOKEN), Docker buildx multi-arch push
to Docker Hub when DOCKERHUB_USERNAME/TOKEN are present, GitHub
Release with auto-generated notes and install.sh attached.
Bug fix surfaced in the encryption suite: the original "ciphertext
doesn't include plaintext" assertion used a single-character sample
that occasionally appeared in random base64 output and flaked the
suite ~5% of runs. Test inputs now have enough entropy to make
collisions vanishingly unlikely.
Verified end-to-end:
pnpm typecheck → clean (server + ui)
pnpm test → 28 server + 15 ui = 43 passing, sequential
pnpm build → server bundle 2.4 MB · ui 90.3 KB gzipped JS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.