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,1053 @@
1
+ #!/usr/bin/env node
2
+ import { t as NPX } from "./version-BxH7OuWD.js";
3
+ import { a as mcpServerName, c as backupFile, f as writePrivateFile, u as isTooOpen } from "./profile-NmMKAczz.js";
4
+ import "./paths-yzOKD6ow.js";
5
+ import { c as helperScriptPath, n as claudeServerEntry, o as helperNodeNote } from "./mcpheader-C9H4vudu.js";
6
+ import "./recipes-DG_-doGq.js";
7
+ import { i as safeText } from "./ui-B8Sd4Y7Y.js";
8
+ import { chmodSync, existsSync, readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { homedir } from "node:os";
11
+ import { spawnSync } from "node:child_process";
12
+ import { isDeepStrictEqual } from "node:util";
13
+ //#region server/connections-cli/toml.ts
14
+ /**
15
+ * A small TOML reader for Codex's `[mcp_servers.*]` tables, without a TOML library. It reads table headers,
16
+ * dotted and quoted keys, basic and literal strings, numbers, booleans, single- or multi-line arrays of those,
17
+ * and one-line inline tables. Multi-line strings, dates and arrays of tables are UNREADABLE (for their value or
18
+ * table), so the caller can skip an entry it can't read instead of guessing.
19
+ */
20
+ const UNREADABLE$1 = Symbol("unreadable");
21
+ var Cursor = class {
22
+ s;
23
+ i = 0;
24
+ constructor(s) {
25
+ this.s = s;
26
+ }
27
+ ws() {
28
+ while (this.i < this.s.length && /[ \t]/.test(this.s[this.i])) this.i++;
29
+ }
30
+ /** Whitespace, newlines and comments (inside arrays). */
31
+ wsAll() {
32
+ for (;;) {
33
+ while (this.i < this.s.length && /[ \t\r\n]/.test(this.s[this.i])) this.i++;
34
+ if (this.s[this.i] !== "#") return;
35
+ while (this.i < this.s.length && this.s[this.i] !== "\n") this.i++;
36
+ }
37
+ }
38
+ peek() {
39
+ return this.s[this.i];
40
+ }
41
+ done() {
42
+ return this.i >= this.s.length;
43
+ }
44
+ };
45
+ var Unreadable = class extends Error {};
46
+ const ESCAPES = {
47
+ b: "\b",
48
+ t: " ",
49
+ n: "\n",
50
+ f: "\f",
51
+ r: "\r",
52
+ "\"": "\"",
53
+ "\\": "\\",
54
+ e: "\x1B"
55
+ };
56
+ function readString(c) {
57
+ const q = c.peek();
58
+ if (c.s.startsWith(`${q}${q}${q}`, c.i)) throw new Unreadable("multi-line string");
59
+ c.i++;
60
+ let out = "";
61
+ while (!c.done()) {
62
+ const ch = c.s[c.i++];
63
+ if (ch === "\n") break;
64
+ if (ch === q) return out;
65
+ if (q === "\"" && ch === "\\") {
66
+ const e = c.s[c.i++];
67
+ if (ESCAPES[e] !== void 0) out += ESCAPES[e];
68
+ else if (e === "u" || e === "U") {
69
+ const len = e === "u" ? 4 : 8;
70
+ const hex = c.s.slice(c.i, c.i + len);
71
+ if (!/^[0-9a-fA-F]+$/.test(hex) || hex.length !== len) throw new Unreadable("bad escape");
72
+ out += String.fromCodePoint(Number.parseInt(hex, 16));
73
+ c.i += len;
74
+ } else throw new Unreadable("bad escape");
75
+ } else out += ch;
76
+ }
77
+ throw new Unreadable("unterminated string");
78
+ }
79
+ function readKey(c) {
80
+ const parts = [];
81
+ for (;;) {
82
+ c.ws();
83
+ const ch = c.peek();
84
+ if (ch === "\"" || ch === "'") parts.push(readString(c));
85
+ else {
86
+ const m = /^[A-Za-z0-9_-]+/.exec(c.s.slice(c.i));
87
+ if (!m) throw new Unreadable("bad key");
88
+ parts.push(m[0]);
89
+ c.i += m[0].length;
90
+ }
91
+ c.ws();
92
+ if (c.peek() !== ".") return parts;
93
+ c.i++;
94
+ }
95
+ }
96
+ function readValue(c, multiline) {
97
+ const ch = c.peek();
98
+ if (ch === "\"" || ch === "'") return readString(c);
99
+ if (ch === "[") {
100
+ c.i++;
101
+ const items = [];
102
+ for (;;) {
103
+ if (multiline) c.wsAll();
104
+ else c.ws();
105
+ if (c.peek() === "]") {
106
+ c.i++;
107
+ return items;
108
+ }
109
+ items.push(readValue(c, multiline));
110
+ if (multiline) c.wsAll();
111
+ else c.ws();
112
+ if (c.peek() === ",") c.i++;
113
+ else if (c.peek() !== "]") throw new Unreadable("bad array");
114
+ }
115
+ }
116
+ if (ch === "{") {
117
+ c.i++;
118
+ const table = {};
119
+ c.ws();
120
+ if (c.peek() === "}") {
121
+ c.i++;
122
+ return table;
123
+ }
124
+ for (;;) {
125
+ const key = readKey(c);
126
+ if (c.peek() !== "=") throw new Unreadable("bad inline table");
127
+ c.i++;
128
+ c.ws();
129
+ setPath(table, key, readValue(c, false));
130
+ c.ws();
131
+ if (c.peek() === ",") {
132
+ c.i++;
133
+ continue;
134
+ }
135
+ if (c.peek() === "}") {
136
+ c.i++;
137
+ return table;
138
+ }
139
+ throw new Unreadable("bad inline table");
140
+ }
141
+ }
142
+ const m = /^[^\s,\]}#]+/.exec(c.s.slice(c.i));
143
+ if (!m) throw new Unreadable("missing value");
144
+ c.i += m[0].length;
145
+ if (m[0] === "true") return true;
146
+ if (m[0] === "false") return false;
147
+ const n = m[0].replace(/_/g, "");
148
+ if (/^[-+]?(\d+(\.\d+)?([eE][-+]?\d+)?|0x[0-9a-fA-F]+|0o[0-7]+|0b[01]+)$/.test(n)) return Number(n);
149
+ throw new Unreadable("unsupported value");
150
+ }
151
+ function setPath(table, path, value) {
152
+ let t = table;
153
+ for (const p of path.slice(0, -1)) {
154
+ const next = t[p];
155
+ if (next === void 0) t = t[p] = {};
156
+ else if (typeof next === "object" && !Array.isArray(next)) t = next;
157
+ else throw new Unreadable("key redefined");
158
+ }
159
+ const last = path[path.length - 1];
160
+ if (Object.hasOwn(t, last)) throw new Unreadable("key redefined");
161
+ t[last] = value;
162
+ }
163
+ function tableAt(root, path) {
164
+ let t = root;
165
+ for (const p of path) {
166
+ const next = t[p];
167
+ if (next === void 0) t = t[p] = {};
168
+ else if (typeof next === "object" && !Array.isArray(next)) t = next;
169
+ else throw new Unreadable("table redefined");
170
+ }
171
+ return t;
172
+ }
173
+ /**
174
+ * The `mcp_servers` table of a Codex config.toml: server name → entry. A server whose table (or a value in it)
175
+ * can't be read maps to UNREADABLE. Problems outside mcp_servers are ignored.
176
+ */
177
+ function readTomlMcpServers(toml) {
178
+ const root = {};
179
+ const bad = /* @__PURE__ */ new Set();
180
+ let current = [];
181
+ const lines = toml.replace(/^/, "").split("\n");
182
+ for (let n = 0; n < lines.length; n++) {
183
+ const line = lines[n];
184
+ const trimmed = line.trim();
185
+ if (!trimmed || trimmed.startsWith("#")) continue;
186
+ if (trimmed.startsWith("[[")) {
187
+ const c = new Cursor(trimmed.slice(2));
188
+ try {
189
+ current = ["\0array", ...readKey(c)];
190
+ } catch {
191
+ current = ["\0array"];
192
+ }
193
+ if (current[1] === "mcp_servers" && current[2]) bad.add(current[2]);
194
+ continue;
195
+ }
196
+ if (trimmed.startsWith("[")) {
197
+ const c = new Cursor(trimmed.slice(1));
198
+ try {
199
+ current = readKey(c);
200
+ if (c.peek() !== "]") throw new Unreadable("bad header");
201
+ if (current[0] === "mcp_servers") tableAt(root, current);
202
+ } catch {
203
+ current = ["\0bad"];
204
+ }
205
+ continue;
206
+ }
207
+ if (current[0] !== "mcp_servers" && !/^\s*mcp_servers\s*[.=]/.test(line)) continue;
208
+ let text = line;
209
+ let consumed = n;
210
+ let parsed = null;
211
+ for (;;) {
212
+ const c = new Cursor(text);
213
+ try {
214
+ const key = readKey(c);
215
+ if (c.peek() !== "=") throw new Unreadable("missing =");
216
+ c.i++;
217
+ c.ws();
218
+ const value = readValue(c, true);
219
+ c.ws();
220
+ if (!c.done() && c.peek() !== "#") throw new Unreadable("trailing characters");
221
+ parsed = {
222
+ key,
223
+ value
224
+ };
225
+ break;
226
+ } catch {
227
+ if (!((text.match(/\[/g) ?? []).length > (text.match(/]/g) ?? []).length) || consumed + 1 >= lines.length) break;
228
+ text += `\n${lines[++consumed]}`;
229
+ }
230
+ }
231
+ n = consumed;
232
+ const full = parsed ? [...current, ...parsed.key] : current;
233
+ const server = full[0] === "mcp_servers" ? full[1] : current[1];
234
+ if (!parsed) {
235
+ if (server) bad.add(server);
236
+ continue;
237
+ }
238
+ try {
239
+ setPath(root, full, parsed.value);
240
+ } catch {
241
+ if (server) bad.add(server);
242
+ }
243
+ }
244
+ const servers = root.mcp_servers;
245
+ const out = {};
246
+ if (servers && typeof servers === "object" && !Array.isArray(servers)) for (const [name, entry] of Object.entries(servers)) out[name] = bad.has(name) || !entry || typeof entry !== "object" || Array.isArray(entry) ? UNREADABLE$1 : entry;
247
+ for (const name of bad) out[name] = UNREADABLE$1;
248
+ return out;
249
+ }
250
+ //#endregion
251
+ //#region server/connections-cli/yaml.ts
252
+ /**
253
+ * A small reader for the `mcp_servers:` block of a Hermes config.yaml, without a YAML library (like
254
+ * server/listen/mcpsetup.ts). It handles the block style Hermes writes and documents: nested maps, plain and
255
+ * quoted scalars, flow sequences of scalars (`args: ["-y", "pkg"]`) and block sequences of scalars. Anything else
256
+ * (anchors, block scalars, flow maps, tabs) becomes UNREADABLE for that value, so the caller can skip the entry
257
+ * that uses it instead of guessing. Only the mcp_servers block is read: the rest of the file never matters.
258
+ */
259
+ const UNREADABLE = Symbol("unreadable");
260
+ /** The text of a line without a trailing comment (a `#` after whitespace, outside quotes). */
261
+ function stripComment(line) {
262
+ let quote = null;
263
+ for (let i = 0; i < line.length; i++) {
264
+ const ch = line[i];
265
+ if (quote) {
266
+ if (ch === "\\" && quote === "\"") i++;
267
+ else if (ch === quote) quote = null;
268
+ } else if (ch === "\"" || ch === "'") {
269
+ if (i === 0 || /[\s:[{,-]/.test(line[i - 1])) quote = ch;
270
+ } else if (ch === "#" && (i === 0 || /\s/.test(line[i - 1]))) return line.slice(0, i).trimEnd();
271
+ }
272
+ return line.trimEnd();
273
+ }
274
+ /** The line range of the top-level `mcp_servers:` block, or null when there isn't exactly one. */
275
+ function mcpServersBlock(yaml) {
276
+ return topBlock(yaml, "mcp_servers");
277
+ }
278
+ const keyRe = (key) => new RegExp(`^${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*:`);
279
+ /** The line range of a top-level `<key>:` block, or null when there isn't exactly one. */
280
+ function topBlock(yaml, key) {
281
+ const re = keyRe(key);
282
+ const lines = yaml.replace(/^/, "").split("\n");
283
+ const tops = lines.flatMap((l, i) => re.test(l) ? [i] : []);
284
+ if (tops.length !== 1) return null;
285
+ const start = tops[0];
286
+ let end = start + 1;
287
+ while (end < lines.length && (lines[end].trim() === "" || /^\s/.test(lines[end]) || lines[end].startsWith("#"))) end++;
288
+ while (end > start + 1 && (lines[end - 1].trim() === "" || lines[end - 1].startsWith("#"))) end--;
289
+ return {
290
+ start,
291
+ end,
292
+ inline: stripComment(lines[start].replace(re, "")).trim()
293
+ };
294
+ }
295
+ function unquote(raw) {
296
+ if (raw.startsWith("\"")) {
297
+ if (!raw.endsWith("\"") || raw.length < 2) return UNREADABLE;
298
+ try {
299
+ return JSON.parse(raw.replace(/\\\//g, "/"));
300
+ } catch {
301
+ return UNREADABLE;
302
+ }
303
+ }
304
+ if (raw.startsWith("'")) {
305
+ if (!raw.endsWith("'") || raw.length < 2) return UNREADABLE;
306
+ return raw.slice(1, -1).replace(/''/g, "'");
307
+ }
308
+ return raw;
309
+ }
310
+ /** Splits a flow sequence body on top-level commas. */
311
+ function splitFlow(body) {
312
+ const out = [];
313
+ let cur = "";
314
+ let quote = null;
315
+ for (let i = 0; i < body.length; i++) {
316
+ const ch = body[i];
317
+ if (quote) {
318
+ cur += ch;
319
+ if (ch === "\\" && quote === "\"") cur += body[++i] ?? "";
320
+ else if (ch === quote) quote = null;
321
+ } else if (ch === "\"" || ch === "'") {
322
+ quote = ch;
323
+ cur += ch;
324
+ } else if (ch === "[" || ch === "{" || ch === "]" || ch === "}") return null;
325
+ else if (ch === ",") {
326
+ out.push(cur.trim());
327
+ cur = "";
328
+ } else cur += ch;
329
+ }
330
+ if (quote) return null;
331
+ if (cur.trim()) out.push(cur.trim());
332
+ return out;
333
+ }
334
+ function scalar(raw) {
335
+ const v = raw.trim();
336
+ if (v === "" || v === "~" || v === "null" || v === "Null" || v === "NULL") return null;
337
+ if (/^[&*!|>]/.test(v) || v.startsWith("<<") || v.startsWith("{")) return UNREADABLE;
338
+ if (v.startsWith("[")) {
339
+ if (!v.endsWith("]")) return UNREADABLE;
340
+ const items = splitFlow(v.slice(1, -1));
341
+ return items ? items.map((i) => scalar(i)) : UNREADABLE;
342
+ }
343
+ if (v.startsWith("\"") || v.startsWith("'")) return unquote(v);
344
+ if (/^(true|True|TRUE)$/.test(v)) return true;
345
+ if (/^(false|False|FALSE)$/.test(v)) return false;
346
+ if (/^[-+]?\d+(\.\d+)?$/.test(v)) return Number(v);
347
+ return v;
348
+ }
349
+ /** `key: rest` with a plain or quoted key; null when the line isn't one. */
350
+ function keyValue(text) {
351
+ const m = /^("(?:[^"\\]|\\.)*"|'(?:[^']|'')*'|[^"'#:\s][^:]*?)\s*:(?:\s+(.*)|$)/.exec(text);
352
+ if (!m) return null;
353
+ const key = unquote(m[1]);
354
+ if (key === UNREADABLE) return null;
355
+ return {
356
+ key,
357
+ rest: (m[2] ?? "").trim()
358
+ };
359
+ }
360
+ function parseLines(lines, from, indent) {
361
+ const first = lines[from];
362
+ if (!first || first.indent < indent) return {
363
+ value: null,
364
+ next: from
365
+ };
366
+ const childIndent = first.indent;
367
+ if (first.text.startsWith("- ") || first.text === "-") {
368
+ const items = [];
369
+ let i = from;
370
+ while (i < lines.length && lines[i].indent === childIndent && (lines[i].text.startsWith("- ") || lines[i].text === "-")) {
371
+ const item = lines[i].text.slice(1).trim();
372
+ i++;
373
+ if (!item || keyValue(item)) {
374
+ items.push(UNREADABLE);
375
+ while (i < lines.length && lines[i].indent > childIndent) i++;
376
+ } else items.push(scalar(item));
377
+ }
378
+ return {
379
+ value: items,
380
+ next: i
381
+ };
382
+ }
383
+ const map = {};
384
+ let i = from;
385
+ while (i < lines.length && lines[i].indent >= childIndent) {
386
+ const line = lines[i];
387
+ if (line.indent > childIndent) {
388
+ const keys = Object.keys(map);
389
+ if (keys.length) map[keys[keys.length - 1]] = UNREADABLE;
390
+ i++;
391
+ continue;
392
+ }
393
+ const kv = keyValue(line.text);
394
+ i++;
395
+ if (!kv) {
396
+ map[`\u0000line${i}`] = UNREADABLE;
397
+ continue;
398
+ }
399
+ if (kv.rest) {
400
+ map[kv.key] = Object.hasOwn(map, kv.key) ? UNREADABLE : scalar(kv.rest);
401
+ continue;
402
+ }
403
+ if (!(i < lines.length && (lines[i].indent > childIndent || lines[i].indent === childIndent && lines[i].text.startsWith("- ")))) {
404
+ map[kv.key] = null;
405
+ continue;
406
+ }
407
+ const sub = parseLines(lines, i, lines[i].indent > childIndent ? childIndent + 1 : childIndent);
408
+ map[kv.key] = Object.hasOwn(map, kv.key) ? UNREADABLE : sub.value;
409
+ i = sub.next;
410
+ }
411
+ return {
412
+ value: map,
413
+ next: i
414
+ };
415
+ }
416
+ /**
417
+ * The `mcp_servers` map of a Hermes config.yaml: server name → its (partly) parsed entry. null when the file has
418
+ * no single readable block (`{}` and an absent key give an empty map).
419
+ */
420
+ function readMcpServers(yaml) {
421
+ return readTopMap(yaml, "mcp_servers");
422
+ }
423
+ /** A top-level block map (e.g. `platform_toolsets`), read like mcp_servers. */
424
+ function readTopMap(yaml, key) {
425
+ const block = topBlock(yaml, key);
426
+ if (!block) return new RegExp(keyRe(key).source, "m").test(yaml) ? null : {};
427
+ if (block.inline) return block.inline === "{}" || block.inline === "null" || block.inline === "~" ? {} : null;
428
+ const raw = yaml.replace(/^/, "").split("\n").slice(block.start + 1, block.end);
429
+ const lines = [];
430
+ for (const l of raw) {
431
+ const text = stripComment(l);
432
+ if (!text.trim()) continue;
433
+ const ws = /^(\s*)/.exec(text)[1];
434
+ if (ws.includes(" ")) return null;
435
+ lines.push({
436
+ indent: ws.length,
437
+ text: text.trim()
438
+ });
439
+ }
440
+ if (!lines.length) return {};
441
+ const { value } = parseLines(lines, 0, 1);
442
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
443
+ }
444
+ const escapeRe$1 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
445
+ /**
446
+ * Removes `mcp_servers.<name>` for each name, keeping everything else byte for byte. Returns null when any of
447
+ * them isn't a plain block entry this can cut out safely (then nothing should be written).
448
+ */
449
+ /**
450
+ * Cuts the named entries out of an mcp_servers block body (its lines after `mcp_servers:`), whose entries sit at
451
+ * `indent`. Comment and blank lines met inside a cut entry are held until the next real line decides: a sibling
452
+ * key at `indent` (or less) keeps them with what follows (unless that is cut too), a deeper line means they were
453
+ * part of the cut entry. So a commented-out property (`# timeout: 120`) or a note (` # TODO`) inside an entry
454
+ * never ends it early, and none of its lines can end up under a neighbour. null when a layout can't be cut safely.
455
+ */
456
+ /**
457
+ * Whether lines use YAML anchors, aliases or merge keys (`&a`, `*a`, `<<:`) outside quotes and comments. Cutting
458
+ * an entry could then remove an anchor a kept entry still refers to, so those blocks are never rewritten.
459
+ */
460
+ function hasAnchors(lines) {
461
+ return lines.some((l) => {
462
+ const t = stripComment(l).replace(/"(?:[^"\\]|\\.)*"/g, "\"\"").replace(/'(?:[^']|'')*'/g, "''");
463
+ return /(^|[\s:[{,-])[&*][^\s,\]}]/.test(t) || /(^|[\s{,-])<<\s*:/.test(t);
464
+ });
465
+ }
466
+ function cutEntries(body, indent, names) {
467
+ if (hasAnchors(body)) return null;
468
+ const isKey = (l, name) => new RegExp(`^${escapeRe$1(indent)}(["']?)${escapeRe$1(name)}\\1\\s*:`).test(l);
469
+ const ind = (l) => /^(\s*)/.exec(l)[1].length;
470
+ const isComment = (l) => l.trimStart().startsWith("#");
471
+ const kept = [];
472
+ const found = /* @__PURE__ */ new Set();
473
+ let skipping = false;
474
+ let pending = [];
475
+ for (const l of body) {
476
+ const blank = !l.trim();
477
+ if (skipping && (blank || isComment(l))) {
478
+ pending.push(l);
479
+ continue;
480
+ }
481
+ if (blank || isComment(l)) {
482
+ kept.push(l);
483
+ continue;
484
+ }
485
+ const own = ind(l);
486
+ if (own < indent.length) return null;
487
+ if (own > indent.length) {
488
+ if (skipping) pending = [];
489
+ else kept.push(l);
490
+ continue;
491
+ }
492
+ const hit = names.find((n) => isKey(l, n)) ?? null;
493
+ if (hit) {
494
+ if (found.has(hit)) return null;
495
+ found.add(hit);
496
+ if (stripComment(l).replace(/^[^:]*:/, "").trim()) return null;
497
+ pending = [];
498
+ while (kept.length && isComment(kept[kept.length - 1]) && ind(kept[kept.length - 1]) <= indent.length) kept.pop();
499
+ skipping = true;
500
+ continue;
501
+ }
502
+ if (skipping) kept.push(...pending.filter((x) => !x.trim() || ind(x) <= indent.length));
503
+ pending = [];
504
+ skipping = false;
505
+ kept.push(l);
506
+ }
507
+ return {
508
+ kept,
509
+ found
510
+ };
511
+ }
512
+ /**
513
+ * Removes `mcp_servers.<name>` for each name, keeping everything else byte for byte. Returns null when any of
514
+ * them isn't a plain block entry this can cut out safely (then nothing should be written).
515
+ */
516
+ function removeYamlServers(yaml, names) {
517
+ if (!names.length) return yaml;
518
+ const block = mcpServersBlock(yaml);
519
+ if (!block || block.inline) return null;
520
+ const lines = yaml.split("\n");
521
+ const body = lines.slice(block.start + 1, block.end);
522
+ const first = body.find((l) => l.trim() && !l.trimStart().startsWith("#"));
523
+ if (!first) return null;
524
+ const indent = /^(\s*)/.exec(first)[1];
525
+ if (!indent || indent.includes(" ")) return null;
526
+ const cut = cutEntries(body, indent, names);
527
+ if (!cut || cut.found.size !== names.length) return null;
528
+ const { kept } = cut;
529
+ const hasChild = kept.some((l) => l.trim() && !l.trimStart().startsWith("#"));
530
+ const head = hasChild ? lines[block.start] : "mcp_servers: {}";
531
+ return [
532
+ ...lines.slice(0, block.start),
533
+ head,
534
+ ...hasChild ? kept : kept.filter((l) => l.trim()),
535
+ ...lines.slice(block.end)
536
+ ].join("\n");
537
+ }
538
+ //#endregion
539
+ //#region server/listen/mcpsetup.ts
540
+ /**
541
+ * The safety net under every rewrite: the new file, read back with the same readers, must hold exactly the old
542
+ * entries minus the removed ones (each unchanged, deep-equal), plus our entry as intended. Anything else means the
543
+ * rewrite moved a line where it doesn't belong (e.g. a header under another server): refuse it. Returns the
544
+ * problem, or null when it's right.
545
+ */
546
+ function checkRewrite(before, after, ours, removed) {
547
+ if (!before || !after) return "it can't be read back";
548
+ const opaque = (v) => typeof v === "symbol" || !!v && typeof v === "object" && Object.values(v).some(opaque);
549
+ const gone = new Set(removed);
550
+ for (const [name, entry] of Object.entries(before)) {
551
+ if (name === ours.name || gone.has(name)) continue;
552
+ if (!(name in after)) return `"${name}" would disappear`;
553
+ if (opaque(entry) || opaque(after[name])) return `"${name}" has a value this can't read, so the result can't be verified`;
554
+ if (!isDeepStrictEqual(after[name], entry)) return `"${name}" would change`;
555
+ }
556
+ for (const name of removed) if (name in after) return `"${name}" would still be there`;
557
+ for (const name of Object.keys(after)) if (name !== ours.name && !(name in before)) return `"${name}" would appear`;
558
+ if (!isDeepStrictEqual(after[ours.name], ours.entry)) return `"${ours.name}" wouldn't be exactly the new entry`;
559
+ return null;
560
+ }
561
+ const refused = (file, why, manual) => ({
562
+ ok: false,
563
+ message: `Not rewriting ${file}: read back, the result isn't right (${why}). Nothing was changed.`,
564
+ manual
565
+ });
566
+ /** The entries to remove, never the xaq entry itself. */
567
+ const removals = (t, opts) => (opts.removeServers ?? []).filter((r) => r.name !== t.name);
568
+ const removedNote = (extra) => extra.length ? ` Removed ${extra.map((r) => `"${r.name}"`).join(", ")} (now served through xaq).` : "";
569
+ const removedLine = (name, file) => `remove ${name} from ${file} (it's now served through xaq)`;
570
+ /** Placeholder shown wherever the key would go in printed file contents. */
571
+ const KEY_SHOWN = "<the key from your xaq profile>";
572
+ const backupLine = (file) => existsSync(file) ? `back up ${file} to ${file}.xaq-backup-<timestamp> (0600)` : `${file} doesn't exist yet: nothing to back up`;
573
+ const indent = (text) => text.replace(/\n$/, "").split("\n").map((l) => ` ${l}`);
574
+ const redact = (s, key) => s.split(key).join("aib_…(redacted)");
575
+ /**
576
+ * The MCP endpoint is always derived from the URL the user connected to (and transport-checked
577
+ * there), never taken from the daemon: a daemon's advertised mcpUrl comes from its publicUrl setting,
578
+ * which defaults to http://localhost:8425 and could also point the key somewhere else entirely.
579
+ */
580
+ function mcpTarget(profileName, profile) {
581
+ return {
582
+ name: mcpServerName(profileName),
583
+ url: `${profile.url}/mcp`,
584
+ key: profile.apiKey,
585
+ profile: profileName
586
+ };
587
+ }
588
+ function setupMcp(tool, t, profileFile, opts = {}) {
589
+ switch (tool) {
590
+ case "claude-code": return setupClaude(t, t.profile ?? "default", opts);
591
+ case "codex": return setupCodex(t, profileFile, opts);
592
+ case "cursor": return setupCursor(t, profileFile, opts);
593
+ case "hermes": return setupHermes(t, profileFile, opts);
594
+ default: return {
595
+ ok: false,
596
+ message: "No automatic MCP setup for --tool other.",
597
+ manual: manualGeneric(t, profileFile)
598
+ };
599
+ }
600
+ }
601
+ function manualGeneric(t, profileFile) {
602
+ return [`MCP endpoint (streamable HTTP): ${t.url}`, `Header: Authorization: Bearer <key>. The key is the "apiKey" field in ${profileFile}; tools that run a header helper can use \`${NPX} mcp-header\`, which prints it as JSON to a pipe.`];
603
+ }
604
+ const claudeConfigFile = () => process.env.CLAUDE_CONFIG_DIR ? join(process.env.CLAUDE_CONFIG_DIR, ".claude.json") : join(homedir(), ".claude.json");
605
+ function setupClaude(t, profile, opts) {
606
+ const json = JSON.stringify(claudeServerEntry(t.url, profile, { install: !opts.dryRun }));
607
+ const addCommand = `claude mcp add-json --scope user ${t.name} '${json.replace(/'/g, `'"'"'`)}'`;
608
+ const manual = [addCommand];
609
+ const file = claudeConfigFile();
610
+ const verify = [`claude mcp list (shows "${t.name}" as connected; inside Claude Code: /mcp)`];
611
+ const notes = [helperNodeNote(profile, `${NPX} setup-mcp --tool claude-code${profile === "default" ? "" : ` --profile ${profile}`}`)];
612
+ const removeArgs = removals(t, opts).map((r) => ({
613
+ r,
614
+ args: [
615
+ "mcp",
616
+ "remove",
617
+ r.name,
618
+ "--scope",
619
+ r.scope ?? "user"
620
+ ],
621
+ cwd: r.projectDir
622
+ }));
623
+ const changes = [
624
+ `write the header helper ${helperScriptPath()} (0600; reads the key from your profile when Claude Code connects)`,
625
+ backupLine(file),
626
+ `run: claude mcp remove ${t.name} --scope user (drops an earlier "${t.name}" entry, if any)`,
627
+ `run: ${addCommand}`,
628
+ ...removeArgs.map((x) => `run: claude ${x.args.join(" ")}${x.cwd ? ` (in ${x.cwd})` : ""} (now served through xaq)`),
629
+ `the key stays in your xaq profile: neither the command line nor ${file} contains it`
630
+ ];
631
+ if (opts.dryRun) return {
632
+ ok: true,
633
+ dryRun: true,
634
+ message: `Dry run for Claude Code: nothing was changed. setup-mcp would:`,
635
+ changes,
636
+ verify,
637
+ notes
638
+ };
639
+ const probe = spawnSync("claude", ["--version"], { stdio: "ignore" });
640
+ if (probe.error || probe.status !== 0) return {
641
+ ok: false,
642
+ message: "The `claude` CLI was not found on PATH.",
643
+ manual
644
+ };
645
+ const backup = backupFile(file);
646
+ spawnSync("claude", [
647
+ "mcp",
648
+ "remove",
649
+ t.name,
650
+ "--scope",
651
+ "user"
652
+ ], { stdio: "ignore" });
653
+ const r = spawnSync("claude", [
654
+ "mcp",
655
+ "add-json",
656
+ "--scope",
657
+ "user",
658
+ t.name,
659
+ json
660
+ ], {
661
+ encoding: "utf8",
662
+ stdio: [
663
+ "ignore",
664
+ "pipe",
665
+ "pipe"
666
+ ]
667
+ });
668
+ if (r.status !== 0) {
669
+ const out = redact(`${r.stderr ?? ""}${r.stdout ?? ""}`, t.key).trim();
670
+ return {
671
+ ok: false,
672
+ message: `claude mcp add-json failed${out ? `: ${safeText(out, 300)}` : ""}`,
673
+ backup,
674
+ manual
675
+ };
676
+ }
677
+ const done = [];
678
+ for (const x of removeArgs) if (spawnSync("claude", x.args, {
679
+ encoding: "utf8",
680
+ stdio: [
681
+ "ignore",
682
+ "pipe",
683
+ "pipe"
684
+ ],
685
+ cwd: x.cwd
686
+ }).status === 0) done.push(`ran: claude ${x.args.join(" ")}`);
687
+ else notes.push(`Couldn't remove "${x.r.name}" (${x.r.scope ?? "user"} scope): remove it yourself with claude ${x.args.join(" ")}${x.cwd ? ` in ${x.cwd}` : ""}.`);
688
+ if (existsSync(file) && isTooOpen(file)) chmodSync(file, 384);
689
+ return {
690
+ ok: true,
691
+ message: `Registered MCP server "${t.name}" in Claude Code (user scope). The key stays in your xaq profile; Claude Code asks for it through a header helper.`,
692
+ backup,
693
+ changes: [
694
+ `ran: ${addCommand}`,
695
+ `${file} now lists "${t.name}" (user scope)`,
696
+ ...done
697
+ ],
698
+ verify,
699
+ notes,
700
+ removed: removeArgs.filter((x) => done.includes(`ran: claude ${x.args.join(" ")}`)).map((x) => x.r.name)
701
+ };
702
+ }
703
+ const codexConfigFile = () => join(process.env.CODEX_HOME || join(homedir(), ".codex"), "config.toml");
704
+ const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
705
+ const tomlString = (s) => JSON.stringify(s);
706
+ /** Removes `[mcp_servers.<name>]` and its sub-tables from a TOML document, or returns null if it's defined in a form we won't rewrite safely. */
707
+ function removeTomlServer(toml, name) {
708
+ const keyForms = `(?:${escapeRe(name)}|"${escapeRe(name)}"|'${escapeRe(name)}')`;
709
+ const header = new RegExp(`^\\s*\\[\\s*mcp_servers\\s*\\.\\s*${keyForms}\\s*(?:\\.[^\\]]*)?\\]\\s*(?:#.*)?$`);
710
+ const anyHeader = /^\s*\[/;
711
+ if (new RegExp(`^\\s*mcp_servers\\s*(?:=|\\.\\s*${keyForms}\\s*[.=])`, "m").test(toml)) return null;
712
+ const out = [];
713
+ let skipping = false;
714
+ for (const line of toml.split("\n")) {
715
+ if (anyHeader.test(line)) skipping = header.test(line);
716
+ if (!skipping && line.trim() !== CODEX_MARKER && line.trim() !== LEGACY_CODEX_MARKER) out.push(line);
717
+ }
718
+ return out.join("\n").replace(/\n{3,}/g, "\n\n");
719
+ }
720
+ const CODEX_MARKER = "# Added by `xaq setup-mcp`. The key is also in your xaq profile; rotate it in the console.";
721
+ /** The marker setup-mcp wrote before the rename: dropped too when the table is rewritten. */
722
+ const LEGACY_CODEX_MARKER = "# Added by `agentinbox setup-mcp`. The key is also in your agentinbox profile; rotate it in the console.";
723
+ function codexBlock(t) {
724
+ return [
725
+ CODEX_MARKER,
726
+ `[mcp_servers.${t.name}]`,
727
+ `url = ${tomlString(t.url)}`,
728
+ `http_headers = { Authorization = ${tomlString(`Bearer ${t.key}`)} }`,
729
+ `# codex exec never prompts: approve ${t.name} tools up front (the grant's send mode still applies).`,
730
+ `default_tools_approval_mode = "approve"`,
731
+ ""
732
+ ].join("\n");
733
+ }
734
+ function setupCodex(t, profileFile, opts) {
735
+ const file = codexConfigFile();
736
+ const manual = [
737
+ `Add to ${file}:`,
738
+ ` [mcp_servers.${t.name}]`,
739
+ ` url = "${t.url}"`,
740
+ ` http_headers = { Authorization = "Bearer <key>" } # <key>: the apiKey field of ${profileFile}`,
741
+ ` default_tools_approval_mode = "approve"`
742
+ ];
743
+ const current = existsSync(file) ? readFileSync(file, "utf8") : "";
744
+ const withoutOurs = removeTomlServer(current, t.name);
745
+ if (withoutOurs === null) return {
746
+ ok: false,
747
+ message: `${file} defines mcp_servers inline; not rewriting it automatically.`,
748
+ manual
749
+ };
750
+ let without = withoutOurs;
751
+ const extra = removals(t, opts);
752
+ for (const r of extra) {
753
+ const next = removeTomlServer(without, r.name);
754
+ if (next === null || next === without) return {
755
+ ok: false,
756
+ message: `Couldn't remove [mcp_servers.${r.name}] from ${file} safely; nothing was changed.`,
757
+ manual
758
+ };
759
+ without = next;
760
+ }
761
+ const verify = [`codex mcp list (shows "${t.name}")`];
762
+ const replaces = withoutOurs !== current.replace(/\n{3,}/g, "\n\n") ? `replace [mcp_servers.${t.name}] in` : `add [mcp_servers.${t.name}] to`;
763
+ const changes = [
764
+ backupLine(file),
765
+ ...extra.map((r) => removedLine(`[mcp_servers.${r.name}]`, file)),
766
+ `${replaces} ${file} (other tables are kept) and set the file to 0600, since it will hold the key:`,
767
+ ...indent(codexBlock({
768
+ ...t,
769
+ key: KEY_SHOWN
770
+ }))
771
+ ];
772
+ const base = without.replace(/\s*$/, "");
773
+ const next = `${base ? `${base}\n\n` : ""}${codexBlock(t)}`;
774
+ const toTable = (x) => x;
775
+ const problem = checkRewrite(toTable(readTomlMcpServers(current)), toTable(readTomlMcpServers(next)), {
776
+ name: t.name,
777
+ entry: {
778
+ url: t.url,
779
+ http_headers: { Authorization: `Bearer ${t.key}` },
780
+ default_tools_approval_mode: "approve"
781
+ }
782
+ }, extra.map((r) => r.name));
783
+ if (problem) return refused(file, problem, manual);
784
+ if (opts.dryRun) return {
785
+ ok: true,
786
+ dryRun: true,
787
+ message: `Dry run for Codex: nothing was changed. setup-mcp would:`,
788
+ changes,
789
+ verify
790
+ };
791
+ const backup = backupFile(file);
792
+ writePrivateFile(file, next, { privateDir: false });
793
+ return {
794
+ ok: true,
795
+ message: `Added [mcp_servers.${t.name}] to ${file} (now mode 0600: it holds the key).${removedNote(extra)}`,
796
+ backup,
797
+ verify,
798
+ removed: extra.map((r) => r.name)
799
+ };
800
+ }
801
+ const cursorConfigFile = () => join(homedir(), ".cursor", "mcp.json");
802
+ function setupCursor(t, profileFile, opts) {
803
+ const file = cursorConfigFile();
804
+ const manual = [`Add to ${file} under "mcpServers":`, ` "${t.name}": { "url": "${t.url}", "headers": { "Authorization": "Bearer <key>" } } // <key>: the apiKey field of ${profileFile}`];
805
+ let doc = {};
806
+ if (existsSync(file)) {
807
+ const text = readFileSync(file, "utf8");
808
+ if (text.trim()) {
809
+ try {
810
+ doc = JSON.parse(text);
811
+ } catch {
812
+ return {
813
+ ok: false,
814
+ message: `${file} is not plain JSON (comments?); not rewriting it automatically.`,
815
+ manual
816
+ };
817
+ }
818
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) return {
819
+ ok: false,
820
+ message: `${file} is not a JSON object.`,
821
+ manual
822
+ };
823
+ }
824
+ }
825
+ const servers = doc.mcpServers && typeof doc.mcpServers === "object" && !Array.isArray(doc.mcpServers) ? doc.mcpServers : {};
826
+ const verify = [`agent mcp list (Cursor CLI; or Cursor Settings → MCP shows "${t.name}")`];
827
+ const entry = (key) => ({
828
+ url: t.url,
829
+ headers: { Authorization: `Bearer ${key}` }
830
+ });
831
+ const extra = removals(t, opts);
832
+ const absent = extra.find((r) => !(r.name in servers));
833
+ if (absent) return {
834
+ ok: false,
835
+ message: `"mcpServers.${absent.name}" isn't in ${file}; nothing was changed.`,
836
+ manual
837
+ };
838
+ const changes = [
839
+ backupLine(file),
840
+ ...extra.map((r) => removedLine(`"mcpServers.${r.name}"`, file)),
841
+ `${t.name in servers ? "replace" : "add"} "mcpServers.${t.name}" ${t.name in servers ? "in" : "to"} ${file} (other servers are kept) and set the file to 0600, since it will hold the key:`,
842
+ ...indent(`"${t.name}": ${JSON.stringify(entry(KEY_SHOWN), null, 2)}`)
843
+ ];
844
+ if (opts.dryRun) return {
845
+ ok: true,
846
+ dryRun: true,
847
+ message: `Dry run for Cursor: nothing was changed. setup-mcp would:`,
848
+ changes,
849
+ verify
850
+ };
851
+ const before = structuredClone(servers);
852
+ for (const r of extra) delete servers[r.name];
853
+ servers[t.name] = entry(t.key);
854
+ doc.mcpServers = servers;
855
+ const reread = JSON.parse(JSON.stringify(doc)).mcpServers;
856
+ const problem = checkRewrite(before, reread, {
857
+ name: t.name,
858
+ entry: entry(t.key)
859
+ }, extra.map((r) => r.name));
860
+ if (problem) return refused(file, problem, manual);
861
+ const backup = backupFile(file);
862
+ writePrivateFile(file, JSON.stringify(doc, null, 2) + "\n", { privateDir: false });
863
+ return {
864
+ ok: true,
865
+ message: `Added "${t.name}" to ${file} (now mode 0600: it holds the key).${removedNote(extra)} Cursor may ask you to enable it once.`,
866
+ backup,
867
+ verify,
868
+ removed: extra.map((r) => r.name)
869
+ };
870
+ }
871
+ /**
872
+ * The Hermes home a bare `hermes` uses (hermes_constants.py / hermes_cli/profiles.py): $HERMES_HOME, else
873
+ * ~/.hermes (plus HERMES_DATA_DIR_SUFFIX), switched to profiles/<name> by the sticky `active_profile` file.
874
+ * Commands xaq runs get HERMES_HOME set to this, so they edit the same files.
875
+ */
876
+ function hermesHome(env = process.env) {
877
+ const set = env.HERMES_HOME?.trim();
878
+ if (set) return set.replace(/^~(?=\/|$)/, homedir());
879
+ const root = join(homedir(), `.hermes${env.HERMES_DATA_DIR_SUFFIX ?? ""}`);
880
+ let active = "";
881
+ try {
882
+ active = readFileSync(join(root, "active_profile"), "utf8").replace(/^\uFEFF/, "").trim().toLowerCase();
883
+ } catch {}
884
+ return active && active !== "default" && /^[a-z0-9][a-z0-9_-]*$/.test(active) ? join(root, "profiles", active) : root;
885
+ }
886
+ /**
887
+ * Whether `mcp_servers.<name>` is on in a Hermes config.yaml ("enabled" absent, null or not a false word
888
+ * is on, like Hermes' mcp_server_enabled). "unknown" for layouts this small block-YAML reader won't judge:
889
+ * callers that need it on treat that as not on.
890
+ */
891
+ function yamlMcpServerState(yaml, name) {
892
+ const lines = yaml.replace(/^\uFEFF/, "").split("\n");
893
+ const tops = lines.flatMap((l, i) => /^mcp_servers\s*:/.test(l) ? [i] : []);
894
+ if (tops.length === 0) return "missing";
895
+ if (tops.length > 1) return "unknown";
896
+ const top = tops[0];
897
+ const rest = lines[top].replace(/^mcp_servers\s*:/, "").replace(/\s#.*$/, "").trim();
898
+ if (rest) return rest === "{}" || rest === "null" || rest === "~" ? "missing" : "unknown";
899
+ let end = top + 1;
900
+ while (end < lines.length && (lines[end].trim() === "" || /^\s/.test(lines[end]) || lines[end].startsWith("#"))) end++;
901
+ const body = lines.slice(top + 1, end).filter((l) => l.trim() && !l.trimStart().startsWith("#"));
902
+ if (!body.length) return "missing";
903
+ const indent = body[0].match(/^(\s*)/)[1];
904
+ if (!indent || /\t/.test(indent) || body.some((l) => /[&*]|<</.test(l.split("#")[0]))) return "unknown";
905
+ const key = new RegExp(`^${indent}(["']?)${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\1\\s*:(.*)$`);
906
+ const starts = body.flatMap((l, i) => key.test(l) ? [i] : []);
907
+ if (starts.length === 0) return "missing";
908
+ if (starts.length > 1) return "unknown";
909
+ if (key.exec(body[starts[0]])[2].replace(/\s#.*$/, "").trim()) return "unknown";
910
+ const own = [];
911
+ for (const l of body.slice(starts[0] + 1)) {
912
+ if (l.match(/^(\s*)/)[1].length <= indent.length) break;
913
+ own.push(l);
914
+ }
915
+ if (!own.length) return "unknown";
916
+ const childIndent = own[0].match(/^(\s*)/)[1].length;
917
+ const enabled = own.filter((l) => l.match(/^(\s*)/)[1].length === childIndent && /^\s*["']?enabled["']?\s*:/.test(l));
918
+ if (enabled.length > 1) return "unknown";
919
+ if (!enabled.length) return "enabled";
920
+ const value = enabled[0].replace(/^\s*["']?enabled["']?\s*:/, "").replace(/\s#.*$/, "").trim().replace(/^["']|["']$/g, "").toLowerCase();
921
+ return [
922
+ "false",
923
+ "0",
924
+ "no",
925
+ "off"
926
+ ].includes(value) ? "disabled" : "enabled";
927
+ }
928
+ const hermesEnvVar = (name) => name.toUpperCase().replace(/[^A-Z0-9]/g, "_") + "_API_KEY";
929
+ /**
930
+ * Inserts or replaces `mcp_servers.<name>` in a YAML document without a YAML library, handling the
931
+ * common block-style layout. Returns null for anything unusual (flow style, anchors, odd indentation).
932
+ */
933
+ function upsertYamlServer(yaml, name, entry) {
934
+ const lines = yaml.split("\n");
935
+ const top = lines.findIndex((l) => /^mcp_servers\s*:/.test(l));
936
+ const block = (indent) => [`${indent}${name}:`, ...entry.map((e) => `${indent}${indent}${e}`)];
937
+ if (top < 0) {
938
+ const base = yaml.replace(/\s*$/, "");
939
+ return `${base ? `${base}\n\n` : ""}mcp_servers:\n${block(" ").join("\n")}\n`;
940
+ }
941
+ const rest = lines[top].replace(/^mcp_servers\s*:/, "").replace(/#.*$/, "").trim();
942
+ if (rest && rest !== "{}" && rest !== "null" && rest !== "~") return null;
943
+ if (rest) lines[top] = "mcp_servers:";
944
+ let end = top + 1;
945
+ while (end < lines.length && (lines[end].trim() === "" || /^\s/.test(lines[end]))) end++;
946
+ const body = lines.slice(top + 1, end);
947
+ const firstChild = body.find((l) => l.trim() && !l.trimStart().startsWith("#"));
948
+ const indent = firstChild ? firstChild.match(/^(\s*)/)[1] : " ";
949
+ if (!indent || /\t/.test(indent)) return null;
950
+ const cut = cutEntries(body, indent, [name]);
951
+ if (!cut) return null;
952
+ const { kept } = cut;
953
+ const insert = block(indent);
954
+ return [
955
+ ...lines.slice(0, top + 1),
956
+ ...insert,
957
+ ...kept,
958
+ ...lines.slice(end)
959
+ ].join("\n");
960
+ }
961
+ function upsertEnvLine(text, key, value) {
962
+ const line = `${key}=${value}`;
963
+ const re = new RegExp(`^\\s*(?:export\\s+)?${escapeRe(key)}\\s*=.*$`, "m");
964
+ if (re.test(text)) return text.replace(re, line);
965
+ const base = text.replace(/\s*$/, "");
966
+ return `${base ? `${base}\n` : ""}${line}\n`;
967
+ }
968
+ function setupHermes(t, profileFile, opts) {
969
+ const home = hermesHome();
970
+ const configFile = join(home, "config.yaml");
971
+ const envFile = join(home, ".env");
972
+ const envVar = hermesEnvVar(t.name);
973
+ const entry = [
974
+ `url: ${JSON.stringify(t.url)}`,
975
+ `headers:`,
976
+ ` Authorization: "Bearer \${${envVar}}"`
977
+ ];
978
+ const manual = [
979
+ `Add to ${configFile}:`,
980
+ ` mcp_servers:`,
981
+ ` ${t.name}:`,
982
+ ...entry.map((e) => ` ${e}`),
983
+ `and to ${envFile}: ${envVar}=<key> (<key>: the apiKey field of ${profileFile})`,
984
+ `then check it with: hermes mcp test ${t.name}`
985
+ ];
986
+ const current = existsSync(configFile) ? readFileSync(configFile, "utf8") : "";
987
+ const extra = removals(t, opts);
988
+ const blk = mcpServersBlock(current);
989
+ if (blk && hasAnchors(current.split("\n").slice(blk.start + 1, blk.end))) return {
990
+ ok: false,
991
+ message: `${configFile} uses YAML anchors, aliases or merge keys (&, *, <<) under mcp_servers; not rewriting it automatically. Nothing was changed.`,
992
+ manual
993
+ };
994
+ const upserted = upsertYamlServer(current, t.name, entry);
995
+ const next = upserted === null ? null : removeYamlServers(upserted, extra.map((r) => r.name));
996
+ if (upserted !== null && next === null) return {
997
+ ok: false,
998
+ message: `Couldn't remove ${extra.map((r) => `"${r.name}"`).join(", ")} from ${configFile} safely; nothing was changed.`,
999
+ manual
1000
+ };
1001
+ if (next === null) return {
1002
+ ok: false,
1003
+ message: `${configFile} uses a layout we won't rewrite automatically.`,
1004
+ manual
1005
+ };
1006
+ {
1007
+ const ours = {
1008
+ url: t.url,
1009
+ headers: { Authorization: `Bearer \${${envVar}}` }
1010
+ };
1011
+ const problem = checkRewrite(readMcpServers(current), readMcpServers(next), {
1012
+ name: t.name,
1013
+ entry: ours
1014
+ }, extra.map((r) => r.name));
1015
+ if (problem) return refused(configFile, problem, manual);
1016
+ }
1017
+ const verify = [`hermes mcp test ${t.name}`];
1018
+ const changes = [
1019
+ backupLine(configFile),
1020
+ backupLine(envFile),
1021
+ ...extra.map((r) => removedLine(`"mcp_servers.${r.name}"`, configFile)),
1022
+ `add (or replace) "mcp_servers.${t.name}" in ${configFile} (0600):`,
1023
+ ...indent([
1024
+ "mcp_servers:",
1025
+ ` ${t.name}:`,
1026
+ ...entry.map((e) => ` ${e}`)
1027
+ ].join("\n")),
1028
+ `set ${envVar}=${KEY_SHOWN} in ${envFile} (0600; Hermes' place for secrets)`
1029
+ ];
1030
+ if (opts.dryRun) return {
1031
+ ok: true,
1032
+ dryRun: true,
1033
+ message: `Dry run for Hermes: nothing was changed. setup-mcp would:`,
1034
+ changes,
1035
+ verify
1036
+ };
1037
+ const envCurrent = existsSync(envFile) ? readFileSync(envFile, "utf8") : "";
1038
+ const backup = backupFile(configFile);
1039
+ const envBackup = backupFile(envFile);
1040
+ writePrivateFile(envFile, upsertEnvLine(envCurrent, envVar, t.key), { privateDir: false });
1041
+ writePrivateFile(configFile, next.endsWith("\n") ? next : `${next}\n`, { privateDir: false });
1042
+ return {
1043
+ ok: true,
1044
+ message: `Added "${t.name}" to ${configFile}; the key is in ${envFile} as ${envVar} (0600).${removedNote(extra)}`,
1045
+ backup: [backup, envBackup].filter(Boolean).join(", ") || null,
1046
+ verify,
1047
+ removed: extra.map((r) => r.name)
1048
+ };
1049
+ }
1050
+ //#endregion
1051
+ export { readTomlMcpServers as _, cursorConfigFile as a, mcpTarget as c, upsertYamlServer as d, yamlMcpServerState as f, UNREADABLE$1 as g, readTopMap as h, codexConfigFile as i, removeTomlServer as l, readMcpServers as m, claudeConfigFile as n, hermesEnvVar as o, UNREADABLE as p, codexBlock as r, hermesHome as s, checkRewrite as t, setupMcp as u };
1052
+
1053
+ //# sourceMappingURL=mcpsetup-DXXo2qjf.js.map