Always-on localhost observability + task control for the plan-factory
(factory-run.sh --drive). Full product + technical spec lives in
docs/SPEC.md.
A Linear-quality, keyboard-driven local web app that watches a configured
list of repo roots for factory activity (.factory/results, .factory/state,
plans/, git refs) and gives full observability (workers, waves, costs,
gate/closer results) plus guarded task management (create/edit/reprioritize
plan files) over them. One Hono Node process serves the API, SSE stream, and
the built SPA on localhost:5599.
pnpm run console:install-service
This copies service/com.berth-dev.berth-console.plist into
~/Library/LaunchAgents/, substituting this repo's absolute path, then
launchctl bootstraps it (bootout-then-bootstrap if already loaded, so
running it twice is safe). macOS only — the service is a launchd
LaunchAgent; there is no equivalent wired for other platforms. Uninstall with
launchctl bootout gui/$(id -u)/com.berth-dev.berth-console.
INSTALL_SERVICE_DRY_RUN=1 pnpm run console:install-service prints the
planned actions without touching launchctl or the filesystem — this is the
mode CI exercises (ubuntu-latest has no launchctl at all); a real install
is a manual, owner-run step on this machine.
apps/server/berth-console.config.json lists the watched repo roots
({"repos": ["/path/to/repo", ...]}) — only roots that actually contain
.factory/ get watchers. Override the path with CONSOLE_CONFIG (used by
tests/smokes to point at a fixture config instead).
PORT/CONSOLE_PORT— the server listens on5599by default; the plist setsCONSOLE_PORT=5599explicitly (the code's actual env var name — see Maintenance notes inplans/008-service-ci-hardening.mdfor why this differs from the plan's literal wording).- Logs:
data/logs/service.out.log/service.err.log, capped at 10MB —service/run.shrotates an oversized log to<name>.1(single generation, no compression) before exec-ing the built server on every service start.
- Phase Overview — wave stepper, live worker cards (model, elapsed vs. 45m cap, tool-call count), integration branch + PR badge, last closer verdict.
- Board — TODO / RUNNING / REVISE / REVIEW / DONE / BLOCKED columns, plan cards with a peek sheet, task create/edit/reject/reprioritize.
- Task Detail — plan markdown + tool timeline split view, run picker (worker/closer/fablefix/rev rounds), gate diagnostics, cost badge.
- Waves/DAG — dependency graph, per-wave gate ledger + closer verdicts.
- Usage — token/cost charts by model and day, factory-vs-interactive and sidechain filters, session drilldown, backfill progress.
- Events/Diagnostics — cross-repo feed (blocked/needs-owner/gate-fail/ diag/closer/crash) and a read-only artifact browser/viewer.
Linear-style, everything routable (URL state):
| Key | Action |
|---|---|
Space |
Peek (side-right overlay on Board) |
j / k / ↑ / ↓ |
Walk cards under an open peek |
Enter |
Promote peek to full route |
Esc |
Back, focus retained |
cmd+K |
Command palette |
- Crash mid-tail: ingestion stores a durable byte offset in the SAME
sqlite transaction as the rows it inserts, so a
kill -9mid-tail and restart resumes from that offset with no duplicate or missing events — proved bynode scripts/smoke-phase8.mjs(starts the real built server, replays half a fixture run, kills it, restarts against the same data dir, replays the rest, asserts zero duplicate/gapseqvalues). .factory/.gitignoreis*— a driver--cleanwipes the watched repo's.factory/results+.factory/stateentirely, but console history is unaffected: sqlite (data/*.sqlite, gitignored, never committed) is the only durable history store.- Log rotation:
service/run.shcapsdata/logs/*.logat 10MB per file, rotating to a single.1generation on every service start (see Config).
bash scripts/lint-write-paths.sh— CI gate enforcing the read-only invariants (.factory/is never written; onlyapps/server/src/write/mutates anything; git subprocess calls are confined towrite/gitGuard.ts+ the read-only allowlist inwatch/gitState.ts). It is grep/regex-based (see the script's own comments) — a floor, not a guarantee: dynamic property access or indirect wrappers can evade it.scripts/replay-fixture.sh+fixtures/worker-sample.jsonl— the standing test harness used by everyscripts/smoke-phase*.mjs; no paid workers needed to test ingestion.- Real launchd verification (
launchctl kickstart, reboot-sim viabootout/bootstrap) is a manual REVIEW-STEP on this machine — CI cannot exercise it (ubuntu-latesthas nolaunchctl).