CLI
The CLI and the TUI share one supervisor. Global flag: --config <path> (file or .devctl directory).
devctl # TUI (attaches to a daemon, spawning one if none is running)
devctl version
devctl start [svc…] [--profile] [--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 down [--repo <path>] [--keep-services]
devctl status [--repo <path>] [--json] [--watch]
devctl config import compose <file> [--write]
devctl logs [svc…] [--level] [--search] [--regex] [--source] [--since] [--until] [--trace] [--attribute key=value] [--output] [--json] [-f|--follow] [--all]
devctl logs export --output FILE
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] [--json]
devctl config validate|show|diff [--json]
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 that profile’s members (plus dependencies).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).downstops the daemon's services and the daemon itself;--keep-servicesstops only the daemon, leaving services running to be adopted later.--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.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 and MCP listen lines when a supervisor is up.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.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.
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) |