Skip to main content

Dev Console

Localhost-only status page for the dev team. Serves a single HTML page at http://localhost:6300 with four panels:

  1. Open PRs — title, author, branch, required-check pass/fail/pending roll-up
  2. Recent main runs — last 15 GitHub Actions runs (in-progress current step + failing job + step)
  3. Self-hosted runners — online/offline, busy/idle, what they're executing
  4. Spec deliveryspecs/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:6300 only, 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/status call.
  • 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 gh keyring, 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

EndpointMethodPurpose
/GETStatic page (HTML + CSS + JS, no auth)
/api/statusGETAggregates PRs + runs + runners + specs (server-to-GitHub)
/api/healthGET{ ok, hasToken, tokenSource: 'env'|'manual'|'auto'|'none', hostMode, tokenFromHostEnv }
/api/session/restartPOSTProbes 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/reauthenticatePOSTBody: { token }. Validates against GET /rate_limit with the new token; rolls back on failure.
/api/session/autoPOSTHost 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. If hasToken=false && hostMode=true, automatically calls /api/session/auto before the first /api/status request — this is the zero-paste path under dev-console:host.
  • On /api/status failure (GitHub API 401|403|404), retries /api/session/auto once before showing the manual paste prompt. The recovery banner widens to match 404 because anonymous calls against a private repo's /pulls return 404, not 401.
  • The reauthenticate prompt explicitly suggests npm run dev-console:host as 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.