infra-kit 0.1.113 → 0.1.115

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 (218) hide show
  1. package/.eslintcache +1 -1
  2. package/.omc/state/agent-replay-0d1515ce-4ebe-4ce8-a865-2897f68de168.jsonl +1 -0
  3. package/.omc/state/agent-replay-1fcfe0ec-7567-41c3-9bfd-501e99e86819.jsonl +19 -0
  4. package/.omc/state/agent-replay-38382db5-3088-4b5a-aff7-2561a60827c7.jsonl +17 -0
  5. package/.omc/state/agent-replay-50c36be9-a3a2-42b9-9c85-5a06d3e22f6a.jsonl +5 -0
  6. package/.omc/state/agent-replay-8198b3e5-fad6-4ee0-9c59-ef773ad89af4.jsonl +1 -0
  7. package/.omc/state/agent-replay-8cd7652a-b8b5-49cf-9b1f-bee8e5972458.jsonl +3 -0
  8. package/.omc/state/agent-replay-9bea518f-a9ee-4d14-bb53-a936ebaf647c.jsonl +7 -0
  9. package/.omc/state/agent-replay-b52d89fb-0e24-4901-bc66-27e3c1697126.jsonl +2 -0
  10. package/.omc/state/agent-replay-f581a1ae-0ee4-48a3-96f3-ce31a0633bb0.jsonl +1 -0
  11. package/.omc/state/agent-replay-f99d8dad-81d1-4987-8183-62edf0843d0c.jsonl +2 -0
  12. package/.omc/state/agent-replay-fe1057b4-9a25-4e8e-8c3f-3e11652ee697.jsonl +5 -0
  13. package/.omc/state/idle-notif-cooldown.json +1 -1
  14. package/.omc/state/sessions/0d1515ce-4ebe-4ce8-a865-2897f68de168/pre-tool-advisory-throttle.json +10 -0
  15. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/last-tool-error-state.json +7 -0
  16. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/mission-state.json +109 -0
  17. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/pre-tool-advisory-throttle.json +42 -0
  18. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/ralph-state.json +13 -0
  19. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/skill-active-state.json +15 -0
  20. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/subagent-tracking-state.json +62 -0
  21. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/ultrawork-state.json +11 -0
  22. package/.omc/state/sessions/23d0ff82-17e5-428b-8743-38f7979bb6f3/last-tool-error-state.json +7 -0
  23. package/.omc/state/sessions/23d0ff82-17e5-428b-8743-38f7979bb6f3/pre-tool-advisory-throttle.json +18 -0
  24. package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/last-tool-error-state.json +7 -0
  25. package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/mission-state.json +145 -0
  26. package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/pre-tool-advisory-throttle.json +38 -0
  27. package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/ralplan-state.json +11 -0
  28. package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/subagent-tracking-state.json +98 -0
  29. package/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/last-tool-error-state.json +7 -0
  30. package/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/pre-tool-advisory-throttle.json +18 -0
  31. package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/mission-state.json +61 -0
  32. package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/pre-tool-advisory-throttle.json +22 -0
  33. package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/skill-active-state.json +15 -0
  34. package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/subagent-tracking-state.json +26 -0
  35. package/.omc/state/sessions/560ea519-574a-4713-b134-5bf6cfaf3c33/pre-tool-advisory-throttle.json +18 -0
  36. package/.omc/state/sessions/8198b3e5-fad6-4ee0-9c59-ef773ad89af4/pre-tool-advisory-throttle.json +14 -0
  37. package/.omc/state/sessions/8198b3e5-fad6-4ee0-9c59-ef773ad89af4/subagent-tracking-state.json +17 -0
  38. package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/pre-tool-advisory-throttle.json +18 -0
  39. package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/ralplan-state.json +11 -0
  40. package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/subagent-tracking-state.json +35 -0
  41. package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/last-tool-error-state.json +7 -0
  42. package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/mission-state.json +79 -0
  43. package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/pre-tool-advisory-throttle.json +22 -0
  44. package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/progress.txt +7 -0
  45. package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/skill-active-state.json +15 -0
  46. package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/subagent-tracking-state.json +26 -0
  47. package/.omc/state/sessions/b52d89fb-0e24-4901-bc66-27e3c1697126/pre-tool-advisory-throttle.json +14 -0
  48. package/.omc/state/sessions/b52d89fb-0e24-4901-bc66-27e3c1697126/subagent-tracking-state.json +17 -0
  49. package/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/pre-tool-advisory-throttle.json +18 -0
  50. package/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/pre-tool-advisory-throttle.json +18 -0
  51. package/.omc/state/sessions/f581a1ae-0ee4-48a3-96f3-ce31a0633bb0/pre-tool-advisory-throttle.json +18 -0
  52. package/.omc/state/sessions/f581a1ae-0ee4-48a3-96f3-ce31a0633bb0/subagent-tracking-state.json +17 -0
  53. package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +14 -0
  54. package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/ralph-state.json +13 -0
  55. package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/ultrawork-state.json +11 -0
  56. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/last-tool-error-state.json +7 -0
  57. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/mission-state.json +61 -0
  58. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/prd.json +22 -0
  59. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/pre-tool-advisory-throttle.json +22 -0
  60. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/progress.txt +22 -0
  61. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/skill-active-state.json +15 -0
  62. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/subagent-tracking-state.json +26 -0
  63. package/.turbo/turbo-build.log +3 -3
  64. package/.turbo/turbo-test.log +92 -44
  65. package/dist/boot-K4QUAFSP.js +2 -0
  66. package/dist/boot-K4QUAFSP.js.map +7 -0
  67. package/dist/chunk-RTSP2FCA.js +2 -0
  68. package/dist/chunk-RTSP2FCA.js.map +7 -0
  69. package/dist/chunk-VPBZR2PY.js +166 -0
  70. package/dist/chunk-VPBZR2PY.js.map +7 -0
  71. package/dist/cli.js +11 -107
  72. package/dist/cli.js.map +4 -4
  73. package/dist/entry/index.d.ts +2 -0
  74. package/dist/index.js +1 -1
  75. package/dist/index.js.map +4 -4
  76. package/dist/lib/vendor/config-schema.d.ts +58 -0
  77. package/dist/mcp.js +1 -54
  78. package/dist/mcp.js.map +4 -4
  79. package/eslint.config.js +33 -1
  80. package/package.json +5 -1
  81. package/scripts/build.js +11 -1
  82. package/src/.omc/state/agent-replay-c513ef38-cc3e-4a29-9c4c-1e0137245dd5.jsonl +4 -0
  83. package/src/.omc/state/agent-replay-ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a.jsonl +2 -0
  84. package/src/.omc/state/idle-notif-cooldown.json +1 -1
  85. package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/mission-state.json +79 -0
  86. package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/pre-tool-advisory-throttle.json +22 -0
  87. package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/subagent-tracking-state.json +26 -0
  88. package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/pre-tool-advisory-throttle.json +18 -0
  89. package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/ralph-state.json +13 -0
  90. package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/ultrawork-state.json +11 -0
  91. package/src/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +14 -0
  92. package/src/commands/config/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/last-tool-error-state.json +7 -0
  93. package/src/commands/config/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/pre-tool-advisory-throttle.json +18 -0
  94. package/src/commands/config/config.ts +7 -37
  95. package/src/commands/doctor/__tests__/agent-files.test.ts +8 -30
  96. package/src/commands/doctor/__tests__/check-legacy-user-global-config.test.ts +110 -0
  97. package/src/commands/doctor/doctor.ts +110 -44
  98. package/src/commands/env-autoload/env-autoload.ts +12 -0
  99. package/src/commands/env-autoload/index.ts +1 -0
  100. package/src/commands/env-clear/__tests__/env-clear.test.ts +27 -0
  101. package/src/commands/env-clear/env-clear.ts +22 -8
  102. package/src/commands/env-load/__tests__/env-load.test.ts +36 -1
  103. package/src/commands/env-load/env-load.ts +152 -35
  104. package/src/commands/env-load/index.ts +2 -1
  105. package/src/commands/env-status/__tests__/env-status.test.ts +78 -0
  106. package/src/commands/env-status/env-status.ts +19 -7
  107. package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +45 -1
  108. package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +46 -1
  109. package/src/commands/init/__tests__/agent-files.test.ts +145 -27
  110. package/src/commands/init/__tests__/migrate-config.test.ts +136 -5
  111. package/src/commands/init/__tests__/migrate-factory-config.test.ts +93 -0
  112. package/src/commands/init/__tests__/seed-user-global-config.test.ts +64 -0
  113. package/src/commands/init/__tests__/shell-body.test.ts +30 -0
  114. package/src/commands/init/agent-files.ts +65 -45
  115. package/src/commands/init/init.ts +140 -31
  116. package/src/commands/init/migrate-config.ts +160 -32
  117. package/src/commands/vendor-check/__tests__/runtime-isolation.test.ts +71 -0
  118. package/src/commands/vendor-check/__tests__/vendor-check.test.ts +87 -0
  119. package/src/commands/vendor-check/index.ts +1 -0
  120. package/src/commands/vendor-check/vendor-check.ts +127 -0
  121. package/src/commands/vendor-config/__tests__/vendor-config.test.ts +103 -0
  122. package/src/commands/vendor-config/index.ts +1 -0
  123. package/src/commands/vendor-config/vendor-config.ts +162 -0
  124. package/src/commands/vendor-diff/__tests__/vendor-diff.test.ts +74 -0
  125. package/src/commands/vendor-diff/index.ts +1 -0
  126. package/src/commands/vendor-diff/vendor-diff.ts +77 -0
  127. package/src/commands/vendor-manifest/index.ts +1 -0
  128. package/src/commands/vendor-manifest/vendor-manifest.ts +60 -0
  129. package/src/commands/vendor-sync/__tests__/vendor-write.test.ts +86 -0
  130. package/src/commands/vendor-sync/index.ts +1 -0
  131. package/src/commands/vendor-sync/vendor-sync.ts +90 -0
  132. package/src/commands/worktrees-add/worktrees-add.ts +1 -1
  133. package/src/commands/worktrees-reload/worktrees-reload.ts +6 -5
  134. package/src/commands/worktrees-remove/worktrees-remove.ts +10 -1
  135. package/src/commands/worktrees-sync/worktrees-sync.ts +9 -1
  136. package/src/entry/cli.ts +417 -210
  137. package/src/entry/index.ts +7 -1
  138. package/src/integrations/cmux/__tests__/open-workspace-with-layout.test.ts +127 -0
  139. package/src/integrations/cmux/close-workspace-by-title.ts +2 -2
  140. package/src/integrations/cmux/list-workspace-titles.ts +1 -1
  141. package/src/integrations/cmux/open-workspace-with-layout.ts +15 -4
  142. package/src/integrations/cursor/.omc/state/agent-replay-f99d8dad-81d1-4987-8183-62edf0843d0c.jsonl +2 -0
  143. package/src/integrations/cursor/.omc/state/idle-notif-cooldown.json +3 -0
  144. package/src/integrations/cursor/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +18 -0
  145. package/src/integrations/cursor/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/subagent-tracking-state.json +17 -0
  146. package/src/integrations/cursor/index.ts +1 -0
  147. package/src/integrations/cursor/launch-cursor.ts +12 -0
  148. package/src/integrations/cursor/open-cursor-workspace.ts +8 -14
  149. package/src/integrations/doppler/__tests__/doppler-errors.test.ts +76 -0
  150. package/src/integrations/doppler/doppler-cli-auth.ts +12 -4
  151. package/src/integrations/doppler/doppler-errors.ts +88 -0
  152. package/src/integrations/doppler/doppler-project.ts +80 -0
  153. package/src/integrations/doppler/index.ts +3 -1
  154. package/src/integrations/gh/gh-cli-auth/gh-cli-auth.ts +9 -5
  155. package/src/integrations/gh/gh-release-prs/gh-release-prs.ts +16 -8
  156. package/src/integrations/ide/__tests__/ide-facade.test.ts +57 -10
  157. package/src/integrations/ide/add-ide-worktree-folders.ts +2 -4
  158. package/src/integrations/ide/open-ide-workspace.ts +6 -6
  159. package/src/integrations/ide/remove-ide-worktree-folders.ts +95 -30
  160. package/src/integrations/ide/types.ts +10 -1
  161. package/src/integrations/zed/__tests__/open-zed-workspace.test.ts +1 -17
  162. package/src/integrations/zed/__tests__/reuse-zed-workspace.test.ts +86 -0
  163. package/src/integrations/zed/index.ts +1 -0
  164. package/src/integrations/zed/open-zed-workspace.ts +4 -10
  165. package/src/integrations/zed/reuse-zed-workspace.ts +57 -0
  166. package/src/lib/command-catalog/__tests__/__snapshots__/command-catalog.test.ts.snap +212 -0
  167. package/src/lib/command-catalog/__tests__/command-catalog.test.ts +193 -0
  168. package/src/lib/command-catalog/command-catalog.ts +141 -0
  169. package/src/lib/command-catalog/index.ts +2 -0
  170. package/src/lib/command-echo/__tests__/command-echo.test.ts +127 -0
  171. package/src/lib/constants/constants.ts +13 -0
  172. package/src/lib/constants/index.ts +2 -0
  173. package/src/lib/env-autoload/__tests__/env-autoload.test.ts +288 -0
  174. package/src/lib/env-autoload/env-autoload.ts +294 -0
  175. package/src/lib/env-autoload/index.ts +9 -0
  176. package/src/lib/errors/operation-error.ts +1 -1
  177. package/src/lib/infra-kit-config/__tests__/env-autoload-schema.test.ts +71 -0
  178. package/src/lib/infra-kit-config/__tests__/infra-kit-config.test.ts +77 -6
  179. package/src/lib/infra-kit-config/index.ts +9 -1
  180. package/src/lib/infra-kit-config/infra-kit-config.ts +71 -11
  181. package/src/lib/json-output/__tests__/json-output.test.ts +139 -0
  182. package/src/lib/json-output/index.ts +1 -0
  183. package/src/lib/json-output/json-output.ts +47 -0
  184. package/src/lib/managed-block/managed-block.ts +4 -4
  185. package/src/lib/path-display/index.ts +1 -0
  186. package/src/lib/path-display/path-display.ts +42 -0
  187. package/src/lib/render/__tests__/render.test.ts +48 -0
  188. package/src/lib/render/index.ts +1 -0
  189. package/src/lib/render/render.ts +24 -0
  190. package/src/lib/vendor/__tests__/config-schema.test.ts +31 -0
  191. package/src/lib/vendor/__tests__/config.test.ts +63 -0
  192. package/src/lib/vendor/__tests__/factory-config.test.ts +92 -0
  193. package/src/lib/vendor/__tests__/vendor-lib.test.ts +158 -0
  194. package/src/lib/vendor/__tests__/zx-mock.ts +32 -0
  195. package/src/lib/vendor/config-schema.ts +78 -0
  196. package/src/lib/vendor/config.ts +58 -0
  197. package/src/lib/vendor/factory-config-schema.ts +35 -0
  198. package/src/lib/vendor/factory-config.ts +91 -0
  199. package/src/lib/vendor/hash.ts +16 -0
  200. package/src/lib/vendor/index.ts +23 -0
  201. package/src/lib/vendor/manifest.ts +151 -0
  202. package/src/lib/vendor/skip-sets.ts +48 -0
  203. package/src/lib/vendor/sync-ops.ts +290 -0
  204. package/src/lib/vendor/walk.ts +56 -0
  205. package/src/lib/worktrees/remove-worktrees.ts +15 -0
  206. package/src/mcp/tools/index.ts +5 -42
  207. package/src/sanity.test.tsx +1 -0
  208. package/src/tui/__tests__/no-react-boundary.test.ts +51 -0
  209. package/src/tui/boot.tsx +45 -0
  210. package/src/tui/screens/__tests__/command-palette.test.tsx +136 -0
  211. package/src/tui/screens/command-palette.tsx +159 -0
  212. package/src/tui/types.ts +8 -0
  213. package/tsconfig.json +1 -0
  214. package/tsconfig.tsbuildinfo +1 -1
  215. package/vitest.config.ts +8 -0
  216. package/src/commands/worktrees-open/__tests__/open-cmux.test.ts +0 -96
  217. package/src/commands/worktrees-open/index.ts +0 -1
  218. package/src/commands/worktrees-open/worktrees-open.ts +0 -195
package/src/entry/cli.ts CHANGED
@@ -5,6 +5,7 @@ import process from 'node:process'
5
5
  import { audit } from 'src/commands/audit'
6
6
  import { configEdit, configPath } from 'src/commands/config'
7
7
  import { doctor } from 'src/commands/doctor'
8
+ import { envAutoload } from 'src/commands/env-autoload'
8
9
  import { envClear } from 'src/commands/env-clear'
9
10
  import { envList } from 'src/commands/env-list'
10
11
  import { envLoad } from 'src/commands/env-load'
@@ -17,16 +18,24 @@ import { ghReleaseList } from 'src/commands/gh-release-list'
17
18
  import { init } from 'src/commands/init'
18
19
  import { releaseCreate } from 'src/commands/release-create'
19
20
  import { releaseDescEdit } from 'src/commands/release-desc-edit'
21
+ import { vendorCheck } from 'src/commands/vendor-check'
22
+ import { vendorConfig } from 'src/commands/vendor-config'
23
+ import { vendorDiff } from 'src/commands/vendor-diff'
24
+ import { vendorManifest } from 'src/commands/vendor-manifest'
25
+ import { vendorSync } from 'src/commands/vendor-sync'
20
26
  import { version } from 'src/commands/version'
21
27
  import { worktreesAdd } from 'src/commands/worktrees-add'
22
28
  import { worktreesList } from 'src/commands/worktrees-list'
23
- import { worktreesOpen } from 'src/commands/worktrees-open'
24
29
  import { worktreesReload } from 'src/commands/worktrees-reload'
25
30
  import { worktreesRemove } from 'src/commands/worktrees-remove'
26
31
  import { worktreesSync } from 'src/commands/worktrees-sync'
27
32
  import { IDE_MODES } from 'src/integrations/ide'
28
33
  import type { IdeMode } from 'src/integrations/ide'
34
+ import { getMenuGroupCommands } from 'src/lib/command-catalog'
35
+ import { runEnvAutoLoad } from 'src/lib/env-autoload'
36
+ import { addJsonOption, emit, jsonOutput } from 'src/lib/json-output'
29
37
  import { logger } from 'src/lib/logger'
38
+ import { formatAlignedRows } from 'src/lib/render'
30
39
  import { parseReleaseSpec } from 'src/lib/version-utils'
31
40
  import type { ReleaseInput } from 'src/lib/version-utils'
32
41
 
@@ -36,6 +45,11 @@ const collectReleaseSpec = (value: string, prev: string[]): string[] => {
36
45
  return [...prev, value]
37
46
  }
38
47
 
48
+ /** Parse a `--repos a,b,c` option into a target-name list (undefined = all). */
49
+ const parseRepos = (value: unknown): string[] | undefined => {
50
+ return typeof value === 'string' ? value.split(',').filter(Boolean) : undefined
51
+ }
52
+
39
53
  const normalizeIdeMode = (value: unknown, flagName: '--ide' | '--cursor'): IdeMode | undefined => {
40
54
  if (typeof value === 'undefined') {
41
55
  return undefined
@@ -71,164 +85,244 @@ const runProgram = async (argv?: string[]): Promise<void> => {
71
85
  }
72
86
  }
73
87
 
74
- program
75
- .command('merge-dev')
76
- .description('Merge dev branch into every release branch')
77
- .option('-a, --all', 'Select all active release branches')
78
- .option('-y, --yes', 'Skip confirmation prompt')
79
- .action(async (options) => {
80
- await ghMergeDev({ all: options.all, confirmedCommand: options.yes })
88
+ // --- Deprecation support for flat command aliases (Phase 3 grouping) ---
89
+ // Flat names (`release-create`, `worktrees-add`, `vendor-config`, ...) are kept
90
+ // as working aliases of the grouped forms (`release create`, ...) for one
91
+ // release cycle. They warn once when invoked directly, but stay silent when the
92
+ // interactive no-arg menu drives them (the menu is a guided surface).
93
+ const invokedViaMenu = { value: false }
94
+
95
+ const deprecatedAlias = (cmd: Command, preferred: string): Command => {
96
+ return cmd.hook('preAction', () => {
97
+ if (!invokedViaMenu.value) {
98
+ logger.warn(`"${cmd.name()}" is a deprecated alias; use "${preferred}" instead.`)
99
+ }
81
100
  })
101
+ }
82
102
 
83
- program
84
- .command('release-list')
85
- .description('List all release branches')
86
- .action(async () => {
87
- await ghReleaseList()
88
- })
103
+ // --- Command configurators (one source of options + action, shared by the
104
+ // grouped form and its flat alias so the two can never diverge) ---
105
+ const configureMergeDev = (cmd: Command): Command => {
106
+ return cmd
107
+ .description('Merge dev branch into every release branch')
108
+ .option('-a, --all', 'Select all active release branches')
109
+ .option('-y, --yes', 'Skip confirmation prompt')
110
+ .action(async (options) => {
111
+ emit(await ghMergeDev({ all: options.all, confirmedCommand: options.yes }))
112
+ })
113
+ }
89
114
 
90
- program
91
- .command('release-create')
92
- .description('Create one or more release branches (each entry can mix regular/hotfix and its own description)')
93
- .option(
94
- '-r, --release <spec>',
95
- 'Release spec "<version|next|name>[:type[:description]]" (repeatable). The token is a semver ("1.2.5"), the literal "next", or a kebab-case name ("checkout-redesign"). Type is regular|hotfix (default regular). Examples: "1.2.5", "1.2.5:hotfix", "next:regular:Holiday backend", "checkout-redesign:regular:Q3 redesign".',
96
- collectReleaseSpec,
97
- [],
98
- )
99
- .option('-y, --yes', 'Skip confirmation prompt')
100
- .action(async (options) => {
101
- const specs = options.release as string[]
102
- const inputs: ReleaseInput[] = specs.map(parseReleaseSpec)
103
- const releases = inputs.length > 0 ? inputs : undefined
115
+ const configureReleaseList = (cmd: Command): Command => {
116
+ return cmd.description('List all release branches').action(async () => {
117
+ emit(await ghReleaseList())
118
+ })
119
+ }
104
120
 
105
- await releaseCreate({
106
- releases,
107
- confirmedCommand: options.yes,
121
+ const configureReleaseCreate = (cmd: Command): Command => {
122
+ return cmd
123
+ .description('Create one or more release branches (each entry can mix regular/hotfix and its own description)')
124
+ .option(
125
+ '-r, --release <spec>',
126
+ 'Release spec "<version|next|name>[:type[:description]]" (repeatable). The token is a semver ("1.2.5"), the literal "next", or a kebab-case name ("checkout-redesign"). Type is regular|hotfix (default regular). Examples: "1.2.5", "1.2.5:hotfix", "next:regular:Holiday backend", "checkout-redesign:regular:Q3 redesign".',
127
+ collectReleaseSpec,
128
+ [],
129
+ )
130
+ .option('-y, --yes', 'Skip confirmation prompt')
131
+ .action(async (options) => {
132
+ const specs = options.release as string[]
133
+ const inputs: ReleaseInput[] = specs.map(parseReleaseSpec)
134
+ const releases = inputs.length > 0 ? inputs : undefined
135
+
136
+ emit(
137
+ await releaseCreate({
138
+ releases,
139
+ confirmedCommand: options.yes,
140
+ }),
141
+ )
108
142
  })
109
- })
143
+ }
110
144
 
111
- program
112
- .command('release-desc-edit')
113
- .description("Edit a release's description in Jira and in the matching GitHub PR body")
114
- .option('-v, --version <version>', 'Release version (e.g. 1.2.5) or release name (e.g. checkout-redesign)')
115
- .option('-d, --description <description>', 'New description (use "" to clear)')
116
- .option('-y, --yes', 'Skip confirmation prompt')
117
- .action(async (options) => {
118
- await releaseDescEdit({
119
- version: options.version,
120
- description: options.description,
121
- confirmedCommand: options.yes,
145
+ const configureReleaseDescEdit = (cmd: Command): Command => {
146
+ return cmd
147
+ .description("Edit a release's description in Jira and in the matching GitHub PR body")
148
+ .option('-v, --version <version>', 'Release version (e.g. 1.2.5) or release name (e.g. checkout-redesign)')
149
+ .option('-d, --description <description>', 'New description (use "" to clear)')
150
+ .option('-y, --yes', 'Skip confirmation prompt')
151
+ .action(async (options) => {
152
+ emit(
153
+ await releaseDescEdit({
154
+ version: options.version,
155
+ description: options.description,
156
+ confirmedCommand: options.yes,
157
+ }),
158
+ )
122
159
  })
123
- })
160
+ }
124
161
 
125
- program
126
- .command('release-deploy-all')
127
- .description('Deploy any release branch to any environment')
128
- .option(
129
- '-v, --version <version>',
130
- 'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deploy; "dev" deploys from the dev branch',
131
- )
132
- .option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')
133
- .option('--skip-terraform', 'Skip terraform deployment step')
134
- .action(async (options) => {
135
- await ghReleaseDeployAll({ version: options.version, env: options.env, skipTerraform: options.skipTerraform })
136
- })
162
+ const configureReleaseDeployAll = (cmd: Command): Command => {
163
+ return cmd
164
+ .description('Deploy any release branch to any environment')
165
+ .option(
166
+ '-v, --version <version>',
167
+ 'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deploy; "dev" deploys from the dev branch',
168
+ )
169
+ .option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')
170
+ .option('--skip-terraform', 'Skip terraform deployment step')
171
+ .option('-y, --yes', 'Skip confirmation prompt')
172
+ .action(async (options) => {
173
+ emit(
174
+ await ghReleaseDeployAll({
175
+ version: options.version,
176
+ env: options.env,
177
+ skipTerraform: options.skipTerraform,
178
+ confirmedCommand: options.yes,
179
+ }),
180
+ )
181
+ })
182
+ }
137
183
 
138
- program
139
- .command('release-deploy-selected')
140
- .description('Deploy selected services from release branch to any environment')
141
- .option(
142
- '-v, --version <version>',
143
- 'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deploy; "dev" deploys from the dev branch',
144
- )
145
- .option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')
146
- .option('-s, --services <services...>', 'Specify services to deploy, e.g. client-be client-fe')
147
- .option('--skip-terraform', 'Skip terraform deployment step')
148
- .action(async (options) => {
149
- await ghReleaseDeploySelected({
150
- version: options.version,
151
- env: options.env,
152
- services: options.services,
153
- skipTerraform: options.skipTerraform,
184
+ const configureReleaseDeploySelected = (cmd: Command): Command => {
185
+ return cmd
186
+ .description('Deploy selected services from release branch to any environment')
187
+ .option(
188
+ '-v, --version <version>',
189
+ 'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deploy; "dev" deploys from the dev branch',
190
+ )
191
+ .option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')
192
+ .option('-s, --services <services...>', 'Specify services to deploy, e.g. client-be client-fe')
193
+ .option('--skip-terraform', 'Skip terraform deployment step')
194
+ .option('-y, --yes', 'Skip confirmation prompt')
195
+ .action(async (options) => {
196
+ emit(
197
+ await ghReleaseDeploySelected({
198
+ version: options.version,
199
+ env: options.env,
200
+ services: options.services,
201
+ skipTerraform: options.skipTerraform,
202
+ confirmedCommand: options.yes,
203
+ }),
204
+ )
154
205
  })
155
- })
206
+ }
156
207
 
157
- program
158
- .command('release-deliver')
159
- .description('Release a new version to production')
160
- .option('-v, --version <version>', 'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deliver')
161
- .option('-y, --yes', 'Skip confirmation prompt')
162
- .action(async (options) => {
163
- await ghReleaseDeliver({ version: options.version, confirmedCommand: options.yes })
164
- })
208
+ const configureReleaseDeliver = (cmd: Command): Command => {
209
+ return cmd
210
+ .description('Release a new version to production')
211
+ .option('-v, --version <version>', 'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deliver')
212
+ .option('-y, --yes', 'Skip confirmation prompt')
213
+ .action(async (options) => {
214
+ emit(await ghReleaseDeliver({ version: options.version, confirmedCommand: options.yes }))
215
+ })
216
+ }
165
217
 
166
- program
167
- .command('worktrees-sync')
168
- .description('Remove release worktrees whose PRs are no longer open')
169
- .option('-y, --yes', 'Skip confirmation prompt')
170
- .action(async (options) => {
171
- await worktreesSync({ confirmedCommand: options.yes })
172
- })
218
+ const configureWorktreesSync = (cmd: Command): Command => {
219
+ return cmd
220
+ .description('Remove release worktrees whose PRs are no longer open')
221
+ .option('-y, --yes', 'Skip confirmation prompt')
222
+ .action(async (options) => {
223
+ emit(await worktreesSync({ confirmedCommand: options.yes }))
224
+ })
225
+ }
173
226
 
174
- program
175
- .command('worktrees-add')
176
- .description('Add git worktrees for release branches')
177
- .option('-y, --yes', 'Skip confirmation prompt')
178
- .option('-a, --all', 'Select all active release branches')
179
- .option('-v, --versions <versions>', 'Specify versions by comma, e.g. 1.2.5, 1.2.6')
180
- .option('-i, --ide [mode]', 'Editor mode for created worktrees: workspace (default) | none')
181
- .option('--no-ide', 'Skip the editor (alias for --ide none)')
182
- .option('-c, --cursor [mode]', 'Deprecated alias for --ide')
183
- .option('--no-cursor', 'Deprecated alias for --no-ide')
184
- .option('-g, --github-desktop', 'Open created worktrees in GitHub Desktop')
185
- .option('--no-github-desktop', 'Skip GitHub Desktop prompt')
186
- .option('-m, --cmux', 'Open created worktrees in cmux (3-pane layout)')
187
- .option('--no-cmux', 'Skip cmux prompt')
188
- .action(async (options) => {
189
- // `--ide` wins over the deprecated `--cursor` alias when both are provided.
190
- const ide = normalizeIdeMode(options.ide, '--ide') ?? normalizeIdeMode(options.cursor, '--cursor')
191
-
192
- await worktreesAdd({
193
- confirmedCommand: options.yes,
194
- all: options.all,
195
- versions: options.versions,
196
- ide,
197
- githubDesktop: options.githubDesktop,
198
- cmux: options.cmux,
227
+ const configureWorktreesAdd = (cmd: Command): Command => {
228
+ return cmd
229
+ .description('Add git worktrees for release branches')
230
+ .option('-y, --yes', 'Skip confirmation prompt')
231
+ .option('-a, --all', 'Select all active release branches')
232
+ .option('-v, --versions <versions>', 'Specify versions by comma, e.g. 1.2.5, 1.2.6')
233
+ .option('-i, --ide [mode]', 'Editor mode for created worktrees: workspace (default) | none')
234
+ .option('--no-ide', 'Skip the editor (alias for --ide none)')
235
+ .option('-c, --cursor [mode]', 'Deprecated alias for --ide')
236
+ .option('--no-cursor', 'Deprecated alias for --no-ide')
237
+ .option('-g, --github-desktop', 'Open created worktrees in GitHub Desktop')
238
+ .option('--no-github-desktop', 'Skip GitHub Desktop prompt')
239
+ .option('-m, --cmux', 'Open created worktrees in cmux (3-pane layout)')
240
+ .option('--no-cmux', 'Skip cmux prompt')
241
+ .action(async (options) => {
242
+ // `--ide` wins over the deprecated `--cursor` alias when both are provided.
243
+ const ide = normalizeIdeMode(options.ide, '--ide') ?? normalizeIdeMode(options.cursor, '--cursor')
244
+
245
+ emit(
246
+ await worktreesAdd({
247
+ confirmedCommand: options.yes,
248
+ all: options.all,
249
+ versions: options.versions,
250
+ ide,
251
+ githubDesktop: options.githubDesktop,
252
+ cmux: options.cmux,
253
+ }),
254
+ )
199
255
  })
200
- })
256
+ }
201
257
 
202
- program
203
- .command('worktrees-list')
204
- .description('List all git worktrees with detailed information')
205
- .action(async () => {
206
- await worktreesList()
258
+ const configureWorktreesList = (cmd: Command): Command => {
259
+ return cmd.description('List all git worktrees with detailed information').action(async () => {
260
+ emit(await worktreesList())
207
261
  })
262
+ }
208
263
 
209
- program
210
- .command('worktrees-remove')
211
- .description('Remove git worktrees for release branches')
212
- .option('-y, --yes', 'Skip confirmation prompt')
213
- .option('-a, --all', 'Select all active release branches')
214
- .option('-v, --versions <versions>', 'Specify versions by comma, e.g. 1.2.5, 1.2.6')
215
- .action(async (options) => {
216
- await worktreesRemove({ confirmedCommand: options.yes, all: options.all, versions: options.versions })
217
- })
264
+ const configureWorktreesRemove = (cmd: Command): Command => {
265
+ return cmd
266
+ .description('Remove git worktrees for release branches')
267
+ .option('-y, --yes', 'Skip confirmation prompt')
268
+ .option('-a, --all', 'Select all active release branches')
269
+ .option('-v, --versions <versions>', 'Specify versions by comma, e.g. 1.2.5, 1.2.6')
270
+ .action(async (options) => {
271
+ emit(await worktreesRemove({ confirmedCommand: options.yes, all: options.all, versions: options.versions }))
272
+ })
273
+ }
218
274
 
219
- program
220
- .command('worktrees-open')
221
- .description('Open the configured editor (Cursor/Zed) + cmux for existing release worktrees (cold-start restore)')
222
- .action(async () => {
223
- await worktreesOpen()
224
- })
275
+ const configureWorktreesReload = (cmd: Command): Command => {
276
+ return cmd
277
+ .description(
278
+ 'Close all cmux/editor worktree windows, then reopen the current release worktrees (also cold-start restore)',
279
+ )
280
+ .action(async () => {
281
+ emit(await worktreesReload())
282
+ })
283
+ }
225
284
 
226
- program
227
- .command('worktrees-reload')
228
- .description('Close all cmux/editor worktree windows, then reopen the current release worktrees')
229
- .action(async () => {
230
- await worktreesReload()
231
- })
285
+ const configureVendorConfig = (cmd: Command): Command => {
286
+ return cmd
287
+ .description('Show the machine-local factory config (~/.infra-kit/vendor.json) or scaffold it with --init')
288
+ .option('--init', 'Scaffold ~/.infra-kit/vendor.json (skips if it already exists)')
289
+ .action(async (options) => {
290
+ emit(await vendorConfig({ init: options.init }))
291
+ })
292
+ }
293
+
294
+ // --- Grouped command surface (preferred form) ---
295
+ const releaseGroup = program.command('release').description('Release management commands')
296
+
297
+ configureMergeDev(releaseGroup.command('merge-dev'))
298
+ configureReleaseList(releaseGroup.command('list'))
299
+ configureReleaseCreate(releaseGroup.command('create'))
300
+ configureReleaseDescEdit(releaseGroup.command('desc-edit'))
301
+ configureReleaseDeployAll(releaseGroup.command('deploy-all'))
302
+ configureReleaseDeploySelected(releaseGroup.command('deploy-selected'))
303
+ configureReleaseDeliver(releaseGroup.command('deliver'))
304
+
305
+ const worktreesGroup = program.command('worktrees').description('Git worktree management commands')
306
+
307
+ configureWorktreesAdd(worktreesGroup.command('add'))
308
+ configureWorktreesList(worktreesGroup.command('list'))
309
+ configureWorktreesRemove(worktreesGroup.command('remove'))
310
+ configureWorktreesSync(worktreesGroup.command('sync'))
311
+ configureWorktreesReload(worktreesGroup.command('reload'))
312
+
313
+ // --- Deprecated flat aliases (kept one release cycle; warn when used directly) ---
314
+ deprecatedAlias(configureMergeDev(program.command('merge-dev')), 'release merge-dev')
315
+ deprecatedAlias(configureReleaseList(program.command('release-list')), 'release list')
316
+ deprecatedAlias(configureReleaseCreate(program.command('release-create')), 'release create')
317
+ deprecatedAlias(configureReleaseDescEdit(program.command('release-desc-edit')), 'release desc-edit')
318
+ deprecatedAlias(configureReleaseDeployAll(program.command('release-deploy-all')), 'release deploy-all')
319
+ deprecatedAlias(configureReleaseDeploySelected(program.command('release-deploy-selected')), 'release deploy-selected')
320
+ deprecatedAlias(configureReleaseDeliver(program.command('release-deliver')), 'release deliver')
321
+ deprecatedAlias(configureWorktreesAdd(program.command('worktrees-add')), 'worktrees add')
322
+ deprecatedAlias(configureWorktreesList(program.command('worktrees-list')), 'worktrees list')
323
+ deprecatedAlias(configureWorktreesRemove(program.command('worktrees-remove')), 'worktrees remove')
324
+ deprecatedAlias(configureWorktreesSync(program.command('worktrees-sync')), 'worktrees sync')
325
+ deprecatedAlias(configureWorktreesReload(program.command('worktrees-reload')), 'worktrees reload')
232
326
 
233
327
  const configCmd = program.command('config').description('Manage infra-kit configuration files')
234
328
 
@@ -236,14 +330,14 @@ configCmd
236
330
  .command('path')
237
331
  .description('Show the resolved config merge chain and file paths')
238
332
  .action(async () => {
239
- await configPath()
333
+ emit(await configPath())
240
334
  })
241
335
 
242
336
  configCmd
243
337
  .command('edit')
244
338
  .description('Open the user-scope per-project override file in $EDITOR')
245
339
  .action(async () => {
246
- await configEdit()
340
+ emit(await configEdit())
247
341
  })
248
342
 
249
343
  program
@@ -254,44 +348,97 @@ program
254
348
  .action(async (options) => {
255
349
  const result = await audit({ all: options.all, root: options.root })
256
350
 
351
+ emit(result)
352
+
257
353
  if (!result.structuredContent.allPassed) {
258
354
  process.exitCode = 1
259
355
  }
260
356
  })
261
357
 
358
+ const vendorCmd = program.command('vendor').description('Verify and sync the mirrored vendor/ tree')
359
+
360
+ vendorCmd
361
+ .command('check')
362
+ .description('Verify vendor/ matches vendor/.sync-manifest.json (self-contained; for any consumer repo)')
363
+ .action(async () => {
364
+ const result = await vendorCheck()
365
+
366
+ emit(result)
367
+
368
+ if (!result.structuredContent.ok) {
369
+ process.exitCode = 1
370
+ }
371
+ })
372
+
373
+ vendorCmd
374
+ .command('sync')
375
+ .description('Copy vendored files from the source repo into each target and regenerate manifests')
376
+ .option('-y, --yes', 'Skip confirmation prompt')
377
+ .option('-r, --repos <repos>', 'Restrict to comma-separated target repo names')
378
+ .action(async (options) => {
379
+ emit(await vendorSync({ confirmedCommand: options.yes, repos: parseRepos(options.repos) }))
380
+ })
381
+
382
+ vendorCmd
383
+ .command('manifest')
384
+ .description('Regenerate each target vendor/.sync-manifest.json + README from current content (no copy)')
385
+ .option('-r, --repos <repos>', 'Restrict to comma-separated target repo names')
386
+ .action(async (options) => {
387
+ emit(await vendorManifest({ confirmedCommand: true, repos: parseRepos(options.repos) }))
388
+ })
389
+
390
+ vendorCmd
391
+ .command('diff')
392
+ .description('Source-aware drift check (rsync dry-run) of each target vendored subtree vs the source')
393
+ .option('-r, --repos <repos>', 'Restrict to comma-separated target repo names')
394
+ .action(async (options) => {
395
+ const result = await vendorDiff({ repos: parseRepos(options.repos) })
396
+
397
+ emit(result)
398
+
399
+ if (!result.structuredContent.ok) {
400
+ process.exitCode = 1
401
+ }
402
+ })
403
+
404
+ // Grouped form (preferred); the flat `vendor-config` below is a deprecated alias.
405
+ configureVendorConfig(vendorCmd.command('config'))
406
+
407
+ deprecatedAlias(configureVendorConfig(program.command('vendor-config')), 'vendor config')
408
+
262
409
  program
263
410
  .command('doctor')
264
411
  .description('Check installation and authentication status of gh and doppler CLIs')
265
412
  .action(async () => {
266
- await doctor()
413
+ emit(await doctor())
267
414
  })
268
415
 
269
416
  program
270
417
  .command('version')
271
418
  .description('Print the installed infra-kit CLI version')
272
419
  .action(async () => {
273
- await version()
420
+ emit(await version())
274
421
  })
275
422
 
276
423
  program
277
424
  .command('env-status')
278
- .description('Show Doppler authentication status and detected project info')
425
+ .description('Show which env is loaded in this session (local introspection; no Doppler call)')
279
426
  .action(async () => {
280
- await envStatus()
427
+ emit(await envStatus())
281
428
  })
282
429
 
283
430
  program
284
431
  .command('env-list')
285
432
  .description('List available Doppler configs for the detected project')
286
433
  .action(async () => {
287
- await envList()
434
+ emit(await envList())
288
435
  })
289
436
 
290
437
  program
291
438
  .command('init')
292
439
  .description('Inject shell integration into .zshrc and sync repo agent-instruction files')
293
440
  .action(async () => {
294
- await init()
441
+ emit(await init())
295
442
  })
296
443
 
297
444
  program
@@ -299,45 +446,67 @@ program
299
446
  .description('Load Doppler env vars for a config. Source the returned file path to apply.')
300
447
  .option('-c, --config <config>', 'Environment config name to load (e.g. dev, arthur)')
301
448
  .action(async (options) => {
302
- await envLoad({ config: options.config })
449
+ emit(await envLoad({ config: options.config }))
303
450
  })
304
451
 
305
452
  program
306
453
  .command('env-clear')
307
454
  .description('Clear loaded env vars. Source the returned file path to apply.')
308
455
  .action(async () => {
309
- await envClear()
456
+ emit(await envClear())
310
457
  })
311
458
 
459
+ // Internal: driven by the init shell-startup integration (backgrounded). Writes
460
+ // env-load.sh when envAutoLoad is configured + eligible; the precmd hook sources
461
+ // it. Hidden + no stdout output so it never pollutes the shell or the menu.
462
+ program
463
+ .command('env-autoload', { hidden: true })
464
+ .description('Internal: prime env for the shell-startup auto-load trigger')
465
+ .action(async () => {
466
+ await envAutoload()
467
+ })
468
+
469
+ // Register `--json` on every command, then resolve the flag before each action
470
+ // runs. In JSON mode we lower the logger to `warn` so the human-oriented info
471
+ // lines stop cluttering stderr while errors still surface; the structured
472
+ // payload is written to stdout by `emit`. No handler logic is affected.
473
+ program.commands.forEach(addJsonOption)
474
+
475
+ // Commands excluded from the cli-invocation auto-load trigger: the env-* family
476
+ // (avoids recursion — `env-autoload`/`env-load` would re-enter), plus the
477
+ // host-inspecting / meta commands where priming Doppler env would be surprising
478
+ // (`init` bootstraps the shell block, `doctor` inspects auth, `version` prints a
479
+ // string). `--help`/`--version`/the bare-arg menu don't fire preAction at all.
480
+ const isAutoLoadExcludedCommand = (name: string): boolean => {
481
+ return name.startsWith('env-') || name === 'init' || name === 'doctor' || name === 'version'
482
+ }
483
+
484
+ program.hook('preAction', async (_thisCommand, actionCommand) => {
485
+ // `optsWithGlobals` (not `opts`) so `--json` is seen on grouped subcommands:
486
+ // for `release list --json` Commander binds the post-subcommand flag to the
487
+ // parent `release` group, so the leaf's own `opts()` would not carry it.
488
+ jsonOutput.enabled = Boolean(actionCommand.optsWithGlobals().json)
489
+
490
+ if (jsonOutput.enabled) {
491
+ logger.level = 'warn'
492
+ }
493
+
494
+ // cli-invocation auto-load: primes the shell env for SUBSEQUENT commands. The
495
+ // current command does NOT see these vars — a child process can't mutate its
496
+ // parent shell; the precmd hook sources the written file on the next prompt.
497
+ // runEnvAutoLoad self-gates on config trigger and swallows transient failures,
498
+ // so this is a no-op unless configured for cli-invocation and never blocks.
499
+ if (!isAutoLoadExcludedCommand(actionCommand.name())) {
500
+ await runEnvAutoLoad({ expectedTrigger: 'cli-invocation' })
501
+ }
502
+ })
503
+
312
504
  if (process.argv.length <= 2) {
313
- const releaseCommands = [
314
- 'merge-dev',
315
- 'release-list',
316
- 'release-create',
317
- 'release-desc-edit',
318
- 'release-deploy-all',
319
- 'release-deploy-selected',
320
- 'release-deliver',
321
- ]
322
- const worktreeCommands = [
323
- 'worktrees-add',
324
- 'worktrees-list',
325
- 'worktrees-open',
326
- 'worktrees-reload',
327
- 'worktrees-remove',
328
- 'worktrees-sync',
329
- ]
330
- const envCommands = [
331
- 'audit',
332
- 'doctor',
333
- 'init',
334
- 'version',
335
- 'config',
336
- 'env-status',
337
- 'env-list',
338
- 'env-load',
339
- 'env-clear',
340
- ]
505
+ // Menu groups derive from the single command catalog (no hand-maintained
506
+ // name arrays). Membership and order live in src/lib/command-catalog.
507
+ const releaseCommands = getMenuGroupCommands('release')
508
+ const worktreeCommands = getMenuGroupCommands('worktrees')
509
+ const envCommands = getMenuGroupCommands('environment')
341
510
 
342
511
  const commandMap = new Map(
343
512
  program.commands.map((cmd) => {
@@ -345,45 +514,83 @@ if (process.argv.length <= 2) {
345
514
  }),
346
515
  )
347
516
 
348
- const allNames = [...releaseCommands, ...worktreeCommands, ...envCommands]
349
- const maxLen = Math.max(
350
- ...allNames.map((n) => {
351
- return n.length
352
- }),
353
- )
517
+ const groups = [
518
+ { label: 'Release Management', names: releaseCommands },
519
+ { label: 'Worktrees', names: worktreeCommands },
520
+ { label: 'Environment', names: envCommands },
521
+ ]
354
522
 
355
- const toChoices = (names: string[]) => {
523
+ // Flat {name, description, group} list shared by both the Ink palette and the
524
+ // Inquirer fallback; descriptions come from Commander (single source).
525
+ const paletteItems = groups.flatMap(({ label, names }) => {
356
526
  return names
357
- .filter((n) => {
358
- return commandMap.has(n)
527
+ .filter((name) => {
528
+ return commandMap.has(name)
359
529
  })
360
- .map((n) => {
361
- return {
362
- name: `${n.padEnd(maxLen)} ${commandMap.get(n)!.description()}`,
363
- value: n,
364
- }
530
+ .map((name) => {
531
+ return { name, description: commandMap.get(name)!.description(), group: label }
365
532
  })
533
+ })
534
+
535
+ let selected: string | null
536
+
537
+ // Interactive TTY → Ink command palette, loaded lazily via dynamic import so
538
+ // React/Ink never touch the MCP / `--json` / non-TTY code paths. Otherwise fall
539
+ // back to the Inquirer menu (scripts, pipes, CI).
540
+ if (process.stdout.isTTY && process.stdin.isTTY) {
541
+ const { runCommandPalette } = await import('src/tui/boot')
542
+
543
+ selected = await runCommandPalette(paletteItems)
544
+ } else {
545
+ const alignedLabels = formatAlignedRows(
546
+ paletteItems.map((item) => {
547
+ return [item.name, item.description] as const
548
+ }),
549
+ )
550
+ const labelByName = new Map<string, string>()
551
+
552
+ paletteItems.forEach((item, index) => {
553
+ labelByName.set(item.name, alignedLabels[index] ?? item.name)
554
+ })
555
+
556
+ const toChoices = (names: string[]) => {
557
+ return names
558
+ .filter((name) => {
559
+ return commandMap.has(name)
560
+ })
561
+ .map((name) => {
562
+ return {
563
+ name: labelByName.get(name) ?? name,
564
+ value: name,
565
+ }
566
+ })
567
+ }
568
+
569
+ selected = await select(
570
+ {
571
+ message: 'Select a command to run',
572
+ choices: [
573
+ new Separator(' '),
574
+ new Separator('— Release Management —'),
575
+ ...toChoices(releaseCommands),
576
+ new Separator(' '),
577
+ new Separator('— Worktrees —'),
578
+ ...toChoices(worktreeCommands),
579
+ new Separator(' '),
580
+ new Separator('— Environment —'),
581
+ ...toChoices(envCommands),
582
+ ],
583
+ },
584
+ { output: process.stderr },
585
+ )
366
586
  }
367
587
 
368
- const selected = await select(
369
- {
370
- message: 'Select a command to run',
371
- choices: [
372
- new Separator(' '),
373
- new Separator('— Release Management —'),
374
- ...toChoices(releaseCommands),
375
- new Separator(' '),
376
- new Separator('— Worktrees —'),
377
- ...toChoices(worktreeCommands),
378
- new Separator(' '),
379
- new Separator('— Environment —'),
380
- ...toChoices(envCommands),
381
- ],
382
- },
383
- { output: process.stderr },
384
- )
588
+ // The menu is a guided surface; don't nag about deprecated flat names here.
589
+ if (selected) {
590
+ invokedViaMenu.value = true
385
591
 
386
- await runProgram(['node', 'infra-kit', selected])
592
+ await runProgram(['node', 'infra-kit', selected])
593
+ }
387
594
  } else {
388
595
  await runProgram()
389
596
  }