Skip to content

Scripting reference

CLI verbs, selectors, JSON-RPC methods, event frames, config keys, workspace specs, flow specs, MCP tools, hooks, and exit codes for Paneflow automation.

This is the compact reference for Scripting and automation. It names the public surface a human script or LLM can quote exactly.

CLI verbs

The paneflow binary intercepts these verbs and exits before GUI startup. Unknown verbs exit with usage code 2 instead of silently launching the app.

VerbPrimary method or engineWrites to panes?Use
ls [--human]surface.listNoList panes in the active workspace
read <target>surface.readNoRead pane scrollback
search <target> <pattern>surface.searchNoSearch pane scrollback
ps [--json]fleet.listNoList detected agents across workspaces
status <target> [--json]surface.statusNoRead one pane's agent state
newworkspace.createNoCreate a workspace
select <index>workspace.selectNoSelect a workspace
split <h|v>surface.splitNoSplit a pane
focus <target>surface.focusNoFocus a pane and its workspace
send <target> <text>surface.send_textGatedStage or submit text
key <target> <keystroke>surface.send_keystrokeGatedSend one non-submitting keystroke
wait --match <sel>surface.read, events.subscribeNoBlock until pattern, idle, or both
watch [--surface <sel>] [--type <event>]events.subscribeNoStream lifecycle and surface events
up <file>Workspace spec enginePrefill onlyCreate a declarative workspace
flow run <file>Flow engineGated for submitting stepsRun a local multi-agent DAG

Aliases accepted by the CLI: list_panes maps to ls, read_pane maps to read, and search_pane maps to search.

Selectors

SelectorExampleNotes
Numeric idpaneflow read 42Matches surface_id exactly
Namepaneflow status backendBest selector for durable scripts
cmdline:<substr>paneflow read cmdline:viteFull foreground argv on Linux, executable basename on macOS and Windows
cwd:<path>paneflow read cwd:/home/me/apiMatches the pane working directory

A selector that matches nothing or several panes exits with code 3, except commands that explicitly accept multiple matches such as send --broadcast, wait --any, and wait --all.

Exit codes

CodeMeaning
0Success
1Runtime failure: instance unreachable, pane closed, gate refused, or handler error
2CLI usage error
3Target not found or ambiguous
4wait timeout or flow ready timeout

Write gates

Reading is allowed by default. Writes are split by capability:

OperationGate
send without --submitPANEFLOW_IPC_SCRIPTING=1 or ai_unrestricted
send --submitPANEFLOW_IPC_SCRIPTING=1 or ai_unrestricted
keyPANEFLOW_IPC_SCRIPTING=1
Flow step with submit = trueScripting capability reported by system.capabilities

send does not append a carriage return unless --submit is present. key rejects submitting keystrokes such as enter, ctrl-m, and ctrl-j. A single surface.send_text payload is capped at 64 KiB.

Relevant config keys:

KeyDefaultMeaning
ai_unrestrictedfalseAllows trusted AI automation to submit text without the env gate
ai_injection_fencetrueWraps surface.read text in an untrusted terminal envelope
submit_paste_delay_ms70Base delay between bracketed paste and the submit carriage return
terminal.envnoneEnvironment variables injected into new terminals

Read fields

paneflow read <target> --json and raw surface.read return:

FieldMeaning
textScrollback text, fenced by default
linesReturned line count
total_linesTotal retained lines
eofWhether the read reached the oldest retained line
output_generationMonotonic counter advanced by pane output

Defaults and limits: lines defaults to 200 and clamps to 1-4000. offset starts from the end of the buffer. Passing an out-of-range offset is an invalid-params error.

The fenced JSON-RPC param defaults to ai_injection_fence. The CLI flag --raw passes fenced: false.

Agent state fields

paneflow ps --json returns {"agents":[...]}. paneflow status <target> --json returns one status object.

FieldMeaning
pidAgent process id, when known
toolAgent family such as claude, codex, opencode, or gemini
statethinking, waiting_for_input, finished, errored, stalled, idle, or unknown_running
hookedWhether lifecycle hook events are attached
reasonDetection reason, including no_hook
surface_idPane id
surface_namePane name
workspaceWorkspace index
active_tool_nameTool currently running inside the agent
messageWaiting prompt or permission text
last_resultLast turn summary, when available
waiting_msTime spent waiting for input
idle_msTime since observed activity
output_generationPane output counter, on status

An empty fleet is {"agents":[]} with exit code 0. A pane with no tracked agent returns idle state, not an error.

Workspace spec

paneflow up <file> reads a TOML workspace spec.

Top-level fieldTypeDefaultNotes
namestring"Workspace"Workspace title
layoutstring"even_h"even_h, even_v, main_vertical, or tiled
port_baseinteger3000Base for ${port_offset} allocation
[[panes]]arrayrequiredOne entry per pane
Pane fieldTypeDefaultNotes
cwdstringnoneMust exist after expansion and canonicalization
agentstringnoneAgent launcher name, mutually exclusive with command
commandstringnoneRaw command, mutually exclusive with agent
promptstringnonePrefilled into an agent input, never submitted by up
focusboolfalseGives initial focus to this pane
envtablenoneMerged over terminal.env; supports ${port_offset}
namestringnoneStable selector name
worktreestringnoneBranch name for a managed worktree under <repo>.worktrees/
copy_envbooltrueCopies gitignored .env* files into the worktree
setupstringnoneCommand run before launch
setup_timeout_secsinteger300Setup timeout
worktree_teardownstring"auto"auto removes clean worktrees on close; keep leaves them

${port_offset} substitutes only inside env values. Unknown keys are errors. Workspace creation validates paths before creating panes.

Flow spec

paneflow flow run <file> reads a TOML flow spec and runs it against the current Paneflow instance.

FieldTypeNotes
idstringRequired, unique step id
needsarrayDependencies; on foreach, waits for all instances
foreacharrayFan-out, one instance per item
paneinline tableSpawn a pane using workspace pane fields
sendinline table{ target, text, submit? }; requires a dependency
readytable{ pattern, timeout_secs? }; regex barrier
capturetable{ var, lines }; captures 1-500 lines after ready
submitboolSubmits a spawned pane prompt; requires scripting access

Variables:

VariableScope
${item}foreach steps: cwd, name, worktree, env, send.target, ready.pattern, prompts, and texts
${var}Captured values inside send.text and submitting pane.prompt
${var.<item>}Captures from a foreach group

The runner validates unknown keys, missing dependencies, dependency cycles, invalid regexes, undefined captures, pane budget, and missing timeouts before execution. Ctrl-C stops the orchestration loop; panes that were created remain in Paneflow.

JSON-RPC connection

PropertyValue
Linux endpoint$XDG_RUNTIME_DIR/paneflow/paneflow.sock
macOS endpointUser runtime dir, same Paneflow socket name
Windows endpoint\\.\pipe\paneflow
FramingNewline-delimited JSON-RPC 2.0
Request modelOne request per connection, except events.subscribe
Local trustSame user only; no network listener, no token, no TLS
BackpressureConnection cap and bounded event queues return structured errors or dropped frames

Probe capabilities at runtime:

bash
printf '%s\n' '{"jsonrpc":"2.0","method":"system.capabilities","params":{},"id":1}' \
| nc -U "$PANEFLOW_SOCKET_PATH"

JSON-RPC methods

MethodParamsReturns or notes
system.ping-Liveness check
system.capabilities-{scripting, methods[]}
system.identify-{name, version, protocol}
workspace.list-Workspaces with indexes and titles
workspace.current-Active workspace
workspace.createname?, cwd?, layout?Create a workspace
workspace.selectindexSwitch workspace
workspace.closeindex?Close a workspace
workspace.upname, layout, panes[]Declarative spawn used by up and flow roots
workspace.restore_layoutlayoutApply a layout tree
surface.list-{surfaces:[{surface_id,name,title,cwd,cmd,workspace}]}
surface.readsurface_id, lines?, offset?, fenced?Scrollback and output_generation
surface.searchsurface_id, pattern, max_matches?Case-insensitive substring matches
surface.renamesurface_id, nameRename or clear a pane name
surface.focussurface_idFocus pane and workspace
surface.statussurface_idAgent state for one pane
surface.send_textsurface_id, text, submit?, paste?Gated PTY text write
surface.send_keystrokesurface_id, keystrokeEnv-gated non-submitting keystroke
surface.splitdirection, surface_id?, cwd?, command?, prompt?, env?, name?, managed_worktree?Split a pane
fleet.list-Read-only fleet snapshot
events.subscribesurfaces?, types?Persistent newline-delimited event stream
ai.session_starthook payloadLifecycle telemetry
ai.prompt_submithook payloadLifecycle telemetry
ai.tool_usehook payloadLifecycle telemetry
ai.notificationhook payloadLifecycle telemetry
ai.stophook payloadLifecycle telemetry
ai.exithook payloadLifecycle telemetry
ai.session_endhook payloadLifecycle telemetry

Structured failures use JSON-RPC error envelopes: -32602 invalid params, -32601 gated method, -32001 permission, and -32000 backpressure. Some legacy validation errors still arrive as {"error":"..."} inside result; clients should treat both shapes as failures.

Events

paneflow watch and raw events.subscribe emit newline-delimited JSON. The first frame acknowledges the subscription.

Event typeMeaning
subscribedSubscription acknowledged
ai.session_startAgent session starts
ai.prompt_submitPrompt is submitted
ai.tool_useAgent reports tool use
ai.notificationAgent asks for input or permission
ai.stopAgent turn stops
ai.exitAgent process exits
ai.session_endAgent session closes
surface_changedPane output_generation advanced
heartbeatIdle keepalive
droppedSubscriber lagged and events were shed

After a dropped frame, resync with paneflow ps --json or paneflow status <target> --json.

MCP bridge

paneflow-mcp is a read-only stdio MCP server over the same Paneflow socket.

ToolParamsReturns
list_panes-Panes with surface_id, name, title, cwd, cmd, workspace
read_panetarget, lines?, offset?Scrollback text
search_panetarget, pattern, max_matches?Matching lines

It has no tool for typing, submitting, focusing, or splitting panes. Returned terminal output is fenced as untrusted data.

Lifecycle hooks

paneflow-ai-hook reads event JSON on stdin, posts one JSON-RPC ai.* frame, and exits 0 so a stopped Paneflow instance does not break the agent. The hook surface powers status, notifications, ps, status, and watch.

Persistent paneflow hooks setup is Claude Code scoped. Codex uses per-launch shim hooks. Agents with no hook surface still run, but their state may be limited to process detection.