xaq-cli 0.0.0 → 0.1.0-nightly.71

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 (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +79 -0
  3. package/dist/admin-5gThRwgj.js +162 -0
  4. package/dist/admin-5gThRwgj.js.map +1 -0
  5. package/dist/admin-Btrm8mCd.js +218 -0
  6. package/dist/admin-Btrm8mCd.js.map +1 -0
  7. package/dist/admin-CWofnT_Q.js +2 -0
  8. package/dist/api-BFpElwqK.js +89 -0
  9. package/dist/api-BFpElwqK.js.map +1 -0
  10. package/dist/audit-BB7jK9Z5.js +2 -0
  11. package/dist/audit-BVYknW2i.js +79 -0
  12. package/dist/audit-BVYknW2i.js.map +1 -0
  13. package/dist/auth-CXmBdMqw.js +2 -0
  14. package/dist/auth-D-GmwNv-.js +550 -0
  15. package/dist/auth-D-GmwNv-.js.map +1 -0
  16. package/dist/brand--ZB8jTn1.js +32 -0
  17. package/dist/brand--ZB8jTn1.js.map +1 -0
  18. package/dist/bus-Cz0flS37.js +12 -0
  19. package/dist/bus-Cz0flS37.js.map +1 -0
  20. package/dist/bus-OKIGut72.js +2 -0
  21. package/dist/cli.js +234 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/commands-BNVLjYXA.js +167 -0
  24. package/dist/commands-BNVLjYXA.js.map +1 -0
  25. package/dist/commands-CdA6-WE7.js +112 -0
  26. package/dist/commands-CdA6-WE7.js.map +1 -0
  27. package/dist/config-C9XmYZWO.js +94 -0
  28. package/dist/config-C9XmYZWO.js.map +1 -0
  29. package/dist/config-CNVi0v5t.js +2 -0
  30. package/dist/connect-TtJ9n0Rw.js +362 -0
  31. package/dist/connect-TtJ9n0Rw.js.map +1 -0
  32. package/dist/content-C1lKMxFS.js +1170 -0
  33. package/dist/content-C1lKMxFS.js.map +1 -0
  34. package/dist/content-worker.js +28 -0
  35. package/dist/content-worker.js.map +1 -0
  36. package/dist/crypto-fv2fj3S8.js +75 -0
  37. package/dist/crypto-fv2fj3S8.js.map +1 -0
  38. package/dist/db-BEUEUPyP.js +561 -0
  39. package/dist/db-BEUEUPyP.js.map +1 -0
  40. package/dist/db-Q5BQuwga.js +2 -0
  41. package/dist/errors-FbMS3QBo.js +39 -0
  42. package/dist/errors-FbMS3QBo.js.map +1 -0
  43. package/dist/filter-Ck6OMxvu.js +158 -0
  44. package/dist/filter-Ck6OMxvu.js.map +1 -0
  45. package/dist/hermes-CKyzt1mH.js +3 -0
  46. package/dist/hermes-DHnDTYJp.js +1023 -0
  47. package/dist/hermes-DHnDTYJp.js.map +1 -0
  48. package/dist/import-DsN5NZT3.js +910 -0
  49. package/dist/import-DsN5NZT3.js.map +1 -0
  50. package/dist/instance-CwWbFbJ8.js +2 -0
  51. package/dist/instance-Dj9TpJB2.js +195 -0
  52. package/dist/instance-Dj9TpJB2.js.map +1 -0
  53. package/dist/listen-B9iO7HgQ.js +1568 -0
  54. package/dist/listen-B9iO7HgQ.js.map +1 -0
  55. package/dist/listen-CXVKG9XD.js +2 -0
  56. package/dist/manage-Bo6COB4p.js +477 -0
  57. package/dist/manage-Bo6COB4p.js.map +1 -0
  58. package/dist/manage-DoXqvjnU.js +2 -0
  59. package/dist/mcpheader-B8c5YO1B.js +2 -0
  60. package/dist/mcpheader-C9H4vudu.js +185 -0
  61. package/dist/mcpheader-C9H4vudu.js.map +1 -0
  62. package/dist/mcpsetup-BnHudwpq.js +2 -0
  63. package/dist/mcpsetup-DXXo2qjf.js +1053 -0
  64. package/dist/mcpsetup-DXXo2qjf.js.map +1 -0
  65. package/dist/node-check-CjTk6VNL.js +24 -0
  66. package/dist/node-check-CjTk6VNL.js.map +1 -0
  67. package/dist/paths-d-C2kdjo.js +2 -0
  68. package/dist/paths-yzOKD6ow.js +46 -0
  69. package/dist/paths-yzOKD6ow.js.map +1 -0
  70. package/dist/profile-CcMZ4hn7.js +2 -0
  71. package/dist/profile-NmMKAczz.js +222 -0
  72. package/dist/profile-NmMKAczz.js.map +1 -0
  73. package/dist/prompt-CKMQx_MJ.js +114 -0
  74. package/dist/prompt-CKMQx_MJ.js.map +1 -0
  75. package/dist/recipes-C7TIDlRj.js +3 -0
  76. package/dist/recipes-DG_-doGq.js +314 -0
  77. package/dist/recipes-DG_-doGq.js.map +1 -0
  78. package/dist/send-Dk-t8ia-.js +108 -0
  79. package/dist/send-Dk-t8ia-.js.map +1 -0
  80. package/dist/server-CDqDp1q3.js +20640 -0
  81. package/dist/server-CDqDp1q3.js.map +1 -0
  82. package/dist/service-BVn3soZZ.js +766 -0
  83. package/dist/service-BVn3soZZ.js.map +1 -0
  84. package/dist/service-BaWGPmSd.js +2 -0
  85. package/dist/service-C7jfo-Ex.js +457 -0
  86. package/dist/service-C7jfo-Ex.js.map +1 -0
  87. package/dist/service-DxDyKDQ-.js +2 -0
  88. package/dist/settings-B5qSHxRM.js +134 -0
  89. package/dist/settings-B5qSHxRM.js.map +1 -0
  90. package/dist/settings-DQvZaMN1.js +2 -0
  91. package/dist/setup-token-CwuSzfLK.js +48 -0
  92. package/dist/setup-token-CwuSzfLK.js.map +1 -0
  93. package/dist/skill/SKILL.md +445 -0
  94. package/dist/skill-1UQj3qgW.js +69 -0
  95. package/dist/skill-1UQj3qgW.js.map +1 -0
  96. package/dist/skill-pelq18JQ.js +2 -0
  97. package/dist/ssrf-BlVpGZ7C.js +181 -0
  98. package/dist/ssrf-BlVpGZ7C.js.map +1 -0
  99. package/dist/types-BhJDbSzE.js +135 -0
  100. package/dist/types-BhJDbSzE.js.map +1 -0
  101. package/dist/ui-B8Sd4Y7Y.js +81 -0
  102. package/dist/ui-B8Sd4Y7Y.js.map +1 -0
  103. package/dist/urls-Dp8pBGMU.js +2 -0
  104. package/dist/urls-U_LfYV61.js +27 -0
  105. package/dist/urls-U_LfYV61.js.map +1 -0
  106. package/dist/version-BxH7OuWD.js +20 -0
  107. package/dist/version-BxH7OuWD.js.map +1 -0
  108. package/dist/web/assets/_agentId-DonOCOlC.js +1 -0
  109. package/dist/web/assets/_app-B7t5lEia.js +1 -0
  110. package/dist/web/assets/_app-DMTGqYpM.js +65 -0
  111. package/dist/web/assets/_connectionId-DR5vFXOg.js +1 -0
  112. package/dist/web/assets/_webhookId-LfUlsULp.js +1 -0
  113. package/dist/web/assets/account-status-CEszs13C.js +1 -0
  114. package/dist/web/assets/accounts-5m7aiXuZ.js +1 -0
  115. package/dist/web/assets/accounts-list-YfJzThZa.js +1 -0
  116. package/dist/web/assets/activity-DOIe7SAg.js +1 -0
  117. package/dist/web/assets/add-connection-C8JR-13R.js +1 -0
  118. package/dist/web/assets/agent-create-form-CPxnPYH4.js +1 -0
  119. package/dist/web/assets/agents-B2Qj5KxT.js +1 -0
  120. package/dist/web/assets/approvals-BGaVaoFy.js +1 -0
  121. package/dist/web/assets/approve._id-BLrQnzPM.js +1 -0
  122. package/dist/web/assets/arrow-left-DZVAXoKj.js +1 -0
  123. package/dist/web/assets/arrow-right-C2zcqRr2.js +1 -0
  124. package/dist/web/assets/attention-gO9Nw5uW.js +1 -0
  125. package/dist/web/assets/audit-BPXXre8S.js +1 -0
  126. package/dist/web/assets/audit-bits-CGyIprzK.js +1 -0
  127. package/dist/web/assets/bell-ring-ig3zn1iS.js +1 -0
  128. package/dist/web/assets/bot-Zk76q_39.js +1 -0
  129. package/dist/web/assets/calendar-notice-DsCfq3VJ.js +1 -0
  130. package/dist/web/assets/calendar-plus-Cycfkk0y.js +1 -0
  131. package/dist/web/assets/calendar-setup-Pn61Wj1n.js +2 -0
  132. package/dist/web/assets/card-GSO5o-Ug.js +1 -0
  133. package/dist/web/assets/check-check-Ds1bPIGv.js +1 -0
  134. package/dist/web/assets/checkbox-B937U-Fd.js +1 -0
  135. package/dist/web/assets/chevron-right-Cqh4mDk1.js +1 -0
  136. package/dist/web/assets/chips-input-2xWDofmE.js +1 -0
  137. package/dist/web/assets/choice-cards-CgAIqBGX.js +1 -0
  138. package/dist/web/assets/circle-x-DcU-0zVh.js +1 -0
  139. package/dist/web/assets/command-khhto70y.js +1 -0
  140. package/dist/web/assets/common-BPZV8TFb.js +1 -0
  141. package/dist/web/assets/connect-agent-guide-CJdw31p2.js +1 -0
  142. package/dist/web/assets/connect-host-lr2c3xWh.js +6 -0
  143. package/dist/web/assets/connection-row-DYx_5o0c.js +1 -0
  144. package/dist/web/assets/connections-C4NOFb5R.js +1 -0
  145. package/dist/web/assets/data-table-kW1pkRB5.js +1 -0
  146. package/dist/web/assets/empty-DzTDioBP.js +1 -0
  147. package/dist/web/assets/events-C9zJnPKK.js +1 -0
  148. package/dist/web/assets/eye-B9gT1QSq.js +1 -0
  149. package/dist/web/assets/eye-off-Diktqz_g.js +1 -0
  150. package/dist/web/assets/field-C6ejurju.js +1 -0
  151. package/dist/web/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  152. package/dist/web/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  153. package/dist/web/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  154. package/dist/web/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  155. package/dist/web/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  156. package/dist/web/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  157. package/dist/web/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  158. package/dist/web/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  159. package/dist/web/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  160. package/dist/web/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  161. package/dist/web/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  162. package/dist/web/assets/get-started-C38bq1gZ.js +2 -0
  163. package/dist/web/assets/hidden-chars-Dr9TxfvF.js +1 -0
  164. package/dist/web/assets/index-DGzo3kGh.js +11 -0
  165. package/dist/web/assets/index-zKOCQLGX.css +2 -0
  166. package/dist/web/assets/input-CWwJgozn.js +1 -0
  167. package/dist/web/assets/input-group-HG8OXcJ4.js +1 -0
  168. package/dist/web/assets/label-multi-select-sH8mllw8.js +1 -0
  169. package/dist/web/assets/labels-B5UqVwFo.js +1 -0
  170. package/dist/web/assets/laptop-BxJ4Nbj7.js +1 -0
  171. package/dist/web/assets/last-call-D-O5O94z.js +1 -0
  172. package/dist/web/assets/listener-handled-HNCuVrQ7.js +1 -0
  173. package/dist/web/assets/login-DaeKclcf.js +1 -0
  174. package/dist/web/assets/mail-C5fm2pTC.js +1 -0
  175. package/dist/web/assets/mcp-call-UoOTPFyh.js +1 -0
  176. package/dist/web/assets/mcp-call-card-Cr-OEDX2.js +3 -0
  177. package/dist/web/assets/message-square-quote-se8Bf3RP.js +1 -0
  178. package/dist/web/assets/mutations-CLPqJ6cj.js +1 -0
  179. package/dist/web/assets/network-igNw0VVx.js +1 -0
  180. package/dist/web/assets/pairing-XjuaVr2u.js +1 -0
  181. package/dist/web/assets/pairing-notifier-KPzOEZSO.js +1 -0
  182. package/dist/web/assets/paperclip-znS4iTrg.js +1 -0
  183. package/dist/web/assets/password-input-DV3aqcNf.js +1 -0
  184. package/dist/web/assets/pause-o-dK3khK.js +1 -0
  185. package/dist/web/assets/pencil-DkPFAhEq.js +1 -0
  186. package/dist/web/assets/play-DvCHND93.js +1 -0
  187. package/dist/web/assets/plug-CXAniEiR.js +1 -0
  188. package/dist/web/assets/plug-zap-BwR-l5wb.js +1 -0
  189. package/dist/web/assets/plus-B_TUslu3.js +1 -0
  190. package/dist/web/assets/preload-helper-Cc_aKyUb.js +1 -0
  191. package/dist/web/assets/public-url-rF2Xjyel.js +1 -0
  192. package/dist/web/assets/queries-Cv3nXx0U.js +42 -0
  193. package/dist/web/assets/refresh-cw-Ch5FNrby.js +1 -0
  194. package/dist/web/assets/revealed-keys-C_9NtJPY.js +1 -0
  195. package/dist/web/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  196. package/dist/web/assets/rotate-ccw-B-mzPDk0.js +1 -0
  197. package/dist/web/assets/search-CTVKGomS.js +1 -0
  198. package/dist/web/assets/select-Bb806bpq.js +1 -0
  199. package/dist/web/assets/send-DuMVuYs1.js +1 -0
  200. package/dist/web/assets/settings-BWauhfFf.js +1 -0
  201. package/dist/web/assets/setup-B7bNKQOp.js +1 -0
  202. package/dist/web/assets/sheet-CYPwpUsH.js +1 -0
  203. package/dist/web/assets/shield-alert-CSYB-ThV.js +1 -0
  204. package/dist/web/assets/shield-check-BulpsYqK.js +1 -0
  205. package/dist/web/assets/sparkles-CUTiob4K.js +1 -0
  206. package/dist/web/assets/stamp-BNXUGNa3.js +1 -0
  207. package/dist/web/assets/step-url-51aMNak-.js +1 -0
  208. package/dist/web/assets/stream-Du7fY5_Y.js +1 -0
  209. package/dist/web/assets/tag-I5_-1jFL.js +1 -0
  210. package/dist/web/assets/terminal-BvxDFTJc.js +1 -0
  211. package/dist/web/assets/test-email-button-DYi7V_6X.js +1 -0
  212. package/dist/web/assets/toggle-group-Bdo1gmQ1.js +1 -0
  213. package/dist/web/assets/trash-Ci0aICEl.js +1 -0
  214. package/dist/web/assets/use-delivery-speed-BBh7W2q6.js +1 -0
  215. package/dist/web/assets/use-label-names-BXicesnP.js +1 -0
  216. package/dist/web/assets/use-sign-out-CPrY_oVO.js +1 -0
  217. package/dist/web/assets/useInfiniteQuery-DJLIM8yJ.js +1 -0
  218. package/dist/web/assets/useQuery-MrTwwNy5.js +1 -0
  219. package/dist/web/assets/useRouterState-D4OBK8l5.js +1 -0
  220. package/dist/web/assets/webhook-DjeeZiNC.js +1 -0
  221. package/dist/web/assets/webhook-bits-jw-y2D3u.js +1 -0
  222. package/dist/web/assets/webhook-sheet-B8h0skDe.js +34 -0
  223. package/dist/web/assets/webhooks-Cfsdpoyd.js +1 -0
  224. package/dist/web/assets/x-MF2TZ9aG.js +1 -0
  225. package/dist/web/favicon.svg +1 -0
  226. package/dist/web/index.html +27 -0
  227. package/package.json +98 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Unlabored Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # xaq
2
+
3
+ [![CI](https://github.com/wynn-dev/xaq/actions/workflows/ci.yml/badge.svg)](https://github.com/wynn-dev/xaq/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/xaq-cli)](https://www.npmjs.com/package/xaq-cli)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
6
+
7
+ A self-hosted MCP broker for AI agents (formerly agentinbox, see [Upgrading from agentinbox](#upgrading-from-agentinbox)). One small server holds all your credentials (Notion, WHOOP, any MCP server,
8
+ Gmail and Google Calendar) and gives each agent a single MCP endpoint with only the tools it's allowed. Risky actions
9
+ (deleting, sending, publishing, payments) can wait for your one-tap approval, and every call is audited.
10
+
11
+ ```sh
12
+ npx xaq-cli
13
+ ```
14
+
15
+ Open the link it prints and pick what to connect first: Notion, WHOOP, another MCP server, or Gmail and Calendar.
16
+ Only Gmail and Calendar need a Google Cloud project; everything else is a few clicks. The console then connects your
17
+ first agent. Requires Node.js 22.13 or newer.
18
+
19
+ From the terminal on the same machine: `npx xaq-cli connections add notion` (or `whoop`, or an MCP server URL),
20
+ and `npx xaq-cli import --from hermes` (also `claude-code`, `cursor`, `codex`) to move an agent's existing MCP
21
+ servers behind the broker. Details: [Connections](docs/reference.md#connections-mcp-broker).
22
+
23
+ For Gmail, Google Calendar is supported too (optional). It needs the Calendar API enabled in your Google Cloud project, the
24
+ Calendar scopes on the consent screen, Calendar added to each account, and `calendar.read` (and, to make changes,
25
+ `calendar.write`) on the agent's grant. The console's setup covers all of it; existing installs use Overview → "Add
26
+ Google Calendar". Details: [docs/calendar.md](docs/calendar.md).
27
+
28
+ ![Overview](docs/screenshots/overview.png)
29
+
30
+ <!-- rename-xaq: keep-start -->
31
+ ## Upgrading from agentinbox
32
+
33
+ xaq is the new name of agentinbox (the npm package `agentinbox-cli` is now `xaq-cli`). An existing install keeps
34
+ working as it is; to move it to the new names, run one command per service, on the machine it runs on:
35
+
36
+ ```sh
37
+ npx xaq-cli service install # the daemon
38
+ npx xaq-cli listen service install # each listener (add --profile <name> for other profiles)
39
+ npx xaq-cli skill install --target <claude|codex|…> --url <your server> # each agent's skill, if you installed it
40
+ ```
41
+
42
+ Each `service install` finds the agentinbox service (launchd `com.unlaboredlabs.agentinbox…`, systemd
43
+ `agentinbox.service` / `agentinbox-listen-<profile>.service`), stops it and waits until it is gone, then installs and
44
+ starts the xaq one (launchd `sh.xaq.daemon` / `sh.xaq.listen.<profile>`, systemd `xaq.service` /
45
+ `xaq-listen-<profile>.service`). The two never run at the same time, and if the new one doesn't start, the old one
46
+ is put back exactly as it was. Running it again is a normal reinstall. Mail that arrives meanwhile waits on the daemon
47
+ and is delivered once the listener is back: nothing is lost or delivered twice.
48
+
49
+ **Kept:**
50
+ - the data directory: `~/.agentinbox` (or the `--data` directory the old service used) stays in use, with its
51
+ `master.key`, `agentinbox.db` and settings. Nothing is moved; `~/.xaq` is only for new installs.
52
+ - every agent key (`aib_…`), Gmail and Calendar connection, grant, webhook and Pub/Sub setup;
53
+ - the listener's profiles, keys, logs and event cursor (`~/.config/agentinbox`, `~/.local/state/agentinbox`,
54
+ `~/.local/share/agentinbox` stay in use while the `…/xaq` ones don't exist);
55
+ - the names your agents already use: a profile set up with agentinbox keeps its MCP server name `agentinbox`
56
+ (`agentinbox-<profile>`), so Hermes routes confined to `["mcp-agentinbox"]`, cron jobs limited to
57
+ `["agentinbox"]`, the Hermes route `/webhooks/agentinbox…` and Claude Code's `mcp__agentinbox` keep working;
58
+ - `AGENTINBOX_*` environment variables: still read when the `XAQ_*` one isn't set, and `--exec` commands still get
59
+ `AGENTINBOX_EVENT_FILE` and the other ids next to the new `XAQ_*` ones.
60
+
61
+ **Changes:**
62
+ - the command is `xaq` (`npx xaq-cli …`); `agentinbox` (from the `agentinbox-cli` package, now a shim) and
63
+ `npx agentinbox-cli …` still work: they run xaq after saying so;
64
+ - the service names above, and the services write `XAQ_*` variables;
65
+ - new profiles and setups use `xaq` / `xaq-<profile>` for the MCP server and Hermes route, and the skill is
66
+ installed as `xaq` (replacing the `agentinbox` one);
67
+ - the console, logs and messages say xaq.
68
+ <!-- rename-xaq: keep-end -->
69
+
70
+ ## More
71
+
72
+ - [Reference](docs/reference.md): features, configuration, running as a service or behind a proxy, security model,
73
+ webhooks, data and backups, development
74
+ - [Waking local agents with `xaq listen`](docs/listen.md), e.g. `npx xaq-cli listen --recipe claude-code`
75
+ - [Screenshots](docs/screenshots/README.md)
76
+
77
+ ## License
78
+
79
+ [MIT](LICENSE)
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+ import { i as sha256 } from "./crypto-fv2fj3S8.js";
3
+ import { r as SESSION_COOKIE, t as CSRF_HEADER, y as SESSION_MAX_AGE_MS } from "./auth-D-GmwNv-.js";
4
+ import { n as dataFiles } from "./config-C9XmYZWO.js";
5
+ import { c as runningInstance } from "./instance-Dj9TpJB2.js";
6
+ import { existsSync } from "node:fs";
7
+ import { DatabaseSync } from "node:sqlite";
8
+ import { randomBytes } from "node:crypto";
9
+ //#region server/connections-cli/admin.ts
10
+ /**
11
+ * The CLI's admin channel to the daemon on this machine (`connections …`, `import`), without the console password.
12
+ *
13
+ * Whoever can write the data directory already holds master.key and the database (`reset-password` relies on the
14
+ * same thing), so the CLI proves it is the admin by writing there: it inserts a one-time admin session (only
15
+ * sha256(token) is stored, like console sessions) that expires after 15 minutes, uses it as the session cookie
16
+ * against the daemon's own /api on loopback, and deletes it when the command ends. Nothing new listens, no
17
+ * token file sits on disk, and the daemon's admin auth (requireAdmin + the CSRF header) is unchanged: every API
18
+ * call is checked, audited and rate-limited exactly like the console's.
19
+ *
20
+ * Console sessions slide forward on use up to SESSION_MAX_AGE_MS after created_at. The CLI's row is written with
21
+ * created_at = expiry − SESSION_MAX_AGE_MS, so that hard limit is the 15-minute expiry and the row can never be
22
+ * extended (see docs/FOLLOWUPS.md: a session kind column would say this directly).
23
+ */
24
+ const CLI_SESSION_TTL_MS = 9e5;
25
+ const CLI_SESSION_LABEL = "xaq CLI (local admin, one-time)";
26
+ var AdminApiError = class extends Error {
27
+ status;
28
+ code;
29
+ hint;
30
+ body;
31
+ constructor(status, code, message, hint, body) {
32
+ super(message);
33
+ this.status = status;
34
+ this.code = code;
35
+ this.hint = hint;
36
+ this.body = body;
37
+ this.name = "AdminApiError";
38
+ }
39
+ };
40
+ /** Signals that end a CLI command: the one-time session is deleted on each (and on a normal exit). */
41
+ const CLEANUP_SIGNALS = [
42
+ "SIGINT",
43
+ "SIGTERM",
44
+ "SIGHUP"
45
+ ];
46
+ /** Where the CLI reaches the daemon: its bind address, or loopback when it listens on every interface. */
47
+ function daemonOrigin(instance) {
48
+ const host = instance.host;
49
+ const h = !host || host === "0.0.0.0" || host === "::" || host === "[::]" ? "127.0.0.1" : host.replace(/^\[|\]$/g, "");
50
+ return `http://${h.includes(":") ? `[${h}]` : h}:${instance.port}`;
51
+ }
52
+ /** Inserts the one-time admin session; returns the token. */
53
+ function createCliSession(dbPath, clock = Date.now) {
54
+ const token = randomBytes(32).toString("base64url");
55
+ const hash = sha256(token);
56
+ const expires = clock() + CLI_SESSION_TTL_MS;
57
+ const db = new DatabaseSync(dbPath);
58
+ try {
59
+ db.exec("PRAGMA busy_timeout = 5000");
60
+ db.prepare("INSERT INTO admin_sessions (token_hash, created_at, expires_at, ip, user_agent) VALUES (?, ?, ?, ?, ?)").run(hash, new Date(expires - SESSION_MAX_AGE_MS).toISOString(), new Date(expires).toISOString(), "local-cli", CLI_SESSION_LABEL);
61
+ } finally {
62
+ db.close();
63
+ }
64
+ let removed = false;
65
+ return {
66
+ token,
67
+ remove() {
68
+ if (removed) return;
69
+ removed = true;
70
+ try {
71
+ const d = new DatabaseSync(dbPath);
72
+ try {
73
+ d.exec("PRAGMA busy_timeout = 5000");
74
+ d.prepare("DELETE FROM admin_sessions WHERE token_hash = ?").run(hash);
75
+ } finally {
76
+ d.close();
77
+ }
78
+ } catch {}
79
+ }
80
+ };
81
+ }
82
+ function openAdminChannel(opts) {
83
+ const dbPath = dataFiles(opts.dataDir).db;
84
+ if (!existsSync(dbPath)) throw new Error(`No xaq data in ${opts.dataDir}. Run this on the machine xaq runs on, as the user it runs as (with sudo for a system service), or pass its data directory with --data.`);
85
+ const instance = opts.instance === void 0 ? runningInstance(opts.dataDir) : opts.instance;
86
+ if (!instance && !opts.baseUrl) throw new Error(`xaq isn't running with the data directory ${opts.dataDir}. Start it first (or pass --data).`);
87
+ const baseUrl = opts.baseUrl ?? daemonOrigin(instance);
88
+ let session;
89
+ try {
90
+ session = createCliSession(dbPath, opts.clock);
91
+ } catch (err) {
92
+ const code = err.code;
93
+ throw new Error(`Can't open ${dbPath}${code ? ` (${code})` : ""}: ${err.message}. Run this as the user xaq runs as (with sudo for a system service).`);
94
+ }
95
+ const onExit = () => session.remove();
96
+ process.once("exit", onExit);
97
+ const doFetch = opts.fetch ?? fetch;
98
+ const send = (method, path, body) => {
99
+ const headers = {
100
+ cookie: `${SESSION_COOKIE}=${session.token}`,
101
+ [CSRF_HEADER]: "1",
102
+ accept: "application/json"
103
+ };
104
+ if (body !== void 0) headers["content-type"] = "application/json";
105
+ return doFetch(`${baseUrl}${path}`, {
106
+ method,
107
+ headers,
108
+ body: body === void 0 ? void 0 : JSON.stringify(body),
109
+ signal: AbortSignal.timeout(12e4)
110
+ });
111
+ };
112
+ return {
113
+ baseUrl,
114
+ async request(method, path, body) {
115
+ let res;
116
+ try {
117
+ res = await send(method, path, body);
118
+ if (res.status === 401) {
119
+ await res.body?.cancel().catch(() => {});
120
+ session.remove();
121
+ session = createCliSession(dbPath, opts.clock);
122
+ res = await send(method, path, body);
123
+ }
124
+ } catch (err) {
125
+ throw new Error(`Couldn't reach xaq at ${baseUrl}: ${err.message}`);
126
+ }
127
+ if (res.status === 204) return void 0;
128
+ const text = await res.text();
129
+ let data = null;
130
+ try {
131
+ data = text ? JSON.parse(text) : null;
132
+ } catch {}
133
+ if (!res.ok) {
134
+ const e = data?.error;
135
+ throw new AdminApiError(res.status, e?.code ?? `http_${res.status}`, e?.message ?? `HTTP ${res.status} from ${path}`, e?.hint, data);
136
+ }
137
+ return data;
138
+ },
139
+ credentialStamp(connectionId) {
140
+ try {
141
+ const db = new DatabaseSync(dbPath, { readOnly: true });
142
+ try {
143
+ db.exec("PRAGMA busy_timeout = 5000");
144
+ const r = db.prepare("SELECT issued_at, access_token_enc, header_value_enc FROM connection_credentials WHERE connection_id = ?").get(connectionId);
145
+ return r ? sha256(`${r.issued_at ?? ""}|${r.access_token_enc ?? ""}|${r.header_value_enc ?? ""}`) : null;
146
+ } finally {
147
+ db.close();
148
+ }
149
+ } catch {
150
+ return null;
151
+ }
152
+ },
153
+ close() {
154
+ process.removeListener("exit", onExit);
155
+ session.remove();
156
+ }
157
+ };
158
+ }
159
+ //#endregion
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
+
162
+ //# sourceMappingURL=admin-5gThRwgj.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-5gThRwgj.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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"}
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env node
2
+ import { t as NPX } from "./version-BxH7OuWD.js";
3
+ import { i as saveProfile, l as ensurePrivateDir, n as loadProfile } from "./profile-NmMKAczz.js";
4
+ import { c as profilePath, i as deadPath, o as logPath, p as validProfileName } from "./paths-yzOKD6ow.js";
5
+ import { b as TransportError, f as isHermesRoute, x as checkTransport } from "./recipes-DG_-doGq.js";
6
+ import { i as safeText, r as formatDuration } from "./ui-B8Sd4Y7Y.js";
7
+ 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-DHnDTYJp.js";
8
+ 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-B9iO7HgQ.js";
9
+ import { r as installedServiceLabel } from "./service-C7jfo-Ex.js";
10
+ import pc from "picocolors";
11
+ //#region server/listen/admin.ts
12
+ const out = (s = "") => process.stdout.write(`${s}\n`);
13
+ const flagFor = (profile) => profile === "default" ? "" : ` --profile ${profile}`;
14
+ const ago = (iso) => iso ? `${formatDuration(Date.now() - Date.parse(iso))} ago` : "never";
15
+ /** Prints the forward-mode signing secret (generating and saving it on first use). */
16
+ function printSecret(profileName, opts) {
17
+ const name = validProfileName(profileName);
18
+ const profile = loadProfile(name);
19
+ let { secret, created } = forwardSecret(profile);
20
+ if (opts.rotate) {
21
+ secret = forwardSecret({
22
+ ...profile,
23
+ forward: void 0,
24
+ forwardSecret: void 0
25
+ }).secret;
26
+ created = true;
27
+ }
28
+ if (created) {
29
+ if (profile.forward) profile.forward = {
30
+ ...profile.forward,
31
+ secret
32
+ };
33
+ else profile.forwardSecret = secret;
34
+ saveProfile(name, profile);
35
+ process.stderr.write(pc.gray(`${opts.rotate ? "Rotated" : "Generated"} the forward signing secret for profile "${name}"${opts.rotate ? "; restart the listener" : ""}.\n`));
36
+ }
37
+ out(secret);
38
+ }
39
+ async function printStatus(profileName) {
40
+ const name = validProfileName(profileName);
41
+ const profile = loadProfile(name);
42
+ const st = readStatus(name);
43
+ const alive = st ? pidAlive(st.pid) && !st.stoppedAt : false;
44
+ const dead = readDead(name);
45
+ out(`${pc.bold("Profile")} ${name} ${pc.gray(profilePath(name))}`);
46
+ out(`${pc.bold("Agent")} ${safeText(profile.agentName)} ${pc.gray(`(${profile.agentId})`)}`);
47
+ out(`${pc.bold("Daemon")} ${profile.url}`);
48
+ out(`${pc.bold("Consumer")} ${st?.consumer ?? profile.consumer}`);
49
+ const exec = st && st.exec !== void 0 ? st.exec : profile.exec;
50
+ const forward = st && st.forward !== void 0 ? st.forward : profile.forward?.url;
51
+ const targets = [exec ? `exec ${safeText(exec, 120)}` : null, forward ? `forward ${forward}` : null].filter(Boolean);
52
+ out(`${pc.bold("Runs")} ${targets.length ? targets.join(" · ") : pc.gray("nothing saved (pass --exec/--forward with --save)")}`);
53
+ out(`${pc.bold("Events")} ${profile.types?.length ? profile.types.join(", ") : `${DEFAULT_LISTEN_TYPES.join(", ")} ${pc.gray("(default types; add --types all --save for every event)")}`}`);
54
+ let saved = null;
55
+ try {
56
+ saved = resolveListen(name, profile, {}).config;
57
+ } catch {}
58
+ out(`${pc.bold("Filters")} ${alive && st?.filters || (saved ? describeFilter(saved) : "?")}`);
59
+ const batchSeconds = alive && st && st.batchSeconds !== void 0 ? st.batchSeconds : saved?.batchSeconds ?? 0;
60
+ if (batchSeconds) out(`${pc.bold("Batch")} ${formatDuration(batchSeconds * 1e3)} window, up to 20 events per --exec run`);
61
+ const route = forward && isHermesRoute(forward) ? routeWarning(name, forward) : null;
62
+ if (route?.level === "warn") out(`${pc.bold("Route")} ${pc.yellow(route.message)}`);
63
+ out(`${pc.bold("Output")} ${outputLine(name, {
64
+ exec: exec ?? null,
65
+ forward: forward ? { url: forward } : null
66
+ }, logPath(name))}`);
67
+ let svc = "not installed";
68
+ if (process.platform === "linux" || process.platform === "darwin") {
69
+ const installed = installedServiceLabel(name);
70
+ if (installed) svc = `installed (${installed}; details: ${NPX} listen service status${flagFor(name)})`;
71
+ }
72
+ out(`${pc.bold("Service")} ${svc}`);
73
+ try {
74
+ await checkTransport(profile.url, { insecure: profile.insecure });
75
+ const self = await getSelf(profile.url, profile.apiKey, 5e3);
76
+ out(`${pc.bold("Key")} ${pc.green("valid")}`);
77
+ const lines = describeGrants(self.grants, self.connections);
78
+ out(`${pc.bold("Access")} ${hasAccess(self) ? lines[0] : pc.yellow(lines[0])}`);
79
+ for (const l of lines.slice(1)) out(` ${l}`);
80
+ } catch (err) {
81
+ const revoked = err instanceof ApiError && (err.status === 401 || err.status === 403);
82
+ const why = err instanceof ApiError && err.status === 0 ? err.cause : err instanceof Error ? err.message.split("\n")[0] : String(err);
83
+ if (err instanceof TransportError) out(`${pc.bold("Key")} ${pc.red(`not checked: ${why}`)}`);
84
+ else out(`${pc.bold("Key")} ${revoked ? pc.red(`rejected: ${err.message}`) : pc.yellow(`not checked (${why})`)}`);
85
+ }
86
+ if (!st) out(`${pc.bold("Listener")} ${pc.gray("has not run yet")}`);
87
+ else {
88
+ const state = !alive ? pc.gray(`not running${st.stoppedAt ? ` (stopped ${ago(st.stoppedAt)}${st.exitReason ? `: ${safeText(st.exitReason, 160)}` : ""})` : " (exited unexpectedly)"}`) : st.connected ? pc.green(`running (pid ${st.pid}), connected ${ago(st.connectedSince)}`) : pc.yellow(`running (pid ${st.pid}), disconnected${st.lastDisconnect ? ` ${ago(st.lastDisconnect.at)}: ${safeText(st.lastDisconnect.reason, 160)}` : ""}`);
89
+ out(`${pc.bold("Listener")} ${state}`);
90
+ out(`${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)}`);
91
+ if (st.recent.length) {
92
+ out(pc.bold("Recent"));
93
+ for (const r of st.recent.slice(0, 8)) {
94
+ const icon = r.outcome === "succeeded" ? pc.green("✓") : r.outcome === "dead" ? pc.red("✖") : r.outcome === "retrying" ? pc.yellow("↻") : pc.gray("·");
95
+ 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)}` : ""}`)}`);
96
+ }
97
+ }
98
+ }
99
+ out(`${pc.bold("Dead")} ${dead.length ? pc.red(`${dead.length} event${dead.length > 1 ? "s" : ""}`) + pc.gray(` (inspect: ${NPX} listen dead${flagFor(name)})`) : "0"}`);
100
+ out(`${pc.bold("Log")} ${logPath(name)}`);
101
+ }
102
+ function printDead(profileName, opts) {
103
+ const name = validProfileName(profileName);
104
+ const entries = readDead(name);
105
+ if (opts.json) {
106
+ for (const e of entries) out(JSON.stringify(e));
107
+ return;
108
+ }
109
+ if (!entries.length) {
110
+ out(`No dead events for profile "${name}".`);
111
+ return;
112
+ }
113
+ for (const e of entries) {
114
+ const m = e.payload.data.message;
115
+ out(`${pc.red("✖")} #${e.seq} ${e.type} ${pc.gray(e.id)} ${pc.gray(`died ${ago(e.deadAt)} after ${e.attempts} attempts`)}`);
116
+ if (m) out(` from ${safeText(m.from?.email ?? "?", 80)} "${safeText(m.subject, 80)}"`);
117
+ out(pc.gray(` ${safeText(e.error, 300)}`));
118
+ }
119
+ out();
120
+ out(pc.gray(`${entries.length} dead event${entries.length > 1 ? "s" : ""} in ${deadPath(name)}.`));
121
+ out(pc.gray(`Replay with: ${NPX} listen dead --replay${flagFor(name)} (one: --id <evt_…>; drop all: --clear)`));
122
+ 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>'.`));
123
+ }
124
+ function clearDead(profileName, id) {
125
+ const name = validProfileName(profileName);
126
+ const entries = readDead(name);
127
+ const keep = id ? entries.filter((e) => e.id !== id) : [];
128
+ writeDead(name, keep);
129
+ out(`Removed ${entries.length - keep.length} dead event${entries.length - keep.length === 1 ? "" : "s"}.`);
130
+ }
131
+ /**
132
+ * Runs dead events again through the profile's targets (or the ones passed as flags). Successes are
133
+ * removed from dead.jsonl; failures stay. Returns how many still failed.
134
+ */
135
+ async function replayDead(flags, opts = {}) {
136
+ const name = validProfileName(flags.profile ?? "default");
137
+ const profile = loadProfile(name);
138
+ const { config } = resolveListen(name, profile, {
139
+ ...flags,
140
+ save: false
141
+ });
142
+ const chosen = readDead(name).filter((e) => !flags.id || e.id === flags.id);
143
+ if (!chosen.length) {
144
+ out(flags.id ? `No dead event with id ${flags.id}.` : `No dead events for profile "${name}".`);
145
+ return 0;
146
+ }
147
+ const available = [];
148
+ if (config.exec) available.push("exec");
149
+ if (config.forward) available.push("forward");
150
+ 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 …\`).`);
151
+ if (config.exec && process.platform === "win32") throw new Error("--exec isn't supported on Windows; replay with --forward instead.");
152
+ const timeoutMs = config.timeoutSeconds * 1e3;
153
+ if (config.cwdIsDefault) ensurePrivateDir(config.cwd);
154
+ const failed = /* @__PURE__ */ new Map();
155
+ const bySeq = new Map(chosen.map((e) => [e.seq, e]));
156
+ const dispatcher = new Dispatcher({
157
+ targets: available,
158
+ concurrency: config.concurrency,
159
+ maxAttempts: config.maxAttempts,
160
+ retryDelayMs: opts.retryDelayMs,
161
+ run: async (job, signal) => {
162
+ const errors = [];
163
+ if (job.remaining.has("exec") && config.exec) {
164
+ const r = await runCommand(config.exec, job.payload, {
165
+ timeoutMs,
166
+ signal,
167
+ cwd: config.cwd
168
+ });
169
+ if (r.ok) job.remaining.delete("exec");
170
+ else errors.push(`command: ${r.error}`);
171
+ }
172
+ if (job.remaining.has("forward") && config.forward) {
173
+ const r = await forwardEvent(config.forward.url, config.forward.secret, job.payload, {
174
+ timeoutMs: Math.min(timeoutMs, 3e4),
175
+ signal,
176
+ wrap: isHermesRoute(config.forward.url)
177
+ });
178
+ if (r.ok) job.remaining.delete("forward");
179
+ else errors.push(`forward: ${r.error}`);
180
+ }
181
+ return errors;
182
+ },
183
+ onAdvance: () => {},
184
+ onDead: (job) => {
185
+ const e = bySeq.get(job.seq);
186
+ failed.set(e.id, {
187
+ ...e,
188
+ deadAt: (/* @__PURE__ */ new Date()).toISOString(),
189
+ attempts: e.attempts + job.attempts,
190
+ error: job.lastError ?? "failed"
191
+ });
192
+ out(`${pc.red("✖")} #${job.seq} still failing: ${safeText(job.lastError ?? "failed", 200)}`);
193
+ },
194
+ onFinished: (job) => {
195
+ if (job.outcome === "succeeded") out(`${pc.green("✓")} #${job.seq} ${job.payload.type} replayed`);
196
+ }
197
+ });
198
+ out(`Replaying ${chosen.length} dead event${chosen.length > 1 ? "s" : ""} (up to ${config.maxAttempts} attempts each)…`);
199
+ for (const e of [...chosen].sort((a, b) => a.seq - b.seq)) {
200
+ const targets = e.targets?.length ? e.targets.filter((t) => available.includes(t)) : available;
201
+ if (!targets.length) {
202
+ out(`${pc.yellow("!")} #${e.seq} needs ${e.targets.join(" and ")}, which isn't configured now; pass --${e.targets[0]} to replay it.`);
203
+ failed.set(e.id, e);
204
+ continue;
205
+ }
206
+ dispatcher.push(e.payload, { targets });
207
+ }
208
+ await dispatcher.idle();
209
+ const replayed = new Set(chosen.map((e) => e.id));
210
+ const current = readDead(name);
211
+ writeDead(name, [...current.filter((e) => !replayed.has(e.id)), ...failed.values()]);
212
+ out(`${chosen.length - failed.size} replayed, ${failed.size} still dead.`);
213
+ return failed.size;
214
+ }
215
+ //#endregion
216
+ export { clearDead, printDead, printSecret, printStatus, replayDead };
217
+
218
+ //# sourceMappingURL=admin-Btrm8mCd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-Btrm8mCd.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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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { n as CLEANUP_SIGNALS, o as daemonOrigin, s as openAdminChannel } from "./admin-5gThRwgj.js";