Skip to content

CLI ​

The CLI and the TUI share one supervisor. Global flags: --config <path> (file or .devctl directory) and --instance <name> (a named stack of this checkout, see parallel stacks; DEVCTL_INSTANCE sets it too). Both go before the command.

text
devctl                         # TUI (attaches to a daemon, spawning one if none is running)
devctl version
devctl start [svc…] [--profile <name>] [--overlay <name>] [--env KEY=VAL] [--wait [--timeout 5m]] [--detach] [--json]
devctl stop [svc…] [--json]
devctl restart [svc…] [--cascade] [--json]
devctl run <task> [--json]
devctl exec <service> -- <command…>
devctl exec <service> --print-env [--reveal] [--json]
devctl env [service] [name] [--json]
devctl down [--repo <path>] [--keep-services]
devctl instances [--json]
devctl instances prune
devctl test [--profile <name> | --services a,b] [--timeout 5m] [--env-from <svc>] [--artifacts <dir>] [--keep] (<task> | -- <command…>)
devctl bundle [--since 10m] [--output <dir|file.tgz>]
devctl status [--repo <path>] [--json] [--watch]
devctl config import compose <file> [--write]
devctl logs [svc…] [--level] [--search] [--regex] [--source] [--since] [--until] [--trace] [--request-id] [--attribute key=value] [--dedupe-request-id] [--hide-grpc] [--output] [--json] [-f|--follow] [--all]
devctl logs export --output FILE
devctl llm [--source] [--caller] [--model] [--status] [--search] [--since] [--until] [--json] [-f|--follow]
devctl llm show <id> [--json]
devctl traffic [--route] [--caller] [--method] [--status] [--transport] [--search] [--since] [--until] [--json] [-f|--follow]
devctl traffic show <id> [--json]
devctl daemon logs [-f|--follow]
devctl reload
devctl doctor [--json]
devctl setup [--force]
devctl auth status|login|logout|refresh [--json]
devctl proxy status|start|stop
devctl mcp [--on|--off] [--port N] [--rotate] [--write claude|cursor|kilo] [--json]
devctl web status|start|stop|rotate [--print-url]
devctl config validate|show|diff [--json] [--overlay <name>]
devctl attach
devctl completion zsh|bash|fish
devctl update [--json] [--check]

_supervisor is an internal command. Do not invoke it by hand.

devctl --help lists every command, global flags, and exit codes

Start, stop, status ​

devctl status — per-service state and health, plus the proxy, MCP, and web listener addresses

  • start with --profile starts exactly that profile’s members. Omitted dependencies are not spawned; point members at deployed backends with environments / service_environment (see Profiles).
  • start --overlay <name> merges .devctl/overlays/<name>.yaml after config.local.yaml (same keys, presence-aware). The name is sticky for the session like --profile — omit it on later starts to keep it. Recorded in state.json as config_overlay. A missing file fails with overlay "X" not found: .devctl/overlays/X.yaml.
  • start --env KEY=VAL (repeatable) overlays those keys on this start’s launch environment for the services named on the command. A profile-only start with no names applies them to every service that start launches. Ephemeral: not written to YAML, state.json, or the stored client environment used for later automatic restarts.
  • start with no profile and no names uses the active session profile, then the first configured profile (alphabetically). With no profiles it errors instead of starting every service.
  • start always ensures a daemon and leaves it (and its services) running after the command exits — that is not conditional on any flag.
  • --detach is deprecated: it predates that always-on daemon and no longer changes behavior. Passing it prints a warning on stderr; it does nothing else.
  • start exits 5 when a requested service fails to spawn, 6 when it starts but never becomes healthy.
  • stop with no names stops every started service; naming one or more services also stops everything that (transitively) depends on them — never their dependencies, which other running services may still need. This is a breaking change: stop used to mirror start and pull in dependencies instead.
  • restart touches only the named services, not their dependents; --cascade also restarts those dependents (the same set stop would affect). Either way, start still expands the named services' own dependencies if they aren't already running.
  • run starts the task's declared service dependencies, executes the transient command once with the resolved project/task environment, and returns its exit status. --json includes captured stdout and stderr. The TUI equivalent is /run <task> (output lands in Logs under task:<name>).
  • exec runs once in a service's resolved environment and working directory, even when that service is stopped. --print-env prints the exact environment without running a command; secret-like values are redacted unless --reveal is explicitly supplied. The TUI equivalents are /exec <service> -- <command…> and /exec <service> --print-env (the env inspector shows the same resolved map, not config-only vars/defaults).
  • env lists per-service named overlays (services.<name>.environments) and the one selected for this session. devctl env invoices-api shows that service; devctl env invoices-api deployed selects deployed for invoices-api only. Other services are unchanged. A running process keeps the overlay it started with until you restart it.
  • down stops the daemon's services and the daemon itself; --keep-services stops only the daemon, leaving services running to be adopted later. It returns once the daemon has released its lock. If the daemon is still there after the shutdown grace plus two seconds (at least five), down exits 1 and says the supervisor is still stopping; the daemon goes on, and running down again waits for it. --repo targets a repository directly, without needing a loadable configuration there; the global --config also resolves it (by file location, not by parsing) when --repo is not given.
  • start --wait blocks until every service it started is ready (running, and healthy when it has a health check). It exits 5 if one fails and 6 when --timeout runs out, naming the services still not ready. test runs a task or a command against a throwaway named instance, writes a redacted bundle to --artifacts on failure, tears the stack down, and exits with the command's code. bundle writes the same evidence on demand. See Tests and CI.
  • instances lists the stacks holding a port slot for parallel stacks: slot, port offset, checkout path, instance name, proxy/web/OTLP ports, and status (running, stopped, or missing for a deleted checkout). A stack is running while a live process holds its daemon lock, also when the daemon is slow to answer. instances prune stops the stacks of missing checkouts and frees their slots. A full down also frees the checkout's slot; down --keep-services keeps it.
  • status and down resolve their target the same way: --repo wins outright, else the global --config (or plain discovery from the working directory) locates it by file, else a state-directory scan finds a still-live daemon whose original config is now gone.
  • status with no socket prints persisted per-repo state (or “stopped”) and exits 0.
  • status also prints proxy, MCP, and WEB listen lines when a supervisor is up. Each service row includes ENV (the selected named overlay, empty when the service has none).
  • status --watch reprints the same status every 2 seconds, each under its own timestamp header, until interrupted (ctrl+c).
  • logs -f (and the TUI's own live view) keeps printing new matching events until interrupted instead of exiting after the current page; see Logs for pagination and filtering details. --request-id filters by X-Devctl-Request-ID; --dedupe-request-id collapses nearby events that share that id after the page is fetched; --hide-grpc omits gRPC proxy logs (and records marked rpc.system=grpc) from the page, the follow stream, and export; --trace prints the span tree plus correlated logs.
  • devctl llm lists recent LLM calls from configured llm.sources (LiteLLM spend logs first). --caller filters by originating service. --follow polls until interrupted. devctl llm show <id> prints one call including redacted bodies. See LLM inspector.
  • devctl traffic lists recent HTTP and gRPC hops captured on inspect.enabled proxy routes. --caller filters by originating service (- or none for hops with no caller). --follow polls until interrupted. devctl traffic show <id> prints one hop including redacted bodies. Direct sockets that never hit the proxy are not listed. See Proxy.
  • devctl daemon logs [-f] prints the detached supervisor's own bootstrap stderr (its log location, before it has a config to start services from) — useful when start/attach reports "supervisor failed to start" and points at a path. Prints "no daemon bootstrap log yet" if the daemon has never been spawned for this repository. -f follows it live the same way logs -f does. The TUI equivalent is /daemon.

devctl attach dials an existing supervisor only. It does not start one. If nothing is listening, it errors with a hint to run devctl start first.

devctl reload re-reads .devctl/. Changing the plugins path list hot-applies token providers and proxy middleware; editing an already-imported plugin file still requires devctl down && devctl start. Opt-in service.watch (see Services) restarts that one service when listed paths change.

Setup ​

devctl setup honors --config (the repo/path to set up, not just the one to read). It never overwrites a configuration that already exists there — it prints the existing path and writes nothing. Pass --force to overwrite it instead; without a config on disk yet, setup runs its interactive wizard as usual.

RPC errors include { error, kind, hint } so the CLI maps kinds to the table below.

Completions ​

devctl completion zsh (or bash / fish) prints a script that calls devctl __complete for live values (profiles, service names, start / stop / logs).

bash
# zsh — add to ~/.zshrc
source <(devctl completion zsh)

# bash
source <(devctl completion bash)

# fish
devctl completion fish > ~/.config/fish/completions/devctl.fish

__complete is an internal helper. It loads configuration the same way the CLI does.

Configuration provenance ​

devctl config diff lists every explicitly configured effective value, the file and merge layer that won, and any earlier sources it shadowed. --json returns the same information as { "entries": [...] } for automation. The TUI equivalent is /diff (secret-like values stay redacted unless /reveal is on).

Update ​

devctl update checks the latest GitHub Release, reports which install channel this process is (npm, npx, Homebrew, GitHub Release binary, or source), and installs when that channel has a known package-manager command (npm global or Homebrew). --json and --check only report. After a successful install, restart a running daemon with devctl down then start again. /update in the TUI does the same install; /version still only checks. The TUI and web console also surface a notice when a newer release exists — dismiss for this version, or hide until the next session.

Web ​

devctl web status|start|stop|rotate controls the opt-in loopback console. web start prints the console origin only; the control token travels in the URL fragment (#token=), and web start --print-url prints the full access link when you need it. After the first authorized visit, the browser keeps the token. The saved token stays until web rotate, unless regenerate_session_tokens is on (then it remints after 7 days, same as MCP). devctl status prints a WEB line without the token. Off until web.enabled: true or you run web start. See Web console.

Exit codes ​

CodeMeaning
0success
1general error
2configuration (also devctl doctor when any check is not ok)
3authentication
4authorization
5service startup
6health check
7proxy (proxy start with no listen port, or bind failed)

Released under the MIT License. Use at your own risk.