infra-kit 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/boot-EEEVHAMO.js +2 -0
  2. package/dist/boot-EEEVHAMO.js.map +7 -0
  3. package/dist/{chunk-NC2TIQ3M.js → chunk-7F5YKMWK.js} +2 -2
  4. package/dist/chunk-7F5YKMWK.js.map +7 -0
  5. package/dist/chunk-A7FAXZGI.js +2 -0
  6. package/dist/chunk-A7FAXZGI.js.map +7 -0
  7. package/dist/chunk-AJW5M44V.js +231 -0
  8. package/dist/chunk-AJW5M44V.js.map +7 -0
  9. package/dist/{chunk-E2NWNNC6.js → chunk-FTCM2766.js} +2 -2
  10. package/dist/{chunk-E2NWNNC6.js.map → chunk-FTCM2766.js.map} +1 -1
  11. package/dist/chunk-IPF7ILCB.js +7 -0
  12. package/dist/chunk-IPF7ILCB.js.map +7 -0
  13. package/dist/{chunk-KZP2SY37.js → chunk-KHEUED4B.js} +2 -2
  14. package/dist/chunk-KHEUED4B.js.map +7 -0
  15. package/dist/chunk-NLOLELZ5.js +6 -0
  16. package/dist/chunk-NLOLELZ5.js.map +7 -0
  17. package/dist/chunk-UO3FYLPX.js +2 -0
  18. package/dist/chunk-UO3FYLPX.js.map +7 -0
  19. package/dist/cli.js +10 -9
  20. package/dist/cli.js.map +3 -3
  21. package/dist/dev-server.js +39 -27
  22. package/dist/dev-server.js.map +4 -4
  23. package/dist/dev-wizard-run-5NOQIMR5.js +2 -0
  24. package/dist/dev-wizard-run-5NOQIMR5.js.map +7 -0
  25. package/dist/mcp.js +1 -1
  26. package/dist/mcp.js.map +3 -3
  27. package/dist/persistent-ink-dev-ui-TLUJJC2D.js +2 -0
  28. package/dist/persistent-ink-dev-ui-TLUJJC2D.js.map +7 -0
  29. package/dist/update-check.js +1 -1
  30. package/package.json +2 -2
  31. package/dist/boot-MTVHM4N7.js +0 -2
  32. package/dist/boot-MTVHM4N7.js.map +0 -7
  33. package/dist/chunk-4MRYIYJP.js +0 -4
  34. package/dist/chunk-4MRYIYJP.js.map +0 -7
  35. package/dist/chunk-6FU2TRU5.js +0 -2
  36. package/dist/chunk-6FU2TRU5.js.map +0 -7
  37. package/dist/chunk-E2SEBLGJ.js +0 -2
  38. package/dist/chunk-E2SEBLGJ.js.map +0 -7
  39. package/dist/chunk-KZP2SY37.js.map +0 -7
  40. package/dist/chunk-NC2TIQ3M.js.map +0 -7
  41. package/dist/chunk-RLZ5IB2E.js +0 -238
  42. package/dist/chunk-RLZ5IB2E.js.map +0 -7
  43. package/dist/chunk-TI33V5G2.js +0 -6
  44. package/dist/chunk-TI33V5G2.js.map +0 -7
  45. package/dist/dev-wizard-run-5HQOGE3R.js +0 -2
  46. package/dist/dev-wizard-run-5HQOGE3R.js.map +0 -7
  47. package/dist/persistent-ink-dev-ui-KOI44ZVT.js +0 -2
  48. package/dist/persistent-ink-dev-ui-KOI44ZVT.js.map +0 -7
@@ -0,0 +1,2 @@
1
+ import{f as M,i as l,l as f}from"./chunk-IPF7ILCB.js";import{I as b,Q as W,v as d,w as y,x as k,y as w}from"./chunk-KHEUED4B.js";import{e as z}from"./chunk-CHETBZ6M.js";import $ from"@inquirer/checkbox";import O from"@inquirer/confirm";import j,{Separator as B}from"@inquirer/select";import{loadDev as q}from"@slip-stream-kit/config/internal";import K from"node:path";import P from"node:process";var g=e=>e.split("/")[0],C=e=>new Map(e.apps.map(r=>[r.name,r])),N=(e,r)=>{let a={},s=!1;for(let t of e.backends){let n=t.localCapable&&t.ownerApp!=null&&r.has(t.ownerApp);n||(s=!0);for(let o of t.routes)a[o]=n?"local":"cloud"}return{overrides:a,anyCloud:s}},A=(e,r)=>{let a=C(r),s=e.targets.filter(i=>i.endsWith("/ui")),t=new Set(e.targets.filter(i=>i.endsWith("/api")).map(g)),n={},o=!1;for(let i of s){let u=a.get(g(i)),{overrides:c,anyCloud:m}=u?N(u,t):{overrides:{},anyCloud:!1};m&&(o=!0),n[i]=Object.keys(c).length>0?{proxy:c}:{}}for(let i of t)n[`${i}/api`]??={};return{presetDef:{apps:n,cmux:e.cmux},anyCloudRoute:o,targetKeys:Object.keys(n).sort()}},D=(e,r,a)=>{let s=C(a),t=[...new Set(e.targetKeys.map(g))].sort(),n=new Set(e.targetKeys),o=t.every(u=>{let c=s.get(u);if(!c)return!1;let m=!c.hasUi||n.has(`${u}/ui`),p=!c.hasApi||n.has(`${u}/api`);return m&&p}),i=[`--app=${t.join(",")}`];return r.watch&&i.push("--watch"),r.cmux&&i.push("--cmux"),{flags:i.join(" "),exact:o}};var h={output:P.stderr,clearPromptOnDone:!0},U={select:e=>f(r=>j({message:e.message,choices:e.choices,default:e.default},r),h),checkbox:e=>f(r=>$({message:e.message,choices:e.choices},r),h),confirm:e=>f(r=>O({message:e.message,default:e.default},r),h)},_=(e,r)=>{let a=new Map;for(let[s,t]of Object.entries(e)){let n=a.get(t.packageName),o=t.from.includes("local");n?(n.routes.push(s),n.localCapable=n.localCapable||o):a.set(t.packageName,{packageName:t.packageName,routes:[s],localCapable:o,ownerApp:r.get(t.packageName)})}return[...a.values()]},E=async e=>{let r=k(e),a=w(e),s=await W(),t=new Map(r.map(p=>[p.packageName,p.name])),n=new Map(r.map(p=>[p.name,p.packageName])),o=new Set(a.map(p=>p.name)),i=new Set(r.map(p=>p.name)),u=[...new Set([...i,...o])].sort(),c=await Promise.all(u.map(async p=>{let v=o.has(p),x=v?(await q(K.join(e,"apps",p,"ui")))?.proxy?.routes:void 0;return{name:p,hasApi:i.has(p),hasUi:v,apiPackage:n.get(p),backends:x?_(x,t):[]}})),m=await M();return{apps:c,presets:Object.keys(s.devServersPresets??{}),environments:Object.keys(m?.envs??{}).sort()}},I=e=>{let r=[];for(let a of e.apps){if(a.hasUi){let s=a.backends.flatMap(n=>n.routes),t=s.length>0?`frontend \u2014 proxies ${[...s].sort().join(", ")}`:"frontend";r.push({name:`${a.name}/ui`,value:`${a.name}/ui`,description:t})}a.hasApi&&r.push({name:`${a.name}/api`,value:`${a.name}/api`,description:"backend"})}return r},T=e=>{let r={api:e.apps.filter(t=>t.hasApi).map(t=>t.name),ui:e.apps.filter(t=>t.hasUi).map(t=>t.name)},a={},s=new Map;for(let t of e.apps){t.apiPackage!=null&&(a[t.name]=t.apiPackage);for(let n of t.backends)for(let o of n.routes)s.set(`${t.name} ${o}`,n.packageName)}return{discovered:r,apiPkgByApp:a,routePkg:(t,n)=>s.get(`${t} ${n}`)}},F=(e,r)=>b({__wizard__:e},T(r)).map(a=>a.message),S=async(e,r)=>{if(r.apps.length===0)return d.warn("No apps discovered to run."),null;let a=await e.checkbox({message:"\u{1F4E6} Which packages?",choices:I(r)});if(a.length===0)return d.warn("No packages selected."),null;let s=await e.confirm({message:"\u{1F440} Rebuild & restart on save (watch)?",default:!1}),t=await e.confirm({message:"\u{1F9E9} Run each app in its own cmux pane?",default:!1}),n={targets:a,watch:s,cmux:t},o=A(n,r);o.anyCloudRoute&&(n.env=await e.select({message:"\u2601\uFE0F Point cloud routes at which environment?",choices:r.environments.map(c=>({name:c,value:c}))}),P.env[z]=n.env);let i=F(o.presetDef,r);if(i.length>0){d.warn("\u26A0\uFE0F Proxy audit found issues with this selection:");for(let c of i)d.warn(` \u2022 ${c}`);return null}L(o,n,r);let u=o.targetKeys.filter(c=>c.endsWith("/api")).map(c=>c.split("/")[0]);return t&&u.length>0?{include:u,presetDef:o.presetDef,watch:s,cmux:!0}:(t&&d.info("\u2139\uFE0F cmux needs at least one local backend (panes are backend-only) \u2014 running in-process instead."),{presetDef:o.presetDef,watch:s,cmux:!1})},L=(e,r,a)=>{l.setInteractive();let s=D(e,r,a);l.addOption(s.flags,!0),l.print(),s.exact||d.info("\u2139\uFE0F This part-level selection has no exact single-flag form \u2014 save it as a devPreset to reproduce it.")},V=async(e,r)=>{let a=await e.confirm({message:"\u{1F440} Rebuild & restart on save (watch)?",default:!1});return l.setInteractive(),l.addOption(r,!0),a&&l.addOption("--watch",!0),l.print(),{preset:r,watch:a,cmux:!1}},R=" manual",H=async(e,r)=>{if(r.presets.length===0)return S(e,r);let a=await e.select({message:"\u{1F680} Start from a preset, or configure manually?",choices:[...r.presets.map(s=>({name:s,value:s})),new B(" "),{name:"Manual (custom)\u2026",value:R}]});return a===R?S(e,r):V(e,a)},ue=async(e=U,r=y(P.cwd()))=>H(e,await E(r));export{R as MANUAL_CHOICE,F as auditManualPlan,U as defaultPrompts,E as gatherWizardModel,ue as runDevWizard,H as runWizardFlow};
2
+ //# sourceMappingURL=dev-wizard-run-5NOQIMR5.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/dev/dev-wizard-run.ts", "../src/dev/dev-wizard.ts"],
4
+ "sourcesContent": ["/**\n * Impure driver for the interactive `infra-kit dev` wizard: filesystem/config discovery, the inquirer\n * prompt flow (step-0 preset-or-manual, then the manual matrix), the pre-flight proxy audit, and the\n * `commandEcho` teaching line. The pure answer\u2192plan mapping lives in {@link file://./dev-wizard.ts}.\n *\n * Prompts are behind the injectable {@link WizardPrompts} seam so the flow is testable without a TTY.\n * This module is reached ONLY from the bare-invocation TTY branch of the entry point; every flagged /\n * non-TTY / `--json` / MCP invocation bypasses it entirely.\n */\nimport inquirerCheckbox from '@inquirer/checkbox'\nimport inquirerConfirm from '@inquirer/confirm'\nimport inquirerSelect, { Separator } from '@inquirer/select'\nimport { loadDev } from '@slip-stream-kit/config/internal'\nimport path from 'node:path'\nimport process from 'node:process'\n\nimport { discoverApiApps, discoverUiApps, findMonorepoRoot } from 'src/dev/discovery'\nimport { commandEcho } from 'src/lib/command-echo'\nimport { INFRA_KIT_ENV_VAR } from 'src/lib/constants'\nimport { readTokenStore } from 'src/lib/env-tokens'\nimport { getInfraKitConfig } from 'src/lib/infra-kit-config'\nimport type { DevPreset } from 'src/lib/infra-kit-config'\nimport { logger } from 'src/lib/logger'\nimport { withEscape } from 'src/lib/prompts/escapable-context'\n\nimport { deriveManualPlan, equivalentCommand } from './dev-wizard.js'\nimport type { DerivedPlan, ManualSelection, ProxyBackend, WizardApp, WizardModel } from './dev-wizard.js'\nimport { validatePresetProxy } from './presets.js'\nimport type { DiscoveredParts, PresetProxyContext } from './presets.js'\n\n/** A wizard prompt choice \u2014 all values are strings, so the seam needs no generics. */\nexport interface WizardChoice {\n name: string\n value: string\n description?: string\n checked?: boolean\n disabled?: boolean | string\n}\n\n/**\n * Injectable prompt seam (a string-valued subset of `@inquirer/*`). The default implementation\n * ({@link defaultPrompts}) delegates to the real prompts, rendering to stderr so the dev-server's\n * stdout stays clean. Tests pass a scripted object.\n */\nexport interface WizardPrompts {\n select: (cfg: { message: string; choices: (WizardChoice | Separator)[]; default?: string }) => Promise<string>\n checkbox: (cfg: { message: string; choices: WizardChoice[] }) => Promise<string[]>\n confirm: (cfg: { message: string; default?: boolean }) => Promise<boolean>\n}\n\n/**\n * Shared `@inquirer/*` context. Rendered to stderr (mirrors the bare-`infra-kit` command palette).\n *\n * `clearPromptOnDone` erases each answered prompt instead of leaving a `\u2714 <question> <answer>` line\n * behind, so the wizard collapses to nothing once it finishes and the dev-server's ready header lands\n * at the top of a clean screen. The choices are not lost: {@link echoManual} / {@link runPresetBranch}\n * print the equivalent flag command, which is the durable record of what was picked.\n */\nconst promptContext = { output: process.stderr, clearPromptOnDone: true }\n\n/**\n * Real `@inquirer/*` prompts, rendered to stderr and erased once answered.\n *\n * Every one goes through {@link withEscape}, which binds Esc to cancellation. The wrap is here rather\n * than around the {@link WizardPrompts} seam so the scripted prompts the tests inject stay untouched:\n * the seam is the test boundary, `defaultPrompts` is the only implementation that talks to a terminal.\n *\n * Esc rejects with an `AbortPromptError`, which `entry/cli.ts` catches as a cancellation \u2192 exit 0. The\n * wizard therefore needs no cancel branch of its own: a cancelled prompt never returns here at all, so\n * no server is started.\n */\nexport const defaultPrompts: WizardPrompts = {\n select: (cfg) => {\n return withEscape((context) => {\n return inquirerSelect({ message: cfg.message, choices: cfg.choices, default: cfg.default }, context)\n }, promptContext)\n },\n checkbox: (cfg) => {\n return withEscape((context) => {\n return inquirerCheckbox({ message: cfg.message, choices: cfg.choices }, context)\n }, promptContext)\n },\n confirm: (cfg) => {\n return withEscape((context) => {\n return inquirerConfirm({ message: cfg.message, default: cfg.default }, context)\n }, promptContext)\n },\n}\n\n/** What the wizard hands back to the entry point (merged into `DevServerOptions`), or null on cancel. */\nexport interface WizardResult {\n /** Named preset (preset branch) \u2014 passed through as `options.preset`. */\n preset?: string\n /** In-memory preset (manual branch, non-cmux) \u2014 passed through as `options.presetDef`. */\n presetDef?: DevPreset\n /**\n * App-name include list for the cmux path only: which discovered API apps get a pane. Paired with\n * `presetDef`, which tells each pane the exact parts to run. An app selected UI-only gets no pane\n * (cmux panes are opened per API app). Unset for the non-cmux manual path.\n */\n include?: string[]\n watch: boolean\n cmux: boolean\n}\n\n/** Group one frontend's `dev.proxy.routes` into {@link ProxyBackend}s keyed by backend package. */\nconst groupBackends = (\n routes: Record<string, { packageName: string; from: readonly string[] }>,\n ownerByPkg: Map<string, string>,\n): ProxyBackend[] => {\n const byPkg = new Map<string, ProxyBackend>()\n\n for (const [route, def] of Object.entries(routes)) {\n const existing = byPkg.get(def.packageName)\n const localCapable = def.from.includes('local')\n\n if (existing) {\n existing.routes.push(route)\n existing.localCapable = existing.localCapable || localCapable\n } else {\n byPkg.set(def.packageName, {\n packageName: def.packageName,\n routes: [route],\n localCapable,\n ownerApp: ownerByPkg.get(def.packageName),\n })\n }\n }\n\n return [...byPkg.values()]\n}\n\n/** Discover apps + configs and assemble the {@link WizardModel} (backends resolved per frontend). */\nexport const gatherWizardModel = async (root: string): Promise<WizardModel> => {\n const apiApps = discoverApiApps(root)\n const uiApps = discoverUiApps(root)\n const config = await getInfraKitConfig()\n\n const ownerByPkg = new Map(\n apiApps.map((a) => {\n return [a.packageName, a.name] as const\n }),\n )\n const apiPkgByName = new Map(\n apiApps.map((a) => {\n return [a.name, a.packageName] as const\n }),\n )\n const uiNames = new Set(\n uiApps.map((a) => {\n return a.name\n }),\n )\n const apiNames = new Set(\n apiApps.map((a) => {\n return a.name\n }),\n )\n const allNames = [...new Set([...apiNames, ...uiNames])].sort()\n\n const apps: WizardApp[] = await Promise.all(\n allNames.map(async (name): Promise<WizardApp> => {\n const hasUi = uiNames.has(name)\n const routes = hasUi ? (await loadDev(path.join(root, 'apps', name, 'ui')))?.proxy?.routes : undefined\n\n return {\n name,\n hasApi: apiNames.has(name),\n hasUi,\n apiPackage: apiPkgByName.get(name),\n backends: routes ? groupBackends(routes, ownerByPkg) : [],\n }\n }),\n )\n\n // The cloud-env choices are the envs we hold a token for \u2014 the same authority as `env-load`, because\n // it is the same question. This picker writes INFRA_KIT_ENV, which `@slip-stream-kit/config/vite`\n // reads to build the cloud backend's URL, and whose only other writer is `env-load` (which cannot run\n // without a Doppler token). Sourcing it from the workflow options instead would put `prod` in the list\n // for everyone \u2014 including a developer holding no prod credential \u2014 and point a local UI at\n // production. No token, no entry.\n const store = await readTokenStore()\n\n return {\n apps,\n presets: Object.keys(config.devServersPresets ?? {}),\n environments: Object.keys(store?.envs ?? {}).sort(),\n }\n}\n\n/**\n * Build the flat `<app>/<part>` checkbox choices, grouped per app as `ui` then `api`. A frontend choice\n * carries a description of the routes it proxies, so ticking the matching api part reads as \"run that\n * route locally\". This is the checkbox the user directly picks their targets from.\n *\n * Nothing starts checked: the manual branch is opt-in, so a run only ever launches what was explicitly\n * ticked. Pre-checking every part would make the fast path (accept the defaults) boot the whole monorepo\n * \u2014 the exact opposite of why someone reached for \"Manual\" over a preset.\n */\nconst buildPartChoices = (model: WizardModel): WizardChoice[] => {\n const choices: WizardChoice[] = []\n\n for (const app of model.apps) {\n if (app.hasUi) {\n const routes = app.backends.flatMap((b) => {\n return b.routes\n })\n const description = routes.length > 0 ? `frontend \u2014 proxies ${[...routes].sort().join(', ')}` : 'frontend'\n\n choices.push({ name: `${app.name}/ui`, value: `${app.name}/ui`, description })\n }\n if (app.hasApi) {\n choices.push({ name: `${app.name}/api`, value: `${app.name}/api`, description: 'backend' })\n }\n }\n\n return choices\n}\n\n/** Build the audit context from the gathered model (no re-reading of configs). */\nconst buildAuditContext = (model: WizardModel): PresetProxyContext => {\n const discovered: DiscoveredParts = {\n api: model.apps\n .filter((a) => {\n return a.hasApi\n })\n .map((a) => {\n return a.name\n }),\n ui: model.apps\n .filter((a) => {\n return a.hasUi\n })\n .map((a) => {\n return a.name\n }),\n }\n const apiPkgByApp: Record<string, string> = {}\n const routeToPkg = new Map<string, string>()\n\n for (const app of model.apps) {\n // Map EVERY discovered api app to its own api package \u2014 including api-only apps a frontend proxies\n // to cross-app \u2014 so `launchedPkgs` in validatePresetProxy is faithful (mirrors preset-proxy-check.ts).\n // Deriving this from proxy backends alone would miss api-only owners and false-positive the audit.\n if (app.apiPackage != null) apiPkgByApp[app.name] = app.apiPackage\n for (const b of app.backends) {\n for (const route of b.routes) {\n routeToPkg.set(`${app.name} ${route}`, b.packageName)\n }\n }\n }\n\n return {\n discovered,\n apiPkgByApp,\n routePkg: (app, route) => {\n return routeToPkg.get(`${app} ${route}`)\n },\n }\n}\n\n/**\n * Run the assembled plan through the SAME proxy-locality rule the root audit uses. Returns issue\n * messages (empty when clean). Catches a `local` override whose backend won't launch \u2014 the wizard's\n * derivation launches owners, so a hit means the backend has no discoverable owning app.\n */\nexport const auditManualPlan = (presetDef: DevPreset, model: WizardModel): string[] => {\n return validatePresetProxy({ __wizard__: presetDef }, buildAuditContext(model)).map((i) => {\n return i.message\n })\n}\n\n/** The manual-branch flow: app + per-app proxy + env + watch + cmux \u2192 audited plan \u2192 echo. */\nconst runManualBranch = async (prompts: WizardPrompts, model: WizardModel): Promise<WizardResult | null> => {\n if (model.apps.length === 0) {\n logger.warn('No apps discovered to run.')\n\n return null\n }\n\n const selectedTargets = await prompts.checkbox({ message: '\uD83D\uDCE6 Which packages?', choices: buildPartChoices(model) })\n\n if (selectedTargets.length === 0) {\n logger.warn('No packages selected.')\n\n return null\n }\n\n const watch = await prompts.confirm({ message: '\uD83D\uDC40 Rebuild & restart on save (watch)?', default: false })\n const cmux = await prompts.confirm({ message: '\uD83E\uDDE9 Run each app in its own cmux pane?', default: false })\n\n const selection: ManualSelection = { targets: selectedTargets, watch, cmux }\n const plan = deriveManualPlan(selection, model)\n\n if (plan.anyCloudRoute) {\n selection.env = await prompts.select({\n message: '\u2601\uFE0F Point cloud routes at which environment?',\n choices: model.environments.map((e) => {\n return { name: e, value: e }\n }),\n })\n process.env[INFRA_KIT_ENV_VAR] = selection.env\n }\n\n const issues = auditManualPlan(plan.presetDef, model)\n\n if (issues.length > 0) {\n logger.warn('\u26A0\uFE0F Proxy audit found issues with this selection:')\n for (const issue of issues) {\n logger.warn(` \u2022 ${issue}`)\n }\n\n return null\n }\n\n echoManual(plan, selection, model)\n\n // cmux opens one pane per selected API app. `include` picks WHICH apps get a pane; `presetDef` tells\n // each pane exactly which of its parts to run. Without the latter a pane runs `--app=<name>`, which\n // expands to every part the app has \u2014 silently starting a UI the user just unticked.\n const apiApps = plan.targetKeys\n .filter((k) => {\n return k.endsWith('/api')\n })\n .map((k) => {\n return k.split('/')[0]!\n })\n\n // An empty include would collapse to `null` in `normalizeAppInclude` and make cmux run EVERY api app,\n // so a cmux run with no selected backends (e.g. an all-frontend selection) falls back to in-process.\n if (cmux && apiApps.length > 0) {\n return { include: apiApps, presetDef: plan.presetDef, watch, cmux: true }\n }\n\n if (cmux) {\n logger.info('\u2139\uFE0F cmux needs at least one local backend (panes are backend-only) \u2014 running in-process instead.')\n }\n\n return { presetDef: plan.presetDef, watch, cmux: false }\n}\n\n/** Print the equivalent flag command (and, for a part-level selection, the save-as-preset hint). */\nconst echoManual = (plan: DerivedPlan, selection: ManualSelection, model: WizardModel): void => {\n commandEcho.setInteractive()\n\n const eq = equivalentCommand(plan, selection, model)\n\n commandEcho.addOption(eq.flags, true)\n commandEcho.print()\n\n if (!eq.exact) {\n logger.info('\u2139\uFE0F This part-level selection has no exact single-flag form \u2014 save it as a devPreset to reproduce it.')\n }\n}\n\n/** The preset-branch flow: run a named preset, asking only whether to watch (presets can't encode it). */\nconst runPresetBranch = async (prompts: WizardPrompts, preset: string): Promise<WizardResult> => {\n const watch = await prompts.confirm({ message: '\uD83D\uDC40 Rebuild & restart on save (watch)?', default: false })\n\n commandEcho.setInteractive()\n commandEcho.addOption(preset, true)\n if (watch) commandEcho.addOption('--watch', true)\n commandEcho.print()\n\n return { preset, watch, cmux: false }\n}\n\n/** Sentinel value for the \"Manual (custom)\" step-0 choice (a preset name can never be empty). */\nexport const MANUAL_CHOICE = ' manual'\n\n/**\n * Drive the wizard's branch flow over an ALREADY-gathered model (no disk/config access). Split from\n * {@link runDevWizard} so the flow is unit-testable with scripted prompts + a fixture model.\n */\nexport const runWizardFlow = async (prompts: WizardPrompts, model: WizardModel): Promise<WizardResult | null> => {\n if (model.presets.length === 0) {\n return runManualBranch(prompts, model)\n }\n\n const choice = await prompts.select({\n message: '\uD83D\uDE80 Start from a preset, or configure manually?',\n choices: [\n ...model.presets.map((p) => {\n return { name: p, value: p }\n }),\n new Separator(' '),\n { name: 'Manual (custom)\u2026', value: MANUAL_CHOICE },\n ],\n })\n\n return choice === MANUAL_CHOICE ? runManualBranch(prompts, model) : runPresetBranch(prompts, choice)\n}\n\n/**\n * Gather the model from disk/config, then drive the wizard flow, returning the resolved run options (or\n * null on cancel / empty selection). The entry point calls this on a bare TTY `infra-kit dev`.\n */\nexport const runDevWizard = async (\n prompts: WizardPrompts = defaultPrompts,\n root: string = findMonorepoRoot(process.cwd()),\n): Promise<WizardResult | null> => {\n return runWizardFlow(prompts, await gatherWizardModel(root))\n}\n", "/**\n * Pure core of the interactive `infra-kit dev` wizard (the no-args, TTY path).\n *\n * The wizard is FRONTEND-CENTRIC: you pick apps to run, and for each frontend you choose, per backend\n * it proxies to, whether that backend runs LOCALLY (its `/api` is launched \u2192 the route resolves local)\n * or stays CLOUD (not launched \u2192 the route proxies to the `<env>` cloud target). A backend runs iff a\n * frontend points at it locally, or its app is selected and not demoted to cloud. This module turns\n * those answers into an in-memory {@link DevPreset} the runner consumes verbatim (part-level targets\n * that the app-name-only `--app` include cannot express) \u2014 and is side-effect-free so the mapping is\n * fully unit-testable. The impure I/O (discovery, inquirer prompts, audit, launch) lives in\n * {@link file://./dev-wizard-run.ts}.\n */\nimport type { DevPreset, ProxySource } from 'src/lib/infra-kit-config'\n\n/**\n * One backend a frontend's proxy routes can be pointed at, grouped by the backend package. A frontend\n * that maps `/api` and `/media` to the same package yields ONE {@link ProxyBackend} with both routes \u2014\n * launching the package flips every one of its `local`-capable routes together (see `pickSource`).\n */\nexport interface ProxyBackend {\n /** Backend package name (a route's `packageName`). */\n packageName: string\n /** Route paths that resolve to this package (for display + the audit's per-route overrides). */\n routes: string[]\n /** True when at least one of `routes` lists `local` in its `from` capabilities \u2014 i.e. it is toggleable. A `false` backend is fixed-cloud, shown as info. */\n localCapable: boolean\n /** App folder whose `api` package is {@link packageName}, or undefined when no discovered app owns it (a `local` choice then has nothing to launch \u2014 the audit flags it). */\n ownerApp?: string\n}\n\n/** A discovered app as the wizard sees it: which parts exist + which backends its frontend proxies to. */\nexport interface WizardApp {\n /** App folder name (e.g. `client`). */\n name: string\n /** Has `apps/<app>/api` (a launchable backend). */\n hasApi: boolean\n /** Has `apps/<app>/ui` (a launchable frontend). */\n hasUi: boolean\n /**\n * This app's OWN `api` package name (from `apps/<app>/api`), or undefined when it has no api. Carried\n * so the audit can build a faithful `app \u2192 apiPackage` map for EVERY discovered api app \u2014 including\n * api-only apps a frontend proxies to cross-app \u2014 mirroring the root audit (`preset-proxy-check.ts`).\n */\n apiPackage?: string\n /** Backends this app's frontend proxies to (empty for api-only apps or a frontend with no `dev.proxy`). */\n backends: ProxyBackend[]\n}\n\n/** Everything the wizard needs, gathered impurely by {@link file://./dev-wizard-run.ts}. */\nexport interface WizardModel {\n /** All discovered apps (api and/or ui), sorted by name. */\n apps: WizardApp[]\n /** `devServersPresets` names (drives the preset-or-manual step-0). */\n presets: string[]\n /** Configured `environments` (the cloud-env `select` options). */\n environments: string[]\n}\n\n/** The manual-branch answers collected from the prompts, fed to {@link deriveManualPlan}. */\nexport interface ManualSelection {\n /**\n * The `<app>/<part>` target keys the user ticked directly (e.g. `client/ui`, `client/api`). A frontend's\n * proxy route resolves LOCAL iff the backend's owner-api part is in this set \u2014 otherwise CLOUD \u2014 so the\n * part checkbox IS the local/cloud decision; there is no separate per-backend question.\n */\n targets: string[]\n /** Chosen cloud env for cloud routes (undefined when nothing resolves cloud). */\n env?: string\n /** Rebuild + restart on save. */\n watch: boolean\n /** One cmux pane per app. */\n cmux: boolean\n}\n\n/** The resolved run plan derived from a {@link ManualSelection}. */\nexport interface DerivedPlan {\n /** In-memory preset handed to the runner (`options.presetDef`). */\n presetDef: DevPreset\n /** At least one route resolves to cloud \u2192 an env must be chosen (pre-flight gate). */\n anyCloudRoute: boolean\n /** Resolved `<app>/<part>` target keys, sorted \u2014 for the echo + audit display. */\n targetKeys: string[]\n}\n\n/** App folder name from an `<app>/<part>` target key. */\nconst appOf = (key: string): string => {\n return key.split('/')[0]!\n}\n\n/** Index a model's apps by folder name for O(1) lookup. */\nconst byName = (model: WizardModel): Map<string, WizardApp> => {\n return new Map(\n model.apps.map((a) => {\n return [a.name, a]\n }),\n )\n}\n\n/**\n * Resolve ONE selected frontend's proxy routes against the set of api parts the user ticked: a route is\n * `local` iff its backend is `localCapable` AND that backend's owner-api part was selected \u2014 otherwise\n * `cloud`. Kept separate from {@link deriveManualPlan} so the per-app override loop stays flat.\n */\nconst resolveUiProxy = (\n app: WizardApp,\n selectedApiApps: ReadonlySet<string>,\n): { overrides: Record<string, ProxySource>; anyCloud: boolean } => {\n const overrides: Record<string, ProxySource> = {}\n let anyCloud = false\n\n for (const backend of app.backends) {\n const isLocal = backend.localCapable && backend.ownerApp != null && selectedApiApps.has(backend.ownerApp)\n\n if (!isLocal) anyCloud = true\n for (const route of backend.routes) {\n overrides[route] = isLocal ? 'local' : 'cloud'\n }\n }\n\n return { overrides, anyCloud }\n}\n\n/**\n * Turn a part-level {@link ManualSelection} into an in-memory {@link DevPreset}. Because the user picks\n * `<app>/<part>` targets directly, the mapping is straightforward:\n * - every selected `<app>/ui` runs that frontend;\n * - every selected `<app>/api` runs that backend locally;\n * - each frontend's proxy route resolves `local` iff the backend's owner-api part was also selected\n * (even cross-app), else `cloud` \u2014 i.e. ticking the api part IS the local choice.\n *\n * @example\n * // client frontend only (its /api left unticked \u2192 its proxy route resolves to cloud):\n * deriveManualPlan(\n * { targets: ['client/ui'], watch: false, cmux: false },\n * { apps: [{ name: 'client', hasApi: true, hasUi: true,\n * backends: [{ packageName: 'client-api', routes: ['/api'], localCapable: true, ownerApp: 'client' }] }],\n * presets: [], environments: ['dev'] },\n * ).targetKeys // => ['client/ui']\n */\nexport const deriveManualPlan = (selection: ManualSelection, model: WizardModel): DerivedPlan => {\n const apps = byName(model)\n const uiKeys = selection.targets.filter((t) => {\n return t.endsWith('/ui')\n })\n const apiApps = new Set(\n selection.targets\n .filter((t) => {\n return t.endsWith('/api')\n })\n .map(appOf),\n )\n\n const presetApps: Record<string, { proxy?: Record<string, ProxySource> }> = {}\n let anyCloud = false\n\n for (const key of uiKeys) {\n const app = apps.get(appOf(key))\n const { overrides, anyCloud: cloud } = app ? resolveUiProxy(app, apiApps) : { overrides: {}, anyCloud: false }\n\n if (cloud) anyCloud = true\n presetApps[key] = Object.keys(overrides).length > 0 ? { proxy: overrides } : {}\n }\n for (const name of apiApps) {\n presetApps[`${name}/api`] ??= {}\n }\n\n return {\n presetDef: { apps: presetApps, cmux: selection.cmux },\n anyCloudRoute: anyCloud,\n targetKeys: Object.keys(presetApps).sort(),\n }\n}\n\n/** The equivalent non-interactive command, plus whether it reproduces the selection exactly. */\nexport interface EquivalentCommand {\n /** The `--app=\u2026`-form flag string (no leading `infra-kit dev`). */\n flags: string\n /**\n * True when `--app` reproduces the plan exactly \u2014 i.e. every involved app runs ALL the parts it has.\n * False for a part-level selection (e.g. frontend-only), which `--app` (app-name granularity) cannot\n * express; the caller then hints \"save as a preset\" for exact reproduction.\n */\n exact: boolean\n}\n\n/**\n * Build the equivalent `infra-kit dev --app=\u2026 [--watch] [--cmux]` flag string for a derived plan.\n * `exact` is true only when the target set covers every part each involved app HAS (a whole-app\n * selection), since `--app` filters by app name, not part.\n */\nexport const equivalentCommand = (\n plan: DerivedPlan,\n selection: ManualSelection,\n model: WizardModel,\n): EquivalentCommand => {\n const apps = byName(model)\n const involved = [...new Set(plan.targetKeys.map(appOf))].sort()\n const present = new Set(plan.targetKeys)\n\n const exact = involved.every((name) => {\n const app = apps.get(name)\n\n if (!app) return false\n\n const uiOk = !app.hasUi || present.has(`${name}/ui`)\n const apiOk = !app.hasApi || present.has(`${name}/api`)\n\n return uiOk && apiOk\n })\n\n const parts = [`--app=${involved.join(',')}`]\n\n if (selection.watch) parts.push('--watch')\n if (selection.cmux) parts.push('--cmux')\n\n return { flags: parts.join(' '), exact }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["inquirerCheckbox", "inquirerConfirm", "inquirerSelect", "Separator", "loadDev", "path", "process", "appOf", "key", "byName", "model", "a", "resolveUiProxy", "app", "selectedApiApps", "overrides", "anyCloud", "backend", "isLocal", "route", "deriveManualPlan", "selection", "apps", "uiKeys", "t", "apiApps", "presetApps", "cloud", "name", "equivalentCommand", "plan", "involved", "present", "exact", "uiOk", "apiOk", "parts", "promptContext", "process", "defaultPrompts", "cfg", "withEscape", "context", "inquirerSelect", "inquirerCheckbox", "inquirerConfirm", "groupBackends", "routes", "ownerByPkg", "byPkg", "route", "def", "existing", "localCapable", "gatherWizardModel", "root", "apiApps", "discoverApiApps", "uiApps", "discoverUiApps", "config", "getInfraKitConfig", "a", "apiPkgByName", "uiNames", "apiNames", "allNames", "apps", "name", "hasUi", "loadDev", "path", "store", "readTokenStore", "buildPartChoices", "model", "choices", "app", "b", "description", "buildAuditContext", "discovered", "apiPkgByApp", "routeToPkg", "auditManualPlan", "presetDef", "validatePresetProxy", "i", "runManualBranch", "prompts", "logger", "selectedTargets", "watch", "cmux", "selection", "plan", "deriveManualPlan", "e", "INFRA_KIT_ENV_VAR", "issues", "issue", "echoManual", "k", "commandEcho", "eq", "equivalentCommand", "runPresetBranch", "preset", "MANUAL_CHOICE", "runWizardFlow", "choice", "p", "Separator", "runDevWizard", "findMonorepoRoot"]
7
+ }
package/dist/mcp.js CHANGED
@@ -1,2 +1,2 @@
1
- import{ea as d,ha as u,v as l}from"./chunk-RLZ5IB2E.js";import"./chunk-NC2TIQ3M.js";import"./chunk-E2NWNNC6.js";import"./chunk-6FU2TRU5.js";import{a as m}from"./chunk-4MRYIYJP.js";import{t as c,u as p,v as s}from"./chunk-KZP2SY37.js";import"./chunk-CHETBZ6M.js";import{StdioServerTransport as y}from"@modelcontextprotocol/sdk/server/stdio.js";import g from"node:process";import o from"node:process";var f=r=>{o.on("SIGINT",()=>{r.info({msg:"Received SIGINT. Shutting down..."}),o.exit(0)}),o.on("SIGTERM",()=>{r.info({msg:"Received SIGTERM. Shutting down..."}),o.exit(0)}),o.on("uncaughtException",e=>{r.fatal({err:e,msg:"Uncaught Exception"}),r.error(`Uncaught Exception! Check ${c}. Shutting down...`),r.flush(),o.exit(1)}),o.on("unhandledRejection",(e,n)=>{r.fatal({reason:e,promise:n,msg:"Unhandled Rejection"}),r.error(`Unhandled Rejection! Check ${c}. Shutting down...`),r.flush(),o.exit(1)})};import{McpServer as T}from"@modelcontextprotocol/sdk/server/mcp.js";var v=async r=>{};var h=async r=>{};var a=({toolName:r,handler:e})=>async n=>{s.info({msg:`Tool execution started: ${r}`,params:n});try{await l(),m.reset();let i=await e({...n,confirmedCommand:!0});return s.info({msg:`Tool execution successful: ${r}`}),i}catch(i){throw s.error({err:i,params:n,msg:`Tool execution failed: ${r}`}),i}};var x=async r=>{for(let e of d())r.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema,outputSchema:e.outputSchema},a({toolName:e.name,handler:e.handler}))};async function S(){let r=new T({name:"infra-kit",version:"1.0.0"},{capabilities:{resources:{},tools:{},prompts:{}}});return await v(r),await h(r),await x(r),r}u();var t=p(),w=async()=>{let r;try{r=await S(),t.info("MCP Server instance created")}catch(e){t.error({err:e,msg:"Failed to create MCP server"}),t.error("Fatal error during server creation."),g.exit(1)}try{let e=new y;await r.connect(e),t.info({msg:"Server connected to transport. Ready."})}catch(e){t.error({err:e,msg:"Failed to initialize server"}),t.error("Fatal error during server transport init."),g.exit(1)}};f(t);w();
1
+ import{_ as u,ba as f,p as d}from"./chunk-AJW5M44V.js";import"./chunk-7F5YKMWK.js";import"./chunk-FTCM2766.js";import"./chunk-A7FAXZGI.js";import{i as m,j as l}from"./chunk-IPF7ILCB.js";import{t as c,u as p,v as s}from"./chunk-KHEUED4B.js";import"./chunk-CHETBZ6M.js";import{StdioServerTransport as w}from"@modelcontextprotocol/sdk/server/stdio.js";import T from"node:process";import o from"node:process";var v=r=>{o.on("SIGINT",()=>{r.info({msg:"Received SIGINT. Shutting down..."}),o.exit(0)}),o.on("SIGTERM",()=>{r.info({msg:"Received SIGTERM. Shutting down..."}),o.exit(0)}),o.on("uncaughtException",e=>{r.fatal({err:e,msg:"Uncaught Exception"}),r.error(`Uncaught Exception! Check ${c}. Shutting down...`),r.flush(),o.exit(1)}),o.on("unhandledRejection",(e,n)=>{r.fatal({reason:e,promise:n,msg:"Unhandled Rejection"}),r.error(`Unhandled Rejection! Check ${c}. Shutting down...`),r.flush(),o.exit(1)})};import{McpServer as y}from"@modelcontextprotocol/sdk/server/mcp.js";var h=async r=>{};var x=async r=>{};var a=({toolName:r,handler:e})=>async n=>{s.info({msg:`Tool execution started: ${r}`,params:n});try{await d(),m.reset();let i=await e({...n,confirmedCommand:!0});return s.info({msg:`Tool execution successful: ${r}`}),i}catch(i){throw s.error({err:i,params:n,msg:`Tool execution failed: ${r}`}),i}};var S=async r=>{for(let e of u())r.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema,outputSchema:e.outputSchema},a({toolName:e.name,handler:e.handler}))};async function g(){l.enabled=!0;let r=new y({name:"infra-kit",version:"1.0.0"},{capabilities:{resources:{},tools:{},prompts:{}}});return await h(r),await x(r),await S(r),r}f();var t=p(),M=async()=>{let r;try{r=await g(),t.info("MCP Server instance created")}catch(e){t.error({err:e,msg:"Failed to create MCP server"}),t.error("Fatal error during server creation."),T.exit(1)}try{let e=new w;await r.connect(e),t.info({msg:"Server connected to transport. Ready."})}catch(e){t.error({err:e,msg:"Failed to initialize server"}),t.error("Fatal error during server transport init."),T.exit(1)}};v(t);M();
2
2
  //# sourceMappingURL=mcp.js.map
package/dist/mcp.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/entry/mcp.ts", "../src/lib/error-handlers/index.ts", "../src/mcp/server.ts", "../src/mcp/prompts/index.ts", "../src/mcp/resources/index.ts", "../src/lib/tool-handler/tool-handler.ts", "../src/mcp/tools/index.ts"],
4
- "sourcesContent": ["import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport process from 'node:process'\n\nimport { setupErrorHandlers } from 'src/lib/error-handlers'\nimport { initLoggerMcp } from 'src/lib/logger'\nimport { suppressTypelessPackageJsonWarning } from 'src/lib/node-warnings'\n\nimport { createMcpServer } from '../mcp/server'\n\n// The MCP tools run the same commands as the CLI, so they hit the same consumer `.ts` configs.\n// This keeps the client's log pane clean; it cannot affect protocol framing either way, since\n// the stdio transport frames JSON-RPC on stdout and `emitWarning` only ever writes to stderr.\nsuppressTypelessPackageJsonWarning()\n\nconst logger = initLoggerMcp()\n\nconst startServer = async () => {\n let server\n\n try {\n server = await createMcpServer()\n\n logger.info('MCP Server instance created')\n } catch (error) {\n logger.error({ err: error, msg: 'Failed to create MCP server' })\n logger.error(`Fatal error during server creation.`)\n\n process.exit(1)\n }\n\n try {\n const transport = new StdioServerTransport()\n\n await server.connect(transport)\n\n logger.info({ msg: 'Server connected to transport. Ready.' })\n } catch (error) {\n logger.error({ err: error, msg: 'Failed to initialize server' })\n logger.error(`Fatal error during server transport init.`)\n\n process.exit(1)\n }\n}\n\n// Setup error handlers\nsetupErrorHandlers(logger)\n\n// Start the server\nstartServer()\n", "import process from 'node:process'\nimport type { Logger } from 'pino'\n\nimport { LOG_FILE_PATH } from '../logger/index'\n\n/**\n * Setup error handlers for the application\n * @param logger - The logger instance\n *\n * ONLY FOR SERVER!\n */\nexport const setupErrorHandlers = (logger: Logger) => {\n process.on('SIGINT', () => {\n logger.info({ msg: 'Received SIGINT. Shutting down...' })\n process.exit(0)\n })\n\n process.on('SIGTERM', () => {\n logger.info({ msg: 'Received SIGTERM. Shutting down...' })\n process.exit(0)\n })\n\n process.on('uncaughtException', (error) => {\n logger.fatal({ err: error, msg: 'Uncaught Exception' })\n logger.error(`Uncaught Exception! Check ${LOG_FILE_PATH}. Shutting down...`)\n logger.flush()\n process.exit(1)\n })\n\n process.on('unhandledRejection', (reason, promise) => {\n logger.fatal({ reason, promise, msg: 'Unhandled Rejection' })\n logger.error(`Unhandled Rejection! Check ${LOG_FILE_PATH}. Shutting down...`)\n logger.flush()\n process.exit(1)\n })\n}\n", "import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { initializePrompts } from './prompts'\nimport { initializeResources } from './resources'\nimport { initializeTools } from './tools'\n\nexport async function createMcpServer() {\n const server = new McpServer(\n {\n name: 'infra-kit',\n version: '1.0.0',\n },\n {\n capabilities: {\n resources: {},\n tools: {},\n prompts: {},\n },\n },\n )\n\n await initializePrompts(server)\n await initializeResources(server)\n await initializeTools(server)\n\n return server\n}\n", "import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nexport const initializePrompts = async (_server: McpServer) => {}\n", "import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nexport const initializeResources = async (_server: McpServer) => {}\n", "import { commandEcho } from 'src/lib/command-echo'\nimport { ensureUserProjectConfig } from 'src/lib/config-bootstrap'\nimport { logger } from 'src/lib/logger'\nimport type { ToolsExecutionResult } from 'src/types'\n\ninterface ToolHandlerArgs {\n toolName: string\n handler: (params: any) => Promise<ToolsExecutionResult>\n}\n\nexport const createToolHandler = ({\n toolName,\n handler,\n}: ToolHandlerArgs): ((params: unknown) => Promise<ToolsExecutionResult>) => {\n return async (params: unknown) => {\n logger.info({ msg: `Tool execution started: ${toolName}`, params })\n try {\n // MCP entry-boundary seed. This wrapper is the sole chokepoint for every exposed tool\n // (`src/mcp/tools/index.ts` holds the only `registerTool` call), and the `mcp` command itself is\n // in program.ts's SEED_EXCLUDED \u2014 so an MCP server seeds lazily, on its FIRST tool invocation,\n // and a server that never receives one never writes to $HOME. The once-per-process guard inside\n // ensureUserProjectConfig makes calls 2..N free on this long-lived process.\n //\n // Deliberately INSIDE the try, not before it: defense-in-depth. ensureUserProjectConfig is\n // contractually never-throw, but if that contract ever broke, an unhandled rejection here would\n // take down the long-lived server. Inside the try it degrades to an ordinary tool error.\n await ensureUserProjectConfig()\n\n // The command handlers record their resolved flags into the `commandEcho` singleton. On the CLI,\n // Commander's `preAction` clears it before every command; this long-lived server never runs that\n // hook, so without a reset here one tool call's flags would leak into the next one's snapshot and\n // the options array would grow for the life of the process.\n commandEcho.reset()\n\n const payload = await handler({ ...(params as object), confirmedCommand: true })\n\n logger.info({ msg: `Tool execution successful: ${toolName}` })\n\n return payload\n } catch (error) {\n logger.error({\n err: error,\n params,\n msg: `Tool execution failed: ${toolName}`,\n })\n\n throw error\n }\n }\n}\n", "import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { getExposedMcpTools } from 'src/lib/command-catalog'\nimport { createToolHandler } from 'src/lib/tool-handler'\n\nexport const initializeTools = async (server: McpServer) => {\n // The registered tool set is derived from the single command catalog, filtered\n // by its explicit `mcpExposed` allowlist. doctor / vendor-sync / vendor-manifest\n // are intentionally excluded there and must never be registered here.\n for (const tool of getExposedMcpTools()) {\n server.registerTool(\n tool.name,\n {\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n },\n createToolHandler({ toolName: tool.name, handler: tool.handler }),\n )\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["StdioServerTransport", "process", "process", "setupErrorHandlers", "logger", "process", "error", "LOG_FILE_PATH", "reason", "promise", "McpServer", "initializePrompts", "_server", "initializeResources", "_server", "createToolHandler", "toolName", "handler", "params", "logger", "ensureUserProjectConfig", "commandEcho", "payload", "error", "initializeTools", "server", "tool", "getExposedMcpTools", "createToolHandler", "createMcpServer", "server", "McpServer", "initializePrompts", "initializeResources", "initializeTools", "suppressTypelessPackageJsonWarning", "logger", "initLoggerMcp", "startServer", "server", "createMcpServer", "error", "process", "transport", "StdioServerTransport", "setupErrorHandlers"]
4
+ "sourcesContent": ["import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport process from 'node:process'\n\nimport { setupErrorHandlers } from 'src/lib/error-handlers'\nimport { initLoggerMcp } from 'src/lib/logger'\nimport { suppressTypelessPackageJsonWarning } from 'src/lib/node-warnings'\n\nimport { createMcpServer } from '../mcp/server'\n\n// The MCP tools run the same commands as the CLI, so they hit the same consumer `.ts` configs.\n// This keeps the client's log pane clean; it cannot affect protocol framing either way, since\n// the stdio transport frames JSON-RPC on stdout and `emitWarning` only ever writes to stderr.\nsuppressTypelessPackageJsonWarning()\n\nconst logger = initLoggerMcp()\n\nconst startServer = async () => {\n let server\n\n try {\n server = await createMcpServer()\n\n logger.info('MCP Server instance created')\n } catch (error) {\n logger.error({ err: error, msg: 'Failed to create MCP server' })\n logger.error(`Fatal error during server creation.`)\n\n process.exit(1)\n }\n\n try {\n const transport = new StdioServerTransport()\n\n await server.connect(transport)\n\n logger.info({ msg: 'Server connected to transport. Ready.' })\n } catch (error) {\n logger.error({ err: error, msg: 'Failed to initialize server' })\n logger.error(`Fatal error during server transport init.`)\n\n process.exit(1)\n }\n}\n\n// Setup error handlers\nsetupErrorHandlers(logger)\n\n// Start the server\nstartServer()\n", "import process from 'node:process'\nimport type { Logger } from 'pino'\n\nimport { LOG_FILE_PATH } from '../logger/index'\n\n/**\n * Setup error handlers for the application\n * @param logger - The logger instance\n *\n * ONLY FOR SERVER!\n */\nexport const setupErrorHandlers = (logger: Logger) => {\n process.on('SIGINT', () => {\n logger.info({ msg: 'Received SIGINT. Shutting down...' })\n process.exit(0)\n })\n\n process.on('SIGTERM', () => {\n logger.info({ msg: 'Received SIGTERM. Shutting down...' })\n process.exit(0)\n })\n\n process.on('uncaughtException', (error) => {\n logger.fatal({ err: error, msg: 'Uncaught Exception' })\n logger.error(`Uncaught Exception! Check ${LOG_FILE_PATH}. Shutting down...`)\n logger.flush()\n process.exit(1)\n })\n\n process.on('unhandledRejection', (reason, promise) => {\n logger.fatal({ reason, promise, msg: 'Unhandled Rejection' })\n logger.error(`Unhandled Rejection! Check ${LOG_FILE_PATH}. Shutting down...`)\n logger.flush()\n process.exit(1)\n })\n}\n", "import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { mcpMode } from 'src/lib/mcp-mode'\n\nimport { initializePrompts } from './prompts'\nimport { initializeResources } from './resources'\nimport { initializeTools } from './tools'\n\nexport async function createMcpServer() {\n // Marks `process.stdin` as the JSON-RPC transport for every prompt guard in the\n // process. Set HERE rather than in `entry/mcp.ts` (the sole caller) because that\n // module starts a server at import time and so can never be unit-tested \u2014 a test\n // there could only stub the flag, which proves nothing about it ever being set.\n // This runs before `server.connect`, so no tool handler can outrun it.\n mcpMode.enabled = true\n\n const server = new McpServer(\n {\n name: 'infra-kit',\n version: '1.0.0',\n },\n {\n capabilities: {\n resources: {},\n tools: {},\n prompts: {},\n },\n },\n )\n\n await initializePrompts(server)\n await initializeResources(server)\n await initializeTools(server)\n\n return server\n}\n", "import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nexport const initializePrompts = async (_server: McpServer) => {}\n", "import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nexport const initializeResources = async (_server: McpServer) => {}\n", "import { commandEcho } from 'src/lib/command-echo'\nimport { ensureUserProjectConfig } from 'src/lib/config-bootstrap'\nimport { logger } from 'src/lib/logger'\nimport type { ToolsExecutionResult } from 'src/types'\n\ninterface ToolHandlerArgs {\n toolName: string\n handler: (params: any) => Promise<ToolsExecutionResult>\n}\n\nexport const createToolHandler = ({\n toolName,\n handler,\n}: ToolHandlerArgs): ((params: unknown) => Promise<ToolsExecutionResult>) => {\n return async (params: unknown) => {\n logger.info({ msg: `Tool execution started: ${toolName}`, params })\n try {\n // MCP entry-boundary seed. This wrapper is the sole chokepoint for every exposed tool\n // (`src/mcp/tools/index.ts` holds the only `registerTool` call), and the `mcp` command itself is\n // in program.ts's SEED_EXCLUDED \u2014 so an MCP server seeds lazily, on its FIRST tool invocation,\n // and a server that never receives one never writes to $HOME. The once-per-process guard inside\n // ensureUserProjectConfig makes calls 2..N free on this long-lived process.\n //\n // Deliberately INSIDE the try, not before it: defense-in-depth. ensureUserProjectConfig is\n // contractually never-throw, but if that contract ever broke, an unhandled rejection here would\n // take down the long-lived server. Inside the try it degrades to an ordinary tool error.\n await ensureUserProjectConfig()\n\n // The command handlers record their resolved flags into the `commandEcho` singleton. On the CLI,\n // Commander's `preAction` clears it before every command; this long-lived server never runs that\n // hook, so without a reset here one tool call's flags would leak into the next one's snapshot and\n // the options array would grow for the life of the process.\n commandEcho.reset()\n\n const payload = await handler({ ...(params as object), confirmedCommand: true })\n\n logger.info({ msg: `Tool execution successful: ${toolName}` })\n\n return payload\n } catch (error) {\n logger.error({\n err: error,\n params,\n msg: `Tool execution failed: ${toolName}`,\n })\n\n throw error\n }\n }\n}\n", "import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { getExposedMcpTools } from 'src/lib/command-catalog'\nimport { createToolHandler } from 'src/lib/tool-handler'\n\nexport const initializeTools = async (server: McpServer) => {\n // The registered tool set is derived from the single command catalog, filtered\n // by its explicit `mcpExposed` allowlist. doctor / vendor-sync / vendor-manifest\n // are intentionally excluded there and must never be registered here.\n for (const tool of getExposedMcpTools()) {\n server.registerTool(\n tool.name,\n {\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n },\n createToolHandler({ toolName: tool.name, handler: tool.handler }),\n )\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["StdioServerTransport", "process", "process", "setupErrorHandlers", "logger", "process", "error", "LOG_FILE_PATH", "reason", "promise", "McpServer", "initializePrompts", "_server", "initializeResources", "_server", "createToolHandler", "toolName", "handler", "params", "logger", "ensureUserProjectConfig", "commandEcho", "payload", "error", "initializeTools", "server", "tool", "getExposedMcpTools", "createToolHandler", "createMcpServer", "mcpMode", "server", "McpServer", "initializePrompts", "initializeResources", "initializeTools", "suppressTypelessPackageJsonWarning", "logger", "initLoggerMcp", "startServer", "server", "createMcpServer", "error", "process", "transport", "StdioServerTransport", "setupErrorHandlers"]
7
7
  }
@@ -0,0 +1,2 @@
1
+ import{a as p,c as u}from"./chunk-NLOLELZ5.js";import{a as l}from"./chunk-TPVXMJ7N.js";import{render as C}from"ink";import O from"node:process";import{Box as c,Static as S,Text as d}from"ink";import{useEffect as F,useState as b}from"react";import{jsx as a,jsxs as v}from"react/jsx-runtime";var g=r=>{let{phase:e,narration:t,readyLines:i}=r,[o,f]=b(0);return F(()=>{if(i)return;let n=setInterval(()=>{f(s=>s+1)},w);return()=>{clearInterval(n)}},[i]),i?a(S,{items:i,children:(n,s)=>a(d,{children:n},s)}):v(c,{flexDirection:"column",children:[v(c,{children:[a(d,{color:"cyan",children:m[o%m.length]}),a(d,{dimColor:!0,children:` ${e}`})]}),t?a(d,{dimColor:!0,children:t}):null]})},m=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],w=80;import{Box as P,Static as D,Text as L}from"ink";import{Fragment as T,jsx as h,jsxs as z}from"react/jsx-runtime";var R=r=>{let{headerLines:e,logLines:t,footerLines:i}=r,o=[...e,...t],f=B(i,r.maxFooterLines);return z(T,{children:[h(D,{items:o,children:(n,s)=>h(L,{children:n},s)}),h(P,{flexDirection:"column",children:f.map((n,s)=>h(L,{children:n},s))})]})},B=(r,e)=>{if(e==null||e<1||r.length<=e)return r;let t=r.slice(0,e-1);return[...t,` \u2026 +${r.length-t.length} more`]};import{jsx as x}from"react/jsx-runtime";var y=class{renderer;renderFn;stdout;verbose;now;instance=null;finalized=!1;persistent=!1;phase="";narration="";headerLines=[];logLines=[];footerLines=[];constructor(e){this.verbose=e.verbose??!1,this.renderFn=e.render??C,this.stdout=e.stdout??l(O.stdout),this.now=e.now??(()=>new Date),this.renderer=new u({appendLog:e.appendLog,verbose:this.verbose,isTTY:!0,write:t=>{this.stdout.write(t)},now:this.now})}logFn=(e,t="info")=>{this.log(e,t)};renderBoot(e){let t=x(g,{phase:this.phase,narration:this.narration,readyLines:e});if(this.instance){this.instance.rerender(t);return}this.instance=this.renderFn(t,{stdout:this.stdout,exitOnCtrlC:!1,patchConsole:!1})}unmount(){this.instance&&(this.instance.unmount(),this.instance=null)}narrate(e){if(this.renderer.teeOnly(e,"info"),this.persistent){this.verbose&&(this.logLines.push(e),this.rerenderPersistent());return}this.finalized||(this.narration=e,this.instance&&this.renderBoot())}log(e,t="info",i={}){let o=t!=="debug"||this.verbose;if(this.persistent){i.tee!==!1&&this.renderer.teeOnly(e,t),o&&(this.logLines.push(e),this.rerenderPersistent());return}o&&this.unmount(),this.renderer.log(e,t,i)}bootStep(e){this.renderer.teeOnly(e,"info"),!this.finalized&&(this.phase=e,this.renderBoot())}stopSpinner(){this.persistent||this.unmount()}ready(e){this.headerLines=this.renderer.formatHeaderLines(e),this.footerLines=this.renderer.formatFooterLines(e),this.logLines=[];for(let t of this.headerLines)this.renderer.teeOnly(t,"info");this.finalized=!0,this.persistent=!0,typeof this.stdout.on=="function"&&this.stdout.on("resize",this.onResize),this.rerenderPersistent()}maxFooterLines(){return Math.max(3,(this.stdout.rows??24)-6)}rerenderPersistent(){let e=x(R,{headerLines:this.headerLines,logLines:this.logLines,footerLines:this.footerLines,maxFooterLines:this.maxFooterLines()});if(this.instance){this.instance.rerender(e);return}this.instance=this.renderFn(e,{stdout:this.stdout,exitOnCtrlC:!1,patchConsole:!1})}onResize=()=>{this.persistent&&this.rerenderPersistent()};event(e){if(this.persistent){let t=` ${p(this.now())} ${e.tag} ${e.text}`;this.renderer.teeOnly(`${e.tag} ${e.text}`,"info"),this.logLines.push(t),this.rerenderPersistent();return}this.unmount(),this.renderer.event(e)}refresh(e){this.persistent&&(this.footerLines=this.renderer.formatFooterLines(e),this.rerenderPersistent())}dispose(){this.persistent&&(this.rerenderPersistent(),typeof this.stdout.off=="function"&&this.stdout.off("resize",this.onResize)),this.finalized=!0,this.persistent=!1,this.unmount()}};export{y as PersistentInkDevUi};
2
+ //# sourceMappingURL=persistent-ink-dev-ui-TLUJJC2D.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/tui/dev-ui/persistent-ink-dev-ui.tsx", "../src/tui/dev-ui/boot-region.tsx", "../src/tui/dev-ui/live-region.tsx"],
4
+ "sourcesContent": ["import { render as inkRender } from 'ink'\nimport process from 'node:process'\nimport type { ReactElement } from 'react'\n\nimport type { DevUi, LogLevel, LogOptions, ReadySummary } from 'src/dev/dev-ui'\nimport { DevRenderer, formatClock } from 'src/dev/render'\nimport { createSafeStream } from 'src/tui/safe-stderr'\n\nimport { BootRegion } from './boot-region'\nimport { LiveRegion } from './live-region'\n\n/**\n * Ink-backed {@link DevUi} for `infra-kit dev`: a boot region that collapses into a PERSISTENT status\n * panel. It is the UI on any interactive TTY.\n *\n * There is one shape of session now, not two. Until recently `ready` branched on `summary.hasUiChild`\n * and, for a UI session, unmounted Ink and handed off to a DECSTBM scroll-region UI \u2014 because the\n * `turbo run dev` child's piped output produced an unbounded tail, Ink repaints its live region on every\n * appended line, and a pinned footer can only survive a scrolling stream if the TERMINAL is told to\n * confine it.\n *\n * That tail no longer exists. Framework lines and request lines go to their per-service log files and\n * are never printed, so there is nothing to scroll past the panel and nothing for a scroll region to\n * defend it from. The branch, the handoff, and the whole DECSTBM machinery were deleted with the stream\n * that justified them.\n *\n * What still prints above the panel is only what the RUNNER says \u2014 a restart, an unhealthy app, a dead\n * engine. Those are rare and they are events, not logs.\n *\n * The embedded {@link DevRenderer} owns the file-tee format (`teeOnly`); in persistent mode it is used\n * ONLY for teeing, never its direct stdout write, which would collide with the live region. Every\n * component here is OUTPUT-ONLY (no `useInput`) \u2014 see live-region.tsx.\n */\n\n/** The subset of an Ink render handle this UI drives \u2014 shared by `ink` and `ink-testing-library`. */\ninterface RenderHandle {\n rerender: (tree: ReactElement) => void\n unmount: () => void\n}\n\n/** Render seam: `ink`'s `render` by default; `ink-testing-library`'s `render` is injected in tests. */\ntype RenderFn = (tree: ReactElement, options?: unknown) => RenderHandle\n\nexport interface PersistentInkDevUiDeps {\n /** File tee \u2014 the same appender the runner hands the plain renderer. */\n appendLog: (text: string) => void\n /** Mirrors the renderer's verbose gate: only in verbose does a `debug` log reach the terminal. */\n verbose?: boolean\n /** Clock seam (for deterministic tee + event timestamps in tests). */\n now?: () => Date\n /** Render seam (default `ink`'s `render`); tests inject `ink-testing-library`'s `render`. */\n render?: RenderFn\n /**\n * Target stream (default: `process.stdout` behind the scrollback-safe filter).\n *\n * The runner injects a stream whose `write` BYPASSES the output interceptor. That is not optional:\n * the interceptor owns `process.stdout` for the life of the session, so a panel painted through the\n * raw stream would have its frames filed into a log file instead of drawn \u2014 a blank screen, with no\n * error and no failing test.\n */\n stdout?: NodeJS.WriteStream\n}\n\nexport class PersistentInkDevUi implements DevUi {\n private readonly renderer: DevRenderer\n private readonly renderFn: RenderFn\n private readonly stdout: NodeJS.WriteStream\n private readonly verbose: boolean\n private readonly now: () => Date\n /** Live Ink handle, or `null` when not mounted. */\n private instance: RenderHandle | null = null\n /** True once boot is done: never mount the boot region again. */\n private finalized = false\n /** True while the persistent panel owns the terminal (after ready). */\n private persistent = false\n private phase = ''\n private narration = ''\n private headerLines: string[] = []\n private logLines: string[] = []\n private footerLines: string[] = []\n\n constructor(deps: PersistentInkDevUiDeps) {\n this.verbose = deps.verbose ?? false\n this.renderFn = deps.render ?? (inkRender as RenderFn)\n // NOT raw `process.stdout`: Ink writes `ESC[3J` (erase saved lines) whenever a frame overflows the\n // viewport, which wipes the terminal's SCROLLBACK \u2014 the user's history, and every previous command's\n // output when `dev` runs as a session-shell child. `createSafeStream` drops that one escape and\n // forwards everything else live.\n this.stdout = deps.stdout ?? createSafeStream(process.stdout as NodeJS.WriteStream)\n this.now =\n deps.now ??\n ((): Date => {\n return new Date()\n })\n this.renderer = new DevRenderer({\n appendLog: deps.appendLog,\n verbose: this.verbose,\n isTTY: true,\n write: (text) => {\n this.stdout.write(text)\n },\n now: this.now,\n })\n }\n\n /** `LogFn`-shaped adapter for the build runner seam. */\n readonly logFn = (message: string, level: LogLevel = 'info'): void => {\n this.log(message, level)\n }\n\n // ---- boot ---------------------------------------------------------------\n\n /** Mount the Ink boot region (or rerender it in place) with the current phase + narration. */\n private renderBoot(readyLines?: string[]): void {\n const tree = <BootRegion phase={this.phase} narration={this.narration} readyLines={readyLines} />\n\n if (this.instance) {\n this.instance.rerender(tree)\n\n return\n }\n this.instance = this.renderFn(tree, { stdout: this.stdout, exitOnCtrlC: false, patchConsole: false })\n }\n\n /** Unmount Ink if mounted (idempotent). */\n private unmount(): void {\n if (this.instance) {\n this.instance.unmount()\n this.instance = null\n }\n }\n\n narrate(message: string): void {\n this.renderer.teeOnly(message, 'info')\n if (this.persistent) {\n // Visible only in verbose (matches the plain renderer); tee already happened above.\n if (this.verbose) {\n this.logLines.push(message)\n this.rerenderPersistent()\n }\n\n return\n }\n if (this.finalized) {\n return\n }\n this.narration = message\n if (this.instance) {\n this.renderBoot()\n }\n }\n\n log(message: string, level: LogLevel = 'info', options: LogOptions = {}): void {\n const willWrite = level !== 'debug' || this.verbose\n\n if (this.persistent) {\n // Never the embedded renderer's direct stdout write here \u2014 append to the live region instead.\n // `tee: false` means the caller has ALREADY filed this line (see `DevServerRunner.reportFault`);\n // teeing it again would put a second copy of every fault into `runner.log`.\n if (options.tee !== false) {\n this.renderer.teeOnly(message, level)\n }\n if (willWrite) {\n this.logLines.push(message)\n this.rerenderPersistent()\n }\n\n return\n }\n if (willWrite) {\n this.unmount()\n }\n this.renderer.log(message, level, options)\n }\n\n bootStep(phase: string): void {\n this.renderer.teeOnly(phase, 'info')\n if (this.finalized) {\n return\n }\n this.phase = phase\n this.renderBoot()\n }\n\n stopSpinner(): void {\n if (!this.persistent) {\n this.unmount()\n }\n }\n\n // ---- the persistent panel ----------------------------------------------\n\n /** Commit the header, tee it, mount the live panel, and STAY mounted for the rest of the session. */\n ready(summary: ReadySummary): void {\n this.headerLines = this.renderer.formatHeaderLines(summary)\n this.footerLines = this.renderer.formatFooterLines(summary)\n this.logLines = []\n for (const line of this.headerLines) {\n this.renderer.teeOnly(line, 'info')\n }\n this.finalized = true\n this.persistent = true\n // Re-clamp the panel when the terminal resizes. Guarded: an injected (test/piped) stream may not\n // be an EventEmitter TTY.\n if (typeof this.stdout.on === 'function') {\n this.stdout.on('resize', this.onResize)\n }\n this.rerenderPersistent()\n }\n\n /**\n * Cap for the pinned panel: it is a live region and cannot scroll, so it must never exceed the\n * viewport. Reserve rows for the runner's occasional lines above it; fall back to an 80\u00D724 assumption\n * when the stream reports no row count (piped / test stdout).\n */\n private maxFooterLines(): number {\n return Math.max(3, (this.stdout.rows ?? 24) - 6)\n }\n\n /** Mount or repaint the live region from the current header / logs / panel snapshot. */\n private rerenderPersistent(): void {\n const tree = (\n <LiveRegion\n headerLines={this.headerLines}\n logLines={this.logLines}\n footerLines={this.footerLines}\n maxFooterLines={this.maxFooterLines()}\n />\n )\n\n if (this.instance) {\n this.instance.rerender(tree)\n\n return\n }\n this.instance = this.renderFn(tree, { stdout: this.stdout, exitOnCtrlC: false, patchConsole: false })\n }\n\n /** Re-clamp + repaint on terminal resize (SIGWINCH), so a shrink never overflows the viewport. */\n private readonly onResize = (): void => {\n if (this.persistent) {\n this.rerenderPersistent()\n }\n }\n\n /**\n * A tagged, timestamped line. The runner no longer emits any \u2014 framework and request lines go to their\n * per-service files instead of the terminal \u2014 but the seam stays on the {@link DevUi} contract for the\n * plain/non-TTY renderer, which still streams.\n */\n event(input: { tag: string; text: string }): void {\n if (this.persistent) {\n const line = ` ${formatClock(this.now())} ${input.tag} ${input.text}`\n\n this.renderer.teeOnly(`${input.tag} ${input.text}`, 'info')\n this.logLines.push(line)\n this.rerenderPersistent()\n\n return\n }\n this.unmount()\n this.renderer.event(input)\n }\n\n /**\n * Repaint the live status rows in place from a fresh summary (header + runner lines untouched).\n *\n * This is the panel's heartbeat. With no log tail on screen, a panel whose numbers never move is\n * indistinguishable from a hung process \u2014 so this is what proves the session is alive.\n */\n refresh(summary: ReadySummary): void {\n // A session still in boot has no panel to refresh.\n if (!this.persistent) {\n return\n }\n this.footerLines = this.renderer.formatFooterLines(summary)\n this.rerenderPersistent()\n }\n\n dispose(): void {\n if (this.persistent) {\n // Commit the last panel state, then release the terminal.\n this.rerenderPersistent()\n if (typeof this.stdout.off === 'function') {\n this.stdout.off('resize', this.onResize)\n }\n }\n this.finalized = true\n this.persistent = false\n this.unmount()\n }\n}\n", "import { Box, Static, Text } from 'ink'\nimport { useEffect, useState } from 'react'\n\n/**\n * The Ink boot region for `infra-kit dev`: an animated spinner + phase line while the server boots,\n * then the ready header committed to scrollback via `<Static>`.\n *\n * HARD INVARIANT \u2014 this component (and every boot component under `src/tui/dev-ui/`) is OUTPUT-ONLY.\n * It MUST NEVER call `useInput`. `useInput` arms Ink's raw mode + Ctrl-C interception, which would\n * steal SIGINT from the process signal handler that owns dev-server shutdown. The spinner animates via\n * a self-contained interval only; there is no keyboard input during boot. Do not add `useInput` here.\n */\n\nexport interface BootRegionProps {\n /** Current boot phase, shown beside the spinner. */\n phase: string\n /** Latest narration detail (dim subtitle under the spinner); empty hides the line. */\n narration: string\n /**\n * When set, boot is done: the pre-formatted ready-header lines are committed to scrollback via\n * `<Static>` and the transient spinner region is gone. The renderer unmounts right after.\n */\n readyLines?: string[]\n}\n\n/** Animated boot spinner + phase/narration, or the committed ready header once `readyLines` is set. */\nexport const BootRegion = (props: BootRegionProps) => {\n const { phase, narration, readyLines } = props\n\n const [frame, setFrame] = useState(0)\n\n useEffect(() => {\n // No spinner once the ready header is committed.\n if (readyLines) {\n return\n }\n\n const timer = setInterval(() => {\n setFrame((f) => {\n return f + 1\n })\n }, SPINNER_INTERVAL_MS)\n\n return () => {\n clearInterval(timer)\n }\n }, [readyLines])\n\n if (readyLines) {\n // `<Static>` writes each line once, above the (now empty) live region, so the header persists in\n // scrollback after unmount. Lines are pre-formatted by DevRenderer.formatReadyLines.\n return (\n <Static items={readyLines}>\n {(line, index) => {\n return <Text key={index}>{line}</Text>\n }}\n </Static>\n )\n }\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text color=\"cyan\">{SPINNER_FRAMES[frame % SPINNER_FRAMES.length]}</Text>\n <Text dimColor>{` ${phase}`}</Text>\n </Box>\n {narration ? <Text dimColor>{narration}</Text> : null}\n </Box>\n )\n}\n\nconst SPINNER_FRAMES = ['\u280B', '\u2819', '\u2839', '\u2838', '\u283C', '\u2834', '\u2826', '\u2827', '\u2807', '\u280F']\nconst SPINNER_INTERVAL_MS = 80\n", "import { Box, Static, Text } from 'ink'\n\n/**\n * The persistent Ink live region for `infra-kit dev` (backend-only sessions): the committed header +\n * growing log scrollback above, and a live health footer re-rendered in place below.\n *\n * HARD INVARIANT \u2014 this component (and every component under `src/tui/dev-ui/`) is OUTPUT-ONLY. It\n * MUST NEVER call `useInput`. `useInput` arms Ink's raw mode + Ctrl-C interception, which would steal\n * SIGINT from the process signal handler that owns dev-server shutdown. There is no keyboard input in\n * the live region \u2014 it is props only, with no state, no effects, no input. Do not add `useInput` here.\n */\n\nexport interface LiveRegionProps {\n /** Reference header, committed once to scrollback via `<Static>` (from `formatHeaderLines`). */\n headerLines: string[]\n /** Log lines committed above the footer as they arrive (also via the SAME `<Static>`). */\n logLines: string[]\n /** Live footer, re-rendered in place on every health change (from `formatFooterLines`). */\n footerLines: string[]\n /**\n * Cap on rendered footer rows so a many-backend session never eats a short viewport (the footer is a\n * live region \u2014 it cannot scroll). Overflow collapses into a trailing dim `\u2026 +N more` line. Omit for\n * no cap. The pinned footer must stay short; the full health picture is always in the log file.\n */\n maxFooterLines?: number\n}\n\n/**\n * A SINGLE `<Static>` streams the header then the growing logs (Ink writes only newly-appended items\n * each commit, so the header lands first and logs accrete beneath it \u2014 one block, never two). The\n * footer is an ordinary `<Box>`, so it repaints in place on each rerender without touching scrollback.\n */\nexport const LiveRegion = (props: LiveRegionProps) => {\n const { headerLines, logLines, footerLines } = props\n\n const staticItems = [...headerLines, ...logLines]\n const footer = clampFooter(footerLines, props.maxFooterLines)\n\n return (\n <>\n <Static items={staticItems}>\n {(line, index) => {\n return <Text key={index}>{line}</Text>\n }}\n </Static>\n <Box flexDirection=\"column\">\n {footer.map((line, index) => {\n return <Text key={index}>{line}</Text>\n })}\n </Box>\n </>\n )\n}\n\n/** Cap the footer to `max` rows, collapsing the overflow into one trailing ` \u2026 +N more` line. */\nexport const clampFooter = (lines: string[], max?: number): string[] => {\n if (max == null || max < 1 || lines.length <= max) {\n return lines\n }\n\n const shown = lines.slice(0, max - 1)\n\n return [...shown, ` \u2026 +${lines.length - shown.length} more`]\n}\n"],
5
+ "mappings": "uFAAA,OAAS,UAAUA,MAAiB,MACpC,OAAOC,MAAa,eCDpB,OAAS,OAAAC,EAAK,UAAAC,EAAQ,QAAAC,MAAY,MAClC,OAAS,aAAAC,EAAW,YAAAC,MAAgB,QAqDnB,cAAAC,EAQX,QAAAC,MARW,oBA5BV,IAAMC,EAAcC,GAA2B,CACpD,GAAM,CAAE,MAAAC,EAAO,UAAAC,EAAW,WAAAC,CAAW,EAAIH,EAEnC,CAACI,EAAOC,CAAQ,EAAIT,EAAS,CAAC,EAmBpC,OAjBAD,EAAU,IAAM,CAEd,GAAIQ,EACF,OAGF,IAAMG,EAAQ,YAAY,IAAM,CAC9BD,EAAUE,GACDA,EAAI,CACZ,CACH,EAAGC,CAAmB,EAEtB,MAAO,IAAM,CACX,cAAcF,CAAK,CACrB,CACF,EAAG,CAACH,CAAU,CAAC,EAEXA,EAIAN,EAACJ,EAAA,CAAO,MAAOU,EACZ,UAACM,EAAMC,IACCb,EAACH,EAAA,CAAkB,SAAAe,GAARC,CAAa,EAEnC,EAKFZ,EAACN,EAAA,CAAI,cAAc,SACjB,UAAAM,EAACN,EAAA,CACC,UAAAK,EAACH,EAAA,CAAK,MAAM,OAAQ,SAAAiB,EAAeP,EAAQO,EAAe,MAAM,EAAE,EAClEd,EAACH,EAAA,CAAK,SAAQ,GAAE,aAAIO,CAAK,GAAG,GAC9B,EACCC,EAAYL,EAACH,EAAA,CAAK,SAAQ,GAAE,SAAAQ,EAAU,EAAU,MACnD,CAEJ,EAEMS,EAAiB,CAAC,SAAK,SAAK,SAAK,SAAK,SAAK,SAAK,SAAK,SAAK,SAAK,QAAG,EAClEH,EAAsB,GCxE5B,OAAS,OAAAI,EAAK,UAAAC,EAAQ,QAAAC,MAAY,MAuC9B,mBAAAC,EAGa,OAAAC,EAHb,QAAAC,MAAA,oBAPG,IAAMC,EAAcC,GAA2B,CACpD,GAAM,CAAE,YAAAC,EAAa,SAAAC,EAAU,YAAAC,CAAY,EAAIH,EAEzCI,EAAc,CAAC,GAAGH,EAAa,GAAGC,CAAQ,EAC1CG,EAASC,EAAYH,EAAaH,EAAM,cAAc,EAE5D,OACEF,EAAAF,EAAA,CACE,UAAAC,EAACH,EAAA,CAAO,MAAOU,EACZ,UAACG,EAAMC,IACCX,EAACF,EAAA,CAAkB,SAAAY,GAARC,CAAa,EAEnC,EACAX,EAACJ,EAAA,CAAI,cAAc,SAChB,SAAAY,EAAO,IAAI,CAACE,EAAMC,IACVX,EAACF,EAAA,CAAkB,SAAAY,GAARC,CAAa,CAChC,EACH,GACF,CAEJ,EAGaF,EAAc,CAACG,EAAiBC,IAA2B,CACtE,GAAIA,GAAO,MAAQA,EAAM,GAAKD,EAAM,QAAUC,EAC5C,OAAOD,EAGT,IAAME,EAAQF,EAAM,MAAM,EAAGC,EAAM,CAAC,EAEpC,MAAO,CAAC,GAAGC,EAAO,aAAQF,EAAM,OAASE,EAAM,MAAM,OAAO,CAC9D,EFmDiB,cAAAC,MAAA,oBAnDV,IAAMC,EAAN,KAA0C,CAC9B,SACA,SACA,OACA,QACA,IAET,SAAgC,KAEhC,UAAY,GAEZ,WAAa,GACb,MAAQ,GACR,UAAY,GACZ,YAAwB,CAAC,EACzB,SAAqB,CAAC,EACtB,YAAwB,CAAC,EAEjC,YAAYC,EAA8B,CACxC,KAAK,QAAUA,EAAK,SAAW,GAC/B,KAAK,SAAWA,EAAK,QAAWC,EAKhC,KAAK,OAASD,EAAK,QAAUE,EAAiBC,EAAQ,MAA4B,EAClF,KAAK,IACHH,EAAK,MACJ,IACQ,IAAI,MAEf,KAAK,SAAW,IAAII,EAAY,CAC9B,UAAWJ,EAAK,UAChB,QAAS,KAAK,QACd,MAAO,GACP,MAAQK,GAAS,CACf,KAAK,OAAO,MAAMA,CAAI,CACxB,EACA,IAAK,KAAK,GACZ,CAAC,CACH,CAGS,MAAQ,CAACC,EAAiBC,EAAkB,SAAiB,CACpE,KAAK,IAAID,EAASC,CAAK,CACzB,EAKQ,WAAWC,EAA6B,CAC9C,IAAMC,EAAOX,EAACY,EAAA,CAAW,MAAO,KAAK,MAAO,UAAW,KAAK,UAAW,WAAYF,EAAY,EAE/F,GAAI,KAAK,SAAU,CACjB,KAAK,SAAS,SAASC,CAAI,EAE3B,MACF,CACA,KAAK,SAAW,KAAK,SAASA,EAAM,CAAE,OAAQ,KAAK,OAAQ,YAAa,GAAO,aAAc,EAAM,CAAC,CACtG,CAGQ,SAAgB,CAClB,KAAK,WACP,KAAK,SAAS,QAAQ,EACtB,KAAK,SAAW,KAEpB,CAEA,QAAQH,EAAuB,CAE7B,GADA,KAAK,SAAS,QAAQA,EAAS,MAAM,EACjC,KAAK,WAAY,CAEf,KAAK,UACP,KAAK,SAAS,KAAKA,CAAO,EAC1B,KAAK,mBAAmB,GAG1B,MACF,CACI,KAAK,YAGT,KAAK,UAAYA,EACb,KAAK,UACP,KAAK,WAAW,EAEpB,CAEA,IAAIA,EAAiBC,EAAkB,OAAQI,EAAsB,CAAC,EAAS,CAC7E,IAAMC,EAAYL,IAAU,SAAW,KAAK,QAE5C,GAAI,KAAK,WAAY,CAIfI,EAAQ,MAAQ,IAClB,KAAK,SAAS,QAAQL,EAASC,CAAK,EAElCK,IACF,KAAK,SAAS,KAAKN,CAAO,EAC1B,KAAK,mBAAmB,GAG1B,MACF,CACIM,GACF,KAAK,QAAQ,EAEf,KAAK,SAAS,IAAIN,EAASC,EAAOI,CAAO,CAC3C,CAEA,SAASE,EAAqB,CAC5B,KAAK,SAAS,QAAQA,EAAO,MAAM,EAC/B,MAAK,YAGT,KAAK,MAAQA,EACb,KAAK,WAAW,EAClB,CAEA,aAAoB,CACb,KAAK,YACR,KAAK,QAAQ,CAEjB,CAKA,MAAMC,EAA6B,CACjC,KAAK,YAAc,KAAK,SAAS,kBAAkBA,CAAO,EAC1D,KAAK,YAAc,KAAK,SAAS,kBAAkBA,CAAO,EAC1D,KAAK,SAAW,CAAC,EACjB,QAAWC,KAAQ,KAAK,YACtB,KAAK,SAAS,QAAQA,EAAM,MAAM,EAEpC,KAAK,UAAY,GACjB,KAAK,WAAa,GAGd,OAAO,KAAK,OAAO,IAAO,YAC5B,KAAK,OAAO,GAAG,SAAU,KAAK,QAAQ,EAExC,KAAK,mBAAmB,CAC1B,CAOQ,gBAAyB,CAC/B,OAAO,KAAK,IAAI,GAAI,KAAK,OAAO,MAAQ,IAAM,CAAC,CACjD,CAGQ,oBAA2B,CACjC,IAAMN,EACJX,EAACkB,EAAA,CACC,YAAa,KAAK,YAClB,SAAU,KAAK,SACf,YAAa,KAAK,YAClB,eAAgB,KAAK,eAAe,EACtC,EAGF,GAAI,KAAK,SAAU,CACjB,KAAK,SAAS,SAASP,CAAI,EAE3B,MACF,CACA,KAAK,SAAW,KAAK,SAASA,EAAM,CAAE,OAAQ,KAAK,OAAQ,YAAa,GAAO,aAAc,EAAM,CAAC,CACtG,CAGiB,SAAW,IAAY,CAClC,KAAK,YACP,KAAK,mBAAmB,CAE5B,EAOA,MAAMQ,EAA4C,CAChD,GAAI,KAAK,WAAY,CACnB,IAAMF,EAAO,KAAKG,EAAY,KAAK,IAAI,CAAC,CAAC,KAAKD,EAAM,GAAG,KAAKA,EAAM,IAAI,GAEtE,KAAK,SAAS,QAAQ,GAAGA,EAAM,GAAG,IAAIA,EAAM,IAAI,GAAI,MAAM,EAC1D,KAAK,SAAS,KAAKF,CAAI,EACvB,KAAK,mBAAmB,EAExB,MACF,CACA,KAAK,QAAQ,EACb,KAAK,SAAS,MAAME,CAAK,CAC3B,CAQA,QAAQH,EAA6B,CAE9B,KAAK,aAGV,KAAK,YAAc,KAAK,SAAS,kBAAkBA,CAAO,EAC1D,KAAK,mBAAmB,EAC1B,CAEA,SAAgB,CACV,KAAK,aAEP,KAAK,mBAAmB,EACpB,OAAO,KAAK,OAAO,KAAQ,YAC7B,KAAK,OAAO,IAAI,SAAU,KAAK,QAAQ,GAG3C,KAAK,UAAY,GACjB,KAAK,WAAa,GAClB,KAAK,QAAQ,CACf,CACF",
6
+ "names": ["inkRender", "process", "Box", "Static", "Text", "useEffect", "useState", "jsx", "jsxs", "BootRegion", "props", "phase", "narration", "readyLines", "frame", "setFrame", "timer", "f", "SPINNER_INTERVAL_MS", "line", "index", "SPINNER_FRAMES", "Box", "Static", "Text", "Fragment", "jsx", "jsxs", "LiveRegion", "props", "headerLines", "logLines", "footerLines", "staticItems", "footer", "clampFooter", "line", "index", "lines", "max", "shown", "jsx", "PersistentInkDevUi", "deps", "inkRender", "createSafeStream", "process", "DevRenderer", "text", "message", "level", "readyLines", "tree", "BootRegion", "options", "willWrite", "phase", "summary", "line", "LiveRegion", "input", "formatClock"]
7
+ }
@@ -1,2 +1,2 @@
1
- import{h as i}from"./chunk-E2SEBLGJ.js";import{a as t}from"./chunk-E2NWNNC6.js";import"./chunk-6FU2TRU5.js";import"./chunk-CHETBZ6M.js";import a from"node:fs";import o from"node:process";import{fileURLToPath as s}from"node:url";var m=r=>{let n=r.indexOf("--parent-pid");if(n===-1)return;let e=Number(r[n+1]);return Number.isInteger(e)&&e>0?e:void 0},p=()=>a.realpathSync(s(new URL("./cli.js",import.meta.url))),d=async()=>{await i(t.version,{selfRealPath:p(),parentPid:m(o.argv)})};d().catch(()=>{o.exitCode=0});
1
+ import{h as i}from"./chunk-UO3FYLPX.js";import{a as t}from"./chunk-FTCM2766.js";import"./chunk-A7FAXZGI.js";import"./chunk-CHETBZ6M.js";import a from"node:fs";import o from"node:process";import{fileURLToPath as s}from"node:url";var m=r=>{let n=r.indexOf("--parent-pid");if(n===-1)return;let e=Number(r[n+1]);return Number.isInteger(e)&&e>0?e:void 0},p=()=>a.realpathSync(s(new URL("./cli.js",import.meta.url))),d=async()=>{await i(t.version,{selfRealPath:p(),parentPid:m(o.argv)})};d().catch(()=>{o.exitCode=0});
2
2
  //# sourceMappingURL=update-check.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infra-kit",
3
3
  "type": "module",
4
- "version": "0.3.0",
4
+ "version": "0.3.2",
5
5
  "files": [
6
6
  "dist"
7
7
  ],
@@ -48,7 +48,7 @@
48
48
  "ink": "^7.1.0",
49
49
  "pino": "^10.3.1",
50
50
  "pino-pretty": "^13.1.3",
51
- "portless": "^0.15.1",
51
+ "portless": "^0.15.2",
52
52
  "react": "19.2.7",
53
53
  "yaml": "^2.9.0",
54
54
  "zod": "^4.4.3",
@@ -1,2 +0,0 @@
1
- import{b as V}from"./chunk-TPVXMJ7N.js";import{render as ye}from"ink";import Ce from"node:process";import{Box as K,Text as P,useApp as se,useInput as ue,useWindowSize as me}from"ink";import{useEffect as Z,useState as G}from"react";import{useMemo as le,useState as J}from"react";var k=(t,e)=>{let[n,p]=J(""),[r,f]=J(0),i=le(()=>{let m=n.trim().toLowerCase();return m?t.filter(h=>e(h).toLowerCase().includes(m)):t},[t,n,e]),l=Math.min(r,Math.max(0,i.length-1));return{filtered:i,activeIndex:l,query:n,handleNavigation:(m,h)=>{let w=i.length;return h.upArrow?(w>0&&f((l-1+w)%w),!0):h.downArrow?(w>0&&f((l+1)%w),!0):h.backspace||h.delete?(p(n.slice(0,-1)),f(0),!0):m&&!h.ctrl&&!h.meta?(p(n+m),f(0),!0):!1}}};var ce=()=>{},I=(t,e=0)=>t<5?{mode:"tiny",chromeRows:0,groups:!1}:t<10?{mode:"compact",chromeRows:4+e,groups:!1}:{mode:"full",chromeRows:6+e,groups:!0},ae=(t,e,n,p)=>{let r=0,f;for(let i=e;i<n;i+=1){let l=t[i];if(!l)break;let a=p(l),m=a!==void 0&&a!==f;r+=1+(m?1:0)+(m&&i>e?1:0),f=a}return r},X=(t,e,n,p)=>{let r=e;for(;r<t.length&&ae(t,e,r+1,p)<=n;)r+=1;return r},E=(t,e,n,p,r)=>{let f=r?.groupOf??ce,i=Math.max(0,p-(r?.chromeRows??6)),l=Math.min(e,n);for(;n>=X(t,l,i,f)&&l<n;)l+=1;let a=X(t,l,i,f);return{visible:t.slice(l,a),start:l,hiddenBefore:l,hiddenAfter:Math.max(0,t.length-a)}};import{jsx as O,jsxs as H}from"react/jsx-runtime";var j=t=>{let{items:e,onSubmit:n,onCancel:p,required:r=!0,allowSelectAll:f=!0}=t,i={hint:"Select branches \u2014 type to filter, \u2191\u2193 to move, Space to toggle, Ctrl-A all, Enter to submit, Esc to cancel",prompt:"\u276F ",empty:"No matching branches",tiny:"terminal too short \u2014 resize to pick branches",requiredHint:"select at least one",more:"\u2026"},l=e.reduce((d,y)=>Math.max(d,y.label.length),0),{exit:a}=se(),{rows:m}=me(),{filtered:h,activeIndex:w,query:v,handleNavigation:$}=k(e,de),[b,W]=G(()=>new Set),[S,x]=G(!1),[s,u]=G(0),[T,q]=G(!1);Z(()=>{T&&a()},[T,a]);let L=I(m,1),o=E(h,s,w,m,{chromeRows:L.chromeRows});Z(()=>{o.start!==s&&u(o.start)},[o.start,s]);let c=d=>{W(y=>{let g=new Set(y);return g.has(d)?g.delete(d):g.add(d),g})},M=()=>{W(d=>e.every(g=>d.has(g.value))?new Set:new Set(e.map(g=>g.value)))},N=()=>{if(r&&b.size===0){x(!0);return}n([...b]),q(!0)};return ue((d,y)=>{if(!T){if(y.escape||y.ctrl&&d==="c"){p(),q(!0);return}if(y.ctrl&&d==="a"){f&&M();return}if(d===" "){let g=h[w];g&&c(g.value);return}if(y.return){N();return}$(d,y)}}),T?null:L.mode==="tiny"?O(P,{dimColor:!0,wrap:"truncate",children:i.tiny}):H(K,{flexDirection:"column",children:[L.mode==="full"?O(P,{dimColor:!0,wrap:"truncate",children:i.hint}):null,H(K,{children:[O(P,{color:"cyan",children:i.prompt}),O(P,{wrap:"truncate-start",children:v})]}),H(K,{flexDirection:"column",marginTop:L.mode==="full"?1:0,children:[h.length===0?O(P,{dimColor:!0,wrap:"truncate",children:i.empty}):null,o.hiddenBefore>0?O(P,{dimColor:!0,wrap:"truncate",children:`${i.more} ${o.hiddenBefore} above`}):null,o.visible.map((d,y)=>{let g=o.start+y===w,ie=b.has(d.value);return H(P,{color:g?"green":void 0,wrap:"truncate",children:[g?"\u203A ":" ",ie?"[x] ":"[ ] ",d.label.padEnd(l)," ",O(P,{dimColor:!0,children:d.description??""})]},d.value)}),o.hiddenAfter>0?O(P,{dimColor:!0,wrap:"truncate",children:`${i.more} ${o.hiddenAfter} below`}):null]}),S?O(P,{dimColor:!0,wrap:"truncate",children:i.requiredHint}):null]})};function de(t){return`${t.label} ${t.description??""}`}import{Box as Q,Text as R,useApp as pe,useInput as fe,useWindowSize as he}from"ink";import{useEffect as ee,useState as te}from"react";import{jsx as A,jsxs as z}from"react/jsx-runtime";var re=t=>{let{items:e,onSelect:n,onCancel:p}=t,r={hint:"Select a branch \u2014 type to filter, \u2191\u2193 to move, Enter to select, Esc to cancel",prompt:"\u276F ",empty:"No matching branches",tiny:"terminal too short \u2014 resize to pick a branch",more:"\u2026"},f=e.reduce((s,u)=>Math.max(s,u.label.length),0),{exit:i}=pe(),{rows:l}=he(),{filtered:a,activeIndex:m,query:h,handleNavigation:w}=k(e,xe),[v,$]=te(0),[b,W]=te(!1);ee(()=>{b&&i()},[b,i]);let S=I(l),x=E(a,v,m,l,{chromeRows:S.chromeRows});return ee(()=>{x.start!==v&&$(x.start)},[x.start,v]),fe((s,u)=>{if(!b){if(u.escape||u.ctrl&&s==="c"){p(),W(!0);return}if(u.return){let T=a[m];T&&(n(T.value),W(!0));return}w(s,u)}}),b?null:S.mode==="tiny"?A(R,{dimColor:!0,wrap:"truncate",children:r.tiny}):z(Q,{flexDirection:"column",children:[S.mode==="full"?A(R,{dimColor:!0,wrap:"truncate",children:r.hint}):null,z(Q,{children:[A(R,{color:"cyan",children:r.prompt}),A(R,{wrap:"truncate-start",children:h})]}),z(Q,{flexDirection:"column",marginTop:S.mode==="full"?1:0,children:[a.length===0?A(R,{dimColor:!0,wrap:"truncate",children:r.empty}):null,x.hiddenBefore>0?A(R,{dimColor:!0,wrap:"truncate",children:`${r.more} ${x.hiddenBefore} above`}):null,x.visible.map((s,u)=>{let T=x.start+u===m;return z(R,{color:we(s.type,T),wrap:"truncate",children:[T?"\u203A ":" ",s.label.padEnd(f)," ",A(R,{dimColor:!0,children:s.description??""})]},s.value)}),x.hiddenAfter>0?A(R,{dimColor:!0,wrap:"truncate",children:`${r.more} ${x.hiddenAfter} below`}):null]})]})};function we(t,e){return t==="hotfix"?"red":e?"green":void 0}function xe(t){return`${t.label} ${t.description??""}`}import{Box as D,Text as C,useApp as ge,useInput as be,useWindowSize as Te}from"ink";import{useEffect as ne,useMemo as Se,useState as F}from"react";import{jsx as B,jsxs as _}from"react/jsx-runtime";var oe=t=>{let{items:e,onSelect:n,onCancel:p}=t,r={hint:"Select a command \u2014 type to filter, \u2191\u2193 to move, Enter to run, Esc to cancel",prompt:"\u276F ",empty:"No matching commands",tiny:"terminal too short \u2014 resize to pick a command",more:"\u2026"},f=24,{exit:i}=ge(),{rows:l}=Te(),[a,m]=F(""),[h,w]=F(0),[v,$]=F(0),[b,W]=F(!1);ne(()=>{b&&i()},[b,i]);let S=Se(()=>{let o=a.trim().toLowerCase();return o?e.filter(c=>`${c.name} ${c.description}`.toLowerCase().includes(o)):e},[e,a]),x=Math.min(h,Math.max(0,S.length-1)),s=I(l),u=E(S,v,x,l,{chromeRows:s.chromeRows,groupOf:s.groups?o=>o.group:void 0});ne(()=>{u.start!==v&&$(u.start)},[u.start,v]);let T=()=>{p(),W(!0)},q=o=>{let c=S.length;c>0&&w((x+o+c)%c)};if(be((o,c)=>{if(!b){if(c.ctrl&&o==="c"){T();return}if(c.escape&&a){m(""),w(0);return}if(c.escape){T();return}if(c.return){let M=S[x];M&&(n(M.name),W(!0));return}if(c.upArrow){q(-1);return}if(c.downArrow){q(1);return}if(c.backspace||c.delete){m(a.slice(0,-1)),w(0);return}o&&!c.ctrl&&!c.meta&&(m(a+o),w(0))}}),b)return null;if(s.mode==="tiny")return B(C,{dimColor:!0,wrap:"truncate",children:r.tiny});let L="";return _(D,{flexDirection:"column",children:[s.mode==="full"?B(C,{dimColor:!0,wrap:"truncate",children:r.hint}):null,_(D,{children:[B(C,{color:"cyan",children:r.prompt}),B(C,{wrap:"truncate-start",children:a})]}),_(D,{flexDirection:"column",marginTop:s.mode==="full"?1:0,children:[S.length===0?B(C,{dimColor:!0,wrap:"truncate",children:r.empty}):null,u.hiddenBefore>0?B(C,{dimColor:!0,wrap:"truncate",children:`${r.more} ${u.hiddenBefore} above`}):null,u.visible.map((o,c)=>{let M=u.start+c===x,N=s.groups&&o.group!==L;return L=o.group,_(D,{flexDirection:"column",children:[N&&c>0?B(C,{children:" "}):null,N?B(C,{color:"yellow",wrap:"truncate",children:`\u2014 ${o.group} \u2014`}):null,_(C,{color:M?"green":void 0,wrap:"truncate",children:[M?"\u203A ":" ",o.name.padEnd(f)," ",B(C,{dimColor:!0,children:o.description})]})]},o.name)}),u.hiddenAfter>0?B(C,{dimColor:!0,wrap:"truncate",children:`${r.more} ${u.hiddenAfter} below`}):null]})]})};import{jsx as Y}from"react/jsx-runtime";var U=async t=>{let{waitUntilExit:e}=ye(t,{stdout:V()});try{await e()}catch{}finally{Ce.stdin.ref()}},Je=async t=>{let e=null;return await U(Y(oe,{items:t,onSelect:n=>{e=n},onCancel:()=>{e=null}})),e},Xe=async t=>{let e=null;return await U(Y(re,{items:t,onSelect:n=>{e=n},onCancel:()=>{e=null}})),e},Ze=async(t,e)=>{let n=null;return await U(Y(j,{items:t,required:e?.required,allowSelectAll:e?.allowSelectAll,onSubmit:p=>{n=p},onCancel:()=>{n=null}})),n};export{Ze as runBranchMultiPicker,Xe as runBranchPicker,Je as runCommandPalette};
2
- //# sourceMappingURL=boot-MTVHM4N7.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/tui/boot.tsx", "../src/tui/screens/branch-multi-picker.tsx", "../src/tui/hooks/use-list-filter.ts", "../src/tui/screens/palette-window.ts", "../src/tui/screens/branch-picker.tsx", "../src/tui/screens/command-palette.tsx"],
4
- "sourcesContent": ["import { render } from 'ink'\nimport process from 'node:process'\nimport type { ReactElement } from 'react'\n\nimport type { BranchPickerItem } from 'src/lib/prompts/types'\n\nimport { safeStderr } from './safe-stderr'\nimport { BranchMultiPicker } from './screens/branch-multi-picker'\nimport { BranchPicker } from './screens/branch-picker'\nimport { CommandPalette } from './screens/command-palette'\nimport type { PaletteItem } from './types'\n\n/**\n * Entry point for the Ink TUI. This module (and everything under `src/tui/`) is\n * the ONLY place allowed to import `ink`/`react`; it is reached exclusively via a\n * dynamic `await import('src/tui/boot')` from the TTY branch of entry/cli.ts, so\n * React never loads on the MCP / `--json` / non-TTY paths. Build splitting keeps\n * it in a separate lazy chunk (see scripts/build.js).\n */\n\n/**\n * Render an Ink element to stderr and resolve once it tears down. Frames are\n * written to stderr so stdout stays clean for the command that runs afterwards\n * (mirrors the previous Inquirer menu).\n *\n * Ink `unref()`s process.stdin when it tears down raw mode on exit. That ref is\n * process-wide and never restored, so the next consumer of stdin \u2014 the Inquirer\n * prompt the selected command opens (e.g. the worktrees-remove checkbox), or a\n * SECOND Ink render in the same process (the bare-menu palette \u2192 picker hop) \u2014\n * reads from an unref'd handle that no longer keeps the event loop alive. Once\n * the command's git/gh subprocesses settle, the loop drains mid-prompt, Node\n * flags entry/cli.ts's top-level await as unsettled, and the process exits with\n * code 13 (the prompt dies on arrival). Re-ref stdin on BOTH the resolve and the\n * reject path so it holds the loop while the next prompt is reading; an idle\n * ref'd stdin does not block exit, so non-interactive follow-up commands still\n * terminate normally. Every render must go through here so the re-ref is never\n * skipped.\n */\nconst renderToStderr = async (element: ReactElement): Promise<void> => {\n const { waitUntilExit } = render(element, {\n stdout: safeStderr(),\n })\n\n try {\n await waitUntilExit()\n } catch {\n // Swallow a teardown rejection: the caller reports cancellation via its own\n // captured sentinel (which stays at its initial value), not via a throw.\n } finally {\n process.stdin.ref()\n }\n}\n\n/**\n * Render the command palette and resolve to the chosen command name, or `null`\n * if the user cancels.\n */\nexport const runCommandPalette = async (items: PaletteItem[]): Promise<string | null> => {\n let selected: string | null = null\n\n await renderToStderr(\n <CommandPalette\n items={items}\n onSelect={(name) => {\n selected = name\n }}\n onCancel={() => {\n selected = null\n }}\n />,\n )\n\n return selected\n}\n\n/**\n * Render the searchable single-select branch picker and resolve to the chosen\n * value, or `null` if the user cancels.\n */\nexport const runBranchPicker = async (items: BranchPickerItem[]): Promise<string | null> => {\n let selected: string | null = null\n\n await renderToStderr(\n <BranchPicker\n items={items}\n onSelect={(value) => {\n selected = value\n }}\n onCancel={() => {\n selected = null\n }}\n />,\n )\n\n return selected\n}\n\n/**\n * Render the searchable multi-select branch picker and resolve to the chosen\n * values, or `null` if the user cancels. The captured variable starts as `null`\n * and is only assigned an array in `onSubmit`, so a cancel is distinguishable\n * from an empty submit.\n */\nexport const runBranchMultiPicker = async (\n items: BranchPickerItem[],\n opts?: { required?: boolean; allowSelectAll?: boolean },\n): Promise<string[] | null> => {\n let selected: string[] | null = null\n\n await renderToStderr(\n <BranchMultiPicker\n items={items}\n required={opts?.required}\n allowSelectAll={opts?.allowSelectAll}\n onSubmit={(values) => {\n selected = values\n }}\n onCancel={() => {\n selected = null\n }}\n />,\n )\n\n return selected\n}\n", "import { Box, Text, useApp, useInput, useWindowSize } from 'ink'\nimport { useEffect, useState } from 'react'\n\nimport type { BranchPickerItem } from '../../lib/prompts/types'\nimport { useListFilter } from '../hooks/use-list-filter'\nimport { listLayout, paletteWindow } from './palette-window'\n\ninterface BranchMultiPickerProps {\n items: BranchPickerItem[]\n /** Called with the selected item values; the app then exits. */\n onSubmit: (values: string[]) => void\n /** Called when the user cancels (Esc / Ctrl-C); the app then exits. */\n onCancel: () => void\n /** When true (default), Enter with an empty selection is ignored. */\n required?: boolean\n /** When true (default), Ctrl-A toggles select-all across all items. */\n allowSelectAll?: boolean\n}\n\n/**\n * Searchable multi-select branch picker rendered with Ink. Same filter/move/cancel\n * behaviour as `BranchPicker`, plus a selection `Set` keyed by item *value* so the\n * selection survives filter changes. Space toggles the active row; Ctrl-A toggles\n * select-all across all items; Enter submits the selected values.\n *\n * The list is WINDOWED to the terminal height for the same safety reason as the\n * palette and the single picker: an overflowing Ink frame corrupts the cursor and\n * welds later output onto the transcript. See `palette-window.ts`.\n */\nexport const BranchMultiPicker = (props: BranchMultiPickerProps) => {\n const { items, onSubmit, onCancel, required = true, allowSelectAll = true } = props\n\n const T = {\n hint: 'Select branches \u2014 type to filter, \u2191\u2193 to move, Space to toggle, Ctrl-A all, Enter to submit, Esc to cancel',\n prompt: '\u276F ',\n empty: 'No matching branches',\n tiny: 'terminal too short \u2014 resize to pick branches',\n requiredHint: 'select at least one',\n more: '\u2026',\n }\n const labelWidth = items.reduce((max, item) => {\n return Math.max(max, item.label.length)\n }, 0)\n\n const { exit } = useApp()\n const { rows } = useWindowSize()\n const { filtered, activeIndex, query, handleNavigation } = useListFilter(items, filterText)\n // Selection keyed by value so it persists across filter changes.\n const [selected, setSelected] = useState<Set<string>>(() => {\n return new Set()\n })\n const [showRequiredHint, setShowRequiredHint] = useState(false)\n // First branch in the visible window; kept in state so the list scrolls by the minimum needed.\n const [windowStart, setWindowStart] = useState(0)\n // When set, the component renders `null` for one frame so Ink erases the list\n // before unmount commits its final frame.\n const [submitted, setSubmitted] = useState(false)\n\n // Exit only after the empty frame has committed, so the list is wiped first.\n useEffect(() => {\n if (submitted) {\n exit()\n }\n }, [submitted, exit])\n\n // The extra chrome row is this screen's `select at least one` hint: counted unconditionally, because\n // it appears on a keystroke and a budget that shrinks mid-frame is a budget that overflows.\n const layout = listLayout(rows, 1)\n // No `groupOf`: a branch list has no headers, so every branch costs exactly one row.\n const view = paletteWindow(filtered, windowStart, activeIndex, rows, { chromeRows: layout.chromeRows })\n\n // Commit the scroll position once the frame that used it has rendered.\n useEffect(() => {\n if (view.start !== windowStart) {\n setWindowStart(view.start)\n }\n }, [view.start, windowStart])\n\n const toggleValue = (value: string) => {\n setSelected((prev) => {\n const next = new Set(prev)\n\n if (next.has(value)) {\n next.delete(value)\n } else {\n next.add(value)\n }\n\n return next\n })\n }\n\n const toggleSelectAll = () => {\n setSelected((prev) => {\n // Clear only when every item (all, not just filtered) is already selected.\n const allSelected = items.every((item) => {\n return prev.has(item.value)\n })\n\n if (allSelected) {\n return new Set()\n }\n\n return new Set(\n items.map((item) => {\n return item.value\n }),\n )\n })\n }\n\n const submit = () => {\n if (required && selected.size === 0) {\n setShowRequiredHint(true)\n\n return\n }\n\n onSubmit([...selected])\n setSubmitted(true)\n }\n\n useInput((input, key) => {\n if (submitted) {\n return\n }\n\n if (key.escape || (key.ctrl && input === 'c')) {\n onCancel()\n setSubmitted(true)\n\n return\n }\n\n if (key.ctrl && input === 'a') {\n if (allowSelectAll) {\n toggleSelectAll()\n }\n\n return\n }\n\n if (input === ' ') {\n const active = filtered[activeIndex]\n\n if (active) {\n toggleValue(active.value)\n }\n\n return\n }\n\n if (key.return) {\n submit()\n\n return\n }\n\n handleNavigation(input, key)\n })\n\n // Final frame: render nothing so Ink erases the list on its way out.\n if (submitted) {\n return null\n }\n\n // Too short for even one branch row plus a prompt. One truncated line always fits.\n if (layout.mode === 'tiny') {\n return (\n <Text dimColor wrap=\"truncate\">\n {T.tiny}\n </Text>\n )\n }\n\n return (\n <Box flexDirection=\"column\">\n {layout.mode === 'full' ? (\n <Text dimColor wrap=\"truncate\">\n {T.hint}\n </Text>\n ) : null}\n <Box>\n <Text color=\"cyan\">{T.prompt}</Text>\n {/* truncate-START: overflow must eat the head of the filter, never the character being typed. */}\n <Text wrap=\"truncate-start\">{query}</Text>\n </Box>\n <Box flexDirection=\"column\" marginTop={layout.mode === 'full' ? 1 : 0}>\n {filtered.length === 0 ? (\n <Text dimColor wrap=\"truncate\">\n {T.empty}\n </Text>\n ) : null}\n {view.hiddenBefore > 0 ? <Text dimColor wrap=\"truncate\">{`${T.more} ${view.hiddenBefore} above`}</Text> : null}\n {view.visible.map((item, position) => {\n const isActive = view.start + position === activeIndex\n const isChecked = selected.has(item.value)\n\n // `wrap=\"truncate\"` keeps one branch = one row at any width. Ink reads the wrap style off the\n // OUTER <Text> only, so it must live here, not on the nested description.\n return (\n <Text color={isActive ? 'green' : undefined} key={item.value} wrap=\"truncate\">\n {isActive ? '\u203A ' : ' '}\n {isChecked ? '[x] ' : '[ ] '}\n {item.label.padEnd(labelWidth)} <Text dimColor>{item.description ?? ''}</Text>\n </Text>\n )\n })}\n {view.hiddenAfter > 0 ? <Text dimColor wrap=\"truncate\">{`${T.more} ${view.hiddenAfter} below`}</Text> : null}\n </Box>\n {showRequiredHint ? (\n <Text dimColor wrap=\"truncate\">\n {T.requiredHint}\n </Text>\n ) : null}\n </Box>\n )\n}\n\nfunction filterText(item: BranchPickerItem): string {\n return `${item.label} ${item.description ?? ''}`\n}\n", "import type { Key } from 'ink'\nimport { useMemo, useState } from 'react'\n\ninterface ListFilter<T> {\n /** Items matching the current query (all items when the query is empty). */\n filtered: T[]\n /** Active row index, always clamped into `filtered`'s bounds. */\n activeIndex: number\n /** Current filter query. */\n query: string\n /**\n * Handle the shared navigation/filter keys (\u2191\u2193 wrap-around, backspace/delete,\n * printable chars). Returns true when the key was consumed so the caller can\n * `return` early; false leaves the key for the component's own handlers.\n */\n handleNavigation: (input: string, key: Key) => boolean\n}\n\n/**\n * Shared list-filter state for the branch pickers: substring filtering,\n * wrap-around \u2191\u2193 movement, and query editing. Extracted so each `useInput`\n * handler stays under the sonarjs cognitive-complexity \u226415 gate.\n */\nexport const useListFilter = <T>(items: T[], toText: (item: T) => string): ListFilter<T> => {\n const [query, setQuery] = useState('')\n const [index, setIndex] = useState(0)\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase()\n\n if (!q) {\n return items\n }\n\n return items.filter((item) => {\n return toText(item).toLowerCase().includes(q)\n })\n }, [items, query, toText])\n\n const activeIndex = Math.min(index, Math.max(0, filtered.length - 1))\n\n const handleNavigation = (input: string, key: Key): boolean => {\n const n = filtered.length\n\n if (key.upArrow) {\n // `+ n` sidesteps JS negative modulo (e.g. -1 % 5 === -1, not 4).\n if (n > 0) {\n setIndex((activeIndex - 1 + n) % n)\n }\n\n return true\n }\n\n if (key.downArrow) {\n if (n > 0) {\n setIndex((activeIndex + 1) % n)\n }\n\n return true\n }\n\n if (key.backspace || key.delete) {\n setQuery(query.slice(0, -1))\n setIndex(0)\n\n return true\n }\n\n // Printable character: append to the filter and reset to the first row.\n if (input && !key.ctrl && !key.meta) {\n setQuery(query + input)\n setIndex(0)\n\n return true\n }\n\n return false\n }\n\n return { filtered, activeIndex, query, handleNavigation }\n}\n", "/**\n * Pure viewport arithmetic shared by every Ink list screen (the command palette and both branch\n * pickers): which slice of the (already filtered) list fits the terminal, and how much chrome the\n * screen may spend around it.\n *\n * This is a SAFETY guard, not a readability one. Ink's cleanup walks the cursor upward one line at a\n * time (`ESC[2K ESC[1A` per row) and `ESC[1A` SATURATES AT ROW 0. So the moment a frame is taller than\n * the viewport, the terminal scrolls while Ink draws, the upward walk erases the wrong rows, and the\n * cursor is left parked ABOVE live content \u2014 after which every later write (the session's status\n * footer, the next palette frame) is deposited on top of the transcript. That is the \"welded rows\" bug:\n * `\u276F ` printed over the head of `ERROR: \u2026`, leaving `ROR: \u2026` behind.\n *\n * The invariant this module exists to enforce, for every (columns, rows):\n *\n * rendered frame height < terminal rows\n *\n * Hold that, and Ink never takes its overflow path: `clearTerminal` never fires, `eraseLines` never\n * saturates, the cursor stays below the transcript, and a fresh (cold) Ink instance \u2014 which erases\n * NOTHING before its first frame \u2014 simply appends below the transcript like any other CLI. No new\n * escape bytes required.\n *\n * Two things make the row count knowable at all:\n * - Every list row is rendered `wrap=\"truncate\"`, so one item is always exactly one row. Without that\n * a long description wraps to two rows and this arithmetic silently under-counts (Ink wraps text\n * itself and measures the WRAPPED height, so it, not us, would be right).\n * - Chrome is counted unconditionally, even when a given row is not drawn (see `CHROME_ROWS`). Over-\n * counting shrinks the list; under-counting overflows. Only one of those is a bug.\n *\n * `src/tui/safe-stderr.ts` (the `ESC[3J` byte filter) remains the last-resort backstop for the case\n * this cannot cover \u2014 a SIGWINCH that invalidates a frame's height retroactively.\n */\nimport type { PaletteItem } from '../types'\n\n/**\n * Reads an item's group label. Omitted by the branch pickers, whose lists have no headers at all.\n * (Passed as an accessor rather than constrained with a `{ group?: string }` marker interface: a type\n * whose properties are ALL optional is \"weak\", and TypeScript refuses to match a `BranchPickerItem`\n * against it \u2014 inference silently falls back to the constraint and every field access breaks.)\n */\nexport type GroupOf<T> = (item: T) => string | undefined\n\n/** The slice to render, plus how many rows fell off each end (for the `\u2026 n more` affordances). */\nexport interface ListWindow<T> {\n visible: T[]\n /** Index the window starts at \u2014 the component stores it so the list does not jump between frames. */\n start: number\n hiddenBefore: number\n hiddenAfter: number\n}\n\n/** Back-compat alias: the command palette's window is a window of `PaletteItem`s. */\nexport type PaletteWindow = ListWindow<PaletteItem>\n\n/**\n * Rows a screen spends on everything that is not a list row, counted UNCONDITIONALLY (a `\u2026 n above`\n * that is not drawn today may be drawn on the next keystroke, and re-deriving the budget mid-frame is\n * how you get an off-by-one that only bites on a full list):\n * hint, `\u276F` prompt, the list's `marginTop`, `\u2026 n above`, `\u2026 n below`, and one row of headroom.\n *\n * The headroom row is what makes the frame strictly SHORTER than the viewport: Ink treats a frame\n * exactly as tall as the terminal as fullscreen and clears the screen.\n */\nexport const CHROME_ROWS = 6\n\n/** Compact chrome: the hint and the list's `marginTop` are dropped. Prompt + affordances + headroom. */\nexport const COMPACT_CHROME_ROWS = 4\n\n/** Below this many rows a screen drops its hint, its `marginTop`, and its group headers. */\nexport const COMPACT_BELOW_ROWS = 10\n\n/** Below this many rows no usable list fits at all; the screen renders a single-line notice instead. */\nexport const TINY_BELOW_ROWS = 5\n\n/** How a screen must lay itself out to keep its frame shorter than `rows`. */\nexport type ListLayoutMode = 'full' | 'compact' | 'tiny'\n\nexport interface ListLayout {\n mode: ListLayoutMode\n /** Rows reserved for chrome. Pass to `paletteWindow` as `chromeRows`. */\n chromeRows: number\n /** Whether group headers may be drawn (they cost a row each, so a compact frame drops them). */\n groups: boolean\n}\n\n/** A list with no group headers: every item costs exactly one row. */\nconst NO_GROUPS = (): undefined => {\n return undefined\n}\n\n/**\n * The layout a screen must use at this terminal height. `extraChromeRows` is for chrome a screen has\n * that the others do not \u2014 the multi-picker's `select at least one` hint is the only one today.\n *\n * A `tiny` frame is one truncated line, which is strictly shorter than any terminal of 2+ rows. (At\n * `rows === 1` nothing can be shorter than the viewport; Ink will clear. That is a degenerate terminal,\n * and `safe-stderr` keeps even that harmless.)\n *\n * @example\n * listLayout(24) // => { mode: 'full', chromeRows: 6, groups: true }\n * listLayout(8) // => { mode: 'compact', chromeRows: 4, groups: false }\n * listLayout(8, 1) // => { mode: 'compact', chromeRows: 5, groups: false } (multi-picker)\n * listLayout(4) // => { mode: 'tiny', chromeRows: 0, groups: false }\n */\nexport const listLayout = (rows: number, extraChromeRows = 0): ListLayout => {\n if (rows < TINY_BELOW_ROWS) {\n return { mode: 'tiny', chromeRows: 0, groups: false }\n }\n\n if (rows < COMPACT_BELOW_ROWS) {\n return { mode: 'compact', chromeRows: COMPACT_CHROME_ROWS + extraChromeRows, groups: false }\n }\n\n return { mode: 'full', chromeRows: CHROME_ROWS + extraChromeRows, groups: true }\n}\n\n/**\n * How many rows rendering `items[start..end)` costs: one per item (guaranteed by `wrap=\"truncate\"`),\n * plus, when `groups` is on, one for each group header and one for the blank line between groups (the\n * first rendered group gets a header but no blank \u2014 it mirrors the `showGroup && position > 0` rule in\n * the components). A list whose items carry no `group` never spends a header row.\n */\nconst heightOf = <T>(items: T[], start: number, end: number, groupOf: GroupOf<T>): number => {\n let height = 0\n let previousGroup: string | undefined\n\n for (let index = start; index < end; index += 1) {\n const item = items[index]\n\n if (!item) break\n\n const group = groupOf(item)\n const startsGroup = group !== undefined && group !== previousGroup\n\n height += 1 + (startsGroup ? 1 : 0) + (startsGroup && index > start ? 1 : 0)\n previousGroup = group\n }\n\n return height\n}\n\n/** The last index (exclusive) that still fits `budget` rows when rendering from `start`. */\nconst endThatFits = <T>(items: T[], start: number, budget: number, groupOf: GroupOf<T>): number => {\n let end = start\n\n while (end < items.length && heightOf(items, start, end + 1, groupOf) <= budget) {\n end += 1\n }\n\n return end\n}\n\n/**\n * The window of items to render: the largest slice starting at or after `start` that fits the terminal\n * while still containing `activeIndex`. Scrolls by the minimum needed \u2014 the caller keeps `start` in\n * state, so the list only moves when the cursor would otherwise leave the window.\n *\n * An empty `visible` is a legitimate result on a terminal too short to hold even one row. It is NOT\n * clamped up to one item: forcing a row that does not fit is exactly how the frame overflows and the\n * cursor corruption starts. A screen that gets nothing back renders its `tiny` notice instead \u2014 see\n * `listLayout`.\n *\n * @example\n * const window = paletteWindow(items, 0, 12, 24)\n * window.visible.length // => however many commands fit 18 rows\n * window.hiddenAfter // => the rest, surfaced as '\u2026 n below'\n */\nexport const paletteWindow = <T>(\n items: T[],\n start: number,\n activeIndex: number,\n rows: number,\n opts?: { chromeRows?: number; groupOf?: GroupOf<T> },\n): ListWindow<T> => {\n const groupOf = opts?.groupOf ?? NO_GROUPS\n const budget = Math.max(0, rows - (opts?.chromeRows ?? CHROME_ROWS))\n\n // Scroll up only if the cursor moved above the window; otherwise scroll down just far enough.\n let first = Math.min(start, activeIndex)\n\n while (activeIndex >= endThatFits(items, first, budget, groupOf) && first < activeIndex) {\n first += 1\n }\n\n const end = endThatFits(items, first, budget, groupOf)\n\n return {\n visible: items.slice(first, end),\n start: first,\n hiddenBefore: first,\n hiddenAfter: Math.max(0, items.length - end),\n }\n}\n", "import { Box, Text, useApp, useInput, useWindowSize } from 'ink'\nimport { useEffect, useState } from 'react'\n\nimport type { BranchPickerItem } from '../../lib/prompts/types'\nimport { useListFilter } from '../hooks/use-list-filter'\nimport { listLayout, paletteWindow } from './palette-window'\n\ninterface BranchPickerProps {\n items: BranchPickerItem[]\n /** Called with the chosen item's value; the app then exits. */\n onSelect: (value: string) => void\n /** Called when the user cancels (Esc / Ctrl-C); the app then exits. */\n onCancel: () => void\n}\n\n/**\n * Searchable single-select branch picker rendered with Ink. Mirrors\n * `CommandPalette` minus the group headers: type-to-filter over label +\n * description, \u2191\u2193 wrap-around, Enter selects the active item's *value*.\n *\n * Like the palette, the list is WINDOWED to the terminal height. That is a safety\n * requirement, not a nicety: a repo with ~20 open releases produced a frame taller\n * than an 80x24 window, and an overflowing Ink frame corrupts the cursor and welds\n * the next frame onto the command's own output. See `palette-window.ts`.\n */\nexport const BranchPicker = (props: BranchPickerProps) => {\n const { items, onSelect, onCancel } = props\n\n const T = {\n hint: 'Select a branch \u2014 type to filter, \u2191\u2193 to move, Enter to select, Esc to cancel',\n prompt: '\u276F ',\n empty: 'No matching branches',\n tiny: 'terminal too short \u2014 resize to pick a branch',\n more: '\u2026',\n }\n const labelWidth = items.reduce((max, item) => {\n return Math.max(max, item.label.length)\n }, 0)\n\n const { exit } = useApp()\n const { rows } = useWindowSize()\n const { filtered, activeIndex, query, handleNavigation } = useListFilter(items, filterText)\n // First branch in the visible window; kept in state so the list scrolls by the minimum needed.\n const [windowStart, setWindowStart] = useState(0)\n // When set, the component renders `null` for one frame so Ink erases the list\n // before unmount commits its final frame.\n const [submitted, setSubmitted] = useState(false)\n\n // Exit only after the empty frame has committed, so the list is wiped first.\n useEffect(() => {\n if (submitted) {\n exit()\n }\n }, [submitted, exit])\n\n const layout = listLayout(rows)\n // No `groupOf`: a branch list has no headers, so every branch costs exactly one row.\n const view = paletteWindow(filtered, windowStart, activeIndex, rows, { chromeRows: layout.chromeRows })\n\n // Commit the scroll position once the frame that used it has rendered.\n useEffect(() => {\n if (view.start !== windowStart) {\n setWindowStart(view.start)\n }\n }, [view.start, windowStart])\n\n useInput((input, key) => {\n if (submitted) {\n return\n }\n\n if (key.escape || (key.ctrl && input === 'c')) {\n onCancel()\n setSubmitted(true)\n\n return\n }\n\n if (key.return) {\n const selected = filtered[activeIndex]\n\n if (selected) {\n onSelect(selected.value)\n setSubmitted(true)\n }\n\n return\n }\n\n handleNavigation(input, key)\n })\n\n // Final frame: render nothing so Ink erases the list on its way out.\n if (submitted) {\n return null\n }\n\n // Too short for even one branch row plus a prompt. One truncated line always fits.\n if (layout.mode === 'tiny') {\n return (\n <Text dimColor wrap=\"truncate\">\n {T.tiny}\n </Text>\n )\n }\n\n return (\n <Box flexDirection=\"column\">\n {layout.mode === 'full' ? (\n <Text dimColor wrap=\"truncate\">\n {T.hint}\n </Text>\n ) : null}\n <Box>\n <Text color=\"cyan\">{T.prompt}</Text>\n {/* truncate-START: overflow must eat the head of the filter, never the character being typed. */}\n <Text wrap=\"truncate-start\">{query}</Text>\n </Box>\n <Box flexDirection=\"column\" marginTop={layout.mode === 'full' ? 1 : 0}>\n {filtered.length === 0 ? (\n <Text dimColor wrap=\"truncate\">\n {T.empty}\n </Text>\n ) : null}\n {view.hiddenBefore > 0 ? <Text dimColor wrap=\"truncate\">{`${T.more} ${view.hiddenBefore} above`}</Text> : null}\n {view.visible.map((item, position) => {\n const isActive = view.start + position === activeIndex\n\n // `wrap=\"truncate\"` keeps one branch = one row at any width (a long branch name would\n // otherwise wrap and make the frame taller than this component believes). Ink reads the wrap\n // style off the OUTER <Text> only, so it must live here, not on the nested description.\n return (\n <Text color={labelColor(item.type, isActive)} key={item.value} wrap=\"truncate\">\n {isActive ? '\u203A ' : ' '}\n {item.label.padEnd(labelWidth)} <Text dimColor>{item.description ?? ''}</Text>\n </Text>\n )\n })}\n {view.hiddenAfter > 0 ? <Text dimColor wrap=\"truncate\">{`${T.more} ${view.hiddenAfter} below`}</Text> : null}\n </Box>\n </Box>\n )\n}\n\n/**\n * Colour for a row's label, derived from the release type. Kept a plain lookup so\n * an unknown/absent type simply falls through to the default (green when active).\n */\nfunction labelColor(type: string | undefined, isActive: boolean): string | undefined {\n if (type === 'hotfix') {\n return 'red'\n }\n\n return isActive ? 'green' : undefined\n}\n\nfunction filterText(item: BranchPickerItem): string {\n return `${item.label} ${item.description ?? ''}`\n}\n", "import { Box, Text, useApp, useInput, useWindowSize } from 'ink'\nimport { useEffect, useMemo, useState } from 'react'\n\nimport type { PaletteItem } from '../types'\nimport { listLayout, paletteWindow } from './palette-window'\n\ninterface CommandPaletteProps {\n items: PaletteItem[]\n /** Called with the chosen command name; the app then exits. */\n onSelect: (name: string) => void\n /** Called when the user cancels (Esc / Ctrl-C); the app then exits. */\n onCancel: () => void\n}\n\n/**\n * Fuzzy-ish command picker rendered with Ink. Pure presentation: it receives a\n * flat list of commands (already grouped/ordered by the catalog) and returns the\n * selected name. It never executes anything \u2014 the caller runs the command via\n * the existing Commander path.\n */\nexport const CommandPalette = (props: CommandPaletteProps) => {\n const { items, onSelect, onCancel } = props\n\n const T = {\n hint: 'Select a command \u2014 type to filter, \u2191\u2193 to move, Enter to run, Esc to cancel',\n prompt: '\u276F ',\n empty: 'No matching commands',\n tiny: 'terminal too short \u2014 resize to pick a command',\n more: '\u2026',\n }\n const nameWidth = 24\n\n const { exit } = useApp()\n const { rows } = useWindowSize()\n const [query, setQuery] = useState('')\n const [index, setIndex] = useState(0)\n // First command in the visible window. Kept in state so the list scrolls by the minimum needed\n // instead of jumping every time the cursor moves.\n const [windowStart, setWindowStart] = useState(0)\n // When set, the component renders `null` for one frame so Ink erases the list\n // before unmount commits its final frame (Ink's `log.done()` would otherwise\n // freeze the last drawn frame \u2014 the command list \u2014 into the scrollback).\n const [submitted, setSubmitted] = useState(false)\n\n // Exit only after the empty frame has committed, so the list is wiped first.\n useEffect(() => {\n if (submitted) {\n exit()\n }\n }, [submitted, exit])\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase()\n\n if (!q) {\n return items\n }\n\n return items.filter((item) => {\n return `${item.name} ${item.description}`.toLowerCase().includes(q)\n })\n }, [items, query])\n\n const activeIndex = Math.min(index, Math.max(0, filtered.length - 1))\n\n // The slice that fits this terminal, and how much chrome we may spend around it. A frame taller than\n // the viewport corrupts the cursor (see palette-window.ts) \u2014 every row here is budgeted, and every\n // row below is `wrap=\"truncate\"` so one command is always exactly one row.\n const layout = listLayout(rows)\n const view = paletteWindow(filtered, windowStart, activeIndex, rows, {\n chromeRows: layout.chromeRows,\n // A compact frame drops group headers, so they must not be budgeted for either.\n groupOf: layout.groups\n ? (item) => {\n return item.group\n }\n : undefined,\n })\n\n // Commit the scroll position once the frame that used it has rendered.\n useEffect(() => {\n if (view.start !== windowStart) {\n setWindowStart(view.start)\n }\n }, [view.start, windowStart])\n\n // Back out with nothing picked (Ctrl-C, or Esc on an empty filter).\n const quit = () => {\n onCancel()\n setSubmitted(true)\n }\n\n // Move the active row by `delta`, wrapping. The `+ n` sidesteps JS negative modulo\n // (e.g. -1 % 5 === -1, not 4). No-op on an empty list.\n const step = (delta: number) => {\n const n = filtered.length\n\n if (n > 0) {\n setIndex((activeIndex + delta + n) % n)\n }\n }\n\n useInput((input, key) => {\n if (submitted) {\n return\n }\n\n // Ctrl-C always quits; Esc clears a non-empty filter first, and only quits on an empty filter \u2014\n // the standard REPL/palette split, so the session shell keeps running while the user narrows.\n if (key.ctrl && input === 'c') {\n quit()\n\n return\n }\n\n if (key.escape && query) {\n setQuery('')\n setIndex(0)\n\n return\n }\n\n if (key.escape) {\n quit()\n\n return\n }\n\n if (key.return) {\n const selected = filtered[activeIndex]\n\n if (selected) {\n onSelect(selected.name)\n setSubmitted(true)\n }\n\n return\n }\n\n if (key.upArrow) {\n step(-1)\n\n return\n }\n\n if (key.downArrow) {\n step(1)\n\n return\n }\n\n if (key.backspace || key.delete) {\n setQuery(query.slice(0, -1))\n setIndex(0)\n\n return\n }\n\n // Printable character: append to the filter.\n if (input && !key.ctrl && !key.meta) {\n setQuery(query + input)\n setIndex(0)\n }\n })\n\n // Final frame: render nothing so Ink erases the list on its way out.\n if (submitted) {\n return null\n }\n\n // Too short for even one command row plus a prompt. One truncated line always fits.\n if (layout.mode === 'tiny') {\n return (\n <Text dimColor wrap=\"truncate\">\n {T.tiny}\n </Text>\n )\n }\n\n let lastGroup = ''\n\n return (\n <Box flexDirection=\"column\">\n {layout.mode === 'full' ? (\n <Text dimColor wrap=\"truncate\">\n {T.hint}\n </Text>\n ) : null}\n <Box>\n <Text color=\"cyan\">{T.prompt}</Text>\n {/* truncate-START on the input: overflow must eat the head of the filter, never the character\n being typed. */}\n <Text wrap=\"truncate-start\">{query}</Text>\n </Box>\n <Box flexDirection=\"column\" marginTop={layout.mode === 'full' ? 1 : 0}>\n {filtered.length === 0 ? (\n <Text dimColor wrap=\"truncate\">\n {T.empty}\n </Text>\n ) : null}\n {view.hiddenBefore > 0 ? <Text dimColor wrap=\"truncate\">{`${T.more} ${view.hiddenBefore} above`}</Text> : null}\n {view.visible.map((item, position) => {\n const isActive = view.start + position === activeIndex\n const showGroup = layout.groups && item.group !== lastGroup\n\n lastGroup = item.group\n\n return (\n <Box flexDirection=\"column\" key={item.name}>\n {/* Blank line between groups (not before the first row of the window). */}\n {showGroup && position > 0 ? <Text> </Text> : null}\n {showGroup ? <Text color=\"yellow\" wrap=\"truncate\">{`\u2014 ${item.group} \u2014`}</Text> : null}\n {/* `wrap=\"truncate\"` is load-bearing, not cosmetic: it is what makes \"one command = one\n row\" true at any width, which is what keeps the frame shorter than the viewport. Ink\n reads the wrap style off the OUTER <Text> only \u2014 it squashes the nested <Text> into\n this one's string \u2014 so it must live here. */}\n <Text color={isActive ? 'green' : undefined} wrap=\"truncate\">\n {isActive ? '\u203A ' : ' '}\n {item.name.padEnd(nameWidth)} <Text dimColor>{item.description}</Text>\n </Text>\n </Box>\n )\n })}\n {view.hiddenAfter > 0 ? <Text dimColor wrap=\"truncate\">{`${T.more} ${view.hiddenAfter} below`}</Text> : null}\n </Box>\n </Box>\n )\n}\n"],
5
- "mappings": "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",
6
- "names": ["render", "process", "Box", "Text", "useApp", "useInput", "useWindowSize", "useEffect", "useState", "useMemo", "useState", "useListFilter", "items", "toText", "query", "setQuery", "index", "setIndex", "filtered", "q", "item", "activeIndex", "input", "key", "n", "NO_GROUPS", "listLayout", "rows", "extraChromeRows", "heightOf", "items", "start", "end", "groupOf", "height", "previousGroup", "index", "item", "group", "startsGroup", "endThatFits", "budget", "paletteWindow", "activeIndex", "opts", "first", "jsx", "jsxs", "BranchMultiPicker", "props", "items", "onSubmit", "onCancel", "required", "allowSelectAll", "T", "labelWidth", "max", "item", "exit", "useApp", "rows", "useWindowSize", "filtered", "activeIndex", "query", "handleNavigation", "useListFilter", "filterText", "selected", "setSelected", "useState", "showRequiredHint", "setShowRequiredHint", "windowStart", "setWindowStart", "submitted", "setSubmitted", "useEffect", "layout", "listLayout", "view", "paletteWindow", "toggleValue", "value", "prev", "next", "toggleSelectAll", "submit", "useInput", "input", "key", "active", "Text", "Box", "position", "isActive", "isChecked", "Box", "Text", "useApp", "useInput", "useWindowSize", "useEffect", "useState", "jsx", "jsxs", "BranchPicker", "props", "items", "onSelect", "onCancel", "T", "labelWidth", "max", "item", "exit", "useApp", "rows", "useWindowSize", "filtered", "activeIndex", "query", "handleNavigation", "useListFilter", "filterText", "windowStart", "setWindowStart", "useState", "submitted", "setSubmitted", "useEffect", "layout", "listLayout", "view", "paletteWindow", "useInput", "input", "key", "selected", "Text", "Box", "position", "isActive", "labelColor", "type", "Box", "Text", "useApp", "useInput", "useWindowSize", "useEffect", "useMemo", "useState", "jsx", "jsxs", "CommandPalette", "props", "items", "onSelect", "onCancel", "T", "nameWidth", "exit", "useApp", "rows", "useWindowSize", "query", "setQuery", "useState", "index", "setIndex", "windowStart", "setWindowStart", "submitted", "setSubmitted", "useEffect", "filtered", "useMemo", "q", "item", "activeIndex", "layout", "listLayout", "view", "paletteWindow", "quit", "step", "delta", "n", "useInput", "input", "key", "selected", "Text", "lastGroup", "Box", "position", "isActive", "showGroup", "jsx", "renderToStderr", "element", "waitUntilExit", "render", "safeStderr", "process", "runCommandPalette", "items", "selected", "CommandPalette", "name", "runBranchPicker", "BranchPicker", "value", "runBranchMultiPicker", "opts", "BranchMultiPicker", "values"]
7
- }
@@ -1,4 +0,0 @@
1
- import{v as i}from"./chunk-KZP2SY37.js";var s=()=>{let n="",r=[],o=!1;return{start(t){n=t,r=[],o=!1},setInteractive(){o=!0},addOption(t,a){r.push({flag:t,value:a})},formatOptions(){return r.map(t=>typeof t.value=="boolean"?t.value?t.flag:"":Array.isArray(t.value)?`${t.flag} "${t.value.join(", ")}"`:`${t.flag} "${t.value}"`).filter(Boolean).join(" ")},snapshot(){return r.length===0?null:{formattedOptions:this.formatOptions()}},print(){!o||r.length===0||!n||i.info(`\u{1F4DF} Equivalent command:
2
- pnpm exec infra-kit ${n} ${this.formatOptions()}
3
- `)},reset(){n="",r=[],o=!1}}},e=s();import f from"@inquirer/confirm";import l from"node:process";var m=async(n,r)=>{let o=n?!0:await f({message:r});n||e.setInteractive(),o||(i.info("Operation cancelled. Exiting..."),l.exit(0))};export{e as a,m as b};
4
- //# sourceMappingURL=chunk-4MRYIYJP.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/lib/command-echo/command-echo.ts", "../src/lib/command-echo/confirm-or-exit.ts"],
4
- "sourcesContent": ["import { logger } from 'src/lib/logger'\n\ninterface CommandOption {\n flag: string\n value: string | string[] | boolean\n}\n\nconst createCommandEcho = () => {\n let cliPath = ''\n let options: CommandOption[] = []\n let isInteractive = false\n\n return {\n /**\n * Bind the echo to the command about to run, and clear the previous one's recording.\n *\n * `cliPath` must be the argv Commander actually parsed (`release create`), which is why the ONLY\n * caller is program.ts's `preAction` hook, feeding it `commandPath(actionCommand)`. Commands used to\n * name themselves here, and that is precisely how the printed line rotted: they passed the flat\n * `release-create`, which stopped being a command the day the flat aliases were dropped, so the\n * \"equivalent command\" we told the user to retype no longer parsed. Commander is the only thing that\n * knows the real path, so it is the only thing allowed to say it.\n */\n start(path: string): void {\n cliPath = path\n options = []\n isInteractive = false\n },\n\n /**\n * Mark that the command had interactive input (prompts)\n * Call this once when ANY prompt happens\n */\n setInteractive(): void {\n isInteractive = true\n },\n\n /**\n * Track an option selection\n * @param flag The CLI flag (e.g., \"--versions\")\n * @param value The selected value\n */\n addOption(flag: string, value: string | string[] | boolean): void {\n options.push({ flag, value })\n },\n\n /**\n * Format the tracked options into a replayable flag string (empty when none).\n */\n formatOptions(): string {\n return options\n .map((opt) => {\n if (typeof opt.value === 'boolean') {\n return opt.value ? opt.flag : ''\n }\n\n if (Array.isArray(opt.value)) {\n return `${opt.flag} \"${opt.value.join(', ')}\"`\n }\n\n return `${opt.flag} \"${opt.value}\"`\n })\n .filter(Boolean)\n .join(' ')\n },\n\n /**\n * The recorded interactive flags for the session shell's report side channel, so the transcript's\n * equivalent line carries the resolved options (`--versions \"1.2.5\"`), not just the bare command.\n * `null` when nothing was recorded \u2014 the caller then falls back to the bare spawned argv.\n */\n snapshot(): { formattedOptions: string } | null {\n if (options.length === 0) {\n return null\n }\n\n return { formattedOptions: this.formatOptions() }\n },\n\n /**\n * Print the equivalent CLI command if there was interactive input.\n *\n * Silent without a bound `cliPath`: the only caller that binds one is Commander's `preAction`, so an\n * unbound echo means the command ran off the CLI (an MCP tool), where a `pnpm exec` line would be\n * nonsense. Printing a path-less `pnpm exec infra-kit --yes` would be worse than printing nothing.\n */\n print(): void {\n if (!isInteractive || options.length === 0 || !cliPath) {\n return\n }\n\n logger.info(`\uD83D\uDCDF Equivalent command: \\npnpm exec infra-kit ${cliPath} ${this.formatOptions()}\\n`)\n },\n\n /**\n * Reset state (useful for testing)\n */\n reset(): void {\n cliPath = ''\n options = []\n isInteractive = false\n },\n }\n}\n\n// Singleton instance (same pattern as logger)\nexport const commandEcho = createCommandEcho()\n", "import confirm from '@inquirer/confirm'\nimport process from 'node:process'\n\nimport { logger } from 'src/lib/logger'\n\nimport { commandEcho } from './command-echo'\n\n/**\n * Shared interactive-confirmation gate for mutating commands.\n *\n * When `confirmedCommand` is truthy (CLI `--yes` or an MCP call, which always\n * injects `confirmedCommand: true`) the prompt is skipped and execution\n * proceeds. Otherwise it prompts the user, marks the echo as interactive, and\n * exits cleanly if the user declines.\n *\n * The `process.exit(0)` only runs on the interactive-decline path, which is\n * unreachable under MCP \u2014 so this stays safe for the long-lived MCP server.\n *\n * Callers remain responsible for their own `commandEcho.addOption('--yes', \u2026)`\n * bookkeeping, which varies between commands.\n */\nexport const confirmOrExit = async (confirmedCommand: boolean | undefined, message: string): Promise<void> => {\n const answer = confirmedCommand ? true : await confirm({ message })\n\n if (!confirmedCommand) {\n commandEcho.setInteractive()\n }\n\n if (!answer) {\n logger.info('Operation cancelled. Exiting...')\n process.exit(0)\n }\n}\n"],
5
- "mappings": "wCAOA,IAAMA,EAAoB,IAAM,CAC9B,IAAIC,EAAU,GACVC,EAA2B,CAAC,EAC5BC,EAAgB,GAEpB,MAAO,CAWL,MAAMC,EAAoB,CACxBH,EAAUG,EACVF,EAAU,CAAC,EACXC,EAAgB,EAClB,EAMA,gBAAuB,CACrBA,EAAgB,EAClB,EAOA,UAAUE,EAAcC,EAA0C,CAChEJ,EAAQ,KAAK,CAAE,KAAAG,EAAM,MAAAC,CAAM,CAAC,CAC9B,EAKA,eAAwB,CACtB,OAAOJ,EACJ,IAAKK,GACA,OAAOA,EAAI,OAAU,UAChBA,EAAI,MAAQA,EAAI,KAAO,GAG5B,MAAM,QAAQA,EAAI,KAAK,EAClB,GAAGA,EAAI,IAAI,KAAKA,EAAI,MAAM,KAAK,IAAI,CAAC,IAGtC,GAAGA,EAAI,IAAI,KAAKA,EAAI,KAAK,GACjC,EACA,OAAO,OAAO,EACd,KAAK,GAAG,CACb,EAOA,UAAgD,CAC9C,OAAIL,EAAQ,SAAW,EACd,KAGF,CAAE,iBAAkB,KAAK,cAAc,CAAE,CAClD,EASA,OAAc,CACR,CAACC,GAAiBD,EAAQ,SAAW,GAAK,CAACD,GAI/CO,EAAO,KAAK;AAAA,sBAAgDP,CAAO,IAAI,KAAK,cAAc,CAAC;AAAA,CAAI,CACjG,EAKA,OAAc,CACZA,EAAU,GACVC,EAAU,CAAC,EACXC,EAAgB,EAClB,CACF,CACF,EAGaM,EAAcT,EAAkB,EC1G7C,OAAOU,MAAa,oBACpB,OAAOC,MAAa,eAoBb,IAAMC,EAAgB,MAAOC,EAAuCC,IAAmC,CAC5G,IAAMC,EAASF,EAAmB,GAAO,MAAMG,EAAQ,CAAE,QAAAF,CAAQ,CAAC,EAE7DD,GACHI,EAAY,eAAe,EAGxBF,IACHG,EAAO,KAAK,iCAAiC,EAC7CC,EAAQ,KAAK,CAAC,EAElB",
6
- "names": ["createCommandEcho", "cliPath", "options", "isInteractive", "path", "flag", "value", "opt", "logger", "commandEcho", "confirm", "process", "confirmOrExit", "confirmedCommand", "message", "answer", "confirm", "commandEcho", "logger", "process"]
7
- }
@@ -1,2 +0,0 @@
1
- var r=t=>Object.fromEntries(Object.entries(t).filter(([e])=>!e.startsWith("npm_")&&e!=="PNPM_SCRIPT_SRC_DIR"));export{r as a};
2
- //# sourceMappingURL=chunk-6FU2TRU5.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/lib/pm-env/pm-env.ts"],
4
- "sourcesContent": ["/**\n * `env` minus every package-manager marker a spawned child could mistake for a throwaway install.\n *\n * Any child we launch through `node <bin>` (the portless driver, self-update, the mcp launcher) inherits\n * our env, and a tool that sniffs those markers will conclude it was run via `npx`/`pnpm dlx` and refuse\n * to work. portless is the motivating case; its guard (cli.js `main()`) is:\n *\n * isNpx = npm_command === 'exec' && !npm_lifecycle_event\n * isPnpmDlx = !!PNPM_SCRIPT_SRC_DIR && !npm_lifecycle_event\n * if ((isNpx || isPnpmDlx) && !isLocallyInstalled()) \u2192 abort\n *\n * `isLocallyInstalled()` walks up from the CHILD's cwd, so in a consumer repo (where portless is a\n * transitive dep of infra-kit, absent from the root `node_modules`) it is false and the guard is live.\n * `pnpm exec infra-kit dev` exports `npm_command=exec` with no lifecycle event \u2192 `isNpx` \u2192 every\n * portless call exits 1 \u2192 `isAvailable()` is false \u2192 `ensureProxy` aborts the whole dev boot with\n * \"portless is not installed\", on a machine where it plainly is.\n *\n * `PNPM_SCRIPT_SRC_DIR` must be dropped ALONGSIDE the `npm_*` block, not left behind: it is set by\n * `pnpm run <script>`, where `npm_lifecycle_event` is what suppresses `isPnpmDlx`. Stripping only\n * `npm_*` removes that suppressor and *creates* an abort for the script-style invocation that\n * previously worked. Both markers go, or neither is safe.\n *\n * The result makes the child look exactly like a direct `node <pkg>/dist/cli.js` run \u2014 which it is.\n */\nexport const withoutPackageManagerEnv = (env: NodeJS.ProcessEnv): NodeJS.ProcessEnv => {\n return Object.fromEntries(\n Object.entries(env).filter(([key]) => {\n return !key.startsWith('npm_') && key !== 'PNPM_SCRIPT_SRC_DIR'\n }),\n )\n}\n"],
5
- "mappings": "AAwBO,IAAMA,EAA4BC,GAChC,OAAO,YACZ,OAAO,QAAQA,CAAG,EAAE,OAAO,CAAC,CAACC,CAAG,IACvB,CAACA,EAAI,WAAW,MAAM,GAAKA,IAAQ,qBAC3C,CACH",
6
- "names": ["withoutPackageManagerEnv", "env", "key"]
7
- }
@@ -1,2 +0,0 @@
1
- import{a as P}from"./chunk-6FU2TRU5.js";import{m as f,q as x}from"./chunk-CHETBZ6M.js";import c from"node:path";var m="infra-kit",g=`${m}@latest`,S=(t,e,n)=>{let r=c.relative(n(c.resolve(t)),c.resolve(e));return r!==""&&!r.startsWith("..")&&!c.isAbsolute(r)},l=(t,e)=>c.resolve(t).split(c.sep).includes(e),J=(t,e)=>{let n=c.resolve(t).split(c.sep),r=n.indexOf("Cellar");return r!==-1&&n[r+1]===e},w=(t,e,n,r)=>{let a=t[e];return a!=null&&a!==""&&S(a,n,r)},H=[{manager:"volta",test:({selfRealPath:t,env:e,realpath:n})=>w(e,"VOLTA_HOME",t,n)||l(t,".volta")||l(t,"volta"),updateCommand:["volta","install",m],canSelfSpawn:!0},{manager:"homebrew",test:({selfRealPath:t})=>J(t,m),updateCommand:["brew","upgrade",m],canSelfSpawn:!1},{manager:"pnpm",test:({selfRealPath:t,env:e,realpath:n})=>w(e,"PNPM_HOME",t,n)||l(t,"pnpm")&&l(t,"global"),updateCommand:["pnpm","add","-g",g],canSelfSpawn:!0},{manager:"bun",test:({selfRealPath:t,env:e,realpath:n})=>w(e,"BUN_INSTALL",t,n)||l(t,".bun"),updateCommand:["bun","add","-g",g],canSelfSpawn:!0},{manager:"yarn",test:({selfRealPath:t})=>l(t,".yarn")||l(t,"yarn")&&l(t,"global"),updateCommand:["yarn","global","add",g],canSelfSpawn:!0},{manager:"npm",test:({selfRealPath:t,env:e,realpath:n})=>w(e,"npm_config_prefix",t,n),updateCommand:["npm","install","-g",g],canSelfSpawn:!0}],E=["npm","install","-g",g],b=t=>{let{selfRealPath:e,env:n,realpath:r,lazyNpmRoot:a}=t,s=H.find(({test:i})=>i({selfRealPath:e,env:n,realpath:r}));if(s)return{manager:s.manager,updateCommand:s.updateCommand,canSelfSpawn:s.canSelfSpawn};let o=a?.();return o!=null&&o!==""&&S(o,e,r)?{manager:"npm",updateCommand:E,canSelfSpawn:!0}:{manager:"unknown",updateCommand:E,canSelfSpawn:!1}},K=(t,e,n)=>l(t,"node_modules")&&S(e,t,n);import{realpathSync as G}from"node:fs";import W from"node:path";var M=t=>{try{return G(t)}catch{return W.resolve(t)}};import{execFileSync as $}from"node:child_process";import B from"node:process";var Y=3e3,k=()=>{try{let e=$("npm",["root","-g"],{encoding:"utf-8",timeout:Y,stdio:["ignore","pipe","ignore"],shell:B.platform==="win32"}).trim();return e===""?void 0:e}catch{return}};import{spawnSync as me}from"node:child_process";import{homedir as de}from"node:os";import v from"node:process";import d from"node:fs";import I from"node:path";import Q from"node:process";var X="update-check.lock",Z=600*1e3,ee=()=>I.join(f(),X),te=(t,e)=>{try{e-d.statSync(t).mtimeMs>Z&&d.rmSync(t,{force:!0})}catch{}},N=(t={})=>{let e=t.nowMs??Date.now(),n=t.lockPath??ee();try{d.mkdirSync(I.dirname(n),{recursive:!0})}catch{return null}let r=()=>{try{return d.openSync(n,"wx",384)}catch{return null}},a=r();if(a===null&&(te(n,e),a=r()),a===null)return null;try{d.writeFileSync(a,String(Q.pid))}catch{}return()=>{try{d.closeSync(a)}catch{}try{d.rmSync(n,{force:!0})}catch{}}};var ne="https://registry.npmjs.org",re=2500,ae=t=>{let e=t.npm_config_registry,n=e!=null&&e!==""?e:ne;for(;n.endsWith("/");)n=n.slice(0,-1);return n},A=async(t,e=fetch)=>{let n=new AbortController,r=setTimeout(()=>{n.abort()},re);try{let a=await e(`${ae(t)}/${m}/latest`,{signal:n.signal,headers:{accept:"application/json"}});if(!a.ok)return null;let o=(await a.json())?.version;return typeof o=="string"&&o!==""?o:null}catch{return null}finally{clearTimeout(r)}};var oe=/^(\d+)\.(\d+)\.(\d+)$/,_=t=>{let[e=""]=t.trim().split("+"),[n="",...r]=e.split("-"),a=oe.exec(n);if(!a)return null;let[s,o,i]=[a[1],a[2],a[3]].map(Number);return{release:[s,o,i],prerelease:r.length>0?r.join("-").split("."):[]}},R=/^\d+$/,se=(t,e)=>{let n=R.test(t),r=R.test(e);return n&&r?Number(t)-Number(e):n!==r?n?-1:1:t<e?-1:1},le=(t,e)=>{for(let n=0;n<Math.max(t.length,e.length);n+=1){let r=t[n],a=e[n];if(r===void 0)return-1;if(a===void 0)return 1;if(r!==a)return se(r,a)}return 0},L=(t,e)=>{let n=_(t),r=_(e);if(!n||!r)return!1;for(let o=0;o<3;o+=1){let i=n.release[o],h=r.release[o];if(i!==h)return i>h}let a=n.prerelease.length===0,s=r.prerelease.length===0;return a!==s?a:le(n.prerelease,r.prerelease)>0};import U from"node:fs";import T from"node:path";var O="update-check.json",ie=1440*60*1e3,ce=()=>T.join(f(),O),ue=t=>Array.isArray(t)&&t.every(e=>typeof e=="string"),pe=t=>{if(typeof t!="object"||t===null)return!1;let{lastCheckMs:e,latestVersion:n,updateCommand:r}=t;return typeof e=="number"&&Number.isFinite(e)&&(n===null||typeof n=="string")&&(r===null||ue(r))},He=(t=e=>U.readFileSync(e,"utf8"))=>{try{let e=JSON.parse(t(ce()));return pe(e)?e:null}catch{return null}},F=t=>{let e=f();U.mkdirSync(e,{recursive:!0}),x(T.join(e,O),JSON.stringify(t),384)},Ke=(t,e)=>{if(!t)return!0;let n=e-t.lastCheckMs;return n>=ie||n<0};var fe=200,ge=300*1e3,he=t=>{try{return v.kill(t,0),!0}catch{return!1}},ye=t=>new Promise(e=>{setTimeout(e,t)}),we=async(t,e)=>{let n=e.clock()+ge;for(;e.clock()<n;){if(!e.isProcessAlive(t))return!0;await e.sleep(fe)}return!e.isProcessAlive(t)},nt=async(t,e)=>{let r=(e.acquireLock??N)();if(!r)return"already-running";try{return await Ce(t,e)}finally{r()}},Ce=async(t,e)=>{let n=e.env??v.env,r=e.nowMs??Date.now(),a=e.fetchLatest??A,s=e.writeCache??F,o=e.isProcessAlive??he,i=e.sleep??ye,h=e.clock??Date.now,D=e.lazyNpmRoot??k,V=e.spawnSync??me,u=await a(n),p=(q,z)=>(s({lastCheckMs:r,...z}),q);if(u===null)return p("fetch-failed",{latestVersion:null,updateCommand:null});if(!L(u,t))return p("up-to-date",{latestVersion:u,updateCommand:null});let{canSelfSpawn:j,updateCommand:y}=b({selfRealPath:e.selfRealPath,env:n,realpath:M,lazyNpmRoot:D});if(!j)return p("cannot-self-spawn",{latestVersion:u,updateCommand:y});if(e.parentPid==null)return p("parent-unknown",{latestVersion:u,updateCommand:null});if(!await we(e.parentPid,{isProcessAlive:o,sleep:i,clock:h}))return p("parent-still-running",{latestVersion:u,updateCommand:null});let C=V(y[0],y.slice(1),{stdio:"ignore",shell:v.platform==="win32",cwd:de(),env:P(n),windowsHide:!0});return C.error||C.signal||C.status!==0?p("install-failed",{latestVersion:u,updateCommand:y}):p("installed",{latestVersion:null,updateCommand:null})};export{b as a,K as b,k as c,M as d,L as e,He as f,Ke as g,nt as h};
2
- //# sourceMappingURL=chunk-E2SEBLGJ.js.map