Skip to content

CLI

The CLI and the TUI share one supervisor. Global flag: --config <path> (file or .devctl directory).

text
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

  • start with --profile starts that profile’s members (plus dependencies).
  • 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).
  • down stops the daemon's services and the daemon itself; --keep-services stops only the daemon, leaving services running to be adopted later. --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.
  • 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 and MCP listen lines when a supervisor is up.
  • 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.
  • 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.

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.