@noir-ai/cli 1.14.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/bin.d.ts +10 -3
- package/dist/bin.js +1240 -365
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +39 -12
- package/dist/index.js +1258 -383
- package/dist/index.js.map +1 -1
- package/dist/{output-BNE9htEm.d.ts → output-2T2lGR7a.d.ts} +3 -3
- package/dist/tui/{chunk-6LO4LXEH.js → chunk-5KWETEMF.js} +1191 -352
- package/dist/tui/chunk-5KWETEMF.js.map +1 -0
- package/dist/tui/{chunk-ESZTOQEC.js → chunk-KETMM3BA.js} +25 -3
- package/dist/tui/chunk-KETMM3BA.js.map +1 -0
- package/dist/tui/{chunk-2RK6AFZQ.js → chunk-TPNJEN4B.js} +2 -2
- package/dist/tui/chunk-TPNJEN4B.js.map +1 -0
- package/dist/tui/{create-FD4E626Z.js → create-6KDIFPUL.js} +10 -6
- package/dist/tui/create-6KDIFPUL.js.map +1 -0
- package/dist/tui/index.js +994 -120
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/{install-YGTUSLRO.js → install-KA35ONQP.js} +2 -2
- package/dist/tui/{sync-N633SQSS.js → sync-AII5WCRO.js} +8 -4
- package/dist/tui/sync-AII5WCRO.js.map +1 -0
- package/dist/tui/{update-LET5BWOU.js → update-XGKJ4KBR.js} +3 -3
- package/package.json +11 -11
- package/dist/tui/chunk-2RK6AFZQ.js.map +0 -1
- package/dist/tui/chunk-6LO4LXEH.js.map +0 -1
- package/dist/tui/chunk-ESZTOQEC.js.map +0 -1
- package/dist/tui/create-FD4E626Z.js.map +0 -1
- package/dist/tui/sync-N633SQSS.js.map +0 -1
- /package/dist/tui/{install-YGTUSLRO.js.map → install-KA35ONQP.js.map} +0 -0
- /package/dist/tui/{update-LET5BWOU.js.map → update-XGKJ4KBR.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/install.ts","../../src/output.ts","../../src/theme.ts"],"sourcesContent":["import { chmodSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport {\n atomicWriteFile,\n type DetectResult,\n detectActiveMethod,\n detectInstallMethods,\n ensureShimExecutable,\n loadProjectInfo,\n MANAGED_NODE_VERSION,\n NOIR_VERSION,\n noirHome,\n type ProvisionedNode,\n type ProvisionOptions,\n provisionManagedNode,\n readInstallRecord,\n runManagerCmd,\n writeInstallRecord,\n} from '@noir-ai/core';\nimport { type CliOptions, EXIT, fail, info, spinner, success, warn } from '../output.js';\n\nexport interface InstallOptions extends CliOptions {\n list?: boolean;\n uninstallPrev?: boolean;\n spec?: string;\n /** Dismiss the migration banner for the current CLI version (persists in\n * install.json's `dismissedVersions`). Typically passed with `--list`. */\n dismiss?: boolean;\n}\n\nexport interface MigrationPlan {\n detected: DetectResult[];\n currentMethod: string;\n targetSpec: string;\n installedVersion: string | null;\n shouldMigrate: boolean;\n isDowngrade: boolean;\n /**\n * I2 — `update.minVersion` floor (config.ts:238). True when the resolved\n * target is a concrete version below the configured floor. Channel targets\n * (`latest`/`beta`) resolve to the newest at install time and never trip it.\n * Enforced in {@link install} with the same warn/refuse pattern as the\n * downgrade guard.\n */\n belowMinVersion: boolean;\n nativeVersion: string;\n prevUninstallCmd: string | null;\n autoUninstall: boolean; // always false unless --uninstall-prev\n}\n\n/** Per-segment numeric semver comparison. Returns true when `a < b`. */\nfunction semverLt(a: string, b: string): boolean {\n // Strip a leading `v` (npm-idiomatic exact-version spelling that\n // installManagedNode explicitly supports) — bare Number('v1.9.0') is NaN → 0.\n const seg = (s: string): number => Number(s.replace(/^v/, '')) || 0;\n const pa = a.split('.').map(seg);\n const pb = b.split('.').map(seg);\n for (let i = 0; i < 3; i++) {\n const x = pa[i] ?? 0;\n const y = pb[i] ?? 0;\n if (x !== y) return x < y;\n }\n return false;\n}\n\n/** Pure: build the migration plan from detection + target. No I/O. */\nexport function buildMigrationPlan(opts: {\n detected: DetectResult[];\n currentMethod: string;\n targetSpec: string;\n installedVersion: string | null;\n /**\n * The `update.minVersion` floor from config (default `'1.6.0'`). When\n * omitted, the config default applies. See I2.\n */\n minVersion?: string;\n}): MigrationPlan {\n const detected = opts.detected;\n const shouldMigrate = detected.length > 0 && opts.currentMethod !== 'native';\n const nativeVersion = opts.targetSpec;\n let isDowngrade = false;\n if (opts.installedVersion && nativeVersion !== 'latest' && nativeVersion !== 'beta') {\n isDowngrade = semverLt(nativeVersion, opts.installedVersion);\n }\n // I2 — minVersion floor: only a concrete version below the floor trips it;\n // 'latest'/'beta' resolve to the newest at install time (always >= floor).\n const floor = opts.minVersion ?? '1.6.0';\n const belowMinVersion =\n nativeVersion !== 'latest' && nativeVersion !== 'beta' && semverLt(nativeVersion, floor);\n return {\n detected,\n currentMethod: opts.currentMethod,\n targetSpec: opts.targetSpec,\n installedVersion: opts.installedVersion,\n shouldMigrate,\n isDowngrade,\n belowMinVersion,\n nativeVersion,\n prevUninstallCmd: detected[0]?.uninstallCmd ?? null,\n autoUninstall: false,\n };\n}\n\nexport interface InstallManagedNodeOptions {\n channel?: string;\n version?: string;\n env: NodeJS.ProcessEnv;\n /**\n * Forwarded into {@link provisionManagedNode} (P1). Carries the offline mock\n * seams (`fetch`, `exec`, `target`, `signal`) so the install pipeline is\n * unit-testable without network — same pattern as `provisionManagedNode`'s\n * own suite in `packages/core/test/node-provision.test.ts`.\n */\n provision?: Pick<ProvisionOptions, 'fetch' | 'exec' | 'target' | 'signal'>;\n}\n\n/** Normalized record of which runtime backed an install (P2). */\nexport interface InstallManagedNodeResult {\n ok: boolean;\n version: string | null;\n /** What runtime backed this install: `'managed'` (downloaded into ~/.noir)\n * or `'system'` (used the system-Node fallback). */\n runtimeSource?: ProvisionedNode['source'];\n error?: string;\n}\n\n/**\n * Provision the managed Node runtime (P1) and install `@noir-ai/cli` into an\n * isolated `~/.noir/cli` prefix, shimmed from `~/.noir/bin/noir`.\n *\n * Replaces the prior \"not provisioned\" fail branch: instead of requiring the\n * user to pre-run `install.sh`/`install.ps1`, this now delegates to\n * {@link provisionManagedNode} (download + verify + extract, fail-closed), then\n * installs the CLI with the provisioned npm. On a system-Node fallback the\n * fallback node/npm paths are used and a warning is emitted; the install\n * record's `managedRuntimeVersion` reflects which path was taken so `noir\n * doctor` can report it accurately.\n */\nexport async function installManagedNode(\n opts: InstallManagedNodeOptions,\n): Promise<InstallManagedNodeResult> {\n const spec = opts.version ?? opts.channel ?? 'latest';\n const home = noirHome();\n const cliDir = join(home, 'cli');\n const binDir = join(home, 'bin');\n mkdirSync(cliDir, { recursive: true });\n mkdirSync(binDir, { recursive: true });\n\n // Provision the managed Node runtime (P1). Idempotent — re-runs are no-ops.\n // On any failure, provisionManagedNode either falls back to a system Node\n // >=22 (returns { source: 'system' }) or throws — we surface the throw as an\n // {ok:false} envelope rather than letting it kill the CLI.\n let runtime: ProvisionedNode;\n try {\n runtime = await provisionManagedNode({\n ...(opts.provision ?? {}),\n env: opts.env,\n });\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n return { ok: false, version: null, error: `managed-Node provision failed: ${reason}` };\n }\n if (runtime.source === 'system') {\n warn(\n `managed-Node download failed; using system Node ${runtime.version} at ${runtime.nodeBin}.`,\n );\n }\n\n const { nodeBin, npmBin } = runtime;\n\n // Install @noir-ai/cli into the isolated prefix using the provisioned npm.\n const { code, stderr } = await runManagerCmd(\n [npmBin, 'install', '-g', `@noir-ai/cli@${spec}`, `--prefix=${cliDir}`],\n { env: opts.env, timeoutMs: 120_000 },\n );\n if (code !== 0) {\n return { ok: false, version: null, error: `npm install failed: ${stderr.slice(0, 300)}` };\n }\n\n // Shim: ~/.noir/bin/noir -> provisioned node + isolated prefix.\n const shim = join(binDir, 'noir');\n const shimBody = `#!/usr/bin/env bash\\n\"${nodeBin}\" \"${join(cliDir, 'lib', 'node_modules', '@noir-ai', 'cli', 'dist', 'bin.js')}\" \"$@\"\\n`;\n atomicWriteFile(shim, shimBody);\n chmodSync(shim, 0o755); // must be executable — atomicWriteFile sets 0o644 by default\n ensureShimExecutable(); // defense-in-depth: re-assert 0o755 (idempotent, never throws)\n // (POSIX shim; Windows uses a .cmd wrapper -- install.sh/install.ps1, P3.)\n\n // Resolve installed version via the provisioned node.\n const ver = await runManagerCmd(\n [\n nodeBin,\n join(cliDir, 'lib', 'node_modules', '@noir-ai', 'cli', 'dist', 'bin.js'),\n '--version',\n ],\n { env: opts.env, timeoutMs: 30_000 },\n );\n const version = ver.code === 0 ? ver.stdout.trim() : null;\n writeInstallRecord({\n method: 'native',\n version: version ?? '0.0.0',\n channel: opts.channel ?? 'latest',\n installedAt: new Date().toISOString(),\n // Reflect which runtime backs this install: the pinned managed version\n // (e.g. '22.23.2') for managed, or 'system-<v>' for the fallback so doctor\n // can distinguish a fallback install from a real managed one.\n managedRuntimeVersion:\n runtime.source === 'managed' ? MANAGED_NODE_VERSION : `system-${runtime.version}`,\n });\n return { ok: true, version, runtimeSource: runtime.source };\n}\n\nexport async function install(opts: InstallOptions = {}): Promise<void> {\n // --dismiss: append the current CLI version to install.json's\n // `dismissedVersions` so the migration banner stops showing for this\n // version. Idempotent (no duplicate entries). Typically combined with\n // `--list`; works standalone too. Requires an existing install record —\n // if there's none, there's nothing to dismiss (no banner was shown).\n // Track the dismiss outcome so `--dismiss --list --json` folds it into the\n // SINGLE list envelope — never two JSON documents on stdout.\n let dismissOutcome: { dismissed: boolean; version: string | null } | undefined;\n if (opts.dismiss === true) {\n const rec = readInstallRecord();\n if (!rec) {\n dismissOutcome = { dismissed: false, version: null };\n if (!opts.json) info('No install record found; nothing to dismiss.');\n } else {\n const dismissed = new Set(rec.dismissedVersions ?? []);\n if (!dismissed.has(NOIR_VERSION)) dismissed.add(NOIR_VERSION);\n writeInstallRecord({ ...rec, dismissedVersions: [...dismissed] });\n dismissOutcome = { dismissed: true, version: NOIR_VERSION };\n if (!opts.json) success(`Migration banner dismissed for ${NOIR_VERSION}.`);\n }\n }\n\n // --dismiss alone (no --list): emit the single result now.\n if (opts.dismiss === true && opts.list !== true) {\n if (opts.json) process.stdout.write(`${JSON.stringify({ ok: true, data: dismissOutcome })}\\n`);\n return;\n }\n\n if (opts.list === true) {\n const detected = await detectInstallMethods(process.env);\n if (opts.json) {\n process.stdout.write(\n `${JSON.stringify({ ok: true, data: { ...(dismissOutcome ?? {}), detected } })}\\n`,\n );\n return;\n }\n info('Detected installs:');\n for (const d of detected) info(` ${d.method} (${d.version ?? 'unknown'})`);\n return;\n }\n\n const currentMethod = detectActiveMethod();\n const detected = await detectInstallMethods(process.env);\n const installedRecord = readInstallRecord();\n // I2 — read the `update.minVersion` floor from the project config (falls\n // back to the config default '1.6.0' when the project isn't initialized or\n // the block is absent). Same try/catch pattern as home.ts's update-config read.\n let minVersion = '1.6.0';\n try {\n minVersion = loadProjectInfo(process.cwd()).config.update.minVersion;\n } catch {\n // Not initialized yet — the config default applies.\n }\n const plan = buildMigrationPlan({\n detected,\n currentMethod,\n targetSpec: opts.spec ?? 'latest',\n installedVersion: installedRecord?.version ?? null,\n minVersion,\n });\n\n // I2 — refuse to install a version below the configured minVersion floor, and\n // refuse a downgrade, UNCONDITIONALLY. The floor/downgrade-guard exist as\n // safety invariants; gating them on `--no-input` meant the common interactive\n // path (and `--json`, which does not set input:false) silently proceeded with\n // only a stderr warning — the invariant was never actually enforced.\n if (plan.belowMinVersion) {\n fail(\n EXIT.USAGE,\n `refusing install of ${plan.nativeVersion} (below minVersion ${minVersion})`,\n opts,\n );\n }\n\n if (plan.isDowngrade) {\n fail(\n EXIT.USAGE,\n `refusing downgrade to ${plan.nativeVersion} (installed ${plan.installedVersion})`,\n opts,\n );\n }\n\n const installSpin = spinner('Installing Noir via native installer...', opts).start();\n const result = await installManagedNode({\n channel: plan.nativeVersion === 'beta' ? 'beta' : undefined,\n version: plan.nativeVersion.startsWith('v')\n ? plan.nativeVersion\n : plan.nativeVersion === 'latest' || plan.nativeVersion === 'beta'\n ? undefined\n : plan.nativeVersion,\n env: process.env,\n });\n if (!result.ok) {\n installSpin.fail('Native install failed');\n fail(EXIT.ERROR, result.error ?? 'native install failed', opts);\n }\n installSpin.succeed(`Installed noir ${result.version}`);\n\n // Migration step runs FIRST, in BOTH modes — the --json early-return below\n // must not skip it (a scripted/CI migration relying on --uninstall-prev would\n // otherwise silently leave the stale install in place while reporting success).\n // Its diagnostics go to stderr (warn/success), which --json leaves untouched.\n if (plan.prevUninstallCmd && opts.uninstallPrev !== true) {\n warn(`To finish the migration, uninstall the previous install: ${plan.prevUninstallCmd}`);\n warn(\n ' Or re-run with --uninstall-prev to do it now. Rollback: reinstall via the previous manager.',\n );\n } else if (plan.prevUninstallCmd && opts.uninstallPrev === true) {\n const { code } = await runManagerCmd(plan.prevUninstallCmd.split(' '), { env: process.env });\n if (code === 0) success('Previous install removed.');\n else warn('Previous install NOT removed (non-zero exit). You can remove it manually.');\n }\n\n // S9 --json: emit the canonical {ok:true,data} success envelope on stdout.\n if (opts.json === true) {\n process.stdout.write(\n `${JSON.stringify({ ok: true, data: { version: result.version, runtimeSource: result.runtimeSource } })}\\n`,\n );\n return;\n }\n\n info(`Installed native: ${result.version}.`);\n // Claude Code #41806/#27910 mitigation: the old npm bin may still resolve\n // first in $PATH after migration (the shell hashes command paths, and a\n // stale global install can shadow ~/.noir/bin). Hint the user to refresh.\n info('If `noir` still points at the old install, run: hash -r && which -a noir');\n success(`noir ${result.version} installed via native. Run \\`noir doctor\\` to verify.`);\n}\n","// S9 t2 — centralized output + exit-code infrastructure for `@noir-ai/cli`.\n//\n// Stream discipline (S9): machine-readable data → stdout; every other\n// diagnostic (progress, warnings, errors, tables, help) → stderr. Under `--json`\n// the decorated stderr helpers are silenced so stdout stays pristine JSON, and\n// `fail()` emits a structured `{ok:false,error}` envelope to stdout instead.\n//\n// Decoration (picocolors / cli-table3 / ora) auto-disables under `--json`,\n// `--quiet`, `CI`, `NO_COLOR`, or a non-TTY, so the same code path is safe in an\n// interactive shell, a pipe, and CI.\n//\n// Exit codes (S9): 0 ok · 1 error · 2 usage · 3 not-found · 4 daemon-down ·\n// 5 cancelled. These constants live HERE (the single source of truth); bin.ts\n// re-exports them so existing imports from `./bin.js` keep working.\n\nimport Table from 'cli-table3';\nimport { CommanderError } from 'commander';\nimport ora, { type Ora } from 'ora';\nimport { c, isCiEnv, terminalWidth } from './theme.js';\n\n// ---------------------------------------------------------------------------\n// Exit-code contract + error types\n// ---------------------------------------------------------------------------\nexport const EXIT = {\n OK: 0,\n ERROR: 1,\n USAGE: 2,\n NOT_FOUND: 3,\n DAEMON_DOWN: 4,\n CANCELLED: 5,\n} as const;\n\nexport type ExitCode = (typeof EXIT)[keyof typeof EXIT];\n\n/**\n * Application-level CLI error carrying an S9 exit code. Thrown directly by\n * bin.ts for legacy usage paths; `fail()` is the ergonomic throw helper.\n * `inferExitCode` / `handleError` map it onto `process.exitCode`.\n */\nexport class NoirCliError extends Error {\n readonly exitCode: number;\n constructor(exitCode: number, message: string) {\n super(message);\n this.name = 'NoirCliError';\n this.exitCode = exitCode;\n }\n}\n\n/**\n * The `code` stamped onto every CommanderError our own `fail()` throws, so\n * `inferExitCode` can tell \"we threw this on purpose with an authoritative\n * exitCode\" apart from \"commander threw this for help/usage/unknown-command\"\n * (which must be remapped onto the S9 contract by `commanderExitCode`).\n */\nconst NOIR_ERROR_CODE = 'noir.error';\n\n// ---------------------------------------------------------------------------\n// Global-option shape. Commander stores `--no-input` under the attribute\n// `input` (default `true`; the flag sets it `false`) — see commander's\n// `Option.attributeName()` which strips the leading `no-`. Both `input`\n// (commander's real key) and `noInput` (the spec's intent) are accepted so\n// callers may pass either commander's raw globals or a hand-built object.\n// ---------------------------------------------------------------------------\nexport interface CliOptions {\n readonly json?: boolean;\n readonly quiet?: boolean;\n readonly verbose?: boolean;\n /** Commander's storage for `--no-input` (`false` ⇒ no input). */\n readonly input?: boolean;\n /** Convenience alias matching the `--no-input` intent (`true` ⇒ no input). */\n readonly noInput?: boolean;\n /** TUI-policy flag. `false` (`--no-tui`) forces bare `noir` onto the\n * non-interactive `status` path even in a TTY; `true` (`--tui`) is an\n * advisory hint that still requires a TTY; absent (auto) defers to\n * {@link isInteractive}. Advisory only — never hard-gates a subcommand. */\n readonly tui?: boolean;\n /** Hint-suppression flag (`--no-tips`). When `true`, redirect /\n * deprecation hints (and any other {@link tip} output) are silenced for\n * CI / log-friendly runs. */\n readonly noTips?: boolean;\n}\n\nfunction isJsonMode(opts: CliOptions): boolean {\n return opts.json === true;\n}\n\nfunction isQuietMode(opts: CliOptions): boolean {\n return opts.quiet === true;\n}\n\n/** True when the user has asked for no interactive input (either spelling). */\nexport function isNoInput(opts: CliOptions = {}): boolean {\n return opts.noInput === true || opts.input === false;\n}\n\nfunction envFlagSet(name: string): boolean {\n // NO_COLOR spec: present AND non-empty (regardless of value) disables color.\n const v = process.env[name];\n return v !== undefined && v !== '';\n}\n\n// `isCiEnv` is imported from ./theme.js (the color authority) — see note there.\n\n// ---------------------------------------------------------------------------\n// Interactivity gate. Drives both the @clack home menu and the\n// decoration of every helper below. Requires BOTH stdin and stdout to be TTYs:\n// @clack reads keypresses from stdin while ora/picocolors render to stdout.\n// ---------------------------------------------------------------------------\nexport function isInteractive(opts: CliOptions = {}): boolean {\n if (isJsonMode(opts) || isNoInput(opts)) return false;\n if (isCiEnv() || envFlagSet('NO_COLOR')) return false;\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n}\n\n/**\n * Derive the explicit `interactive` flag the scaffold engine reads (via\n * {@link ScaffoldOptions.interactive}) from the bridge bin.ts owns. The bin's\n * `preAction` sets `NOIR_NON_INTERACTIVE=1` under `--json`/`--no-input`; this\n * helper folds that with the {@link isInteractive} TTY/CI/NO_COLOR gate so a\n * single boolean flows into the engine + `buildConflictOpts`. The ENGINE itself\n * never reads `process.env` for interactivity — only this CLI helper does.\n */\nexport function resolveInteractive(): boolean {\n if (flaggedNonInteractiveCli()) return false;\n return isInteractive();\n}\n\n/** True when the bin's `preAction` flagged this invocation non-interactive\n * (`--json` / `--no-input` → `NOIR_NON_INTERACTIVE`). CLI-internal; the engine\n * reads {@link ScaffoldOptions.interactive} instead. */\nfunction flaggedNonInteractiveCli(): boolean {\n const v = process.env.NOIR_NON_INTERACTIVE;\n return v !== undefined && v !== '';\n}\n\n// ---------------------------------------------------------------------------\n// Data output (stdout). The ONLY helper here that writes to stdout; under\n// `--json` a command calls this exactly once with its full payload and never\n// mixes other stdout writes, keeping machine output pristine.\n// ---------------------------------------------------------------------------\nexport function json(obj: unknown): void {\n process.stdout.write(`${JSON.stringify(obj)}\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Human diagnostics (stderr). picocolors auto-strips ANSI under NO_COLOR /\n// non-TTY, and each helper additionally short-circuits under `--json`\n// (decoration off, payload is the output) — and `info`/`log`/`success` also\n// under `--quiet`. `warn`/`error` survive `--quiet` (they carry signal).\n// ---------------------------------------------------------------------------\nexport function log(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || isQuietMode(opts)) return;\n process.stderr.write(`${msg}\\n`);\n}\n\nexport function info(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || isQuietMode(opts)) return;\n process.stderr.write(`${c.info(msg)}\\n`);\n}\n\nexport function success(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || isQuietMode(opts)) return;\n process.stderr.write(`${c.ok(msg)}\\n`);\n}\n\nexport function warn(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts)) return;\n process.stderr.write(`${c.warn(msg)}\\n`);\n}\n\nexport function error(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts)) return;\n process.stderr.write(`${c.error(msg)}\\n`);\n}\n\n/**\n * Redirect / deprecation hint (stderr). Silenced under `--json` (a CI\n * consumer's stdout envelope must stay pristine) AND under `--no-tips`\n * ({@link CliOptions.noTips}). This is the ONLY helper deprecation / redirect\n * notices should use, so a single `--no-tips` flag quiets them all in CI / logs.\n * Style matches {@link warn} (these are advisory, not errors).\n */\nexport function tip(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || opts.noTips === true) return;\n process.stderr.write(`${c.warn(msg)}\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Tables (stderr). Suppressed entirely under `--json` — the command has\n// already emitted the rows as a JSON array via `json()`.\n//\n// Design: cli-table3 styles headers/borders via `@colors/colors`\n// (whose NO_COLOR/TTY semantics differ from picocolors), which painted EVERY\n// header red while the body stripped — RED is now reserved strictly for ERROR.\n// We bypass `@colors/colors` entirely by passing EMPTY `style.head`/`style.border`\n// arrays (cli-table3's `wrapWithStyleColors` early-returns on length 0) and\n// PRE-COLOR the header strings ourselves via `theme.c` (picocolors). Result:\n// the header, border, and body all strip consistently under NO_COLOR / non-TTY,\n// because picocolors is the SOLE color authority. Responsive `colWidths` are\n// computed from `terminalWidth()` so tables never overflow; `wordWrap` keeps\n// free-text cells readable, and `truncate: '…'` caps any single over-long token.\n// ---------------------------------------------------------------------------\nexport function table(\n rows: readonly Record<string, unknown>[],\n cols: readonly string[],\n opts: CliOptions = {},\n): void {\n if (isJsonMode(opts)) return;\n if (rows.length === 0) {\n info('(no rows)', opts);\n return;\n }\n const t = new Table({\n head: cols.map((col) => c.bold(c.info(col))),\n // Empty arrays ⇒ cli-table3 applies NO @colors/colors wrap (neither the\n // default red header nor a colored border). All color is ours, via theme.\n style: { head: [], border: [] },\n colWidths: computeColWidths(rows, cols),\n wordWrap: true,\n truncate: '…',\n });\n for (const row of rows) {\n t.push(cols.map((col) => formatCell(row[col])));\n }\n process.stderr.write(`${t.toString()}\\n`);\n}\n\n/**\n * Compute per-column widths (cli-table3 `colWidths`, which INCLUDE each column's\n * 2 padding chars) that fit `terminalWidth()`. Strategy:\n *\n * 1. Measure each column's NATURAL content width (longest cell, at least the\n * header label so a header never wraps/truncates).\n * 2. If the natural total fits, use it as-is — every cell renders whole.\n * 3. Otherwise GREEDILY TRIM ONLY THE WIDEST column (down to its header width)\n * until the row fits. Narrow columns (paths, ids, statuses) keep their full\n * width — they're single tokens that can't word-wrap, so truncating them\n * loses information. The widest column is the free-text one (descriptions,\n * details, snippets); it has spaces and absorbs the shrink via `wordWrap`.\n *\n * cli-table3 accepts only positive integers (negatives collapse). Each returned\n * width is `content + 2` to reserve the column's padding, so the content area\n * equals the measured width and the math against `terminalWidth()` is exact.\n */\nfunction computeColWidths(\n rows: readonly Record<string, unknown>[],\n cols: readonly string[],\n): number[] {\n const n = cols.length;\n if (n === 0) return [];\n const headerLen = cols.map((col) => col.length);\n const natural = cols.map((col, i) => {\n let max = Math.max(headerLen[i] ?? col.length, 3);\n for (const row of rows) {\n const len = formatCell(row[col]).length;\n if (len > max) max = len;\n }\n return max;\n });\n // cli-table3 row overhead: per column 1 left border + 2 padding, plus a final\n // right border ⇒ 3n + 1 non-content chars. `contentBudget` is what remains for\n // the sum of column CONTENT areas.\n const contentBudget = Math.max(n * 4, terminalWidth() - 3 * n - 1);\n const naturalSum = natural.reduce((a, b) => a + b, 0);\n if (naturalSum <= contentBudget) {\n return natural.map((w) => w + 2);\n }\n // Overflow: greedily cut the widest reducible column (≥ its header width).\n const content = natural.slice();\n let sum = naturalSum;\n const floor = headerLen.map((h) => Math.max(h, 3));\n while (sum > contentBudget) {\n let idx = -1;\n let widest = -1;\n for (let i = 0; i < n; i++) {\n const c = content[i] ?? 0;\n const f = floor[i] ?? 3;\n if (c > f && c > widest) {\n widest = c;\n idx = i;\n }\n }\n if (idx === -1) break; // every column is at its header floor — stop\n const overshoot = sum - contentBudget;\n const reducible = (content[idx] ?? 0) - (floor[idx] ?? 3);\n const cut = Math.min(overshoot, reducible);\n content[idx] = (content[idx] ?? 0) - cut;\n sum -= cut;\n }\n return content.map((w) => w + 2);\n}\n\nfunction formatCell(value: unknown): string {\n if (value === undefined || value === null) return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value);\n }\n return JSON.stringify(value);\n}\n\n// ---------------------------------------------------------------------------\n// Definition list (stderr). A two-column Field/Value rendering for snapshot-\n// shaped output (status / task / context-status). `status.ts` hand-rolled this\n// exact shape; this generalizes it. Rendered via the same responsive `table()`,\n// so it inherits the clean (non-red) header + NO_COLOR behavior automatically.\n// ---------------------------------------------------------------------------\nexport interface DefinitionRow {\n /** Field label (left column). */\n label: string;\n /** Field value; coerced to a string the same way table cells are. */\n value: unknown;\n}\n\nexport function definitionList(rows: readonly DefinitionRow[], opts: CliOptions = {}): void {\n table(\n rows.map((r) => ({ Field: r.label, Value: formatCell(r.value) })),\n ['Field', 'Value'],\n opts,\n );\n}\n\n/** Render a single `label: value` line to stderr (human only). */\nexport function kv(label: string, value: unknown, opts: CliOptions = {}): void {\n log(`${c.bold(label)}: ${formatCell(value)}`, opts);\n}\n\n// ---------------------------------------------------------------------------\n// Spinner (stderr). ora when interactive; a no-op otherwise so scriptable /\n// CI / `--json` / `--quiet` runs pay nothing and never animate a pipe.\n// ---------------------------------------------------------------------------\nexport interface Spinner {\n start(text?: string): Spinner;\n stop(): Spinner;\n clear(): Spinner;\n succeed(text?: string): void;\n fail(text?: string): void;\n warn(text?: string): void;\n info(text?: string): void;\n text: string;\n}\n\nclass NoopSpinner implements Spinner {\n text = '';\n start(_text?: string): Spinner {\n return this;\n }\n stop(): Spinner {\n return this;\n }\n clear(): Spinner {\n return this;\n }\n succeed(_text?: string): void {}\n fail(_text?: string): void {}\n warn(_text?: string): void {}\n info(_text?: string): void {}\n}\n\n/** Wraps an `ora` instance so the `Spinner` interface stays decoupled from ora's exact type. */\nclass OraSpinner implements Spinner {\n private readonly handle: Ora;\n\n constructor(initialText: string) {\n this.handle = ora(initialText);\n }\n\n get text(): string {\n return this.handle.text;\n }\n set text(value: string) {\n this.handle.text = value;\n }\n\n start(text?: string): Spinner {\n if (text !== undefined) this.handle.text = text;\n this.handle.start();\n return this;\n }\n\n stop(): Spinner {\n this.handle.stop();\n return this;\n }\n\n clear(): Spinner {\n this.handle.clear();\n return this;\n }\n\n succeed(text?: string): void {\n this.handle.succeed(text);\n }\n\n fail(text?: string): void {\n this.handle.fail(text);\n }\n\n warn(text?: string): void {\n this.handle.warn(text);\n }\n\n info(text?: string): void {\n this.handle.info(text);\n }\n}\n\nexport function spinner(text = '', opts: CliOptions = {}): Spinner {\n if (isJsonMode(opts) || isQuietMode(opts) || !isInteractive(opts)) {\n return new NoopSpinner();\n }\n return new OraSpinner(text);\n}\n\n// ---------------------------------------------------------------------------\n// Failure + exit-code mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Write a diagnostic and throw a `CommanderError` carrying the S9 exit code,\n * so bin.ts's `exitOverride` + `handleError` surface it as `process.exitCode`\n * without any mid-action `process.exit`. Under `--json` the message becomes a\n * structured `{ok:false,error}` envelope on STDOUT; otherwise plain text on\n * STDERR. Always throws (`never`).\n */\nexport function fail(exitCode: number, message: string, opts: CliOptions = {}): never {\n if (isJsonMode(opts)) {\n process.stdout.write(`${JSON.stringify({ ok: false, error: { code: exitCode, message } })}\\n`);\n } else if (message.length > 0) {\n process.stderr.write(`${message}\\n`);\n }\n throw new CommanderError(exitCode, NOIR_ERROR_CODE, message);\n}\n\n/**\n * Guard for commands that would otherwise block on a prompt. Honors the S9\n * hard rule: \"no blocking prompts when !isTTY or --no-input — error exit 2\n * naming the missing flag instead\". `hint` describes the operation so the\n * message points the user at the flag-based alternative.\n */\nexport function requireInteractive(opts: CliOptions, hint: string): void {\n if (!isInteractive(opts)) {\n fail(\n EXIT.USAGE,\n `${hint} needs an interactive terminal, but input is disabled (non-TTY, --no-input, --json, CI, or NO_COLOR). Re-run in a TTY or supply the value as a flag.`,\n opts,\n );\n }\n}\n\n// Remap commander's own exit codes onto the S9 contract:\n// help/version → 0 · unknown command → 3 · every other usage error → 2\nfunction commanderExitCode(err: CommanderError): number {\n switch (err.code) {\n case 'commander.helpDisplayed':\n case 'commander.versionDisplayed':\n case 'commander.version': // commander v12 uses this code for --version (exit 0)\n return EXIT.OK;\n case 'commander.unknownCommand':\n return EXIT.NOT_FOUND;\n default:\n return EXIT.USAGE;\n }\n}\n\n/** Map any thrown value onto the S9 exit-code contract. */\nexport function inferExitCode(err: unknown): number {\n if (err instanceof NoirCliError) return err.exitCode;\n if (err instanceof CommanderError) {\n // Errors our own `fail()` threw carry a `noir.*` code and an authoritative\n // exitCode — respect it. Commander's own errors are remapped by `code`.\n if (err.code.startsWith('noir.')) return err.exitCode;\n return commanderExitCode(err);\n }\n return EXIT.ERROR;\n}\n\n/**\n * Map a thrown error onto stderr + `process.exitCode`. Never throws, never\n * calls `process.exit` (commander's `exitOverride` already prevented that for\n * commander's own errors). Used by the bin entry's `run()`.\n */\nexport function handleError(err: unknown): void {\n if (err instanceof NoirCliError) {\n if (err.message.length > 0) process.stderr.write(`${err.message}\\n`);\n } else if (err instanceof CommanderError) {\n // If `fail()` threw it, the message is already written (stderr or the JSON\n // stdout envelope); if commander threw it, `configureOutput.writeErr`\n // already wrote it during parse. Don't double-write either way.\n } else {\n process.stderr.write(`noir: ${err instanceof Error ? err.message : String(err)}\\n`);\n }\n process.exitCode = inferExitCode(err);\n}\n","// Single owner of the Noir CLI's semantic palette + status badges.\n//\n// Built on `picocolors` (the SAME lib the rest of the CLI already uses), so the\n// decoration strips consistently under NO_COLOR / non-TTY. cli-table3 styles\n// via a SECOND lib (@colors/colors) whose NO_COLOR/TTY semantics differ — that\n// split is what painted every table header red while the body stripped. Routing\n// ALL color through this module (the `c` object + `badge()`) closes that leak:\n// there is exactly one color authority, and it honors NO_COLOR / CLICOLOR_FORCE\n// / TTY uniformly.\n//\n// Accessibility:\n// - `badge()` ALWAYS returns SYMBOL + TEXT LABEL (e.g. `⚠ warn`), so NO_COLOR\n// and colorblind users get identical information. Color is decorative.\n// - `NOIR_ACCESSIBLE` disables the banner gradient (→ solid accent) and is the\n// documented opt-in for users who want the most legible form; badges already\n// carry symbol+text unconditionally, so the flag is a no-op for them.\n\nimport pc from 'picocolors';\n\n// A private always-on colorizer. The PUBLIC `c` object gates every call through\n// `useColor()`, so `useColor()` is the single live authority over whether ANY\n// ANSI leaves this module. (The default `picocolors` export captures its\n// `isColorSupported` snapshot once at module load and does NOT honor\n// CLICOLOR_FORCE; gating a forced instance here fixes both.)\nconst FORCE = pc.createColors(true);\n\n/** Badge state → semantic color role. */\nexport type BadgeState = 'ok' | 'warn' | 'error' | 'info';\n\n/**\n * The semantic palette. Each method returns its input unchanged when color is\n * off (NO_COLOR / non-TTY / not forced), so callers can wrap freely without\n * guarding. Compose with `c.bold(c.info(s))` etc.\n */\nexport const c = {\n /** Green — success / healthy. */\n ok: (s: string): string => (useColor() ? FORCE.green(s) : s),\n /** Yellow — degraded but usable. */\n warn: (s: string): string => (useColor() ? FORCE.yellow(s) : s),\n /** Red — reserved strictly for ERROR. Never a table header. */\n error: (s: string): string => (useColor() ? FORCE.red(s) : s),\n /** Cyan — informational / brand-neutral accent for labels. */\n info: (s: string): string => (useColor() ? FORCE.cyan(s) : s),\n /** Blue — the Midnight Cobalt brand role (banner / accent). */\n accent: (s: string): string => (useColor() ? FORCE.blue(s) : s),\n /** Dim — secondary text, table borders when color is on. */\n dim: (s: string): string => (useColor() ? FORCE.dim(s) : s),\n /** Bold — emphasis (table headers compose this with `info`). */\n bold: (s: string): string => (useColor() ? FORCE.bold(s) : s),\n /** Reverse video (SGR 7) — inverted background for the active list row. */\n inverse: (s: string): string => (useColor() ? FORCE.inverse(s) : s),\n};\n\n// @clack-style status symbols (the vocabulary Noir inherits via @clack/prompts\n// + ora). Paired 1:1 with a state name so a screen reader / pipe / colorblind\n// user reads the same fact a sighted interactive user sees.\nconst BADGE_SYMBOL: Readonly<Record<BadgeState, string>> = {\n ok: '✓',\n warn: '⚠',\n error: '✗',\n info: 'ℹ',\n};\n\n/**\n * Render a status badge: always `SYMBOL LABEL`, colored by `state` when color\n * is on. The TEXT LABEL is unconditional — this is the accessibility invariant.\n * `label` defaults to the state name, so `badge('ok')` reads `✓ ok`.\n *\n * @example badge('ok') // → green \"✓ ok\"\n * @example badge('warn','degraded') // → yellow \"⚠ degraded\"\n * @example badge('error','down') // → red \"✗ down\"\n */\nexport function badge(state: BadgeState, label: string = state): string {\n const text = `${BADGE_SYMBOL[state]} ${label}`;\n switch (state) {\n case 'ok':\n return c.ok(text);\n case 'warn':\n return c.warn(text);\n case 'error':\n return c.error(text);\n case 'info':\n return c.info(text);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Environment gates. Evaluated LIVE (not captured at module load) so tests that\n// flip NO_COLOR / CLICOLOR_FORCE between cases see the right answer, and so a\n// user exporting NO_COLOR mid-session is honored on the next render.\n// ---------------------------------------------------------------------------\n\nfunction envFlagSet(name: string): boolean {\n // NO_COLOR spec: present AND non-empty disables color (any value).\n const v = process.env[name];\n return v !== undefined && v !== '';\n}\n\n/**\n * True when running under a CI runner (GitHub Actions / GitLab / CircleCI /\n * Drone set `CI=true` at the conventional value). Treats the explicit opt-outs\n * `0` / `false` as \"not CI\" so a user can force-disable the guard. Owned here\n * (the color authority) and re-exported so output.ts doesn't duplicate it.\n */\nexport function isCiEnv(): boolean {\n const v = process.env.CI;\n if (v === undefined || v === '') return false;\n return v !== '0' && v !== 'false';\n}\n\n/**\n * The single authority on whether decoration should emit ANSI. Honors:\n * - `NO_COLOR` → always off (spec: present + non-empty).\n * - `CLICOLOR_FORCE=1` → always on (forces color on a redirected stream).\n * - `CI=true` (alone) → always OFF. picocolors' `isColorSupported`\n * snapshot turns color ON under `!!env.CI` (it\n * assumes a CI viewer that renders ANSI). But ANSI\n * escapes inflate the raw byte length of rendered\n * lines, which breaks the responsive-width\n * guarantee `max(line.length) <= terminalWidth()`\n * that `table()` exists to uphold — and the\n * regression test that locks it (fits 60/80/120 in\n * theme.test.ts). The CLI's own top-of-file\n * contract states decoration auto-disables under\n * CI / non-TTY; this honors that. A CI viewer that\n * wants color sets `CLICOLOR_FORCE=1` (above).\n * - otherwise → picocolors' detection (TTY / FORCE_COLOR).\n */\nexport function useColor(): boolean {\n if (envFlagSet('NO_COLOR')) return false;\n if (process.env.CLICOLOR_FORCE === '1') return true;\n if (isCiEnv()) return false;\n return pc.isColorSupported;\n}\n\n/**\n * `NOIR_ACCESSIBLE`: opt-in maximum legibility. Badges already carry symbol+\n * text unconditionally (so the flag is a no-op for them); the material effect\n * is disabling the banner gradient (→ solid accent) and is reserved here for\n * future accessible-display tweaks.\n */\nexport function accessibleMode(): boolean {\n return envFlagSet('NOIR_ACCESSIBLE');\n}\n\n/**\n * Terminal width in columns. Honors `COLUMNS` (the conventional override) then\n * `process.stdout.columns`, defaulting to 80 when neither is set (the same\n * default `picocolors` / `cli-table3` assume). Floored at a small minimum so a\n * misconfigured shell never produces a degenerate zero/negative-width table.\n */\nexport function terminalWidth(): number {\n const fromEnv = process.env.COLUMNS;\n if (fromEnv !== undefined && fromEnv !== '') {\n const n = Number(fromEnv);\n if (Number.isInteger(n) && n > 0) return Math.max(n, 20);\n }\n const cols = process.stdout.columns;\n if (typeof cols === 'number' && cols > 0) return Math.max(cols, 20);\n return 80;\n}\n\n/**\n * Usable content width inside a bordered TUI panel. Ink's `borderStyle` adds a\n * 1-char border on each side, and `paddingX={1}` adds 1 char inside that — so a\n * full-width rounded panel has `terminalWidth - 4` columns of usable text.\n * Components that truncate long lines (OutputPane) MUST use this, not\n * {@link terminalWidth}, or content overflows the border.\n *\n * @param border chars consumed by the border (default 2 = left + right).\n * @param padding chars consumed by inner padding (default 2 = left + right).\n */\nexport function contentWidth(border = 2, padding = 2): number {\n return Math.max(20, terminalWidth() - border - padding);\n}\n\n/**\n * A horizontal divider line of `─` sized for the current content width. Used\n * inside bordered panels to separate regions (e.g. status bar ↔ output ↔ input)\n * without nesting Ink borders. Always dim so it reads as structure, not content.\n */\nexport function divider(): string {\n return c.dim('─'.repeat(Math.max(1, contentWidth())));\n}\n"],"mappings":"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","names":["envFlagSet","c","detected"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/create.ts"],"sourcesContent":["// `noir create [dir]` — greenfield first-run for the AI layer only.\n//\n// Slice S — new command. Bootstraps Noir's AI layer (.noir/ store, host\n// pointers, skills) into `dir` (created if absent), with NO external\n// scaffolder chaining (S-OQ1 = AI-layer only; no `pnpm create` wrapping).\n//\n// Reuses init's transport/url handling (including the localhost security\n// gate) by calling into the same engine: `scaffold({mode:'create', root})`.\n// The only difference from `noir init` is the mode tag (which selects the\n// manifest's first-run subset + generates a fresh project id unconditionally)\n// and that the target dir is `resolve(dir ?? cwd)` rather than `process.cwd()`.\n//\n// S10 multi-host: gains `--host <id>` (default `'claude'`); the host drives\n// both scaffold emission + skills emission via `resolveAdapter(host)` —\n// shared with init through `emitHostSkills`. See `init.ts` for the matrix.\n//\n// Stream discipline matches init: diagnostics → stderr, nothing on stdout\n// (so `noir create --json` is safe by construction — there is no data payload).\n//\n// Exit codes: `assertTransportUrl` is called DIRECTLY (M2) so transport/url\n// errors propagate as plain `Error` → bin.ts's handleError → exit 1, EXACTLY\n// matching `noir init` for the same error class. (The S9 spec lists usage=2 for\n// a missing `--url`; neither `init` nor `create` implements that split today —\n// both report it as a plain error=1 — and the review asked for consistency\n// over a one-sided fix. Changing them together is a separate, contract-level\n// change.) The target dir is created by the engine's `create` mode (N1: the\n// duplicate mkdir that used to live here is gone — one attributable failure\n// point, in scaffold.ts).\n\nimport { resolve } from 'node:path';\nimport { type HostId, resolveAdapter } from '@noir-ai/adapters';\nimport { loadProjectInfo, type ProjectInfo } from '@noir-ai/core';\nimport { type ScaffoldResult, scaffold } from '@noir-ai/create';\nimport { type CompileTarget, emitSkillsToDir } from '@noir-ai/skills';\nimport { buildConflictOpts } from '../conflict.js';\nimport { checkWritePathDedup } from '../dedup-write.js';\nimport { assertTransportUrl, reportPlannedWrites } from '../init.js';\nimport { resolveInteractive } from '../output.js';\n\nexport interface CreateOptions {\n transport: 'stdio' | 'streamable-http';\n url?: string;\n /** S10 target host. Defaults to `'claude'`. */\n host?: HostId;\n /** SP-A: re-scaffold even if already initialized. */\n force?: boolean;\n /** F1: `--dry-run`/`--preview` — report the planned writes to stderr\n * without touching disk (incl. NOT creating the target dir). */\n dryRun?: boolean;\n /** F1: alias for `--dry-run`. Kept on the options bag so direct callers can\n * pass either spelling; the bin collapses both flags before dispatch. */\n preview?: boolean;\n}\n\n/**\n * Bootstrap Noir's AI layer in `dir`. Returns the {@link ScaffoldResult} (with\n * structured `conflicts[]` + any dedup records appended); the bin\n * emits it under `--json`. `undefined` when the already-initialized guard\n * short-circuited (a no-op).\n *\n * @param dir Target directory (created if absent). Defaults to `process.cwd()`.\n * @param opts Transport + URL + host (same semantics as `noir init`).\n */\nexport async function create(\n dir: string | undefined,\n opts: CreateOptions,\n): Promise<ScaffoldResult | undefined> {\n // M2: call assertTransportUrl directly so the SAME error class yields the SAME\n // exit code as `noir init` (plain Error → exit 1 for both missing-url and\n // non-localhost). The previous NoirCliError(EXIT.USAGE) wrapper made `create`\n // exit 2 where `init` exits 1.\n assertTransportUrl(opts);\n\n const root = resolve(dir ?? process.cwd());\n const host: HostId = opts.host ?? 'claude';\n // Derive interactive once (consistency with init/sync) so both scaffold\n // and skills emission see the same hermetic flag.\n const interactive = resolveInteractive();\n const conflictOpts = buildConflictOpts({ force: opts.force, interactive });\n // F1: --dry-run/--preview collapse to a single dryRun boolean. The engine\n // skips the target-dir mkdir + every write; skills emission + dedup are\n // skipped too (they would touch disk / load the embedder).\n const dryRun = opts.dryRun === true || opts.preview === true;\n\n const res = await scaffold({\n root,\n mode: 'create',\n host,\n transport: opts.transport,\n interactive,\n ...(opts.url !== undefined ? { url: opts.url } : {}),\n ...(opts.force === true ? { force: true } : {}),\n ...(dryRun ? { dryRun: true } : {}),\n ...conflictOpts,\n });\n // F1: dry-run reports the planned writes and stops BEFORE skills emission +\n // the \"created\" message — the target dir was NOT created, so nothing may\n // touch disk. (The engine's create-mode mkdir is gated on !dryRun.)\n if (dryRun) {\n reportPlannedWrites(res);\n return res;\n }\n // SP-A: a no-op (already-initialized guard) must not re-emit skills.\n if (res.noop) return res;\n\n // Skills are out-of-manifest by design — same composition as init.\n const adapter = resolveAdapter(host);\n const skillsDir = adapter.skillsDir?.({ root });\n if (skillsDir === undefined) {\n // N1: standardized wording — same phrase across init/sync/create so logs\n // grep uniformly. (Pre-N1 each command phrased this differently.)\n process.stderr.write(`host '${host}' has no skill emitter; skipping skills\\n`);\n } else {\n const target: CompileTarget = host;\n // Forward conflictOpts so an interactive `noir create` with a\n // conflicting skill emit consults the resolver (mirrors init/sync).\n type SkillEmitOpts = NonNullable<Parameters<typeof emitSkillsToDir>[1]>;\n const skillOpts: SkillEmitOpts = {\n includeIntegrations: true,\n target,\n conflictPolicy: conflictOpts.conflictPolicy,\n interactive,\n };\n if (conflictOpts.onConflict !== undefined) {\n skillOpts.onConflict = conflictOpts.onConflict as SkillEmitOpts['onConflict'];\n }\n const summary = await emitSkillsToDir(skillsDir, skillOpts);\n const relDir = skillsDir.replace(`${root}/`, '');\n process.stderr.write(\n `Emitted ${summary.emitted.length} Noir skills to ${relDir}/ (target: ${target}).\\n`,\n );\n }\n\n // Write-path semantic dedup. Greenfield create rarely has\n // existing host-context candidates (the target dir is fresh), so the fast\n // path fires; `--force` on a pre-existing Noir project reads the config and\n // runs the full dedup. Best-effort: a missing/corrupt config warn-skips.\n let projectInfo: ProjectInfo | undefined;\n try {\n projectInfo = loadProjectInfo(root);\n } catch {\n projectInfo = undefined;\n }\n const dedup = await checkWritePathDedup(root, res, { interactive, project: projectInfo });\n if (dedup.conflicts.length > 0) res.conflicts.push(...dedup.conflicts);\n\n process.stderr.write(`Noir created in ${root} (host: ${host}, transport: ${opts.transport}).\\n`);\n process.stderr.write('Next: cd into the new directory, then run `noir` to open the home menu.\\n');\n return res;\n}\n"],"mappings":";;;;;;;;;;;;AA6BA,SAAS,eAAe;AACxB,SAAsB,sBAAsB;AAC5C,SAAS,uBAAyC;AAClD,SAA8B,gBAAgB;AAC9C,SAA6B,uBAAuB;AA8BpD,eAAsB,OACpB,KACA,MACqC;AAKrC,qBAAmB,IAAI;AAEvB,QAAM,OAAO,QAAQ,OAAO,QAAQ,IAAI,CAAC;AACzC,QAAM,OAAe,KAAK,QAAQ;AAGlC,QAAM,cAAc,mBAAmB;AACvC,QAAM,eAAe,kBAAkB,EAAE,OAAO,KAAK,OAAO,YAAY,CAAC;AAIzE,QAAM,SAAS,KAAK,WAAW,QAAQ,KAAK,YAAY;AAExD,QAAM,MAAM,MAAM,SAAS;AAAA,IACzB;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA,WAAW,KAAK;AAAA,IAChB;AAAA,IACA,GAAI,KAAK,QAAQ,SAAY,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,IAClD,GAAI,KAAK,UAAU,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;AAAA,IAC7C,GAAI,SAAS,EAAE,QAAQ,KAAK,IAAI,CAAC;AAAA,IACjC,GAAG;AAAA,EACL,CAAC;AAID,MAAI,QAAQ;AACV,wBAAoB,GAAG;AACvB,WAAO;AAAA,EACT;AAEA,MAAI,IAAI,KAAM,QAAO;AAGrB,QAAM,UAAU,eAAe,IAAI;AACnC,QAAM,YAAY,QAAQ,YAAY,EAAE,KAAK,CAAC;AAC9C,MAAI,cAAc,QAAW;AAG3B,YAAQ,OAAO,MAAM,SAAS,IAAI;AAAA,CAA2C;AAAA,EAC/E,OAAO;AACL,UAAM,SAAwB;AAI9B,UAAM,YAA2B;AAAA,MAC/B,qBAAqB;AAAA,MACrB;AAAA,MACA,gBAAgB,aAAa;AAAA,MAC7B;AAAA,IACF;AACA,QAAI,aAAa,eAAe,QAAW;AACzC,gBAAU,aAAa,aAAa;AAAA,IACtC;AACA,UAAM,UAAU,MAAM,gBAAgB,WAAW,SAAS;AAC1D,UAAM,SAAS,UAAU,QAAQ,GAAG,IAAI,KAAK,EAAE;AAC/C,YAAQ,OAAO;AAAA,MACb,WAAW,QAAQ,QAAQ,MAAM,mBAAmB,MAAM,cAAc,MAAM;AAAA;AAAA,IAChF;AAAA,EACF;AAMA,MAAI;AACJ,MAAI;AACF,kBAAc,gBAAgB,IAAI;AAAA,EACpC,QAAQ;AACN,kBAAc;AAAA,EAChB;AACA,QAAM,QAAQ,MAAM,oBAAoB,MAAM,KAAK,EAAE,aAAa,SAAS,YAAY,CAAC;AACxF,MAAI,MAAM,UAAU,SAAS,EAAG,KAAI,UAAU,KAAK,GAAG,MAAM,SAAS;AAErE,UAAQ,OAAO,MAAM,mBAAmB,IAAI,WAAW,IAAI,gBAAgB,KAAK,SAAS;AAAA,CAAM;AAC/F,UAAQ,OAAO,MAAM,2EAA2E;AAChG,SAAO;AACT;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/sync.ts"],"sourcesContent":["// `noir sync` — re-emit the runtime subset of the scaffold manifest.\n//\n// Slice S-T2 refactor: delegates to `@noir-ai/create`'s `scaffold({mode:'sync'})`,\n// which emits ONLY the regenerate + managedBlock entries (the always-safe-to-\n// rewrite subset). Per the S-T2 contract notes, this ADOPTS THE ENGINE'S\n// SEMANTICS and retires the predecessor's ad-hoc behavior:\n//\n// - The engine re-emits `.mcp.json` (regenerate) + NOIR.md brief\n// (managedBlock) + CLAUDE.md context/rules blocks + ignore-file blocks.\n// The predecessor's sync re-emitted only CLAUDE.md blocks + ignores and\n// did NOT refresh `.mcp.json`/NOIR.md — the engine is a strict superset\n// here, so a transport/url change is now picked up by `noir sync`.\n// - The engine does NOT seed `.noir/rules/RULES.md` on sync (RULES.md is\n// skipIfExists, owned by init/create). The predecessor seeded it when\n// missing; init now owns all seeds. (Spec-aligned: \"sync re-emits\n// generated/managed content; init owns seeds.\")\n//\n// S10 multi-host: sync resolves the host from `.noir/config.yml` (persisted by\n// `noir init --host <id>` via the config seed's `host: {{host}}` literal) OR\n// from an explicit `--host <id>` override. The resolved adapter drives the\n// manifest + skills emission. Skills emission stays a core sync feature for\n// hosts with a `skillsDir` (claude → `.claude/skills/`; cursor → `.cursor/rules/`\n// as `.mdc`); gemini/agents-md/opencode have no skill concept and the step is\n// skipped. `scaffold({mode:'sync'})` throws when the project isn't initialized\n// (no .noir/project.id), matching the predecessor's `loadProjectInfo` gate.\n\nimport { existsSync } from 'node:fs';\nimport { type HostId, resolveAdapter } from '@noir-ai/adapters';\nimport { loadProjectInfo, paths } from '@noir-ai/core';\nimport { type ScaffoldResult, scaffold } from '@noir-ai/create';\nimport { type CompileTarget, emitSkillsToDir } from '@noir-ai/skills';\nimport { buildConflictOpts } from './conflict.js';\nimport { checkWritePathDedup } from './dedup-write.js';\nimport { reportPlannedWrites } from './init.js';\nimport { resolveInteractive } from './output.js';\n\nexport interface SyncOptions {\n /** S10 `--host <id>` override. When set, takes precedence over the\n * `.noir/config.yml` `host:` field. Useful for re-emitting under a\n * different host without re-init (advanced — the canonical host stays\n * whatever init wrote). */\n host?: HostId;\n /** SP-C: overwrite differing regenerated files without prompting (bypasses\n * the conflict menu). */\n force?: boolean;\n /** SP-D: three-way merge managed regions (preserve hand-edits inside\n * `<!-- noir:* -->` markers across a template update). DEFAULT TRUE;\n * `--merge` is now a no-op (kept for backward compatibility). */\n merge?: boolean;\n /** Opt OUT of managed-region merge (restore strip-replace). Exposed on\n * the bin as `--no-merge-regions`. When explicitly `false`, hand-edits\n * inside `<!-- noir:* -->` markers are discarded on a template upgrade. */\n mergeManagedRegions?: boolean;\n /** F1: `--dry-run`/`--preview` — report the planned writes to stderr\n * without touching disk. sync still requires a valid `.noir/project.id`\n * (the engine's \"not initialized\" gate fires regardless). */\n dryRun?: boolean;\n /** F1: alias for `--dry-run`. Kept on the options bag so direct callers can\n * pass either spelling; the bin collapses both flags before dispatch. */\n preview?: boolean;\n}\n\nexport async function sync(root: string, opts: SyncOptions = {}): Promise<ScaffoldResult> {\n const host = resolveSyncHost(root, opts);\n // The engine reads ScaffoldOptions.interactive (hermetic — never\n // process.env). The CLI derives it once from the bridge + TTY/CI/NO_COLOR gate.\n const interactive = resolveInteractive();\n const conflictOpts = buildConflictOpts({ force: opts.force, interactive });\n\n // Load project info once for both the dedup hook (embedder config)\n // and the existing config-host fallback. Best-effort: a missing/corrupt file\n // is left to scaffold's \"not initialized\" gate below.\n let projectInfo: ReturnType<typeof loadProjectInfo> | undefined;\n try {\n projectInfo = loadProjectInfo(root);\n } catch {\n projectInfo = undefined;\n }\n\n // F1: --dry-run/--preview collapse to a single dryRun boolean.\n const dryRun = opts.dryRun === true || opts.preview === true;\n\n const res = await scaffold({\n root,\n mode: 'sync',\n host,\n interactive,\n ...(dryRun ? { dryRun: true } : {}),\n ...conflictOpts,\n // Merge defaults TRUE inside scaffold; only forward an explicit opt-out.\n ...(opts.mergeManagedRegions === false ? { mergeManagedRegions: false } : {}),\n });\n\n // F1: dry-run reports the planned writes and stops BEFORE skills emission +\n // dedup (both would touch disk / load the embedder). The engine still throws\n // its \"not initialized\" gate when `.noir/project.id` is missing, so a dry-run\n // sync on an uninitialized project is a clean error (not a misleading no-op).\n if (dryRun) {\n reportPlannedWrites(res);\n return res;\n }\n\n // Surface the no-op so users see sync was a true no-op on disk (the\n // scaffold wrote nothing — every runtime file was content-hash identical).\n // Skills emission below still runs (it has its own dedup).\n if (res.written.length === 0 && res.identical.length > 0) {\n process.stderr.write('Nothing to sync (Noir-managed files are up to date).\\n');\n }\n\n const adapter = resolveAdapter(host);\n const skillsDir = adapter.skillsDir?.({ root });\n if (skillsDir === undefined) {\n // N1: standardized wording — same phrase across init/sync/create so logs\n // grep uniformly. (Pre-N1 sync phrased this as \"nothing to sync\".)\n process.stderr.write(`host '${host}' has no skill emitter; skipping skills\\n`);\n } else {\n const target: CompileTarget = host;\n // Forward conflictOpts so the skills-emit conflict flow is\n // live in interactive mode (mirrors init.ts). --json/--no-input stays\n // prompt-free via the `interactive: false` guard.\n type SkillEmitOpts = NonNullable<Parameters<typeof emitSkillsToDir>[1]>;\n const skillOpts: SkillEmitOpts = {\n includeIntegrations: true,\n target,\n conflictPolicy: conflictOpts.conflictPolicy,\n interactive,\n };\n if (conflictOpts.onConflict !== undefined) {\n skillOpts.onConflict = conflictOpts.onConflict as SkillEmitOpts['onConflict'];\n }\n const summary = await emitSkillsToDir(skillsDir, skillOpts);\n const relDir = skillsDir.replace(`${root}/`, '');\n process.stderr.write(\n `Synced ${summary.emitted.length} Noir skills to ${relDir}/ (target: ${target}).\\n`,\n );\n // Surface stale-dir pruning so a user can see when a previous Noir\n // version's builtin was removed (the dir was deleted from .claude/skills/).\n // Pure hygiene; never affects correctness of the freshly-emitted pack.\n const pruned = summary.pruned ?? [];\n if (pruned.length > 0) {\n process.stderr.write(\n `Pruned ${pruned.length} stale noir-* skill dir${pruned.length === 1 ? '' : 's'}: ${pruned.join(', ')}\\n`,\n );\n }\n }\n\n // Write-path semantic dedup. Non-blocking; degrades to a\n // stderr warn-skip when the embedder is unavailable. Records near-dups on\n // `res.conflicts` so `--json` consumers see them without a prompt.\n const dedup = await checkWritePathDedup(root, res, { interactive, project: projectInfo });\n if (dedup.conflicts.length > 0) res.conflicts.push(...dedup.conflicts);\n\n return res;\n}\n\n/** Resolve the sync host: explicit `--host` override > `.noir/config.yml`\n * `host:` field > `'claude'` (default — preserves the v1.1 regression anchor\n * for projects initialized before S10). The config read is best-effort: if\n * `.noir/config.yml` is absent OR fails to parse, scaffold's own\n * \"not initialized\" gate (no `.noir/project.id`) fires below with the locked\n * error string. */\nfunction resolveSyncHost(root: string, opts: SyncOptions): HostId {\n if (opts.host !== undefined) return opts.host;\n if (!existsSync(paths.config(root))) return 'claude';\n try {\n return loadProjectInfo(root).config.host;\n } catch {\n return 'claude';\n }\n}\n"],"mappings":";;;;;;;;;;;AA0BA,SAAS,kBAAkB;AAC3B,SAAsB,sBAAsB;AAC5C,SAAS,iBAAiB,aAAa;AACvC,SAA8B,gBAAgB;AAC9C,SAA6B,uBAAuB;AAgCpD,eAAsB,KAAK,MAAc,OAAoB,CAAC,GAA4B;AACxF,QAAM,OAAO,gBAAgB,MAAM,IAAI;AAGvC,QAAM,cAAc,mBAAmB;AACvC,QAAM,eAAe,kBAAkB,EAAE,OAAO,KAAK,OAAO,YAAY,CAAC;AAKzE,MAAI;AACJ,MAAI;AACF,kBAAc,gBAAgB,IAAI;AAAA,EACpC,QAAQ;AACN,kBAAc;AAAA,EAChB;AAGA,QAAM,SAAS,KAAK,WAAW,QAAQ,KAAK,YAAY;AAExD,QAAM,MAAM,MAAM,SAAS;AAAA,IACzB;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA,GAAI,SAAS,EAAE,QAAQ,KAAK,IAAI,CAAC;AAAA,IACjC,GAAG;AAAA;AAAA,IAEH,GAAI,KAAK,wBAAwB,QAAQ,EAAE,qBAAqB,MAAM,IAAI,CAAC;AAAA,EAC7E,CAAC;AAMD,MAAI,QAAQ;AACV,wBAAoB,GAAG;AACvB,WAAO;AAAA,EACT;AAKA,MAAI,IAAI,QAAQ,WAAW,KAAK,IAAI,UAAU,SAAS,GAAG;AACxD,YAAQ,OAAO,MAAM,wDAAwD;AAAA,EAC/E;AAEA,QAAM,UAAU,eAAe,IAAI;AACnC,QAAM,YAAY,QAAQ,YAAY,EAAE,KAAK,CAAC;AAC9C,MAAI,cAAc,QAAW;AAG3B,YAAQ,OAAO,MAAM,SAAS,IAAI;AAAA,CAA2C;AAAA,EAC/E,OAAO;AACL,UAAM,SAAwB;AAK9B,UAAM,YAA2B;AAAA,MAC/B,qBAAqB;AAAA,MACrB;AAAA,MACA,gBAAgB,aAAa;AAAA,MAC7B;AAAA,IACF;AACA,QAAI,aAAa,eAAe,QAAW;AACzC,gBAAU,aAAa,aAAa;AAAA,IACtC;AACA,UAAM,UAAU,MAAM,gBAAgB,WAAW,SAAS;AAC1D,UAAM,SAAS,UAAU,QAAQ,GAAG,IAAI,KAAK,EAAE;AAC/C,YAAQ,OAAO;AAAA,MACb,UAAU,QAAQ,QAAQ,MAAM,mBAAmB,MAAM,cAAc,MAAM;AAAA;AAAA,IAC/E;AAIA,UAAM,SAAS,QAAQ,UAAU,CAAC;AAClC,QAAI,OAAO,SAAS,GAAG;AACrB,cAAQ,OAAO;AAAA,QACb,UAAU,OAAO,MAAM,0BAA0B,OAAO,WAAW,IAAI,KAAK,GAAG,KAAK,OAAO,KAAK,IAAI,CAAC;AAAA;AAAA,MACvG;AAAA,IACF;AAAA,EACF;AAKA,QAAM,QAAQ,MAAM,oBAAoB,MAAM,KAAK,EAAE,aAAa,SAAS,YAAY,CAAC;AACxF,MAAI,MAAM,UAAU,SAAS,EAAG,KAAI,UAAU,KAAK,GAAG,MAAM,SAAS;AAErE,SAAO;AACT;AAQA,SAAS,gBAAgB,MAAc,MAA2B;AAChE,MAAI,KAAK,SAAS,OAAW,QAAO,KAAK;AACzC,MAAI,CAAC,WAAW,MAAM,OAAO,IAAI,CAAC,EAAG,QAAO;AAC5C,MAAI;AACF,WAAO,gBAAgB,IAAI,EAAE,OAAO;AAAA,EACtC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
File without changes
|
|
File without changes
|