diff --git a/.gitignore b/.gitignore index 71fce69..2a3dc12 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist/ .DS_Store .env .env.secrets +*.cast diff --git a/README.md b/README.md index d420bb9..0de1ae5 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,14 @@ You never touch the VM. Config lives on your host, git-tracked and reproducible.

---- - -**clawctl gives each OpenClaw gateway its own isolated Ubuntu VM** via -[Lima](https://lima-vm.io), provisions it with everything OpenClaw needs, and -manages the full lifecycle from your Mac. You never shell in or piece together -scripts — just answer a few questions (or hand it a config file) and the -gateway is running. Config and data are mounted into a project directory on -your host, so they're editable, git-trackable, and safe from VM rebuilds. +

+ Getting Started · + Headless Mode · + Config Reference · + Troubleshooting +

-> **Terminology**: A clawctl **instance** is a Lima VM running an OpenClaw -> **gateway**. The gateway hosts one or more **agents** — each with its own -> workspace, sessions, and tools. clawctl manages the instance lifecycle; -> OpenClaw manages the agents inside it. +--- ## Install @@ -29,23 +24,17 @@ your host, so they're editable, git-trackable, and safe from VM rebuilds. curl -fsSL https://raw.githubusercontent.com/TimBeyer/clawctl/main/install.sh | bash ``` -To update an existing installation, run the same command again. - -**Requires** macOS on Apple Silicon (M1/M2/M3/M4) with -[Homebrew](https://brew.sh) installed. Lima is installed automatically if not -already present. +Requires macOS on Apple Silicon (M1–M4) with [Homebrew](https://brew.sh). Lima is installed automatically. ## Quickstart ```bash -# Interactive wizard — answers a few questions, does everything else clawctl create - -# Headless — config-file-driven, no prompts, great for CI/CD -clawctl create --config config.json ``` -## What you get +

+ clawctl create wizard +

In about five minutes, the wizard gives you: @@ -53,8 +42,7 @@ In about five minutes, the wizard gives you: - An isolated Ubuntu 24.04 VM with Node.js, Tailscale, and the 1Password CLI pre-installed - A project directory on your Mac with git-tracked config and persistent data that survives VM rebuilds -You just answer a few questions. clawctl handles prerequisites, VM creation, -provisioning, and — optionally — credential setup and OpenClaw onboarding. +For automated setups, pass a JSON config file and skip the prompts — see [Headless Mode](docs/headless-mode.md). ## Features @@ -70,6 +58,11 @@ provisioning, and — optionally — credential setup and OpenClaw onboarding. ## Commands +> [!NOTE] +> **Terminology**: A clawctl **instance** is a Lima VM running an OpenClaw +> **gateway**. The gateway hosts one or more **agents**. clawctl manages the +> instance lifecycle; OpenClaw manages the agents inside it. + | Command | Description | | ------------------------------------------ | ------------------------------------------------- | | `clawctl create` | Interactive wizard | @@ -88,71 +81,14 @@ provisioning, and — optionally — credential setup and OpenClaw onboarding. | `clawctl register --project ` | Register an existing (pre-registry) instance | | `clawctl completions ` | Generate shell completion script (bash or zsh) | -All instance commands (`status`, `start`, `stop`, `restart`, `delete`, `shell`, -`openclaw`) accept an optional positional name, a `-i`/`--instance` flag, or -resolve the instance automatically via context. Run `clawctl --help` for -details. - -### Instance context - -You don't have to type the instance name every time. clawctl resolves the -target instance in this order: - -1. `--instance` / `-i` flag — `clawctl status -i my-agent` -2. `CLAWCTL_INSTANCE` env var — `export CLAWCTL_INSTANCE=my-agent` -3. `.clawctl` file — walks up from your current directory (like `.nvmrc`) -4. Global context — `~/.config/clawctl/context.json` - -Set context with `clawctl use`: - -```bash -clawctl use my-agent # write .clawctl in current directory -clawctl use my-agent --global # set global default -clawctl use # show current context and its source -``` - -### Running openclaw commands from the host +Instance commands accept an optional name, a `-i`/`--instance` flag, or +resolve automatically from context (`CLAWCTL_INSTANCE` env var, `.clawctl` +file, or global default via `clawctl use`). -No need to shell in for routine operations — `clawctl openclaw` (or `oc` for -short) runs any `openclaw` subcommand inside the VM: - -```bash -clawctl oc doctor # health check -clawctl oc config get gateway.name -clawctl oc daemon status -clawctl oc telegram list -``` - -For arbitrary commands, use `clawctl shell --`: - -```bash -clawctl shell -- whoami -clawctl shell -- systemctl --user status openclaw-gateway -``` - -### Day-to-day management - -clawctl isn't just an installer — it's how you manage your gateways after setup too. - -```bash -# Set your default instance once -clawctl use my-agent - -# See what's running -clawctl list - -# Quick health check — no need to shell in -clawctl oc doctor - -# Restart one that's acting up -clawctl restart - -# Spin up a second gateway for a different project -clawctl create - -# Tear it down when you're done (keeps your project dir by default) -clawctl delete my-agent -``` +> [!TIP] +> No need to shell in for routine operations — `clawctl oc` runs any +> `openclaw` subcommand inside the VM: `clawctl oc doctor`, +> `clawctl oc config get gateway.name`, etc. ### Shell completions @@ -171,11 +107,6 @@ openclaw subcommand completions (including deep completion like `oc config set `) are cached from the VM and refreshed automatically. See [Shell Completions](docs/shell-completions.md) for details. -Instances are tracked in `~/.config/clawctl/instances.json` and registered -automatically on create, or manually via `clawctl register`. Run as many -gateways as your hardware allows — each gets its own isolated VM, project -directory, and config. - ## Documentation - [Getting Started](docs/getting-started.md) — guided walkthrough for first-time users diff --git a/docs/assets/demo.gif b/docs/assets/demo.gif new file mode 100644 index 0000000..768d683 Binary files /dev/null and b/docs/assets/demo.gif differ diff --git a/docs/demo-recording.md b/docs/demo-recording.md new file mode 100644 index 0000000..f6edb8e --- /dev/null +++ b/docs/demo-recording.md @@ -0,0 +1,54 @@ +# Demo Recording + +The README GIF (`docs/assets/demo.gif`) is generated from an automated +terminal recording. Re-record it when the wizard UI changes. + +## Requirements + +```bash +brew install tmux asciinema agg +brew install --cask font-fira-code # for Unicode spinner glyphs +``` + +## Record and convert + +```bash +./scripts/record-demo.sh +agg --font-dir ~/Library/Fonts docs/assets/demo.cast docs/assets/demo.gif +``` + +The script launches `clawctl create` inside a tmux session, drives the +wizard with scripted keystrokes, and records with asciinema. It runs the +real wizard — after recording, the VM cleanup runs normally. + +## What it records + +The storyboard walks through: + +1. Wizard loads with default config +2. Name set to "hal" +3. Provider → Anthropic selected, API key entered +4. Agent Identity → name "Hal", vibe filled in +5. Review screen with validation passing +6. Provisioning starts (runs for ~8 seconds, then recording ends) + +## Editing the storyboard + +The script is at `scripts/record-demo.sh`. Key helpers: + +- `wait_for "text"` — wait for text to appear on screen before proceeding +- `type_slow "text"` — type with natural speed (50ms per char) +- `down` / `enter` / `esc` — send keys with appropriate delay +- `sleep N` — visual pacing for the viewer + +If navigation changes (fields reordered, sections added), adjust the +`down` counts. The `wait_for` calls are the safety net — if navigation +is wrong, the script hangs instead of silently desyncing. + +## Files + +| File | Purpose | +| ------------------------ | --------------------------------------------------------------- | +| `scripts/record-demo.sh` | Automated recording script | +| `docs/assets/demo.cast` | Raw asciicast recording (gitignored, regenerated by the script) | +| `docs/assets/demo.gif` | Final GIF embedded in README | diff --git a/packages/cli/src/commands/create.ts b/packages/cli/src/commands/create.ts index 5efdd92..7c941ad 100644 --- a/packages/cli/src/commands/create.ts +++ b/packages/cli/src/commands/create.ts @@ -40,7 +40,7 @@ export async function runCreateFromConfig(driver: VMDriver, configPath: string): // /dev/tty unavailable — Ink will use process.stdin } - const useAltScreen = process.stdout.isTTY; + const useAltScreen = process.stdout.isTTY && !process.env.NO_ALT_SCREEN; if (useAltScreen) { process.stdout.write("\x1b[?1049h"); } @@ -108,7 +108,7 @@ export async function runCreateWizard(driver: VMDriver): Promise { // Enter alternate screen buffer for a clean canvas. This prevents ghost // elements from Ink's differential rendering when the output height // shrinks between phases. On exit, the original terminal is restored. - const useAltScreen = process.stdout.isTTY; + const useAltScreen = process.stdout.isTTY && !process.env.NO_ALT_SCREEN; if (useAltScreen) { process.stdout.write("\x1b[?1049h"); } diff --git a/scripts/record-demo.sh b/scripts/record-demo.sh new file mode 100755 index 0000000..c2fcb1f --- /dev/null +++ b/scripts/record-demo.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash +# +# record-demo.sh — Record the clawctl create wizard demo. +# +# Uses tmux for reliable keystroke delivery and screen content detection, +# with asciinema for clean terminal recording. Convert with agg afterward. +# +# Usage (from repo root): +# ./scripts/record-demo.sh +# agg docs/assets/demo.cast docs/assets/demo.gif +# +# Requirements: tmux, asciinema, agg +# +# NOTE: This runs `clawctl create` for real. After recording you may +# need to clean up: limactl delete hal + +set -euo pipefail + +CAST="docs/assets/demo.cast" +SESSION="clawctl-demo" +COLS=160 +ROWS=35 + +# --- Helpers --- + +wait_for() { + local pattern="$1" + local max=60 + local i=0 + while ! tmux capture-pane -t "$SESSION" -p | grep -qF "$pattern"; do + sleep 0.5 + ((i++)) + if (( i >= max )); then + echo "Timeout waiting for: $pattern" >&2 + tmux kill-session -t "$SESSION" 2>/dev/null + exit 1 + fi + done +} + +type_slow() { + local text="$1" + for (( i=0; i<${#text}; i++ )); do + tmux send-keys -t "$SESSION" -l "${text:$i:1}" + sleep 0.05 + done +} + +down() { + tmux send-keys -t "$SESSION" Down + sleep 0.3 +} + +# --- Setup --- + +tmux kill-session -t "$SESSION" 2>/dev/null || true +tmux new-session -d -s "$SESSION" -x "$COLS" -y "$ROWS" +sleep 0.5 + +# Start asciinema recording wrapping clawctl create +tmux send-keys -t "$SESSION" \ + "NO_ALT_SCREEN=1 asciinema rec --cols $COLS --rows $ROWS --overwrite -c 'clawctl create' $CAST" Enter + +# --- Scene 1: Wait for config builder --- + +wait_for "Review & Create" +sleep 2 + +# --- Scene 2: Type instance name "hal" --- + +tmux send-keys -t "$SESSION" Enter +sleep 0.5 +type_slow "hal" +sleep 1 +tmux send-keys -t "$SESSION" Enter +sleep 1 + +# --- Scene 3: Navigate to Provider and expand --- + +# name → project → resources → provider +down; down; down +tmux send-keys -t "$SESSION" Enter +sleep 0.5 + +# Move to provider.type +down + +# Open provider type select +tmux send-keys -t "$SESSION" Enter +wait_for "anthropic" +sleep 1 + +# Select anthropic +tmux send-keys -t "$SESSION" Enter +sleep 0.5 + +# --- Scene 4: Fill API key --- + +down +tmux send-keys -t "$SESSION" Enter +sleep 0.3 +type_slow "sk-ant-api03-xYzDeMoKeY" +sleep 0.8 +tmux send-keys -t "$SESSION" Enter +sleep 0.5 + +# Collapse provider +tmux send-keys -t "$SESSION" Escape +sleep 0.8 + +# --- Scene 5: Agent Identity --- + +# provider → network → cap:tailscale → cap:one-password → bootstrap +down; down; down; down + +# Expand bootstrap (Agent Identity) +tmux send-keys -t "$SESSION" Enter +sleep 0.5 + +# Agent Name +down +tmux send-keys -t "$SESSION" Enter +sleep 0.3 +type_slow "Hal" +sleep 0.5 +tmux send-keys -t "$SESSION" Enter +sleep 0.3 + +# Agent Vibe +down +tmux send-keys -t "$SESSION" Enter +sleep 0.3 +type_slow "Calm, precise, just a little too helpful." +sleep 0.8 +tmux send-keys -t "$SESSION" Enter +sleep 0.5 + +# Collapse bootstrap +tmux send-keys -t "$SESSION" Escape +sleep 1 + +# --- Scene 6: Review --- + +tmux send-keys -t "$SESSION" "r" +wait_for "Review Configuration" +sleep 3 + +# --- Scene 7: Start provisioning --- + +tmux send-keys -t "$SESSION" Enter +wait_for "Provisioning" +sleep 20 + +# --- Done --- + +# Kill the session. asciinema writes .cast incrementally, so the file +# is complete up to this point. +tmux kill-session -t "$SESSION" 2>/dev/null || true +sleep 1 + +# Trim everything after cleanup signals appear in the recording. +# Find the first line containing cleanup text and keep everything before it. +if [[ -f "$CAST" ]]; then + cleanup_line=$(grep -n "SIGTERM\|SIGHUP\|cleaning up\|Provisioning failed\|Deleting VM" "$CAST" | head -1 | cut -d: -f1) + if [[ -n "$cleanup_line" ]]; then + total=$(wc -l < "$CAST" | tr -d ' ') + echo "Trimming from line $cleanup_line (of $total) — removing cleanup output" + head -n "$((cleanup_line - 1))" "$CAST" > "${CAST}.tmp" + mv "${CAST}.tmp" "$CAST" + fi +fi + +echo "" +echo "Recording saved to $CAST" +echo "Convert to GIF: agg $CAST docs/assets/demo.gif"