infra-kit 0.3.14 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/boot-AJLBGRED.js +2 -0
  2. package/dist/boot-AJLBGRED.js.map +7 -0
  3. package/dist/{chunk-LKKKDB6V.js → chunk-7POMR4QK.js} +1 -1
  4. package/dist/chunk-7POMR4QK.js.map +7 -0
  5. package/dist/chunk-D7QNBU5A.js +7 -0
  6. package/dist/chunk-D7QNBU5A.js.map +7 -0
  7. package/dist/{chunk-YDW6324A.js → chunk-NKXTDKWV.js} +2 -2
  8. package/dist/chunk-NKXTDKWV.js.map +7 -0
  9. package/dist/chunk-NS5KIBXS.js +2 -0
  10. package/dist/chunk-NS5KIBXS.js.map +7 -0
  11. package/dist/chunk-PVMF4FH7.js +10 -0
  12. package/dist/chunk-PVMF4FH7.js.map +7 -0
  13. package/dist/chunk-QRY6BS6K.js +249 -0
  14. package/dist/chunk-QRY6BS6K.js.map +7 -0
  15. package/dist/{chunk-X46DP23W.js → chunk-QWVLAZ6N.js} +1 -1
  16. package/dist/chunk-QWVLAZ6N.js.map +7 -0
  17. package/dist/chunk-TVG47UHG.js +2 -0
  18. package/dist/chunk-TVG47UHG.js.map +7 -0
  19. package/dist/{chunk-RVTI3AL2.js → chunk-VDBJ73LS.js} +5 -5
  20. package/dist/chunk-VDBJ73LS.js.map +7 -0
  21. package/dist/{chunk-TPVXMJ7N.js → chunk-WIZJQ57Q.js} +1 -1
  22. package/dist/chunk-WIZJQ57Q.js.map +7 -0
  23. package/dist/chunk-Z2SKAHP4.js +6 -0
  24. package/dist/chunk-Z2SKAHP4.js.map +7 -0
  25. package/dist/cli.js +12 -11
  26. package/dist/cli.js.map +4 -4
  27. package/dist/dev-server.js +24 -32
  28. package/dist/dev-server.js.map +4 -4
  29. package/dist/dev-wizard-run-YMH3L7GE.js +2 -0
  30. package/dist/{dev-wizard-run-XM5L23UP.js.map → dev-wizard-run-YMH3L7GE.js.map} +2 -2
  31. package/dist/mcp.js +1 -1
  32. package/dist/mcp.js.map +4 -4
  33. package/dist/persistent-ink-dev-ui-P65EJCC6.js +2 -0
  34. package/dist/persistent-ink-dev-ui-P65EJCC6.js.map +7 -0
  35. package/dist/safe-stderr-ECYA6DRR.js +2 -0
  36. package/dist/update-check.js +1 -1
  37. package/package.json +19 -16
  38. package/readme.md +15 -7
  39. package/dist/boot-OA6HGGO6.js +0 -2
  40. package/dist/boot-OA6HGGO6.js.map +0 -7
  41. package/dist/chunk-4EPUAY73.js +0 -2
  42. package/dist/chunk-4EPUAY73.js.map +0 -7
  43. package/dist/chunk-KSSGYYS2.js +0 -238
  44. package/dist/chunk-KSSGYYS2.js.map +0 -7
  45. package/dist/chunk-LKKKDB6V.js.map +0 -7
  46. package/dist/chunk-QLAQ7F4M.js +0 -7
  47. package/dist/chunk-QLAQ7F4M.js.map +0 -7
  48. package/dist/chunk-RVTI3AL2.js.map +0 -7
  49. package/dist/chunk-SHFPI4QA.js +0 -2
  50. package/dist/chunk-SHFPI4QA.js.map +0 -7
  51. package/dist/chunk-SSBYKOHY.js +0 -4
  52. package/dist/chunk-SSBYKOHY.js.map +0 -7
  53. package/dist/chunk-TPVXMJ7N.js.map +0 -7
  54. package/dist/chunk-X46DP23W.js.map +0 -7
  55. package/dist/chunk-YDW6324A.js.map +0 -7
  56. package/dist/dev-wizard-run-XM5L23UP.js +0 -2
  57. package/dist/persistent-ink-dev-ui-PLLSRSGL.js +0 -2
  58. package/dist/persistent-ink-dev-ui-PLLSRSGL.js.map +0 -7
  59. package/dist/safe-stderr-HXDR2DNJ.js +0 -2
  60. /package/dist/{safe-stderr-HXDR2DNJ.js.map → safe-stderr-ECYA6DRR.js.map} +0 -0
package/dist/cli.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 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 { 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 per throttle window 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 { configGet } from 'src/commands/config-get'\nimport { devStatus } from 'src/commands/dev-status'\nimport { doctor, printDoctorReport } 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 { localDeployAll, localDeploySelected } from 'src/commands/local-deploy'\nimport { runMcp } from 'src/commands/mcp'\nimport { releaseCreate } from 'src/commands/release-create'\nimport { releaseDescEdit } from 'src/commands/release-desc-edit'\nimport { reopen } from 'src/commands/reopen'\nimport { runSelfUpdate } from 'src/commands/self-update'\nimport { vendorCheck } from 'src/commands/vendor-check'\nimport { vendorConfig } from 'src/commands/vendor-config'\nimport { version } from 'src/commands/version'\nimport { worktreesAdd } from 'src/commands/worktrees-add'\nimport { worktreesList } from 'src/commands/worktrees-list'\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\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 configureReopen = (cmd: Command): Command => {\n return cmd\n .description('Reopen editor + cmux windows for every active worktree in the current project (additive, idempotent)')\n .option('--all', 'Reopen across every discovered infra-kit project (Stage 3 \u2014 not yet implemented)')\n .option('--project <names...>', 'Restrict --all to these project names')\n .option('--root <paths...>', 'Discovery roots for --all (repeatable)')\n .option('--release-only', 'Restrict to release worktrees (reproduces the legacy worktrees-reload scope)')\n .option('--force', 'Close each cmux workspace first, then reopen (the legacy worktrees-reload behaviour)')\n .option('--dry-run', 'Print the plan (paths + cmux titles) and spawn nothing')\n .action(async (options) => {\n emit(\n await reopen({\n all: options.all,\n project: options.project,\n root: options.root,\n releaseOnly: options.releaseOnly,\n force: options.force,\n dryRun: options.dryRun,\n }),\n )\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 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\n // Top-level (`infra-kit reopen`), not under the worktrees group \u2014 house rule: new commands go\n // top-level with related names, not nested under a group. It still renders in the Worktrees palette\n // group via its catalog menuGroup.\n configureReopen(program.command('reopen'))\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 // Top-level (`infra-kit config-get`), not under the config group \u2014 house rule: new commands go\n // top-level with related names. Read-only: prints the fully merged config.\n program\n .command('config-get')\n .description('Print the fully merged infra-kit config (project + user-global + per-project override layers)')\n .action(async () => {\n emit(await configGet())\n })\n\n // Deliberately mirrors the `release deploy-all` / `release deploy-selected` pair, so the only thing\n // the name changes is WHERE it runs: `local` executes the repo's own devops/scripts/deploy-*.sh on\n // this machine, `release` dispatches the GitHub workflow that runs the very same scripts.\n const localCmd = program.command('local').description('Run deploys on this machine instead of dispatching CI')\n\n const withLocalDeployOptions = (cmd: Command): Command => {\n return cmd\n .option('-e, --env <name>', 'Target environment (prompts when omitted)')\n .option('-y, --yes', 'Skip the confirmation prompt')\n .option('--dry-run', 'Resolve target, contract and commands without deploying')\n .option('--print-env', 'Print the resolved build contract')\n .option(\n '--skip-preflight <check...>',\n 'Waive a named check (clean-tree, toolchain) \u2014 never env/account, and never clean-tree for a protected env',\n )\n }\n\n withLocalDeployOptions(localCmd.command('deploy-all'))\n .description('Deploy every service enabled for the environment, from this machine')\n .action(async (options) => {\n emit(\n await localDeployAll({\n env: options.env,\n yes: options.yes,\n dryRun: options.dryRun,\n printEnv: options.printEnv,\n skipPreflight: options.skipPreflight,\n }),\n )\n })\n\n withLocalDeployOptions(localCmd.command('deploy-selected'))\n .description('Deploy chosen services from this machine (prompts when --service is omitted)')\n .option('-s, --service <name...>', 'Service name(s), as in deploy-<name>.sh')\n .action(async (options) => {\n emit(\n await localDeploySelected({\n env: options.env,\n service: options.service,\n yes: options.yes,\n dryRun: options.dryRun,\n printEnv: options.printEnv,\n skipPreflight: options.skipPreflight,\n }),\n )\n })\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 the mirrored vendor/ tree')\n\n configureVendorCheck(vendorCmd.command('check'))\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 .option('--ascii', 'Render the report with ASCII markers instead of unicode glyphs (check messages are unchanged)')\n .action(async (options) => {\n const result = await doctor({ fix: Boolean(options.fix) })\n\n // Presentation lives here, not in `doctor()`: the MCP tool shares that function and has no\n // terminal. Skipped entirely under `--json` so stdout carries the payload and nothing else.\n if (!jsonOutput.enabled) {\n printDoctorReport(result.structuredContent.checks, { ascii: Boolean(options.ascii) })\n }\n\n emit(result)\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 // Read-only companion to `dev`: reports what dev currently has running from the on-disk dev-context\n // fragments (never starts a server). Top-level per the house rule.\n program\n .command('dev-status')\n .description('Show what `infra-kit dev` currently has running (reads dev-context fragments; starts nothing)')\n .action(async () => {\n emit(await devStatus())\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, formatUpdateCommand, 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 = formatUpdateCommand(updateCommand)\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 throttled 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 * Clock for the pnpm-relay window. A seam, not a convenience: the window is defined by elapsed time,\n * so a test that cannot move the clock could only assert it by sleeping.\n *\n * DELIBERATELY SEPARATE from the loop's `now` (which stamps transcript durations), even though both\n * default to `Date.now`. They want opposite things: a duration fake wants a FROZEN clock for a\n * deterministic footer, while this one reads a frozen clock as \"no time has passed\", which holds the\n * relay window permanently open and swallows every SIGTERM \u2014 the exact bug the window exists to fix.\n * Aliasing them made the seam fail OPEN. Keep them apart.\n */\n now: () => number\n /**\n * Terminal hygiene on the way out of a between-iterations exit, where the PALETTE \u2014 not a child \u2014\n * owns the screen. Ink is mid-render there: raw mode armed, cursor hidden, a frame on screen. An\n * `exit()` from a signal handler runs none of Ink's teardown, so without this the user's shell comes\n * back to a hidden cursor, a stranded frame and a tty that eats their keystrokes.\n *\n * Required, not optional: a silent default would hand a fake `signals` the real escapes and spray\n * them through the test reporter, which is precisely the accident this seam exists to prevent.\n */\n resetTerminal: () => 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 * How long after a Ctrl-C a SIGTERM may still be pnpm's relay of it.\n *\n * MEASURED, so the number is not a guess: driving a real `pnpm exec node` on a pty and pressing Ctrl-C,\n * the relayed SIGTERM landed **6ms and 16ms** after the SIGINT. A second is ~60x that \u2014 wide enough that\n * load will not push a relay outside it, narrow enough that a human's deliberate `kill` never lands\n * inside it. Beyond the window a SIGTERM has no keypress to belong to and is treated as the external\n * `kill` it is.\n *\n * That same measurement also showed the relay is NOT guaranteed: 2 of 4 runs produced no SIGTERM at all.\n * Which is exactly why this is a WINDOW and not a counter. The first cut licensed one swallow per Ctrl-C,\n * but only a relay ever spends a licence \u2014 so on every run without one (and on the direct bin, which\n * never relays) the licence was left unspent and a genuine `kill` seconds later was still swallowed.\n * That narrowed the original bug from unbounded to N rather than fixing it.\n *\n * If the window is ever wrong, the failure is a session that quits after the current child instead of\n * redrawing the palette \u2014 recoverable by rerunning `infra-kit`. It is deliberately the mild direction:\n * the opposite error is a shell that cannot be killed.\n */\nconst PNPM_RELAY_WINDOW_MS = 1_000\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 RECENTLY, for THIS\n // child. Proximity is the whole rule \u2014 see PNPM_RELAY_WINDOW_MS for why it is a timestamp and not the\n // flag (or the counter) this started as.\n let lastSigintAt: number | null = null\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 WHEN the Ctrl-C happened, so a\n // SIGTERM arriving right behind it can be recognised as pnpm's relay rather than an external kill.\n lastSigintAt = deps.now()\n\n return\n }\n\n // The palette owns the screen here, and this exit skips Ink's teardown entirely \u2014 see\n // `resetTerminal` on SessionSignalDeps.\n deps.resetTerminal()\n deps.exit(0)\n }\n const onSigterm = (): void => {\n if (!isChildRunning()) {\n // Same window, same reason as SIGINT above: an external `kill` can land mid-palette.\n deps.resetTerminal()\n deps.exit(0)\n\n return\n }\n\n if (lastSigintAt != null && deps.now() - lastSigintAt <= PNPM_RELAY_WINDOW_MS) {\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 lastSigintAt = null\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 // Its OWN clock, NOT `resolved.now` \u2014 see the field's doc on SessionSignalDeps. Sharing the loop's\n // seam meant a test freezing `now` for deterministic transcript durations silently pinned the relay\n // window open, so the seam failed open instead of closed.\n now: () => {\n return Date.now()\n },\n // Routed through `resolved` rather than the bare import so a test injecting\n // `deps.resetTerminal` still sees the signal path's resets.\n resetTerminal: () => {\n // Never the alternate screen: this fires while the PALETTE holds the terminal, and the palette\n // does not enter it. `?1049l` here would leave a buffer the session never entered.\n resolved.resetTerminal({ entersAltScreen: false })\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 { formatUpdateCommand, 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 plus a filesystem prefix, so a conservative charset costs nothing and closes that.\n *\n * The space is deliberate and is NOT a hole: a prefix path may legitimately contain one\n * (`--prefix /Users/Ada Lovelace/.nvm/...`), and dropping the whole notice over it would silence exactly\n * the users who most need it. `formatUpdateCommand` single-quotes such a token, which is safe precisely\n * because the quote characters themselves remain outside this charset.\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: ${formatUpdateCommand(cache.updateCommand)}`,\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", "formatUpdateCommand", "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", "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", "configureReopen", "reopen", "configureVendorConfig", "vendorConfig", "configureVendorCheck", "result", "vendorCheck", "process", "configureConfigPath", "configPath", "configureConfigEdit", "configEdit", "AUTO_LOAD_EXCLUDED", "isAutoLoadExcludedCommand", "name", "SEED_EXCLUDED", "commandPath", "leaf", "parts", "node", "buildProgram", "program", "Command", "releaseGroup", "worktreesGroup", "configCmd", "configGet", "localCmd", "withLocalDeployOptions", "localDeployAll", "localDeploySelected", "audit", "vendorCmd", "doctor", "jsonOutput", "printDoctorReport", "runSelfUpdate", "runMcp", "preset", "runDevServerCli", "tty", "devStatus", "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", "PNPM_RELAY_WINDOW_MS", "installSessionSignals", "isChildRunning", "lastSigintAt", "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", "formatUpdateCommand", "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"]
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/release-deploy/release-deploy.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/post-run-pause.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 { 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 per throttle window 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 { configGet } from 'src/commands/config-get'\nimport { devStatus } from 'src/commands/dev-status'\nimport { doctor, printDoctorReport } 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 { withRunCleanup } from 'src/commands/gh-merge-dev/run-cleanup'\nimport { ghReleaseDeliver } from 'src/commands/gh-release-deliver'\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 { deprecatedLocalDeploy, releaseDeployAll, releaseDeploySelected } from 'src/commands/release-deploy'\nimport { releaseDescEdit } from 'src/commands/release-desc-edit'\nimport { reopen } from 'src/commands/reopen'\nimport { runSelfUpdate } from 'src/commands/self-update'\nimport { vendorCheck } from 'src/commands/vendor-check'\nimport { vendorConfig } from 'src/commands/vendor-config'\nimport { version } from 'src/commands/version'\nimport { worktreesAdd } from 'src/commands/worktrees-add'\nimport { worktreesList } from 'src/commands/worktrees-list'\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 { DEPLOY_SOURCES } from 'src/lib/release-deploy'\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\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('-v, --versions <versions>', 'Specify versions by comma, e.g. 1.2.5, 1.2.6')\n .option('-y, --yes', 'Skip confirmation prompt')\n .option('--dry-run', 'Compute and print the merge plan without pushing anything')\n .option(\n '--verify [command]',\n 'Check each merge before pushing it. Bare runs `pnpm install --frozen-lockfile`; pass a command to run that instead. A branch that fails is dropped from the push, never rolled back',\n )\n .action(async (options) => {\n // The signal guard is installed HERE, on the CLI path, and nowhere else:\n // `ghMergeDev` is also the MCP tool handler inside a long-lived server,\n // where a per-invocation handler calling process.exit would preempt the\n // host's shutdown and take the server down on a stray signal.\n const result = await withRunCleanup(() => {\n return ghMergeDev({\n all: options.all,\n versions: options.versions,\n dryRun: options.dryRun,\n verify: options.verify,\n confirmedCommand: options.yes,\n })\n })\n\n emit(result)\n\n // A partial run used to exit 0, so no CI step or wrapper script could tell\n // \"merged all six\" from \"merged two, gave up on four\". Same shape as\n // `audit` and `vendor check` below: the action owns the exit code, `emit`\n // never touches it.\n if (result.structuredContent.failedMerges > 0) {\n process.exitCode = 1\n }\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\n/**\n * The merged deploy option set. `--from` carries NO default on purpose: it decides whether a reviewed\n * release ref or the current working tree ships, and a default would silently re-create the ambiguity\n * the flag exists to remove. Flags belonging to the other source are refused, not ignored\n * (`assertFlagsMatchSource`) \u2014 a silently dropped `--skip-terraform` reads as \"terraform was skipped\".\n */\nconst withDeploySourceOptions = (cmd: Command): Command => {\n return cmd\n .option('-f, --from <where>', `Where the deploy runs: ${DEPLOY_SOURCES.join(' | ')} (required)`)\n .option(\n '-v, --version <version>',\n 'With --from ci: version (e.g. 1.2.5) or release name to deploy; \"dev\" deploys from the dev branch. Prompts when omitted',\n )\n .option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')\n .option('-y, --yes', 'Skip confirmation prompt')\n .option('--skip-terraform', 'With --from ci: skip the terraform deployment step')\n .option('--dry-run', 'With --from local: resolve target, contract and commands without deploying')\n .option('--print-env', 'With --from local: print the resolved build contract')\n}\n\nconst configureReleaseDeployAll = (cmd: Command): Command => {\n return withDeploySourceOptions(cmd)\n .description('Deploy every service to any environment, in CI or from this machine')\n .action(async (options) => {\n emit(\n await releaseDeployAll({\n from: options.from,\n version: options.version,\n env: options.env,\n skipTerraform: options.skipTerraform,\n yes: options.yes,\n dryRun: options.dryRun,\n printEnv: options.printEnv,\n }),\n )\n })\n}\n\nconst configureReleaseDeploySelected = (cmd: Command): Command => {\n return withDeploySourceOptions(cmd)\n .description('Deploy selected services to any environment, in CI or from this machine')\n .option('-s, --services <services...>', 'Specify services to deploy, e.g. client-be client-fe')\n .action(async (options) => {\n emit(\n await releaseDeploySelected({\n from: options.from,\n version: options.version,\n env: options.env,\n services: options.services,\n skipTerraform: options.skipTerraform,\n yes: options.yes,\n dryRun: options.dryRun,\n printEnv: options.printEnv,\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 configureReopen = (cmd: Command): Command => {\n return cmd\n .description('Reopen editor + cmux windows for every active worktree in the current project (additive, idempotent)')\n .option('--all', 'Reopen across every discovered infra-kit project (Stage 3 \u2014 not yet implemented)')\n .option('--project <names...>', 'Restrict --all to these project names')\n .option('--root <paths...>', 'Discovery roots for --all (repeatable)')\n .option('--release-only', 'Restrict to release worktrees (reproduces the legacy worktrees-reload scope)')\n .option('--force', 'Close each cmux workspace first, then reopen (the legacy worktrees-reload behaviour)')\n .option('--dry-run', 'Print the plan (paths + cmux titles) and spawn nothing')\n .action(async (options) => {\n emit(\n await reopen({\n all: options.all,\n project: options.project,\n root: options.root,\n releaseOnly: options.releaseOnly,\n force: options.force,\n dryRun: options.dryRun,\n }),\n )\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 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\n // Top-level (`infra-kit reopen`), not under the worktrees group \u2014 house rule: new commands go\n // top-level with related names, not nested under a group. It still renders in the Worktrees palette\n // group via its catalog menuGroup.\n configureReopen(program.command('reopen'))\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 // Top-level (`infra-kit config-get`), not under the config group \u2014 house rule: new commands go\n // top-level with related names. Read-only: prints the fully merged config.\n program\n .command('config-get')\n .description('Print the fully merged infra-kit config (project + user-global + per-project override layers)')\n .action(async () => {\n emit(await configGet())\n })\n\n // DEPRECATED. The CI/local choice moved onto `release deploy-* --from`, so it is stated on every\n // invocation instead of being carried by which command group you typed. These stay one release so\n // muscle memory does not simply fail, are hidden from the palette (`menuGroup: null` in the catalog),\n // and warn on every run.\n const localCmd = program\n .command('local')\n .description('Deprecated \u2014 use `release deploy-all|deploy-selected --from local`')\n\n const withDeprecatedLocalOptions = (cmd: Command): Command => {\n return cmd\n .option('-e, --env <name>', 'Target environment (prompts when omitted)')\n .option('-y, --yes', 'Skip the confirmation prompt')\n .option('--dry-run', 'Resolve target, contract and commands without deploying')\n .option('--print-env', 'Print the resolved build contract')\n }\n\n withDeprecatedLocalOptions(localCmd.command('deploy-all'))\n .description('Deprecated \u2014 use `release deploy-all --from local`')\n .action(async (options) => {\n emit(\n await deprecatedLocalDeploy(\n {\n env: options.env,\n yes: options.yes,\n dryRun: options.dryRun,\n printEnv: options.printEnv,\n },\n 'all',\n ),\n )\n })\n\n withDeprecatedLocalOptions(localCmd.command('deploy-selected'))\n .description('Deprecated \u2014 use `release deploy-selected --from local`')\n .option('-s, --service <name...>', 'Service name(s), as in deploy-<name>.sh (prompts when omitted)')\n .action(async (options) => {\n emit(\n await deprecatedLocalDeploy(\n {\n env: options.env,\n services: options.service,\n yes: options.yes,\n dryRun: options.dryRun,\n printEnv: options.printEnv,\n },\n 'selected',\n ),\n )\n })\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 .option(\n '--fix',\n 'Write the infra-kit guidance block into CLAUDE.md for the audited scope before checking (CLI-only)',\n )\n .option('--design', 'With --fix: scaffold DESIGN.md for frontend/mobile packages that lack one')\n .action(async (options) => {\n // Commander expresses no flag dependency natively, so the combination is validated by hand.\n // This is an ERROR rather than a warning on purpose: a user who typed `--design` asked for a\n // file to be written, and a warning printed above a green audit reads as success.\n if (options.design && !options.fix) {\n logger.error('--design requires --fix (it scaffolds DESIGN.md; there is nothing to scaffold without a fix run)')\n process.exitCode = 1\n\n return\n }\n\n const result = await audit({\n all: options.all,\n root: options.root,\n fix: options.fix,\n design: options.design,\n })\n\n emit(result)\n\n // This action is the SOLE carrier of the fix-write-failure signal: `audit()` never touches\n // the exit code, so the MCP tool can reuse it. The second clause is not redundant \u2014 before\n // adoption a `missing` block PASSES, so a fix run whose only write failed would otherwise\n // report a green audit and exit 0.\n const { allPassed, fixed } = result.structuredContent\n\n const anyWriteFailed = (fixed ?? []).some((entry) => {\n return entry.action === 'failed'\n })\n\n if (!allPassed || anyWriteFailed) {\n process.exitCode = 1\n }\n })\n\n const vendorCmd = program.command('vendor').description('Verify the mirrored vendor/ tree')\n\n configureVendorCheck(vendorCmd.command('check'))\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 .option('--ascii', 'Render the report with ASCII markers instead of unicode glyphs (check messages are unchanged)')\n .action(async (options) => {\n const result = await doctor({ fix: Boolean(options.fix) })\n\n // Presentation lives here, not in `doctor()`: the MCP tool shares that function and has no\n // terminal. Skipped entirely under `--json` so stdout carries the payload and nothing else.\n if (!jsonOutput.enabled) {\n printDoctorReport(result.structuredContent.checks, { ascii: Boolean(options.ascii) })\n }\n\n emit(result)\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 // Read-only companion to `dev`: reports what dev currently has running from the on-disk dev-context\n // fragments (never starts a server). Top-level per the house rule.\n program\n .command('dev-status')\n .description('Show what `infra-kit dev` currently has running (reads dev-context fragments; starts nothing)')\n .action(async () => {\n emit(await devStatus())\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). Takes the ERROR, not its\n * text. 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// Why the error and not its text: env-autoload catches what `env-load` THREW, and by then\n// `translateDopplerDownloadError` has already replaced Doppler's raw stderr with a human-facing\n// message \u2014 so the raw markers this used to grep for were never there. That is exactly how a\n// revoked token shipped silent with green tests on both sides.\n//\n// The default is deliberately WIDER than `isDopplerAuthError`. Narrowing it back to \"Doppler\n// refused us\" re-opens the bug in its second form: a MISSING token (the migration case \u2014 the user\n// upgraded and never minted one) never reaches Doppler at all, and a CORRUPT store never reaches\n// the resolver, so neither would be auth-class, neither would write the sticky marker, and both\n// would go silent forever.\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 * 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 * @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 */\n// \"MISSING, INVALID, OR UNREADABLE\" spans the whole auth class on purpose. It covers the three\n// 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// The fix command is written in a BACKTICK code span, which is the repo convention for naming a\n// command to a human AND the anchor `program.test.ts` scans to prove every command we print is a\n// command we accept. Written without backticks it would be invisible to that guard and free to rot.\n// (Which is also why the @example names the command as `infra-kit env-token-set <env>` rather than\n// with a literal arg: to that scan an `<env>` placeholder ends the command, but a literal `dev` is\n// indistinguishable from a subcommand and reads as a dead one.)\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 * `canWarn` gates the WARNING only, never the STICKY MARKER.\n */\n// Validation lives here (not in the schema) so a typo only disables this optional feature instead\n// of throwing inside the merged-config parse and breaking every command. The Doppler project is\n// resolved from the SAME config read (no second getInfraKitConfig), so the skip path stays cheap.\n//\n// Why the marker is written even when we cannot warn: the shell-startup callsite runs backgrounded\n// with stderr discarded, so warning there is invisible \u2014 but a missing token is one of the three\n// durable auth-class failures buildAuthFailureWarning documents (alongside a revoked/mis-scoped\n// token and a corrupt store), so it is recorded unconditionally and replayed by\n// surfaceStickyAuthFailure on the next interactive command, the channel that actually reaches the\n// user. Skipping the record here would strand the migration case (upgraded, never minted a token)\n// with no channel at all.\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", "import { ghReleaseDeployAll } from 'src/commands/gh-release-deploy-all'\nimport { ghReleaseDeploySelected } from 'src/commands/gh-release-deploy-selected'\nimport { localDeployAll, localDeploySelected } from 'src/commands/local-deploy'\nimport { commandEcho } from 'src/lib/command-echo'\nimport { logger } from 'src/lib/logger'\nimport { assertFlagsMatchSource, resolveDeploySource } from 'src/lib/release-deploy'\n\nexport interface ReleaseDeployArgs {\n from?: string\n version?: string\n env?: string\n services?: string[]\n skipTerraform?: boolean\n yes?: boolean\n dryRun?: boolean\n printEnv?: boolean\n}\n\ntype Selection = 'all' | 'selected'\n\n/**\n * The shared body of `release deploy-all` and `release deploy-selected`.\n *\n * Both take `--from`, which decides where the deploy runs: `ci` dispatches the GitHub workflow for a\n * release ref, `local` runs `devops/scripts/deploy-*.sh` here against the current checkout. That used\n * to be encoded in which command group you typed (`release \u2026` vs `local \u2026`) \u2014 never restated at the\n * confirm prompt, and never stated at all once you were past the command name.\n *\n * What is deliberately NOT merged: the `-all` / `-selected` split. On the CI side those dispatch\n * different workflow files that declare DIFFERENT environment lists, so folding them into one\n * `--services`-present-or-absent flag would make the env picker's domain depend on that flag \u2014 and\n * would turn the palette's bare-path dispatch (which today opens a service checkbox) into a\n * deploy-everything.\n */\nconst runReleaseDeploy = async (args: ReleaseDeployArgs, selection: Selection, echoFrom = true) => {\n const { from, version, env, services, skipTerraform, yes, dryRun, printEnv } = args\n\n const source = await resolveDeploySource(from)\n\n // Echoed only on the commands that ACCEPT `--from`. When the runner was picked rather than typed,\n // the printed command is the only record of which one ran \u2014 but the deprecated `local \u2026` aliases\n // register no `--from`, so echoing it there produced a line that commander rejects with\n // `unknown option '--from'`. A printed command that cannot be re-run is worse than none.\n if (echoFrom) commandEcho.addOption('--from', source)\n\n assertFlagsMatchSource(source, {\n '--version': version,\n '--skip-terraform': skipTerraform,\n '--dry-run': dryRun,\n '--print-env': printEnv,\n })\n\n if (source === 'local') {\n // `--services` is the one service flag on the merged surface; the local entrypoints still take\n // `service`, which is what their MCP tools declare and must keep declaring.\n return selection === 'all'\n ? localDeployAll({ env, yes, dryRun, printEnv })\n : localDeploySelected({ env, service: services, yes, dryRun, printEnv })\n }\n\n return selection === 'all'\n ? ghReleaseDeployAll({ version, env, skipTerraform, confirmedCommand: yes })\n : ghReleaseDeploySelected({ version, env, services, skipTerraform, confirmedCommand: yes })\n}\n\n/** Deploy every service the environment accepts, from a release ref or from this working tree. */\nexport const releaseDeployAll = async (args: ReleaseDeployArgs) => {\n return runReleaseDeploy(args, 'all')\n}\n\n/** Deploy a chosen subset of services, from a release ref or from this working tree. */\nexport const releaseDeploySelected = async (args: ReleaseDeployArgs) => {\n return runReleaseDeploy(args, 'selected')\n}\n\n/**\n * Back-compat shim for `local deploy-all` / `local deploy-selected`.\n *\n * The old commands keep working for one release so a muscle-memory invocation does not simply fail,\n * but they are gone from the palette (`menuGroup: null`) and say so on every run. They pin\n * `--from local`, which is exactly what they always meant.\n */\nexport const deprecatedLocalDeploy = async (\n // Narrower than `Omit<\u2026, 'from'>`: the aliases register no `--version`/`--skip-terraform`, so\n // admitting them would describe a call no caller can make.\n args: Pick<ReleaseDeployArgs, 'env' | 'services' | 'yes' | 'dryRun' | 'printEnv'>,\n selection: Selection,\n) => {\n const replacement = `release deploy-${selection} --from local`\n\n logger.warn(`\\`local deploy-${selection}\\` is deprecated and will be removed \u2014 use \\`${replacement}\\` instead.`)\n\n // `echoFrom: false` \u2014 these aliases have no `--from` to print. They pin the runner internally.\n return runReleaseDeploy({ ...args, from: 'local' }, selection, false)\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, formatUpdateCommand, 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 = formatUpdateCommand(updateCommand)\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 { awaitPostRunKey } from './post-run-pause'\nimport type { PauseContext } from './post-run-pause'\nimport { SESSION_REPORT_ENV, newReportPath, readAndUnlinkReport } from './report'\nimport { resetTerminal } from './reset-terminal'\nimport { suspendForeground } from './suspend-foreground'\n\n/**\n * @fileoverview\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 pause until the user presses a key \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 * What happens between a finished command and the next palette draw. Resolves `'palette'` to\n * loop, or `'quit'` to end the session. Default: a raw single-keypress pause (see\n * `awaitPostRunKey`) on a TTY, and a no-op that resolves `'palette'` immediately anywhere else \u2014\n * a non-TTY stdin arms nothing and writes nothing.\n *\n * OBLIGATION \u2014 an implementation that BLOCKS on input MUST call `ctx.armed()`, which flips the\n * signal phase to `'pause'`. One that returns synchronously must not.\n */\n // WHY A PAUSE AT ALL \u2014 scrollback. The palette frame is nearly viewport-tall, so redrawing it the\n // instant a command finishes scrolls that command's own output off the screen before the user has\n // read a line of it. The framing this shell exists to provide (header above, footer below, the\n // child's output between) is only worth having if it stays legible, so the loop stops here and\n // waits.\n //\n // Blocking with the phase still `'child'` runs the whole wait under the child's signal policy:\n // every SIGINT swallowed, and every SIGTERM deferred into a `quitRequested` flag nobody reads\n // until after a keypress that a `kill` cannot supply \u2014 an unkillable shell. `armed()` is an\n // assignment, so it is idempotent and a defensive second call is free.\n afterRun?: (ctx: PauseContext) => Promise<'palette' | 'quit'>\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 refreshStderrSize()\n\n const columns: number | undefined = process.stderr.columns\n\n return columns != null && columns > 0 ? columns : undefined\n}\n\n/**\n * Re-read the terminal size from the kernel before every width read, via `_refreshSize()`.\n *\n * That method is underscore-prefixed and therefore not covered by node's semver contract, so it is\n * called through a `typeof` guard: on a non-tty stderr, or a node that renames it, the width simply\n * falls back to the cached value and the hint reverts to being stale rather than throwing.\n */\n// `process.stderr.columns` is a CACHED field. Node fills it once at startup and refreshes it from\n// SIGWINCH \u2014 and a stopped job is not in the foreground process group, so the resize that happens\n// between a Ctrl-Z and an `fg` is delivered to the user's SHELL and never to us. The cached value\n// then stays stale for the rest of the session. Measured on a pty: window 60 -> Ctrl-Z -> resize to\n// 33 -> `fg`, and both `columns` and `getWindowSize()` still report 60 (`getWindowSize` returns the\n// same cached pair, it is not an ioctl). The post-`fg` hint was therefore truncated to the OLD\n// width, wrapped across two rows at the new one, and the single-row erase left the first row behind\n// as a corpse \u2014 the exact failure the pause's fresh-read rule exists to prevent.\n//\n// `_refreshSize()` is the ioctl, and it is the only public-enough way to get one: it updates\n// `columns`/`rows` in place and emits `'resize'` on change. Measured, it reports 33 in the case\n// above.\nconst refreshStderrSize = (): void => {\n const stream = process.stderr as unknown as { _refreshSize?: () => void }\n\n if (typeof stream._refreshSize === 'function') stream._refreshSize()\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 (command: SessionCommand, deps: ResolvedDeps, enterChildPhase: () => void): 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 throttled 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 enterChildPhase()\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 leave the `'child'` phase. The child's `exit` has fired, but the terminal reset\n // below, the report read, and the footer + palette draw are all still ahead \u2014 and a user force-quitting\n // the child MASHES Ctrl-C, so more SIGINTs are still landing. Moving the phase on here made every one of\n // them take the `'palette'` branch and `exit(0)` the WHOLE SESSION SHELL instead of returning to the\n // palette. The loop moves it back 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 * Clock for the pnpm-relay window. A seam, not a convenience: the window is defined by elapsed time,\n * so a test that cannot move the clock could only assert it by sleeping.\n *\n * DELIBERATELY SEPARATE from the loop's `now` (which stamps transcript durations), even though both\n * default to `Date.now`. They want opposite things: a duration fake wants a FROZEN clock for a\n * deterministic footer, while this one reads a frozen clock as \"no time has passed\", which holds the\n * relay window permanently open and swallows every SIGTERM \u2014 the exact bug the window exists to fix.\n * Aliasing them made the seam fail OPEN. Keep them apart.\n */\n now: () => number\n /**\n * Terminal hygiene on the way out of a between-iterations exit, where the PALETTE \u2014 not a child \u2014\n * owns the screen. Ink is mid-render there: raw mode armed, cursor hidden, a frame on screen. An\n * `exit()` from a signal handler runs none of Ink's teardown, so without this the user's shell comes\n * back to a hidden cursor, a stranded frame and a tty that eats their keystrokes.\n *\n * Required, not optional: a silent default would hand a fake `signals` the real escapes and spray\n * them through the test reporter, which is precisely the accident this seam exists to prevent.\n */\n resetTerminal: () => 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 * Which part of the session owns the terminal right now. The signal owner's whole policy is keyed on it.\n *\n * - `'palette'` \u2014 Ink owns stdin and the screen; the session sits between commands.\n * - `'child'` \u2014 a spawned command owns the tty. DELIBERATELY WIDER than \"the child process is alive\": it\n * stays set through the terminal reset, the report read and the footer, and is only handed back at the\n * top of the next iteration (see the assignment there for the Ctrl-C-mash reasoning).\n * - `'pause'` \u2014 the post-run keypress pause: raw stdin, a single hint row, no Ink frame on screen.\n *\n * This replaced a `childOwnsSigint` boolean, which carried TWO meanings at once \u2014 \"swallow a stray SIGINT\"\n * and \"the tty has already stopped the foreground group, so follow it down\". Those two agree as long as\n * only a child and a palette exist, and they come apart for the first time inside the pause, which\n * swallows SIGINT but must NOT stop itself. `command-palette.tsx` ends its account of the same\n * distinction with \"DO NOT UNIFY THE TWO\"; a phase is how that stays un-unified here.\n */\nexport type SessionPhase = 'palette' | 'child' | 'pause'\n\n/**\n * How long after a Ctrl-C a SIGTERM may still be pnpm's relay of it. Beyond the window a SIGTERM\n * has no keypress to belong to and is treated as the external `kill` it is.\n */\n// MEASURED, so the number is not a guess: driving a real `pnpm exec node` on a pty and pressing\n// Ctrl-C, the relayed SIGTERM landed 6ms and 16ms after the SIGINT. A second is ~60x that \u2014 wide\n// enough that load will not push a relay outside it, narrow enough that a human's deliberate `kill`\n// never lands inside it.\n//\n// That same measurement also showed the relay is NOT guaranteed: 2 of 4 runs produced no SIGTERM at\n// all. Which is exactly why this is a WINDOW and not a counter. The first cut licensed one swallow\n// per Ctrl-C, but only a relay ever spends a licence \u2014 so on every run without one (and on the\n// direct bin, which never relays) the licence was left unspent and a genuine `kill` seconds later\n// was still swallowed. That narrowed the original bug from unbounded to N rather than fixing it.\n//\n// If the window is ever wrong, the failure is a session that quits after the current child instead\n// of redrawing the palette \u2014 recoverable by rerunning `infra-kit`. It is deliberately the mild\n// direction: the opposite error is a shell that cannot be killed.\nconst PNPM_RELAY_WINDOW_MS = 1_000\n\n/**\n * Is this SIGTERM close enough behind a Ctrl-C to be pnpm's relay of it, rather than an external `kill`?\n *\n * Hoisted to module scope so each handler stays one flat run of guards \u2014 the technique `CommandPalette`\n * uses for the same reason.\n */\nconst isPnpmRelay = (lastSigintAt: number | null, now: number): boolean => {\n return lastSigintAt != null && now - lastSigintAt <= PNPM_RELAY_WINDOW_MS\n}\n\n/**\n * End the session from inside a signal handler. The reset comes FIRST and is not optional: `exit` runs\n * none of Ink's teardown, so without it the user's shell comes back to a hidden cursor, a stranded frame\n * and a tty that eats their keystrokes. See `resetTerminal` on `SessionSignalDeps`.\n */\nconst exitSession = (deps: SessionSignalDeps): void => {\n deps.resetTerminal()\n deps.exit(0)\n}\n\n/**\n * Install the session's phase-aware signal owners. The child is spawned WITHOUT `detached`, so it\n * shares this process's group \u2014 every tty-delivered signal reaches the child directly, and the\n * parent's job is only to decide what IT does. That depends entirely on which phase owns the\n * terminal (`SessionPhase`): a child on a cooked tty, the palette's Ink frame, or the post-run\n * pause's raw hint row.\n *\n * - SIGINT \u2014 swallowed in BOTH `'child'` and `'pause'`, so the tty's Ctrl-C stops only the child\n * and the parent survives to render `\u2298 cancelled` and loop. In `'palette'` it ends the session.\n * - SIGTERM \u2014 DEFERRED while a child runs (finish it, commit its transcript, then end the session),\n * swallowed when it is pnpm's relay of a Ctrl-C, and an immediate exit in `'pause'` when it is not.\n * - SIGHUP \u2014 NEVER swallowed, in any phase: exit 129 with no reset.\n * - SIGTSTP \u2014 while a child runs we stop OURSELVES, so the whole foreground group suspends as a\n * unit; in `'palette'` and `'pause'` it is delivered and IGNORED.\n */\n// SIGINT. The two swallows differ in one respect. `'child'` RECORDS the moment (`lastSigintAt`) so\n// a SIGTERM arriving right behind it can be recognised as pnpm's relay. `'pause'` records NOTHING,\n// and that is the bound on the whole relay exemption: the pause blocks indefinitely by design, so a\n// recorded SIGINT there would let a repeated `kill -INT` extend the swallow window without limit\n// and hold the session immune to SIGTERM for as long as someone kept sending them. Not recording\n// pins the window to the last Ctrl-C delivered while a CHILD ran (`lastSigintAt` is otherwise\n// cleared only by `childStarted`), which bounds the exposure at one `PNPM_RELAY_WINDOW_MS` \u2014 after\n// which the very next SIGTERM is honoured.\n//\n// The tty cannot generate a SIGINT during `'pause'` at all (raw mode clears ISIG), so the sources\n// are an external `kill -INT` and \u2014 the case that matters \u2014 a tty SIGINT generated microseconds\n// earlier while the child still held a cooked terminal, whose handler runs at the next event-loop\n// boundary and so lands after the phase has flipped. That is the mashed-Ctrl-C case in signals\n// rather than bytes.\n//\n// SIGTERM is DEFERRED, not dropped, while a child runs: dropping it outright would make the session\n// unkillable by anything short of SIGKILL for as long as a child ran \u2014 and `dev` runs for hours.\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. (Causality, not a timing heuristic: the relay only\n// exists because of the Ctrl-C.) Without a handler at all, that relay was an instant unhandled\n// parent death \u2014 the shell prompt returned while the child still owned the tty and wrote teardown\n// output over it.\n//\n// `'pause'` KEEPS that relay test, because the relay lands there: it was measured at 6ms and 16ms\n// behind the SIGINT, and nothing suspends between the child's `exit` and the pause arming \u2014 the\n// reset, the report read, the formatter and the footer are all synchronous, and the one `await` in\n// the chain resolves as a microtask, which cannot interleave with a signal dispatched from libuv's\n// poll phase. So a fast child stopped with Ctrl-C has its relay arrive in `'pause'`, and routing\n// SIGTERM there straight to exit would kill the session on an ordinary Ctrl-C. What `'pause'`\n// changes is the OTHER half: a SIGTERM that FAILS the relay test exits IMMEDIATELY instead of\n// setting `quitRequested`, because there is no child left whose transcript needs committing and\n// nothing would read the flag until a keypress that a `kill` cannot supply.\n//\n// SIGHUP means the terminal is gone, so staying alive would leave the session looping on a dead\n// tty, drawing frames into a closed fd. There is nothing left to clean. The child gets its own\n// SIGHUP and is responsible for its own reap (see `dev/signal-shutdown.ts`).\n//\n// SIGTSTP. While a CHILD runs we STOP OURSELVES, because the tty already delivered SIGTSTP to the\n// whole foreground group: the child has stopped, and if the parent merely ignored the signal (as it\n// used to) it would sit in `waitForExit` awaiting an exit that can never come, wedging the terminal\n// with no prompt and no way back. Stopping too suspends the FOREGROUND GROUP as a unit, so the\n// 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\n// their own process groups, so a Ctrl-Z out of `dev` suspends `dev` itself while its servers stay\n// up and bound to their ports. Recoverable with `fg`, and strictly better than the wedge it\n// replaced, but \"suspended\" is not the whole truth for `dev`.\n//\n// In `'palette'` AND `'pause'` SIGTSTP is ignored because raw mode stops the tty from generating\n// one there, but an external `kill -TSTP` still arrives, and self-stopping would hand the user's\n// shell a raw tty with a stranded row (the pause's hint) or a half-drawn Ink frame (the palette).\n//\n// DO NOT UNIFY THIS WITH THE SIGINT ROW. A tty SIGTSTP means the whole group has already stopped\n// and we must follow it down; a keyboard Ctrl-Z at the pause is the raw byte `0x1a` read by the\n// pause itself, which drops raw mode before suspending. They look alike and are not the same event.\nexport const installSessionSignals = (getPhase: () => SessionPhase, deps: SessionSignalDeps): SessionSignals => {\n // Per-child signal history: a SIGTERM is pnpm's relay only if a SIGINT preceded it RECENTLY, for THIS\n // child. Proximity is the whole rule \u2014 see PNPM_RELAY_WINDOW_MS for why it is a timestamp and not the\n // flag (or the counter) this started as.\n let lastSigintAt: number | null = null\n let quitRequested = false\n\n const onSigint = (): void => {\n const phase = getPhase()\n\n if (phase === 'child') {\n // The child got its own copy from the tty; ours only records WHEN the Ctrl-C happened, so a\n // SIGTERM arriving right behind it can be recognised as pnpm's relay rather than an external kill.\n lastSigintAt = deps.now()\n\n return\n }\n\n if (phase === 'pause') {\n // Swallowed, and deliberately NOT recorded \u2014 recording here would let a repeated `kill -INT` extend\n // the relay-swallow window without bound. See the handler doc above.\n return\n }\n\n exitSession(deps)\n }\n const onSigterm = (): void => {\n const phase = getPhase()\n\n if (phase === 'palette') {\n // Same reason as SIGINT above: an external `kill` can land mid-palette, and Ink's teardown is skipped.\n exitSession(deps)\n\n return\n }\n\n if (isPnpmRelay(lastSigintAt, deps.now())) {\n // pnpm's relay of the Ctrl-C the user aimed at the child. Not a request to end the session \u2014 and it\n // frequently arrives after the child is already gone and the pause is armed.\n return\n }\n\n if (phase === 'pause') {\n // A genuine external `kill` with no child left to finish. Deferring it would wait for a keypress the\n // `kill` cannot supply, so end the session here.\n exitSession(deps)\n\n return\n }\n\n // A genuine external `kill` during a child. 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 (getPhase() !== 'child') {\n // Palette or pause: ignore, or we would suspend leaving a partial Ink frame \u2014 or a raw tty and a\n // stranded hint row \u2014 committed to the user's 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 lastSigintAt = null\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 * The platform authority on Ctrl-Z, decided ONCE at module scope rather than per pause.\n *\n * `undefined` means suspending is impossible: win32 has no SIGSTOP, so the pause drops the suspend\n * clause from its hint and reads `0x1a` as an ordinary key. See `suspendForeground` for the caller\n * preconditions the pause satisfies before it calls this.\n */\nconst SUSPEND_SEAM: (() => void) | undefined =\n process.platform === 'win32'\n ? undefined\n : () => {\n suspendForeground()\n }\n\n/**\n * The default `afterRun`: the raw single-keypress pause, wired to the same stderr seam, width function\n * and glyph/colour verdicts the transcript is framed with \u2014 so a resize between two commands changes the\n * hint exactly as it changes the footer's rule.\n *\n * Hoisted to module scope so `runSession` stays one flat run of dep resolution; `stdin` is deliberately\n * omitted, because the pause defaults it to `process.stdin` itself and that is the only place the real\n * stream should be named.\n */\nconst defaultAfterRun = (write: (text: string) => void, resolved: ResolvedDeps) => {\n return (ctx: PauseContext): Promise<'palette' | 'quit'> => {\n return awaitPostRunKey(ctx, {\n write,\n columns: resolved.columns,\n ascii: resolved.ascii,\n color: resolved.color,\n suspend: SUSPEND_SEAM,\n })\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 /**\n * Which phase owns the terminal right now \u2014 read by the signal handlers below on every delivery, and by\n * the seam's own reset. Declared BEFORE `signals` because that object's `resetTerminal` closure reads it.\n *\n * See `SessionPhase` for what each value covers and why this is a phase rather than the boolean it\n * replaced. `'child'` is set around the spawn; `'pause'` is set by the `afterRun` context below, on\n * behalf of whichever implementation of that seam is blocking on input.\n */\n let phase: SessionPhase = 'palette'\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 // Its OWN clock, NOT `resolved.now` \u2014 see the field's doc on SessionSignalDeps. Sharing the loop's\n // seam meant a test freezing `now` for deterministic transcript durations silently pinned the relay\n // window open, so the seam failed open instead of closed.\n now: () => {\n return Date.now()\n },\n // Routed through `resolved` rather than the bare import so a test injecting\n // `deps.resetTerminal` still sees the signal path's resets.\n resetTerminal: () => {\n if (phase === 'pause') {\n // Erase the pause's hint row before the reset, or the shell prompt draws at column 0 with the\n // hint's tail still to its right. PHASE-SCOPED on purpose: `\\u001B[2K` (EL) is erase-in-LINE, and a\n // stranded palette frame is multi-row by construction \u2014 blanking one row of six would make that\n // corpse worse, not better. It is also never on the post-child reset above, which must not wipe\n // the child's last output line.\n write('\\r\\u001B[2K')\n }\n\n // Never the alternate screen: this fires while the PALETTE (or the pause) holds the terminal, and\n // neither enters it. `?1049l` here would leave a buffer the session never entered.\n resolved.resetTerminal({ entersAltScreen: false })\n },\n }\n\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 phase\n }, signals)\n\n /**\n * Built HERE and passed on EVERY call \u2014 to the default implementation and to any injected one alike.\n * Closing the signal window is a loop invariant, not a property of one implementation: baking these\n * two callbacks privately into the default factory would make an injected `afterRun` silently wrong,\n * and would divorce every unit test from the behaviour it is meant to pin.\n */\n const pauseContext: PauseContext = {\n armed: () => {\n phase = 'pause'\n },\n quitRequested: () => {\n return sessionSignals.quitRequested()\n },\n }\n const afterRun = deps.afterRun ?? defaultAfterRun(write, resolved)\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 `'palette'`\n // branch would `exit(0)` the entire session. From this line on, the palette's Ink holds stdin in raw\n // mode (which disables ISIG), so a Ctrl-C there arrives as a `0x03` byte it handles itself \u2014 not as a\n // SIGINT at all.\n //\n // This is also where the loop comes back from `'pause'`: that phase drops raw mode before it\n // resolves, so the sequence into the palette is exactly the one below.\n phase = 'palette'\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 phase = 'child'\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 // AFTER the footer, and on every iteration that actually ran a command: the transcript block must\n // be complete on screen before we ask the user to read it. (The `!command` path above `continue`s\n // past this \u2014 nothing ran, so there is nothing to hold the screen for.)\n if ((await afterRun(pauseContext)) === 'quit') {\n return\n }\n }\n } finally {\n sessionSignals.dispose()\n }\n}\n", "/**\n * @fileoverview\n * Pure formatters for the lines the session shell itself owns \u2014 everything\n * that frames a run without ever touching the child's own output.\n *\n * The bulk of this file is the two-line transcript block a session leaves\n * behind in the scrollback once a command 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 *\n * The other formatter here, {@link formatPauseHint}, owns a different line \u2014\n * the dim hint the post-run pause writes into the blank row the footer's\n * trailing newline just opened. It shares this file for the same reason the\n * footer's rule does: both are lines the session shell draws around a child\n * it never touches, using the same `chromeStyler`/`cellWidth` machinery.\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/**\n * Copy for the post-run pause hint \u2014 deliberately NOT part of {@link T}, whose doc scopes it to \"the\n * transcript entry\" (see the block above): the pause hint is a different line, written after the\n * footer rather than as part of it, so it gets its own module-scope const.\n *\n * The `suspend` variant is offered only where suspending is actually possible (win32 has no\n * `SIGSTOP`); the `ascii` variant swaps the middle-dot separator for a hyphen, same words.\n */\nconst PAUSE_HINT: Record<'suspend' | 'plain', { unicode: string; ascii: string }> = {\n suspend: {\n unicode: 'any key commands \u00B7 Esc / Ctrl-C quit \u00B7 Ctrl-Z suspend',\n ascii: 'any key commands - Esc / Ctrl-C quit - Ctrl-Z suspend',\n },\n plain: {\n unicode: 'any key commands \u00B7 Esc / Ctrl-C quit',\n ascii: 'any key commands - Esc / Ctrl-C quit',\n },\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/**\n * Truncate `text` to `width - 1` cells, or return it untouched when `width` is `undefined`.\n *\n * The `undefined` case is its own branch rather than falling out of the arithmetic, mirroring\n * {@link ruleSuffix}'s `width == null` guard at the top of this file. `columns()` is\n * `() => number | undefined`, and callers normalise a missing or zero terminal width to\n * `undefined` \u2014 so `undefined - 1` is `NaN`, and every comparison against `NaN` is `false`. A\n * naive `cellWidth(text) > width - 1` would therefore silently never truncate, which reads as\n * \"it works\" right up until a real narrow terminal proves it doesn't.\n */\nconst truncateToWidth = (text: string, width: number | undefined): string => {\n if (width == null) return text\n\n const limit = Math.max(0, width - 1)\n\n if (cellWidth(text) <= limit) return text\n\n return [...text].slice(0, limit).join('')\n}\n\n/** Everything {@link formatPauseHint} needs to render the post-run pause's hint line. */\nexport interface PauseHintInput {\n /** Whether `Ctrl-Z suspend` is offered (platform-dependent: no `SIGSTOP` on win32). */\n canSuspend: boolean\n /** Use ASCII glyphs instead of unicode (for `!isTTY` / `TERM=dumb`). */\n ascii?: boolean\n /** Emit ANSI colour (default `false`). The caller owns TTY/`NO_COLOR` detection. */\n color?: boolean\n /** Terminal width in columns; the hint truncates to one cell short of it. `undefined` leaves it whole. */\n width?: number\n}\n\n/**\n * The dim hint the post-run pause writes into the blank row the footer's trailing newline just\n * opened: \"any key commands\", plus the ways out, plus `Ctrl-Z suspend` where suspending is\n * possible. No I/O and no trailing newline \u2014 the caller decides where it lands and how it is\n * erased (`\\r\\x1b[2K`, before anything else draws).\n *\n * @example\n * formatPauseHint({ canSuspend: true }) // => 'any key commands \u00B7 Esc / Ctrl-C quit \u00B7 Ctrl-Z suspend'\n */\nexport const formatPauseHint = (input: PauseHintInput): string => {\n const dim = chromeStyler(input.color === true).dim\n const variant = PAUSE_HINT[input.canSuspend ? 'suspend' : 'plain']\n const text = input.ascii === true ? variant.ascii : variant.unicode\n\n return dim(truncateToWidth(text, input.width))\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\nimport { acquireStdin, releaseStdin } from 'src/lib/prompts/stdin-ref'\n\nimport { formatPauseHint } from './format-entry'\n\n/**\n * @fileoverview\n * The one blocking keypress the session shell puts between a command's status footer and the next\n * palette draw, so the output the user just asked for stays on screen until they ask for the list\n * back. One dim hint row is written into the blank row the footer's trailing newline opened, one raw\n * key is read, the hint is erased, and control goes back to the loop. Esc / Ctrl-C / Ctrl-D quit the\n * session; Ctrl-Z suspends the job and comes back to the same pause; anything else reopens the\n * palette.\n *\n * **The stdin ref is what keeps node alive across the pause.** `acquireStdin` (lib/prompts/stdin-ref)\n * is the only sanctioned way to take it, and step 2 of the arm sequence is not optional: `resume()`\n * does not re-ref a handle that was explicitly unref'd, and the Ink teardown before us leaves stdin\n * unref'd and paused. Armed without the ref, a pause with a pending promise drains the event loop\n * and node EXITS WITH STATUS 0 about 150 ms into every command \u2014 measured on a pty, and status 0 is\n * the worst possible code because it is indistinguishable from a deliberate quit. `releaseStdin` is\n * therefore the LAST step of the disarm, symmetric with the acquire being the first of the arm.\n *\n * **The arm block and the suspend block each run with no `await` inside them.** Node dispatches\n * signal handlers and `data` events between event-loop turns, so a synchronous statement sequence has\n * no window at all: there is no instant at which the signal phase says `'pause'` while the tty is\n * still cooked, and none at which stdin is armed but unref'd. In the suspend block the same rule is\n * load-bearing for a second reason \u2014 the disarm releases the last reader and therefore unrefs the\n * handle, and only the re-acquire three statements later takes it back. Insert an `await` anywhere in\n * that window (a logging seam, a promise-returning \"flush before suspend\" write) and the loop drains\n * with nothing ref'd and node exits, which is the exit-0 defect above in a second location.\n *\n * **Resume from a suspend is `fg` only.** `bg` or a bare `kill -CONT` leaves us in a background\n * process group, where re-arming raw mode is a `tcsetattr` that raises SIGTTOU and stops the job\n * again, silently. Same as vim, same as Ink. Inherited from `suspendForeground`, whose preconditions\n * this module satisfies: raw mode dropped before the stop, cursor visible, cursor at column 0.\n *\n * **Non-goals, all of which fail toward the palette rather than toward quitting.** A coalesced\n * `Esc`+key chunk reads as one \"continue\", and so does a bracketed paste: the classifier's rule is\n * chunk length, the same rule `withEscape` uses (lib/prompts/escapable-context), and a lone `0x1b` is\n * the only Esc it recognises. And a resize while the hint is on screen is accepted damage: the\n * emulator reflows the row into two, the erase clears one, and the other survives as a dim corpse\n * once per resize. No fixed width is safe against an arbitrary narrowing, and a SIGWINCH redraw has\n * the defect `src/tui/safe-stderr.ts` records one layer down \u2014 it reasons about the new viewport with\n * the previous frame's geometry. The `fg` case is different and IS handled, because there the hint is\n * being rewritten anyway \u2014 one ioctl (`stderrColumns` in run-session refreshes the size, because a\n * stopped job never receives the resize) and the fresh `columns()` read is right.\n */\n\n/** What a raw chunk read at the pause means. */\nexport type PauseKey = 'quit' | 'suspend' | 'continue'\n\n/**\n * The only chunks that mean anything other than \"a key was pressed\". Every one is a single control\n * byte, because chunk length is the whole disambiguation rule: `\\x1b[A` (an arrow) and `\\x1bx`\n * (a coalesced Esc) are longer and therefore ordinary keys.\n */\nconst SINGLE_BYTE: Record<number, PauseKey> = {\n 0x03: 'quit',\n 0x04: 'quit',\n 0x1a: 'suspend',\n 0x1b: 'quit',\n}\n\n/**\n * Classify one raw stdin chunk. Total and platform-free: where suspending is impossible the CALLER\n * maps `'suspend'` to `'continue'`, mirroring how the palette takes its `onSuspend` as the platform\n * authority's verdict rather than inspecting `process`.\n *\n * Accepts a string OR a `Uint8Array`; both are production shapes.\n *\n * @example\n * classifyPauseKey(Uint8Array.from([0x1b])) // => 'quit'\n * classifyPauseKey('\\x1b') // => 'quit' (the production shape: Ink left stdin in utf8 mode)\n * classifyPauseKey(Uint8Array.from([0x1b, 0x5b, 0x41])) // => 'continue' (an arrow key)\n */\n// A CHUNK IS A STRING IN PRODUCTION, NOT A `Uint8Array`. Ink calls `stdin.setEncoding('utf8')` on\n// every `handleSetRawMode` (ink 7.1.1 `App.js:217`) and never clears it, so from the moment the\n// first palette mounts, `process.stdin` emits strings for the rest of the process. Typed as\n// `Uint8Array` only, this function read `chunk[0]` as the CHARACTER `'\\x1a'`, `SINGLE_BYTE` missed\n// on it, and every key at the pause \u2014 Esc, Ctrl-C, Ctrl-D, Ctrl-Z alike \u2014 classified as\n// `'continue'`: Esc redrew the palette instead of quitting and Ctrl-Z never suspended. Measured on\n// a pty under tmux (`scripts/qa/post-run-pause-pty.sh`, and directly: `setEncoding('utf8')` then\n// Ctrl-Z reports `typeof=string chunk[0]=\"\\x1a\"`). Both representations carry the same code for\n// every byte in `SINGLE_BYTE` \u2014 each is ASCII, so its UTF-8 encoding is one byte and its UTF-16\n// unit is that same number \u2014 which is why one lookup table serves both.\nexport const classifyPauseKey = (chunk: Uint8Array | string): PauseKey => {\n if (chunk.length !== 1) return 'continue'\n\n const code = typeof chunk === 'string' ? chunk.charCodeAt(0) : chunk[0]\n\n if (code === undefined || Number.isNaN(code)) return 'continue'\n\n return SINGLE_BYTE[code] ?? 'continue'\n}\n\n/**\n * How long every byte arriving at a freshly armed pause is discarded.\n *\n * A user stopping `dev` mashes Ctrl-C, and the kernel tty buffer can still hold those `0x03` bytes\n * when the pause arms microseconds after the child dies. Read naively, the first one would quit the\n * session \u2014 a regression on the most common flow in the shell.\n *\n * What it costs when wrong, in the style of `PNPM_RELAY_WINDOW_MS`: too short and a stale Ctrl-C\n * quits the session; too long and a deliberate keypress is swallowed and the user presses again. The\n * mild direction is LONGER. 150 ms sits below the threshold at which a delay reads as unresponsive,\n * which is why the hint is written after the drain rather than before it \u2014 a hint on screen means\n * keys are live, and a provisional hint would cost an extra write and an extra erase on the hottest\n * path in the shell.\n */\nexport const PAUSE_DRAIN_MS = 150\n\n/** `\\r` then erase-in-line: put the cursor at column 0 and wipe the hint row it opened. */\nconst ERASE_HINT = '\\r\\u001B[2K'\n\n/** The row break that guarantees a post-`fg` hint lands on a row the pause itself opened. */\nconst FRESH_ROW = '\\r\\n'\n\n/**\n * The stdin surface the pause needs, declared structurally so tests inject a fake instead of a real\n * tty. `process.stdin` satisfies it; the shape mirrors `ResetTerminalDeps.stdin`, one seam wider.\n */\nexport interface PauseStdin {\n isTTY?: boolean\n isRaw?: boolean\n setRawMode: (mode: boolean) => void\n resume: () => void\n pause: () => void\n isPaused: () => boolean\n on: (event: 'data', listener: (chunk: PauseChunk) => void) => void\n off: (event: 'data', listener: (chunk: PauseChunk) => void) => void\n}\n\n/**\n * What a `data` listener actually receives. A STRING once anything has called\n * `stdin.setEncoding('utf8')` \u2014 which Ink does on every raw-mode arm and never undoes \u2014 and a\n * `Uint8Array` before that. See {@link classifyPauseKey} for what mistaking one for the other cost.\n */\nexport type PauseChunk = Uint8Array | string\n\n/**\n * What `runSession` hands the pause so the signal owner and the pause agree on who holds the\n * terminal. Built by the loop and passed to EVERY implementation of the seam, default or injected \u2014\n * closing the signal window is a loop invariant, not a property of one implementation.\n */\nexport interface PauseContext {\n /**\n * Flip the signal phase to `'pause'`. Idempotent. An implementation that BLOCKS on input MUST call\n * this; one that returns synchronously must not. Blocking without it runs the whole pause with the\n * phase still `'child'`: every SIGINT swallowed, and every SIGTERM deferred into a `quitRequested`\n * flag nobody reads until after a keypress a `kill` cannot supply \u2014 an unkillable shell.\n */\n armed: () => void\n /** A SIGTERM that landed during the child, deferred. Read once, before anything is armed. */\n quitRequested: () => boolean\n}\n\n/** Everything the pause needs from its caller. */\nexport interface PostRunPauseDeps {\n /** Where the hint and the erase go \u2014 the same stderr seam the transcript uses. */\n write: (text: string) => void\n /** Terminal width, read FRESH at every hint write; `undefined` leaves the hint untruncated. */\n columns: () => number | undefined\n /** Use ASCII glyphs instead of unicode. */\n ascii: boolean\n /** Emit ANSI colour. The caller owns TTY / `NO_COLOR` detection. */\n color: boolean\n /**\n * The platform authority's verdict on Ctrl-Z. Absent means suspending is impossible (win32), and\n * `0x1a` then reads as an ordinary key: the hint drops its suspend clause and the pause resolves.\n */\n suspend?: () => void\n /** Injectable stdin, defaulting to `process.stdin`. */\n stdin?: PauseStdin\n /** Drain window override; production uses {@link PAUSE_DRAIN_MS}. */\n drainMs?: number\n}\n\n/**\n * Everything one arming owns, in one object so the arm/disarm/settle helpers can live at module\n * scope \u2014 the technique `CommandPalette` uses to keep each function one flat run of guards.\n */\ninterface PauseState {\n deps: PostRunPauseDeps\n stdin: PauseStdin\n /** The currently attached `data` listener, or null when nothing is attached. */\n listener: ((chunk: PauseChunk) => void) | null\n /** Whether a hint write has landed and is therefore owed exactly one erase. */\n hintOnScreen: boolean\n /** The once-only guard for {@link settle}. Deliberately NOT shared with `disarmPause`. */\n settled: boolean\n}\n\n/** Swap in a `data` listener, detaching whatever was there. */\nconst attachListener = (state: PauseState, listener: (chunk: PauseChunk) => void): void => {\n detachListener(state)\n state.stdin.on('data', listener)\n state.listener = listener\n}\n\n/** Detach the current `data` listener, if any. Repeatable. */\nconst detachListener = (state: PauseState): void => {\n if (state.listener === null) return\n\n state.stdin.off('data', state.listener)\n state.listener = null\n}\n\n/**\n * Take the terminal: ref stdin, arm raw mode, start the flow, attach a listener. Raw mode is armed\n * BEFORE the resume so no cooked-mode line lands between the two.\n *\n * Synchronous by contract \u2014 see the module doc. The caller flips the signal phase immediately after,\n * in the same block.\n */\nconst armPause = (state: PauseState, listener: (chunk: PauseChunk) => void): void => {\n acquireStdin()\n state.stdin.setRawMode(true)\n\n if (state.stdin.isPaused()) state.stdin.resume()\n\n attachListener(state, listener)\n}\n\n/**\n * Give the terminal back: erase the hint, detach the listener, drop raw mode, release the ref LAST.\n *\n * REPEATABLE, and paired with {@link armPause} \u2014 the suspend path calls it once per Ctrl-Z. It must\n * NOT share {@link settle}'s once-only guard: a shared guard is spent at the first suspend, and every\n * later exit becomes a no-op that leaves raw mode armed, the listener attached and stdin ref'd. On a\n * quit that is a hang; on a return to the palette, Ink boots on top of a raw, ref'd, flowing stdin.\n */\nconst disarmPause = (state: PauseState): void => {\n if (state.hintOnScreen) {\n state.hintOnScreen = false\n state.deps.write(ERASE_HINT)\n }\n\n detachListener(state)\n\n // Guarded exactly like `reset-terminal`: a stream that never had a tty has no `isRaw` to drop.\n if (state.stdin.isTTY === true && state.stdin.isRaw === true) state.stdin.setRawMode(false)\n\n // NO `stdin.pause()` HERE, and the reason is measured, not stylistic.\n //\n // `pause()` on `process.stdin` reaches libuv: it calls `_handle.readStop()` (the chain is spelled\n // out in `tui/boot.tsx`). A STOPPED handle is INACTIVE, and an inactive handle does not hold the\n // event loop open no matter how many times it is `ref()`d. Ink's mount refs stdin and attaches a\n // `readable` listener (ink 7.1.1 `App.js:225`, `:206`) \u2014 and that listener only restarts the read\n // when `_readableState.reading` is false. This pause reads in FLOWING mode (`on('data')`), where\n // `reading` stays true, so the restart never fires: the palette drew and then node exited 0 with\n // the frame still on screen, once per command. Measured on a pty; the same sequence with a\n // `readable`-mode reader survives, which is why `boot.tsx` may pause and this may not.\n //\n // `boot.tsx` still pauses on ITS teardown, before the session spawns a `stdio: 'inherit'` child, so\n // the \"parent must stop reading before the child starts\" guarantee is unchanged \u2014 nothing between\n // this line and that one spawns anything. The cost is a window of a few milliseconds, from here to\n // Ink's mount, in which the stream is flowing with no listener and a keystroke would be dropped.\n // Closing it means reading in `readable` mode here too, which is the real fix and a wider change\n // than this file.\n releaseStdin()\n}\n\n/**\n * The once-only exit. Runs from a `finally` around everything after the acquire, so a throw anywhere\n * \u2014 `setRawMode` on a stream that lost its tty, the `write` seam, a formatter bug \u2014 restores the\n * terminal before it propagates instead of leaving a session that eats keystrokes and never exits.\n */\nconst settle = (state: PauseState): void => {\n if (state.settled) return\n\n state.settled = true\n disarmPause(state)\n}\n\n/**\n * Format and write the hint from a FRESH `columns()` read, and record that one erase is now owed.\n *\n * The width is never snapshotted: the user may have resized while the job was stopped, and a stale\n * string on a narrowed terminal wraps to two rows of which the next erase clears only one. The flag\n * is set only after the write returns, so a write that throws owes no erase.\n */\nconst writeHint = (state: PauseState, prefix: string): void => {\n const hint = formatPauseHint({\n canSuspend: state.deps.suspend !== undefined,\n ascii: state.deps.ascii,\n color: state.deps.color,\n width: state.deps.columns(),\n })\n\n state.deps.write(prefix + hint)\n state.hintOnScreen = true\n}\n\n/** Discard everything for the drain window, then resolve. A timer, deliberately not an iteration. */\nconst drain = async (ms: number): Promise<void> => {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, ms)\n })\n}\n\n/**\n * Ctrl-Z: hand the terminal back, stop the whole foreground group, and take the terminal again on\n * `fg`. One synchronous statement sequence \u2014 `process.kill` is synchronous and no event-loop turn\n * happens while the process is stopped, so nothing is ref'd only for as long as the CPU takes to\n * reach the re-acquire.\n *\n * The leading `\\r\\n` on the post-resume hint is load-bearing. Between the stop and the `fg` the user\n * was in their own shell, so the cursor is at an unknown column on a row the SHELL owns; writing a\n * bare hint there overwrites their output and the next erase would wipe a row the pause never\n * created. The `\\r` is explicit because raw mode clears ONLCR, so a bare `\\n` keeps the column.\n */\nconst suspendAndRearm = (state: PauseState, suspend: () => void, listener: (chunk: PauseChunk) => void): void => {\n disarmPause(state)\n suspend()\n armPause(state, listener)\n writeHint(state, FRESH_ROW)\n}\n\n/**\n * Write the hint and block on one classified key. Ctrl-Z does not resolve \u2014 the same pause is still\n * there on return, exactly as the palette redraws itself with its filter intact.\n */\nconst readPauseKey = async (state: PauseState): Promise<'palette' | 'quit'> => {\n return new Promise<'palette' | 'quit'>((resolve) => {\n const onData = (chunk: PauseChunk): void => {\n const key = classifyPauseKey(chunk)\n const suspend = state.deps.suspend\n\n if (key === 'quit') {\n resolve('quit')\n\n return\n }\n\n if (key === 'suspend' && suspend !== undefined) {\n suspendAndRearm(state, suspend, onData)\n\n return\n }\n\n resolve('palette')\n }\n\n attachListener(state, onData)\n writeHint(state, '')\n })\n}\n\n/**\n * Hold the session shell at one keypress after a command finishes, and report where the loop goes\n * next. Resolves `'palette'` to redraw the command list, `'quit'` to leave the session.\n *\n * The sequence is fixed and every other doc in this module cites it rather than re-describing it:\n * read `quitRequested()`; ref stdin; arm raw mode; resume; attach a DISCARD listener; flip the signal\n * phase; drain for {@link PAUSE_DRAIN_MS}; swap in the classifying listener and write the hint; wait\n * for a key. Steps one through six are one synchronous block, and flipping the phase inside it rather\n * than after the drain is what makes the drain window signal-correct: SIGINT swallowed, SIGTSTP\n * ignored rather than self-stopping, SIGTERM answered.\n *\n * @example\n * const ctx = { armed: () => {}, quitRequested: () => true }\n * await awaitPostRunKey(ctx, deps) // => 'quit' (nothing armed, nothing written)\n */\nexport const awaitPostRunKey = async (ctx: PauseContext, deps: PostRunPauseDeps): Promise<'palette' | 'quit'> => {\n if (ctx.quitRequested()) return 'quit'\n\n const stdin = deps.stdin ?? process.stdin\n\n // No tty, no keypress to wait for. Matches `sessionGateEnabled`, which already refuses a non-TTY\n // session; arming here would ref a handle nobody can type into and hang the process.\n if (stdin.isTTY !== true) return 'palette'\n\n const state: PauseState = { deps, stdin, listener: null, hintOnScreen: false, settled: false }\n\n try {\n // ONE synchronous block, no `await` \u2014 see the module doc.\n armPause(state, () => {\n // The drain's listener: every byte aimed at the child that just died is discarded.\n })\n ctx.armed()\n\n await drain(deps.drainMs ?? PAUSE_DRAIN_MS)\n\n return await readPauseKey(state)\n } finally {\n settle(state)\n }\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 { formatUpdateCommand, 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 plus a filesystem prefix, so a conservative charset costs nothing and closes that.\n *\n * The space is deliberate and is NOT a hole: a prefix path may legitimately contain one\n * (`--prefix /Users/Ada Lovelace/.nvm/...`), and dropping the whole notice over it would silence exactly\n * the users who most need it. `formatUpdateCommand` single-quotes such a token, which is safe precisely\n * because the quote characters themselves remain outside this charset.\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: ${formatUpdateCommand(cache.updateCommand)}`,\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", "runReleaseDeploy", "args", "selection", "echoFrom", "from", "version", "env", "services", "skipTerraform", "yes", "dryRun", "printEnv", "source", "resolveDeploySource", "commandEcho", "assertFlagsMatchSource", "localDeployAll", "localDeploySelected", "ghReleaseDeployAll", "ghReleaseDeploySelected", "releaseDeployAll", "releaseDeploySelected", "deprecatedLocalDeploy", "replacement", "logger", "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", "formatUpdateCommand", "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", "normalizeIdeMode", "flagName", "IDE_MODES", "configureMergeDev", "cmd", "options", "result", "withRunCleanup", "ghMergeDev", "emit", "process", "configureReleaseList", "ghReleaseList", "configureReleaseCreate", "inputs", "parseReleaseSpec", "releases", "releaseCreate", "configureReleaseDescEdit", "releaseDescEdit", "withDeploySourceOptions", "DEPLOY_SOURCES", "configureReleaseDeployAll", "releaseDeployAll", "configureReleaseDeploySelected", "releaseDeploySelected", "configureReleaseDeliver", "ghReleaseDeliver", "configureWorktreesSync", "worktreesSync", "configureWorktreesAdd", "ide", "worktreesAdd", "configureWorktreesList", "worktreesList", "configureWorktreesRemove", "worktreesRemove", "configureReopen", "reopen", "configureVendorConfig", "vendorConfig", "configureVendorCheck", "vendorCheck", "configureConfigPath", "configPath", "configureConfigEdit", "configEdit", "AUTO_LOAD_EXCLUDED", "isAutoLoadExcludedCommand", "name", "SEED_EXCLUDED", "commandPath", "leaf", "parts", "node", "buildProgram", "program", "Command", "releaseGroup", "worktreesGroup", "configCmd", "configGet", "localCmd", "withDeprecatedLocalOptions", "deprecatedLocalDeploy", "logger", "audit", "allPassed", "fixed", "anyWriteFailed", "entry", "vendorCmd", "doctor", "jsonOutput", "printDoctorReport", "runSelfUpdate", "runMcp", "preset", "runDevServerCli", "tty", "devStatus", "version", "envStatus", "envList", "init", "envLoad", "envClear", "env", "envTokenSet", "envTokenList", "envTokenRemove", "envAutoload", "addJsonOption", "_thisCommand", "actionCommand", "commandEcho", "ensureUserProjectConfig", "surfaceStickyAuthFailure", "runEnvAutoLoad", "path", "isLongRunningCommand", "flags", "suffix", "line", "writeSessionReport", "equivalentLine", "formatAlignedRows", "rows", "gap", "width", "max", "left", "right", "chalkStderr", "nodeSpawn", "process", "Chalk", "ansi", "T", "PAUSE_HINT", "GLYPHS", "RULE", "MIN_RULE_WIDTH", "plain", "text", "OUTCOME_COLOR", "chromeStyler", "color", "outcomeStyler", "outcome", "cellWidth", "truncateToWidth", "width", "limit", "formatPauseHint", "input", "dim", "variant", "formatRunHeader", "line", "opts", "chrome", "formatDuration", "durationMs", "labelFor", "findingsCount", "ruleSuffix", "statusWidth", "ruleChar", "ruleWidth", "formatTranscriptEntry", "verdictColor", "ascii", "prefix", "glyph", "label", "detail", "firstSummary", "verdict", "trailer", "rule", "lines", "SIGNAL_STOP_EXIT_CODES", "classifyOutcome", "exitCode", "signal", "reportPresent", "longRunning", "process", "SINGLE_BYTE", "classifyPauseKey", "chunk", "code", "PAUSE_DRAIN_MS", "ERASE_HINT", "FRESH_ROW", "attachListener", "state", "listener", "detachListener", "armPause", "acquireStdin", "disarmPause", "releaseStdin", "settle", "writeHint", "prefix", "hint", "formatPauseHint", "drain", "ms", "resolve", "suspendAndRearm", "suspend", "readPauseKey", "onData", "key", "awaitPostRunKey", "ctx", "deps", "stdin", "process", "process", "ANSI", "resetTerminal", "opts", "deps", "write", "text", "stdin", "escapes", "commandLine", "command", "stderrColumns", "refreshStderrSize", "columns", "process", "stream", "waitForExit", "child", "resolve", "code", "signal", "runOne", "deps", "enterChildPhase", "reportPath", "newReportPath", "childEnv", "SESSION_REPORT_ENV", "start", "result", "record", "readAndUnlinkReport", "outcome", "classifyOutcome", "headerLine", "equivalent", "equivalentLine", "formatTranscriptEntry", "PNPM_RELAY_WINDOW_MS", "isPnpmRelay", "lastSigintAt", "now", "exitSession", "installSessionSignals", "getPhase", "quitRequested", "onSigint", "phase", "onSigterm", "onSighup", "onSigtstp", "sessionGateEnabled", "env", "streams", "SUSPEND_SEAM", "suspendForeground", "defaultAfterRun", "write", "resolved", "ctx", "awaitPostRunKey", "runSession", "items", "nodeSpawn", "chalkStderr", "opts", "resetTerminal", "text", "signals", "handler", "noopSignals", "sessionSignals", "pauseContext", "afterRun", "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", "formatUpdateCommand", "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
  }