import type { Command } from 'commander'; import { withAuth } from './with-auth.js'; import { fetchProjects } from '../tui/gateway-api.js'; /** * `mosaic prdy` — thin adapter over PrdService (@mosaicstack/prdy). * All reads/writes go through the service; there is no local writer path. */ export function registerPrdyCommand(program: Command) { const cmd = program .command('prdy') .description('PRD wizard — create and manage Product Requirement Documents') .option('-g, --gateway ', 'Gateway URL', 'http://localhost:14242') .option('--init [name]', 'Create a new PRD') .option('--update [name]', 'Update an existing PRD') .option('--import ', 'Import a YAML PRD document (validated, conflict-aware)') .option('--accept-successor', 'With --import: accept a conflicted import as next version') .option('--export [id]', 'Export a PRD as a labeled generated-view Markdown file') .option('--project ', 'Scope to project') .action( async (opts: { gateway: string; init?: string | boolean; update?: string | boolean; import?: string; acceptSuccessor?: boolean; export?: string | boolean; project?: string; }) => { // Detect project context when --project flag is provided if (opts.project) { try { const auth = await withAuth(opts.gateway); const projects = await fetchProjects(auth.gateway, auth.cookie); const match = projects.find((p) => p.id === opts.project || p.name === opts.project); if (match) { console.log(`Project context: ${match.name} (${match.id})\n`); } } catch { // Gateway not available — proceed without project context } } const { PrdService, runPrdWizard } = await import('@mosaicstack/prdy'); const service = new PrdService({ projectPath: process.cwd() }); try { if (opts.import !== undefined) { const input = { filePath: opts.import }; if (opts.acceptSuccessor) { const successor = await service.acceptSuccessor(input); console.log( `Import accepted as successor: ${successor.id} v${successor.version} (status: ${successor.status})`, ); return; } const result = await service.importDocument(input); console.log( result.kind === 'created' ? `Imported PRD ${result.document.id} v${result.document.version} (status: ${result.document.status})` : `PRD ${result.document.id} already present with identical content — nothing to do.`, ); return; } if (opts.export !== undefined) { const id = typeof opts.export === 'string' && opts.export.length > 0 ? opts.export : undefined; const result = await service.exportMarkdown({ id }); console.log( `Generated view written: ${result.filePath} (source authority: YAML under docs/prdy/ — do not edit the Markdown)`, ); return; } const name = typeof opts.init === 'string' ? opts.init : typeof opts.update === 'string' ? opts.update : 'untitled'; if (process.stdout.isTTY) { await runPrdWizard({ name, projectPath: process.cwd(), interactive: true, }); return; } // Non-interactive fallback routes through the service directly. const doc = await service.create({ name }); console.log(`PRD created: ${doc.id} v${doc.version} (status: ${doc.status})`); } catch (err) { if (err instanceof Error && err.name === 'PrdImportConflictError') { const conflict = err as { proposal?: { version?: number } }; console.error(`${err.message}`); console.error( `Original PRD left untouched. To accept the proposed successor (v${conflict.proposal?.version}), re-run with --accept-successor.`, ); process.exit(1); } console.error(`PRD wizard failed: ${err instanceof Error ? err.message : String(err)}`); process.exit(1); } }, ); return cmd; }