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.
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.

Start, stop, status

startwith--profilestarts exactly that profile’s members. Omitted dependencies are not spawned; point members at deployed backends withenvironments/service_environment(see Profiles).start --overlay <name>merges.devctl/overlays/<name>.yamlafterconfig.local.yaml(same keys, presence-aware). The name is sticky for the session like--profile— omit it on later starts to keep it. Recorded instate.jsonasconfig_overlay. A missing file fails withoverlay "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.startwith 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.startalways ensures a daemon and leaves it (and its services) running after the command exits — that is not conditional on any flag.--detachis deprecated: it predates that always-on daemon and no longer changes behavior. Passing it prints a warning on stderr; it does nothing else.startexits 5 when a requested service fails to spawn, 6 when it starts but never becomes healthy.stopwith 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.restarttouches only the named services, not their dependents;--cascadealso restarts those dependents (the same setstopwould affect). Either way, start still expands the named services' own dependencies if they aren't already running.runstarts the task's declared service dependencies, executes the transient command once with the resolved project/task environment, and returns its exit status.--jsonincludes captured stdout and stderr. The TUI equivalent is/run <task>(output lands in Logs undertask:<name>).execruns once in a service's resolved environment and working directory, even when that service is stopped.--print-envprints the exact environment without running a command; secret-like values are redacted unless--revealis explicitly supplied. The TUI equivalents are/exec <service> -- <command…>and/exec <service> --print-env(the env inspector shows the same resolved map, not config-onlyvars/defaults).envlists per-service named overlays (services.<name>.environments) and the one selected for this session.devctl env invoices-apishows that service;devctl env invoices-api deployedselectsdeployedfor invoices-api only. Other services are unchanged. A running process keeps the overlay it started with until you restart it.downstops the daemon's services and the daemon itself;--keep-servicesstops 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),downexits 1 and says the supervisor is still stopping; the daemon goes on, and runningdownagain waits for it.--repotargets a repository directly, without needing a loadable configuration there; the global--configalso resolves it (by file location, not by parsing) when--repois not given.start --waitblocks 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--timeoutruns out, naming the services still not ready.testruns a task or a command against a throwaway named instance, writes a redacted bundle to--artifactson failure, tears the stack down, and exits with the command's code.bundlewrites the same evidence on demand. See Tests and CI.instanceslists the stacks holding a port slot for parallel stacks: slot, port offset, checkout path, instance name, proxy/web/OTLP ports, and status (running,stopped, ormissingfor a deleted checkout). A stack isrunningwhile a live process holds its daemon lock, also when the daemon is slow to answer.instances prunestops the stacks of missing checkouts and frees their slots. A fulldownalso frees the checkout's slot;down --keep-serviceskeeps it.statusanddownresolve their target the same way:--repowins 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.statuswith no socket prints persisted per-repo state (or “stopped”) and exits 0.statusalso prints proxy, MCP, and WEB listen lines when a supervisor is up. Each service row includesENV(the selected named overlay, empty when the service has none).status --watchreprints 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-idfilters byX-Devctl-Request-ID;--dedupe-request-idcollapses nearby events that share that id after the page is fetched;--hide-grpcomits gRPC proxy logs (and records markedrpc.system=grpc) from the page, the follow stream, and export;--traceprints the span tree plus correlated logs.devctl llmlists recent LLM calls from configuredllm.sources(LiteLLM spend logs first).--callerfilters by originating service.--followpolls until interrupted.devctl llm show <id>prints one call including redacted bodies. See LLM inspector.devctl trafficlists recent HTTP and gRPC hops captured oninspect.enabledproxy routes.--callerfilters by originating service (-ornonefor hops with no caller).--followpolls 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 whenstart/attachreports "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.-ffollows it live the same waylogs -fdoes. 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).
# 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
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | general error |
| 2 | configuration (also devctl doctor when any check is not ok) |
| 3 | authentication |
| 4 | authorization |
| 5 | service startup |
| 6 | health check |
| 7 | proxy (proxy start with no listen port, or bind failed) |