| 1 | /** |
| 2 | * JSON-first output helpers per RFC-03. |
| 3 | * `--json` (default for agent) emits NDJSON-friendly single-object lines. |
| 4 | * Non-JSON mode uses simple readable text. |
| 5 | */ |
| 6 | |
| 7 | let JSON_MODE = true; |
| 8 | |
| 9 | export function setJsonMode(on: boolean) { |
| 10 | JSON_MODE = on; |
| 11 | } |
| 12 | |
| 13 | export function ok(payload: unknown): void { |
| 14 | if (JSON_MODE) { |
| 15 | process.stdout.write(`${JSON.stringify({ status: 'ok', ...(payload as object) })}\n`); |
| 16 | } else { |
| 17 | process.stdout.write(`${pretty(payload)}\n`); |
| 18 | } |
| 19 | } |
| 20 | |
| 21 | export function fail(code: string, message: string, ctx: Record<string, unknown> = {}): never { |
| 22 | if (JSON_MODE) { |
| 23 | process.stdout.write(`${JSON.stringify({ status: 'error', code, message, ...ctx })}\n`); |
| 24 | } else { |
| 25 | process.stderr.write(`✘ ${code}: ${message}\n`); |
| 26 | } |
| 27 | process.exit(1); |
| 28 | } |
| 29 | |
| 30 | export function progress(stage: string, pct: number, extra: Record<string, unknown> = {}): void { |
| 31 | if (JSON_MODE) { |
| 32 | process.stdout.write( |
| 33 | `${JSON.stringify({ type: 'progress', stage, pct, ...extra })}\n`, |
| 34 | ); |
| 35 | } else { |
| 36 | process.stdout.write(` ${stage}: ${pct}%\n`); |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | function pretty(p: unknown): string { |
| 41 | if (p == null) return '(empty)'; |
| 42 | if (typeof p === 'string') return p; |
| 43 | return JSON.stringify(p, null, 2); |
| 44 | } |
| 45 |