subshell-server CLI
Every verb of the control plane binary (run, init, configure, status, update, backup, service, mcp, report), with the flags each one accepts and the exit code each one answers with.
subshell-server is the control plane's single binary: run without a subcommand it boots the server; run with one it is a management tool. This page mirrors the binary's own usage text: the flags below are exactly the flags it parses, and anything else is a usage error.
Usage
subshell-server: the Subshell control plane
usage:
subshell-server run the server (boot path: no subcommand)
subshell-server version print the version and exit
subshell-server license print the copyright and licence and exit
subshell-server status print the resolved config view and exit (--json for machine output)
subshell-server init first run: config home + auth secret + config.env + the service
subshell-server configure (re)write config.env; interactive unless --yes
subshell-server update install a newer server over this one (--check to look only)
subshell-server backup snapshot the database now (--json for machine output)
subshell-server service install background the server (systemd user unit / launchd agent);
--no-autostart to run it now but not at login
subshell-server service uninstall stop it and remove the service definition
subshell-server service enable start it at login (does not touch the running process)
subshell-server service disable stop starting it at login (does not touch the running process)
subshell-server service status what the service manager reports (--json for machine output)
subshell-server service start start the installed service
subshell-server service stop stop it (the definition stays installed)
subshell-server service restart restart it (--force to override the live-pane refusal)
subshell-server mcp serve the pane-spawned stdio MCP server (spawned by harnesses)
subshell-server report attention turn_complete|needs_attention
subshell-server report session report a pane's state (run by harness hooks, not by hand)
init/configure flags: --port <n> --host <h> --base-url <url> --db-path <path> --yes
--trusted-origins <origin,origin> other addresses browsers will
use (empty clears the list)
init-only flags: --service / --no-service install the background service,
or skip it (default: install)
update flags: --check report what is available and stop
--to <version> install this published version
--from <file> install a local file (no digest to check)
--force allow a downgrade, and override the pane refusal
--yes skip the confirmation
--json machine-readable output
--no-restart swap the binary; the caller restarts
--rollback undo the last update (binary + database)
config precedence: process env > config.env > .env > built-in defaultsValue flags also accept the --flag=value form, so a URL containing = still works.
The boot path is the no-subcommand form. A service unit or launchd job that names the binary with no subcommand (or with only a leading flag) boots the server. This is a pinned contract: never teach a bare invocation a new meaning. Every other word is a subcommand, and an unknown word is refused with the usage text above and exit 1.
Exit codes: status and service status always exit 0 when the flags were valid: they are views, and a script should not have to tell "not running" from "the call failed". report always exits 0 whatever happens, because harness hooks run it and their stderr lands in your session. mcp runs until its pane's client disconnects. Every other verb exits 0 on success and 1 on any refusal or failure, including usage errors, unlike the node CLI, which uses exit 2 for those.
init: first run
init is the headless entry point: it creates the config home (0700), bootstraps BETTER_AUTH_SECRET, runs the configure flow, asks one question about the background service, and ends by telling you where to create the admin account.
The secret ladder it writes: a non-empty BETTER_AUTH_SECRET already in config.env wins (running init twice never rotates it), else a non-empty value in the environment is adopted and persisted, else a fresh one is generated: 32 random bytes, base64url. init is idempotent; a cancelled service question is not a failed init (exit 0, config written, handoff printed).
Flags: the configure flags below, plus --service / --no-service (accepted by init alone: configure installs nothing, so the flag there is an unknown flag rather than a silent no-op). --yes or a non-TTY takes every default; the service question defaults to yes.
configure: (re)write config.env
Interactive unless you pass --yes. It owns five keys in config.env (SERVER_PORT, HOST, APP_BASE_URL, DATABASE_PATH, and optionally TRUSTED_ORIGINS) and preserves every other line of the file untouched. Defaults follow the file: pressing Enter keeps what is configured.
--port <n>,--host <h>,--base-url <url>,--db-path <path>set their key.--trusted-origins <origin,origin>sets the extra browser origins; an empty value is the only way to clear the list non-interactively.--yesanswers every question with its default and prints nothing but results.
configure will write a key the environment already sets: it consults only config.env, its flags and your answers. The environment still outranks the file at the next boot, so the write has no effect until the variable goes; the refuse-instead-of-mask guard is the dashboard's Service-page writer, which answers 409 for exactly this reason.
status: what would this boot with
Opens with the same subshell-server <version> line as version, then reports: the config.env path and whether it exists, each setting tagged with the layer it came from (process env, config.env, or built-in default), the auth secret only as set-or-missing (never its value), tmux presence, the resolved MCP entrypoint, the plugin registry URL, whether the port is already answering, the service definition on disk, and whether the first admin account exists yet. It opens the database read-only and never creates one. --json emits the same facts as machine-readable data.
update: replace this binary, reversibly
update installs a newer subshell-server over the one the service definition names (never a path by convention), and the swap is a transaction the new binary finishes at its next boot: migrations pass and the update is audited; migrations fail and the previous binary and the database snapshot come back. An old binary cannot boot a newer database, which is why every update takes a full database backup first (see Backups).
Flags:
--check: report what is available and stop.--to <version>: install that published version; only the newest published release is resolvable.--from <file>: install a local file instead of downloading. There is no digest to check on a file you name, so the binary must answerversionand report the version it claims.--force: allow a downgrade, and override the refusal to restart a service definition that would kill live panes.--yes: skip the confirmation.--json: machine-readable output.--no-restart: swap the binary and let the caller restart.--rollback: undo the last update by putting the previous binary back with its database snapshot. Takes only--yes,--forceand--jsonalongside it.
--to and --from name two different sources and cannot be combined. A host with an empty SUBSHELL_RELEASE_URL fetches nothing and refuses with a line pointing at --from. After the swap the command waits for the restarted binary to settle the transaction, up to sixty seconds.
backup
backup takes a single-file snapshot of the database now (VACUUM INTO), in the same directory and under the same retention as the ones update takes automatically, five by default (SUBSHELL_DB_BACKUPS_KEEP; 0 keeps everything). --json prints the path, size, and how many are kept. Exit 1 names the reason, the ordinary one being a configured server that has never booted and so has no database yet.
service: the background process
One definition, two shapes: a systemd user unit on Linux (~/.config/systemd/user/subshell-server.service) and a launchd agent on macOS (~/Library/LaunchAgents/dev.subshell.server.plist).
| Verb | What it does |
|---|---|
install | Write the definition and start the server. Refuses before writing anything without a config.env; run init first. --no-autostart runs it now but does not arm login start. On Linux it then asks logind whether your user lingers, and prints the loginctl enable-linger advice only when the answer is not yes. |
uninstall | Stop and remove the definition. Deliberately gates on nothing: a stranded unit must always be able to come down. |
enable / disable | Arm or disarm start-at-login without touching the running process. On macOS this moves the plist between ~/Library/LaunchAgents (starts at login) and the config home (runs only when loaded). |
status | What the manager reports: run state, pid, starts-at-login, whether a teardown keeps live panes, and where the manager's log is. --json for scripts. Always exits 0. |
start / stop | Drive an installed service; they never install one. stop warns (but proceeds) when the definition predates the pane-sparing directive and would take live panes down. |
restart | Restart the service. Refuses unless a teardown is known to keep live panes; pass --force to override. An unreadable definition fails closed the same way. |
Each local subshell's tmux server is a child of the service, so an old definition without KillMode=process (systemd) or AbandonProcessGroup=true (launchd) means a stop or restart kills every running pane. New installs write the sparing directive; service status tells you which kind you have.
mcp and report: internal verbs
mcp serves the stdio MCP server that a subshell pane's harness spawns (The subshell MCP server). It is the one long-running command; a server-only host resolves its MCP entrypoint through its own binary, which is why this subcommand exists on both products. report attention <kind> and report session are how harness hooks report a pane's state. Both are built from the pane's environment by the control plane, not typed by humans.
version and license
version prints subshell-server <version>, a machine contract, matched exactly by the release pipeline. license prints the copyright and licence terms, since the binary ships as a bare file with no LICENSE beside it.
See also
- subshell CLI: the node's own binary, similar shape, different exit-code convention.
- Environment Variables: what
config.envand the process environment can carry. - Files & Paths: every file these verbs create, with its permissions.
- Running as a Service: the same ground from the operator's side.
- Updating the server: the dashboard path and the transaction in full.
Last updated on
Contributing to These Docs
How to add or fix a documentation page: fork, branch, write MDX, run the content tests, changeset, and the tagged deploy that ships it.
subshell CLI
Every verb of the node CLI (setup, enroll, configure, run, service, maintenance, status, update, mcp, report), with its flags and its exit code.
