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
+
+
+
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"