xaq-cli 0.0.0 → 0.1.0-nightly.72

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,910 @@
1
+ #!/usr/bin/env node
2
+ import { t as NPX } from "./version-BxH7OuWD.js";
3
+ import { a as mcpServerName, n as loadProfile, r as profileExists } from "./profile-NmMKAczz.js";
4
+ import { c as profilePath } from "./paths-yzOKD6ow.js";
5
+ import "./recipes-DG_-doGq.js";
6
+ import { i as safeText } from "./ui-B8Sd4Y7Y.js";
7
+ import { _ as readTomlMcpServers, a as cursorConfigFile, c as mcpTarget, g as UNREADABLE$1, h as readTopMap, i as codexConfigFile, m as readMcpServers, n as claudeConfigFile, p as UNREADABLE, s as hermesHome, u as setupMcp } from "./mcpsetup-DXXo2qjf.js";
8
+ import { _ as chooseAccess, a as isWorking, d as waitWorking, f as reconnectHint, g as agentsLabel, h as accessLabel, m as checkTarget, p as slugify, v as chooseAgents, y as parseAccess } from "./manage-Bo6COB4p.js";
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import pc from "picocolors";
12
+ //#region server/connections-cli/routes.ts
13
+ /**
14
+ * Places in a client's config that confine something to the xaq MCP server alone: Hermes platform toolsets,
15
+ * webhook routes (`toolsets: ["mcp-<server>", …]`) and cron jobs (`enabled_toolsets: ["<server>"]`). Once the
16
+ * imported servers are served through xaq, each of these also sees their tools, so `import` warns before
17
+ * replacing anything (docs/listen.md: an email-triggered route confined to xaq).
18
+ */
19
+ function confinedRoutes(client, serverName, env = process.env) {
20
+ if (client !== "hermes") return [];
21
+ const home = hermesHome(env);
22
+ const names = /* @__PURE__ */ new Set([serverName, `mcp-${serverName}`]);
23
+ const confines = (v) => Array.isArray(v) && v.some((x) => typeof x === "string" && names.has(x));
24
+ const out = [];
25
+ const read = (file) => {
26
+ try {
27
+ return existsSync(file) ? JSON.parse(readFileSync(file, "utf8").replace(/^/, "")) : null;
28
+ } catch {
29
+ return null;
30
+ }
31
+ };
32
+ try {
33
+ const config = join(home, "config.yaml");
34
+ if (existsSync(config)) {
35
+ const yaml = readFileSync(config, "utf8");
36
+ const platforms = readTopMap(yaml, "platform_toolsets") ?? {};
37
+ for (const [platform, toolsets] of Object.entries(platforms)) if (confines(toolsets)) out.push(`platform_toolsets.${platform} (config.yaml)`);
38
+ const get = (v, k) => v && typeof v === "object" && !Array.isArray(v) ? v[k] : void 0;
39
+ const routes = get(get(get(readTopMap(yaml, "platforms") ?? void 0, "webhook"), "extra"), "routes");
40
+ if (routes === UNREADABLE || Array.isArray(routes) && routes.some((r) => r === UNREADABLE)) out.push("webhook routes in config.yaml (platforms.webhook.extra.routes: couldn't be read, so check their toolsets)");
41
+ else if (Array.isArray(routes)) routes.forEach((r, i) => {
42
+ if (confines(get(r, "toolsets"))) out.push(`webhook route ${String(get(r, "name") ?? `#${i + 1}`)} (config.yaml)`);
43
+ });
44
+ else if (routes && typeof routes === "object") {
45
+ for (const [name, r] of Object.entries(routes)) if (confines(get(r, "toolsets"))) out.push(`webhook route ${name} (config.yaml)`);
46
+ }
47
+ }
48
+ } catch {}
49
+ const subs = read(join(home, "webhook_subscriptions.json"));
50
+ if (subs && typeof subs === "object" && !Array.isArray(subs)) {
51
+ for (const [route, r] of Object.entries(subs)) if (confines(r?.toolsets)) out.push(`webhook route ${route}`);
52
+ }
53
+ const cron = read(join(home, "cron", "jobs.json"));
54
+ const jobs = Array.isArray(cron) ? cron : cron?.jobs ?? [];
55
+ if (Array.isArray(jobs)) {
56
+ for (const j of jobs) if (confines(j?.enabled_toolsets)) out.push(`cron job ${String(j.name ?? j.id ?? "?")}`);
57
+ }
58
+ return out;
59
+ }
60
+ //#endregion
61
+ //#region server/connections-cli/clients.ts
62
+ /** Reads the MCP servers an agent's own tool is configured with (for `import --from`). */
63
+ const IMPORT_CLIENTS = [
64
+ "hermes",
65
+ "claude-code",
66
+ "cursor",
67
+ "codex"
68
+ ];
69
+ const CLIENT_LABELS = {
70
+ hermes: "Hermes",
71
+ "claude-code": "Claude Code",
72
+ cursor: "Cursor",
73
+ codex: "Codex"
74
+ };
75
+ const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : void 0;
76
+ function droppedFrom(e, filters, settingKeys) {
77
+ const d = { settings: settingKeys.filter((k) => e[k] !== void 0 && e[k] !== null) };
78
+ const inc = strings(filters.include);
79
+ const exc = strings(filters.exclude);
80
+ if (inc?.length) d.include = inc;
81
+ if (exc?.length) d.exclude = exc;
82
+ return d.include || d.exclude || d.settings.length ? d : void 0;
83
+ }
84
+ /** Parses a dotenv file (KEY=VALUE, optional `export`, quotes). */
85
+ function parseDotenv(text) {
86
+ const out = {};
87
+ for (const raw of text.split("\n")) {
88
+ const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(raw);
89
+ if (!m) continue;
90
+ let v = m[2];
91
+ if (v.startsWith("\"") && v.endsWith("\"") && v.length >= 2 || v.startsWith("'") && v.endsWith("'") && v.length >= 2) v = v.slice(1, -1);
92
+ else v = v.replace(/\s+#.*$/, "");
93
+ out[m[1]] = v;
94
+ }
95
+ return out;
96
+ }
97
+ /** `${VAR}` (and `${VAR:-default}`, `${env:VAR}`) from `vars`. */
98
+ function expandVars(value, vars) {
99
+ const used = [];
100
+ let missing = null;
101
+ const out = value.replace(/\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g, (_m, name, def) => {
102
+ const v = vars[name];
103
+ if (v !== void 0 && v !== "") {
104
+ used.push(name);
105
+ return v;
106
+ }
107
+ if (def !== void 0) return def;
108
+ missing ??= name;
109
+ return "";
110
+ });
111
+ return missing ? { missing } : {
112
+ value: out,
113
+ vars: used
114
+ };
115
+ }
116
+ const isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
117
+ const readJson = (file) => {
118
+ if (!existsSync(file)) return null;
119
+ const text = readFileSync(file, "utf8");
120
+ if (!text.trim()) return {};
121
+ try {
122
+ const doc = JSON.parse(text);
123
+ if (!isObj(doc)) throw new Error("not an object");
124
+ return doc;
125
+ } catch {
126
+ throw new Error(`${file} isn't plain JSON (comments?), so it can't be read automatically.`);
127
+ }
128
+ };
129
+ /** Shared by the JSON-shaped clients (Claude Code, Cursor): one `mcpServers` entry. */
130
+ function jsonEntry(name, raw, file, vars, extra = {}) {
131
+ const base = {
132
+ name,
133
+ file,
134
+ transport: "http",
135
+ headers: {},
136
+ headerVars: [],
137
+ headerSources: {},
138
+ oauth: false,
139
+ enabled: true,
140
+ ...extra
141
+ };
142
+ if (!isObj(raw)) return {
143
+ ...base,
144
+ problem: "this entry isn't an object"
145
+ };
146
+ if (raw.disabled === true || raw.enabled === false) base.enabled = false;
147
+ const type = typeof raw.type === "string" ? raw.type.toLowerCase() : null;
148
+ if (type === "stdio" || typeof raw.command === "string" && !raw.url) {
149
+ const args = Array.isArray(raw.args) ? raw.args.map(String) : [];
150
+ return {
151
+ ...base,
152
+ transport: "stdio",
153
+ command: [String(raw.command ?? ""), ...args].join(" ").trim()
154
+ };
155
+ }
156
+ if (typeof raw.url !== "string") return {
157
+ ...base,
158
+ problem: "no url"
159
+ };
160
+ const url = expandVars(raw.url, vars);
161
+ if ("missing" in url) return {
162
+ ...base,
163
+ problem: `its url uses \${${url.missing}}, which isn't set here`
164
+ };
165
+ const entry = {
166
+ ...base,
167
+ url: url.value,
168
+ transport: type === "sse" ? "sse" : "http"
169
+ };
170
+ if (raw.headersHelper !== void 0 || raw.headers_helper !== void 0) return {
171
+ ...entry,
172
+ problem: "it gets its headers from a helper command, which the broker can't run"
173
+ };
174
+ if (raw.oauth !== void 0 || raw.auth !== void 0) entry.oauth = true;
175
+ return withHeaders(entry, raw.headers, vars);
176
+ }
177
+ function withHeaders(entry, headers, vars) {
178
+ if (headers === void 0 || headers === null) return entry;
179
+ if (!isObj(headers)) return {
180
+ ...entry,
181
+ problem: "its headers can't be read"
182
+ };
183
+ for (const [k, v] of Object.entries(headers)) {
184
+ if (typeof v !== "string") return {
185
+ ...entry,
186
+ problem: `its ${k} header can't be read`
187
+ };
188
+ const x = expandVars(v, vars);
189
+ if ("missing" in x) return {
190
+ ...entry,
191
+ problem: `its ${k} header uses \${${x.missing}}, which isn't set here`
192
+ };
193
+ entry.headers[k] = x.value;
194
+ entry.headerVars.push(...x.vars);
195
+ entry.headerSources[k] = x.vars;
196
+ }
197
+ return entry;
198
+ }
199
+ function hermesFiles(env = process.env) {
200
+ const home = hermesHome(env);
201
+ return {
202
+ config: join(home, "config.yaml"),
203
+ env: join(home, ".env")
204
+ };
205
+ }
206
+ function readHermes(opts) {
207
+ const env = opts.env ?? process.env;
208
+ const files = hermesFiles(env);
209
+ const result = {
210
+ client: "hermes",
211
+ files: [files.config],
212
+ entries: []
213
+ };
214
+ if (!existsSync(files.config)) return result;
215
+ const servers = readMcpServers(readFileSync(files.config, "utf8"));
216
+ if (!servers) throw new Error(`${files.config} has an mcp_servers layout this can't read (flow style, several blocks, or tabs). Nothing was changed.`);
217
+ const vars = {
218
+ ...env,
219
+ ...existsSync(files.env) ? parseDotenv(readFileSync(files.env, "utf8")) : {}
220
+ };
221
+ for (const [name, raw] of Object.entries(servers)) {
222
+ if (name.startsWith("\0")) continue;
223
+ const base = {
224
+ name,
225
+ file: files.config,
226
+ transport: "http",
227
+ headers: {},
228
+ headerVars: [],
229
+ headerSources: {},
230
+ oauth: false,
231
+ enabled: true
232
+ };
233
+ if (!raw || raw === UNREADABLE || typeof raw !== "object" || Array.isArray(raw)) {
234
+ result.entries.push({
235
+ ...base,
236
+ problem: "this entry can't be read"
237
+ });
238
+ continue;
239
+ }
240
+ const e = raw;
241
+ const unreadable = [
242
+ "url",
243
+ "headers",
244
+ "command",
245
+ "auth",
246
+ "transport",
247
+ "enabled"
248
+ ].find((k) => e[k] === UNREADABLE);
249
+ if (unreadable) {
250
+ result.entries.push({
251
+ ...base,
252
+ problem: `its ${unreadable} can't be read`
253
+ });
254
+ continue;
255
+ }
256
+ if (e.enabled === false || typeof e.enabled === "string" && [
257
+ "false",
258
+ "no",
259
+ "off",
260
+ "0"
261
+ ].includes(e.enabled.toLowerCase())) base.enabled = false;
262
+ if (typeof e.command === "string") {
263
+ const args = Array.isArray(e.args) ? e.args.filter((a) => typeof a === "string") : [];
264
+ result.entries.push({
265
+ ...base,
266
+ transport: "stdio",
267
+ command: [e.command, ...args].join(" ")
268
+ });
269
+ continue;
270
+ }
271
+ if (typeof e.url !== "string") {
272
+ result.entries.push({
273
+ ...base,
274
+ problem: "no url"
275
+ });
276
+ continue;
277
+ }
278
+ const url = expandVars(e.url, vars);
279
+ if ("missing" in url) {
280
+ result.entries.push({
281
+ ...base,
282
+ problem: `its url uses \${${url.missing}}, which isn't set in ${files.env}`
283
+ });
284
+ continue;
285
+ }
286
+ const tools = isObj(e.tools) ? e.tools : {};
287
+ const entry = {
288
+ ...base,
289
+ url: url.value,
290
+ transport: e.transport === "sse" ? "sse" : "http",
291
+ oauth: e.auth === "oauth" || isObj(e.oauth),
292
+ dropped: droppedFrom(e, {
293
+ include: tools.include,
294
+ exclude: tools.exclude
295
+ }, ["timeout", "connect_timeout"])
296
+ };
297
+ if (e.client_cert !== void 0 || e.identity_header !== void 0) {
298
+ result.entries.push({
299
+ ...entry,
300
+ problem: "it uses a client certificate or identity header, which the broker doesn't support yet"
301
+ });
302
+ continue;
303
+ }
304
+ result.entries.push(withHeaders(entry, e.headers ?? void 0, vars));
305
+ }
306
+ return result;
307
+ }
308
+ function readClaude(opts) {
309
+ const env = opts.env ?? process.env;
310
+ const cwd = opts.cwd ?? process.cwd();
311
+ const userFile = claudeConfigFile();
312
+ const projectFile = join(cwd, ".mcp.json");
313
+ const result = {
314
+ client: "claude-code",
315
+ files: [userFile, projectFile].filter(existsSync),
316
+ entries: []
317
+ };
318
+ const seen = /* @__PURE__ */ new Set();
319
+ const add = (e) => {
320
+ if (seen.has(e.name)) return;
321
+ seen.add(e.name);
322
+ result.entries.push(e);
323
+ };
324
+ const doc = readJson(userFile);
325
+ const proj = doc && isObj(doc.projects) && isObj(doc.projects[cwd]) ? doc.projects[cwd] : {};
326
+ const local = proj.mcpServers;
327
+ if (isObj(local)) for (const [n, raw] of Object.entries(local)) add(jsonEntry(n, raw, userFile, env, {
328
+ scope: "local",
329
+ projectDir: cwd
330
+ }));
331
+ const project = readJson(projectFile);
332
+ if (project && isObj(project.mcpServers)) {
333
+ const list = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
334
+ const enabled = new Set(list(proj.enabledMcpjsonServers));
335
+ const disabled = new Set(list(proj.disabledMcpjsonServers));
336
+ const all = proj.enableAllProjectMcpServers === true;
337
+ for (const [n, raw] of Object.entries(project.mcpServers)) {
338
+ const e = jsonEntry(n, raw, projectFile, env, {
339
+ scope: "project",
340
+ projectDir: cwd,
341
+ fromProject: true
342
+ });
343
+ const why = disabled.has(n) ? "turned off for this project in Claude Code" : !all && !enabled.has(n) ? "not approved in Claude Code" : null;
344
+ if (why) result.entries.push({
345
+ ...e,
346
+ problem: why
347
+ });
348
+ else add(e);
349
+ }
350
+ }
351
+ if (doc && isObj(doc.mcpServers)) for (const [n, raw] of Object.entries(doc.mcpServers)) add(jsonEntry(n, raw, userFile, env, { scope: "user" }));
352
+ return result;
353
+ }
354
+ function readCursor(opts) {
355
+ const env = opts.env ?? process.env;
356
+ const file = cursorConfigFile();
357
+ const result = {
358
+ client: "cursor",
359
+ files: [file],
360
+ entries: []
361
+ };
362
+ const doc = readJson(file);
363
+ if (doc && isObj(doc.mcpServers)) for (const [n, raw] of Object.entries(doc.mcpServers)) result.entries.push(jsonEntry(n, raw, file, env));
364
+ return result;
365
+ }
366
+ function readCodex(opts) {
367
+ const env = opts.env ?? process.env;
368
+ const file = codexConfigFile();
369
+ const result = {
370
+ client: "codex",
371
+ files: [file],
372
+ entries: []
373
+ };
374
+ if (!existsSync(file)) return result;
375
+ for (const [name, raw] of Object.entries(readTomlMcpServers(readFileSync(file, "utf8")))) {
376
+ const base = {
377
+ name,
378
+ file,
379
+ transport: "http",
380
+ headers: {},
381
+ headerVars: [],
382
+ headerSources: {},
383
+ oauth: false,
384
+ enabled: true
385
+ };
386
+ if (raw === UNREADABLE$1) {
387
+ result.entries.push({
388
+ ...base,
389
+ problem: "this entry can't be read"
390
+ });
391
+ continue;
392
+ }
393
+ const e = raw;
394
+ if (e.enabled === false) base.enabled = false;
395
+ if (typeof e.command === "string") {
396
+ const args = Array.isArray(e.args) ? e.args.filter((a) => typeof a === "string") : [];
397
+ result.entries.push({
398
+ ...base,
399
+ transport: "stdio",
400
+ command: [e.command, ...args].join(" ")
401
+ });
402
+ continue;
403
+ }
404
+ if (typeof e.url !== "string") {
405
+ result.entries.push({
406
+ ...base,
407
+ problem: "no url"
408
+ });
409
+ continue;
410
+ }
411
+ const entry = {
412
+ ...base,
413
+ url: e.url,
414
+ oauth: isObj(e.oauth) || e.auth === "oauth",
415
+ dropped: droppedFrom(e, {
416
+ include: e.enabled_tools,
417
+ exclude: e.disabled_tools
418
+ }, ["tool_timeout_sec", "startup_timeout_sec"])
419
+ };
420
+ if (e.http_headers_helper !== void 0) {
421
+ result.entries.push({
422
+ ...entry,
423
+ problem: "it gets its headers from a helper command, which the broker can't run"
424
+ });
425
+ continue;
426
+ }
427
+ const headers = {};
428
+ let problem;
429
+ if (e.http_headers !== void 0) {
430
+ if (!isObj(e.http_headers) || Object.values(e.http_headers).some((v) => typeof v !== "string")) problem = "its http_headers can't be read";
431
+ else {
432
+ Object.assign(headers, e.http_headers);
433
+ for (const h of Object.keys(e.http_headers)) entry.headerSources[h] = [];
434
+ }
435
+ }
436
+ const fromEnv = isObj(e.env_http_headers) ? { ...e.env_http_headers } : {};
437
+ if (e.bearer_token_env_var !== void 0) fromEnv.Authorization = e.bearer_token_env_var;
438
+ for (const [h, varName] of Object.entries(fromEnv)) if (typeof varName !== "string") problem ??= `its ${h} header can't be read`;
439
+ else if (!env[varName]) problem ??= `its ${h} header comes from $${varName}, which isn't set here`;
440
+ else {
441
+ headers[h] = h === "Authorization" && e.bearer_token_env_var === varName ? `Bearer ${env[varName]}` : env[varName];
442
+ entry.headerVars.push(varName);
443
+ entry.headerSources[h] = [varName];
444
+ }
445
+ entry.headers = headers;
446
+ result.entries.push(problem ? {
447
+ ...entry,
448
+ problem
449
+ } : entry);
450
+ }
451
+ return result;
452
+ }
453
+ function readClientConfig(client, opts = {}) {
454
+ switch (client) {
455
+ case "hermes": return readHermes(opts);
456
+ case "claude-code": return readClaude(opts);
457
+ case "cursor": return readCursor(opts);
458
+ case "codex": return readCodex(opts);
459
+ }
460
+ }
461
+ function parseImportClient(value) {
462
+ const v = value.trim().toLowerCase().replace(/^claude$/, "claude-code");
463
+ if (!IMPORT_CLIENTS.includes(v)) throw new Error(`Unknown --from "${value}". Choose one of: ${IMPORT_CLIENTS.join(", ")}.`);
464
+ return v;
465
+ }
466
+ //#endregion
467
+ //#region server/connections-cli/import.ts
468
+ const SELF_NAME = /^(?:xaq|agentinbox)(?:-[a-z0-9][a-z0-9_-]*)?$/;
469
+ function isSelf(e, selfOrigins) {
470
+ if (SELF_NAME.test(e.name)) return true;
471
+ if (Object.values(e.headers).some((v) => /^Bearer\s+aib_/i.test(v.trim()))) return true;
472
+ if (!e.url) return false;
473
+ try {
474
+ const u = new URL(e.url);
475
+ return u.pathname.replace(/\/+$/, "").endsWith("/mcp") && selfOrigins.includes(u.origin);
476
+ } catch {
477
+ return false;
478
+ }
479
+ }
480
+ const sameUrl = (a, b) => {
481
+ try {
482
+ return !!a && new URL(a).href.replace(/\/+$/, "") === new URL(b).href.replace(/\/+$/, "");
483
+ } catch {
484
+ return false;
485
+ }
486
+ };
487
+ async function planImport(config, ctx = {}) {
488
+ const label = CLIENT_LABELS[config.client];
489
+ const taken = new Set((ctx.existing ?? []).map((c) => c.slug));
490
+ const items = [];
491
+ for (const entry of config.entries) {
492
+ if (isSelf(entry, ctx.selfOrigins ?? [])) {
493
+ items.push({
494
+ action: "self",
495
+ entry
496
+ });
497
+ continue;
498
+ }
499
+ if (entry.transport === "stdio") {
500
+ items.push({
501
+ action: "stdio",
502
+ entry
503
+ });
504
+ continue;
505
+ }
506
+ if (entry.problem) {
507
+ items.push({
508
+ action: "skip",
509
+ entry,
510
+ reason: entry.problem
511
+ });
512
+ continue;
513
+ }
514
+ if (!entry.enabled) {
515
+ items.push({
516
+ action: "skip",
517
+ entry,
518
+ reason: `it's turned off in ${label}`
519
+ });
520
+ continue;
521
+ }
522
+ let url;
523
+ try {
524
+ url = new URL(entry.url);
525
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("scheme");
526
+ } catch {
527
+ items.push({
528
+ action: "skip",
529
+ entry,
530
+ reason: "its url isn't an http(s) URL"
531
+ });
532
+ continue;
533
+ }
534
+ const headerNames = Object.keys(entry.headers);
535
+ if (headerNames.length > 1) {
536
+ items.push({
537
+ action: "skip",
538
+ entry,
539
+ reason: `it sends ${headerNames.length} headers (${headerNames.join(", ")}); a connection keeps one (not supported yet)`
540
+ });
541
+ continue;
542
+ }
543
+ const existing = (ctx.existing ?? []).find((c) => sameUrl(c.url, url.href));
544
+ if (existing) {
545
+ items.push({
546
+ action: "exists",
547
+ entry,
548
+ connection: existing
549
+ });
550
+ continue;
551
+ }
552
+ const target = await checkTarget(url, ctx.lookup);
553
+ if (target.kind === "never") {
554
+ items.push({
555
+ action: "skip",
556
+ entry,
557
+ reason: target.why === "ipv6_literal" ? `the private-network allow takes a hostname or an IPv4 address, not ${target.address}` : `it's on ${target.address}, a link-local or cloud metadata address, which is never allowed`
558
+ });
559
+ continue;
560
+ }
561
+ if (target.kind === "public" && url.protocol === "http:") {
562
+ items.push({
563
+ action: "skip",
564
+ entry,
565
+ reason: "plain http to a public host isn't allowed; use its https URL"
566
+ });
567
+ continue;
568
+ }
569
+ let slug = slugify(entry.name);
570
+ for (let n = 2; taken.has(slug); n++) slug = `${slugify(entry.name).slice(0, 21)}-${n}`;
571
+ taken.add(slug);
572
+ const auth = headerNames.length ? {
573
+ type: "header",
574
+ name: headerNames[0],
575
+ value: entry.headers[headerNames[0]]
576
+ } : entry.oauth ? { type: "oauth" } : { type: "none" };
577
+ items.push({
578
+ action: "create",
579
+ entry,
580
+ slug,
581
+ url: entry.url.trim(),
582
+ auth,
583
+ privateTarget: target.kind === "private" ? target.origin : null
584
+ });
585
+ }
586
+ return items;
587
+ }
588
+ const where = (e) => e.scope ? ` (${e.scope} scope)` : "";
589
+ const hostOf = (url) => {
590
+ try {
591
+ return new URL(url).host;
592
+ } catch {
593
+ return url;
594
+ }
595
+ };
596
+ /** "its Authorization header (from $GITHUB_TOKEN) is sent to mcp.example.com": where each secret ends up. */
597
+ function headerLine(i) {
598
+ if (i.auth.type !== "header") return null;
599
+ const vars = i.entry.headerSources[i.auth.name] ?? [];
600
+ const from = vars.length ? ` (from ${vars.map((v) => `$${v}`).join(", ")})` : "";
601
+ return `its ${i.auth.name} header${from} moves into xaq (encrypted; never shown) and is sent to ${safeText(hostOf(i.url), 120)}`;
602
+ }
603
+ /** The plan in plain words. Never prints a header value. */
604
+ function renderPlan(config, items) {
605
+ const label = CLIENT_LABELS[config.client];
606
+ const out = [];
607
+ const width = Math.min(24, Math.max(4, ...items.map((i) => i.entry.name.length)));
608
+ const pad = (s) => s.padEnd(width);
609
+ const cont = " ".repeat(width + 4);
610
+ out.push(`${label}: ${config.files.join(", ") || "no config file"} lists ${items.length} MCP server${items.length === 1 ? "" : "s"}.`);
611
+ for (const i of items) {
612
+ const name = ` ${pad(safeText(i.entry.name, 40))} `;
613
+ switch (i.action) {
614
+ case "self":
615
+ out.push(`${name}${pc.dim("skip: this is xaq itself")}`);
616
+ break;
617
+ case "stdio":
618
+ out.push(`${name}${pc.yellow("not supported yet (M4)")}: a local command (${safeText(i.entry.command ?? "", 60)}); it stays in ${label}`);
619
+ break;
620
+ case "skip":
621
+ out.push(`${name}${pc.yellow("skip")}: ${i.reason}; it stays in ${label}`);
622
+ break;
623
+ case "exists":
624
+ if (isWorking(i.connection)) out.push(`${name}already connected: "${i.connection.slug}" (${i.connection.tools.total} tools)`);
625
+ else {
626
+ const why = i.entry.oauth || i.connection.authType === "oauth" || i.connection.status === "needs_reauth" ? "needs Connect" : `${i.connection.status}, ${i.connection.tools.total} tools`;
627
+ out.push(`${name}already a connection: "${i.connection.slug}", ${why}; it stays in ${label} until it works`);
628
+ out.push(`${cont}${reconnectHint(i.connection)}`);
629
+ }
630
+ out.push(...droppedLines(i.entry, cont));
631
+ break;
632
+ case "create": {
633
+ out.push(`${name}${pc.green("create")} connection "${i.slug}" → ${safeText(i.url, 120)}${where(i.entry)}`);
634
+ if (i.entry.fromProject) out.push(`${cont}${pc.yellow("from the project's .mcp.json")}: asked separately (--yes doesn't answer it)`);
635
+ const h = headerLine(i);
636
+ if (h) out.push(`${cont}${h}`);
637
+ else if (i.auth.type === "oauth") out.push(`${cont}OAuth: created needing Connect (${label} keeps its own tokens); sign in once with the link it prints`);
638
+ else out.push(`${cont}no credential in ${label}: connected as it is (if it turns out to use OAuth, it needs Connect)`);
639
+ if (i.privateTarget) out.push(`${cont}${pc.yellow("private network")}: needs the allow for exactly ${i.privateTarget}`);
640
+ out.push(...droppedLines(i.entry, cont));
641
+ break;
642
+ }
643
+ }
644
+ }
645
+ return out;
646
+ }
647
+ /** What the client did for this server that the broker doesn't: said in the plan, never silently dropped. */
648
+ function droppedLines(e, cont) {
649
+ const d = e.dropped;
650
+ if (!d) return [];
651
+ const out = [];
652
+ const list = (xs) => xs.map((x) => safeText(x, 64)).join(", ");
653
+ if (d.exclude) out.push(`${cont}${pc.yellow("not carried over")}: its tool filter hides ${list(d.exclude)}; through xaq every tool is listed (subject to the access level)`);
654
+ if (d.include) out.push(`${cont}${pc.yellow("not carried over")}: its tool filter shows only ${list(d.include)}; through xaq every tool is listed (subject to the access level)`);
655
+ if (d.settings.length) out.push(`${cont}${pc.yellow("not carried over")}: ${d.settings.join(", ")} (xaq uses its own 60 s per call)`);
656
+ return out;
657
+ }
658
+ const works = isWorking;
659
+ function setupLines(r) {
660
+ const out = [r.ok ? `${r.dryRun ? pc.cyan("○") : pc.green("✔")} ${r.message}` : `${pc.yellow("!")} ${r.message}`];
661
+ for (const l of r.changes ?? []) out.push(l.startsWith(" ") ? pc.gray(` ${l}`) : ` - ${l}`);
662
+ if (r.backup) out.push(pc.gray(` Backup: ${r.backup}`));
663
+ if (!r.ok) for (const l of r.manual ?? []) out.push(` ${l}`);
664
+ for (const n of r.notes ?? []) out.push(pc.dim(` ${n}`));
665
+ if (r.ok && r.verify?.length) out.push(` ${r.dryRun ? "Afterwards, check it with" : "Check it with"}: ${r.verify.map((v) => pc.cyan(v)).join("; ")}`);
666
+ return out;
667
+ }
668
+ const toRemove = (e) => ({
669
+ name: e.name,
670
+ scope: e.scope,
671
+ projectDir: e.scope === "user" ? void 0 : e.projectDir
672
+ });
673
+ async function runImport(flags, deps) {
674
+ const p = deps.prompter;
675
+ const client = parseImportClient(flags.from);
676
+ const label = CLIENT_LABELS[client];
677
+ const access = flags.access ? parseAccess(flags.access) : void 0;
678
+ const config = readClientConfig(client, {
679
+ env: deps.env,
680
+ cwd: deps.cwd
681
+ });
682
+ if (!config.entries.length) {
683
+ p.print(`${label} has no MCP servers configured (${config.files.join(", ") || "no config file"}). Nothing to import.`);
684
+ return 0;
685
+ }
686
+ const profileName = flags.profile ?? "default";
687
+ if (flags.dryRun) {
688
+ let existing;
689
+ try {
690
+ existing = await deps.openApi().list();
691
+ } catch {
692
+ existing = void 0;
693
+ }
694
+ const items = await planImport(config, {
695
+ existing,
696
+ lookup: deps.lookup,
697
+ selfOrigins: deps.selfOrigins
698
+ });
699
+ p.print(pc.bold("Dry run: nothing is created or changed."));
700
+ for (const l of renderPlan(config, items)) p.print(l);
701
+ const creates = items.filter((i) => i.action === "create");
702
+ const priv = creates.filter((i) => i.privateTarget);
703
+ p.print("");
704
+ p.print(`Would create ${creates.length} connection${creates.length === 1 ? "" : "s"} with ${access ? accessLabel(access) : "the access you choose (default: Ask before risky actions)"}.`);
705
+ if (priv.length) p.print(`Would ask before allowing ${priv.map((i) => i.privateTarget).join(", ")} (--yes never does; --allow-private <origin> does for that origin).`);
706
+ if (!existing) p.print(pc.dim("xaq wasn't reachable, so existing connections weren't checked (the real run does: nothing is created twice)."));
707
+ const removable = [...items.filter((i) => i.action === "exists" && works(i.connection)).map((i) => toRemove(i.entry)), ...creates.filter((i) => i.auth.type !== "oauth").map((i) => toRemove(i.entry))];
708
+ p.print("");
709
+ if (!removable.length) p.print(`${label}'s config isn't changed.`);
710
+ else if (!profileExists(profileName)) p.print(`Afterwards it offers to replace the imported entries in ${label} with the single xaq entry, once this machine's ${label} is paired (${NPX} connect <xaq-url> --tool ${client}).`);
711
+ else {
712
+ p.print(`Afterwards it offers setup-mcp --tool ${client}, which would (for the entries whose connection works and that ${label}'s agent can use; OAuth ones after you Reconnect them):`);
713
+ const r = setupMcp(client, mcpTarget(profileName, loadProfile(profileName)), profilePath(profileName), {
714
+ dryRun: true,
715
+ removeServers: removable
716
+ });
717
+ for (const l of setupLines(r)) p.print(l);
718
+ }
719
+ return 0;
720
+ }
721
+ const api = deps.openApi();
722
+ const items = await planImport(config, {
723
+ existing: await api.list(),
724
+ lookup: deps.lookup,
725
+ selfOrigins: deps.selfOrigins
726
+ });
727
+ for (const l of renderPlan(config, items)) p.print(l);
728
+ let creates = items.filter((i) => i.action === "create");
729
+ const already = items.filter((i) => i.action === "exists");
730
+ p.print("");
731
+ const fromProject = creates.filter((i) => i.entry.fromProject);
732
+ if (fromProject.length) {
733
+ p.print(pc.yellow(`These come from ${fromProject[0].entry.file}, which ships with the project (anyone who can commit to it wrote them):`));
734
+ for (const i of fromProject) p.print(` ${safeText(i.entry.name, 40)} → ${safeText(i.url, 120)}${headerLine(i) ? `; ${headerLine(i)}` : ""}`);
735
+ if (!await p.confirmExplicit(`Import ${fromProject.length === 1 ? "it" : `these ${fromProject.length}`} into xaq?`, "they stay in Claude Code")) creates = creates.filter((i) => !i.entry.fromProject);
736
+ }
737
+ const preAllowed = new Set(flags.allowPrivate ?? []);
738
+ const priv = creates.filter((i) => i.privateTarget && !preAllowed.has(i.privateTarget));
739
+ for (const i of creates.filter((x) => x.privateTarget && preAllowed.has(x.privateTarget))) p.print(`Allowing ${i.privateTarget} (${safeText(i.entry.name, 40)}): --allow-private.`);
740
+ if (priv.length) {
741
+ p.print(pc.yellow("These servers are on this machine or your private network:"));
742
+ for (const i of priv) p.print(` ${i.privateTarget} (${safeText(i.entry.name, 40)})`);
743
+ p.print(pc.dim("A connection to such a server can reach other services there too. Each allow covers exactly that scheme://host:port, for that connection only."));
744
+ const names = priv.map((i) => i.entry.name).join(", ");
745
+ if (!await p.confirmExplicit(`Allow xaq to connect to ${priv.length === 1 ? "it" : `these ${priv.length}`}?`, `skipping ${names} (pass --allow-private <origin> to allow one)`)) creates = creates.filter((i) => !priv.includes(i));
746
+ }
747
+ const who = await clientAgent(api, profileName);
748
+ if (!creates.length) {
749
+ p.print("Nothing to create.");
750
+ return offerSetup(deps, api, client, who, already.filter((a) => works(a.connection)).map((a) => ({
751
+ entry: a.entry,
752
+ connection: a.connection
753
+ })), already.filter((a) => !works(a.connection)).map((a) => a.entry.name), 0);
754
+ }
755
+ const level = await chooseAccess(p, access);
756
+ let agents = await chooseAgents(p, api, flags.agent);
757
+ if (agents !== "all" && who.agent && !agents.includes(who.agent.id)) {
758
+ p.print(pc.yellow(`${safeText(who.agent.name, 60)} (the agent ${label} on this machine uses) isn't among them, so ${label} couldn't use these through xaq.`));
759
+ if (await p.confirm(`Include ${safeText(who.agent.name, 60)}?`, true)) agents = [...agents, who.agent.id];
760
+ }
761
+ if (!await p.confirm(`Create ${creates.length} connection${creates.length === 1 ? "" : "s"} (${accessLabel(level)}, ${agentsLabel(agents)})?`, true)) {
762
+ p.print("Nothing was created.");
763
+ return 0;
764
+ }
765
+ const outcomes = [];
766
+ for (const item of creates) {
767
+ let created = null;
768
+ try {
769
+ created = await api.create({
770
+ slug: item.slug,
771
+ name: item.entry.name,
772
+ url: item.url,
773
+ levelDefault: level,
774
+ ...agents === "all" ? {} : { agentIds: agents }
775
+ });
776
+ if (item.privateTarget) await api.allowPrivateTarget(created.id, true);
777
+ const r = await api.connect(created.id, item.auth.type === "header" ? { header: {
778
+ name: item.auth.name,
779
+ value: item.auth.value
780
+ } } : {});
781
+ if (r.status === "connected") {
782
+ const after = await waitWorking(api, r.connection, {
783
+ pollMs: deps.pollMs,
784
+ ms: deps.verifyMs
785
+ });
786
+ outcomes.push({
787
+ item,
788
+ status: works(after) ? "connected" : "unverified",
789
+ connection: after
790
+ });
791
+ } else if (r.status === "authorize") outcomes.push({
792
+ item,
793
+ status: "authorize",
794
+ connection: created,
795
+ authorizeUrl: r.authorizeUrl
796
+ });
797
+ else throw new Error(r.message);
798
+ } catch (err) {
799
+ if (created) await api.remove(created.id).catch(() => {});
800
+ outcomes.push({
801
+ item,
802
+ status: "failed",
803
+ error: err instanceof Error ? err.message : String(err)
804
+ });
805
+ }
806
+ }
807
+ p.print("");
808
+ for (const o of outcomes) {
809
+ const name = safeText(o.item.entry.name, 40);
810
+ if (o.status === "failed") p.print(`${pc.red("✘")} ${name}: ${safeText(o.error, 300)} (it stays in ${label})`);
811
+ else if (o.status === "connected") p.print(`${pc.green("✔")} ${name} → "${o.connection.slug}", ${o.connection.tools.total} tools`);
812
+ else if (o.status === "unverified") {
813
+ const c = o.connection;
814
+ p.print(`${pc.yellow("○")} ${name} → "${c.slug}": not working (${c.status}${c.statusReason ? `: ${safeText(c.statusReason, 200)}` : ""}, ${c.tools.total} tools), so it stays in ${label}.`);
815
+ p.print(` ${reconnectHint(c)} then run this import again.`);
816
+ } else {
817
+ p.print(`${pc.yellow("○")} ${name} → "${o.connection.slug}": needs Connect. Sign in (link valid 15 minutes):`);
818
+ p.print(` ${pc.cyan(o.authorizeUrl)}`);
819
+ p.print(` ${reconnectHint(o.connection)}`);
820
+ }
821
+ }
822
+ const failed = outcomes.filter((o) => o.status === "failed").length;
823
+ return offerSetup(deps, api, client, who, [...already.filter((a) => works(a.connection)).map((a) => ({
824
+ entry: a.entry,
825
+ connection: a.connection
826
+ })), ...outcomes.filter((o) => o.status === "connected").map((o) => ({
827
+ entry: o.item.entry,
828
+ connection: o.connection
829
+ }))], [...already.filter((a) => !works(a.connection)).map((a) => a.entry.name), ...outcomes.filter((o) => o.status === "authorize" || o.status === "unverified").map((o) => o.item.entry.name)], failed ? 1 : 0);
830
+ }
831
+ async function clientAgent(api, profileName) {
832
+ if (!profileExists(profileName)) return {
833
+ profileName,
834
+ profile: null,
835
+ agent: null
836
+ };
837
+ const profile = loadProfile(profileName);
838
+ return {
839
+ profileName,
840
+ profile,
841
+ agent: (await api.agents()).find((a) => a.id === profile.agentId && !a.disabled) ?? null
842
+ };
843
+ }
844
+ /**
845
+ * Step 2: replace the entries now served by the broker with the single xaq entry (setup-mcp). Only entries
846
+ * the client's own agent can use through xaq go: the profile's agent must be on this daemon and have a grant
847
+ * on the connection (offered when it has none).
848
+ */
849
+ async function offerSetup(deps, api, client, who, working, pending, code) {
850
+ const p = deps.prompter;
851
+ const label = CLIENT_LABELS[client];
852
+ if (pending.length) p.print(pc.dim(`${pending.join(", ")} stay${pending.length === 1 ? "s" : ""} in ${label} until ${pending.length === 1 ? "it works" : "they work"} through xaq (Reconnect); then run this import again to replace ${pending.length === 1 ? "it" : "them"}.`));
853
+ if (!working.length) return code;
854
+ if (!who.profile) {
855
+ p.print(`To replace ${working.map((w) => w.entry.name).join(", ")} in ${label} with the single xaq entry, pair it first (${NPX} connect <xaq-url> --tool ${client}) and run this import again.`);
856
+ return code;
857
+ }
858
+ if (!who.agent) {
859
+ p.print(pc.yellow(`The agent in your "${who.profileName}" profile (${safeText(who.profile.agentId, 40)}) isn't an agent of this xaq (or is disabled), so ${label} couldn't use these through it. Not replacing anything in ${label}. Pair it with this xaq (${NPX} connect <xaq-url> --tool ${client} --force), then run this import again.`));
860
+ return code;
861
+ }
862
+ const agentName = safeText(who.agent.name, 60);
863
+ const usable = [];
864
+ for (const w of working) {
865
+ if (w.entry.fromProject) {
866
+ p.print(pc.dim(` "${w.entry.name}" stays in ${w.entry.file}: it ships with the project. Remove it there if the project no longer needs it.`));
867
+ continue;
868
+ }
869
+ const grants = await api.grants(w.connection.id);
870
+ if (grants.some((g) => g.agentId === who.agent.id)) {
871
+ usable.push(w.entry);
872
+ continue;
873
+ }
874
+ if (await p.confirm(`${agentName} (the agent ${label} uses) has no access to "${w.connection.slug}". Give it ${accessLabel(w.connection.levelDefault)}?`, true)) {
875
+ await api.setGrants(w.connection.id, [...grants.map((g) => ({
876
+ agentId: g.agentId,
877
+ level: g.level
878
+ })), {
879
+ agentId: who.agent.id,
880
+ level: w.connection.levelDefault
881
+ }]);
882
+ usable.push(w.entry);
883
+ } else p.print(pc.dim(` "${w.entry.name}" stays in ${label}: its agent couldn't use "${w.connection.slug}".`));
884
+ }
885
+ if (!usable.length) return code;
886
+ p.print("");
887
+ const question = `Replace ${usable.map((e) => `"${e.name}"`).join(", ")} in ${label} with the single xaq entry (setup-mcp --tool ${client}, with a backup)?`;
888
+ const routes = confinedRoutes(client, mcpServerName(who.profileName), deps.env);
889
+ let ok;
890
+ if (routes.length) {
891
+ const slugs = working.filter((w) => usable.includes(w.entry)).map((w) => w.connection.slug);
892
+ p.print(pc.yellow(`These ${label} settings are limited to the xaq server only:`));
893
+ for (const r of routes) p.print(` ${safeText(r, 120)}`);
894
+ p.print(pc.yellow(`Through xaq they now also see the tools of ${slugs.map((s) => `"${s}"`).join(", ")} (as <name>__<tool>), which ${routes.length === 1 ? "it" : "they"} couldn't reach before. If one is triggered by email or webhooks, those tools become reachable from that content. Give ${agentName} less access on those connections, or keep them out, if that's not what you want.`));
895
+ ok = await p.confirmExplicit(question, `${label}'s config wasn't changed; decide in a terminal`);
896
+ } else ok = await p.confirm(question, true);
897
+ if (!ok) {
898
+ p.print(`${label}'s config wasn't changed. Run this import again any time to do it.`);
899
+ return code;
900
+ }
901
+ const r = setupMcp(client, mcpTarget(who.profileName, who.profile), profilePath(who.profileName), { removeServers: usable.map(toRemove) });
902
+ for (const l of setupLines(r)) p.print(l);
903
+ const vars = [...new Set(usable.flatMap((e) => (r.removed ?? []).includes(e.name) ? e.headerVars : []))];
904
+ if (r.ok && vars.length) p.print(pc.dim(` ${vars.join(", ")} ${vars.length === 1 ? "is" : "are"} no longer needed by the removed entries; delete ${vars.length === 1 ? "it" : "them"} from your environment${client === "hermes" ? " (~/.hermes/.env)" : ""} once everything works through xaq.`));
905
+ return r.ok ? code : 1;
906
+ }
907
+ //#endregion
908
+ export { runImport };
909
+
910
+ //# sourceMappingURL=import-DsN5NZT3.js.map