borgmcp 1.1.15 → 2.0.1

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 (459) hide show
  1. package/CONTRIBUTING.md +15 -0
  2. package/LICENSE +201 -33
  3. package/NOTICE +2 -0
  4. package/README.md +85 -36
  5. package/SECURITY.md +7 -0
  6. package/dist/agent-runtime.d.ts.map +1 -0
  7. package/dist/agent-runtime.js +63 -1
  8. package/dist/agent-runtime.js.map +1 -0
  9. package/dist/assimilate-cmd.d.ts +96 -17
  10. package/dist/assimilate-cmd.d.ts.map +1 -0
  11. package/dist/assimilate-cmd.js +1396 -48
  12. package/dist/assimilate-cmd.js.map +1 -0
  13. package/dist/assimilate-deps.d.ts.map +1 -0
  14. package/dist/assimilate-deps.js +384 -3
  15. package/dist/assimilate-deps.js.map +1 -0
  16. package/dist/assimilate-guard.d.ts +5 -7
  17. package/dist/assimilate-guard.d.ts.map +1 -0
  18. package/dist/assimilate-guard.js +60 -2
  19. package/dist/assimilate-guard.js.map +1 -0
  20. package/dist/assimilate-welcome.d.ts +1 -1
  21. package/dist/assimilate-welcome.d.ts.map +1 -0
  22. package/dist/assimilate-welcome.js +32 -2
  23. package/dist/assimilate-welcome.js.map +1 -0
  24. package/dist/auth-env.d.ts +7 -50
  25. package/dist/auth-env.d.ts.map +1 -0
  26. package/dist/auth-env.js +22 -1
  27. package/dist/auth-env.js.map +1 -0
  28. package/dist/backends/launch-all-pastelist.d.ts.map +1 -0
  29. package/dist/backends/launch-all-pastelist.js +11 -4
  30. package/dist/backends/launch-all-pastelist.js.map +1 -0
  31. package/dist/backends/launch-all-tmux.d.ts.map +1 -0
  32. package/dist/backends/launch-all-tmux.js +49 -1
  33. package/dist/backends/launch-all-tmux.js.map +1 -0
  34. package/dist/backends/launch-all-windows.d.ts.map +1 -0
  35. package/dist/backends/launch-all-windows.js +86 -12
  36. package/dist/backends/launch-all-windows.js.map +1 -0
  37. package/dist/bare-launch-menu.d.ts.map +1 -0
  38. package/dist/bare-launch-menu.js +83 -6
  39. package/dist/bare-launch-menu.js.map +1 -0
  40. package/dist/claude-launch-args.d.ts.map +1 -0
  41. package/dist/claude-launch-args.js +34 -1
  42. package/dist/claude-launch-args.js.map +1 -0
  43. package/dist/claude.d.ts.map +1 -0
  44. package/dist/claude.js +486 -14
  45. package/dist/claude.js.map +1 -0
  46. package/dist/cleanup-cmd.d.ts +5 -5
  47. package/dist/cleanup-cmd.d.ts.map +1 -0
  48. package/dist/cleanup-cmd.js +453 -14
  49. package/dist/cleanup-cmd.js.map +1 -0
  50. package/dist/clear-rewake-core.d.ts.map +1 -0
  51. package/dist/clear-rewake-core.js +23 -2
  52. package/dist/clear-rewake-core.js.map +1 -0
  53. package/dist/clear-rewake.d.ts.map +1 -0
  54. package/dist/clear-rewake.js +19 -1
  55. package/dist/clear-rewake.js.map +1 -0
  56. package/dist/cli-help.d.ts +5 -0
  57. package/dist/cli-help.d.ts.map +1 -0
  58. package/dist/cli-help.js +104 -56
  59. package/dist/cli-help.js.map +1 -0
  60. package/dist/cli-platform.d.ts.map +1 -0
  61. package/dist/cli-platform.js +106 -4
  62. package/dist/cli-platform.js.map +1 -0
  63. package/dist/cli-tool-approval.d.ts +84 -0
  64. package/dist/cli-tool-approval.d.ts.map +1 -0
  65. package/dist/cli-tool-approval.js +583 -0
  66. package/dist/cli-tool-approval.js.map +1 -0
  67. package/dist/codex-app-server.d.ts.map +1 -0
  68. package/dist/codex-app-server.js +228 -4
  69. package/dist/codex-app-server.js.map +1 -0
  70. package/dist/codex-app-wake.d.ts.map +1 -0
  71. package/dist/codex-app-wake.js +432 -2
  72. package/dist/codex-app-wake.js.map +1 -0
  73. package/dist/codex-launch.d.ts.map +1 -0
  74. package/dist/codex-launch.js +102 -2
  75. package/dist/codex-launch.js.map +1 -0
  76. package/dist/codex-remote.d.ts +6 -1
  77. package/dist/codex-remote.d.ts.map +1 -0
  78. package/dist/codex-remote.js +394 -1
  79. package/dist/codex-remote.js.map +1 -0
  80. package/dist/codex-wake-resolve.d.ts.map +1 -0
  81. package/dist/codex-wake-resolve.js +127 -1
  82. package/dist/codex-wake-resolve.js.map +1 -0
  83. package/dist/config-utils.d.ts +2 -2
  84. package/dist/config-utils.d.ts.map +1 -0
  85. package/dist/config-utils.js +766 -3
  86. package/dist/config-utils.js.map +1 -0
  87. package/dist/config.d.ts +98 -61
  88. package/dist/config.d.ts.map +1 -0
  89. package/dist/config.js +426 -1
  90. package/dist/config.js.map +1 -0
  91. package/dist/console-prefix.d.ts.map +1 -0
  92. package/dist/console-prefix.js +96 -1
  93. package/dist/console-prefix.js.map +1 -0
  94. package/dist/cube-name.d.ts.map +1 -0
  95. package/dist/cube-name.js +67 -1
  96. package/dist/cube-name.js.map +1 -0
  97. package/dist/cubes.d.ts +126 -18
  98. package/dist/cubes.d.ts.map +1 -0
  99. package/dist/cubes.js +412 -4
  100. package/dist/cubes.js.map +1 -0
  101. package/dist/debug.d.ts.map +1 -0
  102. package/dist/debug.js +71 -1
  103. package/dist/debug.js.map +1 -0
  104. package/dist/direct-log.d.ts.map +1 -0
  105. package/dist/direct-log.js +11 -1
  106. package/dist/direct-log.js.map +1 -0
  107. package/dist/docs-sections.d.ts +11 -10
  108. package/dist/docs-sections.d.ts.map +1 -0
  109. package/dist/docs-sections.js +113 -3
  110. package/dist/docs-sections.js.map +1 -0
  111. package/dist/drone-lifecycle.d.ts +5 -21
  112. package/dist/drone-lifecycle.d.ts.map +1 -0
  113. package/dist/drone-lifecycle.js +57 -8
  114. package/dist/drone-lifecycle.js.map +1 -0
  115. package/dist/drone-management.d.ts +29 -0
  116. package/dist/drone-management.d.ts.map +1 -0
  117. package/dist/drone-management.js +109 -0
  118. package/dist/drone-management.js.map +1 -0
  119. package/dist/ensure-mcp-config.d.ts.map +1 -0
  120. package/dist/ensure-mcp-config.js +36 -1
  121. package/dist/ensure-mcp-config.js.map +1 -0
  122. package/dist/evict-drone.d.ts +10 -2
  123. package/dist/evict-drone.d.ts.map +1 -0
  124. package/dist/evict-drone.js +60 -1
  125. package/dist/evict-drone.js.map +1 -0
  126. package/dist/gc-orphan-inboxes.d.ts.map +1 -0
  127. package/dist/gc-orphan-inboxes.js +177 -1
  128. package/dist/gc-orphan-inboxes.js.map +1 -0
  129. package/dist/inbox-monitor.d.ts +1 -1
  130. package/dist/inbox-monitor.d.ts.map +1 -0
  131. package/dist/inbox-monitor.js +856 -3
  132. package/dist/inbox-monitor.js.map +1 -0
  133. package/dist/index.d.ts +6 -4
  134. package/dist/index.d.ts.map +1 -0
  135. package/dist/index.js +1034 -37
  136. package/dist/index.js.map +1 -0
  137. package/dist/launch-all-cmd.d.ts.map +1 -0
  138. package/dist/launch-all-cmd.js +377 -37
  139. package/dist/launch-all-cmd.js.map +1 -0
  140. package/dist/launch-all-command.d.ts.map +1 -0
  141. package/dist/launch-all-command.js +24 -1
  142. package/dist/launch-all-command.js.map +1 -0
  143. package/dist/launch-all-deps.d.ts +3 -8
  144. package/dist/launch-all-deps.d.ts.map +1 -0
  145. package/dist/launch-all-deps.js +99 -1
  146. package/dist/launch-all-deps.js.map +1 -0
  147. package/dist/launch-all-discovery.d.ts +1 -0
  148. package/dist/launch-all-discovery.d.ts.map +1 -0
  149. package/dist/launch-all-discovery.js +90 -4
  150. package/dist/launch-all-discovery.js.map +1 -0
  151. package/dist/launch-all-locks.d.ts.map +1 -0
  152. package/dist/launch-all-locks.js +54 -1
  153. package/dist/launch-all-locks.js.map +1 -0
  154. package/dist/launch-gate.d.ts +2 -2
  155. package/dist/launch-gate.d.ts.map +1 -0
  156. package/dist/launch-gate.js +73 -1
  157. package/dist/launch-gate.js.map +1 -0
  158. package/dist/lifecycle-log-guard.d.ts.map +1 -0
  159. package/dist/lifecycle-log-guard.js +93 -2
  160. package/dist/lifecycle-log-guard.js.map +1 -0
  161. package/dist/list-roles-render.d.ts.map +1 -0
  162. package/dist/list-roles-render.js +40 -6
  163. package/dist/list-roles-render.js.map +1 -0
  164. package/dist/local-log-routing.d.ts +19 -0
  165. package/dist/local-log-routing.d.ts.map +1 -0
  166. package/dist/local-log-routing.js +67 -0
  167. package/dist/local-log-routing.js.map +1 -0
  168. package/dist/local-manage-tool-result.d.ts +8 -0
  169. package/dist/local-manage-tool-result.d.ts.map +1 -0
  170. package/dist/local-manage-tool-result.js +10 -0
  171. package/dist/local-manage-tool-result.js.map +1 -0
  172. package/dist/local-server-cursor.d.ts +34 -0
  173. package/dist/local-server-cursor.d.ts.map +1 -0
  174. package/dist/local-server-cursor.js +151 -0
  175. package/dist/local-server-cursor.js.map +1 -0
  176. package/dist/log-audit.d.ts.map +1 -0
  177. package/dist/log-audit.js +194 -3
  178. package/dist/log-audit.js.map +1 -0
  179. package/dist/log-stream.d.ts +31 -18
  180. package/dist/log-stream.d.ts.map +1 -0
  181. package/dist/log-stream.js +1214 -12
  182. package/dist/log-stream.js.map +1 -0
  183. package/dist/model-presets.d.ts.map +1 -0
  184. package/dist/model-presets.js +19 -1
  185. package/dist/model-presets.js.map +1 -0
  186. package/dist/name-validator.d.ts.map +1 -0
  187. package/dist/name-validator.js +22 -1
  188. package/dist/name-validator.js.map +1 -0
  189. package/dist/opencode-drone.d.ts.map +1 -0
  190. package/dist/opencode-drone.js +438 -4
  191. package/dist/opencode-drone.js.map +1 -0
  192. package/dist/opencode-plugin.d.ts +1 -1
  193. package/dist/opencode-plugin.d.ts.map +1 -0
  194. package/dist/opencode-plugin.js +29 -4
  195. package/dist/opencode-plugin.js.map +1 -0
  196. package/dist/parse-assimilate-args.d.ts +1 -1
  197. package/dist/parse-assimilate-args.d.ts.map +1 -0
  198. package/dist/parse-assimilate-args.js +125 -1
  199. package/dist/parse-assimilate-args.js.map +1 -0
  200. package/dist/parse-launch-all-args.d.ts.map +1 -0
  201. package/dist/parse-launch-all-args.js +70 -1
  202. package/dist/parse-launch-all-args.js.map +1 -0
  203. package/dist/postinstall-banner.d.ts.map +1 -0
  204. package/dist/postinstall-banner.js +27 -2
  205. package/dist/postinstall-banner.js.map +1 -0
  206. package/dist/postinstall.d.ts.map +1 -0
  207. package/dist/postinstall.js +32 -5
  208. package/dist/postinstall.js.map +1 -0
  209. package/dist/readiness-probe.d.ts.map +1 -0
  210. package/dist/readiness-probe.js +9 -1
  211. package/dist/readiness-probe.js.map +1 -0
  212. package/dist/regen-format.d.ts.map +1 -0
  213. package/dist/regen-format.js +545 -13
  214. package/dist/regen-format.js.map +1 -0
  215. package/dist/regen.d.ts.map +1 -0
  216. package/dist/regen.js +141 -5
  217. package/dist/regen.js.map +1 -0
  218. package/dist/remote-client.d.ts +65 -175
  219. package/dist/remote-client.d.ts.map +1 -0
  220. package/dist/remote-client.js +942 -1
  221. package/dist/remote-client.js.map +1 -0
  222. package/dist/reset-local-seat-cmd.d.ts +50 -0
  223. package/dist/reset-local-seat-cmd.d.ts.map +1 -0
  224. package/dist/reset-local-seat-cmd.js +169 -0
  225. package/dist/reset-local-seat-cmd.js.map +1 -0
  226. package/dist/role-match.d.ts.map +1 -0
  227. package/dist/role-match.js +23 -1
  228. package/dist/role-match.js.map +1 -0
  229. package/dist/role-resolver.d.ts.map +1 -0
  230. package/dist/role-resolver.js +60 -1
  231. package/dist/role-resolver.js.map +1 -0
  232. package/dist/roster-render.d.ts.map +1 -0
  233. package/dist/roster-render.js +121 -3
  234. package/dist/roster-render.js.map +1 -0
  235. package/dist/seat-probe.d.ts +32 -13
  236. package/dist/seat-probe.d.ts.map +1 -0
  237. package/dist/seat-probe.js +78 -1
  238. package/dist/seat-probe.js.map +1 -0
  239. package/dist/seat-store.d.ts +76 -0
  240. package/dist/seat-store.d.ts.map +1 -0
  241. package/dist/seat-store.js +309 -0
  242. package/dist/seat-store.js.map +1 -0
  243. package/dist/seats.d.ts +303 -0
  244. package/dist/seats.d.ts.map +1 -0
  245. package/dist/seats.js +551 -0
  246. package/dist/seats.js.map +1 -0
  247. package/dist/self-path.d.ts +27 -0
  248. package/dist/self-path.d.ts.map +1 -0
  249. package/dist/self-path.js +43 -0
  250. package/dist/self-path.js.map +1 -0
  251. package/dist/server-endpoint.d.ts +9 -0
  252. package/dist/server-endpoint.d.ts.map +1 -0
  253. package/dist/server-endpoint.js +38 -0
  254. package/dist/server-endpoint.js.map +1 -0
  255. package/dist/server-errors.d.ts +50 -0
  256. package/dist/server-errors.d.ts.map +1 -0
  257. package/dist/server-errors.js +79 -0
  258. package/dist/server-errors.js.map +1 -0
  259. package/dist/server-handshake.d.ts +197 -0
  260. package/dist/server-handshake.d.ts.map +1 -0
  261. package/dist/server-handshake.js +505 -0
  262. package/dist/server-handshake.js.map +1 -0
  263. package/dist/server-response.d.ts +3 -0
  264. package/dist/server-response.d.ts.map +1 -0
  265. package/dist/server-response.js +67 -0
  266. package/dist/server-response.js.map +1 -0
  267. package/dist/server-trust.d.ts +14 -0
  268. package/dist/server-trust.d.ts.map +1 -0
  269. package/dist/server-trust.js +225 -0
  270. package/dist/server-trust.js.map +1 -0
  271. package/dist/setup-confirm.d.ts +3 -3
  272. package/dist/setup-confirm.d.ts.map +1 -0
  273. package/dist/setup-confirm.js +126 -2
  274. package/dist/setup-confirm.js.map +1 -0
  275. package/dist/setup.d.ts +5 -3
  276. package/dist/setup.d.ts.map +1 -0
  277. package/dist/setup.js +169 -46
  278. package/dist/setup.js.map +1 -0
  279. package/dist/shell-escape.d.ts.map +1 -0
  280. package/dist/shell-escape.js +22 -1
  281. package/dist/shell-escape.js.map +1 -0
  282. package/dist/spawn.d.ts +2 -2
  283. package/dist/spawn.d.ts.map +1 -0
  284. package/dist/spawn.js +29 -10
  285. package/dist/spawn.js.map +1 -0
  286. package/dist/startup-services.d.ts +0 -1
  287. package/dist/startup-services.d.ts.map +1 -0
  288. package/dist/startup-services.js +26 -1
  289. package/dist/startup-services.js.map +1 -0
  290. package/dist/stream-owner.d.ts +6 -2
  291. package/dist/stream-owner.d.ts.map +1 -0
  292. package/dist/stream-owner.js +547 -2
  293. package/dist/stream-owner.js.map +1 -0
  294. package/dist/stream-status.d.ts +1 -1
  295. package/dist/stream-status.d.ts.map +1 -0
  296. package/dist/stream-status.js +268 -3
  297. package/dist/stream-status.js.map +1 -0
  298. package/dist/sync-roles-render.d.ts.map +1 -0
  299. package/dist/sync-roles-render.js +118 -3
  300. package/dist/sync-roles-render.js.map +1 -0
  301. package/dist/sync.d.ts +1 -1
  302. package/dist/sync.d.ts.map +1 -0
  303. package/dist/sync.js +286 -22
  304. package/dist/sync.js.map +1 -0
  305. package/dist/terminal-title.d.ts.map +1 -0
  306. package/dist/terminal-title.js +68 -1
  307. package/dist/terminal-title.js.map +1 -0
  308. package/dist/token-store.d.ts +17 -111
  309. package/dist/token-store.d.ts.map +1 -0
  310. package/dist/token-store.js +81 -1
  311. package/dist/token-store.js.map +1 -0
  312. package/dist/tool-manifest.d.ts +4 -8
  313. package/dist/tool-manifest.d.ts.map +1 -0
  314. package/dist/tool-manifest.js +554 -1
  315. package/dist/tool-manifest.js.map +1 -0
  316. package/dist/tool-scope.d.ts +8 -12
  317. package/dist/tool-scope.d.ts.map +1 -0
  318. package/dist/tool-scope.js +97 -1
  319. package/dist/tool-scope.js.map +1 -0
  320. package/dist/types.d.ts +1 -5
  321. package/dist/types.d.ts.map +1 -0
  322. package/dist/types.js +5 -0
  323. package/dist/types.js.map +1 -0
  324. package/dist/unknown-subcommand.d.ts +1 -1
  325. package/dist/unknown-subcommand.d.ts.map +1 -0
  326. package/dist/unknown-subcommand.js +37 -1
  327. package/dist/unknown-subcommand.js.map +1 -0
  328. package/dist/version.d.ts +1 -1
  329. package/dist/version.d.ts.map +1 -0
  330. package/dist/version.js +78 -2
  331. package/dist/version.js.map +1 -0
  332. package/dist/working-repo.d.ts.map +1 -0
  333. package/dist/working-repo.js +81 -1
  334. package/dist/working-repo.js.map +1 -0
  335. package/dist/worktree-lifecycle.d.ts.map +1 -0
  336. package/dist/worktree-lifecycle.js +205 -2
  337. package/dist/worktree-lifecycle.js.map +1 -0
  338. package/docs/EXTRACTION_PROVENANCE.md +40 -0
  339. package/docs/LOCAL_SERVER.md +103 -0
  340. package/docs/RELEASING.md +120 -0
  341. package/package.json +52 -34
  342. package/src/agent-runtime.ts +69 -0
  343. package/src/assimilate-cmd.ts +1864 -0
  344. package/src/assimilate-deps.ts +444 -0
  345. package/src/assimilate-guard.ts +84 -0
  346. package/src/assimilate-welcome.ts +39 -0
  347. package/src/auth-env.ts +21 -0
  348. package/src/backends/launch-all-pastelist.ts +18 -0
  349. package/src/backends/launch-all-tmux.ts +75 -0
  350. package/src/backends/launch-all-windows.ts +110 -0
  351. package/src/bare-launch-menu.ts +148 -0
  352. package/src/claude-launch-args.ts +35 -0
  353. package/src/claude.ts +561 -0
  354. package/src/cleanup-cmd.ts +581 -0
  355. package/src/clear-rewake-core.ts +34 -0
  356. package/src/clear-rewake.ts +18 -0
  357. package/src/cli-help.ts +116 -0
  358. package/src/cli-platform.ts +127 -0
  359. package/src/cli-tool-approval.ts +716 -0
  360. package/src/codex-app-server.ts +238 -0
  361. package/src/codex-app-wake.ts +512 -0
  362. package/src/codex-launch.ts +140 -0
  363. package/src/codex-remote.ts +500 -0
  364. package/src/codex-wake-resolve.ts +155 -0
  365. package/src/config-utils.ts +811 -0
  366. package/src/config.ts +595 -0
  367. package/src/console-prefix.ts +99 -0
  368. package/src/cube-name.ts +69 -0
  369. package/src/cubes.ts +583 -0
  370. package/src/debug.ts +73 -0
  371. package/src/direct-log.ts +9 -0
  372. package/src/docs-sections.ts +129 -0
  373. package/src/drone-lifecycle.ts +62 -0
  374. package/src/drone-management.ts +162 -0
  375. package/src/ensure-mcp-config.ts +54 -0
  376. package/src/evict-drone.ts +82 -0
  377. package/src/gc-orphan-inboxes.ts +229 -0
  378. package/src/inbox-monitor.ts +1005 -0
  379. package/src/index.ts +1201 -0
  380. package/src/launch-all-cmd.ts +450 -0
  381. package/src/launch-all-command.ts +30 -0
  382. package/src/launch-all-deps.ts +188 -0
  383. package/src/launch-all-discovery.ts +118 -0
  384. package/src/launch-all-locks.ts +74 -0
  385. package/src/launch-gate.ts +80 -0
  386. package/src/lifecycle-log-guard.ts +144 -0
  387. package/src/list-roles-render.ts +52 -0
  388. package/src/local-log-routing.ts +106 -0
  389. package/src/local-manage-tool-result.ts +10 -0
  390. package/src/local-server-cursor.ts +188 -0
  391. package/src/log-audit.ts +207 -0
  392. package/src/log-stream.ts +1505 -0
  393. package/src/model-presets.ts +22 -0
  394. package/src/name-validator.ts +23 -0
  395. package/src/opencode-drone.ts +528 -0
  396. package/src/opencode-plugin.ts +38 -0
  397. package/src/parse-assimilate-args.ts +125 -0
  398. package/src/parse-launch-all-args.ts +100 -0
  399. package/src/postinstall-banner.ts +37 -0
  400. package/src/postinstall.ts +36 -0
  401. package/src/readiness-probe.ts +12 -0
  402. package/src/regen-format.ts +651 -0
  403. package/src/regen.ts +150 -0
  404. package/src/remote-client.ts +1379 -0
  405. package/src/reset-local-seat-cmd.ts +227 -0
  406. package/src/role-match.ts +29 -0
  407. package/src/role-resolver.ts +93 -0
  408. package/src/roster-render.ts +196 -0
  409. package/src/seat-probe.ts +115 -0
  410. package/src/seat-store.ts +333 -0
  411. package/src/seats.ts +741 -0
  412. package/src/self-path.ts +51 -0
  413. package/src/server-endpoint.ts +38 -0
  414. package/src/server-errors.ts +93 -0
  415. package/src/server-handshake.ts +783 -0
  416. package/src/server-response.ts +76 -0
  417. package/src/server-trust.ts +245 -0
  418. package/src/setup-confirm.ts +171 -0
  419. package/src/setup.ts +186 -0
  420. package/src/shell-escape.ts +22 -0
  421. package/src/spawn.ts +31 -0
  422. package/src/startup-services.ts +36 -0
  423. package/src/stream-owner.ts +686 -0
  424. package/src/stream-status.ts +337 -0
  425. package/src/sync-roles-render.ts +163 -0
  426. package/src/sync.ts +363 -0
  427. package/src/terminal-title.ts +76 -0
  428. package/src/token-store.ts +102 -0
  429. package/src/tool-manifest.ts +610 -0
  430. package/src/tool-scope.ts +110 -0
  431. package/src/types.ts +5 -0
  432. package/src/unknown-subcommand.ts +35 -0
  433. package/src/version.ts +82 -0
  434. package/src/working-repo.ts +99 -0
  435. package/src/worktree-lifecycle.ts +254 -0
  436. package/dist/auth-recovery.d.ts +0 -33
  437. package/dist/auth-recovery.js +0 -1
  438. package/dist/auth.d.ts +0 -103
  439. package/dist/auth.js +0 -39
  440. package/dist/device-auth.d.ts +0 -75
  441. package/dist/device-auth.js +0 -1
  442. package/dist/drone-address.d.ts +0 -26
  443. package/dist/drone-address.js +0 -1
  444. package/dist/get-started.d.ts +0 -20
  445. package/dist/get-started.js +0 -2
  446. package/dist/health-beat.d.ts +0 -134
  447. package/dist/health-beat.js +0 -1
  448. package/dist/role-section.d.ts +0 -110
  449. package/dist/role-section.js +0 -8
  450. package/dist/setup-action.d.ts +0 -20
  451. package/dist/setup-action.js +0 -1
  452. package/dist/stale-version-check.d.ts +0 -40
  453. package/dist/stale-version-check.js +0 -1
  454. package/dist/subscription-retry.d.ts +0 -40
  455. package/dist/subscription-retry.js +0 -1
  456. package/dist/templates.d.ts +0 -107
  457. package/dist/templates.js +0 -359
  458. package/dist/token-crypto.d.ts +0 -50
  459. package/dist/token-crypto.js +0 -1
@@ -0,0 +1,716 @@
1
+ import { spawn, spawnSync } from 'node:child_process';
2
+ import { chmod, copyFile, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import { homedir, tmpdir } from 'node:os';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import type { BorgCli } from './cubes.js';
6
+
7
+ /**
8
+ * Client #20: the narrow coordination surface a borg-launched agent needs
9
+ * without an approval round-trip. Keep the direct-tool list deliberately
10
+ * smaller than the complete Borg MCP surface. borg_tool's transitive scope is
11
+ * disclosed separately before consent.
12
+ */
13
+ export const BORG_COORDINATION_TOOLS = [
14
+ 'regen',
15
+ 'log',
16
+ 'read-log',
17
+ 'roster',
18
+ 'ack',
19
+ 'stream-status',
20
+ 'whoami',
21
+ // Required by the canonical lean orientation before acting/after compaction.
22
+ 'cube',
23
+ 'role',
24
+ 'playbook',
25
+ 'tool',
26
+ 'describe-tool',
27
+ ] as const;
28
+
29
+ export const CODEX_BORG_COORDINATION_TOOLS = BORG_COORDINATION_TOOLS.map(
30
+ (name) => `borg:${name}`
31
+ );
32
+
33
+ // OpenCode sanitizes MCP tool names as <server>_<raw tool name>, preserving
34
+ // hyphens. The Borg raw tool prefix is itself `borg_`.
35
+ export const OPENCODE_BORG_COORDINATION_TOOLS = BORG_COORDINATION_TOOLS.map(
36
+ (name) => `borg_borg_${name}`
37
+ );
38
+
39
+ export const BORG_DISPATCHER_APPROVAL_DISCLOSURE =
40
+ 'This set includes borg_tool: approving the dispatcher also approves any Borg operation invoked through it.';
41
+
42
+ type ApprovalAction = 'auto' | 'prompt' | 'writes' | 'approve';
43
+ type OpenCodePermissionAction = 'allow' | 'ask' | 'deny';
44
+
45
+ export interface ApprovalInspection {
46
+ restrictiveTools: string[];
47
+ repairSnippet: string;
48
+ }
49
+
50
+ export interface LaunchApprovalDecision {
51
+ codexArgs: string[];
52
+ openCodePermission?: string;
53
+ warning?: string;
54
+ }
55
+
56
+ function parseCodexModes(text: string): {
57
+ defaultMode?: ApprovalAction;
58
+ toolModes: Map<string, ApprovalAction>;
59
+ } {
60
+ let section: 'other' | 'borg' | { tool: string } = 'other';
61
+ let defaultMode: ApprovalAction | undefined;
62
+ const toolModes = new Map<string, ApprovalAction>();
63
+
64
+ for (const line of text.split(/\r?\n/)) {
65
+ const header = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/);
66
+ if (header) {
67
+ if (header[1] === 'mcp_servers.borg') {
68
+ section = 'borg';
69
+ } else {
70
+ const tool = header[1].match(/^mcp_servers\.borg\.tools\."([^"]+)"$/);
71
+ section = tool ? { tool: tool[1] } : 'other';
72
+ }
73
+ continue;
74
+ }
75
+
76
+ const value = line.match(
77
+ /^\s*(default_tools_approval_mode|approval_mode)\s*=\s*["'](auto|prompt|writes|approve)["']\s*(?:#.*)?$/
78
+ );
79
+ if (!value) continue;
80
+ const mode = value[2] as ApprovalAction;
81
+ if (section === 'borg' && value[1] === 'default_tools_approval_mode') {
82
+ defaultMode = mode;
83
+ } else if (typeof section === 'object' && value[1] === 'approval_mode') {
84
+ toolModes.set(section.tool, mode);
85
+ }
86
+ }
87
+
88
+ return { ...(defaultMode ? { defaultMode } : {}), toolModes };
89
+ }
90
+
91
+ export function codexApprovalRepairSnippet(tools = CODEX_BORG_COORDINATION_TOOLS): string {
92
+ return tools
93
+ .map(
94
+ (tool) =>
95
+ `[mcp_servers.borg.tools."${tool}"]\n` +
96
+ `approval_mode = "auto"`
97
+ )
98
+ .join('\n\n');
99
+ }
100
+
101
+ export function inspectCodexBorgApprovals(config: unknown): ApprovalInspection {
102
+ let defaultMode: ApprovalAction | undefined;
103
+ let toolModes = new Map<string, ApprovalAction>();
104
+ if (typeof config === 'string') {
105
+ ({ defaultMode, toolModes } = parseCodexModes(config));
106
+ } else if (config && typeof config === 'object' && !Array.isArray(config)) {
107
+ const servers = (config as Record<string, unknown>).mcp_servers;
108
+ const borg = servers && typeof servers === 'object' && !Array.isArray(servers)
109
+ ? (servers as Record<string, unknown>).borg
110
+ : undefined;
111
+ if (borg && typeof borg === 'object' && !Array.isArray(borg)) {
112
+ const record = borg as Record<string, unknown>;
113
+ if (typeof record.default_tools_approval_mode === 'string') {
114
+ defaultMode = record.default_tools_approval_mode as ApprovalAction;
115
+ }
116
+ if (record.tools && typeof record.tools === 'object' && !Array.isArray(record.tools)) {
117
+ toolModes = new Map(Object.entries(record.tools as Record<string, unknown>).flatMap(
118
+ ([tool, value]) => value && typeof value === 'object' &&
119
+ typeof (value as Record<string, unknown>).approval_mode === 'string'
120
+ ? [[tool, (value as Record<string, unknown>).approval_mode as ApprovalAction] as const]
121
+ : []
122
+ ));
123
+ }
124
+ }
125
+ }
126
+ const restrictiveTools = CODEX_BORG_COORDINATION_TOOLS.filter((tool) => {
127
+ const mode = toolModes.get(tool) ?? defaultMode;
128
+ return mode !== undefined && mode !== 'auto';
129
+ });
130
+ return {
131
+ restrictiveTools,
132
+ repairSnippet: codexApprovalRepairSnippet(restrictiveTools.length > 0 ? restrictiveTools : CODEX_BORG_COORDINATION_TOOLS),
133
+ };
134
+ }
135
+
136
+ function globMatches(pattern: string, value: string): boolean {
137
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.');
138
+ return new RegExp(`^${escaped}$`).test(value);
139
+ }
140
+
141
+ function effectiveOpenCodePermission(
142
+ permission: unknown,
143
+ tool: string
144
+ ): OpenCodePermissionAction | undefined {
145
+ if (permission === 'allow' || permission === 'ask' || permission === 'deny') {
146
+ return permission;
147
+ }
148
+ if (!permission || typeof permission !== 'object' || Array.isArray(permission)) return undefined;
149
+
150
+ let result: OpenCodePermissionAction | undefined;
151
+ for (const [pattern, action] of Object.entries(permission as Record<string, unknown>)) {
152
+ if (
153
+ globMatches(pattern, tool) &&
154
+ (action === 'allow' || action === 'ask' || action === 'deny')
155
+ ) {
156
+ result = action;
157
+ }
158
+ }
159
+ return result;
160
+ }
161
+
162
+ export function openCodeApprovalRepairObject(
163
+ tools = OPENCODE_BORG_COORDINATION_TOOLS
164
+ ): Record<string, OpenCodePermissionAction> {
165
+ return Object.fromEntries(tools.map((tool) => [tool, 'allow'])) as Record<
166
+ string,
167
+ OpenCodePermissionAction
168
+ >;
169
+ }
170
+
171
+ /** Preserve every existing OpenCode permission rule, then append the exact
172
+ * Borg coordination allows. This remains safe whether OPENCODE_PERMISSION is
173
+ * deep-merged or replaces the configured permission value. */
174
+ export function mergeOpenCodePermission(
175
+ permission: unknown,
176
+ tools = OPENCODE_BORG_COORDINATION_TOOLS
177
+ ): Record<string, unknown> {
178
+ let existing: Record<string, unknown> = {};
179
+ if (permission === 'allow' || permission === 'ask' || permission === 'deny') {
180
+ existing = { '*': permission };
181
+ } else if (permission && typeof permission === 'object' && !Array.isArray(permission)) {
182
+ existing = { ...(permission as Record<string, unknown>) };
183
+ }
184
+ // Reinsert exact keys at the END. OpenCode resolves matching permission
185
+ // patterns in order, so merely overwriting an earlier exact key would keep
186
+ // its old insertion position and a later wildcard could still win.
187
+ const toolSet = new Set<string>(tools);
188
+ const preserved = Object.fromEntries(
189
+ Object.entries(existing).filter(([key]) => !toolSet.has(key))
190
+ );
191
+ return { ...preserved, ...openCodeApprovalRepairObject(tools) };
192
+ }
193
+
194
+ export function inspectOpenCodeBorgApprovals(config: unknown): ApprovalInspection {
195
+ const permission =
196
+ config && typeof config === 'object' && !Array.isArray(config)
197
+ ? (config as Record<string, unknown>).permission
198
+ : undefined;
199
+ const restrictiveTools = OPENCODE_BORG_COORDINATION_TOOLS.filter((tool) => {
200
+ const action = effectiveOpenCodePermission(permission, tool);
201
+ return action === 'ask' || action === 'deny';
202
+ });
203
+ return {
204
+ restrictiveTools,
205
+ repairSnippet: JSON.stringify({ permission: mergeOpenCodePermission(permission, restrictiveTools.length > 0 ? restrictiveTools : OPENCODE_BORG_COORDINATION_TOOLS) }, null, 2),
206
+ };
207
+ }
208
+
209
+ export function codexBorgApprovalArgs(tools = CODEX_BORG_COORDINATION_TOOLS): string[] {
210
+ if (tools.length === 0) return [];
211
+ // Codex's dotted CLI override parser treats quotes in a segment literally,
212
+ // so `tools."borg:regen"` creates the wrong key. An inline TOML table is the
213
+ // supported way to address colon-named tools and deep-merges with the
214
+ // remaining effective tool config.
215
+ const toolTable = tools
216
+ .map((tool) => `${JSON.stringify(tool)}={approval_mode="auto"}`)
217
+ .join(',');
218
+ return ['-c', `mcp_servers.borg.tools={${toolTable}}`];
219
+ }
220
+
221
+ export interface ApprovalIo {
222
+ readCodexConfig: (approvalArgs?: string[]) => Promise<unknown> | unknown;
223
+ readOpenCodeConfig: (permissionOverride?: string) => Promise<unknown> | unknown;
224
+ isTTY: () => boolean;
225
+ confirm: (message: string) => Promise<string>;
226
+ }
227
+
228
+ export interface EffectiveConfigOptions {
229
+ cwd: string;
230
+ env: NodeJS.ProcessEnv;
231
+ /** User-selected Codex profile/config flags, in their launch precedence. */
232
+ codexArgs: string[];
233
+ loadCodex?: (
234
+ args: string[],
235
+ cwd: string,
236
+ env: NodeJS.ProcessEnv,
237
+ profile?: string
238
+ ) => Promise<unknown>;
239
+ loadOpenCode?: (cwd: string, env: NodeJS.ProcessEnv) => Promise<unknown> | unknown;
240
+ }
241
+
242
+ export interface CodexEffectiveConfigRuntime {
243
+ spawnProcess?: typeof spawn;
244
+ timeoutMs?: number;
245
+ maxResponseBytes?: number;
246
+ profile?: string;
247
+ }
248
+
249
+ /** Keep only flags that app-server supports and that participate in config
250
+ * resolution. Runtime-only --profile/-p is resolved separately because Codex
251
+ * rejects it on the app-server subcommand. */
252
+ export function codexEffectiveConfigArgs(args: string[]): string[] {
253
+ const out: string[] = [];
254
+ const paired = new Set(['-c', '--config', '--enable', '--disable']);
255
+ for (let i = 0; i < args.length; i += 1) {
256
+ const arg = args[i];
257
+ if (arg === '--') break;
258
+ if (arg === '-p' || arg === '--profile') {
259
+ if (args[i + 1] !== undefined) i += 1;
260
+ continue;
261
+ }
262
+ if (arg.startsWith('--profile=') || (arg.startsWith('-p') && arg.length > 2)) {
263
+ continue;
264
+ }
265
+ if (paired.has(arg)) {
266
+ if (args[i + 1] !== undefined) out.push(arg, args[++i]);
267
+ continue;
268
+ }
269
+ if (/^--(?:config|enable|disable)=/.test(arg) || arg === '--strict-config') {
270
+ out.push(arg);
271
+ }
272
+ }
273
+ return out;
274
+ }
275
+
276
+ /** Resolve Codex's selected runtime profile. Short attached forms are accepted
277
+ * by Codex/clap; the final occurrence before -- wins. */
278
+ export function codexSelectedProfile(args: string[]): string | undefined {
279
+ let selected: string | undefined;
280
+ for (let index = 0; index < args.length; index += 1) {
281
+ const arg = args[index];
282
+ if (arg === '--') break;
283
+ if (arg === '-p' || arg === '--profile') {
284
+ const value = args[index + 1];
285
+ if (value !== undefined && value.length > 0) {
286
+ selected = value;
287
+ index += 1;
288
+ }
289
+ continue;
290
+ }
291
+ if (arg.startsWith('--profile=')) {
292
+ const value = arg.slice('--profile='.length);
293
+ if (value.length > 0) selected = value;
294
+ continue;
295
+ }
296
+ if (arg.startsWith('-p') && arg.length > 2) {
297
+ const value = arg.slice(2).replace(/^=/, '');
298
+ if (value.length > 0) selected = value;
299
+ }
300
+ }
301
+ return selected;
302
+ }
303
+
304
+ interface CodexConfigLayer {
305
+ name?: { type?: string; profile?: string | null };
306
+ config?: unknown;
307
+ disabledReason?: string | null;
308
+ }
309
+
310
+ interface CodexConfigSnapshot {
311
+ config?: unknown;
312
+ layers?: CodexConfigLayer[] | null;
313
+ }
314
+
315
+ function isRecord(value: unknown): value is Record<string, unknown> {
316
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
317
+ }
318
+
319
+ function mergeConfigValue(base: unknown, overlay: unknown): unknown {
320
+ if (!isRecord(base) || !isRecord(overlay)) return structuredClone(overlay);
321
+ const merged: Record<string, unknown> = structuredClone(base);
322
+ for (const [key, value] of Object.entries(overlay)) {
323
+ merged[key] = key in merged ? mergeConfigValue(merged[key], value) : structuredClone(value);
324
+ }
325
+ return merged;
326
+ }
327
+
328
+ function borgConfigFragment(config: unknown): unknown {
329
+ if (!isRecord(config) || !isRecord(config.mcp_servers)) return {};
330
+ const borg = config.mcp_servers.borg;
331
+ return isRecord(borg) ? borg : {};
332
+ }
333
+
334
+ /** Rebuild the native ordered layer stack with the selected profile inserted
335
+ * immediately above the base user layer, matching Codex's runtime loader. */
336
+ export function composeCodexProfileConfig(
337
+ snapshot: CodexConfigSnapshot,
338
+ profileConfig: unknown
339
+ ): unknown {
340
+ const layers = snapshot.layers;
341
+ if (!Array.isArray(layers)) throw new Error('Codex effective-config layers were unavailable');
342
+ let mergedBorg: unknown = {};
343
+ let inserted = false;
344
+ // config/read returns highest precedence first; Codex's merge runs from
345
+ // system/base-user upward, so replay the array in reverse.
346
+ for (const layer of [...layers].reverse()) {
347
+ if (layer.disabledReason) continue;
348
+ mergedBorg = mergeConfigValue(mergedBorg, borgConfigFragment(layer.config));
349
+ if (!inserted && layer.name?.type === 'user' && layer.name.profile == null) {
350
+ mergedBorg = mergeConfigValue(mergedBorg, borgConfigFragment(profileConfig));
351
+ inserted = true;
352
+ }
353
+ }
354
+ if (!inserted) throw new Error('Codex base user config layer was unavailable');
355
+ return { mcp_servers: { borg: mergedBorg } };
356
+ }
357
+
358
+ async function withNativeCodexProfileLayer<T>(
359
+ profile: string,
360
+ env: NodeJS.ProcessEnv,
361
+ query: (profileCwd: string, trustOverride: string) => Promise<CodexConfigSnapshot>
362
+ ): Promise<T> {
363
+ const codexHome = resolve(env.CODEX_HOME || join(homedir(), '.codex'));
364
+ const profilePath = resolve(codexHome, `${profile}.config.toml`);
365
+ if (dirname(profilePath) !== codexHome) {
366
+ throw new Error('Codex profile path was invalid');
367
+ }
368
+ const temporaryHome = await mkdtemp(join(tmpdir(), 'borg-codex-profile-'));
369
+ try {
370
+ const dotCodex = join(temporaryHome, '.codex');
371
+ const temporaryConfig = join(dotCodex, 'config.toml');
372
+ await mkdir(dotCodex, { mode: 0o700 });
373
+ // Codex discovers project config from its configured project-root markers;
374
+ // an empty .git directory is sufficient and never invokes Git itself.
375
+ await mkdir(join(temporaryHome, '.git'), { mode: 0o700 });
376
+ try {
377
+ await copyFile(profilePath, temporaryConfig);
378
+ await chmod(temporaryConfig, 0o600);
379
+ } catch (error: any) {
380
+ if (error?.code !== 'ENOENT') throw error;
381
+ // Codex treats a selected but absent profile as an empty layer.
382
+ await writeFile(temporaryConfig, '', { mode: 0o600 });
383
+ }
384
+ const trustOverride = `projects={${JSON.stringify(temporaryHome)}={trust_level="trusted"}}`;
385
+ const snapshot = await query(temporaryHome, trustOverride);
386
+ const profileLayer = snapshot.layers?.find(
387
+ (layer) => !layer.disabledReason && layer.name?.type === 'project'
388
+ );
389
+ if (!profileLayer) throw new Error('Codex profile layer was unavailable');
390
+ return profileLayer.config as T;
391
+ } catch {
392
+ throw new Error('Codex selected-profile query failed');
393
+ } finally {
394
+ await rm(temporaryHome, { recursive: true, force: true }).catch(() => undefined);
395
+ }
396
+ }
397
+
398
+ async function readCodexConfigSnapshot(
399
+ args: string[],
400
+ cwd: string,
401
+ env: NodeJS.ProcessEnv,
402
+ runtime: CodexEffectiveConfigRuntime
403
+ ): Promise<CodexConfigSnapshot> {
404
+ return new Promise((resolveSnapshot, reject) => {
405
+ let child: ReturnType<typeof spawn>;
406
+ try {
407
+ child = (runtime.spawnProcess ?? spawn)(
408
+ 'codex',
409
+ [...args, 'app-server', '--stdio'],
410
+ { cwd, env, stdio: ['pipe', 'pipe', 'ignore'] }
411
+ );
412
+ } catch {
413
+ reject(new Error('Codex effective-config query failed'));
414
+ return;
415
+ }
416
+ if (!child.stdin || !child.stdout) {
417
+ try {
418
+ child.kill();
419
+ } catch {
420
+ // No usable protocol streams exist; reject with a static failure.
421
+ }
422
+ reject(new Error('Codex effective-config query failed'));
423
+ return;
424
+ }
425
+ const stdin = child.stdin;
426
+ const stdout = child.stdout;
427
+ let buffer = '';
428
+ let receivedBytes = 0;
429
+ let initialized = false;
430
+ let settled = false;
431
+ let timer: NodeJS.Timeout | undefined;
432
+ const finish = (error?: Error, value?: unknown) => {
433
+ if (settled) return;
434
+ settled = true;
435
+ if (timer) clearTimeout(timer);
436
+ try {
437
+ child.kill();
438
+ } catch {
439
+ // The query is already settled; process teardown is best-effort.
440
+ }
441
+ if (error) reject(error);
442
+ else resolveSnapshot(value as CodexConfigSnapshot);
443
+ };
444
+ const fail = () => finish(new Error('Codex effective-config query failed'));
445
+ const safeWrite = (payload: string): boolean => {
446
+ if (settled) return false;
447
+ try {
448
+ stdin.write(payload, (error) => {
449
+ if (error) fail();
450
+ });
451
+ return true;
452
+ } catch {
453
+ fail();
454
+ return false;
455
+ }
456
+ };
457
+ timer = setTimeout(
458
+ () => finish(new Error('Codex effective-config query timed out')),
459
+ runtime.timeoutMs ?? 5_000
460
+ );
461
+ child.on('error', fail);
462
+ stdin.on('error', fail);
463
+ stdout.on('error', fail);
464
+ child.on('exit', () => {
465
+ if (!settled) finish(new Error('Codex effective-config query exited before responding'));
466
+ });
467
+ stdout.on('data', (chunk: Buffer) => {
468
+ if (settled) return;
469
+ receivedBytes += chunk.length;
470
+ if (receivedBytes > (runtime.maxResponseBytes ?? 4 * 1024 * 1024)) {
471
+ finish(new Error('Codex effective-config response exceeded 4 MiB'));
472
+ return;
473
+ }
474
+ buffer += chunk.toString('utf8');
475
+ for (;;) {
476
+ if (settled) return;
477
+ const newline = buffer.indexOf('\n');
478
+ if (newline < 0) break;
479
+ const line = buffer.slice(0, newline).trim();
480
+ buffer = buffer.slice(newline + 1);
481
+ if (!line) continue;
482
+ let message: any;
483
+ try {
484
+ message = JSON.parse(line);
485
+ } catch {
486
+ continue;
487
+ }
488
+ if (message.id === 1 && !initialized) {
489
+ initialized = true;
490
+ if (!safeWrite(`${JSON.stringify({ method: 'initialized', params: {} })}\n`)) return;
491
+ safeWrite(`${JSON.stringify({
492
+ id: 2,
493
+ method: 'config/read',
494
+ params: { cwd, includeLayers: true },
495
+ })}\n`);
496
+ } else if (message.id === 2) {
497
+ if (message.error) finish(new Error('Codex effective-config query was rejected'));
498
+ else finish(undefined, message.result ?? {});
499
+ }
500
+ }
501
+ });
502
+ safeWrite(`${JSON.stringify({
503
+ id: 1,
504
+ method: 'initialize',
505
+ params: {
506
+ clientInfo: { name: 'borgmcp', title: null, version: '0' },
507
+ capabilities: { experimentalApi: true },
508
+ },
509
+ })}\n`);
510
+ });
511
+ }
512
+
513
+ export async function readCodexEffectiveConfig(
514
+ args: string[],
515
+ cwd: string,
516
+ env: NodeJS.ProcessEnv,
517
+ runtime: CodexEffectiveConfigRuntime = {}
518
+ ): Promise<unknown> {
519
+ const snapshot = await readCodexConfigSnapshot(args, cwd, env, runtime);
520
+ if (!runtime.profile) return snapshot.config ?? null;
521
+ const profileConfig = await withNativeCodexProfileLayer<unknown>(
522
+ runtime.profile,
523
+ env,
524
+ (profileCwd, trustOverride) => readCodexConfigSnapshot(
525
+ [...args.filter((arg) => arg === '--strict-config'), '-c', trustOverride],
526
+ profileCwd,
527
+ env,
528
+ { ...runtime, profile: undefined }
529
+ )
530
+ );
531
+ return composeCodexProfileConfig(snapshot, profileConfig);
532
+ }
533
+
534
+ export function readOpenCodeEffectiveConfig(
535
+ cwd: string,
536
+ env: NodeJS.ProcessEnv
537
+ ): unknown {
538
+ const result = spawnSync('opencode', ['debug', 'config'], {
539
+ cwd,
540
+ env,
541
+ encoding: 'utf8',
542
+ timeout: 5_000,
543
+ maxBuffer: 4 * 1024 * 1024,
544
+ });
545
+ if (result.error || result.status !== 0 || !result.stdout.trim()) {
546
+ throw new Error('OpenCode effective-config query failed');
547
+ }
548
+ return JSON.parse(result.stdout);
549
+ }
550
+
551
+ export function defaultApprovalIo(
552
+ confirm: (message: string) => Promise<string>,
553
+ isTTY: () => boolean,
554
+ options: Partial<EffectiveConfigOptions> = {}
555
+ ): ApprovalIo {
556
+ const cwd = options.cwd ?? process.cwd();
557
+ const env = options.env ?? process.env;
558
+ const selectedCodexArgs = codexEffectiveConfigArgs(options.codexArgs ?? []);
559
+ const selectedCodexProfile = codexSelectedProfile(options.codexArgs ?? []);
560
+ const loadCodex = options.loadCodex ?? (
561
+ (args: string[], loadCwd: string, loadEnv: NodeJS.ProcessEnv, profile?: string) =>
562
+ readCodexEffectiveConfig(args, loadCwd, loadEnv, { profile })
563
+ );
564
+ const loadOpenCode = options.loadOpenCode ?? readOpenCodeEffectiveConfig;
565
+ return {
566
+ readCodexConfig: (approvalArgs = []) => {
567
+ const args = [...approvalArgs, ...selectedCodexArgs];
568
+ return selectedCodexProfile === undefined
569
+ ? loadCodex(args, cwd, env)
570
+ : loadCodex(args, cwd, env, selectedCodexProfile);
571
+ },
572
+ readOpenCodeConfig: (permissionOverride) =>
573
+ loadOpenCode(cwd, {
574
+ ...env,
575
+ ...(permissionOverride === undefined
576
+ ? {}
577
+ : { OPENCODE_PERMISSION: permissionOverride }),
578
+ }),
579
+ isTTY,
580
+ confirm,
581
+ };
582
+ }
583
+
584
+ function accepted(answer: string): boolean {
585
+ return /^(?:y|yes)$/i.test(answer.trim());
586
+ }
587
+
588
+ export async function resolveLaunchBorgApprovals(
589
+ cli: BorgCli,
590
+ io: ApprovalIo
591
+ ): Promise<LaunchApprovalDecision> {
592
+ if (cli === 'claude') return { codexArgs: [] };
593
+
594
+ let inspection: ApprovalInspection;
595
+ let openCodeConfig: unknown;
596
+ try {
597
+ if (cli === 'codex') {
598
+ inspection = inspectCodexBorgApprovals(await io.readCodexConfig());
599
+ } else {
600
+ openCodeConfig = await io.readOpenCodeConfig();
601
+ inspection = inspectOpenCodeBorgApprovals(openCodeConfig);
602
+ }
603
+ } catch (error: any) {
604
+ return {
605
+ codexArgs: [],
606
+ warning: `Could not inspect ${cli} Borg tool approvals: ${error?.message ?? error}. No approval override was applied.`,
607
+ };
608
+ }
609
+ if (inspection.restrictiveTools.length === 0) return { codexArgs: [] };
610
+
611
+ const intro =
612
+ `${cli === 'codex' ? 'Codex' : 'OpenCode'} requires approval for ${inspection.restrictiveTools.length} Borg tool${inspection.restrictiveTools.length === 1 ? '' : 's'}. ` +
613
+ BORG_DISPATCHER_APPROVAL_DISCLOSURE;
614
+ if (!io.isTTY()) {
615
+ return {
616
+ codexArgs: [],
617
+ warning: `${intro} Re-run in a terminal to approve a launch-only fix, or add:\n${inspection.repairSnippet}`,
618
+ };
619
+ }
620
+
621
+ const answer = await io.confirm(`${intro} Apply this launch-only Borg approval set? [y/N] `);
622
+ if (!accepted(answer)) {
623
+ return {
624
+ codexArgs: [],
625
+ warning: `${intro} Continuing without the launch-only fix. To repair it globally, add:\n${inspection.repairSnippet}`,
626
+ };
627
+ }
628
+
629
+ if (cli === 'codex') {
630
+ const codexArgs = codexBorgApprovalArgs(inspection.restrictiveTools);
631
+ let effectiveWithOverride: ApprovalInspection;
632
+ try {
633
+ effectiveWithOverride = inspectCodexBorgApprovals(
634
+ await io.readCodexConfig(codexArgs)
635
+ );
636
+ } catch {
637
+ return {
638
+ codexArgs: [],
639
+ warning: 'Could not verify the Codex launch-only approval override against effective config. No override was applied.',
640
+ };
641
+ }
642
+ if (effectiveWithOverride.restrictiveTools.length > 0) {
643
+ return {
644
+ codexArgs: [],
645
+ warning: `Codex managed policy prevents the launch-only Borg approval override. Ask your Codex administrator to allow these tools:\n${effectiveWithOverride.repairSnippet}`,
646
+ };
647
+ }
648
+ return { codexArgs };
649
+ }
650
+ const openCodePermission = JSON.stringify(mergeOpenCodePermission(
651
+ openCodeConfig && typeof openCodeConfig === 'object'
652
+ ? (openCodeConfig as Record<string, unknown>).permission
653
+ : undefined,
654
+ inspection.restrictiveTools
655
+ ));
656
+ let effectiveWithOverride: ApprovalInspection;
657
+ try {
658
+ effectiveWithOverride = inspectOpenCodeBorgApprovals(
659
+ await io.readOpenCodeConfig(openCodePermission)
660
+ );
661
+ } catch {
662
+ return {
663
+ codexArgs: [],
664
+ warning: 'Could not verify the OpenCode launch-only approval override against effective config. No override was applied.',
665
+ };
666
+ }
667
+ if (effectiveWithOverride.restrictiveTools.length > 0) {
668
+ return {
669
+ codexArgs: [],
670
+ warning: `OpenCode managed policy prevents the launch-only Borg approval override. Ask your OpenCode administrator to allow these tools:\n${effectiveWithOverride.repairSnippet}`,
671
+ };
672
+ }
673
+ return {
674
+ codexArgs: [],
675
+ openCodePermission,
676
+ };
677
+ }
678
+
679
+ export function buildOpenCodeLaunchArgs(
680
+ cwd: string,
681
+ port: number,
682
+ prompt: string,
683
+ passthroughArgs: string[] = []
684
+ ): string[] {
685
+ // Deliberately no `--auto`: that switch auto-approves unrelated shell/file
686
+ // actions. Exact Borg coordination consent is carried in the child env.
687
+ return [cwd, '--port', String(port), '--prompt', prompt, ...passthroughArgs];
688
+ }
689
+
690
+ export async function setupApprovalWarnings(
691
+ deps: Pick<ApprovalIo, 'readCodexConfig' | 'readOpenCodeConfig'>,
692
+ selected: { codex?: boolean; opencode?: boolean } = { codex: true, opencode: true }
693
+ ): Promise<string[]> {
694
+ const warnings: string[] = [];
695
+ try {
696
+ const codex = selected.codex
697
+ ? inspectCodexBorgApprovals(await deps.readCodexConfig())
698
+ : null;
699
+ if (codex && codex.restrictiveTools.length > 0) {
700
+ warnings.push(`Codex Borg approvals are restrictive. ${BORG_DISPATCHER_APPROVAL_DISCLOSURE} Borg launches will offer a launch-only fix. Global repair:\n${codex.repairSnippet}`);
701
+ }
702
+ } catch (error: any) {
703
+ warnings.push(`Could not inspect Codex Borg tool approvals: ${error?.message ?? error}`);
704
+ }
705
+ try {
706
+ const opencode = selected.opencode
707
+ ? inspectOpenCodeBorgApprovals(await deps.readOpenCodeConfig())
708
+ : null;
709
+ if (opencode && opencode.restrictiveTools.length > 0) {
710
+ warnings.push(`OpenCode Borg approvals are restrictive. ${BORG_DISPATCHER_APPROVAL_DISCLOSURE} Borg launches will offer a launch-only fix. Global repair:\n${opencode.repairSnippet}`);
711
+ }
712
+ } catch (error: any) {
713
+ warnings.push(`Could not inspect OpenCode Borg tool approvals: ${error?.message ?? error}`);
714
+ }
715
+ return warnings;
716
+ }