diff --git a/.agents/skills/use-appclaw-cli/SKILL.md b/.agents/skills/use-appclaw-cli/SKILL.md index 50f2c07..2da3056 100644 --- a/.agents/skills/use-appclaw-cli/SKILL.md +++ b/.agents/skills/use-appclaw-cli/SKILL.md @@ -1,7 +1,7 @@ --- name: use-appclaw-cli description: > - Use the AppClaw CLI to run YAML flows, start interactive playground, explore apps, + Use the AppClaw CLI to run YAML flows, start the interactive TUI shell, explore apps, record/replay sessions, configure devices, and troubleshoot. Trigger for any request involving appclaw commands, device setup, .env configuration, running flows, vision setup, or debugging execution failures. @@ -91,17 +91,19 @@ appclaw --flow tests/flows/youtube-phased.yaml --env dev **No LLM key needed** unless the flow has steps that fall back to LLM parsing (unrecognized natural language). -### 3. Playground — interactive REPL +### 3. Terminal Studio (`--tui`, alias `--playground`) ```sh -appclaw --playground -appclaw --playground --platform ios --device-type simulator -appclaw --playground --device "iPhone 17 Pro" +appclaw --tui +appclaw --tui --platform ios --device-type simulator +appclaw --tui --device "iPhone 17 Pro" ``` -Type natural language commands that execute live on the device. Steps accumulate and can be exported to a YAML flow. +The interactive mode. Type natural language commands that execute live on the device; steps accumulate and can be exported as a YAML flow or an `@appclaw/runner` spec via `/export`. -**REPL commands:** `/help`, `/steps`, `/export`, `/clear`, `/device`, `/disconnect` +`--playground` is an alias for `--tui` — the old playground REPL was removed. (`--json --playground` is different: a headless NDJSON bridge used by the VS Code / Cursor extension, not something to run by hand.) + +Full-screen Ink shell: platform/device picker, slash-command palette, goal REPL, settings, run history. `/stream` (Android only) mirrors the device screen **inside the terminal** — Kitty graphics on Ghostty/kitty/WezTerm, 24-bit ANSI half-blocks everywhere else — at ~5fps via `adb screencap`; `/stream-close` stops it. Requires an interactive terminal; incompatible with `--json`. ### 4. Explorer — PRD to test flows @@ -317,7 +319,7 @@ Records successful trajectories to `~/.appclaw/trajectories.json` and reuses the - `appclaw "goal"` (agent mode — uses LLM credits, takes actions on device) - `appclaw --explore` (LLM credits + device crawling) - `appclaw --record` (agent mode + saves recording) -- `appclaw --playground` (interactive device session) +- `appclaw --tui` (interactive device session; goals typed inside it use LLM credits — `--playground` is an alias for it) Why: agent and explorer modes consume LLM API credits and take real actions on the connected device. @@ -390,8 +392,8 @@ appclaw --flow tests/flows/youtube-phased.yaml --env dev ### Quick test on iOS simulator ```sh -appclaw --platform ios --device-type simulator --playground -# In REPL: type commands, test them, /export to YAML +appclaw --platform ios --device-type simulator --tui +# In the TUI: type commands, test them, /export to YAML ``` ### Generate test flows from a PRD diff --git a/.gitignore b/.gitignore index 60b3a2d..61dac7d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ dist/ .env *.log .DS_Store +# JetBrains workspace state — per-developer, never shared +.idea/ +*.iml .claude/ recordings/ logs/ diff --git a/.kiro/steering/product.md b/.kiro/steering/product.md index 0bd7d6f..26531a4 100644 --- a/.kiro/steering/product.md +++ b/.kiro/steering/product.md @@ -6,7 +6,7 @@ AppClaw is an agentic AI layer for mobile automation on Android and iOS. Users d - **Agent mode** — LLM-driven goal execution (e.g. `appclaw "Send a WhatsApp message to Mom"`) - **YAML flows** — declarative, zero-LLM automation steps defined in YAML files -- **Playground** — interactive REPL for building flows live on a device +- **Terminal Studio** (`--tui`, alias `--playground`) — the interactive mode: step recorder, device picker, command palette, run history, and in-terminal device stream - **Explorer** — generates YAML test flows from a PRD or app description - **Record/Replay** — capture and adaptively replay goal executions - **Report** — Express server serving HTML run reports diff --git a/.kiro/steering/structure.md b/.kiro/steering/structure.md index 1b6bcc3..196f9c8 100644 --- a/.kiro/steering/structure.md +++ b/.kiro/steering/structure.md @@ -36,7 +36,8 @@ appclaw/ | `device/` | Device setup pipeline — platform/device picker, iOS setup, Appium session | | `memory/` | Episodic memory — trajectory recording, fingerprinting, retrieval | | `explorer/` | PRD → YAML flow generation, screen crawler | -| `playground/` | Interactive REPL for building flows | +| `step-recorder/` | Shared step-recording helpers + the headless `--json --playground` NDJSON bridge | +| `tui/` | Terminal Studio — multi-screen Ink app (`--tui`, alias `--playground`) | | `recording/` | Session recorder and adaptive replayer | | `report/` | Run artifact collection, HTML report rendering, Express server | | `sdk/` | Public SDK — `GoalRunner`, `FlowRunner`, `StepRunner`, config builder | diff --git a/CLAUDE.md b/CLAUDE.md index 58dc1ca..4cfd0ff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,12 +27,12 @@ The CLI routes to 6 modes based on flags: - **Interactive** (default) — prompts for platform/device/goal, runs agent loop - **YAML Flow** (`--flow file.yaml`) — declarative automation, zero LLM cost -- **Playground** (`--playground`) — interactive REPL for building flows +- **Terminal Studio** (`--tui`, or its alias `--playground`) — multi-screen Ink app: platform/device picker, slash-command palette, step recording, `/goal` agent runs, settings, run history, and device mirroring — `/stream` renders the screen inside the terminal (see Terminal Studio below). This is the interactive mode; the old `--playground` REPL was removed and the flag now routes here. - **Explorer** (`--explore`) — PRD → YAML test flow generation - **Record/Replay** (`--record`, `--replay`) — capture and replay sessions - **Report** (`--report`) — Express server serving HTML reports from `.appclaw/runs/` -The interactive and goal-direct paths also accept `--export [path]` (optionally `--export-dir
For a full test project — config file, spec files, npm scripts — scaffold it with @@ -1786,13 +1941,17 @@
--tuiappclaw --flow tests/youtube-search.yaml --env dev-
+ A full-screen terminal workspace where you type one instruction at a time and see it
+ execute immediately, alongside a device picker, a slash-command palette and optional
+ device mirroring. Great for exploring an app and building flows interactively.
+ --playground is an alias for it.
+
- An interactive REPL where you type one instruction at a time and see it execute
- immediately. Great for exploring an app and building flows interactively.
+ Requirements. The shell itself needs only an interactive terminal (at
+ least 27 rows — it will tell you if the window is too small). The optional device
+ mirroring is Android-only: /stream mirrors the screen inside the
+ shell. Terminals that support the kitty graphics protocol (Ghostty, kitty, WezTerm) show
+ real pixels; everywhere else falls back to coloured half-block characters. No install
+ needed.
appclaw --playground --platform ios --device-type simulator+
appclaw --tui --platform ios --device-type simulator
When you're happy with the recorded steps, type /export to save them. By
- default this writes an SDK vitest spec (.test.ts); pass a
- .yaml filename to export a YAML flow instead.
+ default this writes an @appclaw/runner spec (.spec.ts);
+ pass a .yaml filename to export a YAML flow instead.
# In the REPL prompt: -/export # → SDK vitest spec (flow-<ts>.test.ts) -/export login.test.ts # → SDK vitest spec +# At the shell prompt: +/export # → runner spec (flow-<ts>.spec.ts) +/export login.spec.ts # → runner spec, run with: appclaw test login /export login.yaml # → YAML flow
- Bare-filename exports land in EXPORT_DIR (default
- .appclaw/exports). Override it per run with --export-dir, and
- load a custom .env with --env-path:
+ Bare-filename exports land in EXPORT_DIR (default tests, the
+ runner's own testDir, so an export is runnable where it lands). Override it per run with
+ --export-dir, and load a custom .env with
+ --env-path:
appclaw --tui --env-path path/to/.env --export-dir tests/generated+
+ appclaw --tui is a full-screen terminal workspace for building a flow by
+ hand: you type one instruction at a time, it runs on the device immediately, and every
+ step that succeeds is recorded. When the flow looks right, /export turns it
+ into a runnable spec. --playground is an alias for it.
+
+ Two columns. On the left, a scrollable transcript of everything that has run, the + command palette, and the prompt. On the right, the device screen, mirrored live. +
+appclaw --playground --env-path path/to/.env --export-dir tests/generated+
+ Anything you type that is not a slash command runs as a single deterministic + instruction and is appended to the recording. A step that fails is reported but not + recorded, so the flow you export only contains steps that actually worked. +
+
+ /goal <text> is the exception: it hands the line to the autonomous
+ agent, which may take many steps to get there. Agent runs are not
+ recorded — use it to explore, then record the steps you settle on.
+
+ Type / to filter the palette; Tab completes. The full list is
+ in /help.
+
| Command | +What it does | +
|---|---|
/list |
+ Show the recorded steps | +
/yaml · /preview |
+ Preview the YAML flow or generated spec without saving | +
/export [file] |
+
+ Write a runner spec (.spec.ts) or YAML flow (.yaml)
+ |
+
+ /undo · /edit · /insert ·
+ /delete
+ |
+ Fix the recording without starting over | +
/meta |
+ Set flow name, appId or platform | +
/goal <text> |
+ Run the autonomous agent (not recorded) | +
/stream · /stream-close |
+ Mirror the device in the side panel, or stop mirroring | +
/device · /platform |
+ Switch device or platform mid-session | +
/settings · /history · /doctor |
+ Edit .env, browse past runs, run the preflight |
+
/session |
+ Path to this session's JSON log | +
| Key | +At the prompt | +In the transcript | +
|---|---|---|
↑ ↓ |
+ Recall previous instructions | +Scroll | +
Shift+Tab |
+ Move focus between the prompt and the transcript | +|
Tab |
+ Complete a slash command | +— | +
Ctrl+C |
+ Quit (asks first if steps are unexported) | +|
+ History is per-session and never written to disk. Typing any character while the + transcript has focus jumps back to the prompt and keeps the keystroke. +
+ +
+ /stream draws the device screen in the right-hand panel a few times a
+ second while the prompt stays usable. How it draws depends on your terminal, and it
+ always works — the only question is fidelity.
+
| Requirement | +Needed for | +If missing | +
|---|---|---|
| Android device or emulator | +Mirroring at all | +/stream refuses on iOS — use the Simulator window |
+
adb on PATH |
+ Capturing frames (adb exec-out screencap) |
+ Stream does not start | +
| Kitty graphics protocol | +Real pixels | +Falls back to half-blocks automatically | +
| 24-bit colour | +The half-block fallback | +Colours are approximated by the terminal | +
+ appclaw doctor checks the first two rows — adb and a
+ connected Android device — and prints the exact commands for anything missing. The
+ last two only change fidelity, never whether the stream runs.
+
+ AppClaw picks the backend from environment variables rather than asking the terminal. A + capability probe would be answered on stdin, which the shell already holds in raw mode + — the reply would arrive as keystrokes. So detection is: +
+| Terminal | +Detected via | +Result | +
|---|---|---|
| Ghostty | +TERM_PROGRAM=ghostty |
+ kitty graphics | +
| kitty | +
+ KITTY_WINDOW_ID, or TERM containing kitty
+ |
+ kitty graphics | +
| WezTerm | +WEZTERM_EXECUTABLE |
+ kitty graphics | +
| Everything else | +— | +ANSI half-blocks | +
+ That includes Terminal.app, iTerm2 and the VS Code terminal: they still stream, just as + coloured half-block characters — enough to see which screen you are on, not enough + to read small text. Multiplexers such as tmux and screen sit between AppClaw and the + terminal and generally will not pass graphics through, so expect the fallback there too. +
+# Exercise the fallback on a terminal that supports graphics +APPCLAW_STREAM_BACKEND=halfblock appclaw --tui+
+ /stream-close stops the mirror and hands the panel back; switching device
+ or quitting stops it for you.
+
+ Every session writes .appclaw/sessions/<id>.json as it goes —
+ each instruction, what it resolved to, how long it took, and the failures that never
+ became steps. It is rewritten after every event, so a crash still leaves a readable
+ file, and past sessions show up in /history alongside YAML flow runs.
+
+ /export writes an @appclaw/runner spec by
+ default, or a YAML flow if you give it a .yaml name. Bare filenames land in
+ EXPORT_DIR (default tests), so the file is runnable where it
+ lands:
+
appclaw --tui +# …record steps at the prompt, then: +/export login.spec.ts + +# then run it like any other spec +appclaw test login
--export writes
- (default: .appclaw/exports). Overridden by --export-dir.
+ (default: tests, the runner's own testDir). Overridden by
+ --export-dir.
--caps path/to/caps.json (interactive, YAML flow,
- --playground, --record — every mode).
+ --tui, --record — every mode).
CAPABILITIES_FILE=path/to/caps.json in
@@ -3637,7 +4112,7 @@ Execute a single natural-language instruction directly on the device — the programmatic - equivalent of typing a command in the playground REPL. Each call is one atomic action: + equivalent of typing an instruction in Terminal Studio. Each call is one atomic action: parse the instruction, execute it, return the result.
diff --git a/package-lock.json b/package-lock.json index a424081..b2db41d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5947,7 +5947,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/appium-uiautomator2-driver/-/appium-uiautomator2-driver-8.0.1.tgz", "integrity": "sha512-NCTEk3Ou13MoGsPE8hE613EjvEuxrqorx19uBDwvBkcS2GkHhvnhLkJmUR/GeSPI3TO0epDdZMpYhroeHS+bKw==", - "hasShrinkwrap": true, "license": "Apache-2.0", "dependencies": { "@appium/css-locator-to-native": "^1.0.1", @@ -24653,7 +24652,8 @@ "ink": "^5.2.1", "ink-spinner": "^5.0.0", "ink-text-input": "^6.0.0", - "react": "^18.3.1" + "react": "^18.3.1", + "wrap-ansi": "^9.0.2" }, "bin": { "appclaw": "bin/appclaw.js" diff --git a/packages/cli/package.json b/packages/cli/package.json index 1ca4c4d..90cee63 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -30,7 +30,8 @@ "ink": "^5.2.1", "ink-spinner": "^5.0.0", "ink-text-input": "^6.0.0", - "react": "^18.3.1" + "react": "^18.3.1", + "wrap-ansi": "^9.0.2" }, "engines": { "node": ">=22" diff --git a/packages/cli/src/cli/doctor.ts b/packages/cli/src/cli/doctor.ts index aaa571b..c9cf863 100644 --- a/packages/cli/src/cli/doctor.ts +++ b/packages/cli/src/cli/doctor.ts @@ -340,6 +340,9 @@ async function checkHandshake( } export async function runDoctor(args: string[]): Promise