/* cli index — command-line interactions for managing kernel applications and executing commands. Do not handle raw input validation beyond flag parsing. Do not manage application lifecycle or orchestration. Do not implement business logic for commands executed. CLI execution must route through registered kernel commands and preserve typed flag parsing. RFC-0870: add pipeline hint to Unknown command and not-registered error messages. RFC-0972: global --json exit code policy — always exit 0 when outputFormat is json. RFC-1097: step 6 — compass.migrate codemod run Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order. RFC-1097: sweep — werkstatt-engine clean Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics. RFC-1326: single Cloudflare permission registry, token preflight, birth provisioning ADR-0022, RFC-0686 */ import process from "node:process"; import { buildCommandManifest, executeKernelCommand, executeKernelPipeline, findWorkspaceRoot, listSiteWorkspaces, } from "../index.ts"; import { setRegistryCacheEnabled } from "../runtime/registry-cache.ts"; import { flushFactoryTelemetry } from "../runtime/telemetry.ts"; import { pipelineHint } from "../pipeline-hint.ts"; import { argvRequestsHelp, argvRequestsJson, consumeCommonFlags, printFilesModified, printUsage, } from "./flags.ts"; import { assertBatchHasNoPositional, BatchUsageError, runBatch } from "./batch.ts"; import { loadWorkshopEnvAllowlisted } from "../workshop-env.ts"; // @ai-invariant: CLI execution must route through registered kernel commands and preserve typed flag parsing. /** RFC-0266: render one command's manifest entry (description + declared flags) for `--help`. */ async function printCommandHelp(workspaceRoot: string, commandName: string): Promise { const manifest = await buildCommandManifest(workspaceRoot); const entry = manifest.commands.find((command) => command.name === commandName); if (!entry) { console.log(`Unknown command "${commandName}".${pipelineHint(commandName)}`); return; } console.log(`${entry.name} — ${entry.description}`); console.log( ` scope: ${entry.scope} mutatesState: ${entry.mutatesState} requiresNetwork: ${entry.requiresNetwork}`, ); const flagNames = Object.keys(entry.flags); if (flagNames.length === 0) { console.log(" flags: (none declared)"); } else { console.log(" flags:"); for (const flagName of flagNames) { const flag = entry.flags[flagName]!; const requiredLabel = flag.required ? " (required)" : ""; console.log(` --${flagName} <${flag.kind}>${requiredLabel} — ${flag.description}`); } } } function printTopLevelError(error: unknown): void { const message = error instanceof Error ? error.message : String(error); if (argvRequestsJson(process.argv.slice(2))) { console.log( JSON.stringify( { commandName: process.argv[3] ?? process.argv[2] ?? "werkstatt", exitCode: 1, ok: false, summary: message, logs: [], }, null, 2, ), ); return; } console.error(error instanceof Error ? (error.stack ?? error.message) : error); } export async function main() { const workspaceRoot = await findWorkspaceRoot(); // RFC-1326: allow-listed workshop .env resolution replaces the blanket // dotenv/config import — only WORKSHOP_ENV_ALLOWLIST keys reach // process.env; site/service keys stay site-level via workpieceEnv. await loadWorkshopEnvAllowlisted(workspaceRoot); const [subcommand, ...rest] = process.argv.slice(2); // ADR-0022: --no-registry-cache disables the process-lifetime registry cache. // Must be set before any registry-building call. if ( rest.some( (entry) => entry === "--no-registry-cache" || entry === "--no-registry-cache=true" || entry === "--no-registry-cache=1", ) ) { setRegistryCacheEnabled(false); } if (!subcommand) { printUsage(); process.exitCode = 1; return; } if (subcommand === "sites") { const [operation, ...argv] = rest; if (operation !== "list") { printUsage(); process.exitCode = 1; return; } const { outputFormat } = consumeCommonFlags(argv); const result = await listSiteWorkspaces(workspaceRoot); if (outputFormat === "json") { console.log(JSON.stringify(result, null, 2)); return; } console.log(`Workspace: ${result.workspaceRoot}`); for (const site of result.sites) { console.log(`- ${site.name}${site.configPath ? ` -> ${site.configPath}` : " -> no config"}`); } return; } if (subcommand === "run") { const { siteName, allSites, dryRun, force, quiet, batch, stopOnFail, outputFormat, remaining } = consumeCommonFlags(rest); const [commandName, ...argv] = remaining; // RFC-1309: --batch executes a manifest of commands in this one process; // it is mutually exclusive with a positional command name. if (batch !== undefined) { try { assertBatchHasNoPositional(commandName ? [commandName, ...argv] : []); const { report, exitCode } = await runBatch({ workspaceRoot, manifestPath: batch, globals: { siteName, allSites, dryRun, force, quiet, stopOnFail, outputFormat, }, execute: executeKernelCommand, }); if (outputFormat === "json") { console.log(JSON.stringify(report, null, 2)); process.exitCode = 0; // RFC-0972: JSON mode always exits 0 } else { process.exitCode = exitCode; } } catch (error) { if (error instanceof BatchUsageError) { console.error(`usage error: ${error.message}`); process.exitCode = 1; return; } throw error; } return; } if (!commandName) { printUsage(); process.exitCode = 1; return; } if (argvRequestsHelp(argv)) { await printCommandHelp(workspaceRoot, commandName); return; } const result = await executeKernelCommand({ workspaceRoot, commandName, siteName, siteExplicit: siteName !== undefined, allSites, dryRun, force, quiet, outputFormat, argv, }); if (outputFormat === "json") { console.log(JSON.stringify(result, null, 2)); } else { const reports = Array.isArray(result) ? result : [result]; for (const report of reports) { printFilesModified(report, outputFormat); } } const allReports = Array.isArray(result) ? result : [result]; const failed = allReports.find((report) => !report.ok); process.exitCode = outputFormat === "json" ? 0 : (failed?.exitCode ?? 0); return; } if (subcommand === "pipeline") { const { siteName, allSites, dryRun, force, concurrency, collectErrors, quiet, outputFormat, remaining: pipelineRemaining, } = consumeCommonFlags(rest); const [pipelineName] = pipelineRemaining; if (!pipelineName) { printUsage(); process.exitCode = 1; return; } const result = await executeKernelPipeline({ workspaceRoot, pipelineName, siteName, allSites, dryRun, force, quiet, outputFormat, ...(concurrency !== undefined ? { concurrency } : {}), ...(collectErrors ? { collectErrors: true } : {}), }); if (outputFormat === "json") { console.log(JSON.stringify(result, null, 2)); } else { const reports = Array.isArray(result) ? result : [result]; for (const report of reports) { printFilesModified(report, outputFormat); } } const allReports = Array.isArray(result) ? result : [result]; const failed = allReports.find((report) => !report.ok); process.exitCode = outputFormat === "json" ? 0 : (failed?.exitCode ?? 0); return; } printUsage(); process.exitCode = 1; } main() .catch((error) => { printTopLevelError(error); process.exitCode = 1; }) .finally(() => flushFactoryTelemetry());