/** * lib/output.ts — Standardized stdout JSON envelope for dev CLIs. * * Every SmartStack Studio dev CLI returns a single JSON object on stdout. * This module provides helper functions to build that envelope consistently * so Claude can parse all CLIs with the same logic. * * Envelope shape: * { * success: boolean, * command: string, * data?: Record, // generic data bag * filesCreated?: string[], // for generate sub-pattern * filesModified?: string[], // for generate sub-pattern * report?: Record, // for execute sub-pattern * errors: string[], * warnings: string[], * nextSteps: string[], * } */ export interface GenerateEnvelope { success: boolean; command: string; data?: Record; filesCreated: string[]; filesModified: string[]; errors: string[]; warnings: string[]; nextSteps: string[]; } export interface ExecuteEnvelope> { success: boolean; command: string; data?: Record; report: R | null; errors: string[]; warnings: string[]; nextSteps: string[]; } export type CliEnvelope> = | GenerateEnvelope | ExecuteEnvelope; export function generateEnvelope( command: string, overrides: Partial = {}, ): GenerateEnvelope { return { success: true, command, filesCreated: [], filesModified: [], errors: [], warnings: [], nextSteps: [], ...overrides, }; } export function executeEnvelope>( command: string, overrides: Partial> = {}, ): ExecuteEnvelope { return { success: true, command, report: null, errors: [], warnings: [], nextSteps: [], ...overrides, }; } /** * Print the envelope as pretty JSON on stdout. * Single source of truth for what Claude reads after invoking a CLI. * * Generic over the report shape so execute CLIs can print a strongly-typed * `ExecuteEnvelope` (an interface lacks the implicit index signature * of `Record`). Pure widening — every existing caller still * compiles; runtime behaviour is unchanged (types are erased). */ export function printEnvelope>(envelope: CliEnvelope): void { console.log(JSON.stringify(envelope, null, 2)); } /** Convenience: build a failure envelope (generate variant). */ export function failGenerate(command: string, errors: string[]): GenerateEnvelope { return { success: false, command, filesCreated: [], filesModified: [], errors, warnings: [], nextSteps: [], }; } /** Convenience: build a failure envelope (execute variant). */ export function failExecute>( command: string, errors: string[], ): ExecuteEnvelope { return { success: false, command, report: null, errors, warnings: [], nextSteps: [], }; }