
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.
SKILL.md
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 <name>(defaultdefault, envSHELL_USE_SESSION) selects a terminal. The first command auto-starts the daemon; the session persists across calls untilclose. 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
--jsonon 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/-vstarts the daemon with a full PTY traffic log (see Debugging). Only takes effect when the daemon starts. - Defaults. New sessions are
80x30.wait text/wait idleandexpect textdefault to a 5s timeout;wait command/wait exitto 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 <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 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 <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). |
PTY control
| 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. |
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
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
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
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
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
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.
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.--notwaits for it to disappear.wait command: waits until the current command finishes, via the shell's OSC integration markers. This is what you want aftersubmit-ing a command. Requires shell integration; without it, falls back to "prompt returned and screen idle". Bump--timeoutfor long commands (default 30s).wait idle: waits until the screen stops repainting. This tracks visual quiescence, not completion: a silent command likesleep 100counts 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 sendingexit.
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).
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.
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.
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.
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/<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.
More from Microsoft
View publisherrushstack-best-practices
manage Rush monorepos with best practices
rushstack
Apr 6EngineeringLocal DevelopmentMicrosoftProject Management +1azure-ai-agents-persistent-dotnet
build AI agents with Azure .NET SDK
skills
Jul 3.NETAgentsAzureLLMazure-ai-anomalydetector-java
build anomaly detection applications with Java
skills
May 13AnalyticsAzureData AnalysisJava +2azure-ai-contentsafety-java
build content moderation applications with Azure AI
skills
Jul 7AI InfrastructureAzureJavaSecurityazure-ai-contentsafety-py
detect harmful content with Azure AI Content Safety
skills
Jul 18AzureComplianceLLMMicrosoft +2azure-ai-language-conversations-py
implement conversational language understanding with Python
skills
Jul 18AnalyticsAzureLLMPython