agenticonsult logoagent i /consultDocs
Agentic infrastructureMCP servers

cc-terminals

The terminal control plane — agents open, drive, and read visible terminal sessions and manage the mission fleet, under your pause control and audit log.

This server is a control plane into the running app: it lets an agent session operate the Terminals workspace the same way you do — open and close terminals, write input and send named keys, read output incrementally, run commands and wait for them, resize and focus panes, and manage whole workspaces. On top of that sit the mission-fleet verbs: assign a mission to a commander session, spawn workers, send and signal across the fleet. Unlike the daemon-fronting servers, this one reaches the running desktop app itself through a local control endpoint the app writes at startup.

This is the machinery behind persistent, watchable delegation. A session can stand up a full, grounded Claude session in a terminal — context artifacts injected at launch, all capabilities intact — then drive it, read it, and tear it down. Every terminal an agent opens is a regular, operator-visible pane; there is no hidden session type, so what you see in the Terminals view is everything that is running.

The safety posture is the one documented on the Terminals page, and it applies to every tool here: the agent-control pill pauses all mutating agent actions instantly (reads keep working, so you can inspect a paused fleet); the audit log at documents/terminal-workspaces/.control/audit.log records every mutating call as one JSON line; and the whole plane sits behind an enable switch in your configuration, so you can run the platform with agent terminal control off entirely.

How your agents use it

  • "Stand up a worker on the parser bug with the debugging context."terminal_open with agent mode and the named context artifacts, then terminal_write + terminal_send_key to direct it, and terminal_read for token-efficient incremental output.
  • "Dispatch the mission fleet." — the Missions Board's dispatch runs through the same verbs an agent can use: mission_assign, mission_spawn_worker, mission_send, mission_signal.
  • "Run the test suite over there and tell me when it is done."terminal_run and terminal_wait_for_exit against any pane, agent or plain shell.

Prerequisites

The desktop app must be running with agent terminal control enabled — the switch, and the rest of the terminal-plane settings, are covered in the configuration reference. Grounded sessions the plane spawns run on your own Claude subscription.

Tool reference

ToolParametersWhat it does
terminal_pingRound-trip health check against the Command Center's terminal control server. Returns {pong:true} when reachable, or a structured error if the CC app isn't running or the terminal control plane is disabled (config/cc.config.json control.terminal — ON by default). Call this first to confirm the control plane is live before driving terminals.
terminal_listList the live terminal session ids in the Command Center. Each id is a pane id (session_id == pane id). Use these ids with terminal_read / terminal_write. Returns [] if no terminal panes are open.
terminal_readid*: string, mode: string, rows: integer, cols: integer, cursor: string, tail_lines: integer, max_bytes: integer, since: integer, strip_ansi: booleanRead a terminal's output. The DEFAULT (no mode) AUTO-CLEANS so you never pull noise: a redrawing TUI / spinner / progress bar is RENDERED to its clean current screen (dozens of redraw frames collapsed → far fewer tokens), while normal append output is returned as the clean stream (no scrollback loss). `rendered` tells you which path was used. Other modes: 'new' = only what's NEW since your last read (incremental follow — a SERVER-SIDE cursor; survives compaction/handoff; NEVER drops, older output served from the transcript) · 'peek' = new without advancing · 'all' = paginate the ENTIRE history (pass `since=next`) · 'screen' = force-render the current screen · `tail_lines=N` = the last N lines · 'raw' = exact bytes, no clean (LAST-RESORT, e.g. inspecting escape sequences). Returns `next`/`has_more`/`has_older`. terminal_info sizes up an unfamiliar terminal; terminal_read_structured gives per-command results; terminal_run runs+captures.
terminal_infoid*: stringSize up a terminal BEFORE reading (cheap — returns NO bulk output). Tells you how much it holds — {running, exit_code, bell_pending, total_bytes, retained_bytes, retained_lines, commands_retained, last_command, has_transcript, transcript_path, cursors}. exit_code = how a finished session ended (null while running); bell_pending = it rang the terminal bell and no input has arrived since (an unresolved needs-attention flag). The size fields tell you how to read a terminal that already accumulated a lot: terminal_read_structured(last_n=N) for a compact 'what happened' · terminal_read(mode='tail', tail_lines=N) for the recent screen · terminal_read(mode='all', since=…) to paginate ALL of it (clean text) · or Read/Grep `transcript_path` directly for surgical access to the full RAW history. NOTE: retained_* reflect the in-memory ring; total_bytes is the full session size.
terminal_read_structuredid*: string, last_n: integer, mode: stringRead a terminal's recent commands as STRUCTURED blocks — [{command, output, exit_code, running}] from OSC-133 markers (PowerShell sessions from terminal_open). The most TOKEN-EFFICIENT 'what happened / did it succeed' view (far smaller than raw). `last_n` bounds how many recent command blocks you get (default 15) — use it on a terminal with a lot of history so you don't pull everything. `mode='new'` returns only commands since your last structured read (a server cursor) — for ongoing work. Returns `returned`/`total_in_ring`/`has_older` (tail) or `next`/`has_more` (new). PowerShell-only; cmd/other shells (or sessions opened before integration) yield [].
terminal_writeid*: string, data*: string, enter: boolean, paste: booleanWrite input/keystrokes to a live terminal (stdin of the shell process). WRITING TEXT DOES NOT RUN IT: to actually submit/execute, set enter=true (or follow with terminal_send_key enter) — a prompt typed without it just sits in the input line and the session waits. Then call terminal_read to observe output. LONG / MULTILINE TEXT JUST WORKS: bulk input is automatically delivered as a bracketed paste (ESC[200~ … ESC[201~) so a TUI — a nested `claude`, vim, etc. — inserts it verbatim and atomically (no truncation, no per-line auto-submit, no loose-keystroke chunking); the submit Enter is emitted AFTER the paste. Pass paste=false to force raw keystrokes (e.g. a lone control sequence) or paste=true to force paste mode. Works on ANY terminal in the Command Center — full session parity, so a terminal the agent opened and one the operator opened are equivalent. SAFETY: this runs whatever you send in a REAL shell; the operator can pause all agent terminal control at any time from the Terminals view, and every write is recorded to the audit log.
terminal_openid: string, name: string, workspace_id: string, cwd: string, cols: integer, rows: integer, model: string, effort: string, skip_permissions: boolean, first_message: string, agent: boolean, contexts: array, inherit_workspace: booleanOpen a NEW PowerShell terminal in the Command Center and return its id. It is spawned immediately (drive it right away with terminal_write/run/read) AND surfaced as a REGULAR, visible pane in the Terminals UI — the operator sees and can use it like any other terminal (full session parity; no agent-exclusive sessions). Pass workspace_id to choose which workspace the pane lands in (from workspace_list); if omitted, it lands in the currently-active workspace. Close it with terminal_kill (which also removes the pane). cwd defaults to the project root. GROUNDING: set agent=true to launch an interactive Claude Code session in the terminal instead of a plain shell; pass contexts=[...] (context-artifact names or paths from context_list) to inject them as the session's system-prompt context (via --append-system-prompt-file). By default (inherit_workspace=true) the session ALSO inherits the context attached to its workspace — so an agent-spawned terminal gets the same context the operator set. The grounded session is a FULL Claude Code session (it manages its own tools/model/permissions); only the CONTEXT is injected.
terminal_first_messageid*: string, text*: string, submit: boolean, quiet_ms: integer, ceiling_ms: integerType an opening instruction into an EXISTING terminal session once its CLI is READY, then submit it. Use this instead of terminal_write + terminal_send_key enter when the session was only just spawned: a freshly launched Claude Code TUI is not listening for input yet, so a plain write races the boot and the text is silently lost. This waits for the terminal's output to start and then settle, clears any auto-suggested prompt sitting in the input box, types the text and presses Enter. Returns as soon as the wait is queued — delivery happens in the background. For a session you are opening right now, prefer terminal_open's `first_message` (one call). For an established, idle session a plain terminal_write is still fine.
terminal_killid*: stringKill a terminal session by id (the shell process TREE is terminated) AND remove its visible pane from the Command Center. Use to clean up a terminal you opened with terminal_open. Returns {killed:true} if the session existed.
terminal_runid*: string, command*: string, timeout_sec: numberRun a PowerShell command in a session and return its output + exit code (the reliable 'execute and capture' primitive). Completion is detected via the shell's own OSC-133 markers — no command mutation, no visible sentinel — so the command shows cleanly in the pane (falls back to a sentinel for non-integrated sessions). Best for PowerShell sessions opened with terminal_open. For cmd.exe or interactive TUIs use terminal_write + terminal_read instead. Long output is tail-capped.
terminal_wait_for_exitid*: string, timeout_ms: integerBlock until the session's process exits (or the timeout elapses), then return its exit code. Use for a session running a finite command/process. An interactive shell (the default agent session) never exits on its own — use terminal_run for per-command completion instead.
terminal_resizeid*: string, cols*: integer, rows*: integerResize a terminal session's PTY (columns x rows). Returns the applied size.
workspace_listList the Command Center's terminal WORKSPACES (folder-bound, tabbed containers of panes) and which one is active. Returns {workspaces:[{id,name,color,order,root,panes:[{id,name,shell,status,attention,unseen_stopped}]}], active, focused_pane_id, focused}. Per-pane runtime state answers 'which terminals need attendance?': status = running|exited; attention = null|bell|permission|idle (an UNRESOLVED needs-a-human flag — a permission prompt or idle wait; it clears when input reaches the session, e.g. your terminal_write); unseen_stopped = it exited and the operator hasn't seen that yet. (`focused`/`focused_pane_id` = the operator's currently-focused terminal pane — call terminal_focus for just that, denormalized.) NOTE: requires the Terminals view to have been opened in CC once (so the index exists).
mission_runtimeslug: stringRead a MISSION's fleet runtime + ASSIGNMENT DIRECTORY — the panes bound to a mission joined to their live status, PLUS the mission's operations and who is on what. Returns {agents:[{pane_id,role,operation_id,status,attention,ready,live}], operations:[{id,op_slug,status,name,folder_path}], orphaned_operations:[…] (planned/active ops with NO assigned worker — spawn or assign one), idle_workers:[{pane_id}] (workers with NO operation — give them work), agent_count, running_count, attention_count}. role = commander|worker; ready = safe to message (running, not at a permission prompt). Call on the commander's turn to see who is working on what and what is orphaned — do NOT poll it in a loop.
mission_signalmission*: string, from: string, kind: string, text: stringAppend a worker→commander signal to a mission's durable _mailbox.md (the commander drains it on its turns). Use to report progress/blockers up the fleet. kind = DONE|BLOCKED|ATTENTION|note. The mission must be activated (have a working folder). Mission/operation/goal CONTENT is authored as markdown directly — this verb is ONLY the signal channel, not a data-write API.
mission_assignpane*: string, operation: stringAssign (or clear) a worker pane's OPERATION — sets the runtime binding so mission_runtime + the board show who is working on what. Pass operation as the operation id '<mission>/<op-slug>' to assign, or omit it to clear (return the worker to idle). The operation CONTENT is authored as markdown; this only sets the assignment.
mission_sendmission*: string, from: string, to*: string, text: string, submit: boolean, interrupt: booleanSend a live message to another pane in your fleet — the primary coordination channel. Durably enqueues to the mission mailbox (the GUARANTEED path the recipient drains on its turn) AND best-effort delivers it live into the recipient's terminal when it is ready. Commander→worker: dispatch ('start op X · brief at <path> · begin'). Worker→commander: use 'commander' as `to`. submit (default true) presses Enter; interrupt=true re-tasks a busy worker (Ctrl-C first). Heavy payload belongs in the operation markdown — the message is the trigger + pointer, not the data.
mission_spawn_workermission*: string, operation: stringStaff an orphaned operation by standing up a NEW worker terminal in the mission's workspace and binding it to the mission (so mission_runtime sees it). Returns its pane_id. Then dispatch its context with mission_send (the message carries the dossier + op pointers). Use when you have MORE operations than workers (parallel capacity); for serial work, re-task a freed worker with mission_assign + mission_send instead. Pass operation to pre-assign it.
terminal_focusGet the operator's CURRENTLY-FOCUSED terminal + active workspace — the deictic 'here'. Returns {active_workspace_id, active_workspace_name, focused_pane_id, focused_pane_name, focused_workspace_id, focused_workspace_name}. When the operator says 'run X in my focused terminal' / 'in the workspace I have open' WITHOUT naming it, call this, then act on focused_pane_id (terminal_write / terminal_run / send_key). `focused_pane_id` is null if no terminal is focused (or the Terminals view was never opened) — then ask which terminal. The focused pane may live in a different workspace than the active tab (a popped-out terminal). Read-only.
workspace_switchid*: stringSwitch the Command Center's active terminal workspace by id (from workspace_list).
workspace_createname: string, root: string, count: integer, run: string, model: string, effort: string, template: string, connected: boolean, mcp_scope: string, first_message: string, first_message_target: stringCreate a terminal workspace EXACTLY as the operator does in the UI: a working folder + how many terminals + what each runs. `count` terminals (1-12, default 1) each launch `run` — 'claude' (default → a grounded, CC-anchored Claude session with the workspace's connection + context), blank (a plain PowerShell), or any command (a shell that runs it on startup). `model` + `effort` tune a grounded ('claude') run. The frontend creates it + returns the real id. CROSS-ROOT: `root` may be ANY absolute path. When it is OUTSIDE the Command Center the workspace is CONNECTED by default — its sessions reach ALL CC tools/agents/memory AND the folder (both repos); connected=false = Standalone (folder only). `mcp_scope` picks the MCP servers for a connected external workspace. In-root workspaces are always CC-connected (connected/mcp_scope ignored). Every terminal inherits the workspace's connection.
workspace_closeid*: stringClose (delete) a terminal workspace by id. Kills its panes' sessions and trashes its .md.
workspace_reorderfrom*: integer, to*: integerReorder workspace tabs — move the workspace at index `from` to index `to` (indices from workspace_list `order`).
pane_addworkspace_id*: string, target_pane_id: string, dir: string, shell: stringAdd an OPERATOR-VISIBLE terminal pane to a workspace (splits the last pane, or fills an empty workspace). After, call workspace_list for the new pane id, then drive it with terminal_write/read/run. IMPORTANT: a GUI pane's shell only starts when that workspace is VIEWED in Command Center (lazy-mount) — for a reliable agent-private terminal use terminal_open (headless) instead.
pane_closeworkspace_id*: string, pane_id*: stringClose an operator-visible pane in a workspace by id (from workspace_list). Kills its session.
pane_renameworkspace_id*: string, pane_id*: string, name*: stringRename a pane in a workspace (the pane's display label).
terminal_send_keyid*: string, key*: stringSend a NAMED key/keystroke to a terminal — the clean way to drive interactive TUIs (e.g. submit a prompt to a nested `claude` with key='enter', interrupt with 'ctrl_c', navigate with 'up'/'down'). A TUI treats text+Enter in one terminal_write as a paste, so type with terminal_write then submit with terminal_send_key enter. Keys: enter, tab, esc, space, backspace, delete, ctrl_c/d/z/l/u/a/e/r, up, down, left, right, home, end, pageup, pagedown.
workspace_broadcastworkspace_id*: string, text*: stringWrite the same text to EVERY live terminal in a workspace (e.g. broadcast a command). Done directly via the host, so it works even if the workspace isn't being viewed. Append \r in the text to run it.
context_listList the context artifacts available in the Command Center (name, description, knowledge-graph state) — the 'what context can I attach or inject' surface. Use a returned name/path with terminal_open(contexts=[...]) to ground a session, or with context_attach to attach it to a workspace/pane.
context_attachartifact*: string, workspace_id*: string, pane_id: string, detach: booleanAttach (or detach) a context artifact to a terminal WORKSPACE default or a specific PANE — just like the operator does in the Context UI. Pass workspace_id to set the workspace default (every grounded terminal in it inherits the context); add pane_id to set one pane's own context. Affected agent panes respawn to pick up the change. Set detach=true to remove. `artifact` is a context name or .md path (from context_list). SAFETY: a pause-gated, audited control-plane op.
list_drivesDiscover WHERE a workspace can live on this machine BEFORE creating one: returns the available hard-drive ROOTS (e.g. 'C:\\', 'D:\\', '<drive>:\') + the current DEFAULT working folder (what workspace_create uses when `root` is omitted). Call this when you need to place a workspace on a specific drive you don't yet know exists, or to learn the default root. Lean by design — it lists DRIVES only, never folder trees (to browse inside a folder, run a shell command in a terminal). Read-only.

Where to go next

On this page