How it fits together
devctl is one product with four faces on the same supervisor.
Nothing in the application knows your services by name. The supervisor reads .devctl/, starts argv (or explicit shell) processes and optional Docker/Podman containers, injects resolved env, and reports health.
Supervisor
The supervisor is the long-lived process. It:
- Starts, stops, and restarts host processes and optional Docker/Podman containers in dependency waves
- Optionally starts the proxy and the MCP listener
- Ingests stdout/stderr, health, auth, and proxy events into one log buffer
- Persists session state under
~/.devctl/state/<repoID>/(state.json,devctl.lock, and on Unixdevctl.sock)
repoID is the first 16 hex characters of sha256(absolute repo root). Two checkouts get two state directories. A leftover ~/.devctl/sessions/<id>/ is migrated once.
devctl start always ensures a daemon and leaves it running after the command exits (--detach is deprecated and no longer changes that). devctl (no args) and devctl attach dial the session socket: devctl.sock on macOS/Linux, \\.\pipe\devctl-<repoID> on Windows. Attach never starts a supervisor; the default TUI may. devctl down stops the daemon (and, unless --keep-services, its services).
Override the home directory with DEVCTL_HOME (default ~/.devctl).
TUI
The TUI is an OpenTUI React app. It attaches to a supervisor and paints status, logs, identity, doctor, config, and settings. It does not own child processes. Closing the TUI can stop services or detach, depending on shutdown.stop_services_on_exit — see TUI.
Startup locates and attaches to an already-running daemon first, independent of whether the on-disk config still parses — an attached daemon's config_snapshot (its own last-known-good in-memory config) is always the effective config, never a local reparse. Local parsing only comes into play when no daemon is reachable: a valid config spawns a fresh one, a missing config opens setup, and anything else (invalid YAML, a schema violation) is a real boot error with nothing started. Once attached, a /reload (or an external edit picked up by the config-file watcher) refetches the snapshot on success; a failed reload leaves a banner up under the nav bar until the next one succeeds.
CLI
The CLI is the same controller over the same socket. Use it for scripts, CI, and one-shot status. See CLI.
MCP
Coding agents cannot keep a TUI child alive, so MCP is a localhost Streamable HTTP server on the supervisor. Default off. See MCP.
Configuration vs preferences
| What | Where |
|---|---|
| Services, profiles, proxy, Google project | .devctl/config.yaml and modular YAML |
| Machine overlay (gitignored) | .devctl/config.local.yaml and ~/.devctl/config.local.yaml |
| TUI theme, keys, MCP listen flag | ~/.devctl/tui.json (or DEVCTL_TUI_CONFIG) |
| Session / lock / socket | ~/.devctl/state/<repoID>/ |
| Persisted logs | ~/.devctl/logs/ |
| Log exports | ~/.devctl/exports/ |
| Credential files (if no keychain) | ~/.devctl/credentials/ (mode 0600) |
Typical loop
Local-only services (the demo platform) run without gcloud.