microsoft/shell-use
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.
npx skills add https://github.com/microsoft/shell-use --skill 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.
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.--session <name> (default default, env SHELL_USE_SESSION)selects a terminal. The first command auto-starts that session's daemon; the
session persists across calls until close. Sessions are independent.
State (screen, cwd, last command) lives in the daemon, not the CLI.
--json on any command for machine-readable output. Data goesto stdout, diagnostics to stderr. On failure the JSON carries a "kind"
(assertion / usage / no_session / internal).
--verbose / -v starts the daemon with a full PTY traffic log(see Debugging). Only takes effect when the daemon starts.
80x30. Timeouts come in five classes: textand idle default to 5s; command, exit, and ready to 30s. Set a session
default with open --timeout-<class> <ms>, or override one call with
--timeout. state reports the effective values.
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 | 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 <program> [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 start | Start this session's daemon. Most commands start one on demand. |
| daemon status | Inspect a session's daemon (pid, log path). Exit 3 if none is running. |
| daemon stop --session N \| --all | Stop one session's daemon, or every daemon. Needs a target. |
| Command | Description |
| --- | --- |
| state | cwd, size, cursor, last command + exit code, timeouts, 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. |
| 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 <Key...> | 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). |
| Command | Description |
| --- | --- |
| resize COLS ROWS | Resize the PTY and emulator. |
| write <data> | 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. |
| 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. |
| wait ready [--timeout MS] | Until the shell reports a ready prompt (needs shell integration). open waits by default. |
| 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 [--timeout MS] | The last command's exit code. Waits for the command to finish first. |
| 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).
| 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). |
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").
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
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)
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
shell-use expect text "plain" --fg default # asserts the cell set no color
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__/<NAME>.snap next to where you run the command.
wait text "T": waits until text/regex is visible. The most precise wait; useit 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 OSCintegration markers. Use it after submit. Without shell integration it falls
back to "screen idle". Bump --timeout for long commands (default 30s).
wait idle: waits until the screen stops repainting. This tracks visualquiescence, 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 forrun <program> sessions or after sending exit.
wait ready: waits until the shell reports a ready prompt. open does thisfor you.
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).
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.
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.
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.
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.
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:
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):
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: 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.
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.
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/<session>.log. Logging is fixed when the daemon starts, so enable
it on a fresh daemon (close any existing one first):
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 <cmd> 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.
Take microsoft/shell-use from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference pip, npm.
Without those the skill loads but fails at the first command.