Skip to content

feat: vm-side CLI (claw) with tool abstraction and signal cleanup#19

Merged
TimBeyer merged 18 commits intomainfrom
feat/vm-cli-claw
Mar 15, 2026
Merged

feat: vm-side CLI (claw) with tool abstraction and signal cleanup#19
TimBeyer merged 18 commits intomainfrom
feat/vm-cli-claw

Conversation

@TimBeyer
Copy link
Owner

Summary

  • Add @clawctl/vm-cli package — a CLI (claw) that runs inside the Lima VM and handles provisioning, health checks, and checkpoint signaling
  • Introduce a tools/ abstraction layer: typed wrappers for each system tool (apt, systemd, node, tailscale, homebrew, op-cli, openclaw) so provision commands are thin orchestrators
  • Clean up partially-created VMs on Ctrl+C / SIGTERM during creation (both headless and interactive wizard paths)
  • Fix verify step treating doctor warnings (e.g. gateway service not yet active) as fatal errors

Test plan

  • bun test — 244 pass
  • bun run lint — clean
  • bun run build:claw — binary compiles
  • clawctl-dev create --config ... — full end-to-end provisioning
  • Ctrl+C during headless create → VM and project dir cleaned up
  • Ctrl+C during wizard create → VM and project dir cleaned up

🤖 Generated with Claude Code

TimBeyer and others added 18 commits March 15, 2026 17:43
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New VM-side CLI that replaces bash provisioning scripts:
- `claw provision system|tools|openclaw` — idempotent provisioning
- `claw doctor` — structured health checks with --json support
- `claw checkpoint` — atomic signal file for host-side auto-commit

Adds CHECKPOINT_REQUEST_FILE and CLAW_BIN_PATH constants to @clawctl/types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rewrite provision.ts to deploy claw binary and call `claw provision`
  subcommands instead of bash scripts
- Simplify verify.ts to delegate to `claw doctor --json`
- Add `clawctl watch` command for checkpoint-driven auto-commits
- Add `build:claw` script to root package.json
- Remove dead provisioning template files and their tests
- Update templates/index.ts exports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The VM mounts the instance project directory at /mnt/project, not the
clawctl repo. Use driver.copy() (limactl copy) to transfer the binary
from the host, then sudo mv to /usr/local/bin. Resolves the binary path
from the monorepo root via import.meta.dir, with a clear error message
if the binary hasn't been built yet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
clawctl-dev now builds the VM-side claw binary (cross-compiled for
linux-arm64) before launching the CLI. The build takes ~0.5s and
ensures the binary is always up to date during development.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
exec() now forwards stdout/stderr in real-time by default so the user
can follow installation progress (apt-get, homebrew, tailscale, etc.).
Short check commands (version queries, dpkg -l, systemctl is-active)
pass `quiet: true` to suppress their output. In JSON mode, forwarded
output goes to stderr to keep the JSON envelope on stdout clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…arn-only

claw is a compiled standalone binary — bun is not needed in the VM.
service-gateway and openclaw-doctor checks are now warn-only since they
only pass after bootstrap/onboarding, not after provisioning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract all raw exec() calls from provision commands and doctor into
typed tool wrappers in tools/. Each module (apt, systemd, node,
tailscale, homebrew, op-cli, openclaw) encapsulates interactions with
one system tool. Provision commands become thin orchestrators.

- Unify SystemStep/ToolStep/OpenclawStep into ProvisionResult
- Replace exec("rm",...) with fs/promises where possible
- Replace awk passwd parsing with pure TypeScript
- Delete helpers.ts (distributed across tool modules)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract cleanup logic into shared cleanup.ts module with:
- cleanupVM(): idempotent VM + project dir removal
- onSignalCleanup(): register SIGINT/SIGTERM handlers

Headless path: signal handlers active during provisioning window,
removed in finally block.

Wizard path: track creation state via mutable ref passed to App.
Ink captures Ctrl+C as raw input (not SIGINT), so we detect
interrupted exits via the waitUntilExit() result. SIGTERM handled
via process signal listener. Handlers removed once the wizard
exits normally to avoid cleaning up during retryable onboarding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
verifyProvisioning() was dropping the `warn` field from doctor checks,
so headless.ts treated service-gateway (warn-only, expected inactive
before bootstrap) as a hard failure — aborting provisioning and cleaning
up the VM.

Preserve the warn flag in VerifyResult and skip warnings in the headless
verify loop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create docs/vm-cli.md: dedicated doc explaining why we use a compiled
  binary inside the VM instead of shell scripts, the tool abstraction
  layer, ProvisionResult protocol, cross-tool composition, and how to
  add new tools
- Update docs/architecture.md: new directory structure reflecting the
  monorepo layout, add internal CLI as a key design decision, update
  provisioning flow diagrams, update templates section
- Rewrite docs/vm-provisioning.md: replace shell-script-based content
  with the current claw-based approach, document each provisioning
  stage, idempotency guarantees, verification, and cleanup on failure
- Update CLAUDE.md: add vm-cli to workspace structure, add key
  directories, add internal CLI conventions section, add build:claw
  command

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ings

Replace ad-hoc orchestrator functions with declarative ProvisionStage
constants and a shared runStage() runner. Replace hardcoded warn: true
flags in doctor checks with availableAfter lifecycle phases, computed
via the new --after flag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…isioning

Update references from "orchestrators" to "stages", document the
--after flag in architecture flows, and add stage/lifecycle conventions
to CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…atterns

Move detailed stage and lifecycle documentation out of CLAUDE.md (which
should stay concise) into module-level comments in stages.ts, doctor.ts,
and constants.ts where the patterns are declared. Fix last "orchestrator"
reference in vm-cli.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@TimBeyer TimBeyer merged commit 5f6d3bc into main Mar 15, 2026
4 checks passed
@TimBeyer TimBeyer deleted the feat/vm-cli-claw branch March 15, 2026 19:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant