infra-kit 0.3.1 → 0.3.2

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.
Files changed (39) hide show
  1. package/dist/boot-EEEVHAMO.js +2 -0
  2. package/dist/boot-EEEVHAMO.js.map +7 -0
  3. package/dist/{chunk-V4XK2YEH.js → chunk-7F5YKMWK.js} +2 -2
  4. package/dist/chunk-7F5YKMWK.js.map +7 -0
  5. package/dist/chunk-AJW5M44V.js +231 -0
  6. package/dist/chunk-AJW5M44V.js.map +7 -0
  7. package/dist/{chunk-JPTSUMWI.js → chunk-FTCM2766.js} +2 -2
  8. package/dist/{chunk-JPTSUMWI.js.map → chunk-FTCM2766.js.map} +1 -1
  9. package/dist/chunk-IPF7ILCB.js +7 -0
  10. package/dist/{chunk-KAKTWDWJ.js.map → chunk-IPF7ILCB.js.map} +4 -4
  11. package/dist/{chunk-AVLZBOOA.js → chunk-KHEUED4B.js} +1 -1
  12. package/dist/chunk-KHEUED4B.js.map +7 -0
  13. package/dist/chunk-NLOLELZ5.js +6 -0
  14. package/dist/chunk-NLOLELZ5.js.map +7 -0
  15. package/dist/cli.js +9 -9
  16. package/dist/cli.js.map +3 -3
  17. package/dist/dev-server.js +39 -25
  18. package/dist/dev-server.js.map +4 -4
  19. package/dist/dev-wizard-run-5NOQIMR5.js +2 -0
  20. package/dist/dev-wizard-run-5NOQIMR5.js.map +7 -0
  21. package/dist/mcp.js +1 -1
  22. package/dist/mcp.js.map +3 -3
  23. package/dist/persistent-ink-dev-ui-TLUJJC2D.js +2 -0
  24. package/dist/persistent-ink-dev-ui-TLUJJC2D.js.map +7 -0
  25. package/dist/update-check.js +1 -1
  26. package/package.json +1 -1
  27. package/dist/boot-YQAPYAKT.js +0 -2
  28. package/dist/boot-YQAPYAKT.js.map +0 -7
  29. package/dist/chunk-AVLZBOOA.js.map +0 -7
  30. package/dist/chunk-KAKTWDWJ.js +0 -7
  31. package/dist/chunk-TI33V5G2.js +0 -6
  32. package/dist/chunk-TI33V5G2.js.map +0 -7
  33. package/dist/chunk-TL7LO2FF.js +0 -231
  34. package/dist/chunk-TL7LO2FF.js.map +0 -7
  35. package/dist/chunk-V4XK2YEH.js.map +0 -7
  36. package/dist/dev-wizard-run-2ISRQPGA.js +0 -2
  37. package/dist/dev-wizard-run-2ISRQPGA.js.map +0 -7
  38. package/dist/persistent-ink-dev-ui-KOI44ZVT.js +0 -2
  39. package/dist/persistent-ink-dev-ui-KOI44ZVT.js.map +0 -7
package/dist/cli.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/entry/cli.ts", "../src/lib/command-catalog/palette.ts", "../src/lib/program/program.ts", "../src/commands/config/config.ts", "../src/lib/env-autoload/auth-failure.ts", "../src/lib/env-autoload/env-autoload.ts", "../src/commands/env-autoload/env-autoload.ts", "../src/commands/env-token-remove/env-token-remove.ts", "../src/commands/env-token-set/env-token-set.ts", "../src/commands/mcp/mcp.ts", "../src/commands/self-update/self-update.ts", "../src/commands/vendor-config/vendor-config.ts", "../src/lib/session/equivalent.ts", "../src/lib/session/report.ts", "../src/lib/render/render.ts", "../src/lib/session/run-session.ts", "../src/lib/session/format-entry.ts", "../src/lib/session/outcome.ts", "../src/lib/session/reset-terminal.ts", "../src/lib/update-check/auto-update.ts", "../src/lib/update-check/guards.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n// The hashbang must stay on line 1: `npm i -g` symlinks the bin and execs it\n// directly, unlike pnpm's `.bin` shim which invokes `node <path>` explicitly.\n// esbuild preserves it on this entry only \u2014 never on the library entries.\nimport select, { Separator } from '@inquirer/select'\nimport { realpathSync } from 'node:fs'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\n\nimport { commandCatalog } from 'src/lib/command-catalog'\nimport { buildPaletteItems as buildPaletteItemsFromCommands } from 'src/lib/command-catalog/palette'\nimport type { PaletteItem } from 'src/lib/command-catalog/palette'\nimport { isPromptCancellation } from 'src/lib/errors/is-prompt-cancellation'\nimport { isLocalNodeModulesInstall, safeRealpath } from 'src/lib/install-manager'\nimport { logger } from 'src/lib/logger'\nimport { suppressTypelessPackageJsonWarning } from 'src/lib/node-warnings'\nimport { buildProgram } from 'src/lib/program'\nimport { formatAlignedRows } from 'src/lib/render'\nimport { captureSessionReportPath } from 'src/lib/session/report'\nimport { runSession, sessionGateEnabled } from 'src/lib/session/run-session'\nimport { maybeAutoUpdate } from 'src/lib/update-check'\n\nimport packageJson from '../../package.json' with { type: 'json' }\n\n// Install BEFORE any command imports a consumer's `infra-kit.config.ts` (dev, audit, vendor-config\n// all do), otherwise Node prints a MODULE_TYPELESS_PACKAGE_JSON banner naming an app package.json\n// the user cannot safely add `\"type\": \"module\"` to.\nsuppressTypelessPackageJsonWarning()\n\n// Capture (and delete from the env) the session-shell report path BEFORE any command runs, so the\n// running command can write its report while no descendant (gh / git / a hook / $EDITOR) inherits the\n// var and clobbers the file. A no-op for a normal top-level invocation (the var is unset).\ncaptureSessionReportPath()\n\nconst program = buildProgram()\n\nconst runProgram = async (argv?: string[]): Promise<void> => {\n try {\n if (argv) {\n await program.parseAsync(argv)\n } else {\n await program.parseAsync()\n }\n } catch (error) {\n // Ctrl-C / Esc out of any prompt is a deliberate back-out, not a failure:\n // exit quietly with success so it matches the explicit \"Operation cancelled.\"\n // decline path and never trips scripts/CI into treating a cancel as an error.\n if (isPromptCancellation(error)) {\n logger.info('Operation cancelled.')\n process.exit(0)\n }\n\n const message = error instanceof Error ? error.message : String(error)\n\n logger.error(message)\n process.exit(1)\n }\n}\n\n/**\n * Best-effort nudge when the CLI runs out of a project-local `node_modules`. Advisory only: it must never\n * throw and never change the exit code.\n *\n * The cwd clause inside `isLocalNodeModulesInstall` is what distinguishes a project-local install from a\n * global root \u2014 BOTH contain a `node_modules` segment (`pnpm root -g` is\n * `/Users/<me>/Library/pnpm/global/5/node_modules`). Without it we would nag every global pnpm user.\n *\n * `logger` is pino-pretty with `destination: 2`, i.e. stderr \u2014 never stdout, so `--json` payloads and the\n * MCP child's stdio framing stay clean. It is still suppressed for `--json` (machine consumers want no\n * chatter) and for `mcp` (that child's stdio should carry nothing but the transport).\n */\nconst warnIfLocalInstall = (): void => {\n try {\n if (process.env.INFRA_KIT_NO_LOCATION_WARN) return\n if (process.argv.includes('--json') || process.argv[2] === 'mcp') return\n\n if (isLocalNodeModulesInstall(realpathSync(fileURLToPath(import.meta.url)), process.cwd(), safeRealpath)) {\n logger.info('Running from a project-local node_modules. Install globally for faster startup: npm i -g infra-kit')\n }\n } catch {\n // Advisory only \u2014 a failure to advise is never a failure of the command.\n }\n}\n\nwarnIfLocalInstall()\n\n// Fire-and-forget: reads a cached timestamp, and at most once a day hands off to a detached\n// `dist/update-check.js` that fetches, and silently installs, AFTER this process exits. Adds no\n// startup latency, never throws, never changes the exit code. Opt out with INFRA_KIT_NO_AUTO_UPDATE.\nmaybeAutoUpdate(packageJson.version)\n\n/**\n * Bare-invocation interactive menu: build the grouped command palette from the single command\n * catalog and let the user pick one command (returns its name, or null when nothing is picked).\n * Interactive TTY \u2192 Ink command palette, loaded lazily via dynamic import so React/Ink never touch\n * the MCP / `--json` / non-TTY code paths. Otherwise falls back to the Inquirer menu (scripts, pipes,\n * CI). Ctrl-C / Esc at the menu backs out cleanly (returns null).\n */\n/**\n * Flat {name, description, group} palette list shared by the Ink palette, the Inquirer fallback, and\n * the session shell. Membership and order live in the command catalog; descriptions come from\n * Commander (single source). Each `name` is the canonical grouped path (`release create`).\n */\nconst buildPaletteItems = (): PaletteItem[] => {\n return buildPaletteItemsFromCommands(program.commands)\n}\n\nconst runInteractiveMenu = async (): Promise<string | null> => {\n const paletteItems = buildPaletteItems()\n\n let selected: string | null = null\n\n // Ctrl-C / Esc at the menu throws from the Inquirer fallback; treat it as a clean exit.\n try {\n if (process.stdout.isTTY && process.stdin.isTTY) {\n const { runCommandPalette } = await import('src/tui/boot')\n\n selected = await runCommandPalette(paletteItems)\n } else {\n const alignedLabels = formatAlignedRows(\n paletteItems.map((item) => {\n return [item.name, item.description] as const\n }),\n )\n\n // Headers come from the palette's own group column, which is already in MENU_GROUPS order and\n // already omits empty groups \u2014 so this cannot render a `\u2014 Label \u2014` header over nothing, and the\n // two menu surfaces cannot list different commands.\n const choices = paletteItems.flatMap((item, index) => {\n const header =\n paletteItems[index - 1]?.group === item.group ? [] : [new Separator(' '), new Separator(`\u2014 ${item.group} \u2014`)]\n\n return [...header, { name: alignedLabels[index] ?? item.name, value: item.name }]\n })\n\n selected = await select({ message: 'Select a command to run', choices }, { output: process.stderr })\n }\n } catch (error) {\n // Ctrl-C / Esc at the menu is a clean back-out; leave `selected` as null.\n if (!isPromptCancellation(error)) throw error\n }\n\n return selected\n}\n\n/**\n * Persistent session shell: bare `infra-kit` in a capable interactive TTY keeps running after each\n * command. Each pick echoes `$ infra-kit <canonical argv>` and spawns it as a fresh child that\n * inherits this terminal, so the command's real output lands in the scrollback and stays there, closed\n * out by a status footer. React-free loop logic lives in `src/lib/session`; the Ink palette is reached\n * only through the lazy `src/tui/boot` import.\n */\nconst runSessionShell = async (): Promise<void> => {\n const { runCommandPalette } = await import('src/tui/boot')\n const cliPath = fileURLToPath(import.meta.url)\n // Keyed by the SAME string the palette rows carry \u2014 the space-joined group path \u2014 so a pick resolves\n // by what it displays. Keying by `cliName` worked only while every command had a flat alias to name it.\n const catalogByName = new Map(\n commandCatalog.map((entry) => {\n return [entry.groupPath.join(' '), entry]\n }),\n )\n\n await runSession(buildPaletteItems(), {\n renderPalette: runCommandPalette,\n resolveCommand: (name) => {\n const entry = catalogByName.get(name)\n\n return entry\n ? {\n groupPath: entry.groupPath,\n entersAltScreen: entry.entersAltScreen,\n sessionEnvNotice: entry.sessionEnvNotice,\n longRunning: entry.longRunning,\n }\n : undefined\n },\n cliPath,\n })\n}\n\nif (process.argv.length <= 2) {\n const streams = {\n stdoutIsTTY: Boolean(process.stdout.isTTY),\n stdinIsTTY: Boolean(process.stdin.isTTY),\n // The palette and the transcript both render to stderr; redirect it and the shell is unusable.\n stderrIsTTY: Boolean(process.stderr.isTTY),\n }\n\n if (sessionGateEnabled(process.env, streams)) {\n // A capable interactive TTY \u2192 the persistent session shell (exit code always 0).\n await runSessionShell()\n } else {\n // Pipes / CI / non-TTY / opt-out \u2192 today's one-shot behaviour and exit codes.\n const selected = await runInteractiveMenu()\n\n // `selected` is the grouped path (`release create`), so it must be split back into argv tokens \u2014\n // passed whole, Commander would look for a single command literally named \"release create\".\n if (selected) {\n await runProgram(['node', 'infra-kit', ...selected.split(' ')])\n }\n }\n} else {\n await runProgram()\n}\n", "import type { Command } from 'commander'\n\nimport { MENU_GROUPS, getMenuGroupEntries } from './command-catalog'\n\n/**\n * The flat, grouped command list behind every no-arg surface: the Ink palette, the Inquirer fallback,\n * and the session shell. Membership and group ORDER come from the catalog's {@link MENU_GROUPS};\n * descriptions come from Commander, so there is exactly one source for each and no way to restate them\n * out of sync. (`entry/cli.ts` used to hardcode the group list three separate times.)\n */\n\n/** One palette row: what to run, what it does, and which group header it sits under. */\nexport interface PaletteItem {\n /**\n * The command's canonical grouped path, space-joined (`release create`). It is BOTH the label the row\n * renders AND the argv every surface dispatches, so a row can no longer advertise one name and run\n * another \u2014 which is exactly what the old flat `release-create` label did.\n */\n name: string\n description: string\n group: string\n}\n\n/**\n * Resolve a catalog `groupPath` to its Commander leaf by walking the command tree one token at a time\n * (`['vendor','check']` \u2192 the `check` subcommand of `vendor`).\n *\n * A flat top-level lookup cannot reach a grouped leaf, and that limitation is the whole reason those\n * leaves used to need a hidden flat sibling (`vendor-check`) for the menu to find them. Walking the tree\n * removes the reason those aliases existed.\n *\n * @example\n * resolveLeaf(program.commands, ['vendor', 'check'])?.name() // => 'check'\n */\nexport const resolveLeaf = (roots: readonly Command[], groupPath: readonly string[]): Command | undefined => {\n let level: readonly Command[] = roots\n let found: Command | undefined\n\n for (const token of groupPath) {\n found = level.find((command) => {\n return command.name() === token\n })\n\n if (!found) return undefined\n\n level = found.commands\n }\n\n return found\n}\n\n/**\n * Build the palette rows from the registered Commander program, in MENU_GROUPS order.\n *\n * A catalog entry whose `groupPath` resolves to no Commander command is SKIPPED rather than rendered as\n * a dead row the user could pick and watch fail \u2014 the catalog test asserts every menu entry resolves to\n * a real leaf, so a skip here means that guard was bypassed, not that the row is legitimately absent.\n *\n * @example\n * buildPaletteItems(program.commands)[0] // => { name: 'dev', description: 'Run local dev servers\u2026', group: 'Develop' }\n */\nexport const buildPaletteItems = (commands: readonly Command[]): PaletteItem[] => {\n return MENU_GROUPS.flatMap(({ key, label }) => {\n return getMenuGroupEntries(key).flatMap((entry) => {\n const command = resolveLeaf(commands, entry.groupPath)\n\n return command ? [{ name: entry.groupPath.join(' '), description: command.description(), group: label }] : []\n })\n })\n}\n", "import { Command } from 'commander'\nimport process from 'node:process'\n\nimport { audit } from 'src/commands/audit'\nimport { configEdit, configPath } from 'src/commands/config'\nimport { doctor } from 'src/commands/doctor'\nimport { envAutoload } from 'src/commands/env-autoload'\nimport { envClear } from 'src/commands/env-clear'\nimport { envList } from 'src/commands/env-list'\nimport { envLoad } from 'src/commands/env-load'\nimport { envStatus } from 'src/commands/env-status'\nimport { envTokenList } from 'src/commands/env-token-list'\nimport { envTokenRemove } from 'src/commands/env-token-remove'\nimport { envTokenSet } from 'src/commands/env-token-set'\nimport { ghMergeDev } from 'src/commands/gh-merge-dev'\nimport { ghReleaseDeliver } from 'src/commands/gh-release-deliver'\nimport { ghReleaseDeployAll } from 'src/commands/gh-release-deploy-all'\nimport { ghReleaseDeploySelected } from 'src/commands/gh-release-deploy-selected'\nimport { ghReleaseList } from 'src/commands/gh-release-list'\nimport { init } from 'src/commands/init'\nimport { runMcp } from 'src/commands/mcp'\nimport { releaseCreate } from 'src/commands/release-create'\nimport { releaseDescEdit } from 'src/commands/release-desc-edit'\nimport { runSelfUpdate } from 'src/commands/self-update'\nimport { vendorCheck } from 'src/commands/vendor-check'\nimport { vendorConfig } from 'src/commands/vendor-config'\nimport { vendorDiff } from 'src/commands/vendor-diff'\nimport { vendorManifest } from 'src/commands/vendor-manifest'\nimport { vendorSync } from 'src/commands/vendor-sync'\nimport { version } from 'src/commands/version'\nimport { worktreesAdd } from 'src/commands/worktrees-add'\nimport { worktreesList } from 'src/commands/worktrees-list'\nimport { worktreesReload } from 'src/commands/worktrees-reload'\nimport { worktreesRemove } from 'src/commands/worktrees-remove'\nimport { worktreesSync } from 'src/commands/worktrees-sync'\nimport { IDE_MODES } from 'src/integrations/ide'\nimport type { IdeMode } from 'src/integrations/ide'\nimport { isLongRunningCommand } from 'src/lib/command-catalog'\nimport { commandEcho } from 'src/lib/command-echo'\nimport { ensureUserProjectConfig } from 'src/lib/config-bootstrap'\nimport { runEnvAutoLoad, surfaceStickyAuthFailure } from 'src/lib/env-autoload'\nimport { addJsonOption, emit, jsonOutput } from 'src/lib/json-output'\nimport { logger } from 'src/lib/logger'\nimport { equivalentLine } from 'src/lib/session/equivalent'\nimport { writeSessionReport } from 'src/lib/session/report'\nimport { parseReleaseSpec } from 'src/lib/version-utils'\nimport type { ReleaseInput } from 'src/lib/version-utils'\n\n/**\n * Side-effect-free construction of the Commander program. It is deliberately isolated from\n * `src/entry/cli.ts` (which runs `warnIfLocalInstall()`, `maybeAutoUpdate()`, and a top-level `await`\n * at module load) so a test \u2014 or the session shell \u2014 can import and walk the command tree WITHOUT\n * triggering those boot side effects. `cli.ts` calls `buildProgram()` once and owns everything else.\n */\n\nconst collectReleaseSpec = (value: string, prev: string[]): string[] => {\n return [...prev, value]\n}\n\n/** Parse a `--repos a,b,c` option into a target-name list (undefined = all). */\nconst parseRepos = (value: unknown): string[] | undefined => {\n return typeof value === 'string' ? value.split(',').filter(Boolean) : undefined\n}\n\nconst normalizeIdeMode = (value: unknown, flagName: '--ide' | '--cursor'): IdeMode | undefined => {\n if (typeof value === 'undefined') {\n return undefined\n }\n\n if (value === true) {\n return 'workspace'\n }\n\n if (value === false) {\n return 'none'\n }\n\n if (typeof value === 'string' && (IDE_MODES as readonly string[]).includes(value)) {\n return value as IdeMode\n }\n\n throw new Error(`Invalid ${flagName} value \"${String(value)}\". Expected one of: ${IDE_MODES.join(', ')}.`)\n}\n\n// --- Command configurators (one source of options + action per command) ---\n//\n// These were factored out when each command had TWO registrations \u2014 a grouped form and a flat alias \u2014\n// that had to be kept from diverging. The flat aliases are gone; the configurators stay because they\n// keep `buildProgram` readable, and a command still gets exactly one definition.\nconst configureMergeDev = (cmd: Command): Command => {\n return cmd\n .description('Merge dev branch into every release branch')\n .option('-a, --all', 'Select all active release branches')\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n emit(await ghMergeDev({ all: options.all, confirmedCommand: options.yes }))\n })\n}\n\nconst configureReleaseList = (cmd: Command): Command => {\n return cmd.description('List all release branches').action(async () => {\n emit(await ghReleaseList())\n })\n}\n\nconst configureReleaseCreate = (cmd: Command): Command => {\n return cmd\n .description('Create one or more release branches (each entry can mix regular/hotfix and its own description)')\n .option(\n '-r, --release <spec>',\n 'Release spec \"<version|next|name>[:type[:description]]\" (repeatable). The token is a semver (\"1.2.5\"), the literal \"next\", or a kebab-case name (\"checkout-redesign\"). Type is regular|hotfix (default regular). Examples: \"1.2.5\", \"1.2.5:hotfix\", \"next:regular:Holiday backend\", \"checkout-redesign:regular:Q3 redesign\".',\n collectReleaseSpec,\n [],\n )\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n const specs = options.release as string[]\n const inputs: ReleaseInput[] = specs.map(parseReleaseSpec)\n const releases = inputs.length > 0 ? inputs : undefined\n\n emit(\n await releaseCreate({\n releases,\n confirmedCommand: options.yes,\n }),\n )\n })\n}\n\nconst configureReleaseDescEdit = (cmd: Command): Command => {\n return cmd\n .description(\"Edit a release's description in Jira and in the matching GitHub PR body\")\n .option('-v, --version <version>', 'Release version (e.g. 1.2.5) or release name (e.g. checkout-redesign)')\n .option('-d, --description <description>', 'New description (use \"\" to clear)')\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n emit(\n await releaseDescEdit({\n version: options.version,\n description: options.description,\n confirmedCommand: options.yes,\n }),\n )\n })\n}\n\nconst configureReleaseDeployAll = (cmd: Command): Command => {\n return cmd\n .description('Deploy any release branch to any environment')\n .option(\n '-v, --version <version>',\n 'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deploy; \"dev\" deploys from the dev branch',\n )\n .option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')\n .option('--skip-terraform', 'Skip terraform deployment step')\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n emit(\n await ghReleaseDeployAll({\n version: options.version,\n env: options.env,\n skipTerraform: options.skipTerraform,\n confirmedCommand: options.yes,\n }),\n )\n })\n}\n\nconst configureReleaseDeploySelected = (cmd: Command): Command => {\n return cmd\n .description('Deploy selected services from release branch to any environment')\n .option(\n '-v, --version <version>',\n 'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deploy; \"dev\" deploys from the dev branch',\n )\n .option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')\n .option('-s, --services <services...>', 'Specify services to deploy, e.g. client-be client-fe')\n .option('--skip-terraform', 'Skip terraform deployment step')\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n emit(\n await ghReleaseDeploySelected({\n version: options.version,\n env: options.env,\n services: options.services,\n skipTerraform: options.skipTerraform,\n confirmedCommand: options.yes,\n }),\n )\n })\n}\n\nconst configureReleaseDeliver = (cmd: Command): Command => {\n return cmd\n .description('Release a new version to production')\n .option('-v, --version <version>', 'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deliver')\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n emit(await ghReleaseDeliver({ version: options.version, confirmedCommand: options.yes }))\n })\n}\n\nconst configureWorktreesSync = (cmd: Command): Command => {\n return cmd\n .description('Remove release worktrees whose PRs are no longer open')\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n emit(await worktreesSync({ confirmedCommand: options.yes }))\n })\n}\n\nconst configureWorktreesAdd = (cmd: Command): Command => {\n return cmd\n .description('Add git worktrees for release branches')\n .option('-y, --yes', 'Skip confirmation prompt')\n .option('-a, --all', 'Select all active release branches')\n .option('-v, --versions <versions>', 'Specify versions by comma, e.g. 1.2.5, 1.2.6')\n .option('-i, --ide [mode]', 'Editor mode for created worktrees: workspace (default) | none')\n .option('--no-ide', 'Skip the editor (alias for --ide none)')\n .option('-c, --cursor [mode]', 'Deprecated alias for --ide')\n .option('--no-cursor', 'Deprecated alias for --no-ide')\n .option('-g, --github-desktop', 'Open created worktrees in GitHub Desktop')\n .option('--no-github-desktop', 'Skip GitHub Desktop prompt')\n .option('-m, --cmux', 'Open created worktrees in cmux (3-pane layout)')\n .option('--no-cmux', 'Skip cmux prompt')\n .action(async (options) => {\n // `--ide` wins over the deprecated `--cursor` alias when both are provided.\n const ide = normalizeIdeMode(options.ide, '--ide') ?? normalizeIdeMode(options.cursor, '--cursor')\n\n emit(\n await worktreesAdd({\n confirmedCommand: options.yes,\n all: options.all,\n versions: options.versions,\n ide,\n githubDesktop: options.githubDesktop,\n cmux: options.cmux,\n }),\n )\n })\n}\n\nconst configureWorktreesList = (cmd: Command): Command => {\n return cmd.description('List all git worktrees with detailed information').action(async () => {\n emit(await worktreesList())\n })\n}\n\nconst configureWorktreesRemove = (cmd: Command): Command => {\n return cmd\n .description('Remove git worktrees for release branches')\n .option('-y, --yes', 'Skip confirmation prompt')\n .option('-a, --all', 'Select all active release branches')\n .option('-v, --versions <versions>', 'Specify versions by comma, e.g. 1.2.5, 1.2.6')\n .action(async (options) => {\n emit(await worktreesRemove({ confirmedCommand: options.yes, all: options.all, versions: options.versions }))\n })\n}\n\nconst configureWorktreesReload = (cmd: Command): Command => {\n return cmd\n .description(\n 'Close all cmux/editor worktree windows, then reopen the current release worktrees (also cold-start restore)',\n )\n .action(async () => {\n emit(await worktreesReload())\n })\n}\n\nconst configureVendorConfig = (cmd: Command): Command => {\n return cmd\n .description('Show the machine-local factory config (~/.infra-kit/vendor.json) or scaffold it with --init')\n .option('--init', 'Scaffold ~/.infra-kit/vendor.json (skips if it already exists)')\n .action(async (options) => {\n emit(await vendorConfig({ init: options.init }))\n })\n}\n\nconst configureVendorCheck = (cmd: Command): Command => {\n return cmd\n .description('Verify vendor/ matches vendor/.sync-manifest.json (self-contained; for any consumer repo)')\n .action(async () => {\n const result = await vendorCheck()\n\n emit(result)\n\n if (!result.structuredContent.ok) {\n process.exitCode = 1\n }\n })\n}\n\nconst configureVendorDiff = (cmd: Command): Command => {\n return cmd\n .description('Source-aware drift check (rsync dry-run) of each target vendored subtree vs the source')\n .option('-r, --repos <repos>', 'Restrict to comma-separated target repo names')\n .action(async (options) => {\n const result = await vendorDiff({ repos: parseRepos(options.repos) })\n\n emit(result)\n\n if (!result.structuredContent.ok) {\n process.exitCode = 1\n }\n })\n}\n\nconst configureConfigPath = (cmd: Command): Command => {\n return cmd.description('Show the resolved config merge chain and file paths').action(async () => {\n emit(await configPath())\n })\n}\n\nconst configureConfigEdit = (cmd: Command): Command => {\n return cmd.description('Open the user-scope per-project override file in $EDITOR').action(async () => {\n emit(await configEdit())\n })\n}\n\n// Commands excluded from the cli-invocation auto-load trigger: the env-* family\n// (avoids recursion \u2014 `env-autoload`/`env-load` would re-enter), plus the\n// host-inspecting / meta commands where priming Doppler env would be surprising\n// (`init` bootstraps the shell block, `doctor` inspects auth, `version` prints a\n// string, `dev` is a long-running server that manages its own env,\n// `self-update` replaces this binary, and `mcp` hands its stdio to a child).\n// `--help`/`--version`/the bare-arg menu don't fire preAction at all.\nconst AUTO_LOAD_EXCLUDED = new Set(['init', 'doctor', 'version', 'dev', 'self-update', 'mcp'])\n\nconst isAutoLoadExcludedCommand = (name: string): boolean => {\n return name.startsWith('env-') || AUTO_LOAD_EXCLUDED.has(name)\n}\n\n// Commands excluded from the layer-3 config auto-seed. A SEPARATE, much SMALLER set than\n// AUTO_LOAD_EXCLUDED above \u2014 the asymmetry is intentional, not an oversight:\n// - `env-autoload` is the hidden command the zsh precmd hook fires BACKGROUNDED on every prompt\n// (see the shell body in init.ts). It runs constantly; the seed has no business on that path.\n// - `mcp` is excluded for LAZINESS, not cwd: the MCP server seeds at its first TOOL INVOCATION\n// (lib/tool-handler), so a server that never receives a tool call never writes to $HOME.\n// - `version` / `self-update` touch config ZERO times today, so seeding is pure new cost on the two\n// fastest paths; `self-update` also re-execs the binary.\n// Everything else DOES seed \u2014 `doctor`, `config path`, `dev` and `init` included (they are all in\n// AUTO_LOAD_EXCLUDED, but that set answers a different question), as does the whole `env-*` family\n// apart from `env-autoload`.\nconst SEED_EXCLUDED = new Set(['env-autoload', 'mcp', 'version', 'self-update'])\n\n/**\n * Canonical space-joined command path for a leaf (e.g. the `check` leaf of `vendor` \u2192 \"vendor check\").\n * Stops at the root program (a node with no parent) so its name \u2014 the bin basename like \"cli\" \u2014 never\n * leaks into the equivalent line.\n */\nexport const commandPath = (leaf: Command): string => {\n const parts: string[] = []\n\n for (let node: Command | null = leaf; node && node.parent; node = node.parent) {\n parts.unshift(node.name())\n }\n\n return parts.join(' ')\n}\n\n/**\n * Build the full Commander program: ONE surface per command (the grouped form \u2014 `release create`, never\n * `release-create`), plus `--json` on every command and the pre-action auto-load hook. Pure: no I/O, no\n * top-level await, no process mutation \u2014 safe to import from a test to introspect the command tree.\n *\n * @example\n * const program = buildProgram()\n * program.commands.some((c) => c.name() === 'doctor') // => true\n */\nexport const buildProgram = (): Command => {\n const program = new Command()\n\n // --- Grouped command surface (preferred form) ---\n const releaseGroup = program.command('release').description('Release management commands')\n\n configureMergeDev(releaseGroup.command('merge-dev'))\n configureReleaseList(releaseGroup.command('list'))\n configureReleaseCreate(releaseGroup.command('create'))\n configureReleaseDescEdit(releaseGroup.command('desc-edit'))\n configureReleaseDeployAll(releaseGroup.command('deploy-all'))\n configureReleaseDeploySelected(releaseGroup.command('deploy-selected'))\n configureReleaseDeliver(releaseGroup.command('deliver'))\n\n const worktreesGroup = program.command('worktrees').description('Git worktree management commands')\n\n configureWorktreesAdd(worktreesGroup.command('add'))\n configureWorktreesList(worktreesGroup.command('list'))\n configureWorktreesRemove(worktreesGroup.command('remove'))\n configureWorktreesSync(worktreesGroup.command('sync'))\n configureWorktreesReload(worktreesGroup.command('reload'))\n\n const configCmd = program.command('config').description('Manage infra-kit configuration files')\n\n configureConfigPath(configCmd.command('path'))\n configureConfigEdit(configCmd.command('edit'))\n\n program\n .command('audit')\n .description('Audit against infra-kit.config.ts rules (--all for every package, --root for the monorepo root)')\n .option('-a, --all', 'Audit every non-vendor workspace package')\n .option('-r, --root', 'Audit the monorepo root (turbo pipeline + root commands)')\n .action(async (options) => {\n const result = await audit({ all: options.all, root: options.root })\n\n emit(result)\n\n if (!result.structuredContent.allPassed) {\n process.exitCode = 1\n }\n })\n\n const vendorCmd = program.command('vendor').description('Verify and sync the mirrored vendor/ tree')\n\n configureVendorCheck(vendorCmd.command('check'))\n\n vendorCmd\n .command('sync')\n .description('Copy vendored files from the source repo into each target and regenerate manifests')\n .option('-y, --yes', 'Skip confirmation prompt')\n .option('-r, --repos <repos>', 'Restrict to comma-separated target repo names')\n .action(async (options) => {\n emit(await vendorSync({ confirmedCommand: options.yes, repos: parseRepos(options.repos) }))\n })\n\n vendorCmd\n .command('manifest')\n .description('Regenerate each target vendor/.sync-manifest.json + README from current content (no copy)')\n .option('-r, --repos <repos>', 'Restrict to comma-separated target repo names')\n .action(async (options) => {\n emit(await vendorManifest({ confirmedCommand: true, repos: parseRepos(options.repos) }))\n })\n\n configureVendorDiff(vendorCmd.command('diff'))\n configureVendorConfig(vendorCmd.command('config'))\n\n program\n .command('doctor')\n .description('Check installation and authentication status of gh and doppler CLIs')\n .option(\n '--fix',\n 'Remove portless routes left behind by a dev-server that was killed (kill -9, OOM, force-quit). Refuses while a dev session is running, when a booting UI is indistinguishable from a dead route.',\n )\n .action(async (options) => {\n emit(await doctor({ fix: Boolean(options.fix) }))\n })\n\n // No `update` alias: the `update` verb is reserved.\n program\n .command('self-update')\n .description('Update this CLI using the package manager that installed it')\n .option('--dry-run', 'Print the detected manager and the command that would run; install nothing')\n .action((options) => {\n runSelfUpdate({ dryRun: Boolean(options.dryRun) })\n })\n\n program\n .command('mcp')\n .description('Run the infra-kit MCP server (stdio transport)')\n .action(() => {\n runMcp()\n })\n\n program\n .command('dev')\n .description('Run local dev servers for a named devServersPresets preset (or all apps); api + ui')\n .argument('[preset]', 'Named preset from devServersPresets (omit to run every app)')\n .option('-w, --watch', 'Rebuild and restart on file save')\n .option('--app <names>', 'Further narrow to these app folder names (comma-separated)')\n .option(\n '--target <keys>',\n 'Run exactly these <app>/api|<app>/ui packages (comma-separated); part-level, unlike --app',\n )\n .option(\n '--cmux',\n 'Run each app in its own cmux pane (one workspace, N panes; falls back to single terminal if cmux is unavailable)',\n )\n .option('--self', 'Run only the app of the current directory (infer from cwd; use inside apps/<app>/\u2026)')\n .option('-V, --verbose', 'Print full boot narration (default: quiet; full detail always in the session log)')\n .option('--routes', 'Print each app\u2019s registered METHOD /path routes at startup (default: off)')\n .action(async (preset, options) => {\n // Lazy import so fastify/chokidar (and the whole dev stack, plus the wizard's inquirer/config\n // graph) never load on the eager cli graph \u2014 they land in a split chunk reached only for `dev`.\n const { runDevServerCli } = await import('src/entry/dev-server')\n\n // A bare `infra-kit dev` in a TTY launches the interactive wizard; any flag/preset/pipe/--json\n // runs directly. `runDevServerCli` owns that decision (see `shouldRunWizard`).\n const tty = Boolean(process.stdout.isTTY && process.stdin.isTTY)\n\n await runDevServerCli({ ...options, preset }, tty, jsonOutput.enabled)\n })\n\n program\n .command('version')\n .description('Print the installed infra-kit CLI version')\n .action(async () => {\n emit(await version())\n })\n\n program\n .command('env-status')\n .description('Show which env is loaded in this session (local introspection; no Doppler call)')\n .action(async () => {\n emit(await envStatus())\n })\n\n program\n .command('env-list')\n .description(\n 'List available Doppler configs for the detected project, and whether a service token resolves for each',\n )\n .action(async () => {\n emit(await envList())\n })\n\n program\n .command('init')\n .description('Inject shell integration into .zshrc and sync repo agent-instruction files')\n .action(async () => {\n emit(await init())\n })\n\n program\n .command('env-load')\n .description('Load Doppler env vars for a config. Source the returned file path to apply.')\n .option('-c, --config <config>', 'Environment config name to load (e.g. dev, arthur)')\n .action(async (options) => {\n emit(await envLoad({ config: options.config }))\n })\n\n program\n .command('env-clear')\n .description('Clear loaded env vars. Source the returned file path to apply.')\n .option('--purge', \"Also delete this project's warm cache outright (durable disable)\")\n .action(async (options) => {\n emit(await envClear({ purge: Boolean(options.purge) }))\n })\n\n // --- Doppler service tokens (flat, related names \u2014 not a nested `env token <sub>` group) ---\n //\n // There is deliberately NO `--token <value>` flag on `env-token-set`, and there must never be one:\n // argv is world-visible in `ps`, lands in shell history, and `commandEcho` prints option VALUES back\n // to the terminal. The three input channels below all keep the token out of argv.\n program\n .command('env-token-set')\n .description('Store the Doppler service token for an env (masked prompt; validated against Doppler before writing)')\n .argument('<env>', 'Environment / Doppler config the token is scoped to (e.g. dev)')\n .option('--stdin', 'Read the token from stdin instead of prompting (e.g. from a password manager)')\n .option('--from-env <var>', 'Read the token from the named environment variable (the NAME, never the value)')\n .option('--force', 'Store even when the token\u2019s scope could not be verified. Never overrides a real mismatch.')\n .action(async (env, options) => {\n emit(\n await envTokenSet({\n env,\n stdin: options.stdin,\n fromEnv: options.fromEnv,\n force: options.force,\n }),\n )\n })\n\n program\n .command('env-token-list')\n .description('Show which envs have a Doppler service token (redacted), and where it came from')\n .option('--check', 'Also ask Doppler whether each token is valid and correctly scoped')\n .action(async (options) => {\n emit(await envTokenList({ check: Boolean(options.check) }))\n })\n\n program\n .command('env-token-remove')\n .description('Delete an env\u2019s Doppler service token from the local store (does NOT revoke it in Doppler)')\n .argument('<env>', 'Environment / Doppler config whose token to remove')\n .action(async (env) => {\n emit(await envTokenRemove({ env }))\n })\n\n // Internal: driven by the init shell-startup integration (backgrounded). Writes\n // env-load.sh when envAutoLoad is configured + eligible; the precmd hook sources\n // it. Hidden + no stdout output so it never pollutes the shell or the menu.\n program\n .command('env-autoload', { hidden: true })\n .description('Internal: prime env for the shell-startup auto-load trigger')\n // The shell passes its already-canonicalized (`${dir:A}`) project dir so node can\n // key the warm cache identically; see writeEnvLoadFile / shouldWriteWarm.\n .option('--project-dir <dir>', 'Canonical project dir for the warm-cache key (shell-startup only)')\n .action(async (options) => {\n await envAutoload({ projectDir: options.projectDir })\n })\n\n // Register `--json` on every command, then resolve the flag before each action\n // runs. In JSON mode we lower the logger to `warn` so the human-oriented info\n // lines stop cluttering stderr while errors still surface; the structured\n // payload is written to stdout by `emit`. No handler logic is affected.\n program.commands.forEach(addJsonOption)\n\n program.hook('preAction', async (_thisCommand, actionCommand) => {\n // Bind the \"\uD83D\uDCDF Equivalent command\" line to the argv Commander just parsed. This is the only place\n // that knows it, so it is the only place that says it: when the commands named themselves, they\n // printed the flat `release-create`, which the grouped-only surface no longer parses \u2014 a replay line\n // that errors out. `commandPath()` is the same string the menu spawns, so what we print is runnable\n // by construction, and stays runnable through any future regrouping.\n commandEcho.start(commandPath(actionCommand))\n\n // `optsWithGlobals` (not `opts`) so `--json` is seen on grouped subcommands:\n // for `release list --json` Commander binds the post-subcommand flag to the\n // parent `release` group, so the leaf's own `opts()` would not carry it.\n jsonOutput.enabled = Boolean(actionCommand.optsWithGlobals().json)\n\n if (jsonOutput.enabled) {\n logger.level = 'warn'\n }\n\n // Layer-3 config auto-seed: ensure ~/.infra-kit/projects/<main-repo>/infra-kit.json exists (plus\n // its annotated .example.jsonc sibling) so a user always has a per-project override file to edit.\n // Self-gating: no-ops outside a git repo, and when the repo has no committed `infra-kit.json`.\n // Never throws, never changes the exit code, and performs zero writes in the steady state.\n //\n // Gated on `commandPath()`, NOT `actionCommand.name()`: a leaf's `name()` is only its last\n // segment (`config path`'s name() is just 'path'), so a name-keyed set cannot distinguish grouped\n // leaves and would collide. Nothing in SEED_EXCLUDED is a grouped leaf today, but the set must be\n // able to express one without a silent trap.\n //\n // Placed after the `--json` warn downgrade purely so a first-run `logger.info` stays out of a\n // machine consumer's stderr. That is COSMETIC, not stdout safety: `logger` is pino with\n // `destination: 2` (stderr) and `emit()` is the sole stdout writer, so the seed's log line could\n // never corrupt `--json` stdout.\n if (!SEED_EXCLUDED.has(commandPath(actionCommand))) {\n await ensureUserProjectConfig()\n }\n\n // Replay a sticky Doppler auth failure (revoked / mis-scoped service token) recorded\n // by the BACKGROUNDED shell-startup auto-load, which runs with stderr discarded and\n // so has no channel of its own. Deliberately OUTSIDE the auto-load exclusion below:\n // warning is not loading. `version`, `doctor` and `dev` never auto-load, but they are\n // exactly what a user whose shell env went quiet runs next \u2014 gating the warning on the\n // same set would leave them warned by nothing. Warns at most once per session, never\n // throws, and writes nothing to stdout.\n surfaceStickyAuthFailure()\n\n // cli-invocation auto-load: primes the shell env for SUBSEQUENT commands. The\n // current command does NOT see these vars \u2014 a child process can't mutate its\n // parent shell; the precmd hook sources the written file on the next prompt.\n // runEnvAutoLoad self-gates on config trigger and swallows transient failures,\n // so this is a no-op unless configured for cli-invocation and never blocks.\n if (!isAutoLoadExcludedCommand(actionCommand.name())) {\n await runEnvAutoLoad({ expectedTrigger: 'cli-invocation' })\n }\n })\n\n // Session-shell side channel: after a leaf action RESOLVES (Commander runs postAction only on\n // success \u2014 never when the action throws or calls process.exit), write the report file the parent\n // reads. Its presence is what lets the parent tell a completed run from a cancel. A no-op outside a\n // session (no captured report path) and never on the MCP path (that never builds this program).\n program.hook('postAction', (_thisCommand, actionCommand) => {\n const path = commandPath(actionCommand)\n\n // A long-running command (`dev`) resolves its action SECONDS AFTER BOOT and then keeps running on\n // its open handles. Writing a report here would tell the parent \"this produced a verdict\" while the\n // server is merely starting \u2014 and report-presence is exactly how the parent tells a completed run\n // from a cancel. It would stamp a Ctrl-C out of dev `\u26A0 completed with findings`, and an Esc out of\n // the dev wizard `\u2713 ok` (claiming a server ran when none did). No report: the exit code decides.\n if (isLongRunningCommand(path)) {\n return\n }\n\n const snapshot = commandEcho.snapshot()\n const flags = snapshot?.formattedOptions ?? ''\n const suffix = flags ? ` ${flags}` : ''\n const line = `infra-kit ${path}${suffix}`\n\n writeSessionReport({ equivalent: equivalentLine(line, true) })\n })\n\n return program\n}\n", "import process from 'node:process'\nimport { $ } from 'zx'\n\nimport { seedCreatedMessage, seedUserProjectConfig } from 'src/lib/config-bootstrap'\nimport { describeOverrides, readOverrideSummary } from 'src/lib/config-overrides'\nimport { getInfraKitConfigPaths, resetInfraKitConfigCache } from 'src/lib/infra-kit-config'\nimport { logger } from 'src/lib/logger'\nimport { fileExists, tildify } from 'src/lib/path-display'\nimport type { ToolsExecutionResult } from 'src/types'\n\n/**\n * Print the file paths that participate in the config merge chain along with existence markers, so\n * the user can see at a glance which override layers are active. The user-project row additionally\n * reports its CONTENT \u2014 an auto-seeded empty file is indistinguishable from a real one by existence\n * alone, since every command now seeds layer 3.\n *\n * `layers[].exists` keeps its original meaning (the file exists) and still drives the markers;\n * `hasOverrides` / `overrideKeys` are top-level additions describing the user-project layer only.\n *\n * @example\n * // CLI: `infra-kit config path`\n * // INFO: Project name: api\n * // INFO: Config merge chain (later overrides earlier):\n * // INFO: [\u2713] project (committed) ~/projects/api/infra-kit.json\n * // INFO: [ ] user global ~/.infra-kit/infra-kit.json\n * // INFO: [\u2713] user project ~/.infra-kit/projects/api/infra-kit.json (2 override(s): ide, dev)\n */\nexport const configPath = async (): Promise<ToolsExecutionResult> => {\n const paths = await getInfraKitConfigPaths()\n\n const rows: { label: string; path: string; exists: boolean }[] = await Promise.all(\n [\n { label: 'project (committed)', path: paths.main },\n { label: 'user global', path: paths.userGlobal },\n { label: 'user project', path: paths.userProject },\n ].map(async (row) => {\n return { ...row, exists: await fileExists(row.path) }\n }),\n )\n\n const userProjectExists = rows.at(-1)?.exists === true\n const summary = await readOverrideSummary(paths.userProject, userProjectExists)\n // Absent layer-3: no suffix \u2014 the `[ ]` marker already says it. (`doctor` owns the louder\n // \"the seed should have created this\" reading of the same state.)\n const note = userProjectExists ? describeOverrides(summary) : ''\n\n logger.info(`Project name: ${paths.projectName}\\n`)\n logger.info('Config merge chain (later overrides earlier):\\n')\n\n for (const row of rows) {\n const marker = row.exists ? ' [\u2713]' : ' [ ]'\n const suffix = row.path === paths.userProject && note !== '' ? ` ${note}` : ''\n\n logger.info(`${marker} ${row.label.padEnd(22)} ${tildify(row.path)}${suffix}`)\n }\n\n const structuredContent = {\n projectName: paths.projectName,\n layers: rows.map((r) => {\n return { label: r.label, path: r.path, exists: r.exists }\n }),\n hasOverrides: summary.hasOverrides,\n overrideKeys: summary.overrideKeys,\n }\n\n return {\n content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }],\n structuredContent,\n }\n}\n\n/**\n * Open the user-scope per-project override file in $EDITOR, creating it (and refreshing its\n * annotated `.example.jsonc` sibling) on first use. Resets the config cache after the editor exits\n * so subsequent reads pick up the user's edits without a restart.\n *\n * This double-seeds in practice: the program's preAction hook already ran the gated\n * `ensureUserProjectConfig()`, and this runs the ungated primitive again. `seedUserProjectConfig` is\n * idempotent (zero writes in steady state), so that is harmless \u2014 and the ungated call must STAY: it\n * is what makes `config edit` work in a context where the gate declined (no committed project\n * config, `INFRA_KIT_NO_SEED`, or a seed that failed). Asking to edit the file is consent to create\n * it.\n *\n * @example\n * // CLI: `infra-kit config edit`\n * // first run \u2014 creates ~/.infra-kit/projects/api/infra-kit.json ({}) + a sibling\n * // infra-kit.example.jsonc reference, then $EDITOR opens the .json\n * // subsequent runs \u2014 opens the existing file as-is (a stale example is refreshed silently)\n */\nexport const configEdit = async (): Promise<ToolsExecutionResult> => {\n const paths = await getInfraKitConfigPaths()\n const editor = process.env.EDITOR || process.env.VISUAL || 'vi'\n\n const seed = await seedUserProjectConfig(paths)\n\n if (seed.createdConfig) {\n logger.info(seedCreatedMessage(seed))\n }\n\n logger.info(`Opening ${tildify(paths.userProject)} in ${editor}`)\n\n await $({ stdio: 'inherit' })`${editor} ${paths.userProject}`\n\n resetInfraKitConfigCache()\n\n const structuredContent = { path: paths.userProject, editor }\n\n return {\n content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }],\n structuredContent,\n }\n}\n", "/**\n * Auth-class failure policy for env auto-load: classification + message, both pure\n * (no I/O) so they are unit-testable without Doppler or a shell. The marker's disk\n * I/O lives next to the other session-cache helpers in `env-autoload.ts`.\n *\n * WHY this is a separate concern from the transient failure marker: under the\n * token-only Doppler model a user with no (or a revoked/mis-scoped) service token\n * silently stops getting env vars in new shells. The shell-startup spawn is\n * backgrounded with stderr discarded, so that failure has NO channel of its own \u2014\n * the sticky marker written here is what carries it to the next interactive\n * command. A 30s backoff marker would expire long before the user runs one.\n */\n/**\n * Decide whether a caught failure is an AUTH-class failure \u2014 durable and the user's to\n * fix (no token minted, a bad/revoked/mis-scoped token, an unreadable token store) \u2014\n * rather than a transient one (network, timeout, Doppler down).\n *\n * Takes the ERROR, not its text. env-autoload catches what `env-load` THREW, and by\n * then `translateDopplerDownloadError` has already replaced Doppler's raw stderr with\n * a human-facing message \u2014 so the raw markers this used to grep for were never there.\n * That is exactly how a revoked token shipped silent with green tests on both sides.\n * The classification now rides ON the error (`EnvAuthError`).\n *\n * Injected as `isAuthFailure` into `runEnvAutoLoad` so the auto-load policy stays\n * testable without a Doppler in the loop; the DEFAULT is `isEnvAuthFailure`, the type\n * guard from `lib/errors/env-auth-error`.\n *\n * That default is deliberately WIDER than `isDopplerAuthError`. Narrowing it back to\n * \"Doppler refused us\" re-opens the bug in its second form: a MISSING token (the\n * migration case \u2014 the user upgraded and never minted one) never reaches Doppler at\n * all, and a CORRUPT store never reaches the resolver, so neither would be auth-class,\n * neither would write the sticky marker, and both would go silent forever.\n */\nexport type AuthFailureClassifier = (error: unknown) => boolean\n\n/** The sticky auth-failure marker's on-disk shape (`autoload-auth-fail.json`). */\nexport interface AuthFailureMarker {\n /** The env / Doppler config name the failed auto-load targeted. */\n config: string\n /** The raw failure text. Debug-only \u2014 NEVER rendered into the user-facing warning. */\n reason: string\n /** Unix ms the failure was recorded. Informational: the marker never expires. */\n at: number\n}\n\n/**\n * Narrow arbitrary parsed JSON to an {@link AuthFailureMarker}. A hand-corrupted or\n * truncated marker must degrade to \"no marker\", never to a thrown error on the hot\n * path of every command.\n *\n * @example\n * parseAuthFailureMarker({ config: 'dev', reason: 'Invalid Auth token', at: 1 }) // => the marker\n * parseAuthFailureMarker({ config: 'dev' }) // => null\n */\nexport const parseAuthFailureMarker = (value: unknown): AuthFailureMarker | null => {\n if (typeof value !== 'object' || value === null) return null\n\n const { config, reason, at } = value as Partial<AuthFailureMarker>\n\n if (typeof config !== 'string' || config.length === 0) return null\n\n if (typeof reason !== 'string') return null\n\n if (typeof at !== 'number' || !Number.isFinite(at)) return null\n\n return { config, reason, at }\n}\n\n/**\n * The one-line, actionable warning for a sticky auth failure. Names the env, states\n * that the token is missing/invalid/unreadable, gives the exact fix command, and points\n * at the Doppler dashboard page a replacement token is minted on.\n *\n * \"MISSING, INVALID, OR UNREADABLE\" spans the whole auth class on purpose. It covers the\n * three durable failures that all land here and all share this one entry point:\n * - missing \u2014 no token minted for this env (the MIGRATION case: upgraded, never ran env-token-set);\n * - invalid \u2014 Doppler refused it (revoked, garbage, or scoped to another config);\n * - unreadable \u2014 the token STORE is corrupt JSON, or belongs to a same-named foreign repo.\n * The last of these is neither invalid nor missing, and calling it either would misdescribe the\n * user's machine. `env-token-set` is still the right first move for all three \u2014 it is where a\n * broken store surfaces its own, more specific \"fix or delete the file\" refusal.\n *\n * Deliberately does NOT interpolate the marker's `reason`: the warning is the one\n * thing guaranteed to reach a human, and no token value may ever be printed. The\n * reason goes to `logger.debug` instead.\n *\n * The fix command is written in a BACKTICK code span, which is the repo convention\n * for naming a command to a human AND the anchor `program.test.ts` scans to prove\n * every command we print is a command we accept. Written without backticks it would\n * be invisible to that guard and free to rot.\n *\n * (Which is also why this @example names the command as `infra-kit env-token-set <env>`\n * rather than with a literal arg: to that scan an `<env>` placeholder ends the command,\n * but a literal `dev` is indistinguishable from a subcommand and reads as a dead one.)\n *\n * @example\n * buildAuthFailureWarning('dev')\n * // => 'infra-kit: Doppler token for env \"dev\" is missing, invalid, or unreadable \u2014 env\n * // auto-load is not running. Fix: run `infra-kit env-token-set <env>` (mint one at\n * // https://dashboard.doppler.com under this config's Access tab)', with <env> = dev\n */\nexport const buildAuthFailureWarning = (config: string): string => {\n return (\n `infra-kit: Doppler token for env \"${config}\" is missing, invalid, or unreadable \u2014 env auto-load is not running. ` +\n `Fix: run \\`infra-kit env-token-set ${config}\\` (mint one at https://dashboard.doppler.com under this config's Access tab).`\n )\n}\n", "import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\n\nimport { writeEnvLoadFile } from 'src/commands/env-load'\nimport { envTokenExists } from 'src/integrations/doppler/token-resolver'\nimport {\n ENV_CLEAR_FILE,\n ENV_LOAD_FILE,\n INFRA_KIT_ENV_AUTOLOADED_VAR,\n INFRA_KIT_ENV_CLEARED_VAR,\n INFRA_KIT_ENV_CONFIG_VAR,\n INFRA_KIT_ENV_PROJECT_VAR,\n INFRA_KIT_SESSION_VAR,\n atomicWriteFileSync,\n getSessionCacheDir,\n} from 'src/lib/constants'\nimport { isEnvAuthFailure } from 'src/lib/errors/env-auth-error'\nimport type { EnvAutoLoadConfig } from 'src/lib/infra-kit-config'\nimport { getInfraKitConfig } from 'src/lib/infra-kit-config'\nimport { logger } from 'src/lib/logger'\n\nimport type { AuthFailureClassifier, AuthFailureMarker } from './auth-failure'\nimport { buildAuthFailureWarning, parseAuthFailureMarker } from './auth-failure'\n\n/** Which moment a concrete callsite represents. Matches the config `trigger`. */\nexport type AutoLoadTrigger = EnvAutoLoadConfig['trigger']\n\n/** Per-session flag de-duping the transient-FAILURE warning (Doppler down/unauth). */\nconst WARN_FAIL_SENTINEL_FILE = 'autoload-warn-fail.flag'\n/** Per-session flag de-duping the AUTH-FAILURE warning (bad/missing service token). */\nconst WARN_AUTH_FAIL_SENTINEL_FILE = 'autoload-warn-auth-fail.flag'\n\n/** Per-session marker recording the last auto-load failure (mtime = when). */\nconst FAIL_SENTINEL_FILE = 'autoload-fail.flag'\n\n/**\n * Per-session STICKY marker recording the last AUTH-class auto-load failure. Unlike\n * {@link FAIL_SENTINEL_FILE} it carries a payload and has NO expiry: a token failure\n * does not heal on a 30s timer, and the only human who can fix it may not run an\n * interactive command for hours.\n */\nconst AUTH_FAIL_MARKER_FILE = 'autoload-auth-fail.json'\n\n/**\n * After a failed auto-load, suppress retries for this long so a down/unauthenticated\n * Doppler isn't re-probed on every cli-invocation. A new shell starts a fresh\n * session cache dir, so this only throttles within one session.\n */\nconst FAIL_BACKOFF_MS = 30_000\n\n/** Resolved auto-load inputs: the chosen trigger + the env config and Doppler project to load. */\nexport interface ResolvedEnvAutoLoad {\n trigger: AutoLoadTrigger\n config: string\n project: string\n}\n\n/**\n * Read the resolved + validated env auto-load inputs, or `null` when auto-load\n * should not run. Returns `null` (never throws) when:\n * - we're not inside an infra-kit project (getInfraKitConfig throws), or config is unreadable;\n * - `envAutoLoad` is absent (feature off);\n * - no service token resolves for `envAutoLoad.config` \u2014 warns once per session, then disables.\n *\n * Validation lives here (not in the schema) so a typo only disables this optional\n * feature instead of throwing inside the merged-config parse and breaking every command.\n * Resolves the Doppler project from the SAME config read (no second getInfraKitConfig),\n * so the skip path stays cheap.\n *\n * `canWarn` gates the WARNING only, never the STICKY MARKER: the shell-startup callsite\n * runs backgrounded with stderr discarded, so warning there is invisible \u2014 but a missing\n * token is one of the three durable auth-class failures {@link buildAuthFailureWarning}\n * documents (alongside a revoked/mis-scoped token and a corrupt store), so it is recorded\n * unconditionally and replayed by {@link surfaceStickyAuthFailure} on the next interactive\n * command \u2014 the channel that actually reaches the user. Skipping the record here would\n * strand the migration case (upgraded, never minted a token) with no channel at all.\n */\nexport const resolveEnvAutoLoad = async (canWarn = true): Promise<ResolvedEnvAutoLoad | null> => {\n let config\n\n try {\n config = await getInfraKitConfig()\n } catch {\n return null\n }\n\n const autoLoad = config.envAutoLoad\n\n if (!autoLoad) return null\n\n const resolved = {\n trigger: autoLoad.trigger,\n config: autoLoad.config,\n project: config.envManagement.config.name,\n }\n\n // The authority for \"is this env usable?\" is the token store, not a declared list \u2014 a name we hold no\n // token for cannot be loaded no matter who declared it. Checking it HERE (a local file read) keeps a\n // typo'd `envAutoLoad.config` from spawning a doomed Doppler download at every single shell open.\n try {\n if (await envTokenExists(autoLoad.config)) return resolved\n } catch {\n // The store itself is broken \u2014 a different failure entirely, and NOT one to disable quietly on.\n // Proceed so the loader hits `resolveEnvToken`'s EnvAuthError and the sticky auth-failure marker\n // surfaces it on the next channel that can actually be seen.\n return resolved\n }\n\n // No token minted for this env \u2014 the MIGRATION case (upgraded, never ran\n // `env-token-set`). Record it via the SAME sticky-marker mechanism a thrown auth\n // failure uses, unconditionally, so the backgrounded shell-startup spawn's silent\n // disable is not lost \u2014 the next interactive command's `surfaceStickyAuthFailure()`\n // replay is what actually reaches the user.\n recordAuthFailure({\n config: autoLoad.config,\n reason: `No Doppler service token for env \"${autoLoad.config}\"`,\n at: Date.now(),\n })\n\n if (canWarn) {\n warnOnce(buildAuthFailureWarning(autoLoad.config), WARN_AUTH_FAIL_SENTINEL_FILE)\n }\n\n return null\n}\n\n/** The env-var snapshot the freshness/suppression guards read. */\nexport interface AutoLoadEnvSnapshot {\n session?: string\n cleared?: string\n currentConfig?: string\n currentProject?: string\n autoLoadedMarker?: string\n}\n\nexport interface AutoLoadDecisionInput {\n /** The configured trigger. */\n trigger: AutoLoadTrigger\n /** Which trigger this callsite represents. */\n expectedTrigger: AutoLoadTrigger\n targetConfig: string\n targetProject: string\n env: AutoLoadEnvSnapshot\n /**\n * Bypass ONLY the \"already auto-loaded, same config+project\" no-op skip, forcing\n * a fresh Doppler fetch. The shell-startup refresh sets this: after a WARM source\n * the shell has already exported INFRA_KIT_ENV_AUTOLOADED + the same config, which\n * the child process inherits \u2014 without `force` the refresh would self-skip and the\n * warm (possibly rotated) secrets would never be replaced this session. Does NOT\n * relax the clear/manual-load guards.\n */\n force?: boolean\n}\n\nexport type AutoLoadDecision = 'load' | 'skip'\n\n/**\n * Pure decision: should this callsite (auto-)load env right now? Encodes the full\n * guard matrix so it is exhaustively unit-testable without Doppler or a shell:\n * - the configured trigger must match this callsite;\n * - a session must exist (the cache dir is session-scoped);\n * - an explicit clear suppresses auto-load (M2);\n * - a MANUAL load (config set, no auto marker) is never clobbered (C1);\n * - an auto-load already fresh for the same config AND project is a no-op\n * (project-aware so two same-named configs across different-repo worktrees\n * sharing one session don't leak each other's secrets).\n */\nexport const decideAutoLoad = (input: AutoLoadDecisionInput): AutoLoadDecision => {\n const { trigger, expectedTrigger, targetConfig, targetProject, env, force } = input\n\n if (trigger !== expectedTrigger) return 'skip'\n\n if (!env.session) return 'skip'\n\n if (env.cleared) return 'skip'\n\n // Manual load present (a config is loaded but it wasn't auto-loaded) \u2014 leave it.\n if (env.currentConfig && !env.autoLoadedMarker) return 'skip'\n\n // Our own auto-load already matches the target config+project \u2014 normally a no-op,\n // but `force` (the shell-startup warm refresh) must re-fetch: the shell just warm\n // -sourced these same markers, so skipping here would strand stale secrets.\n if (!force && env.autoLoadedMarker && env.currentConfig === targetConfig && env.currentProject === targetProject) {\n return 'skip'\n }\n\n return 'load'\n}\n\nexport interface RunEnvAutoLoadArgs {\n expectedTrigger: AutoLoadTrigger\n /**\n * Canonical (realpath'd) project dir, forwarded to `writeEnvLoadFile` to enable\n * the project-scoped WARM cache. Only the shell-startup spawn passes it (via\n * `--project-dir`); the cli-invocation trigger omits it, so warm is a\n * shell-startup-only optimization.\n */\n projectDir?: string\n /**\n * Force a fresh fetch past the \"already auto-loaded, same config\" no-op skip. The\n * shell-startup refresh sets this so a preceding WARM source (which exports the\n * same markers the child inherits) is always replaced by fresh secrets. See\n * {@link decideAutoLoad}. The cli-invocation trigger leaves it false.\n */\n force?: boolean\n /**\n * Auth-class predicate over the CAUGHT ERROR (not its text), injected so the\n * auto-load policy is testable without a Doppler in the loop. Defaults to\n * `isEnvAuthFailure` \u2014 the type guard for EVERY durable, user-fixable env-auth\n * failure, not just the ones Doppler itself rejected.\n *\n * It was `isDopplerAuthError` and that was too NARROW: a missing token never\n * reaches Doppler, and a corrupt token store never reaches the resolver, so both\n * fell through to \"transient\", expired with the 30s backoff, and went silent \u2014\n * the missing-token one being exactly the migration case this whole feature exists\n * to survive. See `./auth-failure.ts` for why classifying by message text was the\n * first version of this same bug.\n */\n isAuthFailure?: AuthFailureClassifier\n}\n\n/**\n * Resolve config, evaluate the guards, and (if it should) produce env-load.sh with\n * the auto-load marker. Returns the written file path, or `null` when auto-load was\n * skipped or failed. NEVER throws. Transient failures (Doppler offline / network)\n * record a 30s backoff marker so they aren't re-probed on every command, and are\n * surfaced once per session on the interactive (cli-invocation) channel. AUTH-class\n * failures (invalid / missing / mis-scoped service token) additionally record a\n * STICKY marker that is replayed by {@link surfaceStickyAuthFailure} below. No\n * producer-side lock \u2014 a rare cold-shell double-fetch is tolerated (the second write\n * is atomic and idempotent).\n */\nexport const runEnvAutoLoad = async ({\n expectedTrigger,\n projectDir,\n force,\n isAuthFailure = isEnvAuthFailure,\n}: RunEnvAutoLoadArgs): Promise<string | null> => {\n // Only the cli-invocation / interactive callsite reaches a TTY; the shell-startup\n // spawn discards stderr, so warning there is invisible and would poison the dedup.\n const canWarn = expectedTrigger === 'cli-invocation'\n\n // BEFORE any gating. A shell-startup user's token failure is recorded by the\n // backgrounded spawn (stderr \u2192 /dev/null) and would otherwise NEVER surface:\n // decideAutoLoad skips this callsite on the trigger mismatch, and the callsite\n // that DID fail cannot warn. This replay is the only channel that reaches them.\n if (canWarn) {\n surfaceStickyAuthFailure()\n }\n\n let resolved: ResolvedEnvAutoLoad | null = null\n\n try {\n resolved = await resolveEnvAutoLoad(canWarn)\n\n if (!resolved) return null\n\n const decision = decideAutoLoad({\n trigger: resolved.trigger,\n expectedTrigger,\n targetConfig: resolved.config,\n targetProject: resolved.project,\n env: readAutoLoadEnvSnapshot(),\n force,\n })\n\n if (decision === 'skip') return null\n\n // Disk-level clear signal: a clear that hasn't yet been sourced into this\n // process's env still suppresses auto-load (clear file newer than load file).\n if (isClearedOnDisk()) return null\n\n // Back off after a recent failure so a down/unauthenticated Doppler isn't\n // re-probed on every command in the same session.\n if (recentlyFailed()) return null\n\n const preWriteMtime = readLoadFileMtime()\n const result = await writeEnvLoadFile({\n config: resolved.config,\n autoLoaded: true,\n projectDir,\n // Re-check after the slow Doppler download: abort if a clear or a manual load\n // landed meanwhile, so a backgrounded auto-load never clobbers a deliberate action.\n beforeWrite: () => {\n return !isClearedOnDisk() && !manualLoadLandedSince(preWriteMtime)\n },\n })\n\n if (!result) return null\n\n clearFailure()\n clearAuthFailure()\n\n return result.filePath\n } catch (error) {\n const reason = (error as Error).message\n\n // Classify the ERROR, not its message: by the time it lands here `env-load` has\n // already translated Doppler's stderr into human prose, so the raw markers are gone.\n const authFailed = isAuthFailure(error)\n\n recordFailure()\n\n // The token failure outlives the 30s backoff window AND this process: persist it\n // so the next interactive command can tell the user why their shell went quiet.\n if (authFailed && resolved) {\n recordAuthFailure({ config: resolved.config, reason, at: Date.now() })\n }\n\n // Surface the failure once per session on the interactive channel; stay silent\n // (debug only) on the backgrounded shell-startup path. The auth-class message is\n // reason-free by design \u2014 no token value may ever be printed.\n if (canWarn && authFailed && resolved) {\n warnOnce(buildAuthFailureWarning(resolved.config), WARN_AUTH_FAIL_SENTINEL_FILE)\n logger.debug(`env auto-load auth failure: ${reason}`)\n } else if (canWarn) {\n warnOnce(`infra-kit: env auto-load failed \u2014 ${reason} (will retry later)`, WARN_FAIL_SENTINEL_FILE)\n } else {\n logger.debug(`env auto-load skipped: ${reason}`)\n }\n\n return null\n }\n}\n\n/**\n * Replay a previously-recorded STICKY auth failure onto the interactive channel,\n * once per session. Never throws: a missing, unreadable, or corrupt marker is simply\n * \"no marker\" \u2014 this runs on the hot path of EVERY command.\n *\n * Exported because WARNING IS NOT LOADING: `program.ts` excludes `version`, `doctor`,\n * `dev` and the env-* family from the cli-invocation auto-LOAD (priming Doppler env\n * there would be surprising), but those are precisely the commands a user whose shell\n * went quiet reaches for. Gating the replay behind that exclusion would leave a\n * `dev`-only user warned by nothing, forever. So program.ts calls this on EVERY\n * command; `runEnvAutoLoad` still calls it too (the `warnOnce` sentinel de-dups).\n *\n * @example\n * surfaceStickyAuthFailure() // warns once per session iff autoload-auth-fail.json exists\n */\nexport const surfaceStickyAuthFailure = (): void => {\n const marker = readAuthFailure()\n\n if (!marker) return\n\n warnOnce(buildAuthFailureWarning(marker.config), WARN_AUTH_FAIL_SENTINEL_FILE)\n logger.debug(`env auto-load auth failure (recorded ${new Date(marker.at).toISOString()}): ${marker.reason}`)\n}\n\n/** Read the sticky auth-failure marker, or null when absent/unreadable/corrupt. */\nconst readAuthFailure = (): AuthFailureMarker | null => {\n try {\n const raw = fs.readFileSync(path.join(getSessionCacheDir(), AUTH_FAIL_MARKER_FILE), 'utf-8')\n\n return parseAuthFailureMarker(JSON.parse(raw))\n } catch {\n return null\n }\n}\n\n/** Record a sticky auth failure. No expiry \u2014 only a successful load clears it. */\nconst recordAuthFailure = (marker: AuthFailureMarker): void => {\n try {\n const dir = getSessionCacheDir()\n\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 })\n atomicWriteFileSync(path.join(dir, AUTH_FAIL_MARKER_FILE), JSON.stringify(marker), 0o600)\n } catch {\n // No session cache dir \u2014 nothing to record against.\n }\n}\n\n/** Clear the sticky auth-failure marker after a successful load. */\nconst clearAuthFailure = (): void => {\n try {\n fs.rmSync(path.join(getSessionCacheDir(), AUTH_FAIL_MARKER_FILE), { force: true })\n } catch {\n // No session cache dir \u2014 nothing to clear.\n }\n}\n\n/** Snapshot the env vars the guards depend on. */\nconst readAutoLoadEnvSnapshot = (): AutoLoadEnvSnapshot => {\n return {\n session: process.env[INFRA_KIT_SESSION_VAR],\n cleared: process.env[INFRA_KIT_ENV_CLEARED_VAR],\n currentConfig: process.env[INFRA_KIT_ENV_CONFIG_VAR],\n currentProject: process.env[INFRA_KIT_ENV_PROJECT_VAR],\n autoLoadedMarker: process.env[INFRA_KIT_ENV_AUTOLOADED_VAR],\n }\n}\n\n/** mtime of the on-disk env-load.sh, or null if absent/unreadable. */\nconst readLoadFileMtime = (): number | null => {\n try {\n return fs.statSync(path.join(getSessionCacheDir(), ENV_LOAD_FILE)).mtimeMs\n } catch {\n return null\n }\n}\n\n/**\n * True when a MANUAL env-load landed on disk after `sinceMtime` (the file now\n * exists, is newer, and carries the manual-load `unset INFRA_KIT_ENV_AUTOLOADED`\n * line). Used to abort an in-flight auto-load so it never clobbers a deliberate load.\n */\nconst manualLoadLandedSince = (sinceMtime: number | null): boolean => {\n try {\n const p = path.join(getSessionCacheDir(), ENV_LOAD_FILE)\n\n if (!fs.existsSync(p)) return false\n\n const mtime = fs.statSync(p).mtimeMs\n\n if (sinceMtime !== null && mtime <= sinceMtime) return false\n\n // Anchor to a whole line so the marker can't match inside a single-quoted\n // secret value that happens to contain this literal text.\n const manualMarker = new RegExp(`^unset ${INFRA_KIT_ENV_AUTOLOADED_VAR}$`, 'm')\n\n return manualMarker.test(fs.readFileSync(p, 'utf-8'))\n } catch {\n return false\n }\n}\n\n/**\n * True when a clear is pending on disk: an env-clear.sh exists and is at least as\n * new as env-load.sh (or no load file remains). Belt-and-suspenders next to the\n * INFRA_KIT_ENV_CLEARED env guard for the window before the shell sources the clear.\n */\nconst isClearedOnDisk = (): boolean => {\n try {\n const dir = getSessionCacheDir()\n const clearPath = path.join(dir, ENV_CLEAR_FILE)\n\n if (!fs.existsSync(clearPath)) return false\n\n const loadPath = path.join(dir, ENV_LOAD_FILE)\n\n if (!fs.existsSync(loadPath)) return true\n\n return fs.statSync(clearPath).mtimeMs >= fs.statSync(loadPath).mtimeMs\n } catch {\n return false\n }\n}\n\n/** True when an auto-load failed within the backoff window (suppress retries). */\nconst recentlyFailed = (): boolean => {\n try {\n const flagPath = path.join(getSessionCacheDir(), FAIL_SENTINEL_FILE)\n\n if (!fs.existsSync(flagPath)) return false\n\n return Date.now() - fs.statSync(flagPath).mtimeMs < FAIL_BACKOFF_MS\n } catch {\n return false\n }\n}\n\n/** Record an auto-load failure (refreshes the backoff window). */\nconst recordFailure = (): void => {\n try {\n const dir = getSessionCacheDir()\n\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 })\n fs.writeFileSync(path.join(dir, FAIL_SENTINEL_FILE), '', { mode: 0o600 })\n } catch {\n // No session cache dir \u2014 nothing to back off against.\n }\n}\n\n/** Clear the failure marker after a successful load. */\nconst clearFailure = (): void => {\n try {\n fs.rmSync(path.join(getSessionCacheDir(), FAIL_SENTINEL_FILE), { force: true })\n } catch {\n // No session cache dir \u2014 nothing to clear.\n }\n}\n\n/**\n * Emit a warning at most once per shell session. Keyed on a flag file in the\n * session cache dir so a misconfigured `envAutoLoad.config` (or a repeated failure)\n * does not spam a warning on every cli-invocation. Falls back to a plain warn when\n * no session cache dir is available.\n */\nconst warnOnce = (message: string, sentinelFile: string): void => {\n try {\n const dir = getSessionCacheDir()\n const flagPath = path.join(dir, sentinelFile)\n\n if (fs.existsSync(flagPath)) return\n\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 })\n fs.writeFileSync(flagPath, '', { mode: 0o600 })\n } catch {\n // No session cache dir (e.g. INFRA_KIT_SESSION unset) \u2014 warn without de-dup.\n }\n\n logger.warn(message)\n}\n", "import { runEnvAutoLoad } from 'src/lib/env-autoload'\n\nexport interface EnvAutoloadArgs {\n /**\n * Canonical (realpath'd) project dir the shell computed (`${dir:A}`) and passed\n * via `--project-dir`. Forwarded to enable the project-scoped warm cache; absent\n * when invoked without the flag (then no warm copy is written).\n */\n projectDir?: string\n}\n\n/**\n * Internal command invoked (backgrounded) by the `infra-kit init` shell-startup\n * integration. Runs the 'shell-startup' trigger, writing env-load.sh when\n * envAutoLoad is configured for it + eligible; the shell precmd hook sources it on\n * a subsequent prompt. Intentionally writes NOTHING to stdout and never throws \u2014\n * auto-load must never disrupt shell startup.\n */\nexport const envAutoload = async ({ projectDir }: EnvAutoloadArgs = {}): Promise<void> => {\n // `force`: the shell may have just WARM-sourced the same config (exporting the\n // auto-load marker this process inherits); without it the refresh would self-skip\n // and stale warm secrets would never be replaced this session.\n await runEnvAutoLoad({ expectedTrigger: 'shell-startup', projectDir, force: true })\n}\n", "import { getDopplerProject } from 'src/integrations/doppler'\nimport { getTokenStorePath, readTokenStore, removeToken } from 'src/lib/env-tokens'\nimport { logger } from 'src/lib/logger'\nimport { tildify } from 'src/lib/path-display'\nimport { purgeRepoWarmCaches } from 'src/lib/warm-cache'\nimport { textContent } from 'src/types'\n\nexport interface EnvTokenRemoveArgs {\n /** Environment whose token to drop from the local store. */\n env: string\n}\n\n/** Where a token is actually revoked. Deleting it here only makes THIS machine stop using it. */\nconst DOPPLER_DASHBOARD_URL = 'https://dashboard.doppler.com/workplace/projects'\n\n/**\n * The thing a user is most likely to get wrong about this command, so it is the thing it says\n * loudest: a local delete is not a revocation. The token stays live in Doppler \u2014 on someone's\n * clipboard, in a CI secret, in shell history \u2014 until it is revoked in the dashboard.\n */\nconst buildRevokeNotice = (project: string, env: string): string[] => {\n return [\n `Removing it locally does NOT revoke it \u2014 the token still works anywhere else it is stored.`,\n `Revoke it in Doppler (project \"${project}\", config \"${env}\"): ${DOPPLER_DASHBOARD_URL}/${project}`,\n ]\n}\n\n/**\n * Drop one environment's service token from the local store, purge the warm caches it may have\n * populated, and tell the user where to actually revoke it.\n *\n * Not an MCP tool \u2014 see the comment on the catalog entry. The MCP boundary auto-confirms every call,\n * so an agent must never be one call away from destroying a credential.\n */\nexport const envTokenRemove = async ({ env }: EnvTokenRemoveArgs) => {\n const store = await readTokenStore()\n const existed = Boolean(store?.envs[env])\n\n await removeToken(env)\n\n // A removed token must not keep working for another 2h out of a sibling worktree's warm cache \u2014 the\n // cache is keyed per WORKTREE, the store per REPO, so this enumerates every worktree.\n const purged = await purgeRepoWarmCaches()\n\n const project = await getDopplerProject()\n const storePath = await getTokenStorePath()\n\n if (existed) {\n logger.info(`Removed the \"${env}\" service token from ${tildify(storePath)}.`)\n } else {\n logger.info(`No \"${env}\" service token was stored in ${tildify(storePath)} \u2014 nothing to remove.`)\n }\n\n if (purged.length > 0) {\n logger.info(`Purged ${purged.length} warm cache(s) across this repo's worktrees.`)\n }\n\n for (const line of buildRevokeNotice(project, env)) {\n logger.warn(line)\n }\n\n const structuredContent = {\n env,\n removed: existed,\n storePath,\n warmCachesPurged: purged.length,\n revokeUrl: `${DOPPLER_DASHBOARD_URL}/${project}`,\n }\n\n return {\n content: textContent(JSON.stringify(structuredContent, null, 2)),\n structuredContent,\n }\n}\n", "import password from '@inquirer/password'\nimport process from 'node:process'\nimport { $ } from 'zx'\n\nimport { assertTokenScope, buildDopplerChildEnv, parseDopplerSecretsJson } from 'src/commands/env-load'\nimport { classifyDopplerAuthFailure, classifyDopplerFailure, getDopplerProject } from 'src/integrations/doppler'\nimport { commandEcho } from 'src/lib/command-echo'\nimport { getTokenStorePath, setToken } from 'src/lib/env-tokens'\nimport { extractStderr } from 'src/lib/errors/operation-error'\nimport { logger } from 'src/lib/logger'\nimport { tildify } from 'src/lib/path-display'\nimport { redactToken } from 'src/lib/redact'\nimport { purgeRepoWarmCaches } from 'src/lib/warm-cache'\nimport { textContent } from 'src/types'\n\nexport interface EnvTokenSetArgs {\n /** Environment = Doppler config the token must be scoped to. */\n env: string\n /** Read the token from stdin (`\u2026 | infra-kit env-token-set dev --stdin`) instead of prompting. */\n stdin?: boolean\n /** Read the token from the named environment variable (never from argv). */\n fromEnv?: string\n /** Write even when the token's scope could NOT be verified. Never overrides a real mismatch. */\n force?: boolean\n}\n\n/** Where the token came in from. Reported back so a scripted run can prove which channel it used. */\ntype TokenInputSource = 'prompt' | 'stdin' | 'env'\n\n/**\n * There is deliberately NO `--token <value>` flag, and there must never be one.\n *\n * argv is world-visible: `ps -ef` shows it to every user on the box, it lands in the shell history\n * file, and `commandEcho` prints option VALUES back to the terminal as the replayable \"\uD83D\uDCDF Equivalent\n * command\". A flag would put a live credential into all three. The three channels below all keep the\n * token out of argv: a masked prompt, stdin, or an env var named (not valued) on the command line.\n */\nconst readCandidateToken = async ({\n stdin,\n fromEnv,\n}: Pick<EnvTokenSetArgs, 'stdin' | 'fromEnv'>): Promise<{\n token: string\n source: TokenInputSource\n}> => {\n if (stdin) return { token: await readStdin(), source: 'stdin' }\n\n if (fromEnv) {\n const token = process.env[fromEnv]\n\n if (!token) throw new Error(`${fromEnv} is not set (or is empty) \u2014 nothing to store.`)\n\n return { token, source: 'env' }\n }\n\n commandEcho.setInteractive()\n\n const token = await password(\n { message: 'Paste the Doppler service token (input is hidden)', mask: true },\n // stderr, like env-load's picker: some env commands are invoked inside `$(\u2026)` by the shell\n // wrapper, which captures stdout. A prompt rendered to stdout would be swallowed there \u2014 the user\n // would face a blank, silent terminal and type a credential into the void.\n { output: process.stderr },\n )\n\n return { token: token.trim(), source: 'prompt' }\n}\n\n/** Drain stdin. `--stdin` is the CI / password-manager channel: `op read \u2026 | infra-kit env-token-set dev --stdin`. */\nconst readStdin = async (): Promise<string> => {\n const chunks: string[] = []\n\n process.stdin.setEncoding('utf8')\n\n for await (const chunk of process.stdin) {\n chunks.push(chunk as string)\n }\n\n return chunks.join('').trim()\n}\n\n/** The payload key carrying the token's scope (SPIKE-0 Q2 \u2014 the CONFIG, never DOPPLER_ENVIRONMENT). */\nconst DOPPLER_CONFIG_KEY = 'DOPPLER_CONFIG'\n\n/** Same bound as the env-load download: a hung probe must not hold an interactive prompt hostage. */\nconst PROBE_TIMEOUT_MS = 30_000\n\n/**\n * Exercise the candidate token against the real config, exactly as `env-load` will: same\n * `secrets download`, same child env (token by ENV, never argv \u2014 {@link buildDopplerChildEnv}).\n *\n * This is the check that catches the likeliest real-world mistake, a human pasting the `arthur` token\n * into `env-token-set dev`: Doppler itself refuses a cross-config download (SPIKE-0 Q1, exit 1\n * \"does not have access to requested config\"), so a mis-scoped token can never be written.\n */\nconst probeToken = async (token: string, project: string, env: string): Promise<Array<[string, string]>> => {\n const prevQuiet = $.quiet\n\n $.quiet = true\n\n let stdout: string\n\n try {\n const result = await $({\n env: buildDopplerChildEnv(token),\n })`doppler secrets download --no-file --format json --project ${project} --config ${env}`.timeout(PROBE_TIMEOUT_MS)\n\n stdout = result.stdout\n } catch (error: unknown) {\n throw translateProbeFailure(error, env)\n } finally {\n $.quiet = prevQuiet\n }\n\n return parseDopplerSecretsJson(stdout)\n}\n\n/**\n * Turn a rejected probe into a refusal a human can act on. An auth-class failure is the whole point\n * of the probe, so it gets a message naming WHICH of the two diagnoses it is; anything else (network,\n * timeout, a wrong project name) is rethrown untouched so the user is not told their token is bad\n * when Doppler was simply unreachable.\n */\nconst translateProbeFailure = (error: unknown, env: string): Error => {\n const stderr = extractStderr(error) ?? (error instanceof Error ? error.message : String(error))\n\n if (classifyDopplerFailure(stderr) !== 'auth') {\n return error instanceof Error ? error : new Error(String(error))\n }\n\n const detail =\n classifyDopplerAuthFailure(stderr) === 'mis-scoped'\n ? `it is scoped to a DIFFERENT config (pasting another environment's token here is the mistake this check exists to catch).`\n : 'it is invalid or has been revoked.'\n\n return new Error(\n [\n `Doppler refused this token for config \"${env}\" \u2014 ${detail}`,\n 'Nothing was written. Issue a service token scoped to that config and try again.',\n ].join('\\n'),\n )\n}\n\n/**\n * The UNVERIFIABLE case: the download succeeded but the payload carries no `DOPPLER_CONFIG`, so we\n * cannot confirm what the token is scoped to.\n *\n * We FAIL CLOSED here, and fail OPEN in `env-load`'s `assertTokenScope` \u2014 deliberately, and the\n * asymmetry is the design. A human is watching THIS command: refusing costs them one `--force` and\n * they learn something. On the silent shell-startup autoload path nobody is watching, and failing\n * closed there would blank every developer's environment at once the day Doppler changes what it\n * injects.\n */\nconst buildUnverifiableScopeMessage = (env: string): string => {\n return [\n `Could not verify this token's scope: the Doppler payload for \"${env}\" carries no ${DOPPLER_CONFIG_KEY}.`,\n 'Refusing to store a credential whose scope is unknown (a token for the wrong environment would load',\n 'the wrong secrets into every shell).',\n 'Re-run with --force if you are certain the token is scoped to this config.',\n ].join('\\n')\n}\n\n/**\n * Store a Doppler service token for one environment, AFTER proving against Doppler that it is scoped\n * to that environment's config. The write is the last thing that happens; every refusal above leaves\n * `tokens.json` untouched.\n *\n * Not an MCP tool \u2014 see the comment on the catalog entry. The MCP boundary auto-confirms every call,\n * and an agent must never be one tool call away from writing a credential.\n */\nexport const envTokenSet = async ({ env, stdin, fromEnv, force }: EnvTokenSetArgs) => {\n // No declared-list check. Doppler is the authority on whether `env` is a real config, and it is\n // consulted three lines down: `probeToken` downloads with this token and `assertTokenScope` refuses a\n // payload belonging to any other config. A typo cannot survive that. A declared list, meanwhile, could\n // only ever be a stale local copy \u2014 and it was: it refused `prod_observability`, a config that exists\n // in Doppler and holds a live token, purely because nobody had added the name to infra-kit.json.\n const project = await getDopplerProject()\n\n const { token, source } = await readCandidateToken({ stdin, fromEnv })\n\n if (!token) throw new Error('No token provided \u2014 nothing was written.')\n\n const pairs = await probeToken(token, project, env)\n\n // Doppler already refused a cross-config download above; this catches the payload disagreeing with\n // the request anyway (defense in depth \u2014 reused verbatim from the env-load path).\n assertTokenScope(pairs, env)\n\n const scopeVerified = pairs.some(([key]) => {\n return key === DOPPLER_CONFIG_KEY\n })\n\n if (!scopeVerified && !force) throw new Error(buildUnverifiableScopeMessage(env))\n\n await setToken(env, token)\n\n // The warm cache is per-WORKTREE while the token store is per-REPO, so a rotation here would leave\n // sibling worktrees serving up to 2h of secrets fetched with the OLD token at the next shell prompt.\n const purged = await purgeRepoWarmCaches()\n\n const storePath = await getTokenStorePath()\n\n // The token is rendered ONLY through redactToken, and never interpolated into a message string \u2014\n // pino's key-path `redact` cannot censor a string it did not structure (see lib/logger).\n logger.info(`Stored the \"${env}\" service token (${redactToken(token)}) in ${tildify(storePath)} (mode 0600).`)\n\n if (!scopeVerified) {\n logger.warn(`Scope was NOT verified (no ${DOPPLER_CONFIG_KEY} in the payload) \u2014 written because --force was given.`)\n }\n\n if (purged.length > 0) {\n logger.info(\n `Purged ${purged.length} warm cache(s) so the next shell cannot serve secrets fetched with an old token.`,\n )\n }\n\n const structuredContent = {\n env,\n source,\n redactedToken: redactToken(token),\n storePath,\n scopeVerified,\n warmCachesPurged: purged.length,\n }\n\n commandEcho.print()\n\n return {\n content: textContent(JSON.stringify(structuredContent, null, 2)),\n structuredContent,\n }\n}\n", "/**\n * Launch the infra-kit MCP server (stdio transport) as a CHILD process.\n *\n * It must never be imported in-process: src/entry/mcp.ts calls `startServer()` at top-level module load,\n * connecting the stdio transport as a side effect, and `emit()` in src/lib/json-output writes to\n * `process.stdout`. Any stdout write on the CLI dispatch path would corrupt the JSON-RPC framing.\n * Spawning gives the child pristine stdio by construction.\n *\n * `dist/mcp.js` is emitted as a sibling of `dist/cli.js` (esbuild `splitting: true`; both import the\n * shared `chunk-*.js`), so `new URL('./mcp.js', import.meta.url)` resolves correctly from the bundle.\n */\nimport { spawn } from 'node:child_process'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\n\nimport { logger } from 'src/lib/logger'\nimport { withoutPackageManagerEnv } from 'src/lib/pm-env'\n\nexport interface McpDeps {\n spawn?: typeof spawn\n exit?: (code: number) => void\n env?: NodeJS.ProcessEnv\n onError?: (message: string) => void\n}\n\nconst FORWARDED_SIGNALS = ['SIGINT', 'SIGTERM'] as const\n\n/** Resolve the server entry next to this bundle rather than importing it (see module doc). */\nexport const mcpServerPath = (): string => {\n return fileURLToPath(new URL('./mcp.js', import.meta.url))\n}\n\nexport const runMcp = (deps: McpDeps = {}): void => {\n const spawnFn = deps.spawn ?? spawn\n const exit =\n deps.exit ??\n ((code: number) => {\n return process.exit(code)\n })\n const env = deps.env ?? process.env\n const onError =\n deps.onError ??\n ((message: string) => {\n return logger.error(message)\n })\n\n const child = spawnFn(process.execPath, [mcpServerPath()], {\n stdio: 'inherit',\n env: withoutPackageManagerEnv(env),\n })\n\n // An unhandled 'error' event throws. Spawning `process.execPath` should never fail, but a launcher\n // that dies by uncaught exception instead of a named exit is the worst possible failure for a client\n // reading our stdio.\n child.on('error', (error) => {\n onError(`failed to launch the MCP server: ${error.message}`)\n exit(1)\n })\n\n // Forward the client's shutdown signals so the server can close its transport cleanly.\n FORWARDED_SIGNALS.forEach((signal) => {\n process.on(signal, () => {\n child.kill(signal)\n })\n })\n\n child.on('exit', (code, signal) => {\n exit(signal ? 1 : (code ?? 1))\n })\n}\n", "/**\n * Update this CLI in place, using the package manager that actually owns the install.\n *\n * `process.exit` is used directly (rather than `process.exitCode`) because this command is CLI-only and\n * is NEVER exposed over MCP \u2014 see the `self-update` entry in src/lib/command-catalog. Exiting from a\n * long-lived MCP server would kill it; there is no such server on this path.\n *\n * Every process interaction is injected via {@link SelfUpdateDeps} (same seam shape as\n * src/dev/proxy/portless-driver.ts) so tests assert the exact argv and env without shelling out.\n */\nimport { spawnSync } from 'node:child_process'\nimport { realpathSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\n\nimport { defaultLazyNpmRoot, detectInstallManager, safeRealpath } from 'src/lib/install-manager'\nimport { logger } from 'src/lib/logger'\nimport { packageManagerInstallEnv } from 'src/lib/pm-env'\n\nexport interface SelfUpdateDeps {\n spawnSync?: typeof spawnSync\n print?: (line: string) => void\n exit?: (code: number) => never\n env?: NodeJS.ProcessEnv\n selfRealPath?: string\n lazyNpmRoot?: () => string | undefined\n}\n\n/** The realpath of THIS module. A global bin is a symlink, but `import.meta.url` already resolves it. */\nconst defaultSelfRealPath = (): string => {\n return realpathSync(fileURLToPath(import.meta.url))\n}\n\n/**\n * Translate a spawn outcome into an exit. Kept separate from the spawn so each failure mode names itself:\n * a bare `exit(status ?? 1)` would turn a missing binary and a SIGKILL into the same silent `1`.\n */\nconst exitFromResult = (\n result: ReturnType<typeof spawnSync>,\n manager: string,\n print: (line: string) => void,\n exit: (code: number) => never,\n): never => {\n if (result.error) {\n print(`${manager} not found on PATH: ${result.error.message}`)\n\n return exit(1)\n }\n\n if (result.signal) {\n print(`update terminated by signal ${result.signal}`)\n\n return exit(1)\n }\n\n return exit(result.status ?? 1)\n}\n\n/**\n * An EACCES from `npm i -g` (root-owned global dir) surfaces the package manager's own error verbatim\n * through `stdio: 'inherit'`. We NEVER auto-retry with sudo \u2014 silently escalating privileges to write to\n * a global directory is not something a self-updater may do.\n */\nexport const runSelfUpdate = ({ dryRun }: { dryRun: boolean }, deps: SelfUpdateDeps = {}): void => {\n const spawn = deps.spawnSync ?? spawnSync\n const print =\n deps.print ??\n ((line: string) => {\n return logger.info(line)\n })\n const exit =\n deps.exit ??\n ((code: number) => {\n return process.exit(code)\n })\n const env = deps.env ?? process.env\n const selfRealPath = deps.selfRealPath ?? defaultSelfRealPath()\n const lazyNpmRoot = deps.lazyNpmRoot ?? defaultLazyNpmRoot\n\n const { manager, updateCommand, canSelfSpawn } = detectInstallManager({\n selfRealPath,\n env,\n realpath: safeRealpath,\n lazyNpmRoot,\n })\n const printable = updateCommand.join(' ')\n\n if (dryRun) {\n print(`Detected install manager: ${manager}`)\n print(`Would run: ${printable}`)\n\n return\n }\n\n if (!canSelfSpawn) {\n print(`Detected install manager: ${manager}`)\n print(`Run this yourself: ${printable}`)\n print(\n manager === 'homebrew'\n ? 'Not run for you: Homebrew manages this install; running a package manager would create a split-brain install.'\n : 'Not run for you: the install location is unrecognized, so the command above is a guess \u2014 a guessed global install is worse than a printed one.',\n )\n\n return\n }\n\n // `shell` on win32 so the `.cmd` shims npm/pnpm/yarn ship as their global bins resolve.\n //\n // `cwd` is pinned to the home directory for the SAME reason the background worker pins it, and it is a\n // security control rather than tidiness: npm resolves `registry=` from an `.npmrc` on disk relative to\n // the cwd. Inheriting the caller's cwd lets any repo you happen to be standing in redirect this\n // `install -g` to a registry of its choosing and run that package's lifecycle scripts. Being\n // user-initiated is NOT a defence \u2014 npm does not print the registry it resolved, so nothing about the\n // output would give it away. Stripping env vars cannot close this; the redirect lives in a file.\n //\n // `packageManagerInstallEnv` (not the blunt `withoutPackageManagerEnv`) keeps `npm_config_prefix`, so we\n // install into the same prefix `detectInstallManager` just matched on rather than the default one.\n const result = spawn(updateCommand[0]!, updateCommand.slice(1), {\n stdio: 'inherit',\n shell: process.platform === 'win32',\n cwd: homedir(),\n env: packageManagerInstallEnv(env),\n })\n\n exitFromResult(result, manager, print, exit)\n}\n", "import { VENDOR_CONFIG_FILE } from '@slip-stream-kit/config/internal'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { pathToFileURL } from 'node:url'\n\nimport { getProjectRoot } from 'src/lib/git-utils'\nimport { logger } from 'src/lib/logger'\nimport { fileExists, tildify } from 'src/lib/path-display'\nimport { expandTilde, getFactoryConfigPath, loadFactoryConfig } from 'src/lib/vendor/factory-config'\n\ninterface VendorConfigOptions {\n /** Scaffold `~/.infra-kit/vendor.json` instead of printing the current one. */\n init?: boolean\n /** Source repo root used for legacy `targets` auto-seeding. Defaults to the git toplevel. */\n cwd?: string\n}\n\n/** Placeholder workspace dir written by `--init`; the user edits it to their layout. */\nconst PLACEHOLDER_WORKSPACE_DIR = '~/projects'\n\n/**\n * Surface or scaffold the machine-local factory config\n * (`~/.infra-kit/vendor.json`). CLI-only \u2014 NOT an MCP tool; returns nothing\n * and signals problems via `process.exitCode`.\n *\n * Without `--init`: prints the factory file path + existence, the resolved\n * `workspaceDir` + existence, and per-target reachability (`[\u2713]`/`[ ]`). Exits\n * non-zero if the file is missing, the workspace dir is missing, or any target\n * is unreachable, so it is usable as a doctor check.\n *\n * With `--init`: scaffolds the file (skipping if it already exists), seeding\n * `targets` from a legacy source `vendor.config.ts` when one is readable.\n */\nexport const vendorConfig = async (options: VendorConfigOptions = {}): Promise<void> => {\n if (options.init) {\n await initFactoryConfig(options.cwd)\n\n return\n }\n\n await printFactoryConfig()\n}\n\n/** Render the factory config chain with `[\u2713]`/`[ ]` reachability markers. */\nconst printFactoryConfig = async (): Promise<void> => {\n const factoryPath = getFactoryConfigPath()\n const exists = await fileExists(factoryPath)\n\n logger.info(`Factory config: ${tildify(factoryPath)} ${exists ? '[\u2713]' : '[ ]'}`)\n\n if (!exists) {\n logger.info('\\nNot found \u2014 run `infra-kit vendor config --init` to scaffold it.')\n process.exitCode = 1\n\n return\n }\n\n const { workspaceDir, targets } = await loadFactoryConfig()\n const resolvedWorkspace = expandTilde(workspaceDir)\n const workspaceExists = await fileExists(resolvedWorkspace)\n\n logger.info(\n `workspaceDir: ${workspaceDir} (resolved: ${resolvedWorkspace}) ${workspaceExists ? '[\u2713 exists]' : '[ ] not found'}`,\n )\n logger.info('Targets:')\n\n let allReachable = workspaceExists\n\n for (const repo of targets) {\n const targetPath = path.join(resolvedWorkspace, repo)\n const reachable = await fileExists(targetPath)\n\n if (!reachable) {\n allReachable = false\n }\n\n const marker = reachable ? '[\u2713]' : '[ ]'\n const suffix = reachable ? '' : ' (not found \u2014 clone or remove)'\n\n logger.info(` ${marker} ${repo} ${tildify(targetPath)}${suffix}`)\n }\n\n if (!allReachable) {\n process.exitCode = 1\n }\n}\n\n/** Scaffold `~/.infra-kit/vendor.json`, skipping if it already exists. */\nconst initFactoryConfig = async (cwd?: string): Promise<void> => {\n const factoryPath = getFactoryConfigPath()\n\n if (await fileExists(factoryPath)) {\n logger.info(`Factory config already exists at ${tildify(factoryPath)} \u2014 leaving it untouched.`)\n\n return\n }\n\n const sourceRoot = cwd ?? (await getProjectRoot())\n const seededTargets = await readLegacyTargets(sourceRoot)\n\n await fs.mkdir(path.dirname(factoryPath), { recursive: true })\n await fs.writeFile(factoryPath, buildScaffold(seededTargets), 'utf-8')\n\n logger.info(`\u2713 Created ${tildify(factoryPath)}`)\n\n if (seededTargets.length > 0) {\n logger.info(` Seeded ${seededTargets.length} target(s) from the source ${VENDOR_CONFIG_FILE}.`)\n }\n\n logger.info(` Edit \\`workspaceDir\\` (placeholder: ${PLACEHOLDER_WORKSPACE_DIR}) to point at where your repos live.`)\n\n if (seededTargets.length === 0) {\n logger.info(' Add at least one repo name to `targets` before running vendor sync/manifest/diff.')\n }\n}\n\n/**\n * Best-effort read of a legacy `targets` array from the source repo's\n * `vendor.config.ts`. The current schema no longer accepts `targets`, so this\n * reads the raw default export directly (bypassing validation). Returns `[]` on\n * any failure \u2014 seeding is a convenience, never a hard requirement.\n */\nconst readLegacyTargets = async (sourceRoot: string): Promise<string[]> => {\n try {\n const configPath = path.join(sourceRoot, VENDOR_CONFIG_FILE)\n const stat = await fs.stat(configPath)\n const moduleUrl = `${pathToFileURL(configPath).href}?mtime=${Number(stat.mtimeMs)}`\n const imported = (await import(moduleUrl)) as { default?: unknown }\n const raw = imported.default\n const resolved = typeof raw === 'function' ? await (raw as () => unknown)() : raw\n\n if (resolved && typeof resolved === 'object' && 'targets' in resolved) {\n const targets = (resolved as { targets?: unknown }).targets\n\n if (\n Array.isArray(targets) &&\n targets.every((t) => {\n return typeof t === 'string'\n })\n ) {\n return targets as string[]\n }\n }\n } catch {\n // Absent or unreadable source config \u2014 fall through to an empty placeholder.\n }\n\n return []\n}\n\n/**\n * Render the scaffold file body as strict JSON (`vendor.json`). The factory config\n * is static JSON, loaded with `JSON.parse` \u2014 no comments, no executable code. When\n * `targets` is empty the stub writes `\"targets\": []`, which fails the schema's\n * `targets.min(1)` on load: this is intentional \u2014 `--init` produces an incomplete\n * stub the user must edit before running vendor sync/manifest/diff. The annotated\n * guidance lives in the sibling `vendor.example.jsonc` (seeded by `infra-kit init`).\n */\nconst buildScaffold = (targets: string[]): string => {\n return `${JSON.stringify({ workspaceDir: PLACEHOLDER_WORKSPACE_DIR, targets }, null, 2)}\\n`\n}\n", "/**\n * The shared \"equivalent line\" shape \u2014 the replayable, non-interactive invocation\n * a session prints so the user can rerun the same selection without prompts. Kept\n * command-agnostic (dev, vendor, audit \u2026 all emit one) and structural so it never\n * has to import a specific command's wizard types. Pure data + a tiny constructor.\n */\n\n/** One replayable invocation line, plus whether it reproduces the selection exactly. */\nexport interface EquivalentLine {\n /** The full replayable invocation, e.g. \"infra-kit vendor check\". */\n line: string\n /** True when the line reproduces the selection exactly (false \u21D2 it would re-prompt). */\n reproducible: boolean\n}\n\n/**\n * Construct an {@link EquivalentLine}. Defaults to `reproducible: true` for the\n * common case where the printed line replays the selection verbatim.\n *\n * @example\n * equivalentLine('infra-kit vendor check') // => { line: 'infra-kit vendor check', reproducible: true }\n * equivalentLine('infra-kit dev --app=client', false) // => { line: '\u2026', reproducible: false }\n */\nexport const equivalentLine = (line: string, reproducible = true): EquivalentLine => {\n return { line, reproducible }\n}\n", "import fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\n\nimport type { EquivalentLine } from './equivalent'\n\n/**\n * Session-private side channel from a spawned command child back to the session-shell parent. The\n * child writes a small JSON record at the path in `INFRA_KIT_SESSION_REPORT`; the parent reads it to\n * derive the transcript entry. The record is NOT `ToolsExecutionResult` and never touches the `--json`\n * / MCP machine contract \u2014 it exists only so the parent can show a rich `equivalent + report` block.\n *\n * The file's PRESENCE is load-bearing: a decline/cancel path exits before the write, so an absent file\n * (with exit 0) is how the parent tells \"cancelled\" from \"ok\". See `classifyOutcome`.\n */\n\nexport const SESSION_REPORT_ENV = 'INFRA_KIT_SESSION_REPORT'\n\nexport interface SessionReportRecord {\n /** The replayable equivalent line for this invocation (resolved flags folded in when interactive). */\n equivalent?: EquivalentLine\n /** Optional human summary lines a command opted to surface (e.g. \"found 3 violations\"). */\n summary?: string[]\n}\n\n// The child captures the report path ONCE at CLI entry and deletes the env var so no grandchild\n// (gh / git / a hook / $EDITOR) inherits it and clobbers the file. `captured` guards a double-capture.\nlet capturedReportPath: string | null = null\nlet captured = false\nconst summaryLines: string[] = []\n\n/**\n * Capture `INFRA_KIT_SESSION_REPORT` into a module local and delete it from the env so descendants do\n * not inherit it. Call once at CLI entry (in every process); a non-session process simply captures\n * `null`. Idempotent.\n *\n * @example\n * const env = { INFRA_KIT_SESSION_REPORT: '/tmp/r.json' }\n * captureSessionReportPath(env)\n * env.INFRA_KIT_SESSION_REPORT // => undefined (deleted so grandchildren don't inherit)\n */\nexport const captureSessionReportPath = (env: NodeJS.ProcessEnv = process.env): void => {\n if (captured) {\n return\n }\n\n capturedReportPath = env[SESSION_REPORT_ENV] ?? null\n captured = true\n delete env[SESSION_REPORT_ENV]\n}\n\n/** True when this process is running as a session-shell child (a report path was captured). */\nexport const isSessionChild = (): boolean => {\n return capturedReportPath != null\n}\n\n/**\n * Reset the captured path + accumulated summary. For tests only (the process-lifetime singleton is\n * captured exactly once at CLI entry in real use). Mirrors `commandEcho.reset()`.\n *\n * @example\n * resetSessionReport()\n * isSessionChild() // => false\n */\nexport const resetSessionReport = (): void => {\n capturedReportPath = null\n captured = false\n summaryLines.length = 0\n}\n\n/**\n * Accumulate optional human summary lines for this invocation's transcript entry. A no-op outside a\n * session; commands may call it to enrich the report without knowing whether a session is active.\n *\n * @example\n * addSessionSummary('found 3 violations')\n */\nexport const addSessionSummary = (...lines: string[]): void => {\n summaryLines.push(...lines)\n}\n\n/**\n * Write the session report to the captured path. No-op when not running under a session (path null).\n * Best-effort: a write failure never throws (the parent falls back to exit-code-derived status).\n *\n * @example\n * writeSessionReport({ equivalent: { line: 'infra-kit vendor check', reproducible: true } })\n */\nexport const writeSessionReport = (\n record: SessionReportRecord,\n deps?: { write?: (file: string, data: string) => void },\n): void => {\n if (!capturedReportPath) {\n return\n }\n\n const summary = record.summary ?? (summaryLines.length > 0 ? [...summaryLines] : undefined)\n const payload: SessionReportRecord = { ...record, ...(summary ? { summary } : {}) }\n const write =\n deps?.write ??\n ((file: string, data: string) => {\n fs.writeFileSync(file, data)\n })\n\n try {\n write(capturedReportPath, JSON.stringify(payload))\n } catch {\n // Best-effort: an unwritable report degrades the transcript to exit-code-only, never a crash.\n }\n}\n\n// --- Parent side (session shell) -----------------------------------------\n\nlet reportCounter = 0\n\n/**\n * A fresh, unique report path under the OS temp dir for one spawned command. Never uses\n * `getSessionCacheDir()` (which throws when `INFRA_KIT_SESSION` is unset) \u2014 this ephemeral IPC needs\n * no session scoping. Uniqueness comes from pid + a monotonic counter, so no `Math.random` is needed.\n *\n * @example\n * newReportPath() // => '/var/folders/\u2026/infra-kit-session-4123-1.json'\n */\nexport const newReportPath = (deps?: { tmpdir?: () => string; pid?: number }): string => {\n const tmp = deps?.tmpdir?.() ?? os.tmpdir()\n const pid = deps?.pid ?? process.pid\n\n reportCounter += 1\n\n return path.join(tmp, `infra-kit-session-${pid}-${reportCounter}.json`)\n}\n\n/**\n * Read and delete a child's report file. Returns the parsed record, or `null` when the file is absent\n * (the child cancelled / crashed before writing) or unparseable.\n *\n * @example\n * const record = readAndUnlinkReport('/tmp/r.json')\n * record?.equivalent?.line\n */\nexport const readAndUnlinkReport = (\n reportPath: string,\n deps?: { read?: (file: string) => string; unlink?: (file: string) => void; exists?: (file: string) => boolean },\n): SessionReportRecord | null => {\n const exists =\n deps?.exists ??\n ((file: string) => {\n return fs.existsSync(file)\n })\n\n if (!exists(reportPath)) {\n return null\n }\n\n const read =\n deps?.read ??\n ((file: string) => {\n return fs.readFileSync(file, 'utf-8')\n })\n const unlink =\n deps?.unlink ??\n ((file: string) => {\n return fs.unlinkSync(file)\n })\n\n let raw: string\n\n try {\n raw = read(reportPath)\n } catch {\n return null\n } finally {\n try {\n unlink(reportPath)\n } catch {\n // A leftover temp file is harmless; the next iteration uses a fresh counter-suffixed name.\n }\n }\n\n try {\n return JSON.parse(raw) as SessionReportRecord\n } catch {\n return null\n }\n}\n", "/**\n * Small shared rendering helpers for aligned terminal output. These replace\n * the ad-hoc \"compute the max width, then padEnd each row\" pattern that the\n * interactive menu (and similar list views) hand-rolled. Pure string helpers \u2014\n * no I/O, no color \u2014 so they are trivially testable and reusable.\n */\n\n/**\n * Align a list of two-column rows: pad every left cell to the widest left cell,\n * then join the two cells with `gap`. Returns one rendered line per row.\n *\n * @example\n * formatAlignedRows([['a', 'x'], ['bbb', 'y']])\n * // ['a x', 'bbb y'] (left padded to width 3, default 2-space gap)\n */\nexport const formatAlignedRows = (rows: ReadonlyArray<readonly [string, string]>, gap = ' '): string[] => {\n const width = rows.reduce((max, [left]) => {\n return Math.max(max, left.length)\n }, 0)\n\n return rows.map(([left, right]) => {\n return `${left.padEnd(width)}${gap}${right}`\n })\n}\n", "import { chalkStderr } from 'chalk'\nimport { spawn as nodeSpawn } from 'node:child_process'\nimport type { ChildProcess } from 'node:child_process'\nimport process from 'node:process'\n\nimport { equivalentLine } from './equivalent'\nimport { formatRunHeader, formatTranscriptEntry } from './format-entry'\nimport { classifyOutcome } from './outcome'\nimport { SESSION_REPORT_ENV, newReportPath, readAndUnlinkReport } from './report'\nimport { resetTerminal } from './reset-terminal'\n\n/**\n * The persistent `infra-kit` session shell (React-free so it stays out of the eager Ink chunk and is\n * unit-testable with plain vitest). Each iteration: render the palette \u2192 echo the command \u2192 spawn it\n * as a fresh child that inherits this terminal \u2192 reap \u2192 read the child's report file \u2192 commit one\n * status footer \u2192 repeat, until the palette returns `null` (quit).\n *\n * The child runs on the PRIMARY screen, exactly as if the user had typed the command. That is the\n * whole point: its output stays in the scrollback, framed by the echoed `$ infra-kit \u2026` header above\n * and the status footer below. (Children used to run inside the alternate screen buffer, which the\n * terminal discards on exit \u2014 so every command appeared to produce no output at all.)\n *\n * The child boundary is what makes this safe: a `process.exit` in a command exits the child, not the\n * loop; a fresh process means fresh Commander/option state; and the child owns stdin via inherited\n * stdio, so the parent adds zero contention. The session exit code is always 0 \u2014 per-command results\n * live in the transcript, not the shell's exit status.\n */\n\n/** A menu-selectable command resolved from the catalog: what to spawn and how to present it. */\nexport interface SessionCommand {\n /** Canonical Commander argv, e.g. `['vendor', 'check']`. */\n groupPath: string[]\n /**\n * The command hands the terminal to a full-screen child that enters the alternate screen itself (an\n * `$EDITOR`). We never enter it for them \u2014 but if that child is killed before it restores the\n * primary buffer, every later frame would be drawn into an abandoned buffer, so the hygiene reset\n * leaves the alternate screen defensively on their behalf.\n */\n entersAltScreen?: boolean\n /** Append the \"applies after you exit\" notice (env-load/clear can't mutate the parent shell). */\n sessionEnvNotice?: boolean\n /**\n * Runs until the user stops it (`dev`). It writes no report (its action resolves at boot, long before\n * it is done) and exits `128 + signo` on a Ctrl-C rather than dying by the signal, so `classifyOutcome`\n * needs to be told \u2014 otherwise every normal stop reads as a failure.\n */\n longRunning?: boolean\n}\n\n/** A palette row \u2014 structurally compatible with the Ink palette's `PaletteItem`, no React dependency. */\nexport interface SessionPaletteItem {\n name: string\n description: string\n group: string\n}\n\nexport interface RunSessionDeps {\n /** Render the palette and resolve to the picked flat command name, or `null` to quit the session. */\n renderPalette: (items: SessionPaletteItem[]) => Promise<string | null>\n /** Map a picked flat name to its spawn/presentation info (from the command catalog). */\n resolveCommand: (name: string) => SessionCommand | undefined\n /** Absolute path to this bundle's `cli.js`, spawned as `process.execPath <cliPath> <...groupPath>`. */\n cliPath: string\n spawn?: typeof nodeSpawn\n now?: () => number\n /** Commit a finished transcript entry (default: stderr, consistent with all repo human output). */\n write?: (text: string) => void\n env?: NodeJS.ProcessEnv\n /** ASCII glyphs for the transcript (default: derived from stdout TTY / TERM). */\n ascii?: boolean\n /** Colour the header and footer (default: chalk's own TTY / `NO_COLOR` / `FORCE_COLOR` verdict). */\n color?: boolean\n /**\n * Terminal width for the footer's closing rule, or `undefined` for none (default: stderr's columns \u2014\n * the stream the transcript is written to).\n *\n * A FUNCTION, not a number, because the session shell is long-lived: it loops until the user quits, so\n * a width snapshotted at boot goes stale the moment they resize the window. Re-read per run, a shrink\n * just makes the next rule shorter; snapshotted, it would overrun the new margin and wrap across\n * rows \u2014 the exact wall-of-text this framing exists to prevent.\n */\n columns?: () => number | undefined\n /** Terminal hygiene after each child (default: the real escapes; tests inject a spy). */\n resetTerminal?: (opts: { entersAltScreen?: boolean }) => void\n /** Install the real signal handlers (default true; tests pass false to stay signal-free). */\n installSignals?: boolean\n /** Process seams for those handlers (default: the real `process`). Tests inject fakes to assert policy. */\n signals?: SessionSignalDeps\n}\n\n/** The replayable command line for a pick \u2014 echoed as the header, and the default equivalent line. */\nconst commandLine = (command: SessionCommand): string => {\n return `infra-kit ${command.groupPath.join(' ')}`\n}\n\n/**\n * The width of the stream the transcript is written to, or `undefined` when it has none.\n *\n * Node types `columns` as a `number`, but a non-TTY stderr has no such property at all. Passing the\n * resulting `undefined` (or a `0`) through as a width would read to the formatter as a *very narrow*\n * terminal; `undefined` is the value that means \"draw no rule\", so normalise to it explicitly.\n */\nconst stderrColumns = (): number | undefined => {\n const columns: number | undefined = process.stderr.columns\n\n return columns != null && columns > 0 ? columns : undefined\n}\n\nconst waitForExit = (child: ChildProcess): Promise<{ code: number | null; signal: NodeJS.Signals | null }> => {\n return new Promise((resolve) => {\n child.on('exit', (code, signal) => {\n resolve({ code, signal })\n })\n child.on('error', () => {\n // A spawn that never launched is a failed run, not a cancel.\n resolve({ code: 1, signal: null })\n })\n })\n}\n\n/** Every dep `runOne` needs, with the defaults already applied. */\ntype ResolvedDeps = Required<\n Pick<RunSessionDeps, 'spawn' | 'now' | 'env' | 'cliPath' | 'ascii' | 'color' | 'columns' | 'resetTerminal'>\n>\n\n/**\n * Run one picked command as a child on THIS terminal and format the footer that closes it out. The\n * header is echoed by the caller before we spawn, so the child's output arrives under a heading.\n */\nconst runOne = async (\n command: SessionCommand,\n deps: ResolvedDeps,\n markChildOwnsSigint: () => void,\n): Promise<string> => {\n const reportPath = newReportPath()\n const childEnv: NodeJS.ProcessEnv = {\n ...deps.env,\n [SESSION_REPORT_ENV]: reportPath,\n // Every child would otherwise re-run the daily auto-update check and spawn its own detached\n // worker; opt them all out. The parent's own startup check (before the palette) still runs.\n INFRA_KIT_NO_AUTO_UPDATE: '1',\n }\n const start = deps.now()\n\n let result: { code: number | null; signal: NodeJS.Signals | null } = { code: 1, signal: null }\n\n markChildOwnsSigint()\n try {\n // `stdio: 'inherit'` on the primary screen: the child writes straight to the user's terminal, so\n // its output lands in the scrollback and stays there. Never pipe \u2014 a piped child loses its TTY,\n // which strips colour and breaks every interactive prompt (release-create, the branch pickers).\n const child = deps.spawn(process.execPath, [deps.cliPath, ...command.groupPath], {\n stdio: 'inherit',\n env: childEnv,\n })\n\n result = await waitForExit(child)\n } catch {\n // A synchronous spawn throw (or a rejected run) must degrade to a `failed` transcript entry, never\n // tear the session down.\n result = { code: 1, signal: null }\n } finally {\n // NOT the place to hand SIGINT back. The child's `exit` has fired, but the terminal reset below, the\n // report read, and the footer + palette draw are all still ahead \u2014 and a user force-quitting the child\n // MASHES Ctrl-C, so more SIGINTs are still landing. Clearing the flag here made every one of them take\n // the \"no child running\" branch and `exit(0)` the WHOLE SESSION SHELL instead of returning to the\n // palette. The loop clears it immediately before `renderPalette`, once the palette can own Ctrl-C.\n deps.resetTerminal({ entersAltScreen: command.entersAltScreen })\n }\n\n const record = readAndUnlinkReport(reportPath)\n const outcome = classifyOutcome(result.code, result.signal, record != null, command.longRunning)\n const headerLine = commandLine(command)\n const equivalent = record?.equivalent ?? equivalentLine(headerLine, true)\n\n return formatTranscriptEntry({\n equivalent,\n outcome,\n durationMs: deps.now() - start,\n summary: record?.summary,\n envNotice: command.sessionEnvNotice,\n ascii: deps.ascii,\n color: deps.color,\n // Read HERE, not at session boot: the user may have resized the window since the last run.\n width: deps.columns(),\n // We already echoed this line before the spawn. Repeat it only when the child reported back a\n // DIFFERENT one \u2014 an interactive command folding its resolved flags into a replayable `\u2248` line.\n showEquivalent: !(equivalent.reproducible && equivalent.line === headerLine),\n })\n}\n\n/** Process-level seams for the signal owners (tests inject fakes; production wires `process`). */\nexport interface SessionSignalDeps {\n register: (signal: NodeJS.Signals, handler: () => void) => void\n unregister: (signal: NodeJS.Signals, handler: () => void) => void\n exit: (code: number) => void\n /** Deliver a signal to THIS process (used to take SIGTSTP's default action: stop). */\n raise: (signal: NodeJS.Signals) => void\n}\n\n/** The session's live signal owner: handler teardown, plus the state the loop must consult. */\nexport interface SessionSignals {\n /** Remove every installed handler. */\n dispose: () => void\n /** Call when a child is about to run \u2014 resets the per-child signal history. */\n childStarted: () => void\n /** True when an external SIGTERM asked us to stop; the loop must exit after the current child. */\n quitRequested: () => boolean\n}\n\n/**\n * Install the session's state-aware signal owners. The child is spawned WITHOUT `detached`, so it shares\n * this process's group \u2014 every tty-delivered signal reaches the child directly, and the parent's job is\n * only to decide what IT does.\n *\n * - **SIGINT** \u2014 swallowed while a child runs, so the tty's Ctrl-C stops only the child and the parent\n * survives to render `\u2298 cancelled` and loop. Between iterations it ends the session (exit 0).\n * - **SIGTERM** \u2014 DEFERRED, not dropped, while a child runs: we finish the child, commit its transcript\n * entry, and then end the session. Dropping it outright would make the session unkillable by anything\n * short of SIGKILL for as long as a child ran \u2014 and `dev` runs for hours.\n *\n * The exception is pnpm's RELAY. Under `pnpm exec infra-kit`, pnpm forwards a SIGTERM to us moments\n * after the tty's Ctrl-C \u2014 that SIGTERM is an artifact of the SIGINT the user already aimed at the\n * child, not a request to kill the session, so a SIGTERM that FOLLOWS a SIGINT within the same child\n * is swallowed. (Causality, not a timing heuristic: the relay only exists because of the Ctrl-C.)\n * Without a handler at all, that relay was an instant unhandled parent death \u2014 the shell prompt\n * returned while the child still owned the tty and wrote teardown output over it.\n * - **SIGHUP** \u2014 NEVER swallowed. It means the terminal is gone, so staying alive would leave the\n * session looping on a dead tty, drawing palette frames into a closed fd. Always exit (129). The child\n * gets its own SIGHUP and is responsible for its own reap (see `dev/signal-shutdown.ts`).\n * - **SIGTSTP** \u2014 Ctrl-Z. While a child runs we STOP OURSELVES, because the tty already delivered\n * SIGTSTP to the whole foreground group: the child has stopped, and if the parent merely ignored the\n * signal (as it used to) it would sit in `waitForExit` awaiting an exit that can never come, wedging\n * the terminal with no prompt and no way back. Stopping too suspends the FOREGROUND GROUP as a unit,\n * so the user's shell reclaims the tty and prints a prompt; `fg` sends SIGCONT to the group and both\n * resume. Handlers survive stop/continue, so there is nothing to re-arm.\n *\n * CAVEAT \u2014 a child's own DETACHED descendants keep running: `dev`'s turbo/vite children sit in their\n * own process groups, so a Ctrl-Z out of `dev` suspends `dev` itself while its servers stay up and\n * bound to their ports. Recoverable with `fg`, and strictly better than the wedge it replaced, but\n * \"suspended\" is not the whole truth for `dev`. Between iterations SIGTSTP stays IGNORED: suspending\n * mid-palette would strand a half-drawn Ink frame on the screen.\n */\nexport const installSessionSignals = (isChildRunning: () => boolean, deps: SessionSignalDeps): SessionSignals => {\n // Per-child signal history: a SIGTERM is pnpm's relay only if a SIGINT preceded it for THIS child.\n let sigintSeenDuringChild = false\n let quitRequested = false\n\n const onSigint = (): void => {\n if (isChildRunning()) {\n // The child got its own copy from the tty; ours only records that the Ctrl-C happened, so a\n // SIGTERM arriving next can be recognised as pnpm's relay rather than an external kill.\n sigintSeenDuringChild = true\n\n return\n }\n\n deps.exit(0)\n }\n const onSigterm = (): void => {\n if (!isChildRunning()) {\n deps.exit(0)\n\n return\n }\n\n if (sigintSeenDuringChild) {\n // pnpm's relay of the Ctrl-C the user aimed at the child. Not a request to end the session.\n return\n }\n\n // A genuine external `kill`. Let the child finish and its entry commit, then stop.\n quitRequested = true\n }\n const onSighup = (): void => {\n // The tty is gone. There is nothing left to render into and no user to render for.\n deps.exit(129)\n }\n const onSigtstp = (): void => {\n if (!isChildRunning()) {\n // Mid-palette: ignore, or we would suspend with a partial frame committed to the screen.\n return\n }\n\n // SIGSTOP cannot be caught \u2014 this takes the default action the handler is suppressing.\n deps.raise('SIGSTOP')\n }\n\n deps.register('SIGINT', onSigint)\n deps.register('SIGTERM', onSigterm)\n deps.register('SIGHUP', onSighup)\n deps.register('SIGTSTP', onSigtstp)\n\n return {\n dispose: () => {\n deps.unregister('SIGINT', onSigint)\n deps.unregister('SIGTERM', onSigterm)\n deps.unregister('SIGHUP', onSighup)\n deps.unregister('SIGTSTP', onSigtstp)\n },\n childStarted: () => {\n sigintSeenDuringChild = false\n },\n quitRequested: () => {\n return quitRequested\n },\n }\n}\n\n/**\n * Pure gate: may bare `infra-kit` engage the persistent session shell? Requires an interactive TTY on\n * all three streams, a capable terminal, and no opt-out. Deliberately does NOT depend on\n * `INFRA_KIT_SESSION` (the shell must work without `infra-kit init` having run). Everything else keeps\n * the one-shot path.\n *\n * **stderr is load-bearing**, not an afterthought: the palette and every transcript line are written\n * there. Under `infra-kit 2>out.log` the palette would be invisible (it renders into the file), and\n * writes to a pipe are asynchronous on macOS \u2014 so the echoed header could land AFTER the child's\n * output, which is the one ordering the shell guarantees. Both are avoided by never entering the shell\n * when stderr is redirected.\n *\n * @example\n * sessionGateEnabled({ TERM: 'xterm' }, { stdoutIsTTY: true, stdinIsTTY: true, stderrIsTTY: true }) // => true\n */\nexport const sessionGateEnabled = (\n env: NodeJS.ProcessEnv,\n streams: { stdoutIsTTY: boolean; stdinIsTTY: boolean; stderrIsTTY: boolean },\n): boolean => {\n return Boolean(\n streams.stdoutIsTTY &&\n streams.stdinIsTTY &&\n streams.stderrIsTTY &&\n env.TERM !== 'dumb' &&\n !env.INFRA_KIT_NO_SESSION &&\n !env.INFRA_KIT_SESSION_REPORT,\n )\n}\n\n/**\n * Run the session loop until the user quits at the palette.\n *\n * @example\n * await runSession(items, { renderPalette, resolveCommand, cliPath: '/g/dist/cli.js' })\n */\nexport const runSession = async (items: SessionPaletteItem[], deps: RunSessionDeps): Promise<void> => {\n const resolved = {\n spawn: deps.spawn ?? nodeSpawn,\n now:\n deps.now ??\n (() => {\n return Date.now()\n }),\n env: deps.env ?? process.env,\n cliPath: deps.cliPath,\n ascii: deps.ascii ?? !(process.stdout.isTTY && process.env.TERM !== 'dumb'),\n // `chalkStderr`, not `chalk`: the default instance sniffs STDOUT, and every byte of the transcript\n // goes to stderr (see `write` below). It already folds in TTY detection, `NO_COLOR` and\n // `FORCE_COLOR`. The formatter itself is colour-pure, so this is the only place the call is made \u2014\n // and it is made against the same stream `columns` is read from.\n color: deps.color ?? chalkStderr.level > 0,\n columns: deps.columns ?? stderrColumns,\n resetTerminal:\n deps.resetTerminal ??\n ((opts: { entersAltScreen?: boolean }) => {\n resetTerminal(opts)\n }),\n }\n const write =\n deps.write ??\n ((text: string) => {\n return process.stderr.write(text)\n })\n\n const signals: SessionSignalDeps = deps.signals ?? {\n register: (signal, handler) => {\n process.on(signal, handler)\n },\n unregister: (signal, handler) => {\n process.off(signal, handler)\n },\n exit: (code) => {\n process.exit(code)\n },\n raise: (signal) => {\n process.kill(process.pid, signal)\n },\n }\n\n /**\n * Does the CHILD own Ctrl-C right now?\n *\n * Deliberately WIDER than \"a child process is alive\". It stays true from the spawn until the palette is\n * about to be drawn again \u2014 covering the terminal reset, the report read and the footer \u2014 because a user\n * force-quitting a child MASHES Ctrl-C, and the trailing SIGINTs land in exactly that gap. Letting one of\n * them reach the \"no child running\" branch is what used to `exit(0)` the entire session shell instead of\n * returning to the palette.\n */\n let childOwnsSigint = false\n const noopSignals: SessionSignals = {\n dispose: () => {\n return undefined\n },\n childStarted: () => {\n return undefined\n },\n quitRequested: () => {\n return false\n },\n }\n const sessionSignals =\n deps.installSignals === false\n ? noopSignals\n : installSessionSignals(() => {\n return childOwnsSigint\n }, signals)\n\n try {\n for (;;) {\n // An external SIGTERM arrived mid-child. We deferred it so the child could finish and its entry\n // could be committed; honour it now rather than re-rendering the palette over a `kill`.\n if (sessionSignals.quitRequested()) {\n return\n }\n\n // Hand Ctrl-C back to the palette HERE, and nowhere earlier. Everything between the child's exit\n // and this line \u2014 the terminal reset, the report read, the footer \u2014 is still fair game for the\n // trailing SIGINTs of a user mashing Ctrl-C at the child, and one of those reaching the\n // \"no child running\" branch would `exit(0)` the entire session. From this line on, the palette's\n // Ink holds stdin in raw mode (which disables ISIG), so a Ctrl-C there arrives as a `0x03` byte it\n // handles itself \u2014 not as a SIGINT at all.\n childOwnsSigint = false\n\n const selected = await deps.renderPalette(items)\n\n if (selected == null) {\n return\n }\n\n const command = deps.resolveCommand(selected)\n\n if (!command) {\n continue\n }\n\n // Echo first, exactly like a shell: the header must be on screen BEFORE the child starts\n // drawing, or a slow command (a 30s `audit --all`) prints its output under no heading at all.\n const header = formatRunHeader(commandLine(command), { color: resolved.color })\n\n write(`\\n${header}\\n`)\n sessionSignals.childStarted()\n\n const entry = await runOne(command, resolved, () => {\n childOwnsSigint = true\n })\n\n // Leading newline: the child may have exited mid-line, and the footer must not be welded to it.\n // Still inside the child's SIGINT window \u2014 see the top of the loop.\n write(`\\n${entry}\\n`)\n }\n } finally {\n sessionSignals.dispose()\n }\n}\n", "/**\n * Pure formatter for one committed transcript block \u2014 the two-line summary a\n * session leaves behind in the scrollback once it finishes. Line 1 echoes the\n * replayable command (`$ ` when it reproduces exactly, `\u2248 ` when it would\n * re-prompt); line 2 is a status glyph + label + duration + optional summary,\n * optionally run out to a rule that closes the block off; an optional third\n * line carries the env-scope notice. No I/O \u2014 it returns a string the caller\n * writes wherever it likes.\n *\n * The block is framed from the OUTSIDE only. The command runs as a child with\n * `stdio: 'inherit'`, writing straight to the terminal, so nothing here can\n * touch its output \u2014 no left gutter, no indent, no box. The header above and\n * the status rule below are the only lines we own, which is why the rule hangs\n * off the footer: it is the one place we can draw a hard edge between one run\n * and the next.\n */\nimport { Chalk } from 'chalk'\n\nimport type { EquivalentLine } from './equivalent'\nimport type { SessionOutcome } from './outcome'\n\n/**\n * Level 1 (basic 16 colours) FORCED, so the formatter stays pure: what it emits depends on its own\n * `color` argument, never on the ambient TTY. Callers decide \u2014 `runSession` derives the flag from the\n * default chalk instance, which is the thing that honours `NO_COLOR`/`FORCE_COLOR`/TTY detection. Tests\n * can therefore assert real escape codes without faking a terminal.\n */\nconst ansi = new Chalk({ level: 1 })\n\n/** All literal, user-facing copy for the transcript entry. */\nconst T = {\n ok: 'ok',\n findingsPlain: 'completed with findings',\n failed: 'failed',\n cancelled: 'cancelled',\n findingsSuffix: 'findings',\n sep: ' \u00B7 ',\n reproPrefix: '$ ',\n nonReproPrefix: '\u2248 ',\n envNotice: 'Applies to your shell after you exit this session.',\n}\n\n/** Status glyphs keyed by outcome, in both a unicode and an ASCII-safe variant. */\nconst GLYPHS: Record<SessionOutcome, { unicode: string; ascii: string }> = {\n ok: { unicode: '\u2713', ascii: '[ok]' },\n findings: { unicode: '\u26A0', ascii: '[!]' },\n failed: { unicode: '\u2717', ascii: '[x]' },\n cancelled: { unicode: '\u2298', ascii: '[-]' },\n}\n\n/** The rule character that runs the footer out to the right margin. */\nconst RULE = { unicode: '\u2500', ascii: '-' }\n\n/**\n * Shortest rule worth drawing. Below this the footer is left bare: a two-dash stub next to a status\n * reads as a typo, not as an edge, and on a narrow terminal the wrap it risks is worse than no rule.\n */\nconst MIN_RULE_WIDTH = 3\n\n/** Identity styler \u2014 the no-colour branch, so the two paths differ only in the escape codes. */\nconst plain = (text: string): string => {\n return text\n}\n\n/** The outcome's own colour: the one hue in the block, so the eye lands on the verdict first. */\nconst OUTCOME_COLOR: Record<SessionOutcome, (text: string) => string> = {\n ok: (text) => {\n return ansi.green(text)\n },\n findings: (text) => {\n return ansi.yellow(text)\n },\n failed: (text) => {\n return ansi.red(text)\n },\n cancelled: (text) => {\n return ansi.gray(text)\n },\n}\n\n/**\n * The block's chrome \u2014 the `$`/`\u2248` prompt, the command, the rule, the env notice. Carries no outcome,\n * which is why it is split from {@link outcomeStyler}: the header has no verdict to colour.\n */\nconst chromeStyler = (color: boolean): { dim: (text: string) => string; bold: (text: string) => string } => {\n if (!color) return { dim: plain, bold: plain }\n\n return {\n dim: (text: string) => {\n return ansi.dim(text)\n },\n bold: (text: string) => {\n return ansi.bold(text)\n },\n }\n}\n\n/** The verdict's hue: the one saturated colour in the block, so the eye lands on the outcome first. */\nconst outcomeStyler = (color: boolean, outcome: SessionOutcome): ((text: string) => string) => {\n return color ? OUTCOME_COLOR[outcome] : plain\n}\n\n/**\n * Width in terminal cells. `String.length` counts UTF-16 code units, so an astral character \u2014 any emoji\n * a command folds into its summary \u2014 counts as TWO, and the rule comes out a cell short for each one.\n *\n * Deliberately not a complete solution: a CJK or ZWJ-composed summary still mismeasures, since those\n * need a real grapheme + east-asian-width table. Acceptable because the worst case is cosmetic (a rule\n * that wraps or stops short, never a corrupted line) and no command supplies a summary today.\n */\nconst cellWidth = (text: string): number => {\n return [...text].length\n}\n\n/** Everything the formatter needs to render one transcript block. */\nexport interface TranscriptEntryInput {\n /** The replayable command line + whether it reproduces the selection. */\n equivalent: EquivalentLine\n /** Terminal state of the session. */\n outcome: SessionOutcome\n /** Wall-clock duration in milliseconds. */\n durationMs: number\n /** Optional short summary lines; the first is appended to the status line. */\n summary?: string[]\n /** Finding count, used to label a `findings` outcome as `${n} findings`. */\n findingsCount?: number\n /** Append the \"applies to your shell after you exit\" env notice as a third line. */\n envNotice?: boolean\n /** Use ASCII glyphs instead of unicode (for `!isTTY` / `TERM=dumb`). */\n ascii?: boolean\n /**\n * Render the `$ \u2026` / `\u2248 \u2026` equivalent line (default `true`). The session shell echoes the command\n * BEFORE it runs \u2014 like any shell \u2014 so when the child reports back the same line, repeating it under\n * the output would be noise. The caller decides: it is the only party that knows what it echoed.\n */\n showEquivalent?: boolean\n /** Emit ANSI colour (default `false`). The caller owns TTY/`NO_COLOR` detection. */\n color?: boolean\n /**\n * Terminal width in columns. When given, the status line is run out to a rule that closes the block;\n * omit it (or pass a width too narrow for {@link MIN_RULE_WIDTH}) to leave the footer bare.\n */\n width?: number\n}\n\n/**\n * The line a shell prints before it runs something. The session writes this ahead of the spawn, so the\n * child's real output arrives under a heading instead of unannounced.\n *\n * Deliberately NOT ruled or boxed, unlike the footer: this line gets copied out of the scrollback and\n * re-run, so it stays exactly what a user would type. Emphasis is carried by weight, not by decoration.\n *\n * @example\n * formatRunHeader('infra-kit audit') // => '$ infra-kit audit'\n */\nexport const formatRunHeader = (line: string, opts: { color?: boolean } = {}): string => {\n const chrome = chromeStyler(opts.color === true)\n\n return `${chrome.dim(T.reproPrefix.trim())} ${chrome.bold(line)}`\n}\n\n/** Format a duration compactly: sub-second as `820ms`, otherwise `4.2s`. */\nconst formatDuration = (durationMs: number): string => {\n if (durationMs < 1000) return `${Math.round(durationMs)}ms`\n\n return `${(durationMs / 1000).toFixed(1)}s`\n}\n\n/** The human label for an outcome; `findings` folds in the count when present. */\nconst labelFor = (outcome: SessionOutcome, findingsCount?: number): string => {\n if (outcome === 'findings') {\n return findingsCount != null ? `${findingsCount} ${T.findingsSuffix}` : T.findingsPlain\n }\n\n return { ok: T.ok, failed: T.failed, cancelled: T.cancelled }[outcome]\n}\n\n/**\n * The rule that runs the status out to the right margin, giving the block a visible bottom edge \u2014 or\n * `''` when there is no room for one. `statusWidth` is the status line's width in COLUMNS, which is why\n * the caller measures the un-styled text: escape codes occupy no cells.\n *\n * The rule stops one cell short of `width`, because terminals disagree about what filling the last cell\n * means. An eagerly-wrapping one moves to the next row the moment it is written, so the `\\n` the caller\n * appends costs a SECOND row \u2014 a phantom blank line under every entry. (A VT100-family terminal instead\n * defers the wrap and would be fine.) Leaving the last cell empty is correct on both, and the cost is a\n * single unused column.\n */\nconst ruleSuffix = (\n statusWidth: number,\n width: number | undefined,\n ruleChar: string,\n dim: (text: string) => string,\n): string => {\n if (width == null) return ''\n\n // -1 for the last cell we leave empty, -1 for the space between the status and the rule.\n const ruleWidth = width - 2 - statusWidth\n\n if (ruleWidth < MIN_RULE_WIDTH) return ''\n\n return ` ${dim(ruleChar.repeat(ruleWidth))}`\n}\n\n/**\n * Render a committed transcript block from a finished session. Line 1 echoes the\n * equivalent command; line 2 is `<glyph> <label> \u00B7 <duration>[ \u00B7 <summary>]`, run\n * out to a closing rule when a `width` is given; an optional third dim line\n * carries the env notice.\n *\n * @example\n * formatTranscriptEntry({\n * equivalent: { line: 'infra-kit vendor check', reproducible: true },\n * outcome: 'ok', durationMs: 4200,\n * })\n * // '$ infra-kit vendor check\\n\u2713 ok \u00B7 4.2s'\n */\nexport const formatTranscriptEntry = (input: TranscriptEntryInput): string => {\n const color = input.color === true\n const chrome = chromeStyler(color)\n const verdictColor = outcomeStyler(color, input.outcome)\n const ascii = input.ascii === true\n const prefix = input.equivalent.reproducible ? T.reproPrefix : T.nonReproPrefix\n const glyph = ascii ? GLYPHS[input.outcome].ascii : GLYPHS[input.outcome].unicode\n const label = labelFor(input.outcome, input.findingsCount)\n\n const detail = [formatDuration(input.durationMs)]\n // First LINE of the first summary: a summary with an embedded newline would otherwise blow the rule's\n // arithmetic apart \u2014 the measured width would span rows the terminal renders separately.\n const firstSummary = input.summary?.[0]?.split('\\n')[0]\n\n if (firstSummary != null && firstSummary.length > 0) detail.push(firstSummary)\n\n // The verdict carries the colour; the duration and summary trail behind it, dimmed.\n const verdict = `${glyph} ${label}`\n const trailer = `${T.sep}${detail.join(T.sep)}`\n // Measured on the UNSTYLED text and styled after: escape codes occupy no columns, so a rule sized\n // against the styled string would come out ~20 cells short of the margin.\n const statusWidth = cellWidth(verdict) + cellWidth(trailer)\n const rule = ruleSuffix(statusWidth, input.width, ascii ? RULE.ascii : RULE.unicode, chrome.dim)\n\n const lines = [`${verdictColor(verdict)}${chrome.dim(trailer)}${rule}`]\n\n if (input.showEquivalent !== false) {\n lines.unshift(`${chrome.dim(prefix.trim())} ${chrome.bold(input.equivalent.line)}`)\n }\n\n if (input.envNotice === true) lines.push(chrome.dim(T.envNotice))\n\n return lines.join('\\n')\n}\n", "/**\n * Pure classifier that turns a finished session's exit signals into a single\n * {@link SessionOutcome}. Report-presence is decided FIRST: a written report means\n * the command produced a verdict, so a late teardown signal (e.g. SIGINT during\n * cleanup after the report was flushed) is still a success. Only when NO report\n * exists does the signal/exit code decide between a user cancellation and a\n * genuine failure. Side-effect-free so the mapping is fully unit-testable.\n */\n\n/** The four terminal states a session can settle into. */\nexport type SessionOutcome = 'ok' | 'findings' | 'failed' | 'cancelled'\n\n/**\n * The exit codes a command that handles its own termination signal reports on a clean stop: `128 + signo`\n * for SIGINT (130) and SIGTERM (143). Deliberately NOT a `> 128` range check \u2014 SIGKILL (137) means the\n * kernel or an OOM killer stepped in, and SIGSEGV (139) is a crash. Those must stay `failed`.\n *\n * SIGHUP (129) is absent for a reason that lives in ANOTHER file, so changing that file must bring you\n * back here: the session parent exits on SIGHUP the moment it arrives (`run-session.ts`, `onSighup`), so\n * no footer is ever classified for a hangup and 129 is unreachable. If SIGHUP is ever made to DEFER like\n * SIGTERM, add 129 to this set in the same commit \u2014 otherwise a hangup out of `dev` starts silently\n * reading as `\u2717 failed`.\n */\nconst SIGNAL_STOP_EXIT_CODES = new Set([130, 143])\n\n/**\n * Classify a finished session. When a report was written, the run completed:\n * exit 0 (or any teardown signal) is `ok`, a non-zero exit is `findings`. When no\n * report was written, a `SIGINT` or clean exit is a user `cancelled`, anything\n * else is `failed`.\n *\n * `longRunning` marks a child that runs until the user stops it (`dev`). Such a child installs its own\n * SIGINT handler and exits `128 + signo` BY DESIGN \u2014 it is not killed BY the signal, so it reports exit\n * 130 with a null `signal`, which is indistinguishable from a crash on the default path. Without this\n * flag every normal Ctrl-C out of `dev` would be stamped `failed`.\n *\n * @example\n * classifyOutcome(0, null, true) // => 'ok'\n * classifyOutcome(1, null, true) // => 'findings'\n * classifyOutcome(null, 'SIGINT', true) // => 'ok' (teardown after the report was written)\n * classifyOutcome(1, null, false) // => 'failed'\n * classifyOutcome(0, null, false) // => 'cancelled'\n * classifyOutcome(130, null, false, true) // => 'cancelled' (dev, stopped with Ctrl-C)\n * classifyOutcome(1, null, false, true) // => 'failed' (dev, crashed on boot)\n */\nexport const classifyOutcome = (\n exitCode: number | null,\n signal: NodeJS.Signals | null,\n reportPresent: boolean,\n longRunning = false,\n): SessionOutcome => {\n if (reportPresent) {\n return signal != null || exitCode === 0 ? 'ok' : 'findings'\n }\n\n if (longRunning && exitCode != null && SIGNAL_STOP_EXIT_CODES.has(exitCode)) {\n return 'cancelled'\n }\n\n return signal === 'SIGINT' || exitCode === 0 ? 'cancelled' : 'failed'\n}\n", "import process from 'node:process'\n\n/**\n * Terminal hygiene between a finished child and the next palette frame.\n *\n * A child that dies mid-draw \u2014 a SIGINT'd `gh` progress spinner, a crashed pager \u2014 leaves the terminal\n * dirty: cursor hidden, SGR colour still set, autowrap off, the cursor parked mid-line, or a scroll\n * region (DECSTBM) still installed. The session used to run children inside the alternate screen\n * buffer, so all of that was discarded with the buffer on exit. Now that children draw on the primary\n * screen \u2014 which is the point, it is how their output survives \u2014 nobody discards it, and the next\n * status footer would be welded onto a half-written line inside a broken scroll region.\n *\n * So the session cleans up after every child, not just cancelled ones. `?1049l` is NOT sent for\n * ordinary commands (we never entered the alternate screen for them); it is sent only for a command\n * whose child enters it itself and may have died before restoring the primary buffer.\n */\n\n/** The escapes that put a terminal back into a sane state, in the order they must be applied. */\nconst ANSI = {\n /** Return to column 0 \u2014 a child may have exited mid-line. */\n column: '\\r',\n /** Reset SGR: colour, bold, inverse. */\n sgr: '\\u001B[0m',\n /** Show the cursor (a TUI child may have hidden it). */\n cursor: '\\u001B[?25h',\n /** Re-enable autowrap (DECAWM). */\n wrap: '\\u001B[?7h',\n /** Save the cursor position (DECSC). See `scrollRegion`. */\n saveCursor: '\\u001B7',\n /** Restore the cursor position saved by DECSC (DECRC). See `scrollRegion`. */\n restoreCursor: '\\u001B8',\n /**\n * Reset the scroll region to the full screen (DECSTBM) \u2014 a stuck region traps all later output.\n *\n * DECSTBM ALSO HOMES THE CURSOR. That is not a quirk, it is the spec, and it is why this escape must\n * always be bracketed by DECSC/DECRC. Sent bare, it moved the cursor to the top-left after EVERY\n * child \u2014 so the status footer, and then the next palette frame, were drawn from row 1 straight over\n * the command's own output. That is the whole \"welded rows\" bug: `\u2713 ok \u00B7 2.9s` printed on top of an\n * unrelated line, and the palette's `\u276F ` printed over the head of the command's own `ERROR:` line,\n * leaving `\u276F ROR: \u2026` behind.\n */\n scrollRegion: '\\u001B[r',\n /** Leave the alternate screen \u2014 only for a child that entered it and may not have left it. */\n primaryBuffer: '\\u001B[?1049l',\n}\n\nexport interface ResetTerminalDeps {\n write?: (text: string) => void\n /** Take stdin out of raw mode if a dead child left it there. Defaults to `process.stdin`. */\n stdin?: { isTTY?: boolean; isRaw?: boolean; setRawMode?: (mode: boolean) => void }\n}\n\n/**\n * Put the terminal back in a usable state after a child exits. Safe to call unconditionally: every\n * escape is a no-op on a terminal that was already clean.\n *\n * @example\n * const out: string[] = []\n * resetTerminal({ entersAltScreen: false }, { write: (s) => out.push(s), stdin: {} })\n * out.join('') // => '\\u001B7\\u001B[r\\u001B8\\r\\u001B[0m\\u001B[?25h\\u001B[?7h'\n */\nexport const resetTerminal = (opts: { entersAltScreen?: boolean }, deps?: ResetTerminalDeps): void => {\n const write =\n deps?.write ??\n ((text: string) => {\n process.stderr.write(text)\n })\n const stdin = deps?.stdin ?? process.stdin\n\n // DECSTBM homes the cursor, so the scroll-region reset is bracketed by save/restore: the cursor must\n // end up exactly where the child left it \u2014 below the child's output \u2014 or everything written next (the\n // footer, the next palette frame) lands on top of that output instead of after it.\n const escapes = [\n ANSI.saveCursor,\n ANSI.scrollRegion,\n ANSI.restoreCursor,\n ANSI.column,\n ANSI.sgr,\n ANSI.cursor,\n ANSI.wrap,\n ]\n\n // Only for a child that owns the alternate screen: if it was killed before restoring the primary\n // buffer, everything we draw next would land in a buffer the terminal is about to throw away.\n if (opts.entersAltScreen === true) escapes.push(ANSI.primaryBuffer)\n\n write(escapes.join(''))\n\n // A child killed mid-prompt can leave the tty in raw mode, which would eat the palette's keystrokes.\n if (stdin.isTTY === true && stdin.isRaw === true) stdin.setRawMode?.(false)\n}\n", "/**\n * The PARENT half of the auto-update: it never fetches, never installs, and never blocks.\n *\n * All it does is (a) surface a notice the background child left behind for installs we may not touch\n * ourselves, and (b) hand off to a detached child when the throttle window has elapsed. Everything\n * expensive or mutating happens in `src/entry/update-check.ts`, after this process has exited.\n */\nimport { spawn } from 'node:child_process'\nimport fs from 'node:fs'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\n\nimport { safeRealpath } from 'src/lib/install-manager'\nimport { logger } from 'src/lib/logger'\n\nimport { autoUpdateSkipReason } from './guards'\nimport { isNewerVersion } from './semver'\nimport { isStale, readUpdateCache } from './update-cache'\nimport type { UpdateCache } from './update-cache'\n\nexport interface AutoUpdateDeps {\n argv?: string[]\n env?: NodeJS.ProcessEnv\n isTty?: boolean\n cwd?: string\n nowMs?: number\n /** Realpath of the running `dist/cli.js`. */\n selfRealPath?: string\n /** Absolute path to the `dist/update-check.js` sibling bundle. */\n childPath?: string\n readCache?: () => UpdateCache | null\n spawnChild?: (childPath: string) => void\n notify?: (line: string) => void\n fileExists?: (p: string) => boolean\n}\n\n/**\n * The refresh runs as `node dist/update-check.js --parent-pid <pid>`: a DEDICATED entry, never\n * `cli.js`. Re-invoking `cli.js` would re-run this very function (plus commander, the catalog, and\n * every top-level side effect) in the child. The `mcp` command already spawns a sibling bundle for the\n * same reason.\n *\n * `detached` + `unref` + `stdio: 'ignore'` is what lets the child outlive us: a `ik version` exits in\n * ~50ms, far sooner than any registry round-trip. `windowsHide` keeps a console window from flashing.\n */\nconst defaultSpawnChild = (childPath: string): void => {\n const child = spawn(process.execPath, [childPath, '--parent-pid', String(process.pid)], {\n detached: true,\n stdio: 'ignore',\n windowsHide: true,\n })\n\n child.unref()\n}\n\n/** `dist/update-check.js`, resolved beside this bundle exactly as `mcp.ts` resolves `dist/mcp.js`. */\nconst defaultChildPath = (): string => {\n return fileURLToPath(new URL('./update-check.js', import.meta.url))\n}\n\nconst defaultSelfRealPath = (): string => {\n return fs.realpathSync(fileURLToPath(new URL('./cli.js', import.meta.url)))\n}\n\n/**\n * The cache is never executed \u2014 only printed \u2014 but it is still attacker-shaped input if anything with\n * the user's uid can write it. An unconstrained token could carry ANSI escapes that rewrite the terminal\n * around the \"Run: \u2026\" line, turning it into a convincing copy-paste trap. Package-manager argv is drawn\n * from a static table, so a conservative charset costs nothing and closes that.\n */\nconst SAFE_COMMAND_TOKEN = /^[\\w@./+-]+$/i\n\nconst isSafeCommandToken = (token: string): boolean => {\n return SAFE_COMMAND_TOKEN.test(token)\n}\n\n/**\n * Print the pending notice for installs the worker was not allowed to touch (Homebrew, or a location it\n * could not identify). Those users get the one command that will actually work; running a guessed global\n * install on their behalf is worse than printing one.\n *\n * The verdict is READ from the cache, never recomputed: `detectInstallManager` may shell out to\n * `npm root -g`, and the CLI startup path must never pay for a subprocess. A null `updateCommand` means\n * the worker either installs it silently after we exit, or there is nothing to do.\n *\n * `logger` is pino with `destination: 2` \u2014 stderr. Nothing here may reach stdout.\n */\nconst notifyIfUninstallable = (\n cache: UpdateCache | null,\n currentVersion: string,\n notify: (line: string) => void,\n): void => {\n if (!cache?.latestVersion || !cache.updateCommand) return\n if (!isNewerVersion(cache.latestVersion, currentVersion)) return\n if (!cache.updateCommand.every(isSafeCommandToken)) return\n\n notify(\n `infra-kit ${cache.latestVersion} is available (you have ${currentVersion}). Run: ${cache.updateCommand.join(' ')}`,\n )\n}\n\n/**\n * Best-effort. Advisory and background only: it must never throw, never block, and never change the\n * exit code \u2014 a failure to update is not a failure of the command the user actually ran.\n *\n * @example\n * maybeAutoUpdate('0.1.130')\n */\nexport const maybeAutoUpdate = (currentVersion: string, deps: AutoUpdateDeps = {}): void => {\n try {\n const argv = deps.argv ?? process.argv\n const env = deps.env ?? process.env\n const isTty = deps.isTty ?? Boolean(process.stdout.isTTY)\n const cwd = deps.cwd ?? process.cwd()\n const nowMs = deps.nowMs ?? Date.now()\n const selfRealPath = deps.selfRealPath ?? defaultSelfRealPath()\n const readCache = deps.readCache ?? readUpdateCache\n const spawnChild = deps.spawnChild ?? defaultSpawnChild\n const fileExists =\n deps.fileExists ??\n ((p: string) => {\n return fs.existsSync(p)\n })\n const notify =\n deps.notify ??\n ((line: string) => {\n logger.info(line)\n })\n\n if (autoUpdateSkipReason({ argv, env, isTty, selfRealPath, cwd, realpath: safeRealpath }) !== null) return\n\n const cache = readCache()\n\n notifyIfUninstallable(cache, currentVersion, notify)\n\n if (!isStale(cache, nowMs)) return\n\n const childPath = deps.childPath ?? defaultChildPath()\n\n // Absent when running from source (tsx/vitest) rather than a built `dist/`.\n if (!fileExists(childPath)) return\n\n spawnChild(childPath)\n } catch {\n // Advisory only \u2014 see the module doc.\n }\n}\n", "/**\n * Whether this invocation may run the background auto-update at all. Pure: every signal is injected,\n * so the whole matrix is table-tested without spawning or touching argv/env globals.\n */\nimport { isLocalNodeModulesInstall } from 'src/lib/install-manager'\nimport type { RealpathFn } from 'src/lib/install-manager'\n\n/** Set any of these to disable the auto-update entirely. `NO_UPDATE_NOTIFIER`/`CI` are ecosystem convention. */\nexport const OPT_OUT_ENV_VARS = ['INFRA_KIT_NO_AUTO_UPDATE', 'NO_UPDATE_NOTIFIER', 'CI'] as const\n\nexport interface AutoUpdateGuardInput {\n /** Full `process.argv`. */\n argv: string[]\n env: NodeJS.ProcessEnv\n /** `process.stdout.isTTY`. */\n isTty: boolean\n /** Realpath of the running `dist/cli.js`. */\n selfRealPath: string\n cwd: string\n realpath: RealpathFn\n}\n\n/**\n * Reasons to skip, in the order checked. Returned (rather than a bare boolean) so tests assert WHICH\n * guard fired \u2014 a test that only sees `true` cannot tell a working `mcp` guard from a working `CI` one.\n */\nexport type SkipReason = 'opt-out' | 'json' | 'own-command' | 'not-a-tty' | 'local-install'\n\n/**\n * Commands that must never trigger the background updater.\n *\n * `mcp` hands its stdio to a child speaking JSON-RPC. `self-update` already performs the update itself:\n * without this, `ik self-update` would install interactively AND leave behind a detached worker that\n * waits for it to exit and then installs the very same `@latest` a second time.\n */\nconst SELF_MANAGING_COMMANDS = new Set(['mcp', 'self-update'])\n\n/**\n * Positional `argv[2]` mirrors the existing `warnIfLocalInstall` guard and is correct today because the\n * program registers no global options before the subcommand. It is defence-in-depth regardless: the\n * notice goes to stderr, and the `isTty` guard already rejects the MCP server (its stdout is a pipe).\n *\n * NOTE: adding a global `program.option()` would let a flag occupy argv[2] and silently defeat this arm.\n * The stderr + isTty arms are what actually guarantee framing safety.\n */\nconst isSelfManagingCommand = (argv: string[]): boolean => {\n return argv[2] != null && SELF_MANAGING_COMMANDS.has(argv[2])\n}\n\n/**\n * Why this invocation must not auto-update, or null when it may.\n *\n * `local-install` is the subtle one: a repo that pins infra-kit in its `devDependencies` has deliberately\n * chosen a version. Silently upgrading the user's GLOBAL install because their project-local copy is old\n * would be both useless (the project keeps using its pinned copy) and rude.\n *\n * @example\n * autoUpdateSkipReason({ argv: ['node', 'cli.js', 'mcp'], env: {}, isTty: true, selfRealPath: '/g/cli.js', cwd: '/p', realpath: (p) => p })\n * // => 'own-command'\n */\nexport const autoUpdateSkipReason = (input: AutoUpdateGuardInput): SkipReason | null => {\n const { argv, env, isTty, selfRealPath, cwd, realpath } = input\n\n const optedOut = OPT_OUT_ENV_VARS.some((name) => {\n const value = env[name]\n\n return value != null && value !== ''\n })\n\n if (optedOut) return 'opt-out'\n if (argv.includes('--json')) return 'json'\n if (isSelfManagingCommand(argv)) return 'own-command'\n // Never nag or mutate for piped/scripted runs; a human must be present to see the outcome.\n if (!isTty) return 'not-a-tty'\n if (isLocalNodeModulesInstall(selfRealPath, cwd, realpath)) return 'local-install'\n\n return null\n}\n"],
5
- "mappings": "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",
6
- "names": ["select", "Separator", "realpathSync", "process", "fileURLToPath", "resolveLeaf", "roots", "groupPath", "level", "found", "token", "command", "buildPaletteItems", "commands", "MENU_GROUPS", "key", "label", "getMenuGroupEntries", "entry", "Command", "process", "process", "$", "configPath", "paths", "getInfraKitConfigPaths", "rows", "row", "fileExists", "userProjectExists", "summary", "readOverrideSummary", "note", "describeOverrides", "logger", "marker", "suffix", "tildify", "structuredContent", "r", "configEdit", "editor", "process", "seed", "seedUserProjectConfig", "seedCreatedMessage", "$", "resetInfraKitConfigCache", "parseAuthFailureMarker", "value", "config", "reason", "at", "buildAuthFailureWarning", "fs", "path", "process", "WARN_FAIL_SENTINEL_FILE", "WARN_AUTH_FAIL_SENTINEL_FILE", "FAIL_SENTINEL_FILE", "AUTH_FAIL_MARKER_FILE", "FAIL_BACKOFF_MS", "resolveEnvAutoLoad", "canWarn", "config", "getInfraKitConfig", "autoLoad", "resolved", "envTokenExists", "recordAuthFailure", "warnOnce", "buildAuthFailureWarning", "decideAutoLoad", "input", "trigger", "expectedTrigger", "targetConfig", "targetProject", "env", "force", "runEnvAutoLoad", "projectDir", "isAuthFailure", "isEnvAuthFailure", "surfaceStickyAuthFailure", "readAutoLoadEnvSnapshot", "isClearedOnDisk", "recentlyFailed", "preWriteMtime", "readLoadFileMtime", "result", "writeEnvLoadFile", "manualLoadLandedSince", "clearFailure", "clearAuthFailure", "error", "reason", "authFailed", "recordFailure", "logger", "marker", "readAuthFailure", "raw", "fs", "path", "getSessionCacheDir", "parseAuthFailureMarker", "dir", "atomicWriteFileSync", "process", "INFRA_KIT_SESSION_VAR", "INFRA_KIT_ENV_CLEARED_VAR", "INFRA_KIT_ENV_CONFIG_VAR", "INFRA_KIT_ENV_PROJECT_VAR", "INFRA_KIT_ENV_AUTOLOADED_VAR", "ENV_LOAD_FILE", "sinceMtime", "p", "mtime", "clearPath", "ENV_CLEAR_FILE", "loadPath", "flagPath", "message", "sentinelFile", "envAutoload", "projectDir", "runEnvAutoLoad", "DOPPLER_DASHBOARD_URL", "buildRevokeNotice", "project", "env", "envTokenRemove", "existed", "readTokenStore", "removeToken", "purged", "purgeRepoWarmCaches", "getDopplerProject", "storePath", "getTokenStorePath", "logger", "tildify", "line", "structuredContent", "textContent", "password", "process", "$", "readCandidateToken", "stdin", "fromEnv", "readStdin", "token", "process", "commandEcho", "password", "chunks", "chunk", "DOPPLER_CONFIG_KEY", "PROBE_TIMEOUT_MS", "probeToken", "project", "env", "prevQuiet", "$", "stdout", "buildDopplerChildEnv", "error", "translateProbeFailure", "parseDopplerSecretsJson", "stderr", "extractStderr", "classifyDopplerFailure", "detail", "classifyDopplerAuthFailure", "buildUnverifiableScopeMessage", "envTokenSet", "force", "getDopplerProject", "source", "pairs", "assertTokenScope", "scopeVerified", "key", "setToken", "purged", "purgeRepoWarmCaches", "storePath", "getTokenStorePath", "logger", "redactToken", "tildify", "structuredContent", "textContent", "spawn", "process", "fileURLToPath", "FORWARDED_SIGNALS", "mcpServerPath", "fileURLToPath", "runMcp", "deps", "spawnFn", "spawn", "exit", "code", "process", "env", "onError", "message", "logger", "child", "withoutPackageManagerEnv", "error", "signal", "spawnSync", "realpathSync", "homedir", "process", "fileURLToPath", "defaultSelfRealPath", "realpathSync", "fileURLToPath", "exitFromResult", "result", "manager", "print", "exit", "runSelfUpdate", "dryRun", "deps", "spawn", "spawnSync", "line", "logger", "code", "process", "env", "selfRealPath", "lazyNpmRoot", "defaultLazyNpmRoot", "updateCommand", "canSelfSpawn", "detectInstallManager", "safeRealpath", "printable", "homedir", "packageManagerInstallEnv", "VENDOR_CONFIG_FILE", "fs", "path", "process", "pathToFileURL", "PLACEHOLDER_WORKSPACE_DIR", "vendorConfig", "options", "initFactoryConfig", "printFactoryConfig", "factoryPath", "getFactoryConfigPath", "exists", "fileExists", "logger", "tildify", "process", "workspaceDir", "targets", "loadFactoryConfig", "resolvedWorkspace", "expandTilde", "workspaceExists", "allReachable", "repo", "targetPath", "path", "reachable", "marker", "suffix", "cwd", "sourceRoot", "getProjectRoot", "seededTargets", "readLegacyTargets", "fs", "buildScaffold", "VENDOR_CONFIG_FILE", "configPath", "stat", "raw", "pathToFileURL", "resolved", "t", "equivalentLine", "line", "reproducible", "fs", "os", "path", "process", "SESSION_REPORT_ENV", "capturedReportPath", "captured", "summaryLines", "captureSessionReportPath", "env", "writeSessionReport", "record", "deps", "capturedReportPath", "summary", "summaryLines", "payload", "write", "file", "data", "fs", "reportCounter", "newReportPath", "tmp", "os", "pid", "process", "path", "readAndUnlinkReport", "reportPath", "read", "unlink", "raw", "collectReleaseSpec", "value", "prev", "parseRepos", "normalizeIdeMode", "flagName", "IDE_MODES", "configureMergeDev", "cmd", "options", "emit", "ghMergeDev", "configureReleaseList", "ghReleaseList", "configureReleaseCreate", "inputs", "parseReleaseSpec", "releases", "releaseCreate", "configureReleaseDescEdit", "releaseDescEdit", "configureReleaseDeployAll", "ghReleaseDeployAll", "configureReleaseDeploySelected", "ghReleaseDeploySelected", "configureReleaseDeliver", "ghReleaseDeliver", "configureWorktreesSync", "worktreesSync", "configureWorktreesAdd", "ide", "worktreesAdd", "configureWorktreesList", "worktreesList", "configureWorktreesRemove", "worktreesRemove", "configureWorktreesReload", "worktreesReload", "configureVendorConfig", "vendorConfig", "configureVendorCheck", "result", "vendorCheck", "process", "configureVendorDiff", "vendorDiff", "configureConfigPath", "configPath", "configureConfigEdit", "configEdit", "AUTO_LOAD_EXCLUDED", "isAutoLoadExcludedCommand", "name", "SEED_EXCLUDED", "commandPath", "leaf", "parts", "node", "buildProgram", "program", "Command", "releaseGroup", "worktreesGroup", "configCmd", "audit", "vendorCmd", "vendorSync", "vendorManifest", "doctor", "runSelfUpdate", "runMcp", "preset", "runDevServerCli", "tty", "jsonOutput", "version", "envStatus", "envList", "init", "envLoad", "envClear", "env", "envTokenSet", "envTokenList", "envTokenRemove", "envAutoload", "addJsonOption", "_thisCommand", "actionCommand", "commandEcho", "logger", "ensureUserProjectConfig", "surfaceStickyAuthFailure", "runEnvAutoLoad", "path", "isLongRunningCommand", "flags", "suffix", "line", "writeSessionReport", "equivalentLine", "formatAlignedRows", "rows", "gap", "width", "max", "left", "right", "chalkStderr", "nodeSpawn", "process", "Chalk", "ansi", "T", "GLYPHS", "RULE", "MIN_RULE_WIDTH", "plain", "text", "OUTCOME_COLOR", "chromeStyler", "color", "outcomeStyler", "outcome", "cellWidth", "formatRunHeader", "line", "opts", "chrome", "formatDuration", "durationMs", "labelFor", "findingsCount", "ruleSuffix", "statusWidth", "width", "ruleChar", "dim", "ruleWidth", "formatTranscriptEntry", "input", "verdictColor", "ascii", "prefix", "glyph", "label", "detail", "firstSummary", "verdict", "trailer", "rule", "lines", "SIGNAL_STOP_EXIT_CODES", "classifyOutcome", "exitCode", "signal", "reportPresent", "longRunning", "process", "ANSI", "resetTerminal", "opts", "deps", "write", "text", "stdin", "escapes", "commandLine", "command", "stderrColumns", "columns", "process", "waitForExit", "child", "resolve", "code", "signal", "runOne", "deps", "markChildOwnsSigint", "reportPath", "newReportPath", "childEnv", "SESSION_REPORT_ENV", "start", "result", "record", "readAndUnlinkReport", "outcome", "classifyOutcome", "headerLine", "equivalent", "equivalentLine", "formatTranscriptEntry", "installSessionSignals", "isChildRunning", "sigintSeenDuringChild", "quitRequested", "onSigint", "onSigterm", "onSighup", "onSigtstp", "sessionGateEnabled", "env", "streams", "runSession", "items", "resolved", "nodeSpawn", "chalkStderr", "opts", "resetTerminal", "write", "text", "signals", "handler", "childOwnsSigint", "noopSignals", "sessionSignals", "selected", "header", "formatRunHeader", "entry", "spawn", "fs", "process", "fileURLToPath", "OPT_OUT_ENV_VARS", "SELF_MANAGING_COMMANDS", "isSelfManagingCommand", "argv", "autoUpdateSkipReason", "input", "env", "isTty", "selfRealPath", "cwd", "realpath", "name", "value", "isLocalNodeModulesInstall", "defaultSpawnChild", "childPath", "spawn", "process", "defaultChildPath", "fileURLToPath", "defaultSelfRealPath", "fs", "SAFE_COMMAND_TOKEN", "isSafeCommandToken", "token", "notifyIfUninstallable", "cache", "currentVersion", "notify", "isNewerVersion", "maybeAutoUpdate", "deps", "argv", "env", "isTty", "cwd", "nowMs", "selfRealPath", "readCache", "readUpdateCache", "spawnChild", "fileExists", "p", "line", "logger", "autoUpdateSkipReason", "safeRealpath", "isStale", "suppressTypelessPackageJsonWarning", "captureSessionReportPath", "program", "buildProgram", "runProgram", "argv", "error", "isPromptCancellation", "logger", "process", "message", "warnIfLocalInstall", "isLocalNodeModulesInstall", "realpathSync", "fileURLToPath", "safeRealpath", "maybeAutoUpdate", "package_default", "buildPaletteItems", "runInteractiveMenu", "paletteItems", "selected", "runCommandPalette", "alignedLabels", "formatAlignedRows", "item", "choices", "index", "Separator", "select", "runSessionShell", "cliPath", "catalogByName", "commandCatalog", "entry", "runSession", "name", "streams", "sessionGateEnabled"]
4
+ "sourcesContent": ["#!/usr/bin/env node\n// The hashbang must stay on line 1: `npm i -g` symlinks the bin and execs it\n// directly, unlike pnpm's `.bin` shim which invokes `node <path>` explicitly.\n// esbuild preserves it on this entry only \u2014 never on the library entries.\nimport select, { Separator } from '@inquirer/select'\nimport { realpathSync } from 'node:fs'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\n\nimport { commandCatalog } from 'src/lib/command-catalog'\nimport { buildPaletteItems as buildPaletteItemsFromCommands } from 'src/lib/command-catalog/palette'\nimport type { PaletteItem } from 'src/lib/command-catalog/palette'\nimport { isPromptCancellation } from 'src/lib/errors/is-prompt-cancellation'\nimport { isLocalNodeModulesInstall, safeRealpath } from 'src/lib/install-manager'\nimport { logger } from 'src/lib/logger'\nimport { suppressTypelessPackageJsonWarning } from 'src/lib/node-warnings'\nimport { buildProgram } from 'src/lib/program'\nimport { withEscape } from 'src/lib/prompts/escapable-context'\nimport { formatAlignedRows } from 'src/lib/render'\nimport { captureSessionReportPath } from 'src/lib/session/report'\nimport { runSession, sessionGateEnabled } from 'src/lib/session/run-session'\nimport { maybeAutoUpdate } from 'src/lib/update-check'\n\nimport packageJson from '../../package.json' with { type: 'json' }\n\n// Install BEFORE any command imports a consumer's `infra-kit.config.ts` (dev, audit, vendor-config\n// all do), otherwise Node prints a MODULE_TYPELESS_PACKAGE_JSON banner naming an app package.json\n// the user cannot safely add `\"type\": \"module\"` to.\nsuppressTypelessPackageJsonWarning()\n\n// Capture (and delete from the env) the session-shell report path BEFORE any command runs, so the\n// running command can write its report while no descendant (gh / git / a hook / $EDITOR) inherits the\n// var and clobbers the file. A no-op for a normal top-level invocation (the var is unset).\ncaptureSessionReportPath()\n\nconst program = buildProgram()\n\nconst runProgram = async (argv?: string[]): Promise<void> => {\n try {\n if (argv) {\n await program.parseAsync(argv)\n } else {\n await program.parseAsync()\n }\n } catch (error) {\n // Ctrl-C / Esc out of any prompt is a deliberate back-out, not a failure:\n // exit quietly with success so it matches the explicit \"Operation cancelled.\"\n // decline path and never trips scripts/CI into treating a cancel as an error.\n if (isPromptCancellation(error)) {\n logger.info('Operation cancelled.')\n process.exit(0)\n }\n\n const message = error instanceof Error ? error.message : String(error)\n\n logger.error(message)\n process.exit(1)\n }\n}\n\n/**\n * Best-effort nudge when the CLI runs out of a project-local `node_modules`. Advisory only: it must never\n * throw and never change the exit code.\n *\n * The cwd clause inside `isLocalNodeModulesInstall` is what distinguishes a project-local install from a\n * global root \u2014 BOTH contain a `node_modules` segment (`pnpm root -g` is\n * `/Users/<me>/Library/pnpm/global/5/node_modules`). Without it we would nag every global pnpm user.\n *\n * `logger` is pino-pretty with `destination: 2`, i.e. stderr \u2014 never stdout, so `--json` payloads and the\n * MCP child's stdio framing stay clean. It is still suppressed for `--json` (machine consumers want no\n * chatter) and for `mcp` (that child's stdio should carry nothing but the transport).\n */\nconst warnIfLocalInstall = (): void => {\n try {\n if (process.env.INFRA_KIT_NO_LOCATION_WARN) return\n if (process.argv.includes('--json') || process.argv[2] === 'mcp') return\n\n if (isLocalNodeModulesInstall(realpathSync(fileURLToPath(import.meta.url)), process.cwd(), safeRealpath)) {\n logger.info('Running from a project-local node_modules. Install globally for faster startup: npm i -g infra-kit')\n }\n } catch {\n // Advisory only \u2014 a failure to advise is never a failure of the command.\n }\n}\n\nwarnIfLocalInstall()\n\n// Fire-and-forget: reads a cached timestamp, and at most once a day hands off to a detached\n// `dist/update-check.js` that fetches, and silently installs, AFTER this process exits. Adds no\n// startup latency, never throws, never changes the exit code. Opt out with INFRA_KIT_NO_AUTO_UPDATE.\nmaybeAutoUpdate(packageJson.version)\n\n/**\n * Bare-invocation interactive menu: build the grouped command palette from the single command\n * catalog and let the user pick one command (returns its name, or null when nothing is picked).\n * Interactive TTY \u2192 Ink command palette, loaded lazily via dynamic import so React/Ink never touch\n * the MCP / `--json` / non-TTY code paths. Otherwise falls back to the Inquirer menu (scripts, pipes,\n * CI). Ctrl-C / Esc at the menu backs out cleanly (returns null).\n */\n/**\n * Flat {name, description, group} palette list shared by the Ink palette, the Inquirer fallback, and\n * the session shell. Membership and order live in the command catalog; descriptions come from\n * Commander (single source). Each `name` is the canonical grouped path (`release create`).\n */\nconst buildPaletteItems = (): PaletteItem[] => {\n return buildPaletteItemsFromCommands(program.commands)\n}\n\nconst runInteractiveMenu = async (): Promise<string | null> => {\n const paletteItems = buildPaletteItems()\n\n let selected: string | null = null\n\n // Ctrl-C / Esc at the menu throws \u2014 from the Ink palette as a PromptCancelledError, from the\n // inquirer fallback as an AbortPromptError (see lib/prompts/escapable-context). Either is a clean exit.\n //\n // NAME THE STREAM, always: the fallback below is taken when EITHER stream is not a tty, and the two\n // are not interchangeable. With STDOUT redirected but STDIN still a tty (`infra-kit > log.txt`) the\n // fallback runs interactively and Esc GENUINELY CANCELS it \u2014 `withEscape` keys on stdin, so its guard\n // does not fire. Only a non-tty STDIN (a pipe, CI) makes Esc inert here, and correctly so: there is no\n // keypress to receive. Saying \"non-TTY\" unqualified is what makes this spot read as dead code when it\n // is not.\n try {\n if (process.stdout.isTTY && process.stdin.isTTY) {\n const { runCommandPalette } = await import('src/tui/boot')\n\n selected = await runCommandPalette(paletteItems)\n } else {\n const alignedLabels = formatAlignedRows(\n paletteItems.map((item) => {\n return [item.name, item.description] as const\n }),\n )\n\n // Headers come from the palette's own group column, which is already in MENU_GROUPS order and\n // already omits empty groups \u2014 so this cannot render a `\u2014 Label \u2014` header over nothing, and the\n // two menu surfaces cannot list different commands.\n const choices = paletteItems.flatMap((item, index) => {\n const header =\n paletteItems[index - 1]?.group === item.group ? [] : [new Separator(' '), new Separator(`\u2014 ${item.group} \u2014`)]\n\n return [...header, { name: alignedLabels[index] ?? item.name, value: item.name }]\n })\n\n selected = await withEscape(\n (context) => {\n return select({ message: 'Select a command to run', choices }, context)\n },\n { output: process.stderr },\n )\n }\n } catch (error) {\n // Ctrl-C / Esc at the menu is a clean back-out; leave `selected` as null.\n if (!isPromptCancellation(error)) throw error\n }\n\n return selected\n}\n\n/**\n * Persistent session shell: bare `infra-kit` in a capable interactive TTY keeps running after each\n * command. Each pick echoes `$ infra-kit <canonical argv>` and spawns it as a fresh child that\n * inherits this terminal, so the command's real output lands in the scrollback and stays there, closed\n * out by a status footer. React-free loop logic lives in `src/lib/session`; the Ink palette is reached\n * only through the lazy `src/tui/boot` import.\n */\nconst runSessionShell = async (): Promise<void> => {\n const { runCommandPalette } = await import('src/tui/boot')\n const cliPath = fileURLToPath(import.meta.url)\n // Keyed by the SAME string the palette rows carry \u2014 the space-joined group path \u2014 so a pick resolves\n // by what it displays. Keying by `cliName` worked only while every command had a flat alias to name it.\n const catalogByName = new Map(\n commandCatalog.map((entry) => {\n return [entry.groupPath.join(' '), entry]\n }),\n )\n\n await runSession(buildPaletteItems(), {\n renderPalette: runCommandPalette,\n resolveCommand: (name) => {\n const entry = catalogByName.get(name)\n\n return entry\n ? {\n groupPath: entry.groupPath,\n entersAltScreen: entry.entersAltScreen,\n sessionEnvNotice: entry.sessionEnvNotice,\n longRunning: entry.longRunning,\n }\n : undefined\n },\n cliPath,\n })\n}\n\nif (process.argv.length <= 2) {\n const streams = {\n stdoutIsTTY: Boolean(process.stdout.isTTY),\n stdinIsTTY: Boolean(process.stdin.isTTY),\n // The palette and the transcript both render to stderr; redirect it and the shell is unusable.\n stderrIsTTY: Boolean(process.stderr.isTTY),\n }\n\n if (sessionGateEnabled(process.env, streams)) {\n // A capable interactive TTY \u2192 the persistent session shell (exit code always 0).\n await runSessionShell()\n } else {\n // Pipes / CI / non-TTY / opt-out \u2192 today's one-shot behaviour and exit codes.\n const selected = await runInteractiveMenu()\n\n // `selected` is the grouped path (`release create`), so it must be split back into argv tokens \u2014\n // passed whole, Commander would look for a single command literally named \"release create\".\n if (selected) {\n await runProgram(['node', 'infra-kit', ...selected.split(' ')])\n }\n }\n} else {\n await runProgram()\n}\n", "import type { Command } from 'commander'\n\nimport { MENU_GROUPS, getMenuGroupEntries } from './command-catalog'\n\n/**\n * The flat, grouped command list behind every no-arg surface: the Ink palette, the Inquirer fallback,\n * and the session shell. Membership and group ORDER come from the catalog's {@link MENU_GROUPS};\n * descriptions come from Commander, so there is exactly one source for each and no way to restate them\n * out of sync. (`entry/cli.ts` used to hardcode the group list three separate times.)\n */\n\n/** One palette row: what to run, what it does, and which group header it sits under. */\nexport interface PaletteItem {\n /**\n * The command's canonical grouped path, space-joined (`release create`). It is BOTH the label the row\n * renders AND the argv every surface dispatches, so a row can no longer advertise one name and run\n * another \u2014 which is exactly what the old flat `release-create` label did.\n */\n name: string\n description: string\n group: string\n}\n\n/**\n * Resolve a catalog `groupPath` to its Commander leaf by walking the command tree one token at a time\n * (`['vendor','check']` \u2192 the `check` subcommand of `vendor`).\n *\n * A flat top-level lookup cannot reach a grouped leaf, and that limitation is the whole reason those\n * leaves used to need a hidden flat sibling (`vendor-check`) for the menu to find them. Walking the tree\n * removes the reason those aliases existed.\n *\n * @example\n * resolveLeaf(program.commands, ['vendor', 'check'])?.name() // => 'check'\n */\nexport const resolveLeaf = (roots: readonly Command[], groupPath: readonly string[]): Command | undefined => {\n let level: readonly Command[] = roots\n let found: Command | undefined\n\n for (const token of groupPath) {\n found = level.find((command) => {\n return command.name() === token\n })\n\n if (!found) return undefined\n\n level = found.commands\n }\n\n return found\n}\n\n/**\n * Build the palette rows from the registered Commander program, in MENU_GROUPS order.\n *\n * A catalog entry whose `groupPath` resolves to no Commander command is SKIPPED rather than rendered as\n * a dead row the user could pick and watch fail \u2014 the catalog test asserts every menu entry resolves to\n * a real leaf, so a skip here means that guard was bypassed, not that the row is legitimately absent.\n *\n * @example\n * buildPaletteItems(program.commands)[0] // => { name: 'dev', description: 'Run local dev servers\u2026', group: 'Develop' }\n */\nexport const buildPaletteItems = (commands: readonly Command[]): PaletteItem[] => {\n return MENU_GROUPS.flatMap(({ key, label }) => {\n return getMenuGroupEntries(key).flatMap((entry) => {\n const command = resolveLeaf(commands, entry.groupPath)\n\n return command ? [{ name: entry.groupPath.join(' '), description: command.description(), group: label }] : []\n })\n })\n}\n", "import { Command } from 'commander'\nimport process from 'node:process'\n\nimport { audit } from 'src/commands/audit'\nimport { configEdit, configPath } from 'src/commands/config'\nimport { doctor } from 'src/commands/doctor'\nimport { envAutoload } from 'src/commands/env-autoload'\nimport { envClear } from 'src/commands/env-clear'\nimport { envList } from 'src/commands/env-list'\nimport { envLoad } from 'src/commands/env-load'\nimport { envStatus } from 'src/commands/env-status'\nimport { envTokenList } from 'src/commands/env-token-list'\nimport { envTokenRemove } from 'src/commands/env-token-remove'\nimport { envTokenSet } from 'src/commands/env-token-set'\nimport { ghMergeDev } from 'src/commands/gh-merge-dev'\nimport { ghReleaseDeliver } from 'src/commands/gh-release-deliver'\nimport { ghReleaseDeployAll } from 'src/commands/gh-release-deploy-all'\nimport { ghReleaseDeploySelected } from 'src/commands/gh-release-deploy-selected'\nimport { ghReleaseList } from 'src/commands/gh-release-list'\nimport { init } from 'src/commands/init'\nimport { runMcp } from 'src/commands/mcp'\nimport { releaseCreate } from 'src/commands/release-create'\nimport { releaseDescEdit } from 'src/commands/release-desc-edit'\nimport { runSelfUpdate } from 'src/commands/self-update'\nimport { vendorCheck } from 'src/commands/vendor-check'\nimport { vendorConfig } from 'src/commands/vendor-config'\nimport { vendorDiff } from 'src/commands/vendor-diff'\nimport { vendorManifest } from 'src/commands/vendor-manifest'\nimport { vendorSync } from 'src/commands/vendor-sync'\nimport { version } from 'src/commands/version'\nimport { worktreesAdd } from 'src/commands/worktrees-add'\nimport { worktreesList } from 'src/commands/worktrees-list'\nimport { worktreesReload } from 'src/commands/worktrees-reload'\nimport { worktreesRemove } from 'src/commands/worktrees-remove'\nimport { worktreesSync } from 'src/commands/worktrees-sync'\nimport { IDE_MODES } from 'src/integrations/ide'\nimport type { IdeMode } from 'src/integrations/ide'\nimport { isLongRunningCommand } from 'src/lib/command-catalog'\nimport { commandEcho } from 'src/lib/command-echo'\nimport { ensureUserProjectConfig } from 'src/lib/config-bootstrap'\nimport { runEnvAutoLoad, surfaceStickyAuthFailure } from 'src/lib/env-autoload'\nimport { addJsonOption, emit, jsonOutput } from 'src/lib/json-output'\nimport { logger } from 'src/lib/logger'\nimport { equivalentLine } from 'src/lib/session/equivalent'\nimport { writeSessionReport } from 'src/lib/session/report'\nimport { parseReleaseSpec } from 'src/lib/version-utils'\nimport type { ReleaseInput } from 'src/lib/version-utils'\n\n/**\n * Side-effect-free construction of the Commander program. It is deliberately isolated from\n * `src/entry/cli.ts` (which runs `warnIfLocalInstall()`, `maybeAutoUpdate()`, and a top-level `await`\n * at module load) so a test \u2014 or the session shell \u2014 can import and walk the command tree WITHOUT\n * triggering those boot side effects. `cli.ts` calls `buildProgram()` once and owns everything else.\n */\n\nconst collectReleaseSpec = (value: string, prev: string[]): string[] => {\n return [...prev, value]\n}\n\n/** Parse a `--repos a,b,c` option into a target-name list (undefined = all). */\nconst parseRepos = (value: unknown): string[] | undefined => {\n return typeof value === 'string' ? value.split(',').filter(Boolean) : undefined\n}\n\nconst normalizeIdeMode = (value: unknown, flagName: '--ide' | '--cursor'): IdeMode | undefined => {\n if (typeof value === 'undefined') {\n return undefined\n }\n\n if (value === true) {\n return 'workspace'\n }\n\n if (value === false) {\n return 'none'\n }\n\n if (typeof value === 'string' && (IDE_MODES as readonly string[]).includes(value)) {\n return value as IdeMode\n }\n\n throw new Error(`Invalid ${flagName} value \"${String(value)}\". Expected one of: ${IDE_MODES.join(', ')}.`)\n}\n\n// --- Command configurators (one source of options + action per command) ---\n//\n// These were factored out when each command had TWO registrations \u2014 a grouped form and a flat alias \u2014\n// that had to be kept from diverging. The flat aliases are gone; the configurators stay because they\n// keep `buildProgram` readable, and a command still gets exactly one definition.\nconst configureMergeDev = (cmd: Command): Command => {\n return cmd\n .description('Merge dev branch into every release branch')\n .option('-a, --all', 'Select all active release branches')\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n emit(await ghMergeDev({ all: options.all, confirmedCommand: options.yes }))\n })\n}\n\nconst configureReleaseList = (cmd: Command): Command => {\n return cmd.description('List all release branches').action(async () => {\n emit(await ghReleaseList())\n })\n}\n\nconst configureReleaseCreate = (cmd: Command): Command => {\n return cmd\n .description('Create one or more release branches (each entry can mix regular/hotfix and its own description)')\n .option(\n '-r, --release <spec>',\n 'Release spec \"<version|next|name>[:type[:description]]\" (repeatable). The token is a semver (\"1.2.5\"), the literal \"next\", or a kebab-case name (\"checkout-redesign\"). Type is regular|hotfix (default regular). Examples: \"1.2.5\", \"1.2.5:hotfix\", \"next:regular:Holiday backend\", \"checkout-redesign:regular:Q3 redesign\".',\n collectReleaseSpec,\n [],\n )\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n const specs = options.release as string[]\n const inputs: ReleaseInput[] = specs.map(parseReleaseSpec)\n const releases = inputs.length > 0 ? inputs : undefined\n\n emit(\n await releaseCreate({\n releases,\n confirmedCommand: options.yes,\n }),\n )\n })\n}\n\nconst configureReleaseDescEdit = (cmd: Command): Command => {\n return cmd\n .description(\"Edit a release's description in Jira and in the matching GitHub PR body\")\n .option('-v, --version <version>', 'Release version (e.g. 1.2.5) or release name (e.g. checkout-redesign)')\n .option('-d, --description <description>', 'New description (use \"\" to clear)')\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n emit(\n await releaseDescEdit({\n version: options.version,\n description: options.description,\n confirmedCommand: options.yes,\n }),\n )\n })\n}\n\nconst configureReleaseDeployAll = (cmd: Command): Command => {\n return cmd\n .description('Deploy any release branch to any environment')\n .option(\n '-v, --version <version>',\n 'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deploy; \"dev\" deploys from the dev branch',\n )\n .option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')\n .option('--skip-terraform', 'Skip terraform deployment step')\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n emit(\n await ghReleaseDeployAll({\n version: options.version,\n env: options.env,\n skipTerraform: options.skipTerraform,\n confirmedCommand: options.yes,\n }),\n )\n })\n}\n\nconst configureReleaseDeploySelected = (cmd: Command): Command => {\n return cmd\n .description('Deploy selected services from release branch to any environment')\n .option(\n '-v, --version <version>',\n 'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deploy; \"dev\" deploys from the dev branch',\n )\n .option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')\n .option('-s, --services <services...>', 'Specify services to deploy, e.g. client-be client-fe')\n .option('--skip-terraform', 'Skip terraform deployment step')\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n emit(\n await ghReleaseDeploySelected({\n version: options.version,\n env: options.env,\n services: options.services,\n skipTerraform: options.skipTerraform,\n confirmedCommand: options.yes,\n }),\n )\n })\n}\n\nconst configureReleaseDeliver = (cmd: Command): Command => {\n return cmd\n .description('Release a new version to production')\n .option('-v, --version <version>', 'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deliver')\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n emit(await ghReleaseDeliver({ version: options.version, confirmedCommand: options.yes }))\n })\n}\n\nconst configureWorktreesSync = (cmd: Command): Command => {\n return cmd\n .description('Remove release worktrees whose PRs are no longer open')\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async (options) => {\n emit(await worktreesSync({ confirmedCommand: options.yes }))\n })\n}\n\nconst configureWorktreesAdd = (cmd: Command): Command => {\n return cmd\n .description('Add git worktrees for release branches')\n .option('-y, --yes', 'Skip confirmation prompt')\n .option('-a, --all', 'Select all active release branches')\n .option('-v, --versions <versions>', 'Specify versions by comma, e.g. 1.2.5, 1.2.6')\n .option('-i, --ide [mode]', 'Editor mode for created worktrees: workspace (default) | none')\n .option('--no-ide', 'Skip the editor (alias for --ide none)')\n .option('-c, --cursor [mode]', 'Deprecated alias for --ide')\n .option('--no-cursor', 'Deprecated alias for --no-ide')\n .option('-g, --github-desktop', 'Open created worktrees in GitHub Desktop')\n .option('--no-github-desktop', 'Skip GitHub Desktop prompt')\n .option('-m, --cmux', 'Open created worktrees in cmux (3-pane layout)')\n .option('--no-cmux', 'Skip cmux prompt')\n .action(async (options) => {\n // `--ide` wins over the deprecated `--cursor` alias when both are provided.\n const ide = normalizeIdeMode(options.ide, '--ide') ?? normalizeIdeMode(options.cursor, '--cursor')\n\n emit(\n await worktreesAdd({\n confirmedCommand: options.yes,\n all: options.all,\n versions: options.versions,\n ide,\n githubDesktop: options.githubDesktop,\n cmux: options.cmux,\n }),\n )\n })\n}\n\nconst configureWorktreesList = (cmd: Command): Command => {\n return cmd.description('List all git worktrees with detailed information').action(async () => {\n emit(await worktreesList())\n })\n}\n\nconst configureWorktreesRemove = (cmd: Command): Command => {\n return cmd\n .description('Remove git worktrees for release branches')\n .option('-y, --yes', 'Skip confirmation prompt')\n .option('-a, --all', 'Select all active release branches')\n .option('-v, --versions <versions>', 'Specify versions by comma, e.g. 1.2.5, 1.2.6')\n .action(async (options) => {\n emit(await worktreesRemove({ confirmedCommand: options.yes, all: options.all, versions: options.versions }))\n })\n}\n\nconst configureWorktreesReload = (cmd: Command): Command => {\n return cmd\n .description(\n 'Close all cmux/editor worktree windows, then reopen the current release worktrees (also cold-start restore)',\n )\n .action(async () => {\n emit(await worktreesReload())\n })\n}\n\nconst configureVendorConfig = (cmd: Command): Command => {\n return cmd\n .description('Show the machine-local factory config (~/.infra-kit/vendor.json) or scaffold it with --init')\n .option('--init', 'Scaffold ~/.infra-kit/vendor.json (skips if it already exists)')\n .action(async (options) => {\n emit(await vendorConfig({ init: options.init }))\n })\n}\n\nconst configureVendorCheck = (cmd: Command): Command => {\n return cmd\n .description('Verify vendor/ matches vendor/.sync-manifest.json (self-contained; for any consumer repo)')\n .action(async () => {\n const result = await vendorCheck()\n\n emit(result)\n\n if (!result.structuredContent.ok) {\n process.exitCode = 1\n }\n })\n}\n\nconst configureVendorDiff = (cmd: Command): Command => {\n return cmd\n .description('Source-aware drift check (rsync dry-run) of each target vendored subtree vs the source')\n .option('-r, --repos <repos>', 'Restrict to comma-separated target repo names')\n .action(async (options) => {\n const result = await vendorDiff({ repos: parseRepos(options.repos) })\n\n emit(result)\n\n if (!result.structuredContent.ok) {\n process.exitCode = 1\n }\n })\n}\n\nconst configureConfigPath = (cmd: Command): Command => {\n return cmd.description('Show the resolved config merge chain and file paths').action(async () => {\n emit(await configPath())\n })\n}\n\nconst configureConfigEdit = (cmd: Command): Command => {\n return cmd.description('Open the user-scope per-project override file in $EDITOR').action(async () => {\n emit(await configEdit())\n })\n}\n\n// Commands excluded from the cli-invocation auto-load trigger: the env-* family\n// (avoids recursion \u2014 `env-autoload`/`env-load` would re-enter), plus the\n// host-inspecting / meta commands where priming Doppler env would be surprising\n// (`init` bootstraps the shell block, `doctor` inspects auth, `version` prints a\n// string, `dev` is a long-running server that manages its own env,\n// `self-update` replaces this binary, and `mcp` hands its stdio to a child).\n// `--help`/`--version`/the bare-arg menu don't fire preAction at all.\nconst AUTO_LOAD_EXCLUDED = new Set(['init', 'doctor', 'version', 'dev', 'self-update', 'mcp'])\n\nconst isAutoLoadExcludedCommand = (name: string): boolean => {\n return name.startsWith('env-') || AUTO_LOAD_EXCLUDED.has(name)\n}\n\n// Commands excluded from the layer-3 config auto-seed. A SEPARATE, much SMALLER set than\n// AUTO_LOAD_EXCLUDED above \u2014 the asymmetry is intentional, not an oversight:\n// - `env-autoload` is the hidden command the zsh precmd hook fires BACKGROUNDED on every prompt\n// (see the shell body in init.ts). It runs constantly; the seed has no business on that path.\n// - `mcp` is excluded for LAZINESS, not cwd: the MCP server seeds at its first TOOL INVOCATION\n// (lib/tool-handler), so a server that never receives a tool call never writes to $HOME.\n// - `version` / `self-update` touch config ZERO times today, so seeding is pure new cost on the two\n// fastest paths; `self-update` also re-execs the binary.\n// Everything else DOES seed \u2014 `doctor`, `config path`, `dev` and `init` included (they are all in\n// AUTO_LOAD_EXCLUDED, but that set answers a different question), as does the whole `env-*` family\n// apart from `env-autoload`.\nconst SEED_EXCLUDED = new Set(['env-autoload', 'mcp', 'version', 'self-update'])\n\n/**\n * Canonical space-joined command path for a leaf (e.g. the `check` leaf of `vendor` \u2192 \"vendor check\").\n * Stops at the root program (a node with no parent) so its name \u2014 the bin basename like \"cli\" \u2014 never\n * leaks into the equivalent line.\n */\nexport const commandPath = (leaf: Command): string => {\n const parts: string[] = []\n\n for (let node: Command | null = leaf; node && node.parent; node = node.parent) {\n parts.unshift(node.name())\n }\n\n return parts.join(' ')\n}\n\n/**\n * Build the full Commander program: ONE surface per command (the grouped form \u2014 `release create`, never\n * `release-create`), plus `--json` on every command and the pre-action auto-load hook. Pure: no I/O, no\n * top-level await, no process mutation \u2014 safe to import from a test to introspect the command tree.\n *\n * @example\n * const program = buildProgram()\n * program.commands.some((c) => c.name() === 'doctor') // => true\n */\nexport const buildProgram = (): Command => {\n const program = new Command()\n\n // --- Grouped command surface (preferred form) ---\n const releaseGroup = program.command('release').description('Release management commands')\n\n configureMergeDev(releaseGroup.command('merge-dev'))\n configureReleaseList(releaseGroup.command('list'))\n configureReleaseCreate(releaseGroup.command('create'))\n configureReleaseDescEdit(releaseGroup.command('desc-edit'))\n configureReleaseDeployAll(releaseGroup.command('deploy-all'))\n configureReleaseDeploySelected(releaseGroup.command('deploy-selected'))\n configureReleaseDeliver(releaseGroup.command('deliver'))\n\n const worktreesGroup = program.command('worktrees').description('Git worktree management commands')\n\n configureWorktreesAdd(worktreesGroup.command('add'))\n configureWorktreesList(worktreesGroup.command('list'))\n configureWorktreesRemove(worktreesGroup.command('remove'))\n configureWorktreesSync(worktreesGroup.command('sync'))\n configureWorktreesReload(worktreesGroup.command('reload'))\n\n const configCmd = program.command('config').description('Manage infra-kit configuration files')\n\n configureConfigPath(configCmd.command('path'))\n configureConfigEdit(configCmd.command('edit'))\n\n program\n .command('audit')\n .description('Audit against infra-kit.config.ts rules (--all for every package, --root for the monorepo root)')\n .option('-a, --all', 'Audit every non-vendor workspace package')\n .option('-r, --root', 'Audit the monorepo root (turbo pipeline + root commands)')\n .action(async (options) => {\n const result = await audit({ all: options.all, root: options.root })\n\n emit(result)\n\n if (!result.structuredContent.allPassed) {\n process.exitCode = 1\n }\n })\n\n const vendorCmd = program.command('vendor').description('Verify and sync the mirrored vendor/ tree')\n\n configureVendorCheck(vendorCmd.command('check'))\n\n vendorCmd\n .command('sync')\n .description('Copy vendored files from the source repo into each target and regenerate manifests')\n .option('-y, --yes', 'Skip confirmation prompt')\n .option('-r, --repos <repos>', 'Restrict to comma-separated target repo names')\n .action(async (options) => {\n emit(await vendorSync({ confirmedCommand: options.yes, repos: parseRepos(options.repos) }))\n })\n\n vendorCmd\n .command('manifest')\n .description('Regenerate each target vendor/.sync-manifest.json + README from current content (no copy)')\n .option('-r, --repos <repos>', 'Restrict to comma-separated target repo names')\n .action(async (options) => {\n emit(await vendorManifest({ confirmedCommand: true, repos: parseRepos(options.repos) }))\n })\n\n configureVendorDiff(vendorCmd.command('diff'))\n configureVendorConfig(vendorCmd.command('config'))\n\n program\n .command('doctor')\n .description('Check installation and authentication status of gh and doppler CLIs')\n .option(\n '--fix',\n 'Remove portless routes left behind by a dev-server that was killed (kill -9, OOM, force-quit). Refuses while a dev session is running, when a booting UI is indistinguishable from a dead route.',\n )\n .action(async (options) => {\n emit(await doctor({ fix: Boolean(options.fix) }))\n })\n\n // No `update` alias: the `update` verb is reserved.\n program\n .command('self-update')\n .description('Update this CLI using the package manager that installed it')\n .option('--dry-run', 'Print the detected manager and the command that would run; install nothing')\n .action((options) => {\n runSelfUpdate({ dryRun: Boolean(options.dryRun) })\n })\n\n program\n .command('mcp')\n .description('Run the infra-kit MCP server (stdio transport)')\n .action(() => {\n runMcp()\n })\n\n program\n .command('dev')\n .description('Run local dev servers for a named devServersPresets preset (or all apps); api + ui')\n .argument('[preset]', 'Named preset from devServersPresets (omit to run every app)')\n .option('-w, --watch', 'Rebuild and restart on file save')\n .option('--app <names>', 'Further narrow to these app folder names (comma-separated)')\n .option(\n '--target <keys>',\n 'Run exactly these <app>/api|<app>/ui packages (comma-separated); part-level, unlike --app',\n )\n .option(\n '--cmux',\n 'Run each app in its own cmux pane (one workspace, N panes; falls back to single terminal if cmux is unavailable)',\n )\n .option('--self', 'Run only the app of the current directory (infer from cwd; use inside apps/<app>/\u2026)')\n .option('-V, --verbose', 'Print full boot narration (default: quiet; full detail always in the session log)')\n .option('--routes', 'Print each app\u2019s registered METHOD /path routes at startup (default: off)')\n .option(\n '--no-ui-health',\n 'Do not probe the frontends\u2019 liveness (vite\u2019s HMR ping); their rows carry no health dot (also: INFRA_KIT_NO_UI_HEALTH=1)',\n )\n .action(async (preset, options) => {\n // Lazy import so fastify/chokidar (and the whole dev stack, plus the wizard's inquirer/config\n // graph) never load on the eager cli graph \u2014 they land in a split chunk reached only for `dev`.\n const { runDevServerCli } = await import('src/entry/dev-server')\n\n // A bare `infra-kit dev` in a TTY launches the interactive wizard; any flag/preset/pipe/--json\n // runs directly. `runDevServerCli` owns that decision (see `shouldRunWizard`).\n const tty = Boolean(process.stdout.isTTY && process.stdin.isTTY)\n\n await runDevServerCli({ ...options, preset }, tty, jsonOutput.enabled)\n })\n\n program\n .command('version')\n .description('Print the installed infra-kit CLI version')\n .action(async () => {\n emit(await version())\n })\n\n program\n .command('env-status')\n .description('Show which env is loaded in this session (local introspection; no Doppler call)')\n .action(async () => {\n emit(await envStatus())\n })\n\n program\n .command('env-list')\n .description(\n 'List available Doppler configs for the detected project, and whether a service token resolves for each',\n )\n .action(async () => {\n emit(await envList())\n })\n\n program\n .command('init')\n .description('Inject shell integration into .zshrc and sync repo agent-instruction files')\n .action(async () => {\n emit(await init())\n })\n\n program\n .command('env-load')\n .description('Load Doppler env vars for a config. Source the returned file path to apply.')\n .option('-c, --config <config>', 'Environment config name to load (e.g. dev, arthur)')\n .action(async (options) => {\n emit(await envLoad({ config: options.config }))\n })\n\n program\n .command('env-clear')\n .description('Clear loaded env vars. Source the returned file path to apply.')\n .option('--purge', \"Also delete this project's warm cache outright (durable disable)\")\n .action(async (options) => {\n emit(await envClear({ purge: Boolean(options.purge) }))\n })\n\n // --- Doppler service tokens (flat, related names \u2014 not a nested `env token <sub>` group) ---\n //\n // There is deliberately NO `--token <value>` flag on `env-token-set`, and there must never be one:\n // argv is world-visible in `ps`, lands in shell history, and `commandEcho` prints option VALUES back\n // to the terminal. The three input channels below all keep the token out of argv.\n program\n .command('env-token-set')\n .description('Store the Doppler service token for an env (masked prompt; validated against Doppler before writing)')\n .argument('<env>', 'Environment / Doppler config the token is scoped to (e.g. dev)')\n .option('--stdin', 'Read the token from stdin instead of prompting (e.g. from a password manager)')\n .option('--from-env <var>', 'Read the token from the named environment variable (the NAME, never the value)')\n .option('--force', 'Store even when the token\u2019s scope could not be verified. Never overrides a real mismatch.')\n .action(async (env, options) => {\n emit(\n await envTokenSet({\n env,\n stdin: options.stdin,\n fromEnv: options.fromEnv,\n force: options.force,\n }),\n )\n })\n\n program\n .command('env-token-list')\n .description('Show which envs have a Doppler service token (redacted), and where it came from')\n .option('--check', 'Also ask Doppler whether each token is valid and correctly scoped')\n .action(async (options) => {\n emit(await envTokenList({ check: Boolean(options.check) }))\n })\n\n program\n .command('env-token-remove')\n .description('Delete an env\u2019s Doppler service token from the local store (does NOT revoke it in Doppler)')\n .argument('<env>', 'Environment / Doppler config whose token to remove')\n .action(async (env) => {\n emit(await envTokenRemove({ env }))\n })\n\n // Internal: driven by the init shell-startup integration (backgrounded). Writes\n // env-load.sh when envAutoLoad is configured + eligible; the precmd hook sources\n // it. Hidden + no stdout output so it never pollutes the shell or the menu.\n program\n .command('env-autoload', { hidden: true })\n .description('Internal: prime env for the shell-startup auto-load trigger')\n // The shell passes its already-canonicalized (`${dir:A}`) project dir so node can\n // key the warm cache identically; see writeEnvLoadFile / shouldWriteWarm.\n .option('--project-dir <dir>', 'Canonical project dir for the warm-cache key (shell-startup only)')\n .action(async (options) => {\n await envAutoload({ projectDir: options.projectDir })\n })\n\n // Register `--json` on every command, then resolve the flag before each action\n // runs. In JSON mode we lower the logger to `warn` so the human-oriented info\n // lines stop cluttering stderr while errors still surface; the structured\n // payload is written to stdout by `emit`. No handler logic is affected.\n program.commands.forEach(addJsonOption)\n\n program.hook('preAction', async (_thisCommand, actionCommand) => {\n // Bind the \"\uD83D\uDCDF Equivalent command\" line to the argv Commander just parsed. This is the only place\n // that knows it, so it is the only place that says it: when the commands named themselves, they\n // printed the flat `release-create`, which the grouped-only surface no longer parses \u2014 a replay line\n // that errors out. `commandPath()` is the same string the menu spawns, so what we print is runnable\n // by construction, and stays runnable through any future regrouping.\n commandEcho.start(commandPath(actionCommand))\n\n // `optsWithGlobals` (not `opts`) so `--json` is seen on grouped subcommands:\n // for `release list --json` Commander binds the post-subcommand flag to the\n // parent `release` group, so the leaf's own `opts()` would not carry it.\n jsonOutput.enabled = Boolean(actionCommand.optsWithGlobals().json)\n\n if (jsonOutput.enabled) {\n logger.level = 'warn'\n }\n\n // Layer-3 config auto-seed: ensure ~/.infra-kit/projects/<main-repo>/infra-kit.json exists (plus\n // its annotated .example.jsonc sibling) so a user always has a per-project override file to edit.\n // Self-gating: no-ops outside a git repo, and when the repo has no committed `infra-kit.json`.\n // Never throws, never changes the exit code, and performs zero writes in the steady state.\n //\n // Gated on `commandPath()`, NOT `actionCommand.name()`: a leaf's `name()` is only its last\n // segment (`config path`'s name() is just 'path'), so a name-keyed set cannot distinguish grouped\n // leaves and would collide. Nothing in SEED_EXCLUDED is a grouped leaf today, but the set must be\n // able to express one without a silent trap.\n //\n // Placed after the `--json` warn downgrade purely so a first-run `logger.info` stays out of a\n // machine consumer's stderr. That is COSMETIC, not stdout safety: `logger` is pino with\n // `destination: 2` (stderr) and `emit()` is the sole stdout writer, so the seed's log line could\n // never corrupt `--json` stdout.\n if (!SEED_EXCLUDED.has(commandPath(actionCommand))) {\n await ensureUserProjectConfig()\n }\n\n // Replay a sticky Doppler auth failure (revoked / mis-scoped service token) recorded\n // by the BACKGROUNDED shell-startup auto-load, which runs with stderr discarded and\n // so has no channel of its own. Deliberately OUTSIDE the auto-load exclusion below:\n // warning is not loading. `version`, `doctor` and `dev` never auto-load, but they are\n // exactly what a user whose shell env went quiet runs next \u2014 gating the warning on the\n // same set would leave them warned by nothing. Warns at most once per session, never\n // throws, and writes nothing to stdout.\n surfaceStickyAuthFailure()\n\n // cli-invocation auto-load: primes the shell env for SUBSEQUENT commands. The\n // current command does NOT see these vars \u2014 a child process can't mutate its\n // parent shell; the precmd hook sources the written file on the next prompt.\n // runEnvAutoLoad self-gates on config trigger and swallows transient failures,\n // so this is a no-op unless configured for cli-invocation and never blocks.\n if (!isAutoLoadExcludedCommand(actionCommand.name())) {\n await runEnvAutoLoad({ expectedTrigger: 'cli-invocation' })\n }\n })\n\n // Session-shell side channel: after a leaf action RESOLVES (Commander runs postAction only on\n // success \u2014 never when the action throws or calls process.exit), write the report file the parent\n // reads. Its presence is what lets the parent tell a completed run from a cancel. A no-op outside a\n // session (no captured report path) and never on the MCP path (that never builds this program).\n program.hook('postAction', (_thisCommand, actionCommand) => {\n const path = commandPath(actionCommand)\n\n // A long-running command (`dev`) resolves its action SECONDS AFTER BOOT and then keeps running on\n // its open handles. Writing a report here would tell the parent \"this produced a verdict\" while the\n // server is merely starting \u2014 and report-presence is exactly how the parent tells a completed run\n // from a cancel. It would stamp a Ctrl-C out of dev `\u26A0 completed with findings`, and an Esc out of\n // the dev wizard `\u2713 ok` (claiming a server ran when none did). No report: the exit code decides.\n if (isLongRunningCommand(path)) {\n return\n }\n\n const snapshot = commandEcho.snapshot()\n const flags = snapshot?.formattedOptions ?? ''\n const suffix = flags ? ` ${flags}` : ''\n const line = `infra-kit ${path}${suffix}`\n\n writeSessionReport({ equivalent: equivalentLine(line, true) })\n })\n\n return program\n}\n", "import process from 'node:process'\nimport { $ } from 'zx'\n\nimport { seedCreatedMessage, seedUserProjectConfig } from 'src/lib/config-bootstrap'\nimport { describeOverrides, readOverrideSummary } from 'src/lib/config-overrides'\nimport { getInfraKitConfigPaths, resetInfraKitConfigCache } from 'src/lib/infra-kit-config'\nimport { logger } from 'src/lib/logger'\nimport { fileExists, tildify } from 'src/lib/path-display'\nimport type { ToolsExecutionResult } from 'src/types'\n\n/**\n * Print the file paths that participate in the config merge chain along with existence markers, so\n * the user can see at a glance which override layers are active. The user-project row additionally\n * reports its CONTENT \u2014 an auto-seeded empty file is indistinguishable from a real one by existence\n * alone, since every command now seeds layer 3.\n *\n * `layers[].exists` keeps its original meaning (the file exists) and still drives the markers;\n * `hasOverrides` / `overrideKeys` are top-level additions describing the user-project layer only.\n *\n * @example\n * // CLI: `infra-kit config path`\n * // INFO: Project name: api\n * // INFO: Config merge chain (later overrides earlier):\n * // INFO: [\u2713] project (committed) ~/projects/api/infra-kit.json\n * // INFO: [ ] user global ~/.infra-kit/infra-kit.json\n * // INFO: [\u2713] user project ~/.infra-kit/projects/api/infra-kit.json (2 override(s): ide, dev)\n */\nexport const configPath = async (): Promise<ToolsExecutionResult> => {\n const paths = await getInfraKitConfigPaths()\n\n const rows: { label: string; path: string; exists: boolean }[] = await Promise.all(\n [\n { label: 'project (committed)', path: paths.main },\n { label: 'user global', path: paths.userGlobal },\n { label: 'user project', path: paths.userProject },\n ].map(async (row) => {\n return { ...row, exists: await fileExists(row.path) }\n }),\n )\n\n const userProjectExists = rows.at(-1)?.exists === true\n const summary = await readOverrideSummary(paths.userProject, userProjectExists)\n // Absent layer-3: no suffix \u2014 the `[ ]` marker already says it. (`doctor` owns the louder\n // \"the seed should have created this\" reading of the same state.)\n const note = userProjectExists ? describeOverrides(summary) : ''\n\n logger.info(`Project name: ${paths.projectName}\\n`)\n logger.info('Config merge chain (later overrides earlier):\\n')\n\n for (const row of rows) {\n const marker = row.exists ? ' [\u2713]' : ' [ ]'\n const suffix = row.path === paths.userProject && note !== '' ? ` ${note}` : ''\n\n logger.info(`${marker} ${row.label.padEnd(22)} ${tildify(row.path)}${suffix}`)\n }\n\n const structuredContent = {\n projectName: paths.projectName,\n layers: rows.map((r) => {\n return { label: r.label, path: r.path, exists: r.exists }\n }),\n hasOverrides: summary.hasOverrides,\n overrideKeys: summary.overrideKeys,\n }\n\n return {\n content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }],\n structuredContent,\n }\n}\n\n/**\n * Open the user-scope per-project override file in $EDITOR, creating it (and refreshing its\n * annotated `.example.jsonc` sibling) on first use. Resets the config cache after the editor exits\n * so subsequent reads pick up the user's edits without a restart.\n *\n * This double-seeds in practice: the program's preAction hook already ran the gated\n * `ensureUserProjectConfig()`, and this runs the ungated primitive again. `seedUserProjectConfig` is\n * idempotent (zero writes in steady state), so that is harmless \u2014 and the ungated call must STAY: it\n * is what makes `config edit` work in a context where the gate declined (no committed project\n * config, `INFRA_KIT_NO_SEED`, or a seed that failed). Asking to edit the file is consent to create\n * it.\n *\n * @example\n * // CLI: `infra-kit config edit`\n * // first run \u2014 creates ~/.infra-kit/projects/api/infra-kit.json ({}) + a sibling\n * // infra-kit.example.jsonc reference, then $EDITOR opens the .json\n * // subsequent runs \u2014 opens the existing file as-is (a stale example is refreshed silently)\n */\nexport const configEdit = async (): Promise<ToolsExecutionResult> => {\n const paths = await getInfraKitConfigPaths()\n const editor = process.env.EDITOR || process.env.VISUAL || 'vi'\n\n const seed = await seedUserProjectConfig(paths)\n\n if (seed.createdConfig) {\n logger.info(seedCreatedMessage(seed))\n }\n\n logger.info(`Opening ${tildify(paths.userProject)} in ${editor}`)\n\n await $({ stdio: 'inherit' })`${editor} ${paths.userProject}`\n\n resetInfraKitConfigCache()\n\n const structuredContent = { path: paths.userProject, editor }\n\n return {\n content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }],\n structuredContent,\n }\n}\n", "/**\n * Auth-class failure policy for env auto-load: classification + message, both pure\n * (no I/O) so they are unit-testable without Doppler or a shell. The marker's disk\n * I/O lives next to the other session-cache helpers in `env-autoload.ts`.\n *\n * WHY this is a separate concern from the transient failure marker: under the\n * token-only Doppler model a user with no (or a revoked/mis-scoped) service token\n * silently stops getting env vars in new shells. The shell-startup spawn is\n * backgrounded with stderr discarded, so that failure has NO channel of its own \u2014\n * the sticky marker written here is what carries it to the next interactive\n * command. A 30s backoff marker would expire long before the user runs one.\n */\n/**\n * Decide whether a caught failure is an AUTH-class failure \u2014 durable and the user's to\n * fix (no token minted, a bad/revoked/mis-scoped token, an unreadable token store) \u2014\n * rather than a transient one (network, timeout, Doppler down).\n *\n * Takes the ERROR, not its text. env-autoload catches what `env-load` THREW, and by\n * then `translateDopplerDownloadError` has already replaced Doppler's raw stderr with\n * a human-facing message \u2014 so the raw markers this used to grep for were never there.\n * That is exactly how a revoked token shipped silent with green tests on both sides.\n * The classification now rides ON the error (`EnvAuthError`).\n *\n * Injected as `isAuthFailure` into `runEnvAutoLoad` so the auto-load policy stays\n * testable without a Doppler in the loop; the DEFAULT is `isEnvAuthFailure`, the type\n * guard from `lib/errors/env-auth-error`.\n *\n * That default is deliberately WIDER than `isDopplerAuthError`. Narrowing it back to\n * \"Doppler refused us\" re-opens the bug in its second form: a MISSING token (the\n * migration case \u2014 the user upgraded and never minted one) never reaches Doppler at\n * all, and a CORRUPT store never reaches the resolver, so neither would be auth-class,\n * neither would write the sticky marker, and both would go silent forever.\n */\nexport type AuthFailureClassifier = (error: unknown) => boolean\n\n/** The sticky auth-failure marker's on-disk shape (`autoload-auth-fail.json`). */\nexport interface AuthFailureMarker {\n /** The env / Doppler config name the failed auto-load targeted. */\n config: string\n /** The raw failure text. Debug-only \u2014 NEVER rendered into the user-facing warning. */\n reason: string\n /** Unix ms the failure was recorded. Informational: the marker never expires. */\n at: number\n}\n\n/**\n * Narrow arbitrary parsed JSON to an {@link AuthFailureMarker}. A hand-corrupted or\n * truncated marker must degrade to \"no marker\", never to a thrown error on the hot\n * path of every command.\n *\n * @example\n * parseAuthFailureMarker({ config: 'dev', reason: 'Invalid Auth token', at: 1 }) // => the marker\n * parseAuthFailureMarker({ config: 'dev' }) // => null\n */\nexport const parseAuthFailureMarker = (value: unknown): AuthFailureMarker | null => {\n if (typeof value !== 'object' || value === null) return null\n\n const { config, reason, at } = value as Partial<AuthFailureMarker>\n\n if (typeof config !== 'string' || config.length === 0) return null\n\n if (typeof reason !== 'string') return null\n\n if (typeof at !== 'number' || !Number.isFinite(at)) return null\n\n return { config, reason, at }\n}\n\n/**\n * The one-line, actionable warning for a sticky auth failure. Names the env, states\n * that the token is missing/invalid/unreadable, gives the exact fix command, and points\n * at the Doppler dashboard page a replacement token is minted on.\n *\n * \"MISSING, INVALID, OR UNREADABLE\" spans the whole auth class on purpose. It covers the\n * three durable failures that all land here and all share this one entry point:\n * - missing \u2014 no token minted for this env (the MIGRATION case: upgraded, never ran env-token-set);\n * - invalid \u2014 Doppler refused it (revoked, garbage, or scoped to another config);\n * - unreadable \u2014 the token STORE is corrupt JSON, or belongs to a same-named foreign repo.\n * The last of these is neither invalid nor missing, and calling it either would misdescribe the\n * user's machine. `env-token-set` is still the right first move for all three \u2014 it is where a\n * broken store surfaces its own, more specific \"fix or delete the file\" refusal.\n *\n * Deliberately does NOT interpolate the marker's `reason`: the warning is the one\n * thing guaranteed to reach a human, and no token value may ever be printed. The\n * reason goes to `logger.debug` instead.\n *\n * The fix command is written in a BACKTICK code span, which is the repo convention\n * for naming a command to a human AND the anchor `program.test.ts` scans to prove\n * every command we print is a command we accept. Written without backticks it would\n * be invisible to that guard and free to rot.\n *\n * (Which is also why this @example names the command as `infra-kit env-token-set <env>`\n * rather than with a literal arg: to that scan an `<env>` placeholder ends the command,\n * but a literal `dev` is indistinguishable from a subcommand and reads as a dead one.)\n *\n * @example\n * buildAuthFailureWarning('dev')\n * // => 'infra-kit: Doppler token for env \"dev\" is missing, invalid, or unreadable \u2014 env\n * // auto-load is not running. Fix: run `infra-kit env-token-set <env>` (mint one at\n * // https://dashboard.doppler.com under this config's Access tab)', with <env> = dev\n */\nexport const buildAuthFailureWarning = (config: string): string => {\n return (\n `infra-kit: Doppler token for env \"${config}\" is missing, invalid, or unreadable \u2014 env auto-load is not running. ` +\n `Fix: run \\`infra-kit env-token-set ${config}\\` (mint one at https://dashboard.doppler.com under this config's Access tab).`\n )\n}\n", "import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\n\nimport { writeEnvLoadFile } from 'src/commands/env-load'\nimport { envTokenExists } from 'src/integrations/doppler/token-resolver'\nimport {\n ENV_CLEAR_FILE,\n ENV_LOAD_FILE,\n INFRA_KIT_ENV_AUTOLOADED_VAR,\n INFRA_KIT_ENV_CLEARED_VAR,\n INFRA_KIT_ENV_CONFIG_VAR,\n INFRA_KIT_ENV_PROJECT_VAR,\n INFRA_KIT_SESSION_VAR,\n atomicWriteFileSync,\n getSessionCacheDir,\n} from 'src/lib/constants'\nimport { isEnvAuthFailure } from 'src/lib/errors/env-auth-error'\nimport type { EnvAutoLoadConfig } from 'src/lib/infra-kit-config'\nimport { getInfraKitConfig } from 'src/lib/infra-kit-config'\nimport { logger } from 'src/lib/logger'\n\nimport type { AuthFailureClassifier, AuthFailureMarker } from './auth-failure'\nimport { buildAuthFailureWarning, parseAuthFailureMarker } from './auth-failure'\n\n/** Which moment a concrete callsite represents. Matches the config `trigger`. */\nexport type AutoLoadTrigger = EnvAutoLoadConfig['trigger']\n\n/** Per-session flag de-duping the transient-FAILURE warning (Doppler down/unauth). */\nconst WARN_FAIL_SENTINEL_FILE = 'autoload-warn-fail.flag'\n/** Per-session flag de-duping the AUTH-FAILURE warning (bad/missing service token). */\nconst WARN_AUTH_FAIL_SENTINEL_FILE = 'autoload-warn-auth-fail.flag'\n\n/** Per-session marker recording the last auto-load failure (mtime = when). */\nconst FAIL_SENTINEL_FILE = 'autoload-fail.flag'\n\n/**\n * Per-session STICKY marker recording the last AUTH-class auto-load failure. Unlike\n * {@link FAIL_SENTINEL_FILE} it carries a payload and has NO expiry: a token failure\n * does not heal on a 30s timer, and the only human who can fix it may not run an\n * interactive command for hours.\n */\nconst AUTH_FAIL_MARKER_FILE = 'autoload-auth-fail.json'\n\n/**\n * After a failed auto-load, suppress retries for this long so a down/unauthenticated\n * Doppler isn't re-probed on every cli-invocation. A new shell starts a fresh\n * session cache dir, so this only throttles within one session.\n */\nconst FAIL_BACKOFF_MS = 30_000\n\n/** Resolved auto-load inputs: the chosen trigger + the env config and Doppler project to load. */\nexport interface ResolvedEnvAutoLoad {\n trigger: AutoLoadTrigger\n config: string\n project: string\n}\n\n/**\n * Read the resolved + validated env auto-load inputs, or `null` when auto-load\n * should not run. Returns `null` (never throws) when:\n * - we're not inside an infra-kit project (getInfraKitConfig throws), or config is unreadable;\n * - `envAutoLoad` is absent (feature off);\n * - no service token resolves for `envAutoLoad.config` \u2014 warns once per session, then disables.\n *\n * Validation lives here (not in the schema) so a typo only disables this optional\n * feature instead of throwing inside the merged-config parse and breaking every command.\n * Resolves the Doppler project from the SAME config read (no second getInfraKitConfig),\n * so the skip path stays cheap.\n *\n * `canWarn` gates the WARNING only, never the STICKY MARKER: the shell-startup callsite\n * runs backgrounded with stderr discarded, so warning there is invisible \u2014 but a missing\n * token is one of the three durable auth-class failures {@link buildAuthFailureWarning}\n * documents (alongside a revoked/mis-scoped token and a corrupt store), so it is recorded\n * unconditionally and replayed by {@link surfaceStickyAuthFailure} on the next interactive\n * command \u2014 the channel that actually reaches the user. Skipping the record here would\n * strand the migration case (upgraded, never minted a token) with no channel at all.\n */\nexport const resolveEnvAutoLoad = async (canWarn = true): Promise<ResolvedEnvAutoLoad | null> => {\n let config\n\n try {\n config = await getInfraKitConfig()\n } catch {\n return null\n }\n\n const autoLoad = config.envAutoLoad\n\n if (!autoLoad) return null\n\n const resolved = {\n trigger: autoLoad.trigger,\n config: autoLoad.config,\n project: config.envManagement.config.name,\n }\n\n // The authority for \"is this env usable?\" is the token store, not a declared list \u2014 a name we hold no\n // token for cannot be loaded no matter who declared it. Checking it HERE (a local file read) keeps a\n // typo'd `envAutoLoad.config` from spawning a doomed Doppler download at every single shell open.\n try {\n if (await envTokenExists(autoLoad.config)) return resolved\n } catch {\n // The store itself is broken \u2014 a different failure entirely, and NOT one to disable quietly on.\n // Proceed so the loader hits `resolveEnvToken`'s EnvAuthError and the sticky auth-failure marker\n // surfaces it on the next channel that can actually be seen.\n return resolved\n }\n\n // No token minted for this env \u2014 the MIGRATION case (upgraded, never ran\n // `env-token-set`). Record it via the SAME sticky-marker mechanism a thrown auth\n // failure uses, unconditionally, so the backgrounded shell-startup spawn's silent\n // disable is not lost \u2014 the next interactive command's `surfaceStickyAuthFailure()`\n // replay is what actually reaches the user.\n recordAuthFailure({\n config: autoLoad.config,\n reason: `No Doppler service token for env \"${autoLoad.config}\"`,\n at: Date.now(),\n })\n\n if (canWarn) {\n warnOnce(buildAuthFailureWarning(autoLoad.config), WARN_AUTH_FAIL_SENTINEL_FILE)\n }\n\n return null\n}\n\n/** The env-var snapshot the freshness/suppression guards read. */\nexport interface AutoLoadEnvSnapshot {\n session?: string\n cleared?: string\n currentConfig?: string\n currentProject?: string\n autoLoadedMarker?: string\n}\n\nexport interface AutoLoadDecisionInput {\n /** The configured trigger. */\n trigger: AutoLoadTrigger\n /** Which trigger this callsite represents. */\n expectedTrigger: AutoLoadTrigger\n targetConfig: string\n targetProject: string\n env: AutoLoadEnvSnapshot\n /**\n * Bypass ONLY the \"already auto-loaded, same config+project\" no-op skip, forcing\n * a fresh Doppler fetch. The shell-startup refresh sets this: after a WARM source\n * the shell has already exported INFRA_KIT_ENV_AUTOLOADED + the same config, which\n * the child process inherits \u2014 without `force` the refresh would self-skip and the\n * warm (possibly rotated) secrets would never be replaced this session. Does NOT\n * relax the clear/manual-load guards.\n */\n force?: boolean\n}\n\nexport type AutoLoadDecision = 'load' | 'skip'\n\n/**\n * Pure decision: should this callsite (auto-)load env right now? Encodes the full\n * guard matrix so it is exhaustively unit-testable without Doppler or a shell:\n * - the configured trigger must match this callsite;\n * - a session must exist (the cache dir is session-scoped);\n * - an explicit clear suppresses auto-load (M2);\n * - a MANUAL load (config set, no auto marker) is never clobbered (C1);\n * - an auto-load already fresh for the same config AND project is a no-op\n * (project-aware so two same-named configs across different-repo worktrees\n * sharing one session don't leak each other's secrets).\n */\nexport const decideAutoLoad = (input: AutoLoadDecisionInput): AutoLoadDecision => {\n const { trigger, expectedTrigger, targetConfig, targetProject, env, force } = input\n\n if (trigger !== expectedTrigger) return 'skip'\n\n if (!env.session) return 'skip'\n\n if (env.cleared) return 'skip'\n\n // Manual load present (a config is loaded but it wasn't auto-loaded) \u2014 leave it.\n if (env.currentConfig && !env.autoLoadedMarker) return 'skip'\n\n // Our own auto-load already matches the target config+project \u2014 normally a no-op,\n // but `force` (the shell-startup warm refresh) must re-fetch: the shell just warm\n // -sourced these same markers, so skipping here would strand stale secrets.\n if (!force && env.autoLoadedMarker && env.currentConfig === targetConfig && env.currentProject === targetProject) {\n return 'skip'\n }\n\n return 'load'\n}\n\nexport interface RunEnvAutoLoadArgs {\n expectedTrigger: AutoLoadTrigger\n /**\n * Canonical (realpath'd) project dir, forwarded to `writeEnvLoadFile` to enable\n * the project-scoped WARM cache. Only the shell-startup spawn passes it (via\n * `--project-dir`); the cli-invocation trigger omits it, so warm is a\n * shell-startup-only optimization.\n */\n projectDir?: string\n /**\n * Force a fresh fetch past the \"already auto-loaded, same config\" no-op skip. The\n * shell-startup refresh sets this so a preceding WARM source (which exports the\n * same markers the child inherits) is always replaced by fresh secrets. See\n * {@link decideAutoLoad}. The cli-invocation trigger leaves it false.\n */\n force?: boolean\n /**\n * Auth-class predicate over the CAUGHT ERROR (not its text), injected so the\n * auto-load policy is testable without a Doppler in the loop. Defaults to\n * `isEnvAuthFailure` \u2014 the type guard for EVERY durable, user-fixable env-auth\n * failure, not just the ones Doppler itself rejected.\n *\n * It was `isDopplerAuthError` and that was too NARROW: a missing token never\n * reaches Doppler, and a corrupt token store never reaches the resolver, so both\n * fell through to \"transient\", expired with the 30s backoff, and went silent \u2014\n * the missing-token one being exactly the migration case this whole feature exists\n * to survive. See `./auth-failure.ts` for why classifying by message text was the\n * first version of this same bug.\n */\n isAuthFailure?: AuthFailureClassifier\n}\n\n/**\n * Resolve config, evaluate the guards, and (if it should) produce env-load.sh with\n * the auto-load marker. Returns the written file path, or `null` when auto-load was\n * skipped or failed. NEVER throws. Transient failures (Doppler offline / network)\n * record a 30s backoff marker so they aren't re-probed on every command, and are\n * surfaced once per session on the interactive (cli-invocation) channel. AUTH-class\n * failures (invalid / missing / mis-scoped service token) additionally record a\n * STICKY marker that is replayed by {@link surfaceStickyAuthFailure} below. No\n * producer-side lock \u2014 a rare cold-shell double-fetch is tolerated (the second write\n * is atomic and idempotent).\n */\nexport const runEnvAutoLoad = async ({\n expectedTrigger,\n projectDir,\n force,\n isAuthFailure = isEnvAuthFailure,\n}: RunEnvAutoLoadArgs): Promise<string | null> => {\n // Only the cli-invocation / interactive callsite reaches a TTY; the shell-startup\n // spawn discards stderr, so warning there is invisible and would poison the dedup.\n const canWarn = expectedTrigger === 'cli-invocation'\n\n // BEFORE any gating. A shell-startup user's token failure is recorded by the\n // backgrounded spawn (stderr \u2192 /dev/null) and would otherwise NEVER surface:\n // decideAutoLoad skips this callsite on the trigger mismatch, and the callsite\n // that DID fail cannot warn. This replay is the only channel that reaches them.\n if (canWarn) {\n surfaceStickyAuthFailure()\n }\n\n let resolved: ResolvedEnvAutoLoad | null = null\n\n try {\n resolved = await resolveEnvAutoLoad(canWarn)\n\n if (!resolved) return null\n\n const decision = decideAutoLoad({\n trigger: resolved.trigger,\n expectedTrigger,\n targetConfig: resolved.config,\n targetProject: resolved.project,\n env: readAutoLoadEnvSnapshot(),\n force,\n })\n\n if (decision === 'skip') return null\n\n // Disk-level clear signal: a clear that hasn't yet been sourced into this\n // process's env still suppresses auto-load (clear file newer than load file).\n if (isClearedOnDisk()) return null\n\n // Back off after a recent failure so a down/unauthenticated Doppler isn't\n // re-probed on every command in the same session.\n if (recentlyFailed()) return null\n\n const preWriteMtime = readLoadFileMtime()\n const result = await writeEnvLoadFile({\n config: resolved.config,\n autoLoaded: true,\n projectDir,\n // Re-check after the slow Doppler download: abort if a clear or a manual load\n // landed meanwhile, so a backgrounded auto-load never clobbers a deliberate action.\n beforeWrite: () => {\n return !isClearedOnDisk() && !manualLoadLandedSince(preWriteMtime)\n },\n })\n\n if (!result) return null\n\n clearFailure()\n clearAuthFailure()\n\n return result.filePath\n } catch (error) {\n const reason = (error as Error).message\n\n // Classify the ERROR, not its message: by the time it lands here `env-load` has\n // already translated Doppler's stderr into human prose, so the raw markers are gone.\n const authFailed = isAuthFailure(error)\n\n recordFailure()\n\n // The token failure outlives the 30s backoff window AND this process: persist it\n // so the next interactive command can tell the user why their shell went quiet.\n if (authFailed && resolved) {\n recordAuthFailure({ config: resolved.config, reason, at: Date.now() })\n }\n\n // Surface the failure once per session on the interactive channel; stay silent\n // (debug only) on the backgrounded shell-startup path. The auth-class message is\n // reason-free by design \u2014 no token value may ever be printed.\n if (canWarn && authFailed && resolved) {\n warnOnce(buildAuthFailureWarning(resolved.config), WARN_AUTH_FAIL_SENTINEL_FILE)\n logger.debug(`env auto-load auth failure: ${reason}`)\n } else if (canWarn) {\n warnOnce(`infra-kit: env auto-load failed \u2014 ${reason} (will retry later)`, WARN_FAIL_SENTINEL_FILE)\n } else {\n logger.debug(`env auto-load skipped: ${reason}`)\n }\n\n return null\n }\n}\n\n/**\n * Replay a previously-recorded STICKY auth failure onto the interactive channel,\n * once per session. Never throws: a missing, unreadable, or corrupt marker is simply\n * \"no marker\" \u2014 this runs on the hot path of EVERY command.\n *\n * Exported because WARNING IS NOT LOADING: `program.ts` excludes `version`, `doctor`,\n * `dev` and the env-* family from the cli-invocation auto-LOAD (priming Doppler env\n * there would be surprising), but those are precisely the commands a user whose shell\n * went quiet reaches for. Gating the replay behind that exclusion would leave a\n * `dev`-only user warned by nothing, forever. So program.ts calls this on EVERY\n * command; `runEnvAutoLoad` still calls it too (the `warnOnce` sentinel de-dups).\n *\n * @example\n * surfaceStickyAuthFailure() // warns once per session iff autoload-auth-fail.json exists\n */\nexport const surfaceStickyAuthFailure = (): void => {\n const marker = readAuthFailure()\n\n if (!marker) return\n\n warnOnce(buildAuthFailureWarning(marker.config), WARN_AUTH_FAIL_SENTINEL_FILE)\n logger.debug(`env auto-load auth failure (recorded ${new Date(marker.at).toISOString()}): ${marker.reason}`)\n}\n\n/** Read the sticky auth-failure marker, or null when absent/unreadable/corrupt. */\nconst readAuthFailure = (): AuthFailureMarker | null => {\n try {\n const raw = fs.readFileSync(path.join(getSessionCacheDir(), AUTH_FAIL_MARKER_FILE), 'utf-8')\n\n return parseAuthFailureMarker(JSON.parse(raw))\n } catch {\n return null\n }\n}\n\n/** Record a sticky auth failure. No expiry \u2014 only a successful load clears it. */\nconst recordAuthFailure = (marker: AuthFailureMarker): void => {\n try {\n const dir = getSessionCacheDir()\n\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 })\n atomicWriteFileSync(path.join(dir, AUTH_FAIL_MARKER_FILE), JSON.stringify(marker), 0o600)\n } catch {\n // No session cache dir \u2014 nothing to record against.\n }\n}\n\n/** Clear the sticky auth-failure marker after a successful load. */\nconst clearAuthFailure = (): void => {\n try {\n fs.rmSync(path.join(getSessionCacheDir(), AUTH_FAIL_MARKER_FILE), { force: true })\n } catch {\n // No session cache dir \u2014 nothing to clear.\n }\n}\n\n/** Snapshot the env vars the guards depend on. */\nconst readAutoLoadEnvSnapshot = (): AutoLoadEnvSnapshot => {\n return {\n session: process.env[INFRA_KIT_SESSION_VAR],\n cleared: process.env[INFRA_KIT_ENV_CLEARED_VAR],\n currentConfig: process.env[INFRA_KIT_ENV_CONFIG_VAR],\n currentProject: process.env[INFRA_KIT_ENV_PROJECT_VAR],\n autoLoadedMarker: process.env[INFRA_KIT_ENV_AUTOLOADED_VAR],\n }\n}\n\n/** mtime of the on-disk env-load.sh, or null if absent/unreadable. */\nconst readLoadFileMtime = (): number | null => {\n try {\n return fs.statSync(path.join(getSessionCacheDir(), ENV_LOAD_FILE)).mtimeMs\n } catch {\n return null\n }\n}\n\n/**\n * True when a MANUAL env-load landed on disk after `sinceMtime` (the file now\n * exists, is newer, and carries the manual-load `unset INFRA_KIT_ENV_AUTOLOADED`\n * line). Used to abort an in-flight auto-load so it never clobbers a deliberate load.\n */\nconst manualLoadLandedSince = (sinceMtime: number | null): boolean => {\n try {\n const p = path.join(getSessionCacheDir(), ENV_LOAD_FILE)\n\n if (!fs.existsSync(p)) return false\n\n const mtime = fs.statSync(p).mtimeMs\n\n if (sinceMtime !== null && mtime <= sinceMtime) return false\n\n // Anchor to a whole line so the marker can't match inside a single-quoted\n // secret value that happens to contain this literal text.\n const manualMarker = new RegExp(`^unset ${INFRA_KIT_ENV_AUTOLOADED_VAR}$`, 'm')\n\n return manualMarker.test(fs.readFileSync(p, 'utf-8'))\n } catch {\n return false\n }\n}\n\n/**\n * True when a clear is pending on disk: an env-clear.sh exists and is at least as\n * new as env-load.sh (or no load file remains). Belt-and-suspenders next to the\n * INFRA_KIT_ENV_CLEARED env guard for the window before the shell sources the clear.\n */\nconst isClearedOnDisk = (): boolean => {\n try {\n const dir = getSessionCacheDir()\n const clearPath = path.join(dir, ENV_CLEAR_FILE)\n\n if (!fs.existsSync(clearPath)) return false\n\n const loadPath = path.join(dir, ENV_LOAD_FILE)\n\n if (!fs.existsSync(loadPath)) return true\n\n return fs.statSync(clearPath).mtimeMs >= fs.statSync(loadPath).mtimeMs\n } catch {\n return false\n }\n}\n\n/** True when an auto-load failed within the backoff window (suppress retries). */\nconst recentlyFailed = (): boolean => {\n try {\n const flagPath = path.join(getSessionCacheDir(), FAIL_SENTINEL_FILE)\n\n if (!fs.existsSync(flagPath)) return false\n\n return Date.now() - fs.statSync(flagPath).mtimeMs < FAIL_BACKOFF_MS\n } catch {\n return false\n }\n}\n\n/** Record an auto-load failure (refreshes the backoff window). */\nconst recordFailure = (): void => {\n try {\n const dir = getSessionCacheDir()\n\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 })\n fs.writeFileSync(path.join(dir, FAIL_SENTINEL_FILE), '', { mode: 0o600 })\n } catch {\n // No session cache dir \u2014 nothing to back off against.\n }\n}\n\n/** Clear the failure marker after a successful load. */\nconst clearFailure = (): void => {\n try {\n fs.rmSync(path.join(getSessionCacheDir(), FAIL_SENTINEL_FILE), { force: true })\n } catch {\n // No session cache dir \u2014 nothing to clear.\n }\n}\n\n/**\n * Emit a warning at most once per shell session. Keyed on a flag file in the\n * session cache dir so a misconfigured `envAutoLoad.config` (or a repeated failure)\n * does not spam a warning on every cli-invocation. Falls back to a plain warn when\n * no session cache dir is available.\n */\nconst warnOnce = (message: string, sentinelFile: string): void => {\n try {\n const dir = getSessionCacheDir()\n const flagPath = path.join(dir, sentinelFile)\n\n if (fs.existsSync(flagPath)) return\n\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 })\n fs.writeFileSync(flagPath, '', { mode: 0o600 })\n } catch {\n // No session cache dir (e.g. INFRA_KIT_SESSION unset) \u2014 warn without de-dup.\n }\n\n logger.warn(message)\n}\n", "import { runEnvAutoLoad } from 'src/lib/env-autoload'\n\nexport interface EnvAutoloadArgs {\n /**\n * Canonical (realpath'd) project dir the shell computed (`${dir:A}`) and passed\n * via `--project-dir`. Forwarded to enable the project-scoped warm cache; absent\n * when invoked without the flag (then no warm copy is written).\n */\n projectDir?: string\n}\n\n/**\n * Internal command invoked (backgrounded) by the `infra-kit init` shell-startup\n * integration. Runs the 'shell-startup' trigger, writing env-load.sh when\n * envAutoLoad is configured for it + eligible; the shell precmd hook sources it on\n * a subsequent prompt. Intentionally writes NOTHING to stdout and never throws \u2014\n * auto-load must never disrupt shell startup.\n */\nexport const envAutoload = async ({ projectDir }: EnvAutoloadArgs = {}): Promise<void> => {\n // `force`: the shell may have just WARM-sourced the same config (exporting the\n // auto-load marker this process inherits); without it the refresh would self-skip\n // and stale warm secrets would never be replaced this session.\n await runEnvAutoLoad({ expectedTrigger: 'shell-startup', projectDir, force: true })\n}\n", "import { getDopplerProject } from 'src/integrations/doppler'\nimport { getTokenStorePath, readTokenStore, removeToken } from 'src/lib/env-tokens'\nimport { logger } from 'src/lib/logger'\nimport { tildify } from 'src/lib/path-display'\nimport { purgeRepoWarmCaches } from 'src/lib/warm-cache'\nimport { textContent } from 'src/types'\n\nexport interface EnvTokenRemoveArgs {\n /** Environment whose token to drop from the local store. */\n env: string\n}\n\n/** Where a token is actually revoked. Deleting it here only makes THIS machine stop using it. */\nconst DOPPLER_DASHBOARD_URL = 'https://dashboard.doppler.com/workplace/projects'\n\n/**\n * The thing a user is most likely to get wrong about this command, so it is the thing it says\n * loudest: a local delete is not a revocation. The token stays live in Doppler \u2014 on someone's\n * clipboard, in a CI secret, in shell history \u2014 until it is revoked in the dashboard.\n */\nconst buildRevokeNotice = (project: string, env: string): string[] => {\n return [\n `Removing it locally does NOT revoke it \u2014 the token still works anywhere else it is stored.`,\n `Revoke it in Doppler (project \"${project}\", config \"${env}\"): ${DOPPLER_DASHBOARD_URL}/${project}`,\n ]\n}\n\n/**\n * Drop one environment's service token from the local store, purge the warm caches it may have\n * populated, and tell the user where to actually revoke it.\n *\n * Not an MCP tool \u2014 see the comment on the catalog entry. The MCP boundary auto-confirms every call,\n * so an agent must never be one call away from destroying a credential.\n */\nexport const envTokenRemove = async ({ env }: EnvTokenRemoveArgs) => {\n const store = await readTokenStore()\n const existed = Boolean(store?.envs[env])\n\n await removeToken(env)\n\n // A removed token must not keep working for another 2h out of a sibling worktree's warm cache \u2014 the\n // cache is keyed per WORKTREE, the store per REPO, so this enumerates every worktree.\n const purged = await purgeRepoWarmCaches()\n\n const project = await getDopplerProject()\n const storePath = await getTokenStorePath()\n\n if (existed) {\n logger.info(`Removed the \"${env}\" service token from ${tildify(storePath)}.`)\n } else {\n logger.info(`No \"${env}\" service token was stored in ${tildify(storePath)} \u2014 nothing to remove.`)\n }\n\n if (purged.length > 0) {\n logger.info(`Purged ${purged.length} warm cache(s) across this repo's worktrees.`)\n }\n\n for (const line of buildRevokeNotice(project, env)) {\n logger.warn(line)\n }\n\n const structuredContent = {\n env,\n removed: existed,\n storePath,\n warmCachesPurged: purged.length,\n revokeUrl: `${DOPPLER_DASHBOARD_URL}/${project}`,\n }\n\n return {\n content: textContent(JSON.stringify(structuredContent, null, 2)),\n structuredContent,\n }\n}\n", "import password from '@inquirer/password'\nimport process from 'node:process'\nimport { $ } from 'zx'\n\nimport { assertTokenScope, buildDopplerChildEnv, parseDopplerSecretsJson } from 'src/commands/env-load'\nimport { classifyDopplerAuthFailure, classifyDopplerFailure, getDopplerProject } from 'src/integrations/doppler'\nimport { commandEcho } from 'src/lib/command-echo'\nimport { getTokenStorePath, setToken } from 'src/lib/env-tokens'\nimport { extractStderr } from 'src/lib/errors/operation-error'\nimport { logger } from 'src/lib/logger'\nimport { tildify } from 'src/lib/path-display'\nimport { withEscape } from 'src/lib/prompts/escapable-context'\nimport { redactToken } from 'src/lib/redact'\nimport { purgeRepoWarmCaches } from 'src/lib/warm-cache'\nimport { textContent } from 'src/types'\n\nexport interface EnvTokenSetArgs {\n /** Environment = Doppler config the token must be scoped to. */\n env: string\n /** Read the token from stdin (`\u2026 | infra-kit env-token-set dev --stdin`) instead of prompting. */\n stdin?: boolean\n /** Read the token from the named environment variable (never from argv). */\n fromEnv?: string\n /** Write even when the token's scope could NOT be verified. Never overrides a real mismatch. */\n force?: boolean\n}\n\n/** Where the token came in from. Reported back so a scripted run can prove which channel it used. */\ntype TokenInputSource = 'prompt' | 'stdin' | 'env'\n\n/**\n * There is deliberately NO `--token <value>` flag, and there must never be one.\n *\n * argv is world-visible: `ps -ef` shows it to every user on the box, it lands in the shell history\n * file, and `commandEcho` prints option VALUES back to the terminal as the replayable \"\uD83D\uDCDF Equivalent\n * command\". A flag would put a live credential into all three. The three channels below all keep the\n * token out of argv: a masked prompt, stdin, or an env var named (not valued) on the command line.\n */\nconst readCandidateToken = async ({\n stdin,\n fromEnv,\n}: Pick<EnvTokenSetArgs, 'stdin' | 'fromEnv'>): Promise<{\n token: string\n source: TokenInputSource\n}> => {\n if (stdin) return { token: await readStdin(), source: 'stdin' }\n\n if (fromEnv) {\n const token = process.env[fromEnv]\n\n if (!token) throw new Error(`${fromEnv} is not set (or is empty) \u2014 nothing to store.`)\n\n return { token, source: 'env' }\n }\n\n commandEcho.setInteractive()\n\n // Esc abandons a half-typed credential (withEscape \u2192 AbortPromptError \u2192 exit 0, nothing written).\n // The typed value never reaches the output either way: `mask: true` renders one `*` per keystroke, so\n // the transcript holds asterisks, not the token. (The masked line itself SURVIVES the abort \u2014 this\n // context sets no `clearPromptOnDone` \u2014 but it carries no secret.)\n const token = await withEscape(\n (context) => {\n return password({ message: 'Paste the Doppler service token (input is hidden)', mask: true }, context)\n },\n // stderr, like env-load's picker: some env commands are invoked inside `$(\u2026)` by the shell\n // wrapper, which captures stdout. A prompt rendered to stdout would be swallowed there \u2014 the user\n // would face a blank, silent terminal and type a credential into the void.\n { output: process.stderr },\n )\n\n return { token: token.trim(), source: 'prompt' }\n}\n\n/** Drain stdin. `--stdin` is the CI / password-manager channel: `op read \u2026 | infra-kit env-token-set dev --stdin`. */\nconst readStdin = async (): Promise<string> => {\n const chunks: string[] = []\n\n process.stdin.setEncoding('utf8')\n\n for await (const chunk of process.stdin) {\n chunks.push(chunk as string)\n }\n\n return chunks.join('').trim()\n}\n\n/** The payload key carrying the token's scope (SPIKE-0 Q2 \u2014 the CONFIG, never DOPPLER_ENVIRONMENT). */\nconst DOPPLER_CONFIG_KEY = 'DOPPLER_CONFIG'\n\n/** Same bound as the env-load download: a hung probe must not hold an interactive prompt hostage. */\nconst PROBE_TIMEOUT_MS = 30_000\n\n/**\n * Exercise the candidate token against the real config, exactly as `env-load` will: same\n * `secrets download`, same child env (token by ENV, never argv \u2014 {@link buildDopplerChildEnv}).\n *\n * This is the check that catches the likeliest real-world mistake, a human pasting the `arthur` token\n * into `env-token-set dev`: Doppler itself refuses a cross-config download (SPIKE-0 Q1, exit 1\n * \"does not have access to requested config\"), so a mis-scoped token can never be written.\n */\nconst probeToken = async (token: string, project: string, env: string): Promise<Array<[string, string]>> => {\n const prevQuiet = $.quiet\n\n $.quiet = true\n\n let stdout: string\n\n try {\n const result = await $({\n env: buildDopplerChildEnv(token),\n })`doppler secrets download --no-file --format json --project ${project} --config ${env}`.timeout(PROBE_TIMEOUT_MS)\n\n stdout = result.stdout\n } catch (error: unknown) {\n throw translateProbeFailure(error, env)\n } finally {\n $.quiet = prevQuiet\n }\n\n return parseDopplerSecretsJson(stdout)\n}\n\n/**\n * Turn a rejected probe into a refusal a human can act on. An auth-class failure is the whole point\n * of the probe, so it gets a message naming WHICH of the two diagnoses it is; anything else (network,\n * timeout, a wrong project name) is rethrown untouched so the user is not told their token is bad\n * when Doppler was simply unreachable.\n */\nconst translateProbeFailure = (error: unknown, env: string): Error => {\n const stderr = extractStderr(error) ?? (error instanceof Error ? error.message : String(error))\n\n if (classifyDopplerFailure(stderr) !== 'auth') {\n return error instanceof Error ? error : new Error(String(error))\n }\n\n const detail =\n classifyDopplerAuthFailure(stderr) === 'mis-scoped'\n ? `it is scoped to a DIFFERENT config (pasting another environment's token here is the mistake this check exists to catch).`\n : 'it is invalid or has been revoked.'\n\n return new Error(\n [\n `Doppler refused this token for config \"${env}\" \u2014 ${detail}`,\n 'Nothing was written. Issue a service token scoped to that config and try again.',\n ].join('\\n'),\n )\n}\n\n/**\n * The UNVERIFIABLE case: the download succeeded but the payload carries no `DOPPLER_CONFIG`, so we\n * cannot confirm what the token is scoped to.\n *\n * We FAIL CLOSED here, and fail OPEN in `env-load`'s `assertTokenScope` \u2014 deliberately, and the\n * asymmetry is the design. A human is watching THIS command: refusing costs them one `--force` and\n * they learn something. On the silent shell-startup autoload path nobody is watching, and failing\n * closed there would blank every developer's environment at once the day Doppler changes what it\n * injects.\n */\nconst buildUnverifiableScopeMessage = (env: string): string => {\n return [\n `Could not verify this token's scope: the Doppler payload for \"${env}\" carries no ${DOPPLER_CONFIG_KEY}.`,\n 'Refusing to store a credential whose scope is unknown (a token for the wrong environment would load',\n 'the wrong secrets into every shell).',\n 'Re-run with --force if you are certain the token is scoped to this config.',\n ].join('\\n')\n}\n\n/**\n * Store a Doppler service token for one environment, AFTER proving against Doppler that it is scoped\n * to that environment's config. The write is the last thing that happens; every refusal above leaves\n * `tokens.json` untouched.\n *\n * Not an MCP tool \u2014 see the comment on the catalog entry. The MCP boundary auto-confirms every call,\n * and an agent must never be one tool call away from writing a credential.\n */\nexport const envTokenSet = async ({ env, stdin, fromEnv, force }: EnvTokenSetArgs) => {\n // No declared-list check. Doppler is the authority on whether `env` is a real config, and it is\n // consulted three lines down: `probeToken` downloads with this token and `assertTokenScope` refuses a\n // payload belonging to any other config. A typo cannot survive that. A declared list, meanwhile, could\n // only ever be a stale local copy \u2014 and it was: it refused `prod_observability`, a config that exists\n // in Doppler and holds a live token, purely because nobody had added the name to infra-kit.json.\n const project = await getDopplerProject()\n\n const { token, source } = await readCandidateToken({ stdin, fromEnv })\n\n if (!token) throw new Error('No token provided \u2014 nothing was written.')\n\n const pairs = await probeToken(token, project, env)\n\n // Doppler already refused a cross-config download above; this catches the payload disagreeing with\n // the request anyway (defense in depth \u2014 reused verbatim from the env-load path).\n assertTokenScope(pairs, env)\n\n const scopeVerified = pairs.some(([key]) => {\n return key === DOPPLER_CONFIG_KEY\n })\n\n if (!scopeVerified && !force) throw new Error(buildUnverifiableScopeMessage(env))\n\n await setToken(env, token)\n\n // The warm cache is per-WORKTREE while the token store is per-REPO, so a rotation here would leave\n // sibling worktrees serving up to 2h of secrets fetched with the OLD token at the next shell prompt.\n const purged = await purgeRepoWarmCaches()\n\n const storePath = await getTokenStorePath()\n\n // The token is rendered ONLY through redactToken, and never interpolated into a message string \u2014\n // pino's key-path `redact` cannot censor a string it did not structure (see lib/logger).\n logger.info(`Stored the \"${env}\" service token (${redactToken(token)}) in ${tildify(storePath)} (mode 0600).`)\n\n if (!scopeVerified) {\n logger.warn(`Scope was NOT verified (no ${DOPPLER_CONFIG_KEY} in the payload) \u2014 written because --force was given.`)\n }\n\n if (purged.length > 0) {\n logger.info(\n `Purged ${purged.length} warm cache(s) so the next shell cannot serve secrets fetched with an old token.`,\n )\n }\n\n const structuredContent = {\n env,\n source,\n redactedToken: redactToken(token),\n storePath,\n scopeVerified,\n warmCachesPurged: purged.length,\n }\n\n commandEcho.print()\n\n return {\n content: textContent(JSON.stringify(structuredContent, null, 2)),\n structuredContent,\n }\n}\n", "/**\n * Launch the infra-kit MCP server (stdio transport) as a CHILD process.\n *\n * It must never be imported in-process: src/entry/mcp.ts calls `startServer()` at top-level module load,\n * connecting the stdio transport as a side effect, and `emit()` in src/lib/json-output writes to\n * `process.stdout`. Any stdout write on the CLI dispatch path would corrupt the JSON-RPC framing.\n * Spawning gives the child pristine stdio by construction.\n *\n * `dist/mcp.js` is emitted as a sibling of `dist/cli.js` (esbuild `splitting: true`; both import the\n * shared `chunk-*.js`), so `new URL('./mcp.js', import.meta.url)` resolves correctly from the bundle.\n */\nimport { spawn } from 'node:child_process'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\n\nimport { logger } from 'src/lib/logger'\nimport { withoutPackageManagerEnv } from 'src/lib/pm-env'\n\nexport interface McpDeps {\n spawn?: typeof spawn\n exit?: (code: number) => void\n env?: NodeJS.ProcessEnv\n onError?: (message: string) => void\n}\n\nconst FORWARDED_SIGNALS = ['SIGINT', 'SIGTERM'] as const\n\n/** Resolve the server entry next to this bundle rather than importing it (see module doc). */\nexport const mcpServerPath = (): string => {\n return fileURLToPath(new URL('./mcp.js', import.meta.url))\n}\n\nexport const runMcp = (deps: McpDeps = {}): void => {\n const spawnFn = deps.spawn ?? spawn\n const exit =\n deps.exit ??\n ((code: number) => {\n return process.exit(code)\n })\n const env = deps.env ?? process.env\n const onError =\n deps.onError ??\n ((message: string) => {\n return logger.error(message)\n })\n\n const child = spawnFn(process.execPath, [mcpServerPath()], {\n stdio: 'inherit',\n env: withoutPackageManagerEnv(env),\n })\n\n // An unhandled 'error' event throws. Spawning `process.execPath` should never fail, but a launcher\n // that dies by uncaught exception instead of a named exit is the worst possible failure for a client\n // reading our stdio.\n child.on('error', (error) => {\n onError(`failed to launch the MCP server: ${error.message}`)\n exit(1)\n })\n\n // Forward the client's shutdown signals so the server can close its transport cleanly.\n FORWARDED_SIGNALS.forEach((signal) => {\n process.on(signal, () => {\n child.kill(signal)\n })\n })\n\n child.on('exit', (code, signal) => {\n exit(signal ? 1 : (code ?? 1))\n })\n}\n", "/**\n * Update this CLI in place, using the package manager that actually owns the install.\n *\n * `process.exit` is used directly (rather than `process.exitCode`) because this command is CLI-only and\n * is NEVER exposed over MCP \u2014 see the `self-update` entry in src/lib/command-catalog. Exiting from a\n * long-lived MCP server would kill it; there is no such server on this path.\n *\n * Every process interaction is injected via {@link SelfUpdateDeps} (same seam shape as\n * src/dev/proxy/portless-driver.ts) so tests assert the exact argv and env without shelling out.\n */\nimport { spawnSync } from 'node:child_process'\nimport { realpathSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\n\nimport { defaultLazyNpmRoot, detectInstallManager, safeRealpath } from 'src/lib/install-manager'\nimport { logger } from 'src/lib/logger'\nimport { packageManagerInstallEnv } from 'src/lib/pm-env'\n\nexport interface SelfUpdateDeps {\n spawnSync?: typeof spawnSync\n print?: (line: string) => void\n exit?: (code: number) => never\n env?: NodeJS.ProcessEnv\n selfRealPath?: string\n lazyNpmRoot?: () => string | undefined\n}\n\n/** The realpath of THIS module. A global bin is a symlink, but `import.meta.url` already resolves it. */\nconst defaultSelfRealPath = (): string => {\n return realpathSync(fileURLToPath(import.meta.url))\n}\n\n/**\n * Translate a spawn outcome into an exit. Kept separate from the spawn so each failure mode names itself:\n * a bare `exit(status ?? 1)` would turn a missing binary and a SIGKILL into the same silent `1`.\n */\nconst exitFromResult = (\n result: ReturnType<typeof spawnSync>,\n manager: string,\n print: (line: string) => void,\n exit: (code: number) => never,\n): never => {\n if (result.error) {\n print(`${manager} not found on PATH: ${result.error.message}`)\n\n return exit(1)\n }\n\n if (result.signal) {\n print(`update terminated by signal ${result.signal}`)\n\n return exit(1)\n }\n\n return exit(result.status ?? 1)\n}\n\n/**\n * An EACCES from `npm i -g` (root-owned global dir) surfaces the package manager's own error verbatim\n * through `stdio: 'inherit'`. We NEVER auto-retry with sudo \u2014 silently escalating privileges to write to\n * a global directory is not something a self-updater may do.\n */\nexport const runSelfUpdate = ({ dryRun }: { dryRun: boolean }, deps: SelfUpdateDeps = {}): void => {\n const spawn = deps.spawnSync ?? spawnSync\n const print =\n deps.print ??\n ((line: string) => {\n return logger.info(line)\n })\n const exit =\n deps.exit ??\n ((code: number) => {\n return process.exit(code)\n })\n const env = deps.env ?? process.env\n const selfRealPath = deps.selfRealPath ?? defaultSelfRealPath()\n const lazyNpmRoot = deps.lazyNpmRoot ?? defaultLazyNpmRoot\n\n const { manager, updateCommand, canSelfSpawn } = detectInstallManager({\n selfRealPath,\n env,\n realpath: safeRealpath,\n lazyNpmRoot,\n })\n const printable = updateCommand.join(' ')\n\n if (dryRun) {\n print(`Detected install manager: ${manager}`)\n print(`Would run: ${printable}`)\n\n return\n }\n\n if (!canSelfSpawn) {\n print(`Detected install manager: ${manager}`)\n print(`Run this yourself: ${printable}`)\n print(\n manager === 'homebrew'\n ? 'Not run for you: Homebrew manages this install; running a package manager would create a split-brain install.'\n : 'Not run for you: the install location is unrecognized, so the command above is a guess \u2014 a guessed global install is worse than a printed one.',\n )\n\n return\n }\n\n // `shell` on win32 so the `.cmd` shims npm/pnpm/yarn ship as their global bins resolve.\n //\n // `cwd` is pinned to the home directory for the SAME reason the background worker pins it, and it is a\n // security control rather than tidiness: npm resolves `registry=` from an `.npmrc` on disk relative to\n // the cwd. Inheriting the caller's cwd lets any repo you happen to be standing in redirect this\n // `install -g` to a registry of its choosing and run that package's lifecycle scripts. Being\n // user-initiated is NOT a defence \u2014 npm does not print the registry it resolved, so nothing about the\n // output would give it away. Stripping env vars cannot close this; the redirect lives in a file.\n //\n // `packageManagerInstallEnv` (not the blunt `withoutPackageManagerEnv`) keeps `npm_config_prefix`, so we\n // install into the same prefix `detectInstallManager` just matched on rather than the default one.\n const result = spawn(updateCommand[0]!, updateCommand.slice(1), {\n stdio: 'inherit',\n shell: process.platform === 'win32',\n cwd: homedir(),\n env: packageManagerInstallEnv(env),\n })\n\n exitFromResult(result, manager, print, exit)\n}\n", "import { VENDOR_CONFIG_FILE } from '@slip-stream-kit/config/internal'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { pathToFileURL } from 'node:url'\n\nimport { getProjectRoot } from 'src/lib/git-utils'\nimport { logger } from 'src/lib/logger'\nimport { fileExists, tildify } from 'src/lib/path-display'\nimport { expandTilde, getFactoryConfigPath, loadFactoryConfig } from 'src/lib/vendor/factory-config'\n\ninterface VendorConfigOptions {\n /** Scaffold `~/.infra-kit/vendor.json` instead of printing the current one. */\n init?: boolean\n /** Source repo root used for legacy `targets` auto-seeding. Defaults to the git toplevel. */\n cwd?: string\n}\n\n/** Placeholder workspace dir written by `--init`; the user edits it to their layout. */\nconst PLACEHOLDER_WORKSPACE_DIR = '~/projects'\n\n/**\n * Surface or scaffold the machine-local factory config\n * (`~/.infra-kit/vendor.json`). CLI-only \u2014 NOT an MCP tool; returns nothing\n * and signals problems via `process.exitCode`.\n *\n * Without `--init`: prints the factory file path + existence, the resolved\n * `workspaceDir` + existence, and per-target reachability (`[\u2713]`/`[ ]`). Exits\n * non-zero if the file is missing, the workspace dir is missing, or any target\n * is unreachable, so it is usable as a doctor check.\n *\n * With `--init`: scaffolds the file (skipping if it already exists), seeding\n * `targets` from a legacy source `vendor.config.ts` when one is readable.\n */\nexport const vendorConfig = async (options: VendorConfigOptions = {}): Promise<void> => {\n if (options.init) {\n await initFactoryConfig(options.cwd)\n\n return\n }\n\n await printFactoryConfig()\n}\n\n/** Render the factory config chain with `[\u2713]`/`[ ]` reachability markers. */\nconst printFactoryConfig = async (): Promise<void> => {\n const factoryPath = getFactoryConfigPath()\n const exists = await fileExists(factoryPath)\n\n logger.info(`Factory config: ${tildify(factoryPath)} ${exists ? '[\u2713]' : '[ ]'}`)\n\n if (!exists) {\n logger.info('\\nNot found \u2014 run `infra-kit vendor config --init` to scaffold it.')\n process.exitCode = 1\n\n return\n }\n\n const { workspaceDir, targets } = await loadFactoryConfig()\n const resolvedWorkspace = expandTilde(workspaceDir)\n const workspaceExists = await fileExists(resolvedWorkspace)\n\n logger.info(\n `workspaceDir: ${workspaceDir} (resolved: ${resolvedWorkspace}) ${workspaceExists ? '[\u2713 exists]' : '[ ] not found'}`,\n )\n logger.info('Targets:')\n\n let allReachable = workspaceExists\n\n for (const repo of targets) {\n const targetPath = path.join(resolvedWorkspace, repo)\n const reachable = await fileExists(targetPath)\n\n if (!reachable) {\n allReachable = false\n }\n\n const marker = reachable ? '[\u2713]' : '[ ]'\n const suffix = reachable ? '' : ' (not found \u2014 clone or remove)'\n\n logger.info(` ${marker} ${repo} ${tildify(targetPath)}${suffix}`)\n }\n\n if (!allReachable) {\n process.exitCode = 1\n }\n}\n\n/** Scaffold `~/.infra-kit/vendor.json`, skipping if it already exists. */\nconst initFactoryConfig = async (cwd?: string): Promise<void> => {\n const factoryPath = getFactoryConfigPath()\n\n if (await fileExists(factoryPath)) {\n logger.info(`Factory config already exists at ${tildify(factoryPath)} \u2014 leaving it untouched.`)\n\n return\n }\n\n const sourceRoot = cwd ?? (await getProjectRoot())\n const seededTargets = await readLegacyTargets(sourceRoot)\n\n await fs.mkdir(path.dirname(factoryPath), { recursive: true })\n await fs.writeFile(factoryPath, buildScaffold(seededTargets), 'utf-8')\n\n logger.info(`\u2713 Created ${tildify(factoryPath)}`)\n\n if (seededTargets.length > 0) {\n logger.info(` Seeded ${seededTargets.length} target(s) from the source ${VENDOR_CONFIG_FILE}.`)\n }\n\n logger.info(` Edit \\`workspaceDir\\` (placeholder: ${PLACEHOLDER_WORKSPACE_DIR}) to point at where your repos live.`)\n\n if (seededTargets.length === 0) {\n logger.info(' Add at least one repo name to `targets` before running vendor sync/manifest/diff.')\n }\n}\n\n/**\n * Best-effort read of a legacy `targets` array from the source repo's\n * `vendor.config.ts`. The current schema no longer accepts `targets`, so this\n * reads the raw default export directly (bypassing validation). Returns `[]` on\n * any failure \u2014 seeding is a convenience, never a hard requirement.\n */\nconst readLegacyTargets = async (sourceRoot: string): Promise<string[]> => {\n try {\n const configPath = path.join(sourceRoot, VENDOR_CONFIG_FILE)\n const stat = await fs.stat(configPath)\n const moduleUrl = `${pathToFileURL(configPath).href}?mtime=${Number(stat.mtimeMs)}`\n const imported = (await import(moduleUrl)) as { default?: unknown }\n const raw = imported.default\n const resolved = typeof raw === 'function' ? await (raw as () => unknown)() : raw\n\n if (resolved && typeof resolved === 'object' && 'targets' in resolved) {\n const targets = (resolved as { targets?: unknown }).targets\n\n if (\n Array.isArray(targets) &&\n targets.every((t) => {\n return typeof t === 'string'\n })\n ) {\n return targets as string[]\n }\n }\n } catch {\n // Absent or unreadable source config \u2014 fall through to an empty placeholder.\n }\n\n return []\n}\n\n/**\n * Render the scaffold file body as strict JSON (`vendor.json`). The factory config\n * is static JSON, loaded with `JSON.parse` \u2014 no comments, no executable code. When\n * `targets` is empty the stub writes `\"targets\": []`, which fails the schema's\n * `targets.min(1)` on load: this is intentional \u2014 `--init` produces an incomplete\n * stub the user must edit before running vendor sync/manifest/diff. The annotated\n * guidance lives in the sibling `vendor.example.jsonc` (seeded by `infra-kit init`).\n */\nconst buildScaffold = (targets: string[]): string => {\n return `${JSON.stringify({ workspaceDir: PLACEHOLDER_WORKSPACE_DIR, targets }, null, 2)}\\n`\n}\n", "/**\n * The shared \"equivalent line\" shape \u2014 the replayable, non-interactive invocation\n * a session prints so the user can rerun the same selection without prompts. Kept\n * command-agnostic (dev, vendor, audit \u2026 all emit one) and structural so it never\n * has to import a specific command's wizard types. Pure data + a tiny constructor.\n */\n\n/** One replayable invocation line, plus whether it reproduces the selection exactly. */\nexport interface EquivalentLine {\n /** The full replayable invocation, e.g. \"infra-kit vendor check\". */\n line: string\n /** True when the line reproduces the selection exactly (false \u21D2 it would re-prompt). */\n reproducible: boolean\n}\n\n/**\n * Construct an {@link EquivalentLine}. Defaults to `reproducible: true` for the\n * common case where the printed line replays the selection verbatim.\n *\n * @example\n * equivalentLine('infra-kit vendor check') // => { line: 'infra-kit vendor check', reproducible: true }\n * equivalentLine('infra-kit dev --app=client', false) // => { line: '\u2026', reproducible: false }\n */\nexport const equivalentLine = (line: string, reproducible = true): EquivalentLine => {\n return { line, reproducible }\n}\n", "import fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\n\nimport type { EquivalentLine } from './equivalent'\n\n/**\n * Session-private side channel from a spawned command child back to the session-shell parent. The\n * child writes a small JSON record at the path in `INFRA_KIT_SESSION_REPORT`; the parent reads it to\n * derive the transcript entry. The record is NOT `ToolsExecutionResult` and never touches the `--json`\n * / MCP machine contract \u2014 it exists only so the parent can show a rich `equivalent + report` block.\n *\n * The file's PRESENCE is load-bearing: a decline/cancel path exits before the write, so an absent file\n * (with exit 0) is how the parent tells \"cancelled\" from \"ok\". See `classifyOutcome`.\n */\n\nexport const SESSION_REPORT_ENV = 'INFRA_KIT_SESSION_REPORT'\n\nexport interface SessionReportRecord {\n /** The replayable equivalent line for this invocation (resolved flags folded in when interactive). */\n equivalent?: EquivalentLine\n /** Optional human summary lines a command opted to surface (e.g. \"found 3 violations\"). */\n summary?: string[]\n}\n\n// The child captures the report path ONCE at CLI entry and deletes the env var so no grandchild\n// (gh / git / a hook / $EDITOR) inherits it and clobbers the file. `captured` guards a double-capture.\nlet capturedReportPath: string | null = null\nlet captured = false\nconst summaryLines: string[] = []\n\n/**\n * Capture `INFRA_KIT_SESSION_REPORT` into a module local and delete it from the env so descendants do\n * not inherit it. Call once at CLI entry (in every process); a non-session process simply captures\n * `null`. Idempotent.\n *\n * @example\n * const env = { INFRA_KIT_SESSION_REPORT: '/tmp/r.json' }\n * captureSessionReportPath(env)\n * env.INFRA_KIT_SESSION_REPORT // => undefined (deleted so grandchildren don't inherit)\n */\nexport const captureSessionReportPath = (env: NodeJS.ProcessEnv = process.env): void => {\n if (captured) {\n return\n }\n\n capturedReportPath = env[SESSION_REPORT_ENV] ?? null\n captured = true\n delete env[SESSION_REPORT_ENV]\n}\n\n/** True when this process is running as a session-shell child (a report path was captured). */\nexport const isSessionChild = (): boolean => {\n return capturedReportPath != null\n}\n\n/**\n * Reset the captured path + accumulated summary. For tests only (the process-lifetime singleton is\n * captured exactly once at CLI entry in real use). Mirrors `commandEcho.reset()`.\n *\n * @example\n * resetSessionReport()\n * isSessionChild() // => false\n */\nexport const resetSessionReport = (): void => {\n capturedReportPath = null\n captured = false\n summaryLines.length = 0\n}\n\n/**\n * Accumulate optional human summary lines for this invocation's transcript entry. A no-op outside a\n * session; commands may call it to enrich the report without knowing whether a session is active.\n *\n * @example\n * addSessionSummary('found 3 violations')\n */\nexport const addSessionSummary = (...lines: string[]): void => {\n summaryLines.push(...lines)\n}\n\n/**\n * Write the session report to the captured path. No-op when not running under a session (path null).\n * Best-effort: a write failure never throws (the parent falls back to exit-code-derived status).\n *\n * @example\n * writeSessionReport({ equivalent: { line: 'infra-kit vendor check', reproducible: true } })\n */\nexport const writeSessionReport = (\n record: SessionReportRecord,\n deps?: { write?: (file: string, data: string) => void },\n): void => {\n if (!capturedReportPath) {\n return\n }\n\n const summary = record.summary ?? (summaryLines.length > 0 ? [...summaryLines] : undefined)\n const payload: SessionReportRecord = { ...record, ...(summary ? { summary } : {}) }\n const write =\n deps?.write ??\n ((file: string, data: string) => {\n fs.writeFileSync(file, data)\n })\n\n try {\n write(capturedReportPath, JSON.stringify(payload))\n } catch {\n // Best-effort: an unwritable report degrades the transcript to exit-code-only, never a crash.\n }\n}\n\n// --- Parent side (session shell) -----------------------------------------\n\nlet reportCounter = 0\n\n/**\n * A fresh, unique report path under the OS temp dir for one spawned command. Never uses\n * `getSessionCacheDir()` (which throws when `INFRA_KIT_SESSION` is unset) \u2014 this ephemeral IPC needs\n * no session scoping. Uniqueness comes from pid + a monotonic counter, so no `Math.random` is needed.\n *\n * @example\n * newReportPath() // => '/var/folders/\u2026/infra-kit-session-4123-1.json'\n */\nexport const newReportPath = (deps?: { tmpdir?: () => string; pid?: number }): string => {\n const tmp = deps?.tmpdir?.() ?? os.tmpdir()\n const pid = deps?.pid ?? process.pid\n\n reportCounter += 1\n\n return path.join(tmp, `infra-kit-session-${pid}-${reportCounter}.json`)\n}\n\n/**\n * Read and delete a child's report file. Returns the parsed record, or `null` when the file is absent\n * (the child cancelled / crashed before writing) or unparseable.\n *\n * @example\n * const record = readAndUnlinkReport('/tmp/r.json')\n * record?.equivalent?.line\n */\nexport const readAndUnlinkReport = (\n reportPath: string,\n deps?: { read?: (file: string) => string; unlink?: (file: string) => void; exists?: (file: string) => boolean },\n): SessionReportRecord | null => {\n const exists =\n deps?.exists ??\n ((file: string) => {\n return fs.existsSync(file)\n })\n\n if (!exists(reportPath)) {\n return null\n }\n\n const read =\n deps?.read ??\n ((file: string) => {\n return fs.readFileSync(file, 'utf-8')\n })\n const unlink =\n deps?.unlink ??\n ((file: string) => {\n return fs.unlinkSync(file)\n })\n\n let raw: string\n\n try {\n raw = read(reportPath)\n } catch {\n return null\n } finally {\n try {\n unlink(reportPath)\n } catch {\n // A leftover temp file is harmless; the next iteration uses a fresh counter-suffixed name.\n }\n }\n\n try {\n return JSON.parse(raw) as SessionReportRecord\n } catch {\n return null\n }\n}\n", "/**\n * Small shared rendering helpers for aligned terminal output. These replace\n * the ad-hoc \"compute the max width, then padEnd each row\" pattern that the\n * interactive menu (and similar list views) hand-rolled. Pure string helpers \u2014\n * no I/O, no color \u2014 so they are trivially testable and reusable.\n */\n\n/**\n * Align a list of two-column rows: pad every left cell to the widest left cell,\n * then join the two cells with `gap`. Returns one rendered line per row.\n *\n * @example\n * formatAlignedRows([['a', 'x'], ['bbb', 'y']])\n * // ['a x', 'bbb y'] (left padded to width 3, default 2-space gap)\n */\nexport const formatAlignedRows = (rows: ReadonlyArray<readonly [string, string]>, gap = ' '): string[] => {\n const width = rows.reduce((max, [left]) => {\n return Math.max(max, left.length)\n }, 0)\n\n return rows.map(([left, right]) => {\n return `${left.padEnd(width)}${gap}${right}`\n })\n}\n", "import { chalkStderr } from 'chalk'\nimport { spawn as nodeSpawn } from 'node:child_process'\nimport type { ChildProcess } from 'node:child_process'\nimport process from 'node:process'\n\nimport { equivalentLine } from './equivalent'\nimport { formatRunHeader, formatTranscriptEntry } from './format-entry'\nimport { classifyOutcome } from './outcome'\nimport { SESSION_REPORT_ENV, newReportPath, readAndUnlinkReport } from './report'\nimport { resetTerminal } from './reset-terminal'\n\n/**\n * The persistent `infra-kit` session shell (React-free so it stays out of the eager Ink chunk and is\n * unit-testable with plain vitest). Each iteration: render the palette \u2192 echo the command \u2192 spawn it\n * as a fresh child that inherits this terminal \u2192 reap \u2192 read the child's report file \u2192 commit one\n * status footer \u2192 repeat, until the palette returns `null` (quit).\n *\n * The child runs on the PRIMARY screen, exactly as if the user had typed the command. That is the\n * whole point: its output stays in the scrollback, framed by the echoed `$ infra-kit \u2026` header above\n * and the status footer below. (Children used to run inside the alternate screen buffer, which the\n * terminal discards on exit \u2014 so every command appeared to produce no output at all.)\n *\n * The child boundary is what makes this safe: a `process.exit` in a command exits the child, not the\n * loop; a fresh process means fresh Commander/option state; and the child owns stdin via inherited\n * stdio, so the parent adds zero contention. The session exit code is always 0 \u2014 per-command results\n * live in the transcript, not the shell's exit status.\n */\n\n/** A menu-selectable command resolved from the catalog: what to spawn and how to present it. */\nexport interface SessionCommand {\n /** Canonical Commander argv, e.g. `['vendor', 'check']`. */\n groupPath: string[]\n /**\n * The command hands the terminal to a full-screen child that enters the alternate screen itself (an\n * `$EDITOR`). We never enter it for them \u2014 but if that child is killed before it restores the\n * primary buffer, every later frame would be drawn into an abandoned buffer, so the hygiene reset\n * leaves the alternate screen defensively on their behalf.\n */\n entersAltScreen?: boolean\n /** Append the \"applies after you exit\" notice (env-load/clear can't mutate the parent shell). */\n sessionEnvNotice?: boolean\n /**\n * Runs until the user stops it (`dev`). It writes no report (its action resolves at boot, long before\n * it is done) and exits `128 + signo` on a Ctrl-C rather than dying by the signal, so `classifyOutcome`\n * needs to be told \u2014 otherwise every normal stop reads as a failure.\n */\n longRunning?: boolean\n}\n\n/** A palette row \u2014 structurally compatible with the Ink palette's `PaletteItem`, no React dependency. */\nexport interface SessionPaletteItem {\n name: string\n description: string\n group: string\n}\n\nexport interface RunSessionDeps {\n /** Render the palette and resolve to the picked flat command name, or `null` to quit the session. */\n renderPalette: (items: SessionPaletteItem[]) => Promise<string | null>\n /** Map a picked flat name to its spawn/presentation info (from the command catalog). */\n resolveCommand: (name: string) => SessionCommand | undefined\n /** Absolute path to this bundle's `cli.js`, spawned as `process.execPath <cliPath> <...groupPath>`. */\n cliPath: string\n spawn?: typeof nodeSpawn\n now?: () => number\n /** Commit a finished transcript entry (default: stderr, consistent with all repo human output). */\n write?: (text: string) => void\n env?: NodeJS.ProcessEnv\n /** ASCII glyphs for the transcript (default: derived from stdout TTY / TERM). */\n ascii?: boolean\n /** Colour the header and footer (default: chalk's own TTY / `NO_COLOR` / `FORCE_COLOR` verdict). */\n color?: boolean\n /**\n * Terminal width for the footer's closing rule, or `undefined` for none (default: stderr's columns \u2014\n * the stream the transcript is written to).\n *\n * A FUNCTION, not a number, because the session shell is long-lived: it loops until the user quits, so\n * a width snapshotted at boot goes stale the moment they resize the window. Re-read per run, a shrink\n * just makes the next rule shorter; snapshotted, it would overrun the new margin and wrap across\n * rows \u2014 the exact wall-of-text this framing exists to prevent.\n */\n columns?: () => number | undefined\n /** Terminal hygiene after each child (default: the real escapes; tests inject a spy). */\n resetTerminal?: (opts: { entersAltScreen?: boolean }) => void\n /** Install the real signal handlers (default true; tests pass false to stay signal-free). */\n installSignals?: boolean\n /** Process seams for those handlers (default: the real `process`). Tests inject fakes to assert policy. */\n signals?: SessionSignalDeps\n}\n\n/** The replayable command line for a pick \u2014 echoed as the header, and the default equivalent line. */\nconst commandLine = (command: SessionCommand): string => {\n return `infra-kit ${command.groupPath.join(' ')}`\n}\n\n/**\n * The width of the stream the transcript is written to, or `undefined` when it has none.\n *\n * Node types `columns` as a `number`, but a non-TTY stderr has no such property at all. Passing the\n * resulting `undefined` (or a `0`) through as a width would read to the formatter as a *very narrow*\n * terminal; `undefined` is the value that means \"draw no rule\", so normalise to it explicitly.\n */\nconst stderrColumns = (): number | undefined => {\n const columns: number | undefined = process.stderr.columns\n\n return columns != null && columns > 0 ? columns : undefined\n}\n\nconst waitForExit = (child: ChildProcess): Promise<{ code: number | null; signal: NodeJS.Signals | null }> => {\n return new Promise((resolve) => {\n child.on('exit', (code, signal) => {\n resolve({ code, signal })\n })\n child.on('error', () => {\n // A spawn that never launched is a failed run, not a cancel.\n resolve({ code: 1, signal: null })\n })\n })\n}\n\n/** Every dep `runOne` needs, with the defaults already applied. */\ntype ResolvedDeps = Required<\n Pick<RunSessionDeps, 'spawn' | 'now' | 'env' | 'cliPath' | 'ascii' | 'color' | 'columns' | 'resetTerminal'>\n>\n\n/**\n * Run one picked command as a child on THIS terminal and format the footer that closes it out. The\n * header is echoed by the caller before we spawn, so the child's output arrives under a heading.\n */\nconst runOne = async (\n command: SessionCommand,\n deps: ResolvedDeps,\n markChildOwnsSigint: () => void,\n): Promise<string> => {\n const reportPath = newReportPath()\n const childEnv: NodeJS.ProcessEnv = {\n ...deps.env,\n [SESSION_REPORT_ENV]: reportPath,\n // Every child would otherwise re-run the daily auto-update check and spawn its own detached\n // worker; opt them all out. The parent's own startup check (before the palette) still runs.\n INFRA_KIT_NO_AUTO_UPDATE: '1',\n }\n const start = deps.now()\n\n let result: { code: number | null; signal: NodeJS.Signals | null } = { code: 1, signal: null }\n\n markChildOwnsSigint()\n try {\n // `stdio: 'inherit'` on the primary screen: the child writes straight to the user's terminal, so\n // its output lands in the scrollback and stays there. Never pipe \u2014 a piped child loses its TTY,\n // which strips colour and breaks every interactive prompt (release-create, the branch pickers).\n const child = deps.spawn(process.execPath, [deps.cliPath, ...command.groupPath], {\n stdio: 'inherit',\n env: childEnv,\n })\n\n result = await waitForExit(child)\n } catch {\n // A synchronous spawn throw (or a rejected run) must degrade to a `failed` transcript entry, never\n // tear the session down.\n result = { code: 1, signal: null }\n } finally {\n // NOT the place to hand SIGINT back. The child's `exit` has fired, but the terminal reset below, the\n // report read, and the footer + palette draw are all still ahead \u2014 and a user force-quitting the child\n // MASHES Ctrl-C, so more SIGINTs are still landing. Clearing the flag here made every one of them take\n // the \"no child running\" branch and `exit(0)` the WHOLE SESSION SHELL instead of returning to the\n // palette. The loop clears it immediately before `renderPalette`, once the palette can own Ctrl-C.\n deps.resetTerminal({ entersAltScreen: command.entersAltScreen })\n }\n\n const record = readAndUnlinkReport(reportPath)\n const outcome = classifyOutcome(result.code, result.signal, record != null, command.longRunning)\n const headerLine = commandLine(command)\n const equivalent = record?.equivalent ?? equivalentLine(headerLine, true)\n\n return formatTranscriptEntry({\n equivalent,\n outcome,\n durationMs: deps.now() - start,\n summary: record?.summary,\n envNotice: command.sessionEnvNotice,\n ascii: deps.ascii,\n color: deps.color,\n // Read HERE, not at session boot: the user may have resized the window since the last run.\n width: deps.columns(),\n // We already echoed this line before the spawn. Repeat it only when the child reported back a\n // DIFFERENT one \u2014 an interactive command folding its resolved flags into a replayable `\u2248` line.\n showEquivalent: !(equivalent.reproducible && equivalent.line === headerLine),\n })\n}\n\n/** Process-level seams for the signal owners (tests inject fakes; production wires `process`). */\nexport interface SessionSignalDeps {\n register: (signal: NodeJS.Signals, handler: () => void) => void\n unregister: (signal: NodeJS.Signals, handler: () => void) => void\n exit: (code: number) => void\n /** Deliver a signal to THIS process (used to take SIGTSTP's default action: stop). */\n raise: (signal: NodeJS.Signals) => void\n}\n\n/** The session's live signal owner: handler teardown, plus the state the loop must consult. */\nexport interface SessionSignals {\n /** Remove every installed handler. */\n dispose: () => void\n /** Call when a child is about to run \u2014 resets the per-child signal history. */\n childStarted: () => void\n /** True when an external SIGTERM asked us to stop; the loop must exit after the current child. */\n quitRequested: () => boolean\n}\n\n/**\n * Install the session's state-aware signal owners. The child is spawned WITHOUT `detached`, so it shares\n * this process's group \u2014 every tty-delivered signal reaches the child directly, and the parent's job is\n * only to decide what IT does.\n *\n * - **SIGINT** \u2014 swallowed while a child runs, so the tty's Ctrl-C stops only the child and the parent\n * survives to render `\u2298 cancelled` and loop. Between iterations it ends the session (exit 0).\n * - **SIGTERM** \u2014 DEFERRED, not dropped, while a child runs: we finish the child, commit its transcript\n * entry, and then end the session. Dropping it outright would make the session unkillable by anything\n * short of SIGKILL for as long as a child ran \u2014 and `dev` runs for hours.\n *\n * The exception is pnpm's RELAY. Under `pnpm exec infra-kit`, pnpm forwards a SIGTERM to us moments\n * after the tty's Ctrl-C \u2014 that SIGTERM is an artifact of the SIGINT the user already aimed at the\n * child, not a request to kill the session, so a SIGTERM that FOLLOWS a SIGINT within the same child\n * is swallowed. (Causality, not a timing heuristic: the relay only exists because of the Ctrl-C.)\n * Without a handler at all, that relay was an instant unhandled parent death \u2014 the shell prompt\n * returned while the child still owned the tty and wrote teardown output over it.\n * - **SIGHUP** \u2014 NEVER swallowed. It means the terminal is gone, so staying alive would leave the\n * session looping on a dead tty, drawing palette frames into a closed fd. Always exit (129). The child\n * gets its own SIGHUP and is responsible for its own reap (see `dev/signal-shutdown.ts`).\n * - **SIGTSTP** \u2014 Ctrl-Z. While a child runs we STOP OURSELVES, because the tty already delivered\n * SIGTSTP to the whole foreground group: the child has stopped, and if the parent merely ignored the\n * signal (as it used to) it would sit in `waitForExit` awaiting an exit that can never come, wedging\n * the terminal with no prompt and no way back. Stopping too suspends the FOREGROUND GROUP as a unit,\n * so the user's shell reclaims the tty and prints a prompt; `fg` sends SIGCONT to the group and both\n * resume. Handlers survive stop/continue, so there is nothing to re-arm.\n *\n * CAVEAT \u2014 a child's own DETACHED descendants keep running: `dev`'s turbo/vite children sit in their\n * own process groups, so a Ctrl-Z out of `dev` suspends `dev` itself while its servers stay up and\n * bound to their ports. Recoverable with `fg`, and strictly better than the wedge it replaced, but\n * \"suspended\" is not the whole truth for `dev`. Between iterations SIGTSTP stays IGNORED: suspending\n * mid-palette would strand a half-drawn Ink frame on the screen.\n */\nexport const installSessionSignals = (isChildRunning: () => boolean, deps: SessionSignalDeps): SessionSignals => {\n // Per-child signal history: a SIGTERM is pnpm's relay only if a SIGINT preceded it for THIS child.\n let sigintSeenDuringChild = false\n let quitRequested = false\n\n const onSigint = (): void => {\n if (isChildRunning()) {\n // The child got its own copy from the tty; ours only records that the Ctrl-C happened, so a\n // SIGTERM arriving next can be recognised as pnpm's relay rather than an external kill.\n sigintSeenDuringChild = true\n\n return\n }\n\n deps.exit(0)\n }\n const onSigterm = (): void => {\n if (!isChildRunning()) {\n deps.exit(0)\n\n return\n }\n\n if (sigintSeenDuringChild) {\n // pnpm's relay of the Ctrl-C the user aimed at the child. Not a request to end the session.\n return\n }\n\n // A genuine external `kill`. Let the child finish and its entry commit, then stop.\n quitRequested = true\n }\n const onSighup = (): void => {\n // The tty is gone. There is nothing left to render into and no user to render for.\n deps.exit(129)\n }\n const onSigtstp = (): void => {\n if (!isChildRunning()) {\n // Mid-palette: ignore, or we would suspend with a partial frame committed to the screen.\n return\n }\n\n // SIGSTOP cannot be caught \u2014 this takes the default action the handler is suppressing.\n deps.raise('SIGSTOP')\n }\n\n deps.register('SIGINT', onSigint)\n deps.register('SIGTERM', onSigterm)\n deps.register('SIGHUP', onSighup)\n deps.register('SIGTSTP', onSigtstp)\n\n return {\n dispose: () => {\n deps.unregister('SIGINT', onSigint)\n deps.unregister('SIGTERM', onSigterm)\n deps.unregister('SIGHUP', onSighup)\n deps.unregister('SIGTSTP', onSigtstp)\n },\n childStarted: () => {\n sigintSeenDuringChild = false\n },\n quitRequested: () => {\n return quitRequested\n },\n }\n}\n\n/**\n * Pure gate: may bare `infra-kit` engage the persistent session shell? Requires an interactive TTY on\n * all three streams, a capable terminal, and no opt-out. Deliberately does NOT depend on\n * `INFRA_KIT_SESSION` (the shell must work without `infra-kit init` having run). Everything else keeps\n * the one-shot path.\n *\n * **stderr is load-bearing**, not an afterthought: the palette and every transcript line are written\n * there. Under `infra-kit 2>out.log` the palette would be invisible (it renders into the file), and\n * writes to a pipe are asynchronous on macOS \u2014 so the echoed header could land AFTER the child's\n * output, which is the one ordering the shell guarantees. Both are avoided by never entering the shell\n * when stderr is redirected.\n *\n * @example\n * sessionGateEnabled({ TERM: 'xterm' }, { stdoutIsTTY: true, stdinIsTTY: true, stderrIsTTY: true }) // => true\n */\nexport const sessionGateEnabled = (\n env: NodeJS.ProcessEnv,\n streams: { stdoutIsTTY: boolean; stdinIsTTY: boolean; stderrIsTTY: boolean },\n): boolean => {\n return Boolean(\n streams.stdoutIsTTY &&\n streams.stdinIsTTY &&\n streams.stderrIsTTY &&\n env.TERM !== 'dumb' &&\n !env.INFRA_KIT_NO_SESSION &&\n !env.INFRA_KIT_SESSION_REPORT,\n )\n}\n\n/**\n * Run the session loop until the user quits at the palette.\n *\n * @example\n * await runSession(items, { renderPalette, resolveCommand, cliPath: '/g/dist/cli.js' })\n */\nexport const runSession = async (items: SessionPaletteItem[], deps: RunSessionDeps): Promise<void> => {\n const resolved = {\n spawn: deps.spawn ?? nodeSpawn,\n now:\n deps.now ??\n (() => {\n return Date.now()\n }),\n env: deps.env ?? process.env,\n cliPath: deps.cliPath,\n ascii: deps.ascii ?? !(process.stdout.isTTY && process.env.TERM !== 'dumb'),\n // `chalkStderr`, not `chalk`: the default instance sniffs STDOUT, and every byte of the transcript\n // goes to stderr (see `write` below). It already folds in TTY detection, `NO_COLOR` and\n // `FORCE_COLOR`. The formatter itself is colour-pure, so this is the only place the call is made \u2014\n // and it is made against the same stream `columns` is read from.\n color: deps.color ?? chalkStderr.level > 0,\n columns: deps.columns ?? stderrColumns,\n resetTerminal:\n deps.resetTerminal ??\n ((opts: { entersAltScreen?: boolean }) => {\n resetTerminal(opts)\n }),\n }\n const write =\n deps.write ??\n ((text: string) => {\n return process.stderr.write(text)\n })\n\n const signals: SessionSignalDeps = deps.signals ?? {\n register: (signal, handler) => {\n process.on(signal, handler)\n },\n unregister: (signal, handler) => {\n process.off(signal, handler)\n },\n exit: (code) => {\n process.exit(code)\n },\n raise: (signal) => {\n process.kill(process.pid, signal)\n },\n }\n\n /**\n * Does the CHILD own Ctrl-C right now?\n *\n * Deliberately WIDER than \"a child process is alive\". It stays true from the spawn until the palette is\n * about to be drawn again \u2014 covering the terminal reset, the report read and the footer \u2014 because a user\n * force-quitting a child MASHES Ctrl-C, and the trailing SIGINTs land in exactly that gap. Letting one of\n * them reach the \"no child running\" branch is what used to `exit(0)` the entire session shell instead of\n * returning to the palette.\n */\n let childOwnsSigint = false\n const noopSignals: SessionSignals = {\n dispose: () => {\n return undefined\n },\n childStarted: () => {\n return undefined\n },\n quitRequested: () => {\n return false\n },\n }\n const sessionSignals =\n deps.installSignals === false\n ? noopSignals\n : installSessionSignals(() => {\n return childOwnsSigint\n }, signals)\n\n try {\n for (;;) {\n // An external SIGTERM arrived mid-child. We deferred it so the child could finish and its entry\n // could be committed; honour it now rather than re-rendering the palette over a `kill`.\n if (sessionSignals.quitRequested()) {\n return\n }\n\n // Hand Ctrl-C back to the palette HERE, and nowhere earlier. Everything between the child's exit\n // and this line \u2014 the terminal reset, the report read, the footer \u2014 is still fair game for the\n // trailing SIGINTs of a user mashing Ctrl-C at the child, and one of those reaching the\n // \"no child running\" branch would `exit(0)` the entire session. From this line on, the palette's\n // Ink holds stdin in raw mode (which disables ISIG), so a Ctrl-C there arrives as a `0x03` byte it\n // handles itself \u2014 not as a SIGINT at all.\n childOwnsSigint = false\n\n const selected = await deps.renderPalette(items)\n\n if (selected == null) {\n return\n }\n\n const command = deps.resolveCommand(selected)\n\n if (!command) {\n continue\n }\n\n // Echo first, exactly like a shell: the header must be on screen BEFORE the child starts\n // drawing, or a slow command (a 30s `audit --all`) prints its output under no heading at all.\n const header = formatRunHeader(commandLine(command), { color: resolved.color })\n\n write(`\\n${header}\\n`)\n sessionSignals.childStarted()\n\n const entry = await runOne(command, resolved, () => {\n childOwnsSigint = true\n })\n\n // Leading newline: the child may have exited mid-line, and the footer must not be welded to it.\n // Still inside the child's SIGINT window \u2014 see the top of the loop.\n write(`\\n${entry}\\n`)\n }\n } finally {\n sessionSignals.dispose()\n }\n}\n", "/**\n * Pure formatter for one committed transcript block \u2014 the two-line summary a\n * session leaves behind in the scrollback once it finishes. Line 1 echoes the\n * replayable command (`$ ` when it reproduces exactly, `\u2248 ` when it would\n * re-prompt); line 2 is a status glyph + label + duration + optional summary,\n * optionally run out to a rule that closes the block off; an optional third\n * line carries the env-scope notice. No I/O \u2014 it returns a string the caller\n * writes wherever it likes.\n *\n * The block is framed from the OUTSIDE only. The command runs as a child with\n * `stdio: 'inherit'`, writing straight to the terminal, so nothing here can\n * touch its output \u2014 no left gutter, no indent, no box. The header above and\n * the status rule below are the only lines we own, which is why the rule hangs\n * off the footer: it is the one place we can draw a hard edge between one run\n * and the next.\n */\nimport { Chalk } from 'chalk'\n\nimport type { EquivalentLine } from './equivalent'\nimport type { SessionOutcome } from './outcome'\n\n/**\n * Level 1 (basic 16 colours) FORCED, so the formatter stays pure: what it emits depends on its own\n * `color` argument, never on the ambient TTY. Callers decide \u2014 `runSession` derives the flag from the\n * default chalk instance, which is the thing that honours `NO_COLOR`/`FORCE_COLOR`/TTY detection. Tests\n * can therefore assert real escape codes without faking a terminal.\n */\nconst ansi = new Chalk({ level: 1 })\n\n/** All literal, user-facing copy for the transcript entry. */\nconst T = {\n ok: 'ok',\n findingsPlain: 'completed with findings',\n failed: 'failed',\n cancelled: 'cancelled',\n findingsSuffix: 'findings',\n sep: ' \u00B7 ',\n reproPrefix: '$ ',\n nonReproPrefix: '\u2248 ',\n envNotice: 'Applies to your shell after you exit this session.',\n}\n\n/** Status glyphs keyed by outcome, in both a unicode and an ASCII-safe variant. */\nconst GLYPHS: Record<SessionOutcome, { unicode: string; ascii: string }> = {\n ok: { unicode: '\u2713', ascii: '[ok]' },\n findings: { unicode: '\u26A0', ascii: '[!]' },\n failed: { unicode: '\u2717', ascii: '[x]' },\n cancelled: { unicode: '\u2298', ascii: '[-]' },\n}\n\n/** The rule character that runs the footer out to the right margin. */\nconst RULE = { unicode: '\u2500', ascii: '-' }\n\n/**\n * Shortest rule worth drawing. Below this the footer is left bare: a two-dash stub next to a status\n * reads as a typo, not as an edge, and on a narrow terminal the wrap it risks is worse than no rule.\n */\nconst MIN_RULE_WIDTH = 3\n\n/** Identity styler \u2014 the no-colour branch, so the two paths differ only in the escape codes. */\nconst plain = (text: string): string => {\n return text\n}\n\n/** The outcome's own colour: the one hue in the block, so the eye lands on the verdict first. */\nconst OUTCOME_COLOR: Record<SessionOutcome, (text: string) => string> = {\n ok: (text) => {\n return ansi.green(text)\n },\n findings: (text) => {\n return ansi.yellow(text)\n },\n failed: (text) => {\n return ansi.red(text)\n },\n cancelled: (text) => {\n return ansi.gray(text)\n },\n}\n\n/**\n * The block's chrome \u2014 the `$`/`\u2248` prompt, the command, the rule, the env notice. Carries no outcome,\n * which is why it is split from {@link outcomeStyler}: the header has no verdict to colour.\n */\nconst chromeStyler = (color: boolean): { dim: (text: string) => string; bold: (text: string) => string } => {\n if (!color) return { dim: plain, bold: plain }\n\n return {\n dim: (text: string) => {\n return ansi.dim(text)\n },\n bold: (text: string) => {\n return ansi.bold(text)\n },\n }\n}\n\n/** The verdict's hue: the one saturated colour in the block, so the eye lands on the outcome first. */\nconst outcomeStyler = (color: boolean, outcome: SessionOutcome): ((text: string) => string) => {\n return color ? OUTCOME_COLOR[outcome] : plain\n}\n\n/**\n * Width in terminal cells. `String.length` counts UTF-16 code units, so an astral character \u2014 any emoji\n * a command folds into its summary \u2014 counts as TWO, and the rule comes out a cell short for each one.\n *\n * Deliberately not a complete solution: a CJK or ZWJ-composed summary still mismeasures, since those\n * need a real grapheme + east-asian-width table. Acceptable because the worst case is cosmetic (a rule\n * that wraps or stops short, never a corrupted line) and no command supplies a summary today.\n */\nconst cellWidth = (text: string): number => {\n return [...text].length\n}\n\n/** Everything the formatter needs to render one transcript block. */\nexport interface TranscriptEntryInput {\n /** The replayable command line + whether it reproduces the selection. */\n equivalent: EquivalentLine\n /** Terminal state of the session. */\n outcome: SessionOutcome\n /** Wall-clock duration in milliseconds. */\n durationMs: number\n /** Optional short summary lines; the first is appended to the status line. */\n summary?: string[]\n /** Finding count, used to label a `findings` outcome as `${n} findings`. */\n findingsCount?: number\n /** Append the \"applies to your shell after you exit\" env notice as a third line. */\n envNotice?: boolean\n /** Use ASCII glyphs instead of unicode (for `!isTTY` / `TERM=dumb`). */\n ascii?: boolean\n /**\n * Render the `$ \u2026` / `\u2248 \u2026` equivalent line (default `true`). The session shell echoes the command\n * BEFORE it runs \u2014 like any shell \u2014 so when the child reports back the same line, repeating it under\n * the output would be noise. The caller decides: it is the only party that knows what it echoed.\n */\n showEquivalent?: boolean\n /** Emit ANSI colour (default `false`). The caller owns TTY/`NO_COLOR` detection. */\n color?: boolean\n /**\n * Terminal width in columns. When given, the status line is run out to a rule that closes the block;\n * omit it (or pass a width too narrow for {@link MIN_RULE_WIDTH}) to leave the footer bare.\n */\n width?: number\n}\n\n/**\n * The line a shell prints before it runs something. The session writes this ahead of the spawn, so the\n * child's real output arrives under a heading instead of unannounced.\n *\n * Deliberately NOT ruled or boxed, unlike the footer: this line gets copied out of the scrollback and\n * re-run, so it stays exactly what a user would type. Emphasis is carried by weight, not by decoration.\n *\n * @example\n * formatRunHeader('infra-kit audit') // => '$ infra-kit audit'\n */\nexport const formatRunHeader = (line: string, opts: { color?: boolean } = {}): string => {\n const chrome = chromeStyler(opts.color === true)\n\n return `${chrome.dim(T.reproPrefix.trim())} ${chrome.bold(line)}`\n}\n\n/** Format a duration compactly: sub-second as `820ms`, otherwise `4.2s`. */\nconst formatDuration = (durationMs: number): string => {\n if (durationMs < 1000) return `${Math.round(durationMs)}ms`\n\n return `${(durationMs / 1000).toFixed(1)}s`\n}\n\n/** The human label for an outcome; `findings` folds in the count when present. */\nconst labelFor = (outcome: SessionOutcome, findingsCount?: number): string => {\n if (outcome === 'findings') {\n return findingsCount != null ? `${findingsCount} ${T.findingsSuffix}` : T.findingsPlain\n }\n\n return { ok: T.ok, failed: T.failed, cancelled: T.cancelled }[outcome]\n}\n\n/**\n * The rule that runs the status out to the right margin, giving the block a visible bottom edge \u2014 or\n * `''` when there is no room for one. `statusWidth` is the status line's width in COLUMNS, which is why\n * the caller measures the un-styled text: escape codes occupy no cells.\n *\n * The rule stops one cell short of `width`, because terminals disagree about what filling the last cell\n * means. An eagerly-wrapping one moves to the next row the moment it is written, so the `\\n` the caller\n * appends costs a SECOND row \u2014 a phantom blank line under every entry. (A VT100-family terminal instead\n * defers the wrap and would be fine.) Leaving the last cell empty is correct on both, and the cost is a\n * single unused column.\n */\nconst ruleSuffix = (\n statusWidth: number,\n width: number | undefined,\n ruleChar: string,\n dim: (text: string) => string,\n): string => {\n if (width == null) return ''\n\n // -1 for the last cell we leave empty, -1 for the space between the status and the rule.\n const ruleWidth = width - 2 - statusWidth\n\n if (ruleWidth < MIN_RULE_WIDTH) return ''\n\n return ` ${dim(ruleChar.repeat(ruleWidth))}`\n}\n\n/**\n * Render a committed transcript block from a finished session. Line 1 echoes the\n * equivalent command; line 2 is `<glyph> <label> \u00B7 <duration>[ \u00B7 <summary>]`, run\n * out to a closing rule when a `width` is given; an optional third dim line\n * carries the env notice.\n *\n * @example\n * formatTranscriptEntry({\n * equivalent: { line: 'infra-kit vendor check', reproducible: true },\n * outcome: 'ok', durationMs: 4200,\n * })\n * // '$ infra-kit vendor check\\n\u2713 ok \u00B7 4.2s'\n */\nexport const formatTranscriptEntry = (input: TranscriptEntryInput): string => {\n const color = input.color === true\n const chrome = chromeStyler(color)\n const verdictColor = outcomeStyler(color, input.outcome)\n const ascii = input.ascii === true\n const prefix = input.equivalent.reproducible ? T.reproPrefix : T.nonReproPrefix\n const glyph = ascii ? GLYPHS[input.outcome].ascii : GLYPHS[input.outcome].unicode\n const label = labelFor(input.outcome, input.findingsCount)\n\n const detail = [formatDuration(input.durationMs)]\n // First LINE of the first summary: a summary with an embedded newline would otherwise blow the rule's\n // arithmetic apart \u2014 the measured width would span rows the terminal renders separately.\n const firstSummary = input.summary?.[0]?.split('\\n')[0]\n\n if (firstSummary != null && firstSummary.length > 0) detail.push(firstSummary)\n\n // The verdict carries the colour; the duration and summary trail behind it, dimmed.\n const verdict = `${glyph} ${label}`\n const trailer = `${T.sep}${detail.join(T.sep)}`\n // Measured on the UNSTYLED text and styled after: escape codes occupy no columns, so a rule sized\n // against the styled string would come out ~20 cells short of the margin.\n const statusWidth = cellWidth(verdict) + cellWidth(trailer)\n const rule = ruleSuffix(statusWidth, input.width, ascii ? RULE.ascii : RULE.unicode, chrome.dim)\n\n const lines = [`${verdictColor(verdict)}${chrome.dim(trailer)}${rule}`]\n\n if (input.showEquivalent !== false) {\n lines.unshift(`${chrome.dim(prefix.trim())} ${chrome.bold(input.equivalent.line)}`)\n }\n\n if (input.envNotice === true) lines.push(chrome.dim(T.envNotice))\n\n return lines.join('\\n')\n}\n", "/**\n * Pure classifier that turns a finished session's exit signals into a single\n * {@link SessionOutcome}. Report-presence is decided FIRST: a written report means\n * the command produced a verdict, so a late teardown signal (e.g. SIGINT during\n * cleanup after the report was flushed) is still a success. Only when NO report\n * exists does the signal/exit code decide between a user cancellation and a\n * genuine failure. Side-effect-free so the mapping is fully unit-testable.\n */\n\n/** The four terminal states a session can settle into. */\nexport type SessionOutcome = 'ok' | 'findings' | 'failed' | 'cancelled'\n\n/**\n * The exit codes a command that handles its own termination signal reports on a clean stop: `128 + signo`\n * for SIGINT (130) and SIGTERM (143). Deliberately NOT a `> 128` range check \u2014 SIGKILL (137) means the\n * kernel or an OOM killer stepped in, and SIGSEGV (139) is a crash. Those must stay `failed`.\n *\n * SIGHUP (129) is absent for a reason that lives in ANOTHER file, so changing that file must bring you\n * back here: the session parent exits on SIGHUP the moment it arrives (`run-session.ts`, `onSighup`), so\n * no footer is ever classified for a hangup and 129 is unreachable. If SIGHUP is ever made to DEFER like\n * SIGTERM, add 129 to this set in the same commit \u2014 otherwise a hangup out of `dev` starts silently\n * reading as `\u2717 failed`.\n */\nconst SIGNAL_STOP_EXIT_CODES = new Set([130, 143])\n\n/**\n * Classify a finished session. When a report was written, the run completed:\n * exit 0 (or any teardown signal) is `ok`, a non-zero exit is `findings`. When no\n * report was written, a `SIGINT` or clean exit is a user `cancelled`, anything\n * else is `failed`.\n *\n * `longRunning` marks a child that runs until the user stops it (`dev`). Such a child installs its own\n * SIGINT handler and exits `128 + signo` BY DESIGN \u2014 it is not killed BY the signal, so it reports exit\n * 130 with a null `signal`, which is indistinguishable from a crash on the default path. Without this\n * flag every normal Ctrl-C out of `dev` would be stamped `failed`.\n *\n * @example\n * classifyOutcome(0, null, true) // => 'ok'\n * classifyOutcome(1, null, true) // => 'findings'\n * classifyOutcome(null, 'SIGINT', true) // => 'ok' (teardown after the report was written)\n * classifyOutcome(1, null, false) // => 'failed'\n * classifyOutcome(0, null, false) // => 'cancelled'\n * classifyOutcome(130, null, false, true) // => 'cancelled' (dev, stopped with Ctrl-C)\n * classifyOutcome(1, null, false, true) // => 'failed' (dev, crashed on boot)\n */\nexport const classifyOutcome = (\n exitCode: number | null,\n signal: NodeJS.Signals | null,\n reportPresent: boolean,\n longRunning = false,\n): SessionOutcome => {\n if (reportPresent) {\n return signal != null || exitCode === 0 ? 'ok' : 'findings'\n }\n\n if (longRunning && exitCode != null && SIGNAL_STOP_EXIT_CODES.has(exitCode)) {\n return 'cancelled'\n }\n\n return signal === 'SIGINT' || exitCode === 0 ? 'cancelled' : 'failed'\n}\n", "import process from 'node:process'\n\n/**\n * Terminal hygiene between a finished child and the next palette frame.\n *\n * A child that dies mid-draw \u2014 a SIGINT'd `gh` progress spinner, a crashed pager \u2014 leaves the terminal\n * dirty: cursor hidden, SGR colour still set, autowrap off, the cursor parked mid-line, or a scroll\n * region (DECSTBM) still installed. The session used to run children inside the alternate screen\n * buffer, so all of that was discarded with the buffer on exit. Now that children draw on the primary\n * screen \u2014 which is the point, it is how their output survives \u2014 nobody discards it, and the next\n * status footer would be welded onto a half-written line inside a broken scroll region.\n *\n * So the session cleans up after every child, not just cancelled ones. `?1049l` is NOT sent for\n * ordinary commands (we never entered the alternate screen for them); it is sent only for a command\n * whose child enters it itself and may have died before restoring the primary buffer.\n */\n\n/** The escapes that put a terminal back into a sane state, in the order they must be applied. */\nconst ANSI = {\n /** Return to column 0 \u2014 a child may have exited mid-line. */\n column: '\\r',\n /** Reset SGR: colour, bold, inverse. */\n sgr: '\\u001B[0m',\n /** Show the cursor (a TUI child may have hidden it). */\n cursor: '\\u001B[?25h',\n /** Re-enable autowrap (DECAWM). */\n wrap: '\\u001B[?7h',\n /** Save the cursor position (DECSC). See `scrollRegion`. */\n saveCursor: '\\u001B7',\n /** Restore the cursor position saved by DECSC (DECRC). See `scrollRegion`. */\n restoreCursor: '\\u001B8',\n /**\n * Reset the scroll region to the full screen (DECSTBM) \u2014 a stuck region traps all later output.\n *\n * DECSTBM ALSO HOMES THE CURSOR. That is not a quirk, it is the spec, and it is why this escape must\n * always be bracketed by DECSC/DECRC. Sent bare, it moved the cursor to the top-left after EVERY\n * child \u2014 so the status footer, and then the next palette frame, were drawn from row 1 straight over\n * the command's own output. That is the whole \"welded rows\" bug: `\u2713 ok \u00B7 2.9s` printed on top of an\n * unrelated line, and the palette's `\u276F ` printed over the head of the command's own `ERROR:` line,\n * leaving `\u276F ROR: \u2026` behind.\n */\n scrollRegion: '\\u001B[r',\n /** Leave the alternate screen \u2014 only for a child that entered it and may not have left it. */\n primaryBuffer: '\\u001B[?1049l',\n}\n\nexport interface ResetTerminalDeps {\n write?: (text: string) => void\n /** Take stdin out of raw mode if a dead child left it there. Defaults to `process.stdin`. */\n stdin?: { isTTY?: boolean; isRaw?: boolean; setRawMode?: (mode: boolean) => void }\n}\n\n/**\n * Put the terminal back in a usable state after a child exits. Safe to call unconditionally: every\n * escape is a no-op on a terminal that was already clean.\n *\n * @example\n * const out: string[] = []\n * resetTerminal({ entersAltScreen: false }, { write: (s) => out.push(s), stdin: {} })\n * out.join('') // => '\\u001B7\\u001B[r\\u001B8\\r\\u001B[0m\\u001B[?25h\\u001B[?7h'\n */\nexport const resetTerminal = (opts: { entersAltScreen?: boolean }, deps?: ResetTerminalDeps): void => {\n const write =\n deps?.write ??\n ((text: string) => {\n process.stderr.write(text)\n })\n const stdin = deps?.stdin ?? process.stdin\n\n // DECSTBM homes the cursor, so the scroll-region reset is bracketed by save/restore: the cursor must\n // end up exactly where the child left it \u2014 below the child's output \u2014 or everything written next (the\n // footer, the next palette frame) lands on top of that output instead of after it.\n const escapes = [\n ANSI.saveCursor,\n ANSI.scrollRegion,\n ANSI.restoreCursor,\n ANSI.column,\n ANSI.sgr,\n ANSI.cursor,\n ANSI.wrap,\n ]\n\n // Only for a child that owns the alternate screen: if it was killed before restoring the primary\n // buffer, everything we draw next would land in a buffer the terminal is about to throw away.\n if (opts.entersAltScreen === true) escapes.push(ANSI.primaryBuffer)\n\n write(escapes.join(''))\n\n // A child killed mid-prompt can leave the tty in raw mode, which would eat the palette's keystrokes.\n if (stdin.isTTY === true && stdin.isRaw === true) stdin.setRawMode?.(false)\n}\n", "/**\n * The PARENT half of the auto-update: it never fetches, never installs, and never blocks.\n *\n * All it does is (a) surface a notice the background child left behind for installs we may not touch\n * ourselves, and (b) hand off to a detached child when the throttle window has elapsed. Everything\n * expensive or mutating happens in `src/entry/update-check.ts`, after this process has exited.\n */\nimport { spawn } from 'node:child_process'\nimport fs from 'node:fs'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\n\nimport { safeRealpath } from 'src/lib/install-manager'\nimport { logger } from 'src/lib/logger'\n\nimport { autoUpdateSkipReason } from './guards'\nimport { isNewerVersion } from './semver'\nimport { isStale, readUpdateCache } from './update-cache'\nimport type { UpdateCache } from './update-cache'\n\nexport interface AutoUpdateDeps {\n argv?: string[]\n env?: NodeJS.ProcessEnv\n isTty?: boolean\n cwd?: string\n nowMs?: number\n /** Realpath of the running `dist/cli.js`. */\n selfRealPath?: string\n /** Absolute path to the `dist/update-check.js` sibling bundle. */\n childPath?: string\n readCache?: () => UpdateCache | null\n spawnChild?: (childPath: string) => void\n notify?: (line: string) => void\n fileExists?: (p: string) => boolean\n}\n\n/**\n * The refresh runs as `node dist/update-check.js --parent-pid <pid>`: a DEDICATED entry, never\n * `cli.js`. Re-invoking `cli.js` would re-run this very function (plus commander, the catalog, and\n * every top-level side effect) in the child. The `mcp` command already spawns a sibling bundle for the\n * same reason.\n *\n * `detached` + `unref` + `stdio: 'ignore'` is what lets the child outlive us: a `ik version` exits in\n * ~50ms, far sooner than any registry round-trip. `windowsHide` keeps a console window from flashing.\n */\nconst defaultSpawnChild = (childPath: string): void => {\n const child = spawn(process.execPath, [childPath, '--parent-pid', String(process.pid)], {\n detached: true,\n stdio: 'ignore',\n windowsHide: true,\n })\n\n child.unref()\n}\n\n/** `dist/update-check.js`, resolved beside this bundle exactly as `mcp.ts` resolves `dist/mcp.js`. */\nconst defaultChildPath = (): string => {\n return fileURLToPath(new URL('./update-check.js', import.meta.url))\n}\n\nconst defaultSelfRealPath = (): string => {\n return fs.realpathSync(fileURLToPath(new URL('./cli.js', import.meta.url)))\n}\n\n/**\n * The cache is never executed \u2014 only printed \u2014 but it is still attacker-shaped input if anything with\n * the user's uid can write it. An unconstrained token could carry ANSI escapes that rewrite the terminal\n * around the \"Run: \u2026\" line, turning it into a convincing copy-paste trap. Package-manager argv is drawn\n * from a static table, so a conservative charset costs nothing and closes that.\n */\nconst SAFE_COMMAND_TOKEN = /^[\\w@./+-]+$/i\n\nconst isSafeCommandToken = (token: string): boolean => {\n return SAFE_COMMAND_TOKEN.test(token)\n}\n\n/**\n * Print the pending notice for installs the worker was not allowed to touch (Homebrew, or a location it\n * could not identify). Those users get the one command that will actually work; running a guessed global\n * install on their behalf is worse than printing one.\n *\n * The verdict is READ from the cache, never recomputed: `detectInstallManager` may shell out to\n * `npm root -g`, and the CLI startup path must never pay for a subprocess. A null `updateCommand` means\n * the worker either installs it silently after we exit, or there is nothing to do.\n *\n * `logger` is pino with `destination: 2` \u2014 stderr. Nothing here may reach stdout.\n */\nconst notifyIfUninstallable = (\n cache: UpdateCache | null,\n currentVersion: string,\n notify: (line: string) => void,\n): void => {\n if (!cache?.latestVersion || !cache.updateCommand) return\n if (!isNewerVersion(cache.latestVersion, currentVersion)) return\n if (!cache.updateCommand.every(isSafeCommandToken)) return\n\n notify(\n `infra-kit ${cache.latestVersion} is available (you have ${currentVersion}). Run: ${cache.updateCommand.join(' ')}`,\n )\n}\n\n/**\n * Best-effort. Advisory and background only: it must never throw, never block, and never change the\n * exit code \u2014 a failure to update is not a failure of the command the user actually ran.\n *\n * @example\n * maybeAutoUpdate('0.1.130')\n */\nexport const maybeAutoUpdate = (currentVersion: string, deps: AutoUpdateDeps = {}): void => {\n try {\n const argv = deps.argv ?? process.argv\n const env = deps.env ?? process.env\n const isTty = deps.isTty ?? Boolean(process.stdout.isTTY)\n const cwd = deps.cwd ?? process.cwd()\n const nowMs = deps.nowMs ?? Date.now()\n const selfRealPath = deps.selfRealPath ?? defaultSelfRealPath()\n const readCache = deps.readCache ?? readUpdateCache\n const spawnChild = deps.spawnChild ?? defaultSpawnChild\n const fileExists =\n deps.fileExists ??\n ((p: string) => {\n return fs.existsSync(p)\n })\n const notify =\n deps.notify ??\n ((line: string) => {\n logger.info(line)\n })\n\n if (autoUpdateSkipReason({ argv, env, isTty, selfRealPath, cwd, realpath: safeRealpath }) !== null) return\n\n const cache = readCache()\n\n notifyIfUninstallable(cache, currentVersion, notify)\n\n if (!isStale(cache, nowMs)) return\n\n const childPath = deps.childPath ?? defaultChildPath()\n\n // Absent when running from source (tsx/vitest) rather than a built `dist/`.\n if (!fileExists(childPath)) return\n\n spawnChild(childPath)\n } catch {\n // Advisory only \u2014 see the module doc.\n }\n}\n", "/**\n * Whether this invocation may run the background auto-update at all. Pure: every signal is injected,\n * so the whole matrix is table-tested without spawning or touching argv/env globals.\n */\nimport { isLocalNodeModulesInstall } from 'src/lib/install-manager'\nimport type { RealpathFn } from 'src/lib/install-manager'\n\n/** Set any of these to disable the auto-update entirely. `NO_UPDATE_NOTIFIER`/`CI` are ecosystem convention. */\nexport const OPT_OUT_ENV_VARS = ['INFRA_KIT_NO_AUTO_UPDATE', 'NO_UPDATE_NOTIFIER', 'CI'] as const\n\nexport interface AutoUpdateGuardInput {\n /** Full `process.argv`. */\n argv: string[]\n env: NodeJS.ProcessEnv\n /** `process.stdout.isTTY`. */\n isTty: boolean\n /** Realpath of the running `dist/cli.js`. */\n selfRealPath: string\n cwd: string\n realpath: RealpathFn\n}\n\n/**\n * Reasons to skip, in the order checked. Returned (rather than a bare boolean) so tests assert WHICH\n * guard fired \u2014 a test that only sees `true` cannot tell a working `mcp` guard from a working `CI` one.\n */\nexport type SkipReason = 'opt-out' | 'json' | 'own-command' | 'not-a-tty' | 'local-install'\n\n/**\n * Commands that must never trigger the background updater.\n *\n * `mcp` hands its stdio to a child speaking JSON-RPC. `self-update` already performs the update itself:\n * without this, `ik self-update` would install interactively AND leave behind a detached worker that\n * waits for it to exit and then installs the very same `@latest` a second time.\n */\nconst SELF_MANAGING_COMMANDS = new Set(['mcp', 'self-update'])\n\n/**\n * Positional `argv[2]` mirrors the existing `warnIfLocalInstall` guard and is correct today because the\n * program registers no global options before the subcommand. It is defence-in-depth regardless: the\n * notice goes to stderr, and the `isTty` guard already rejects the MCP server (its stdout is a pipe).\n *\n * NOTE: adding a global `program.option()` would let a flag occupy argv[2] and silently defeat this arm.\n * The stderr + isTty arms are what actually guarantee framing safety.\n */\nconst isSelfManagingCommand = (argv: string[]): boolean => {\n return argv[2] != null && SELF_MANAGING_COMMANDS.has(argv[2])\n}\n\n/**\n * Why this invocation must not auto-update, or null when it may.\n *\n * `local-install` is the subtle one: a repo that pins infra-kit in its `devDependencies` has deliberately\n * chosen a version. Silently upgrading the user's GLOBAL install because their project-local copy is old\n * would be both useless (the project keeps using its pinned copy) and rude.\n *\n * @example\n * autoUpdateSkipReason({ argv: ['node', 'cli.js', 'mcp'], env: {}, isTty: true, selfRealPath: '/g/cli.js', cwd: '/p', realpath: (p) => p })\n * // => 'own-command'\n */\nexport const autoUpdateSkipReason = (input: AutoUpdateGuardInput): SkipReason | null => {\n const { argv, env, isTty, selfRealPath, cwd, realpath } = input\n\n const optedOut = OPT_OUT_ENV_VARS.some((name) => {\n const value = env[name]\n\n return value != null && value !== ''\n })\n\n if (optedOut) return 'opt-out'\n if (argv.includes('--json')) return 'json'\n if (isSelfManagingCommand(argv)) return 'own-command'\n // Never nag or mutate for piped/scripted runs; a human must be present to see the outcome.\n if (!isTty) return 'not-a-tty'\n if (isLocalNodeModulesInstall(selfRealPath, cwd, realpath)) return 'local-install'\n\n return null\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["select", "Separator", "realpathSync", "process", "fileURLToPath", "resolveLeaf", "roots", "groupPath", "level", "found", "token", "command", "buildPaletteItems", "commands", "MENU_GROUPS", "key", "label", "getMenuGroupEntries", "entry", "Command", "process", "process", "$", "configPath", "paths", "getInfraKitConfigPaths", "rows", "row", "fileExists", "userProjectExists", "summary", "readOverrideSummary", "note", "describeOverrides", "logger", "marker", "suffix", "tildify", "structuredContent", "r", "configEdit", "editor", "process", "seed", "seedUserProjectConfig", "seedCreatedMessage", "$", "resetInfraKitConfigCache", "parseAuthFailureMarker", "value", "config", "reason", "at", "buildAuthFailureWarning", "fs", "path", "process", "WARN_FAIL_SENTINEL_FILE", "WARN_AUTH_FAIL_SENTINEL_FILE", "FAIL_SENTINEL_FILE", "AUTH_FAIL_MARKER_FILE", "FAIL_BACKOFF_MS", "resolveEnvAutoLoad", "canWarn", "config", "getInfraKitConfig", "autoLoad", "resolved", "envTokenExists", "recordAuthFailure", "warnOnce", "buildAuthFailureWarning", "decideAutoLoad", "input", "trigger", "expectedTrigger", "targetConfig", "targetProject", "env", "force", "runEnvAutoLoad", "projectDir", "isAuthFailure", "isEnvAuthFailure", "surfaceStickyAuthFailure", "readAutoLoadEnvSnapshot", "isClearedOnDisk", "recentlyFailed", "preWriteMtime", "readLoadFileMtime", "result", "writeEnvLoadFile", "manualLoadLandedSince", "clearFailure", "clearAuthFailure", "error", "reason", "authFailed", "recordFailure", "logger", "marker", "readAuthFailure", "raw", "fs", "path", "getSessionCacheDir", "parseAuthFailureMarker", "dir", "atomicWriteFileSync", "process", "INFRA_KIT_SESSION_VAR", "INFRA_KIT_ENV_CLEARED_VAR", "INFRA_KIT_ENV_CONFIG_VAR", "INFRA_KIT_ENV_PROJECT_VAR", "INFRA_KIT_ENV_AUTOLOADED_VAR", "ENV_LOAD_FILE", "sinceMtime", "p", "mtime", "clearPath", "ENV_CLEAR_FILE", "loadPath", "flagPath", "message", "sentinelFile", "envAutoload", "projectDir", "runEnvAutoLoad", "DOPPLER_DASHBOARD_URL", "buildRevokeNotice", "project", "env", "envTokenRemove", "existed", "readTokenStore", "removeToken", "purged", "purgeRepoWarmCaches", "getDopplerProject", "storePath", "getTokenStorePath", "logger", "tildify", "line", "structuredContent", "textContent", "password", "process", "$", "readCandidateToken", "stdin", "fromEnv", "readStdin", "token", "process", "commandEcho", "withEscape", "context", "password", "chunks", "chunk", "DOPPLER_CONFIG_KEY", "PROBE_TIMEOUT_MS", "probeToken", "project", "env", "prevQuiet", "$", "stdout", "buildDopplerChildEnv", "error", "translateProbeFailure", "parseDopplerSecretsJson", "stderr", "extractStderr", "classifyDopplerFailure", "detail", "classifyDopplerAuthFailure", "buildUnverifiableScopeMessage", "envTokenSet", "force", "getDopplerProject", "source", "pairs", "assertTokenScope", "scopeVerified", "key", "setToken", "purged", "purgeRepoWarmCaches", "storePath", "getTokenStorePath", "logger", "redactToken", "tildify", "structuredContent", "textContent", "spawn", "process", "fileURLToPath", "FORWARDED_SIGNALS", "mcpServerPath", "fileURLToPath", "runMcp", "deps", "spawnFn", "spawn", "exit", "code", "process", "env", "onError", "message", "logger", "child", "withoutPackageManagerEnv", "error", "signal", "spawnSync", "realpathSync", "homedir", "process", "fileURLToPath", "defaultSelfRealPath", "realpathSync", "fileURLToPath", "exitFromResult", "result", "manager", "print", "exit", "runSelfUpdate", "dryRun", "deps", "spawn", "spawnSync", "line", "logger", "code", "process", "env", "selfRealPath", "lazyNpmRoot", "defaultLazyNpmRoot", "updateCommand", "canSelfSpawn", "detectInstallManager", "safeRealpath", "printable", "homedir", "packageManagerInstallEnv", "VENDOR_CONFIG_FILE", "fs", "path", "process", "pathToFileURL", "PLACEHOLDER_WORKSPACE_DIR", "vendorConfig", "options", "initFactoryConfig", "printFactoryConfig", "factoryPath", "getFactoryConfigPath", "exists", "fileExists", "logger", "tildify", "process", "workspaceDir", "targets", "loadFactoryConfig", "resolvedWorkspace", "expandTilde", "workspaceExists", "allReachable", "repo", "targetPath", "path", "reachable", "marker", "suffix", "cwd", "sourceRoot", "getProjectRoot", "seededTargets", "readLegacyTargets", "fs", "buildScaffold", "VENDOR_CONFIG_FILE", "configPath", "stat", "raw", "pathToFileURL", "resolved", "t", "equivalentLine", "line", "reproducible", "fs", "os", "path", "process", "SESSION_REPORT_ENV", "capturedReportPath", "captured", "summaryLines", "captureSessionReportPath", "env", "writeSessionReport", "record", "deps", "capturedReportPath", "summary", "summaryLines", "payload", "write", "file", "data", "fs", "reportCounter", "newReportPath", "tmp", "os", "pid", "process", "path", "readAndUnlinkReport", "reportPath", "read", "unlink", "raw", "collectReleaseSpec", "value", "prev", "parseRepos", "normalizeIdeMode", "flagName", "IDE_MODES", "configureMergeDev", "cmd", "options", "emit", "ghMergeDev", "configureReleaseList", "ghReleaseList", "configureReleaseCreate", "inputs", "parseReleaseSpec", "releases", "releaseCreate", "configureReleaseDescEdit", "releaseDescEdit", "configureReleaseDeployAll", "ghReleaseDeployAll", "configureReleaseDeploySelected", "ghReleaseDeploySelected", "configureReleaseDeliver", "ghReleaseDeliver", "configureWorktreesSync", "worktreesSync", "configureWorktreesAdd", "ide", "worktreesAdd", "configureWorktreesList", "worktreesList", "configureWorktreesRemove", "worktreesRemove", "configureWorktreesReload", "worktreesReload", "configureVendorConfig", "vendorConfig", "configureVendorCheck", "result", "vendorCheck", "process", "configureVendorDiff", "vendorDiff", "configureConfigPath", "configPath", "configureConfigEdit", "configEdit", "AUTO_LOAD_EXCLUDED", "isAutoLoadExcludedCommand", "name", "SEED_EXCLUDED", "commandPath", "leaf", "parts", "node", "buildProgram", "program", "Command", "releaseGroup", "worktreesGroup", "configCmd", "audit", "vendorCmd", "vendorSync", "vendorManifest", "doctor", "runSelfUpdate", "runMcp", "preset", "runDevServerCli", "tty", "jsonOutput", "version", "envStatus", "envList", "init", "envLoad", "envClear", "env", "envTokenSet", "envTokenList", "envTokenRemove", "envAutoload", "addJsonOption", "_thisCommand", "actionCommand", "commandEcho", "logger", "ensureUserProjectConfig", "surfaceStickyAuthFailure", "runEnvAutoLoad", "path", "isLongRunningCommand", "flags", "suffix", "line", "writeSessionReport", "equivalentLine", "formatAlignedRows", "rows", "gap", "width", "max", "left", "right", "chalkStderr", "nodeSpawn", "process", "Chalk", "ansi", "T", "GLYPHS", "RULE", "MIN_RULE_WIDTH", "plain", "text", "OUTCOME_COLOR", "chromeStyler", "color", "outcomeStyler", "outcome", "cellWidth", "formatRunHeader", "line", "opts", "chrome", "formatDuration", "durationMs", "labelFor", "findingsCount", "ruleSuffix", "statusWidth", "width", "ruleChar", "dim", "ruleWidth", "formatTranscriptEntry", "input", "verdictColor", "ascii", "prefix", "glyph", "label", "detail", "firstSummary", "verdict", "trailer", "rule", "lines", "SIGNAL_STOP_EXIT_CODES", "classifyOutcome", "exitCode", "signal", "reportPresent", "longRunning", "process", "ANSI", "resetTerminal", "opts", "deps", "write", "text", "stdin", "escapes", "commandLine", "command", "stderrColumns", "columns", "process", "waitForExit", "child", "resolve", "code", "signal", "runOne", "deps", "markChildOwnsSigint", "reportPath", "newReportPath", "childEnv", "SESSION_REPORT_ENV", "start", "result", "record", "readAndUnlinkReport", "outcome", "classifyOutcome", "headerLine", "equivalent", "equivalentLine", "formatTranscriptEntry", "installSessionSignals", "isChildRunning", "sigintSeenDuringChild", "quitRequested", "onSigint", "onSigterm", "onSighup", "onSigtstp", "sessionGateEnabled", "env", "streams", "runSession", "items", "resolved", "nodeSpawn", "chalkStderr", "opts", "resetTerminal", "write", "text", "signals", "handler", "childOwnsSigint", "noopSignals", "sessionSignals", "selected", "header", "formatRunHeader", "entry", "spawn", "fs", "process", "fileURLToPath", "OPT_OUT_ENV_VARS", "SELF_MANAGING_COMMANDS", "isSelfManagingCommand", "argv", "autoUpdateSkipReason", "input", "env", "isTty", "selfRealPath", "cwd", "realpath", "name", "value", "isLocalNodeModulesInstall", "defaultSpawnChild", "childPath", "spawn", "process", "defaultChildPath", "fileURLToPath", "defaultSelfRealPath", "fs", "SAFE_COMMAND_TOKEN", "isSafeCommandToken", "token", "notifyIfUninstallable", "cache", "currentVersion", "notify", "isNewerVersion", "maybeAutoUpdate", "deps", "argv", "env", "isTty", "cwd", "nowMs", "selfRealPath", "readCache", "readUpdateCache", "spawnChild", "fileExists", "p", "line", "logger", "autoUpdateSkipReason", "safeRealpath", "isStale", "suppressTypelessPackageJsonWarning", "captureSessionReportPath", "program", "buildProgram", "runProgram", "argv", "error", "isPromptCancellation", "logger", "process", "message", "warnIfLocalInstall", "isLocalNodeModulesInstall", "realpathSync", "fileURLToPath", "safeRealpath", "maybeAutoUpdate", "package_default", "buildPaletteItems", "runInteractiveMenu", "paletteItems", "selected", "runCommandPalette", "alignedLabels", "formatAlignedRows", "item", "choices", "index", "Separator", "withEscape", "context", "select", "runSessionShell", "cliPath", "catalogByName", "commandCatalog", "entry", "runSession", "name", "streams", "sessionGateEnabled"]
7
7
  }