agentinbox-cli 0.0.0 → 0.1.0-nightly.10

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 (176) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +337 -0
  3. package/dist/admin-CXkmdTYo.js +196 -0
  4. package/dist/admin-CXkmdTYo.js.map +1 -0
  5. package/dist/audit-bItbJYFP.js +65 -0
  6. package/dist/audit-bItbJYFP.js.map +1 -0
  7. package/dist/audit-gVdDzkAE.js +2 -0
  8. package/dist/auth-0NrMvHdE.js +550 -0
  9. package/dist/auth-0NrMvHdE.js.map +1 -0
  10. package/dist/auth-CoHvbWgO.js +2 -0
  11. package/dist/bus-Cz0flS37.js +12 -0
  12. package/dist/bus-Cz0flS37.js.map +1 -0
  13. package/dist/bus-OKIGut72.js +2 -0
  14. package/dist/cli.js +220 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/commands-DZ60j8_y.js +103 -0
  17. package/dist/commands-DZ60j8_y.js.map +1 -0
  18. package/dist/config-CIvgMqLx.js +2 -0
  19. package/dist/config-v975gxv4.js +44 -0
  20. package/dist/config-v975gxv4.js.map +1 -0
  21. package/dist/connect-BHC2trBi.js +292 -0
  22. package/dist/connect-BHC2trBi.js.map +1 -0
  23. package/dist/content-BXblC_Qs.js +1162 -0
  24. package/dist/content-BXblC_Qs.js.map +1 -0
  25. package/dist/content-worker.js +28 -0
  26. package/dist/content-worker.js.map +1 -0
  27. package/dist/crypto-DYZ4R7c2.js +74 -0
  28. package/dist/crypto-DYZ4R7c2.js.map +1 -0
  29. package/dist/db-B6gLQGBK.js +2 -0
  30. package/dist/db-D0gvshCc.js +282 -0
  31. package/dist/db-D0gvshCc.js.map +1 -0
  32. package/dist/errors-FbMS3QBo.js +39 -0
  33. package/dist/errors-FbMS3QBo.js.map +1 -0
  34. package/dist/filter-DGVz6-P1.js +140 -0
  35. package/dist/filter-DGVz6-P1.js.map +1 -0
  36. package/dist/instance-BBzEsYZO.js +175 -0
  37. package/dist/instance-BBzEsYZO.js.map +1 -0
  38. package/dist/instance-DLNTj9nm.js +2 -0
  39. package/dist/listen-D8DkEhqM.js +1724 -0
  40. package/dist/listen-D8DkEhqM.js.map +1 -0
  41. package/dist/listen-DT-omyD6.js +2 -0
  42. package/dist/mcpheader-By2dIyZq.js +2 -0
  43. package/dist/mcpheader-D9rrI-aa.js +65 -0
  44. package/dist/mcpheader-D9rrI-aa.js.map +1 -0
  45. package/dist/mcpsetup-7KxroOCp.js +2 -0
  46. package/dist/mcpsetup-DJ7ebFGJ.js +266 -0
  47. package/dist/mcpsetup-DJ7ebFGJ.js.map +1 -0
  48. package/dist/paths-BCBp8vrG.js +2 -0
  49. package/dist/paths-TLp8hiMt.js +40 -0
  50. package/dist/paths-TLp8hiMt.js.map +1 -0
  51. package/dist/profile-CgGgJ4Dm.js +181 -0
  52. package/dist/profile-CgGgJ4Dm.js.map +1 -0
  53. package/dist/profile-RbN2FRz7.js +2 -0
  54. package/dist/recipes-7d_Xws4M.js +2 -0
  55. package/dist/recipes-UalOc_LB.js +100 -0
  56. package/dist/recipes-UalOc_LB.js.map +1 -0
  57. package/dist/send-Dk-t8ia-.js +108 -0
  58. package/dist/send-Dk-t8ia-.js.map +1 -0
  59. package/dist/server-Hunjpt8n.js +8103 -0
  60. package/dist/server-Hunjpt8n.js.map +1 -0
  61. package/dist/service-BAgZDh22.js +2 -0
  62. package/dist/service-DYdD92CC.js +2 -0
  63. package/dist/service-D_ZEs3Zk.js +508 -0
  64. package/dist/service-D_ZEs3Zk.js.map +1 -0
  65. package/dist/service-whbugeQp.js +295 -0
  66. package/dist/service-whbugeQp.js.map +1 -0
  67. package/dist/settings-CeEsv4CE.js +2 -0
  68. package/dist/settings-DLUStIN7.js +109 -0
  69. package/dist/settings-DLUStIN7.js.map +1 -0
  70. package/dist/setup-token-BO89xnpb.js +48 -0
  71. package/dist/setup-token-BO89xnpb.js.map +1 -0
  72. package/dist/skill/SKILL.md +277 -0
  73. package/dist/skill-BJFn2bHK.js +2 -0
  74. package/dist/skill-WKbeyNct.js +43 -0
  75. package/dist/skill-WKbeyNct.js.map +1 -0
  76. package/dist/types-3eEsBFVj.js +37 -0
  77. package/dist/types-3eEsBFVj.js.map +1 -0
  78. package/dist/ui-B8Sd4Y7Y.js +81 -0
  79. package/dist/ui-B8Sd4Y7Y.js.map +1 -0
  80. package/dist/urls-Dp8pBGMU.js +2 -0
  81. package/dist/urls-U_LfYV61.js +27 -0
  82. package/dist/urls-U_LfYV61.js.map +1 -0
  83. package/dist/web/assets/_agentId-DAj08NDq.js +1 -0
  84. package/dist/web/assets/_app-B6q_HS9y.js +1 -0
  85. package/dist/web/assets/_app-CAQ6Mu1s.js +63 -0
  86. package/dist/web/assets/_webhookId-CMgBUN2Z.js +1 -0
  87. package/dist/web/assets/account-status-omoUlgBI.js +1 -0
  88. package/dist/web/assets/accounts-DyLfACj_.js +1 -0
  89. package/dist/web/assets/accounts-list-DiXACW0-.js +1 -0
  90. package/dist/web/assets/activity-zrckFhPk.js +1 -0
  91. package/dist/web/assets/agent-create-form-DKwaY1Dg.js +1 -0
  92. package/dist/web/assets/agents-Drvjccgt.js +1 -0
  93. package/dist/web/assets/approvals-4kd0_J8X.js +2 -0
  94. package/dist/web/assets/arrow-left-DVJV4Zx7.js +1 -0
  95. package/dist/web/assets/arrow-right-4PFX6MSS.js +1 -0
  96. package/dist/web/assets/audit-76H45orR.js +1 -0
  97. package/dist/web/assets/audit-bits-B1We-fQ3.js +1 -0
  98. package/dist/web/assets/bot-DVw15lBn.js +1 -0
  99. package/dist/web/assets/card-EQTpqntn.js +1 -0
  100. package/dist/web/assets/checkbox-DDZZurgK.js +1 -0
  101. package/dist/web/assets/chevron-down-892ylB4R.js +1 -0
  102. package/dist/web/assets/chevron-right-CbBKS6-T.js +1 -0
  103. package/dist/web/assets/chips-input--xgMZpg2.js +1 -0
  104. package/dist/web/assets/circle-x-Bi4IGwky.js +1 -0
  105. package/dist/web/assets/common-PG6weTqi.js +1 -0
  106. package/dist/web/assets/connect-host-DdkOJnMu.js +1 -0
  107. package/dist/web/assets/data-table-Ca9aQZKR.js +1 -0
  108. package/dist/web/assets/dialog-CxNRCZdE.js +1 -0
  109. package/dist/web/assets/dist-B0e3Ju3r.js +1 -0
  110. package/dist/web/assets/empty-CE_WZGEC.js +1 -0
  111. package/dist/web/assets/es2015-DPaldoxG.js +41 -0
  112. package/dist/web/assets/event-row-2xoyzgbP.js +1 -0
  113. package/dist/web/assets/events-Bizq3DPC.js +1 -0
  114. package/dist/web/assets/eye-off-DjpaihHf.js +1 -0
  115. package/dist/web/assets/field-CcyMn3ey.js +1 -0
  116. package/dist/web/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  117. package/dist/web/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  118. package/dist/web/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  119. package/dist/web/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  120. package/dist/web/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  121. package/dist/web/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  122. package/dist/web/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  123. package/dist/web/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  124. package/dist/web/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  125. package/dist/web/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  126. package/dist/web/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  127. package/dist/web/assets/get-started-DXUVmyNX.js +1 -0
  128. package/dist/web/assets/grants-BhXj1Pew.js +1 -0
  129. package/dist/web/assets/index-Bd4Y5wSp.js +10 -0
  130. package/dist/web/assets/index-akvt_1UI.css +2 -0
  131. package/dist/web/assets/input-CMhmmHiH.js +1 -0
  132. package/dist/web/assets/input-group-DRs3TNaT.js +1 -0
  133. package/dist/web/assets/key-round-CVIZRCLM.js +1 -0
  134. package/dist/web/assets/labels-C_Pwium9.js +1 -0
  135. package/dist/web/assets/login-aCr-cPqJ.js +1 -0
  136. package/dist/web/assets/mail-CAVmuESo.js +1 -0
  137. package/dist/web/assets/mutations-DVlbDx0M.js +1 -0
  138. package/dist/web/assets/paperclip-QFjM4y6n.js +1 -0
  139. package/dist/web/assets/password-input-_m9Ycphv.js +1 -0
  140. package/dist/web/assets/pencil-Bf1JhsG0.js +1 -0
  141. package/dist/web/assets/play-DiPk49JH.js +1 -0
  142. package/dist/web/assets/plus-CETmK0SB.js +1 -0
  143. package/dist/web/assets/preload-helper-BFuRUfdt.js +1 -0
  144. package/dist/web/assets/progress-3ZOsbLSK.js +6 -0
  145. package/dist/web/assets/queries-a3M5_loa.js +2 -0
  146. package/dist/web/assets/refresh-cw-DOn9kD7g.js +1 -0
  147. package/dist/web/assets/search-gcYjitzV.js +1 -0
  148. package/dist/web/assets/select-C8tJBHb5.js +1 -0
  149. package/dist/web/assets/send-BhQwhuJo.js +1 -0
  150. package/dist/web/assets/settings-H7ExHD9L.js +1 -0
  151. package/dist/web/assets/settings-form-CNUionsI.js +1 -0
  152. package/dist/web/assets/setup-DLbH5Dvl.js +1 -0
  153. package/dist/web/assets/sheet-BQKpA2xa.js +1 -0
  154. package/dist/web/assets/shield-alert-CbFgd06X.js +1 -0
  155. package/dist/web/assets/stamp-CH2OvjH2.js +1 -0
  156. package/dist/web/assets/stream-C2Tr2d5O.js +1 -0
  157. package/dist/web/assets/tabs-ySiz7Mni.js +1 -0
  158. package/dist/web/assets/tag-CP72Kk6Y.js +1 -0
  159. package/dist/web/assets/test-connection-Bd2Gac0O.js +1 -0
  160. package/dist/web/assets/textarea-CCokBZIw.js +1 -0
  161. package/dist/web/assets/toggle-group-ne0pVDg6.js +1 -0
  162. package/dist/web/assets/trash-DxdMNg8B.js +1 -0
  163. package/dist/web/assets/use-sign-out-BuzlTb0Q.js +1 -0
  164. package/dist/web/assets/useInfiniteQuery-DE6WChlb.js +1 -0
  165. package/dist/web/assets/useMatch-BKUnm54_.js +1 -0
  166. package/dist/web/assets/useMutation-DeWvLw1a.js +1 -0
  167. package/dist/web/assets/useQuery-DAhf1YDH.js +1 -0
  168. package/dist/web/assets/useSelector-D70eKNYs.js +1 -0
  169. package/dist/web/assets/webhook-BWT75M78.js +1 -0
  170. package/dist/web/assets/webhook-sheet-Cy_jHIqG.js +34 -0
  171. package/dist/web/assets/webhooks-CTAj5Ovp.js +1 -0
  172. package/dist/web/assets/with-selector-CkNyXYMW.js +1 -0
  173. package/dist/web/assets/x-BHjDLXNO.js +1 -0
  174. package/dist/web/favicon.svg +1 -0
  175. package/dist/web/index.html +31 -0
  176. package/package.json +95 -1
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env node
2
+ import { i as safeText } from "./ui-B8Sd4Y7Y.js";
3
+ import { c as isTooOpen, d as NPX, o as backupFile, u as writePrivateFile } from "./profile-CgGgJ4Dm.js";
4
+ import { n as claudeServerEntry } from "./mcpheader-D9rrI-aa.js";
5
+ import { r as mcpServerName } from "./recipes-UalOc_LB.js";
6
+ import { chmodSync, existsSync, readFileSync } from "node:fs";
7
+ import { homedir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { spawnSync } from "node:child_process";
10
+ //#region server/listen/mcpsetup.ts
11
+ const redact = (s, key) => s.split(key).join("aib_…(redacted)");
12
+ /**
13
+ * The MCP endpoint is always derived from the URL the user connected to (and transport-checked
14
+ * there), never taken from the daemon: a daemon's advertised mcpUrl comes from its publicUrl setting,
15
+ * which defaults to http://localhost:8425 and could also point the key somewhere else entirely.
16
+ */
17
+ function mcpTarget(profileName, profile) {
18
+ return {
19
+ name: mcpServerName(profileName),
20
+ url: `${profile.url}/mcp`,
21
+ key: profile.apiKey,
22
+ profile: profileName
23
+ };
24
+ }
25
+ function setupMcp(tool, t, profileFile) {
26
+ switch (tool) {
27
+ case "claude-code": return setupClaude(t, t.profile ?? "default");
28
+ case "codex": return setupCodex(t, profileFile);
29
+ case "cursor": return setupCursor(t, profileFile);
30
+ case "hermes": return setupHermes(t, profileFile);
31
+ default: return {
32
+ ok: false,
33
+ message: "No automatic MCP setup for --tool other.",
34
+ manual: manualGeneric(t, profileFile)
35
+ };
36
+ }
37
+ }
38
+ function manualGeneric(t, profileFile) {
39
+ 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.`];
40
+ }
41
+ const claudeConfigFile = () => process.env.CLAUDE_CONFIG_DIR ? join(process.env.CLAUDE_CONFIG_DIR, ".claude.json") : join(homedir(), ".claude.json");
42
+ function setupClaude(t, profile) {
43
+ const json = JSON.stringify(claudeServerEntry(t.url, profile));
44
+ const manual = [`claude mcp add-json --scope user ${t.name} '${json.replace(/'/g, `'"'"'`)}'`];
45
+ const probe = spawnSync("claude", ["--version"], { stdio: "ignore" });
46
+ if (probe.error || probe.status !== 0) return {
47
+ ok: false,
48
+ message: "The `claude` CLI was not found on PATH.",
49
+ manual
50
+ };
51
+ const file = claudeConfigFile();
52
+ const backup = backupFile(file);
53
+ spawnSync("claude", [
54
+ "mcp",
55
+ "remove",
56
+ t.name,
57
+ "--scope",
58
+ "user"
59
+ ], { stdio: "ignore" });
60
+ const r = spawnSync("claude", [
61
+ "mcp",
62
+ "add-json",
63
+ "--scope",
64
+ "user",
65
+ t.name,
66
+ json
67
+ ], {
68
+ encoding: "utf8",
69
+ stdio: [
70
+ "ignore",
71
+ "pipe",
72
+ "pipe"
73
+ ]
74
+ });
75
+ if (r.status !== 0) {
76
+ const out = redact(`${r.stderr ?? ""}${r.stdout ?? ""}`, t.key).trim();
77
+ return {
78
+ ok: false,
79
+ message: `claude mcp add-json failed${out ? `: ${safeText(out, 300)}` : ""}`,
80
+ backup,
81
+ manual
82
+ };
83
+ }
84
+ if (existsSync(file) && isTooOpen(file)) chmodSync(file, 384);
85
+ return {
86
+ ok: true,
87
+ message: `Registered MCP server "${t.name}" in Claude Code (user scope). The key stays in your agentinbox profile; Claude Code asks for it through a header helper.`,
88
+ backup
89
+ };
90
+ }
91
+ const codexConfigFile = () => join(process.env.CODEX_HOME || join(homedir(), ".codex"), "config.toml");
92
+ const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
93
+ const tomlString = (s) => JSON.stringify(s);
94
+ /** 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. */
95
+ function removeTomlServer(toml, name) {
96
+ const keyForms = `(?:${escapeRe(name)}|"${escapeRe(name)}"|'${escapeRe(name)}')`;
97
+ const header = new RegExp(`^\\s*\\[\\s*mcp_servers\\s*\\.\\s*${keyForms}\\s*(?:\\.[^\\]]*)?\\]\\s*(?:#.*)?$`);
98
+ const anyHeader = /^\s*\[/;
99
+ if (new RegExp(`^\\s*mcp_servers\\s*(?:=|\\.\\s*${keyForms}\\s*[.=])`, "m").test(toml)) return null;
100
+ const out = [];
101
+ let skipping = false;
102
+ for (const line of toml.split("\n")) {
103
+ if (anyHeader.test(line)) skipping = header.test(line);
104
+ if (!skipping && line.trim() !== CODEX_MARKER) out.push(line);
105
+ }
106
+ return out.join("\n").replace(/\n{3,}/g, "\n\n");
107
+ }
108
+ const CODEX_MARKER = "# Added by `agentinbox setup-mcp`. The key is also in your agentinbox profile; rotate it in the console.";
109
+ function codexBlock(t) {
110
+ return [
111
+ CODEX_MARKER,
112
+ `[mcp_servers.${t.name}]`,
113
+ `url = ${tomlString(t.url)}`,
114
+ `http_headers = { Authorization = ${tomlString(`Bearer ${t.key}`)} }`,
115
+ `# codex exec never prompts: approve agentinbox tools up front (the grant's send mode still applies).`,
116
+ `default_tools_approval_mode = "approve"`,
117
+ ""
118
+ ].join("\n");
119
+ }
120
+ function setupCodex(t, profileFile) {
121
+ const file = codexConfigFile();
122
+ const manual = [
123
+ `Add to ${file}:`,
124
+ ` [mcp_servers.${t.name}]`,
125
+ ` url = "${t.url}"`,
126
+ ` http_headers = { Authorization = "Bearer <key>" } # <key>: the apiKey field of ${profileFile}`,
127
+ ` default_tools_approval_mode = "approve"`
128
+ ];
129
+ const without = removeTomlServer(existsSync(file) ? readFileSync(file, "utf8") : "", t.name);
130
+ if (without === null) return {
131
+ ok: false,
132
+ message: `${file} defines mcp_servers inline; not rewriting it automatically.`,
133
+ manual
134
+ };
135
+ const backup = backupFile(file);
136
+ const base = without.replace(/\s*$/, "");
137
+ writePrivateFile(file, `${base ? `${base}\n\n` : ""}${codexBlock(t)}`, { privateDir: false });
138
+ return {
139
+ ok: true,
140
+ message: `Added [mcp_servers.${t.name}] to ${file} (now mode 0600: it holds the key).`,
141
+ backup
142
+ };
143
+ }
144
+ const cursorConfigFile = () => join(homedir(), ".cursor", "mcp.json");
145
+ function setupCursor(t, profileFile) {
146
+ const file = cursorConfigFile();
147
+ const manual = [`Add to ${file} under "mcpServers":`, ` "${t.name}": { "url": "${t.url}", "headers": { "Authorization": "Bearer <key>" } } // <key>: the apiKey field of ${profileFile}`];
148
+ let doc = {};
149
+ if (existsSync(file)) {
150
+ const text = readFileSync(file, "utf8");
151
+ if (text.trim()) {
152
+ try {
153
+ doc = JSON.parse(text);
154
+ } catch {
155
+ return {
156
+ ok: false,
157
+ message: `${file} is not plain JSON (comments?); not rewriting it automatically.`,
158
+ manual
159
+ };
160
+ }
161
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) return {
162
+ ok: false,
163
+ message: `${file} is not a JSON object.`,
164
+ manual
165
+ };
166
+ }
167
+ }
168
+ const servers = doc.mcpServers && typeof doc.mcpServers === "object" && !Array.isArray(doc.mcpServers) ? doc.mcpServers : {};
169
+ servers[t.name] = {
170
+ url: t.url,
171
+ headers: { Authorization: `Bearer ${t.key}` }
172
+ };
173
+ doc.mcpServers = servers;
174
+ const backup = backupFile(file);
175
+ writePrivateFile(file, JSON.stringify(doc, null, 2) + "\n", { privateDir: false });
176
+ return {
177
+ ok: true,
178
+ message: `Added "${t.name}" to ${file} (now mode 0600: it holds the key). Cursor may ask you to enable it once.`,
179
+ backup
180
+ };
181
+ }
182
+ const hermesHome = () => process.env.HERMES_HOME || join(homedir(), ".hermes");
183
+ const hermesEnvVar = (name) => name.toUpperCase().replace(/[^A-Z0-9]/g, "_") + "_API_KEY";
184
+ /**
185
+ * Inserts or replaces `mcp_servers.<name>` in a YAML document without a YAML library, handling the
186
+ * common block-style layout. Returns null for anything unusual (flow style, anchors, odd indentation).
187
+ */
188
+ function upsertYamlServer(yaml, name, entry) {
189
+ const lines = yaml.split("\n");
190
+ const top = lines.findIndex((l) => /^mcp_servers\s*:/.test(l));
191
+ const block = (indent) => [`${indent}${name}:`, ...entry.map((e) => `${indent}${indent}${e}`)];
192
+ if (top < 0) {
193
+ const base = yaml.replace(/\s*$/, "");
194
+ return `${base ? `${base}\n\n` : ""}mcp_servers:\n${block(" ").join("\n")}\n`;
195
+ }
196
+ const rest = lines[top].replace(/^mcp_servers\s*:/, "").replace(/#.*$/, "").trim();
197
+ if (rest && rest !== "{}" && rest !== "null" && rest !== "~") return null;
198
+ if (rest) lines[top] = "mcp_servers:";
199
+ let end = top + 1;
200
+ while (end < lines.length && (lines[end].trim() === "" || /^\s/.test(lines[end]))) end++;
201
+ const body = lines.slice(top + 1, end);
202
+ const firstChild = body.find((l) => l.trim() && !l.trimStart().startsWith("#"));
203
+ const indent = firstChild ? firstChild.match(/^(\s*)/)[1] : " ";
204
+ if (!indent || /\t/.test(indent)) return null;
205
+ const kept = [];
206
+ let skipping = false;
207
+ for (const l of body) {
208
+ const own = l.match(/^(\s*)/)[1].length;
209
+ if (l.trim() && own === indent.length) skipping = new RegExp(`^${escapeRe(indent)}["']?${escapeRe(name)}["']?\\s*:`).test(l);
210
+ else if (l.trim() && own < indent.length) return null;
211
+ if (!skipping) kept.push(l);
212
+ }
213
+ const insert = block(indent);
214
+ return [
215
+ ...lines.slice(0, top + 1),
216
+ ...insert,
217
+ ...kept,
218
+ ...lines.slice(end)
219
+ ].join("\n");
220
+ }
221
+ function upsertEnvLine(text, key, value) {
222
+ const line = `${key}=${value}`;
223
+ const re = new RegExp(`^\\s*(?:export\\s+)?${escapeRe(key)}\\s*=.*$`, "m");
224
+ if (re.test(text)) return text.replace(re, line);
225
+ const base = text.replace(/\s*$/, "");
226
+ return `${base ? `${base}\n` : ""}${line}\n`;
227
+ }
228
+ function setupHermes(t, profileFile) {
229
+ const home = hermesHome();
230
+ const configFile = join(home, "config.yaml");
231
+ const envFile = join(home, ".env");
232
+ const envVar = hermesEnvVar(t.name);
233
+ const entry = [
234
+ `url: ${JSON.stringify(t.url)}`,
235
+ `headers:`,
236
+ ` Authorization: "Bearer \${${envVar}}"`
237
+ ];
238
+ const manual = [
239
+ `Add to ${configFile}:`,
240
+ ` mcp_servers:`,
241
+ ` ${t.name}:`,
242
+ ...entry.map((e) => ` ${e}`),
243
+ `and to ${envFile}: ${envVar}=<key> (<key>: the apiKey field of ${profileFile})`,
244
+ `then check it with: hermes mcp test ${t.name}`
245
+ ];
246
+ const next = upsertYamlServer(existsSync(configFile) ? readFileSync(configFile, "utf8") : "", t.name, entry);
247
+ if (next === null) return {
248
+ ok: false,
249
+ message: `${configFile} uses a layout we won't rewrite automatically.`,
250
+ manual
251
+ };
252
+ const envCurrent = existsSync(envFile) ? readFileSync(envFile, "utf8") : "";
253
+ const backup = backupFile(configFile);
254
+ const envBackup = backupFile(envFile);
255
+ writePrivateFile(envFile, upsertEnvLine(envCurrent, envVar, t.key), { privateDir: false });
256
+ writePrivateFile(configFile, next.endsWith("\n") ? next : `${next}\n`, { privateDir: false });
257
+ return {
258
+ ok: true,
259
+ message: `Added "${t.name}" to ${configFile}; the key is in ${envFile} as ${envVar} (0600). Check it with: hermes mcp test ${t.name}`,
260
+ backup: [backup, envBackup].filter(Boolean).join(", ") || null
261
+ };
262
+ }
263
+ //#endregion
264
+ export { hermesEnvVar as a, removeTomlServer as c, cursorConfigFile as i, setupMcp as l, codexBlock as n, hermesHome as o, codexConfigFile as r, mcpTarget as s, claudeConfigFile as t, upsertYamlServer as u };
265
+
266
+ //# sourceMappingURL=mcpsetup-DJ7ebFGJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcpsetup-DJ7ebFGJ.js","names":[],"sources":["../server/listen/mcpsetup.ts"],"sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport { chmodSync, existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"../../shared/agent-api.ts\";\nimport { backupFile, isTooOpen, writePrivateFile } from \"./fsutil.ts\";\nimport { claudeServerEntry } from \"./mcpheader.ts\";\nimport type { Profile } from \"./profile.ts\";\nimport { mcpServerName } from \"./recipes.ts\";\nimport { NPX } from \"./version.ts\";\nimport { safeText } from \"./ui.ts\";\n\n/**\n * `--setup-mcp`: registers the agentinbox MCP server in the agent's own tool without the key ever\n * being shown or pasted. Every file we modify is backed up first; the key is redacted from any\n * output we relay.\n */\n\nexport interface McpSetupResult {\n ok: boolean;\n /** What happened, safe to print (never contains the key). */\n message: string;\n backup?: string | null;\n /** Manual steps when automatic setup isn't possible. Never contains the key. */\n manual?: string[];\n}\n\nexport interface McpSetupTarget {\n name: string;\n url: string;\n key: string;\n}\n\nconst redact = (s: string, key: string) => s.split(key).join(\"aib_…(redacted)\");\n\n/**\n * The MCP endpoint is always derived from the URL the user connected to (and transport-checked\n * there), never taken from the daemon: a daemon's advertised mcpUrl comes from its publicUrl setting,\n * which defaults to http://localhost:8425 and could also point the key somewhere else entirely.\n */\nexport function mcpTarget(profileName: string, profile: Profile): McpSetupTarget & { profile: string } {\n return { name: mcpServerName(profileName), url: `${profile.url}/mcp`, key: profile.apiKey, profile: profileName };\n}\n\nexport function setupMcp(tool: AgentTool, t: McpSetupTarget & { profile?: string }, profileFile: string): McpSetupResult {\n switch (tool) {\n case \"claude-code\":\n return setupClaude(t, t.profile ?? \"default\");\n case \"codex\":\n return setupCodex(t, profileFile);\n case \"cursor\":\n return setupCursor(t, profileFile);\n case \"hermes\":\n return setupHermes(t, profileFile);\n default:\n return { ok: false, message: \"No automatic MCP setup for --tool other.\", manual: manualGeneric(t, profileFile) };\n }\n}\n\nfunction manualGeneric(t: McpSetupTarget, profileFile: string): string[] {\n return [\n `MCP endpoint (streamable HTTP): ${t.url}`,\n `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.`,\n ];\n}\n\n// ───────────── Claude Code ─────────────\n\nexport const claudeConfigFile = () => (process.env.CLAUDE_CONFIG_DIR ? join(process.env.CLAUDE_CONFIG_DIR, \".claude.json\") : join(homedir(), \".claude.json\"));\n\nfunction setupClaude(t: McpSetupTarget, profile: string): McpSetupResult {\n // headersHelper: Claude Code asks our helper for the header at connect time, so the key is never in\n // argv (visible in `ps`), in ~/.claude.json, or on screen.\n const json = JSON.stringify(claudeServerEntry(t.url, profile));\n const manual = [`claude mcp add-json --scope user ${t.name} '${json.replace(/'/g, `'\"'\"'`)}'`];\n const probe = spawnSync(\"claude\", [\"--version\"], { stdio: \"ignore\" });\n if (probe.error || probe.status !== 0) return { ok: false, message: \"The `claude` CLI was not found on PATH.\", manual };\n const file = claudeConfigFile();\n const backup = backupFile(file);\n // `add-json` refuses an existing name, so replace it. argv arrays only, and nothing secret in them.\n spawnSync(\"claude\", [\"mcp\", \"remove\", t.name, \"--scope\", \"user\"], { stdio: \"ignore\" });\n const r = spawnSync(\"claude\", [\"mcp\", \"add-json\", \"--scope\", \"user\", t.name, json], { encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n if (r.status !== 0) {\n const out = redact(`${r.stderr ?? \"\"}${r.stdout ?? \"\"}`, t.key).trim();\n return { ok: false, message: `claude mcp add-json failed${out ? `: ${safeText(out, 300)}` : \"\"}`, backup, manual };\n }\n if (existsSync(file) && isTooOpen(file)) chmodSync(file, 0o600);\n return { ok: true, message: `Registered MCP server \"${t.name}\" in Claude Code (user scope). The key stays in your agentinbox profile; Claude Code asks for it through a header helper.`, backup };\n}\n\n// ───────────── Codex (~/.codex/config.toml) ─────────────\n\nexport const codexConfigFile = () => join(process.env.CODEX_HOME || join(homedir(), \".codex\"), \"config.toml\");\n\nconst escapeRe = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\nconst tomlString = (s: string) => JSON.stringify(s);\n\n/** 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. */\nexport function removeTomlServer(toml: string, name: string): string | null {\n const keyForms = `(?:${escapeRe(name)}|\"${escapeRe(name)}\"|'${escapeRe(name)}')`;\n const header = new RegExp(`^\\\\s*\\\\[\\\\s*mcp_servers\\\\s*\\\\.\\\\s*${keyForms}\\\\s*(?:\\\\.[^\\\\]]*)?\\\\]\\\\s*(?:#.*)?$`);\n const anyHeader = /^\\s*\\[/;\n // Inline or dotted definitions (`mcp_servers = { … }`, `mcp_servers.agentinbox.url = …`): leave to the human.\n if (new RegExp(`^\\\\s*mcp_servers\\\\s*(?:=|\\\\.\\\\s*${keyForms}\\\\s*[.=])`, \"m\").test(toml)) return null;\n const out: string[] = [];\n let skipping = false;\n for (const line of toml.split(\"\\n\")) {\n if (anyHeader.test(line)) skipping = header.test(line);\n // Our own marker comment sits just above the table: drop it too, so re-running doesn't pile them up.\n if (!skipping && line.trim() !== CODEX_MARKER) out.push(line);\n }\n return out.join(\"\\n\").replace(/\\n{3,}/g, \"\\n\\n\");\n}\n\nconst CODEX_MARKER = \"# Added by `agentinbox setup-mcp`. The key is also in your agentinbox profile; rotate it in the console.\";\n\nexport function codexBlock(t: McpSetupTarget): string {\n return [\n CODEX_MARKER,\n `[mcp_servers.${t.name}]`,\n `url = ${tomlString(t.url)}`,\n `http_headers = { Authorization = ${tomlString(`Bearer ${t.key}`)} }`,\n `# codex exec never prompts: approve agentinbox tools up front (the grant's send mode still applies).`,\n `default_tools_approval_mode = \"approve\"`,\n \"\",\n ].join(\"\\n\");\n}\n\nfunction setupCodex(t: McpSetupTarget, profileFile: string): McpSetupResult {\n const file = codexConfigFile();\n const manual = [\n `Add to ${file}:`,\n ` [mcp_servers.${t.name}]`,\n ` url = \"${t.url}\"`,\n ` http_headers = { Authorization = \"Bearer <key>\" } # <key>: the apiKey field of ${profileFile}`,\n ` default_tools_approval_mode = \"approve\"`,\n ];\n const current = existsSync(file) ? readFileSync(file, \"utf8\") : \"\";\n const without = removeTomlServer(current, t.name);\n if (without === null) return { ok: false, message: `${file} defines mcp_servers inline; not rewriting it automatically.`, manual };\n const backup = backupFile(file);\n const base = without.replace(/\\s*$/, \"\");\n writePrivateFile(file, `${base ? `${base}\\n\\n` : \"\"}${codexBlock(t)}`, { privateDir: false });\n return { ok: true, message: `Added [mcp_servers.${t.name}] to ${file} (now mode 0600: it holds the key).`, backup };\n}\n\n// ───────────── Cursor (~/.cursor/mcp.json) ─────────────\n\nexport const cursorConfigFile = () => join(homedir(), \".cursor\", \"mcp.json\");\n\nfunction setupCursor(t: McpSetupTarget, profileFile: string): McpSetupResult {\n const file = cursorConfigFile();\n const manual = [\n `Add to ${file} under \"mcpServers\":`,\n ` \"${t.name}\": { \"url\": \"${t.url}\", \"headers\": { \"Authorization\": \"Bearer <key>\" } } // <key>: the apiKey field of ${profileFile}`,\n ];\n let doc: Record<string, unknown> = {};\n if (existsSync(file)) {\n const text = readFileSync(file, \"utf8\");\n if (text.trim()) {\n try {\n doc = JSON.parse(text) as Record<string, unknown>;\n } catch {\n return { ok: false, message: `${file} is not plain JSON (comments?); not rewriting it automatically.`, manual };\n }\n if (!doc || typeof doc !== \"object\" || Array.isArray(doc)) return { ok: false, message: `${file} is not a JSON object.`, manual };\n }\n }\n const servers = doc.mcpServers && typeof doc.mcpServers === \"object\" && !Array.isArray(doc.mcpServers) ? (doc.mcpServers as Record<string, unknown>) : {};\n servers[t.name] = { url: t.url, headers: { Authorization: `Bearer ${t.key}` } };\n doc.mcpServers = servers;\n const backup = backupFile(file);\n writePrivateFile(file, JSON.stringify(doc, null, 2) + \"\\n\", { privateDir: false });\n return { ok: true, message: `Added \"${t.name}\" to ${file} (now mode 0600: it holds the key). Cursor may ask you to enable it once.`, backup };\n}\n\n// ───────────── Hermes (~/.hermes/config.yaml + ~/.hermes/.env) ─────────────\n\nexport const hermesHome = () => process.env.HERMES_HOME || join(homedir(), \".hermes\");\nexport const hermesEnvVar = (name: string) => name.toUpperCase().replace(/[^A-Z0-9]/g, \"_\") + \"_API_KEY\";\n\n/**\n * Inserts or replaces `mcp_servers.<name>` in a YAML document without a YAML library, handling the\n * common block-style layout. Returns null for anything unusual (flow style, anchors, odd indentation).\n */\nexport function upsertYamlServer(yaml: string, name: string, entry: string[]): string | null {\n const lines = yaml.split(\"\\n\");\n const top = lines.findIndex((l) => /^mcp_servers\\s*:/.test(l));\n const block = (indent: string) => [`${indent}${name}:`, ...entry.map((e) => `${indent}${indent}${e}`)];\n if (top < 0) {\n const base = yaml.replace(/\\s*$/, \"\");\n return `${base ? `${base}\\n\\n` : \"\"}mcp_servers:\\n${block(\" \").join(\"\\n\")}\\n`;\n }\n const rest = lines[top]!.replace(/^mcp_servers\\s*:/, \"\").replace(/#.*$/, \"\").trim();\n if (rest && rest !== \"{}\" && rest !== \"null\" && rest !== \"~\") return null;\n if (rest) lines[top] = \"mcp_servers:\";\n // The block runs until the next non-blank line at column 0.\n let end = top + 1;\n while (end < lines.length && (lines[end]!.trim() === \"\" || /^\\s/.test(lines[end]!))) end++;\n const body = lines.slice(top + 1, end);\n const firstChild = body.find((l) => l.trim() && !l.trimStart().startsWith(\"#\"));\n const indent = firstChild ? firstChild.match(/^(\\s*)/)![1]! : \" \";\n if (!indent || /\\t/.test(indent)) return null;\n const kept: string[] = [];\n let skipping = false;\n for (const l of body) {\n const own = l.match(/^(\\s*)/)![1]!.length;\n if (l.trim() && own === indent.length) skipping = new RegExp(`^${escapeRe(indent)}[\"']?${escapeRe(name)}[\"']?\\\\s*:`).test(l);\n else if (l.trim() && own < indent.length) return null;\n if (!skipping) kept.push(l);\n }\n const insert = block(indent);\n return [...lines.slice(0, top + 1), ...insert, ...kept, ...lines.slice(end)].join(\"\\n\");\n}\n\nfunction upsertEnvLine(text: string, key: string, value: string): string {\n const line = `${key}=${value}`;\n const re = new RegExp(`^\\\\s*(?:export\\\\s+)?${escapeRe(key)}\\\\s*=.*$`, \"m\");\n if (re.test(text)) return text.replace(re, line);\n const base = text.replace(/\\s*$/, \"\");\n return `${base ? `${base}\\n` : \"\"}${line}\\n`;\n}\n\nfunction setupHermes(t: McpSetupTarget, profileFile: string): McpSetupResult {\n const home = hermesHome();\n const configFile = join(home, \"config.yaml\");\n const envFile = join(home, \".env\");\n const envVar = hermesEnvVar(t.name);\n const entry = [`url: ${JSON.stringify(t.url)}`, `headers:`, ` Authorization: \"Bearer \\${${envVar}}\"`];\n const manual = [\n `Add to ${configFile}:`,\n ` mcp_servers:`,\n ` ${t.name}:`,\n ...entry.map((e) => ` ${e}`),\n `and to ${envFile}: ${envVar}=<key> (<key>: the apiKey field of ${profileFile})`,\n `then check it with: hermes mcp test ${t.name}`,\n ];\n const current = existsSync(configFile) ? readFileSync(configFile, \"utf8\") : \"\";\n const next = upsertYamlServer(current, t.name, entry);\n if (next === null) return { ok: false, message: `${configFile} uses a layout we won't rewrite automatically.`, manual };\n const envCurrent = existsSync(envFile) ? readFileSync(envFile, \"utf8\") : \"\";\n const backup = backupFile(configFile);\n const envBackup = backupFile(envFile);\n writePrivateFile(envFile, upsertEnvLine(envCurrent, envVar, t.key), { privateDir: false });\n writePrivateFile(configFile, next.endsWith(\"\\n\") ? next : `${next}\\n`, { privateDir: false });\n return {\n ok: true,\n message: `Added \"${t.name}\" to ${configFile}; the key is in ${envFile} as ${envVar} (0600). Check it with: hermes mcp test ${t.name}`,\n backup: [backup, envBackup].filter(Boolean).join(\", \") || null,\n };\n}\n"],"mappings":";;;;;;;;;;AAiCA,MAAM,UAAU,GAAW,QAAgB,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,iBAAiB;;;;;;AAO9E,SAAgB,UAAU,aAAqB,SAAwD;CACrG,OAAO;EAAE,MAAM,cAAc,WAAW;EAAG,KAAK,GAAG,QAAQ,IAAI;EAAO,KAAK,QAAQ;EAAQ,SAAS;CAAY;AAClH;AAEA,SAAgB,SAAS,MAAiB,GAA0C,aAAqC;CACvH,QAAQ,MAAR;EACE,KAAK,eACH,OAAO,YAAY,GAAG,EAAE,WAAW,SAAS;EAC9C,KAAK,SACH,OAAO,WAAW,GAAG,WAAW;EAClC,KAAK,UACH,OAAO,YAAY,GAAG,WAAW;EACnC,KAAK,UACH,OAAO,YAAY,GAAG,WAAW;EACnC,SACE,OAAO;GAAE,IAAI;GAAO,SAAS;GAA4C,QAAQ,cAAc,GAAG,WAAW;EAAE;CACnH;AACF;AAEA,SAAS,cAAc,GAAmB,aAA+B;CACvE,OAAO,CACL,mCAAmC,EAAE,OACrC,yEAAyE,YAAY,6CAA6C,IAAI,kDACxI;AACF;AAIA,MAAa,yBAA0B,QAAQ,IAAI,oBAAoB,KAAK,QAAQ,IAAI,mBAAmB,cAAc,IAAI,KAAK,QAAQ,GAAG,cAAc;AAE3J,SAAS,YAAY,GAAmB,SAAiC;CAGvE,MAAM,OAAO,KAAK,UAAU,kBAAkB,EAAE,KAAK,OAAO,CAAC;CAC7D,MAAM,SAAS,CAAC,oCAAoC,EAAE,KAAK,IAAI,KAAK,QAAQ,MAAM,OAAO,EAAE,EAAE;CAC7F,MAAM,QAAQ,UAAU,UAAU,CAAC,WAAW,GAAG,EAAE,OAAO,SAAS,CAAC;CACpE,IAAI,MAAM,SAAS,MAAM,WAAW,GAAG,OAAO;EAAE,IAAI;EAAO,SAAS;EAA2C;CAAO;CACtH,MAAM,OAAO,iBAAiB;CAC9B,MAAM,SAAS,WAAW,IAAI;CAE9B,UAAU,UAAU;EAAC;EAAO;EAAU,EAAE;EAAM;EAAW;CAAM,GAAG,EAAE,OAAO,SAAS,CAAC;CACrF,MAAM,IAAI,UAAU,UAAU;EAAC;EAAO;EAAY;EAAW;EAAQ,EAAE;EAAM;CAAI,GAAG;EAAE,UAAU;EAAQ,OAAO;GAAC;GAAU;GAAQ;EAAM;CAAE,CAAC;CAC3I,IAAI,EAAE,WAAW,GAAG;EAClB,MAAM,MAAM,OAAO,GAAG,EAAE,UAAU,KAAK,EAAE,UAAU,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK;EACrE,OAAO;GAAE,IAAI;GAAO,SAAS,6BAA6B,MAAM,KAAK,SAAS,KAAK,GAAG,MAAM;GAAM;GAAQ;EAAO;CACnH;CACA,IAAI,WAAW,IAAI,KAAK,UAAU,IAAI,GAAG,UAAU,MAAM,GAAK;CAC9D,OAAO;EAAE,IAAI;EAAM,SAAS,0BAA0B,EAAE,KAAK;EAA4H;CAAO;AAClM;AAIA,MAAa,wBAAwB,KAAK,QAAQ,IAAI,cAAc,KAAK,QAAQ,GAAG,QAAQ,GAAG,aAAa;AAE5G,MAAM,YAAY,MAAc,EAAE,QAAQ,uBAAuB,MAAM;AACvE,MAAM,cAAc,MAAc,KAAK,UAAU,CAAC;;AAGlD,SAAgB,iBAAiB,MAAc,MAA6B;CAC1E,MAAM,WAAW,MAAM,SAAS,IAAI,EAAE,IAAI,SAAS,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE;CAC7E,MAAM,SAAS,IAAI,OAAO,qCAAqC,SAAS,oCAAoC;CAC5G,MAAM,YAAY;CAElB,IAAI,IAAI,OAAO,mCAAmC,SAAS,YAAY,GAAG,CAAC,CAAC,KAAK,IAAI,GAAG,OAAO;CAC/F,MAAM,MAAgB,CAAC;CACvB,IAAI,WAAW;CACf,KAAK,MAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;EACnC,IAAI,UAAU,KAAK,IAAI,GAAG,WAAW,OAAO,KAAK,IAAI;EAErD,IAAI,CAAC,YAAY,KAAK,KAAK,MAAM,cAAc,IAAI,KAAK,IAAI;CAC9D;CACA,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,QAAQ,WAAW,MAAM;AACjD;AAEA,MAAM,eAAe;AAErB,SAAgB,WAAW,GAA2B;CACpD,OAAO;EACL;EACA,gBAAgB,EAAE,KAAK;EACvB,SAAS,WAAW,EAAE,GAAG;EACzB,oCAAoC,WAAW,UAAU,EAAE,KAAK,EAAE;EAClE;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;AACb;AAEA,SAAS,WAAW,GAAmB,aAAqC;CAC1E,MAAM,OAAO,gBAAgB;CAC7B,MAAM,SAAS;EACb,UAAU,KAAK;EACf,kBAAkB,EAAE,KAAK;EACzB,YAAY,EAAE,IAAI;EAClB,sFAAsF;EACtF;CACF;CAEA,MAAM,UAAU,iBADA,WAAW,IAAI,IAAI,aAAa,MAAM,MAAM,IAAI,IACtB,EAAE,IAAI;CAChD,IAAI,YAAY,MAAM,OAAO;EAAE,IAAI;EAAO,SAAS,GAAG,KAAK;EAA+D;CAAO;CACjI,MAAM,SAAS,WAAW,IAAI;CAC9B,MAAM,OAAO,QAAQ,QAAQ,QAAQ,EAAE;CACvC,iBAAiB,MAAM,GAAG,OAAO,GAAG,KAAK,QAAQ,KAAK,WAAW,CAAC,KAAK,EAAE,YAAY,MAAM,CAAC;CAC5F,OAAO;EAAE,IAAI;EAAM,SAAS,sBAAsB,EAAE,KAAK,OAAO,KAAK;EAAsC;CAAO;AACpH;AAIA,MAAa,yBAAyB,KAAK,QAAQ,GAAG,WAAW,UAAU;AAE3E,SAAS,YAAY,GAAmB,aAAqC;CAC3E,MAAM,OAAO,iBAAiB;CAC9B,MAAM,SAAS,CACb,UAAU,KAAK,uBACf,MAAM,EAAE,KAAK,eAAe,EAAE,IAAI,sFAAsF,aAC1H;CACA,IAAI,MAA+B,CAAC;CACpC,IAAI,WAAW,IAAI,GAAG;EACpB,MAAM,OAAO,aAAa,MAAM,MAAM;EACtC,IAAI,KAAK,KAAK,GAAG;GACf,IAAI;IACF,MAAM,KAAK,MAAM,IAAI;GACvB,QAAQ;IACN,OAAO;KAAE,IAAI;KAAO,SAAS,GAAG,KAAK;KAAkE;IAAO;GAChH;GACA,IAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG,OAAO;IAAE,IAAI;IAAO,SAAS,GAAG,KAAK;IAAyB;GAAO;EAClI;CACF;CACA,MAAM,UAAU,IAAI,cAAc,OAAO,IAAI,eAAe,YAAY,CAAC,MAAM,QAAQ,IAAI,UAAU,IAAK,IAAI,aAAyC,CAAC;CACxJ,QAAQ,EAAE,QAAQ;EAAE,KAAK,EAAE;EAAK,SAAS,EAAE,eAAe,UAAU,EAAE,MAAM;CAAE;CAC9E,IAAI,aAAa;CACjB,MAAM,SAAS,WAAW,IAAI;CAC9B,iBAAiB,MAAM,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,YAAY,MAAM,CAAC;CACjF,OAAO;EAAE,IAAI;EAAM,SAAS,UAAU,EAAE,KAAK,OAAO,KAAK;EAA4E;CAAO;AAC9I;AAIA,MAAa,mBAAmB,QAAQ,IAAI,eAAe,KAAK,QAAQ,GAAG,SAAS;AACpF,MAAa,gBAAgB,SAAiB,KAAK,YAAY,CAAC,CAAC,QAAQ,cAAc,GAAG,IAAI;;;;;AAM9F,SAAgB,iBAAiB,MAAc,MAAc,OAAgC;CAC3F,MAAM,QAAQ,KAAK,MAAM,IAAI;CAC7B,MAAM,MAAM,MAAM,WAAW,MAAM,mBAAmB,KAAK,CAAC,CAAC;CAC7D,MAAM,SAAS,WAAmB,CAAC,GAAG,SAAS,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,GAAG,SAAS,SAAS,GAAG,CAAC;CACrG,IAAI,MAAM,GAAG;EACX,MAAM,OAAO,KAAK,QAAQ,QAAQ,EAAE;EACpC,OAAO,GAAG,OAAO,GAAG,KAAK,QAAQ,GAAG,gBAAgB,MAAM,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE;CAC7E;CACA,MAAM,OAAO,MAAM,IAAI,CAAE,QAAQ,oBAAoB,EAAE,CAAC,CAAC,QAAQ,QAAQ,EAAE,CAAC,CAAC,KAAK;CAClF,IAAI,QAAQ,SAAS,QAAQ,SAAS,UAAU,SAAS,KAAK,OAAO;CACrE,IAAI,MAAM,MAAM,OAAO;CAEvB,IAAI,MAAM,MAAM;CAChB,OAAO,MAAM,MAAM,WAAW,MAAM,IAAI,CAAE,KAAK,MAAM,MAAM,MAAM,KAAK,MAAM,IAAK,IAAI;CACrF,MAAM,OAAO,MAAM,MAAM,MAAM,GAAG,GAAG;CACrC,MAAM,aAAa,KAAK,MAAM,MAAM,EAAE,KAAK,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,WAAW,GAAG,CAAC;CAC9E,MAAM,SAAS,aAAa,WAAW,MAAM,QAAQ,CAAC,CAAE,KAAM;CAC9D,IAAI,CAAC,UAAU,KAAK,KAAK,MAAM,GAAG,OAAO;CACzC,MAAM,OAAiB,CAAC;CACxB,IAAI,WAAW;CACf,KAAK,MAAM,KAAK,MAAM;EACpB,MAAM,MAAM,EAAE,MAAM,QAAQ,CAAC,CAAE,EAAE,CAAE;EACnC,IAAI,EAAE,KAAK,KAAK,QAAQ,OAAO,QAAQ,WAAW,IAAI,OAAO,IAAI,SAAS,MAAM,EAAE,OAAO,SAAS,IAAI,EAAE,WAAW,CAAC,CAAC,KAAK,CAAC;OACtH,IAAI,EAAE,KAAK,KAAK,MAAM,OAAO,QAAQ,OAAO;EACjD,IAAI,CAAC,UAAU,KAAK,KAAK,CAAC;CAC5B;CACA,MAAM,SAAS,MAAM,MAAM;CAC3B,OAAO;EAAC,GAAG,MAAM,MAAM,GAAG,MAAM,CAAC;EAAG,GAAG;EAAQ,GAAG;EAAM,GAAG,MAAM,MAAM,GAAG;CAAC,CAAC,CAAC,KAAK,IAAI;AACxF;AAEA,SAAS,cAAc,MAAc,KAAa,OAAuB;CACvE,MAAM,OAAO,GAAG,IAAI,GAAG;CACvB,MAAM,KAAK,IAAI,OAAO,uBAAuB,SAAS,GAAG,EAAE,WAAW,GAAG;CACzE,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,KAAK,QAAQ,IAAI,IAAI;CAC/C,MAAM,OAAO,KAAK,QAAQ,QAAQ,EAAE;CACpC,OAAO,GAAG,OAAO,GAAG,KAAK,MAAM,KAAK,KAAK;AAC3C;AAEA,SAAS,YAAY,GAAmB,aAAqC;CAC3E,MAAM,OAAO,WAAW;CACxB,MAAM,aAAa,KAAK,MAAM,aAAa;CAC3C,MAAM,UAAU,KAAK,MAAM,MAAM;CACjC,MAAM,SAAS,aAAa,EAAE,IAAI;CAClC,MAAM,QAAQ;EAAC,QAAQ,KAAK,UAAU,EAAE,GAAG;EAAK;EAAY,+BAA+B,OAAO;CAAG;CACrG,MAAM,SAAS;EACb,UAAU,WAAW;EACrB;EACA,OAAO,EAAE,KAAK;EACd,GAAG,MAAM,KAAK,MAAM,SAAS,GAAG;EAChC,UAAU,QAAQ,KAAK,OAAO,uCAAuC,YAAY;EACjF,uCAAuC,EAAE;CAC3C;CAEA,MAAM,OAAO,iBADG,WAAW,UAAU,IAAI,aAAa,YAAY,MAAM,IAAI,IACrC,EAAE,MAAM,KAAK;CACpD,IAAI,SAAS,MAAM,OAAO;EAAE,IAAI;EAAO,SAAS,GAAG,WAAW;EAAiD;CAAO;CACtH,MAAM,aAAa,WAAW,OAAO,IAAI,aAAa,SAAS,MAAM,IAAI;CACzE,MAAM,SAAS,WAAW,UAAU;CACpC,MAAM,YAAY,WAAW,OAAO;CACpC,iBAAiB,SAAS,cAAc,YAAY,QAAQ,EAAE,GAAG,GAAG,EAAE,YAAY,MAAM,CAAC;CACzF,iBAAiB,YAAY,KAAK,SAAS,IAAI,IAAI,OAAO,GAAG,KAAK,KAAK,EAAE,YAAY,MAAM,CAAC;CAC5F,OAAO;EACL,IAAI;EACJ,SAAS,UAAU,EAAE,KAAK,OAAO,WAAW,kBAAkB,QAAQ,MAAM,OAAO,0CAA0C,EAAE;EAC/H,QAAQ,CAAC,QAAQ,SAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI,KAAK;CAC5D;AACF"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { s as profilePath } from "./paths-TLp8hiMt.js";
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ //#region server/listen/paths.ts
5
+ /**
6
+ * Agent-side locations (the machine running `connect` / `listen`, not the daemon). XDG on Linux and
7
+ * macOS alike, so one set of paths works everywhere:
8
+ * config $XDG_CONFIG_HOME/agentinbox (~/.config/agentinbox) profiles/<name>.json (0600)
9
+ * state $XDG_STATE_HOME/agentinbox (~/.local/state/agentinbox) listen/<name>/{listen.log,dead.jsonl,status.json}
10
+ * data $XDG_DATA_HOME/agentinbox (~/.local/share/agentinbox) app/ (stable install the services run)
11
+ */
12
+ const xdg = (envVar, fallback) => {
13
+ const v = process.env[envVar];
14
+ return v && v.startsWith("/") ? v : join(homedir(), fallback);
15
+ };
16
+ const configDir = () => join(xdg("XDG_CONFIG_HOME", ".config"), "agentinbox");
17
+ const stateDir = () => join(xdg("XDG_STATE_HOME", ".local/state"), "agentinbox");
18
+ const dataDir = () => join(xdg("XDG_DATA_HOME", ".local/share"), "agentinbox");
19
+ const profilesDir = () => join(configDir(), "profiles");
20
+ const profilePath = (name) => join(profilesDir(), `${name}.json`);
21
+ const profileStateDir = (name) => join(stateDir(), "listen", name);
22
+ const logPath = (name) => join(profileStateDir(name), "listen.log");
23
+ const deadPath = (name) => join(profileStateDir(name), "dead.jsonl");
24
+ const statusPath = (name) => join(profileStateDir(name), "status.json");
25
+ /** Default working directory for --exec commands: private (0700), so no planted CLAUDE.md, .mcp.json or hooks apply. */
26
+ const workDir = (name) => join(profileStateDir(name), "work");
27
+ /** The listeners' stable install (shared by all profiles), staged and swapped like the daemon's `<data>/app`. */
28
+ const listenAppDir = () => join(dataDir(), "app");
29
+ const DEFAULT_PROFILE = "default";
30
+ const PROFILE_RE = /^[a-z0-9][a-z0-9_-]{0,39}$/;
31
+ /** Profile names become file names and service names, so they are kept boring. */
32
+ function validProfileName(name) {
33
+ const n = name.trim().toLowerCase();
34
+ if (!PROFILE_RE.test(n)) throw new Error(`Invalid profile name "${name}": use 1-40 lowercase letters, digits, "-" or "_", starting with a letter or digit.`);
35
+ return n;
36
+ }
37
+ //#endregion
38
+ export { listenAppDir as a, profileStateDir as c, statusPath as d, validProfileName as f, deadPath as i, profilesDir as l, configDir as n, logPath as o, workDir as p, dataDir as r, profilePath as s, DEFAULT_PROFILE as t, stateDir as u };
39
+
40
+ //# sourceMappingURL=paths-TLp8hiMt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths-TLp8hiMt.js","names":[],"sources":["../server/listen/paths.ts"],"sourcesContent":["import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * Agent-side locations (the machine running `connect` / `listen`, not the daemon). XDG on Linux and\n * macOS alike, so one set of paths works everywhere:\n * config $XDG_CONFIG_HOME/agentinbox (~/.config/agentinbox) profiles/<name>.json (0600)\n * state $XDG_STATE_HOME/agentinbox (~/.local/state/agentinbox) listen/<name>/{listen.log,dead.jsonl,status.json}\n * data $XDG_DATA_HOME/agentinbox (~/.local/share/agentinbox) app/ (stable install the services run)\n */\n\nconst xdg = (envVar: string, fallback: string) => {\n const v = process.env[envVar];\n // The XDG spec says relative paths are invalid and must be ignored.\n return v && v.startsWith(\"/\") ? v : join(homedir(), fallback);\n};\n\nexport const configDir = () => join(xdg(\"XDG_CONFIG_HOME\", \".config\"), \"agentinbox\");\nexport const stateDir = () => join(xdg(\"XDG_STATE_HOME\", \".local/state\"), \"agentinbox\");\nexport const dataDir = () => join(xdg(\"XDG_DATA_HOME\", \".local/share\"), \"agentinbox\");\n\nexport const profilesDir = () => join(configDir(), \"profiles\");\nexport const profilePath = (name: string) => join(profilesDir(), `${name}.json`);\nexport const profileStateDir = (name: string) => join(stateDir(), \"listen\", name);\nexport const logPath = (name: string) => join(profileStateDir(name), \"listen.log\");\nexport const deadPath = (name: string) => join(profileStateDir(name), \"dead.jsonl\");\nexport const statusPath = (name: string) => join(profileStateDir(name), \"status.json\");\n/** Default working directory for --exec commands: private (0700), so no planted CLAUDE.md, .mcp.json or hooks apply. */\nexport const workDir = (name: string) => join(profileStateDir(name), \"work\");\n/** The listeners' stable install (shared by all profiles), staged and swapped like the daemon's `<data>/app`. */\nexport const listenAppDir = () => join(dataDir(), \"app\");\n\nexport const DEFAULT_PROFILE = \"default\";\nconst PROFILE_RE = /^[a-z0-9][a-z0-9_-]{0,39}$/;\n\n/** Profile names become file names and service names, so they are kept boring. */\nexport function validProfileName(name: string): string {\n const n = name.trim().toLowerCase();\n if (!PROFILE_RE.test(n)) {\n throw new Error(`Invalid profile name \"${name}\": use 1-40 lowercase letters, digits, \"-\" or \"_\", starting with a letter or digit.`);\n }\n return n;\n}\n"],"mappings":";;;;;;;;;;;AAWA,MAAM,OAAO,QAAgB,aAAqB;CAChD,MAAM,IAAI,QAAQ,IAAI;CAEtB,OAAO,KAAK,EAAE,WAAW,GAAG,IAAI,IAAI,KAAK,QAAQ,GAAG,QAAQ;AAC9D;AAEA,MAAa,kBAAkB,KAAK,IAAI,mBAAmB,SAAS,GAAG,YAAY;AACnF,MAAa,iBAAiB,KAAK,IAAI,kBAAkB,cAAc,GAAG,YAAY;AACtF,MAAa,gBAAgB,KAAK,IAAI,iBAAiB,cAAc,GAAG,YAAY;AAEpF,MAAa,oBAAoB,KAAK,UAAU,GAAG,UAAU;AAC7D,MAAa,eAAe,SAAiB,KAAK,YAAY,GAAG,GAAG,KAAK,MAAM;AAC/E,MAAa,mBAAmB,SAAiB,KAAK,SAAS,GAAG,UAAU,IAAI;AAChF,MAAa,WAAW,SAAiB,KAAK,gBAAgB,IAAI,GAAG,YAAY;AACjF,MAAa,YAAY,SAAiB,KAAK,gBAAgB,IAAI,GAAG,YAAY;AAClF,MAAa,cAAc,SAAiB,KAAK,gBAAgB,IAAI,GAAG,aAAa;;AAErF,MAAa,WAAW,SAAiB,KAAK,gBAAgB,IAAI,GAAG,MAAM;;AAE3E,MAAa,qBAAqB,KAAK,QAAQ,GAAG,KAAK;AAEvD,MAAa,kBAAkB;AAC/B,MAAM,aAAa;;AAGnB,SAAgB,iBAAiB,MAAsB;CACrD,MAAM,IAAI,KAAK,KAAK,CAAC,CAAC,YAAY;CAClC,IAAI,CAAC,WAAW,KAAK,CAAC,GACpB,MAAM,IAAI,MAAM,yBAAyB,KAAK,oFAAoF;CAEpI,OAAO;AACT"}
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+ import { n as EVENT_TYPES, r as NPM_PACKAGE } from "./types-3eEsBFVj.js";
3
+ import { r as webhookFilterSchema } from "./filter-DGVz6-P1.js";
4
+ import { f as validProfileName, l as profilesDir, s as profilePath, t as DEFAULT_PROFILE } from "./paths-TLp8hiMt.js";
5
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ import { randomBytes } from "node:crypto";
8
+ import { z } from "zod";
9
+ //#region server/listen/version.ts
10
+ /** How printed instructions invoke the CLI without a global install. */
11
+ const NPX = `npx ${NPM_PACKAGE}`;
12
+ /** package.json sits above dist/ (bundled) or two levels above server/listen/ (source). */
13
+ function readVersion() {
14
+ for (const rel of ["..", "../.."]) try {
15
+ const pkg = JSON.parse(readFileSync(join(import.meta.dirname, rel, "package.json"), "utf8"));
16
+ if (pkg.name === "agentinbox-cli" && pkg.version) return pkg.version;
17
+ } catch {}
18
+ return "0.0.0";
19
+ }
20
+ const version = readVersion();
21
+ //#endregion
22
+ //#region server/listen/fsutil.ts
23
+ /** Creates a directory (and parents) readable only by this user. Tightens an existing one. */
24
+ function ensurePrivateDir(dir) {
25
+ mkdirSync(dir, {
26
+ recursive: true,
27
+ mode: 448
28
+ });
29
+ if (process.platform !== "win32" && (statSync(dir).mode & 63) !== 0) chmodSync(dir, 448);
30
+ }
31
+ /**
32
+ * Writes a file with mode 0600 atomically (temp file + rename), so a crash never leaves a
33
+ * half-written profile and the secret is never briefly world-readable.
34
+ */
35
+ function writePrivateFile(linkOrPath, content, opts = {}) {
36
+ const path = resolveLink(linkOrPath);
37
+ if (opts.privateDir !== false) ensurePrivateDir(dirname(path));
38
+ else mkdirSync(dirname(path), { recursive: true });
39
+ const tmp = `${path}.${randomBytes(4).toString("hex")}.tmp`;
40
+ try {
41
+ writeFileSync(tmp, content, {
42
+ mode: 384,
43
+ flag: "wx"
44
+ });
45
+ chmodSync(tmp, 384);
46
+ renameSync(tmp, path);
47
+ } catch (err) {
48
+ rmSync(tmp, { force: true });
49
+ throw err;
50
+ }
51
+ }
52
+ function resolveLink(path) {
53
+ try {
54
+ return lstatSync(path).isSymbolicLink() ? realpathSync(path) : path;
55
+ } catch {
56
+ return path;
57
+ }
58
+ }
59
+ /** Copies `path` next to itself as `<path>.agentinbox-backup-<timestamp>` (0600). Returns the copy, or null if there was nothing to back up. */
60
+ function backupFile(path) {
61
+ if (!existsSync(path)) return null;
62
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
63
+ const content = readFileSync(path);
64
+ for (let n = 0;; n++) {
65
+ const backup = `${path}.agentinbox-backup-${stamp}${n ? `-${n}` : ""}`;
66
+ try {
67
+ writeFileSync(backup, content, {
68
+ mode: 384,
69
+ flag: "wx"
70
+ });
71
+ return backup;
72
+ } catch (err) {
73
+ if (err.code !== "EEXIST" || n > 20) throw err;
74
+ }
75
+ }
76
+ }
77
+ /** Tightens an existing file to 0600 (files created by appendFileSync keep their first mode). */
78
+ function tighten(path) {
79
+ if (process.platform !== "win32" && existsSync(path) && (statSync(path).mode & 63) !== 0) chmodSync(path, 384);
80
+ }
81
+ /**
82
+ * A directory commands may run in: must exist, be owned by this user and not be writable by group or
83
+ * others (otherwise someone else could plant agent instructions, MCP configs or hooks there).
84
+ */
85
+ function assertTrustedDir(dir) {
86
+ let st;
87
+ try {
88
+ st = statSync(dir);
89
+ } catch {
90
+ throw new Error(`--cwd ${dir} does not exist.`);
91
+ }
92
+ if (!st.isDirectory()) throw new Error(`--cwd ${dir} is not a directory.`);
93
+ const uid = process.getuid?.();
94
+ if (uid !== void 0 && st.uid !== uid) throw new Error(`Refusing --cwd ${dir}: it belongs to another user, who could plant instructions or hooks for the agent there.`);
95
+ if (process.platform !== "win32" && (st.mode & 18) !== 0) throw new Error(`Refusing --cwd ${dir}: it is writable by group or others (mode ${(st.mode & 511).toString(8)}). Use a directory only you can write to.`);
96
+ for (let p = dirname(dir); p !== dirname(p); p = dirname(p)) try {
97
+ const ps = statSync(p);
98
+ if (process.platform !== "win32" && (ps.mode & 18) !== 0 && (ps.mode & 512) === 0) {
99
+ process.stderr.write(`warning: --cwd ${dir}: its parent ${p} is writable by group or others, so they could replace it.\n`);
100
+ break;
101
+ }
102
+ } catch {
103
+ break;
104
+ }
105
+ return dir;
106
+ }
107
+ /** True when group or others can read/write the file. */
108
+ const isTooOpen = (path) => process.platform !== "win32" && (statSync(path).mode & 63) !== 0;
109
+ //#endregion
110
+ //#region server/listen/profile.ts
111
+ const profileSchema = z.object({
112
+ url: z.string().refine((u) => URL.canParse(u), "url must be a full URL"),
113
+ apiKey: z.string().min(8),
114
+ agentId: z.string().min(1),
115
+ agentName: z.string(),
116
+ consumer: z.string().min(1).max(100),
117
+ insecure: z.boolean().optional(),
118
+ types: z.array(z.enum(EVENT_TYPES)).optional(),
119
+ filter: webhookFilterSchema.optional(),
120
+ includeBody: z.boolean().optional(),
121
+ exec: z.string().min(1).optional(),
122
+ forward: z.object({
123
+ url: z.string(),
124
+ secret: z.string().startsWith("whsec_")
125
+ }).optional(),
126
+ concurrency: z.number().int().min(1).max(32).optional(),
127
+ timeoutSeconds: z.number().int().min(1).max(86400).optional(),
128
+ maxAttempts: z.number().int().min(1).max(100).optional(),
129
+ tool: z.enum([
130
+ "claude-code",
131
+ "codex",
132
+ "cursor",
133
+ "hermes",
134
+ "other"
135
+ ]).optional(),
136
+ mcpUrl: z.string().optional(),
137
+ skillUrl: z.string().optional(),
138
+ forwardSecret: z.string().startsWith("whsec_").optional(),
139
+ cwd: z.string().startsWith("/").optional(),
140
+ createdAt: z.string().optional()
141
+ }).loose();
142
+ const profileExists = (name) => existsSync(profilePath(validProfileName(name)));
143
+ function listProfiles() {
144
+ if (!existsSync(profilesDir())) return [];
145
+ return readdirSync(profilesDir()).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)).sort();
146
+ }
147
+ function loadProfile(name = DEFAULT_PROFILE) {
148
+ const n = validProfileName(name);
149
+ const path = profilePath(n);
150
+ if (!existsSync(path)) {
151
+ const others = listProfiles();
152
+ const hint = others.length ? ` Known profiles: ${others.join(", ")} (pick one with --profile).` : ` Run \`${NPX} connect <daemon-url>\` first.`;
153
+ throw new Error(`No agentinbox profile "${n}" (${path}).${hint}`);
154
+ }
155
+ if (isTooOpen(path)) {
156
+ chmodSync(path, 384);
157
+ process.stderr.write(`warning: ${path} was readable by other users; it is 0600 again. If anyone else could have read it, rotate the agent's key in the console and run \`${NPX} connect … --force\`.\n`);
158
+ }
159
+ let raw;
160
+ try {
161
+ raw = JSON.parse(readFileSync(path, "utf8"));
162
+ } catch (err) {
163
+ throw new Error(`Profile ${path} is not valid JSON (${err.message}). Fix it or run \`${NPX} connect\` again.`);
164
+ }
165
+ const parsed = profileSchema.safeParse(raw);
166
+ if (!parsed.success) {
167
+ const issue = parsed.error.issues[0];
168
+ throw new Error(`Profile ${path} is invalid: ${issue?.path.join(".") || "file"}: ${issue?.message}`);
169
+ }
170
+ return parsed.data;
171
+ }
172
+ function saveProfile(name, profile) {
173
+ const path = profilePath(validProfileName(name));
174
+ const clean = Object.fromEntries(Object.entries(profile).filter(([, v]) => v !== void 0));
175
+ writePrivateFile(path, JSON.stringify(clean, null, 2) + "\n");
176
+ return path;
177
+ }
178
+ //#endregion
179
+ export { assertTrustedDir as a, isTooOpen as c, NPX as d, version as f, saveProfile as i, tighten as l, loadProfile as n, backupFile as o, profileExists as r, ensurePrivateDir as s, listProfiles as t, writePrivateFile as u };
180
+
181
+ //# sourceMappingURL=profile-CgGgJ4Dm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-CgGgJ4Dm.js","names":[],"sources":["../server/listen/version.ts","../server/listen/fsutil.ts","../server/listen/profile.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport { NPM_PACKAGE } from \"../../shared/types.ts\";\n\n/** How printed instructions invoke the CLI without a global install. */\nexport const NPX = `npx ${NPM_PACKAGE}`;\n\n/** package.json sits above dist/ (bundled) or two levels above server/listen/ (source). */\nfunction readVersion(): string {\n for (const rel of [\"..\", \"../..\"]) {\n try {\n const pkg = JSON.parse(readFileSync(join(import.meta.dirname, rel, \"package.json\"), \"utf8\")) as { name?: string; version?: string };\n if (pkg.name === NPM_PACKAGE && pkg.version) return pkg.version;\n } catch {\n // Try the next candidate.\n }\n }\n return \"0.0.0\";\n}\n\nexport const version = readVersion();\n","import { randomBytes } from \"node:crypto\";\nimport { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\n/** Creates a directory (and parents) readable only by this user. Tightens an existing one. */\nexport function ensurePrivateDir(dir: string): void {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n if (process.platform !== \"win32\" && (statSync(dir).mode & 0o077) !== 0) chmodSync(dir, 0o700);\n}\n\n/**\n * Writes a file with mode 0600 atomically (temp file + rename), so a crash never leaves a\n * half-written profile and the secret is never briefly world-readable.\n */\nexport function writePrivateFile(linkOrPath: string, content: string, opts: { privateDir?: boolean } = {}): void {\n // A symlinked config (dotfiles repos) is updated at its real location instead of being replaced by a plain file.\n const path = resolveLink(linkOrPath);\n if (opts.privateDir !== false) ensurePrivateDir(dirname(path));\n else mkdirSync(dirname(path), { recursive: true });\n const tmp = `${path}.${randomBytes(4).toString(\"hex\")}.tmp`;\n try {\n writeFileSync(tmp, content, { mode: 0o600, flag: \"wx\" });\n chmodSync(tmp, 0o600);\n renameSync(tmp, path);\n } catch (err) {\n rmSync(tmp, { force: true });\n throw err;\n }\n}\n\nfunction resolveLink(path: string): string {\n try {\n return lstatSync(path).isSymbolicLink() ? realpathSync(path) : path;\n } catch {\n return path;\n }\n}\n\n/** Copies `path` next to itself as `<path>.agentinbox-backup-<timestamp>` (0600). Returns the copy, or null if there was nothing to back up. */\nexport function backupFile(path: string): string | null {\n if (!existsSync(path)) return null;\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const content = readFileSync(path);\n // Created 0600 and exclusively (never through an existing file or symlink), then filled.\n for (let n = 0; ; n++) {\n const backup = `${path}.agentinbox-backup-${stamp}${n ? `-${n}` : \"\"}`;\n try {\n writeFileSync(backup, content, { mode: 0o600, flag: \"wx\" });\n return backup;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\" || n > 20) throw err;\n }\n }\n}\n\n/** Tightens an existing file to 0600 (files created by appendFileSync keep their first mode). */\nexport function tighten(path: string): void {\n if (process.platform !== \"win32\" && existsSync(path) && (statSync(path).mode & 0o077) !== 0) chmodSync(path, 0o600);\n}\n\n/**\n * A directory commands may run in: must exist, be owned by this user and not be writable by group or\n * others (otherwise someone else could plant agent instructions, MCP configs or hooks there).\n */\nexport function assertTrustedDir(dir: string): string {\n let st;\n try {\n st = statSync(dir);\n } catch {\n throw new Error(`--cwd ${dir} does not exist.`);\n }\n if (!st.isDirectory()) throw new Error(`--cwd ${dir} is not a directory.`);\n const uid = process.getuid?.();\n if (uid !== undefined && st.uid !== uid) throw new Error(`Refusing --cwd ${dir}: it belongs to another user, who could plant instructions or hooks for the agent there.`);\n if (process.platform !== \"win32\" && (st.mode & 0o022) !== 0) {\n throw new Error(`Refusing --cwd ${dir}: it is writable by group or others (mode ${(st.mode & 0o777).toString(8)}). Use a directory only you can write to.`);\n }\n // A parent others can write to (without the sticky bit) lets them swap this directory out.\n for (let p = dirname(dir); p !== dirname(p); p = dirname(p)) {\n try {\n const ps = statSync(p);\n if (process.platform !== \"win32\" && (ps.mode & 0o022) !== 0 && (ps.mode & 0o1000) === 0) {\n process.stderr.write(`warning: --cwd ${dir}: its parent ${p} is writable by group or others, so they could replace it.\\n`);\n break;\n }\n } catch {\n break;\n }\n }\n return dir;\n}\n\n/** True when group or others can read/write the file. */\nexport const isTooOpen = (path: string) => process.platform !== \"win32\" && (statSync(path).mode & 0o077) !== 0;\n","import { chmodSync, existsSync, readdirSync, readFileSync, rmSync } from \"node:fs\";\nimport { z } from \"zod\";\nimport type { AgentTool, ListenerConfig } from \"../../shared/agent-api.ts\";\nimport { EVENT_TYPES } from \"../../shared/types.ts\";\nimport { webhookFilterSchema } from \"../webhooks/filter.ts\";\nimport { isTooOpen, writePrivateFile } from \"./fsutil.ts\";\nimport { DEFAULT_PROFILE, profilePath, profilesDir, validProfileName } from \"./paths.ts\";\nimport { NPX } from \"./version.ts\";\n\n/**\n * A profile file: the shared ListenerConfig plus a few agent-side extras. Holds the agent's API key,\n * so it is always 0600 inside a 0700 directory, and nothing ever prints it.\n */\nexport interface Profile extends ListenerConfig {\n tool?: AgentTool;\n mcpUrl?: string;\n skillUrl?: string;\n /** Forward-mode signing secret while no forward URL is saved (see forwardSecret()). */\n forwardSecret?: string;\n /** Working directory for --exec commands (default: a private dir in the state dir). */\n cwd?: string;\n createdAt?: string;\n}\n\nconst TOOLS = [\"claude-code\", \"codex\", \"cursor\", \"hermes\", \"other\"] as const;\n\nconst profileSchema = z\n .object({\n url: z.string().refine((u) => URL.canParse(u), \"url must be a full URL\"),\n apiKey: z.string().min(8),\n agentId: z.string().min(1),\n agentName: z.string(),\n consumer: z.string().min(1).max(100),\n insecure: z.boolean().optional(),\n types: z.array(z.enum(EVENT_TYPES)).optional(),\n filter: webhookFilterSchema.optional(),\n includeBody: z.boolean().optional(),\n exec: z.string().min(1).optional(),\n forward: z.object({ url: z.string(), secret: z.string().startsWith(\"whsec_\") }).optional(),\n concurrency: z.number().int().min(1).max(32).optional(),\n timeoutSeconds: z.number().int().min(1).max(86_400).optional(),\n maxAttempts: z.number().int().min(1).max(100).optional(),\n tool: z.enum(TOOLS).optional(),\n mcpUrl: z.string().optional(),\n skillUrl: z.string().optional(),\n forwardSecret: z.string().startsWith(\"whsec_\").optional(),\n cwd: z.string().startsWith(\"/\").optional(),\n createdAt: z.string().optional(),\n })\n .loose();\n\nexport const profileExists = (name: string) => existsSync(profilePath(validProfileName(name)));\n\nexport function listProfiles(): string[] {\n if (!existsSync(profilesDir())) return [];\n return readdirSync(profilesDir())\n .filter((f) => f.endsWith(\".json\"))\n .map((f) => f.slice(0, -5))\n .sort();\n}\n\nexport function loadProfile(name: string = DEFAULT_PROFILE): Profile {\n const n = validProfileName(name);\n const path = profilePath(n);\n if (!existsSync(path)) {\n const others = listProfiles();\n const hint = others.length ? ` Known profiles: ${others.join(\", \")} (pick one with --profile).` : ` Run \\`${NPX} connect <daemon-url>\\` first.`;\n throw new Error(`No agentinbox profile \"${n}\" (${path}).${hint}`);\n }\n // Someone loosened the permissions: tighten them, and say the key may have been read meanwhile.\n if (isTooOpen(path)) {\n chmodSync(path, 0o600);\n process.stderr.write(\n `warning: ${path} was readable by other users; it is 0600 again. If anyone else could have read it, rotate the agent's key in the console and run \\`${NPX} connect … --force\\`.\\n`,\n );\n }\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(path, \"utf8\"));\n } catch (err) {\n throw new Error(`Profile ${path} is not valid JSON (${(err as Error).message}). Fix it or run \\`${NPX} connect\\` again.`);\n }\n const parsed = profileSchema.safeParse(raw);\n if (!parsed.success) {\n const issue = parsed.error.issues[0];\n throw new Error(`Profile ${path} is invalid: ${issue?.path.join(\".\") || \"file\"}: ${issue?.message}`);\n }\n return parsed.data as Profile;\n}\n\nexport function saveProfile(name: string, profile: Profile): string {\n const path = profilePath(validProfileName(name));\n const clean = Object.fromEntries(Object.entries(profile).filter(([, v]) => v !== undefined));\n writePrivateFile(path, JSON.stringify(clean, null, 2) + \"\\n\");\n return path;\n}\n\nexport function deleteProfile(name: string): void {\n rmSync(profilePath(validProfileName(name)), { force: true });\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { n as loadProfile } from "./profile-CgGgJ4Dm.js";
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { i as recipe } from "./recipes-UalOc_LB.js";