@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
|
@@ -313,16 +313,37 @@ function spinner(text = "", opts = {}) {
|
|
|
313
313
|
}
|
|
314
314
|
return new OraSpinner(text);
|
|
315
315
|
}
|
|
316
|
+
function failureEnvelope(exitCode, message) {
|
|
317
|
+
return `${JSON.stringify({ ok: false, error: { code: exitCode, message } })}
|
|
318
|
+
`;
|
|
319
|
+
}
|
|
316
320
|
function fail(exitCode, message, opts = {}) {
|
|
317
321
|
if (isJsonMode(opts)) {
|
|
318
|
-
process.stdout.write(
|
|
319
|
-
`);
|
|
322
|
+
process.stdout.write(failureEnvelope(exitCode, message));
|
|
320
323
|
} else if (message.length > 0) {
|
|
321
324
|
process.stderr.write(`${message}
|
|
322
325
|
`);
|
|
323
326
|
}
|
|
324
327
|
throw new CommanderError(exitCode, NOIR_ERROR_CODE, message);
|
|
325
328
|
}
|
|
329
|
+
var EXIT_FLUSH_MS = 250;
|
|
330
|
+
function failAndExit(exitCode, message, opts = {}) {
|
|
331
|
+
process.exitCode = exitCode;
|
|
332
|
+
const jsonMode = isJsonMode(opts);
|
|
333
|
+
const text = jsonMode ? failureEnvelope(exitCode, message) : message.length > 0 ? `${message}
|
|
334
|
+
` : "";
|
|
335
|
+
if (text.length === 0) {
|
|
336
|
+
process.exit(exitCode);
|
|
337
|
+
}
|
|
338
|
+
let left = false;
|
|
339
|
+
const leave = () => {
|
|
340
|
+
if (left) return;
|
|
341
|
+
left = true;
|
|
342
|
+
process.exit(exitCode);
|
|
343
|
+
};
|
|
344
|
+
(jsonMode ? process.stdout : process.stderr).write(text, leave);
|
|
345
|
+
setTimeout(leave, EXIT_FLUSH_MS);
|
|
346
|
+
}
|
|
326
347
|
function requireInteractive(opts, hint) {
|
|
327
348
|
if (!isInteractive(opts)) {
|
|
328
349
|
fail(
|
|
@@ -574,10 +595,11 @@ export {
|
|
|
574
595
|
definitionList,
|
|
575
596
|
spinner,
|
|
576
597
|
fail,
|
|
598
|
+
failAndExit,
|
|
577
599
|
requireInteractive,
|
|
578
600
|
handleError,
|
|
579
601
|
buildMigrationPlan,
|
|
580
602
|
installManagedNode,
|
|
581
603
|
install
|
|
582
604
|
};
|
|
583
|
-
//# sourceMappingURL=chunk-
|
|
605
|
+
//# sourceMappingURL=chunk-KETMM3BA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 * True when the resolved target is a concrete version below the configured\n * `update.minVersion` 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 the floor check in {@link install}.\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 // 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}. 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. */\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 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. 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.)\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 // 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 // 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 // Under --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","// Centralized output + exit-code infrastructure for `@noir-ai/cli`.\n//\n// Stream discipline: 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: 0 ok · 1 error · 2 usage · 3 not-found · 4 daemon-down ·\n// 5 cancelled · 130/143 interrupted (128 + the signal that stopped the run:\n// SIGINT and SIGTERM). The first six are the `EXIT` enum below; the interrupt\n// codes come from the run's own stop path, not from `fail()`'s contract. These\n// constants live HERE (the single source of truth); bin.ts re-exports them so\n// 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 a contracted 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 CLI exit-code 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 * The one-line `{ok:false,error}` envelope every `--json` failure writes. Built\n * in one place so a failure written by `fail()` and one written on the way out\n * of a signal handler cannot drift apart.\n */\nfunction failureEnvelope(exitCode: number, message: string): string {\n return `${JSON.stringify({ ok: false, error: { code: exitCode, message } })}\\n`;\n}\n\n/**\n * Write a diagnostic and throw a `CommanderError` carrying the given 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(failureEnvelope(exitCode, message));\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/** How long a leaving path waits for its own last line to land before it goes. */\nconst EXIT_FLUSH_MS = 250;\n\n/**\n * {@link fail} for the paths that leave from a signal handler, where there is no\n * caller left to catch what it throws: the verdict is written and the exit\n * follows it, with the same shape and the same exit code an ordinary failure\n * would have produced. Under `--json` the envelope is the run's last output, so\n * the exit waits for the write to land — a process that exits with stdout still\n * queued loses it — and goes without it if the write never lands, because the\n * whole point of this path is to leave.\n */\nexport function failAndExit(exitCode: number, message: string, opts: CliOptions = {}): void {\n // The code this process leaves with, even if the write below never lands.\n process.exitCode = exitCode;\n const jsonMode = isJsonMode(opts);\n const text = jsonMode\n ? failureEnvelope(exitCode, message)\n : message.length > 0\n ? `${message}\\n`\n : '';\n if (text.length === 0) {\n process.exit(exitCode);\n }\n let left = false;\n const leave = (): void => {\n if (left) return;\n left = true;\n process.exit(exitCode);\n };\n (jsonMode ? process.stdout : process.stderr).write(text, leave);\n setTimeout(leave, EXIT_FLUSH_MS);\n}\n\n/**\n * Guard for commands that would otherwise block on a prompt. Honors the\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 CLI exit-code 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 CLI 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":";AAAA,SAAS,WAAW,iBAAiB;AACrC,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACAP,OAAO,WAAW;AAClB,SAAS,sBAAsB;AAC/B,OAAO,SAAuB;;;ACH9B,OAAO,QAAQ;AAOf,IAAM,QAAQ,GAAG,aAAa,IAAI;AAU3B,IAAM,IAAI;AAAA;AAAA,EAEf,IAAI,CAAC,MAAuB,SAAS,IAAI,MAAM,MAAM,CAAC,IAAI;AAAA;AAAA,EAE1D,MAAM,CAAC,MAAuB,SAAS,IAAI,MAAM,OAAO,CAAC,IAAI;AAAA;AAAA,EAE7D,OAAO,CAAC,MAAuB,SAAS,IAAI,MAAM,IAAI,CAAC,IAAI;AAAA;AAAA,EAE3D,MAAM,CAAC,MAAuB,SAAS,IAAI,MAAM,KAAK,CAAC,IAAI;AAAA;AAAA,EAE3D,QAAQ,CAAC,MAAuB,SAAS,IAAI,MAAM,KAAK,CAAC,IAAI;AAAA;AAAA,EAE7D,KAAK,CAAC,MAAuB,SAAS,IAAI,MAAM,IAAI,CAAC,IAAI;AAAA;AAAA,EAEzD,MAAM,CAAC,MAAuB,SAAS,IAAI,MAAM,KAAK,CAAC,IAAI;AAAA;AAAA,EAE3D,SAAS,CAAC,MAAuB,SAAS,IAAI,MAAM,QAAQ,CAAC,IAAI;AACnE;AAKA,IAAM,eAAqD;AAAA,EACzD,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AAWO,SAAS,MAAM,OAAmB,QAAgB,OAAe;AACtE,QAAM,OAAO,GAAG,aAAa,KAAK,CAAC,IAAI,KAAK;AAC5C,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO,EAAE,GAAG,IAAI;AAAA,IAClB,KAAK;AACH,aAAO,EAAE,KAAK,IAAI;AAAA,IACpB,KAAK;AACH,aAAO,EAAE,MAAM,IAAI;AAAA,IACrB,KAAK;AACH,aAAO,EAAE,KAAK,IAAI;AAAA,EACtB;AACF;AAQA,SAAS,WAAW,MAAuB;AAEzC,QAAM,IAAI,QAAQ,IAAI,IAAI;AAC1B,SAAO,MAAM,UAAa,MAAM;AAClC;AAQO,SAAS,UAAmB;AACjC,QAAM,IAAI,QAAQ,IAAI;AACtB,MAAI,MAAM,UAAa,MAAM,GAAI,QAAO;AACxC,SAAO,MAAM,OAAO,MAAM;AAC5B;AAoBO,SAAS,WAAoB;AAClC,MAAI,WAAW,UAAU,EAAG,QAAO;AACnC,MAAI,QAAQ,IAAI,mBAAmB,IAAK,QAAO;AAC/C,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO,GAAG;AACZ;AAQO,SAAS,iBAA0B;AACxC,SAAO,WAAW,iBAAiB;AACrC;AAQO,SAAS,gBAAwB;AACtC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,YAAY,UAAa,YAAY,IAAI;AAC3C,UAAM,IAAI,OAAO,OAAO;AACxB,QAAI,OAAO,UAAU,CAAC,KAAK,IAAI,EAAG,QAAO,KAAK,IAAI,GAAG,EAAE;AAAA,EACzD;AACA,QAAM,OAAO,QAAQ,OAAO;AAC5B,MAAI,OAAO,SAAS,YAAY,OAAO,EAAG,QAAO,KAAK,IAAI,MAAM,EAAE;AAClE,SAAO;AACT;AAYO,SAAS,aAAa,SAAS,GAAG,UAAU,GAAW;AAC5D,SAAO,KAAK,IAAI,IAAI,cAAc,IAAI,SAAS,OAAO;AACxD;AAOO,SAAS,UAAkB;AAChC,SAAO,EAAE,IAAI,SAAI,OAAO,KAAK,IAAI,GAAG,aAAa,CAAC,CAAC,CAAC;AACtD;;;AD7JO,IAAM,OAAO;AAAA,EAClB,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,OAAO;AAAA,EACP,WAAW;AAAA,EACX,aAAa;AAAA,EACb,WAAW;AACb;AASO,IAAM,eAAN,cAA2B,MAAM;AAAA,EAC7B;AAAA,EACT,YAAY,UAAkB,SAAiB;AAC7C,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW;AAAA,EAClB;AACF;AAQA,IAAM,kBAAkB;AA4BxB,SAAS,WAAW,MAA2B;AAC7C,SAAO,KAAK,SAAS;AACvB;AAEA,SAAS,YAAY,MAA2B;AAC9C,SAAO,KAAK,UAAU;AACxB;AAGO,SAAS,UAAU,OAAmB,CAAC,GAAY;AACxD,SAAO,KAAK,YAAY,QAAQ,KAAK,UAAU;AACjD;AAEA,SAASA,YAAW,MAAuB;AAEzC,QAAM,IAAI,QAAQ,IAAI,IAAI;AAC1B,SAAO,MAAM,UAAa,MAAM;AAClC;AASO,SAAS,cAAc,OAAmB,CAAC,GAAY;AAC5D,MAAI,WAAW,IAAI,KAAK,UAAU,IAAI,EAAG,QAAO;AAChD,MAAI,QAAQ,KAAKA,YAAW,UAAU,EAAG,QAAO;AAChD,SAAO,QAAQ,MAAM,UAAU,QAAQ,QAAQ,OAAO,UAAU;AAClE;AAUO,SAAS,qBAA8B;AAC5C,MAAI,yBAAyB,EAAG,QAAO;AACvC,SAAO,cAAc;AACvB;AAKA,SAAS,2BAAoC;AAC3C,QAAM,IAAI,QAAQ,IAAI;AACtB,SAAO,MAAM,UAAa,MAAM;AAClC;AAOO,SAAS,KAAK,KAAoB;AACvC,UAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,GAAG,CAAC;AAAA,CAAI;AACjD;AAQO,SAAS,IAAI,KAAa,OAAmB,CAAC,GAAS;AAC5D,MAAI,WAAW,IAAI,KAAK,YAAY,IAAI,EAAG;AAC3C,UAAQ,OAAO,MAAM,GAAG,GAAG;AAAA,CAAI;AACjC;AAEO,SAAS,KAAK,KAAa,OAAmB,CAAC,GAAS;AAC7D,MAAI,WAAW,IAAI,KAAK,YAAY,IAAI,EAAG;AAC3C,UAAQ,OAAO,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAAA,CAAI;AACzC;AAEO,SAAS,QAAQ,KAAa,OAAmB,CAAC,GAAS;AAChE,MAAI,WAAW,IAAI,KAAK,YAAY,IAAI,EAAG;AAC3C,UAAQ,OAAO,MAAM,GAAG,EAAE,GAAG,GAAG,CAAC;AAAA,CAAI;AACvC;AAEO,SAAS,KAAK,KAAa,OAAmB,CAAC,GAAS;AAC7D,MAAI,WAAW,IAAI,EAAG;AACtB,UAAQ,OAAO,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAAA,CAAI;AACzC;AAEO,SAAS,MAAM,KAAa,OAAmB,CAAC,GAAS;AAC9D,MAAI,WAAW,IAAI,EAAG;AACtB,UAAQ,OAAO,MAAM,GAAG,EAAE,MAAM,GAAG,CAAC;AAAA,CAAI;AAC1C;AASO,SAAS,IAAI,KAAa,OAAmB,CAAC,GAAS;AAC5D,MAAI,WAAW,IAAI,KAAK,KAAK,WAAW,KAAM;AAC9C,UAAQ,OAAO,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAAA,CAAI;AACzC;AAiBO,SAAS,MACd,MACA,MACA,OAAmB,CAAC,GACd;AACN,MAAI,WAAW,IAAI,EAAG;AACtB,MAAI,KAAK,WAAW,GAAG;AACrB,SAAK,aAAa,IAAI;AACtB;AAAA,EACF;AACA,QAAM,IAAI,IAAI,MAAM;AAAA,IAClB,MAAM,KAAK,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC;AAAA;AAAA;AAAA,IAG3C,OAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,EAAE;AAAA,IAC9B,WAAW,iBAAiB,MAAM,IAAI;AAAA,IACtC,UAAU;AAAA,IACV,UAAU;AAAA,EACZ,CAAC;AACD,aAAW,OAAO,MAAM;AACtB,MAAE,KAAK,KAAK,IAAI,CAAC,QAAQ,WAAW,IAAI,GAAG,CAAC,CAAC,CAAC;AAAA,EAChD;AACA,UAAQ,OAAO,MAAM,GAAG,EAAE,SAAS,CAAC;AAAA,CAAI;AAC1C;AAmBA,SAAS,iBACP,MACA,MACU;AACV,QAAM,IAAI,KAAK;AACf,MAAI,MAAM,EAAG,QAAO,CAAC;AACrB,QAAM,YAAY,KAAK,IAAI,CAAC,QAAQ,IAAI,MAAM;AAC9C,QAAM,UAAU,KAAK,IAAI,CAAC,KAAK,MAAM;AACnC,QAAI,MAAM,KAAK,IAAI,UAAU,CAAC,KAAK,IAAI,QAAQ,CAAC;AAChD,eAAW,OAAO,MAAM;AACtB,YAAM,MAAM,WAAW,IAAI,GAAG,CAAC,EAAE;AACjC,UAAI,MAAM,IAAK,OAAM;AAAA,IACvB;AACA,WAAO;AAAA,EACT,CAAC;AAID,QAAM,gBAAgB,KAAK,IAAI,IAAI,GAAG,cAAc,IAAI,IAAI,IAAI,CAAC;AACjE,QAAM,aAAa,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AACpD,MAAI,cAAc,eAAe;AAC/B,WAAO,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC;AAAA,EACjC;AAEA,QAAM,UAAU,QAAQ,MAAM;AAC9B,MAAI,MAAM;AACV,QAAM,QAAQ,UAAU,IAAI,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,CAAC;AACjD,SAAO,MAAM,eAAe;AAC1B,QAAI,MAAM;AACV,QAAI,SAAS;AACb,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAMC,KAAI,QAAQ,CAAC,KAAK;AACxB,YAAM,IAAI,MAAM,CAAC,KAAK;AACtB,UAAIA,KAAI,KAAKA,KAAI,QAAQ;AACvB,iBAASA;AACT,cAAM;AAAA,MACR;AAAA,IACF;AACA,QAAI,QAAQ,GAAI;AAChB,UAAM,YAAY,MAAM;AACxB,UAAM,aAAa,QAAQ,GAAG,KAAK,MAAM,MAAM,GAAG,KAAK;AACvD,UAAM,MAAM,KAAK,IAAI,WAAW,SAAS;AACzC,YAAQ,GAAG,KAAK,QAAQ,GAAG,KAAK,KAAK;AACrC,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC;AACjC;AAEA,SAAS,WAAW,OAAwB;AAC1C,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,aAAa,OAAO,UAAU,UAAU;AACxF,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,SAAO,KAAK,UAAU,KAAK;AAC7B;AAeO,SAAS,eAAe,MAAgC,OAAmB,CAAC,GAAS;AAC1F;AAAA,IACE,KAAK,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,OAAO,WAAW,EAAE,KAAK,EAAE,EAAE;AAAA,IAChE,CAAC,SAAS,OAAO;AAAA,IACjB;AAAA,EACF;AACF;AAsBA,IAAM,cAAN,MAAqC;AAAA,EACnC,OAAO;AAAA,EACP,MAAM,OAAyB;AAC7B,WAAO;AAAA,EACT;AAAA,EACA,OAAgB;AACd,WAAO;AAAA,EACT;AAAA,EACA,QAAiB;AACf,WAAO;AAAA,EACT;AAAA,EACA,QAAQ,OAAsB;AAAA,EAAC;AAAA,EAC/B,KAAK,OAAsB;AAAA,EAAC;AAAA,EAC5B,KAAK,OAAsB;AAAA,EAAC;AAAA,EAC5B,KAAK,OAAsB;AAAA,EAAC;AAC9B;AAGA,IAAM,aAAN,MAAoC;AAAA,EACjB;AAAA,EAEjB,YAAY,aAAqB;AAC/B,SAAK,SAAS,IAAI,WAAW;AAAA,EAC/B;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA,EACA,IAAI,KAAK,OAAe;AACtB,SAAK,OAAO,OAAO;AAAA,EACrB;AAAA,EAEA,MAAM,MAAwB;AAC5B,QAAI,SAAS,OAAW,MAAK,OAAO,OAAO;AAC3C,SAAK,OAAO,MAAM;AAClB,WAAO;AAAA,EACT;AAAA,EAEA,OAAgB;AACd,SAAK,OAAO,KAAK;AACjB,WAAO;AAAA,EACT;AAAA,EAEA,QAAiB;AACf,SAAK,OAAO,MAAM;AAClB,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ,MAAqB;AAC3B,SAAK,OAAO,QAAQ,IAAI;AAAA,EAC1B;AAAA,EAEA,KAAK,MAAqB;AACxB,SAAK,OAAO,KAAK,IAAI;AAAA,EACvB;AAAA,EAEA,KAAK,MAAqB;AACxB,SAAK,OAAO,KAAK,IAAI;AAAA,EACvB;AAAA,EAEA,KAAK,MAAqB;AACxB,SAAK,OAAO,KAAK,IAAI;AAAA,EACvB;AACF;AAEO,SAAS,QAAQ,OAAO,IAAI,OAAmB,CAAC,GAAY;AACjE,MAAI,WAAW,IAAI,KAAK,YAAY,IAAI,KAAK,CAAC,cAAc,IAAI,GAAG;AACjE,WAAO,IAAI,YAAY;AAAA,EACzB;AACA,SAAO,IAAI,WAAW,IAAI;AAC5B;AAWA,SAAS,gBAAgB,UAAkB,SAAyB;AAClE,SAAO,GAAG,KAAK,UAAU,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,UAAU,QAAQ,EAAE,CAAC,CAAC;AAAA;AAC7E;AASO,SAAS,KAAK,UAAkB,SAAiB,OAAmB,CAAC,GAAU;AACpF,MAAI,WAAW,IAAI,GAAG;AACpB,YAAQ,OAAO,MAAM,gBAAgB,UAAU,OAAO,CAAC;AAAA,EACzD,WAAW,QAAQ,SAAS,GAAG;AAC7B,YAAQ,OAAO,MAAM,GAAG,OAAO;AAAA,CAAI;AAAA,EACrC;AACA,QAAM,IAAI,eAAe,UAAU,iBAAiB,OAAO;AAC7D;AAGA,IAAM,gBAAgB;AAWf,SAAS,YAAY,UAAkB,SAAiB,OAAmB,CAAC,GAAS;AAE1F,UAAQ,WAAW;AACnB,QAAM,WAAW,WAAW,IAAI;AAChC,QAAM,OAAO,WACT,gBAAgB,UAAU,OAAO,IACjC,QAAQ,SAAS,IACf,GAAG,OAAO;AAAA,IACV;AACN,MAAI,KAAK,WAAW,GAAG;AACrB,YAAQ,KAAK,QAAQ;AAAA,EACvB;AACA,MAAI,OAAO;AACX,QAAM,QAAQ,MAAY;AACxB,QAAI,KAAM;AACV,WAAO;AACP,YAAQ,KAAK,QAAQ;AAAA,EACvB;AACA,GAAC,WAAW,QAAQ,SAAS,QAAQ,QAAQ,MAAM,MAAM,KAAK;AAC9D,aAAW,OAAO,aAAa;AACjC;AAQO,SAAS,mBAAmB,MAAkB,MAAoB;AACvE,MAAI,CAAC,cAAc,IAAI,GAAG;AACxB;AAAA,MACE,KAAK;AAAA,MACL,GAAG,IAAI;AAAA,MACP;AAAA,IACF;AAAA,EACF;AACF;AAIA,SAAS,kBAAkB,KAA6B;AACtD,UAAQ,IAAI,MAAM;AAAA,IAChB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,KAAK;AAAA,IACd,KAAK;AACH,aAAO,KAAK;AAAA,IACd;AACE,aAAO,KAAK;AAAA,EAChB;AACF;AAGO,SAAS,cAAc,KAAsB;AAClD,MAAI,eAAe,aAAc,QAAO,IAAI;AAC5C,MAAI,eAAe,gBAAgB;AAGjC,QAAI,IAAI,KAAK,WAAW,OAAO,EAAG,QAAO,IAAI;AAC7C,WAAO,kBAAkB,GAAG;AAAA,EAC9B;AACA,SAAO,KAAK;AACd;AAOO,SAAS,YAAY,KAAoB;AAC9C,MAAI,eAAe,cAAc;AAC/B,QAAI,IAAI,QAAQ,SAAS,EAAG,SAAQ,OAAO,MAAM,GAAG,IAAI,OAAO;AAAA,CAAI;AAAA,EACrE,WAAW,eAAe,gBAAgB;AAAA,EAI1C,OAAO;AACL,YAAQ,OAAO,MAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AAAA,EACpF;AACA,UAAQ,WAAW,cAAc,GAAG;AACtC;;;ADxeA,SAAS,SAAS,GAAW,GAAoB;AAG/C,QAAM,MAAM,CAAC,MAAsB,OAAO,EAAE,QAAQ,MAAM,EAAE,CAAC,KAAK;AAClE,QAAM,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,GAAG;AAC/B,QAAM,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,GAAG;AAC/B,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,IAAI,GAAG,CAAC,KAAK;AACnB,UAAM,IAAI,GAAG,CAAC,KAAK;AACnB,QAAI,MAAM,EAAG,QAAO,IAAI;AAAA,EAC1B;AACA,SAAO;AACT;AAGO,SAAS,mBAAmB,MAUjB;AAChB,QAAM,WAAW,KAAK;AACtB,QAAM,gBAAgB,SAAS,SAAS,KAAK,KAAK,kBAAkB;AACpE,QAAM,gBAAgB,KAAK;AAC3B,MAAI,cAAc;AAClB,MAAI,KAAK,oBAAoB,kBAAkB,YAAY,kBAAkB,QAAQ;AACnF,kBAAc,SAAS,eAAe,KAAK,gBAAgB;AAAA,EAC7D;AAGA,QAAM,QAAQ,KAAK,cAAc;AACjC,QAAM,kBACJ,kBAAkB,YAAY,kBAAkB,UAAU,SAAS,eAAe,KAAK;AACzF,SAAO;AAAA,IACL;AAAA,IACA,eAAe,KAAK;AAAA,IACpB,YAAY,KAAK;AAAA,IACjB,kBAAkB,KAAK;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB,SAAS,CAAC,GAAG,gBAAgB;AAAA,IAC/C,eAAe;AAAA,EACjB;AACF;AAqCA,eAAsB,mBACpB,MACmC;AACnC,QAAM,OAAO,KAAK,WAAW,KAAK,WAAW;AAC7C,QAAM,OAAO,SAAS;AACtB,QAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,QAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,YAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AACrC,YAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AAMrC,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,qBAAqB;AAAA,MACnC,GAAI,KAAK,aAAa,CAAC;AAAA,MACvB,KAAK,KAAK;AAAA,IACZ,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,WAAO,EAAE,IAAI,OAAO,SAAS,MAAM,OAAO,kCAAkC,MAAM,GAAG;AAAA,EACvF;AACA,MAAI,QAAQ,WAAW,UAAU;AAC/B;AAAA,MACE,mDAAmD,QAAQ,OAAO,OAAO,QAAQ,OAAO;AAAA,IAC1F;AAAA,EACF;AAEA,QAAM,EAAE,SAAS,OAAO,IAAI;AAG5B,QAAM,EAAE,MAAM,OAAO,IAAI,MAAM;AAAA,IAC7B,CAAC,QAAQ,WAAW,MAAM,gBAAgB,IAAI,IAAI,YAAY,MAAM,EAAE;AAAA,IACtE,EAAE,KAAK,KAAK,KAAK,WAAW,KAAQ;AAAA,EACtC;AACA,MAAI,SAAS,GAAG;AACd,WAAO,EAAE,IAAI,OAAO,SAAS,MAAM,OAAO,uBAAuB,OAAO,MAAM,GAAG,GAAG,CAAC,GAAG;AAAA,EAC1F;AAGA,QAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,QAAM,WAAW;AAAA,GAAyB,OAAO,MAAM,KAAK,QAAQ,OAAO,gBAAgB,YAAY,OAAO,QAAQ,QAAQ,CAAC;AAAA;AAC/H,kBAAgB,MAAM,QAAQ;AAC9B,YAAU,MAAM,GAAK;AACrB,uBAAqB;AAIrB,QAAM,MAAM,MAAM;AAAA,IAChB;AAAA,MACE;AAAA,MACA,KAAK,QAAQ,OAAO,gBAAgB,YAAY,OAAO,QAAQ,QAAQ;AAAA,MACvE;AAAA,IACF;AAAA,IACA,EAAE,KAAK,KAAK,KAAK,WAAW,IAAO;AAAA,EACrC;AACA,QAAM,UAAU,IAAI,SAAS,IAAI,IAAI,OAAO,KAAK,IAAI;AACrD,qBAAmB;AAAA,IACjB,QAAQ;AAAA,IACR,SAAS,WAAW;AAAA,IACpB,SAAS,KAAK,WAAW;AAAA,IACzB,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA;AAAA;AAAA;AAAA,IAIpC,uBACE,QAAQ,WAAW,YAAY,uBAAuB,UAAU,QAAQ,OAAO;AAAA,EACnF,CAAC;AACD,SAAO,EAAE,IAAI,MAAM,SAAS,eAAe,QAAQ,OAAO;AAC5D;AAEA,eAAsB,QAAQ,OAAuB,CAAC,GAAkB;AAQtE,MAAI;AACJ,MAAI,KAAK,YAAY,MAAM;AACzB,UAAM,MAAM,kBAAkB;AAC9B,QAAI,CAAC,KAAK;AACR,uBAAiB,EAAE,WAAW,OAAO,SAAS,KAAK;AACnD,UAAI,CAAC,KAAK,KAAM,MAAK,8CAA8C;AAAA,IACrE,OAAO;AACL,YAAM,YAAY,IAAI,IAAI,IAAI,qBAAqB,CAAC,CAAC;AACrD,UAAI,CAAC,UAAU,IAAI,YAAY,EAAG,WAAU,IAAI,YAAY;AAC5D,yBAAmB,EAAE,GAAG,KAAK,mBAAmB,CAAC,GAAG,SAAS,EAAE,CAAC;AAChE,uBAAiB,EAAE,WAAW,MAAM,SAAS,aAAa;AAC1D,UAAI,CAAC,KAAK,KAAM,SAAQ,kCAAkC,YAAY,GAAG;AAAA,IAC3E;AAAA,EACF;AAGA,MAAI,KAAK,YAAY,QAAQ,KAAK,SAAS,MAAM;AAC/C,QAAI,KAAK,KAAM,SAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,EAAE,IAAI,MAAM,MAAM,eAAe,CAAC,CAAC;AAAA,CAAI;AAC7F;AAAA,EACF;AAEA,MAAI,KAAK,SAAS,MAAM;AACtB,UAAMC,YAAW,MAAM,qBAAqB,QAAQ,GAAG;AACvD,QAAI,KAAK,MAAM;AACb,cAAQ,OAAO;AAAA,QACb,GAAG,KAAK,UAAU,EAAE,IAAI,MAAM,MAAM,EAAE,GAAI,kBAAkB,CAAC,GAAI,UAAAA,UAAS,EAAE,CAAC,CAAC;AAAA;AAAA,MAChF;AACA;AAAA,IACF;AACA,SAAK,oBAAoB;AACzB,eAAW,KAAKA,UAAU,MAAK,KAAK,EAAE,MAAM,KAAK,EAAE,WAAW,SAAS,GAAG;AAC1E;AAAA,EACF;AAEA,QAAM,gBAAgB,mBAAmB;AACzC,QAAM,WAAW,MAAM,qBAAqB,QAAQ,GAAG;AACvD,QAAM,kBAAkB,kBAAkB;AAI1C,MAAI,aAAa;AACjB,MAAI;AACF,iBAAa,gBAAgB,QAAQ,IAAI,CAAC,EAAE,OAAO,OAAO;AAAA,EAC5D,QAAQ;AAAA,EAER;AACA,QAAM,OAAO,mBAAmB;AAAA,IAC9B;AAAA,IACA;AAAA,IACA,YAAY,KAAK,QAAQ;AAAA,IACzB,kBAAkB,iBAAiB,WAAW;AAAA,IAC9C;AAAA,EACF,CAAC;AAOD,MAAI,KAAK,iBAAiB;AACxB;AAAA,MACE,KAAK;AAAA,MACL,uBAAuB,KAAK,aAAa,sBAAsB,UAAU;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AAEA,MAAI,KAAK,aAAa;AACpB;AAAA,MACE,KAAK;AAAA,MACL,yBAAyB,KAAK,aAAa,eAAe,KAAK,gBAAgB;AAAA,MAC/E;AAAA,IACF;AAAA,EACF;AAEA,QAAM,cAAc,QAAQ,2CAA2C,IAAI,EAAE,MAAM;AACnF,QAAM,SAAS,MAAM,mBAAmB;AAAA,IACtC,SAAS,KAAK,kBAAkB,SAAS,SAAS;AAAA,IAClD,SAAS,KAAK,cAAc,WAAW,GAAG,IACtC,KAAK,gBACL,KAAK,kBAAkB,YAAY,KAAK,kBAAkB,SACxD,SACA,KAAK;AAAA,IACX,KAAK,QAAQ;AAAA,EACf,CAAC;AACD,MAAI,CAAC,OAAO,IAAI;AACd,gBAAY,KAAK,uBAAuB;AACxC,SAAK,KAAK,OAAO,OAAO,SAAS,yBAAyB,IAAI;AAAA,EAChE;AACA,cAAY,QAAQ,kBAAkB,OAAO,OAAO,EAAE;AAMtD,MAAI,KAAK,oBAAoB,KAAK,kBAAkB,MAAM;AACxD,SAAK,4DAA4D,KAAK,gBAAgB,EAAE;AACxF;AAAA,MACE;AAAA,IACF;AAAA,EACF,WAAW,KAAK,oBAAoB,KAAK,kBAAkB,MAAM;AAC/D,UAAM,EAAE,KAAK,IAAI,MAAM,cAAc,KAAK,iBAAiB,MAAM,GAAG,GAAG,EAAE,KAAK,QAAQ,IAAI,CAAC;AAC3F,QAAI,SAAS,EAAG,SAAQ,2BAA2B;AAAA,QAC9C,MAAK,2EAA2E;AAAA,EACvF;AAGA,MAAI,KAAK,SAAS,MAAM;AACtB,YAAQ,OAAO;AAAA,MACb,GAAG,KAAK,UAAU,EAAE,IAAI,MAAM,MAAM,EAAE,SAAS,OAAO,SAAS,eAAe,OAAO,cAAc,EAAE,CAAC,CAAC;AAAA;AAAA,IACzG;AACA;AAAA,EACF;AAEA,OAAK,qBAAqB,OAAO,OAAO,GAAG;AAI3C,OAAK,0EAA0E;AAC/E,UAAQ,QAAQ,OAAO,OAAO,uDAAuD;AACvF;","names":["envFlagSet","c","detected"]}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
spinner,
|
|
7
7
|
success,
|
|
8
8
|
warn
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-KETMM3BA.js";
|
|
10
10
|
|
|
11
11
|
// src/commands/update.ts
|
|
12
12
|
import {
|
|
@@ -242,4 +242,4 @@ export {
|
|
|
242
242
|
buildUpdateNotice,
|
|
243
243
|
runAsyncUpdateCheck
|
|
244
244
|
};
|
|
245
|
-
//# sourceMappingURL=chunk-
|
|
245
|
+
//# sourceMappingURL=chunk-TPNJEN4B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/update.ts"],"sourcesContent":["import {\n detectActiveMethod,\n ensureShimExecutable,\n fetchLatestVersion,\n loadProjectInfo,\n readInstallRecord,\n readUpdateCache,\n runManagerCmd,\n shouldCheckForUpdate,\n type UpdateConfigLike,\n writeUpdateCache,\n} from '@noir-ai/core';\nimport { type CliOptions, EXIT, fail, info, spinner, success, warn } from '../output.js';\nimport { installManagedNode } from './install.js';\n\nexport const DEFAULT_UPDATE_CONFIG: UpdateConfigLike = {\n checkEnabled: true,\n checkIntervalHours: 24,\n channel: 'latest',\n minVersion: '1.6.0',\n display: 'notice',\n};\n\nexport interface UpdateOptions extends CliOptions {\n check?: boolean;\n spec?: string;\n}\n\nexport interface UpdateTarget {\n method: string;\n targetSpec: string;\n currentVersion: string | null;\n latestKnown: string | null;\n isUpgrade: boolean;\n /**\n * True when the latest-known version the registry returned is below the\n * configured `update.minVersion` floor (e.g. a beta/prerelease channel lag,\n * or a yanked dist-tag). Enforced in {@link update} with the same\n * warn/refuse pattern as the downgrade guard.\n */\n belowMinVersion: boolean;\n}\n\nexport function buildUpdateTarget(opts: {\n method: string;\n channel: string;\n spec?: string;\n currentVersion: string | null;\n latestKnown: string | null;\n /**\n * The `update.minVersion` floor from config (default `'1.6.0'`). When\n * omitted, the config default applies. See the floor check in {@link update}.\n */\n minVersion?: string;\n}): UpdateTarget {\n const targetSpec = opts.spec ?? opts.channel;\n // The guard target: a concrete `spec` (an exact version) WINS over the\n // fetched registry version, because the downgrade and minVersion guards must\n // evaluate what we will ACTUALLY install — not what the registry happens to\n // offer. A pinned `spec 1.9.0` against a 1.12.0 registry is a DOWNGRADE,\n // and a pinned `spec 1.5.0` can trip the minVersion floor; both were\n // previously missed because the guards only ever looked at `latestKnown`.\n const concrete =\n opts.spec != null && opts.spec !== 'latest' && opts.spec !== 'beta'\n ? opts.spec\n : opts.latestKnown;\n // Semver downgrade guard (hardening): only treat the target as an upgrade\n // when it is STRICTLY NEWER than the current one. The prior inequality check\n // (`latestKnown !== currentVersion`) would treat a registry that returned an\n // OLDER version (e.g. beta/prerelease channel lag) as an \"upgrade\" and\n // silently downgrade the install.\n const isUpgrade =\n concrete != null && opts.currentVersion != null && semverGt(concrete, opts.currentVersion);\n // minVersion floor: refuse a target version below the floor. null\n // latestKnown is handled separately (registry-unreachable branch).\n const floor = opts.minVersion ?? '1.6.0';\n const belowMinVersion = concrete != null && semverLt(concrete, floor);\n return {\n method: opts.method,\n targetSpec,\n currentVersion: opts.currentVersion,\n latestKnown: opts.latestKnown,\n isUpgrade,\n belowMinVersion,\n };\n}\n\n/**\n * Per-segment numeric semver comparison (major.minor.patch). Returns true when\n * `a` is strictly greater than `b`. Pre-release suffixes are ignored — Noir\n * versioning is `MAJOR.MINOR.PATCH` only, and the registry's dist-tag is the\n * source of truth for \"latest\". Non-numeric segments coerce to 0. Used here\n * instead of a full semver library to keep the CLI's dependency surface\n * unchanged (the same helper lives in install.ts for the downgrade guard).\n */\nfunction semverGt(a: string, b: string): boolean {\n // Strip a leading `v` (npm-idiomatic) — bare Number('v1') is NaN.\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/**\n * Per-segment numeric semver `<` (the minVersion floor comparison). Same comparison\n * discipline as {@link semverGt} / install.ts's downgrade guard: numeric per\n * segment, non-numeric → 0. Returns true when `a` is strictly less than `b`.\n */\nfunction semverLt(a: string, b: string): boolean {\n // Strip a leading `v` (npm-idiomatic) — bare Number('v1') is NaN.\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\nexport async function update(opts: UpdateOptions = {}): Promise<void> {\n // NOIR_DISABLE_UPDATES is the hard kill-switch for the SELF-UPDATE surface\n // (distinct from NOIR_DISABLE_UPDATE_CHECK, which only gags the background\n // version check). When set, `noir update` refuses to run at all — enterprises\n // can pin this in the environment to enforce \"updates flow only through the\n // package manager / image rebuild, never from inside the CLI\". `fail` is\n // `: never` (always throws) — the explicit `return` is defensive.\n if (process.env.NOIR_DISABLE_UPDATES !== undefined) {\n fail(\n EXIT.USAGE,\n 'self-update is disabled (NOIR_DISABLE_UPDATES is set); update via your package manager or image rebuild.',\n opts,\n );\n return;\n }\n\n const method = detectActiveMethod();\n const rec = readInstallRecord();\n const currentVersion = rec?.version ?? null;\n\n if (opts.check === true) {\n const checkSpin = spinner('Checking for updates...', opts).start();\n const latest = await fetchLatestVersion('latest');\n if (latest) {\n checkSpin.succeed(`Latest: ${latest} (you have ${currentVersion ?? 'unknown'})`);\n if (opts.json === true) {\n process.stdout.write(`${JSON.stringify({ ok: true, data: { latest, currentVersion } })}\\n`);\n }\n } else {\n checkSpin.fail('Could not reach the registry.');\n // fail() emits the canonical {ok:false,error} envelope under --json AND\n // exits 1 (a manual envelope here would leave the process at exit 0 while\n // the payload claims code 1).\n fail(EXIT.ERROR, 'Could not reach the registry.', opts);\n }\n return;\n }\n\n // Fetch latest (network; timeout-bound).\n const fetchSpin = spinner('Checking for updates...', opts).start();\n const latest = await fetchLatestVersion('latest');\n if (latest === null) {\n fetchSpin.fail('Could not reach the registry.');\n // Registry-unreachable is a FAILURE: route through fail() so --json emits\n // {ok:false,error} on stdout AND the process exits 1 (a silent return would\n // leave stdout empty + exit 0 — indistinguishable from success for scripts).\n fail(EXIT.ERROR, 'Could not reach the registry.', opts);\n }\n fetchSpin.succeed(`Latest: ${latest}`);\n // 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 = DEFAULT_UPDATE_CONFIG.minVersion;\n try {\n minVersion = loadProjectInfo(process.cwd()).config.update.minVersion;\n } catch {\n // Not initialized yet — the config default applies.\n }\n const target = buildUpdateTarget({\n method,\n channel: 'latest',\n spec: opts.spec,\n currentVersion,\n latestKnown: latest,\n minVersion,\n });\n\n // Refuse to install a registry-offered version below the configured\n // minVersion floor (same warn/refuse pattern as install.ts's downgrade guard).\n // Checked before the isUpgrade branch: a below-floor offer is unsafe even if\n // it happens to be newer than the (very old) current install.\n if (target.belowMinVersion) {\n // Name the ACTUAL target (a concrete positional spec), not the registry's latest.\n warn(`Target ${target.targetSpec} is below the minVersion floor (${minVersion}).`);\n fail(\n EXIT.USAGE,\n `refusing update to ${target.targetSpec} (below minVersion ${minVersion})`,\n opts,\n );\n }\n\n if (!target.isUpgrade) {\n fetchSpin.succeed(`noir ${currentVersion} is up to date`);\n if (opts.json === true) {\n process.stdout.write(\n `${JSON.stringify({ ok: true, data: { version: currentVersion, upToDate: true } })}\\n`,\n );\n return;\n }\n info(`noir ${currentVersion} is up to date.`, opts);\n return;\n }\n\n if (method === 'native') {\n const updateSpin = spinner(\n `Updating to ${target.latestKnown} via native installer...`,\n opts,\n ).start();\n const res = await installManagedNode({\n version: target.targetSpec === 'latest' ? undefined : target.targetSpec,\n env: process.env,\n });\n if (!res.ok) {\n updateSpin.fail('Update failed');\n fail(EXIT.ERROR, res.error ?? 'update failed', opts);\n }\n // Chicken-egg safety net: the update was ORCHESTRATED by the OLD binary\n // (which may predate the chmod fix). Re-assert the freshly-written shim is\n // executable so the user's NEXT `noir …` doesn't fail with permission\n // denied. This is the ONLY line that closes the regression once 1.7.4+ is\n // installed — it runs in the NEW binary's process.\n ensureShimExecutable();\n updateSpin.succeed(`Updated to noir ${res.version}`);\n if (opts.json === true) {\n process.stdout.write(`${JSON.stringify({ ok: true, data: { version: res.version } })}\\n`);\n return;\n }\n success(`Updated to ${res.version}.`);\n return;\n }\n\n // npm/pnpm/yarn/bun/homebrew/scoop -> reinstall via the same manager.\n const cmd = updateCmdFor(method, target.targetSpec);\n if (!cmd)\n fail(EXIT.USAGE, `cannot auto-update a ${method} install; use the manager directly`, opts);\n const mgrSpin = spinner(`Updating via ${method}...`, opts).start();\n const { code, stderr } = await runManagerCmd(cmd, { env: process.env });\n if (code !== 0) {\n mgrSpin.fail(`Update failed`);\n fail(EXIT.ERROR, `update failed: ${stderr.slice(0, 300)}`, opts);\n }\n mgrSpin.succeed('Updated');\n if (opts.json === true) {\n process.stdout.write(`${JSON.stringify({ ok: true, data: { version: target.targetSpec } })}\\n`);\n return;\n }\n success('Updated.');\n}\n\nfunction updateCmdFor(method: string, spec: string): string[] | null {\n switch (method) {\n case 'npm':\n return ['npm', 'install', '-g', `@noir-ai/cli@${spec}`];\n case 'pnpm':\n return ['pnpm', 'add', '-g', `@noir-ai/cli@${spec}`];\n case 'yarn':\n return ['yarn', 'global', 'add', `@noir-ai/cli@${spec}`];\n case 'bun':\n return ['bun', 'add', '-g', `@noir-ai/cli@${spec}`];\n case 'homebrew':\n return ['brew', 'upgrade', 'noir'];\n case 'scoop':\n return ['scoop', 'update', 'noir'];\n default:\n return null;\n }\n}\n\n/**\n * The human-readable update command for an install method. Mirrors\n * {@link updateCmdFor} (the machine command) with the manager the user should\n * actually run — `noir update` is the universal entry (it re-dispatches to the\n * right manager), but the package-manager spellings are shown for transparency.\n */\nexport function updateAdvice(method: string): string {\n switch (method) {\n case 'npm':\n return 'run `npm install -g @noir-ai/cli@latest`';\n case 'pnpm':\n return 'run `pnpm add -g @noir-ai/cli@latest`';\n case 'yarn':\n return 'run `yarn global add @noir-ai/cli@latest`';\n case 'bun':\n return 'run `bun add -g @noir-ai/cli@latest`';\n case 'homebrew':\n return 'run `brew upgrade noir`';\n case 'scoop':\n return 'run `scoop update noir`';\n case 'native':\n return 'run `noir update`';\n default:\n return 'run `noir update`';\n }\n}\n\n/** A \"new version available\" notice for the home menu (version + advice). */\nexport interface UpdateNotice {\n readonly latestVersion: string;\n readonly currentVersion: string;\n readonly method: string;\n readonly advice: string;\n}\n\n/**\n * Build the home-menu \"update available\" notice from the cached latest version.\n * Returns `null` when there is no cached latest, no current version, or the\n * latest is NOT strictly newer (the semver downgrade guard — a stale/older\n * cache entry is never advertised as an upgrade).\n */\nexport function buildUpdateNotice(opts: {\n method: string;\n currentVersion: string | null;\n latestKnown: string | null;\n}): UpdateNotice | null {\n const { method, currentVersion, latestKnown } = opts;\n if (currentVersion == null || latestKnown == null) return null;\n if (!semverGt(latestKnown, currentVersion)) return null;\n return { latestVersion: latestKnown, currentVersion, method, advice: updateAdvice(method) };\n}\n\n/** Non-blocking, time-boxed startup check. Writes the cache on success; silent on any failure. */\nexport async function runAsyncUpdateCheck(opts: {\n env: NodeJS.ProcessEnv;\n configUpdate: UpdateConfigLike;\n quiet: boolean;\n}): Promise<void> {\n try {\n const cache = readUpdateCache();\n if (!shouldCheckForUpdate({ env: opts.env, configUpdate: opts.configUpdate, cache })) return;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 2000);\n const latest = await fetchLatestVersion(opts.configUpdate.channel, controller.signal);\n clearTimeout(timer);\n if (latest != null) {\n writeUpdateCache({\n lastCheckAt: new Date().toISOString(),\n latestVersion: latest,\n channel: opts.configUpdate.channel,\n });\n }\n // Non-blocking: never print in quiet/CI/non-TTY; a mismatch only nudges.\n } catch {\n // silent on any failure — this is a non-blocking background check\n }\n}\n"],"mappings":"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","names":["latest"]}
|
|
@@ -2,12 +2,13 @@ import {
|
|
|
2
2
|
assertTransportUrl,
|
|
3
3
|
buildConflictOpts,
|
|
4
4
|
checkWritePathDedup,
|
|
5
|
+
preservedStaleLine,
|
|
5
6
|
reportPlannedWrites
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-5KWETEMF.js";
|
|
8
|
+
import "./chunk-TPNJEN4B.js";
|
|
8
9
|
import {
|
|
9
10
|
resolveInteractive
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-KETMM3BA.js";
|
|
11
12
|
|
|
12
13
|
// src/commands/create.ts
|
|
13
14
|
import { resolve } from "path";
|
|
@@ -18,14 +19,13 @@ import { emitSkillsToDir } from "@noir-ai/skills";
|
|
|
18
19
|
async function create(dir, opts) {
|
|
19
20
|
assertTransportUrl(opts);
|
|
20
21
|
const root = resolve(dir ?? process.cwd());
|
|
21
|
-
const host = opts.host ?? "claude";
|
|
22
22
|
const interactive = resolveInteractive();
|
|
23
23
|
const conflictOpts = buildConflictOpts({ force: opts.force, interactive });
|
|
24
24
|
const dryRun = opts.dryRun === true || opts.preview === true;
|
|
25
25
|
const res = await scaffold({
|
|
26
26
|
root,
|
|
27
27
|
mode: "create",
|
|
28
|
-
host,
|
|
28
|
+
...opts.host !== void 0 ? { host: opts.host } : {},
|
|
29
29
|
transport: opts.transport,
|
|
30
30
|
interactive,
|
|
31
31
|
...opts.url !== void 0 ? { url: opts.url } : {},
|
|
@@ -38,6 +38,7 @@ async function create(dir, opts) {
|
|
|
38
38
|
return res;
|
|
39
39
|
}
|
|
40
40
|
if (res.noop) return res;
|
|
41
|
+
const host = res.host;
|
|
41
42
|
const adapter = resolveAdapter(host);
|
|
42
43
|
const skillsDir = adapter.skillsDir?.({ root });
|
|
43
44
|
if (skillsDir === void 0) {
|
|
@@ -60,6 +61,9 @@ async function create(dir, opts) {
|
|
|
60
61
|
`Emitted ${summary.emitted.length} Noir skills to ${relDir}/ (target: ${target}).
|
|
61
62
|
`
|
|
62
63
|
);
|
|
64
|
+
const stale = preservedStaleLine(summary.preserved ?? []);
|
|
65
|
+
if (stale !== void 0) process.stderr.write(`${stale}
|
|
66
|
+
`);
|
|
63
67
|
}
|
|
64
68
|
let projectInfo;
|
|
65
69
|
try {
|
|
@@ -77,4 +81,4 @@ async function create(dir, opts) {
|
|
|
77
81
|
export {
|
|
78
82
|
create
|
|
79
83
|
};
|
|
80
|
-
//# sourceMappingURL=create-
|
|
84
|
+
//# sourceMappingURL=create-6KDIFPUL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/create.ts"],"sourcesContent":["// `noir create [dir]` — greenfield first-run for the AI layer only.\n//\n// New command. Bootstraps Noir's AI layer (.noir/ store, host\n// pointers, skills) into `dir` (created if absent), with NO external\n// scaffolder chaining (the 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// 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 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 CLI exit-code contract\n// lists usage=2 for a missing `--url`; neither `init` nor `create` implements\n// 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 (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, preservedStaleLine, reportPlannedWrites } from '../init.js';\nimport { resolveInteractive } from '../output.js';\n\nexport interface CreateOptions {\n transport: 'stdio' | 'streamable-http';\n url?: string;\n /** Target host. Defaults to `'claude'`. */\n host?: HostId;\n /** Re-scaffold even if already initialized. */\n force?: boolean;\n /** `--dry-run`/`--preview` — report the planned writes to stderr\n * without touching disk (incl. NOT creating the target dir). */\n dryRun?: boolean;\n /** 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 // 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 // Host is left to the engine (explicit `--host` > the project's configured\n // host > `'claude'`) so `create --force` over an existing project keeps its\n // host; `res.host` reports the host actually emitted.\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 // --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 ...(opts.host !== undefined ? { host: opts.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 // 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 // A no-op (already-initialized guard) must not re-emit skills.\n if (res.noop) return res;\n\n // The host the engine actually emitted (explicit --host, or the project's\n // configured host resolved inside scaffold).\n const host: HostId = res.host;\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 // Standardized wording — same phrase across init/sync/create so logs\n // grep uniformly. (Each command used to phrase 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 // The emitted count above is skills that are fully current; a skill whose\n // files were preserved is stale on disk and has to be named, in the same\n // words `noir init` uses.\n const stale = preservedStaleLine(summary.preserved ?? []);\n if (stale !== undefined) process.stderr.write(`${stale}\\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":";;;;;;;;;;;;;AA8BA,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;AAMzC,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,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,IACrD,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;AAIrB,QAAM,OAAe,IAAI;AAGzB,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;AAIA,UAAM,QAAQ,mBAAmB,QAAQ,aAAa,CAAC,CAAC;AACxD,QAAI,UAAU,OAAW,SAAQ,OAAO,MAAM,GAAG,KAAK;AAAA,CAAI;AAAA,EAC5D;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":[]}
|