Skip to content

How it fits together ​

devctl is one product with four faces on the same supervisor.

The same supervisor drives every surface — here the loopback web console showing services, profiles, live proxy requests, and recent errors together:

The devctl web console overview — service health, KPI tiles, profiles, proxy requests, and recent errors, all from one 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, rpc-token, and on Unix devctl.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.

Web ​

An opt-in loopback console on the same supervisor (services, traces, logs, LLM inspector, graph). Off by default. devctl web start prints a URL with a session token that is reused across listener restarts until devctl web rotate (7-day remint only when regenerate_session_tokens is on); devctl status shows the listen address without it. See Telemetry.

Configuration vs preferences ​

WhatWhere
Services, profiles, HTTP recipes, proxy, Google project.devctl/config.yaml and modular YAML
Machine overlay (gitignored).devctl/config.local.yaml and ~/.devctl/config.local.yaml
Session overlay (optional, committable).devctl/overlays/<name>.yaml via devctl start --overlay
TUI theme, keys, MCP listen, web appearance~/.devctl/tui.json plus per-checkout ~/.devctl/state/<repoID>/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.

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