xaq-cli 0.1.0-nightly.78 → 0.1.0-nightly.80

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 (233) hide show
  1. package/README.md +9 -0
  2. package/dist/{admin-B2t1ATpC.js → admin-Buw2v1X4.js} +1 -1
  3. package/dist/{admin-DI2rumeX.js → admin-CoawcLsy.js} +2 -2
  4. package/dist/{admin-DI2rumeX.js.map → admin-CoawcLsy.js.map} +1 -1
  5. package/dist/{admin-DeCN2VDX.js → admin-vJum87dw.js} +3 -3
  6. package/dist/{admin-DeCN2VDX.js.map → admin-vJum87dw.js.map} +1 -1
  7. package/dist/{api-CmTgScTD.js → api-2WDmyFjs.js} +6 -2
  8. package/dist/api-2WDmyFjs.js.map +1 -0
  9. package/dist/{audit-BVYknW2i.js → audit-CxI6Rh_6.js} +2 -2
  10. package/dist/{audit-BVYknW2i.js.map → audit-CxI6Rh_6.js.map} +1 -1
  11. package/dist/audit-De-XCyMm.js +2 -0
  12. package/dist/cli-Bfiv6zge.js +196 -0
  13. package/dist/cli-Bfiv6zge.js.map +1 -0
  14. package/dist/cli.js +18 -16
  15. package/dist/cli.js.map +1 -1
  16. package/dist/{commands-tyhvFqiJ.js → commands-CqNK71Z1.js} +9 -9
  17. package/dist/{commands-tyhvFqiJ.js.map → commands-CqNK71Z1.js.map} +1 -1
  18. package/dist/{commands-CyntHgEq.js → commands-tVCaKX2m.js} +18 -14
  19. package/dist/commands-tVCaKX2m.js.map +1 -0
  20. package/dist/{config-D3jl_sOt.js → config-cEBzNniL.js} +2 -2
  21. package/dist/{config-D3jl_sOt.js.map → config-cEBzNniL.js.map} +1 -1
  22. package/dist/{config-C4vBEO-V.js → config-y4faHFGn.js} +1 -1
  23. package/dist/{connect-D7oQXLIF.js → connect-Bc-_XGu6.js} +2 -2
  24. package/dist/{connect-D7oQXLIF.js.map → connect-Bc-_XGu6.js.map} +1 -1
  25. package/dist/{db-BEUEUPyP.js → db-Bp-06SJb.js} +19 -1
  26. package/dist/{db-BEUEUPyP.js.map → db-Bp-06SJb.js.map} +1 -1
  27. package/dist/db-CVCYO1H5.js +2 -0
  28. package/dist/{import-BgRQ4ohM.js → import-BpXEZv-g.js} +2 -2
  29. package/dist/{import-BgRQ4ohM.js.map → import-BpXEZv-g.js.map} +1 -1
  30. package/dist/instance-DXhF0tmJ.js +2 -0
  31. package/dist/{instance-CPpDQZ1c.js → instance-cYidg0yj.js} +2 -2
  32. package/dist/{instance-CPpDQZ1c.js.map → instance-cYidg0yj.js.map} +1 -1
  33. package/dist/manage-B0GMd74N.js +2 -0
  34. package/dist/{manage-D3eTpw6R.js → manage-ZWS4TtDB.js} +53 -2
  35. package/dist/manage-ZWS4TtDB.js.map +1 -0
  36. package/dist/{server-DLhj6g0y.js → server-C8SwDPDp.js} +2855 -1235
  37. package/dist/server-C8SwDPDp.js.map +1 -0
  38. package/dist/{service-Cw_iN4OR.js → service-BoPgGKUs.js} +1 -1
  39. package/dist/{service-DjWgCF6M.js → service-CYSNDoQk.js} +2 -2
  40. package/dist/{service-DjWgCF6M.js.map → service-CYSNDoQk.js.map} +1 -1
  41. package/dist/{service-DsZwNQ0Y.js → service-CYfXq2jn.js} +3 -3
  42. package/dist/{service-DsZwNQ0Y.js.map → service-CYfXq2jn.js.map} +1 -1
  43. package/dist/{service-DyTQZeH9.js → service-DIaovZKF.js} +1 -1
  44. package/dist/{settings-B5qSHxRM.js → settings-GL6H4WcS.js} +2 -2
  45. package/dist/{settings-B5qSHxRM.js.map → settings-GL6H4WcS.js.map} +1 -1
  46. package/dist/settings-LfG-uxiq.js +2 -0
  47. package/dist/sources-DO6MmQHi.js +730 -0
  48. package/dist/sources-DO6MmQHi.js.map +1 -0
  49. package/dist/types-BhJDbSzE.js.map +1 -1
  50. package/dist/user-server-huynScXf.js.map +1 -1
  51. package/dist/web/assets/_agentId-GeoEyMgJ.js +1 -0
  52. package/dist/web/assets/{_app-BfXHcLKy.js → _app-BTcv7j6b.js} +17 -17
  53. package/dist/web/assets/_app-_X4pIA9e.js +1 -0
  54. package/dist/web/assets/_connectionId-zMUYMV2-.js +1 -0
  55. package/dist/web/assets/_webhookId-BzZqtVDC.js +1 -0
  56. package/dist/web/assets/{account-status-QCMDrdBO.js → account-status-BLKaQQ2q.js} +1 -1
  57. package/dist/web/assets/accounts-B2_8bFfM.js +1 -0
  58. package/dist/web/assets/accounts-list-B5p_Twni.js +1 -0
  59. package/dist/web/assets/{activity-BSfUSncq.js → activity-BSu3O3Ii.js} +1 -1
  60. package/dist/web/assets/add-connection-CkZgTiFB.js +1 -0
  61. package/dist/web/assets/agent-create-form-FxADP7ru.js +1 -0
  62. package/dist/web/assets/agents-B2q_Aqxa.js +1 -0
  63. package/dist/web/assets/approvals-BnYPYunq.js +1 -0
  64. package/dist/web/assets/approve._id-CV2d_77B.js +1 -0
  65. package/dist/web/assets/{arrow-left-Dn41Ca3s.js → arrow-left-CpZXKJ1-.js} +1 -1
  66. package/dist/web/assets/{arrow-right-D0-bRtm8.js → arrow-right-2KVqVHDC.js} +1 -1
  67. package/dist/web/assets/attention-Dgl57eLP.js +1 -0
  68. package/dist/web/assets/audit-CztVJuz2.js +1 -0
  69. package/dist/web/assets/{audit-bits-DLeCrh-b.js → audit-bits-Bnl59N8_.js} +1 -1
  70. package/dist/web/assets/{bell-ring-BZanlAe0.js → bell-ring-s8djG9I-.js} +1 -1
  71. package/dist/web/assets/{bot-DOhWCb5p.js → bot-Bk_iA2Nm.js} +1 -1
  72. package/dist/web/assets/calendar-notice-3ccrTjvg.js +1 -0
  73. package/dist/web/assets/{calendar-plus-DfYRzTtC.js → calendar-plus-GgdNHmud.js} +1 -1
  74. package/dist/web/assets/calendar-setup-b8L3oH02.js +2 -0
  75. package/dist/web/assets/card-syzRdPEy.js +1 -0
  76. package/dist/web/assets/{check-check-D7XF6Qx9.js → check-check-CgO_6j2s.js} +1 -1
  77. package/dist/web/assets/checkbox-DMlY-PJt.js +1 -0
  78. package/dist/web/assets/chevron-right-yWQD_9E-.js +1 -0
  79. package/dist/web/assets/chips-input-DGr5xdms.js +1 -0
  80. package/dist/web/assets/choice-cards-Bxbo73Th.js +1 -0
  81. package/dist/web/assets/circle-arrow-up-BNNmlYdn.js +1 -0
  82. package/dist/web/assets/{circle-x-CK5aOMT2.js → circle-x-CTcJW-l-.js} +1 -1
  83. package/dist/web/assets/command-C9--j5qk.js +1 -0
  84. package/dist/web/assets/common-DIYGNz_F.js +1 -0
  85. package/dist/web/assets/connect-agent-guide-mjIv-bxM.js +1 -0
  86. package/dist/web/assets/{connect-host-BW9ZCk0-.js → connect-host-D4RyfiWZ.js} +2 -2
  87. package/dist/web/assets/connection-row-C9btdnxA.js +1 -0
  88. package/dist/web/assets/connections-CLZHg7Ku.js +1 -0
  89. package/dist/web/assets/data-table-CXk1mAQJ.js +1 -0
  90. package/dist/web/assets/empty-C8fboh2s.js +1 -0
  91. package/dist/web/assets/events-DYKK455p.js +1 -0
  92. package/dist/web/assets/extensions-Di3FeHuQ.js +1 -0
  93. package/dist/web/assets/{eye-CBBY6TzG.js → eye-j2BjoOrV.js} +1 -1
  94. package/dist/web/assets/{eye-off-C6FSE-OI.js → eye-off-Dou-5cth.js} +1 -1
  95. package/dist/web/assets/field-Dw86ioh5.js +1 -0
  96. package/dist/web/assets/get-started-CyJb68Vl.js +2 -0
  97. package/dist/web/assets/{hidden-chars-1_7Oycp7.js → hidden-chars-t70MkJO5.js} +1 -1
  98. package/dist/web/assets/index-BG2SDaTP.js +11 -0
  99. package/dist/web/assets/index-BOU9RRWo.css +2 -0
  100. package/dist/web/assets/input-CsZVjr0u.js +1 -0
  101. package/dist/web/assets/input-group-C41TDbAD.js +1 -0
  102. package/dist/web/assets/label-multi-select-WIvlufaS.js +1 -0
  103. package/dist/web/assets/{labels-DtDR-Bvf.js → labels-u5yJA-FE.js} +1 -1
  104. package/dist/web/assets/{laptop-Bh9Fe3mg.js → laptop-BUNO3ozf.js} +1 -1
  105. package/dist/web/assets/{last-call-CrjwObrP.js → last-call-C7aKME0q.js} +1 -1
  106. package/dist/web/assets/link-C0QJvwFS.js +1 -0
  107. package/dist/web/assets/link-D_PzqQva.js +2 -0
  108. package/dist/web/assets/{listener-handled-o8k-teN1.js → listener-handled-CO2clgw-.js} +1 -1
  109. package/dist/web/assets/login-CaxOZUzr.js +1 -0
  110. package/dist/web/assets/{mail-BHYjZxbc.js → mail-o0VhZVJ2.js} +1 -1
  111. package/dist/web/assets/mcp-call-card-BUwytoHc.js +3 -0
  112. package/dist/web/assets/{message-square-quote-BwFfaN6r.js → message-square-quote-D2bV111_.js} +1 -1
  113. package/dist/web/assets/mutations-CMxxk8sI.js +1 -0
  114. package/dist/web/assets/{network-n8OwNmxr.js → network-FDpWTlbM.js} +1 -1
  115. package/dist/web/assets/pairing-DyNn3-jh.js +1 -0
  116. package/dist/web/assets/pairing-notifier-D1u5VEgM.js +1 -0
  117. package/dist/web/assets/{paperclip-CXvffYnq.js → paperclip-CHenB1vl.js} +1 -1
  118. package/dist/web/assets/password-input-ByIwKZ6n.js +1 -0
  119. package/dist/web/assets/{pause-C0554OzA.js → pause-CMIaN41B.js} +1 -1
  120. package/dist/web/assets/{pencil-BDWpYBnN.js → pencil-BJghQnyH.js} +1 -1
  121. package/dist/web/assets/{play-rDra2Uck.js → play--4rES4tt.js} +1 -1
  122. package/dist/web/assets/{plug-23cBZTdR.js → plug-CsAZwa4J.js} +1 -1
  123. package/dist/web/assets/{plug-zap-0lKlk0E6.js → plug-zap-Dixf73_J.js} +1 -1
  124. package/dist/web/assets/plus-BOH2g9Pn.js +1 -0
  125. package/dist/web/assets/preload-helper-CvTQD7bZ.js +1 -0
  126. package/dist/web/assets/{public-url-BxwAe3ba.js → public-url-RdgsU78L.js} +1 -1
  127. package/dist/web/assets/puzzle-CXYGMyuL.js +1 -0
  128. package/dist/web/assets/queries-DHQ3H4AF.js +41 -0
  129. package/dist/web/assets/{refresh-cw-B_zNQl2p.js → refresh-cw-BV268hgn.js} +1 -1
  130. package/dist/web/assets/revealed-keys-BLl8hCqX.js +1 -0
  131. package/dist/web/assets/{rotate-ccw-UmvMSyOX.js → rotate-ccw-l7eezUfl.js} +1 -1
  132. package/dist/web/assets/{search-D0qVIdHx.js → search-BUnx6oT7.js} +1 -1
  133. package/dist/web/assets/select-Cv5fucW7.js +1 -0
  134. package/dist/web/assets/{send-CmOZjqoc.js → send-OXTMEWvq.js} +1 -1
  135. package/dist/web/assets/settings-C0oQCtgf.js +1 -0
  136. package/dist/web/assets/setup-DZS5AilY.js +1 -0
  137. package/dist/web/assets/sheet-PewQsdBv.js +1 -0
  138. package/dist/web/assets/{shield-alert-K214ggzY.js → shield-alert-BxDFn1nU.js} +1 -1
  139. package/dist/web/assets/{shield-check-D-5FsaOb.js → shield-check-CshqxsL8.js} +1 -1
  140. package/dist/web/assets/{sparkles-BOtlLkbU.js → sparkles-lf3zV5nm.js} +1 -1
  141. package/dist/web/assets/{stamp-HT0B-mKD.js → stamp-B5floV2f.js} +1 -1
  142. package/dist/web/assets/step-url-DSs6pSXM.js +1 -0
  143. package/dist/web/assets/stream-3magGhpY.js +1 -0
  144. package/dist/web/assets/tag-CKf3sI8S.js +1 -0
  145. package/dist/web/assets/{terminal-BI4_kJe-.js → terminal-BFbMzA04.js} +1 -1
  146. package/dist/web/assets/test-email-button-7-VR9Xio.js +1 -0
  147. package/dist/web/assets/textarea-6saf6Kz3.js +1 -0
  148. package/dist/web/assets/toggle-group-C5V56DZG.js +1 -0
  149. package/dist/web/assets/{trash-D6uvctV9.js → trash-B2KorJGf.js} +1 -1
  150. package/dist/web/assets/use-delivery-speed-C_zTJz0L.js +1 -0
  151. package/dist/web/assets/{use-label-names-jiPWM-Lg.js → use-label-names-CHSm054h.js} +1 -1
  152. package/dist/web/assets/use-sign-out-DdvAcDN4.js +1 -0
  153. package/dist/web/assets/{useInfiniteQuery-BpUrBg-y.js → useInfiniteQuery-C3WHneSb.js} +1 -1
  154. package/dist/web/assets/useQuery-D1uTZvNk.js +1 -0
  155. package/dist/web/assets/useRouterState-BvRVt3m6.js +1 -0
  156. package/dist/web/assets/{webhook-DMnUEIeQ.js → webhook-Bpyd2pTL.js} +1 -1
  157. package/dist/web/assets/webhook-bits-DvEpXGJm.js +1 -0
  158. package/dist/web/assets/{webhook-sheet-CfC2QnNk.js → webhook-sheet-BIguf2bW.js} +2 -2
  159. package/dist/web/assets/webhooks-CLF820_e.js +1 -0
  160. package/dist/web/assets/x-DM3jpoy5.js +1 -0
  161. package/dist/web/index.html +6 -6
  162. package/package.json +1 -1
  163. package/dist/api-CmTgScTD.js.map +0 -1
  164. package/dist/audit-BB7jK9Z5.js +0 -2
  165. package/dist/commands-CyntHgEq.js.map +0 -1
  166. package/dist/db-Q5BQuwga.js +0 -2
  167. package/dist/instance-yXK1-2SE.js +0 -2
  168. package/dist/manage-D3eTpw6R.js.map +0 -1
  169. package/dist/manage-RJqfnHBq.js +0 -2
  170. package/dist/server-DLhj6g0y.js.map +0 -1
  171. package/dist/settings-DQvZaMN1.js +0 -2
  172. package/dist/web/assets/_agentId-B4iPsyc1.js +0 -1
  173. package/dist/web/assets/_app-AYuHHl4Q.js +0 -1
  174. package/dist/web/assets/_connectionId-D7FTuDOg.js +0 -1
  175. package/dist/web/assets/_webhookId-CjTMJxRv.js +0 -1
  176. package/dist/web/assets/accounts-HUFqTHdu.js +0 -1
  177. package/dist/web/assets/accounts-list-_aD4kKJh.js +0 -1
  178. package/dist/web/assets/add-connection-DRfieiRB.js +0 -1
  179. package/dist/web/assets/agent-create-form-Ds_D6gJJ.js +0 -1
  180. package/dist/web/assets/agents-DthLT7ob.js +0 -1
  181. package/dist/web/assets/approvals-m47m5VY5.js +0 -1
  182. package/dist/web/assets/approve._id-BjrI8Mrq.js +0 -1
  183. package/dist/web/assets/attention-C7d_TmAO.js +0 -1
  184. package/dist/web/assets/audit-CuwpvW4-.js +0 -1
  185. package/dist/web/assets/calendar-notice-CnDqFS6A.js +0 -1
  186. package/dist/web/assets/calendar-setup-Csge9Rlc.js +0 -2
  187. package/dist/web/assets/card-OoDU0z7Y.js +0 -1
  188. package/dist/web/assets/checkbox-C3enYRr3.js +0 -1
  189. package/dist/web/assets/chevron-right-BMre7-G3.js +0 -1
  190. package/dist/web/assets/chips-input-CptCIGMo.js +0 -1
  191. package/dist/web/assets/choice-cards-cdUaM-_t.js +0 -1
  192. package/dist/web/assets/command-DNnLWG72.js +0 -1
  193. package/dist/web/assets/common-5hhe1SeG.js +0 -1
  194. package/dist/web/assets/connect-agent-guide-C5ClCF7N.js +0 -1
  195. package/dist/web/assets/connection-row-CNqLBR1i.js +0 -1
  196. package/dist/web/assets/connections-C1IpAcWR.js +0 -1
  197. package/dist/web/assets/data-table-BvxYQlKG.js +0 -1
  198. package/dist/web/assets/empty-S0yBUx1s.js +0 -1
  199. package/dist/web/assets/events-DfpBS1R8.js +0 -1
  200. package/dist/web/assets/field-D3og-MC2.js +0 -1
  201. package/dist/web/assets/get-started-Bjn6yh4e.js +0 -2
  202. package/dist/web/assets/index-CXE-zE4L.css +0 -2
  203. package/dist/web/assets/index-DZpRTT5Y.js +0 -11
  204. package/dist/web/assets/input-C742dYRC.js +0 -1
  205. package/dist/web/assets/input-group-Cl_miHC9.js +0 -1
  206. package/dist/web/assets/label-multi-select-Dsp3ZtIH.js +0 -1
  207. package/dist/web/assets/login-BGxHAaO3.js +0 -1
  208. package/dist/web/assets/mcp-call-card-DNX07Ywo.js +0 -3
  209. package/dist/web/assets/mutations-B9VxIGUu.js +0 -1
  210. package/dist/web/assets/pairing-QdgK5aKC.js +0 -1
  211. package/dist/web/assets/pairing-notifier-BhE_iSwW.js +0 -1
  212. package/dist/web/assets/password-input-LfWzs6e3.js +0 -1
  213. package/dist/web/assets/plus-DgbEC-TH.js +0 -1
  214. package/dist/web/assets/preload-helper-j0EKW2si.js +0 -1
  215. package/dist/web/assets/queries-Bj56TfoQ.js +0 -42
  216. package/dist/web/assets/revealed-keys-BsJnyIy3.js +0 -1
  217. package/dist/web/assets/rolldown-runtime-XKyaHtl9.js +0 -1
  218. package/dist/web/assets/select-ItccDGGy.js +0 -1
  219. package/dist/web/assets/settings-DNvv2DG9.js +0 -1
  220. package/dist/web/assets/setup-Epgx31vR.js +0 -1
  221. package/dist/web/assets/sheet-xUEO77dN.js +0 -1
  222. package/dist/web/assets/step-url-BXJXsJ4i.js +0 -1
  223. package/dist/web/assets/stream-CGDIebb7.js +0 -1
  224. package/dist/web/assets/tag-DfHTkXXr.js +0 -1
  225. package/dist/web/assets/test-email-button-DU4JRc5A.js +0 -1
  226. package/dist/web/assets/toggle-group-CAK1N7Uk.js +0 -1
  227. package/dist/web/assets/use-delivery-speed-Cz7wwlsF.js +0 -1
  228. package/dist/web/assets/use-sign-out-DjrTXR4G.js +0 -1
  229. package/dist/web/assets/useQuery-mLIt8a3j.js +0 -1
  230. package/dist/web/assets/useRouterState-BPAVcV8T.js +0 -1
  231. package/dist/web/assets/webhook-bits-CWEcMHDk.js +0 -1
  232. package/dist/web/assets/webhooks-Gnqbma_F.js +0 -1
  233. package/dist/web/assets/x-BKiZ2rOt.js +0 -1
package/README.md CHANGED
@@ -27,6 +27,15 @@ Calendar scopes on the consent screen, Calendar added to each account, and `cale
27
27
  `calendar.write`) on the agent's grant. The console's setup covers all of it; existing installs use Overview → "Add
28
28
  Google Calendar". Details: [docs/calendar.md](docs/calendar.md).
29
29
 
30
+ ## Extensions
31
+
32
+ Integrations are extensions: `npx xaq-cli ext add notion` installs one from the official
33
+ [xaq-extensions](https://github.com/wynn-dev/xaq-extensions) registry, pinned to a commit and an integrity hash, and
34
+ `ext list`, `ext update` (shows the change, asks first; nothing updates on its own) and `ext remove` manage them, as
35
+ does the console's Extensions page. A GitHub URL, another git URL or a local directory works too, as a third-party
36
+ extension, after a warning: a `code` extension runs on your machine with network access. Node's permission model
37
+ limits its files, child processes and workers, not its network. Details: [Extensions](docs/reference.md#extensions).
38
+
30
39
  ![Overview](docs/screenshots/overview.png)
31
40
 
32
41
  <!-- rename-xaq: keep-start -->
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export { n as CLEANUP_SIGNALS, o as daemonOrigin, s as openAdminChannel } from "./admin-DeCN2VDX.js";
2
+ export { n as CLEANUP_SIGNALS, o as daemonOrigin, s as openAdminChannel } from "./admin-vJum87dw.js";
@@ -7,7 +7,7 @@ import { b as TransportError, f as isHermesRoute, x as checkTransport } from "./
7
7
  import { i as safeText, r as formatDuration } from "./ui-B8Sd4Y7Y.js";
8
8
  import { D as ApiError, N as getSelf, b as forwardEvent, g as resolveListen, h as forwardSecret, m as describeFilter, o as routeWarning, u as DEFAULT_LISTEN_TYPES } from "./hermes-BXvD29dj.js";
9
9
  import { c as readDead, d as runCommand, f as Dispatcher, i as outputLine, l as readStatus, m as hasAccess, p as describeGrants, s as pidAlive, u as writeDead } from "./listen-Ce24Ito9.js";
10
- import { r as installedServiceLabel } from "./service-DjWgCF6M.js";
10
+ import { r as installedServiceLabel } from "./service-CYSNDoQk.js";
11
11
  import pc from "picocolors";
12
12
  //#region server/listen/admin.ts
13
13
  const out = (s = "") => process.stdout.write(`${s}\n`);
@@ -216,4 +216,4 @@ async function replayDead(flags, opts = {}) {
216
216
  //#endregion
217
217
  export { clearDead, printDead, printSecret, printStatus, replayDead };
218
218
 
219
- //# sourceMappingURL=admin-DI2rumeX.js.map
219
+ //# sourceMappingURL=admin-CoawcLsy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin-DI2rumeX.js","names":[],"sources":["../server/listen/admin.ts"],"sourcesContent":["import pc from \"picocolors\";\nimport { ApiError, getSelf } from \"./api.ts\";\nimport { describeGrants, hasAccess } from \"./grants.ts\";\nimport { Dispatcher, type Job, type Target } from \"./dispatcher.ts\";\nimport { runCommand } from \"./exec.ts\";\nimport { ensurePrivateDir } from \"./fsutil.ts\";\nimport { forwardEvent } from \"./forward.ts\";\nimport { routeWarning } from \"./hermes.ts\";\nimport { describeFilter, type ListenFlags, MAX_BATCH, resolveListen } from \"./options.ts\";\nimport { outputLine } from \"./listen.ts\";\nimport { isHermesRoute } from \"./recipes.ts\";\nimport { DEFAULT_PROFILE, deadPath, logPath, profilePath, validProfileName } from \"./paths.ts\";\nimport { loadProfile, saveProfile } from \"./profile.ts\";\nimport { DEFAULT_LISTEN_TYPES, forwardSecret } from \"./options.ts\";\nimport { installedServiceLabel } from \"./service.ts\";\nimport { type DeadEntry, pidAlive, readDead, readStatus, writeDead } from \"./state.ts\";\nimport { checkTransport, TransportError } from \"./transport.ts\";\nimport { formatDuration, safeText } from \"./ui.ts\";\nimport { NPX } from \"./version.ts\";\n\nconst out = (s = \"\") => process.stdout.write(`${s}\\n`);\nconst flagFor = (profile: string) => (profile === DEFAULT_PROFILE ? \"\" : ` --profile ${profile}`);\nconst ago = (iso: string | null | undefined) => (iso ? `${formatDuration(Date.now() - Date.parse(iso))} ago` : \"never\");\n\n// ───────────── listen secret ─────────────\n\n/** Prints the forward-mode signing secret (generating and saving it on first use). */\nexport function printSecret(profileName: string, opts: { rotate?: boolean }): void {\n const name = validProfileName(profileName);\n const profile = loadProfile(name);\n let { secret, created } = forwardSecret(profile);\n if (opts.rotate) {\n secret = forwardSecret({ ...profile, forward: undefined, forwardSecret: undefined }).secret;\n created = true;\n }\n if (created) {\n if (profile.forward) profile.forward = { ...profile.forward, secret };\n else profile.forwardSecret = secret;\n saveProfile(name, profile);\n process.stderr.write(pc.gray(`${opts.rotate ? \"Rotated\" : \"Generated\"} the forward signing secret for profile \"${name}\"${opts.rotate ? \"; restart the listener\" : \"\"}.\\n`));\n }\n // The secret alone on stdout, so `export XAQ_WEBHOOK_SECRET=$(xaq listen secret)` works.\n out(secret);\n}\n\n// ───────────── listen status ─────────────\n\nexport async function printStatus(profileName: string): Promise<void> {\n const name = validProfileName(profileName);\n const profile = loadProfile(name);\n const st = readStatus(name);\n const alive = st ? pidAlive(st.pid) && !st.stoppedAt : false;\n const dead = readDead(name);\n\n out(`${pc.bold(\"Profile\")} ${name} ${pc.gray(profilePath(name))}`);\n out(`${pc.bold(\"Agent\")} ${safeText(profile.agentName)} ${pc.gray(`(${profile.agentId})`)}`);\n out(`${pc.bold(\"Daemon\")} ${profile.url}`);\n out(`${pc.bold(\"Consumer\")} ${st?.consumer ?? profile.consumer}`);\n // What the last run used (flags included), else what the profile has saved.\n const exec = st && st.exec !== undefined ? st.exec : profile.exec;\n const forward = st && st.forward !== undefined ? st.forward : profile.forward?.url;\n const targets = [exec ? `exec ${safeText(exec, 120)}` : null, forward ? `forward ${forward}` : null].filter(Boolean);\n out(`${pc.bold(\"Runs\")} ${targets.length ? targets.join(\" · \") : pc.gray(\"nothing saved (pass --exec/--forward with --save)\")}`);\n out(\n `${pc.bold(\"Events\")} ${\n profile.types?.length\n ? profile.types.join(\", \")\n : `${DEFAULT_LISTEN_TYPES.join(\", \")} ${pc.gray(\"(default types; add --types all --save for every event)\")}`\n }`,\n );\n // The running listener's filters (flags included), else the profile's.\n let saved: ReturnType<typeof resolveListen>[\"config\"] | null = null;\n try {\n saved = resolveListen(name, profile, {}).config;\n } catch {\n // An invalid saved setting: `listen` itself reports it.\n }\n out(`${pc.bold(\"Filters\")} ${(alive && st?.filters) || (saved ? describeFilter(saved) : \"?\")}`);\n const batchSeconds = alive && st && st.batchSeconds !== undefined ? st.batchSeconds : (saved?.batchSeconds ?? 0);\n if (batchSeconds) out(`${pc.bold(\"Batch\")} ${formatDuration(batchSeconds * 1000)} window, up to ${MAX_BATCH} events per --exec run`);\n const route = forward && isHermesRoute(forward) ? routeWarning(name, forward) : null;\n if (route?.level === \"warn\") out(`${pc.bold(\"Route\")} ${pc.yellow(route.message)}`);\n out(`${pc.bold(\"Output\")} ${outputLine(name, { exec: exec ?? null, forward: forward ? { url: forward } : null }, logPath(name))}`);\n\n let svc = \"not installed\";\n if (process.platform === \"linux\" || process.platform === \"darwin\") {\n const installed = installedServiceLabel(name);\n if (installed) svc = `installed (${installed}; details: ${NPX} listen service status${flagFor(name)})`;\n }\n out(`${pc.bold(\"Service\")} ${svc}`);\n\n try {\n // Never send the key over a connection `listen` itself would refuse.\n await checkTransport(profile.url, { insecure: profile.insecure });\n const self = await getSelf(profile.url, profile.apiKey, 5000);\n out(`${pc.bold(\"Key\")} ${pc.green(\"valid\")}`);\n // The access the console actually granted, in plain words (what the agent should report to its human).\n const lines = describeGrants(self.grants, self.connections);\n out(`${pc.bold(\"Access\")} ${hasAccess(self) ? lines[0] : pc.yellow(lines[0]!)}`);\n for (const l of lines.slice(1)) out(` ${l}`);\n } catch (err) {\n const revoked = err instanceof ApiError && (err.status === 401 || err.status === 403);\n const why = err instanceof ApiError && err.status === 0 ? (err.cause as string) : err instanceof Error ? err.message.split(\"\\n\")[0] : String(err);\n if (err instanceof TransportError) out(`${pc.bold(\"Key\")} ${pc.red(`not checked: ${why}`)}`);\n else out(`${pc.bold(\"Key\")} ${revoked ? pc.red(`rejected: ${(err as Error).message}`) : pc.yellow(`not checked (${why})`)}`);\n }\n\n if (!st) {\n out(`${pc.bold(\"Listener\")} ${pc.gray(\"has not run yet\")}`);\n } else {\n const state = !alive\n ? pc.gray(`not running${st.stoppedAt ? ` (stopped ${ago(st.stoppedAt)}${st.exitReason ? `: ${safeText(st.exitReason, 160)}` : \"\"})` : \" (exited unexpectedly)\"}`)\n : st.connected\n ? pc.green(`running (pid ${st.pid}), connected ${ago(st.connectedSince)}`)\n : pc.yellow(`running (pid ${st.pid}), disconnected${st.lastDisconnect ? ` ${ago(st.lastDisconnect.at)}: ${safeText(st.lastDisconnect.reason, 160)}` : \"\"}`);\n out(`${pc.bold(\"Listener\")} ${state}`);\n out(\n `${pc.bold(\"Cursor\")} acked #${st.ackedSeq} · received #${st.receivedSeq}${alive && st.running ? ` · ${st.running} in progress` : \"\"}${st.skipped ? ` · ${st.skipped} skipped by filters` : \"\"} · last event ${ago(st.lastEventAt)}`,\n );\n if (st.recent.length) {\n out(pc.bold(\"Recent\"));\n for (const r of st.recent.slice(0, 8)) {\n const icon = r.outcome === \"succeeded\" ? pc.green(\"✓\") : r.outcome === \"dead\" ? pc.red(\"✖\") : r.outcome === \"retrying\" ? pc.yellow(\"↻\") : pc.gray(\"·\");\n out(` ${icon} #${r.seq} ${r.type} ${pc.gray(`${ago(r.at)}${r.attempts > 1 ? `, ${r.attempts} attempts` : \"\"}${r.error && r.outcome !== \"succeeded\" ? `, ${safeText(r.error, 100)}` : \"\"}`)}`);\n }\n }\n }\n out(`${pc.bold(\"Dead\")} ${dead.length ? pc.red(`${dead.length} event${dead.length > 1 ? \"s\" : \"\"}`) + pc.gray(` (inspect: ${NPX} listen dead${flagFor(name)})`) : \"0\"}`);\n out(`${pc.bold(\"Log\")} ${logPath(name)}`);\n}\n\n// ───────────── listen dead ─────────────\n\nexport function printDead(profileName: string, opts: { json?: boolean }): void {\n const name = validProfileName(profileName);\n const entries = readDead(name);\n if (opts.json) {\n for (const e of entries) out(JSON.stringify(e));\n return;\n }\n if (!entries.length) {\n out(`No dead events for profile \"${name}\".`);\n return;\n }\n for (const e of entries) {\n const m = e.payload.data.message;\n out(`${pc.red(\"✖\")} #${e.seq} ${e.type} ${pc.gray(e.id)} ${pc.gray(`died ${ago(e.deadAt)} after ${e.attempts} attempts`)}`);\n if (m) out(` from ${safeText(m.from?.email ?? \"?\", 80)} \"${safeText(m.subject, 80)}\"`);\n out(pc.gray(` ${safeText(e.error, 300)}`));\n }\n out();\n out(pc.gray(`${entries.length} dead event${entries.length > 1 ? \"s\" : \"\"} in ${deadPath(name)}.`));\n out(pc.gray(`Replay with: ${NPX} listen dead --replay${flagFor(name)} (one: --id <evt_…>; drop all: --clear)`));\n out(pc.gray(`Replay uses the profile's saved --exec/--forward; if you ran them as flags only, pass them again, e.g. --replay --exec '<command>'.`));\n}\n\nexport function clearDead(profileName: string, id?: string): void {\n const name = validProfileName(profileName);\n const entries = readDead(name);\n const keep = id ? entries.filter((e) => e.id !== id) : [];\n writeDead(name, keep);\n out(`Removed ${entries.length - keep.length} dead event${entries.length - keep.length === 1 ? \"\" : \"s\"}.`);\n}\n\n/**\n * Runs dead events again through the profile's targets (or the ones passed as flags). Successes are\n * removed from dead.jsonl; failures stay. Returns how many still failed.\n */\nexport async function replayDead(flags: ListenFlags & { id?: string }, opts: { retryDelayMs?: (n: number) => number } = {}): Promise<number> {\n const name = validProfileName(flags.profile ?? DEFAULT_PROFILE);\n const profile = loadProfile(name);\n const { config } = resolveListen(name, profile, { ...flags, save: false });\n const all = readDead(name);\n const chosen = all.filter((e) => !flags.id || e.id === flags.id);\n if (!chosen.length) {\n out(flags.id ? `No dead event with id ${flags.id}.` : `No dead events for profile \"${name}\".`);\n return 0;\n }\n const available: Target[] = [];\n if (config.exec) available.push(\"exec\");\n if (config.forward) available.push(\"forward\");\n if (!available.length) throw new Error(`Nothing to replay to: pass --recipe <tool> (claude-code, codex, cursor, hermes), --exec '<command>' and/or --forward <url> (or save them in the profile with \\`${NPX} listen --save …\\`).`);\n if (config.exec && process.platform === \"win32\") throw new Error(\"--exec isn't supported on Windows; replay with --forward instead.\");\n\n const timeoutMs = config.timeoutSeconds * 1000;\n if (config.cwdIsDefault) ensurePrivateDir(config.cwd);\n const failed = new Map<string, DeadEntry>();\n const bySeq = new Map(chosen.map((e) => [e.seq, e]));\n const dispatcher = new Dispatcher({\n targets: available,\n concurrency: config.concurrency,\n maxAttempts: config.maxAttempts,\n retryDelayMs: opts.retryDelayMs,\n run: async (job: Job, signal) => {\n const errors: string[] = [];\n if (job.remaining.has(\"exec\") && config.exec) {\n const r = await runCommand(config.exec, job.payload, { timeoutMs, signal, cwd: config.cwd });\n if (r.ok) job.remaining.delete(\"exec\");\n else errors.push(`command: ${r.error}`);\n }\n if (job.remaining.has(\"forward\") && config.forward) {\n const r = await forwardEvent(config.forward.url, config.forward.secret, job.payload, { timeoutMs: Math.min(timeoutMs, 30_000), signal, wrap: isHermesRoute(config.forward.url) });\n if (r.ok) job.remaining.delete(\"forward\");\n else errors.push(`forward: ${r.error}`);\n }\n return errors;\n },\n onAdvance: () => {},\n onDead: (job) => {\n const e = bySeq.get(job.seq)!;\n failed.set(e.id, { ...e, deadAt: new Date().toISOString(), attempts: e.attempts + job.attempts, error: job.lastError ?? \"failed\" });\n out(`${pc.red(\"✖\")} #${job.seq} still failing: ${safeText(job.lastError ?? \"failed\", 200)}`);\n },\n onFinished: (job) => {\n if (job.outcome === \"succeeded\") out(`${pc.green(\"✓\")} #${job.seq} ${job.payload.type} replayed`);\n },\n });\n out(`Replaying ${chosen.length} dead event${chosen.length > 1 ? \"s\" : \"\"} (up to ${config.maxAttempts} attempts each)…`);\n // Only the targets that never succeeded (a forward that went through isn't POSTed again).\n for (const e of [...chosen].sort((a, b) => a.seq - b.seq)) {\n const targets = e.targets?.length ? e.targets.filter((t) => available.includes(t)) : available;\n if (!targets.length) {\n out(`${pc.yellow(\"!\")} #${e.seq} needs ${e.targets.join(\" and \")}, which isn't configured now; pass --${e.targets[0]} to replay it.`);\n failed.set(e.id, e);\n continue;\n }\n dispatcher.push(e.payload, { targets });\n }\n await dispatcher.idle();\n\n const replayed = new Set(chosen.map((e) => e.id));\n // Re-read so entries added by a running listener meanwhile are kept.\n const current = readDead(name);\n writeDead(name, [...current.filter((e) => !replayed.has(e.id)), ...failed.values()]);\n out(`${chosen.length - failed.size} replayed, ${failed.size} still dead.`);\n return failed.size;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"admin-CoawcLsy.js","names":[],"sources":["../server/listen/admin.ts"],"sourcesContent":["import pc from \"picocolors\";\nimport { ApiError, getSelf } from \"./api.ts\";\nimport { describeGrants, hasAccess } from \"./grants.ts\";\nimport { Dispatcher, type Job, type Target } from \"./dispatcher.ts\";\nimport { runCommand } from \"./exec.ts\";\nimport { ensurePrivateDir } from \"./fsutil.ts\";\nimport { forwardEvent } from \"./forward.ts\";\nimport { routeWarning } from \"./hermes.ts\";\nimport { describeFilter, type ListenFlags, MAX_BATCH, resolveListen } from \"./options.ts\";\nimport { outputLine } from \"./listen.ts\";\nimport { isHermesRoute } from \"./recipes.ts\";\nimport { DEFAULT_PROFILE, deadPath, logPath, profilePath, validProfileName } from \"./paths.ts\";\nimport { loadProfile, saveProfile } from \"./profile.ts\";\nimport { DEFAULT_LISTEN_TYPES, forwardSecret } from \"./options.ts\";\nimport { installedServiceLabel } from \"./service.ts\";\nimport { type DeadEntry, pidAlive, readDead, readStatus, writeDead } from \"./state.ts\";\nimport { checkTransport, TransportError } from \"./transport.ts\";\nimport { formatDuration, safeText } from \"./ui.ts\";\nimport { NPX } from \"./version.ts\";\n\nconst out = (s = \"\") => process.stdout.write(`${s}\\n`);\nconst flagFor = (profile: string) => (profile === DEFAULT_PROFILE ? \"\" : ` --profile ${profile}`);\nconst ago = (iso: string | null | undefined) => (iso ? `${formatDuration(Date.now() - Date.parse(iso))} ago` : \"never\");\n\n// ───────────── listen secret ─────────────\n\n/** Prints the forward-mode signing secret (generating and saving it on first use). */\nexport function printSecret(profileName: string, opts: { rotate?: boolean }): void {\n const name = validProfileName(profileName);\n const profile = loadProfile(name);\n let { secret, created } = forwardSecret(profile);\n if (opts.rotate) {\n secret = forwardSecret({ ...profile, forward: undefined, forwardSecret: undefined }).secret;\n created = true;\n }\n if (created) {\n if (profile.forward) profile.forward = { ...profile.forward, secret };\n else profile.forwardSecret = secret;\n saveProfile(name, profile);\n process.stderr.write(pc.gray(`${opts.rotate ? \"Rotated\" : \"Generated\"} the forward signing secret for profile \"${name}\"${opts.rotate ? \"; restart the listener\" : \"\"}.\\n`));\n }\n // The secret alone on stdout, so `export XAQ_WEBHOOK_SECRET=$(xaq listen secret)` works.\n out(secret);\n}\n\n// ───────────── listen status ─────────────\n\nexport async function printStatus(profileName: string): Promise<void> {\n const name = validProfileName(profileName);\n const profile = loadProfile(name);\n const st = readStatus(name);\n const alive = st ? pidAlive(st.pid) && !st.stoppedAt : false;\n const dead = readDead(name);\n\n out(`${pc.bold(\"Profile\")} ${name} ${pc.gray(profilePath(name))}`);\n out(`${pc.bold(\"Agent\")} ${safeText(profile.agentName)} ${pc.gray(`(${profile.agentId})`)}`);\n out(`${pc.bold(\"Daemon\")} ${profile.url}`);\n out(`${pc.bold(\"Consumer\")} ${st?.consumer ?? profile.consumer}`);\n // What the last run used (flags included), else what the profile has saved.\n const exec = st && st.exec !== undefined ? st.exec : profile.exec;\n const forward = st && st.forward !== undefined ? st.forward : profile.forward?.url;\n const targets = [exec ? `exec ${safeText(exec, 120)}` : null, forward ? `forward ${forward}` : null].filter(Boolean);\n out(`${pc.bold(\"Runs\")} ${targets.length ? targets.join(\" · \") : pc.gray(\"nothing saved (pass --exec/--forward with --save)\")}`);\n out(\n `${pc.bold(\"Events\")} ${\n profile.types?.length\n ? profile.types.join(\", \")\n : `${DEFAULT_LISTEN_TYPES.join(\", \")} ${pc.gray(\"(default types; add --types all --save for every event)\")}`\n }`,\n );\n // The running listener's filters (flags included), else the profile's.\n let saved: ReturnType<typeof resolveListen>[\"config\"] | null = null;\n try {\n saved = resolveListen(name, profile, {}).config;\n } catch {\n // An invalid saved setting: `listen` itself reports it.\n }\n out(`${pc.bold(\"Filters\")} ${(alive && st?.filters) || (saved ? describeFilter(saved) : \"?\")}`);\n const batchSeconds = alive && st && st.batchSeconds !== undefined ? st.batchSeconds : (saved?.batchSeconds ?? 0);\n if (batchSeconds) out(`${pc.bold(\"Batch\")} ${formatDuration(batchSeconds * 1000)} window, up to ${MAX_BATCH} events per --exec run`);\n const route = forward && isHermesRoute(forward) ? routeWarning(name, forward) : null;\n if (route?.level === \"warn\") out(`${pc.bold(\"Route\")} ${pc.yellow(route.message)}`);\n out(`${pc.bold(\"Output\")} ${outputLine(name, { exec: exec ?? null, forward: forward ? { url: forward } : null }, logPath(name))}`);\n\n let svc = \"not installed\";\n if (process.platform === \"linux\" || process.platform === \"darwin\") {\n const installed = installedServiceLabel(name);\n if (installed) svc = `installed (${installed}; details: ${NPX} listen service status${flagFor(name)})`;\n }\n out(`${pc.bold(\"Service\")} ${svc}`);\n\n try {\n // Never send the key over a connection `listen` itself would refuse.\n await checkTransport(profile.url, { insecure: profile.insecure });\n const self = await getSelf(profile.url, profile.apiKey, 5000);\n out(`${pc.bold(\"Key\")} ${pc.green(\"valid\")}`);\n // The access the console actually granted, in plain words (what the agent should report to its human).\n const lines = describeGrants(self.grants, self.connections);\n out(`${pc.bold(\"Access\")} ${hasAccess(self) ? lines[0] : pc.yellow(lines[0]!)}`);\n for (const l of lines.slice(1)) out(` ${l}`);\n } catch (err) {\n const revoked = err instanceof ApiError && (err.status === 401 || err.status === 403);\n const why = err instanceof ApiError && err.status === 0 ? (err.cause as string) : err instanceof Error ? err.message.split(\"\\n\")[0] : String(err);\n if (err instanceof TransportError) out(`${pc.bold(\"Key\")} ${pc.red(`not checked: ${why}`)}`);\n else out(`${pc.bold(\"Key\")} ${revoked ? pc.red(`rejected: ${(err as Error).message}`) : pc.yellow(`not checked (${why})`)}`);\n }\n\n if (!st) {\n out(`${pc.bold(\"Listener\")} ${pc.gray(\"has not run yet\")}`);\n } else {\n const state = !alive\n ? pc.gray(`not running${st.stoppedAt ? ` (stopped ${ago(st.stoppedAt)}${st.exitReason ? `: ${safeText(st.exitReason, 160)}` : \"\"})` : \" (exited unexpectedly)\"}`)\n : st.connected\n ? pc.green(`running (pid ${st.pid}), connected ${ago(st.connectedSince)}`)\n : pc.yellow(`running (pid ${st.pid}), disconnected${st.lastDisconnect ? ` ${ago(st.lastDisconnect.at)}: ${safeText(st.lastDisconnect.reason, 160)}` : \"\"}`);\n out(`${pc.bold(\"Listener\")} ${state}`);\n out(\n `${pc.bold(\"Cursor\")} acked #${st.ackedSeq} · received #${st.receivedSeq}${alive && st.running ? ` · ${st.running} in progress` : \"\"}${st.skipped ? ` · ${st.skipped} skipped by filters` : \"\"} · last event ${ago(st.lastEventAt)}`,\n );\n if (st.recent.length) {\n out(pc.bold(\"Recent\"));\n for (const r of st.recent.slice(0, 8)) {\n const icon = r.outcome === \"succeeded\" ? pc.green(\"✓\") : r.outcome === \"dead\" ? pc.red(\"✖\") : r.outcome === \"retrying\" ? pc.yellow(\"↻\") : pc.gray(\"·\");\n out(` ${icon} #${r.seq} ${r.type} ${pc.gray(`${ago(r.at)}${r.attempts > 1 ? `, ${r.attempts} attempts` : \"\"}${r.error && r.outcome !== \"succeeded\" ? `, ${safeText(r.error, 100)}` : \"\"}`)}`);\n }\n }\n }\n out(`${pc.bold(\"Dead\")} ${dead.length ? pc.red(`${dead.length} event${dead.length > 1 ? \"s\" : \"\"}`) + pc.gray(` (inspect: ${NPX} listen dead${flagFor(name)})`) : \"0\"}`);\n out(`${pc.bold(\"Log\")} ${logPath(name)}`);\n}\n\n// ───────────── listen dead ─────────────\n\nexport function printDead(profileName: string, opts: { json?: boolean }): void {\n const name = validProfileName(profileName);\n const entries = readDead(name);\n if (opts.json) {\n for (const e of entries) out(JSON.stringify(e));\n return;\n }\n if (!entries.length) {\n out(`No dead events for profile \"${name}\".`);\n return;\n }\n for (const e of entries) {\n const m = e.payload.data.message;\n out(`${pc.red(\"✖\")} #${e.seq} ${e.type} ${pc.gray(e.id)} ${pc.gray(`died ${ago(e.deadAt)} after ${e.attempts} attempts`)}`);\n if (m) out(` from ${safeText(m.from?.email ?? \"?\", 80)} \"${safeText(m.subject, 80)}\"`);\n out(pc.gray(` ${safeText(e.error, 300)}`));\n }\n out();\n out(pc.gray(`${entries.length} dead event${entries.length > 1 ? \"s\" : \"\"} in ${deadPath(name)}.`));\n out(pc.gray(`Replay with: ${NPX} listen dead --replay${flagFor(name)} (one: --id <evt_…>; drop all: --clear)`));\n out(pc.gray(`Replay uses the profile's saved --exec/--forward; if you ran them as flags only, pass them again, e.g. --replay --exec '<command>'.`));\n}\n\nexport function clearDead(profileName: string, id?: string): void {\n const name = validProfileName(profileName);\n const entries = readDead(name);\n const keep = id ? entries.filter((e) => e.id !== id) : [];\n writeDead(name, keep);\n out(`Removed ${entries.length - keep.length} dead event${entries.length - keep.length === 1 ? \"\" : \"s\"}.`);\n}\n\n/**\n * Runs dead events again through the profile's targets (or the ones passed as flags). Successes are\n * removed from dead.jsonl; failures stay. Returns how many still failed.\n */\nexport async function replayDead(flags: ListenFlags & { id?: string }, opts: { retryDelayMs?: (n: number) => number } = {}): Promise<number> {\n const name = validProfileName(flags.profile ?? DEFAULT_PROFILE);\n const profile = loadProfile(name);\n const { config } = resolveListen(name, profile, { ...flags, save: false });\n const all = readDead(name);\n const chosen = all.filter((e) => !flags.id || e.id === flags.id);\n if (!chosen.length) {\n out(flags.id ? `No dead event with id ${flags.id}.` : `No dead events for profile \"${name}\".`);\n return 0;\n }\n const available: Target[] = [];\n if (config.exec) available.push(\"exec\");\n if (config.forward) available.push(\"forward\");\n if (!available.length) throw new Error(`Nothing to replay to: pass --recipe <tool> (claude-code, codex, cursor, hermes), --exec '<command>' and/or --forward <url> (or save them in the profile with \\`${NPX} listen --save …\\`).`);\n if (config.exec && process.platform === \"win32\") throw new Error(\"--exec isn't supported on Windows; replay with --forward instead.\");\n\n const timeoutMs = config.timeoutSeconds * 1000;\n if (config.cwdIsDefault) ensurePrivateDir(config.cwd);\n const failed = new Map<string, DeadEntry>();\n const bySeq = new Map(chosen.map((e) => [e.seq, e]));\n const dispatcher = new Dispatcher({\n targets: available,\n concurrency: config.concurrency,\n maxAttempts: config.maxAttempts,\n retryDelayMs: opts.retryDelayMs,\n run: async (job: Job, signal) => {\n const errors: string[] = [];\n if (job.remaining.has(\"exec\") && config.exec) {\n const r = await runCommand(config.exec, job.payload, { timeoutMs, signal, cwd: config.cwd });\n if (r.ok) job.remaining.delete(\"exec\");\n else errors.push(`command: ${r.error}`);\n }\n if (job.remaining.has(\"forward\") && config.forward) {\n const r = await forwardEvent(config.forward.url, config.forward.secret, job.payload, { timeoutMs: Math.min(timeoutMs, 30_000), signal, wrap: isHermesRoute(config.forward.url) });\n if (r.ok) job.remaining.delete(\"forward\");\n else errors.push(`forward: ${r.error}`);\n }\n return errors;\n },\n onAdvance: () => {},\n onDead: (job) => {\n const e = bySeq.get(job.seq)!;\n failed.set(e.id, { ...e, deadAt: new Date().toISOString(), attempts: e.attempts + job.attempts, error: job.lastError ?? \"failed\" });\n out(`${pc.red(\"✖\")} #${job.seq} still failing: ${safeText(job.lastError ?? \"failed\", 200)}`);\n },\n onFinished: (job) => {\n if (job.outcome === \"succeeded\") out(`${pc.green(\"✓\")} #${job.seq} ${job.payload.type} replayed`);\n },\n });\n out(`Replaying ${chosen.length} dead event${chosen.length > 1 ? \"s\" : \"\"} (up to ${config.maxAttempts} attempts each)…`);\n // Only the targets that never succeeded (a forward that went through isn't POSTed again).\n for (const e of [...chosen].sort((a, b) => a.seq - b.seq)) {\n const targets = e.targets?.length ? e.targets.filter((t) => available.includes(t)) : available;\n if (!targets.length) {\n out(`${pc.yellow(\"!\")} #${e.seq} needs ${e.targets.join(\" and \")}, which isn't configured now; pass --${e.targets[0]} to replay it.`);\n failed.set(e.id, e);\n continue;\n }\n dispatcher.push(e.payload, { targets });\n }\n await dispatcher.idle();\n\n const replayed = new Set(chosen.map((e) => e.id));\n // Re-read so entries added by a running listener meanwhile are kept.\n const current = readDead(name);\n writeDead(name, [...current.filter((e) => !replayed.has(e.id)), ...failed.values()]);\n out(`${chosen.length - failed.size} replayed, ${failed.size} still dead.`);\n return failed.size;\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { i as sha256 } from "./crypto-s8x6up-l.js";
3
3
  import { r as SESSION_COOKIE, t as CSRF_HEADER, y as SESSION_MAX_AGE_MS } from "./auth-BLYwD_zT.js";
4
- import { n as dataFiles } from "./config-D3jl_sOt.js";
5
- import { c as runningInstance } from "./instance-CPpDQZ1c.js";
4
+ import { n as dataFiles } from "./config-cEBzNniL.js";
5
+ import { c as runningInstance } from "./instance-cYidg0yj.js";
6
6
  import { existsSync } from "node:fs";
7
7
  import { DatabaseSync } from "node:sqlite";
8
8
  import { randomBytes } from "node:crypto";
@@ -159,4 +159,4 @@ function openAdminChannel(opts) {
159
159
  //#endregion
160
160
  export { createCliSession as a, CLI_SESSION_TTL_MS as i, CLEANUP_SIGNALS as n, daemonOrigin as o, CLI_SESSION_LABEL as r, openAdminChannel as s, AdminApiError as t };
161
161
 
162
- //# sourceMappingURL=admin-DeCN2VDX.js.map
162
+ //# sourceMappingURL=admin-vJum87dw.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin-DeCN2VDX.js","names":[],"sources":["../server/connections-cli/admin.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { SESSION_COOKIE, CSRF_HEADER } from \"../auth/index.ts\";\nimport { SESSION_MAX_AGE_MS } from \"../auth/sessions.ts\";\nimport { dataFiles } from \"../config.ts\";\nimport { sha256 } from \"../crypto.ts\";\nimport { type InstanceInfo, runningInstance } from \"../instance.ts\";\n\n/**\n * The CLI's admin channel to the daemon on this machine (`connections …`, `import`), without the console password.\n *\n * Whoever can write the data directory already holds master.key and the database (`reset-password` relies on the\n * same thing), so the CLI proves it is the admin by writing there: it inserts a one-time admin session (only\n * sha256(token) is stored, like console sessions) that expires after 15 minutes, uses it as the session cookie\n * against the daemon's own /api on loopback, and deletes it when the command ends. Nothing new listens, no\n * token file sits on disk, and the daemon's admin auth (requireAdmin + the CSRF header) is unchanged: every API\n * call is checked, audited and rate-limited exactly like the console's.\n *\n * Console sessions slide forward on use up to SESSION_MAX_AGE_MS after created_at. The CLI's row is written with\n * created_at = expiry − SESSION_MAX_AGE_MS, so that hard limit is the 15-minute expiry and the row can never be\n * extended (see docs/FOLLOWUPS.md: a session kind column would say this directly).\n */\n\nexport const CLI_SESSION_TTL_MS = 15 * 60_000;\nexport const CLI_SESSION_LABEL = \"xaq CLI (local admin, one-time)\";\n\nexport class AdminApiError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n readonly hint?: string,\n /** The parsed response body (some routes answer an error with their own shape, e.g. /finish). */\n readonly body?: unknown,\n ) {\n super(message);\n this.name = \"AdminApiError\";\n }\n}\n\nexport interface AdminChannel {\n /** The daemon's loopback origin, e.g. http://127.0.0.1:8425. */\n readonly baseUrl: string;\n request<T>(method: string, path: string, body?: unknown): Promise<T>;\n /**\n * A fingerprint of the connection's stored credential (issued_at plus its randomised ciphertext), read from the\n * database: it changes on every saved sign-in, so a Reconnect's completion is seen even on an active connection.\n * null when nothing is stored (or it can't be read). Never the credential itself.\n */\n credentialStamp(connectionId: string): string | null;\n /** Deletes the one-time session. Idempotent. */\n close(): void;\n}\n\n/** Signals that end a CLI command: the one-time session is deleted on each (and on a normal exit). */\nexport const CLEANUP_SIGNALS = [\"SIGINT\", \"SIGTERM\", \"SIGHUP\"] as const;\n\nexport interface OpenAdminOptions {\n dataDir: string;\n /** Tests: the running instance, instead of reading the lock file. */\n instance?: InstanceInfo | null;\n /** Tests: the daemon origin, instead of deriving it from the instance. */\n baseUrl?: string;\n fetch?: typeof fetch;\n clock?: () => number;\n}\n\n/** Where the CLI reaches the daemon: its bind address, or loopback when it listens on every interface. */\nexport function daemonOrigin(instance: Pick<InstanceInfo, \"host\" | \"port\">): string {\n const host = instance.host;\n const any = !host || host === \"0.0.0.0\" || host === \"::\" || host === \"[::]\";\n const h = any ? \"127.0.0.1\" : host.replace(/^\\[|\\]$/g, \"\");\n return `http://${h.includes(\":\") ? `[${h}]` : h}:${instance.port}`;\n}\n\n/** Inserts the one-time admin session; returns the token. */\nexport function createCliSession(dbPath: string, clock: () => number = Date.now): { token: string; remove: () => void } {\n const token = randomBytes(32).toString(\"base64url\");\n const hash = sha256(token);\n const expires = clock() + CLI_SESSION_TTL_MS;\n const db = new DatabaseSync(dbPath);\n try {\n db.exec(\"PRAGMA busy_timeout = 5000\");\n db.prepare(\"INSERT INTO admin_sessions (token_hash, created_at, expires_at, ip, user_agent) VALUES (?, ?, ?, ?, ?)\").run(\n hash,\n new Date(expires - SESSION_MAX_AGE_MS).toISOString(),\n new Date(expires).toISOString(),\n \"local-cli\",\n CLI_SESSION_LABEL,\n );\n } finally {\n db.close();\n }\n let removed = false;\n return {\n token,\n remove() {\n if (removed) return;\n removed = true;\n try {\n const d = new DatabaseSync(dbPath);\n try {\n d.exec(\"PRAGMA busy_timeout = 5000\");\n d.prepare(\"DELETE FROM admin_sessions WHERE token_hash = ?\").run(hash);\n } finally {\n d.close();\n }\n } catch {\n // It expires on its own within 15 minutes.\n }\n },\n };\n}\n\nexport function openAdminChannel(opts: OpenAdminOptions): AdminChannel {\n // xaq.db, or agentinbox.db in a data directory from before the rename (rename-xaq: keep).\n const dbPath = dataFiles(opts.dataDir).db;\n if (!existsSync(dbPath)) {\n throw new Error(\n `No xaq data in ${opts.dataDir}. Run this on the machine xaq runs on, as the user it runs as ` +\n \"(with sudo for a system service), or pass its data directory with --data.\",\n );\n }\n const instance = opts.instance === undefined ? runningInstance(opts.dataDir) : opts.instance;\n if (!instance && !opts.baseUrl) throw new Error(`xaq isn't running with the data directory ${opts.dataDir}. Start it first (or pass --data).`);\n const baseUrl = opts.baseUrl ?? daemonOrigin(instance!);\n let session: ReturnType<typeof createCliSession>;\n try {\n session = createCliSession(dbPath, opts.clock);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n throw new Error(\n `Can't open ${dbPath}${code ? ` (${code})` : \"\"}: ${(err as Error).message}. Run this as the user xaq runs as (with sudo for a system service).`,\n );\n }\n const onExit = () => session.remove();\n process.once(\"exit\", onExit);\n const doFetch = opts.fetch ?? fetch;\n const send = (method: string, path: string, body: unknown) => {\n const headers: Record<string, string> = { cookie: `${SESSION_COOKIE}=${session.token}`, [CSRF_HEADER]: \"1\", accept: \"application/json\" };\n if (body !== undefined) headers[\"content-type\"] = \"application/json\";\n return doFetch(`${baseUrl}${path}`, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(120_000) });\n };\n return {\n baseUrl,\n async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n let res: Response;\n try {\n res = await send(method, path, body);\n // The session's 15 minutes ran out (a slow sign-in): mint a fresh one and retry once.\n if (res.status === 401) {\n await res.body?.cancel().catch(() => {});\n session.remove();\n session = createCliSession(dbPath, opts.clock);\n res = await send(method, path, body);\n }\n } catch (err) {\n throw new Error(`Couldn't reach xaq at ${baseUrl}: ${(err as Error).message}`);\n }\n if (res.status === 204) return undefined as T;\n const text = await res.text();\n let data: unknown = null;\n try {\n data = text ? JSON.parse(text) : null;\n } catch {\n // Not JSON: reported below.\n }\n if (!res.ok) {\n const e = (data as { error?: { code?: string; message?: string; hint?: string } } | null)?.error;\n throw new AdminApiError(res.status, e?.code ?? `http_${res.status}`, e?.message ?? `HTTP ${res.status} from ${path}`, e?.hint, data);\n }\n return data as T;\n },\n credentialStamp(connectionId: string): string | null {\n try {\n const db = new DatabaseSync(dbPath, { readOnly: true });\n try {\n db.exec(\"PRAGMA busy_timeout = 5000\");\n const r = db.prepare(\"SELECT issued_at, access_token_enc, header_value_enc FROM connection_credentials WHERE connection_id = ?\").get(connectionId) as\n | { issued_at: string | null; access_token_enc: string | null; header_value_enc: string | null }\n | undefined;\n return r ? sha256(`${r.issued_at ?? \"\"}|${r.access_token_enc ?? \"\"}|${r.header_value_enc ?? \"\"}`) : null;\n } finally {\n db.close();\n }\n } catch {\n return null;\n }\n },\n close() {\n process.removeListener(\"exit\", onExit);\n session.remove();\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"admin-vJum87dw.js","names":[],"sources":["../server/connections-cli/admin.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { SESSION_COOKIE, CSRF_HEADER } from \"../auth/index.ts\";\nimport { SESSION_MAX_AGE_MS } from \"../auth/sessions.ts\";\nimport { dataFiles } from \"../config.ts\";\nimport { sha256 } from \"../crypto.ts\";\nimport { type InstanceInfo, runningInstance } from \"../instance.ts\";\n\n/**\n * The CLI's admin channel to the daemon on this machine (`connections …`, `import`), without the console password.\n *\n * Whoever can write the data directory already holds master.key and the database (`reset-password` relies on the\n * same thing), so the CLI proves it is the admin by writing there: it inserts a one-time admin session (only\n * sha256(token) is stored, like console sessions) that expires after 15 minutes, uses it as the session cookie\n * against the daemon's own /api on loopback, and deletes it when the command ends. Nothing new listens, no\n * token file sits on disk, and the daemon's admin auth (requireAdmin + the CSRF header) is unchanged: every API\n * call is checked, audited and rate-limited exactly like the console's.\n *\n * Console sessions slide forward on use up to SESSION_MAX_AGE_MS after created_at. The CLI's row is written with\n * created_at = expiry − SESSION_MAX_AGE_MS, so that hard limit is the 15-minute expiry and the row can never be\n * extended (see docs/FOLLOWUPS.md: a session kind column would say this directly).\n */\n\nexport const CLI_SESSION_TTL_MS = 15 * 60_000;\nexport const CLI_SESSION_LABEL = \"xaq CLI (local admin, one-time)\";\n\nexport class AdminApiError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n readonly hint?: string,\n /** The parsed response body (some routes answer an error with their own shape, e.g. /finish). */\n readonly body?: unknown,\n ) {\n super(message);\n this.name = \"AdminApiError\";\n }\n}\n\nexport interface AdminChannel {\n /** The daemon's loopback origin, e.g. http://127.0.0.1:8425. */\n readonly baseUrl: string;\n request<T>(method: string, path: string, body?: unknown): Promise<T>;\n /**\n * A fingerprint of the connection's stored credential (issued_at plus its randomised ciphertext), read from the\n * database: it changes on every saved sign-in, so a Reconnect's completion is seen even on an active connection.\n * null when nothing is stored (or it can't be read). Never the credential itself.\n */\n credentialStamp(connectionId: string): string | null;\n /** Deletes the one-time session. Idempotent. */\n close(): void;\n}\n\n/** Signals that end a CLI command: the one-time session is deleted on each (and on a normal exit). */\nexport const CLEANUP_SIGNALS = [\"SIGINT\", \"SIGTERM\", \"SIGHUP\"] as const;\n\nexport interface OpenAdminOptions {\n dataDir: string;\n /** Tests: the running instance, instead of reading the lock file. */\n instance?: InstanceInfo | null;\n /** Tests: the daemon origin, instead of deriving it from the instance. */\n baseUrl?: string;\n fetch?: typeof fetch;\n clock?: () => number;\n}\n\n/** Where the CLI reaches the daemon: its bind address, or loopback when it listens on every interface. */\nexport function daemonOrigin(instance: Pick<InstanceInfo, \"host\" | \"port\">): string {\n const host = instance.host;\n const any = !host || host === \"0.0.0.0\" || host === \"::\" || host === \"[::]\";\n const h = any ? \"127.0.0.1\" : host.replace(/^\\[|\\]$/g, \"\");\n return `http://${h.includes(\":\") ? `[${h}]` : h}:${instance.port}`;\n}\n\n/** Inserts the one-time admin session; returns the token. */\nexport function createCliSession(dbPath: string, clock: () => number = Date.now): { token: string; remove: () => void } {\n const token = randomBytes(32).toString(\"base64url\");\n const hash = sha256(token);\n const expires = clock() + CLI_SESSION_TTL_MS;\n const db = new DatabaseSync(dbPath);\n try {\n db.exec(\"PRAGMA busy_timeout = 5000\");\n db.prepare(\"INSERT INTO admin_sessions (token_hash, created_at, expires_at, ip, user_agent) VALUES (?, ?, ?, ?, ?)\").run(\n hash,\n new Date(expires - SESSION_MAX_AGE_MS).toISOString(),\n new Date(expires).toISOString(),\n \"local-cli\",\n CLI_SESSION_LABEL,\n );\n } finally {\n db.close();\n }\n let removed = false;\n return {\n token,\n remove() {\n if (removed) return;\n removed = true;\n try {\n const d = new DatabaseSync(dbPath);\n try {\n d.exec(\"PRAGMA busy_timeout = 5000\");\n d.prepare(\"DELETE FROM admin_sessions WHERE token_hash = ?\").run(hash);\n } finally {\n d.close();\n }\n } catch {\n // It expires on its own within 15 minutes.\n }\n },\n };\n}\n\nexport function openAdminChannel(opts: OpenAdminOptions): AdminChannel {\n // xaq.db, or agentinbox.db in a data directory from before the rename (rename-xaq: keep).\n const dbPath = dataFiles(opts.dataDir).db;\n if (!existsSync(dbPath)) {\n throw new Error(\n `No xaq data in ${opts.dataDir}. Run this on the machine xaq runs on, as the user it runs as ` +\n \"(with sudo for a system service), or pass its data directory with --data.\",\n );\n }\n const instance = opts.instance === undefined ? runningInstance(opts.dataDir) : opts.instance;\n if (!instance && !opts.baseUrl) throw new Error(`xaq isn't running with the data directory ${opts.dataDir}. Start it first (or pass --data).`);\n const baseUrl = opts.baseUrl ?? daemonOrigin(instance!);\n let session: ReturnType<typeof createCliSession>;\n try {\n session = createCliSession(dbPath, opts.clock);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n throw new Error(\n `Can't open ${dbPath}${code ? ` (${code})` : \"\"}: ${(err as Error).message}. Run this as the user xaq runs as (with sudo for a system service).`,\n );\n }\n const onExit = () => session.remove();\n process.once(\"exit\", onExit);\n const doFetch = opts.fetch ?? fetch;\n const send = (method: string, path: string, body: unknown) => {\n const headers: Record<string, string> = { cookie: `${SESSION_COOKIE}=${session.token}`, [CSRF_HEADER]: \"1\", accept: \"application/json\" };\n if (body !== undefined) headers[\"content-type\"] = \"application/json\";\n return doFetch(`${baseUrl}${path}`, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(120_000) });\n };\n return {\n baseUrl,\n async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n let res: Response;\n try {\n res = await send(method, path, body);\n // The session's 15 minutes ran out (a slow sign-in): mint a fresh one and retry once.\n if (res.status === 401) {\n await res.body?.cancel().catch(() => {});\n session.remove();\n session = createCliSession(dbPath, opts.clock);\n res = await send(method, path, body);\n }\n } catch (err) {\n throw new Error(`Couldn't reach xaq at ${baseUrl}: ${(err as Error).message}`);\n }\n if (res.status === 204) return undefined as T;\n const text = await res.text();\n let data: unknown = null;\n try {\n data = text ? JSON.parse(text) : null;\n } catch {\n // Not JSON: reported below.\n }\n if (!res.ok) {\n const e = (data as { error?: { code?: string; message?: string; hint?: string } } | null)?.error;\n throw new AdminApiError(res.status, e?.code ?? `http_${res.status}`, e?.message ?? `HTTP ${res.status} from ${path}`, e?.hint, data);\n }\n return data as T;\n },\n credentialStamp(connectionId: string): string | null {\n try {\n const db = new DatabaseSync(dbPath, { readOnly: true });\n try {\n db.exec(\"PRAGMA busy_timeout = 5000\");\n const r = db.prepare(\"SELECT issued_at, access_token_enc, header_value_enc FROM connection_credentials WHERE connection_id = ?\").get(connectionId) as\n | { issued_at: string | null; access_token_enc: string | null; header_value_enc: string | null }\n | undefined;\n return r ? sha256(`${r.issued_at ?? \"\"}|${r.access_token_enc ?? \"\"}|${r.header_value_enc ?? \"\"}`) : null;\n } finally {\n db.close();\n }\n } catch {\n return null;\n }\n },\n close() {\n process.removeListener(\"exit\", onExit);\n session.remove();\n },\n };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as AdminApiError } from "./admin-DeCN2VDX.js";
2
+ import { t as AdminApiError } from "./admin-vJum87dw.js";
3
3
  //#region server/connections-cli/api.ts
4
4
  /** The routes aren't there: an older daemon, or brokerUpstreams is off. */
5
5
  var BrokerOffError = class extends Error {
@@ -53,6 +53,10 @@ var ConnectionsApi = class {
53
53
  kill(id) {
54
54
  return this.call("POST", `/api/connections/${enc(id)}/kill`, {});
55
55
  }
56
+ /** PATCH: a new slug renames the exposed tools (`<slug>__<tool>`); approval rules carry over. */
57
+ update(id, patch) {
58
+ return this.call("PATCH", `/api/connections/${enc(id)}`, patch);
59
+ }
56
60
  remove(id) {
57
61
  return this.call("DELETE", `/api/connections/${enc(id)}`);
58
62
  }
@@ -86,4 +90,4 @@ var ConnectionsApi = class {
86
90
  //#endregion
87
91
  export { ConnectionsApi };
88
92
 
89
- //# sourceMappingURL=api-CmTgScTD.js.map
93
+ //# sourceMappingURL=api-2WDmyFjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-2WDmyFjs.js","names":[],"sources":["../server/connections-cli/api.ts"],"sourcesContent":["import type {\n AccessLevel,\n Agent,\n ConnectionGrant,\n ConnectionProbe,\n ConnectionView,\n CreateConnectionInput,\n PresetView,\n StartConnectInput,\n StartConnectResult,\n} from \"../../shared/types.ts\";\nimport { AdminApiError, type AdminChannel } from \"./admin.ts\";\n\n/**\n * The admin connections API (server/api/connections.ts, DTOs in shared/types.ts) as the CLI uses it. While the\n * `brokerUpstreams` setting is off every /api/connections route answers 404; the CLI says so in plain words.\n */\n\nexport type { ConnectionProbe, ConnectionView, PresetView, StartConnectResult };\n\n/** The routes aren't there: an older daemon, or brokerUpstreams is off. */\nexport class BrokerOffError extends Error {\n constructor() {\n super(\n \"This xaq doesn't have connections turned on yet (the brokerUpstreams setting), or is older than this CLI. \" +\n \"Update it, turn connections on in the console's Settings, then run this again.\",\n );\n this.name = \"BrokerOffError\";\n }\n}\n\nconst enc = encodeURIComponent;\n\nexport class ConnectionsApi {\n constructor(readonly channel: AdminChannel) {}\n\n private async call<T>(method: string, path: string, body?: unknown): Promise<T> {\n try {\n return await this.channel.request<T>(method, path, body);\n } catch (err) {\n if (err instanceof AdminApiError && err.status === 404 && err.code === \"not_found\" && path.startsWith(\"/api/connections\")) throw new BrokerOffError();\n throw err;\n }\n }\n\n list(): Promise<ConnectionView[]> {\n return this.call(\"GET\", \"/api/connections\");\n }\n presets(): Promise<PresetView[]> {\n return this.call(\"GET\", \"/api/connections/presets\");\n }\n /** With `connectionId`, the probe uses that connection's private-target allow (when it covers the URL). */\n probe(url: string, connectionId?: string): Promise<ConnectionProbe> {\n return this.call(\"POST\", \"/api/connections/probe\", connectionId ? { url, connectionId } : { url });\n }\n create(input: CreateConnectionInput): Promise<ConnectionView> {\n return this.call(\"POST\", \"/api/connections\", input);\n }\n get(id: string): Promise<ConnectionView> {\n return this.call(\"GET\", `/api/connections/${enc(id)}`);\n }\n /** Stores a static header (and connects), or starts OAuth and returns the authorize URL. */\n connect(id: string, input: StartConnectInput = {}): Promise<StartConnectResult> {\n return this.call(\"POST\", `/api/connections/${enc(id)}/connect`, input);\n }\n /** The admin-only private-target allow, pinned server-side to the URL's exact scheme://host:port. */\n allowPrivateTarget(id: string, allow: boolean): Promise<ConnectionView> {\n return this.call(\"PUT\", `/api/connections/${enc(id)}/private-target`, { allow });\n }\n /** The kill switch: disabled at once, pending calls cancelled, credentials deleted. */\n kill(id: string): Promise<void> {\n return this.call(\"POST\", `/api/connections/${enc(id)}/kill`, {});\n }\n /** PATCH: a new slug renames the exposed tools (`<slug>__<tool>`); approval rules carry over. */\n update(id: string, patch: { slug?: string; name?: string }): Promise<ConnectionView> {\n return this.call(\"PATCH\", `/api/connections/${enc(id)}`, patch);\n }\n remove(id: string): Promise<void> {\n return this.call(\"DELETE\", `/api/connections/${enc(id)}`);\n }\n async bySlug(slug: string): Promise<ConnectionView> {\n const all = await this.list();\n const found = all.find((c) => c.slug === slug);\n if (!found) throw new Error(`No connection \"${slug}\". ${all.length ? `There are: ${all.map((c) => c.slug).join(\", \")}.` : \"There are none yet.\"}`);\n return found;\n }\n grants(id: string): Promise<ConnectionGrant[]> {\n return this.call(\"GET\", `/api/connections/${enc(id)}/grants`);\n }\n /** The full list: agents not in it lose access. */\n setGrants(id: string, grants: { agentId: string; level: AccessLevel }[]): Promise<ConnectionGrant[]> {\n return this.call(\"PUT\", `/api/connections/${enc(id)}/grants`, { grants });\n }\n agents(): Promise<Agent[]> {\n return this.channel.request(\"GET\", \"/api/agents\");\n }\n\n /** Paste mode: the address the browser landed on after approving (the daemon checks state and issuer). */\n async finish(url: string): Promise<StartConnectResult> {\n try {\n return await this.call<StartConnectResult>(\"POST\", \"/api/connections/finish\", { url });\n } catch (err) {\n // A refused sign-in is a 400 carrying a StartConnectResult, not the usual error envelope.\n const body = err instanceof AdminApiError ? (err.body as StartConnectResult | null) : null;\n if (body?.status === \"error\") return body;\n throw err;\n }\n }\n}\n"],"mappings":";;;;AAqBA,IAAa,iBAAb,cAAoC,MAAM;CACxC,cAAc;EACZ,MACE,0LAEF;EACA,KAAK,OAAO;CACd;AACF;AAEA,MAAM,MAAM;AAEZ,IAAa,iBAAb,MAA4B;CACL;CAArB,YAAY,SAAgC;EAAvB,KAAA,UAAA;CAAwB;CAE7C,MAAc,KAAQ,QAAgB,MAAc,MAA4B;EAC9E,IAAI;GACF,OAAO,MAAM,KAAK,QAAQ,QAAW,QAAQ,MAAM,IAAI;EACzD,SAAS,KAAK;GACZ,IAAI,eAAe,iBAAiB,IAAI,WAAW,OAAO,IAAI,SAAS,eAAe,KAAK,WAAW,kBAAkB,GAAG,MAAM,IAAI,eAAe;GACpJ,MAAM;EACR;CACF;CAEA,OAAkC;EAChC,OAAO,KAAK,KAAK,OAAO,kBAAkB;CAC5C;CACA,UAAiC;EAC/B,OAAO,KAAK,KAAK,OAAO,0BAA0B;CACpD;;CAEA,MAAM,KAAa,cAAiD;EAClE,OAAO,KAAK,KAAK,QAAQ,0BAA0B,eAAe;GAAE;GAAK;EAAa,IAAI,EAAE,IAAI,CAAC;CACnG;CACA,OAAO,OAAuD;EAC5D,OAAO,KAAK,KAAK,QAAQ,oBAAoB,KAAK;CACpD;CACA,IAAI,IAAqC;EACvC,OAAO,KAAK,KAAK,OAAO,oBAAoB,IAAI,EAAE,GAAG;CACvD;;CAEA,QAAQ,IAAY,QAA2B,CAAC,GAAgC;EAC9E,OAAO,KAAK,KAAK,QAAQ,oBAAoB,IAAI,EAAE,EAAE,WAAW,KAAK;CACvE;;CAEA,mBAAmB,IAAY,OAAyC;EACtE,OAAO,KAAK,KAAK,OAAO,oBAAoB,IAAI,EAAE,EAAE,kBAAkB,EAAE,MAAM,CAAC;CACjF;;CAEA,KAAK,IAA2B;EAC9B,OAAO,KAAK,KAAK,QAAQ,oBAAoB,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;CACjE;;CAEA,OAAO,IAAY,OAAkE;EACnF,OAAO,KAAK,KAAK,SAAS,oBAAoB,IAAI,EAAE,KAAK,KAAK;CAChE;CACA,OAAO,IAA2B;EAChC,OAAO,KAAK,KAAK,UAAU,oBAAoB,IAAI,EAAE,GAAG;CAC1D;CACA,MAAM,OAAO,MAAuC;EAClD,MAAM,MAAM,MAAM,KAAK,KAAK;EAC5B,MAAM,QAAQ,IAAI,MAAM,MAAM,EAAE,SAAS,IAAI;EAC7C,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,kBAAkB,KAAK,KAAK,IAAI,SAAS,cAAc,IAAI,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,KAAK,uBAAuB;EACjJ,OAAO;CACT;CACA,OAAO,IAAwC;EAC7C,OAAO,KAAK,KAAK,OAAO,oBAAoB,IAAI,EAAE,EAAE,QAAQ;CAC9D;;CAEA,UAAU,IAAY,QAA+E;EACnG,OAAO,KAAK,KAAK,OAAO,oBAAoB,IAAI,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC;CAC1E;CACA,SAA2B;EACzB,OAAO,KAAK,QAAQ,QAAQ,OAAO,aAAa;CAClD;;CAGA,MAAM,OAAO,KAA0C;EACrD,IAAI;GACF,OAAO,MAAM,KAAK,KAAyB,QAAQ,2BAA2B,EAAE,IAAI,CAAC;EACvF,SAAS,KAAK;GAEZ,MAAM,OAAO,eAAe,gBAAiB,IAAI,OAAqC;GACtF,IAAI,MAAM,WAAW,SAAS,OAAO;GACrC,MAAM;EACR;CACF;AACF"}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as now, r as json } from "./db-BEUEUPyP.js";
2
+ import { i as now, r as json } from "./db-Bp-06SJb.js";
3
3
  //#region server/audit.ts
4
4
  /**
5
5
  * SQL condition for an MCP tool call in audit_log: an agent actor and an action without a "." (tool
@@ -76,4 +76,4 @@ var AuditLog = class {
76
76
  //#endregion
77
77
  export { mcpToolCallSql as n, AuditLog as t };
78
78
 
79
- //# sourceMappingURL=audit-BVYknW2i.js.map
79
+ //# sourceMappingURL=audit-CxI6Rh_6.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"audit-BVYknW2i.js","names":[],"sources":["../server/audit.ts"],"sourcesContent":["import type { ActorType, AuditEntry, AuditOutcome, Page } from \"../shared/types.ts\";\nimport type { Bus } from \"./bus.ts\";\nimport { type Db, json, now } from \"./db/index.ts\";\n\nexport interface AuditInput {\n actorType: ActorType;\n actorId?: string | null;\n /** MCP tool name or console action, e.g. \"send_message\", \"agent.create\", \"account.connect\". */\n action: string;\n accountId?: string | null;\n target?: string | null;\n outcome: AuditOutcome;\n /** Required for denials: the guardrail that blocked it (\"allow_to\", \"label_scope\", \"rate_limit\", …). */\n reason?: string | null;\n durationMs?: number | null;\n /** Small, non-sensitive context. Never message bodies, keys or tokens. */\n detail?: Record<string, unknown> | null;\n}\n\n/**\n * SQL condition for an MCP tool call in audit_log: an agent actor and an action without a \".\" (tool\n * names are like \"send_message\"; \"pairing.key_delivered\" and console actions have a dot). The same\n * rule as isMcpToolAction in shared/types.ts (used by the console). `t` is the table alias, if any.\n *\n * Every outcome counts, including calls rejected before a tool ran (unknown tool, invalid arguments,\n * a malformed tools/call): those are recorded with outcome \"error\" (see rejectedCall in\n * server/mcp/server.ts), so the Overview's \"Tool calls\" counts them. Anything that needs proof that\n * an agent works (anyAgentVerified, the wizard's checks) adds `outcome = 'ok'`.\n */\nexport function mcpToolCallSql(t?: string): string {\n const c = t ? `${t}.` : \"\";\n return `${c}actor_type = 'agent' AND instr(${c}action, '.') = 0`;\n}\n\nexport interface AuditQuery {\n agentId?: string;\n accountId?: string;\n outcome?: AuditOutcome;\n action?: string;\n /** Opaque cursor from a previous page. */\n cursor?: string | null;\n limit?: number;\n}\n\ninterface AuditRow {\n id: number;\n ts: string;\n actor_type: ActorType;\n actor_id: string | null;\n actor_name: string | null;\n action: string;\n account_id: string | null;\n target: string | null;\n outcome: AuditOutcome;\n reason: string | null;\n duration_ms: number | null;\n detail: string | null;\n}\n\nconst SELECT = `SELECT a.*, CASE a.actor_type WHEN 'agent' THEN ag.name WHEN 'admin' THEN 'admin' ELSE NULL END AS actor_name\n FROM audit_log a LEFT JOIN agents ag ON a.actor_type = 'agent' AND ag.id = a.actor_id`;\n\nconst toEntry = (r: AuditRow): AuditEntry => ({\n id: r.id,\n ts: r.ts,\n actorType: r.actor_type,\n actorId: r.actor_id,\n actorName: r.actor_name,\n action: r.action,\n accountId: r.account_id,\n target: r.target,\n outcome: r.outcome,\n reason: r.reason,\n durationMs: r.duration_ms,\n detail: json.parse<Record<string, unknown> | null>(r.detail, null),\n});\n\n/** Append-only audit log. Every agent tool call and every admin mutation is recorded. */\nexport class AuditLog {\n constructor(\n private readonly db: Db,\n private readonly bus: Bus,\n ) {}\n\n record(input: AuditInput): AuditEntry {\n const { lastInsertRowid } = this.db.run(\n `INSERT INTO audit_log (ts, actor_type, actor_id, action, account_id, target, outcome, reason, duration_ms, detail)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n now(),\n input.actorType,\n input.actorId ?? null,\n input.action,\n input.accountId ?? null,\n input.target ?? null,\n input.outcome,\n input.reason ?? null,\n input.durationMs ?? null,\n input.detail ? json.str(input.detail) : null,\n );\n const entry = toEntry(this.db.get<AuditRow>(`${SELECT} WHERE a.id = ?`, lastInsertRowid)!);\n this.bus.emit(\"audit\", entry);\n return entry;\n }\n\n /** Newest first. */\n list(q: AuditQuery = {}): Page<AuditEntry> {\n const where: string[] = [];\n const params: (string | number)[] = [];\n const add = (clause: string, value: string | number) => {\n where.push(clause);\n params.push(value);\n };\n if (q.agentId) add(\"a.actor_id = ?\", q.agentId);\n if (q.accountId) add(\"a.account_id = ?\", q.accountId);\n if (q.outcome) add(\"a.outcome = ?\", q.outcome);\n if (q.action) add(\"a.action = ?\", q.action);\n const before = Number(q.cursor);\n if (q.cursor && Number.isInteger(before)) add(\"a.id < ?\", before);\n const limit = Math.min(Math.max(q.limit ?? 50, 1), 200);\n const rows = this.db.all<AuditRow>(\n `${SELECT} ${where.length ? \"WHERE \" + where.join(\" AND \") : \"\"} ORDER BY a.id DESC LIMIT ?`,\n ...params,\n limit + 1,\n );\n const items = rows.slice(0, limit).map(toEntry);\n return { items, nextCursor: rows.length > limit ? String(items.at(-1)!.id) : null };\n }\n\n /** Deletes entries older than `beforeIso`. Returns the number removed. */\n prune(beforeIso: string): number {\n return this.db.run(\"DELETE FROM audit_log WHERE ts < ?\", beforeIso).changes;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAgB,eAAe,GAAoB;CACjD,MAAM,IAAI,IAAI,GAAG,EAAE,KAAK;CACxB,OAAO,GAAG,EAAE,iCAAiC,EAAE;AACjD;AA2BA,MAAM,SAAS;;AAGf,MAAM,WAAW,OAA6B;CAC5C,IAAI,EAAE;CACN,IAAI,EAAE;CACN,WAAW,EAAE;CACb,SAAS,EAAE;CACX,WAAW,EAAE;CACb,QAAQ,EAAE;CACV,WAAW,EAAE;CACb,QAAQ,EAAE;CACV,SAAS,EAAE;CACX,QAAQ,EAAE;CACV,YAAY,EAAE;CACd,QAAQ,KAAK,MAAsC,EAAE,QAAQ,IAAI;AACnE;;AAGA,IAAa,WAAb,MAAsB;CAED;CACA;CAFnB,YACE,IACA,KACA;EAFiB,KAAA,KAAA;EACA,KAAA,MAAA;CAChB;CAEH,OAAO,OAA+B;EACpC,MAAM,EAAE,oBAAoB,KAAK,GAAG,IAClC;+CAEA,IAAI,GACJ,MAAM,WACN,MAAM,WAAW,MACjB,MAAM,QACN,MAAM,aAAa,MACnB,MAAM,UAAU,MAChB,MAAM,SACN,MAAM,UAAU,MAChB,MAAM,cAAc,MACpB,MAAM,SAAS,KAAK,IAAI,MAAM,MAAM,IAAI,IAC1C;EACA,MAAM,QAAQ,QAAQ,KAAK,GAAG,IAAc,GAAG,OAAO,kBAAkB,eAAe,CAAE;EACzF,KAAK,IAAI,KAAK,SAAS,KAAK;EAC5B,OAAO;CACT;;CAGA,KAAK,IAAgB,CAAC,GAAqB;EACzC,MAAM,QAAkB,CAAC;EACzB,MAAM,SAA8B,CAAC;EACrC,MAAM,OAAO,QAAgB,UAA2B;GACtD,MAAM,KAAK,MAAM;GACjB,OAAO,KAAK,KAAK;EACnB;EACA,IAAI,EAAE,SAAS,IAAI,kBAAkB,EAAE,OAAO;EAC9C,IAAI,EAAE,WAAW,IAAI,oBAAoB,EAAE,SAAS;EACpD,IAAI,EAAE,SAAS,IAAI,iBAAiB,EAAE,OAAO;EAC7C,IAAI,EAAE,QAAQ,IAAI,gBAAgB,EAAE,MAAM;EAC1C,MAAM,SAAS,OAAO,EAAE,MAAM;EAC9B,IAAI,EAAE,UAAU,OAAO,UAAU,MAAM,GAAG,IAAI,YAAY,MAAM;EAChE,MAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,EAAE,SAAS,IAAI,CAAC,GAAG,GAAG;EACtD,MAAM,OAAO,KAAK,GAAG,IACnB,GAAG,OAAO,GAAG,MAAM,SAAS,WAAW,MAAM,KAAK,OAAO,IAAI,GAAG,8BAChE,GAAG,QACH,QAAQ,CACV;EACA,MAAM,QAAQ,KAAK,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,OAAO;EAC9C,OAAO;GAAE;GAAO,YAAY,KAAK,SAAS,QAAQ,OAAO,MAAM,GAAG,EAAE,CAAC,CAAE,EAAE,IAAI;EAAK;CACpF;;CAGA,MAAM,WAA2B;EAC/B,OAAO,KAAK,GAAG,IAAI,sCAAsC,SAAS,CAAC,CAAC;CACtE;AACF"}
1
+ {"version":3,"file":"audit-CxI6Rh_6.js","names":[],"sources":["../server/audit.ts"],"sourcesContent":["import type { ActorType, AuditEntry, AuditOutcome, Page } from \"../shared/types.ts\";\nimport type { Bus } from \"./bus.ts\";\nimport { type Db, json, now } from \"./db/index.ts\";\n\nexport interface AuditInput {\n actorType: ActorType;\n actorId?: string | null;\n /** MCP tool name or console action, e.g. \"send_message\", \"agent.create\", \"account.connect\". */\n action: string;\n accountId?: string | null;\n target?: string | null;\n outcome: AuditOutcome;\n /** Required for denials: the guardrail that blocked it (\"allow_to\", \"label_scope\", \"rate_limit\", …). */\n reason?: string | null;\n durationMs?: number | null;\n /** Small, non-sensitive context. Never message bodies, keys or tokens. */\n detail?: Record<string, unknown> | null;\n}\n\n/**\n * SQL condition for an MCP tool call in audit_log: an agent actor and an action without a \".\" (tool\n * names are like \"send_message\"; \"pairing.key_delivered\" and console actions have a dot). The same\n * rule as isMcpToolAction in shared/types.ts (used by the console). `t` is the table alias, if any.\n *\n * Every outcome counts, including calls rejected before a tool ran (unknown tool, invalid arguments,\n * a malformed tools/call): those are recorded with outcome \"error\" (see rejectedCall in\n * server/mcp/server.ts), so the Overview's \"Tool calls\" counts them. Anything that needs proof that\n * an agent works (anyAgentVerified, the wizard's checks) adds `outcome = 'ok'`.\n */\nexport function mcpToolCallSql(t?: string): string {\n const c = t ? `${t}.` : \"\";\n return `${c}actor_type = 'agent' AND instr(${c}action, '.') = 0`;\n}\n\nexport interface AuditQuery {\n agentId?: string;\n accountId?: string;\n outcome?: AuditOutcome;\n action?: string;\n /** Opaque cursor from a previous page. */\n cursor?: string | null;\n limit?: number;\n}\n\ninterface AuditRow {\n id: number;\n ts: string;\n actor_type: ActorType;\n actor_id: string | null;\n actor_name: string | null;\n action: string;\n account_id: string | null;\n target: string | null;\n outcome: AuditOutcome;\n reason: string | null;\n duration_ms: number | null;\n detail: string | null;\n}\n\nconst SELECT = `SELECT a.*, CASE a.actor_type WHEN 'agent' THEN ag.name WHEN 'admin' THEN 'admin' ELSE NULL END AS actor_name\n FROM audit_log a LEFT JOIN agents ag ON a.actor_type = 'agent' AND ag.id = a.actor_id`;\n\nconst toEntry = (r: AuditRow): AuditEntry => ({\n id: r.id,\n ts: r.ts,\n actorType: r.actor_type,\n actorId: r.actor_id,\n actorName: r.actor_name,\n action: r.action,\n accountId: r.account_id,\n target: r.target,\n outcome: r.outcome,\n reason: r.reason,\n durationMs: r.duration_ms,\n detail: json.parse<Record<string, unknown> | null>(r.detail, null),\n});\n\n/** Append-only audit log. Every agent tool call and every admin mutation is recorded. */\nexport class AuditLog {\n constructor(\n private readonly db: Db,\n private readonly bus: Bus,\n ) {}\n\n record(input: AuditInput): AuditEntry {\n const { lastInsertRowid } = this.db.run(\n `INSERT INTO audit_log (ts, actor_type, actor_id, action, account_id, target, outcome, reason, duration_ms, detail)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n now(),\n input.actorType,\n input.actorId ?? null,\n input.action,\n input.accountId ?? null,\n input.target ?? null,\n input.outcome,\n input.reason ?? null,\n input.durationMs ?? null,\n input.detail ? json.str(input.detail) : null,\n );\n const entry = toEntry(this.db.get<AuditRow>(`${SELECT} WHERE a.id = ?`, lastInsertRowid)!);\n this.bus.emit(\"audit\", entry);\n return entry;\n }\n\n /** Newest first. */\n list(q: AuditQuery = {}): Page<AuditEntry> {\n const where: string[] = [];\n const params: (string | number)[] = [];\n const add = (clause: string, value: string | number) => {\n where.push(clause);\n params.push(value);\n };\n if (q.agentId) add(\"a.actor_id = ?\", q.agentId);\n if (q.accountId) add(\"a.account_id = ?\", q.accountId);\n if (q.outcome) add(\"a.outcome = ?\", q.outcome);\n if (q.action) add(\"a.action = ?\", q.action);\n const before = Number(q.cursor);\n if (q.cursor && Number.isInteger(before)) add(\"a.id < ?\", before);\n const limit = Math.min(Math.max(q.limit ?? 50, 1), 200);\n const rows = this.db.all<AuditRow>(\n `${SELECT} ${where.length ? \"WHERE \" + where.join(\" AND \") : \"\"} ORDER BY a.id DESC LIMIT ?`,\n ...params,\n limit + 1,\n );\n const items = rows.slice(0, limit).map(toEntry);\n return { items, nextCursor: rows.length > limit ? String(items.at(-1)!.id) : null };\n }\n\n /** Deletes entries older than `beforeIso`. Returns the number removed. */\n prune(beforeIso: string): number {\n return this.db.run(\"DELETE FROM audit_log WHERE ts < ?\", beforeIso).changes;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAgB,eAAe,GAAoB;CACjD,MAAM,IAAI,IAAI,GAAG,EAAE,KAAK;CACxB,OAAO,GAAG,EAAE,iCAAiC,EAAE;AACjD;AA2BA,MAAM,SAAS;;AAGf,MAAM,WAAW,OAA6B;CAC5C,IAAI,EAAE;CACN,IAAI,EAAE;CACN,WAAW,EAAE;CACb,SAAS,EAAE;CACX,WAAW,EAAE;CACb,QAAQ,EAAE;CACV,WAAW,EAAE;CACb,QAAQ,EAAE;CACV,SAAS,EAAE;CACX,QAAQ,EAAE;CACV,YAAY,EAAE;CACd,QAAQ,KAAK,MAAsC,EAAE,QAAQ,IAAI;AACnE;;AAGA,IAAa,WAAb,MAAsB;CAED;CACA;CAFnB,YACE,IACA,KACA;EAFiB,KAAA,KAAA;EACA,KAAA,MAAA;CAChB;CAEH,OAAO,OAA+B;EACpC,MAAM,EAAE,oBAAoB,KAAK,GAAG,IAClC;+CAEA,IAAI,GACJ,MAAM,WACN,MAAM,WAAW,MACjB,MAAM,QACN,MAAM,aAAa,MACnB,MAAM,UAAU,MAChB,MAAM,SACN,MAAM,UAAU,MAChB,MAAM,cAAc,MACpB,MAAM,SAAS,KAAK,IAAI,MAAM,MAAM,IAAI,IAC1C;EACA,MAAM,QAAQ,QAAQ,KAAK,GAAG,IAAc,GAAG,OAAO,kBAAkB,eAAe,CAAE;EACzF,KAAK,IAAI,KAAK,SAAS,KAAK;EAC5B,OAAO;CACT;;CAGA,KAAK,IAAgB,CAAC,GAAqB;EACzC,MAAM,QAAkB,CAAC;EACzB,MAAM,SAA8B,CAAC;EACrC,MAAM,OAAO,QAAgB,UAA2B;GACtD,MAAM,KAAK,MAAM;GACjB,OAAO,KAAK,KAAK;EACnB;EACA,IAAI,EAAE,SAAS,IAAI,kBAAkB,EAAE,OAAO;EAC9C,IAAI,EAAE,WAAW,IAAI,oBAAoB,EAAE,SAAS;EACpD,IAAI,EAAE,SAAS,IAAI,iBAAiB,EAAE,OAAO;EAC7C,IAAI,EAAE,QAAQ,IAAI,gBAAgB,EAAE,MAAM;EAC1C,MAAM,SAAS,OAAO,EAAE,MAAM;EAC9B,IAAI,EAAE,UAAU,OAAO,UAAU,MAAM,GAAG,IAAI,YAAY,MAAM;EAChE,MAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,EAAE,SAAS,IAAI,CAAC,GAAG,GAAG;EACtD,MAAM,OAAO,KAAK,GAAG,IACnB,GAAG,OAAO,GAAG,MAAM,SAAS,WAAW,MAAM,KAAK,OAAO,IAAI,GAAG,8BAChE,GAAG,QACH,QAAQ,CACV;EACA,MAAM,QAAQ,KAAK,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,OAAO;EAC9C,OAAO;GAAE;GAAO,YAAY,KAAK,SAAS,QAAQ,OAAO,MAAM,GAAG,EAAE,CAAC,CAAE,EAAE,IAAI;EAAK;CACpF;;CAGA,MAAM,WAA2B;EAC/B,OAAO,KAAK,GAAG,IAAI,sCAAsC,SAAS,CAAC,CAAC;CACtE;AACF"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { t as AuditLog } from "./audit-CxI6Rh_6.js";
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env node
2
+ import { t as LEGACY_PRODUCT } from "./brand-Bg9VPAym.js";
3
+ import { s as resolveDataDir } from "./config-cEBzNniL.js";
4
+ import { t as AdminApiError } from "./admin-vJum87dw.js";
5
+ import { t as NPX } from "./version-BxH7OuWD.js";
6
+ import { i as safeText } from "./ui-B8Sd4Y7Y.js";
7
+ import { o as parseSource } from "./sources-DO6MmQHi.js";
8
+ import pc from "picocolors";
9
+ //#region server/extensions/cli.ts
10
+ const enc = encodeURIComponent;
11
+ /** The admin extensions API as the CLI uses it. */
12
+ var ExtensionsApi = class {
13
+ channel;
14
+ constructor(channel) {
15
+ this.channel = channel;
16
+ }
17
+ list() {
18
+ return this.channel.request("GET", "/api/extensions");
19
+ }
20
+ resolve(source) {
21
+ return this.channel.request("POST", "/api/extensions/resolve", { source });
22
+ }
23
+ install(source, confirmThirdParty, expectIntegrity) {
24
+ return this.channel.request("POST", "/api/extensions", {
25
+ source,
26
+ confirmThirdParty,
27
+ expectIntegrity
28
+ });
29
+ }
30
+ update(name, body) {
31
+ return this.channel.request("POST", `/api/extensions/${enc(name)}/update`, body);
32
+ }
33
+ remove(name, force) {
34
+ return this.channel.request("DELETE", `/api/extensions/${enc(name)}${force ? "?force=true" : ""}`);
35
+ }
36
+ };
37
+ const shortCommit = (c) => c ? c.slice(0, 12) : "(directory)";
38
+ const origin = (e) => e.official ? pc.green("official") : pc.yellow("third-party");
39
+ function describe(p, e) {
40
+ p.print(`${pc.bold(safeText(e.displayName, 60))} ${pc.dim(`(${e.name})`)} ${safeText(e.version, 40)}, kind ${e.kind}, ${origin(e)}`);
41
+ p.print(` source ${safeText(e.source, 200)}`);
42
+ p.print(` commit ${shortCommit(e.commit)}`);
43
+ p.print(` integrity ${e.integrity}`);
44
+ if (e.kind === "code") p.print(` network ${e.network.length ? e.network.map((h) => safeText(h, 100)).join(", ") : "none declared"}`);
45
+ }
46
+ function printWarnings(p, warnings) {
47
+ if (!warnings.length) return;
48
+ p.print("");
49
+ for (const w of warnings) p.print(pc.yellow(`! ${safeText(w, 600)}`));
50
+ p.print("");
51
+ }
52
+ /** An API error body (a 409 that carries usedBy / warnings), or null. */
53
+ const errorBody = (err) => err instanceof AdminApiError ? err.body ?? null : null;
54
+ async function addExtension(input, deps) {
55
+ const { api, prompter: p } = deps;
56
+ const spec = parseSource(input, { cwd: deps.cwd ?? process.cwd() });
57
+ const source = spec.type === "local" ? spec.path : input.trim();
58
+ p.print(`Fetching ${pc.cyan(safeText(source, 200))}…`);
59
+ const { candidate, warnings, installed } = await api.resolve(source);
60
+ describe(p, candidate);
61
+ if (installed) {
62
+ if (installed.integrity === candidate.integrity) {
63
+ p.print(`${pc.green("✔")} ${candidate.name} ${installed.version} is already installed.`);
64
+ return 0;
65
+ }
66
+ p.print(pc.yellow(`${candidate.name} is already installed (${installed.version}, ${shortCommit(installed.commit)}). To change it: ${NPX} ext update ${candidate.name}`));
67
+ return 1;
68
+ }
69
+ if (!candidate.official) {
70
+ printWarnings(p, warnings);
71
+ if (!await p.confirm(`Install the third-party extension ${candidate.name}?`, false)) {
72
+ p.print("Nothing was installed.");
73
+ return 0;
74
+ }
75
+ }
76
+ const ext = await api.install(source, !candidate.official, candidate.integrity);
77
+ p.print(`${pc.green("✔")} Installed ${ext.name} ${ext.version}${ext.commit ? ` (${shortCommit(ext.commit)})` : ""}.`);
78
+ return 0;
79
+ }
80
+ async function listExtensions(deps) {
81
+ const { api, prompter: p } = deps;
82
+ const all = await api.list();
83
+ if (!all.length) {
84
+ p.print(`No extensions installed. Add one: ${NPX} ext add <name|github-url|git-url|./dir>`);
85
+ return 0;
86
+ }
87
+ const w = Math.max(4, ...all.map((e) => e.name.length));
88
+ const v = Math.max(7, ...all.map((e) => safeText(e.version, 20).length));
89
+ p.print(pc.dim(`${"NAME".padEnd(w)} ${"VERSION".padEnd(v)} ${"COMMIT".padEnd(12)} ${"ORIGIN".padEnd(11)} ${"USED BY".padEnd(16)} SOURCE`));
90
+ for (const e of all) {
91
+ const used = e.usedBy.length ? safeText(e.usedBy.join(", "), 40) : "-";
92
+ p.print(`${e.name.padEnd(w)} ${safeText(e.version, 20).padEnd(v)} ${shortCommit(e.commit).padEnd(12)} ${(e.official ? "official" : "third-party").padEnd(11)} ${used.padEnd(16)} ${safeText(e.source, 120)}`);
93
+ }
94
+ return 0;
95
+ }
96
+ async function updateExtension(name, deps) {
97
+ const { api, prompter: p } = deps;
98
+ p.print(`Checking ${pc.cyan(name)} for an update…`);
99
+ const preview = await api.update(name, { confirm: false });
100
+ if (!preview.update) {
101
+ p.print(`${pc.green("✔")} ${name} is up to date.`);
102
+ return 0;
103
+ }
104
+ const { from, to, warnings } = preview.update;
105
+ const row = (label, a, b) => p.print(` ${label.padEnd(10)} ${a} → ${a === b ? pc.dim(b) : pc.bold(b)}`);
106
+ p.print(`Update ${pc.bold(name)}:`);
107
+ row("version", safeText(from.version, 40), safeText(to.version, 40));
108
+ row("commit", shortCommit(from.commit), shortCommit(to.commit));
109
+ row("integrity", from.integrity, to.integrity);
110
+ if (from.kind !== to.kind) row("kind", from.kind, to.kind);
111
+ const hosts = (e) => e.network.length ? e.network.map((h) => safeText(h, 100)).join(", ") : "none";
112
+ if (to.kind === "code" && hosts(from) !== hosts(to)) row("network", hosts(from), hosts(to));
113
+ printWarnings(p, warnings);
114
+ if (from.usedBy.length) p.print(pc.dim(`Used by ${from.usedBy.join(", ")}: they switch to the new version.`));
115
+ if (!await p.confirm(`Install ${name} ${safeText(to.version, 40)}?`, false)) {
116
+ p.print("Nothing was changed.");
117
+ return 0;
118
+ }
119
+ const done = await api.update(name, {
120
+ confirm: true,
121
+ expectIntegrity: to.integrity
122
+ });
123
+ if (!done.update) {
124
+ p.print(`${pc.green("✔")} ${name} is up to date.`);
125
+ return 0;
126
+ }
127
+ p.print(`${pc.green("✔")} Updated ${name} to ${safeText(done.update.to.version, 40)}${done.update.to.commit ? ` (${shortCommit(done.update.to.commit)})` : ""}.`);
128
+ return 0;
129
+ }
130
+ async function removeExtension(name, flags, deps) {
131
+ const { api, prompter: p } = deps;
132
+ const ext = (await api.list()).find((e) => e.name === name);
133
+ if (!ext) throw new Error(`${name} isn't installed.`);
134
+ if (ext.usedBy.length && !flags.force) {
135
+ p.print(pc.yellow(`${name} is used by ${ext.usedBy.join(", ")}.`));
136
+ p.print(`Remove those connections first (${NPX} connections remove <name>), or pass --force, which disables them (their credentials stay until you remove them).`);
137
+ return 1;
138
+ }
139
+ const what = ext.usedBy.length ? ` and disable ${ext.usedBy.join(", ")}` : "";
140
+ if (!await p.confirm(`Remove the extension ${name}${what}?`, false)) return 0;
141
+ try {
142
+ await api.remove(name, !!flags.force);
143
+ } catch (err) {
144
+ const body = errorBody(err);
145
+ if (body?.error.code === "in_use") {
146
+ p.print(pc.yellow(`${name} is used by ${(body.usedBy ?? []).join(", ")}. Pass --force to disable them and remove it.`));
147
+ return 1;
148
+ }
149
+ throw err;
150
+ }
151
+ p.print(`${pc.green("✔")} Removed ${name}.`);
152
+ return 0;
153
+ }
154
+ const GROUP = "MCP connections (on xaq's machine):";
155
+ function registerExtensionCommands(program) {
156
+ const dataOption = (cmd) => cmd.option("-d, --data <dir>", `xaq's data directory (default ~/.xaq, or an existing ~/.${LEGACY_PRODUCT}; env XAQ_DATA)`);
157
+ const withApi = async (o, run) => {
158
+ const { CLEANUP_SIGNALS, openAdminChannel } = await import("./admin-Buw2v1X4.js");
159
+ const { TerminalPrompter } = await import("./prompt-CKMQx_MJ.js");
160
+ const channel = openAdminChannel({ dataDir: resolveDataDir(o.data, { quiet: true }) });
161
+ const handlers = CLEANUP_SIGNALS.map((sig) => {
162
+ const h = () => {
163
+ channel.close();
164
+ process.exit(sig === "SIGINT" ? 130 : 128 + (sig === "SIGTERM" ? 15 : 1));
165
+ };
166
+ process.once(sig, h);
167
+ return [sig, h];
168
+ });
169
+ try {
170
+ process.exitCode = await run({
171
+ api: new ExtensionsApi(channel),
172
+ prompter: new TerminalPrompter(!!o.yes)
173
+ });
174
+ } finally {
175
+ for (const [sig, h] of handlers) process.removeListener(sig, h);
176
+ channel.close();
177
+ }
178
+ };
179
+ const ext = program.command("ext").description("extensions: the integrations xaq can connect to (Notion, WHOOP, T3 Code, …), from the official registry or elsewhere").summary("add, list, update or remove extensions").helpGroup(GROUP);
180
+ dataOption(ext.command("add").description("install an extension: a registry name, a GitHub URL, another git URL (#ref, #ref:path), or a ./directory").argument("<name|url|path>", "e.g. notion, https://github.com/you/repo/tree/main/ext, ./my-extension")).option("-y, --yes", "install a third-party extension without asking (the warning is still printed)").addHelpText("after", `
181
+ Anything that isn't the official registry (wynn-dev/xaq-extensions) is third-party: you see its kind, its declared
182
+ network hosts and its source first, and confirm. A code extension runs on this machine with network access.
183
+
184
+ Examples:
185
+ ${NPX} ext add notion
186
+ ${NPX} ext add https://github.com/you/xaq-ext/tree/main/extensions/thing
187
+ ${NPX} ext add https://git.example.com/you/ext.git#v1.2.0
188
+ ${NPX} ext add ./my-extension`).action((source, o) => withApi(o, (deps) => addExtension(source, deps)));
189
+ dataOption(ext.command("list").description("list installed extensions: version, commit, official or third-party, and the connections using each")).action((o) => withApi(o, (deps) => listExtensions(deps)));
190
+ dataOption(ext.command("update").description("show what an update changes (version, commit, integrity) and install it on confirmation").argument("<name>", "the extension's name")).option("-y, --yes", "install the update without asking").action((name, o) => withApi(o, (deps) => updateExtension(name, deps)));
191
+ dataOption(ext.command("remove").description("remove an extension (refused while connections use it, unless --force)").argument("<name>", "the extension's name")).option("--force", "also when connections use it: they're disabled (credentials stay until you remove them)").option("-y, --yes", "don't ask").action((name, o) => withApi(o, (deps) => removeExtension(name, o, deps)));
192
+ }
193
+ //#endregion
194
+ export { registerExtensionCommands };
195
+
196
+ //# sourceMappingURL=cli-Bfiv6zge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-Bfiv6zge.js","names":[],"sources":["../server/extensions/cli.ts"],"sourcesContent":["// `xaq ext add | list | update | remove`: run on xaq's machine, through the daemon's /api/extensions over the local\n// admin channel (server/connections-cli/admin.ts), so the daemon's own checks (and B2's usedBy) apply.\n\nimport type { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport type { ExtensionErrorBody, ExtensionResolveResult, ExtensionUpdateResult, ExtensionView } from \"../../shared/extensions.ts\";\nimport { LEGACY_PRODUCT } from \"../brand.ts\";\nimport { resolveDataDir } from \"../config.ts\";\nimport { AdminApiError, type AdminChannel } from \"../connections-cli/admin.ts\";\nimport type { Prompter } from \"../connections-cli/prompt.ts\";\nimport { safeText } from \"../listen/ui.ts\";\nimport { NPX } from \"../listen/version.ts\";\nimport { parseSource } from \"./sources.ts\";\n\nconst enc = encodeURIComponent;\n\n/** The admin extensions API as the CLI uses it. */\nexport class ExtensionsApi {\n constructor(readonly channel: Pick<AdminChannel, \"request\">) {}\n list(): Promise<ExtensionView[]> {\n return this.channel.request(\"GET\", \"/api/extensions\");\n }\n resolve(source: string): Promise<ExtensionResolveResult> {\n return this.channel.request(\"POST\", \"/api/extensions/resolve\", { source });\n }\n install(source: string, confirmThirdParty: boolean, expectIntegrity: string): Promise<ExtensionView> {\n return this.channel.request(\"POST\", \"/api/extensions\", { source, confirmThirdParty, expectIntegrity });\n }\n update(name: string, body: { confirm: boolean; expectIntegrity?: string }): Promise<ExtensionUpdateResult> {\n return this.channel.request(\"POST\", `/api/extensions/${enc(name)}/update`, body);\n }\n remove(name: string, force: boolean): Promise<void> {\n return this.channel.request(\"DELETE\", `/api/extensions/${enc(name)}${force ? \"?force=true\" : \"\"}`);\n }\n}\n\nexport interface ExtDeps {\n api: ExtensionsApi;\n prompter: Prompter;\n /** Where a relative directory is resolved (default: this process's cwd). */\n cwd?: string;\n}\n\nconst shortCommit = (c: string | null) => (c ? c.slice(0, 12) : \"(directory)\");\nconst origin = (e: Pick<ExtensionView, \"official\">) => (e.official ? pc.green(\"official\") : pc.yellow(\"third-party\"));\n\nfunction describe(p: Prompter, e: ExtensionView): void {\n p.print(`${pc.bold(safeText(e.displayName, 60))} ${pc.dim(`(${e.name})`)} ${safeText(e.version, 40)}, kind ${e.kind}, ${origin(e)}`);\n p.print(` source ${safeText(e.source, 200)}`);\n p.print(` commit ${shortCommit(e.commit)}`);\n p.print(` integrity ${e.integrity}`);\n if (e.kind === \"code\") p.print(` network ${e.network.length ? e.network.map((h) => safeText(h, 100)).join(\", \") : \"none declared\"}`);\n}\n\nfunction printWarnings(p: Prompter, warnings: string[]): void {\n if (!warnings.length) return;\n p.print(\"\");\n for (const w of warnings) p.print(pc.yellow(`! ${safeText(w, 600)}`));\n p.print(\"\");\n}\n\n/** An API error body (a 409 that carries usedBy / warnings), or null. */\nconst errorBody = (err: unknown): ExtensionErrorBody | null => (err instanceof AdminApiError ? ((err.body as ExtensionErrorBody | null) ?? null) : null);\n\nexport async function addExtension(input: string, deps: ExtDeps): Promise<number> {\n const { api, prompter: p } = deps;\n // A directory is resolved here, against this terminal's cwd (and ~), not the daemon's: it gets the absolute path.\n const spec = parseSource(input, { cwd: deps.cwd ?? process.cwd() });\n const source = spec.type === \"local\" ? spec.path : input.trim();\n p.print(`Fetching ${pc.cyan(safeText(source, 200))}…`);\n const { candidate, warnings, installed } = await api.resolve(source);\n describe(p, candidate);\n if (installed) {\n if (installed.integrity === candidate.integrity) {\n p.print(`${pc.green(\"✔\")} ${candidate.name} ${installed.version} is already installed.`);\n return 0;\n }\n p.print(pc.yellow(`${candidate.name} is already installed (${installed.version}, ${shortCommit(installed.commit)}). To change it: ${NPX} ext update ${candidate.name}`));\n return 1;\n }\n if (!candidate.official) {\n printWarnings(p, warnings);\n if (!(await p.confirm(`Install the third-party extension ${candidate.name}?`, false))) {\n p.print(\"Nothing was installed.\");\n return 0;\n }\n }\n const ext = await api.install(source, !candidate.official, candidate.integrity);\n p.print(`${pc.green(\"✔\")} Installed ${ext.name} ${ext.version}${ext.commit ? ` (${shortCommit(ext.commit)})` : \"\"}.`);\n return 0;\n}\n\nexport async function listExtensions(deps: ExtDeps): Promise<number> {\n const { api, prompter: p } = deps;\n const all = await api.list();\n if (!all.length) {\n p.print(`No extensions installed. Add one: ${NPX} ext add <name|github-url|git-url|./dir>`);\n return 0;\n }\n const w = Math.max(4, ...all.map((e) => e.name.length));\n const v = Math.max(7, ...all.map((e) => safeText(e.version, 20).length));\n p.print(pc.dim(`${\"NAME\".padEnd(w)} ${\"VERSION\".padEnd(v)} ${\"COMMIT\".padEnd(12)} ${\"ORIGIN\".padEnd(11)} ${\"USED BY\".padEnd(16)} SOURCE`));\n for (const e of all) {\n const used = e.usedBy.length ? safeText(e.usedBy.join(\", \"), 40) : \"-\";\n p.print(\n `${e.name.padEnd(w)} ${safeText(e.version, 20).padEnd(v)} ${shortCommit(e.commit).padEnd(12)} ${(e.official ? \"official\" : \"third-party\").padEnd(11)} ${used.padEnd(16)} ${safeText(e.source, 120)}`,\n );\n }\n return 0;\n}\n\nexport async function updateExtension(name: string, deps: ExtDeps): Promise<number> {\n const { api, prompter: p } = deps;\n p.print(`Checking ${pc.cyan(name)} for an update…`);\n const preview = await api.update(name, { confirm: false });\n if (!preview.update) {\n p.print(`${pc.green(\"✔\")} ${name} is up to date.`);\n return 0;\n }\n const { from, to, warnings } = preview.update;\n const row = (label: string, a: string, b: string) => p.print(` ${label.padEnd(10)} ${a} → ${a === b ? pc.dim(b) : pc.bold(b)}`);\n p.print(`Update ${pc.bold(name)}:`);\n row(\"version\", safeText(from.version, 40), safeText(to.version, 40));\n row(\"commit\", shortCommit(from.commit), shortCommit(to.commit));\n row(\"integrity\", from.integrity, to.integrity);\n if (from.kind !== to.kind) row(\"kind\", from.kind, to.kind);\n const hosts = (e: ExtensionView) => (e.network.length ? e.network.map((h) => safeText(h, 100)).join(\", \") : \"none\");\n if (to.kind === \"code\" && hosts(from) !== hosts(to)) row(\"network\", hosts(from), hosts(to));\n printWarnings(p, warnings);\n if (from.usedBy.length) p.print(pc.dim(`Used by ${from.usedBy.join(\", \")}: they switch to the new version.`));\n if (!(await p.confirm(`Install ${name} ${safeText(to.version, 40)}?`, false))) {\n p.print(\"Nothing was changed.\");\n return 0;\n }\n const done = await api.update(name, { confirm: true, expectIntegrity: to.integrity });\n if (!done.update) {\n p.print(`${pc.green(\"✔\")} ${name} is up to date.`);\n return 0;\n }\n p.print(`${pc.green(\"✔\")} Updated ${name} to ${safeText(done.update.to.version, 40)}${done.update.to.commit ? ` (${shortCommit(done.update.to.commit)})` : \"\"}.`);\n return 0;\n}\n\nexport async function removeExtension(name: string, flags: { force?: boolean }, deps: ExtDeps): Promise<number> {\n const { api, prompter: p } = deps;\n const ext = (await api.list()).find((e) => e.name === name);\n if (!ext) throw new Error(`${name} isn't installed.`);\n if (ext.usedBy.length && !flags.force) {\n p.print(pc.yellow(`${name} is used by ${ext.usedBy.join(\", \")}.`));\n p.print(`Remove those connections first (${NPX} connections remove <name>), or pass --force, which disables them (their credentials stay until you remove them).`);\n return 1;\n }\n const what = ext.usedBy.length ? ` and disable ${ext.usedBy.join(\", \")}` : \"\";\n if (!(await p.confirm(`Remove the extension ${name}${what}?`, false))) return 0;\n try {\n await api.remove(name, !!flags.force);\n } catch (err) {\n const body = errorBody(err);\n if (body?.error.code === \"in_use\") {\n p.print(pc.yellow(`${name} is used by ${(body.usedBy ?? []).join(\", \")}. Pass --force to disable them and remove it.`));\n return 1;\n }\n throw err;\n }\n p.print(`${pc.green(\"✔\")} Removed ${name}.`);\n return 0;\n}\n\nconst GROUP = \"MCP connections (on xaq's machine):\";\n\nexport function registerExtensionCommands(program: Command): void {\n const dataOption = (cmd: Command) => cmd.option(\"-d, --data <dir>\", `xaq's data directory (default ~/.xaq, or an existing ~/.${LEGACY_PRODUCT}; env XAQ_DATA)`);\n const withApi = async (o: { data?: string; yes?: boolean }, run: (deps: ExtDeps) => Promise<number>) => {\n const { CLEANUP_SIGNALS, openAdminChannel } = await import(\"../connections-cli/admin.ts\");\n const { TerminalPrompter } = await import(\"../connections-cli/prompt.ts\");\n const channel = openAdminChannel({ dataDir: resolveDataDir(o.data, { quiet: true }) });\n const handlers = CLEANUP_SIGNALS.map((sig) => {\n const h = () => {\n channel.close();\n process.exit(sig === \"SIGINT\" ? 130 : 128 + (sig === \"SIGTERM\" ? 15 : 1));\n };\n process.once(sig, h);\n return [sig, h] as const;\n });\n try {\n process.exitCode = await run({ api: new ExtensionsApi(channel), prompter: new TerminalPrompter(!!o.yes) });\n } finally {\n for (const [sig, h] of handlers) process.removeListener(sig, h);\n channel.close();\n }\n };\n\n const ext = program\n .command(\"ext\")\n .description(\"extensions: the integrations xaq can connect to (Notion, WHOOP, T3 Code, …), from the official registry or elsewhere\")\n .summary(\"add, list, update or remove extensions\")\n .helpGroup(GROUP);\n\n dataOption(\n ext\n .command(\"add\")\n .description(\"install an extension: a registry name, a GitHub URL, another git URL (#ref, #ref:path), or a ./directory\")\n .argument(\"<name|url|path>\", \"e.g. notion, https://github.com/you/repo/tree/main/ext, ./my-extension\"),\n )\n .option(\"-y, --yes\", \"install a third-party extension without asking (the warning is still printed)\")\n .addHelpText(\n \"after\",\n `\nAnything that isn't the official registry (wynn-dev/xaq-extensions) is third-party: you see its kind, its declared\nnetwork hosts and its source first, and confirm. A code extension runs on this machine with network access.\n\nExamples:\n ${NPX} ext add notion\n ${NPX} ext add https://github.com/you/xaq-ext/tree/main/extensions/thing\n ${NPX} ext add https://git.example.com/you/ext.git#v1.2.0\n ${NPX} ext add ./my-extension`,\n )\n .action((source: string, o: { data?: string; yes?: boolean }) => withApi(o, (deps) => addExtension(source, deps)));\n\n dataOption(ext.command(\"list\").description(\"list installed extensions: version, commit, official or third-party, and the connections using each\")).action((o: { data?: string }) =>\n withApi(o, (deps) => listExtensions(deps)),\n );\n\n dataOption(ext.command(\"update\").description(\"show what an update changes (version, commit, integrity) and install it on confirmation\").argument(\"<name>\", \"the extension's name\"))\n .option(\"-y, --yes\", \"install the update without asking\")\n .action((name: string, o: { data?: string; yes?: boolean }) => withApi(o, (deps) => updateExtension(name, deps)));\n\n dataOption(ext.command(\"remove\").description(\"remove an extension (refused while connections use it, unless --force)\").argument(\"<name>\", \"the extension's name\"))\n .option(\"--force\", \"also when connections use it: they're disabled (credentials stay until you remove them)\")\n .option(\"-y, --yes\", \"don't ask\")\n .action((name: string, o: { data?: string; yes?: boolean; force?: boolean }) => withApi(o, (deps) => removeExtension(name, o, deps)));\n}\n"],"mappings":"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"}