@hashicorp/kits 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/README.md +619 -0
  2. package/bin/kits.js +8 -0
  3. package/dist/adapters/base-adapter.d.ts +231 -0
  4. package/dist/adapters/base-adapter.d.ts.map +1 -0
  5. package/dist/adapters/base-adapter.js +703 -0
  6. package/dist/adapters/base-adapter.js.map +1 -0
  7. package/dist/adapters/claude-code/detection.d.ts +63 -0
  8. package/dist/adapters/claude-code/detection.d.ts.map +1 -0
  9. package/dist/adapters/claude-code/detection.js +154 -0
  10. package/dist/adapters/claude-code/detection.js.map +1 -0
  11. package/dist/adapters/claude-code/index.d.ts +178 -0
  12. package/dist/adapters/claude-code/index.d.ts.map +1 -0
  13. package/dist/adapters/claude-code/index.js +643 -0
  14. package/dist/adapters/claude-code/index.js.map +1 -0
  15. package/dist/adapters/claude-code/installer.d.ts +161 -0
  16. package/dist/adapters/claude-code/installer.d.ts.map +1 -0
  17. package/dist/adapters/claude-code/installer.js +413 -0
  18. package/dist/adapters/claude-code/installer.js.map +1 -0
  19. package/dist/adapters/claude-code/model-mapping.d.ts +7 -0
  20. package/dist/adapters/claude-code/model-mapping.d.ts.map +1 -0
  21. package/dist/adapters/claude-code/model-mapping.js +14 -0
  22. package/dist/adapters/claude-code/model-mapping.js.map +1 -0
  23. package/dist/adapters/claude-code/tool-mapping.d.ts +18 -0
  24. package/dist/adapters/claude-code/tool-mapping.d.ts.map +1 -0
  25. package/dist/adapters/claude-code/tool-mapping.js +31 -0
  26. package/dist/adapters/claude-code/tool-mapping.js.map +1 -0
  27. package/dist/adapters/codex/detection.d.ts +60 -0
  28. package/dist/adapters/codex/detection.d.ts.map +1 -0
  29. package/dist/adapters/codex/detection.js +146 -0
  30. package/dist/adapters/codex/detection.js.map +1 -0
  31. package/dist/adapters/codex/index.d.ts +167 -0
  32. package/dist/adapters/codex/index.d.ts.map +1 -0
  33. package/dist/adapters/codex/index.js +344 -0
  34. package/dist/adapters/codex/index.js.map +1 -0
  35. package/dist/adapters/codex/installer.d.ts +147 -0
  36. package/dist/adapters/codex/installer.d.ts.map +1 -0
  37. package/dist/adapters/codex/installer.js +229 -0
  38. package/dist/adapters/codex/installer.js.map +1 -0
  39. package/dist/adapters/codex/model-mapping.d.ts +7 -0
  40. package/dist/adapters/codex/model-mapping.d.ts.map +1 -0
  41. package/dist/adapters/codex/model-mapping.js +14 -0
  42. package/dist/adapters/codex/model-mapping.js.map +1 -0
  43. package/dist/adapters/codex/tool-mapping.d.ts +19 -0
  44. package/dist/adapters/codex/tool-mapping.d.ts.map +1 -0
  45. package/dist/adapters/codex/tool-mapping.js +32 -0
  46. package/dist/adapters/codex/tool-mapping.js.map +1 -0
  47. package/dist/adapters/command-parser.d.ts +72 -0
  48. package/dist/adapters/command-parser.d.ts.map +1 -0
  49. package/dist/adapters/command-parser.js +222 -0
  50. package/dist/adapters/command-parser.js.map +1 -0
  51. package/dist/adapters/file-operations.d.ts +164 -0
  52. package/dist/adapters/file-operations.d.ts.map +1 -0
  53. package/dist/adapters/file-operations.js +526 -0
  54. package/dist/adapters/file-operations.js.map +1 -0
  55. package/dist/adapters/gemini-cli/detection.d.ts +57 -0
  56. package/dist/adapters/gemini-cli/detection.d.ts.map +1 -0
  57. package/dist/adapters/gemini-cli/detection.js +143 -0
  58. package/dist/adapters/gemini-cli/detection.js.map +1 -0
  59. package/dist/adapters/gemini-cli/index.d.ts +182 -0
  60. package/dist/adapters/gemini-cli/index.d.ts.map +1 -0
  61. package/dist/adapters/gemini-cli/index.js +598 -0
  62. package/dist/adapters/gemini-cli/index.js.map +1 -0
  63. package/dist/adapters/gemini-cli/installer.d.ts +158 -0
  64. package/dist/adapters/gemini-cli/installer.d.ts.map +1 -0
  65. package/dist/adapters/gemini-cli/installer.js +457 -0
  66. package/dist/adapters/gemini-cli/installer.js.map +1 -0
  67. package/dist/adapters/gemini-cli/model-mapping.d.ts +7 -0
  68. package/dist/adapters/gemini-cli/model-mapping.d.ts.map +1 -0
  69. package/dist/adapters/gemini-cli/model-mapping.js +14 -0
  70. package/dist/adapters/gemini-cli/model-mapping.js.map +1 -0
  71. package/dist/adapters/gemini-cli/tool-mapping.d.ts +18 -0
  72. package/dist/adapters/gemini-cli/tool-mapping.d.ts.map +1 -0
  73. package/dist/adapters/gemini-cli/tool-mapping.js +31 -0
  74. package/dist/adapters/gemini-cli/tool-mapping.js.map +1 -0
  75. package/dist/adapters/github-copilot/detection.d.ts +58 -0
  76. package/dist/adapters/github-copilot/detection.d.ts.map +1 -0
  77. package/dist/adapters/github-copilot/detection.js +144 -0
  78. package/dist/adapters/github-copilot/detection.js.map +1 -0
  79. package/dist/adapters/github-copilot/index.d.ts +203 -0
  80. package/dist/adapters/github-copilot/index.d.ts.map +1 -0
  81. package/dist/adapters/github-copilot/index.js +595 -0
  82. package/dist/adapters/github-copilot/index.js.map +1 -0
  83. package/dist/adapters/github-copilot/installer.d.ts +124 -0
  84. package/dist/adapters/github-copilot/installer.d.ts.map +1 -0
  85. package/dist/adapters/github-copilot/installer.js +343 -0
  86. package/dist/adapters/github-copilot/installer.js.map +1 -0
  87. package/dist/adapters/github-copilot/model-mapping.d.ts +7 -0
  88. package/dist/adapters/github-copilot/model-mapping.d.ts.map +1 -0
  89. package/dist/adapters/github-copilot/model-mapping.js +14 -0
  90. package/dist/adapters/github-copilot/model-mapping.js.map +1 -0
  91. package/dist/adapters/github-copilot/tool-mapping.d.ts +18 -0
  92. package/dist/adapters/github-copilot/tool-mapping.d.ts.map +1 -0
  93. package/dist/adapters/github-copilot/tool-mapping.js +31 -0
  94. package/dist/adapters/github-copilot/tool-mapping.js.map +1 -0
  95. package/dist/adapters/index.d.ts +39 -0
  96. package/dist/adapters/index.d.ts.map +1 -0
  97. package/dist/adapters/index.js +76 -0
  98. package/dist/adapters/index.js.map +1 -0
  99. package/dist/adapters/interface.d.ts +9 -0
  100. package/dist/adapters/interface.d.ts.map +1 -0
  101. package/dist/adapters/interface.js +8 -0
  102. package/dist/adapters/interface.js.map +1 -0
  103. package/dist/adapters/model-templating.d.ts +16 -0
  104. package/dist/adapters/model-templating.d.ts.map +1 -0
  105. package/dist/adapters/model-templating.js +52 -0
  106. package/dist/adapters/model-templating.js.map +1 -0
  107. package/dist/adapters/opencode/detection.d.ts +57 -0
  108. package/dist/adapters/opencode/detection.d.ts.map +1 -0
  109. package/dist/adapters/opencode/detection.js +140 -0
  110. package/dist/adapters/opencode/detection.js.map +1 -0
  111. package/dist/adapters/opencode/index.d.ts +168 -0
  112. package/dist/adapters/opencode/index.d.ts.map +1 -0
  113. package/dist/adapters/opencode/index.js +494 -0
  114. package/dist/adapters/opencode/index.js.map +1 -0
  115. package/dist/adapters/opencode/installer.d.ts +91 -0
  116. package/dist/adapters/opencode/installer.d.ts.map +1 -0
  117. package/dist/adapters/opencode/installer.js +290 -0
  118. package/dist/adapters/opencode/installer.js.map +1 -0
  119. package/dist/adapters/opencode/model-mapping.d.ts +7 -0
  120. package/dist/adapters/opencode/model-mapping.d.ts.map +1 -0
  121. package/dist/adapters/opencode/model-mapping.js +14 -0
  122. package/dist/adapters/opencode/model-mapping.js.map +1 -0
  123. package/dist/adapters/opencode/tool-mapping.d.ts +18 -0
  124. package/dist/adapters/opencode/tool-mapping.d.ts.map +1 -0
  125. package/dist/adapters/opencode/tool-mapping.js +31 -0
  126. package/dist/adapters/opencode/tool-mapping.js.map +1 -0
  127. package/dist/adapters/registry.d.ts +154 -0
  128. package/dist/adapters/registry.d.ts.map +1 -0
  129. package/dist/adapters/registry.js +223 -0
  130. package/dist/adapters/registry.js.map +1 -0
  131. package/dist/adapters/skill-frontmatter.d.ts +34 -0
  132. package/dist/adapters/skill-frontmatter.d.ts.map +1 -0
  133. package/dist/adapters/skill-frontmatter.js +110 -0
  134. package/dist/adapters/skill-frontmatter.js.map +1 -0
  135. package/dist/adapters/subagent-frontmatter.d.ts +22 -0
  136. package/dist/adapters/subagent-frontmatter.d.ts.map +1 -0
  137. package/dist/adapters/subagent-frontmatter.js +80 -0
  138. package/dist/adapters/subagent-frontmatter.js.map +1 -0
  139. package/dist/adapters/tool-templating.d.ts +162 -0
  140. package/dist/adapters/tool-templating.d.ts.map +1 -0
  141. package/dist/adapters/tool-templating.js +273 -0
  142. package/dist/adapters/tool-templating.js.map +1 -0
  143. package/dist/adapters/types.d.ts +347 -0
  144. package/dist/adapters/types.d.ts.map +1 -0
  145. package/dist/adapters/types.js +33 -0
  146. package/dist/adapters/types.js.map +1 -0
  147. package/dist/cli/index.d.ts +10 -0
  148. package/dist/cli/index.d.ts.map +1 -0
  149. package/dist/cli/index.js +261 -0
  150. package/dist/cli/index.js.map +1 -0
  151. package/dist/cli/info.d.ts +20 -0
  152. package/dist/cli/info.d.ts.map +1 -0
  153. package/dist/cli/info.js +194 -0
  154. package/dist/cli/info.js.map +1 -0
  155. package/dist/cli/install.d.ts +21 -0
  156. package/dist/cli/install.d.ts.map +1 -0
  157. package/dist/cli/install.js +1624 -0
  158. package/dist/cli/install.js.map +1 -0
  159. package/dist/cli/list.d.ts +19 -0
  160. package/dist/cli/list.d.ts.map +1 -0
  161. package/dist/cli/list.js +216 -0
  162. package/dist/cli/list.js.map +1 -0
  163. package/dist/cli/types.d.ts +246 -0
  164. package/dist/cli/types.d.ts.map +1 -0
  165. package/dist/cli/types.js +25 -0
  166. package/dist/cli/types.js.map +1 -0
  167. package/dist/cli/uninstall.d.ts +20 -0
  168. package/dist/cli/uninstall.d.ts.map +1 -0
  169. package/dist/cli/uninstall.js +393 -0
  170. package/dist/cli/uninstall.js.map +1 -0
  171. package/dist/cli/upgrade.d.ts +20 -0
  172. package/dist/cli/upgrade.d.ts.map +1 -0
  173. package/dist/cli/upgrade.js +372 -0
  174. package/dist/cli/upgrade.js.map +1 -0
  175. package/dist/cli/validate.d.ts +14 -0
  176. package/dist/cli/validate.d.ts.map +1 -0
  177. package/dist/cli/validate.js +307 -0
  178. package/dist/cli/validate.js.map +1 -0
  179. package/dist/core/debug.d.ts +23 -0
  180. package/dist/core/debug.d.ts.map +1 -0
  181. package/dist/core/debug.js +69 -0
  182. package/dist/core/debug.js.map +1 -0
  183. package/dist/core/hook-instance.d.ts +8 -0
  184. package/dist/core/hook-instance.d.ts.map +1 -0
  185. package/dist/core/hook-instance.js +62 -0
  186. package/dist/core/hook-instance.js.map +1 -0
  187. package/dist/core/mcp-instance.d.ts +13 -0
  188. package/dist/core/mcp-instance.d.ts.map +1 -0
  189. package/dist/core/mcp-instance.js +80 -0
  190. package/dist/core/mcp-instance.js.map +1 -0
  191. package/dist/core/types.d.ts +461 -0
  192. package/dist/core/types.d.ts.map +1 -0
  193. package/dist/core/types.js +42 -0
  194. package/dist/core/types.js.map +1 -0
  195. package/dist/core/upgrade-executor.d.ts +70 -0
  196. package/dist/core/upgrade-executor.d.ts.map +1 -0
  197. package/dist/core/upgrade-executor.js +368 -0
  198. package/dist/core/upgrade-executor.js.map +1 -0
  199. package/dist/discovery/fetcher-registry.d.ts +87 -0
  200. package/dist/discovery/fetcher-registry.d.ts.map +1 -0
  201. package/dist/discovery/fetcher-registry.js +119 -0
  202. package/dist/discovery/fetcher-registry.js.map +1 -0
  203. package/dist/discovery/git-fetcher.d.ts +61 -0
  204. package/dist/discovery/git-fetcher.d.ts.map +1 -0
  205. package/dist/discovery/git-fetcher.js +150 -0
  206. package/dist/discovery/git-fetcher.js.map +1 -0
  207. package/dist/discovery/index.d.ts +13 -0
  208. package/dist/discovery/index.d.ts.map +1 -0
  209. package/dist/discovery/index.js +15 -0
  210. package/dist/discovery/index.js.map +1 -0
  211. package/dist/discovery/kit-scanner.d.ts +55 -0
  212. package/dist/discovery/kit-scanner.d.ts.map +1 -0
  213. package/dist/discovery/kit-scanner.js +305 -0
  214. package/dist/discovery/kit-scanner.js.map +1 -0
  215. package/dist/discovery/local-fetcher.d.ts +38 -0
  216. package/dist/discovery/local-fetcher.d.ts.map +1 -0
  217. package/dist/discovery/local-fetcher.js +100 -0
  218. package/dist/discovery/local-fetcher.js.map +1 -0
  219. package/dist/discovery/source-parser.d.ts +33 -0
  220. package/dist/discovery/source-parser.d.ts.map +1 -0
  221. package/dist/discovery/source-parser.js +136 -0
  222. package/dist/discovery/source-parser.js.map +1 -0
  223. package/dist/discovery/types.d.ts +145 -0
  224. package/dist/discovery/types.d.ts.map +1 -0
  225. package/dist/discovery/types.js +18 -0
  226. package/dist/discovery/types.js.map +1 -0
  227. package/dist/index.d.ts +13 -0
  228. package/dist/index.d.ts.map +1 -0
  229. package/dist/index.js +19 -0
  230. package/dist/index.js.map +1 -0
  231. package/dist/manifest/index.d.ts +79 -0
  232. package/dist/manifest/index.d.ts.map +1 -0
  233. package/dist/manifest/index.js +200 -0
  234. package/dist/manifest/index.js.map +1 -0
  235. package/dist/manifest/read.d.ts +32 -0
  236. package/dist/manifest/read.d.ts.map +1 -0
  237. package/dist/manifest/read.js +88 -0
  238. package/dist/manifest/read.js.map +1 -0
  239. package/dist/manifest/types.d.ts +119 -0
  240. package/dist/manifest/types.d.ts.map +1 -0
  241. package/dist/manifest/types.js +44 -0
  242. package/dist/manifest/types.js.map +1 -0
  243. package/dist/manifest/upgrade-check.d.ts +72 -0
  244. package/dist/manifest/upgrade-check.d.ts.map +1 -0
  245. package/dist/manifest/upgrade-check.js +215 -0
  246. package/dist/manifest/upgrade-check.js.map +1 -0
  247. package/dist/manifest/utils.d.ts +35 -0
  248. package/dist/manifest/utils.d.ts.map +1 -0
  249. package/dist/manifest/utils.js +57 -0
  250. package/dist/manifest/utils.js.map +1 -0
  251. package/dist/manifest/write.d.ts +44 -0
  252. package/dist/manifest/write.d.ts.map +1 -0
  253. package/dist/manifest/write.js +77 -0
  254. package/dist/manifest/write.js.map +1 -0
  255. package/dist/resolution/env-resolver.d.ts +81 -0
  256. package/dist/resolution/env-resolver.d.ts.map +1 -0
  257. package/dist/resolution/env-resolver.js +233 -0
  258. package/dist/resolution/env-resolver.js.map +1 -0
  259. package/dist/resolution/index.d.ts +55 -0
  260. package/dist/resolution/index.d.ts.map +1 -0
  261. package/dist/resolution/index.js +412 -0
  262. package/dist/resolution/index.js.map +1 -0
  263. package/dist/resolution/multi-kit-resolver.d.ts +43 -0
  264. package/dist/resolution/multi-kit-resolver.d.ts.map +1 -0
  265. package/dist/resolution/multi-kit-resolver.js +258 -0
  266. package/dist/resolution/multi-kit-resolver.js.map +1 -0
  267. package/dist/resolution/primitive-paths.d.ts +17 -0
  268. package/dist/resolution/primitive-paths.d.ts.map +1 -0
  269. package/dist/resolution/primitive-paths.js +59 -0
  270. package/dist/resolution/primitive-paths.js.map +1 -0
  271. package/dist/resolution/primitives-registry.d.ts +137 -0
  272. package/dist/resolution/primitives-registry.d.ts.map +1 -0
  273. package/dist/resolution/primitives-registry.js +295 -0
  274. package/dist/resolution/primitives-registry.js.map +1 -0
  275. package/dist/resolution/reference-parser.d.ts +62 -0
  276. package/dist/resolution/reference-parser.d.ts.map +1 -0
  277. package/dist/resolution/reference-parser.js +182 -0
  278. package/dist/resolution/reference-parser.js.map +1 -0
  279. package/dist/resolution/types.d.ts +77 -0
  280. package/dist/resolution/types.d.ts.map +1 -0
  281. package/dist/resolution/types.js +13 -0
  282. package/dist/resolution/types.js.map +1 -0
  283. package/dist/resolution/version-resolver.d.ts +76 -0
  284. package/dist/resolution/version-resolver.d.ts.map +1 -0
  285. package/dist/resolution/version-resolver.js +269 -0
  286. package/dist/resolution/version-resolver.js.map +1 -0
  287. package/dist/tui/compatibility.d.ts +80 -0
  288. package/dist/tui/compatibility.d.ts.map +1 -0
  289. package/dist/tui/compatibility.js +355 -0
  290. package/dist/tui/compatibility.js.map +1 -0
  291. package/dist/tui/env-prompt.d.ts +129 -0
  292. package/dist/tui/env-prompt.d.ts.map +1 -0
  293. package/dist/tui/env-prompt.js +488 -0
  294. package/dist/tui/env-prompt.js.map +1 -0
  295. package/dist/tui/harness-select.d.ts +54 -0
  296. package/dist/tui/harness-select.d.ts.map +1 -0
  297. package/dist/tui/harness-select.js +171 -0
  298. package/dist/tui/harness-select.js.map +1 -0
  299. package/dist/tui/index.d.ts +112 -0
  300. package/dist/tui/index.d.ts.map +1 -0
  301. package/dist/tui/index.js +213 -0
  302. package/dist/tui/index.js.map +1 -0
  303. package/dist/tui/kit-select.d.ts +72 -0
  304. package/dist/tui/kit-select.d.ts.map +1 -0
  305. package/dist/tui/kit-select.js +209 -0
  306. package/dist/tui/kit-select.js.map +1 -0
  307. package/dist/tui/progress.d.ts +75 -0
  308. package/dist/tui/progress.d.ts.map +1 -0
  309. package/dist/tui/progress.js +267 -0
  310. package/dist/tui/progress.js.map +1 -0
  311. package/dist/tui/resolution.d.ts +62 -0
  312. package/dist/tui/resolution.d.ts.map +1 -0
  313. package/dist/tui/resolution.js +261 -0
  314. package/dist/tui/resolution.js.map +1 -0
  315. package/dist/tui/scope-compatibility.d.ts +139 -0
  316. package/dist/tui/scope-compatibility.d.ts.map +1 -0
  317. package/dist/tui/scope-compatibility.js +230 -0
  318. package/dist/tui/scope-compatibility.js.map +1 -0
  319. package/dist/tui/scope-select.d.ts +67 -0
  320. package/dist/tui/scope-select.d.ts.map +1 -0
  321. package/dist/tui/scope-select.js +134 -0
  322. package/dist/tui/scope-select.js.map +1 -0
  323. package/dist/tui/spinner.d.ts +114 -0
  324. package/dist/tui/spinner.d.ts.map +1 -0
  325. package/dist/tui/spinner.js +186 -0
  326. package/dist/tui/spinner.js.map +1 -0
  327. package/dist/tui/summary.d.ts +71 -0
  328. package/dist/tui/summary.d.ts.map +1 -0
  329. package/dist/tui/summary.js +343 -0
  330. package/dist/tui/summary.js.map +1 -0
  331. package/dist/tui/types.d.ts +234 -0
  332. package/dist/tui/types.d.ts.map +1 -0
  333. package/dist/tui/types.js +7 -0
  334. package/dist/tui/types.js.map +1 -0
  335. package/dist/tui/upgrade-select.d.ts +73 -0
  336. package/dist/tui/upgrade-select.d.ts.map +1 -0
  337. package/dist/tui/upgrade-select.js +324 -0
  338. package/dist/tui/upgrade-select.js.map +1 -0
  339. package/dist/validation/index.d.ts +13 -0
  340. package/dist/validation/index.d.ts.map +1 -0
  341. package/dist/validation/index.js +13 -0
  342. package/dist/validation/index.js.map +1 -0
  343. package/dist/validation/source.d.ts +14 -0
  344. package/dist/validation/source.d.ts.map +1 -0
  345. package/dist/validation/source.js +51 -0
  346. package/dist/validation/source.js.map +1 -0
  347. package/dist/validation/utils.d.ts +29 -0
  348. package/dist/validation/utils.d.ts.map +1 -0
  349. package/dist/validation/utils.js +89 -0
  350. package/dist/validation/utils.js.map +1 -0
  351. package/dist/validation/validate-commands.d.ts +28 -0
  352. package/dist/validation/validate-commands.d.ts.map +1 -0
  353. package/dist/validation/validate-commands.js +151 -0
  354. package/dist/validation/validate-commands.js.map +1 -0
  355. package/dist/validation/validate-hooks.d.ts +13 -0
  356. package/dist/validation/validate-hooks.d.ts.map +1 -0
  357. package/dist/validation/validate-hooks.js +272 -0
  358. package/dist/validation/validate-hooks.js.map +1 -0
  359. package/dist/validation/validate-kits.d.ts +15 -0
  360. package/dist/validation/validate-kits.d.ts.map +1 -0
  361. package/dist/validation/validate-kits.js +185 -0
  362. package/dist/validation/validate-kits.js.map +1 -0
  363. package/dist/validation/validate-mcp.d.ts +12 -0
  364. package/dist/validation/validate-mcp.d.ts.map +1 -0
  365. package/dist/validation/validate-mcp.js +132 -0
  366. package/dist/validation/validate-mcp.js.map +1 -0
  367. package/dist/validation/validate-skills.d.ts +24 -0
  368. package/dist/validation/validate-skills.d.ts.map +1 -0
  369. package/dist/validation/validate-skills.js +223 -0
  370. package/dist/validation/validate-skills.js.map +1 -0
  371. package/dist/validation/validate-subagents.d.ts +27 -0
  372. package/dist/validation/validate-subagents.d.ts.map +1 -0
  373. package/dist/validation/validate-subagents.js +269 -0
  374. package/dist/validation/validate-subagents.js.map +1 -0
  375. package/package.json +91 -0
  376. package/schemas/command.schema.json +23 -0
  377. package/schemas/examples/hook-binding-valid.json +20 -0
  378. package/schemas/examples/hook-program-valid.json +25 -0
  379. package/schemas/examples/http-server-valid.json +82 -0
  380. package/schemas/examples/invalid-sensitive-header-no-envvar.json +16 -0
  381. package/schemas/examples/invalid-sensitive-header-with-value.json +17 -0
  382. package/schemas/examples/invalid-sensitive-var-with-default.json +19 -0
  383. package/schemas/examples/stdio-server-valid.json +55 -0
  384. package/schemas/hook-binding.schema.json +63 -0
  385. package/schemas/hook-program.schema.json +104 -0
  386. package/schemas/kit.schema.json +338 -0
  387. package/schemas/kits.schema.json +117 -0
  388. package/schemas/manifest.schema.json +200 -0
  389. package/schemas/mcp-server.schema.json +305 -0
  390. package/schemas/primitives.schema.json +118 -0
  391. package/schemas/skill.schema.json +96 -0
  392. package/schemas/subagent.schema.json +107 -0
@@ -0,0 +1,1624 @@
1
+ /**
2
+ * Install command implementation.
3
+ *
4
+ * Handles fetching sources, discovering kits, and installing to harnesses.
5
+ * Uses the TUI layer for interactive user interaction.
6
+ */
7
+ import { ExitCode } from "./types.js";
8
+ import * as clack from "@clack/prompts";
9
+ import path from "node:path";
10
+ import pc from "picocolors";
11
+ import { fetchSource, scanKits, filterKitsByName as filterDiscoveryKitsByName, getMissingKitNames as getDiscoveryMissingKitNames, NoFetcherError, SourceParseError, } from "../discovery/index.js";
12
+ import { resolvePrimitiveReferences, PrimitivesRegistryLoader, validateCliEnvFlags, mergeEnvDefs, resolveEnvVarsFromConfig, } from "../resolution/index.js";
13
+ import { hashMcpConfig } from "../core/mcp-instance.js";
14
+ import { hashHookConfig } from "../core/hook-instance.js";
15
+ import { readManifest } from "../manifest/read.js";
16
+ import { writeManifest } from "../manifest/write.js";
17
+ import { createEmptyManifest, } from "../manifest/types.js";
18
+ import { getAdapterRegistry } from "../adapters/index.js";
19
+ import { debugLog, enableDebugLogging, getDebugLogPath, checkDebugLogWritable, } from "../core/debug.js";
20
+ import { expandPath } from "../adapters/file-operations.js";
21
+ import {
22
+ // TUI components
23
+ intro, outroSuccess, outroError, outroCancel, logInfo, logError, confirmInstallPlan, displayInstallPlanSummary,
24
+ // Spinner utilities
25
+ startSpinner, stopSpinnerSuccess, stopSpinnerError, formatSource, formatCount, formatKit,
26
+ // Harness selection
27
+ selectHarnesses, warnMissingHarnesses, displayNoHarnessesDetected, filterHarnessesByName, getMissingHarnesses,
28
+ // Kit selection
29
+ selectKits, displayAvailableKits, displayKitsJson, displayNoKitsFound, displayMissingKits, filterKitsByName,
30
+ // Compatibility
31
+ runCompatibilityCheck, buildCompatibilityMatrix, hasIncompatibilities,
32
+ // Scope selection
33
+ selectScope,
34
+ // Summary
35
+ displayDryRunSummary,
36
+ // Env prompts (Phase 6)
37
+ promptForEnvVars, promptForSharedMcpInstances, } from "../tui/index.js";
38
+ /**
39
+ * Detect installed harnesses.
40
+ */
41
+ async function detectAllHarnesses() {
42
+ const registry = getAdapterRegistry();
43
+ const adapters = registry.list();
44
+ const results = [];
45
+ for (const adapter of adapters) {
46
+ const detection = await adapter.detect();
47
+ const info = {
48
+ name: adapter.name,
49
+ displayName: adapter.displayName,
50
+ detected: detection.detected,
51
+ supportedPrimitives: adapter.getSupportedPrimitives(),
52
+ adapter,
53
+ };
54
+ if (detection.version) {
55
+ info.version = detection.version;
56
+ }
57
+ if (detection.configPath) {
58
+ info.configPath = detection.configPath;
59
+ }
60
+ results.push(info);
61
+ }
62
+ return results;
63
+ }
64
+ /**
65
+ * Run the install command.
66
+ */
67
+ /**
68
+ * Determine scope from CLI options.
69
+ * Returns null if scope needs to be determined via TUI.
70
+ */
71
+ function getScopeFromOptions(options) {
72
+ if (options.global) {
73
+ return "global";
74
+ }
75
+ if (options.project) {
76
+ return "project";
77
+ }
78
+ return null;
79
+ }
80
+ /**
81
+ * Run the install command.
82
+ */
83
+ export async function runInstall(source, options) {
84
+ const isInteractive = !options.yes && !options.json;
85
+ let fetchResult;
86
+ enableDebugLogging(options.debug ?? false);
87
+ if (options.debug) {
88
+ const debugCheck = checkDebugLogWritable();
89
+ if (!debugCheck.ok) {
90
+ console.error(`Warning: Debug logging enabled but cannot write to ${debugCheck.path}: ${debugCheck.error}`);
91
+ }
92
+ void debugLog({
93
+ level: "info",
94
+ event: "debug.enabled",
95
+ message: "Debug logging enabled",
96
+ data: { logPath: getDebugLogPath() },
97
+ });
98
+ }
99
+ void debugLog({
100
+ level: "info",
101
+ event: "install.start",
102
+ data: {
103
+ source,
104
+ options: {
105
+ agent: options.agent,
106
+ kit: options.kit,
107
+ list: options.list ?? false,
108
+ yes: options.yes ?? false,
109
+ dryRun: options.dryRun ?? false,
110
+ json: options.json ?? false,
111
+ verbose: options.verbose ?? false,
112
+ global: options.global ?? false,
113
+ project: options.project ?? false,
114
+ envKeys: options.env ? Object.keys(options.env) : [],
115
+ mcpInstanceKits: options.mcpInstance ? Object.keys(options.mcpInstance) : [],
116
+ },
117
+ },
118
+ });
119
+ // Parse scope from options
120
+ let scope = getScopeFromOptions(options);
121
+ const projectRoot = process.cwd();
122
+ // In non-interactive mode, scope must be specified
123
+ // Exception: --list flag doesn't require scope
124
+ if (!isInteractive && !scope && !options.list) {
125
+ const message = "Scope is required in non-interactive mode. Use --global (-g) or --project (-p).";
126
+ if (options.json) {
127
+ console.log(JSON.stringify({ success: false, error: message }));
128
+ }
129
+ else {
130
+ console.error(`Error: ${message}`);
131
+ }
132
+ return { success: false, exitCode: ExitCode.ScopeRequired, error: message };
133
+ }
134
+ try {
135
+ // Start interactive UI if applicable
136
+ if (isInteractive) {
137
+ intro("@hashicorp/kits");
138
+ }
139
+ // Fetch source
140
+ const fetchSpinner = isInteractive
141
+ ? startSpinner(`Fetching ${formatSource(source)}...`)
142
+ : undefined;
143
+ if (!fetchSpinner && options.verbose) {
144
+ console.error(`Fetching ${source}...`);
145
+ }
146
+ try {
147
+ fetchResult = await fetchSource(source);
148
+ if (fetchSpinner) {
149
+ stopSpinnerSuccess(fetchSpinner, `Fetched ${formatSource(source)}`);
150
+ }
151
+ else if (options.verbose) {
152
+ console.error(`Fetched to ${fetchResult.localPath}`);
153
+ }
154
+ void debugLog({
155
+ level: "info",
156
+ event: "install.fetch.success",
157
+ data: { localPath: fetchResult.localPath },
158
+ });
159
+ }
160
+ catch (error) {
161
+ if (fetchSpinner) {
162
+ stopSpinnerError(fetchSpinner, "Failed to fetch source");
163
+ }
164
+ if (error instanceof NoFetcherError || error instanceof SourceParseError) {
165
+ const message = error.message;
166
+ if (isInteractive) {
167
+ logError(message);
168
+ outroError("Installation failed");
169
+ }
170
+ else if (options.json) {
171
+ console.log(JSON.stringify({ success: false, error: message }));
172
+ }
173
+ else {
174
+ console.error(`Error: ${message}`);
175
+ }
176
+ return { success: false, exitCode: ExitCode.SourceNotFound, error: message };
177
+ }
178
+ throw error;
179
+ }
180
+ // Scan for kits
181
+ const scanSpinner = isInteractive ? startSpinner("Scanning for kits...") : undefined;
182
+ let discovery = await scanKits(fetchResult.localPath, source);
183
+ if (scanSpinner) {
184
+ const kitCount = discovery.kits.length;
185
+ stopSpinnerSuccess(scanSpinner, `Found ${formatCount(kitCount, "kit")}`);
186
+ }
187
+ void debugLog({
188
+ level: "info",
189
+ event: "install.scan.success",
190
+ data: { kits: discovery.kits.map((kit) => kit.name) },
191
+ });
192
+ // Filter by requested kit names
193
+ if (options.kit && options.kit.length > 0) {
194
+ const missing = getDiscoveryMissingKitNames(discovery, options.kit);
195
+ if (missing.length > 0) {
196
+ const message = `Kit${missing.length === 1 ? "" : "s"} not found: ${missing.join(", ")}`;
197
+ if (isInteractive) {
198
+ displayMissingKits(missing, discovery.kits);
199
+ outroError("Installation failed");
200
+ }
201
+ else if (options.json) {
202
+ console.log(JSON.stringify({ success: false, error: message }));
203
+ }
204
+ else {
205
+ console.error(`Error: ${message}`);
206
+ console.error("Available kits: " + discovery.kits.map((k) => k.name).join(", "));
207
+ }
208
+ return { success: false, exitCode: ExitCode.KitNotFound, error: message };
209
+ }
210
+ discovery = filterDiscoveryKitsByName(discovery, options.kit);
211
+ }
212
+ // Handle --list flag
213
+ if (options.list) {
214
+ if (options.json) {
215
+ displayKitsJson(discovery.kits, discovery.source, discovery.repository);
216
+ }
217
+ else {
218
+ displayAvailableKits(discovery.kits);
219
+ }
220
+ return { success: true, exitCode: ExitCode.Success };
221
+ }
222
+ // Check if any kits were found
223
+ if (discovery.kits.length === 0) {
224
+ const message = "No kits found in repository";
225
+ if (isInteractive) {
226
+ displayNoKitsFound();
227
+ outroError("Installation failed");
228
+ }
229
+ else if (options.json) {
230
+ console.log(JSON.stringify({ success: false, error: message }));
231
+ }
232
+ else {
233
+ console.error(`Error: ${message}`);
234
+ }
235
+ return { success: false, exitCode: ExitCode.KitNotFound, error: message };
236
+ }
237
+ // Interactive kit selection (stage 1)
238
+ let selectedKits = discovery.kits;
239
+ if (isInteractive && !options.kit) {
240
+ const result = await selectKits(discovery.kits, {
241
+ showAvailable: false,
242
+ });
243
+ if (result.cancelled) {
244
+ outroCancel("Installation cancelled");
245
+ return { success: false, exitCode: ExitCode.InstallationFailed };
246
+ }
247
+ selectedKits = filterKitsByName(discovery.kits, result.selected);
248
+ }
249
+ void debugLog({
250
+ level: "info",
251
+ event: "install.kits.selected",
252
+ data: { kits: selectedKits.map((kit) => kit.name) },
253
+ });
254
+ // Detect harnesses
255
+ const harnessSpinner = isInteractive ? startSpinner("Detecting AI harnesses...") : undefined;
256
+ const allHarnesses = await detectAllHarnesses();
257
+ const detectedHarnesses = allHarnesses.filter((h) => h.detected);
258
+ if (harnessSpinner) {
259
+ const count = detectedHarnesses.length;
260
+ const message = isInteractive && !options.agent
261
+ ? "Detected harnesses"
262
+ : `Found ${formatCount(count, "harness", "harnesses")}`;
263
+ stopSpinnerSuccess(harnessSpinner, message);
264
+ }
265
+ void debugLog({
266
+ level: "info",
267
+ event: "install.harnesses.detected",
268
+ data: { harnesses: detectedHarnesses.map((h) => h.name) },
269
+ });
270
+ // Filter by requested harnesses
271
+ let targetHarnesses = detectedHarnesses;
272
+ if (options.agent && options.agent.length > 0) {
273
+ const missing = getMissingHarnesses(detectedHarnesses, options.agent);
274
+ if (missing.length > 0) {
275
+ if (isInteractive) {
276
+ warnMissingHarnesses(missing);
277
+ }
278
+ else if (options.verbose) {
279
+ const label = missing.length === 1 ? "Harness" : "Harnesses";
280
+ console.error(`Warning: ${label} not detected: ${missing.join(", ")}`);
281
+ }
282
+ }
283
+ targetHarnesses = filterHarnessesByName(detectedHarnesses, options.agent);
284
+ }
285
+ // Check if any harnesses are available
286
+ if (targetHarnesses.length === 0) {
287
+ const message = "No compatible AI harnesses detected";
288
+ if (isInteractive) {
289
+ displayNoHarnessesDetected();
290
+ outroError("Installation failed");
291
+ }
292
+ else if (options.json) {
293
+ console.log(JSON.stringify({ success: false, error: message }));
294
+ }
295
+ else {
296
+ console.error(`Error: ${message}`);
297
+ }
298
+ return { success: false, exitCode: ExitCode.NoHarnessesDetected, error: message };
299
+ }
300
+ // Interactive harness selection
301
+ let selectedHarnesses = targetHarnesses;
302
+ if (isInteractive && !options.agent) {
303
+ const result = await selectHarnesses(detectedHarnesses, {
304
+ showDetected: true,
305
+ includeAllOption: true,
306
+ });
307
+ if (result.cancelled) {
308
+ outroCancel("Installation cancelled");
309
+ return { success: false, exitCode: ExitCode.InstallationFailed };
310
+ }
311
+ selectedHarnesses = filterHarnessesByName(detectedHarnesses, result.selected);
312
+ }
313
+ void debugLog({
314
+ level: "info",
315
+ event: "install.harnesses.selected",
316
+ data: { harnesses: selectedHarnesses.map((h) => h.name) },
317
+ });
318
+ // Interactive scope selection
319
+ let effectiveScope;
320
+ if (scope) {
321
+ // Scope specified via CLI flag
322
+ effectiveScope = scope;
323
+ }
324
+ else if (isInteractive) {
325
+ // Prompt for scope selection
326
+ const scopeResult = await selectScope({ showInfo: true });
327
+ if (scopeResult.cancelled) {
328
+ outroCancel("Installation cancelled");
329
+ return { success: false, exitCode: ExitCode.InstallationFailed };
330
+ }
331
+ effectiveScope = scopeResult.scope;
332
+ }
333
+ else {
334
+ // Default to global scope in non-interactive mode without flag
335
+ // (This shouldn't happen due to earlier check, but handle gracefully)
336
+ effectiveScope = "global";
337
+ }
338
+ void debugLog({
339
+ level: "info",
340
+ event: "install.scope.selected",
341
+ data: { scope: effectiveScope, projectRoot },
342
+ });
343
+ // Compatibility check (after scope selection)
344
+ const compatibilityHarnesses = selectedHarnesses.map((h) => ({
345
+ ...h,
346
+ supportedPrimitives: h.adapter.getSupportedPrimitives(effectiveScope),
347
+ }));
348
+ let matrices = buildCompatibilityMatrix(selectedKits, compatibilityHarnesses);
349
+ let compatResult = {
350
+ cancelled: false,
351
+ choice: "proceed",
352
+ };
353
+ let compatiblePairs = [];
354
+ let allowCompatibleOnly = false;
355
+ if (isInteractive) {
356
+ const compat = await runCompatibilityCheck(selectedKits, compatibilityHarnesses, effectiveScope, {
357
+ showMatrix: options.verbose ?? false,
358
+ promptOnIncompatible: true,
359
+ });
360
+ matrices = compat.matrices;
361
+ compatResult = compat.result;
362
+ compatiblePairs = compat.compatiblePairs;
363
+ allowCompatibleOnly = compat.result.choice === "install-compatible";
364
+ }
365
+ if (hasIncompatibilities(matrices)) {
366
+ void debugLog({
367
+ level: "warn",
368
+ event: "install.compatibility.failed",
369
+ data: { scope: effectiveScope },
370
+ });
371
+ if (isInteractive) {
372
+ if (compatResult.cancelled || compatResult.choice === "cancel") {
373
+ outroCancel("Installation cancelled");
374
+ return { success: false, exitCode: ExitCode.InstallationFailed };
375
+ }
376
+ if (compatResult.choice === "go-back") {
377
+ outroCancel("Installation cancelled - please run again with different selections");
378
+ return { success: false, exitCode: ExitCode.InstallationFailed };
379
+ }
380
+ if (compatResult.choice === "install-compatible") {
381
+ void debugLog({
382
+ level: "info",
383
+ event: "install.compatibility.proceed",
384
+ data: { mode: "compatible-only", pairs: compatiblePairs.length },
385
+ });
386
+ }
387
+ }
388
+ if (!isInteractive || !allowCompatibleOnly) {
389
+ const message = "No compatible kit-harness combinations for the selected scope";
390
+ if (options.json) {
391
+ console.log(JSON.stringify({ success: false, error: message }));
392
+ }
393
+ else if (!isInteractive) {
394
+ console.error(`Error: ${message}`);
395
+ }
396
+ return { success: false, exitCode: ExitCode.KitIncompatible, error: message };
397
+ }
398
+ }
399
+ void debugLog({
400
+ level: "info",
401
+ event: "install.compatibility.ok",
402
+ data: { scope: effectiveScope },
403
+ });
404
+ // Installation summary (interactive only)
405
+ if (isInteractive && !options.dryRun) {
406
+ const planKitNames = allowCompatibleOnly
407
+ ? new Set(compatiblePairs.map((pair) => pair.kit))
408
+ : new Set(selectedKits.map((kit) => kit.name));
409
+ const planHarnessNames = allowCompatibleOnly
410
+ ? new Set(compatiblePairs.map((pair) => pair.harness))
411
+ : new Set(selectedHarnesses.map((h) => h.name));
412
+ const planKits = selectedKits.filter((kit) => planKitNames.has(kit.name));
413
+ const planHarnesses = selectedHarnesses.filter((h) => planHarnessNames.has(h.name));
414
+ displayInstallPlanSummary(planKits.map((kit) => ({ name: kit.name, version: kit.manifest.version })), planHarnesses.map((h) => ({ displayName: h.displayName })), effectiveScope);
415
+ const proceed = await confirmInstallPlan();
416
+ if (proceed.cancelled || !proceed.proceed) {
417
+ outroCancel("Installation cancelled");
418
+ return { success: false, exitCode: ExitCode.InstallationFailed };
419
+ }
420
+ }
421
+ // Dry run - show what would be installed
422
+ if (options.dryRun) {
423
+ if (options.json) {
424
+ const output = {
425
+ success: true,
426
+ source,
427
+ scope: effectiveScope,
428
+ harnesses: selectedHarnesses.map((h) => ({
429
+ name: h.name,
430
+ configPath: h.configPath || "",
431
+ kits: selectedKits.map((kit) => ({
432
+ name: kit.name,
433
+ version: kit.manifest.version,
434
+ installed: [],
435
+ })),
436
+ })),
437
+ skipped: [],
438
+ };
439
+ if (effectiveScope === "project") {
440
+ output.projectRoot = projectRoot;
441
+ }
442
+ console.log(JSON.stringify(output, null, 2));
443
+ }
444
+ else {
445
+ displayDryRunSummary(selectedKits.map((k) => ({ name: k.name, version: k.manifest.version })), selectedHarnesses.map((h) => ({ name: h.name, displayName: h.displayName })), { scope: effectiveScope, projectRoot });
446
+ }
447
+ return { success: true, exitCode: ExitCode.Success };
448
+ }
449
+ // Execute installation
450
+ const installResults = await executeInstallation(selectedKits, selectedHarnesses, discovery, source, {
451
+ isInteractive,
452
+ verbose: options.verbose ?? false,
453
+ json: options.json ?? false,
454
+ scope: effectiveScope,
455
+ projectRoot,
456
+ env: options.env ?? {},
457
+ mcpInstance: options.mcpInstance ?? {},
458
+ allowCompatibleOnly,
459
+ compatiblePairs: allowCompatibleOnly ? compatiblePairs : [],
460
+ });
461
+ if (!installResults.success) {
462
+ const errorMsg = installResults.error || "Installation failed";
463
+ const exitCode = installResults.exitCode ?? ExitCode.InstallationFailed;
464
+ void debugLog({
465
+ level: "error",
466
+ event: "install.failed",
467
+ message: errorMsg,
468
+ });
469
+ if (isInteractive) {
470
+ outroError(errorMsg);
471
+ }
472
+ else if (options.json) {
473
+ console.log(JSON.stringify({ success: false, error: errorMsg }));
474
+ }
475
+ else {
476
+ console.error(`Error: ${errorMsg}`);
477
+ }
478
+ return { success: false, exitCode, error: errorMsg };
479
+ }
480
+ // Success output
481
+ void debugLog({
482
+ level: "info",
483
+ event: "install.success",
484
+ data: {
485
+ scope: effectiveScope,
486
+ harnesses: selectedHarnesses.map((h) => h.name),
487
+ kits: selectedKits.map((k) => k.name),
488
+ },
489
+ });
490
+ if (options.json) {
491
+ console.log(JSON.stringify(installResults.output, null, 2));
492
+ }
493
+ else if (isInteractive) {
494
+ outroSuccess("Installation complete!");
495
+ }
496
+ const result = { success: true, exitCode: ExitCode.Success };
497
+ if (installResults.output) {
498
+ result.output = installResults.output;
499
+ }
500
+ return result;
501
+ }
502
+ catch (error) {
503
+ const message = error instanceof Error ? error.message : String(error);
504
+ if (isInteractive) {
505
+ logError(message);
506
+ outroError("Installation failed");
507
+ }
508
+ else if (options.json) {
509
+ console.log(JSON.stringify({ success: false, error: message }));
510
+ }
511
+ else {
512
+ console.error(`Error: ${message}`);
513
+ }
514
+ return { success: false, exitCode: ExitCode.InstallationFailed, error: message };
515
+ }
516
+ finally {
517
+ // Clean up fetched source
518
+ if (fetchResult?.cleanup) {
519
+ try {
520
+ await fetchResult.cleanup();
521
+ }
522
+ catch {
523
+ // Ignore cleanup errors
524
+ }
525
+ }
526
+ }
527
+ }
528
+ /**
529
+ * Load MCP server config from a resolved primitive's source path.
530
+ *
531
+ * @param sourcePath - Path to the MCP primitive directory
532
+ * @returns MCP server config or null if not found/invalid
533
+ */
534
+ async function loadMcpConfigFromPath(sourcePath) {
535
+ const fs = await import("node:fs/promises");
536
+ // Try config.json in the primitive directory
537
+ const configPath = path.join(sourcePath, "config.json");
538
+ try {
539
+ const content = await fs.readFile(configPath, "utf-8");
540
+ return JSON.parse(content);
541
+ }
542
+ catch {
543
+ // If config.json doesn't exist, the sourcePath might BE the config file
544
+ // (for inline primitives that point directly to a config.json)
545
+ if (sourcePath.endsWith(".json")) {
546
+ try {
547
+ const content = await fs.readFile(sourcePath, "utf-8");
548
+ return JSON.parse(content);
549
+ }
550
+ catch {
551
+ return null;
552
+ }
553
+ }
554
+ return null;
555
+ }
556
+ }
557
+ /**
558
+ * Collect all environment variable definitions from MCP primitives.
559
+ *
560
+ * @param primitives - Resolved primitives (from multiple kits)
561
+ * @returns Merged record of all env var definitions
562
+ */
563
+ async function collectAllMcpEnvDefs(primitives) {
564
+ const configs = [];
565
+ for (const primitive of primitives) {
566
+ if (primitive.type !== "mcp")
567
+ continue;
568
+ const config = await loadMcpConfigFromPath(primitive.sourcePath);
569
+ if (config) {
570
+ configs.push(config);
571
+ }
572
+ }
573
+ return mergeEnvDefs(configs);
574
+ }
575
+ /**
576
+ * Resolve environment variables for all MCP primitives.
577
+ *
578
+ * This function iterates through all MCP primitives and resolves their
579
+ * environment variable definitions to values using the resolution priority:
580
+ * 1. Sensitive vars: Always passthrough
581
+ * 2. CLI --env flags: For nonsensitive vars only
582
+ * 3. process.env: Current environment value
583
+ * 4. Default value: If specified
584
+ * 5. Passthrough: If no value resolved
585
+ *
586
+ * @param primitives - Resolved primitives (from multiple kits)
587
+ * @param cliEnvFlags - CLI --env flag values
588
+ * @returns Array of env var resolution results for each MCP server
589
+ */
590
+ async function resolveMcpEnvVars(primitives, cliEnvFlags, instanceOverrides) {
591
+ const resolutions = [];
592
+ const seenInstances = new Set();
593
+ for (const primitive of primitives) {
594
+ if (primitive.type !== "mcp")
595
+ continue;
596
+ const config = await loadMcpConfigFromPath(primitive.sourcePath);
597
+ if (!config)
598
+ continue;
599
+ const instanceName = resolveInstanceName(config.name, primitive.instanceName, instanceOverrides, primitive.kitName);
600
+ if (!instanceName) {
601
+ continue;
602
+ }
603
+ if (seenInstances.has(instanceName)) {
604
+ continue;
605
+ }
606
+ seenInstances.add(instanceName);
607
+ const resolution = resolveEnvVarsFromConfig(instanceName, config, cliEnvFlags);
608
+ resolutions.push(resolution);
609
+ }
610
+ return resolutions;
611
+ }
612
+ /**
613
+ * Build TUI env var requirements from resolution results.
614
+ *
615
+ * Converts the resolution results into the format expected by the TUI
616
+ * prompting system, including sensitivity classification.
617
+ *
618
+ * @param resolutions - Env var resolution results from resolveMcpEnvVars
619
+ * @returns Array of env var requirements for TUI prompting
620
+ */
621
+ function buildEnvVarRequirements(resolutions) {
622
+ const requirements = [];
623
+ const seen = new Set();
624
+ for (const resolution of resolutions) {
625
+ for (const resolved of resolution.resolved) {
626
+ // Deduplicate by name (same var may be used by multiple MCP servers)
627
+ if (seen.has(resolved.name))
628
+ continue;
629
+ seen.add(resolved.name);
630
+ requirements.push({
631
+ name: resolved.name,
632
+ description: resolved.description,
633
+ required: resolved.required,
634
+ sensitive: resolved.sensitive,
635
+ defaultValue: resolved.defaultValue,
636
+ currentValue: process.env[resolved.name],
637
+ source: resolution.mcpServer,
638
+ });
639
+ }
640
+ }
641
+ return requirements;
642
+ }
643
+ /**
644
+ * Apply TUI prompt results back to resolution results.
645
+ *
646
+ * Updates the resolution results with values provided by the user through
647
+ * the TUI prompting flow. Only updates nonsensitive vars.
648
+ *
649
+ * @param resolutions - Original resolution results to update
650
+ * @param promptResults - Results from TUI prompting
651
+ */
652
+ function applyPromptResultsToResolutions(resolutions, promptResults) {
653
+ for (const result of promptResults.variables) {
654
+ // Update all resolutions that have this var
655
+ for (const resolution of resolutions) {
656
+ const resolved = resolution.resolved.find((r) => r.name === result.name);
657
+ if (!resolved)
658
+ continue;
659
+ switch (result.choice) {
660
+ case "use-existing": {
661
+ if ("value" in resolved) {
662
+ delete resolved.value;
663
+ }
664
+ resolved.usePassthrough = true;
665
+ resolved.source = "environment";
666
+ break;
667
+ }
668
+ case "use-default": {
669
+ if (result.value !== undefined) {
670
+ resolved.value = result.value;
671
+ }
672
+ else if ("value" in resolved) {
673
+ delete resolved.value;
674
+ }
675
+ resolved.usePassthrough = false;
676
+ resolved.source = "default";
677
+ break;
678
+ }
679
+ case "enter-new": {
680
+ if (result.value !== undefined) {
681
+ resolved.value = result.value;
682
+ }
683
+ else if ("value" in resolved) {
684
+ delete resolved.value;
685
+ }
686
+ resolved.usePassthrough = false;
687
+ resolved.source = "user-input";
688
+ break;
689
+ }
690
+ case "skip":
691
+ default: {
692
+ if ("value" in resolved) {
693
+ delete resolved.value;
694
+ }
695
+ resolved.usePassthrough = true;
696
+ resolved.source = "passthrough";
697
+ break;
698
+ }
699
+ }
700
+ // Remove from missingRequired if now satisfied or passthrough selected
701
+ const idx = resolution.missingRequired.indexOf(result.name);
702
+ if (idx !== -1) {
703
+ resolution.missingRequired.splice(idx, 1);
704
+ }
705
+ }
706
+ }
707
+ }
708
+ function resolveEnvForConfig(config, cliEnvFlags, promptResults, instanceNameOverride) {
709
+ const resolution = resolveEnvVarsFromConfig(instanceNameOverride ?? config.name, config, cliEnvFlags);
710
+ if (promptResults) {
711
+ applyPromptResultsToResolutions([resolution], promptResults);
712
+ }
713
+ return resolution;
714
+ }
715
+ function resolveInstanceName(configName, primitiveOverride, cliOverrides, kitName) {
716
+ const baseName = primitiveOverride ?? configName;
717
+ if (cliOverrides && kitName && cliOverrides[kitName]?.[baseName]) {
718
+ return cliOverrides[kitName][baseName];
719
+ }
720
+ return baseName;
721
+ }
722
+ function resolveHookInstanceName(programName, primitiveOverride, kitName) {
723
+ if (primitiveOverride) {
724
+ return primitiveOverride;
725
+ }
726
+ if (kitName) {
727
+ return `${kitName}.${programName}`;
728
+ }
729
+ return programName;
730
+ }
731
+ function buildPrimitiveKey(kitName, sourcePath) {
732
+ return `${kitName}::${sourcePath}`;
733
+ }
734
+ async function buildMcpPrimitiveInfoBySourcePath(resolvedByKit, cliEnvFlags, promptResults, instanceOverrides) {
735
+ const infoBySourcePath = new Map();
736
+ for (const [kitName, primitives] of resolvedByKit.entries()) {
737
+ for (const primitive of primitives) {
738
+ if (primitive.type !== "mcp")
739
+ continue;
740
+ const key = buildPrimitiveKey(kitName, primitive.sourcePath);
741
+ if (infoBySourcePath.has(key))
742
+ continue;
743
+ const config = await loadMcpConfigFromPath(primitive.sourcePath);
744
+ if (!config?.name)
745
+ continue;
746
+ const instanceName = resolveInstanceName(config.name, primitive.instanceName, instanceOverrides, kitName);
747
+ const resolution = resolveEnvForConfig(config, cliEnvFlags, promptResults, instanceName);
748
+ const configHash = hashMcpConfig(config, resolution.resolved, resolution.resolvedHeaders);
749
+ infoBySourcePath.set(key, {
750
+ instanceName,
751
+ configHash,
752
+ config,
753
+ resolvedEnv: resolution.resolved,
754
+ resolvedHeaders: resolution.resolvedHeaders,
755
+ primitiveName: primitive.name,
756
+ version: primitive.resolvedVersion ?? config.version,
757
+ configName: config.name,
758
+ });
759
+ }
760
+ }
761
+ return infoBySourcePath;
762
+ }
763
+ async function buildHookPrimitiveInfoBySourcePath(resolvedByKit) {
764
+ const infoBySourcePath = new Map();
765
+ for (const [kitName, primitives] of resolvedByKit.entries()) {
766
+ for (const primitive of primitives) {
767
+ if (primitive.type !== "hooks")
768
+ continue;
769
+ if (!primitive.hookProgram || !primitive.hookBinding)
770
+ continue;
771
+ const key = buildPrimitiveKey(kitName, primitive.sourcePath);
772
+ if (infoBySourcePath.has(key))
773
+ continue;
774
+ const instanceName = resolveHookInstanceName(primitive.hookProgram.name ?? primitive.name, primitive.instanceName, kitName);
775
+ const configHash = hashHookConfig(primitive.hookProgram, primitive.hookBinding);
776
+ infoBySourcePath.set(key, {
777
+ instanceName,
778
+ configHash,
779
+ program: primitive.hookProgram,
780
+ binding: primitive.hookBinding,
781
+ primitiveName: primitive.name,
782
+ version: primitive.resolvedVersion ?? primitive.hookProgram.version,
783
+ });
784
+ }
785
+ }
786
+ return infoBySourcePath;
787
+ }
788
+ async function buildMcpUsageByInstance(resolvedByKit, instanceOverrides) {
789
+ const usage = new Map();
790
+ for (const [kitName, primitives] of resolvedByKit.entries()) {
791
+ for (const primitive of primitives) {
792
+ if (primitive.type !== "mcp")
793
+ continue;
794
+ const config = await loadMcpConfigFromPath(primitive.sourcePath);
795
+ if (!config?.name)
796
+ continue;
797
+ const instanceName = resolveInstanceName(config.name, primitive.instanceName, instanceOverrides, kitName);
798
+ const existing = usage.get(instanceName);
799
+ if (existing) {
800
+ existing.add(kitName);
801
+ }
802
+ else {
803
+ usage.set(instanceName, new Set([kitName]));
804
+ }
805
+ }
806
+ }
807
+ return usage;
808
+ }
809
+ function getMcpConfigPath(adapter, scope, projectRoot) {
810
+ const configLocations = adapter.getConfigLocations(scope);
811
+ const mcpPath = configLocations.mcp || configLocations.primary;
812
+ if (scope === "project" && projectRoot) {
813
+ return path.join(projectRoot, mcpPath.replace(/^\.\//, ""));
814
+ }
815
+ return expandPath(mcpPath);
816
+ }
817
+ function getHookConfigPath(adapter, scope, projectRoot) {
818
+ const paths = adapter.getInstallationPaths(scope);
819
+ const hookPath = paths.hooks ?? adapter.getConfigLocations(scope).primary;
820
+ if (scope === "project" && projectRoot) {
821
+ return path.join(projectRoot, hookPath.replace(/^\.\//, ""));
822
+ }
823
+ return expandPath(hookPath);
824
+ }
825
+ async function promptMcpConflictResolution(options) {
826
+ const choice = await clack.select({
827
+ message: `MCP instance "${options.instanceName}" already exists in ${options.harnessDisplayName} with different configuration. How would you like to proceed?`,
828
+ options: [
829
+ { value: "reuse", label: "Reuse existing instance" },
830
+ { value: "fork", label: "Create a new instance name" },
831
+ { value: "cancel", label: "Cancel installation" },
832
+ ],
833
+ });
834
+ if (clack.isCancel(choice)) {
835
+ return "cancel";
836
+ }
837
+ return choice;
838
+ }
839
+ async function promptForMcpInstanceName(instanceName, usedNames) {
840
+ while (true) {
841
+ const value = await clack.text({
842
+ message: `Enter a new name for "${instanceName}":`,
843
+ placeholder: `${instanceName}-alt`,
844
+ });
845
+ if (clack.isCancel(value)) {
846
+ return null;
847
+ }
848
+ const trimmed = String(value).trim();
849
+ if (!trimmed) {
850
+ clack.log.warn("Instance name cannot be empty.");
851
+ continue;
852
+ }
853
+ if (usedNames.has(trimmed)) {
854
+ clack.log.warn(`Instance name "${trimmed}" is already in use.`);
855
+ continue;
856
+ }
857
+ return trimmed;
858
+ }
859
+ }
860
+ /**
861
+ * Execute the actual installation of kits to harnesses.
862
+ */
863
+ async function executeInstallation(kits, harnesses, discovery, source, options) {
864
+ const { isInteractive, verbose, scope, projectRoot, env, mcpInstance, allowCompatibleOnly = false, compatiblePairs = [], } = options;
865
+ // Load or create primitives registry loader
866
+ let registryLoader = null;
867
+ if (discovery.primitivesRegistry) {
868
+ registryLoader = new PrimitivesRegistryLoader(discovery.localPath);
869
+ await registryLoader.load();
870
+ }
871
+ // Pre-pass: resolve all primitives to collect MCP env var definitions for validation
872
+ const allResolvedPrimitives = [];
873
+ const resolvedByKit = new Map();
874
+ const resolutionFailures = [];
875
+ for (const kit of kits) {
876
+ if (registryLoader) {
877
+ const kitBasePath = path.join(discovery.localPath, kit.path);
878
+ const resolution = await resolvePrimitiveReferences(kit.manifest, registryLoader, kitBasePath);
879
+ if (resolution.errors.length > 0) {
880
+ resolutionFailures.push({
881
+ kit: kit.name,
882
+ errors: resolution.errors.map((e) => e.message),
883
+ });
884
+ continue;
885
+ }
886
+ resolvedByKit.set(kit.name, resolution.primitives);
887
+ allResolvedPrimitives.push(...resolution.primitives);
888
+ }
889
+ else {
890
+ const inlinePrimitives = resolveInlinePrimitivesOnly(kit, discovery.localPath);
891
+ resolvedByKit.set(kit.name, inlinePrimitives);
892
+ allResolvedPrimitives.push(...inlinePrimitives);
893
+ }
894
+ }
895
+ if (resolutionFailures.length > 0) {
896
+ const details = resolutionFailures
897
+ .map((f) => `${f.kit}: ${f.errors.join("; ")}`)
898
+ .join(" | ");
899
+ return {
900
+ success: false,
901
+ exitCode: ExitCode.ResolutionFailed,
902
+ error: `Resolution failed: ${details}`,
903
+ };
904
+ }
905
+ // Collect all MCP env var definitions for validation
906
+ const allEnvDefs = await collectAllMcpEnvDefs(allResolvedPrimitives);
907
+ // Validate --env flags against sensitive vars (exit code 13)
908
+ if (env && Object.keys(env).length > 0) {
909
+ const invalidFlags = validateCliEnvFlags(env, allEnvDefs);
910
+ if (invalidFlags.length > 0) {
911
+ const message = `Cannot provide sensitive variables via --env flag: ${invalidFlags.join(", ")}. Sensitive variables must be set in your shell environment.`;
912
+ return {
913
+ success: false,
914
+ exitCode: ExitCode.SensitiveEnvVarProvided,
915
+ error: message,
916
+ };
917
+ }
918
+ }
919
+ // In non-interactive mode, validate required nonsensitive vars are provided (exit code 12)
920
+ if (!isInteractive) {
921
+ // Check for missing required nonsensitive vars
922
+ // A var is considered "provided" if it's in --env flags, process.env, or has a default
923
+ const missingRequired = [];
924
+ for (const [name, def] of Object.entries(allEnvDefs)) {
925
+ // Skip sensitive vars (they're always passthrough, handled differently)
926
+ if (def.sensitive)
927
+ continue;
928
+ // Skip if not required
929
+ if (!def.required)
930
+ continue;
931
+ // Check if value is available from any source
932
+ const hasCliValue = env && name in env;
933
+ const hasEnvValue = !!process.env[name];
934
+ const hasDefault = def.default !== undefined;
935
+ if (!hasCliValue && !hasEnvValue && !hasDefault) {
936
+ missingRequired.push(name);
937
+ }
938
+ }
939
+ if (missingRequired.length > 0) {
940
+ const message = `Required environment variables not provided: ${missingRequired.join(", ")}. Use --env KEY=VALUE to provide values, or run interactively.`;
941
+ return {
942
+ success: false,
943
+ exitCode: ExitCode.EnvVarRequired,
944
+ error: message,
945
+ };
946
+ }
947
+ }
948
+ let effectiveMcpInstance = mcpInstance ?? {};
949
+ if (isInteractive) {
950
+ const usage = await buildMcpUsageByInstance(resolvedByKit, effectiveMcpInstance);
951
+ const sharedPrompt = await promptForSharedMcpInstances(usage, effectiveMcpInstance);
952
+ if (sharedPrompt.cancelled) {
953
+ return {
954
+ success: false,
955
+ exitCode: ExitCode.UserCancelled,
956
+ error: "Environment variable configuration cancelled",
957
+ };
958
+ }
959
+ if (Object.keys(sharedPrompt.overrides).length > 0) {
960
+ for (const [kitName, overrides] of Object.entries(sharedPrompt.overrides)) {
961
+ if (!effectiveMcpInstance[kitName]) {
962
+ effectiveMcpInstance[kitName] = {};
963
+ }
964
+ for (const [instanceName, override] of Object.entries(overrides)) {
965
+ if (!effectiveMcpInstance[kitName][instanceName]) {
966
+ effectiveMcpInstance[kitName][instanceName] = override;
967
+ }
968
+ }
969
+ }
970
+ }
971
+ }
972
+ // Resolve environment variables for all MCP primitives
973
+ const envResolutions = await resolveMcpEnvVars(allResolvedPrimitives, env, effectiveMcpInstance);
974
+ let envPromptResults;
975
+ // Interactive mode: prompt for nonsensitive vars
976
+ if (isInteractive && envResolutions.length > 0) {
977
+ const requirements = buildEnvVarRequirements(envResolutions);
978
+ if (requirements.length > 0) {
979
+ logInfo("Configuring environment variables for MCP servers...");
980
+ const sourceToKits = await buildMcpUsageByInstance(resolvedByKit, effectiveMcpInstance);
981
+ const promptResults = await promptForEnvVars(requirements, {
982
+ sourceToKits,
983
+ skipSatisfied: false,
984
+ skipOptional: false,
985
+ });
986
+ if (promptResults.cancelled) {
987
+ return {
988
+ success: false,
989
+ exitCode: ExitCode.UserCancelled,
990
+ error: "Environment variable configuration cancelled",
991
+ };
992
+ }
993
+ envPromptResults = promptResults;
994
+ applyPromptResultsToResolutions(envResolutions, promptResults);
995
+ }
996
+ }
997
+ // Read or create manifest for the specified scope
998
+ let manifest = await readManifest(scope, scope === "project" ? projectRoot : undefined);
999
+ if (!manifest) {
1000
+ manifest = createEmptyManifest(scope, scope === "project" ? projectRoot : undefined);
1001
+ }
1002
+ manifest.source = source;
1003
+ const harnessResults = [];
1004
+ const skippedResults = [];
1005
+ const compatibleKitsByHarness = new Map();
1006
+ for (const pair of compatiblePairs) {
1007
+ const existing = compatibleKitsByHarness.get(pair.harness);
1008
+ if (existing) {
1009
+ existing.add(pair.kit);
1010
+ }
1011
+ else {
1012
+ compatibleKitsByHarness.set(pair.harness, new Set([pair.kit]));
1013
+ }
1014
+ }
1015
+ const mcpInfoByPrimitiveKey = await buildMcpPrimitiveInfoBySourcePath(resolvedByKit, env, envPromptResults, effectiveMcpInstance);
1016
+ const mcpAssignmentsByHarness = new Map();
1017
+ const mcpForkedInstancesByHarness = new Map();
1018
+ const mcpInstancesToInstallByHarness = new Map();
1019
+ const hookInfoByPrimitiveKey = await buildHookPrimitiveInfoBySourcePath(resolvedByKit);
1020
+ const hookAssignmentsByHarness = new Map();
1021
+ const hookInstancesToInstallByHarness = new Map();
1022
+ for (const harness of harnesses) {
1023
+ const kitsToCheck = allowCompatibleOnly
1024
+ ? kits.filter((kit) => compatibleKitsByHarness.get(harness.name)?.has(kit.name))
1025
+ : kits;
1026
+ if (kitsToCheck.length === 0 || mcpInfoByPrimitiveKey.size === 0) {
1027
+ continue;
1028
+ }
1029
+ const desiredByName = new Map();
1030
+ for (const kit of kitsToCheck) {
1031
+ const primitives = resolvedByKit.get(kit.name) ?? [];
1032
+ for (const primitive of primitives) {
1033
+ if (primitive.type !== "mcp")
1034
+ continue;
1035
+ const key = buildPrimitiveKey(kit.name, primitive.sourcePath);
1036
+ const info = mcpInfoByPrimitiveKey.get(key);
1037
+ if (!info)
1038
+ continue;
1039
+ const byHash = desiredByName.get(info.instanceName) ?? new Map();
1040
+ const entry = byHash.get(info.configHash) ?? {
1041
+ primitives: [],
1042
+ kits: new Set(),
1043
+ info,
1044
+ };
1045
+ entry.primitives.push({ primitive, kitName: kit.name });
1046
+ entry.kits.add(kit.name);
1047
+ byHash.set(info.configHash, entry);
1048
+ desiredByName.set(info.instanceName, byHash);
1049
+ }
1050
+ }
1051
+ for (const [instanceName, byHash] of desiredByName) {
1052
+ if (byHash.size > 1) {
1053
+ const message = `Conflicting MCP server configs detected for instance "${instanceName}". ` +
1054
+ "Resolve the conflict by renaming one of the MCP server instances.";
1055
+ return {
1056
+ success: false,
1057
+ exitCode: ExitCode.InstallationFailed,
1058
+ error: message,
1059
+ };
1060
+ }
1061
+ }
1062
+ const existingInstances = manifest.harnesses[harness.name]?.mcpInstances ?? {};
1063
+ const usedInstanceNames = new Set(Object.keys(existingInstances));
1064
+ const assignments = new Map();
1065
+ const forkedInstances = new Map();
1066
+ const instancesToInstall = new Set();
1067
+ for (const [instanceName, byHash] of desiredByName) {
1068
+ const candidate = Array.from(byHash.values())[0];
1069
+ let finalName = instanceName;
1070
+ let finalHash = candidate.info.configHash;
1071
+ let action = "install";
1072
+ const existing = existingInstances[instanceName];
1073
+ if (existing) {
1074
+ if (existing.configHash === candidate.info.configHash) {
1075
+ action = "reuse";
1076
+ finalHash = existing.configHash;
1077
+ }
1078
+ else {
1079
+ if (!isInteractive) {
1080
+ return {
1081
+ success: false,
1082
+ exitCode: ExitCode.InstallationFailed,
1083
+ error: `MCP instance "${instanceName}" already exists in ${harness.displayName} with a different configuration.`,
1084
+ };
1085
+ }
1086
+ const decision = await promptMcpConflictResolution({
1087
+ instanceName,
1088
+ harnessDisplayName: harness.displayName,
1089
+ });
1090
+ if (decision === "cancel") {
1091
+ return {
1092
+ success: false,
1093
+ exitCode: ExitCode.UserCancelled,
1094
+ error: "Installation cancelled",
1095
+ };
1096
+ }
1097
+ if (decision === "reuse") {
1098
+ action = "reuse";
1099
+ finalHash = existing.configHash;
1100
+ }
1101
+ else {
1102
+ const newName = await promptForMcpInstanceName(instanceName, usedInstanceNames);
1103
+ if (!newName) {
1104
+ return {
1105
+ success: false,
1106
+ exitCode: ExitCode.UserCancelled,
1107
+ error: "Installation cancelled",
1108
+ };
1109
+ }
1110
+ finalName = newName;
1111
+ action = "install";
1112
+ finalHash = candidate.info.configHash;
1113
+ }
1114
+ }
1115
+ }
1116
+ if (action === "install") {
1117
+ instancesToInstall.add(finalName);
1118
+ usedInstanceNames.add(finalName);
1119
+ }
1120
+ for (const { primitive, kitName } of candidate.primitives) {
1121
+ const key = buildPrimitiveKey(kitName, primitive.sourcePath);
1122
+ assignments.set(key, {
1123
+ instanceName: finalName,
1124
+ configHash: finalHash,
1125
+ action,
1126
+ });
1127
+ if (finalName !== candidate.info.instanceName) {
1128
+ forkedInstances.set(finalName, candidate.info.instanceName);
1129
+ }
1130
+ }
1131
+ }
1132
+ if (assignments.size > 0) {
1133
+ mcpAssignmentsByHarness.set(harness.name, assignments);
1134
+ }
1135
+ if (forkedInstances.size > 0) {
1136
+ mcpForkedInstancesByHarness.set(harness.name, forkedInstances);
1137
+ }
1138
+ if (instancesToInstall.size > 0) {
1139
+ mcpInstancesToInstallByHarness.set(harness.name, instancesToInstall);
1140
+ }
1141
+ }
1142
+ if (envResolutions.length > 0 && mcpForkedInstancesByHarness.size > 0) {
1143
+ const addedOverrides = new Set();
1144
+ for (const forkedInstances of mcpForkedInstancesByHarness.values()) {
1145
+ for (const [overrideName, baseName] of forkedInstances.entries()) {
1146
+ if (addedOverrides.has(overrideName))
1147
+ continue;
1148
+ const base = envResolutions.find((r) => r.mcpServer === baseName);
1149
+ if (!base)
1150
+ continue;
1151
+ envResolutions.push({
1152
+ mcpServer: overrideName,
1153
+ resolved: [...base.resolved],
1154
+ resolvedHeaders: [...base.resolvedHeaders],
1155
+ missingRequired: [...base.missingRequired],
1156
+ warnings: [...base.warnings],
1157
+ });
1158
+ addedOverrides.add(overrideName);
1159
+ }
1160
+ }
1161
+ }
1162
+ for (const harness of harnesses) {
1163
+ const kitsToCheck = allowCompatibleOnly
1164
+ ? kits.filter((kit) => compatibleKitsByHarness.get(harness.name)?.has(kit.name))
1165
+ : kits;
1166
+ if (kitsToCheck.length === 0 || hookInfoByPrimitiveKey.size === 0) {
1167
+ continue;
1168
+ }
1169
+ const desiredByName = new Map();
1170
+ for (const kit of kitsToCheck) {
1171
+ const primitives = resolvedByKit.get(kit.name) ?? [];
1172
+ for (const primitive of primitives) {
1173
+ if (primitive.type !== "hooks")
1174
+ continue;
1175
+ const key = buildPrimitiveKey(kit.name, primitive.sourcePath);
1176
+ const info = hookInfoByPrimitiveKey.get(key);
1177
+ if (!info)
1178
+ continue;
1179
+ const byHash = desiredByName.get(info.instanceName) ?? new Map();
1180
+ const entry = byHash.get(info.configHash) ?? {
1181
+ primitives: [],
1182
+ kits: new Set(),
1183
+ info,
1184
+ };
1185
+ entry.primitives.push({ primitive, kitName: kit.name });
1186
+ entry.kits.add(kit.name);
1187
+ byHash.set(info.configHash, entry);
1188
+ desiredByName.set(info.instanceName, byHash);
1189
+ }
1190
+ }
1191
+ for (const [instanceName, byHash] of desiredByName) {
1192
+ if (byHash.size > 1) {
1193
+ const message = `Conflicting hook configs detected for instance "${instanceName}". ` +
1194
+ "Resolve the conflict by renaming one of the hook instances.";
1195
+ return {
1196
+ success: false,
1197
+ exitCode: ExitCode.InstallationFailed,
1198
+ error: message,
1199
+ };
1200
+ }
1201
+ }
1202
+ const existingInstances = manifest.harnesses[harness.name]?.hookInstances ?? {};
1203
+ const assignments = new Map();
1204
+ const instancesToInstall = new Set();
1205
+ for (const [instanceName, byHash] of desiredByName) {
1206
+ const candidate = Array.from(byHash.values())[0];
1207
+ let action = "install";
1208
+ let finalHash = candidate.info.configHash;
1209
+ const existing = existingInstances[instanceName];
1210
+ if (existing) {
1211
+ if (existing.configHash !== candidate.info.configHash) {
1212
+ return {
1213
+ success: false,
1214
+ exitCode: ExitCode.InstallationFailed,
1215
+ error: `Hook instance "${instanceName}" already exists in ${harness.displayName} with a different configuration.`,
1216
+ };
1217
+ }
1218
+ action = "reuse";
1219
+ finalHash = existing.configHash;
1220
+ }
1221
+ if (action === "install") {
1222
+ instancesToInstall.add(instanceName);
1223
+ }
1224
+ for (const { primitive, kitName } of candidate.primitives) {
1225
+ const key = buildPrimitiveKey(kitName, primitive.sourcePath);
1226
+ assignments.set(key, {
1227
+ instanceName,
1228
+ configHash: finalHash,
1229
+ action,
1230
+ });
1231
+ }
1232
+ }
1233
+ if (assignments.size > 0) {
1234
+ hookAssignmentsByHarness.set(harness.name, assignments);
1235
+ }
1236
+ if (instancesToInstall.size > 0) {
1237
+ hookInstancesToInstallByHarness.set(harness.name, instancesToInstall);
1238
+ }
1239
+ }
1240
+ // Install to each harness
1241
+ for (const harness of harnesses) {
1242
+ const kitsToInstall = allowCompatibleOnly
1243
+ ? kits.filter((kit) => compatibleKitsByHarness.get(harness.name)?.has(kit.name))
1244
+ : kits;
1245
+ if (kitsToInstall.length === 0) {
1246
+ continue;
1247
+ }
1248
+ const mcpAssignments = mcpAssignmentsByHarness.get(harness.name);
1249
+ const mcpInstancesToInstall = mcpInstancesToInstallByHarness.get(harness.name);
1250
+ const hookAssignments = hookAssignmentsByHarness.get(harness.name);
1251
+ const hookInstancesToInstall = hookInstancesToInstallByHarness.get(harness.name);
1252
+ if (isInteractive) {
1253
+ logInfo(`Installing to ${harness.displayName}...`);
1254
+ }
1255
+ else if (verbose) {
1256
+ console.error(`Installing to ${harness.displayName}...`);
1257
+ }
1258
+ const kitResults = [];
1259
+ for (const kit of kitsToInstall) {
1260
+ // Check compatibility with scope (should already be validated)
1261
+ const compatibility = harness.adapter.checkCompatibility(kit.manifest, scope);
1262
+ if (!compatibility.compatible) {
1263
+ if (allowCompatibleOnly) {
1264
+ continue;
1265
+ }
1266
+ return {
1267
+ success: false,
1268
+ exitCode: ExitCode.KitIncompatible,
1269
+ error: compatibility.message || "Incompatible kit-harness combination",
1270
+ };
1271
+ }
1272
+ // Use resolved primitives from pre-pass
1273
+ const resolvedPrimitives = resolvedByKit.get(kit.name);
1274
+ if (!resolvedPrimitives) {
1275
+ return {
1276
+ success: false,
1277
+ exitCode: ExitCode.ResolutionFailed,
1278
+ error: `Resolution failed: no primitives resolved for ${kit.name}`,
1279
+ };
1280
+ }
1281
+ const mcpConfigPath = getMcpConfigPath(harness.adapter, scope, projectRoot);
1282
+ const hookConfigPath = getHookConfigPath(harness.adapter, scope, projectRoot);
1283
+ const primitivesForAdapter = [];
1284
+ const skippedMcpPrimitives = [];
1285
+ const skippedHookPrimitives = [];
1286
+ const installedInstanceNames = new Set();
1287
+ const installedHookInstanceNames = new Set();
1288
+ for (const primitive of resolvedPrimitives) {
1289
+ if (primitive.type === "mcp" && mcpAssignments) {
1290
+ const assignment = mcpAssignments.get(buildPrimitiveKey(kit.name, primitive.sourcePath));
1291
+ if (!assignment) {
1292
+ primitivesForAdapter.push(primitive);
1293
+ continue;
1294
+ }
1295
+ const canInstall = assignment.action === "install";
1296
+ const shouldInstall = canInstall &&
1297
+ (!mcpInstancesToInstall || mcpInstancesToInstall.has(assignment.instanceName));
1298
+ if (!shouldInstall) {
1299
+ skippedMcpPrimitives.push(primitive);
1300
+ continue;
1301
+ }
1302
+ primitivesForAdapter.push({ ...primitive, instanceName: assignment.instanceName });
1303
+ installedInstanceNames.add(assignment.instanceName);
1304
+ continue;
1305
+ }
1306
+ if (primitive.type === "hooks" && hookAssignments) {
1307
+ const assignment = hookAssignments.get(buildPrimitiveKey(kit.name, primitive.sourcePath));
1308
+ if (!assignment) {
1309
+ primitivesForAdapter.push(primitive);
1310
+ continue;
1311
+ }
1312
+ const canInstall = assignment.action === "install";
1313
+ const shouldInstall = canInstall &&
1314
+ (!hookInstancesToInstall || hookInstancesToInstall.has(assignment.instanceName));
1315
+ if (!shouldInstall) {
1316
+ skippedHookPrimitives.push(primitive);
1317
+ continue;
1318
+ }
1319
+ primitivesForAdapter.push({ ...primitive, instanceName: assignment.instanceName });
1320
+ installedHookInstanceNames.add(assignment.instanceName);
1321
+ continue;
1322
+ }
1323
+ primitivesForAdapter.push(primitive);
1324
+ }
1325
+ // Install the kit
1326
+ const installOptions = {
1327
+ backup: true,
1328
+ createDirs: true,
1329
+ scope,
1330
+ envResolutions, // Pass resolved env vars to adapter (Phase 6)
1331
+ onProgress: (progress) => {
1332
+ if (verbose) {
1333
+ const primitiveInfo = progress.primitive ? ` ${progress.primitive}` : "";
1334
+ console.error(` [${progress.phase}] ${progress.operation}${primitiveInfo}`);
1335
+ }
1336
+ },
1337
+ };
1338
+ if (scope === "project") {
1339
+ installOptions.projectRoot = projectRoot;
1340
+ }
1341
+ const installResult = await harness.adapter.install(kit.manifest, primitivesForAdapter, installOptions);
1342
+ if (!installResult.success) {
1343
+ skippedResults.push({
1344
+ kit: kit.name,
1345
+ harness: harness.name,
1346
+ reason: installResult.error || "Installation failed",
1347
+ });
1348
+ continue;
1349
+ }
1350
+ for (const instanceName of installedInstanceNames) {
1351
+ mcpInstancesToInstall?.delete(instanceName);
1352
+ }
1353
+ for (const instanceName of installedHookInstanceNames) {
1354
+ hookInstancesToInstall?.delete(instanceName);
1355
+ }
1356
+ if (skippedMcpPrimitives.length > 0) {
1357
+ for (const primitive of skippedMcpPrimitives) {
1358
+ const namespacedName = harness.adapter.getNamespacedName(kit.name, primitive.name);
1359
+ const status = {
1360
+ type: primitive.type,
1361
+ name: primitive.name,
1362
+ namespacedName,
1363
+ status: "unchanged",
1364
+ destination: mcpConfigPath,
1365
+ };
1366
+ if (primitive.ref) {
1367
+ status.ref = primitive.ref;
1368
+ }
1369
+ if (primitive.resolvedVersion) {
1370
+ status.resolvedVersion = primitive.resolvedVersion;
1371
+ }
1372
+ installResult.installedPrimitives.push(status);
1373
+ }
1374
+ }
1375
+ if (skippedHookPrimitives.length > 0) {
1376
+ for (const primitive of skippedHookPrimitives) {
1377
+ const namespacedName = harness.adapter.getNamespacedName(kit.name, primitive.name);
1378
+ const status = {
1379
+ type: primitive.type,
1380
+ name: primitive.name,
1381
+ namespacedName,
1382
+ status: "unchanged",
1383
+ destination: hookConfigPath,
1384
+ };
1385
+ if (primitive.ref) {
1386
+ status.ref = primitive.ref;
1387
+ }
1388
+ if (primitive.resolvedVersion) {
1389
+ status.resolvedVersion = primitive.resolvedVersion;
1390
+ }
1391
+ installResult.installedPrimitives.push(status);
1392
+ }
1393
+ }
1394
+ // Record in manifest
1395
+ if (!manifest.harnesses[harness.name]) {
1396
+ manifest.harnesses[harness.name] = { kits: {} };
1397
+ }
1398
+ const statusByKey = new Map();
1399
+ for (const status of installResult.installedPrimitives) {
1400
+ statusByKey.set(`${status.type}:${status.name}`, status);
1401
+ }
1402
+ const installedPrimitives = resolvedPrimitives.map((primitive) => {
1403
+ const key = `${primitive.type}:${primitive.name}`;
1404
+ const status = statusByKey.get(key);
1405
+ const namespacedName = status?.namespacedName
1406
+ ?? harness.adapter.getNamespacedName(kit.name, primitive.name);
1407
+ const installedPath = status?.destination
1408
+ ?? (primitive.type === "mcp" ? mcpConfigPath : "");
1409
+ const installed = {
1410
+ name: primitive.name,
1411
+ type: primitive.type,
1412
+ namespacedName,
1413
+ isInline: !primitive.ref,
1414
+ installedPath,
1415
+ };
1416
+ if (primitive.resolvedVersion) {
1417
+ installed.version = primitive.resolvedVersion;
1418
+ }
1419
+ if (primitive.ref) {
1420
+ // Extract version spec from ref (e.g., "tf-plan@^1.0.0" -> "^1.0.0")
1421
+ const atIndex = primitive.ref.indexOf("@");
1422
+ if (atIndex !== -1) {
1423
+ installed.versionSpec = primitive.ref.substring(atIndex + 1);
1424
+ }
1425
+ }
1426
+ if (primitive.type === "mcp") {
1427
+ const assignment = mcpAssignments?.get(buildPrimitiveKey(kit.name, primitive.sourcePath));
1428
+ if (assignment) {
1429
+ installed.instanceName = assignment.instanceName;
1430
+ installed.configHash = assignment.configHash;
1431
+ }
1432
+ }
1433
+ if (primitive.type === "hooks") {
1434
+ const assignment = hookAssignments?.get(buildPrimitiveKey(kit.name, primitive.sourcePath));
1435
+ if (assignment) {
1436
+ installed.instanceName = assignment.instanceName;
1437
+ installed.configHash = assignment.configHash;
1438
+ }
1439
+ }
1440
+ return installed;
1441
+ });
1442
+ const installedKit = {
1443
+ version: kit.manifest.version,
1444
+ installedAt: new Date().toISOString(),
1445
+ source,
1446
+ primitives: installedPrimitives,
1447
+ };
1448
+ manifest.harnesses[harness.name].kits[kit.name] = installedKit;
1449
+ if (mcpAssignments && mcpInfoByPrimitiveKey.size > 0) {
1450
+ const harnessEntry = manifest.harnesses[harness.name];
1451
+ if (!harnessEntry.mcpInstances) {
1452
+ harnessEntry.mcpInstances = {};
1453
+ }
1454
+ for (const primitive of resolvedPrimitives) {
1455
+ if (primitive.type !== "mcp")
1456
+ continue;
1457
+ const assignment = mcpAssignments.get(buildPrimitiveKey(kit.name, primitive.sourcePath));
1458
+ if (!assignment)
1459
+ continue;
1460
+ const instanceName = assignment.instanceName;
1461
+ const existingInstance = harnessEntry.mcpInstances[instanceName];
1462
+ if (existingInstance) {
1463
+ if (!existingInstance.usedBy.includes(kit.name)) {
1464
+ existingInstance.usedBy.push(kit.name);
1465
+ }
1466
+ continue;
1467
+ }
1468
+ const info = mcpInfoByPrimitiveKey.get(buildPrimitiveKey(kit.name, primitive.sourcePath));
1469
+ if (!info)
1470
+ continue;
1471
+ harnessEntry.mcpInstances[instanceName] = {
1472
+ version: info.version,
1473
+ configHash: assignment.configHash,
1474
+ usedBy: [kit.name],
1475
+ sourcePrimitive: info.primitiveName,
1476
+ installedAt: new Date().toISOString(),
1477
+ };
1478
+ }
1479
+ }
1480
+ if (hookAssignments && hookInfoByPrimitiveKey.size > 0) {
1481
+ const harnessEntry = manifest.harnesses[harness.name];
1482
+ if (!harnessEntry.hookInstances) {
1483
+ harnessEntry.hookInstances = {};
1484
+ }
1485
+ for (const primitive of resolvedPrimitives) {
1486
+ if (primitive.type !== "hooks")
1487
+ continue;
1488
+ const assignment = hookAssignments.get(buildPrimitiveKey(kit.name, primitive.sourcePath));
1489
+ if (!assignment)
1490
+ continue;
1491
+ const instanceName = assignment.instanceName;
1492
+ const existingInstance = harnessEntry.hookInstances[instanceName];
1493
+ if (existingInstance) {
1494
+ if (!existingInstance.usedBy.includes(kit.name)) {
1495
+ existingInstance.usedBy.push(kit.name);
1496
+ }
1497
+ continue;
1498
+ }
1499
+ const info = hookInfoByPrimitiveKey.get(buildPrimitiveKey(kit.name, primitive.sourcePath));
1500
+ if (!info)
1501
+ continue;
1502
+ harnessEntry.hookInstances[instanceName] = {
1503
+ version: info.version,
1504
+ configHash: assignment.configHash,
1505
+ usedBy: [kit.name],
1506
+ sourcePrimitive: info.primitiveName,
1507
+ installedAt: new Date().toISOString(),
1508
+ };
1509
+ }
1510
+ }
1511
+ // Build result for output
1512
+ kitResults.push({
1513
+ name: kit.name,
1514
+ version: kit.manifest.version,
1515
+ installed: installResult.installedPrimitives.map((p) => {
1516
+ const primitiveOutput = {
1517
+ type: p.type,
1518
+ name: p.name,
1519
+ namespacedName: p.namespacedName,
1520
+ status: p.status,
1521
+ };
1522
+ if (p.resolvedVersion) {
1523
+ primitiveOutput.version = p.resolvedVersion;
1524
+ }
1525
+ return primitiveOutput;
1526
+ }),
1527
+ });
1528
+ if (isInteractive) {
1529
+ logInfo(`${formatKit(kit.name, kit.manifest.version)} ${pc.green("✓")}`);
1530
+ }
1531
+ }
1532
+ if (kitResults.length > 0) {
1533
+ harnessResults.push({
1534
+ name: harness.name,
1535
+ configPath: harness.configPath || "",
1536
+ kits: kitResults,
1537
+ });
1538
+ }
1539
+ }
1540
+ // Write manifest
1541
+ try {
1542
+ await writeManifest(manifest, scope, scope === "project" ? projectRoot : undefined);
1543
+ }
1544
+ catch (error) {
1545
+ return {
1546
+ success: false,
1547
+ error: `Failed to write manifest: ${error instanceof Error ? error.message : String(error)}`,
1548
+ };
1549
+ }
1550
+ // Build output
1551
+ const output = {
1552
+ success: true,
1553
+ source,
1554
+ scope,
1555
+ harnesses: harnessResults,
1556
+ skipped: skippedResults,
1557
+ };
1558
+ if (scope === "project") {
1559
+ output.projectRoot = projectRoot;
1560
+ }
1561
+ // Add env var status to output (Phase 6)
1562
+ if (envResolutions.length > 0) {
1563
+ const configured = [];
1564
+ const passthrough = [];
1565
+ const warnings = [];
1566
+ for (const resolution of envResolutions) {
1567
+ for (const resolved of resolution.resolved) {
1568
+ if (resolved.usePassthrough) {
1569
+ passthrough.push({
1570
+ name: resolved.name,
1571
+ sensitive: resolved.sensitive,
1572
+ mcpServer: resolution.mcpServer,
1573
+ });
1574
+ }
1575
+ else if (resolved.value !== undefined) {
1576
+ configured.push({
1577
+ name: resolved.name,
1578
+ source: resolved.source,
1579
+ mcpServer: resolution.mcpServer,
1580
+ });
1581
+ }
1582
+ }
1583
+ for (const warning of resolution.warnings) {
1584
+ warnings.push({
1585
+ name: warning.name,
1586
+ message: warning.message,
1587
+ type: warning.type,
1588
+ });
1589
+ }
1590
+ }
1591
+ // Only include if there's content
1592
+ if (configured.length > 0 || passthrough.length > 0 || warnings.length > 0) {
1593
+ output.envVars = { configured, passthrough, warnings };
1594
+ }
1595
+ }
1596
+ return { success: true, output };
1597
+ }
1598
+ /**
1599
+ * Resolve inline primitives only (when no primitives registry exists).
1600
+ */
1601
+ function resolveInlinePrimitivesOnly(kit, localPath) {
1602
+ const resolved = [];
1603
+ const kitBasePath = path.join(localPath, kit.path);
1604
+ for (const [type, primitives] of Object.entries(kit.manifest.primitives)) {
1605
+ if (!primitives)
1606
+ continue;
1607
+ for (const primitive of primitives) {
1608
+ // Only handle inline primitives
1609
+ if ("name" in primitive && "entrypoint" in primitive) {
1610
+ resolved.push({
1611
+ kitName: kit.name,
1612
+ name: primitive.name,
1613
+ type: type,
1614
+ sourcePath: path.resolve(kitBasePath, primitive.entrypoint),
1615
+ isInline: true,
1616
+ metadata: primitive.description ? { description: primitive.description } : {},
1617
+ instanceName: primitive.instanceName,
1618
+ });
1619
+ }
1620
+ }
1621
+ }
1622
+ return resolved;
1623
+ }
1624
+ //# sourceMappingURL=install.js.map