borgmcp 1.1.14 → 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 +86 -37
  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
package/src/config.ts ADDED
@@ -0,0 +1,595 @@
1
+ /**
2
+ * Secure local-server credential storage.
3
+ *
4
+ * The self-hosted-server credential group (enrollment credentials, pending
5
+ * enrollment/cube-creation records, and the per-seat drone-session bearers)
6
+ * rests ONLY in the 0600 credential file store (Queen rescope) — parity with the
7
+ * server's own TLS private keys. The OS keychain is gone; the raw secret never
8
+ * leaves the 0600 file, and a single store flock serializes every read-compare-write.
9
+ */
10
+ import os from 'os';
11
+ import path from 'path';
12
+ import { createHash, randomBytes, randomUUID } from 'crypto';
13
+ import type { ServerCapability } from 'borgmcp-shared/protocol';
14
+ import { withStoreLock } from './seat-store.js';
15
+ import {
16
+ makeFileBackend,
17
+ type TokenBackend,
18
+ } from './token-store.js';
19
+
20
+ const SERVER_CREDENTIAL_RECORD_VERSION = 2 as const;
21
+ const SERVER_PENDING_ENROLLMENT_RECORD_VERSION = 1 as const;
22
+ const SERVER_CUBE_RETRY_RECORD_VERSION = 1 as const;
23
+ // The 0600 credential store (Queen rescope: replaces the OS keychain). A single
24
+ // file holds every server credential/session/enrollment record; a single flock
25
+ // serializes every mutator + observer that must (SR-seven #4).
26
+ const CREDENTIALS_FILE = path.join(os.homedir(), '.config', 'borgmcp', 'credentials.json');
27
+ const CREDENTIALS_LOCK = `${CREDENTIALS_FILE}.lock`;
28
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
29
+
30
+ export interface ServerCredentialRecord {
31
+ origin: string;
32
+ trustIdentity: string;
33
+ credential: string;
34
+ clientId?: string | null;
35
+ serverCapabilities?: ServerCapability[];
36
+ }
37
+
38
+ export interface ActiveServerCredentialRecord {
39
+ origin: string;
40
+ trustIdentity: string;
41
+ credential: string;
42
+ clientId: string | null;
43
+ serverCapabilities: ServerCapability[];
44
+ }
45
+
46
+ export interface PendingServerEnrollmentRecord {
47
+ origin: string;
48
+ trustIdentity: string;
49
+ invitation: string;
50
+ retryKey: string;
51
+ credential: string;
52
+ clientName?: string;
53
+ }
54
+
55
+ export interface PendingServerCubeCreationRecord {
56
+ origin: string;
57
+ trustIdentity: string;
58
+ clientId: string;
59
+ repositoryBinding: string;
60
+ retryKey: string;
61
+ name: string;
62
+ template: 'default';
63
+ }
64
+
65
+ /**
66
+ * S1 clean-slate local drone-session record. The client CSPRNG-generates the
67
+ * bearer and persists it PENDING (keyed by the stable per-seat attach identity
68
+ * origin+trustIdentity+cube+role — no drone id yet on first attach) BEFORE the
69
+ * attach request. The bearer digest is the sole server correlator, so a lost
70
+ * response is recovered by re-sending the exact same bearer. After a verified
71
+ * `created`/`reused` response the SAME record is enriched in place with the
72
+ * server-assigned drone/session identity — no generation, no rotation.
73
+ */
74
+ /**
75
+ * The seat/sibling operation dimension for a pending session. Because the client
76
+ * bearer digest is the SOLE server correlator, distinct seats require distinct
77
+ * bearers; a deliberate sibling attach must therefore namespace its bearer apart
78
+ * from the durable in-place seat for the same (origin,trust,cube,role). Ported
79
+ * from the retired local-attach `operationBindingKey`. projectRoot is captured
80
+ * before a successful sibling attach changes cwd, so it is stable across the
81
+ * whole prepare→activate lifecycle.
82
+ */
83
+ export interface ServerSessionOperation {
84
+ projectRoot: string;
85
+ kind: 'seat' | 'sibling';
86
+ operationKey: string;
87
+ }
88
+
89
+
90
+ function validateServerCredentialBinding(origin: string, trustIdentity: string): void {
91
+ let parsed: URL;
92
+ try {
93
+ parsed = new URL(origin);
94
+ } catch {
95
+ throw new Error('invalid Borg server credential origin');
96
+ }
97
+ if (parsed.origin !== origin || parsed.protocol !== 'https:') {
98
+ throw new Error('Borg server credentials require a canonical HTTPS origin');
99
+ }
100
+ if (
101
+ trustIdentity.length < 1 ||
102
+ trustIdentity.length > 512 ||
103
+ /[\u0000-\u001f\u007f]/.test(trustIdentity)
104
+ ) {
105
+ throw new Error('invalid Borg server trust identity');
106
+ }
107
+ }
108
+
109
+ function serverCredentialAccount(origin: string, trustIdentity: string): string {
110
+ validateServerCredentialBinding(origin, trustIdentity);
111
+ const binding = createHash('sha256')
112
+ .update(origin)
113
+ .update('\0')
114
+ .update(trustIdentity)
115
+ .digest('hex');
116
+ return `borg-server-credential:${binding}`;
117
+ }
118
+
119
+ // The whole 0600 credential store is serialized by the SINGLE store lock
120
+ // (seat-store.withStoreLock over CREDENTIALS_LOCK). Every mutator AND every
121
+ // observer that must serialize runs its entire read-compare-write inside one
122
+ // continuous hold, released on EVERY path incl throw (SR-seven #4). There is no
123
+ // per-account lock and no compat shim — call sites acquire the store lock directly.
124
+
125
+ function serverPendingEnrollmentAccount(origin: string, trustIdentity: string): string {
126
+ validateServerCredentialBinding(origin, trustIdentity);
127
+ const binding = createHash('sha256')
128
+ .update(origin)
129
+ .update('\0')
130
+ .update(trustIdentity)
131
+ .digest('hex');
132
+ return `borg-server-enrollment-pending:${binding}`;
133
+ }
134
+
135
+ function validateUuid(value: string, label: string): void {
136
+ if (!UUID_RE.test(value)) throw new Error(`invalid Borg server ${label}`);
137
+ }
138
+
139
+ function validateServerCapabilities(value: readonly string[]): ServerCapability[] {
140
+ if (value.length > 1 || value.some((capability) => capability !== 'create_cube')) {
141
+ throw new Error('invalid Borg server capabilities');
142
+ }
143
+ return [...value] as ServerCapability[];
144
+ }
145
+
146
+ function validateClientName(clientName: string | undefined): void {
147
+ if (
148
+ clientName !== undefined &&
149
+ (Buffer.byteLength(clientName, 'utf8') < 1 ||
150
+ Buffer.byteLength(clientName, 'utf8') > 120 ||
151
+ !/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(clientName))
152
+ ) {
153
+ throw new Error('invalid Borg server client name');
154
+ }
155
+ }
156
+
157
+ function validateInvitation(invitation: string): void {
158
+ if (
159
+ invitation.length < 43 ||
160
+ invitation.length > 1024 ||
161
+ !/^[A-Za-z0-9_-]+$/.test(invitation)
162
+ ) {
163
+ throw new Error('invalid Borg server invitation');
164
+ }
165
+ }
166
+
167
+ function validateEnrollmentCredential(credential: string): void {
168
+ if (!/^[A-Za-z0-9_-]{43}$/.test(credential)) {
169
+ throw new Error('invalid Borg server credential');
170
+ }
171
+ }
172
+
173
+
174
+ function serverCubeRetryAccount(
175
+ origin: string,
176
+ trustIdentity: string,
177
+ clientId: string,
178
+ repositoryBinding: string,
179
+ ): string {
180
+ validateServerCredentialBinding(origin, trustIdentity);
181
+ validateUuid(clientId, 'client identity');
182
+ if (!/^[a-f0-9]{64}$/.test(repositoryBinding)) {
183
+ throw new Error('invalid Borg server repository binding');
184
+ }
185
+ const binding = createHash('sha256')
186
+ .update(origin)
187
+ .update('\0')
188
+ .update(trustIdentity)
189
+ .update('\0')
190
+ .update(clientId)
191
+ .update('\0')
192
+ .update(repositoryBinding)
193
+ .digest('hex');
194
+ return `borg-server-cube-pending:${binding}`;
195
+ }
196
+
197
+
198
+ // Local-server bearers rest ONLY in the 0600 credential store (Queen rescope —
199
+ // parity with the server's TLS keys; no OS keychain, no obfuscation-grade
200
+ // fallback). The single store lock (CREDENTIALS_LOCK) serializes the RCW.
201
+ let serverCredentialBackendPromise: Promise<TokenBackend> | null = null;
202
+ async function getServerCredentialBackend(): Promise<TokenBackend> {
203
+ if (!serverCredentialBackendPromise) {
204
+ serverCredentialBackendPromise = Promise.resolve(makeFileBackend(CREDENTIALS_FILE));
205
+ }
206
+ return serverCredentialBackendPromise;
207
+ }
208
+
209
+ /** Test-only credential-store backend injection. */
210
+ export function __setServerCredentialBackendForTest(backend: TokenBackend | null): void {
211
+ serverCredentialBackendPromise = backend ? Promise.resolve(backend) : null;
212
+ }
213
+
214
+ /**
215
+ * CR3b: the UNLOCKED credential write body. Validates then set()s the account.
216
+ * Callers that ALREADY hold the credential-store lock (activatePendingServerEnrollment)
217
+ * invoke this directly so they do not re-acquire (and self-deadlock on) the single
218
+ * store lock; the public storeServerCredential wraps it in one lock hold.
219
+ */
220
+ async function writeServerCredentialRecord(
221
+ backend: TokenBackend,
222
+ record: ServerCredentialRecord,
223
+ ): Promise<void> {
224
+ validateServerCredentialBinding(record.origin, record.trustIdentity);
225
+ validateEnrollmentCredential(record.credential);
226
+ if (record.clientId !== undefined && record.clientId !== null) {
227
+ validateUuid(record.clientId, 'client identity');
228
+ }
229
+ const serverCapabilities = validateServerCapabilities(record.serverCapabilities ?? []);
230
+ await backend.set(
231
+ serverCredentialAccount(record.origin, record.trustIdentity),
232
+ JSON.stringify({
233
+ version: SERVER_CREDENTIAL_RECORD_VERSION,
234
+ origin: record.origin,
235
+ trustIdentity: record.trustIdentity,
236
+ credential: record.credential,
237
+ clientId: record.clientId ?? null,
238
+ serverCapabilities,
239
+ }),
240
+ );
241
+ }
242
+
243
+ /**
244
+ * Persist one self-hosted server credential in the dedicated 0600 credential store.
245
+ *
246
+ * The account key binds both the canonical authority origin and the verified
247
+ * server/CA identity. A credential enrolled for one authority is therefore
248
+ * never considered for another endpoint or trust anchor. Enrollment owns the
249
+ * write; command-line arguments and environment variables are intentionally
250
+ * not credential sources. CR3b: the load→set→rename runs inside ONE hold of the
251
+ * single store lock so a concurrent writer cannot lose an unrelated account.
252
+ */
253
+ export async function storeServerCredential(record: ServerCredentialRecord): Promise<void> {
254
+ const backend = await getServerCredentialBackend();
255
+ await withStoreLock(CREDENTIALS_LOCK, () => writeServerCredentialRecord(backend, record));
256
+ }
257
+
258
+ /** Read an authority-bound active client record, failing closed on corruption. */
259
+ export async function getServerCredentialRecord(
260
+ origin: string,
261
+ trustIdentity: string,
262
+ ): Promise<ActiveServerCredentialRecord | null> {
263
+ const backend = await getServerCredentialBackend();
264
+ const stored = await backend.get(serverCredentialAccount(origin, trustIdentity));
265
+ if (!stored) return null;
266
+ try {
267
+ const record = JSON.parse(stored) as Partial<ActiveServerCredentialRecord> & {
268
+ version?: unknown;
269
+ };
270
+ if (
271
+ record.version !== SERVER_CREDENTIAL_RECORD_VERSION ||
272
+ record.origin !== origin ||
273
+ record.trustIdentity !== trustIdentity ||
274
+ typeof record.credential !== 'string' ||
275
+ !/^[A-Za-z0-9_-]{43}$/.test(record.credential) ||
276
+ (record.clientId !== null &&
277
+ (typeof record.clientId !== 'string' || !UUID_RE.test(record.clientId))) ||
278
+ !Array.isArray(record.serverCapabilities)
279
+ ) {
280
+ return null;
281
+ }
282
+ let serverCapabilities: ServerCapability[];
283
+ try {
284
+ serverCapabilities = validateServerCapabilities(record.serverCapabilities);
285
+ } catch {
286
+ return null;
287
+ }
288
+ return {
289
+ origin,
290
+ trustIdentity,
291
+ credential: record.credential,
292
+ clientId: record.clientId,
293
+ serverCapabilities,
294
+ };
295
+ } catch {
296
+ return null;
297
+ }
298
+ }
299
+
300
+ /** Read only the bearer for existing call sites that do not need capability metadata. */
301
+ export async function getServerCredential(
302
+ origin: string,
303
+ trustIdentity: string,
304
+ ): Promise<string | null> {
305
+ return (await getServerCredentialRecord(origin, trustIdentity))?.credential ?? null;
306
+ }
307
+
308
+ function decodePendingServerEnrollment(
309
+ stored: string,
310
+ origin: string,
311
+ trustIdentity: string,
312
+ ): PendingServerEnrollmentRecord {
313
+ const record = JSON.parse(stored) as Partial<PendingServerEnrollmentRecord> & {
314
+ version?: unknown;
315
+ state?: unknown;
316
+ };
317
+ if (
318
+ record.version !== SERVER_PENDING_ENROLLMENT_RECORD_VERSION ||
319
+ record.state !== 'pending' ||
320
+ record.origin !== origin ||
321
+ record.trustIdentity !== trustIdentity ||
322
+ typeof record.invitation !== 'string' ||
323
+ typeof record.retryKey !== 'string' || !UUID_RE.test(record.retryKey) ||
324
+ typeof record.credential !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(record.credential) ||
325
+ (record.clientName !== undefined && typeof record.clientName !== 'string')
326
+ ) {
327
+ throw new Error('invalid');
328
+ }
329
+ validateInvitation(record.invitation);
330
+ validateClientName(record.clientName);
331
+ return {
332
+ origin,
333
+ trustIdentity,
334
+ invitation: record.invitation,
335
+ retryKey: record.retryKey,
336
+ credential: record.credential,
337
+ ...(record.clientName === undefined ? {} : { clientName: record.clientName }),
338
+ };
339
+ }
340
+
341
+ /** Load an exact durable PENDING tuple so a new process can resume it. */
342
+ export async function getPendingServerEnrollment(
343
+ origin: string,
344
+ trustIdentity: string,
345
+ ): Promise<PendingServerEnrollmentRecord | null> {
346
+ validateServerCredentialBinding(origin, trustIdentity);
347
+ const backend = await getServerCredentialBackend();
348
+ const account = serverPendingEnrollmentAccount(origin, trustIdentity);
349
+ return withStoreLock(CREDENTIALS_LOCK, async () => {
350
+ const stored = await backend.get(account);
351
+ if (!stored) return null;
352
+ try {
353
+ return decodePendingServerEnrollment(stored, origin, trustIdentity);
354
+ } catch {
355
+ throw new Error('pending Borg server enrollment is corrupt');
356
+ }
357
+ });
358
+ }
359
+
360
+ /**
361
+ * Generate and persist an exact enrollment tuple before network I/O. A
362
+ * pre-existing PENDING tuple must match the invitation and presentation name;
363
+ * this makes response-loss retries exact without minting a second bearer.
364
+ */
365
+ export async function getOrCreatePendingServerEnrollment(
366
+ input: {
367
+ origin: string;
368
+ trustIdentity: string;
369
+ invitation: string;
370
+ clientName?: string;
371
+ },
372
+ ): Promise<PendingServerEnrollmentRecord> {
373
+ validateServerCredentialBinding(input.origin, input.trustIdentity);
374
+ validateInvitation(input.invitation);
375
+ validateClientName(input.clientName);
376
+ const backend = await getServerCredentialBackend();
377
+ const account = serverPendingEnrollmentAccount(input.origin, input.trustIdentity);
378
+ return withStoreLock(CREDENTIALS_LOCK, async () => {
379
+ const stored = await backend.get(account);
380
+ if (stored) {
381
+ try {
382
+ const record = decodePendingServerEnrollment(
383
+ stored,
384
+ input.origin,
385
+ input.trustIdentity,
386
+ );
387
+ if (
388
+ record.invitation !== input.invitation ||
389
+ record.clientName !== input.clientName
390
+ ) {
391
+ throw new Error('mismatch');
392
+ }
393
+ return record;
394
+ } catch {
395
+ throw new Error('pending Borg server enrollment does not match this request');
396
+ }
397
+ }
398
+
399
+ const record: PendingServerEnrollmentRecord = {
400
+ origin: input.origin,
401
+ trustIdentity: input.trustIdentity,
402
+ invitation: input.invitation,
403
+ retryKey: randomUUID(),
404
+ credential: randomBytes(32).toString('base64url'),
405
+ ...(input.clientName === undefined ? {} : { clientName: input.clientName }),
406
+ };
407
+ validateEnrollmentCredential(record.credential);
408
+ await backend.set(account, JSON.stringify({
409
+ version: SERVER_PENDING_ENROLLMENT_RECORD_VERSION,
410
+ state: 'pending',
411
+ ...record,
412
+ }));
413
+ return record;
414
+ });
415
+ }
416
+
417
+ /** Activate the exact pending tuple only after a verified server response. */
418
+ export async function activatePendingServerEnrollment(
419
+ input: {
420
+ origin: string;
421
+ trustIdentity: string;
422
+ retryKey: string;
423
+ credential: string;
424
+ clientId: string;
425
+ serverCapabilities: ServerCapability[];
426
+ },
427
+ ): Promise<void> {
428
+ validateServerCredentialBinding(input.origin, input.trustIdentity);
429
+ validateUuid(input.retryKey, 'enrollment retry key');
430
+ validateEnrollmentCredential(input.credential);
431
+ validateUuid(input.clientId, 'client identity');
432
+ const serverCapabilities = validateServerCapabilities(input.serverCapabilities);
433
+ const backend = await getServerCredentialBackend();
434
+ const pendingAccount = serverPendingEnrollmentAccount(input.origin, input.trustIdentity);
435
+ await withStoreLock(CREDENTIALS_LOCK, async () => {
436
+ const stored = await backend.get(pendingAccount);
437
+ if (!stored) throw new Error('pending Borg server enrollment is missing');
438
+ try {
439
+ const pending = decodePendingServerEnrollment(
440
+ stored,
441
+ input.origin,
442
+ input.trustIdentity,
443
+ );
444
+ if (pending.retryKey !== input.retryKey || pending.credential !== input.credential) {
445
+ throw new Error('mismatch');
446
+ }
447
+ } catch {
448
+ throw new Error('pending Borg server enrollment does not match the verified response');
449
+ }
450
+ // Already inside the single store lock — use the UNLOCKED write body so we do
451
+ // not re-acquire (and self-deadlock on) CREDENTIALS_LOCK (CR3b).
452
+ await writeServerCredentialRecord(backend, {
453
+ origin: input.origin,
454
+ trustIdentity: input.trustIdentity,
455
+ credential: input.credential,
456
+ clientId: input.clientId,
457
+ serverCapabilities,
458
+ });
459
+ await backend.delete(pendingAccount);
460
+ });
461
+ }
462
+
463
+ /** Delete only the exact definitively rejected pending attempt. */
464
+ export async function clearPendingServerEnrollment(
465
+ origin: string,
466
+ trustIdentity: string,
467
+ retryKey: string,
468
+ ): Promise<void> {
469
+ validateUuid(retryKey, 'enrollment retry key');
470
+ const backend = await getServerCredentialBackend();
471
+ const account = serverPendingEnrollmentAccount(origin, trustIdentity);
472
+ await withStoreLock(CREDENTIALS_LOCK, async () => {
473
+ const stored = await backend.get(account);
474
+ if (!stored) return;
475
+ try {
476
+ const pending = decodePendingServerEnrollment(stored, origin, trustIdentity);
477
+ if (pending.retryKey !== retryKey) return;
478
+ } catch {
479
+ return;
480
+ }
481
+ await backend.delete(account);
482
+ });
483
+ }
484
+
485
+ /** Persist one repository-scoped cube-create idempotency key in the 0600 credential store. */
486
+ export async function getOrCreatePendingServerCubeCreation(
487
+ input: {
488
+ origin: string;
489
+ trustIdentity: string;
490
+ clientId: string;
491
+ projectRoot: string;
492
+ name: string;
493
+ template: 'default';
494
+ },
495
+ ): Promise<PendingServerCubeCreationRecord> {
496
+ validateServerCredentialBinding(input.origin, input.trustIdentity);
497
+ validateUuid(input.clientId, 'client identity');
498
+ if (input.projectRoot.length < 1 || input.projectRoot.length > 4096 || /[\u0000-\u001f\u007f]/.test(input.projectRoot)) {
499
+ throw new Error('invalid Borg server repository binding');
500
+ }
501
+ if (Buffer.byteLength(input.name, 'utf8') < 1 || Buffer.byteLength(input.name, 'utf8') > 120 ||
502
+ !/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(input.name)) {
503
+ throw new Error('invalid Borg server cube name');
504
+ }
505
+ const repositoryBinding = createHash('sha256').update(input.projectRoot).digest('hex');
506
+ const backend = await getServerCredentialBackend();
507
+ const account = serverCubeRetryAccount(
508
+ input.origin,
509
+ input.trustIdentity,
510
+ input.clientId,
511
+ repositoryBinding,
512
+ );
513
+ return withStoreLock(CREDENTIALS_LOCK, async () => {
514
+ const stored = await backend.get(account);
515
+ if (stored) {
516
+ try {
517
+ const record = JSON.parse(stored) as Partial<PendingServerCubeCreationRecord> & {
518
+ version?: unknown;
519
+ state?: unknown;
520
+ };
521
+ if (
522
+ record.version !== SERVER_CUBE_RETRY_RECORD_VERSION ||
523
+ record.state !== 'pending' ||
524
+ record.origin !== input.origin ||
525
+ record.trustIdentity !== input.trustIdentity ||
526
+ record.clientId !== input.clientId ||
527
+ record.repositoryBinding !== repositoryBinding ||
528
+ record.name !== input.name ||
529
+ record.template !== input.template ||
530
+ typeof record.retryKey !== 'string' || !UUID_RE.test(record.retryKey)
531
+ ) {
532
+ throw new Error('mismatch');
533
+ }
534
+ return {
535
+ origin: input.origin,
536
+ trustIdentity: input.trustIdentity,
537
+ clientId: input.clientId,
538
+ repositoryBinding,
539
+ retryKey: record.retryKey,
540
+ name: input.name,
541
+ template: input.template,
542
+ };
543
+ } catch {
544
+ throw new Error('pending Borg server cube creation does not match this repository');
545
+ }
546
+ }
547
+ const record: PendingServerCubeCreationRecord = {
548
+ origin: input.origin,
549
+ trustIdentity: input.trustIdentity,
550
+ clientId: input.clientId,
551
+ repositoryBinding,
552
+ retryKey: randomUUID(),
553
+ name: input.name,
554
+ template: input.template,
555
+ };
556
+ await backend.set(account, JSON.stringify({
557
+ version: SERVER_CUBE_RETRY_RECORD_VERSION,
558
+ state: 'pending',
559
+ ...record,
560
+ }));
561
+ return record;
562
+ });
563
+ }
564
+
565
+ export async function clearPendingServerCubeCreation(
566
+ record: PendingServerCubeCreationRecord,
567
+ ): Promise<void> {
568
+ const backend = await getServerCredentialBackend();
569
+ const account = serverCubeRetryAccount(
570
+ record.origin,
571
+ record.trustIdentity,
572
+ record.clientId,
573
+ record.repositoryBinding,
574
+ );
575
+ await withStoreLock(CREDENTIALS_LOCK, async () => {
576
+ const stored = await backend.get(account);
577
+ if (!stored) return;
578
+ try {
579
+ const pending = JSON.parse(stored) as { retryKey?: unknown };
580
+ if (pending.retryKey !== record.retryKey) return;
581
+ } catch {
582
+ return;
583
+ }
584
+ await backend.delete(account);
585
+ });
586
+ }
587
+
588
+ export async function clearServerCredential(origin: string, trustIdentity: string): Promise<void> {
589
+ const backend = await getServerCredentialBackend();
590
+ const pendingAccount = serverPendingEnrollmentAccount(origin, trustIdentity);
591
+ await withStoreLock(CREDENTIALS_LOCK, async () => {
592
+ await backend.delete(serverCredentialAccount(origin, trustIdentity));
593
+ await backend.delete(pendingAccount);
594
+ });
595
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Drone self-identification prefix for client-emitted console messages.
3
+ *
4
+ * Per gh#25: when a drone session emits a console error (e.g.
5
+ * "Authentication expired — your saved login has expired. Run: borg setup"),
6
+ * the Queen has no way to
7
+ * tell which drone window the message came from without scanning every
8
+ * open terminal. Window title alone (set by terminal-title.ts) is
9
+ * insufficient — the Queen reads the active terminal's output stream,
10
+ * not its title bar.
11
+ *
12
+ * This module exports a one-shot initializer that resolves the prefix
13
+ * from the local cube state cache, plus a synchronous getter that
14
+ * call sites use to wrap each console.error.
15
+ *
16
+ * Format (matches the terminal-title.ts middle-dot convention so
17
+ * surfaces stay internally consistent):
18
+ * `[<drone-label> · <cube-name>]` (assimilated)
19
+ * `[borg · <repo-basename>]` (no cube cached)
20
+ *
21
+ * The not-yet-assimilated shape reads as neutral metadata ("this is
22
+ * borg, in project X"), NOT a fault the user must fix — and mirrors the
23
+ * unassimilated terminal-title shape (`borg · <repo-basename>`), so the
24
+ * title bar and the console prefix agree (gh#818 P1).
25
+ */
26
+
27
+ import { basename } from 'node:path';
28
+ import chalk from 'chalk';
29
+ import { getActiveCube } from './cubes.js';
30
+
31
+ let cachedPrefix: string | null = null;
32
+
33
+ /** Neutral prefix for a not-yet-assimilated session (gh#818 P1). */
34
+ function unassimilatedPrefix(): string {
35
+ return `[borg · ${basename(process.cwd())}]`;
36
+ }
37
+
38
+ /**
39
+ * Resolve the drone-self-identification prefix from cube state and
40
+ * cache it for subsequent synchronous reads. Idempotent — calling
41
+ * multiple times returns the same value. Falls back silently to the
42
+ * unassimilated shape on any read error so console emission is never
43
+ * blocked.
44
+ */
45
+ export async function initConsolePrefix(): Promise<string> {
46
+ if (cachedPrefix !== null) return cachedPrefix;
47
+ try {
48
+ const active = await getActiveCube();
49
+ if (active?.droneLabel && active?.name) {
50
+ cachedPrefix = `[${active.droneLabel} · ${active.name}]`;
51
+ return cachedPrefix;
52
+ }
53
+ } catch {
54
+ // Fall through to unassimilated fallback.
55
+ }
56
+ cachedPrefix = unassimilatedPrefix();
57
+ return cachedPrefix;
58
+ }
59
+
60
+ /**
61
+ * Synchronous prefix getter. Returns the cached value if initialized,
62
+ * otherwise the unassimilated fallback — safe to call before
63
+ * initConsolePrefix() resolves.
64
+ */
65
+ export function droneIdPrefix(): string {
66
+ if (cachedPrefix !== null) return cachedPrefix;
67
+ return unassimilatedPrefix();
68
+ }
69
+
70
+ /**
71
+ * Prefix + trailing space, styled dim/gray so the prefix is metadata
72
+ * and the message body retains visual emphasis. Use as
73
+ * `${consolePrefix()}<message>`.
74
+ */
75
+ export function consolePrefix(): string {
76
+ return chalk.gray(droneIdPrefix()) + ' ';
77
+ }
78
+
79
+ /**
80
+ * Drop-in replacement for `console.error` that prepends the drone
81
+ * self-id prefix. If the first arg is a string, the prefix is
82
+ * concatenated to it; otherwise the prefix is emitted as its own arg
83
+ * (handles the `console.error('label:', value)` shape).
84
+ */
85
+ export function cerr(...args: any[]): void {
86
+ if (args.length === 0) {
87
+ console.error(consolePrefix());
88
+ return;
89
+ }
90
+ if (typeof args[0] === 'string') {
91
+ console.error(consolePrefix() + args[0], ...args.slice(1));
92
+ } else {
93
+ console.error(consolePrefix(), ...args);
94
+ }
95
+ }
96
+
97
+ export function _resetCachedPrefixForTests(): void {
98
+ cachedPrefix = null;
99
+ }