# CLI Output Roadmap ## Purpose This roadmap defines the intended final shape for weatherreporter CLI output. The current CLI has drifted: - `run` commands emit JSON summaries to stdout and compact status lines to stderr. - `inspect` commands emit JSON to stdout. - `generate` commands perform substantial work but are silent on success. The target is a predictable command-line contract that is useful for operators, easy to consume from scripts, and explicit enough that future commands naturally reuse the same output path. ## Locked Decisions - Keep application orchestration and domain decisions in `internal/app`. - Keep CLI presentation, stdout/stderr policy, and quiet-mode behavior in `internal/cli`. - Successful non-help commands should have a machine-readable JSON stdout contract unless `--quiet` intentionally suppresses success output for an action command. - Help remains human-readable text. - Stderr is for compact operational status and errors, not primary command payloads. - Do not print partial JSON when command construction, flag parsing, config loading, or pre-run validation fails. - Do not serialize large internal app objects directly as CLI output. - Do not expose secret values in stdout or stderr. - Add `--quiet` for state-changing action commands. - Do not make `--quiet` suppress requested inspection data. ## Command Categories CLI commands should be classified into one of these output categories. ### Help Commands Examples: - `weatherreporter --help` Output: - stdout: human-readable help text - stderr: none on success - `--quiet`: not applicable ### Action Commands Examples: - `weatherreporter generate today` - `weatherreporter generate daily --date YYYY-MM-DD` - `weatherreporter run morning` - `weatherreporter run evening` Output: - stdout: compact JSON summary after the action completes - stderr: compact status lines only when useful, especially for multi-report batch commands - `--quiet`: suppress success stdout and routine status stderr Failure behavior: - For flag/config/pre-run errors, stdout is empty and the command returns an error. - For completed actions that produce an inspectable failure result, default output may still include a JSON failure summary before returning nonzero. - With `--quiet`, failure diagnostics should remain concise and actionable on stderr through the existing top-level error path; routine success summaries stay suppressed. ### Inspection Commands Examples: - `weatherreporter inspect reports` - `weatherreporter inspect metadata RUN_ID` - `weatherreporter inspect modules RUN_ID` - `weatherreporter inspect data-package RUN_ID` - `weatherreporter inspect prior RUN_ID` - `weatherreporter inspect sources RUN_ID` Output: - stdout: requested JSON data - stderr: none on success - `--quiet`: not accepted unless a future inspection command has auxiliary status output to suppress Inspection commands are already data-oriented. Their stdout payload should stay focused on the requested data rather than being hidden by quiet mode. ## Target Action Summary Shape Action command JSON should be small, stable, and path-oriented. It should expose what an operator needs to find artifacts, inspect a run, and understand notification status. ### Generate Summary Target shape: ```json { "command": "generate", "reportId": "today", "reportName": "Today Report", "promptId": "weather.today_generated_text", "runId": "20260529T100000.000000000Z_today", "status": "succeeded", "generatedAt": "2026-05-29T10:00:00Z", "validPeriod": {}, "reportPath": "workspace/reports/today/2026-05-29/report.20260529T100000.000000000Z_today.md", "outputPath": "./today.md", "metadataPath": "workspace/snapshots/today/2026-05-29/metadata.20260529T100000.000000000Z_today.json", "dataPackagePath": "workspace/data-packages/today/2026-05-29/data_package.20260529T100000.000000000Z_today.yaml", "preflightPath": "workspace/preflight/today/2026-05-29/render.20260529T100000.000000000Z_today.json", "notificationPath": "workspace/notifications/today/2026-05-29/distributor.20260529T100000.000000000Z_today.json", "notification": { "status": "succeeded", "runId": "distributor-run", "pipelineId": "weatherreporter.today", "bundleId": "weatherreporter.home.today", "path": "workspace/notifications/today/2026-05-29/distributor.20260529T100000.000000000Z_today.json" } } ``` Rules: - Omit absent optional paths with `omitempty`. - Include generated-text artifact paths only for report modes that produce them. - Include notification fields only when notification was attempted. - Keep module snapshot contents, data package contents, raw generated text, render result bodies, and full notification adapter payloads out of the CLI summary. ### Batch Summary The current `BatchResult` shape is close to the target and should remain the basis for `run` output. The target update is to make the summary explicitly command-like and align status semantics with generate output: ```json { "command": "run", "batch": "morning", "status": "succeeded", "startedAt": "2026-05-29T10:00:00Z", "finishedAt": "2026-05-29T10:01:00Z", "total": 3, "succeeded": 3, "failed": 0, "notification": {}, "reports": [] } ``` Rules: - Keep per-report items compact and path-oriented. - Keep batch notification status at the top level. - Preserve nonzero exit behavior when one or more reports fail. - Preserve the existing behavior that batch report failures do not prevent the JSON summary from being available in default output mode. ## Quiet Mode `--quiet` should be available on action commands: ```sh weatherreporter generate today --quiet weatherreporter run morning --quiet ``` Quiet mode means: - no stdout on successful action commands - no routine status lines on stderr on successful action commands - errors still return nonzero and are still reported by the top-level CLI error path - inspection output is not suppressed Quiet mode does not mean: - skipping artifact writes - skipping distributor notification - changing JSON shape when JSON is emitted - hiding errors Future action commands should opt into quiet mode by using the centralized action-output writer rather than implementing their own flag or writer logic. ## Intended Code Structure `internal/cli` should own a small output layer that future commands can reuse. The output layer should make the consistent path the easiest path. Target files: - `internal/cli/root.go`: command routing and flag parsing - `internal/cli/output.go`: stdout/stderr writers, quiet-mode handling, and output category helpers - `internal/cli/result.go`: CLI-safe summary structs and conversion helpers Target app-layer shape: - Add a detailed generate entry point that returns the generated report result. - Keep `app.Generate(ctx, GenerateRequest) error` as a convenience wrapper for callers that do not need CLI output. - Keep `app.RunBatchDetailed(ctx, BatchRequest) (*BatchResult, error)` as the batch command result source. Target CLI output helpers: - `writeJSON(io.Writer, any) error` - `writeActionResult(stdout, stderr io.Writer, result actionResult, opts outputOptions) error` - `writeBatchStatus(stderr io.Writer, result *app.BatchResult)` - `writeGenerateStatus(stderr io.Writer, result GenerateSummary)` only if single-report status lines become useful The command router should not call `json.NewEncoder` directly outside the central output helpers. ## Deferred Questions None of these are required for the initial harmonization: - global `--format` support - NDJSON progress streams - human-readable success output - machine-readable error envelopes on stderr - making inspection commands use a common envelope These should remain deferred until there is a real consumer need.