CLI options
How the unified CAFleet CLI (cafleet) accepts configuration parameters. This
page catalogs the flags, conventions, and error strings.
Subcommand summary
One row per subcommand. "Identity flags" are the per-subcommand options naming
members: --member-id names the member in question on single-member
commands, and two-party commands name both parties as --from-member-id
(sender) + --to-member-id (recipient/target). In the --fleet-id column,
yes means the flag is a required per-subcommand option; no means the
subcommand rejects it with the parser's unknown-argument error.
Option Source Matrix
Each parameter has exactly one input source:
Environment variables
Every CAFLEET_-prefixed variable cafleet reads:
A flag wins over its environment variable, and the environment variable wins
over the hardcoded default. member.description truncation uses a separate
hard-coded 60-codepoint limit.
Global Options
--version, placed before the subcommand, prints cafleet <version> and
exits 0, bypassing the --fleet-id requirement.
Output shapes
One row per subcommand. --full is the global "give me every field cafleet
has, untruncated, unfiltered" escape hatch; an em-dash marks a subcommand that
does not accept it.
setup, server, and monitor start are absent by design — they stream
progress or run a loop rather than emitting a one-shot payload — so the
Subcommand summary legitimately carries three more rows
than this table.
--quiet semantics
--quiet suppresses the normal output and prints one bare value, for shell
capture. It is a text-only shortcut, ignored in the JSON branch.
JSON output (--json)
--json is a shared per-subcommand flag, placed after the subcommand name —
canonically trailing, after all other flags:
It switches the output to compact single-line JSON; non-ASCII (like the …
truncation suffix) is emitted as UTF-8, not escaped. --json and --full
are independent and composable — truncation is applied to the result before
the json-vs-text fork. --quiet is a text-only shortcut, ignored in the JSON
branch. The trailing position keeps JSON invocations inside the existing
per-subcommand allow patterns (see
permissions.allow coverage).
Subcommands accepting --json, one row per subcommand:
All other subcommands reject --json with the parser's unknown-argument
error (exit 2) — including the root group itself, so a
pre-subcommand cafleet --json <grp> <cmd> does not parse.
Fleet ID (--fleet-id)
--fleet-id is a per-subcommand option, typed int, placed immediately
after the subcommand name (the canonical position). It is required with no
environment default: a spawned member reads its fleet id from the FLEET ID:
line rendered into its spawn prompt and passes it as a literal flag on every
command. Members pass the literal integer — never a shell variable — because
Claude Code's permissions.allow matches Bash invocations as literal command
strings, and matching also depends on the canonical flag order (see
permissions.allow coverage).
Member ID (--member-id)
--member-id is a per-subcommand option typed int (as are
--from-member-id, --to-member-id, and --message-id; each rejects a
non-integer with the parser's invalid-integer error, exit 2). Ids are
DB-assigned integers, typically 1–4 digits, pasted in full — there is no
prefix resolution. --member-id always names the member in question: the
requester on message poll / ack / show, and the target on
member delete / show / prompt / ping and monitor capture.
Sender and recipient (--from-member-id, --to-member-id)
Two-party commands name both parties: --from-member-id is the sender
(message send, message broadcast) and --to-member-id is
the recipient (message send). A pane-touching target
must be an active member of --fleet-id with a placement row — see
Member targeting and key delivery.
permissions.allow coverage
The allow set is generated mechanically, one Bash(...) pattern per
allow-listed subcommand:
- One pattern per subcommand, matching the canonical
--fleet-id-first flag order —Bash(cafleet <grp> <cmd> --fleet-id *). A different flag order does not match and prompts. Trailing flags such as--jsonare covered by the same pattern. member promptis excluded so it stays underpermissions.ask— its positional text body is operator-controlled, in both the plain and the--shellform.
Apply the patterns to your user-level ~/.claude/settings.json manually; the
repo does not ship a committed permissions block.
Message Body Truncation
The four subcommands that emit a user-supplied delivery body —
cafleet message {send,poll,ack,show} — truncate the text body to
the first CAFLEET_MAX_TEXT_LEN Unicode codepoints plus a single …
(U+2026) suffix by default, uniformly in text and --json output. Length is
measured in Unicode codepoints, never bytes. Pass --full (a documented
per-subcommand option) to disable truncation; it composes orthogonally with
--json.
The limit is CAFLEET_MAX_TEXT_LEN — see
Environment variables.
This applies to CLI emit sites only — the WebUI /api/* responses
(webui-api.md) and monitor capture content are untouched.
cafleet setup — Onboarding and Schema Management
cafleet setup is a plain command — the single onboarding and
schema-management entry point (the recommended end-user path — see
Quickstart). Command help: Migrate the database schema and install the coding-agent assets (skills and presets). It takes no
positional arguments — cafleet setup <word> fails with the parser's
unexpected-argument error — and does not accept
--fleet-id.
The one flag is --skip AGENT: optional and repeatable, a choice over
claude / codex / opencode, with duplicates deduplicated
and an unknown value failing with the parser's invalid-value error
(exit 2). Its help text is
Skip the named agent's assets install (repeatable).
The command runs two halves, in order:
-
db half — initializes or migrates the registry database to the head of the migration chain embedded in the binary (idempotent). Each refusal message below becomes the db-half failure
<msg>;<M>/<N>are the integer migration versions: -
assets half — targets are the fixed list
claude,codex,opencode(in that order) minus the skipped agents. Installs, from the data embedded in the binary at build time with no network access, per target the three skill directories plus the agent's bundled preset where one exists (creating the agent's directories as needed), and records oneasset_installsrow per target (see Assets half). When all three agents are skipped, the half is skipped entirely: the command echoesassets half skipped (all agents skipped)and the half counts as not-run.
The halves fail independently (db half failed: <msg> / assets half failed: <msg>); if any half that ran failed, the command exits 1 with the failed
halves joined by ' and '.
The assets-half pre-flight fires only after a db-half failure or an externally broken schema, since the db half always runs first within the same command; it is kept as defense.
Schema-only invocation
The documented invocation for "bring the DB to head without touching assets" (the contributor and CI path):
It is deterministic (independent of which agent homes exist) and exits 0 when
the db half succeeds. The schema-only invocation never records
asset_installs rows.
Assets half
Each agent's preset, where one exists, is a static file embedded in the binary next to the skills:
Per target:
- The three skill directories are delete-and-reinstalled into the agent's
skills dir; a failure aborts with
failed to install skills into <skills_dir>: <error>. - For agents with a bundled preset (codex, opencode), the preset is installed
to its target, overwriting whatever exists there — a regular file, a
directory, or a symlink. A filesystem error aborts with
failed to install preset into <target>: <error>. - The agent's
asset_installsrow is upserted only after both its skills and its preset install successfully — the row attests skills + preset — then the command echoes (the preset line appears for codex and opencode targets only):
An install failure aborts the loop; rows recorded before the failure remain.
Stale-assets guard
Every fleet-scoped command group — fleet, member, message, and
monitor — validates the recorded assets installs before any subcommand body
runs:
Agents with no recorded row are not checked. Three surfaces are exempt:
--help renders at parse time and exits before any command body runs, so
neither group-level help (cafleet fleet --help) nor subcommand help
(cafleet fleet create --help) triggers the guard — both always print help,
even under a missing or stale install.
cafleet fleet — Fleet Management
Fleet lifecycle; writes directly to SQLite — no server required.
fleet create
Omitting --coding-agent exits 2 with the parser's native
missing-required-argument error naming --coding-agent.
Must be run inside a tmux or herdr session — outside one it exits 1 with
Error: cafleet fleet create must be run inside a tmux or herdr session and
writes nothing. It creates the fleet and its root Director (hardcoded
name="Director"; there is no --description flag)
atomically — see data-model.md. Output shapes are in
Output shapes.
fleet list
The only flag is the optional shared --json.
Lists all non-soft-deleted fleets. Each row exposes director_member_id so
the Director's id can be recovered after fleet create output scrolls away.
fleet show
Exits 1 with Error: fleet 'X' not found. if the row does not exist.
Intentionally returns soft-deleted rows, so audit info stays reachable.
fleet delete
The only flag is --fleet-id (required), naming the fleet to delete.
Soft-deletes the fleet in one transaction: stamps deleted_at, deregisters
every active member (root Director included), and removes their placement rows;
messages are untouched. It is idempotent (Deregistered 0 members. on
re-run). Unknown fleet_id
exits 1 with Error: fleet 'X' not found.. Member panes are not closed —
run cafleet member delete per member first for a clean teardown.
cafleet doctor — Placement Diagnostics
Resolves the active multiplexer backend via resolve_multiplexer()
(Multiplexer backends), prints the
resolved backend and the calling pane's session/window/pane identifiers, then
the assets-install report (the runtime CLI version and every recorded
asset_installs row, each marked ok or STALE). doctor is exempt from
the stale-assets guard — a stale or missing install is
reported, not fatal.
The only flag is the optional --json, a trailing
per-subcommand flag.
Exit 1 on any multiplexer or environment failure (no supported multiplexer
detected, ambiguous environment, binary not on PATH, pane not discoverable).
cafleet server — Admin WebUI Server
Starts the admin WebUI app under the built-in HTTP server (single process, no
auto-reload, no worker or log-level flags). CLI commands do not require this
server. Does not accept --fleet-id.
Flag wins over env var; env var wins over the hardcoded default. The WebUI assets are embedded in the binary at build time, so the served SPA always matches the binary; port-in-use errors propagate unwrapped.
cafleet message — Message Broker
All five subcommands require --fleet-id, name the acting member
(--from-member-id on send / broadcast, --member-id on poll / ack /
show), and run behind the
stale-assets guard.
The envelope schema is canonical in
Message envelope; truncation and --full are
canonical above; per-subcommand output shapes are
in Output shapes.
message send
message broadcast
delivered=<k> counts the best-effort inline previews that landed.
message poll
Returns only un-acked (input_required) deliveries addressed to the member.
message ack
message show
cafleet member — Member Lifecycle + Pane Interaction
The cafleet member subgroup owns the member lifecycle: create registers a
member and spawns its coding-agent pane; delete tears it down; prompt
/ ping keystroke an existing member's pane;
show and list are registry reads (no multiplexer requirement). All run
behind the stale-assets guard.
Member targeting and key delivery
Resolution shared by the --member-id verbs (including monitor capture),
by target state:
Any active in-fleet member (the root Director included) is a valid target;
there is no caller-auth check beyond fleet membership. Key sequences are
delivered literally (send-keys with shell=False) — shell meta, key
names, and multi-byte characters all arrive as plain characters.
member create
Register a member and spawn its coding-agent pane. It takes no identity
flag: the acting Director is auto-resolved from fleets.director_member_id
first thing, before registration (the resolved id also feeds the member's
backend inheritance and the spawn-prompt substitution). A fleet has exactly
one root Director by construction, so no override flag exists.
The monitoring member's role and lifecycle are covered in Monitoring.
Spawn command per backend
The per-backend spawn argv and auto-approval flags live in Coding-agent backends.
Spawn-prompt substitution
cafleet member create runs str.format over the resolved prompt body,
substituting exactly four placeholders:
Identity reaches the spawned member as literals rendered into its prompt; the
only environment variable forwarded into the pane is CAFLEET_DATABASE_URL.
A literal brace must be doubled ({{ / }}); an unknown placeholder or
malformed brace expression exits 2 and rolls back the just-registered member
(see Error Messages).
The spawn always creates the pane without stealing focus (tmux
split-window -d): the Director's pane and active window stay active. In the
default output, pane renders (pending) until the pane id is patched onto
the placement.
member delete
The only flag is --member-id (required), naming the member to delete.
Tears down the target's pane (when one exists) and soft-deletes the member.
Targeting the root Director is blocked (see
Error Messages). A placementless or pending-placement
delete is a pure registry soft-delete and succeeds outside a multiplexer.
The pane path kills the pane immediately (tolerating an already-gone pane),
then soft-deletes; exit 0. Pane status renders (no placement) for a
placementless target and (pending — no pane) for a pending placement.
member show
Registry read — no multiplexer requirement. In the --full block, the
placement sub-block renders placement: none when the member is
placementless, and None fields render -. kind is one of director,
monitor, or member.
member list
No flags beyond --fleet-id and the shared trailing --json
flag; no identity flag. Lists every active registry entry of the fleet —
the root Director, the monitoring member, ordinary members, and placementless
rows. An empty roster prints 0 members..
idle is the wall-time since the member's most recent message activity — the
latest of last_sent (most recent outgoing message) and last_recv (most
recent delivery), broadcast summaries excluded — humanized as Ns / Nm /
Nh in text mode. last_ack is the most recent acknowledged delivery.
Per-member detail such as description and registered_at lives on
member show.
member prompt
Director-only keystroke primitive with two forms. The plain form keystrokes
TEXT into a member's pane as a submitted user turn — for text that only
takes effect when it arrives as a direct user turn (slash commands, skill
invocations, and other magic commands a broker message body cannot trigger).
The --shell form keystrokes ! TEXT so the coding agent's ! shortcut runs
the command natively — honored by all three backends; it is the dispatch half
of the cafleet skill's bash-via-Director fallback protocol. Broker messaging
remains the canonical
coordination channel; the plain form is not a substitute for message send.
Shell metacharacters — pipes, &&, ;, $(...), and backticks — are
forwarded opaquely. The newline check runs first, against the original text.
The --shell flag controls both the payload prefix and the Esc safeguard:
In the plain form the trailing Enter submits a real user turn, and the
leading Esc (as in member ping and inline previews) keeps it from blindly
confirming a pending permission prompt; the submitted turn opens the member's
turn directly. The --shell form leads with no Esc because an Esc before
! <cmd> would mis-fire (see
Esc safeguard), and its bang output
only stages in the pane — the ping advances the member's turn to consume it.
The flag performs no content inspection: plain-form TEXT beginning with !
is delivered verbatim without the shell mechanics.
Output shapes are in Output shapes.
member ping
Re-pokes a member's inbox: keystrokes Esc → cafleet message poll --fleet-id <fleet-id> --member-id <member-id> → Enter into the target's pane
(the leading Esc is the permission-prompt safeguard — see
Push notifications). The manual
re-poke for a pane that missed the broker's automatic on-delivery
notification; the action is wholly fixed by the command — no
operator-controlled body — which is why member ping sits in
permissions.allow while member prompt stays in permissions.ask.
A pending placement (a placement row whose pane_id is not yet patched) takes
the skip path: no keystroke is sent and the command succeeds — the pending
member's inbox is intact and it polls it on spawn, so there is nothing a ping
would add. Exit code 0 on both success paths in every mode; the skipped
JSON key is present on both paths (stable schema).
A keystroke non-delivery, an unknown member, and a missing placement row all still exit 1.
cafleet monitor — Supervision Scheduler
The monitor group is exactly the monitoring toolkit: the scheduler loop and
its read primitive. Every monitor subcommand requires --fleet-id and runs
behind the stale-assets guard. The conceptual model is
canonical on the Monitoring concepts page; there
is no monitor stop — the loop terminates with the monitoring member's pane
(member delete), and a still-running loop self-terminates on its next tick
after fleet delete.
monitor start
The one flag is --tick (optional): the scan-tick cadence in seconds
(an integer ≥ 1, default 5). The tick is the floor on interval
precision — see
Monitoring.
Runs the loop in-process (the monitoring member launches it as a
background task in its own pane; the loop blocks the task and writes to its
stdout — one <iso-ts> due member <id> (<name>) [<reasons>] -> wake monitor
line per due member). On startup it runs the multiplexer precondition guard,
atomically claims the single-instance monitor_runtime row, installs
SIGTERM/SIGINT handlers (a clean stop clears the row), and — immediately
after the successful claim, before the first tick — prints the startup line
that backs the monitoring member's ready: monitor live handshake:
If the fleet has no monitoring member, it warns on stderr and runs anyway.
monitor capture
Output shapes are in Output shapes; target resolution is
shared with the member keystroke verbs — see
Member targeting and key delivery. A
pending placement is a hard error (see Error Messages).
Text output remains byte-identical. JSON stamps captured_at from the local UTC
clock at the capture read boundary and computes
content_sha256 = sha256(content.encode("utf-8")) from the exact emitted
content. --no-ansi hashes the ANSI-stripped, carriage-return-defragmented
string; --ansi hashes the ANSI-preserving string. No normalization occurs
after the selected mode, and capture content is never stored in SQLite.