TUI
The TUI is OpenTUI (@opentui/core + @opentui/react). It attaches to the supervisor and never owns child processes.
cd examples/demo-platform
npx @amr-m-abdelgawad/devctl@latestFrom a source checkout (bun on PATH):
cd examples/demo-platform
bun run ../../app/src/bin.tsIf a supervisor session already exists, the TUI attaches to it. Preferences: tui.json / DEVCTL_TUI_CONFIG — see Building from source.
First run
With no .devctl configuration the TUI opens setup: “No configuration found. Would you like to run setup? [Enter] Setup [Esc] Exit”. Enter starts the same 9-step wizard as devctl setup (OpenTUI fields, then write and attach the daemon — no process restart). Invalid existing YAML still refuses overwrite.
If a .devctl/config.yaml exists but fails to parse or validate, the TUI shows Configuration error with the actual error instead — pressing Enter here does not run setup, since that would silently overwrite the file the error is about. Fix the file and restart devctl, or run devctl config validate for the same error from the CLI.
When services exist but none are running, the dashboard empty state:
enterstarts the default profile (first profile name alphabetically) after a plan overlayn/xstart or stop the highlighted row (or the space-selected set)- a lifecycle panel shows start and stop waves; later start waves wait for health and do not run if a wave fails
- the panel stays open until
escso you can read the result opicks a profile, then confirms start
Quit
q / /exit / esc twice:
shutdown.stop_services_on_exit | Behavior |
|---|---|
true | Stop managed services and leave |
false | Detach immediately |
| unset | Confirm: enter stops services, d detaches (daemon stays), k stops the daemon and leaves services (devctl down --keep-services), esc stays |
Detach (d) leaves the supervisor running. /down --keep-services (or quit k) stops the supervisor and persists PIDs so a later start can adopt them. /down stops services and the supervisor. /stop only stops selected services.
Interaction model
Keyboard-first. Chords use command on macOS and ctrl on Linux and Windows. Help, the status bar, and empty-state hints label the modifier for the OS you are on.
| Input | What it does |
|---|---|
/ | Command overlay — ranked as you type (name, alias, fuzzy, then description). /start api still lists start. ↑/↓ to move, enter to run |
command+p / ctrl+p | Same command overlay as / |
command+x / ctrl+x | Leader key (2s), then a shortcut — keymap overlay |
? | Grouped help — j/k scroll when the list is taller than the terminal |
tab / shift+tab / 1–4 | Cycle or jump the four nav tabs. Other screens are /auth, /credentials, /doctor, /config, /profiles, /setup, /stats, /settings, /mcp. On a secondary screen, tab returns to the dashboard. When the strip is wider than the terminal it slides (‹ ›). |
s l a p d c u | Direct letter nav when no overlay owns keys (services, logs, identity, proxy, doctor, config, setup) |
r | Refresh snapshot (doctor r re-runs checks) |
R | Restart selected services |
j k / arrows | Move selection |
enter | Start (empty dashboard) or open service detail |
space | Multi-select a service |
esc | Back / close overlay. Twice (when nothing else is open) asks to quit |
f | Focus log search (g jumps to latest). Remap with keybinds.search |
z | Expand logs to fill the terminal. z or esc exits |
w | Cycle log wrap: wrap every line (default), clip with ellipsis, or unwrap only the selected row |
command+c / ctrl+c | Copy the highlighted selection (drag with the mouse). Remap with keybinds.copy |
command+= / ctrl+= (and - / 0) | Display size (padding/row height, not the terminal font) |
esc esc | Twice to quit when no overlay or back target is open. The OS copy chord does not quit |
| Mouse | Click nav, click a service, scroll logs (toggle in Settings) |
The status bar only lists keys that work on the current screen. There is no idle command row — / and the OS palette chord open the command overlay.
Nav tabs (4)
- dashboard · 2. services · 3. logs · 4. proxy
Everything else is a slash command (or a letter jump): /auth, /credentials, /doctor, /config, /profiles, /setup, /stats, /settings. MCP is /mcp, /agent, or Settings → MCP → Settings page.
Screens
- Dashboard — services, proxy, live log tail. Identity lives on
/auth; ADC status is in the header. When nothing is running, a last session panel shows leftover PIDs from the previous supervisor (same datadevctl statusprints when the socket is down) - Services — list plus a live inspector: status chips, two-column facts, then a scrollable resolved env pane (dotenv, profile, secrets, plugins, runtime ports). Narrow terminals stack the panes.
enteropens the full detail screen - Service detail — same inspector; env pane is focused so
j/kscroll./revealshows secrets.n/x/R/l - Logs — ANSI color codes are stripped so wrap uses visible width; messages wrap to the pane with OpenTUI word wrap.
wcycles wrap all / clip / wrap selected.\\//splitopens a second pane on the same live stream (independent service filter, shared search)./trace <id>or Enter on a log details request id jumps search to that id. See Logs - Identity — user, project, source, ADC, gcloud, configured SAs, impersonation AVAILABLE/UNAVAILABLE, IAP (no tokens).
/auth loginsuspends the TUI, runsgcloud auth application-default loginon the real terminal, then restores the TUI./auth logoutrevokes ADC without leaving the screen - Credentials — store backend and entry names only. Tokens stay in the OS keychain or
~/.devctl/credentials - Proxy — status + routes (match and upstream wrap instead of clipping); click a route for full details.
nstart /xstop - Doctor — re-runs on every visit; ✓ / ! / ✗ with hints.
enteron a busy host port asks to stop that process; it never offers to kill the Docker or Podman daemon.rreruns - Config — merged view including tasks.
v//bufferopens a validate/save overlay oncfg.configPath(invalid YAML is not written;escdiscards).e//editstill opens$EDITOR/DEVCTL_EDITOR./diffshows provenance (devctl config diff)./reloadre-reads after an external edit - Profiles — members;
enterselects and offers start - Setup — onboarding checklist. First-run with no config still opens here
- Settings — grouped prefs: theme, display size, mouse, leader timeout, MCP settings page, about, reset.
←/→writes the highlighted cycle or toggles mouse. Reset asks before restoring defaults. Saves to~/.devctl/tui.jsonunlessDEVCTL_TUI_CONFIGis set - MCP — Listen
[ ON ]/[ OFF ], port stepper‹ N ›, per-agent Copy JSON / Copy TOML, and a Tools list grouped by purpose (inspect, logs, diagnostics, control, setup) with each tool markedreadorwrite;spaceenables or disables the highlighted one, all on by default. Off by default. See MCP
/reveal toggles secret env values for this session only. The header shows secrets shown.
Slash commands
/start [service…] start selection, args, or the current profile
/stop [service…]
/down [--keep-services] stop the supervisor (and services unless --keep-services)
/restart [--cascade|-c] named services only; cascade also restarts dependents
R with dependents: Enter = named, c = cascade
/run [task] one-off task; empty /run opens a picker. Output is in Logs under task:<name>
/exec [service] -- <command…>
empty /exec opens a service picker, then type the command
/exec <service> --print-env [--reveal]
resolved env (dotenv, profile, secrets, plugins, ports), not config-only vars
/logs /services /auth /credentials /proxy /mcp /doctor /config /profiles /setup
/stats system and service statistics (sparklines when the supervisor has samples)
/split second log pane (`\\`); `|` focuses the other pane
/trace <id> set log search to a request_id / trace_id
/dashboard return home
/themes [name] picker with live preview; enter saves to ~/.devctl/tui.json
/settings theme, mouse, display size, MCP page
/filter toggle ERROR+
/pause freeze the live log stream
/reveal show or hide secret env values
/wrap cycle log wrap (all / clip / selected)
/copy copy visible logs to the clipboard
/export [path] write filtered logs to ~/.devctl/exports (or the given path)
/exports open the export folder
/regex /since /until /history /edit /buffer
/clear
/refresh
/reload reload .devctl
/diff winning config sources and what they shadowed (`devctl config diff`)
/import compose [path] [--write]
/daemon supervisor bootstrap stderr (`devctl daemon logs`)
/auth login|logout|refresh
/update install a newer GitHub Release when the method is known (npm/Homebrew)
/version current version, then the same update check (does not install)
/exit /quit /qAliases include /up, /identity, /creds, /agent, /init, /home, /prefs, /task, /provenance, /bootstrap. /down is no longer an alias of /stop.
Leader key
Default leader is command+x on macOS and ctrl+x elsewhere (2 second timeout). Then:
n start x stop R restart (c cascade if dependents) s services l logs
a auth p proxy d doctor c config o profiles
t themes e export r refresh i setup h dashboard
q quit z fullscreenOverride in tui.json (keybinds) or DEVCTL_TUI_CONFIG.
Layout
- Header — product + version as text, then project and profile; chips only for running count, live proxy, MCP when on, ADC, and secrets-shown
- Nav — the four primary tabs; the active tab is highlighted, not filled
- Body — dashboard or a focused screen
- Command overlay —
/andcommand+p/ctrl+popen the same grouped list with a real OpenTUI input - Status bar — live/paused, last human result, contextual keys
Status is never color-only: ✓ healthy, ● running, ! warning, ✗ failed, ○ stopped.
Themes
/themes opens a picker with live preview. Built-ins:
- Product:
devctl(default),ember - Common dark:
tokyonight,catppuccin,nord,gruvbox,kanagawa,dracula,onedark,monokai,rose-pine,everforest,github-dark,iceberg,ayu-dark,oxocarbon,night-owl - Light:
catppuccin-latte,solarized-light - Other:
solarized-dark,terminal(black + VGA ANSI chrome; aliasesansi,xterm,console),system(follows macOSAppleInterfaceStyle/COLORFGBG; light uses Solarized Light)
Aliases: mocha → Catppuccin Mocha, latte, one-dark, solarized (dark), rosepine, github, ayu, night owl.
MCP agent chips use brand colors (Claude terracotta, Cursor blue, Kilo gold, Codex green) with light/dark variants.