toolroll 0.5.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 (626) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1113 -0
  3. package/THIRD_PARTY_NOTICES.md +449 -0
  4. package/dist/accent-colors.d.ts +48 -0
  5. package/dist/accent-colors.js +122 -0
  6. package/dist/action-ledger.d.ts +26 -0
  7. package/dist/action-ledger.js +75 -0
  8. package/dist/agent-fence.d.ts +42 -0
  9. package/dist/agent-fence.js +183 -0
  10. package/dist/agentconfig.d.ts +216 -0
  11. package/dist/agentconfig.js +454 -0
  12. package/dist/api-tokens.d.ts +16 -0
  13. package/dist/api-tokens.js +27 -0
  14. package/dist/approval-policy.d.ts +69 -0
  15. package/dist/approval-policy.js +137 -0
  16. package/dist/approval-rules-ui.d.ts +25 -0
  17. package/dist/approval-rules-ui.js +39 -0
  18. package/dist/assignment-adapters.d.ts +180 -0
  19. package/dist/assignment-adapters.js +239 -0
  20. package/dist/assignment-brief.d.ts +71 -0
  21. package/dist/assignment-brief.js +129 -0
  22. package/dist/assignment-delivery.d.ts +56 -0
  23. package/dist/assignment-delivery.js +160 -0
  24. package/dist/assignment-presentation.d.ts +26 -0
  25. package/dist/assignment-presentation.js +80 -0
  26. package/dist/assignment-status.d.ts +62 -0
  27. package/dist/assignment-status.js +152 -0
  28. package/dist/assignment-ui.d.ts +71 -0
  29. package/dist/assignment-ui.js +103 -0
  30. package/dist/assignment.d.ts +222 -0
  31. package/dist/assignment.js +399 -0
  32. package/dist/attest.d.ts +56 -0
  33. package/dist/attest.js +153 -0
  34. package/dist/backend.d.ts +97 -0
  35. package/dist/backend.js +166 -0
  36. package/dist/backup-ui.d.ts +22 -0
  37. package/dist/backup-ui.js +59 -0
  38. package/dist/backup.d.ts +84 -0
  39. package/dist/backup.js +421 -0
  40. package/dist/beads.d.ts +34 -0
  41. package/dist/beads.js +135 -0
  42. package/dist/bin.d.ts +2 -0
  43. package/dist/bin.js +22 -0
  44. package/dist/board.d.ts +150 -0
  45. package/dist/board.js +210 -0
  46. package/dist/boot-identity.d.ts +63 -0
  47. package/dist/boot-identity.js +99 -0
  48. package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
  49. package/dist/browser/workspace.css +4 -0
  50. package/dist/browser/workspace.js +225 -0
  51. package/dist/browser-crew.d.ts +11 -0
  52. package/dist/browser-crew.js +50 -0
  53. package/dist/browser-shell.d.ts +8 -0
  54. package/dist/browser-shell.js +60 -0
  55. package/dist/browser-workspace.d.ts +780 -0
  56. package/dist/browser-workspace.js +47 -0
  57. package/dist/budget-alerts.d.ts +19 -0
  58. package/dist/budget-alerts.js +46 -0
  59. package/dist/builder.d.ts +370 -0
  60. package/dist/builder.js +3262 -0
  61. package/dist/capscan.d.ts +35 -0
  62. package/dist/capscan.js +113 -0
  63. package/dist/chat-acceptance.d.ts +71 -0
  64. package/dist/chat-acceptance.js +120 -0
  65. package/dist/chat-actions.d.ts +265 -0
  66. package/dist/chat-actions.js +1273 -0
  67. package/dist/chat-channel.d.ts +109 -0
  68. package/dist/chat-channel.js +652 -0
  69. package/dist/chat-continuity.d.ts +32 -0
  70. package/dist/chat-continuity.js +317 -0
  71. package/dist/chat-controls.d.ts +113 -0
  72. package/dist/chat-controls.js +53 -0
  73. package/dist/chat-delivery-state.d.ts +159 -0
  74. package/dist/chat-delivery-state.js +309 -0
  75. package/dist/chat-delivery.d.ts +38 -0
  76. package/dist/chat-delivery.js +693 -0
  77. package/dist/chat-display.d.ts +1 -0
  78. package/dist/chat-display.js +18 -0
  79. package/dist/chat-evidence.d.ts +123 -0
  80. package/dist/chat-evidence.js +207 -0
  81. package/dist/chat-flow.d.ts +44 -0
  82. package/dist/chat-flow.js +215 -0
  83. package/dist/chat-inbox.d.ts +57 -0
  84. package/dist/chat-inbox.js +123 -0
  85. package/dist/chat-polish.d.ts +16 -0
  86. package/dist/chat-polish.js +135 -0
  87. package/dist/chat-review.d.ts +46 -0
  88. package/dist/chat-review.js +98 -0
  89. package/dist/chat-rooms.d.ts +123 -0
  90. package/dist/chat-rooms.js +216 -0
  91. package/dist/chat-task-actions.d.ts +44 -0
  92. package/dist/chat-task-actions.js +79 -0
  93. package/dist/check-progress.d.ts +51 -0
  94. package/dist/check-progress.js +290 -0
  95. package/dist/child-database.d.ts +12 -0
  96. package/dist/child-database.js +30 -0
  97. package/dist/claim.d.ts +688 -0
  98. package/dist/claim.js +1740 -0
  99. package/dist/cli.d.ts +137 -0
  100. package/dist/cli.js +1461 -0
  101. package/dist/codex-limits.d.ts +15 -0
  102. package/dist/codex-limits.js +145 -0
  103. package/dist/coding-context.d.ts +55 -0
  104. package/dist/coding-context.js +92 -0
  105. package/dist/coding-handoff.d.ts +57 -0
  106. package/dist/coding-handoff.js +277 -0
  107. package/dist/coding-provider.d.ts +72 -0
  108. package/dist/coding-provider.js +424 -0
  109. package/dist/coding-shipping-ui.d.ts +4 -0
  110. package/dist/coding-shipping-ui.js +12 -0
  111. package/dist/coding-types.d.ts +62 -0
  112. package/dist/coding-types.js +1 -0
  113. package/dist/coding-ui.d.ts +17 -0
  114. package/dist/coding-ui.js +364 -0
  115. package/dist/coding-update.d.ts +15 -0
  116. package/dist/coding-update.js +149 -0
  117. package/dist/coding-workspace.d.ts +124 -0
  118. package/dist/coding-workspace.js +768 -0
  119. package/dist/container-state.d.ts +5 -0
  120. package/dist/container-state.js +62 -0
  121. package/dist/containment.d.ts +197 -0
  122. package/dist/containment.js +559 -0
  123. package/dist/contest.d.ts +318 -0
  124. package/dist/contest.js +753 -0
  125. package/dist/control-setup.d.ts +35 -0
  126. package/dist/control-setup.js +40 -0
  127. package/dist/control-ui.d.ts +29 -0
  128. package/dist/control-ui.js +53 -0
  129. package/dist/controller-service.d.ts +1 -0
  130. package/dist/controller-service.js +19 -0
  131. package/dist/controller-supervisor.d.ts +30 -0
  132. package/dist/controller-supervisor.js +104 -0
  133. package/dist/converse.d.ts +304 -0
  134. package/dist/converse.js +849 -0
  135. package/dist/coordinator-proposals.d.ts +29 -0
  136. package/dist/coordinator-proposals.js +137 -0
  137. package/dist/coordinator.d.ts +186 -0
  138. package/dist/coordinator.js +447 -0
  139. package/dist/credentials-ui.d.ts +25 -0
  140. package/dist/credentials-ui.js +48 -0
  141. package/dist/daemon.d.ts +209 -0
  142. package/dist/daemon.js +604 -0
  143. package/dist/decision.d.ts +120 -0
  144. package/dist/decision.js +388 -0
  145. package/dist/demo.d.ts +55 -0
  146. package/dist/demo.js +992 -0
  147. package/dist/desktop-access.d.ts +28 -0
  148. package/dist/desktop-access.js +88 -0
  149. package/dist/desktop-bundle.d.ts +16 -0
  150. package/dist/desktop-bundle.js +74 -0
  151. package/dist/desktop-host.d.ts +54 -0
  152. package/dist/desktop-host.js +508 -0
  153. package/dist/desktop-update-gate.d.ts +12 -0
  154. package/dist/desktop-update-gate.js +91 -0
  155. package/dist/desktop-update-recovery.d.ts +14 -0
  156. package/dist/desktop-update-recovery.js +243 -0
  157. package/dist/desktop-update.d.ts +110 -0
  158. package/dist/desktop-update.js +740 -0
  159. package/dist/discord-api.d.ts +20 -0
  160. package/dist/discord-api.js +138 -0
  161. package/dist/discord-chat.d.ts +16 -0
  162. package/dist/discord-chat.js +381 -0
  163. package/dist/discord-settings.d.ts +7 -0
  164. package/dist/discord-settings.js +75 -0
  165. package/dist/discord.d.ts +10 -0
  166. package/dist/discord.js +169 -0
  167. package/dist/discover.d.ts +75 -0
  168. package/dist/discover.js +150 -0
  169. package/dist/dispatch.d.ts +100 -0
  170. package/dist/dispatch.js +311 -0
  171. package/dist/dispose.d.ts +161 -0
  172. package/dist/dispose.js +803 -0
  173. package/dist/email-settings.d.ts +34 -0
  174. package/dist/email-settings.js +50 -0
  175. package/dist/envelope.d.ts +47 -0
  176. package/dist/envelope.js +105 -0
  177. package/dist/evidence-pack.d.ts +185 -0
  178. package/dist/evidence-pack.js +258 -0
  179. package/dist/evidence.d.ts +345 -0
  180. package/dist/evidence.js +782 -0
  181. package/dist/exec.d.ts +285 -0
  182. package/dist/exec.js +1841 -0
  183. package/dist/exhaustion.d.ts +85 -0
  184. package/dist/exhaustion.js +141 -0
  185. package/dist/export-ui.d.ts +4 -0
  186. package/dist/export-ui.js +17 -0
  187. package/dist/export.d.ts +48 -0
  188. package/dist/export.js +305 -0
  189. package/dist/flow-actions.d.ts +77 -0
  190. package/dist/flow-actions.js +203 -0
  191. package/dist/flow-code.d.ts +50 -0
  192. package/dist/flow-code.js +159 -0
  193. package/dist/flow-draft.d.ts +38 -0
  194. package/dist/flow-draft.js +80 -0
  195. package/dist/flow-engine.d.ts +66 -0
  196. package/dist/flow-engine.js +416 -0
  197. package/dist/flow-insights.d.ts +95 -0
  198. package/dist/flow-insights.js +149 -0
  199. package/dist/flow-live.d.ts +24 -0
  200. package/dist/flow-live.js +150 -0
  201. package/dist/flow-people.d.ts +24 -0
  202. package/dist/flow-people.js +91 -0
  203. package/dist/flow-replies.d.ts +48 -0
  204. package/dist/flow-replies.js +113 -0
  205. package/dist/flow-scripts.d.ts +38 -0
  206. package/dist/flow-scripts.js +73 -0
  207. package/dist/flow-secrets.d.ts +13 -0
  208. package/dist/flow-secrets.js +45 -0
  209. package/dist/flow-sort.d.ts +82 -0
  210. package/dist/flow-sort.js +153 -0
  211. package/dist/flow-steps.d.ts +40 -0
  212. package/dist/flow-steps.js +408 -0
  213. package/dist/flow-triggers.d.ts +244 -0
  214. package/dist/flow-triggers.js +959 -0
  215. package/dist/flows-ui.d.ts +20 -0
  216. package/dist/flows-ui.js +175 -0
  217. package/dist/flows.d.ts +229 -0
  218. package/dist/flows.js +968 -0
  219. package/dist/fonts.d.ts +19 -0
  220. package/dist/fonts.js +19 -0
  221. package/dist/gaps.d.ts +35 -0
  222. package/dist/gaps.js +101 -0
  223. package/dist/gate-failure.d.ts +40 -0
  224. package/dist/gate-failure.js +66 -0
  225. package/dist/git.d.ts +54 -0
  226. package/dist/git.js +94 -0
  227. package/dist/google-mail.d.ts +58 -0
  228. package/dist/google-mail.js +151 -0
  229. package/dist/grant.d.ts +136 -0
  230. package/dist/grant.js +238 -0
  231. package/dist/graph.d.ts +164 -0
  232. package/dist/graph.js +383 -0
  233. package/dist/guides.d.ts +22 -0
  234. package/dist/guides.js +363 -0
  235. package/dist/held.d.ts +150 -0
  236. package/dist/held.js +799 -0
  237. package/dist/invoke.d.ts +112 -0
  238. package/dist/invoke.js +780 -0
  239. package/dist/issues.d.ts +41 -0
  240. package/dist/issues.js +131 -0
  241. package/dist/job-object-helper.ps1 +252 -0
  242. package/dist/jsonl-discriminants.d.ts +11 -0
  243. package/dist/jsonl-discriminants.js +93 -0
  244. package/dist/keys.d.ts +104 -0
  245. package/dist/keys.js +229 -0
  246. package/dist/kits-ui.d.ts +17 -0
  247. package/dist/kits-ui.js +46 -0
  248. package/dist/kits.d.ts +86 -0
  249. package/dist/kits.js +215 -0
  250. package/dist/knowledge-cli.d.ts +101 -0
  251. package/dist/knowledge-cli.js +92 -0
  252. package/dist/knowledge-ui.d.ts +24 -0
  253. package/dist/knowledge-ui.js +40 -0
  254. package/dist/lead-context.d.ts +8 -0
  255. package/dist/lead-context.js +56 -0
  256. package/dist/lead-follow.d.ts +22 -0
  257. package/dist/lead-follow.js +167 -0
  258. package/dist/lead-status.d.ts +75 -0
  259. package/dist/lead-status.js +285 -0
  260. package/dist/ledger-chain.d.ts +46 -0
  261. package/dist/ledger-chain.js +187 -0
  262. package/dist/ledger-csv.d.ts +4 -0
  263. package/dist/ledger-csv.js +11 -0
  264. package/dist/ledger-view.d.ts +24 -0
  265. package/dist/ledger-view.js +58 -0
  266. package/dist/limits-ui.d.ts +26 -0
  267. package/dist/limits-ui.js +67 -0
  268. package/dist/link.d.ts +72 -0
  269. package/dist/link.js +217 -0
  270. package/dist/live.d.ts +96 -0
  271. package/dist/live.js +366 -0
  272. package/dist/liveness.d.ts +30 -0
  273. package/dist/liveness.js +42 -0
  274. package/dist/log.d.ts +7 -0
  275. package/dist/log.js +25 -0
  276. package/dist/mailbox.d.ts +57 -0
  277. package/dist/mailbox.js +150 -0
  278. package/dist/maintenance.d.ts +11 -0
  279. package/dist/maintenance.js +35 -0
  280. package/dist/mate-cli.d.ts +52 -0
  281. package/dist/mate-cli.js +345 -0
  282. package/dist/mate-contract.d.ts +10 -0
  283. package/dist/mate-contract.js +30 -0
  284. package/dist/mate-doors.d.ts +69 -0
  285. package/dist/mate-doors.js +548 -0
  286. package/dist/mate-progress.d.ts +29 -0
  287. package/dist/mate-progress.js +121 -0
  288. package/dist/mate-tools.d.ts +94 -0
  289. package/dist/mate-tools.js +1878 -0
  290. package/dist/mate.d.ts +92 -0
  291. package/dist/mate.js +435 -0
  292. package/dist/mcp-connect.d.ts +103 -0
  293. package/dist/mcp-connect.js +252 -0
  294. package/dist/mcp.d.ts +27 -0
  295. package/dist/mcp.js +651 -0
  296. package/dist/memory-cli.d.ts +466 -0
  297. package/dist/memory-cli.js +111 -0
  298. package/dist/memory-pass.d.ts +160 -0
  299. package/dist/memory-pass.js +409 -0
  300. package/dist/metrics.d.ts +3 -0
  301. package/dist/metrics.js +56 -0
  302. package/dist/mobile-viewport.d.ts +3 -0
  303. package/dist/mobile-viewport.js +37 -0
  304. package/dist/model-catalog.d.ts +118 -0
  305. package/dist/model-catalog.js +376 -0
  306. package/dist/models-cli.d.ts +102 -0
  307. package/dist/models-cli.js +66 -0
  308. package/dist/models-ui.d.ts +34 -0
  309. package/dist/models-ui.js +68 -0
  310. package/dist/modes.d.ts +80 -0
  311. package/dist/modes.js +182 -0
  312. package/dist/monitoring-settings.d.ts +44 -0
  313. package/dist/monitoring-settings.js +173 -0
  314. package/dist/monitoring-ui.d.ts +20 -0
  315. package/dist/monitoring-ui.js +51 -0
  316. package/dist/monitoring.d.ts +34 -0
  317. package/dist/monitoring.js +221 -0
  318. package/dist/names.d.ts +67 -0
  319. package/dist/names.js +102 -0
  320. package/dist/observations.d.ts +40 -0
  321. package/dist/observations.js +211 -0
  322. package/dist/oidc.d.ts +71 -0
  323. package/dist/oidc.js +182 -0
  324. package/dist/onboard.d.ts +108 -0
  325. package/dist/onboard.js +325 -0
  326. package/dist/openrouter-models.d.ts +30 -0
  327. package/dist/openrouter-models.js +120 -0
  328. package/dist/operate.d.ts +118 -0
  329. package/dist/operate.js +11321 -0
  330. package/dist/peek-cli.d.ts +97 -0
  331. package/dist/peek-cli.js +215 -0
  332. package/dist/peek.d.ts +134 -0
  333. package/dist/peek.js +578 -0
  334. package/dist/phase-routing.d.ts +307 -0
  335. package/dist/phase-routing.js +658 -0
  336. package/dist/plan-auto.d.ts +14 -0
  337. package/dist/plan-auto.js +92 -0
  338. package/dist/plan.d.ts +186 -0
  339. package/dist/plan.js +401 -0
  340. package/dist/planner-source.d.ts +251 -0
  341. package/dist/planner-source.js +460 -0
  342. package/dist/planner.d.ts +75 -0
  343. package/dist/planner.js +992 -0
  344. package/dist/policy-ui.d.ts +20 -0
  345. package/dist/policy-ui.js +41 -0
  346. package/dist/policy.d.ts +111 -0
  347. package/dist/policy.js +231 -0
  348. package/dist/prepared-evidence.d.ts +16 -0
  349. package/dist/prepared-evidence.js +96 -0
  350. package/dist/pricing.d.ts +45 -0
  351. package/dist/pricing.js +77 -0
  352. package/dist/principal.d.ts +42 -0
  353. package/dist/principal.js +82 -0
  354. package/dist/probe.d.ts +46 -0
  355. package/dist/probe.js +79 -0
  356. package/dist/process-custody.d.ts +17 -0
  357. package/dist/process-custody.js +91 -0
  358. package/dist/process-liveness.d.ts +10 -0
  359. package/dist/process-liveness.js +81 -0
  360. package/dist/process-recovery-anchor.d.ts +75 -0
  361. package/dist/process-recovery-anchor.js +188 -0
  362. package/dist/process-recovery-coalition.d.ts +25 -0
  363. package/dist/process-recovery-coalition.js +105 -0
  364. package/dist/process-recovery-eligibility.d.ts +178 -0
  365. package/dist/process-recovery-eligibility.js +170 -0
  366. package/dist/process-recovery-native.d.ts +172 -0
  367. package/dist/process-recovery-native.js +592 -0
  368. package/dist/process-recovery-provenance.d.ts +163 -0
  369. package/dist/process-recovery-provenance.js +292 -0
  370. package/dist/process-recovery-services.d.ts +56 -0
  371. package/dist/process-recovery-services.js +191 -0
  372. package/dist/process-recovery-settlement.d.ts +32 -0
  373. package/dist/process-recovery-settlement.js +132 -0
  374. package/dist/process-recovery.d.ts +41 -0
  375. package/dist/process-recovery.js +91 -0
  376. package/dist/process-tree.d.ts +41 -0
  377. package/dist/process-tree.js +264 -0
  378. package/dist/project-access.d.ts +14 -0
  379. package/dist/project-access.js +34 -0
  380. package/dist/project-cli.d.ts +18 -0
  381. package/dist/project-cli.js +104 -0
  382. package/dist/project-delete-ui.d.ts +22 -0
  383. package/dist/project-delete-ui.js +48 -0
  384. package/dist/project-delete.d.ts +55 -0
  385. package/dist/project-delete.js +413 -0
  386. package/dist/project-knowledge.d.ts +111 -0
  387. package/dist/project-knowledge.js +241 -0
  388. package/dist/project-learning.d.ts +100 -0
  389. package/dist/project-learning.js +438 -0
  390. package/dist/project-memory.d.ts +81 -0
  391. package/dist/project-memory.js +264 -0
  392. package/dist/project-skills.d.ts +152 -0
  393. package/dist/project-skills.js +660 -0
  394. package/dist/project-tools.d.ts +219 -0
  395. package/dist/project-tools.js +796 -0
  396. package/dist/project.d.ts +61 -0
  397. package/dist/project.js +125 -0
  398. package/dist/prompt.d.ts +22 -0
  399. package/dist/prompt.js +72 -0
  400. package/dist/proof.d.ts +513 -0
  401. package/dist/proof.js +1140 -0
  402. package/dist/proposal.d.ts +82 -0
  403. package/dist/proposal.js +210 -0
  404. package/dist/provider-connection.d.ts +22 -0
  405. package/dist/provider-connection.js +98 -0
  406. package/dist/provider-limits.d.ts +35 -0
  407. package/dist/provider-limits.js +95 -0
  408. package/dist/provider.d.ts +279 -0
  409. package/dist/provider.js +935 -0
  410. package/dist/publish.d.ts +107 -0
  411. package/dist/publish.js +650 -0
  412. package/dist/pulls.d.ts +118 -0
  413. package/dist/pulls.js +240 -0
  414. package/dist/push.d.ts +95 -0
  415. package/dist/push.js +353 -0
  416. package/dist/quality.d.ts +8 -0
  417. package/dist/quality.js +6 -0
  418. package/dist/recipe-ui.d.ts +18 -0
  419. package/dist/recipe-ui.js +152 -0
  420. package/dist/recipes.d.ts +81 -0
  421. package/dist/recipes.js +332 -0
  422. package/dist/remote.d.ts +67 -0
  423. package/dist/remote.js +122 -0
  424. package/dist/render.d.ts +64 -0
  425. package/dist/render.js +587 -0
  426. package/dist/report-summary.d.ts +13 -0
  427. package/dist/report-summary.js +15 -0
  428. package/dist/repos.d.ts +65 -0
  429. package/dist/repos.js +277 -0
  430. package/dist/repository-context-ui.d.ts +2 -0
  431. package/dist/repository-context-ui.js +10 -0
  432. package/dist/repository-context.d.ts +67 -0
  433. package/dist/repository-context.js +376 -0
  434. package/dist/restart-certification.d.ts +134 -0
  435. package/dist/restart-certification.js +226 -0
  436. package/dist/result-actions.d.ts +28 -0
  437. package/dist/result-actions.js +270 -0
  438. package/dist/result-completion.d.ts +18 -0
  439. package/dist/result-completion.js +66 -0
  440. package/dist/result-review.d.ts +213 -0
  441. package/dist/result-review.js +430 -0
  442. package/dist/retention-ui.d.ts +12 -0
  443. package/dist/retention-ui.js +39 -0
  444. package/dist/retention.d.ts +72 -0
  445. package/dist/retention.js +288 -0
  446. package/dist/review-context.d.ts +243 -0
  447. package/dist/review-context.js +1043 -0
  448. package/dist/review-evidence.d.ts +33 -0
  449. package/dist/review-evidence.js +56 -0
  450. package/dist/reviewer.d.ts +97 -0
  451. package/dist/reviewer.js +202 -0
  452. package/dist/routine.d.ts +228 -0
  453. package/dist/routine.js +764 -0
  454. package/dist/runner.d.ts +266 -0
  455. package/dist/runner.js +399 -0
  456. package/dist/scan.d.ts +32 -0
  457. package/dist/scan.js +92 -0
  458. package/dist/scope.d.ts +695 -0
  459. package/dist/scope.js +1176 -0
  460. package/dist/scout-report.d.ts +42 -0
  461. package/dist/scout-report.js +108 -0
  462. package/dist/scout.d.ts +69 -0
  463. package/dist/scout.js +351 -0
  464. package/dist/serve.d.ts +313 -0
  465. package/dist/serve.js +21776 -0
  466. package/dist/session-brief.d.ts +8 -0
  467. package/dist/session-brief.js +24 -0
  468. package/dist/session-cli.d.ts +13 -0
  469. package/dist/session-cli.js +284 -0
  470. package/dist/session-contract.d.ts +183 -0
  471. package/dist/session-contract.js +122 -0
  472. package/dist/session-http.d.ts +19 -0
  473. package/dist/session-http.js +154 -0
  474. package/dist/session-server.d.ts +12 -0
  475. package/dist/session-server.js +37 -0
  476. package/dist/session-service.d.ts +22 -0
  477. package/dist/session-service.js +157 -0
  478. package/dist/setup-guide.d.ts +39 -0
  479. package/dist/setup-guide.js +93 -0
  480. package/dist/sign-in-guard.d.ts +45 -0
  481. package/dist/sign-in-guard.js +74 -0
  482. package/dist/skills-ui.d.ts +11 -0
  483. package/dist/skills-ui.js +58 -0
  484. package/dist/skills.d.ts +97 -0
  485. package/dist/skills.js +276 -0
  486. package/dist/slack-api.d.ts +55 -0
  487. package/dist/slack-api.js +229 -0
  488. package/dist/slack-chat.d.ts +28 -0
  489. package/dist/slack-chat.js +413 -0
  490. package/dist/slack-settings.d.ts +7 -0
  491. package/dist/slack-settings.js +75 -0
  492. package/dist/slack-state.d.ts +13 -0
  493. package/dist/slack-state.js +9 -0
  494. package/dist/slack.d.ts +13 -0
  495. package/dist/slack.js +167 -0
  496. package/dist/spend-ui.d.ts +28 -0
  497. package/dist/spend-ui.js +95 -0
  498. package/dist/spend.d.ts +142 -0
  499. package/dist/spend.js +313 -0
  500. package/dist/sqlite-runtime.d.ts +3 -0
  501. package/dist/sqlite-runtime.js +6 -0
  502. package/dist/sso-settings.d.ts +33 -0
  503. package/dist/sso-settings.js +103 -0
  504. package/dist/sso-ui.d.ts +22 -0
  505. package/dist/sso-ui.js +39 -0
  506. package/dist/storage.d.ts +15 -0
  507. package/dist/storage.js +84 -0
  508. package/dist/store.d.ts +7754 -0
  509. package/dist/store.js +23279 -0
  510. package/dist/structured-output.d.ts +58 -0
  511. package/dist/structured-output.js +103 -0
  512. package/dist/style-asset.d.ts +7 -0
  513. package/dist/style-asset.js +41 -0
  514. package/dist/subscription-chat.d.ts +46 -0
  515. package/dist/subscription-chat.js +275 -0
  516. package/dist/summary.d.ts +33 -0
  517. package/dist/summary.js +71 -0
  518. package/dist/supervisor.mjs +297 -0
  519. package/dist/surface.d.ts +63 -0
  520. package/dist/surface.js +352 -0
  521. package/dist/sync.d.ts +94 -0
  522. package/dist/sync.js +279 -0
  523. package/dist/task-composer.d.ts +13 -0
  524. package/dist/task-composer.js +52 -0
  525. package/dist/task-control.d.ts +165 -0
  526. package/dist/task-control.js +214 -0
  527. package/dist/task-outcome-cli.d.ts +10 -0
  528. package/dist/task-outcome-cli.js +92 -0
  529. package/dist/task-text.d.ts +30 -0
  530. package/dist/task-text.js +41 -0
  531. package/dist/team-cli.d.ts +15 -0
  532. package/dist/team-cli.js +618 -0
  533. package/dist/team-contract.d.ts +92 -0
  534. package/dist/team-contract.js +1 -0
  535. package/dist/team-http.d.ts +17 -0
  536. package/dist/team-http.js +144 -0
  537. package/dist/team-leads.d.ts +81 -0
  538. package/dist/team-leads.js +565 -0
  539. package/dist/team-runtime.d.ts +27 -0
  540. package/dist/team-runtime.js +264 -0
  541. package/dist/team-ui.d.ts +3 -0
  542. package/dist/team-ui.js +9 -0
  543. package/dist/team-updates.d.ts +8 -0
  544. package/dist/team-updates.js +57 -0
  545. package/dist/teammate-admin.d.ts +53 -0
  546. package/dist/teammate-admin.js +111 -0
  547. package/dist/teammate-desk.d.ts +60 -0
  548. package/dist/teammate-desk.js +154 -0
  549. package/dist/teammate-memory.d.ts +64 -0
  550. package/dist/teammate-memory.js +196 -0
  551. package/dist/teammate-question.d.ts +59 -0
  552. package/dist/teammate-question.js +175 -0
  553. package/dist/teammate-tools.d.ts +108 -0
  554. package/dist/teammate-tools.js +271 -0
  555. package/dist/teammate-week.d.ts +68 -0
  556. package/dist/teammate-week.js +132 -0
  557. package/dist/teammate-work.d.ts +76 -0
  558. package/dist/teammate-work.js +280 -0
  559. package/dist/teammates-ui.d.ts +20 -0
  560. package/dist/teammates-ui.js +166 -0
  561. package/dist/teammates.d.ts +180 -0
  562. package/dist/teammates.js +253 -0
  563. package/dist/teams-api.d.ts +33 -0
  564. package/dist/teams-api.js +204 -0
  565. package/dist/teams-chat.d.ts +26 -0
  566. package/dist/teams-chat.js +215 -0
  567. package/dist/teams-settings.d.ts +12 -0
  568. package/dist/teams-settings.js +57 -0
  569. package/dist/teams.d.ts +21 -0
  570. package/dist/teams.js +124 -0
  571. package/dist/telegram-flow.d.ts +49 -0
  572. package/dist/telegram-flow.js +110 -0
  573. package/dist/telegram-mate.d.ts +137 -0
  574. package/dist/telegram-mate.js +707 -0
  575. package/dist/telegram-progress.d.ts +14 -0
  576. package/dist/telegram-progress.js +129 -0
  577. package/dist/telegram-settings.d.ts +9 -0
  578. package/dist/telegram-settings.js +36 -0
  579. package/dist/telegram-status.d.ts +75 -0
  580. package/dist/telegram-status.js +265 -0
  581. package/dist/telegram-team.d.ts +85 -0
  582. package/dist/telegram-team.js +359 -0
  583. package/dist/telegram.d.ts +230 -0
  584. package/dist/telegram.js +1931 -0
  585. package/dist/templates.d.ts +65 -0
  586. package/dist/templates.js +118 -0
  587. package/dist/tool-launcher.d.ts +1 -0
  588. package/dist/tool-launcher.js +101 -0
  589. package/dist/tools-ui.d.ts +30 -0
  590. package/dist/tools-ui.js +40 -0
  591. package/dist/transitions-recipes.d.ts +1 -0
  592. package/dist/transitions-recipes.js +203 -0
  593. package/dist/tree-proof.d.ts +34 -0
  594. package/dist/tree-proof.js +58 -0
  595. package/dist/verification-evidence.d.ts +44 -0
  596. package/dist/verification-evidence.js +238 -0
  597. package/dist/version.d.ts +2 -0
  598. package/dist/version.js +10 -0
  599. package/dist/webhooks.d.ts +91 -0
  600. package/dist/webhooks.js +283 -0
  601. package/dist/work-index.d.ts +68 -0
  602. package/dist/work-index.js +384 -0
  603. package/dist/work-summary.d.ts +57 -0
  604. package/dist/work-summary.js +116 -0
  605. package/dist/workspace-motion.d.ts +4 -0
  606. package/dist/workspace-motion.js +133 -0
  607. package/dist/workspace-revision.d.ts +19 -0
  608. package/dist/workspace-revision.js +92 -0
  609. package/dist/workspace-ui.d.ts +204 -0
  610. package/dist/workspace-ui.js +411 -0
  611. package/dist/worktree-notices.d.ts +13 -0
  612. package/dist/worktree-notices.js +17 -0
  613. package/dist/worktree.d.ts +249 -0
  614. package/dist/worktree.js +720 -0
  615. package/package.json +122 -0
  616. package/scripts/canary-assertions.mjs +42 -0
  617. package/scripts/crash-canary.mjs +253 -0
  618. package/scripts/fixtures/crash-process.mjs +74 -0
  619. package/scripts/fixtures/pilot-scenarios.mjs +85 -0
  620. package/scripts/fixtures/restart-service.mjs +27 -0
  621. package/scripts/launchd-certification.mjs +143 -0
  622. package/scripts/pilot.mjs +121 -0
  623. package/scripts/proof-preflight.mjs +140 -0
  624. package/scripts/provider-canary.mjs +379 -0
  625. package/scripts/recovery-canary.mjs +135 -0
  626. package/scripts/restart-certification.mjs +98 -0
@@ -0,0 +1,796 @@
1
+ /**
2
+ * Project tools (v80): the MCP servers one project's builds may use.
3
+ *
4
+ * A project's list REPLACES everything else a build could load: the
5
+ * operator's global Claude and Codex MCP servers, plugins' servers and a
6
+ * repository's own `.mcp.json` are all shut out, and each build gets
7
+ * exactly the servers its project lists — sealed at approval, so a tool
8
+ * added later reaches only work approved after it, while a tool removed
9
+ * leaves every build at once. Reviewers and the lead chat never get tools.
10
+ *
11
+ * Secret values (API keys, tokens, any env value) never enter the
12
+ * database, a chat, a card or a log: they live in one 0600 file per tool
13
+ * under ~/.toolroll/tool-secrets/ (or an older ~/.standing-orders/), are written only from the
14
+ * console's password-gated screen, and reach exactly that project's
15
+ * server processes at launch.
16
+ */
17
+ import { toolRefusal } from "./policy.js";
18
+ import { createHash } from "node:crypto";
19
+ import { spawn } from "node:child_process";
20
+ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
21
+ import { homedir, tmpdir } from "node:os";
22
+ import { isAbsolute, join } from "node:path";
23
+ import { fileURLToPath } from "node:url";
24
+ import { scanForSecrets } from "./evidence.js";
25
+ import { namedPath } from "./names.js";
26
+ const NAME = /^[a-z0-9][a-z0-9_-]{0,39}$/;
27
+ const SECRET_NAME = /^[A-Z][A-Z0-9_]{0,63}$/;
28
+ const HEADER_NAME = /^[A-Za-z0-9-]{1,64}$/;
29
+ /** Env names a tool may never claim: the process's own plumbing, every
30
+ * provider credential, and anything that changes how a program or its
31
+ * interpreter loads (a secret's value must stay data, never code). */
32
+ const RESERVED_ENV_NAMES = new Set(["PATH", "HOME", "USER", "LOGNAME", "SHELL", "LANG", "TMPDIR", "TERM", "PWD", "IFS", "ENV", "BASH_ENV", "PS4", "PROMPT_COMMAND",
33
+ "NODE_OPTIONS", "NODE_PATH", "NODE_EXTRA_CA_CERTS", "NPM_CONFIG_PREFIX", "NPM_CONFIG_USERCONFIG", "GCONV_PATH", "SSL_CERT_FILE", "SSL_CERT_DIR",
34
+ "JAVA_TOOL_OPTIONS", "_JAVA_OPTIONS", "JDK_JAVA_OPTIONS", "DOTNET_STARTUP_HOOKS",
35
+ "ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "CODEX_HOME", "CLAUDE_CONFIG_DIR", "MCP_TIMEOUT"]);
36
+ const RESERVED_ENV_PREFIX = /^(LD_|DYLD_|PYTHON|PERL5|PERLLIB|RUBY|GIT_|NODE_|NPM_CONFIG_|BUN_|DENO_|UV_|PIP_|LC_|XDG_|CLAUDE_|CODEX_|ANTHROPIC_|OPENAI_)/;
37
+ const RESERVED_ENV = { has: (name) => RESERVED_ENV_NAMES.has(name) || RESERVED_ENV_PREFIX.test(name) };
38
+ /** Common servers, pinned to exact versions: the lead suggests from here first. */
39
+ export const TOOL_CATALOG = [
40
+ { label: "Playwright", name: "playwright", transport: "stdio", command: "npx", args: ["-y", "@playwright/mcp@0.0.82", "--headless"], url: null, secrets: [], bearer: null, headerSecrets: {},
41
+ about: "Drives a real browser to open pages, click through them and take screenshots." },
42
+ { label: "Chrome DevTools", name: "chrome-devtools", transport: "stdio", command: "npx", args: ["-y", "chrome-devtools-mcp@1.10.1", "--headless", "--isolated"], url: null, secrets: [], bearer: null, headerSecrets: {},
43
+ about: "Inspects pages in Chrome: console errors, network requests and performance." },
44
+ { label: "Context7", name: "context7", transport: "stdio", command: "npx", args: ["-y", "@upstash/context7-mcp@4.1.1"], url: null, secrets: [{ name: "CONTEXT7_API_KEY", optional: true }], bearer: null, headerSecrets: {},
45
+ about: "Looks up current documentation for libraries and frameworks." },
46
+ { label: "shadcn/ui", name: "shadcn", transport: "stdio", command: "npx", args: ["-y", "shadcn@4.21.0", "mcp"], url: null, secrets: [], bearer: null, headerSecrets: {},
47
+ about: "Browses and adds shadcn/ui components." },
48
+ { label: "GitHub", name: "github", transport: "http", command: null, args: [], url: "https://api.githubcopilot.com/mcp/", secrets: [{ name: "GITHUB_TOKEN", optional: false }], bearer: "GITHUB_TOKEN", headerSecrets: {},
49
+ about: "Reads and manages GitHub issues, pull requests and code." },
50
+ { label: "Sentry", name: "sentry", transport: "stdio", command: "npx", args: ["-y", "@sentry/mcp-server@0.39.0"], url: null, secrets: [{ name: "SENTRY_ACCESS_TOKEN", optional: false }], bearer: null, headerSecrets: {},
51
+ about: "Reads Sentry issues and errors." },
52
+ { label: "Supabase (read-only)", name: "supabase", transport: "stdio", command: "npx", args: ["-y", "@supabase/mcp-server-supabase@0.13.0", "--read-only"], url: null, secrets: [{ name: "SUPABASE_ACCESS_TOKEN", optional: false }], bearer: null, headerSecrets: {},
53
+ about: "Reads Supabase tables and schema, without changing anything." },
54
+ ];
55
+ export function catalogTool(id) {
56
+ return TOOL_CATALOG.find(one => one.name === id) ?? null;
57
+ }
58
+ function canonical(value) {
59
+ if (Array.isArray(value))
60
+ return `[${value.map(canonical).join(",")}]`;
61
+ if (value !== null && typeof value === "object") {
62
+ return `{${Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
63
+ .map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(",")}}`;
64
+ }
65
+ return JSON.stringify(value);
66
+ }
67
+ /** What a build is held to: how the server starts and which secrets it names — never their values or its description. */
68
+ export function toolDigest(spec) {
69
+ const { about: _about, ...terms } = spec;
70
+ return createHash("sha256").update(`standing-orders:tool:v1:${canonical(terms)}`, "utf8").digest("hex").slice(0, 32);
71
+ }
72
+ const str = (value, cap, what) => {
73
+ if (typeof value !== "string" || value.trim() === "" || value.length > cap)
74
+ throw new Error(`${what} is required, up to ${cap} characters.`);
75
+ if (/[\u0000-\u001f\u007f]/.test(value))
76
+ throw new Error(`${what} cannot contain control characters.`);
77
+ return value.trim();
78
+ };
79
+ /** A tool definition from any source (catalog, console form, the lead, an import), checked whole. Throws in plain words. */
80
+ export function validateToolSpec(input) {
81
+ const name = str(input["name"], 40, "A short name").toLowerCase();
82
+ if (!NAME.test(name))
83
+ throw new Error("The name is lowercase letters, numbers, - and _ (up to 40), starting with a letter or number.");
84
+ const transport = input["transport"] === "http" ? "http" : input["transport"] === "stdio" || input["transport"] === undefined ? "stdio" : null;
85
+ if (transport === null)
86
+ throw new Error("A tool runs as a local program or at a web address.");
87
+ const secretsIn = Array.isArray(input["secrets"]) ? input["secrets"] : [];
88
+ if (secretsIn.length > 12)
89
+ throw new Error("A tool can name up to 12 secrets.");
90
+ const secrets = [];
91
+ for (const one of secretsIn) {
92
+ const entry = typeof one === "string" ? { name: one, optional: false } : one;
93
+ const secretName = typeof entry["name"] === "string" ? entry["name"] : "";
94
+ if (!SECRET_NAME.test(secretName))
95
+ throw new Error("Secret names are UPPER_CASE letters, numbers and _.");
96
+ if (RESERVED_ENV.has(secretName))
97
+ throw new Error(`${secretName} is reserved; choose another secret name.`);
98
+ if (secrets.some(s => s.name === secretName))
99
+ continue;
100
+ secrets.push({ name: secretName, optional: entry["optional"] === true });
101
+ }
102
+ let command = null, args = [], url = null, bearer = null;
103
+ const headerSecrets = {};
104
+ if (transport === "stdio") {
105
+ command = str(input["command"], 400, "The command");
106
+ const argsIn = input["args"] === undefined ? [] : input["args"];
107
+ if (!Array.isArray(argsIn) || argsIn.length > 40 || argsIn.some(arg => typeof arg !== "string" || arg.length > 400 || /[\u0000-\u001f\u007f]/.test(arg)))
108
+ throw new Error("Arguments are up to 40 plain words.");
109
+ args = argsIn;
110
+ }
111
+ else {
112
+ url = str(input["url"], 500, "The address");
113
+ let parsed;
114
+ try {
115
+ parsed = new URL(url);
116
+ }
117
+ catch {
118
+ throw new Error("The address is not a web address.");
119
+ }
120
+ const local = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]";
121
+ if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && local))
122
+ throw new Error("A web tool needs an https address.");
123
+ if (parsed.username !== "" || parsed.password !== "")
124
+ throw new Error("Put credentials in a secret, not in the address.");
125
+ if (input["bearer"] != null) {
126
+ if (typeof input["bearer"] !== "string" || !secrets.some(s => s.name === input["bearer"]))
127
+ throw new Error("The sign-in token must be one of the tool's secrets.");
128
+ bearer = input["bearer"];
129
+ }
130
+ const headersIn = (input["headerSecrets"] ?? {});
131
+ for (const [header, secret] of Object.entries(headersIn)) {
132
+ if (!HEADER_NAME.test(header) || typeof secret !== "string" || !secrets.some(s => s.name === secret))
133
+ throw new Error("Each header must name one of the tool's secrets.");
134
+ if (header.toLowerCase() === "authorization" && bearer !== null)
135
+ throw new Error("Use either a sign-in token or an Authorization header, not both.");
136
+ headerSecrets[header] = secret;
137
+ }
138
+ }
139
+ const about = typeof input["about"] === "string" && input["about"].trim() !== "" ? input["about"].trim().slice(0, 240)
140
+ : TOOL_CATALOG.find(one => one.name === name)?.about
141
+ ?? (transport === "http" ? `Tools from ${new URL(url).hostname}.` : `Tools from ${programName(command, args)}.`);
142
+ const spec = { name, transport, command, args, url, secrets, bearer, headerSecrets, about };
143
+ // Values belong in the secret store; a key typed into a command, argument or address is refused.
144
+ if (scanForSecrets([command ?? "", ...args, url ?? "", about].join("\n")).length > 0)
145
+ throw new Error("That looks like a key or token in the tool's settings. Name it as a secret instead; its value is set on the secure Tools screen.");
146
+ return spec;
147
+ }
148
+ /** What a local tool is, in a word: the package a runner like npx starts, else the program's own name. */
149
+ function programName(command, args) {
150
+ const base = command.split(/[\\/]/).pop() ?? command;
151
+ const runner = ["npx", "bunx", "uvx", "pnpx", "pipx"].includes(base);
152
+ const target = runner ? args.find(arg => !arg.startsWith("-")) : undefined;
153
+ return target === undefined ? base : target.replace(/@[^@/]*$/, "") || target;
154
+ }
155
+ /** How the tool starts, in one line a person can check. Never a secret value. */
156
+ export function toolCommandLine(spec) {
157
+ if (spec.transport === "http")
158
+ return `${spec.url}${spec.bearer === null ? "" : ` (signs in with ${spec.bearer})`}`;
159
+ return [spec.command, ...spec.args].map(part => /^[A-Za-z0-9@%+=:,./_-]+$/.test(part ?? "") ? part : JSON.stringify(part)).join(" ");
160
+ }
161
+ // ---- secrets -----------------------------------------------------------------
162
+ export function toolSecretsDir(home = homedir()) {
163
+ return namedPath(home, ["tool-secrets"], { dot: true });
164
+ }
165
+ function secretFile(repo, tool, home) {
166
+ if (!NAME.test(tool))
167
+ throw new Error("Choose a tool by its name.");
168
+ return join(toolSecretsDir(home), createHash("sha256").update(repo).digest("hex").slice(0, 16), `${tool}.json`);
169
+ }
170
+ export function readToolSecrets(repo, tool, home = homedir()) {
171
+ const file = secretFile(repo, tool, home);
172
+ if (!existsSync(file))
173
+ return {};
174
+ try {
175
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
176
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
177
+ return {};
178
+ return Object.fromEntries(Object.entries(parsed).filter((entry) => SECRET_NAME.test(entry[0]) && typeof entry[1] === "string"));
179
+ }
180
+ catch {
181
+ return {};
182
+ }
183
+ }
184
+ function writeSecrets(repo, tool, values, home) {
185
+ const file = secretFile(repo, tool, home);
186
+ const dir = join(file, "..");
187
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
188
+ chmodSync(toolSecretsDir(home), 0o700);
189
+ chmodSync(dir, 0o700);
190
+ if (Object.keys(values).length === 0) {
191
+ rmSync(file, { force: true });
192
+ return;
193
+ }
194
+ writeFileSync(file, JSON.stringify(values), { mode: 0o600 });
195
+ chmodSync(file, 0o600);
196
+ }
197
+ /** Set (or, with an empty value, clear) one secret of one tool. */
198
+ export function setToolSecret(repo, tool, name, value, home = homedir()) {
199
+ if (!SECRET_NAME.test(name) || RESERVED_ENV.has(name))
200
+ throw new Error("Choose one of the tool's secrets.");
201
+ if (value.length > 8_192 || /[\u0000\r\n]/.test(value))
202
+ throw new Error("A secret is one line, up to 8,192 characters.");
203
+ const values = readToolSecrets(repo, tool, home);
204
+ if (value === "")
205
+ delete values[name];
206
+ else
207
+ values[name] = value;
208
+ writeSecrets(repo, tool, values, home);
209
+ }
210
+ export function setToolSecrets(repo, tool, values, home = homedir()) {
211
+ for (const [name, value] of Object.entries(values))
212
+ setToolSecret(repo, tool, name, value, home);
213
+ }
214
+ export function clearToolSecrets(repo, tool, home = homedir()) {
215
+ rmSync(secretFile(repo, tool, home), { force: true });
216
+ }
217
+ /** Required secrets with no value yet: the tool is left out of builds until they are set. */
218
+ export function missingSecrets(spec, values) {
219
+ return spec.secrets.filter(one => !one.optional && (values[one.name] ?? "") === "").map(one => one.name);
220
+ }
221
+ const envName = (raw) => raw.toUpperCase().replace(/[^A-Z0-9_]/g, "_").replace(/^[^A-Z]+/, "") || "SECRET";
222
+ function fromClaudeEntry(name, raw, source) {
223
+ if (raw === null || typeof raw !== "object")
224
+ return null;
225
+ const entry = raw;
226
+ const type = typeof entry["type"] === "string" ? entry["type"] : typeof entry["command"] === "string" ? "stdio" : typeof entry["url"] === "string" ? "http" : "";
227
+ const values = {};
228
+ const secrets = [];
229
+ const keep = (secret, value) => {
230
+ if (typeof value !== "string" || !SECRET_NAME.test(secret) || RESERVED_ENV.has(secret))
231
+ return false;
232
+ values[secret] = value;
233
+ if (!secrets.some(one => one.name === secret))
234
+ secrets.push({ name: secret, optional: false });
235
+ return true;
236
+ };
237
+ try {
238
+ if (type === "stdio") {
239
+ for (const [key, value] of Object.entries((entry["env"] ?? {})))
240
+ keep(key, value);
241
+ const spec = validateToolSpec({ name: name.toLowerCase().replace(/[^a-z0-9_-]/g, "-").slice(0, 40), transport: "stdio", command: entry["command"], args: entry["args"] ?? [], secrets });
242
+ return { spec, source, values };
243
+ }
244
+ if (type === "http") {
245
+ let bearer = null;
246
+ const headerSecrets = {};
247
+ for (const [header, value] of Object.entries((entry["headers"] ?? {}))) {
248
+ if (typeof value !== "string")
249
+ continue;
250
+ const bearerValue = header.toLowerCase() === "authorization" ? /^Bearer\s+(.+)$/i.exec(value)?.[1] : undefined;
251
+ const secret = envName(`${name}_${bearerValue !== undefined ? "token" : header}`);
252
+ if (bearerValue !== undefined) {
253
+ if (keep(secret, bearerValue))
254
+ bearer = secret;
255
+ }
256
+ else if (keep(secret, value))
257
+ headerSecrets[header] = secret;
258
+ }
259
+ const spec = validateToolSpec({ name: name.toLowerCase().replace(/[^a-z0-9_-]/g, "-").slice(0, 40), transport: "http", url: entry["url"], secrets, bearer, headerSecrets });
260
+ return { spec, source, values };
261
+ }
262
+ }
263
+ catch {
264
+ return null;
265
+ }
266
+ return null;
267
+ }
268
+ function fromCodexEntry(raw, environment) {
269
+ if (raw === null || typeof raw !== "object")
270
+ return null;
271
+ const entry = raw;
272
+ const name = typeof entry["name"] === "string" ? entry["name"] : "";
273
+ const transport = (entry["transport"] ?? {});
274
+ const values = {};
275
+ const secrets = [];
276
+ const keep = (secret, value) => {
277
+ if (typeof value !== "string" || value === "" || !SECRET_NAME.test(secret) || RESERVED_ENV.has(secret))
278
+ return false;
279
+ values[secret] = value;
280
+ if (!secrets.some(one => one.name === secret))
281
+ secrets.push({ name: secret, optional: false });
282
+ return true;
283
+ };
284
+ try {
285
+ if (transport["type"] === "stdio") {
286
+ for (const [key, value] of Object.entries((transport["env"] ?? {})))
287
+ keep(key, value);
288
+ for (const key of Array.isArray(transport["env_vars"]) ? transport["env_vars"] : [])
289
+ if (typeof key === "string")
290
+ keep(key, environment[key]);
291
+ let command = typeof transport["command"] === "string" ? transport["command"] : "";
292
+ const cwd = typeof transport["cwd"] === "string" ? transport["cwd"] : null;
293
+ if (!isAbsolute(command) && command.includes("/") && cwd !== null && isAbsolute(cwd))
294
+ command = join(cwd, command);
295
+ const spec = validateToolSpec({ name: name.toLowerCase().replace(/[^a-z0-9_-]/g, "-").slice(0, 40), transport: "stdio", command, args: transport["args"] ?? [], secrets });
296
+ return { spec, source: "Codex on this computer", values };
297
+ }
298
+ if (transport["type"] === "streamable_http") {
299
+ let bearer = null;
300
+ const headerSecrets = {};
301
+ const bearerName = typeof transport["bearer_token_env_var"] === "string" ? transport["bearer_token_env_var"] : null;
302
+ if (bearerName !== null && keep(bearerName, environment[bearerName]))
303
+ bearer = bearerName;
304
+ for (const [header, env] of Object.entries((transport["env_http_headers"] ?? {})))
305
+ if (typeof env === "string" && keep(env, environment[env]))
306
+ headerSecrets[header] = env;
307
+ for (const [header, value] of Object.entries((transport["http_headers"] ?? {}))) {
308
+ const secret = envName(`${name}_${header}`);
309
+ if (keep(secret, value))
310
+ headerSecrets[header] = secret;
311
+ }
312
+ const spec = validateToolSpec({ name: name.toLowerCase().replace(/[^a-z0-9_-]/g, "-").slice(0, 40), transport: "http", url: transport["url"], secrets, bearer, headerSecrets });
313
+ return { spec, source: "Codex on this computer", values };
314
+ }
315
+ }
316
+ catch {
317
+ return null;
318
+ }
319
+ return null;
320
+ }
321
+ /**
322
+ * The MCP servers this computer already configures for a project: the
323
+ * repository's `.mcp.json`, Claude's global and per-project servers, and
324
+ * Codex's (from `codex mcp list --json`, passed in). The first of each
325
+ * name wins; unusable entries are left out.
326
+ */
327
+ export function discoverTools(repo, codexServers, home = homedir(), environment = process.env) {
328
+ const found = [];
329
+ const add = (one) => { if (one !== null && !found.some(other => other.spec.name === one.spec.name))
330
+ found.push(one); };
331
+ const readJson = (file) => {
332
+ try {
333
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
334
+ return parsed !== null && typeof parsed === "object" ? parsed : null;
335
+ }
336
+ catch {
337
+ return null;
338
+ }
339
+ };
340
+ const project = readJson(join(repo, ".mcp.json"));
341
+ for (const [name, raw] of Object.entries((project?.["mcpServers"] ?? {})))
342
+ add(fromClaudeEntry(name, raw, "this project's .mcp.json"));
343
+ const claude = readJson(join(home, ".claude.json"));
344
+ const perProject = (claude?.["projects"] ?? {})[repo]?.["mcpServers"] ?? {};
345
+ for (const [name, raw] of Object.entries(perProject))
346
+ add(fromClaudeEntry(name, raw, "Claude for this project"));
347
+ for (const [name, raw] of Object.entries((claude?.["mcpServers"] ?? {})))
348
+ add(fromClaudeEntry(name, raw, "Claude on this computer"));
349
+ for (const raw of codexServers ?? [])
350
+ add(fromCodexEntry(raw, environment));
351
+ return found;
352
+ }
353
+ // ---- the Test button --------------------------------------------------------------
354
+ const PROTOCOL = "2025-06-18";
355
+ const initialize = (id) => ({ jsonrpc: "2.0", id, method: "initialize", params: { protocolVersion: PROTOCOL, capabilities: {}, clientInfo: { name: "toolroll", version: "1" } } });
356
+ function toolNames(result) {
357
+ const tools = result?.tools;
358
+ return Array.isArray(tools) ? tools.map(one => one.name).filter((name) => typeof name === "string").slice(0, 200) : [];
359
+ }
360
+ /** Start the server once, ask for its tools, and stop it: the proof a build can use it. */
361
+ export async function testTool(spec, values, options = {}) {
362
+ const answer = await exchange(spec, values, { method: "tools/list", params: {}, doing: "listing its tools" }, options);
363
+ return answer.ok ? { ok: true, tools: toolNames(answer.result) } : answer;
364
+ }
365
+ /**
366
+ * Start the server once and read what each of its tools does (v94): the name,
367
+ * its description, what input it takes, and whether it says it only reads.
368
+ * A teammate is offered these, each under its own rule.
369
+ */
370
+ export async function listProjectToolActions(spec, values, options = {}) {
371
+ const answer = await exchange(spec, values, { method: "tools/list", params: {}, doing: "listing its tools" }, options);
372
+ return answer.ok ? { ok: true, actions: toolActions(answer.result) } : answer;
373
+ }
374
+ export function toolActions(result) {
375
+ const tools = result?.tools;
376
+ if (!Array.isArray(tools))
377
+ return [];
378
+ return tools.flatMap(raw => {
379
+ const one = raw;
380
+ if (typeof one.name !== "string" || !/^[A-Za-z0-9_.-]{1,64}$/.test(one.name))
381
+ return [];
382
+ const input = one.inputSchema !== null && typeof one.inputSchema === "object" && !Array.isArray(one.inputSchema) && JSON.stringify(one.inputSchema).length <= 8000 ? one.inputSchema : null;
383
+ return [{ name: one.name, about: typeof one.description === "string" ? safeLine(one.description).slice(0, 300) : "", input, readOnly: one.annotations?.readOnlyHint === true }];
384
+ }).slice(0, 100);
385
+ }
386
+ /**
387
+ * Start the server once, call one of its tools, and stop it (v87): a flow's
388
+ * "Use a tool" step. The answer's text parts are kept; a tool that says it
389
+ * failed (isError) is an answer, not trouble reaching it.
390
+ */
391
+ export async function callProjectTool(spec, values, name, args, options = {}) {
392
+ const answer = await exchange(spec, values, { method: "tools/call", params: { name, arguments: args }, doing: `using ${name}` }, options);
393
+ if (!answer.ok)
394
+ return answer;
395
+ const result = (answer.result ?? {});
396
+ const parts = Array.isArray(result.content) ? result.content : [];
397
+ const text = parts.map(part => part.type === "text" && typeof part.text === "string" ? String(part.text) : "").filter(Boolean).join("\n")
398
+ || (result.structuredContent === undefined ? "" : JSON.stringify(result.structuredContent));
399
+ return { ok: true, text, isError: result.isError === true };
400
+ }
401
+ function exchange(spec, values, request, options) {
402
+ const missing = missingSecrets(spec, values);
403
+ if (missing.length > 0)
404
+ return Promise.resolve({ ok: false, problem: `Set ${missing.join(", ")} first.` });
405
+ const timeoutMs = options.timeoutMs ?? 60_000;
406
+ return spec.transport === "http" ? exchangeHttp(spec, values, timeoutMs, request) : exchangeStdio(spec, values, timeoutMs, options.env ?? process.env, options.omitEnv ?? [], request);
407
+ }
408
+ function exchangeStdio(spec, values, timeoutMs, base, omit, request) {
409
+ return new Promise(resolve => {
410
+ const env = { ...base };
411
+ for (const name of omit)
412
+ delete env[name];
413
+ Object.assign(env, values);
414
+ let child;
415
+ try {
416
+ child = spawn(spec.command, spec.args, { env, stdio: ["pipe", "pipe", "pipe"], detached: process.platform !== "win32" });
417
+ }
418
+ catch (error) {
419
+ resolve({ ok: false, problem: `It could not start: ${error instanceof Error ? error.message : "unknown error"}.` });
420
+ return;
421
+ }
422
+ let settled = false, buffer = "", stderr = "";
423
+ const finish = (outcome) => {
424
+ if (settled)
425
+ return;
426
+ settled = true;
427
+ clearTimeout(timer);
428
+ try {
429
+ if (child.pid !== undefined && process.platform !== "win32")
430
+ process.kill(-child.pid, "SIGTERM");
431
+ else
432
+ child.kill("SIGTERM");
433
+ }
434
+ catch { /* already gone */ }
435
+ resolve(outcome);
436
+ };
437
+ const timer = setTimeout(() => finish({ ok: false, problem: `It did not answer within ${Math.round(timeoutMs / 1000)} seconds.` }), timeoutMs);
438
+ const send = (message) => { try {
439
+ child.stdin?.write(`${JSON.stringify(message)}\n`);
440
+ }
441
+ catch { /* the exit handler reports */ } };
442
+ child.on("error", error => finish({ ok: false, problem: `It could not start: ${error.message}.` }));
443
+ child.on("exit", code => finish({ ok: false, problem: `It stopped before ${request.doing} (exit ${code ?? "signal"})${stderr.trim() === "" ? "" : `: ${safeLine(stderr)}`}.` }));
444
+ child.stderr?.on("data", (chunk) => { stderr = (stderr + chunk.toString("utf8")).slice(-2_000); });
445
+ child.stdout?.on("data", (chunk) => {
446
+ buffer += chunk.toString("utf8");
447
+ for (let newline = buffer.indexOf("\n"); newline >= 0; newline = buffer.indexOf("\n")) {
448
+ const line = buffer.slice(0, newline).trim();
449
+ buffer = buffer.slice(newline + 1);
450
+ let message;
451
+ try {
452
+ message = JSON.parse(line);
453
+ }
454
+ catch {
455
+ continue;
456
+ }
457
+ if (message.id === 1) {
458
+ if (message.error !== undefined) {
459
+ finish({ ok: false, problem: `It refused to start a session: ${safeLine(String(message.error.message ?? "error"))}.` });
460
+ return;
461
+ }
462
+ send({ jsonrpc: "2.0", method: "notifications/initialized" });
463
+ send({ jsonrpc: "2.0", id: 2, method: request.method, params: request.params });
464
+ }
465
+ else if (message.id === 2) {
466
+ if (message.error !== undefined)
467
+ finish({ ok: false, problem: `It refused ${request.doing}: ${safeLine(String(message.error.message ?? "error"))}.` });
468
+ else
469
+ finish({ ok: true, result: message.result });
470
+ }
471
+ }
472
+ });
473
+ send(initialize(1));
474
+ });
475
+ }
476
+ async function exchangeHttp(spec, values, timeoutMs, request) {
477
+ const headers = { "content-type": "application/json", accept: "application/json, text/event-stream", "mcp-protocol-version": PROTOCOL };
478
+ if (spec.bearer !== null && values[spec.bearer])
479
+ headers["authorization"] = `Bearer ${values[spec.bearer]}`;
480
+ for (const [header, secret] of Object.entries(spec.headerSecrets))
481
+ if (values[secret])
482
+ headers[header] = values[secret];
483
+ const signal = AbortSignal.timeout(timeoutMs);
484
+ const call = async (body, session) => {
485
+ const response = await fetch(spec.url, { method: "POST", headers: { ...headers, ...(session === null ? {} : { "mcp-session-id": session }) }, body: JSON.stringify(body), signal });
486
+ const text = (await response.text()).slice(0, 2_000_000);
487
+ const json = (response.headers.get("content-type") ?? "").includes("text/event-stream")
488
+ ? text.split("\n").filter(line => line.startsWith("data:")).map(line => line.slice(5).trim()).find(line => line.startsWith("{")) ?? ""
489
+ : text;
490
+ let message = null;
491
+ try {
492
+ message = json === "" ? null : JSON.parse(json);
493
+ }
494
+ catch {
495
+ message = null;
496
+ }
497
+ return { status: response.status, session: response.headers.get("mcp-session-id") ?? session, message };
498
+ };
499
+ try {
500
+ const opened = await call(initialize(1), null);
501
+ if (opened.status === 401 || opened.status === 403)
502
+ return { ok: false, problem: "It refused the sign-in. Check the secret's value." };
503
+ if (opened.status >= 400 || opened.message === null)
504
+ return { ok: false, problem: `It did not start a session (HTTP ${opened.status}).` };
505
+ if (opened.message.error !== undefined)
506
+ return { ok: false, problem: `It refused to start a session: ${safeLine(String(opened.message.error.message ?? "error"))}.` };
507
+ await fetch(spec.url, { method: "POST", headers: { ...headers, ...(opened.session === null ? {} : { "mcp-session-id": opened.session }) }, body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }), signal }).then(r => r.body?.cancel()).catch(() => undefined);
508
+ const answered = await call({ jsonrpc: "2.0", id: 2, method: request.method, params: request.params }, opened.session);
509
+ if (answered.message === null)
510
+ return { ok: false, problem: `It did not answer ${request.doing} (HTTP ${answered.status}).` };
511
+ if (answered.message.error !== undefined)
512
+ return { ok: false, problem: `It refused ${request.doing}: ${safeLine(String(answered.message.error.message ?? "error"))}.` };
513
+ return { ok: true, result: answered.message.result };
514
+ }
515
+ catch (error) {
516
+ return { ok: false, problem: error instanceof Error && error.name === "TimeoutError" ? `It did not answer within ${Math.round(timeoutMs / 1000)} seconds.` : "It could not be reached." };
517
+ }
518
+ }
519
+ /** One line of a server's own words, never a secret. */
520
+ function safeLine(text) {
521
+ const line = text.replace(/\s+/g, " ").trim().slice(0, 200);
522
+ return scanForSecrets(line).length > 0 ? "(its message was withheld: it looked like a secret)" : line;
523
+ }
524
+ const toml = (value) => `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
525
+ const tomlList = (values) => `[${values.map(toml).join(",")}]`;
526
+ /** Claude: only these servers (`--strict-mcp-config`), written to a private 0600 file removed after the run. */
527
+ export function claudeToolArgs(launch) {
528
+ const servers = {};
529
+ for (const tool of launch.tools) {
530
+ const spec = tool.spec;
531
+ if (spec.transport === "stdio") {
532
+ servers[spec.name] = { type: "stdio", command: spec.command, args: spec.args, env: tool.values };
533
+ }
534
+ else {
535
+ const headers = {};
536
+ if (spec.bearer !== null && tool.values[spec.bearer])
537
+ headers["Authorization"] = `Bearer ${tool.values[spec.bearer]}`;
538
+ for (const [header, secret] of Object.entries(spec.headerSecrets))
539
+ if (tool.values[secret])
540
+ headers[header] = tool.values[secret];
541
+ servers[spec.name] = { type: "http", url: spec.url, headers };
542
+ }
543
+ }
544
+ const dir = mkdtempSync(join(tmpdir(), "so-tools-"));
545
+ chmodSync(dir, 0o700);
546
+ const file = join(dir, "mcp.json");
547
+ writeFileSync(file, JSON.stringify({ mcpServers: servers }), { mode: 0o600 });
548
+ return {
549
+ argv: ["--strict-mcp-config", "--mcp-config", file],
550
+ // A first `npx` download can take longer than the default start-up wait.
551
+ env: launch.tools.length > 0 ? { MCP_TIMEOUT: "60000" } : {},
552
+ omitEnv: [],
553
+ cleanup: () => rmSync(dir, { recursive: true, force: true }),
554
+ };
555
+ }
556
+ /** The operator's Codex model settings a build keeps when its tools are replaced: how it thinks, never what it can reach. */
557
+ const CARRIED_CODEX_SETTINGS = ["model_reasoning_effort", "model_reasoning_summary", "model_verbosity", "service_tier", "personality"];
558
+ export function carriedCodexSettings(includeModel, codexHome = process.env["CODEX_HOME"] ?? join(homedir(), ".codex")) {
559
+ let text = "";
560
+ try {
561
+ text = readFileSync(join(codexHome, "config.toml"), "utf8");
562
+ }
563
+ catch {
564
+ return [];
565
+ }
566
+ const wanted = includeModel ? ["model", ...CARRIED_CODEX_SETTINGS] : CARRIED_CODEX_SETTINGS;
567
+ const argv = [];
568
+ for (const line of text.split("\n")) {
569
+ if (/^\s*\[/.test(line))
570
+ break;
571
+ const match = /^\s*([a-z_]+)\s*=\s*("[^"\\\n]{0,80}"|[A-Za-z0-9_.-]{1,40})\s*(?:#.*)?$/.exec(line);
572
+ if (match !== null && wanted.includes(match[1]))
573
+ argv.push("-c", `${match[1]}=${match[2]}`);
574
+ }
575
+ return argv;
576
+ }
577
+ /** Where the tool launcher lives beside this module, built. */
578
+ export function toolLauncherPath() {
579
+ return fileURLToPath(new URL("./tool-launcher.js", import.meta.url));
580
+ }
581
+ /**
582
+ * Codex: the operator's config.toml is ignored whole (its MCP servers
583
+ * with it), ChatGPT apps and plugins are off, the model settings above
584
+ * are carried, and each tool is named by `-c` and started through the
585
+ * launcher. Codex gives its own environment to the agent's shell whatever
586
+ * its policy says, so no secret rides it: each tool's values sit in the
587
+ * run's private 0600 file and reach only that tool's process.
588
+ */
589
+ export function codexToolArgs(launch, options) {
590
+ // ChatGPT apps, connectors and plugins are the operator's account, not the project's: all off (plugins carry their own MCP servers).
591
+ const argv = ["--ignore-user-config", ...carriedCodexSettings(options.includeModel, options.codexHome), "-c", "apps._default.enabled=false", "-c", "features.plugins=false"];
592
+ if (launch.tools.length === 0)
593
+ return { argv, env: {}, omitEnv: [], cleanup: () => undefined };
594
+ const dir = mkdtempSync(join(tmpdir(), "so-tools-"));
595
+ chmodSync(dir, 0o700);
596
+ const launcher = options.launcher ?? toolLauncherPath();
597
+ const node = options.node ?? process.execPath;
598
+ for (const tool of launch.tools) {
599
+ const file = join(dir, `${tool.spec.name}.json`);
600
+ writeFileSync(file, JSON.stringify({ spec: tool.spec, values: tool.values }), { mode: 0o600 });
601
+ const key = `mcp_servers.${tool.spec.name}`;
602
+ argv.push("-c", `${key}.command=${toml(node)}`, "-c", `${key}.args=${tomlList([launcher, file])}`, "-c", `${key}.startup_timeout_sec=60`,
603
+ // Adding a tool to a project is the operator's yes to its calls: an unattended build has no one to ask.
604
+ "-c", `${key}.default_tools_approval_mode="approve"`);
605
+ }
606
+ return { argv, env: {}, omitEnv: [], cleanup: () => rmSync(dir, { recursive: true, force: true }), privateDir: dir };
607
+ }
608
+ /** Gemini: no way yet to hand it exactly these servers, so it gets none. */
609
+ export const GEMINI_NO_TOOLS_ARGV = ["--allowed-mcp-server-names", "standing-orders-none"];
610
+ /** The project's active tools with their parsed definitions and last test. */
611
+ export function projectToolsOf(store, repo) {
612
+ return store.projectTools(repo).flatMap(row => {
613
+ try {
614
+ const spec = validateToolSpec(JSON.parse(row.specJson));
615
+ let lastTest = null;
616
+ try {
617
+ lastTest = row.lastTestJson === null ? null : JSON.parse(row.lastTestJson);
618
+ }
619
+ catch {
620
+ lastTest = null;
621
+ }
622
+ return [{ id: row.id, repo: row.repo, name: row.name, spec, digest: row.digest, source: row.source, createdAt: row.createdAt, createdBy: row.createdBy, lastTest }];
623
+ }
624
+ catch {
625
+ return [];
626
+ }
627
+ });
628
+ }
629
+ /**
630
+ * The exact tools one attempt launches with: the project's active tools,
631
+ * limited to those its approval saw (a task approved before tools were
632
+ * sealed gets today's list), each with every required secret set. What is
633
+ * left out says why, in words the task page can show.
634
+ */
635
+ export function toolLaunchFor(store, runId, home = homedir()) {
636
+ const run = store.getRun(runId);
637
+ const ref = run === null ? null : store.refById(run.taskRef);
638
+ if (ref === null || ref.repo === null)
639
+ return { tools: [], skipped: [] };
640
+ const approved = store.getScope(ref.externalId)?.approvedDigest ?? null;
641
+ const seal = approved === null ? null : store.toolSealFor(ref.externalId, approved);
642
+ const launch = { tools: [], skipped: [] };
643
+ const claimed = new Map();
644
+ const policy = store.orgPolicy();
645
+ for (const tool of projectToolsOf(store, ref.repo)) {
646
+ // Sprint 8: a tool the organisation policy doesn't allow is left out, and the run says why.
647
+ const disallowed = toolRefusal(policy, tool.name);
648
+ if (disallowed !== null) {
649
+ launch.skipped.push({ name: tool.name, reason: disallowed });
650
+ continue;
651
+ }
652
+ if (seal !== null && !seal.some(one => one.name === tool.name && one.digest === tool.digest)) {
653
+ launch.skipped.push({ name: tool.name, reason: "added or changed after this task was approved; approve it again to use it" });
654
+ continue;
655
+ }
656
+ const values = readToolSecrets(ref.repo, tool.name, home);
657
+ const missing = missingSecrets(tool.spec, values);
658
+ if (missing.length > 0) {
659
+ launch.skipped.push({ name: tool.name, reason: `needs ${missing.join(", ")} set on the Tools page` });
660
+ continue;
661
+ }
662
+ const kept = Object.fromEntries(tool.spec.secrets.filter(one => (values[one.name] ?? "") !== "").map(one => [one.name, values[one.name]]));
663
+ const clash = Object.entries(kept).find(([name, value]) => claimed.has(name) && claimed.get(name) !== value);
664
+ if (clash !== undefined) {
665
+ launch.skipped.push({ name: tool.name, reason: `uses ${clash[0]} with a different value than another tool` });
666
+ continue;
667
+ }
668
+ for (const [name, value] of Object.entries(kept))
669
+ claimed.set(name, value);
670
+ launch.tools.push({ spec: tool.spec, digest: tool.digest, values: kept });
671
+ }
672
+ return launch;
673
+ }
674
+ /**
675
+ * One attempt's tools, ready for its provider, and recorded on the run:
676
+ * what it launched with and what was left out and why. The caller spawns
677
+ * with `argv`/`env` and calls `cleanup` when the process is done.
678
+ */
679
+ export function prepareRunTools(store, runId, provider, options) {
680
+ const launch = toolLaunchFor(store, runId, options.home);
681
+ const prepared = provider === "claude" ? claudeToolArgs(launch)
682
+ : provider === "codex" ? codexToolArgs(launch, { includeModel: options.includeModel })
683
+ : provider === "openrouter" ? codexToolArgs(launch, { includeModel: false })
684
+ : { argv: [...GEMINI_NO_TOOLS_ARGV], env: {}, omitEnv: [], cleanup: () => undefined };
685
+ const skipped = provider === "gemini"
686
+ ? [...launch.skipped, ...launch.tools.map(one => ({ name: one.spec.name, reason: "Gemini builds can't use project tools yet" }))]
687
+ : launch.skipped;
688
+ store.recordRunTools(runId, JSON.stringify({
689
+ provider,
690
+ tools: provider === "gemini" ? [] : launch.tools.map(one => ({ name: one.spec.name, digest: one.digest })),
691
+ skipped,
692
+ }), options.now);
693
+ return prepared;
694
+ }
695
+ /** When a launch's tools cannot be prepared, it still gets none of anything else's: the same isolation with an empty list. */
696
+ export function noToolsArgs(provider, includeModel) {
697
+ const none = { tools: [], skipped: [] };
698
+ if (provider === "claude")
699
+ return { argv: ["--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}'], env: {}, omitEnv: [], cleanup: () => undefined };
700
+ if (provider === "gemini")
701
+ return { argv: [...GEMINI_NO_TOOLS_ARGV], env: {}, omitEnv: [], cleanup: () => undefined };
702
+ return codexToolArgs(none, { includeModel: provider === "codex" && includeModel });
703
+ }
704
+ /** Add one tool (and, for an import, the values it brings) to a project's builds. */
705
+ export function addToolTo(store, repo, input, source, by, now, options = {}) {
706
+ let spec;
707
+ try {
708
+ spec = validateToolSpec(input);
709
+ }
710
+ catch (error) {
711
+ return { ok: false, message: error instanceof Error ? error.message : "That tool is not valid." };
712
+ }
713
+ if (!store.addProjectTool({ repo, name: spec.name, specJson: JSON.stringify(spec), digest: toolDigest(spec), source: source.slice(0, 120), by }, now)) {
714
+ return { ok: false, message: `This project already has a tool called ${spec.name}. Remove it first to replace it.` };
715
+ }
716
+ const values = Object.fromEntries(Object.entries(options.values ?? {}).filter(([name, value]) => spec.secrets.some(one => one.name === name) && value !== ""));
717
+ try {
718
+ clearToolSecrets(repo, spec.name, options.home);
719
+ if (Object.keys(values).length > 0)
720
+ setToolSecrets(repo, spec.name, values, options.home);
721
+ }
722
+ catch {
723
+ return { ok: true, spec };
724
+ }
725
+ return { ok: true, spec };
726
+ }
727
+ /** Take one tool away from every build now, with its stored secrets. */
728
+ export function removeToolFrom(store, repo, name, by, now, home) {
729
+ if (!store.removeProjectTool(repo, name, by, now))
730
+ return false;
731
+ try {
732
+ clearToolSecrets(repo, name, home);
733
+ }
734
+ catch { /* the row is gone; an orphan file is never read */ }
735
+ return true;
736
+ }
737
+ /** Test one tool of a project and keep the answer on it. */
738
+ export async function testToolOf(store, repo, name, now, options = {}) {
739
+ const tool = projectToolsOf(store, repo).find(one => one.name === name);
740
+ if (tool === undefined)
741
+ return null;
742
+ const outcome = await testTool(tool.spec, readToolSecrets(repo, name, options.home), { ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }), ...(options.omitEnv === undefined ? {} : { omitEnv: options.omitEnv }) });
743
+ const test = outcome.ok ? { at: now.toISOString(), ok: true, tools: outcome.tools, problem: null } : { at: now.toISOString(), ok: false, tools: [], problem: outcome.problem };
744
+ store.recordProjectToolTest(repo, name, JSON.stringify(test));
745
+ return test;
746
+ }
747
+ /** Where a tool stands, in a few words: what a person reads first. */
748
+ export function toolStanding(tool, secretsSet) {
749
+ const missing = tool.spec.secrets.filter(one => !one.optional && !secretsSet.includes(one.name)).map(one => one.name);
750
+ if (missing.length > 0)
751
+ return { ready: false, words: `Needs ${missing.join(", ")}` };
752
+ if (tool.lastTest === null)
753
+ return { ready: true, words: "Not tested yet" };
754
+ if (!tool.lastTest.ok)
755
+ return { ready: false, words: "Last test failed" };
756
+ return { ready: true, words: `Working · ${tool.lastTest.tools.length} tool${tool.lastTest.tools.length === 1 ? "" : "s"}` };
757
+ }
758
+ /** Names of a tool's secrets that have a value (never the values). */
759
+ export function secretsSetFor(repo, tool, home) {
760
+ const values = readToolSecrets(repo, tool.name, home);
761
+ return tool.secrets.filter(one => (values[one.name] ?? "") !== "").map(one => one.name);
762
+ }
763
+ /** A command line split into program and arguments, honouring "double" and 'single' quotes. */
764
+ export function splitCommandLine(line) {
765
+ const parts = [];
766
+ let current = "", quote = null, started = false;
767
+ for (const char of line.trim()) {
768
+ if (quote !== null) {
769
+ if (char === quote)
770
+ quote = null;
771
+ else
772
+ current += char;
773
+ continue;
774
+ }
775
+ if (char === '"' || char === "'") {
776
+ quote = char;
777
+ started = true;
778
+ continue;
779
+ }
780
+ if (/\s/.test(char)) {
781
+ if (started) {
782
+ parts.push(current);
783
+ current = "";
784
+ started = false;
785
+ }
786
+ continue;
787
+ }
788
+ current += char;
789
+ started = true;
790
+ }
791
+ if (quote !== null)
792
+ throw new Error("A quote in the command is not closed.");
793
+ if (started)
794
+ parts.push(current);
795
+ return parts;
796
+ }