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,112 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { type } from "arktype";
5
+ import { fileTypeFromBuffer } from "file-type";
6
+ import type { ToolContext } from "#app/mcp/server";
7
+ import { execute, tool } from "#app/mcp/shared";
8
+ import { apiFetch } from "#app/utils/apiFetch";
9
+ import { log } from "#app/utils/cli";
10
+
11
+ const UploadFileParams = type({
12
+ path: type.string.describe(
13
+ "absolute path to a file inside the repo working tree or the temp dir",
14
+ ),
15
+ });
16
+
17
+ function isWithin(child: string, parent: string): boolean {
18
+ const rel = path.relative(parent, child);
19
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
20
+ }
21
+
22
+ /**
23
+ * Resolve and validate an upload path. `upload_file` returns a permanent PUBLIC
24
+ * URL, so an unconstrained read is a one-shot secret-exfiltration primitive
25
+ * under prompt injection (auth.json, materialized cloud-credential JSON,
26
+ * $GITHUB_EVENT_PATH, …). Constrain reads to the repo working tree or the OS
27
+ * temp dir (where agents scratch screenshots/artifacts), and never the `.git`
28
+ * dir. realpath both sides so symlinks can't escape the boundary.
29
+ */
30
+ function resolveUploadPath(requested: string): string {
31
+ const real = fs.realpathSync(requested);
32
+ const repoRoot = fs.realpathSync(process.env.GITHUB_WORKSPACE || process.cwd());
33
+ const tmpRoot = fs.realpathSync(os.tmpdir());
34
+
35
+ const inRepo = isWithin(real, repoRoot);
36
+ const inTmp = isWithin(real, tmpRoot);
37
+ if (!inRepo && !inTmp) {
38
+ throw new Error(
39
+ `upload_file: refusing to read '${requested}' — only files inside the repo ` +
40
+ `working tree or the temp dir may be uploaded.`,
41
+ );
42
+ }
43
+ if (inRepo && isWithin(real, path.join(repoRoot, ".git"))) {
44
+ throw new Error("upload_file: refusing to read from the .git directory.");
45
+ }
46
+ return real;
47
+ }
48
+
49
+ export function UploadFileTool(ctx: ToolContext) {
50
+ return tool({
51
+ name: "upload_file",
52
+ description:
53
+ "upload a file to get a permanent public URL. use for screenshots, artifacts, or any files you want to reference in PRs/comments. max 10MB, images/text/archives allowed. when embedding uploaded images in comments or PR bodies, always use markdown image syntax: ![description](url)",
54
+ parameters: UploadFileParams,
55
+ execute: execute(async (params) => {
56
+ // validate + resolve the path before reading: upload_file yields a
57
+ // permanent public URL, so an unconstrained read is an exfiltration sink.
58
+ const safePath = resolveUploadPath(params.path);
59
+ // read file from disk eagerly on purpose to avoid its content being changed by the time it's uploaded
60
+ const buffer = fs.readFileSync(safePath);
61
+ const filename = path.basename(safePath);
62
+ const contentLength = buffer.length;
63
+
64
+ const fileType = await fileTypeFromBuffer(buffer);
65
+ const contentType = fileType?.mime || "application/octet-stream";
66
+
67
+ const response = await apiFetch({
68
+ path: "/api/upload/signed-url",
69
+ method: "POST",
70
+ headers: {
71
+ Authorization: `Bearer ${ctx.apiToken}`,
72
+ "Content-Type": "application/json",
73
+ },
74
+ body: JSON.stringify({
75
+ filename,
76
+ contentType,
77
+ contentLength,
78
+ }),
79
+ });
80
+
81
+ if (!response.ok) {
82
+ const error = await response.text();
83
+ throw new Error(`failed to get upload URL: ${error}`);
84
+ }
85
+
86
+ const { uploadUrl, publicUrl, contentDisposition } = (await response.json()) as {
87
+ uploadUrl: string;
88
+ publicUrl: string;
89
+ contentDisposition?: string | undefined;
90
+ };
91
+
92
+ const uploadResponse = await fetch(uploadUrl, {
93
+ method: "PUT",
94
+ headers: {
95
+ "Content-Type": contentType,
96
+ // should be set automatically, but given this header is signed it's better to be explicit
97
+ "Content-Length": String(contentLength),
98
+ ...(contentDisposition && { "Content-Disposition": contentDisposition }),
99
+ },
100
+ body: buffer,
101
+ });
102
+
103
+ if (!uploadResponse.ok) {
104
+ throw new Error(`failed to upload file: ${uploadResponse.statusText}`);
105
+ }
106
+
107
+ log.info(`» uploaded file ${publicUrl}`);
108
+
109
+ return { success: true, publicUrl, filename, contentLength, contentType };
110
+ }),
111
+ });
112
+ }
@@ -0,0 +1,300 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ DEFAULT_PROXY_MODEL,
4
+ getModelEnvVars,
5
+ getModelProvider,
6
+ isBedrockAnthropicId,
7
+ isVertexAnthropicId,
8
+ modelAliases,
9
+ parseModel,
10
+ providers,
11
+ resolveCliModel,
12
+ resolveDisplayAlias,
13
+ resolveModelSlug,
14
+ resolveOpenRouterModel,
15
+ } from "#app/models";
16
+
17
+ describe("parseModel", () => {
18
+ it("parses provider/model format", () => {
19
+ const result = parseModel("anthropic/claude-opus");
20
+ expect(result).toEqual({ provider: "anthropic", model: "claude-opus" });
21
+ });
22
+
23
+ it("handles nested slashes (openrouter format)", () => {
24
+ const result = parseModel("openrouter/anthropic/claude-opus-4.6");
25
+ expect(result).toEqual({ provider: "openrouter", model: "anthropic/claude-opus-4.6" });
26
+ });
27
+
28
+ it("throws on invalid slug without slash", () => {
29
+ expect(() => parseModel("invalid")).toThrow("invalid model slug");
30
+ });
31
+ });
32
+
33
+ describe("getModelProvider", () => {
34
+ it("extracts provider from slug", () => {
35
+ expect(getModelProvider("anthropic/claude-opus")).toBe("anthropic");
36
+ expect(getModelProvider("openai/gpt")).toBe("openai");
37
+ expect(getModelProvider("google/gemini-pro")).toBe("google");
38
+ });
39
+ });
40
+
41
+ describe("getModelEnvVars", () => {
42
+ it("returns correct env vars for anthropic", () => {
43
+ expect(getModelEnvVars("anthropic/claude-opus")).toEqual([
44
+ "ANTHROPIC_API_KEY",
45
+ "CLAUDE_CODE_OAUTH_TOKEN",
46
+ ]);
47
+ });
48
+
49
+ it("returns correct env vars for google (multiple)", () => {
50
+ const envVars = getModelEnvVars("google/gemini-pro");
51
+ expect(envVars).toContain("GOOGLE_GENERATIVE_AI_API_KEY");
52
+ expect(envVars).toContain("GEMINI_API_KEY");
53
+ });
54
+
55
+ it("returns empty array for unknown provider", () => {
56
+ expect(getModelEnvVars("unknown/model")).toEqual([]);
57
+ });
58
+
59
+ it("returns empty env vars for free opencode models", () => {
60
+ expect(getModelEnvVars("opencode/big-pickle")).toEqual([]);
61
+ expect(getModelEnvVars("opencode/mimo-v2-pro-free")).toEqual([]);
62
+ expect(getModelEnvVars("opencode/minimax-m2.5-free")).toEqual([]);
63
+ });
64
+
65
+ it("still requires OPENCODE_API_KEY for non-free opencode models", () => {
66
+ expect(getModelEnvVars("opencode/claude-opus")).toEqual(["OPENCODE_API_KEY"]);
67
+ expect(getModelEnvVars("opencode/minimax-m2.5")).toEqual(["OPENCODE_API_KEY"]);
68
+ expect(getModelEnvVars("opencode/gpt-5-nano")).toEqual(["OPENCODE_API_KEY"]);
69
+ });
70
+ });
71
+
72
+ describe("resolveModelSlug", () => {
73
+ it("resolves known alias to concrete specifier", () => {
74
+ const resolved = resolveModelSlug("anthropic/claude-opus");
75
+ expect(resolved).toBe("anthropic/claude-opus-4-8");
76
+ });
77
+
78
+ it("resolves openai alias", () => {
79
+ const resolved = resolveModelSlug("openai/gpt");
80
+ expect(resolved).toBe("openai/gpt-5.5");
81
+ });
82
+
83
+ it("returns the raw resolve for deprecated aliases (does not walk fallback)", () => {
84
+ expect(resolveModelSlug("openai/gpt-codex")).toBe("openai/gpt-5.3-codex");
85
+ });
86
+
87
+ it("returns undefined for unknown slug", () => {
88
+ expect(resolveModelSlug("unknown/model")).toBeUndefined();
89
+ });
90
+ });
91
+
92
+ describe("resolveCliModel", () => {
93
+ it("returns same as resolveModelSlug (models.dev specifier)", () => {
94
+ const slug = "anthropic/claude-opus";
95
+ expect(resolveCliModel(slug)).toBe(resolveModelSlug(slug));
96
+ });
97
+
98
+ it("returns undefined for unknown slug", () => {
99
+ expect(resolveCliModel("bogus/nope")).toBeUndefined();
100
+ });
101
+
102
+ it("walks fallback chain for deprecated deepseek aliases", () => {
103
+ expect(resolveCliModel("deepseek/deepseek-reasoner")).toBe("deepseek/deepseek-v4-pro");
104
+ expect(resolveCliModel("deepseek/deepseek-chat")).toBe("deepseek/deepseek-v4-flash");
105
+ });
106
+
107
+ it("walks fallback chain for deprecated openai codex aliases", () => {
108
+ expect(resolveCliModel("openai/gpt-codex")).toBe("openai/gpt-5.5");
109
+ expect(resolveCliModel("openai/gpt-codex-mini")).toBe("openai/gpt-5.4-mini");
110
+ expect(resolveCliModel("opencode/gpt-codex")).toBe("opencode/gpt-5.5");
111
+ expect(resolveCliModel("openrouter/gpt-codex")).toBe("openrouter/openai/gpt-5.5");
112
+ });
113
+
114
+ it("walks fallback chain for hidden deprecated minimax-m2.5-free", () => {
115
+ expect(resolveCliModel("opencode/minimax-m2.5-free")).toBe("opencode/big-pickle");
116
+ });
117
+ });
118
+
119
+ describe("resolveDisplayAlias", () => {
120
+ it("returns the alias itself for a non-deprecated slug", () => {
121
+ const alias = resolveDisplayAlias("anthropic/claude-opus");
122
+ expect(alias?.slug).toBe("anthropic/claude-opus");
123
+ expect(alias?.displayName).toBe("Claude Opus");
124
+ });
125
+
126
+ it("walks fallback chain to terminal alias for deprecated slug", () => {
127
+ const alias = resolveDisplayAlias("openai/gpt-codex");
128
+ expect(alias?.slug).toBe("openai/gpt");
129
+ expect(alias?.displayName).toBe("GPT");
130
+ });
131
+
132
+ it("walks fallback chain for deepseek-reasoner -> deepseek-pro", () => {
133
+ const alias = resolveDisplayAlias("deepseek/deepseek-reasoner");
134
+ expect(alias?.slug).toBe("deepseek/deepseek-pro");
135
+ expect(alias?.displayName).toBe("DeepSeek Pro");
136
+ });
137
+
138
+ it("returns undefined for unknown slug", () => {
139
+ expect(resolveDisplayAlias("bogus/nope")).toBeUndefined();
140
+ });
141
+ });
142
+
143
+ describe("DEFAULT_PROXY_MODEL", () => {
144
+ it("tracks deepseek/deepseek-pro openRouterResolve", () => {
145
+ expect(DEFAULT_PROXY_MODEL).toBe(resolveOpenRouterModel("deepseek/deepseek-pro"));
146
+ });
147
+ });
148
+
149
+ describe("resolveOpenRouterModel", () => {
150
+ it("returns the openrouter specifier for a non-deprecated alias", () => {
151
+ expect(resolveOpenRouterModel("anthropic/claude-opus")).toBe(
152
+ "openrouter/anthropic/claude-opus-4.8",
153
+ );
154
+ });
155
+
156
+ it("walks fallback chain for deprecated deepseek aliases", () => {
157
+ expect(resolveOpenRouterModel("deepseek/deepseek-reasoner")).toBe(
158
+ "openrouter/deepseek/deepseek-v4-pro",
159
+ );
160
+ expect(resolveOpenRouterModel("deepseek/deepseek-chat")).toBe(
161
+ "openrouter/deepseek/deepseek-v4-flash",
162
+ );
163
+ expect(resolveOpenRouterModel("openrouter/deepseek-chat")).toBe(
164
+ "openrouter/deepseek/deepseek-v4-flash",
165
+ );
166
+ });
167
+
168
+ it("walks fallback chain for deprecated openai codex aliases", () => {
169
+ expect(resolveOpenRouterModel("openai/gpt-codex")).toBe("openrouter/openai/gpt-5.5");
170
+ expect(resolveOpenRouterModel("openai/gpt-codex-mini")).toBe("openrouter/openai/gpt-5.4-mini");
171
+ });
172
+
173
+ it("returns undefined for free opencode models with no openrouter equivalent", () => {
174
+ expect(resolveOpenRouterModel("opencode/big-pickle")).toBeUndefined();
175
+ });
176
+
177
+ it("returns undefined for unknown slug", () => {
178
+ expect(resolveOpenRouterModel("bogus/nope")).toBeUndefined();
179
+ });
180
+ });
181
+
182
+ describe("modelAliases registry", () => {
183
+ it("has at least one model per provider", () => {
184
+ for (const providerKey of Object.keys(providers)) {
185
+ const providerModels = modelAliases.filter((a) => a.provider === providerKey);
186
+ expect(providerModels.length).toBeGreaterThan(0);
187
+ }
188
+ });
189
+
190
+ it("has exactly one preferred model per provider", () => {
191
+ for (const providerKey of Object.keys(providers)) {
192
+ // routing-only providers (bedrock) deliberately have no preferred
193
+ // model — the user picks the actual model via a per-run env var, so
194
+ // there's no "preferred default" to surface to auto-select.
195
+ const aliases = modelAliases.filter((a) => a.provider === providerKey);
196
+ if (aliases.every((a) => a.routing)) continue;
197
+ const preferred = aliases.filter((a) => a.preferred);
198
+ expect(preferred.length, `${providerKey} should have exactly 1 preferred model`).toBe(1);
199
+ }
200
+ });
201
+
202
+ it("all slugs follow provider/model format", () => {
203
+ for (const alias of modelAliases) {
204
+ expect(alias.slug).toContain("/");
205
+ const parsed = parseModel(alias.slug);
206
+ expect(parsed.provider).toBe(alias.provider);
207
+ }
208
+ });
209
+
210
+ it("all resolve values follow provider/model format", () => {
211
+ for (const alias of modelAliases) {
212
+ // routing slugs use a sentinel `resolve` (e.g. "bedrock") that's never
213
+ // passed to a CLI directly — the harness reads a separate env var to
214
+ // get the real model ID. format check doesn't apply.
215
+ if (alias.routing) continue;
216
+ expect(alias.resolve).toContain("/");
217
+ }
218
+ });
219
+
220
+ it("slugs are unique", () => {
221
+ const slugs = modelAliases.map((a) => a.slug);
222
+ expect(new Set(slugs).size).toBe(slugs.length);
223
+ });
224
+ });
225
+
226
+ describe("isBedrockAnthropicId", () => {
227
+ it("matches geo-prefixed Anthropic foundation IDs", () => {
228
+ expect(isBedrockAnthropicId("eu.anthropic.claude-opus-4-7")).toBe(true);
229
+ expect(isBedrockAnthropicId("eu.anthropic.claude-sonnet-4-6")).toBe(true);
230
+ expect(isBedrockAnthropicId("global.anthropic.claude-haiku-4-5-20251001-v1:0")).toBe(true);
231
+ });
232
+
233
+ it("matches in-region Anthropic foundation IDs", () => {
234
+ expect(isBedrockAnthropicId("anthropic.claude-opus-4-7")).toBe(true);
235
+ });
236
+
237
+ it("rejects non-Anthropic foundation IDs", () => {
238
+ expect(isBedrockAnthropicId("amazon.nova-pro-v1:0")).toBe(false);
239
+ expect(isBedrockAnthropicId("eu.meta.llama4-scout-17b-instruct-v1:0")).toBe(false);
240
+ expect(isBedrockAnthropicId("deepseek.v3.2")).toBe(false);
241
+ });
242
+
243
+ // regression: PR #720 review caught that a substring-only match was
244
+ // fragile for inference-profile ARNs (which BEDROCK_MODEL_ID accepts per
245
+ // the AWS docs). ARN names are user-chosen — both directions of the
246
+ // heuristic could break depending on what name the operator picked.
247
+ // We anchor on a discrete dot-segment match (case-insensitive) instead.
248
+ it("ignores 'anthropic' substrings inside non-segment text", () => {
249
+ // ARN whose user-chosen profile name happens to contain "anthropic" as
250
+ // part of a longer word — would route to claude-code under naive
251
+ // includes("anthropic") even though the backing model is unknown.
252
+ expect(
253
+ isBedrockAnthropicId(
254
+ "arn:aws:bedrock:eu-west-2:123456789012:application-inference-profile/my-anthropicish-profile",
255
+ ),
256
+ ).toBe(false);
257
+ });
258
+
259
+ it("matches when 'anthropic' appears as its own dot-segment in ARN", () => {
260
+ // ARN whose profile name embeds the foundation segment correctly —
261
+ // operator chose to surface the backing model in the name.
262
+ expect(
263
+ isBedrockAnthropicId(
264
+ "arn:aws:bedrock:eu-west-2:123456789012:application-inference-profile/anthropic.claude-opus-4-7",
265
+ ),
266
+ ).toBe(true);
267
+ });
268
+
269
+ it("is case-insensitive", () => {
270
+ expect(isBedrockAnthropicId("US.ANTHROPIC.CLAUDE-OPUS-4-7")).toBe(true);
271
+ });
272
+ });
273
+
274
+ describe("isVertexAnthropicId", () => {
275
+ it("matches Claude Vertex IDs by anchored prefix", () => {
276
+ expect(isVertexAnthropicId("claude-opus-4-1@20250805")).toBe(true);
277
+ });
278
+
279
+ it("rejects Gemini IDs", () => {
280
+ expect(isVertexAnthropicId("gemini-2.5-pro")).toBe(false);
281
+ });
282
+
283
+ it("ignores Anthropic substrings outside the prefix", () => {
284
+ expect(isVertexAnthropicId("publishers/anthropic/models/claude-opus-4-1")).toBe(false);
285
+ });
286
+ });
287
+
288
+ describe("providers registry", () => {
289
+ it("every provider has envVars", () => {
290
+ for (const [key, config] of Object.entries(providers)) {
291
+ expect(config.envVars.length, `${key} should have env vars`).toBeGreaterThan(0);
292
+ }
293
+ });
294
+
295
+ it("every provider has a displayName", () => {
296
+ for (const [key, config] of Object.entries(providers)) {
297
+ expect(config.displayName, `${key} should have a displayName`).toBeTruthy();
298
+ }
299
+ });
300
+ });