Skip to content
Termio

Command-line tool

The termio command — open a project from the shell, and the termio session orchestration API that lets you or another agent spawn, drive, and supervise sessions.

termio is the shell side of the app: it opens projects, and through termio session it is the orchestration API one agent uses to see and steer its siblings. The switch that exposes it is Session control.

Turn on Settings ▸ Server ▸ Command line ▸ Command-line tool. It symlinks a termio command onto your PATH (at /usr/local/bin/termio; macOS asks for permission once), and turning it off removes the link again.

Open a project

termio                 # open the current directory as a project
termio ~/code/myapp    # open a specific folder

Run it in any directory and Termio brings that folder into the sidebar — the shell equivalent of Open Project.

The orchestration model

The termio session family is Termio’s orchestration API: it lets one agent — or your own scripts — see and steer sibling sessions in the same project. Before the verb reference, the design rules everything below follows:

  • Project-scoped by default. Every command resolves the caller to its own project (via the session id the PTY carries, or the working directory) and can only see and drive siblings there — never sessions in unrelated projects.
  • One address, one target. Sessions are addressed by termio://session/<uuid> deep links minted at creation and printed by list (a bare id or unique id-prefix works too). The link names the pane, never its mutable contents, so a copied address survives the session promoting, demoting, or being renamed — and stays self-describing when pasted anywhere.
  • Transcript as truth. An agent’s result is read from its own structured transcript (the path and line range the replies hand back), never scraped off the screen. The screen is the result channel only for plain run terminals, which have no transcript.
  • Waiting is explicit. No command blocks unless you pass --wait, and --wait always means the same thing: wait for the turn’s outcome, fail fast when no outcome can come, and split the result across exit codes.
  • Signal, never kill. The supervision plane observes and reports — watch events, the stalled alarm — but never terminates or auto-answers a session. Acting on a signal is always the supervisor’s decision.

Drive sessions

Add --json to any command for machine-readable output.

CommandWhat it does
termio session listList the sessions in this project with their live status.
termio session watchBlock and stream one line per status change — the push alternative to polling list.
termio session spawn "<prompt>"Start a new agent session on the prompt; replies immediately with its session link.
termio session run "<command>"Start a new plain terminal session typing that shell command — a dev server, a test run — in a visible pane, no LLM.
termio session send <link> "<text>"Type text into an existing session and submit it with a real Return keypress — a prompt to drive it, or a menu choice ("1", "yes") to answer a permission prompt.
termio session read <link>Print the session’s current screen without focusing it (--lines N keeps the tail) — the result channel for run sessions.
termio session close <link>Close one or more sessions. Their programs end.
termio session focus <link>Bring a session to the front in the app.
termio session create -- <program>Start a session on this machine’s Termio server that no app window shows, and print its id.
termio session attach <link>Attach this terminal to a session. Ctrl-\ detaches; the session keeps running.

<link> is the termio://session/<uuid> address list prints (a bare id, unique id-prefix, session title or session name also works). Commands are scoped to the current project automatically, and termio session <verb> --help prints focused help for any one verb. ls is short for list, and sessions is the old name of session; both still work.

The termio session list command showing eight project sessions below Codex and Claude Code

answer is a deprecated alias of send (meaningful for agent sessions only), and send with no target behaves like spawn — both kept so existing scripts keep working. Prefer spawn and send.

Spawning without waiting

spawn returns the moment the session exists: the reply carries the new session’s link, and the prompt itself is typed in once the agent finishes booting. Add --agent <id> (e.g. codex, grok, pi) to choose the agent; it defaults to the caller’s own kind.

--direction right or --direction down places the new pane beside or under yours. --ratio is its share of the split: --direction down --ratio 0.25 is a strip under your pane. A stated ratio holds against later spawns; panes without one share their run evenly. run takes the same two flags.

Readiness is judged from the screen. An agent stopped on a startup prompt, such as a trust prompt or a first-run notice, looks ready, and takes your prompt as its answer. The reply cannot know this. list --json then flags the session as prompt_undelivered: check it before you wait on a reply, then send the prompt again with send.

Running plain commands

Not everything in a fleet is an agent. run starts a terminal session on a shell command — a dev server, a test watcher, a build — in a visible pane you can watch and take over, and read prints any session’s current screen without focusing it. A plain command has no transcript, so its screen is its result channel; agent results keep flowing through transcripts.

termio session run "pnpm test"
termio session read 1a2b3c4d --lines 40

Typing keys

send types its text and then presses Return. Two flags change that:

  • --no-enter types the text and no Return: the lone t a trust prompt waits for.
  • --key <name> presses a named key after the text, in the order given, and drops the Return; add --key enter when you want one. --key escape backs out of a menu, and --key up --key enter reruns the last entry.

Use --key instead of writing escape bytes yourself. A key’s bytes depend on the mode the program asked for: Up is ESC [ A in normal mode and ESC O A in application mode, so only the terminal’s own key encoder gets them right. An unknown name is an error, never text.

Key names follow kitty and tmux, both spellings accepted: enter, escape, tab, space, backspace, delete, insert, up, down, left, right, home, end, pageup, pagedown, f1–f12, and single characters, with ctrl-/c- or shift-/s- for chords (ctrl-c, c-c, shift-tab). Ctrl and Shift are the modifiers a program sees. macOS spends Option on composing text and Command on the app, so those chords are refused. A meta chord is Escape, then the key: --key escape --key b.

Waiting for the outcome: --wait

Waiting is always explicit, and it means the same thing everywhere: --wait waits for the outcome of the turn, never for plumbing. Both send and spawn accept it (with an optional --timeout <ms>, default 300000, clamped 1000–600000):

termio session send termio://session/ab12cd34-9f2e-4c31-b8d7-3e5a12c90f44 "run the tests" --wait
termio session spawn "summarize the failing CI job" --wait --timeout 120000

The call blocks until the turn settles — the session was seen working and has rested off it — and the reply carries the final status, the transcript path, and the cursor..cursor_end line range the response landed in, so the caller reads exactly the new content with its own file tools. Two special cases:

  • A session that stops to ask something short-circuits the wait immediately: the reply is status: "needs-you" with the on-screen question in prompt — answer it with another send.
  • A plain terminal with no status signal falls back to screen settling: the screen changed after the send, then went still.

Waits also fail fast instead of burning the timeout when no outcome can come:

  • A prompt that shows no effect within 5 seconds — no status move, no screen change — errors as prompt_stalled: the input was eaten (an agent still booting, a program that ignores typed text), and no turn is coming.
  • A session that closes mid-wait errors as session_closed; an agent that exits back to its shell mid-wait errors as agent_gone.

On timeout the reply still comes back (timed_out: true) with whatever the current status is; the session keeps running. The exit code splits the three outcomes so scripts can branch without parsing: 0 settled, 1 error (including stalled/vanished), 3 timed out.

Supervising with watch

watch is built so an agent can supervise a fleet without polling:

  • Snapshot on attach. It first prints one line per session with its current status (tagged "snapshot":true in --json), so a supervisor attaching late still learns a session is already waiting on input. Skip it with --no-snapshot.
  • Filtered by default. Live events default to the two states a supervisor acts on, done and needs-you; widen with --state working,idle,done,needs-you.
  • Stall alarm, opt-in. --state stalled adds a watch-plane signal for the unattended runaway: a session still working that has made no repo change and next-to-no transcript growth for 20+ minutes. Sustained output — a long build streaming logs — suppresses it. The event carries the detector’s reasoning in evidence ("working 42m, no repo change, transcript +3 lines"), fires once per quiet stretch, and re-arms when progress resumes. Termio only signals; it never kills — the session’s real status stays working and the sidebar is untouched. Not in the default filter.
  • Actionable events. A needs-you event carries the on-screen question in prompt; a done event carries the session’s transcript path and cursor_end — enough to answer, or to read the result, without another round-trip.
  • Heartbeat. In --json mode the app writes {"heartbeat":true} after 30 seconds of silence, so a quiet stream is distinguishable from a dead one.
  • Exit codes. 0 after Ctrl-C (the normal end of supervision), 2 when the stream closed from the app side — “I chose to stop” and “Termio went away” are different outcomes.

Sessions without the app

Every session runs on a Termio server, and the app is only one way to see it. So termio session works with no app at all:

  • With the app running, a command acts on the sessions of your project, as above. The project scope and the Session control switch hold, with one exception: read of a session the app opened answers from the Termio server directly.
  • With no app running, every command goes to the Termio server. list shows every session on the machine, run types its command into a new shell session there, and a session’s name works as its address while no other session has it.

list --all shows every session on the machine even when the app is running. create and attach always act on the server:

termio session create --name build -- cargo watch -x test
termio session attach build          # Ctrl-\ detaches
termio session ls --all

While the app runs, send, read and close reach a session that create made only when the app shows it in your project. send then needs the link that termio session ls prints for it: a bare name is read as a prompt.

What only the app has: spawn, watch, focus, pane placement, --key and --wait. A command that needs one of them says so.

Another machine: -H

-H <host> (--host) runs any command on another machine, over your own SSH, with that machine’s own termio. The host is an alias from ~/.ssh/config, or user@host. -H goes before the command.

termio -H mybox session ls
termio -H mybox session attach api
termio -H mybox server deploy        # install or update the server there first

server deploy is the one command that runs from here, because it is what puts a build on that machine. Every other command runs there, so the machine answers with its own build. See Remote hosts.

Where am I: whoami

termio whoami shows the session, project, group and pane the current shell runs in, with the host, the channel and the socket it talks to. A value the shell does not have prints as none. Run it before a command that acts on "this session", to see where that command will land.

termio whoami
termio whoami --json

The agent skill: skill

Termio teaches agents its commands with a skill. The skill's text is built into termio, so a new version of Termio brings a new skill.

termio skill show        # print the skill this termio carries
termio skill install     # install it for every agent on this machine
termio skill uninstall   # remove it; hooks stay

termio agent install installs the skill and the status hooks together.

Spawned sessions know their caller

When one Termio session spawns another, the delivered prompt opens with a short provenance envelope: it names the caller’s link and allows exactly one back-channel — a mid-task question, or a one-line completion ping, via termio session send <caller-link> …. Nothing else travels that way: no conversation, no delegating tasks back. Results stay transcript-as-truth — the supervisor reads the worker’s transcript, with watch as the backstop. A spawn from a plain shell (outside any Termio session) gets no envelope.

Fail loud

The CLI is built for callers that trust exit codes, not prose. A command that runs and fails — an error reply, an empty one — exits 1; a command it cannot parse exits 2 without contacting the app. Flags take either spelling, --agent codex or --agent=codex, and -- ends them, which is how text beginning with a dash reaches a session:

termio session send <session> -- --force

One-shot commands give up after a 15-second client timeout rather than hanging on a busy app (override with the TERMIO_CLI_TIMEOUT environment variable; a --wait call widens the client timeout automatically to outlive the server-side wait).

JSON contract

Agents code against --json output, so its shapes are pinned, not reverse-engineered. Every JSON reply carries "schema_version": 1; fields may be added within a version, never renamed or removed. Optional fields are omitted when unknown (never emitted as "" or null).

Every error, on any verb, has one shape — and the exit code is nonzero:

{"ok": false, "error": "<code>", "message": "<human-readable next step>", "schema_version": 1}

The stable codes: disabled, no_scope, bad_op, bad_request, no_text, no_target, not_found, ambiguous, wrong_agent, bad_agent, no_agent, start_failed, not_live, needs_app (a flag that only the app serves, used with no app running), and daemon (the Termio server failed to answer) — plus, on --wait only, prompt_stalled, session_closed, and agent_gone.

Success replies, per verb (schema_version and "ok": true omitted below for brevity):

VerbReply fields
listproject, sessions: [{link, id, title, agent, status, description, transcript?}] — link is the canonical address, id its 8-char short form
spawn / runtarget, title, created: true, queued: true — the payload is still being delivered
sendtarget, title, transcript?, cursor? — read the response from cursor onward
readtarget, title, screen — the current viewport, right-trimmed, trailing blank rows dropped
send/spawn/run --waittarget, title, status, timed_out, created?, transcript?, cursor?, cursor_end?, prompt?
closeclosed, title
focusfocused
list --all, and list with no appsessions: [{id, name, cwd, command, pid, status, …}] — the Termio server’s own rows
createid
run, send, read, close with no appas above, with the session’s id as target; read of a session the app opened keeps its link

The two wait verbs share one reply shape (--wait means the same thing everywhere): the final status, and the transcript line range cursor..cursor_end holding the response. prompt appears only on a needs-you outcome — the question the session is showing on screen.

watch streams newline-delimited JSON events rather than one reply:

{"schema_version": 1, "link": "termio://session/ab12cd34-9f2e-4c31-b8d7-3e5a12c90f44",
 "status": "done", "title": "…", "cwd": "…", "snapshot": true, "prompt": "…",
 "transcript": "…", "cursor_end": 42}

cwd is omitted until the shell reports one; snapshot marks the on-attach roster lines; prompt rides on needs-you events, transcript + cursor_end on done events (exactly as in wait replies), and evidence on stalled events — the stall detector’s reasoning, e.g. "working 42m, no repo change, transcript +3 lines". stalled appears only on the watch stream, never as a session’s status in list or wait replies. After 30 seconds of silence the app writes {"heartbeat": true}.

This is what lets one agent hand work to another — spawn a task in a sibling session, then watch (or poll list) until it reports done and read the result. Watching several at once, termio session watch prints the link the moment any of them turns done or needs-you — one agent supervising a fleet, all on your own machine.

Docs