Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Give your AI agent a real terminal it can drive, and get back reviewable snapsho
[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE)
![Node](https://img.shields.io/node/v/agent-tty)

[Quickstart](#quickstart) · [Why](#why-not-just-tmux-expect-asciinema-or-playwright) · [How it works](#how-it-works) · [Commands](#command-surface) · [Contributing](#contributing)

![agent-tty: drive a terminal session and watch it live in the dashboard](./assets/hero.gif)

Tools like `tmux` or `screen` help _you_ manage your own terminal windows. `agent-tty` is for handing a real, long-lived terminal to an AI coding agent, so it can run commands, drive interactive apps like `nvim` or `htop`, and read the screen back. Because every session is recorded, you never have to take the agent's word for what happened: you (or another agent) get a plain-text snapshot, a real screenshot, or a video of the actual screen, and can replay it to check the work. It works just as well for plain shell automation and CI smoke tests with no agent involved.
Expand Down Expand Up @@ -48,13 +50,13 @@ npm install -g agent-tty
export AGENT_TTY_HOME="$(mktemp -d)"
agent-tty doctor --json # check your environment

# Open a session, do something, wait for it, look at the result.
SID=$(agent-tty create --json -- /bin/bash | jq -r '.result.sessionId')
agent-tty run "$SID" 'printf "hello from agent-tty\n"' --json
agent-tty wait "$SID" --text 'hello from agent-tty' --json
agent-tty snapshot "$SID" --format text --json
agent-tty screenshot "$SID" --json
agent-tty destroy "$SID" --json
# The canonical loop:
SID=$(agent-tty create --json -- /bin/bash | jq -r '.result.sessionId') # 1. open a session
agent-tty run "$SID" 'printf "hello from agent-tty\n"' --json # 2. type into it
agent-tty wait "$SID" --text 'hello from agent-tty' --json # 3. wait for the screen
agent-tty snapshot "$SID" --format text --json # 4. read it back
agent-tty screenshot "$SID" --json # 5. capture proof
agent-tty destroy "$SID" --json # 6. clean up
```

Driving an interactive TUI is the same loop, with key chords and a wait for the screen to settle:
Expand Down Expand Up @@ -111,9 +113,17 @@ A colleague then used `agent-tty` to build an experimental TUI for Coder agents

## Command surface

Every user-facing command takes `--json` and returns a stable, machine-readable envelope. The commands cover the session lifecycle (`create`, `list`, `inspect`, `destroy`, `gc`), input and control (`run`, `type`, `paste`, `send-keys`, `batch`, `resize`, `signal`, `mark`), observation and capture (`wait`, `snapshot`, `screenshot`, `record export`), the live `dashboard`, and environment checks (`version`, `doctor`, `skills`).
Every user-facing command takes `--json` and returns a stable, machine-readable envelope, and exits with a stable code (`0` success, `2` usage error, `3` session not found, …) so scripts can branch without parsing output.

| Group | Commands |
| ----------------------- | ------------------------------------------------------------------------ |
| Session lifecycle | `create`, `list`, `inspect`, `destroy`, `gc` |
| Input and control | `run`, `type`, `paste`, `send-keys`, `batch`, `resize`, `signal`, `mark` |
| Observation and capture | `wait`, `snapshot`, `screenshot`, `record export` |
| Live view | `dashboard` |
| Environment | `version`, `doctor`, `skills` |

See [`docs/USAGE.md`](./docs/USAGE.md) for the full flag reference and [`docs/TROUBLESHOOTING.md`](./docs/TROUBLESHOOTING.md) for renderer and environment issues.
The CLI documents itself: `agent-tty --help` lists every command, and `agent-tty <command> --help` shows its flags. The full reference, including the exit-code table, is in [`docs/USAGE.md`](./docs/USAGE.md); renderer and environment issues are in [`docs/TROUBLESHOOTING.md`](./docs/TROUBLESHOOTING.md).

## Agent skills

Expand Down
23 changes: 23 additions & 0 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ Useful flags:
- `--exit`: wait for the process to exit.
- `--timeout <ms>`: maximum wait time in milliseconds, with `0` meaning infinite.

On timeout, a standalone `wait` still exits `0` and reports `matched: false` / `timedOut: true` in the JSON result — check the envelope, not the exit code. Inside `batch`, a timed-out `wait` step is a step failure (`WAIT_TIMEOUT`, exit code `11` under fail-fast).

### Screen Hash

`snapshot` results (both `--format structured` and `--format text`) and a **matched** `wait` result carry an optional `screenHash`: a lowercase 64-character hex SHA-256 of the visible screen text. Compare it across two calls to tell whether the visible screen actually changed — equal hashes mean identical visible content, even if the event-log sequence advanced on a no-op repaint.
Expand Down Expand Up @@ -229,6 +231,27 @@ agent-tty create --env PROMPT_EOL_MARK='%B%S%#%s%b' -- /bin/zsh

A lone `'%'` does **not** restore the marker (zsh treats it as a prompt escape that expands to nothing); use `'%B%S%#%s%b'` for the styled default or `'%%'` for a plain percent. The default is applied at spawn time and is not stored in the manifest, so it does not appear in `inspect`, `list`, or `create --json` output. If your `~/.zshrc` assigns `PROMPT_EOL_MARK` it runs after the environment is imported and wins, so the marker can reappear — remove that line or set the value you want via `--env`.

## Exit Codes

Every command exits with a stable code, so scripts can branch without parsing output. The `--json` error envelope carries the precise `error.code` (for example `WAIT_TIMEOUT`); the exit code is a coarser, stable summary of it.

| Exit code | Meaning |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `0` | Success. |
| `1` | Internal or unclassified error. |
| `2` | Usage error: unknown command or flag, or an invalid argument (session ID, dimensions, keys, duration, signal, input). |
| `3` | Session not found. |
| `4` | Session is not running or already destroyed. |
| `5` | Session host timed out. |
| `6` | Session host unreachable. |
| `7` | Export failed. |
| `8` | Storage read/write or manifest validation error. |
| `9` | Protocol or RPC error. |
| `10` | Replay failed. |
| `11` | A `wait` step inside a fail-fast `batch` timed out (standalone `wait` exits `0` with `timedOut: true` in the result — see [`wait`](#wait)). |

A fail-fast `batch` exits with the failed step's code (for example `11` for a wait timeout); `--keep-going` exits `1` if any step failed.

## Anti-Patterns

- Do not reach for `tmux`, `screen`, or ad hoc PTY wrappers first when `agent-tty` can provide an isolated, inspectable session.
Expand Down
1 change: 1 addition & 0 deletions src/cli/exitCodes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ERROR_CODES } from '../protocol/errors.js';

// Public contract: documented in docs/USAGE.md ("Exit Codes"). Keep in sync.
const EXIT_CODE_BY_ERROR_CODE: Readonly<Record<string, number>> = Object.freeze(
{
[ERROR_CODES.INVALID_SESSION_ID]: 2,
Expand Down
Loading