terramend 0.2.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 (406) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +145 -0
  3. package/dist/agents/claude.d.ts +73 -0
  4. package/dist/agents/claudePretoolGate.d.ts +99 -0
  5. package/dist/agents/gateServer.d.ts +7 -0
  6. package/dist/agents/index.d.ts +6 -0
  7. package/dist/agents/nativeFsDenies.d.ts +28 -0
  8. package/dist/agents/opencode.d.ts +231 -0
  9. package/dist/agents/opencodePlugin.d.ts +85 -0
  10. package/dist/agents/opencodeShared.d.ts +40 -0
  11. package/dist/agents/postRun.d.ts +132 -0
  12. package/dist/agents/reviewer.d.ts +38 -0
  13. package/dist/agents/sessionLabeler.d.ts +97 -0
  14. package/dist/agents/shared.d.ts +189 -0
  15. package/dist/agents/subagentModels.d.ts +19 -0
  16. package/dist/agents/subagentToolGates.d.ts +55 -0
  17. package/dist/cli.mjs +197426 -0
  18. package/dist/external.d.ts +227 -0
  19. package/dist/index.d.ts +6 -0
  20. package/dist/index.js +196783 -0
  21. package/dist/internal/index.d.ts +18 -0
  22. package/dist/internal.js +1714 -0
  23. package/dist/lifecycle.d.ts +2 -0
  24. package/dist/main.d.ts +8 -0
  25. package/dist/mcp/arkConfig.d.ts +1 -0
  26. package/dist/mcp/checkSuite.d.ts +25 -0
  27. package/dist/mcp/checkout.d.ts +77 -0
  28. package/dist/mcp/comment.d.ts +119 -0
  29. package/dist/mcp/commitInfo.d.ts +9 -0
  30. package/dist/mcp/crosswalk.d.ts +105 -0
  31. package/dist/mcp/dependencies.d.ts +8 -0
  32. package/dist/mcp/geminiSanitizer.d.ts +28 -0
  33. package/dist/mcp/git.d.ts +46 -0
  34. package/dist/mcp/guardrails.d.ts +104 -0
  35. package/dist/mcp/issue.d.ts +18 -0
  36. package/dist/mcp/issueComments.d.ts +9 -0
  37. package/dist/mcp/issueEvents.d.ts +9 -0
  38. package/dist/mcp/issueInfo.d.ts +9 -0
  39. package/dist/mcp/labels.d.ts +12 -0
  40. package/dist/mcp/localContext.d.ts +19 -0
  41. package/dist/mcp/moduleExtraction.d.ts +71 -0
  42. package/dist/mcp/moduleTests.d.ts +104 -0
  43. package/dist/mcp/modules.d.ts +179 -0
  44. package/dist/mcp/output.d.ts +12 -0
  45. package/dist/mcp/pathSafety.d.ts +14 -0
  46. package/dist/mcp/policy.d.ts +48 -0
  47. package/dist/mcp/pr.d.ts +49 -0
  48. package/dist/mcp/prInfo.d.ts +9 -0
  49. package/dist/mcp/providerSchema.d.ts +50 -0
  50. package/dist/mcp/review.d.ts +199 -0
  51. package/dist/mcp/reviewComments.d.ts +178 -0
  52. package/dist/mcp/roots.d.ts +58 -0
  53. package/dist/mcp/scope.d.ts +15 -0
  54. package/dist/mcp/selectMode.d.ts +18 -0
  55. package/dist/mcp/server.d.ts +48 -0
  56. package/dist/mcp/shared.d.ts +47 -0
  57. package/dist/mcp/shell.d.ts +37 -0
  58. package/dist/mcp/staleFix.d.ts +51 -0
  59. package/dist/mcp/terraform/cost.d.ts +55 -0
  60. package/dist/mcp/terraform/currency.d.ts +94 -0
  61. package/dist/mcp/terraform/decisions.d.ts +178 -0
  62. package/dist/mcp/terraform/findings.d.ts +75 -0
  63. package/dist/mcp/terraform/plan.d.ts +157 -0
  64. package/dist/mcp/terraform/scanners.d.ts +131 -0
  65. package/dist/mcp/terraform/tools.d.ts +63 -0
  66. package/dist/mcp/terraform/types.d.ts +172 -0
  67. package/dist/mcp/terraform.d.ts +22 -0
  68. package/dist/mcp/terratest.d.ts +83 -0
  69. package/dist/mcp/upload.d.ts +6 -0
  70. package/dist/models.d.ts +171 -0
  71. package/dist/modes.d.ts +26 -0
  72. package/dist/prep/index.d.ts +7 -0
  73. package/dist/prep/installNodeDependencies.d.ts +2 -0
  74. package/dist/prep/installPythonDependencies.d.ts +2 -0
  75. package/dist/prep/types.d.ts +31 -0
  76. package/dist/reviewQuality.d.ts +64 -0
  77. package/dist/skills/terraform-best-practices/SKILL.md +369 -0
  78. package/dist/toolState.d.ts +135 -0
  79. package/dist/utils/activity.d.ts +40 -0
  80. package/dist/utils/agent.d.ts +20 -0
  81. package/dist/utils/agentHangReport.d.ts +38 -0
  82. package/dist/utils/apiFetch.d.ts +19 -0
  83. package/dist/utils/apiKeys.d.ts +41 -0
  84. package/dist/utils/apiUrl.d.ts +20 -0
  85. package/dist/utils/assets.d.ts +8 -0
  86. package/dist/utils/billingErrors.d.ts +85 -0
  87. package/dist/utils/body.d.ts +34 -0
  88. package/dist/utils/buildTerramendFooter.d.ts +25 -0
  89. package/dist/utils/byokFallback.d.ts +85 -0
  90. package/dist/utils/claudeSubscription.d.ts +30 -0
  91. package/dist/utils/cli.d.ts +10 -0
  92. package/dist/utils/codexHome.d.ts +29 -0
  93. package/dist/utils/codexOAuth.d.ts +60 -0
  94. package/dist/utils/diffCoverage.d.ts +63 -0
  95. package/dist/utils/errorReport.d.ts +17 -0
  96. package/dist/utils/exitHandler.d.ts +8 -0
  97. package/dist/utils/fixDoubleEscapedString.d.ts +1 -0
  98. package/dist/utils/gitAuth.d.ts +84 -0
  99. package/dist/utils/gitAuthServer.d.ts +24 -0
  100. package/dist/utils/github.d.ts +78 -0
  101. package/dist/utils/globals.d.ts +3 -0
  102. package/dist/utils/install.d.ts +60 -0
  103. package/dist/utils/instructions.d.ts +48 -0
  104. package/dist/utils/leapingComment.d.ts +11 -0
  105. package/dist/utils/learnings.d.ts +62 -0
  106. package/dist/utils/learningsTruncate.d.ts +25 -0
  107. package/dist/utils/lifecycle.d.ts +57 -0
  108. package/dist/utils/log.d.ts +111 -0
  109. package/dist/utils/normalizeEnv.d.ts +30 -0
  110. package/dist/utils/openCodeModels.d.ts +11 -0
  111. package/dist/utils/overrides.d.ts +40 -0
  112. package/dist/utils/packageManager.d.ts +49 -0
  113. package/dist/utils/patchWorkflowRunFields.d.ts +29 -0
  114. package/dist/utils/payload.d.ts +105 -0
  115. package/dist/utils/prSummary.d.ts +61 -0
  116. package/dist/utils/progressComment.d.ts +146 -0
  117. package/dist/utils/providerErrors.d.ts +31 -0
  118. package/dist/utils/rangeDiff.d.ts +51 -0
  119. package/dist/utils/remediationCommand.d.ts +55 -0
  120. package/dist/utils/retry.d.ts +13 -0
  121. package/dist/utils/reviewCleanup.d.ts +14 -0
  122. package/dist/utils/run.d.ts +9 -0
  123. package/dist/utils/runContext.d.ts +60 -0
  124. package/dist/utils/runContextData.d.ts +23 -0
  125. package/dist/utils/runErrorRenderer.d.ts +64 -0
  126. package/dist/utils/runLifecycle.d.ts +86 -0
  127. package/dist/utils/runStartupLog.d.ts +15 -0
  128. package/dist/utils/secrets.d.ts +22 -0
  129. package/dist/utils/setup.d.ts +90 -0
  130. package/dist/utils/shell.d.ts +32 -0
  131. package/dist/utils/skills.d.ts +10 -0
  132. package/dist/utils/subprocess.d.ts +80 -0
  133. package/dist/utils/terraformMcp.d.ts +42 -0
  134. package/dist/utils/time.d.ts +15 -0
  135. package/dist/utils/timer.d.ts +23 -0
  136. package/dist/utils/todoTracking.d.ts +16 -0
  137. package/dist/utils/token.d.ts +39 -0
  138. package/dist/utils/version.d.ts +2 -0
  139. package/dist/utils/versioning.d.ts +7 -0
  140. package/dist/utils/vertex.d.ts +16 -0
  141. package/dist/utils/workflow.d.ts +13 -0
  142. package/package.json +119 -0
  143. package/src/agents/claude.test.ts +1016 -0
  144. package/src/agents/claude.ts +1246 -0
  145. package/src/agents/claudePretoolGate.test.ts +28 -0
  146. package/src/agents/claudePretoolGate.ts +173 -0
  147. package/src/agents/gateServer.test.ts +204 -0
  148. package/src/agents/gateServer.ts +124 -0
  149. package/src/agents/index.ts +10 -0
  150. package/src/agents/nativeFsDenies.ts +82 -0
  151. package/src/agents/opencode.test.ts +1440 -0
  152. package/src/agents/opencode.ts +1312 -0
  153. package/src/agents/opencodePlugin.ts +222 -0
  154. package/src/agents/opencodeShared.test.ts +34 -0
  155. package/src/agents/opencodeShared.ts +121 -0
  156. package/src/agents/postRun.test.ts +549 -0
  157. package/src/agents/postRun.ts +535 -0
  158. package/src/agents/reviewer.ts +104 -0
  159. package/src/agents/sessionLabeler.test.ts +247 -0
  160. package/src/agents/sessionLabeler.ts +178 -0
  161. package/src/agents/shared.test.ts +76 -0
  162. package/src/agents/shared.ts +292 -0
  163. package/src/agents/subagentModels.test.ts +113 -0
  164. package/src/agents/subagentModels.ts +40 -0
  165. package/src/agents/subagentRegistration.test.ts +41 -0
  166. package/src/agents/subagentToolGates.ts +114 -0
  167. package/src/cli.test.ts +129 -0
  168. package/src/cli.ts +105 -0
  169. package/src/commands/gha.test.ts +192 -0
  170. package/src/commands/gha.ts +188 -0
  171. package/src/commands/mcp.ts +122 -0
  172. package/src/config.ts +1 -0
  173. package/src/entry.ts +7 -0
  174. package/src/entryPost.stdlibOnly.test.ts +109 -0
  175. package/src/entryPost.ts +99 -0
  176. package/src/external.test.ts +16 -0
  177. package/src/external.ts +302 -0
  178. package/src/index.ts +11 -0
  179. package/src/internal/index.ts +71 -0
  180. package/src/lifecycle.ts +2 -0
  181. package/src/main.test.ts +873 -0
  182. package/src/main.ts +712 -0
  183. package/src/mcp/__fixtures__/terramend-scratch-pr-49-review-3485940013.json +110 -0
  184. package/src/mcp/__fixtures__/terramend-scratch-pr-64-review-3531000326.json +14 -0
  185. package/src/mcp/__fixtures__/terramend-test-repo-pr-1.diff.json +67 -0
  186. package/src/mcp/__snapshots__/checkout.test.ts.snap +109 -0
  187. package/src/mcp/__snapshots__/reviewComments.test.ts.snap +71 -0
  188. package/src/mcp/arkConfig.ts +7 -0
  189. package/src/mcp/checkSuite.test.ts +245 -0
  190. package/src/mcp/checkSuite.ts +255 -0
  191. package/src/mcp/checkout.test.ts +752 -0
  192. package/src/mcp/checkout.ts +886 -0
  193. package/src/mcp/comment.test.ts +772 -0
  194. package/src/mcp/comment.ts +582 -0
  195. package/src/mcp/commitInfo.test.ts +127 -0
  196. package/src/mcp/commitInfo.ts +61 -0
  197. package/src/mcp/crosswalk.test.ts +106 -0
  198. package/src/mcp/crosswalk.ts +339 -0
  199. package/src/mcp/dependencies.test.ts +309 -0
  200. package/src/mcp/dependencies.ts +189 -0
  201. package/src/mcp/geminiSanitizer.test.ts +287 -0
  202. package/src/mcp/geminiSanitizer.ts +207 -0
  203. package/src/mcp/git.test.ts +1083 -0
  204. package/src/mcp/git.ts +890 -0
  205. package/src/mcp/guardrails.test.ts +705 -0
  206. package/src/mcp/guardrails.ts +465 -0
  207. package/src/mcp/issue.test.ts +113 -0
  208. package/src/mcp/issue.ts +73 -0
  209. package/src/mcp/issueComments.test.ts +69 -0
  210. package/src/mcp/issueComments.ts +48 -0
  211. package/src/mcp/issueEvents.test.ts +134 -0
  212. package/src/mcp/issueEvents.ts +100 -0
  213. package/src/mcp/issueInfo.test.ts +104 -0
  214. package/src/mcp/issueInfo.ts +72 -0
  215. package/src/mcp/labels.test.ts +52 -0
  216. package/src/mcp/labels.ts +34 -0
  217. package/src/mcp/localContext.ts +28 -0
  218. package/src/mcp/localServer.test.ts +75 -0
  219. package/src/mcp/localServer.ts +131 -0
  220. package/src/mcp/moduleExtraction.test.ts +261 -0
  221. package/src/mcp/moduleExtraction.ts +313 -0
  222. package/src/mcp/moduleTests.test.ts +269 -0
  223. package/src/mcp/moduleTests.ts +421 -0
  224. package/src/mcp/modules.test.ts +640 -0
  225. package/src/mcp/modules.ts +696 -0
  226. package/src/mcp/output.test.ts +96 -0
  227. package/src/mcp/output.ts +70 -0
  228. package/src/mcp/pathSafety.test.ts +44 -0
  229. package/src/mcp/pathSafety.ts +28 -0
  230. package/src/mcp/policy.test.ts +282 -0
  231. package/src/mcp/policy.ts +199 -0
  232. package/src/mcp/pr.test.ts +387 -0
  233. package/src/mcp/pr.ts +194 -0
  234. package/src/mcp/prInfo.test.ts +96 -0
  235. package/src/mcp/prInfo.ts +91 -0
  236. package/src/mcp/providerSchema.test.ts +85 -0
  237. package/src/mcp/providerSchema.ts +175 -0
  238. package/src/mcp/review.test.ts +936 -0
  239. package/src/mcp/review.ts +923 -0
  240. package/src/mcp/reviewComments.test.ts +549 -0
  241. package/src/mcp/reviewComments.ts +896 -0
  242. package/src/mcp/roots.test.ts +175 -0
  243. package/src/mcp/roots.ts +217 -0
  244. package/src/mcp/scope.test.ts +59 -0
  245. package/src/mcp/scope.ts +65 -0
  246. package/src/mcp/security.test.ts +720 -0
  247. package/src/mcp/selectMode.test.ts +210 -0
  248. package/src/mcp/selectMode.ts +181 -0
  249. package/src/mcp/server.test.ts +292 -0
  250. package/src/mcp/server.ts +403 -0
  251. package/src/mcp/shared.ts +100 -0
  252. package/src/mcp/shell.test.ts +520 -0
  253. package/src/mcp/shell.ts +505 -0
  254. package/src/mcp/staleFix.test.ts +237 -0
  255. package/src/mcp/staleFix.ts +277 -0
  256. package/src/mcp/terraform/cost.ts +163 -0
  257. package/src/mcp/terraform/currency.test.ts +338 -0
  258. package/src/mcp/terraform/currency.ts +336 -0
  259. package/src/mcp/terraform/decisions.ts +527 -0
  260. package/src/mcp/terraform/findings.ts +333 -0
  261. package/src/mcp/terraform/plan.ts +348 -0
  262. package/src/mcp/terraform/scanners.ts +809 -0
  263. package/src/mcp/terraform/tools.test.ts +1071 -0
  264. package/src/mcp/terraform/tools.ts +908 -0
  265. package/src/mcp/terraform/types.ts +305 -0
  266. package/src/mcp/terraform.test.ts +1957 -0
  267. package/src/mcp/terraform.ts +23 -0
  268. package/src/mcp/terratest.test.ts +105 -0
  269. package/src/mcp/terratest.ts +196 -0
  270. package/src/mcp/toolFiltering.test.ts +85 -0
  271. package/src/mcp/upload.test.ts +180 -0
  272. package/src/mcp/upload.ts +112 -0
  273. package/src/models.test.ts +300 -0
  274. package/src/models.ts +708 -0
  275. package/src/modes.test.ts +107 -0
  276. package/src/modes.ts +880 -0
  277. package/src/prep/index.ts +43 -0
  278. package/src/prep/installNodeDependencies.test.ts +298 -0
  279. package/src/prep/installNodeDependencies.ts +196 -0
  280. package/src/prep/installPythonDependencies.test.ts +268 -0
  281. package/src/prep/installPythonDependencies.ts +199 -0
  282. package/src/prep/types.ts +38 -0
  283. package/src/reviewQuality.test.ts +63 -0
  284. package/src/reviewQuality.ts +134 -0
  285. package/src/runCli.test.ts +214 -0
  286. package/src/runCli.ts +282 -0
  287. package/src/skills/terraform-best-practices/SKILL.md +369 -0
  288. package/src/toolState.test.ts +45 -0
  289. package/src/toolState.ts +252 -0
  290. package/src/utils/activity.test.ts +188 -0
  291. package/src/utils/activity.ts +210 -0
  292. package/src/utils/agent.test.ts +251 -0
  293. package/src/utils/agent.ts +139 -0
  294. package/src/utils/agentHangReport.test.ts +203 -0
  295. package/src/utils/agentHangReport.ts +170 -0
  296. package/src/utils/apiFetch.test.ts +115 -0
  297. package/src/utils/apiFetch.ts +62 -0
  298. package/src/utils/apiKeys.test.ts +344 -0
  299. package/src/utils/apiKeys.ts +206 -0
  300. package/src/utils/apiUrl.test.ts +30 -0
  301. package/src/utils/apiUrl.ts +59 -0
  302. package/src/utils/assets.test.ts +153 -0
  303. package/src/utils/assets.ts +107 -0
  304. package/src/utils/billingErrors.test.ts +121 -0
  305. package/src/utils/billingErrors.ts +189 -0
  306. package/src/utils/body.test.ts +217 -0
  307. package/src/utils/body.ts +168 -0
  308. package/src/utils/buildTerramendFooter.test.ts +38 -0
  309. package/src/utils/buildTerramendFooter.ts +82 -0
  310. package/src/utils/byokFallback.test.ts +205 -0
  311. package/src/utils/byokFallback.ts +128 -0
  312. package/src/utils/claudeSubscription.test.ts +179 -0
  313. package/src/utils/claudeSubscription.ts +93 -0
  314. package/src/utils/cli.ts +31 -0
  315. package/src/utils/codexHome.test.ts +190 -0
  316. package/src/utils/codexHome.ts +191 -0
  317. package/src/utils/codexOAuth.ts +147 -0
  318. package/src/utils/codexRefreshDetect.test.ts +85 -0
  319. package/src/utils/codexRefreshDetect.ts +35 -0
  320. package/src/utils/diffCoverage.test.ts +468 -0
  321. package/src/utils/diffCoverage.ts +404 -0
  322. package/src/utils/errorReport.test.ts +135 -0
  323. package/src/utils/errorReport.ts +83 -0
  324. package/src/utils/exitHandler.ts +35 -0
  325. package/src/utils/fixDoubleEscapedString.ts +9 -0
  326. package/src/utils/ghaCore.ts +13 -0
  327. package/src/utils/gitAuth.test.ts +322 -0
  328. package/src/utils/gitAuth.ts +263 -0
  329. package/src/utils/gitAuthServer.test.ts +260 -0
  330. package/src/utils/gitAuthServer.ts +182 -0
  331. package/src/utils/github.test.ts +615 -0
  332. package/src/utils/github.ts +538 -0
  333. package/src/utils/globals.ts +9 -0
  334. package/src/utils/humanEditCapture.test.ts +100 -0
  335. package/src/utils/humanEditCapture.ts +193 -0
  336. package/src/utils/install.test.ts +768 -0
  337. package/src/utils/install.ts +492 -0
  338. package/src/utils/instructions.test.ts +240 -0
  339. package/src/utils/instructions.ts +543 -0
  340. package/src/utils/leapingComment.test.ts +51 -0
  341. package/src/utils/leapingComment.ts +18 -0
  342. package/src/utils/learnings.test.ts +87 -0
  343. package/src/utils/learnings.ts +138 -0
  344. package/src/utils/learningsTocRender.test.ts +116 -0
  345. package/src/utils/learningsTruncate.test.ts +39 -0
  346. package/src/utils/learningsTruncate.ts +42 -0
  347. package/src/utils/lifecycle.test.ts +195 -0
  348. package/src/utils/lifecycle.ts +198 -0
  349. package/src/utils/log.test.ts +402 -0
  350. package/src/utils/log.ts +432 -0
  351. package/src/utils/normalizeEnv.test.ts +91 -0
  352. package/src/utils/normalizeEnv.ts +106 -0
  353. package/src/utils/openCodeModels.ts +82 -0
  354. package/src/utils/overrides.test.ts +89 -0
  355. package/src/utils/overrides.ts +98 -0
  356. package/src/utils/packageManager.test.ts +321 -0
  357. package/src/utils/packageManager.ts +257 -0
  358. package/src/utils/patchWorkflowRunFields.test.ts +92 -0
  359. package/src/utils/patchWorkflowRunFields.ts +150 -0
  360. package/src/utils/payload.test.ts +497 -0
  361. package/src/utils/payload.ts +371 -0
  362. package/src/utils/postApiFetch.ts +51 -0
  363. package/src/utils/prSummary.test.ts +224 -0
  364. package/src/utils/prSummary.ts +147 -0
  365. package/src/utils/progressComment.ts +261 -0
  366. package/src/utils/providerErrors.test.ts +315 -0
  367. package/src/utils/providerErrors.ts +172 -0
  368. package/src/utils/rangeDiff.test.ts +236 -0
  369. package/src/utils/rangeDiff.ts +182 -0
  370. package/src/utils/remediationCommand.test.ts +163 -0
  371. package/src/utils/remediationCommand.ts +119 -0
  372. package/src/utils/retry.test.ts +153 -0
  373. package/src/utils/retry.ts +58 -0
  374. package/src/utils/reviewCleanup.ts +106 -0
  375. package/src/utils/run.ts +99 -0
  376. package/src/utils/runContext.ts +145 -0
  377. package/src/utils/runContextData.ts +58 -0
  378. package/src/utils/runErrorRenderer.test.ts +95 -0
  379. package/src/utils/runErrorRenderer.ts +259 -0
  380. package/src/utils/runFixture.ts +76 -0
  381. package/src/utils/runLifecycle.ts +237 -0
  382. package/src/utils/runStartupLog.ts +60 -0
  383. package/src/utils/secrets.test.ts +103 -0
  384. package/src/utils/secrets.ts +177 -0
  385. package/src/utils/setup.test.ts +509 -0
  386. package/src/utils/setup.ts +352 -0
  387. package/src/utils/shell.ts +103 -0
  388. package/src/utils/skills.test.ts +46 -0
  389. package/src/utils/skills.ts +67 -0
  390. package/src/utils/subprocess.test.ts +170 -0
  391. package/src/utils/subprocess.ts +438 -0
  392. package/src/utils/terraformMcp.test.ts +63 -0
  393. package/src/utils/terraformMcp.ts +83 -0
  394. package/src/utils/time.test.ts +105 -0
  395. package/src/utils/time.ts +59 -0
  396. package/src/utils/timer.test.ts +91 -0
  397. package/src/utils/timer.ts +72 -0
  398. package/src/utils/todoTracking.test.ts +223 -0
  399. package/src/utils/todoTracking.ts +167 -0
  400. package/src/utils/token.test.ts +239 -0
  401. package/src/utils/token.ts +186 -0
  402. package/src/utils/version.ts +10 -0
  403. package/src/utils/versioning.test.ts +34 -0
  404. package/src/utils/versioning.ts +44 -0
  405. package/src/utils/vertex.ts +85 -0
  406. package/src/utils/workflow.ts +25 -0
@@ -0,0 +1,352 @@
1
+ import { execFileSync, execSync } from "node:child_process";
2
+ import { mkdtempSync, readdirSync, realpathSync, unlinkSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import type { ShellPermission } from "#app/external";
6
+ import type { ToolState } from "#app/toolState";
7
+ import { log } from "#app/utils/cli";
8
+ import type { OctokitWithPlugins } from "#app/utils/github";
9
+ import { isInsideDocker } from "#app/utils/globals";
10
+ import { $ } from "#app/utils/shell";
11
+
12
+ export interface SetupOptions {
13
+ tempDir: string;
14
+ }
15
+
16
+ /**
17
+ * Create a shared temp directory for the action
18
+ */
19
+ export function createTempDirectory(): string {
20
+ const sharedTempDir = mkdtempSync(join(tmpdir(), "terramend-"));
21
+ process.env.TERRAMEND_TEMP_DIR = sharedTempDir;
22
+ log.info(`» created temp dir at ${sharedTempDir}`);
23
+ return sharedTempDir;
24
+ }
25
+
26
+ /**
27
+ * snapshot-and-delete the GHA runner's known credential leak surfaces inside
28
+ * `$RUNNER_TEMP` before the agent spawns. without this, a shell-capable agent
29
+ * can grep:
30
+ * - `_runner_file_commands/set_output_*` for `core.setOutput('token', ghs_…)`
31
+ * calls made by earlier composite-action steps (e.g.
32
+ * terramend/terramend/get-installation-token);
33
+ * - `<uuid>.sh` rendered step scripts whose `run: |` body embeds
34
+ * `${{ steps.token.outputs.token }}` literally (GHA expands BEFORE writing);
35
+ * - `git-credentials-*.config` written by `actions/checkout@v6` for the
36
+ * workflow GITHUB_TOKEN.
37
+ *
38
+ * the running bash process already has its own `.sh` open via fd, so the
39
+ * unlink is safe — `unlink` removes the dirent, the kernel keeps reading.
40
+ *
41
+ * preserves every `_runner_file_commands/` file path the runner pre-allocated
42
+ * for OUR step — `$GITHUB_OUTPUT`, `$GITHUB_ENV`, `$GITHUB_PATH`,
43
+ * `$GITHUB_STATE`, `$GITHUB_STEP_SUMMARY`. those are read by the runner
44
+ * AFTER we exit (or by our own `post:` hook), and wiping them would break
45
+ * terramend's `result` output, `post:` state handoff, and job summary.
46
+ *
47
+ * silent no-op when `$RUNNER_TEMP` is unset (local dev, `pnpm dev:run`).
48
+ * per-file errors are tolerated — the runner may delete files between
49
+ * our readdir and our unlink.
50
+ */
51
+ export function wipeRunnerLeakSurface(): void {
52
+ const runnerTemp = process.env.RUNNER_TEMP;
53
+ if (!runnerTemp) return;
54
+
55
+ const preserve = new Set<string>();
56
+ for (const envVar of [
57
+ "GITHUB_OUTPUT",
58
+ "GITHUB_ENV",
59
+ "GITHUB_PATH",
60
+ "GITHUB_STATE",
61
+ "GITHUB_STEP_SUMMARY",
62
+ ]) {
63
+ const path = process.env[envVar];
64
+ if (!path) continue;
65
+ try {
66
+ preserve.add(realpathSync(path));
67
+ } catch {
68
+ // path may not exist yet — preserve the literal in case it gets created later
69
+ preserve.add(path);
70
+ }
71
+ }
72
+
73
+ const wiped: string[] = [];
74
+
75
+ const tryUnlink = (path: string): void => {
76
+ let resolved = path;
77
+ try {
78
+ resolved = realpathSync(path);
79
+ } catch {
80
+ // file may already be gone — fall through to unlink for the race-tolerant path
81
+ }
82
+ if (preserve.has(resolved) || preserve.has(path)) return;
83
+ try {
84
+ unlinkSync(path);
85
+ wiped.push(path);
86
+ } catch {
87
+ // race-tolerant: file may have been deleted between readdir and unlink
88
+ }
89
+ };
90
+
91
+ const listDir = (dir: string): string[] => {
92
+ try {
93
+ return readdirSync(dir);
94
+ } catch {
95
+ return [];
96
+ }
97
+ };
98
+
99
+ const fileCommandsDir = join(runnerTemp, "_runner_file_commands");
100
+ for (const entry of listDir(fileCommandsDir)) {
101
+ tryUnlink(join(fileCommandsDir, entry));
102
+ }
103
+
104
+ for (const entry of listDir(runnerTemp)) {
105
+ if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
106
+ tryUnlink(join(runnerTemp, entry));
107
+ }
108
+ }
109
+
110
+ if (wiped.length > 0) {
111
+ log.info(`» wiped ${wiped.length} leak-surface file(s) from $RUNNER_TEMP`);
112
+ log.debug(`» wiped paths: ${wiped.join(", ")}`);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Setup the test repository for running actions
118
+ */
119
+ export function setupTestRepo(options: SetupOptions): void {
120
+ const tempDir = options.tempDir;
121
+ const repo = process.env.GITHUB_REPOSITORY;
122
+ if (!repo) throw new Error("GITHUB_REPOSITORY is required");
123
+ log.info(`» cloning ${repo} into ${tempDir}...`);
124
+
125
+ // use https with token in ci or when running inside docker
126
+ if (process.env.CI || isInsideDocker) {
127
+ const token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
128
+ if (!token) {
129
+ throw new Error("GITHUB_TOKEN or GH_TOKEN is required for https clone in ci or docker");
130
+ }
131
+ $("git", ["clone", `https://x-access-token:${token}@github.com/${repo}.git`, tempDir]);
132
+ } else {
133
+ $("git", ["clone", `git@github.com:${repo}.git`, tempDir]);
134
+ }
135
+ }
136
+
137
+ /**
138
+ * build an env suitable for targeting a specific git repo via `cwd`.
139
+ *
140
+ * inherited GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE override cwd resolution,
141
+ * which matters when this code runs as a child of `git push` (pre-push hook)
142
+ * or inside another git subcommand. if we don't strip them, a call that
143
+ * names `repoDir` in cwd silently operates on the outer repo instead.
144
+ */
145
+ function envScopedToRepo(): NodeJS.ProcessEnv {
146
+ const scoped = { ...process.env };
147
+ for (const key of Object.keys(scoped)) {
148
+ if (key.startsWith("GIT_")) delete scoped[key];
149
+ }
150
+ return scoped;
151
+ }
152
+
153
+ /**
154
+ * remove any `[includeIf ...]` entries from the local git config so that
155
+ * actions/checkout-persisted credentials don't ride alongside ASKPASS-provided
156
+ * auth for subsequent git operations.
157
+ *
158
+ * SECURITY: git config subsection values can contain arbitrary characters
159
+ * including `$(...)` command substitutions, and `${IFS}` spacing tricks defeat
160
+ * naive split-on-space filtering. we read keys via the `-z` (null-terminated)
161
+ * output format and feed them to a spawn-array `git config --unset-all` so
162
+ * the shell never interpolates key contents — closing the RCE path that a
163
+ * string-interpolated `execSync(...)` would expose.
164
+ */
165
+ export function removeIncludeIfEntries(repoDir: string): void {
166
+ const env = envScopedToRepo();
167
+ let configOutput: string;
168
+ try {
169
+ configOutput = execSync("git config --local --get-regexp -z ^includeif\\.", {
170
+ cwd: repoDir,
171
+ encoding: "utf-8",
172
+ stdio: "pipe",
173
+ env,
174
+ });
175
+ } catch {
176
+ log.debug("» no includeIf credential entries to remove");
177
+ return;
178
+ }
179
+ const seen = new Set<string>();
180
+ for (const entry of configOutput.split("\0")) {
181
+ if (!entry) continue;
182
+ // -z format: each entry is "<key>\n<value>". the key is up to the first newline.
183
+ const nl = entry.indexOf("\n");
184
+ const key = nl === -1 ? entry : entry.slice(0, nl);
185
+ if (!key || seen.has(key)) continue;
186
+ seen.add(key);
187
+ try {
188
+ // execFileSync (not execSync) so the key — which can contain arbitrary
189
+ // characters including shell metacharacters and $() command substitutions
190
+ // — is passed as an argv element and never interpolated by a shell.
191
+ // this is the load-bearing side of a9aa3b2b's injection fix.
192
+ execFileSync("git", ["config", "--local", "--unset-all", key], {
193
+ cwd: repoDir,
194
+ stdio: "pipe",
195
+ env,
196
+ });
197
+ } catch (error) {
198
+ log.debug(
199
+ `» failed to unset ${key}: ${error instanceof Error ? error.message : String(error)}`,
200
+ );
201
+ }
202
+ }
203
+ if (seen.size > 0)
204
+ log.info(
205
+ `» removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`,
206
+ );
207
+ }
208
+
209
+ export interface GitContext {
210
+ gitToken: string;
211
+ owner: string;
212
+ name: string;
213
+ octokit: OctokitWithPlugins;
214
+ toolState: ToolState;
215
+ // shell permission level — controls hook and security behavior:
216
+ // enabled: full shell, hooks run, no restrictions
217
+ // restricted: MCP shell in stripped env, hooks run, token protection on auth ops
218
+ // disabled: no shell, hooks disabled globally, all code execution paths blocked
219
+ shell: ShellPermission;
220
+ postCheckoutScript: string | null;
221
+ }
222
+
223
+ export type SetupGitParams = GitContext;
224
+
225
+ /**
226
+ * setup git configuration and authentication for the repository.
227
+ * - configures git identity (user.email, user.name)
228
+ * - sets up authentication via gitToken (minimal contents:write)
229
+ *
230
+ * gitToken is a minimal-permission token (contents + workflows) used for git operations.
231
+ * it is assumed to be potentially exfiltratable, so it has limited scope.
232
+ */
233
+ export async function setupGit(params: SetupGitParams): Promise<void> {
234
+ const repoDir = process.cwd();
235
+
236
+ // 1. configure git identity
237
+ log.info("» setting up git configuration...");
238
+ try {
239
+ // check current config - only set defaults if not configured or using generic bot
240
+ let currentEmail = "";
241
+ try {
242
+ currentEmail = execSync("git config user.email", {
243
+ cwd: repoDir,
244
+ stdio: "pipe",
245
+ encoding: "utf-8",
246
+ }).trim();
247
+ } catch {
248
+ // not configured
249
+ }
250
+
251
+ const shouldSetDefaults =
252
+ !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
253
+
254
+ if (shouldSetDefaults) {
255
+ // plain noreply email with no numeric account id — the upstream id
256
+ // resolved commits to the wrong bot account on GitHub.
257
+ execSync('git config --local user.email "terramend[bot]@users.noreply.github.com"', {
258
+ cwd: repoDir,
259
+ stdio: "pipe",
260
+ });
261
+ execSync('git config --local user.name "terramend[bot]"', {
262
+ cwd: repoDir,
263
+ stdio: "pipe",
264
+ });
265
+ log.debug("» git user configured (using defaults)");
266
+ } else {
267
+ log.debug(`» git user already configured (${currentEmail}), skipping`);
268
+ }
269
+
270
+ // SECURITY: disable git hooks when shell is disabled to prevent code execution.
271
+ // in restricted mode, hooks run in the stripped sandbox — that's fine.
272
+ // in enabled mode, the agent has full shell anyway.
273
+ // in disabled mode, hooks are the primary code-execution escape vector.
274
+ if (params.shell === "disabled") {
275
+ execSync("git config --local core.hooksPath /dev/null", {
276
+ cwd: repoDir,
277
+ stdio: "pipe",
278
+ });
279
+ log.debug("» git hooks disabled (shell=disabled)");
280
+ }
281
+ } catch (error) {
282
+ // If git config fails, log warning but don't fail the action
283
+ // This can happen if we're not in a git repo or git isn't available
284
+ log.info(`Failed to set git config: ${error instanceof Error ? error.message : String(error)}`);
285
+ }
286
+
287
+ // 2. setup authentication
288
+ // remove existing git auth headers that actions/checkout might have set
289
+ try {
290
+ execSync("git config --local --unset-all http.https://github.com/.extraheader", {
291
+ cwd: repoDir,
292
+ stdio: "pipe",
293
+ });
294
+ log.info("» removed existing authentication headers");
295
+ } catch {
296
+ log.debug("» no existing authentication headers to remove");
297
+ }
298
+
299
+ // remove includeIf entries that actions/checkout@v6 uses for credential persistence.
300
+ // v6 stores credentials in an external file loaded via includeIf.gitdir, which our
301
+ // --unset-all above doesn't catch. without this, stale credentials from actions/checkout
302
+ // would be sent alongside ASKPASS-provided credentials.
303
+ removeIncludeIfEntries(repoDir);
304
+
305
+ // SECURITY: set origin URL without token - auth is injected via GIT_ASKPASS
306
+ // in $git() calls. this prevents token leakage to git hooks and subprocesses.
307
+ const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
308
+ $("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
309
+
310
+ // initialize pushUrl to base repo - may be updated by checkout_pr for fork PRs
311
+ params.toolState.pushUrl = originUrl;
312
+
313
+ // disable credential helpers to prevent prompts and ensure clean auth state
314
+ $("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
315
+
316
+ // pin the run-entry HEAD for the checkout_pr initial-branch invariant; see
317
+ // captureInitialHead for the named-branch vs detached split and why it
318
+ // matters (zed-industries/cloud 2026-05-18 cross-PR clobber shape).
319
+ params.toolState.initialHead = captureInitialHead(repoDir);
320
+
321
+ log.info("» git authentication configured");
322
+ }
323
+
324
+ /**
325
+ * snapshot the current HEAD as either a branch name (when on a named branch)
326
+ * or a literal SHA (when detached). used by setupGit to pin the run-entry
327
+ * position and by checkout_pr to compare the live HEAD against it.
328
+ *
329
+ * splitting the two cases is load-bearing: `git rev-parse --abbrev-ref HEAD`
330
+ * returns the sentinel string `"HEAD"` on detached entry — which is the
331
+ * default `actions/checkout` state for `pull_request` events. storing that
332
+ * raw string would make any future detached state (including a subagent's
333
+ * `git checkout --detach <sha>`) compare equal.
334
+ */
335
+ export function captureInitialHead(
336
+ repoDir: string,
337
+ ): { kind: "branch"; name: string } | { kind: "detached"; sha: string } {
338
+ try {
339
+ const name = $("git", ["symbolic-ref", "--short", "HEAD"], {
340
+ cwd: repoDir,
341
+ log: false,
342
+ }).trim();
343
+ if (name) return { kind: "branch", name };
344
+ } catch {
345
+ // detached HEAD — fall through
346
+ }
347
+ const sha = $("git", ["rev-parse", "HEAD"], {
348
+ cwd: repoDir,
349
+ log: false,
350
+ }).trim();
351
+ return { kind: "detached", sha };
352
+ }
@@ -0,0 +1,103 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { type EnvMode, resolveEnv } from "#app/utils/secrets";
3
+
4
+ interface ShellOptions {
5
+ cwd?: string;
6
+ encoding?:
7
+ | "utf-8"
8
+ | "utf8"
9
+ | "ascii"
10
+ | "base64"
11
+ | "base64url"
12
+ | "hex"
13
+ | "latin1"
14
+ | "ucs-2"
15
+ | "ucs2"
16
+ | "utf16le";
17
+ log?: boolean;
18
+ /**
19
+ * env mode: "restricted" (default) filters secrets, "inherit" passes full env,
20
+ * or provide a custom env object (merged with restricted base)
21
+ */
22
+ env?: EnvMode;
23
+ onError?: (result: { status: number; stdout: string; stderr: string }) => void;
24
+ }
25
+
26
+ /**
27
+ * Execute a shell command safely using spawnSync with argument arrays.
28
+ * Prevents shell injection by avoiding string interpolation in shell commands.
29
+ *
30
+ * SECURITY: by default, env vars are filtered to remove secrets (tokens, keys, passwords).
31
+ * this prevents malicious code (git hooks, npm scripts, etc.) from exfiltrating credentials.
32
+ * use env: "inherit" only when absolutely necessary.
33
+ *
34
+ * @param cmd - The command to execute
35
+ * @param args - Array of arguments to pass to the command
36
+ * @param options - Optional configuration (cwd, encoding, onError)
37
+ * @returns The trimmed stdout output
38
+ * @throws Error if command fails and no onError handler is provided
39
+ */
40
+ export function $(cmd: string, args: string[], options?: ShellOptions): string {
41
+ const encoding = options?.encoding ?? "utf-8";
42
+ const env = resolveEnv(options?.env);
43
+
44
+ // CRITICAL: use "ignore" for stdin instead of "inherit" to avoid breaking MCP transport
45
+ // when running inside an MCP server, stdin is used for JSON-RPC protocol
46
+ const result = spawnSync(cmd, args, {
47
+ stdio: ["ignore", "pipe", "pipe"],
48
+ encoding,
49
+ cwd: options?.cwd,
50
+ env,
51
+ });
52
+
53
+ const stdout = result.stdout ?? "";
54
+ const stderr = result.stderr ?? "";
55
+
56
+ // Write output to process streams so it behaves like stdio: "inherit"
57
+ // CRITICAL: when running inside an MCP server, stdout is used for JSON-RPC protocol
58
+ // so we must write to stderr instead to avoid corrupting the protocol
59
+ // Only log if log option is not explicitly set to false
60
+ if (options?.log !== false) {
61
+ // if stdout is a TTY, it's safe to write to it; otherwise it's likely a pipe used for JSON-RPC
62
+ const canWriteToStdout = process.stdout.isTTY === true;
63
+ if (stdout) {
64
+ if (canWriteToStdout) {
65
+ process.stdout.write(stdout);
66
+ } else {
67
+ // stdout is a pipe (MCP context) - write to stderr instead
68
+ process.stderr.write(stdout);
69
+ }
70
+ }
71
+ if (stderr) {
72
+ process.stderr.write(stderr);
73
+ }
74
+ }
75
+
76
+ // Handle errors
77
+ if (result.status !== 0) {
78
+ const errorResult = {
79
+ status: result.status ?? -1,
80
+ stdout,
81
+ stderr,
82
+ };
83
+
84
+ if (options?.onError) {
85
+ options.onError(errorResult);
86
+ return stdout.trim();
87
+ }
88
+
89
+ // many git subcommands write context-bearing diagnostics to stdout, not
90
+ // stderr (merge conflicts, cherry-pick rejections, diff --exit-code,
91
+ // ls-files --error-unmatch). Falling back to "Unknown error" robbed the
92
+ // agent of any signal and forced an extra MCP round-trip. see #766.
93
+ const detail = [stderr, stdout]
94
+ .map((s) => s.trim())
95
+ .filter(Boolean)
96
+ .join("\n");
97
+ throw new Error(
98
+ `Command failed with exit code ${errorResult.status}: ${detail || "Unknown error"}`,
99
+ );
100
+ }
101
+
102
+ return stdout.trim();
103
+ }
@@ -0,0 +1,46 @@
1
+ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { installBundledSkills } from "#app/utils/skills";
6
+
7
+ vi.mock("#app/utils/cli", () => ({
8
+ log: {
9
+ info: vi.fn(),
10
+ debug: vi.fn(),
11
+ warning: vi.fn(),
12
+ error: vi.fn(),
13
+ success: vi.fn(),
14
+ },
15
+ }));
16
+
17
+ import { log } from "#app/utils/cli";
18
+
19
+ const successMock = vi.mocked(log.success);
20
+
21
+ const TEMP = mkdtempSync(join(tmpdir(), "terramend-skills-"));
22
+
23
+ afterAll(() => {
24
+ rmSync(TEMP, { recursive: true, force: true });
25
+ });
26
+
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ });
30
+
31
+ describe("installBundledSkills", () => {
32
+ it("writes every bundled skill into all agent auto-scan dirs under HOME", () => {
33
+ const home = join(TEMP, "home");
34
+ installBundledSkills({ home });
35
+
36
+ const targets = [".opencode/skills", ".claude/skills", ".agents/skills"].map((dir) =>
37
+ join(home, dir, "terraform-best-practices", "SKILL.md"),
38
+ );
39
+ const contents = targets.map((path) => readFileSync(path, "utf8"));
40
+ for (const content of contents) {
41
+ expect(content.length).toBeGreaterThan(0);
42
+ expect(content).toBe(contents[0]);
43
+ }
44
+ expect(successMock).toHaveBeenCalledWith(expect.stringContaining("terraform-best-practices"));
45
+ });
46
+ });
@@ -0,0 +1,67 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { log } from "#app/utils/cli";
5
+
6
+ /**
7
+ * skills bundled with the action runtime. the SKILL.md files live in
8
+ * `action/skills/<name>/SKILL.md` and are read at runtime — no esbuild loader,
9
+ * no codegen. this matters because the preview / oss path runs `cli.ts` from
10
+ * source (see `runCli.ts#runLocalCli`) where esbuild loaders don't apply.
11
+ */
12
+ const BUNDLED_SKILL_NAMES = ["terraform-best-practices"] as const;
13
+
14
+ /**
15
+ * resolve the on-disk path of a bundled SKILL.md by checking the two locations
16
+ * the file may live in:
17
+ * - source mode (`runLocalCli`): `<actionRoot>/skills/<name>/SKILL.md`,
18
+ * reached as `../skills/...` from `utils/skills.ts`.
19
+ * - bundled mode (npx published package): `<distDir>/skills/<name>/SKILL.md`,
20
+ * reached as `./skills/...` from `dist/cli.mjs`.
21
+ *
22
+ * the bundled-mode copy is produced by an esbuild post-build step in
23
+ * `esbuild.config.js`.
24
+ */
25
+ function resolveSkillPath(name: string): string {
26
+ const here = dirname(fileURLToPath(import.meta.url));
27
+ const candidates = [
28
+ join(here, "..", "skills", name, "SKILL.md"),
29
+ join(here, "skills", name, "SKILL.md"),
30
+ ];
31
+ for (const candidate of candidates) {
32
+ if (existsSync(candidate)) return candidate;
33
+ }
34
+ throw new Error(`bundled skill not found: ${name} (looked in ${candidates.join(", ")})`);
35
+ }
36
+
37
+ /**
38
+ * each agent has its own auto-scan dir under HOME. we write to all of them so
39
+ * the same `installBundledSkills` call works regardless of which agent is
40
+ * running, without coupling skills.ts to agent identity.
41
+ *
42
+ * verified empirically (PR #565):
43
+ * - OpenCode registers skills from `$HOME/.agents/skills/` and `.opencode/skills/`.
44
+ * - Claude Code only registers skills from `$HOME/.claude/skills/` —
45
+ * it does NOT scan `.agents/skills/`, so writing only there leaves the
46
+ * skill on disk but invisible to Claude's `Skill` tool.
47
+ */
48
+ const SKILL_TARGET_DIRS = [".opencode/skills", ".claude/skills", ".agents/skills"] as const;
49
+
50
+ /**
51
+ * write all bundled skills into the fake HOME so OpenCode / Claude Code discover
52
+ * them via their auto-scan directories.
53
+ *
54
+ * called once per agent run from each agent's `run()`. cheap (small file
55
+ * writes), no network, idempotent.
56
+ */
57
+ export function installBundledSkills(params: { home: string }): void {
58
+ for (const name of BUNDLED_SKILL_NAMES) {
59
+ const content = readFileSync(resolveSkillPath(name), "utf8");
60
+ for (const targetDir of SKILL_TARGET_DIRS) {
61
+ const skillDir = join(params.home, targetDir, name);
62
+ mkdirSync(skillDir, { recursive: true });
63
+ writeFileSync(join(skillDir, "SKILL.md"), content);
64
+ }
65
+ }
66
+ log.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
67
+ }