--- name: shell-use description: 'Drive, inspect, assert on, record, and watch a real terminal from the command line with the shell-use CLI. Use when running shells (bash, zsh, fish, PowerShell, pwsh, cmd, xonsh, elvish, nushell) or TUI programs (vim, less, top, etc.) in a headless PTY; sending keystrokes, key combos, or mouse input; resizing, writing raw bytes, or signaling the child; waiting for a command to finish or the screen to settle; asserting on terminal text, colors, exit codes, output, or snapshots; capturing text or full-color SVG screenshots; recording and replaying asciinema sessions; watching a live session while an agent drives it; or doing any of this from Python or Node with the shell-use bindings.' --- # shell-use `shell-use` controls a real terminal from the command line. It runs shells and TUI programs in a headless PTY behind a background daemon: a stateless CLI front end talks to a daemon that owns the PTY and renders it into a full terminal emulator. Each call connects, acts, and exits, and they all share one live session. With it you can spawn a session, read the rendered screen, send keys and mouse input, wait for a condition, assert on the result, and record the session. ## Built for agents: self-documenting commands Three commands let an agent look up the rest of the surface instead of guessing: - `shell-use agent-context`: versioned JSON describing every command, flag, enum, default, and the exit-code taxonomy. It is generated from the CLI, so it stays in sync. Read this first when you need exact argument shapes. - `shell-use usage`: a one-screen command cheatsheet. - `shell-use skill`: this guide. ## Core model - **Sessions.** `--session ` (default `default`, env `SHELL_USE_SESSION`) selects a terminal. The first command auto-starts the daemon; the session persists across calls until `close`. Run many independent sessions by name. - **Stateless calls.** Each invocation connects to the daemon, acts, and exits. State (screen, cwd, last command) lives in the daemon, not the CLI. - **JSON.** Pass `--json` on any command for machine-readable output. Data goes to stdout, diagnostics to stderr. On failure the JSON carries a `"kind"` (`assertion` / `usage` / `no_session` / `internal`). - **Verbose.** `--verbose` / `-v` starts the daemon with a full PTY traffic log (see [Debugging](#debugging)). Only takes effect when the daemon starts. - **Defaults.** New sessions are `80x30`. `wait text` / `wait idle` and `expect text` default to a 5s timeout; `wait command` / `wait exit` to 30s. ## Exit codes Every command returns a stable exit code so you can branch on the failure class without parsing text: | Code | Meaning | | --- | --- | | `0` | success | | `1` | assertion or wait condition not met (`expect` / `wait`) | | `2` | usage / invalid argument | | `3` | no active session (run `open` / `run` first) | | `4` | daemon or IPC error | | `5` | internal error | ## Command reference ### Session & lifecycle | Command | Description | | --- | --- | | `open [--shell S] [--cols N] [--rows N] [--cwd D] [--env K=V]...` | Spawn a shell session (auto-starts the daemon). `--env` is repeatable. | | `run [args...] [--cols N] [--rows N] [--cwd D] [--env K=V]...` | Spawn a session running a program directly (no shell). | | `sessions` | List active sessions. | | `close [--all]` | Close the current session (or every session with `--all`). | | `daemon status` / `daemon stop` | Inspect (PID, log path) or stop the daemon. | ### Inspection | Command | Description | | --- | --- | | `state` | cwd, size, cursor, last command + exit code, and a text snapshot. | | `text [--full]` | Rendered viewport text, or full scrollback with `--full`. | | `screenshot [PATH] [-o FILE] [--full]` | Terminal text to stdout, or a full-color SVG image (crisp at any zoom, svg-term-style window) when a path is given. | | `cells X Y [W H]` | Per-cell attributes (char, fg, bg, flags) for a region. | | `get command\|output\|exit-code\|cwd\|cursor\|size` | One structured field. | ### Input | Command | Description | | --- | --- | | `type "text"` | Type literal text (no return key). | | `submit ["text"]` | Type text then press the shell's return key. Omit text to just submit. | | `press ` | Named keys, e.g. `press Escape : w q Enter`, `press Ctrl+C`. | | `keys "Ctrl+a"` | A single key combo. | | `mouse click X Y` / `mouse click --on-text "OK" [--button N] [--clicks N]` | Click by coordinates or by visible label. | | `mouse move\|down\|up\|drag\|scroll ...` | Full mouse control (`--button` default 0=left, `scroll --amount` default 3). | ### PTY control | Command | Description | | --- | --- | | `resize COLS ROWS` | Resize the PTY and emulator. | | `write ` | Write raw bytes to the PTY (no return key appended). | | `signal INT\|TERM\|KILL\|QUIT` | Send a signal to the session's child process. | | `kill` | Kill the session's child process. | ### Wait (block until a condition holds) | Command | Description | | --- | --- | | `wait text "T" [--regex --full --not --timeout MS]` | Until text/regex is (with `--not`, is not) visible. Most precise wait. | | `wait idle [--timeout MS]` | Until the screen stops repainting (~250ms quiet). | | `wait command [--timeout MS]` | Until the current foreground command finishes (needs shell integration). | | `wait exit [--timeout MS]` | Until the session's program/shell itself exits. | ### Expect (exit 0 = pass, 1 = fail) | Command | Description | | --- | --- | | `expect text "T" [--regex --full --no-strict --not --fg C --bg C --timeout MS]` | Visibility plus optional color. `--no-strict` relaxes a strict single-match. | | `expect exit-code N` | The last command's exit code. | | `expect output "T" [--regex]` | The last command's captured output. | | `expect snapshot NAME [-u] [--include-colors]` | Compare the screen against `__snapshots__/NAME.snap`; `-u` writes/updates it. | Colors accept ansi-256 (`9`), hex (`#ff0000`), or rgb (`255,0,0`). ### Recording, monitor & self-docs | Command | Description | | --- | --- | | `get-recording [session]` | Print a session's asciinema v2 cast to stdout (works even after it stopped). | | `monitor` | Watch the session live, full-color, in another terminal. | | `usage` / `agent-context` / `skill` | Self-documentation (see top of guide). | ## Workflow: run a command and check the result ```sh shell-use open # start a shell session shell-use submit "echo hello" # type text + Enter shell-use wait command # block until the command finishes shell-use expect text "hello" # assert it appeared (exit 1 if not) shell-use expect exit-code 0 # assert the command succeeded shell-use close ``` `submit` types text then presses Enter; `type` types without Enter; `press` sends named keys (`press Escape : w q Enter`, `press Ctrl+C`); `keys` sends one combo (`keys "Ctrl+a"`). ## Workflow: drive a TUI program ```sh shell-use run vim file.txt shell-use wait idle # let the screen finish rendering shell-use press i # enter insert mode shell-use type "some text" shell-use press Escape : w q Enter # save and quit shell-use wait exit ``` ## Workflow: mouse interaction ```sh shell-use mouse click --on-text "OK" # click a label, no coordinates needed shell-use mouse click 10 5 --clicks 2 # double-click at column 10, row 5 shell-use mouse scroll down --amount 5 # scroll the wheel shell-use mouse drag 2 2 20 2 # drag from (2,2) to (20,2) ``` ## Workflow: assert colors ```sh shell-use cells 0 0 10 1 # inspect char/fg/bg/flags shell-use expect text "ERROR" --fg "#ff0000" # text present AND red shell-use expect text "OK" --fg 2 --bg 0 # ansi-256 fg/bg ``` ## Workflow: snapshot testing ```sh shell-use expect snapshot main-view -u # create/update the snapshot shell-use expect snapshot main-view # later: assert it still matches shell-use expect snapshot main-view --include-colors # also compare per-cell colors ``` Snapshots live in `__snapshots__/.snap` next to where you run the command. ## Waiting: pick the right one - `wait text "T"`: waits until text/regex is visible. The most precise wait; use it whenever you know what output to look for. `--not` waits for it to disappear. - `wait command`: waits until the current command finishes, via the shell's OSC integration markers. This is what you want after `submit`-ing a command. Requires shell integration; without it, falls back to "prompt returned and screen idle". Bump `--timeout` for long commands (default 30s). - `wait idle`: waits until the screen stops repainting. This tracks visual quiescence, not completion: a silent command like `sleep 100` counts as idle almost immediately. Use it to let a TUI finish drawing. - `wait exit`: waits until the program/session itself exits. Use for `run ` sessions or after sending `exit`. ## Recording Every session records automatically from the moment it opens, in asciinema v2 cast format, stored in your XDG cache by session name. The path is reported in the `open` / `run` response. Recordings persist after the session ends; stale ones are swept when a daemon next starts (recordings of still-running sessions are kept). ```sh shell-use get-recording > demo.cast # current session's recording to stdout shell-use get-recording work > w.cast # a specific session by name (even if stopped) ``` Play it with `asciinema play demo.cast`, or render a GIF with `agg demo.cast demo.gif`. ## Live monitor Watch a session live in a second terminal while an agent drives it; both share the same daemon. `monitor` takes over an alternate screen and streams the session in full color at ~20fps. Press `q`, `Esc`, or `Ctrl-C` to detach. ```sh shell-use --session work monitor # watch the 'work' session live ``` It needs an interactive terminal (exit `2` otherwise) and an existing session (exit `3` if none). It only reads shared screen state, so watching never blocks the commands the agent runs; resizing the window re-fits the frame. ## Programmatic use (Python, Node, Deno & Bun) Two client libraries drive the same daemon from code instead of the shell, with methods that mirror the CLI command surface. Both are async and dependency-free, and both need the `shell-use` binary on `PATH` (or pointed to with the `SHELL_USE_BIN` env var, or a `binary` argument). They start and reuse the daemon exactly like the CLI, so a session opened from code can be watched with `shell-use monitor` from another terminal. The JavaScript package is a single ESM module that runs on Node, Deno, and Bun; it imports only built-in modules, so it pulls in nothing extra on any of them. ```sh pip install shell-use # Python 3.8+, imported as `shell_use` npm install @microsoft/shell-use # Node 20+ (ESM only) bun add @microsoft/shell-use # Bun deno add npm:@microsoft/shell-use # Deno 2 ``` Python: ```python import asyncio from shell_use import ShellUse async def main(): async with ShellUse() as su: # closes the session on exit await su.open() await su.submit("echo hello") await su.wait_command() await su.expect_text("hello", strict=False) # command echo + output both match await su.expect_exit_code(0) asyncio.run(main()) ``` Node, Deno, or Bun (the same code runs on all three): ```js import { ShellUse } from "@microsoft/shell-use"; const su = new ShellUse(); await su.open(); await su.submit("echo hello"); await su.waitCommand(); await su.expectText("hello", { strict: false }); await su.expectExitCode(0); await su.close(); ``` On Windows, Deno needs `-A` (`--allow-all`) rather than just `--allow-read --allow-write`, because the daemon IPC uses a named pipe. Methods mirror the CLI commands: `open` / `run`, `submit` / `type` / `write`, `press` / `keys`, `mouse.click|move|down|up|drag|scroll`, `resize`, `signal` / `kill`, `state`, `text`, `cells`, `get` (plus shorthands `get_command` / `get_output` / `get_exit_code` / `get_cwd` / `get_cursor` / `get_size`), `screenshot`, `wait_text` / `wait_idle` / `wait_command` / `wait_exit`, `expect_text` / `expect_exit_code` / `expect_output` / `expect_snapshot`, and `close`. Module-level helpers cover the rest: `sessions`, `close_all`, `daemon_status`, `daemon_stop`, `get_recording`. The JavaScript client uses the same names in camelCase (`waitCommand`, `expectText`, `getExitCode`, `closeAll`, etc). The constructor takes the session name plus optional `binary` and `home` overrides: `ShellUse(session="default", *, binary=None, home=None)` in Python, `new ShellUse(session?, { binary?, home? })` in JavaScript. `run` takes the program then its args (`await su.run("vim", "file.txt")` in Python, `await su.run("vim", ["file.txt"])` in JavaScript). Failures raise typed errors instead of returning exit codes, one class per row of the [exit-code table](#exit-codes): `ExpectationError` (1), `UsageError` (2), `NoSessionError` (3), `DaemonError` (4), and `InternalError` (5), all subclasses of `ShellUseError`. On its first call a client also checks that the daemon's version matches the package and raises `VersionMismatchError` if they differ; stop the daemon (`daemon_stop`) so it restarts on the matching binary. ## Supported shells & integration `open --shell S` accepts: `bash`, `zsh`, `fish`, `powershell`, `pwsh`, `cmd`, `xonsh`, `elvish`, `nushell`. Omit `--shell` to use the platform default. `shell-use` injects shell integration (standard OSC 133 semantic-prompt markers, plus OSC 7 for cwd) so it can track command boundaries, exit codes, cwd, and command/output text across shells. This is what powers `wait command`, `expect exit-code`, `get cwd`, and `get command|output`. Integration coverage varies by shell: `powershell` has no native pre-exec hook so command/output text is best-effort (exit code and cwd still track); `cmd` is prompt-only. ## Debugging By default the daemon writes no log. Start it with `--verbose` to record every byte read from and written to the PTY, plus lifecycle events, to `~/.shell-use/.log`. Logging is fixed when the daemon starts, so enable it on a fresh daemon (close any existing one first): ```sh shell-use --session work close # stop any existing daemon shell-use --session work --verbose open # start one with logging on shell-use --session work submit "ls" cat ~/.shell-use/work.log ``` `shell-use daemon status` reports the active log path. **Stuck session?** If the screen is frozen and input seems ignored (e.g. after `git log` / `git diff`), a full-screen pager such as `less` is likely holding the terminal, and `Ctrl+C` won't quit it. Confirm with `shell-use state` (`"ready": false` and a stale last command). Quit the pager with `shell-use press q`, or avoid it with `git --no-pager ` or `GIT_PAGER=cat`. **Platform note.** On Windows ConPTY, `get output` and `get command` text can on some rare occasions be unreliable due to screen repainting; grid-based checks (`expect text`, `expect exit-code`) are unaffected.