The Terminal Automation API provides programmatic control over TUI (text-based UI) applications running in managed terminal sessions. Use these endpoints to snapshot the screen, search rendered output with regex, inject key presses and pasted text, and block until a target condition is met — without driving a real keyboard. Endpoints are backed by a server-side libvterm parser that mirrors the browser’s xterm.js state, so snapshots reflect exactly what a user would see.
Screen Inspection
Section titled “Screen Inspection”GET /api/v1/terminal/snapshot
Section titled “GET /api/v1/terminal/snapshot”Returns a rendered snapshot of the terminal screen as seen by a user. The snapshot includes the visible text grid (lines), cursor position, window title, fullscreen (alt-screen) state, reverse-video highlight spans, and a monotonic sequence counter. On first call for a session the parser is lazily initialized by replaying the session’s output buffer.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
terminal_id | query | string | Yes | Terminal session ID (numeric 1-65535) |
include_colors | query | boolean | No | Include ANSI SGR colored_lines array alongside plain text lines. Default: false |
include_highlights | query | boolean | No | Include reverse-video highlight spans. Default: true |
scroll_offset | query | integer | No | Lines into scrollback (0 = live viewport). Default: 0 |
Response
Section titled “Response”{ "terminal_id": 1042, "sequence": 4271, "title": "vim /etc/hosts", "alt_screen": false, "dimensions": { "rows": 24, "cols": 80 }, "cursor": { "row": 7, "col": 12, "visible": true }, "lines": [ " GNU nano 5.4 /etc/hosts ", "127.0.0.1 localhost", "::1 localhost", "" ], "highlights": [ { "row": 1, "col_start": 0, "col_end": 12, "reverse": true } ], "colored_lines": null}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid parameters"}{ "statusCode": 404, "error": "Not Found", "message": "Session not found"}{ "statusCode": 405, "error": "Method Not Allowed", "message": "method_not_allowed"}{ "statusCode": 503, "error": "Service Unavailable", "message": "VTerm memory cap exceeded"}const snapshot = await client.terminal.terminalAutomation.getTerminalSnapshot({ terminal_id: "1042", include_colors: true, include_highlights: true, scroll_offset: 0});GET /api/v1/terminal/find
Section titled “GET /api/v1/terminal/find”Search the rendered terminal screen (or scrollback) for a PCRE2 regular expression pattern. Returns cell-coordinate hits with matched text. The scan enforces an internal 500 ms wall-clock bound to prevent ReDoS.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
terminal_id | query | string | Yes | Terminal session ID |
pattern | query | string | Yes | PCRE2 regex pattern to search for (max 1024 bytes) |
scope | query | string | No | Search scope: screen (default), scrollback, or all |
limit | query | integer | No | Maximum number of hits to return (default 100, max 1000) |
case_insensitive | query | boolean | No | Case-insensitive matching. Default: false |
scroll_offset | query | integer | No | Scrollback offset for screen scope (0 = live viewport). Default: 0 |
Response
Section titled “Response”{ "terminal_id": 1042, "pattern": "Error.*", "scope": "screen", "total": 2, "truncated": false, "deadline_exceeded": false, "hits": [ { "row": 12, "col": 4, "text": "Error: file not found" }, { "row": 18, "col": 0, "text": "Error: permission denied" } ]}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid parameters or regex"}{ "statusCode": 404, "error": "Not Found", "message": "Session not found"}{ "statusCode": 405, "error": "Method Not Allowed", "message": "method_not_allowed"}{ "statusCode": 503, "error": "Service Unavailable", "message": "VTerm memory cap exceeded"}const results = await client.terminal.terminalAutomation.findInTerminal({ terminal_id: "1042", pattern: "Error.*", scope: "screen", limit: 100, case_insensitive: false, scroll_offset: 0});GET /api/v1/terminal/keys
Section titled “GET /api/v1/terminal/keys”Returns the full list of key names accepted by /api/v1/terminal/press, including aliases and canonical forms. Useful for client-side validation and discoverability. Single printable characters (a-z, 0-9, punctuation) are also accepted but not listed individually.
This endpoint takes no parameters.
Response
Section titled “Response”{ "keys": [ "enter", "tab", "escape", "backspace", "space", "up", "down", "left", "right", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "ctrl+a", "ctrl+b", "ctrl+c", "ctrl+d", "ctrl+e", "ctrl+f", "ctrl+g", "ctrl+h", "ctrl+i", "ctrl+j", "ctrl+k", "ctrl+l", "ctrl+m", "ctrl+n", "ctrl+o", "ctrl+p", "ctrl+q", "ctrl+r", "ctrl+s", "ctrl+t", "ctrl+u", "ctrl+v", "ctrl+w", "ctrl+x", "ctrl+y", "ctrl+z", "esc", "bs", "del", "home", "end", "page_up", "page_down", "insert", "kp_enter", "kp_plus", "kp_minus" ]}{ "statusCode": 405, "error": "Method Not Allowed", "message": "method_not_allowed"}const { keys } = await client.terminal.terminalAutomation.listSupportedKeys();State & Metrics
Section titled “State & Metrics”GET /api/v1/terminal/{terminal_id}/automation
Section titled “GET /api/v1/terminal/{terminal_id}/automation”Returns the VT parser state for a specific session: whether vterm is active, dimensions, update sequence counter, time since last screen change, alt-screen flag, title, scrollback length, and active waiter count. Useful for debugging automation workflows (“why did my wait timeout? did the screen actually change?”).
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
terminal_id | path | string | Yes | Terminal session ID |
Response
Section titled “Response”{ "terminal_id": 1042, "vterm_active": true, "dimensions": { "rows": 24, "cols": 80 }, "update_seq": 4271, "ms_since_change": 142, "alt_screen": false, "title": "vim /etc/hosts", "scrollback_len": 1240, "active_waiters": 1}{ "statusCode": 400, "error": "Bad Request", "message": "Malformed terminal_id in the URL path (not numeric 1-65535)."}{ "statusCode": 404, "error": "Not Found", "message": "Session not found"}{ "statusCode": 405, "error": "Method Not Allowed", "message": "method_not_allowed"}const state = await client.terminal.terminalAutomation.getSessionAutomationState({ terminal_id: "1042"});GET /api/v1/terminal/automation/metrics
Section titled “GET /api/v1/terminal/automation/metrics”Returns global metrics for the server-side VT parser: active vterm session count, memory used/cap in MB, total active wait-waiters across all sessions, and configured limits. Use to monitor resource usage, tune --vterm-memory-cap-mb, and detect leaks.
This endpoint takes no parameters.
Response
Section titled “Response”{ "active_sessions": 14, "memory_used_mb": 87.4, "memory_cap_mb": 512, "active_waiters": 3, "limits": { "max_waiters_per_session": 16, "max_body_size_mb": 8 }}{ "statusCode": 405, "error": "Method Not Allowed", "message": "method_not_allowed"}const metrics = await client.terminal.terminalAutomation.getAutomationMetrics();POST /api/v1/terminal/press
Section titled “POST /api/v1/terminal/press”Send one or more named key presses to a terminal session. Keys are encoded through libvterm’s keyboard API which respects the terminal’s current application-cursor mode (DECCKM) and keypad mode (DECKPAM), ensuring correct byte sequences for programs like vim, htop, and tmux.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
terminal_id | query | string | Yes | Terminal session ID |
Request Body
Section titled “Request Body”Exactly one of keys or key is required.
| Name | Type | Required | Description |
|---|---|---|---|
keys | array | No | Array of key names to press in sequence (e.g. ["ctrl+c", "arrow_up", "enter"]). Mutually exclusive with key. Maximum 256 entries per request. |
key | string | No | Single key name for one-shot press (e.g. "enter"). Mutually exclusive with keys. |
{ "keys": ["ctrl+c", "arrow_up", "enter"]}Response
Section titled “Response”{ "terminal_id": 1042, "keys_pressed": 2, "bytes_written": 6}{ "statusCode": 400, "error": "Bad Request", "message": "Unknown key name or invalid request"}{ "statusCode": 404, "error": "Not Found", "message": "Session not found"}{ "statusCode": 405, "error": "Method Not Allowed", "message": "session_readonly"}{ "statusCode": 413, "error": "Payload Too Large", "message": "Request body exceeds --max-body-size cap (default 8 MB)."}{ "statusCode": 500, "error": "Internal Server Error", "message": "Write to the session's PTY or socket failed, OR the per-request 1 MiB drain cap was hit mid-sequence."}{ "statusCode": 503, "error": "Service Unavailable", "message": "VTerm memory cap exceeded"}await client.terminal.terminalAutomation.pressTerminalKeys({ terminal_id: "1042", data: { keys: ["ctrl+c", "arrow_up", "enter"] }});POST /api/v1/terminal/paste
Section titled “POST /api/v1/terminal/paste”Paste text into a terminal session with optional bracketed paste mode. When bracketed=true (default), the text is wrapped in bracketed paste escape sequences if the running program has enabled DECSET 2004 (e.g., vim, zsh), preventing auto-indent mangling and other paste artifacts. UTF-8 text including emoji and CJK is fully supported.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
terminal_id | query | string | Yes | Terminal session ID |
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
text | string | Yes | Text to paste (UTF-8) |
bracketed | boolean | No | Use bracketed paste mode if the program supports it. Default: true |
{ "text": "git pull origin main\n", "bracketed": true}Response
Section titled “Response”{ "terminal_id": 1042, "bytes_written": 1284, "bracketed_active": true, "esc_neutralized": 0}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid request"}{ "statusCode": 404, "error": "Not Found", "message": "Session not found"}{ "statusCode": 405, "error": "Method Not Allowed", "message": "session_readonly"}{ "statusCode": 413, "error": "Payload Too Large", "message": "Request body exceeds --max-body-size cap (default 8 MB)."}{ "statusCode": 500, "error": "Internal Server Error", "message": "Write to the session's PTY or socket failed, OR the per-request 1 MiB paste drain cap was hit."}{ "statusCode": 503, "error": "Service Unavailable", "message": "VTerm memory cap exceeded"}await client.terminal.terminalAutomation.pasteTerminalText({ terminal_id: "1042", data: { text: "git pull origin main\n", bracketed: true }});Synchronization
Section titled “Synchronization”POST /api/v1/terminal/wait
Section titled “POST /api/v1/terminal/wait”Block until a terminal condition is met, then return an atomic snapshot of the screen at the moment of resolution. The response includes a full snapshot for the matched, stable, timeout, and exited statuses so clients avoid a TOCTOU race between wait and a follow-up /snapshot call. The vterm_reinit status is the lone exception — it fires when the VT parser was torn down mid-wait (typically due to a memory-cap resize) and no coherent snapshot can be captured; the client should retry.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
terminal_id | query | string | Yes | Terminal session ID |
Request Body
Section titled “Request Body”pattern is required when mode is regex or either.
| Name | Type | Required | Description |
|---|---|---|---|
mode | string | No | Wait mode: stable, regex, or either. Default: stable |
debounce_ms | integer | No | Stable mode debounce in milliseconds (10-60000). Default: 100 |
pattern | string | No | PCRE2 regex pattern (required for regex/either modes, max 1024 bytes) |
timeout_ms | integer | No | Hard deadline in milliseconds (10-300000). Default: 5000 |
search_scope | string | No | Where to search: screen, scrollback, or all. Default: screen |
include_colors | boolean | No | Include colored_lines in response snapshot. Default: false |
include_highlights | boolean | No | Include highlights in response snapshot. Default: true |
{ "mode": "stable", "debounce_ms": 150, "timeout_ms": 5000}Response
Section titled “Response”{ "status": "matched", "match": { "row": 3, "col": 0, "text": "build successful" }, "snapshot": { "terminal_id": 1042, "sequence": 4312, "title": "make", "alt_screen": false, "lines": [ "$ make build", "Compiling project v1.2.3...", "build successful" ] }}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid parameters or regex"}{ "statusCode": 404, "error": "Not Found", "message": "Session not found"}{ "statusCode": 405, "error": "Method Not Allowed", "message": "method_not_allowed"}{ "statusCode": 413, "error": "Payload Too Large", "message": "Request body exceeds --max-body-size cap (default 8 MB)."}{ "statusCode": 429, "error": "Too Many Requests", "message": "Too many concurrent waiters"}{ "statusCode": 500, "error": "Internal Server Error", "message": "Waiter could not be created (OOM)."}{ "statusCode": 503, "error": "Service Unavailable", "message": "VTerm memory cap exceeded"}const result = await client.terminal.terminalAutomation.waitForTerminal({ terminal_id: "1042", data: { mode: "stable", debounce_ms: 150, timeout_ms: 5000 }});