Ten sequential spec files plus index covering runtime, observability, security, data, routing, realtime, supervisor, clients, CLI ops, and workflow, each with best practices, structure, rules, guidelines, specs, and cross-references.
17 KiB
09 - CLI Tooling, Dev Server, Operations
Scope: plan.md sections 2, 7-8, 10 step 5 partial. Topics: CLI, dev server, embedder, AI harness, health/metrics, i18n/CSP.
Purpose and philosophy
One binary, molo, is the only project tool: it scaffolds, serves, embeds, supervises, and vibe-codes. Operators get health, readiness, and metrics endpoints that work with stock Prometheus and Kubernetes probes with zero glue code. Developers get a dev server with fast restart and a strict dev/prod settings split. Templates get i18n hooks and a CSP builder by default, so every generated page is localizable and locked down. Philosophy: human-first output that degrades gracefully to machine-readable output, static ceilings on everything watched or exported, and dev/prod parity except where explicitly split.
Best practices (from research)
- CLI design follows the Command Line Interface Guidelines: zero exit code on success and non-zero on failure, primary output to stdout, diagnostics to stderr, concise help by default with extensive help on request, and machine-readable output (JSON lines or
--format json) where it does not hurt usability (clig.dev). - Metrics exposition follows the Prometheus text format 0.0.4: line oriented,
\nterminated with trailing newline on the last line,# HELPand# TYPEcomment lines before samples,Content-Type: text/plain; version=0.0.4, optional gzip encoding, counter/gauge/histogram/summary/untyped primitives (Prometheus docs). - Health semantics follow the Kubernetes probe model: liveness detects a broken state that only a restart fixes (kubelet kills and restarts the container on failure), readiness gates traffic, startup protects slow-starting containers with its own window (Kubernetes docs).
- CSP starts from
default-src 'none'and opens only what is needed;default-srcis the fallback for all other fetch directives; rollout begins in report-only mode (Content-Security-Policy-Report-Only) before enforcing (MDN CSP, field practice). - Streaming uses Server-Sent Events:
Content-Type: text/event-stream,Cache-Control: no-cache, notifications as text blocks terminated by blank lines,data:payload lines (MDN SSE). OpenAI-compatible chat streaming sends data-only SSE chunks terminated by adata: [DONE]message withstream: truein the request (Intel OpenVINO OpenAI-API docs; OpenAI first-party docs unreachable, 403, so this is single-vendor confirmed plus community triangulation). - i18n follows GNU gettext practice: full sentences as message keys (never concatenated fragments), translator comments in source, PO-file based catalogs with context and plural forms, locale selected per request with a compiled-in fallback chain (GNU gettext manual).
Structure
Directories (deep, one concern per file):
tools/molo/ main() entry, links framework as a library
include/molo/cli/ argument parsing, command tree, help, output
src/cli/
include/molo/dev/ dev server, file watcher, reloader
src/dev/
include/molo/embed/ asset embedder (scanner, emitter)
src/embed/
include/molo/ai/ AI harness client, prompts, session
src/ai/
include/molo/ops/ health, readiness, metrics export, probe routes
src/ops/
include/molo/i18n/ locale, catalog, translator, PO loader
src/i18n/
include/molo/csp/ policy model, builder, nonce manager
src/csp/
Namespaces and classes (one class per header/source pair):
molo::cli:Command,CommandTree,Flag,FlagSet,ArgParser,HelpRenderer,TableWriter,JsonWriter,ExitCode(enum: Ok=0, Usage=2, Runtime=1, Unavailable=69).molo::dev:DevServer,FileWatcher(inotify/kqueue/ReadDirectoryChangesW backends behind one interface),Reloader,DevConfigvsProdConfigsplit type.molo::embed:AssetScanner,EmbedEmitter(directory to.cpp/.hpp),EmbeddedBundle(runtime reader used by static routes in 01-runtime.md).molo::ai:AiClient(built on the 08-clients-openapi.md HTTP client),AiSession,PromptPack(framework feature prompts),SseStreamParser(shared with 06-realtime.md SSE client, not duplicated).molo::ops:HealthRegistry,HealthCheck(base),LivenessCheck,ReadinessCheck,StartupGate,PrometheusExporter,OtlpBridge(thin; transport owned by 02-observability.md).molo::i18n:Locale,Catalog,PoLoader,Translator,PluralRule.molo::csp:CspPolicy,CspBuilder,Nonce(per-request, from 03-security.md crypto).
Rules (hard, numbered)
- The CLI never performs a destructive or history-rewriting action autonomously beyond its spec:
molo newrefuses to write into a non-empty directory without--force, and nothing here touches git history (history rules live in 10-workflow-example.md). - Every command returns 0 on success, 2 on usage errors, 1 on runtime failures, 69 when a required service is unavailable; scripts must be able to rely on this mapping.
- Primary command output goes to stdout; logs, progress, and errors go to stderr;
--format jsonoutput on stdout must be parseable JSON with no log interleaving. --helpon every command and subcommand;-hprints concise help,help <cmd>prints the full page with examples; no command without documented usage.- Dev server is dev-only: it refuses to start with production config unless
--allow-prodis passed, and it never enables the reloader against a production database URL. - Health endpoints never require auth, never allocate unbounded memory, and must answer in under 50 ms p99 with all checks short-circuited on timeout (each check capped at 200 ms).
- Metrics export has static cardinality caps: max 10000 series per process, max 64 label pairs per metric, per-route labels use route templates (
/users/{id}), never raw paths. - Embedded builds are byte-identical reproducible: same input directory plus same tool version yields identical generated bytes (sorted file order, no timestamps in output).
- The AI harness never sends secrets:
.env.jsonvalues, key files, and anything matching*_KEY,*_SECRET,*_TOKENare redacted before prompt construction; redaction failures abort the request. - Default CSP is deny-first: generated pages start at
default-src 'none'plus explicit allows, andreport-onlymode is the default until the app opts into enforcement. - All user-visible CLI and template strings go through the translator; no hardcoded English outside the fallback catalog.
Development guidelines
- Build the CLI as data first: declare the command tree (names, flags, help) in one place, then attach handlers; help and shell completion generate from the same tree.
- Reuse, never reimplement: the AI harness uses the 08-clients-openapi.md HTTP client and the 06-realtime.md SSE parser; metrics use the 02-observability.md registry; nonces and hashing use 03-security.md; static serving of embedded bundles uses 01-runtime.md.
- Keep the dev server dumb: watch, rebuild via
make, restart the child process, preserve no state; stateful niceties (state-preserving reload) are explicitly out of scope for v1. - Prefer
std::expectedor an equivalent result type over exceptions across CLI/ops boundaries so exit codes map cleanly. - Every new command ships with its spec entry, its help text, one integration test through the real binary, and a living-example mention where user-visible.
Specifications (concrete behaviors, caps, formats)
CLI command tree (v1):
molo new <name> [--backend postgres|sqlite] [--force]
molo serve [--port 8000] [--reload] [--config .env.json]
molo build [--embed assets/] [--out build/]
molo embed <dir> --out <file.cpp> [--namespace molo::assets] [--gzip]
molo migrate [--check | --apply] (executes 04-data.md plans)
molo supervisor start|stop|restart|status|logs (drives 07-supervisor-replication.md)
molo task enqueue|retry|cancel|inspect (drives 07-supervisor-replication.md)
molo ai new|continue [--stream] [--base-url URL] [--model NAME]
molo health [--format json|text]
molo version [--format json|text]
Global flags: -h/--help, --version, --format json|text (default text, TTY-aware: no color when piped), --verbose/-v (repeatable, max 3), --quiet/-q.
Exit codes: 0 ok, 1 runtime failure, 2 usage error, 69 service unavailable (matches SYSEXITS EX_UNAVAILABLE).
Dev server: molo serve --reload watches src/, templates/, assets/ (max 20000 files, debounce 150 ms, recursive); on change it runs make -j$(nproc) with a 300 s cap, kills the child with SIGTERM then SIGKILL after 5 s, and restarts; crash loops back off 1 s, 2 s, 4 s up to 30 s, then hold with a loud message. Dev/prod split: .env.json plus MOLO_ENV=dev|prod; prod requires TLS config and refuses --reload without --allow-prod.
Embedder: input max 512 MB total, max 50000 files, max 64 MB per file; skips dotfiles and symlinks escaping the root; emits one .cpp plus one .hpp with constexpr byte arrays (chunked at 1 MB per array), a sorted manifest (path, size, sha256, mime), and optional precompressed gzip variants for text types over 1 KB; output includes tool version string for audit.
AI harness: base URL default https://devplace.net/openai/v1, model default molodetz, key from DEVPLACE_API_KEY env only (never a flag, never stored); request POST {base}/chat/completions with {"model","messages","stream":true}; response parsed as SSE (text/event-stream), JSON per data: line, end on data: [DONE]; caps: 30 s connect timeout, 300 s session cap, max 8 MB transcript, max 50 tool/file actions per session, human confirms every file write (plan-then-apply, no silent writes).
Health/metrics endpoints (mounted by molo::ops, paths fixed):
GET /healthzliveness: 200{"status":"ok"}when the loop serves; fails only when shutdown is stuck.GET /readyzreadiness: 200 when DB, KV, and supervisor heartbeats pass; 503{"status":"degraded","failed":[...]}otherwise.GET /startupzstartup gate: 503 until boot completes, then 200.GET /metricsPrometheus text 0.0.4 (text/plain; version=0.0.4), gzip if accepted; OpenTelemetry OTLP export optional via 02-observability.md bridge.- Budgets: scrape render under 100 ms p99 at 10k series; export interval floor 5 s; series cap 10000 with
molo_metrics_dropped_totalcounter on overflow.
i18n: locale format BCP 47 (en, nl-NL); resolution order: route prefix (/nl/...), cookie molo_locale, Accept-Language (q-values honored), config default; catalogs are PO files under locales/<locale>/LC_MESSAGES/*.po, compiled to embedded bundles for prod; plural rules per CLDR cardinal set for shipped locales; missing key falls back through nl-NL to nl to en to msgid, and logs once per key per process.
CSP builder: CspBuilder fluent API (default_src(), script_src(), style_src(), img_src(), connect_src(), frame_ancestors(), report_to()); default policy default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; per-request nonces for inline scripts ('nonce-<base64>', 16 bytes from 03-security.md RNG); report endpoint POST /csp-report (JSON, capped 64 KB per report, 100 reports/min/IP); Content-Security-Policy-Report-Only until enforce() is called.
Execution steps (in build order)
- CLI skeleton:
Command,ArgParser,HelpRenderer, exit-code mapping,--format, TTY detection,molo versionandmolo health(stub checks). molo newscaffolding: Makefile, AGENTS.md stub, directory layout,.env.json,.gitignore,git init, plug-and-play example files.- Health/readiness/startup endpoints plus registry; wire DB/KV/supervisor checks as those components land.
- Prometheus exporter on the 02-observability.md registry with series caps and gzip.
- Dev server with watcher backends, rebuild/restart loop, dev/prod config split enforcement.
- Embedder scanner plus emitter plus runtime bundle reader; static-route integration test.
- CSP builder, nonce plumbing into templates (with 05-request-routing.md), report endpoint, report-only default.
- i18n core: locale resolution, PO loader, translator, template filter/function hooks (with 05-request-routing.md).
- AI harness: client on 08-clients-openapi.md HTTP, SSE parsing via 06-realtime.md, redaction, plan-then-apply loop,
molo aicommands. - Supervisor/task CLI surface on top of 07-supervisor-replication.md APIs; structured JSON output for scripts.
Validation (how to verify each step)
- Run
molo version,molo --help,molo nosuch; assert exit codes 0/0/2 and JSON parseability of--format json; pipe stdout and confirm no color codes. - Scaffold into a temp dir; assert all files exist,
git statusclean after init,makesucceeds, and re-run without--forcefails with exit 1. - Start app, curl
/healthz,/readyz,/startupz; kill the DB and assert/readyzflips to 503 within 2 s while/healthzstays 200; measure p99 under 50 ms over 1000 requests. - Curl
/metrics, validate withpromtool check metrics; assert trailing newline, HELP/TYPE lines, gzip variant identical after decode; flood labels and assert the 10000 cap plus dropped counter. - Touch a template, assert rebuild plus restart under 5 s; assert
--reloadwith prod config exits 2 without--allow-prod; kill child 5 times, assert backoff holds. - Embed a fixture tree twice, assert byte-identical output; serve from the bundle and diff bodies plus ETags against disk serving; feed a 65 MB file and assert rejection.
- Render a page, assert CSP header present with nonce matching the inline script tag; post a violation report and assert 202 plus storage; assert report-only header by default and enforcing header after
enforce(). - Request
/nl/and assert translated strings; remove one key and assert fallback chain plus single log line; load a PO with plurals and assert all plural forms render. - Run
molo ai newagainst a local stub SSE server returning two chunks plus[DONE]; assert transcript assembly, redaction of a plantedAPI_KEYvalue, and that no file is written without confirmation; point at the real DevPlace endpoint only in a manual opt-in test. - Enqueue/retry/cancel a fixture task via CLI with
--format json; assert state transitions match07-supervisor-replication.mdsemantics and output parses.
References (sibling files this depends on or feeds)
- Depends on 01-runtime.md (static serving of bundles, lifecycle), 02-observability.md (registry, logging, OTLP), 03-security.md (nonces, redaction-adjacent crypto, TLS posture), 04-data.md (migrate targets, readiness checks), 05-request-routing.md (template hooks for i18n/CSP, middleware mount points), 06-realtime.md (SSE parser reuse), 07-supervisor-replication.md (CLI surface targets), 08-clients-openapi.md (HTTP client for AI harness).
- Feeds 10-workflow-example.md (CLI-verified commands, example pages proving i18n/CSP/health,
molo newtemplate source).
Open questions
- Should
molobe one static binary or a thin driver plus per-project plugin commands? Leaning single binary for v1. - Do we ship shell completion scripts (bash/zsh/fish) generated from the command tree in v1, or defer to v1.1?
- Should
/metricsrequire auth in production by default ( risk: scraping complexity) or stay open with IP allow-listing? - Which locales ship compiled-in for v1 beyond
en:nlonly, or a wider starter set? - Does the AI harness need a non-streaming fallback for proxies that buffer SSE, or is streaming mandatory?
Sources (exact URLs with retrieval dates)
- Command Line Interface Guidelines, https://clig.dev/ (retrieved 2026-10-23): exit codes, stdout/stderr split, help conventions, machine-readable output.
- Prometheus exposition formats, https://prometheus.io/docs/instrumenting/exposition_formats/ (retrieved 2026-10-23): text 0.0.4 line format, HELP/TYPE, content type, primitives.
- Kubernetes probes task guide, https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ (retrieved 2026-10-23): liveness restart semantics, probe timing fields.
- MDN Content-Security-Policy, https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy (retrieved 2026-10-23): default-src fallback, report-only header, reporting directives.
- MDN Using server-sent events, https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events (retrieved 2026-10-23): text/event-stream, data blocks, blank-line framing.
- Intel OpenVINO OpenAI chat API docs, https://docs.openvino.ai/2024/ovms_docs_rest_api_chat.html (found 2026-10-23 via search snippet): stream flag, data-only SSE,
data: [DONE]terminator. Note: OpenAI first-party streaming reference (https://platform.openai.com/docs/api-reference/chat-streaming) returned HTTP 403 on 2026-10-23; convention triangulated with MDN SSE and multiple compatible-server docs. - GNU gettext manual, https://www.gnu.org/software/gettext/manual/gettext.html (retrieved 2026-10-23): msgid practice, PO format, plural and context entries, locale selection.