Skip to content

Drive TUI apps programmatically — snapshot the rendered screen, search with regex, send named key presses, paste text, inject cell-based mouse events, and block until a condition resolves. All write operations are routed through a server-side VT parser (libvterm) that mirrors the browser’s xterm.js state, so byte sequences respect application-cursor mode (DECCKM), keypad mode (DECKPAM), bracketed-paste mode (DECSET 2004), and active mouse-reporting protocols. Input endpoints are validated all-or-nothing: a single invalid event or key rejects the entire request with no partial writes.

GET /api/v1/terminal/snapshot

Returns a rendered snapshot of the terminal screen as seen by a user. The response includes the visible text grid (lines), cursor position, window title, alt-screen state, reverse-video highlight spans, and a monotonic sequence counter. Optionally returns ANSI SGR colored lines. The snapshot is built from a server-side VT parser (libvterm) that mirrors the browser’s xterm.js state; on first call for a session the parser is lazily initialized by replaying the session’s output buffer.

NameInTypeRequiredDescription
terminal_idquerystringYesTerminal session ID (numeric 1-65535)
include_colorsquerybooleanNoInclude ANSI SGR colored_lines array alongside plain text lines. Default: false
include_highlightsquerybooleanNoInclude reverse-video highlight spans. Default: true
scroll_offsetqueryintegerNoLines into scrollback (0 = live viewport). Default: 0
{
"lines": [
"$ npm install",
"",
"added 247 packages in 12s",
"",
"user@host:~$ "
],
"cursor": { "row": 4, "col": 11 },
"title": "user@host: ~",
"alt_screen": false,
"highlights": [
{ "row": 2, "col_start": 0, "col_end": 23 }
],
"sequence": 42,
"rows": 24,
"cols": 80,
"colored_lines": null
}
const snap = await client.terminal.terminalAutomation.getTerminalSnapshot({
terminal_id: "42",
include_colors: true,
});

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. Supports case-insensitive matching, result limits, and scope selection (screen, scrollback, or all). Pattern length is capped at 1024 bytes. Match limits are enforced to prevent ReDoS — the scan honors an internal 500 ms wall-clock bound and reports deadline_exceeded if hit.

NameInTypeRequiredDescription
terminal_idquerystringYesTerminal session ID
patternquerystringYesPCRE2 regex pattern to search for (max 1024 bytes)
scopequerystringNoSearch scope: screen (default), scrollback, or all
limitqueryintegerNoMaximum number of hits to return (default 100, max 1000)
case_insensitivequerybooleanNoCase-insensitive matching. Default: false
scroll_offsetqueryintegerNoScrollback offset for screen scope (0 = live viewport). Default: 0
{
"matches": [
{ "row": 0, "col": 2, "text": "npm install" },
{ "row": 2, "col": 6, "text": "added" }
],
"total": 2,
"truncated": false,
"deadline_exceeded": false,
"scope": "screen"
}
const hits = await client.terminal.terminalAutomation.findInTerminal({
terminal_id: "42",
pattern: "error|warning",
scope: "all",
limit: 50,
});

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?”).

NameInTypeRequiredDescription
terminal_idpathstringYesTerminal session ID
{
"vterm_active": true,
"rows": 24,
"cols": 80,
"sequence": 42,
"ms_since_change": 1500,
"alt_screen": false,
"title": "user@host: ~",
"scrollback_len": 1000,
"active_waiters": 1
}
const state = await client.terminal.terminalAutomation.getSessionAutomationState({
terminal_id: "42",
});

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.

{
"vterm_sessions": 3,
"vterm_memory_used_mb": 12.4,
"vterm_memory_cap_mb": 256,
"active_waiters": 2,
"limits": {
"max_waiters_per_session": 16,
"max_body_size": 8388608
}
}
const metrics = await client.terminal.terminalAutomation.getAutomationMetrics();

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.

{
"keys": [
"enter", "return", "tab", "escape", "esc", "backspace", "bs", "space",
"up", "down", "left", "right",
"home", "end", "page_up", "page_down", "insert", "delete",
"f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12",
"ctrl+a", "ctrl+b", "ctrl+c", "ctrl+d", "ctrl+e", "ctrl+k", "ctrl+l", "ctrl+r", "ctrl+u", "ctrl+w", "ctrl+z"
]
}
const { keys } = await client.terminal.terminalAutomation.listSupportedKeys();

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. Supports letters, ctrl+letter, arrow keys, function keys, enter, tab, escape, backspace, and more. All keys are validated before any are sent — a single unknown key rejects the entire request with no partial writes.

NameInTypeRequiredDescription
terminal_idquerystringYesTerminal session ID

Exactly one of key or keys must be supplied. Use key for a single one-shot press; use keys for a sequence (max 256 entries per request).

NameTypeRequiredDescription
keystringNoSingle key name for one-shot press (e.g. enter). Mutually exclusive with keys
keysarrayNoArray of key names to press in sequence (e.g. ["ctrl+c", "arrow_up", "enter"]). Mutually exclusive with key. Maximum 256 entries per request
{
"keys": ["ctrl+c", "arrow_up", "enter"]
}
{
"sent": 3,
"keys": ["ctrl+c", "arrow_up", "enter"]
}
await client.terminal.terminalAutomation.pressTerminalKeys({
terminal_id: "42",
data: { keys: ["ctrl+c", "arrow_up", "enter"] },
});

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). This prevents auto-indent mangling and other paste artifacts. When bracketed=false, the text is sent as raw keystrokes. UTF-8 text including emoji and CJK is fully supported.

NameInTypeRequiredDescription
terminal_idquerystringYesTerminal session ID
NameTypeRequiredDescription
textstringYesText to paste (UTF-8)
bracketedbooleanNoUse bracketed paste mode if the program supports it. Default: true
{
"text": "git status && git diff --stat",
"bracketed": true
}
{
"sent": true,
"bracketed_active": true,
"esc_neutralized": 0,
"bytes": 28
}
await client.terminal.terminalAutomation.pasteTerminalText({
terminal_id: "42",
data: { text: "git status && git diff --stat", bracketed: true },
});

POST /api/v1/terminal/mouse

Send deterministic mouse events to a terminal session using libvterm’s mouse API. Coordinates are zero-based terminal cells, not pixels. Mouse protocol output is emitted only when the target program has enabled terminal mouse reporting. Events are validated before any are sent, matching the all-or-nothing contract of /press.

NameInTypeRequiredDescription
terminal_idquerystringYesTerminal session ID

Supply exactly one of event (single event) or events (array of 1-256 events). Each element is a TerminalMouseEvent object.

NameTypeRequiredDescription
eventobjectNoSingle TerminalMouseEvent. Mutually exclusive with events
eventsarrayNoArray of TerminalMouseEvent objects. Mutually exclusive with event. Min 1, max 256

TerminalMouseEvent fields:

NameTypeRequiredDescription
typestringYesMouse event kind: move, down, up, click, or scroll. click expands to down/up; scroll uses wheel buttons
rowintegerYesZero-based terminal row cell (must be zero or positive)
colintegerYesZero-based terminal column cell (must be zero or positive)
buttonintegerNoMouse button (1-5). Non-scroll events accept 1-3; scroll accepts 4-5
amountintegerNoScroll repeat count for scroll events (1-20)
directionstringNoScroll direction: up or down. Overrides the scroll button
modifiersarrayNoKeyboard modifiers: shift, alt, meta, ctrl, or control. Max 8 items
{
"events": [
{ "type": "move", "row": 10, "col": 25 },
{ "type": "down", "row": 10, "col": 25, "button": 1 },
{ "type": "click", "row": 10, "col": 25, "button": 1, "modifiers": ["ctrl"] },
{ "type": "up", "row": 10, "col": 25, "button": 1 }
]
}
{
"sent": 4,
"terminal_id": "42"
}
// Single event form
await client.terminal.terminalAutomation.sendTerminalMouseEvents({
terminal_id: "42",
data: { event: { type: "click", row: 10, col: 25, button: 1 } },
});
// Multiple events form
await client.terminal.terminalAutomation.sendTerminalMouseEvents({
terminal_id: "42",
data: {
events: [
{ type: "move", row: 10, col: 25 },
{ type: "click", row: 10, col: 25, button: 1 },
],
},
});

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. Supports three modes: stable (no screen updates for debounce_ms), regex (PCRE2 pattern matches on screen), or either (first condition wins). The response includes a full snapshot for the matched/stable/timeout/exited terminal 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 and no coherent snapshot can be captured (client should retry). Maximum 16 concurrent waiters per session.

NameInTypeRequiredDescription
terminal_idquerystringYesTerminal session ID
NameTypeRequiredDescription
modestringNoWait mode: stable, regex, or either. Default: stable
debounce_msintegerNoStable mode debounce in milliseconds (10-60000). Default: 100
patternstringNoPCRE2 regex pattern (required for regex/either modes, max 1024 bytes)
timeout_msintegerNoHard deadline in milliseconds (10-300000). Default: 5000
search_scopestringNoWhere to search: screen, scrollback, or all. Default: screen
include_colorsbooleanNoInclude colored_lines in response snapshot. Default: false
include_highlightsbooleanNoInclude highlights in response snapshot. Default: true
{
"mode": "either",
"pattern": "Password:|\\$ ",
"timeout_ms": 30000,
"search_scope": "screen"
}

The status field is one of: matched (regex hit — includes match and snapshot), stable (no damage for debounce_ms — includes snapshot), timeout (hit timeout_ms — includes snapshot), exited (underlying process died mid-wait — includes snapshot), or vterm_reinit (the VT parser was torn down and re-initialized mid-wait due to a memory-cap resize — client should retry, no match or snapshot).

{
"status": "matched",
"match": {
"row": 0,
"col": 6,
"text": "Welcome"
},
"snapshot": {
"lines": [
"Welcome to hoody",
"user@host:~$ "
],
"cursor": { "row": 1, "col": 11 },
"title": "user@host: ~",
"alt_screen": false,
"highlights": [],
"sequence": 17,
"rows": 24,
"cols": 80
}
}
const result = await client.terminal.terminalAutomation.waitForTerminal({
terminal_id: "42",
data: { mode: "either", pattern: "Password:|\\$ ", timeout_ms: 30000 },
});
if (result.status === "matched") {
console.log("matched at", result.match);
} else if (result.status === "stable") {
console.log("screen settled:", result.snapshot.lines);
} else if (result.status === "vterm_reinit") {
// retry the wait
}