Skip to content

Latest commit

 

History

History
28 lines (21 loc) · 9.29 KB

cli.md

File metadata and controls

28 lines (21 loc) · 9.29 KB

CLI reference

CLI mode — a program inside a terminal calls back into the running app. Full syntax and semantics, split out of CLAUDE.md (which keeps the short form). Wire-format details of the backing servers: the TerminalControlServer.kt / RunSocketServer.kt entries in docs/architecture.md.

CLI mode (a program inside a terminal calls back into the running app):

agentic-terminal run --cwd <dir> [--title <t>] [--tab-id <n> | --sub | --group-id <n> | --group-name <s> [--create-group-if-missing]] <cmd...>
agentic-terminal new-tab --cwd <dir> [--title <t>] [--tab-id <n> | --sub | --group-id <n> | --group-name <s> [--create-group-if-missing]] <cmd...>
agentic-terminal close-tab <id>
agentic-terminal archive-tab <id>                   # close, but keep the session in the archive for later restore
agentic-terminal rename-tab <id> <title...>        # empty title → back to "Terminal N"
agentic-terminal get-current-group-id [sessionId]  # default: $AGENTIC_TERM_SESSION_ID
agentic-terminal set-reopen-cmd --cwd <dir> <cmd...>
agentic-terminal clear-reopen-cmd                  # clears only the cmd; cwd stays
agentic-terminal set-icon <symbol>                 # one printable code point or a U+XXXX hex token; renders before the title
agentic-terminal set-badge <symbol>                # one printable code point or a U+XXXX hex token; renders after the title; auto-clears on interaction
agentic-terminal clear-icon
agentic-terminal clear-badge

Subcommands take no -- (the first arg is normalized, so the legacy --set-reopen-cmd / --clear-reopen-cmd spellings still work). Options come BEFORE the command; the command is all remaining args joined by a single space — no quoting needed, and an arg that CONTAINS whitespace is re-wrapped in double quotes so it stays a single argument when the tab's shell re-parses the joined command (argv preserves the call-site grouping; the plain join would destroy it) — a bare -- ends option parsing for a command whose first token starts with --; values containing newlines are rejected — the wire format is line-based. --cwd is MANDATORY for run / new-tab / set-reopen-cmd (missing → usage + exit 2). set-reopen-cmd takes NO placement option — its tab is the env one, so a stray --tab-id/--sub/group option there is a usage error (exit 2), not a silently ignored flag.

Sub-tab placement (--tab-id / --sub). run and new-tab take a third placement flavour besides the group options: --tab-id <n> opens the new tab as a SUB-TAB of the top-level tab with that id — a persistent sub for new-tab (rendered in the parent's horizontal mini-strip, persisted under the parent's subs in state.yml, own session id in the PTY env), an ephemeral auto-closing run sub for run (same blocking tee/stdin/exit-code contract, the tab just lives inside the parent). The placement options are mutually exclusive — --tab-id, --sub, --group-id, --group-name: two of them name contradictory destinations, so a conflict is a usage error (exit 2); --create-group-if-missing requires --group-name. --tab-id <n> must name a live TOP-LEVEL tab — unknown, a sub's own id, a grouped tab or an archived id is an operational failure (exit 1: new-tab answers HTTP 404, run gets an error= frame). A live-but-EPHEMERAL tab is a flavour-split refusal: toState never persists an ephemeral parent's subtree, so a PERSISTENT sub under one would be silently lost — new-tab --tab-id <ephemeral-id> answers HTTP 400 ("an ephemeral tab cannot host persistent sub tabs", exit 1) while run --tab-id <ephemeral-id> is fine (an ephemeral run sub rides along, nothing is persisted). --sub is pure sugar for the same placement resolved from AGENTIC_TERM_SESSION_ID (the env var is unset → exit 1, an operational failure — there is no parent outside a tab). CALLED FROM INSIDE A SUB, --sub spawns a SIBLING: the instance climbs the caller's id to its top-level ancestor (nesting is one level), so a program running in a sub means "in the tab I live in" — while an explicit --tab-id <sub-id> never climbs and stays refused. On the wire, new-tab sends tabId=<n> in the /new-tab key=value body (camelCase, like groupId; a set tabId means no group resolution at all, and subFromSession=1 rides along when the id came from --sub, enabling the ancestor climb) and run sends parentTabId=<n> + subFromSession=0|1 in the WS start frame (a set parentTabId normalizes the group keys away, mirroring the "groupId wins over groupName" cascade one level up; subFromSession=1 marks the --sub sugar and enables the ancestor climb).

Id-commands on sub ids. Tabs and sub-tabs share the ONE global tab-id space (a sub's session id is its own id; each sub's PTY env carries it), so every existing id-command works on a sub with no new syntax: close-tab <sub-id> kills the sub's shell (idempotent as always), rename-tab/set-icon/set-badge/set-reopen-cmd/clear-reopen-cmd mutate the sub itself (its mini-strip row relabels; set-reopen-cmd inside a sub registers the sub's own restart lease), archive-tab <sub-id> shelves the record into the PARENT's sub-archive (the "Unarchive (N)" source; an ephemeral run sub → exit 1, exactly like an ephemeral top-level tab), and get-current-group-id from inside a sub resolves the top-level ANCESTOR's group (by the model's invariants a parent is always an ungrouped top-level tab, so the answer is exit 3 today — the resolution is ancestor-correct either way).

run blocks: it tees the PTY output to the CLI's stdout, pipes the CLI's stdin live into the PTY (a run sub's stdin reaches its shell through the pane's unified tab|sub lookup — writeTabStdin resolves sub ids from the WebSocket threads), exits with the command's own exit code, and its tab is ephemeral (never in state.yml, never restored). new-tab opens a persistent tab and runs the command in its shell WITHOUT persisting it — a restart reopens a plain shell in the recorded cwd (for a bash tab the command rides the per-tab script the static hook sources; every other shell gets it as an immediate keystroke feed — the same shell-kind split as reopenCmd); the same holds for a persistent SUB (its lease is per-sub). archive-tab <id> shelves the live tab with that id: its shell is killed (a real close — the remote bridges detach), and its full state (title/reopenCmd/cwd/icon/badge) moves to the archive — the owning group's archive for a grouped tab, commonArchive for an ungrouped one (POST /archive-tab?sessionId=). Archiving a PARENT shelves its whole SUBTREE in the same operation: persistent subs and the parent's own sub-archive travel with the record (ephemeral subs die without a record). Restore is menus-only (no CLI restore command): the group header's "Unarchive (N)" submenu returns a grouped entry to the bottom of its group, the always-visible unarchive (N) row at the top of the strip returns an ungrouped entry to the end of the strip — a restored parent brings its subs back with it (mini-strip rows, own PTYs, the tab opens on main); a tab's own shelved subs restore via the "Unarchive (N)" item on its strip row or its [main] row. The restored tab keeps its "Terminal N" number, cwd and decorations, and its recorded reopenCmd is delivered through the same clear-at-open lease as a restart restore. Exit codes: 0 ok; 1 no instance, unknown id, or an ephemeral run tab/sub (never archivable); 2 not exactly one numeric id. Unlike close-tab, archive-tab is NOT idempotent — close-tab's unknown-id is a no-op success (closed=0, exit 0), while /archive-tab answers 404 (the /rename-tab pattern) so a failed archive is reported instead of silently swallowed. set-icon / set-badge / clear-icon / clear-badge are HTTP POSTs to the running instance (like set-reopen-cmd), addressed via AGENTIC_TERM_SESSION_ID: the set-* commands take exactly one printable Unicode code point as their sole arg (the args are joined by a single space; a surrogate pair like 🔴 is one code point — multi-char input, an empty arg, or an ISO control char such as \n/\t is a usage error, exit 2) — or the equivalent ASCII hex token U+XXXX (case-insensitive, exactly 4–6 hex digits, decoded by the CLI via parseSymbolToken before the POST; the token exists because the Windows JVM launcher — java.exe AND the jpackage EXE alike — converts argv to the system ANSI codepage with best-fit, so non-ASCII CLI args are lossy: ●→'?', ○→'0' via cp1251), POST it as the raw UTF-8 body, and exit 1 on an unknown tab (404). The icon renders before the title and persists like title (in a sub: before the title in the mini-strip row); the badge renders after the title and persists too, but auto-clears on the first user interaction with that terminal (a mouse-click or any keystroke in its panel) — the clear is persisted, so the badge does not reappear after a restart, and merely switching to the tab does not clear it. The running instance is found via the .port file (port for HTTP commands, wsPort for run); tab-bound commands identify their tab via the AGENTIC_TERM_SESSION_ID env var each tab (and each sub) gets. Via Gradle: ./gradlew run --args="set-reopen-cmd --cwd C:\work claude -r my-session". Exit codes: 0 ok, 1 operational failure (no instance / server error / unknown tab), 2 usage/parse error, 3 get-current-group-id on an ungrouped tab.