Skip to content

Presentation ​

Four faces, one Controller / McpHost. None of them spawn user services.

CLI — presentation/cli/ ​

Commander tree in cli.ts newRoot(runtime, launchDaemon).

ModuleCommands
lifecycle.tsstart, stop, restart, run, exec, status, down, attach
logs.tslogs, hidden _supervisor is not here — addDaemon is session log helpers; _supervisor is addSupervisor in cli.ts
llm.tsllm inspector
traffic.tstraffic inspector
auth.tsauth login/logout/status/refresh
listeners.tsproxy, mcp
web.tsweb
config.tsconfig validate/show/diff, reload
setup.tssetup wizard (uses ClientRuntime + setup-starter)
complete.tsShell completion + hidden __complete
update.tsupdate
shared.ts--config flag, stdout EPIPE, JSON helpers

Offline (no daemon required): setup, doctor, config validate, completion, version. Everything that mutates a session opens a Controller.

Exit codes: shared/errors.ts exitCode() from DevctlError.kind. Uncaught errors in execute print humanMessage and exit.

TUI — presentation/tui/ ​

OpenTUI React (@opentui/react). Entry: index.tsx runTui → openTui → renderApp.

How to read the TUI (do not start in overlays) ​

Overlays are modals (slash palette, confirm, log details, theme picker). They are not the architecture. Behavior lives in hooks + Controller.

AreaPathRole
ShellApp.tsxScreen switch, wires hooks, renders chrome
Chromechrome.tsx, layout.tsx, density.tsxHeader, nav, notice bar, status bar
Screensscreens/*.tsxDashboard, Services, Logs, Auth, Proxy, Doctor, Config, Settings, MCP, LLM, Stats, Profiles, Setup, …
Overlaysoverlays/*.tsxSlash, Help, Plan, Confirm, ConfigEdit, SetupWizard, TraceView, …
Hookshooks/Data, commands, keys
Helpershelpers/Pure formatting / navigation (production imports these, not the old barrel except tests)
Keyskeymap.ts, commands.ts, tui-config.ts, settings.ts, themes.tsBindings and prefs
Workspaceworkspace.tsClientRuntime adapted for screens (plans, doctor)

Hook map:

HookOwns
use-daemon-events.tsSubscribe to bus; merge snapshot
use-lifecycle.tsStart/stop/restart/plan
use-log-view.tsFilter, window, follow, paging
use-llm-view.tsLLM inspector paging
use-traffic-view.tsProxy traffic inspector paging
use-diagnostics.tsDoctor
use-service-environment.tsEnv inspect (redacted)
use-config-editor.tsBuffer validate via validateConfigText before write
use-mcp-controls.tsMCP start/stop/tools
use-preferences.tsScope, theme, persist tui.json layers + allowlisted local YAML
use-notifications.tsGitHub update check; dismiss / later receipts
use-command-dispatcher.tsSlash / command catalog
use-setup-wizard.tsFirst-run
use-app-keyboard.tsTop-level keymap; delegates to keyboard-screens.ts / keyboard-overlays.ts

Boot: attach existing daemon first (even if local YAML is broken). If none: valid config spawns daemon; missing config → Setup screen; invalid YAML → boot error, nothing started.

helpers.ts is a compatibility barrel. New code should import helpers/<file>.ts directly.

MCP — presentation/mcp/ ​

Streamable HTTP, JSON-RPC 2.0, protocol 2025-03-26. Bind loopback only. Bearer token (shared/mcp-token.ts; saved until rotate, 7-day TTL only when regenerate_session_tokens is on). Peer must be loopback (domain/net/hosts.ts).

FileRole
server.tsHTTP + JSON-RPC methods (initialize, tools/list, tools/call, resources)
tools.tsTool table, callMcpTool, redaction, resources devctl://…
port.tsPort pick
docs-search.tsSearch embedded user docs
snippets.tsYAML snippets for setup
*.generated.tsSynced docs/skill text

Tools (names are the RPC contract; do not rename lightly):

Inspect: list_services, get_service, get_status, get_preferences, get_requests, get_llm_calls, get_llm_call, get_traffic_calls, get_traffic_call, list_profiles, get_config, get_config_sources

Logs: get_logs, get_log_stats, get_trace, trace_request, recent_errors

Diagnostics: run_doctor, search_docs, get_doc, validate_config

Control (mutating): start_services, stop_services, restart_services, set_service_environment, reload_config, set_preferences, run_task, start_proxy, stop_proxy

Setup: get_setup_guide

Default-off: exec_service (opt in via TUI mcp_enabled_tools). Deny-list: mcp_disabled_tools.

get_config is redacted. Full config is not an MCP resource.

Web UI — presentation/web/ + app/web/ ​

WebHttpServer serves the bundled SPA (assets.generated.ts) and JSON by reusing MCP tool functions (listServices, getLogs, getLogStats, control POSTs including set_service_environment). Same bearer and loopback rules. The token is disk-backed (web-token, kept until web_rotate, 7-day TTL only when regenerate_session_tokens is on) and the SPA persists it in localStorage after the first #token= visit. Author UI in app/web/ (pages: overview, logs, traces, graph, llm, traffic, doctor, identity, settings). Rebuild assets after UI changes.

Log HTTP: GET /api/logs (limit, direction, regex, cursor / prev_cursor in the response), GET /api/logs/stats, GET /api/logs/sessions, GET /api/logs/sessions/:id, GET /api/logs/export (NDJSON + Content-Disposition). GET /api/doctor is host.doctor() (run_doctor is not mutates: true, so it cannot go through POST /api/control).

web.control.ts / api.ts talk to those HTTP routes. This is not a second orchestrator. Presentation must not import adapters.

Shared presentation constraints ​

  • Redact secrets with Detector / domain redact helpers before display.
  • Do not print tokens in status lines.
  • Long work (start, doctor, token refresh) must be async so the TUI stays responsive.

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