This document describes the implemented tuispec server JSON-RPC behavior.
- Run interactive PTY-backed TUI sessions over JSON-RPC 2.0.
- Keep one active session per server process.
- Provide launch/input/wait/snapshot APIs plus utility methods for batching, notifications, and JSONL recording/replay.
- Framing: newline-delimited JSON objects (one JSON-RPC message per line).
- Input: stdin.
- Output: stdout.
- Mode: request/response plus optional server notifications.
tuispec server --artifact-dir PATH [--cols N] [--rows N] [--timeout-seconds N] [--ambiguity-mode fail|first-visible|last-visible]Defaults:
cols = 134rows = 40timeout-seconds = 5ambiguity-mode = fail
- Single active session at a time.
- Initialize with
initialize. - Non-lifecycle methods require an active session unless noted.
- Row/column coordinates in server APIs are 1-based.
0is reserved as a wildcard for row/column range selectors incurrentViewfilters (entireRow,entireCol, andregion/rowswildcard forms).- Selector
at/within.rectcoordinates must be>= 1.
-
initialize- params:
name(optional, default"session")- optional run overrides:
timeoutSecondsterminalColsterminalRowsambiguityMode(fail|first|first-visible|last|last-visible)snapshotThemeupdateSnapshots
- result:
sessionNameartifactRootrowscolsversion
- params:
-
launch- params:
command(required)args(optional, default[])env(optional object ofstring -> string|null)- string value: set/override
null: unset inherited env var
cwd(optional)readySelector(optional selector)readyTimeoutMs(optional)readyPollIntervalMs(optional)
- result:
ok
- params:
-
sendKey- params:
key
- params:
-
sendText- params:
text
- params:
-
sendLine- params:
text- optional
expectAfterselector - optional
timeoutMs - optional
pollIntervalMs - optional
ambiguityMode
- result:
ok
- params:
-
waitForText- params:
selector- optional
timeoutMs - optional
pollIntervalMs - optional
ambiguityMode
- result:
ok
- params:
-
waitUntil- params:
pattern(regex-like pattern over fullcurrentView.text)- optional
timeoutMs - optional
pollIntervalMs
- result:
ok
- params:
-
waitForStable- params:
debounceMs(required): viewport must be unchanged for this many milliseconds- optional
timeoutMs: overall timeout (defaults to sessiontimeoutSeconds × 1000) - optional
pollIntervalMs: poll interval (default100)
- result:
ok - Polls the viewport at
pollIntervalMsintervals and returns once the visible text has remained identical for at leastdebounceMsconsecutive milliseconds. Use this instead of fixed sleeps to wait for TUI output to settle.
- params:
-
currentView- optional params (choose exactly one filter mode):
rows:{ "start": Int, "end": Int }region:{ "col": Int, "row": Int, "width": Int, "height": Int }entireRow:IntentireCol:Int
- result:
textrowscols
- optional params (choose exactly one filter mode):
-
dumpView- params:
name- optional
format:ansi|png|both(defaultansi) - optional render overrides for PNG mode:
themefontrowscols
- result:
snapshotPath(canonical)metaPath(canonical)artifactRoot(canonical)- optional
pngPath
- params:
-
renderView- params:
name- optional render overrides:
theme,font,rows,cols
- result:
snapshotPathmetaPathpngPathartifactRoot
- params:
-
diffView- params:
leftPathrightPath- optional
mode:text|styled(defaulttext)
- result:
changedchangedLinessummary
- params:
-
expectSnapshot- params:
name - result:
okactualPathbaselinePathbaselineExists
- params:
-
expectVisible- params:
selector - result:
ok
- params:
-
expectNotVisible- params:
selector - result:
ok
- params:
-
viewSubscribe- params (optional):
debounceMs(default100)includeText(defaultfalse)
- result:
okdebounceMsincludeText
- params (optional):
-
viewUnsubscribe- params: none
- result:
ok
When subscribed, the server emits JSON-RPC notifications:
{"jsonrpc":"2.0","method":"view.changed","params":{"rows":40,"cols":134}}If includeText=true, notification params also include text.
batch- params:
steps: array of{ "method": Text, "params": Value }
- execution model:
- sequential
- non-atomic
- stop on first failure
- result:
- on success:
{ "ok": true, "completed": N, "results": [...] } - on failure:
{ "ok": false, "completed": N, "results": [...], "errorStep": K, "error": {...} }
- on success:
- params:
-
recording.start- params:
path(required): output JSONL pathframeIntervalMs(optional, default200): viewport sampling interval in milliseconds. Set to0to disable frame capture. Default 200ms = 5 Hz.
- result:
{ "ok": true, "path": FilePath, "frameIntervalMs": Int }
- params:
-
recording.stop- params: none
- result:
{ "ok": true }
-
recording.status- params: none
- result:
{ "active": Bool, "path": FilePath|null }
-
replay- params:
path- optional
speed:as-fast-as-possible|real-time
- result:
okreplayedRequestspathspeed
- params:
Recording files are JSONL with one event per line and include:
timestampMicrosdirection(request|response|notification|frame|frame-delta)line(raw JSON-RPC line, viewport text, or delta payload)
When frameIntervalMs > 0 (the default), the server spawns a background thread
that samples the viewport at the configured rate. Only frames that differ from
the previous sample are written. Two frame event types are used:
frame— full keyframe, emitted roughly every second.linecontains the complete rendered viewport text.frame-delta— compact delta, emitted between keyframes.linecontains a JSON array of[lineIndex, "new line text"]pairs for each changed line.
The replay CLI reconstructs full frames by applying deltas to the most recent keyframe.
-
server.ping- result:
pong: trueversion
- result:
-
server.shutdown- result:
shuttingDown: true
- then hard-shutdown:
- send
SIGKILLto active child process group - exit process immediately
- send
- result:
- exact:
{"type":"exact","text":"Ready"}
- regex:
{"type":"regex","pattern":"Ready|Done"}
- at (1-based):
{"type":"at","col":10,"row":2}
- within (1-based origin):
{"type":"within","rect":{"col":1,"row":1,"width":40,"height":10},"selector":{...}}
- nth:
{"type":"nth","index":1,"selector":{...}}
JSON-RPC errors:
- parse failures:
pARSE_ERROR - invalid request:
iNVALID_REQUEST - unknown method:
mETHOD_NOT_FOUND - bad params:
iNVALID_PARAMS
Server-domain codes:
-32001no active session-32002session already started-32004method failed
On SIGHUP, server does hard-shutdown:
- send
SIGKILLto active child process group - close recording handle
- exit immediately