API Reference
JSON-RPC 2.0 protocol
Over the socket, each line is one JSON-RPC 2.0 message, newline-delimited. All requests must carry an id (notifications are not supported), and params must be an object.
Methods overview
| method | params | result (success) |
|---|---|---|
pty/read | { top? | bottom? | start?+end? } | { screen, cursor, size } |
pty/write | { action } | { written } |
pty/wait | { match?, timeout? } | { matched, screen, cursor, size, waitedMs } |
pty/status | {} | { running, exitCode, pid } |
pty/resize | { cols, rows } | {} |
pty/kill | {} | {} |
pty/status — query session status
→ {"jsonrpc":"2.0","method":"pty/status","params":{},"id":1}
← {"jsonrpc":"2.0","result":{"running":true,"exitCode":null,"pid":1234},"id":1}running:boolean, whether the session is running.exitCode:number | null, the exit code if it has exited, otherwisenull.pid:number, the pid of the hosted process.
pty/read — read the current screen
→ {"jsonrpc":"2.0","method":"pty/read","params":{},"id":2}
← {"jsonrpc":"2.0","result":{"screen":"root@host:~$\n","cursor":{"row":1,"col":14},"size":{"cols":100,"rows":30}},"id":2}screen:string, the plain text of the current terminal screen (ANSI control sequences removed, trailing spaces trimmed per line, multiple lines joined with\n). Semantically the terminal's current actual screen, not a history log.cursor:object, the cursor position, whererow/colare 1-based.size:object, the terminal size{ cols, rows }.
pty/read supports selecting a subset of the current screen. top/bottom and start+end are two mutually exclusive sets:
→ {"jsonrpc":"2.0","method":"pty/read","params":{"top":3},"id":3}
→ {"jsonrpc":"2.0","method":"pty/read","params":{"bottom":3},"id":4}
→ {"jsonrpc":"2.0","method":"pty/read","params":{"start":2,"end":5},"id":5}top: N— take the top N lines.bottom: N— take the bottom N lines.start/end— take the 1-based closed interval[start, end]; they must be given together.- Validation:
top/bottomare mutually exclusive;start/endmust be given as a pair; the two sets cannot be mixed; out-of-range values are clamped automatically.
pty/write — write data
action is a single action object or an array of actions:
→ {"jsonrpc":"2.0","method":"pty/write","params":{"action":{"text":"echo hello"}},"id":6}
← {"jsonrpc":"2.0","result":{"written":11},"id":6}
→ {"jsonrpc":"2.0","method":"pty/write","params":{"action":{"key":"Enter"}},"id":7}
← {"jsonrpc":"2.0","result":{"written":1},"id":7}
→ {"jsonrpc":"2.0","method":"pty/write","params":{"action":[{"text":"ls"},{"key":"Enter"}]},"id":8}
← {"jsonrpc":"2.0","result":{"written":5},"id":8}- Inside
action,textandkeyare mutually exclusive and cannot be given together; an empty object or an empty array is rejected. written:number, the actual number of UTF-8 bytes written (an array action returns the total).
Key names supported by write (action.key, case-sensitive):
| Category | Key names |
|---|---|
| Direction | ArrowUp ArrowDown ArrowLeft ArrowRight |
| Editing | Enter Tab Backspace Delete Home End PageUp PageDown Insert Escape Space |
| Function | F1 … F12 |
| Combo | Ctrl+letter (Ctrl+A…Ctrl+Z), Shift+letter, Alt+letter, Alt+Enter, Ctrl+Enter, Ctrl+Space, etc., modifiers can be combined (e.g. Ctrl+Alt+Del) |
A single-character text can be written directly with the text field; an unresolvable key returns the -32602 invalid-params error.
pty/wait — wait for a piece of output to appear
→ {"jsonrpc":"2.0","method":"pty/wait","params":{"match":"hello","timeout":6000},"id":9}
← {"jsonrpc":"2.0","result":{"matched":true,"screen":"hello\n","cursor":{"row":2,"col":1},"size":{"cols":100,"rows":30},"waitedMs":45},"id":9}- Matches
matchby substring against the current screen; when nomatchis given, it simply waits a fixed duration according totimeout. timeout: the maximum wait in milliseconds, default10000.matched:boolean, whether the match happened before the timeout.screen: the current screen at the moment of match or timeout.waitedMs: the actual elapsed time in milliseconds.
pty/resize — resize the terminal
→ {"jsonrpc":"2.0","method":"pty/resize","params":{"cols":120,"rows":40},"id":11}
← {"jsonrpc":"2.0","result":{},"id":11}pty/kill — terminate the session and close the endpoint
→ {"jsonrpc":"2.0","method":"pty/kill","params":{},"id":12}
← {"jsonrpc":"2.0","result":{},"id":12}After issuing pty/kill, the session is terminated, the endpoint is closed, the service process exits, and the current connection is closed by the server.
Error responses
→ {"jsonrpc":"2.0","method":"pty/unknown","params":{},"id":13}
← {"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found: pty/unknown"},"id":13}
→ {"jsonrpc":"2.0","method":"pty/write","params":{"action":{"text":"x","key":"Enter"}},"id":14}
← {"jsonrpc":"2.0","error":{"code":-32602,"message":"write 动作内 text 与 key 只能二选一"},"id":14}- An error object has the shape
{ code, message, data? }; see the table below forcode/message. - Responses to normal requests echo the request's
id; unparseable messages (where even theidcannot be extracted) returnid: null.
Error codes
| code | Meaning |
|---|---|
-32700 | Parse error (invalid JSON) |
-32600 | Invalid request (malformed structure / legacy {op} protocol rejected / missing id) |
-32601 | Method not found |
-32602 | Invalid params (e.g. text/key mutually exclusive, top/bottom mutually exclusive, unknown key name) |
-32603 | Internal error |
-32000 | Server error |
Programmatic API (TypeScript)
createSocketPty
import { createSocketPty } from "@ai-zen/socket-pty";
const server = createSocketPty({
endpoint: { type: "unix", path: "/tmp/v.sock" }, // or { type:"tcp", port:5174, host:"127.0.0.1" }
command: "bash", // on Windows use "powershell.exe" / "cmd.exe"
cols: 100,
rows: 30,
cwd: "/path/to/workdir", // optional
});
const address = await server.listen();endpoint is required; the address is specified by the caller.
PtyServer instance methods
| Method | Description |
|---|---|
listen(): Promise<string> | Listen on the endpoint and create the session; returns the connection address |
address(): string | The actual listening address |
session(): IPtySession | null | The current session |
close({ kill? }): Promise<void> | Close the endpoint (terminates the session when kill: true); idempotent |
Session object methods
server.session returns an IPtySession, providing synchronous screen-reading and other capabilities:
const session = server.session;
await session?.readScreen(); // whole screen
await session?.readScreen({ bottom: 5 }); // bottom 5 lines
await session?.readScreen({ start: 2, end: 6 }); // lines 2..6| Method / property | Description |
|---|---|
readScreen(sel?) | Read the current screen, returns { screen, cursor, size } |
wait(opts) | Wait for output, returns { matched, screen, cursor, size, waitedMs } |
write(data): number | Write raw data, returns the UTF-8 byte count |
resize(cols, rows) | Resize |
kill(signal?) | Terminate |
pid / running / exitCode | Process info |
Types and protocol exported from the main entry
The main entry @ai-zen/socket-pty also exports the following (reused by script end-to-end tests and protocol construction):
- Types:
IPtyLike,IPtySession,ReadResult,ScreenSelection,TermCursor,TermSize,WaitResult,PtyServerOptions,SocketEndpoint,PtySessionOptions,XtermScreenOptions,XtermTerminal. - Protocol constants/utilities:
JsonRpcErrorCode,JsonRpcErrorMessage,Methods,JSONRPC,makeRequest,makeResult,makeError,parseRequest,encodeRequest,resolveKey,resolveAction,methodToOp. - Renderer:
XtermRenderer,createRenderer.
CLI options (socket-pty serve)
| Option | Description |
|---|---|
--cmd <command> | The command to host (required). On Windows the .exe suffix is needed, e.g. powershell.exe / cmd.exe |
--socket <path> | Unix domain socket listen path (choose one with --port; one is required) |
--port <port> | TCP listen port, a concrete port in 1-65535 (choose one with --socket; one is required) |
--cols <n> | Terminal columns (default 100) |
--rows <n> | Terminal rows (default 30) |
--cwd <path> | Working directory |
-h / --help | Help |
--socket and --port are mutually exclusive and cannot be given together; if neither is given, or --port is not a concrete port in 1-65535, serve errors out.
The CLI also provides a socket-pty mcp subcommand to start the MCP manager (see MCP Adapter).