Dev Console
Localhost-only status page for the dev team. Serves a single HTML page at http://localhost:6300 with four panels:
- Open PRs — title, author, branch, required-check pass/fail/pending roll-up
- Recent
mainruns — last 15 GitHub Actions runs (in-progress current step + failing job + step) - Self-hosted runners — online/offline, busy/idle, what they're executing
- Spec delivery —
specs/NNN-*directories, their PR state (merged / in-progress / planned), latest activity
Auto-refreshes every 30s. Never persists anything; every panel is a fresh GitHub REST fetch.
Security posture (intentional simplicity)
- No app-layer auth. Access control is the port binding: Docker maps
127.0.0.1:6300only, so only processes on the developer's host can reach it. - Zero PHI. The page reads only GitHub REST API metadata (PR titles, run statuses, spec directory names). No clinical data ever flows through.
- No persistent storage. Fresh fetch on every
/api/statuscall. - The token never reaches the browser. GitHub auth happens server-side; the browser sees rendered metadata only.
Two run modes
docker mode (default)
npm run dev-console:up
↓
Dockerfile builds, GITHUB_TOKEN=$(gh auth token) injected at container start
↓
Container runs node server.mjs on 127.0.0.1:6300
↓
Token can be replaced from the page (POST /api/session/reauthenticate
prompts user to paste a token); no host access from inside the container.
host mode (zero-paste)
npm run dev-console:host
↓
Server runs as a host Node process directly (no Docker)
↓
DEV_CONSOLE_HOST_MODE=1 enables POST /api/session/auto, which shells
out to `gh auth token` server-side via execFile (no shell, PATH-only env).
↓
Page calls /api/session/auto on first paint when no token is loaded;
truly hands-off because the server can re-fetch from the macOS keyring.
Why two modes:
- Docker mode keeps the dev-console isolated like every other service. Drawback: container has no path to the host's
ghkeyring, so token refresh requires either a container restart or a manual paste in the page. - Host mode trades that isolation for the page being able to refresh its own token. Honest documentation of the trade-off lives in
apps/dev-console/README.md.
Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/ | GET | Static page (HTML + CSS + JS, no auth) |
/api/status | GET | Aggregates PRs + runs + runners + specs (server-to-GitHub) |
/api/health | GET | { ok, hasToken, tokenSource: 'env'|'manual'|'auto'|'none', hostMode, tokenFromHostEnv } |
/api/session/restart | POST | Probes the current token via GET /rate_limit. Fail-closed — returns 401 when no token is loaded (a previous build returned 200 because GitHub's rate-limit endpoint accepts unauthenticated requests; that produced false-positive "session restored" UX). |
/api/session/reauthenticate | POST | Body: { token }. Validates against GET /rate_limit with the new token; rolls back on failure. |
/api/session/auto | POST | Host mode only. Shells out to gh auth token via execFile (no shell, PATH-only inherited env). Returns 404 in Docker mode so the frontend gracefully falls through to the manual paste flow. |
Frontend behavior
- On first paint: hits
/api/health. IfhasToken=false && hostMode=true, automatically calls/api/session/autobefore the first/api/statusrequest — this is the zero-paste path underdev-console:host. - On
/api/statusfailure (GitHub API 401|403|404), retries/api/session/autoonce before showing the manual paste prompt. The recovery banner widens to match404because anonymous calls against a private repo's/pullsreturn 404, not 401. - The reauthenticate prompt explicitly suggests
npm run dev-console:hostas the zero-paste alternative.
Operational notes
- Stop docker mode:
npm run dev-console:down - Stop host mode:
kill $(cat /tmp/dev-console-host.pid)(PID stamped on launch) - Logs (docker):
npm run dev-console:logs - Source:
apps/dev-console/server.mjs(~440 lines, zero npm runtime deps — Node 20 stdlib + native fetch only)
Why no MFA / RBAC
The dev-console is a developer-tooling surface. Adding MSAL + role checks would gate it behind production auth without any compliance benefit (no PHI, no session credentials). If it ever moves to a shared dev server (team-wide URL rather than localhost), the upgrade path documented in docs/develop/dev-console.md adds MSAL + admin-role gate before relaxing the port binding — at which point this page is updated to match.