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
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+ import { t as LEGACY_PRODUCT } from "./brand--ZB8jTn1.js";
3
+ import { s as resolveDataDir } from "./config-C9XmYZWO.js";
4
+ import { t as NPX } from "./version-BxH7OuWD.js";
5
+ //#region server/connections-cli/commands.ts
6
+ const GROUP = "MCP connections (on xaq's machine):";
7
+ const collect = (value, previous) => [...previous ?? [], value];
8
+ const dataDirOf = (o) => resolveDataDir(o.data, { quiet: true });
9
+ const dataOption = (cmd) => cmd.option("-d, --data <dir>", `xaq's data directory (default ~/.xaq, or an existing ~/.${LEGACY_PRODUCT}; env XAQ_DATA)`);
10
+ async function withApi(o, run) {
11
+ const { CLEANUP_SIGNALS, openAdminChannel } = await import("./admin-CWofnT_Q.js");
12
+ const { ConnectionsApi } = await import("./api-BFpElwqK.js");
13
+ const { TerminalPrompter } = await import("./prompt-CKMQx_MJ.js");
14
+ const channel = openAdminChannel({ dataDir: dataDirOf(o) });
15
+ const handlers = CLEANUP_SIGNALS.map((sig) => {
16
+ const h = () => {
17
+ channel.close();
18
+ process.exit(sig === "SIGINT" ? 130 : 128 + (sig === "SIGTERM" ? 15 : 1));
19
+ };
20
+ process.once(sig, h);
21
+ return [sig, h];
22
+ });
23
+ try {
24
+ return await run({
25
+ api: new ConnectionsApi(channel),
26
+ prompter: new TerminalPrompter(!!o.yes)
27
+ });
28
+ } finally {
29
+ for (const [sig, h] of handlers) process.removeListener(sig, h);
30
+ channel.close();
31
+ }
32
+ }
33
+ /** This xaq's own MCP origins, so `import` never imports its own entry. */
34
+ async function selfOrigins(dataDir) {
35
+ const { runningInstance } = await import("./instance-CwWbFbJ8.js");
36
+ const { daemonOrigin } = await import("./admin-CWofnT_Q.js");
37
+ const running = runningInstance(dataDir);
38
+ if (!running) return [];
39
+ return [
40
+ daemonOrigin(running),
41
+ `http://localhost:${running.port}`,
42
+ `http://127.0.0.1:${running.port}`,
43
+ `http://[::1]:${running.port}`
44
+ ];
45
+ }
46
+ function registerConnectionsCommands(program) {
47
+ const connections = program.command("connections", { hidden: false }).description("the MCP servers xaq connects agents to (Notion, WHOOP, any MCP URL); run on xaq's machine").summary("add, list, reconnect or remove connected MCP servers").helpGroup(GROUP);
48
+ dataOption(connections.command("add").description("connect an integration (notion, whoop) or any MCP server URL, interactively").argument("<integration-or-url>", "notion, whoop, or an MCP server URL such as https://mcp.example.com/mcp")).option("--slug <name>", "its name: agents see its tools as <name>__<tool>").option("--access <level>", "full, ask (ask before risky actions; the default) or read").option("--agent <name>", "an agent that gets it (repeatable; default: all agents; \"all\")", collect).option("--header <name>", "header for a server that wants a static key (default Authorization)").option("--paste", "paste the address the browser lands on after signing in, instead of waiting for the redirect (automatic over SSH)").option("--allow-private <origin>", "allow this exact scheme://host:port on this machine or a private network without asking (--yes never does)").option("-y, --yes", "answer yes to confirmations and take the defaults (never to a private-network allow)").addHelpText("after", `
49
+ Examples:
50
+ ${NPX} connections add notion
51
+ ${NPX} connections add https://mcp.example.com/mcp --access read
52
+ ${NPX} connections add http://127.0.0.1:8787/mcp # asks before allowing a server on this machine`).action(async (target, o) => {
53
+ const { addConnection } = await import("./manage-DoXqvjnU.js");
54
+ process.exitCode = await withApi(o, (deps) => addConnection(target, o, deps));
55
+ });
56
+ dataOption(connections.command("list").description("list connections with their status and access")).action(async (o) => {
57
+ const { listConnections } = await import("./manage-DoXqvjnU.js");
58
+ process.exitCode = await withApi(o, (deps) => listConnections(deps));
59
+ });
60
+ dataOption(connections.command("reconnect").description("sign in again (OAuth) or replace the key (header)").argument("<name>", "the connection's name")).option("--paste", "paste the address the browser lands on (automatic over SSH)").option("--header <name>", "header connections: the header's name (default: asks, Authorization)").action(async (slug, o) => {
61
+ const { reconnectConnection } = await import("./manage-DoXqvjnU.js");
62
+ process.exitCode = await withApi(o, (deps) => reconnectConnection(slug, o, deps));
63
+ });
64
+ dataOption(connections.command("disable").description("turn a connection off at once (the kill switch); turn it back on in the console").argument("<name>", "the connection's name")).option("-y, --yes", "don't ask").action(async (slug, o) => {
65
+ const { disableConnection } = await import("./manage-DoXqvjnU.js");
66
+ process.exitCode = await withApi(o, (deps) => disableConnection(slug, deps));
67
+ });
68
+ dataOption(connections.command("remove").description("remove a connection and delete its credentials").argument("<name>", "the connection's name")).option("-y, --yes", "don't ask").action(async (slug, o) => {
69
+ const { removeConnection } = await import("./manage-DoXqvjnU.js");
70
+ process.exitCode = await withApi(o, (deps) => removeConnection(slug, deps));
71
+ });
72
+ dataOption(program.command("import", { hidden: false }).description("move an agent tool's MCP servers into xaq: one connection each, credentials moved into xaq").summary("import MCP servers from Hermes, Claude Code, Cursor or Codex").helpGroup(GROUP).requiredOption("--from <client>", "hermes, claude-code, cursor or codex")).option("--dry-run", "print exactly what would be created and changed, and change nothing").option("--access <level>", "for every imported connection: full, ask (the default) or read").option("--agent <name>", "an agent that gets them (repeatable; default: all agents; \"all\")", collect).option("--profile <name>", "the agent profile setup-mcp uses for the single xaq entry (default \"default\")").option("--allow-private <origin>", "allow this exact scheme://host:port on this machine or a private network without asking (repeatable; --yes never does)", collect).option("-y, --yes", "answer yes to the confirmations (scripts); never to a private-network allow or a project's .mcp.json servers").addHelpText("after", `
73
+ Reads: hermes ~/.hermes/config.yaml mcp_servers (+ ~/.hermes/.env) · claude-code ~/.claude.json and ./.mcp.json ·
74
+ cursor ~/.cursor/mcp.json · codex ~/.codex/config.toml. Static headers move into xaq (encrypted); OAuth
75
+ servers are created needing Connect (their tokens stay in the client); local (stdio) servers aren't supported yet
76
+ and stay where they are. Servers on this machine or a private network are created only after you confirm the exact
77
+ host:port. Then it offers setup-mcp --tool <client>, which replaces the entries that now work through xaq
78
+ with the single xaq entry, after a timestamped backup. The entry keeps the name the profile already uses ("xaq", or
79
+ "${LEGACY_PRODUCT}" for a profile set up before the rename), so Hermes routes and cron jobs confined to it keep working.
80
+
81
+ Examples:
82
+ ${NPX} import --from hermes --dry-run
83
+ ${NPX} import --from hermes`).action(async (o) => {
84
+ const { runImport } = await import("./import-DsN5NZT3.js");
85
+ const self = await selfOrigins(dataDirOf(o));
86
+ if (o.dryRun) {
87
+ const { TerminalPrompter } = await import("./prompt-CKMQx_MJ.js");
88
+ const { openAdminChannel } = await import("./admin-CWofnT_Q.js");
89
+ const { ConnectionsApi } = await import("./api-BFpElwqK.js");
90
+ let channel = null;
91
+ try {
92
+ process.exitCode = await runImport(o, {
93
+ prompter: new TerminalPrompter(!!o.yes),
94
+ openApi: () => new ConnectionsApi(channel = openAdminChannel({ dataDir: dataDirOf(o) })),
95
+ selfOrigins: self
96
+ });
97
+ } finally {
98
+ channel?.close();
99
+ }
100
+ return;
101
+ }
102
+ process.exitCode = await withApi(o, (deps) => runImport(o, {
103
+ prompter: deps.prompter,
104
+ openApi: () => deps.api,
105
+ selfOrigins: self
106
+ }));
107
+ });
108
+ }
109
+ //#endregion
110
+ export { registerConnectionsCommands };
111
+
112
+ //# sourceMappingURL=commands-CdA6-WE7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands-CdA6-WE7.js","names":[],"sources":["../server/connections-cli/commands.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { NPX } from \"../listen/version.ts\";\nimport { LEGACY_PRODUCT } from \"../brand.ts\";\nimport { resolveDataDir } from \"../config.ts\";\n\n/**\n * `connections …` and `import --from …`: run on the daemon's machine, talking to its admin API through the\n * one-time local admin session (admin.ts). Listed in --help since the console (#65) shipped; while the daemon's\n * `brokerUpstreams` setting is off they say so (CONNECTIONS_VISIBLE).\n */\nexport const CONNECTIONS_VISIBLE = true;\nconst GROUP = \"MCP connections (on xaq's machine):\";\n\nconst collect = (value: string, previous: string[] | undefined) => [...(previous ?? []), value];\n// --data, else XAQ_DATA (AGENTINBOX_DATA), else ~/.xaq or an existing pre-rename ~/.agentinbox (config.ts; rename-xaq: keep).\nconst dataDirOf = (o: { data?: string }) => resolveDataDir(o.data, { quiet: true });\nconst dataOption = (cmd: Command) => cmd.option(\"-d, --data <dir>\", `xaq's data directory (default ~/.xaq, or an existing ~/.${LEGACY_PRODUCT}; env XAQ_DATA)`);\n\nasync function withApi<T>(o: { data?: string; yes?: boolean }, run: (deps: { api: import(\"./api.ts\").ConnectionsApi; prompter: import(\"./prompt.ts\").Prompter }) => Promise<T>): Promise<T> {\n const { CLEANUP_SIGNALS, openAdminChannel } = await import(\"./admin.ts\");\n const { ConnectionsApi } = await import(\"./api.ts\");\n const { TerminalPrompter } = await import(\"./prompt.ts\");\n const channel = openAdminChannel({ dataDir: dataDirOf(o) });\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 return await run({ api: new ConnectionsApi(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/** This xaq's own MCP origins, so `import` never imports its own entry. */\nasync function selfOrigins(dataDir: string): Promise<string[]> {\n const { runningInstance } = await import(\"../instance.ts\");\n const { daemonOrigin } = await import(\"./admin.ts\");\n const running = runningInstance(dataDir);\n if (!running) return [];\n return [daemonOrigin(running), `http://localhost:${running.port}`, `http://127.0.0.1:${running.port}`, `http://[::1]:${running.port}`];\n}\n\nexport function registerConnectionsCommands(program: Command): void {\n const connections = program\n .command(\"connections\", { hidden: !CONNECTIONS_VISIBLE })\n .description(\"the MCP servers xaq connects agents to (Notion, WHOOP, any MCP URL); run on xaq's machine\")\n .summary(\"add, list, reconnect or remove connected MCP servers\")\n .helpGroup(GROUP);\n\n dataOption(\n connections\n .command(\"add\")\n .description(\"connect an integration (notion, whoop) or any MCP server URL, interactively\")\n .argument(\"<integration-or-url>\", \"notion, whoop, or an MCP server URL such as https://mcp.example.com/mcp\"),\n )\n .option(\"--slug <name>\", \"its name: agents see its tools as <name>__<tool>\")\n .option(\"--access <level>\", \"full, ask (ask before risky actions; the default) or read\")\n .option(\"--agent <name>\", 'an agent that gets it (repeatable; default: all agents; \"all\")', collect)\n .option(\"--header <name>\", \"header for a server that wants a static key (default Authorization)\")\n .option(\"--paste\", \"paste the address the browser lands on after signing in, instead of waiting for the redirect (automatic over SSH)\")\n .option(\"--allow-private <origin>\", \"allow this exact scheme://host:port on this machine or a private network without asking (--yes never does)\")\n .option(\"-y, --yes\", \"answer yes to confirmations and take the defaults (never to a private-network allow)\")\n .addHelpText(\n \"after\",\n `\nExamples:\n ${NPX} connections add notion\n ${NPX} connections add https://mcp.example.com/mcp --access read\n ${NPX} connections add http://127.0.0.1:8787/mcp # asks before allowing a server on this machine`,\n )\n .action(async (target: string, o: import(\"./manage.ts\").AddFlags & { data?: string }) => {\n const { addConnection } = await import(\"./manage.ts\");\n process.exitCode = await withApi(o, (deps) => addConnection(target, o, deps));\n });\n\n dataOption(connections.command(\"list\").description(\"list connections with their status and access\")).action(async (o: { data?: string }) => {\n const { listConnections } = await import(\"./manage.ts\");\n process.exitCode = await withApi(o, (deps) => listConnections(deps));\n });\n\n dataOption(connections.command(\"reconnect\").description(\"sign in again (OAuth) or replace the key (header)\").argument(\"<name>\", \"the connection's name\"))\n .option(\"--paste\", \"paste the address the browser lands on (automatic over SSH)\")\n .option(\"--header <name>\", \"header connections: the header's name (default: asks, Authorization)\")\n .action(async (slug: string, o: { data?: string; paste?: boolean; header?: string }) => {\n const { reconnectConnection } = await import(\"./manage.ts\");\n process.exitCode = await withApi(o, (deps) => reconnectConnection(slug, o, deps));\n });\n\n dataOption(connections.command(\"disable\").description(\"turn a connection off at once (the kill switch); turn it back on in the console\").argument(\"<name>\", \"the connection's name\"))\n .option(\"-y, --yes\", \"don't ask\")\n .action(async (slug: string, o: { data?: string; yes?: boolean }) => {\n const { disableConnection } = await import(\"./manage.ts\");\n process.exitCode = await withApi(o, (deps) => disableConnection(slug, deps));\n });\n\n dataOption(connections.command(\"remove\").description(\"remove a connection and delete its credentials\").argument(\"<name>\", \"the connection's name\"))\n .option(\"-y, --yes\", \"don't ask\")\n .action(async (slug: string, o: { data?: string; yes?: boolean }) => {\n const { removeConnection } = await import(\"./manage.ts\");\n process.exitCode = await withApi(o, (deps) => removeConnection(slug, deps));\n });\n\n dataOption(\n program\n .command(\"import\", { hidden: !CONNECTIONS_VISIBLE })\n .description(\"move an agent tool's MCP servers into xaq: one connection each, credentials moved into xaq\")\n .summary(\"import MCP servers from Hermes, Claude Code, Cursor or Codex\")\n .helpGroup(GROUP)\n .requiredOption(\"--from <client>\", \"hermes, claude-code, cursor or codex\"),\n )\n .option(\"--dry-run\", \"print exactly what would be created and changed, and change nothing\")\n .option(\"--access <level>\", \"for every imported connection: full, ask (the default) or read\")\n .option(\"--agent <name>\", 'an agent that gets them (repeatable; default: all agents; \"all\")', collect)\n .option(\"--profile <name>\", \"the agent profile setup-mcp uses for the single xaq entry (default \\\"default\\\")\")\n .option(\"--allow-private <origin>\", \"allow this exact scheme://host:port on this machine or a private network without asking (repeatable; --yes never does)\", collect)\n .option(\"-y, --yes\", \"answer yes to the confirmations (scripts); never to a private-network allow or a project's .mcp.json servers\")\n .addHelpText(\n \"after\",\n `\nReads: hermes ~/.hermes/config.yaml mcp_servers (+ ~/.hermes/.env) · claude-code ~/.claude.json and ./.mcp.json ·\ncursor ~/.cursor/mcp.json · codex ~/.codex/config.toml. Static headers move into xaq (encrypted); OAuth\nservers are created needing Connect (their tokens stay in the client); local (stdio) servers aren't supported yet\nand stay where they are. Servers on this machine or a private network are created only after you confirm the exact\nhost:port. Then it offers setup-mcp --tool <client>, which replaces the entries that now work through xaq\nwith the single xaq entry, after a timestamped backup. The entry keeps the name the profile already uses (\"xaq\", or\n\"${LEGACY_PRODUCT}\" for a profile set up before the rename), so Hermes routes and cron jobs confined to it keep working.\n\nExamples:\n ${NPX} import --from hermes --dry-run\n ${NPX} import --from hermes`,\n )\n .action(async (o: import(\"./import.ts\").ImportFlags & { data?: string }) => {\n const { runImport } = await import(\"./import.ts\");\n const self = await selfOrigins(dataDirOf(o));\n if (o.dryRun) {\n // Read-only: the admin channel (when the daemon runs) only lists existing connections.\n const { TerminalPrompter } = await import(\"./prompt.ts\");\n const { openAdminChannel } = await import(\"./admin.ts\");\n const { ConnectionsApi } = await import(\"./api.ts\");\n let channel: import(\"./admin.ts\").AdminChannel | null = null;\n try {\n process.exitCode = await runImport(o, {\n prompter: new TerminalPrompter(!!o.yes),\n openApi: () => new ConnectionsApi((channel = openAdminChannel({ dataDir: dataDirOf(o) }))),\n selfOrigins: self,\n });\n } finally {\n (channel as import(\"./admin.ts\").AdminChannel | null)?.close();\n }\n return;\n }\n process.exitCode = await withApi(o, (deps) => runImport(o, { prompter: deps.prompter, openApi: () => deps.api, selfOrigins: self }));\n });\n}\n"],"mappings":";;;;;AAWA,MAAM,QAAQ;AAEd,MAAM,WAAW,OAAe,aAAmC,CAAC,GAAI,YAAY,CAAC,GAAI,KAAK;AAE9F,MAAM,aAAa,MAAyB,eAAe,EAAE,MAAM,EAAE,OAAO,KAAK,CAAC;AAClF,MAAM,cAAc,QAAiB,IAAI,OAAO,oBAAoB,2DAA2D,eAAe,gBAAgB;AAE9J,eAAe,QAAW,GAAqC,KAA6H;CAC1L,MAAM,EAAE,iBAAiB,qBAAqB,MAAM,OAAO;CAC3D,MAAM,EAAE,mBAAmB,MAAM,OAAO;CACxC,MAAM,EAAE,qBAAqB,MAAM,OAAO;CAC1C,MAAM,UAAU,iBAAiB,EAAE,SAAS,UAAU,CAAC,EAAE,CAAC;CAC1D,MAAM,WAAW,gBAAgB,KAAK,QAAQ;EAC5C,MAAM,UAAU;GACd,QAAQ,MAAM;GACd,QAAQ,KAAK,QAAQ,WAAW,MAAM,OAAO,QAAQ,YAAY,KAAK,EAAE;EAC1E;EACA,QAAQ,KAAK,KAAK,CAAC;EACnB,OAAO,CAAC,KAAK,CAAC;CAChB,CAAC;CACD,IAAI;EACF,OAAO,MAAM,IAAI;GAAE,KAAK,IAAI,eAAe,OAAO;GAAG,UAAU,IAAI,iBAAiB,CAAC,CAAC,EAAE,GAAG;EAAE,CAAC;CAChG,UAAU;EACR,KAAK,MAAM,CAAC,KAAK,MAAM,UAAU,QAAQ,eAAe,KAAK,CAAC;EAC9D,QAAQ,MAAM;CAChB;AACF;;AAGA,eAAe,YAAY,SAAoC;CAC7D,MAAM,EAAE,oBAAoB,MAAM,OAAO;CACzC,MAAM,EAAE,iBAAiB,MAAM,OAAO;CACtC,MAAM,UAAU,gBAAgB,OAAO;CACvC,IAAI,CAAC,SAAS,OAAO,CAAC;CACtB,OAAO;EAAC,aAAa,OAAO;EAAG,oBAAoB,QAAQ;EAAQ,oBAAoB,QAAQ;EAAQ,gBAAgB,QAAQ;CAAM;AACvI;AAEA,SAAgB,4BAA4B,SAAwB;CAClE,MAAM,cAAc,QACjB,QAAQ,eAAe,EAAE,QAAQ,MAAqB,CAAC,CAAC,CACxD,YAAY,2FAA2F,CAAC,CACxG,QAAQ,sDAAsD,CAAC,CAC/D,UAAU,KAAK;CAElB,WACE,YACG,QAAQ,KAAK,CAAC,CACd,YAAY,6EAA6E,CAAC,CAC1F,SAAS,wBAAwB,yEAAyE,CAC/G,CAAC,CACE,OAAO,iBAAiB,kDAAkD,CAAC,CAC3E,OAAO,oBAAoB,2DAA2D,CAAC,CACvF,OAAO,kBAAkB,oEAAkE,OAAO,CAAC,CACnG,OAAO,mBAAmB,qEAAqE,CAAC,CAChG,OAAO,WAAW,mHAAmH,CAAC,CACtI,OAAO,4BAA4B,4GAA4G,CAAC,CAChJ,OAAO,aAAa,sFAAsF,CAAC,CAC3G,YACC,SACA;;IAEF,IAAI;IACJ,IAAI;IACJ,IAAI,gGACJ,CAAC,CACA,OAAO,OAAO,QAAgB,MAA0D;EACvF,MAAM,EAAE,kBAAkB,MAAM,OAAO;EACvC,QAAQ,WAAW,MAAM,QAAQ,IAAI,SAAS,cAAc,QAAQ,GAAG,IAAI,CAAC;CAC9E,CAAC;CAEH,WAAW,YAAY,QAAQ,MAAM,CAAC,CAAC,YAAY,+CAA+C,CAAC,CAAC,CAAC,OAAO,OAAO,MAAyB;EAC1I,MAAM,EAAE,oBAAoB,MAAM,OAAO;EACzC,QAAQ,WAAW,MAAM,QAAQ,IAAI,SAAS,gBAAgB,IAAI,CAAC;CACrE,CAAC;CAED,WAAW,YAAY,QAAQ,WAAW,CAAC,CAAC,YAAY,mDAAmD,CAAC,CAAC,SAAS,UAAU,uBAAuB,CAAC,CAAC,CACtJ,OAAO,WAAW,6DAA6D,CAAC,CAChF,OAAO,mBAAmB,sEAAsE,CAAC,CACjG,OAAO,OAAO,MAAc,MAA2D;EACtF,MAAM,EAAE,wBAAwB,MAAM,OAAO;EAC7C,QAAQ,WAAW,MAAM,QAAQ,IAAI,SAAS,oBAAoB,MAAM,GAAG,IAAI,CAAC;CAClF,CAAC;CAEH,WAAW,YAAY,QAAQ,SAAS,CAAC,CAAC,YAAY,iFAAiF,CAAC,CAAC,SAAS,UAAU,uBAAuB,CAAC,CAAC,CAClL,OAAO,aAAa,WAAW,CAAC,CAChC,OAAO,OAAO,MAAc,MAAwC;EACnE,MAAM,EAAE,sBAAsB,MAAM,OAAO;EAC3C,QAAQ,WAAW,MAAM,QAAQ,IAAI,SAAS,kBAAkB,MAAM,IAAI,CAAC;CAC7E,CAAC;CAEH,WAAW,YAAY,QAAQ,QAAQ,CAAC,CAAC,YAAY,gDAAgD,CAAC,CAAC,SAAS,UAAU,uBAAuB,CAAC,CAAC,CAChJ,OAAO,aAAa,WAAW,CAAC,CAChC,OAAO,OAAO,MAAc,MAAwC;EACnE,MAAM,EAAE,qBAAqB,MAAM,OAAO;EAC1C,QAAQ,WAAW,MAAM,QAAQ,IAAI,SAAS,iBAAiB,MAAM,IAAI,CAAC;CAC5E,CAAC;CAEH,WACE,QACG,QAAQ,UAAU,EAAE,QAAQ,MAAqB,CAAC,CAAC,CACnD,YAAY,4FAA4F,CAAC,CACzG,QAAQ,8DAA8D,CAAC,CACvE,UAAU,KAAK,CAAC,CAChB,eAAe,mBAAmB,sCAAsC,CAC7E,CAAC,CACE,OAAO,aAAa,qEAAqE,CAAC,CAC1F,OAAO,oBAAoB,gEAAgE,CAAC,CAC5F,OAAO,kBAAkB,sEAAoE,OAAO,CAAC,CACrG,OAAO,oBAAoB,iFAAiF,CAAC,CAC7G,OAAO,4BAA4B,0HAA0H,OAAO,CAAC,CACrK,OAAO,aAAa,8GAA8G,CAAC,CACnI,YACC,SACA;;;;;;;GAOH,eAAe;;;IAGd,IAAI;IACJ,IAAI,sBACJ,CAAC,CACA,OAAO,OAAO,MAA6D;EAC1E,MAAM,EAAE,cAAc,MAAM,OAAO;EACnC,MAAM,OAAO,MAAM,YAAY,UAAU,CAAC,CAAC;EAC3C,IAAI,EAAE,QAAQ;GAEZ,MAAM,EAAE,qBAAqB,MAAM,OAAO;GAC1C,MAAM,EAAE,qBAAqB,MAAM,OAAO;GAC1C,MAAM,EAAE,mBAAmB,MAAM,OAAO;GACxC,IAAI,UAAoD;GACxD,IAAI;IACF,QAAQ,WAAW,MAAM,UAAU,GAAG;KACpC,UAAU,IAAI,iBAAiB,CAAC,CAAC,EAAE,GAAG;KACtC,eAAe,IAAI,eAAgB,UAAU,iBAAiB,EAAE,SAAS,UAAU,CAAC,EAAE,CAAC,CAAE;KACzF,aAAa;IACf,CAAC;GACH,UAAU;IACR,SAAuD,MAAM;GAC/D;GACA;EACF;EACA,QAAQ,WAAW,MAAM,QAAQ,IAAI,SAAS,UAAU,GAAG;GAAE,UAAU,KAAK;GAAU,eAAe,KAAK;GAAK,aAAa;EAAK,CAAC,CAAC;CACrI,CAAC;AACL"}
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ import { i as underService, n as envVar, r as pickDir, t as LEGACY_PRODUCT } from "./brand--ZB8jTn1.js";
3
+ import { chmodSync, existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
4
+ import { join, resolve } from "node:path";
5
+ import { homedir } from "node:os";
6
+ //#region server/config.ts
7
+ const DEFAULT_PORT = 8425;
8
+ /** New installs keep their data here. */
9
+ const newDataDir = () => join(homedir(), `.xaq`);
10
+ /** Where installs from before the rename keep theirs; still used while `~/.xaq` doesn't exist. */
11
+ const legacyDataDir = () => join(homedir(), `.${LEGACY_PRODUCT}`);
12
+ /**
13
+ * The data directory when neither --data nor XAQ_DATA / AGENTINBOX_DATA says: `~/.xaq`, or `~/.agentinbox`
14
+ * when only that one exists (an install from before the rename). Nothing is moved: master.key stays put.
15
+ */
16
+ const defaultDataDir = () => resolve(pickDir(newDataDir(), legacyDataDir()).dir);
17
+ let toldLegacy = false;
18
+ /**
19
+ * The data directory: --data, else XAQ_DATA (AGENTINBOX_DATA), else the default. Says once per process
20
+ * when the default is the pre-rename `~/.agentinbox` (unless `quiet`).
21
+ */
22
+ function resolveDataDir(flag, opts = {}) {
23
+ const given = flag ?? (envVar("DATA") || void 0);
24
+ if (given !== void 0) return resolve(given);
25
+ const { dir, legacy } = pickDir(newDataDir(), legacyDataDir());
26
+ if (legacy && !opts.quiet && !toldLegacy) {
27
+ toldLegacy = true;
28
+ process.stderr.write(`xaq: using the existing data directory ${dir} (from before agentinbox was renamed to xaq; new installs use ${newDataDir()}).\n`);
29
+ }
30
+ return resolve(dir);
31
+ }
32
+ /**
33
+ * The database, lock and log files in a data directory: `xaq.*`, or in a data directory from before the
34
+ * rename (it has agentinbox.db and no xaq.db) `agentinbox.*`. The lock keeps the name an older
35
+ * agentinbox (from before the rename) still running on that directory uses, so the two never run at once.
36
+ */
37
+ function dataFiles(dataDir) {
38
+ const base = !existsSync(join(dataDir, `xaq.db`)) && existsSync(join(dataDir, `agentinbox.db`)) ? LEGACY_PRODUCT : "xaq";
39
+ return {
40
+ db: join(dataDir, `${base}.db`),
41
+ lock: join(dataDir, `${base}.lock`),
42
+ log: join(dataDir, `${base}.log`)
43
+ };
44
+ }
45
+ function loadConfig(input = {}, version = "0.0.0") {
46
+ const dataDir = resolveDataDir(input.dataDir);
47
+ const existed = hasFiles(dataDir);
48
+ mkdirSync(dataDir, {
49
+ recursive: true,
50
+ mode: 448
51
+ });
52
+ restrictDir(dataDir, existed);
53
+ return {
54
+ dataDir,
55
+ dbPath: dataFiles(dataDir).db,
56
+ masterKeyPath: join(dataDir, "master.key"),
57
+ host: input.host ?? process.env.HOST ?? "0.0.0.0",
58
+ port: input.port ?? (process.env.PORT ? Number(process.env.PORT) : 8425),
59
+ dev: input.dev ?? false,
60
+ trustProxy: process.env.TRUST_PROXY === "1",
61
+ service: underService(),
62
+ version,
63
+ google: {
64
+ oauthBase: envVar("GOOGLE_OAUTH_BASE") ?? "https://oauth2.googleapis.com",
65
+ authBase: envVar("GOOGLE_AUTH_BASE") ?? "https://accounts.google.com",
66
+ gmailBase: envVar("GOOGLE_GMAIL_BASE") ?? "https://gmail.googleapis.com",
67
+ pubsubBase: envVar("GOOGLE_PUBSUB_BASE") ?? "https://pubsub.googleapis.com",
68
+ calendarBase: envVar("GOOGLE_CALENDAR_BASE") ?? "https://www.googleapis.com"
69
+ }
70
+ };
71
+ }
72
+ const hasFiles = (dir) => {
73
+ try {
74
+ return readdirSync(dir).length > 0;
75
+ } catch {
76
+ return false;
77
+ }
78
+ };
79
+ /**
80
+ * An existing data dir may be group/world accessible (umask, manual mkdir): tighten it to owner-only.
81
+ * Only a directory that already held files gets a notice; a new (or empty) one is tightened silently.
82
+ */
83
+ function restrictDir(dir, existed) {
84
+ try {
85
+ if (process.platform !== "win32" && statSync(dir).mode & 63) {
86
+ chmodSync(dir, 448);
87
+ if (existed) process.stderr.write(`xaq: made ${dir} private to your user (it was readable by other users).\n`);
88
+ }
89
+ } catch {}
90
+ }
91
+ //#endregion
92
+ export { loadConfig as a, legacyDataDir as i, dataFiles as n, newDataDir as o, defaultDataDir as r, resolveDataDir as s, DEFAULT_PORT as t };
93
+
94
+ //# sourceMappingURL=config-C9XmYZWO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-C9XmYZWO.js","names":[],"sources":["../server/config.ts"],"sourcesContent":["import { chmodSync, existsSync, mkdirSync, readdirSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { envVar, LEGACY_PRODUCT, pickDir, PRODUCT, underService } from \"./brand.ts\";\n\nexport interface Config {\n dataDir: string;\n dbPath: string;\n masterKeyPath: string;\n host: string;\n port: number;\n /** Dev mode: console served by Vite on :5173, server only serves /api, /mcp, /oauth. */\n dev: boolean;\n /** Honor X-Forwarded-For / X-Forwarded-Proto (behind a reverse proxy). */\n trustProxy: boolean;\n /** Running under `xaq service install` (XAQ_SERVICE=1 in the unit/plist; AGENTINBOX_SERVICE=1 in units from before the rename). */\n service: boolean;\n version: string;\n /**\n * Base URLs for Google APIs. Overridable so tests can point at test/fake-google.ts.\n * Defaults: https://oauth2.googleapis.com, https://accounts.google.com, https://gmail.googleapis.com, https://pubsub.googleapis.com,\n * https://www.googleapis.com (Calendar: paths start with /calendar/v3).\n */\n google: { oauthBase: string; authBase: string; gmailBase: string; pubsubBase: string; calendarBase: string };\n}\n\nexport interface ConfigInput {\n dataDir?: string;\n host?: string;\n port?: number;\n dev?: boolean;\n}\n\nexport const DEFAULT_PORT = 8425;\n\n/** New installs keep their data here. */\nexport const newDataDir = () => join(homedir(), `.${PRODUCT}`);\n/** Where installs from before the rename keep theirs; still used while `~/.xaq` doesn't exist. */\nexport const legacyDataDir = () => join(homedir(), `.${LEGACY_PRODUCT}`);\n\n/**\n * The data directory when neither --data nor XAQ_DATA / AGENTINBOX_DATA says: `~/.xaq`, or `~/.agentinbox`\n * when only that one exists (an install from before the rename). Nothing is moved: master.key stays put.\n */\nexport const defaultDataDir = () => resolve(pickDir(newDataDir(), legacyDataDir()).dir);\n\nlet toldLegacy = false;\n\n/**\n * The data directory: --data, else XAQ_DATA (AGENTINBOX_DATA), else the default. Says once per process\n * when the default is the pre-rename `~/.agentinbox` (unless `quiet`).\n */\nexport function resolveDataDir(flag?: string, opts: { quiet?: boolean } = {}): string {\n const given = flag ?? (envVar(\"DATA\") || undefined);\n if (given !== undefined) return resolve(given);\n const { dir, legacy } = pickDir(newDataDir(), legacyDataDir());\n if (legacy && !opts.quiet && !toldLegacy) {\n toldLegacy = true;\n process.stderr.write(`${PRODUCT}: using the existing data directory ${dir} (from before agentinbox was renamed to ${PRODUCT}; new installs use ${newDataDir()}).\\n`);\n }\n return resolve(dir);\n}\n\n/**\n * The database, lock and log files in a data directory: `xaq.*`, or in a data directory from before the\n * rename (it has agentinbox.db and no xaq.db) `agentinbox.*`. The lock keeps the name an older\n * agentinbox (from before the rename) still running on that directory uses, so the two never run at once.\n */\nexport function dataFiles(dataDir: string): { db: string; lock: string; log: string } {\n const legacy = !existsSync(join(dataDir, `${PRODUCT}.db`)) && existsSync(join(dataDir, `${LEGACY_PRODUCT}.db`));\n const base = legacy ? LEGACY_PRODUCT : PRODUCT;\n return { db: join(dataDir, `${base}.db`), lock: join(dataDir, `${base}.lock`), log: join(dataDir, `${base}.log`) };\n}\n\nexport function loadConfig(input: ConfigInput = {}, version = \"0.0.0\"): Config {\n const dataDir = resolveDataDir(input.dataDir);\n // Only a directory that already had files in it can have exposed anything.\n const existed = hasFiles(dataDir);\n mkdirSync(dataDir, { recursive: true, mode: 0o700 });\n restrictDir(dataDir, existed);\n return {\n dataDir,\n dbPath: dataFiles(dataDir).db,\n masterKeyPath: join(dataDir, \"master.key\"),\n host: input.host ?? process.env.HOST ?? \"0.0.0.0\",\n port: input.port ?? (process.env.PORT ? Number(process.env.PORT) : DEFAULT_PORT),\n dev: input.dev ?? false,\n trustProxy: process.env.TRUST_PROXY === \"1\",\n service: underService(),\n version,\n google: {\n oauthBase: envVar(\"GOOGLE_OAUTH_BASE\") ?? \"https://oauth2.googleapis.com\",\n authBase: envVar(\"GOOGLE_AUTH_BASE\") ?? \"https://accounts.google.com\",\n gmailBase: envVar(\"GOOGLE_GMAIL_BASE\") ?? \"https://gmail.googleapis.com\",\n pubsubBase: envVar(\"GOOGLE_PUBSUB_BASE\") ?? \"https://pubsub.googleapis.com\",\n calendarBase: envVar(\"GOOGLE_CALENDAR_BASE\") ?? \"https://www.googleapis.com\",\n },\n };\n}\n\nconst hasFiles = (dir: string) => {\n try {\n return readdirSync(dir).length > 0;\n } catch {\n return false;\n }\n};\n\n/**\n * An existing data dir may be group/world accessible (umask, manual mkdir): tighten it to owner-only.\n * Only a directory that already held files gets a notice; a new (or empty) one is tightened silently.\n */\nfunction restrictDir(dir: string, existed: boolean): void {\n try {\n if (process.platform !== \"win32\" && statSync(dir).mode & 0o077) {\n chmodSync(dir, 0o700);\n if (existed) process.stderr.write(`xaq: made ${dir} private to your user (it was readable by other users).\\n`);\n }\n } catch {\n // Not ours to change; the files inside are still created owner-only.\n }\n}\n"],"mappings":";;;;;;AAiCA,MAAa,eAAe;;AAG5B,MAAa,mBAAmB,KAAK,QAAQ,GAAG,MAAa;;AAE7D,MAAa,sBAAsB,KAAK,QAAQ,GAAG,IAAI,gBAAgB;;;;;AAMvE,MAAa,uBAAuB,QAAQ,QAAQ,WAAW,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG;AAEtF,IAAI,aAAa;;;;;AAMjB,SAAgB,eAAe,MAAe,OAA4B,CAAC,GAAW;CACpF,MAAM,QAAQ,SAAS,OAAO,MAAM,KAAK,KAAA;CACzC,IAAI,UAAU,KAAA,GAAW,OAAO,QAAQ,KAAK;CAC7C,MAAM,EAAE,KAAK,WAAW,QAAQ,WAAW,GAAG,cAAc,CAAC;CAC7D,IAAI,UAAU,CAAC,KAAK,SAAS,CAAC,YAAY;EACxC,aAAa;EACb,QAAQ,OAAO,MAAM,0CAAiD,IAAI,gEAAuE,WAAW,EAAE,KAAK;CACrK;CACA,OAAO,QAAQ,GAAG;AACpB;;;;;;AAOA,SAAgB,UAAU,SAA4D;CAEpF,MAAM,OADS,CAAC,WAAW,KAAK,SAAS,QAAe,CAAC,KAAK,WAAW,KAAK,SAAS,eAAsB,CAAC,IACxF,iBAAA;CACtB,OAAO;EAAE,IAAI,KAAK,SAAS,GAAG,KAAK,IAAI;EAAG,MAAM,KAAK,SAAS,GAAG,KAAK,MAAM;EAAG,KAAK,KAAK,SAAS,GAAG,KAAK,KAAK;CAAE;AACnH;AAEA,SAAgB,WAAW,QAAqB,CAAC,GAAG,UAAU,SAAiB;CAC7E,MAAM,UAAU,eAAe,MAAM,OAAO;CAE5C,MAAM,UAAU,SAAS,OAAO;CAChC,UAAU,SAAS;EAAE,WAAW;EAAM,MAAM;CAAM,CAAC;CACnD,YAAY,SAAS,OAAO;CAC5B,OAAO;EACL;EACA,QAAQ,UAAU,OAAO,CAAC,CAAC;EAC3B,eAAe,KAAK,SAAS,YAAY;EACzC,MAAM,MAAM,QAAQ,QAAQ,IAAI,QAAQ;EACxC,MAAM,MAAM,SAAS,QAAQ,IAAI,OAAO,OAAO,QAAQ,IAAI,IAAI,IAAA;EAC/D,KAAK,MAAM,OAAO;EAClB,YAAY,QAAQ,IAAI,gBAAgB;EACxC,SAAS,aAAa;EACtB;EACA,QAAQ;GACN,WAAW,OAAO,mBAAmB,KAAK;GAC1C,UAAU,OAAO,kBAAkB,KAAK;GACxC,WAAW,OAAO,mBAAmB,KAAK;GAC1C,YAAY,OAAO,oBAAoB,KAAK;GAC5C,cAAc,OAAO,sBAAsB,KAAK;EAClD;CACF;AACF;AAEA,MAAM,YAAY,QAAgB;CAChC,IAAI;EACF,OAAO,YAAY,GAAG,CAAC,CAAC,SAAS;CACnC,QAAQ;EACN,OAAO;CACT;AACF;;;;;AAMA,SAAS,YAAY,KAAa,SAAwB;CACxD,IAAI;EACF,IAAI,QAAQ,aAAa,WAAW,SAAS,GAAG,CAAC,CAAC,OAAO,IAAO;GAC9D,UAAU,KAAK,GAAK;GACpB,IAAI,SAAS,QAAQ,OAAO,MAAM,aAAa,IAAI,0DAA0D;EAC/G;CACF,QAAQ,CAER;AACF"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { n as dataFiles, a as loadConfig, s as resolveDataDir } from "./config-C9XmYZWO.js";
@@ -0,0 +1,362 @@
1
+ #!/usr/bin/env node
2
+ import { i as CAPABILITIES } from "./types-BhJDbSzE.js";
3
+ import { t as NPX } from "./version-BxH7OuWD.js";
4
+ import { i as saveProfile, n as loadProfile, r as profileExists, t as listProfiles } from "./profile-NmMKAczz.js";
5
+ import { c as profilePath, p as validProfileName } from "./paths-yzOKD6ow.js";
6
+ import { m as writeClaudeMcpConfig } from "./mcpheader-C9H4vudu.js";
7
+ import { S as insecureBanner, a as TOOL_LABELS, i as TOOLS, m as recipe, v as shellQuote, w as normalizeDaemonUrl, x as checkTransport } from "./recipes-DG_-doGq.js";
8
+ import { i as safeText, n as box, r as formatDuration, t as StatusLine } from "./ui-B8Sd4Y7Y.js";
9
+ import { c as mcpTarget, u as setupMcp } from "./mcpsetup-DXXo2qjf.js";
10
+ import { D as ApiError, F as startPairing, N as getSelf, P as pollPairing, j as clientInfo, p as defaultConsumer, y as validConsumer } from "./hermes-DHnDTYJp.js";
11
+ import { m as hasAccess, p as describeGrants } from "./listen-B9iO7HgQ.js";
12
+ import { i as isServiceInstalled } from "./service-C7jfo-Ex.js";
13
+ import { closeSync, constants, fchmodSync, lstatSync, openSync, writeSync } from "node:fs";
14
+ import { hostname } from "node:os";
15
+ import pc from "picocolors";
16
+ //#region server/listen/connect.ts
17
+ /** Exit codes, so scripts (and agents setting themselves up) can tell outcomes apart. */
18
+ const CONNECT_EXIT = {
19
+ approved: 0,
20
+ error: 1,
21
+ denied: 2,
22
+ expired: 3,
23
+ consumed: 4,
24
+ cancelled: 130
25
+ };
26
+ function parseTool(value) {
27
+ if (value === void 0) return "other";
28
+ const v = value.trim().toLowerCase().replace(/^claude$/, "claude-code");
29
+ if (!TOOLS.includes(v)) throw new Error(`Unknown --tool "${value}". Choose one of: ${TOOLS.join(", ")}.`);
30
+ return v;
31
+ }
32
+ function parseCapabilities(value) {
33
+ if (value === void 0) return void 0;
34
+ const list = value.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
35
+ const bad = list.filter((c) => !CAPABILITIES.includes(c));
36
+ if (bad.length) throw new Error(`Unknown capabilit${bad.length > 1 ? "ies" : "y"} ${bad.join(", ")}. Choose from: ${CAPABILITIES.join(", ")}.`);
37
+ return [...new Set(list)];
38
+ }
39
+ const shortHost = () => hostname().replace(/\.(local|lan|home)$/i, "");
40
+ const sleep = (ms, signal) => new Promise((resolve) => {
41
+ const t = setTimeout(resolve, ms);
42
+ signal?.addEventListener("abort", () => {
43
+ clearTimeout(t);
44
+ resolve();
45
+ }, { once: true });
46
+ });
47
+ const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
48
+ /** --log target (fd), written in parallel with the console. */
49
+ let logFd = null;
50
+ const toLog = (s) => {
51
+ if (logFd !== null) writeSync(logFd, stripAnsi(s) + "\n");
52
+ };
53
+ const out = (s = "") => {
54
+ process.stdout.write(`${s}\n`);
55
+ toLog(s);
56
+ };
57
+ const err = (s = "") => {
58
+ process.stderr.write(`${s}\n`);
59
+ toLog(s);
60
+ };
61
+ /**
62
+ * Opens --log safely: a new file is created 0600 with O_EXCL; an existing one (e.g. from `mktemp`) is
63
+ * only reused if it is a regular file owned by us, opened without following symlinks. This is what
64
+ * keeps another local user from pre-creating the log and reading the pairing as it happens.
65
+ */
66
+ function openConnectLog(path) {
67
+ try {
68
+ return openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
69
+ } catch (e) {
70
+ if (e.code !== "EEXIST") throw e;
71
+ }
72
+ const st = lstatSync(path);
73
+ const uid = process.getuid?.();
74
+ if (!st.isFile() || uid !== void 0 && st.uid !== uid) throw new Error(`Refusing --log ${path}: it exists and is not a regular file owned by you.`);
75
+ const fd = openSync(path, constants.O_WRONLY | constants.O_TRUNC | constants.O_NOFOLLOW);
76
+ fchmodSync(fd, 384);
77
+ return fd;
78
+ }
79
+ async function runConnect(rawUrl, flags, opts = {}) {
80
+ if (flags.log) logFd = openConnectLog(flags.log);
81
+ try {
82
+ return await connect(rawUrl, flags, opts);
83
+ } finally {
84
+ if (logFd !== null) closeSync(logFd);
85
+ logFd = null;
86
+ }
87
+ }
88
+ async function connect(rawUrl, flags, opts) {
89
+ const url = normalizeDaemonUrl(rawUrl);
90
+ const verdict = await checkTransport(url, { insecure: flags.insecure });
91
+ if (verdict.warning) err(insecureBanner(verdict.warning));
92
+ const profileName = validProfileName(flags.profile ?? "default");
93
+ let replacing = null;
94
+ if (profileExists(profileName) && flags.force) try {
95
+ replacing = loadProfile(profileName);
96
+ } catch {}
97
+ if (profileExists(profileName) && !flags.force) {
98
+ let who = "";
99
+ try {
100
+ const p = loadProfile(profileName);
101
+ who = ` (agent "${safeText(p.agentName)}" on ${p.url})`;
102
+ } catch {}
103
+ const others = listProfiles();
104
+ throw new Error(`Profile "${profileName}" already exists${who}.\n Pair a second agent with --profile <name>${others.length > 1 ? ` (existing: ${others.join(", ")})` : ""}, or replace this one with --force.`);
105
+ }
106
+ const tool = parseTool(flags.tool);
107
+ const capabilities = parseCapabilities(flags.capabilities);
108
+ const consumer = validConsumer(flags.consumer ?? replacing?.consumer ?? defaultConsumer(profileName));
109
+ const name = (flags.name?.trim() || `${tool === "other" ? "Agent" : TOOL_LABELS[tool]} on ${shortHost()}`).slice(0, 80);
110
+ const input = {
111
+ name,
112
+ requested: {
113
+ ...flags.account?.length ? { accounts: flags.account } : {},
114
+ ...capabilities ? { capabilities } : {},
115
+ ...flags.reason ? { reason: flags.reason } : {}
116
+ },
117
+ client: {
118
+ ...clientInfo(),
119
+ tool
120
+ }
121
+ };
122
+ let start;
123
+ try {
124
+ start = await startPairing(url, input);
125
+ } catch (e) {
126
+ if (e instanceof ApiError && e.status === 404) throw new Error(`${url} doesn't support pairing (no /agent/v1/pairings). Is this a xaq daemon, and is it up to date?`);
127
+ if (e instanceof ApiError) throw new Error(`${e.message}${e.hint ? `\n ${e.hint}` : ""}`);
128
+ throw e;
129
+ }
130
+ const links = approveLinks(url, start);
131
+ out();
132
+ out(box([
133
+ pc.bold("Approve this agent in the xaq console"),
134
+ "",
135
+ `Code: ${pc.bold(pc.yellow(safeText(start.userCode, 40)))}`,
136
+ `Open: ${pc.cyan(safeText(links.primary, 300))}`,
137
+ ...links.publicUrl ? [` or: ${pc.cyan(safeText(links.publicUrl, 300))}`, pc.gray(` (first: the address this CLI used; second: the console's public URL setting)`)] : [],
138
+ `Agent: ${safeText(name)}`,
139
+ "",
140
+ pc.gray("Only approve if the console shows this exact code.")
141
+ ]));
142
+ out();
143
+ const ac = new AbortController();
144
+ const onAbort = () => ac.abort();
145
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
146
+ const onSigint = () => ac.abort();
147
+ if (opts.handleSignals) process.on("SIGINT", onSigint);
148
+ const started = Date.now();
149
+ const expiresAt = Date.parse(start.expiresAt) || started + 6e5;
150
+ let interval = Math.max(1, start.interval || 5) * 1e3;
151
+ let note = "";
152
+ const status = new StatusLine(process.stderr);
153
+ status.start(() => `Waiting for approval · ${formatDuration(Date.now() - started)} elapsed · code expires in ${formatDuration(expiresAt - Date.now())}${note ? pc.yellow(` · ${note}`) : ""}`);
154
+ let result = null;
155
+ try {
156
+ while (!ac.signal.aborted) {
157
+ await sleep(opts.pollIntervalMs ?? interval, ac.signal);
158
+ if (ac.signal.aborted) break;
159
+ try {
160
+ const r = await pollPairing(url, start.deviceCode);
161
+ note = "";
162
+ if (r.status === "pending") continue;
163
+ if (r.status === "slow_down") {
164
+ interval = Math.max(interval + 1e3, (r.interval || 0) * 1e3);
165
+ continue;
166
+ }
167
+ result = r;
168
+ break;
169
+ } catch (e) {
170
+ if (e instanceof ApiError && e.status === 429) {
171
+ interval += 5e3;
172
+ note = "rate limited, slowing down";
173
+ continue;
174
+ }
175
+ if (e instanceof ApiError && e.status >= 400 && e.status < 500 && e.status !== 408) throw new Error(`${e.message}${e.hint ? `\n ${e.hint}` : ""}`);
176
+ note = "can't reach the daemon, retrying";
177
+ if (Date.now() > expiresAt + 3e4) {
178
+ result = { status: "expired" };
179
+ break;
180
+ }
181
+ }
182
+ }
183
+ } finally {
184
+ status.stop();
185
+ if (opts.handleSignals) process.off("SIGINT", onSigint);
186
+ opts.signal?.removeEventListener("abort", onAbort);
187
+ }
188
+ if (!result) {
189
+ err(pc.yellow("Cancelled. Nothing was approved; the code expires on its own."));
190
+ return CONNECT_EXIT.cancelled;
191
+ }
192
+ switch (result.status) {
193
+ case "denied":
194
+ err(pc.red(`✖ The request was denied in the console${result.note ? `: ${safeText(result.note, 300)}` : "."}`));
195
+ return CONNECT_EXIT.denied;
196
+ case "expired":
197
+ err(pc.red("✖ The code expired before it was approved."));
198
+ err(` Run the same command again for a fresh code, and approve it within ${formatDuration(Date.parse(start.expiresAt) - started)}.`);
199
+ return CONNECT_EXIT.expired;
200
+ case "consumed":
201
+ err(pc.red("✖ This pairing was already completed and its key handed out once (perhaps by an earlier run of this command)."));
202
+ err(` For safety the key is never delivered twice. Start over with \`${NPX} connect\`, or rotate the agent's key in the console.`);
203
+ return CONNECT_EXIT.consumed;
204
+ case "approved": break;
205
+ default: throw new Error(`Unexpected pairing status ${JSON.stringify(result.status)}`);
206
+ }
207
+ const profile = {
208
+ ...replacing ? {
209
+ types: replacing.types,
210
+ filter: replacing.filter,
211
+ includeBody: replacing.includeBody,
212
+ exec: replacing.exec,
213
+ forward: replacing.forward,
214
+ forwardSecret: replacing.forwardSecret,
215
+ concurrency: replacing.concurrency,
216
+ timeoutSeconds: replacing.timeoutSeconds,
217
+ maxAttempts: replacing.maxAttempts,
218
+ cwd: replacing.cwd
219
+ } : {},
220
+ url,
221
+ apiKey: result.apiKey,
222
+ agentId: result.agent.id,
223
+ agentName: result.agent.name,
224
+ consumer,
225
+ ...verdict.kind === "insecure" ? { insecure: true } : {},
226
+ tool,
227
+ mcpUrl: `${url}/mcp`,
228
+ skillUrl: `${url}/skill.md`,
229
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
230
+ };
231
+ const path = saveProfile(profileName, profile);
232
+ writeClaudeMcpConfig(profileName, profile.mcpUrl);
233
+ if (typeof result.mcpUrl === "string" && result.mcpUrl !== profile.mcpUrl) err(pc.yellow(`! The daemon advertises its MCP endpoint as ${safeText(result.mcpUrl, 200)} (its "public URL" setting); using ${profile.mcpUrl}, the address you connected to. Set the public URL in the console if agents should use another address.`));
234
+ out(`${pc.green("✔")} Approved. Agent ${pc.bold(safeText(result.agent.name))} ${pc.gray(`(${result.agent.id})`)} is connected.`);
235
+ out(` Key saved to ${pc.cyan(path)} ${pc.gray("(mode 0600; never displayed. Rotate it in the console if it leaks.)")}`);
236
+ if (replacing && (replacing.exec || replacing.forward)) out(` Kept this profile's listener settings. Restart the listener to use the new key${isServiceInstalled(profileName) ? `: ${NPX} listen service install${profileName === "default" ? "" : ` --profile ${profileName}`}` : "."}`);
237
+ if (replacing && replacing.agentId !== profile.agentId) err(pc.yellow(`! Replaced profile "${profileName}" (agent "${safeText(replacing.agentName)}", ${replacing.agentId}). Its key still works until you rotate it or delete that agent in the console; config backups made by setup-mcp (*.xaq-backup-*, or *.agentinbox-backup-* from before the rename) may contain it.`));
238
+ const access = await printGrants(url, profile.apiKey, profileName);
239
+ let mcp = null;
240
+ if (flags.setupMcp) {
241
+ mcp = setupMcp(tool, mcpTarget(profileName, profile), path);
242
+ printMcpResult(mcp);
243
+ }
244
+ printNextSteps(profileName, profile, tool, mcp, { mail: access ? access.grants.length > 0 : true });
245
+ return CONNECT_EXIT.approved;
246
+ }
247
+ /**
248
+ * The approve link. The daemon builds it from its public URL setting (default http://localhost:<port>),
249
+ * which may not be the address this CLI reached it at (127.0.0.1, a LAN IP, an SSH tunnel's port): then
250
+ * the link on the address we used comes first, and the public-URL one is shown as well.
251
+ */
252
+ function approveLinks(url, start) {
253
+ let advertised;
254
+ try {
255
+ advertised = new URL(start.verificationUrl);
256
+ } catch {
257
+ return {
258
+ primary: start.verificationUrl,
259
+ publicUrl: null
260
+ };
261
+ }
262
+ const ours = new URL(url);
263
+ if (advertised.origin === ours.origin) return {
264
+ primary: start.verificationUrl,
265
+ publicUrl: null
266
+ };
267
+ const rest = `${advertised.search}${advertised.hash}`;
268
+ return {
269
+ primary: advertised.pathname.endsWith("/approvals") ? `${url}/approvals${rest}` : `${ours.origin}${advertised.pathname}${rest}`,
270
+ publicUrl: start.verificationUrl
271
+ };
272
+ }
273
+ /** What the approver actually granted, in plain words (the console's choice decides, not the request). Null when unreadable. */
274
+ async function printGrants(url, key, profileName) {
275
+ const p = profileName === "default" ? "" : ` --profile ${profileName}`;
276
+ try {
277
+ const self = await getSelf(url, key, 8e3);
278
+ out(` ${pc.bold("Access granted")} ${pc.gray("(chosen by whoever approved it; change it in the console under Agents):")}`);
279
+ for (const line of describeGrants(self.grants, self.connections)) out(` ${hasAccess(self) ? line : pc.yellow(line)}`);
280
+ return self;
281
+ } catch (e) {
282
+ err(pc.yellow(`! Couldn't read this agent's access yet (${e instanceof Error ? e.message.split("\n")[0] : String(e)}). Check it with: ${NPX} listen status${p}`));
283
+ return null;
284
+ }
285
+ }
286
+ function printMcpResult(r) {
287
+ if (r.ok) out(`${r.dryRun ? pc.cyan("○") : pc.green("✔")} ${r.message}`);
288
+ else err(`${pc.yellow("!")} ${r.message}`);
289
+ if (r.ok && r.changes?.length) for (const l of r.changes) out(l.startsWith(" ") ? pc.gray(` ${l}`) : ` - ${l}`);
290
+ if (r.backup) out(pc.gray(` Backup: ${r.backup}`));
291
+ if (!r.ok && r.manual?.length) {
292
+ out(" Do it by hand:");
293
+ for (const l of r.manual) out(` ${l}`);
294
+ }
295
+ if (r.ok && r.verify?.length) out(` ${r.dryRun ? "Afterwards, check it with" : "Check it with"}: ${r.verify.map((v) => pc.cyan(v)).join("; ")}`);
296
+ if (r.ok) for (const n of r.notes ?? []) out(` ${n}`);
297
+ }
298
+ function skillCommand(tool, url) {
299
+ switch (tool) {
300
+ case "claude-code": return `${NPX} skill install --target claude --url ${url}`;
301
+ case "codex": return `${NPX} skill install --target codex --url ${url}`;
302
+ case "hermes": return `${NPX} skill install --target ~/.hermes/skills/email --url ${url}`;
303
+ case "cursor": return `${NPX} skill install --target agents --url ${url}`;
304
+ default: return `${NPX} skill install --target ./skills --url ${url}`;
305
+ }
306
+ }
307
+ /** `listen hermes … --run`: creates the Hermes route (the console shows the same command). */
308
+ const hermesRouteCommand = (profileName) => `${NPX} listen hermes${profileName === "default" ? "" : ` --profile ${profileName}`} --deliver telegram --run`;
309
+ function printNextSteps(profileName, profile, tool, mcp, o = {}) {
310
+ const mail = o.mail !== false;
311
+ const p = profileName === "default" ? "" : ` --profile ${profileName}`;
312
+ const label = tool === "other" ? "your agent" : TOOL_LABELS[tool];
313
+ const r = recipe(tool, profileName);
314
+ const target = tool === "other" ? `--exec ${shellQuote(r.exec)}` : `--recipe ${tool}`;
315
+ out();
316
+ out(pc.bold("Next steps"));
317
+ let n = 1;
318
+ if (mcp?.ok) out(` ${n++}. ${label} has the xaq MCP tools ${pc.gray(`(restart it if it's running)`)}.`);
319
+ else if (tool === "other") {
320
+ out(` ${n++}. Point your agent's MCP client at ${pc.cyan(profile.mcpUrl ?? `${profile.url}/mcp`)} with header Authorization: Bearer <key>`);
321
+ out(pc.gray(` The key is the "apiKey" field in ${profilePath(profileName)}.`));
322
+ } else {
323
+ out(` ${n++}. Give ${label} the xaq MCP tools without ever seeing the key:`);
324
+ out(` ${pc.cyan(`${NPX} setup-mcp --tool ${tool}${p}`)} ${pc.gray("(add --dry-run to see exactly what it changes first)")}`);
325
+ }
326
+ out(` ${n++}. Install the xaq skill so ${label} knows the rules:`);
327
+ out(` ${pc.cyan(skillCommand(tool, profile.url))}`);
328
+ if (tool === "hermes") {
329
+ out(` ${n++}. Create the Hermes gateway route the listener forwards to, saying where Hermes should tell you (once; needs the gateway's webhook platform on):`);
330
+ out(` ${pc.cyan(hermesRouteCommand(profileName))}`);
331
+ out(pc.gray(` Or --deliver discord|slack|…, or --deliver-chat-id <id>. It sets the listener's secret without showing it and limits the route to the xaq tools.`));
332
+ }
333
+ if (mail) {
334
+ out(` ${n++}. Wake ${label} when mail arrives. First try it in the foreground (Ctrl-C stops it; --save keeps the command in the profile):`);
335
+ out(` ${pc.cyan(`${NPX} listen${p} --save ${target}`)}`);
336
+ out(pc.gray(` Only new mail wakes it (message.received), not mail you send. Change it with --types, e.g. --types thread.reply_received (only replies to threads you sent) or --types all.`));
337
+ out(pc.gray(` Gmail's Promotions, Social and Forums tabs are skipped (--all-categories to include them); skip more with --not-from '*@github.com'. --batch 60s runs a burst of mail once.`));
338
+ } else {
339
+ out(` ${n++}. Wake ${label} when one of its connections needs it. First try it in the foreground (Ctrl-C stops it; --save keeps the command in the profile):`);
340
+ out(` ${pc.cyan(`${NPX} listen${p} --save ${target} --types approval.requested,connection.auth_failed,connection.expiring`)}`);
341
+ out(pc.gray(` Add an integration's own events too, e.g. whoop.recovery_updated.`));
342
+ }
343
+ out(pc.gray(` What the woken agent can use: ${r.confinement}.`));
344
+ out(pc.gray(` Where its output goes: ${r.output}`));
345
+ for (const note of r.notes.filter((x) => tool !== "hermes" || !x.startsWith("Create the Hermes route"))) out(pc.gray(` ${note}`));
346
+ if (tool !== "other") {
347
+ out(pc.gray(` It runs: ${r.runs}`));
348
+ if (r.exec) out(pc.gray(` To change it: ${NPX} listen${p} --print-recipe ${tool} prints it; pass your edit with --exec '…' instead of --recipe.`));
349
+ }
350
+ if (mail) {
351
+ out(` ${n++}. Check that it works: in the console's setup, the "Check that it works" step sends a test email.`);
352
+ out(` Watch it arrive in the foreground listener as ${pc.cyan("→ #… message.received")} followed by one ${pc.green("✓")} line once ${tool === "hermes" ? "Hermes accepted it (Hermes then triages it and tells you if it matters)" : `${label} handled it`}.`);
353
+ }
354
+ out(` ${n++}. Then keep it running in the background (systemd / launchd, starts at login; stop the foreground one first):`);
355
+ out(` ${pc.cyan(`${NPX} listen service install${p}`)}`);
356
+ out(` ${pc.cyan(`${NPX} listen status${p}`)} ${pc.gray("(connection, cursor, recent events and the access granted)")}`);
357
+ out();
358
+ }
359
+ //#endregion
360
+ export { parseTool, printMcpResult, runConnect };
361
+
362
+ //# sourceMappingURL=connect-TtJ9n0Rw.js.map