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,295 @@
1
+ #!/usr/bin/env node
2
+ import { d as NPX, i as saveProfile, n as loadProfile, s as ensurePrivateDir } from "./profile-CgGgJ4Dm.js";
3
+ import { i as pidAlive, m as checkTransport, o as readStatus, r as acquireFileLock, t as EXIT, u as resolveListen } from "./listen-D8DkEhqM.js";
4
+ import { a as listenAppDir, c as profileStateDir, f as validProfileName, o as logPath, r as dataDir, s as profilePath } from "./paths-TLp8hiMt.js";
5
+ import { A as xmlEscape, C as swapIn, S as stagePackage, T as systemctl, _ as sdQuote, a as enableLinger, f as preflight, i as assertSupported, l as launchdTarget, m as removeOwnInstall, p as readText, s as installedEntry, w as systemOps } from "./service-D_ZEs3Zk.js";
6
+ import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
7
+ import { delimiter, dirname, join } from "node:path";
8
+ import pc from "picocolors";
9
+ //#region server/listen/service.ts
10
+ /**
11
+ * `agentinbox listen service install|uninstall|status`: one systemd user unit / launchd agent per
12
+ * profile, running from a stable install (not the npx cache). The unit only says which profile to
13
+ * run; settings and the key stay in the 0600 profile file. Installation reuses the daemon's
14
+ * machinery (server/service.ts): systemd pre-check, the user manager's unit directory, staging into
15
+ * app.new and swapping in, rollback when the new listener doesn't start.
16
+ */
17
+ const unitName = (profile) => `agentinbox-listen-${profile}`;
18
+ const launchdLabel = (profile) => `com.unlaboredlabs.agentinbox.listen.${profile}`;
19
+ /**
20
+ * PATH for the service: this shell's PATH (so `claude`, `codex`… resolve as they do here) minus
21
+ * transient entries npx, npm scripts and version-manager shells add, plus node's own directory.
22
+ */
23
+ function servicePath(raw = process.env.PATH ?? "") {
24
+ const transient = /\/_npx\/|node_modules\/\.bin|node-gyp-bin|fnm_multishells|\/\.pnpm\//;
25
+ const dirs = [dirname(process.execPath), ...raw.split(delimiter)].filter((d) => d && d.startsWith("/") && !transient.test(d));
26
+ if (!dirs.includes("/usr/bin")) dirs.push("/usr/local/bin", "/usr/bin", "/bin");
27
+ return [...new Set(dirs)].join(delimiter);
28
+ }
29
+ /** Environment the service needs to behave like the shell `service install` ran in. Never contains the key. */
30
+ function serviceEnvironment() {
31
+ const env = { AGENTINBOX_SERVICE: "1" };
32
+ env.PATH = servicePath();
33
+ for (const key of [
34
+ "XDG_CONFIG_HOME",
35
+ "XDG_STATE_HOME",
36
+ "XDG_DATA_HOME",
37
+ "CLAUDE_CONFIG_DIR",
38
+ "CODEX_HOME",
39
+ "HERMES_HOME",
40
+ "LANG",
41
+ "AGENTINBOX_DEBUG"
42
+ ]) if (process.env[key]) env[key] = process.env[key];
43
+ return env;
44
+ }
45
+ function systemdListenUnit(profile, entry, cwd, system = false) {
46
+ const vars = Object.entries(serviceEnvironment()).map(([k, v]) => `Environment=${sdQuote(`${k}=${v}`)}`).join("\n");
47
+ return `[Unit]
48
+ Description=agentinbox listener (profile ${profile})${system ? "\nAfter=network-online.target\nWants=network-online.target" : ""}
49
+ StartLimitIntervalSec=0
50
+
51
+ [Service]
52
+ ExecStart=${sdQuote(process.execPath)} ${sdQuote(entry)} listen --profile ${profile}
53
+ WorkingDirectory=${cwd.replace(/%/g, "%%")}
54
+ ${vars}
55
+ Restart=on-failure
56
+ RestartSec=10
57
+ RestartSteps=6
58
+ RestartMaxDelaySec=600
59
+ # ${EXIT.revoked}: the key was revoked or the config is invalid. Restarting can't fix that.
60
+ RestartPreventExitStatus=${EXIT.revoked}
61
+ TimeoutStopSec=20
62
+
63
+ [Install]
64
+ WantedBy=${system ? "multi-user.target" : "default.target"}
65
+ `;
66
+ }
67
+ function launchdListenPlist(profile, entry, cwd) {
68
+ const esc = xmlEscape;
69
+ const vars = Object.entries(serviceEnvironment()).map(([k, v]) => ` <key>${esc(k)}</key><string>${esc(v)}</string>`).join("\n");
70
+ const errLog = esc(join(profileStateDir(profile), "service-stderr.log"));
71
+ return `<?xml version="1.0" encoding="UTF-8"?>
72
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
73
+ <plist version="1.0">
74
+ <dict>
75
+ <key>Label</key><string>${esc(launchdLabel(profile))}</string>
76
+ <key>ProgramArguments</key>
77
+ <array>
78
+ <string>${esc(process.execPath)}</string>
79
+ <string>${esc(entry)}</string>
80
+ <string>listen</string>
81
+ <string>--profile</string>
82
+ <string>${esc(profile)}</string>
83
+ </array>
84
+ <key>WorkingDirectory</key><string>${esc(cwd)}</string>
85
+ <key>EnvironmentVariables</key>
86
+ <dict>
87
+ ${vars}
88
+ </dict>
89
+ <key>RunAtLoad</key><true/>
90
+ <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
91
+ <key>ThrottleInterval</key><integer>10</integer>
92
+ <key>StandardOutPath</key><string>/dev/null</string>
93
+ <key>StandardErrorPath</key><string>${errLog}</string>
94
+ </dict>
95
+ </plist>
96
+ `;
97
+ }
98
+ const unitFile = (ops, profile) => join(ops.unitDir(), ops.platform === "darwin" ? `${launchdLabel(profile)}.plist` : `${unitName(profile)}.service`);
99
+ /** Installed listener profiles (from the unit directory). */
100
+ function installedProfiles(ops) {
101
+ const dir = ops.unitDir();
102
+ if (!existsSync(dir)) return [];
103
+ const re = ops.platform === "darwin" ? /^com\.unlaboredlabs\.agentinbox\.listen\.(.+)\.plist$/ : /^agentinbox-listen-(.+)\.service$/;
104
+ return readdirSync(dir).map((f) => re.exec(f)?.[1]).filter((p) => !!p);
105
+ }
106
+ function isServiceInstalled(profile, ops = systemOps()) {
107
+ if (ops.platform !== "linux" && ops.platform !== "darwin") return false;
108
+ try {
109
+ return existsSync(unitFile(ops, profile));
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
114
+ function listenerHints(ops, profile) {
115
+ if (ops.platform === "darwin") return [`launchctl print ${launchdTarget(ops)}/${launchdLabel(profile)}`, `the log ${logPath(profile)}`];
116
+ const scope = ops.root ? "" : " --user";
117
+ return [
118
+ `systemctl${scope} status ${unitName(profile)}`,
119
+ `journalctl${scope} -u ${unitName(profile)} -e`,
120
+ `the log ${logPath(profile)}`
121
+ ];
122
+ }
123
+ /** Resolves true once a listener started after `since` is running for the profile (it writes status.json on start). */
124
+ async function waitListenerStarted(profile, since, timeoutMs = 2e4) {
125
+ const deadline = Date.now() + timeoutMs;
126
+ while (Date.now() < deadline) {
127
+ const st = readStatus(profile);
128
+ if (st && Date.parse(st.startedAt) >= since - 1e3) {
129
+ if (st.stoppedAt) return false;
130
+ if (pidAlive(st.pid)) return true;
131
+ }
132
+ await new Promise((r) => setTimeout(r, 250));
133
+ }
134
+ return false;
135
+ }
136
+ function startUnit(ops, profile, file) {
137
+ if (ops.platform === "linux") {
138
+ systemctl(ops, "daemon-reload");
139
+ systemctl(ops, "enable", unitName(profile));
140
+ systemctl(ops, "restart", unitName(profile));
141
+ } else {
142
+ try {
143
+ ops.exec("launchctl", ["bootout", `${launchdTarget(ops)}/${launchdLabel(profile)}`], { quiet: true });
144
+ } catch {}
145
+ ops.exec("launchctl", [
146
+ "bootstrap",
147
+ launchdTarget(ops),
148
+ file
149
+ ]);
150
+ }
151
+ }
152
+ /** Stages the listeners' install (no web console needed). */
153
+ const stageListenerApp = (staging) => stagePackage(staging, {
154
+ where: dirname(staging),
155
+ requireWeb: false
156
+ });
157
+ async function installListenService(flags, deps = {}) {
158
+ const ops = deps.ops ?? systemOps();
159
+ assertSupported(ops);
160
+ const profileName = validProfileName(flags.profile ?? "default");
161
+ const profile = loadProfile(profileName);
162
+ const { config, next } = resolveListen(profileName, profile, {
163
+ ...flags,
164
+ save: true
165
+ });
166
+ if (!config.exec && !config.forward) throw new Error(`Nothing for the service to run. Pass --exec "<command>" and/or --forward <url> (they are saved to profile "${profileName}").`);
167
+ await checkTransport(next.url, { insecure: config.insecure });
168
+ preflight(ops);
169
+ const shared = dataDir();
170
+ if (existsSync(join(shared, "agentinbox.db"))) throw new Error(`${shared} holds an agentinbox daemon's data (agentinbox.db), so the listener can't install there. Run the daemon with another --data directory, or set XDG_DATA_HOME for the listener.`);
171
+ const p = profileName === "default" ? "" : ` --profile ${profileName}`;
172
+ const cwd = config.cwd;
173
+ mkdirSync(profileStateDir(profileName), {
174
+ recursive: true,
175
+ mode: 448
176
+ });
177
+ if (config.cwdIsDefault) ensurePrivateDir(cwd);
178
+ mkdirSync(shared, {
179
+ recursive: true,
180
+ mode: 448
181
+ });
182
+ const releaseInstallLock = acquireFileLock(join(shared, "install.lock"), (holder) => /* @__PURE__ */ new Error(`Another listener service install is running (pid ${holder.pid}, since ${holder.startedAt}). Try again when it has finished.`));
183
+ try {
184
+ await installLocked();
185
+ } finally {
186
+ releaseInstallLock();
187
+ }
188
+ async function installLocked() {
189
+ const staging = (deps.stage ?? stageListenerApp)(join(shared, "app.new"));
190
+ saveProfile(profileName, next);
191
+ const app = listenAppDir();
192
+ const entry = installedEntry(app);
193
+ const file = unitFile(ops, profileName);
194
+ const previousUnit = readText(file);
195
+ const since = Date.now();
196
+ let swap = null;
197
+ try {
198
+ swap = swapIn(staging, app);
199
+ mkdirSync(ops.unitDir(), { recursive: true });
200
+ if (ops.platform === "linux") writeFileSync(file, systemdListenUnit(profileName, entry, cwd, ops.root));
201
+ else writeFileSync(file, launchdListenPlist(profileName, entry, cwd), { mode: 384 });
202
+ for (let attempt = 1;; attempt++) try {
203
+ startUnit(ops, profileName, file);
204
+ break;
205
+ } catch (err) {
206
+ if (ops.platform !== "darwin" || attempt >= 5) throw err;
207
+ await new Promise((r) => setTimeout(r, 1e3));
208
+ }
209
+ if (!await (deps.waitStarted ?? waitListenerStarted)(profileName, since)) throw new Error("the listener did not start");
210
+ } catch (err) {
211
+ const quietly = (fn) => {
212
+ try {
213
+ fn();
214
+ } catch {}
215
+ };
216
+ if (swap) quietly(() => swap.restore());
217
+ quietly(() => saveProfile(profileName, profile));
218
+ quietly(() => rmSync(staging, {
219
+ recursive: true,
220
+ force: true
221
+ }));
222
+ if (previousUnit !== null) quietly(() => {
223
+ writeFileSync(file, previousUnit, { mode: ops.platform === "darwin" ? 384 : 420 });
224
+ startUnit(ops, profileName, file);
225
+ });
226
+ else quietly(() => uninstallListenService(profileName, true, { ops }));
227
+ const hints = listenerHints(ops, profileName).map((h) => ` ${h}`).join("\n");
228
+ throw new Error(`Starting the listener service failed (${(err instanceof Error ? err.message : String(err)).split("\n")[0]}). Rolled back to ${previousUnit ? "the previous install" : "no service"}.\n To find out why:\n${hints}`);
229
+ }
230
+ swap.commit();
231
+ enableLinger(ops);
232
+ for (const other of installedProfiles(ops).filter((o) => o !== profileName)) try {
233
+ if (ops.platform === "linux") systemctl(ops, "try-restart", unitName(other));
234
+ else ops.exec("launchctl", [
235
+ "kickstart",
236
+ "-k",
237
+ `${launchdTarget(ops)}/${launchdLabel(other)}`
238
+ ], { quiet: true });
239
+ } catch {}
240
+ }
241
+ const kind = ops.platform === "linux" ? ops.root ? "systemd system service" : "systemd user service" : "launchd agent";
242
+ console.log(`\n ${pc.green("✔")} Installed the listener for profile "${profileName}" as a ${kind} (${ops.platform === "linux" ? unitName(profileName) : launchdLabel(profileName)}).`);
243
+ console.log(` Runs: ${config.exec ? `--exec ${config.exec}` : ""}${config.exec && config.forward ? " and " : ""}${config.forward ? `--forward ${config.forward.url}` : ""}`);
244
+ console.log(` Directory: ${cwd}${config.cwdIsDefault ? " (private; pass --cwd to run in a project)" : ""}`);
245
+ console.log(` Node.js: ${process.execPath} (v${process.versions.node}); if you remove or replace it, run \`${NPX} listen service install${p}\` again.`);
246
+ console.log(` Settings: ${profilePath(profileName)} (change them with \`${NPX} listen${p} … --save\`, then reinstall)`);
247
+ console.log(` Log: ${logPath(profileName)}`);
248
+ console.log(` Status: ${NPX} listen status${p}`);
249
+ if (ops.platform === "linux") console.log(` Follow: journalctl ${ops.root ? "" : "--user "}-u ${unitName(profileName)} -f`);
250
+ console.log();
251
+ }
252
+ function uninstallListenService(profileName, quiet = false, deps = {}) {
253
+ const ops = deps.ops ?? systemOps();
254
+ assertSupported(ops);
255
+ const name = validProfileName(profileName);
256
+ const file = unitFile(ops, name);
257
+ if (!existsSync(file)) {
258
+ if (!quiet) console.log(`No listener service is installed for profile "${name}".`);
259
+ return;
260
+ }
261
+ if (ops.platform === "linux") {
262
+ try {
263
+ systemctl(ops, "disable", "--now", unitName(name));
264
+ } catch {}
265
+ rmSync(file);
266
+ systemctl(ops, "daemon-reload");
267
+ } else {
268
+ try {
269
+ ops.exec("launchctl", ["bootout", `${launchdTarget(ops)}/${launchdLabel(name)}`], { quiet: true });
270
+ } catch {}
271
+ rmSync(file);
272
+ }
273
+ if (!installedProfiles(ops).length) removeOwnInstall(dataDir());
274
+ if (!quiet) console.log(`Removed the listener service for profile "${name}". The profile (and its key) was kept: ${profilePath(name)}`);
275
+ }
276
+ function listenServiceStatus(profileName, deps = {}) {
277
+ const ops = deps.ops ?? systemOps();
278
+ assertSupported(ops);
279
+ const name = validProfileName(profileName);
280
+ if (!isServiceInstalled(name, ops)) {
281
+ console.log(`No listener service is installed for profile "${name}". Install one with \`${NPX} listen service install\`.`);
282
+ process.exitCode = 1;
283
+ return;
284
+ }
285
+ try {
286
+ if (ops.platform === "linux") systemctl(ops, "status", unitName(name), "--no-pager");
287
+ else ops.exec("launchctl", ["print", `${launchdTarget(ops)}/${launchdLabel(name)}`]);
288
+ } catch {
289
+ process.exitCode = 1;
290
+ }
291
+ }
292
+ //#endregion
293
+ export { listenServiceStatus as a, stageListenerApp as c, unitName as d, waitListenerStarted as f, launchdListenPlist as i, systemdListenUnit as l, isServiceInstalled as n, serviceEnvironment as o, launchdLabel as r, servicePath as s, installListenService as t, uninstallListenService as u };
294
+
295
+ //# sourceMappingURL=service-whbugeQp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-whbugeQp.js","names":[],"sources":["../server/listen/service.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { delimiter, dirname, join } from \"node:path\";\nimport pc from \"picocolors\";\nimport {\n assertSupported,\n enableLinger,\n installedEntry,\n launchdTarget,\n preflight,\n readText,\n removeOwnInstall,\n sdQuote,\n type ServiceOps,\n stagePackage,\n swapIn,\n systemctl,\n systemOps,\n xmlEscape,\n} from \"../service.ts\";\nimport { ensurePrivateDir } from \"./fsutil.ts\";\nimport { EXIT } from \"./listen.ts\";\nimport { acquireFileLock } from \"./lock.ts\";\nimport { type ListenFlags, resolveListen } from \"./options.ts\";\nimport { DEFAULT_PROFILE, dataDir, listenAppDir, logPath, profilePath, profileStateDir, validProfileName } from \"./paths.ts\";\nimport { loadProfile, saveProfile } from \"./profile.ts\";\nimport { pidAlive, readStatus } from \"./state.ts\";\nimport { checkTransport } from \"./transport.ts\";\nimport { NPX } from \"./version.ts\";\n\n/**\n * `agentinbox listen service install|uninstall|status`: one systemd user unit / launchd agent per\n * profile, running from a stable install (not the npx cache). The unit only says which profile to\n * run; settings and the key stay in the 0600 profile file. Installation reuses the daemon's\n * machinery (server/service.ts): systemd pre-check, the user manager's unit directory, staging into\n * app.new and swapping in, rollback when the new listener doesn't start.\n */\n\nexport const unitName = (profile: string) => `agentinbox-listen-${profile}`;\nexport const launchdLabel = (profile: string) => `com.unlaboredlabs.agentinbox.listen.${profile}`;\n\n/**\n * PATH for the service: this shell's PATH (so `claude`, `codex`… resolve as they do here) minus\n * transient entries npx, npm scripts and version-manager shells add, plus node's own directory.\n */\nexport function servicePath(raw = process.env.PATH ?? \"\"): string {\n const transient = /\\/_npx\\/|node_modules\\/\\.bin|node-gyp-bin|fnm_multishells|\\/\\.pnpm\\//;\n const dirs = [dirname(process.execPath), ...raw.split(delimiter)].filter((d) => d && d.startsWith(\"/\") && !transient.test(d));\n if (!dirs.includes(\"/usr/bin\")) dirs.push(\"/usr/local/bin\", \"/usr/bin\", \"/bin\");\n return [...new Set(dirs)].join(delimiter);\n}\n\n/** Environment the service needs to behave like the shell `service install` ran in. Never contains the key. */\nexport function serviceEnvironment(): Record<string, string> {\n const env: Record<string, string> = { AGENTINBOX_SERVICE: \"1\" };\n // PATH matters most: `claude`, `codex`, `hermes`… usually live in ~/.local/bin, nvm or Homebrew dirs\n // that a service manager's minimal PATH doesn't include.\n env.PATH = servicePath();\n for (const key of [\"XDG_CONFIG_HOME\", \"XDG_STATE_HOME\", \"XDG_DATA_HOME\", \"CLAUDE_CONFIG_DIR\", \"CODEX_HOME\", \"HERMES_HOME\", \"LANG\", \"AGENTINBOX_DEBUG\"]) {\n if (process.env[key]) env[key] = process.env[key]!;\n }\n return env;\n}\n\nexport function systemdListenUnit(profile: string, entry: string, cwd: string, system = false): string {\n const vars = Object.entries(serviceEnvironment())\n .map(([k, v]) => `Environment=${sdQuote(`${k}=${v}`)}`)\n .join(\"\\n\");\n return `[Unit]\nDescription=agentinbox listener (profile ${profile})${system ? \"\\nAfter=network-online.target\\nWants=network-online.target\" : \"\"}\nStartLimitIntervalSec=0\n\n[Service]\nExecStart=${sdQuote(process.execPath)} ${sdQuote(entry)} listen --profile ${profile}\nWorkingDirectory=${cwd.replace(/%/g, \"%%\")}\n${vars}\nRestart=on-failure\nRestartSec=10\nRestartSteps=6\nRestartMaxDelaySec=600\n# ${EXIT.revoked}: the key was revoked or the config is invalid. Restarting can't fix that.\nRestartPreventExitStatus=${EXIT.revoked}\nTimeoutStopSec=20\n\n[Install]\nWantedBy=${system ? \"multi-user.target\" : \"default.target\"}\n`;\n}\n\nexport function launchdListenPlist(profile: string, entry: string, cwd: string): string {\n const esc = xmlEscape;\n const vars = Object.entries(serviceEnvironment())\n .map(([k, v]) => ` <key>${esc(k)}</key><string>${esc(v)}</string>`)\n .join(\"\\n\");\n // The listener logs to the rotated listen.log and stays quiet on the console under launchd; only\n // crashes reach stderr, so this file stays small.\n const errLog = esc(join(profileStateDir(profile), \"service-stderr.log\"));\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key><string>${esc(launchdLabel(profile))}</string>\n <key>ProgramArguments</key>\n <array>\n <string>${esc(process.execPath)}</string>\n <string>${esc(entry)}</string>\n <string>listen</string>\n <string>--profile</string>\n <string>${esc(profile)}</string>\n </array>\n <key>WorkingDirectory</key><string>${esc(cwd)}</string>\n <key>EnvironmentVariables</key>\n <dict>\n${vars}\n </dict>\n <key>RunAtLoad</key><true/>\n <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>\n <key>ThrottleInterval</key><integer>10</integer>\n <key>StandardOutPath</key><string>/dev/null</string>\n <key>StandardErrorPath</key><string>${errLog}</string>\n</dict>\n</plist>\n`;\n}\n\nconst unitFile = (ops: ServiceOps, profile: string) =>\n join(ops.unitDir(), ops.platform === \"darwin\" ? `${launchdLabel(profile)}.plist` : `${unitName(profile)}.service`);\n\n/** Installed listener profiles (from the unit directory). */\nfunction installedProfiles(ops: ServiceOps): string[] {\n const dir = ops.unitDir();\n if (!existsSync(dir)) return [];\n const re = ops.platform === \"darwin\" ? /^com\\.unlaboredlabs\\.agentinbox\\.listen\\.(.+)\\.plist$/ : /^agentinbox-listen-(.+)\\.service$/;\n return readdirSync(dir)\n .map((f) => re.exec(f)?.[1])\n .filter((p): p is string => !!p);\n}\n\nexport function isServiceInstalled(profile: string, ops: ServiceOps = systemOps()): boolean {\n if (ops.platform !== \"linux\" && ops.platform !== \"darwin\") return false;\n try {\n return existsSync(unitFile(ops, profile));\n } catch {\n return false;\n }\n}\n\nfunction listenerHints(ops: ServiceOps, profile: string): string[] {\n if (ops.platform === \"darwin\") return [`launchctl print ${launchdTarget(ops)}/${launchdLabel(profile)}`, `the log ${logPath(profile)}`];\n const scope = ops.root ? \"\" : \" --user\";\n return [`systemctl${scope} status ${unitName(profile)}`, `journalctl${scope} -u ${unitName(profile)} -e`, `the log ${logPath(profile)}`];\n}\n\n/** Resolves true once a listener started after `since` is running for the profile (it writes status.json on start). */\nexport async function waitListenerStarted(profile: string, since: number, timeoutMs = 20_000): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const st = readStatus(profile);\n if (st && Date.parse(st.startedAt) >= since - 1000) {\n if (st.stoppedAt) return false;\n if (pidAlive(st.pid)) return true;\n }\n await new Promise((r) => setTimeout(r, 250));\n }\n return false;\n}\n\nfunction startUnit(ops: ServiceOps, profile: string, file: string): void {\n if (ops.platform === \"linux\") {\n systemctl(ops, \"daemon-reload\");\n systemctl(ops, \"enable\", unitName(profile));\n // restart, not start: a re-install must pick up the new install and settings.\n systemctl(ops, \"restart\", unitName(profile));\n } else {\n try {\n ops.exec(\"launchctl\", [\"bootout\", `${launchdTarget(ops)}/${launchdLabel(profile)}`], { quiet: true });\n } catch {\n // Not loaded.\n }\n ops.exec(\"launchctl\", [\"bootstrap\", launchdTarget(ops), file]);\n }\n}\n\n/** Stages the listeners' install (no web console needed). */\nexport const stageListenerApp = (staging: string): string => stagePackage(staging, { where: dirname(staging), requireWeb: false });\n\nexport interface ListenServiceDeps {\n ops?: ServiceOps;\n /** Installs the package into the given staging dir (tests). */\n stage?: (staging: string) => string;\n /** Readiness check after (re)starting the unit (tests). */\n waitStarted?: (profile: string, since: number) => Promise<boolean>;\n}\n\nexport async function installListenService(flags: ListenFlags, deps: ListenServiceDeps = {}): Promise<void> {\n const ops = deps.ops ?? systemOps();\n assertSupported(ops);\n const profileName = validProfileName(flags.profile ?? DEFAULT_PROFILE);\n const profile = loadProfile(profileName);\n // Service settings live in the profile: save whatever was passed with this command.\n const { config, next } = resolveListen(profileName, profile, { ...flags, save: true });\n if (!config.exec && !config.forward) {\n throw new Error(`Nothing for the service to run. Pass --exec \"<command>\" and/or --forward <url> (they are saved to profile \"${profileName}\").`);\n }\n await checkTransport(next.url, { insecure: config.insecure });\n preflight(ops);\n const shared = dataDir();\n // A daemon started with --data pointing here would share (and lose) its app/ install with us.\n if (existsSync(join(shared, \"agentinbox.db\"))) {\n throw new Error(`${shared} holds an agentinbox daemon's data (agentinbox.db), so the listener can't install there. Run the daemon with another --data directory, or set XDG_DATA_HOME for the listener.`);\n }\n\n const p = profileName === DEFAULT_PROFILE ? \"\" : ` --profile ${profileName}`;\n // Commands run in config.cwd (a private dir unless --cwd was given); the unit uses it too.\n const cwd = config.cwd;\n mkdirSync(profileStateDir(profileName), { recursive: true, mode: 0o700 });\n if (config.cwdIsDefault) ensurePrivateDir(cwd);\n\n // The same install path as the daemon (stage into app.new, swap into app), under a lock: all\n // profiles share this install, so two installs at once would trip over app.new / app.old.\n mkdirSync(shared, { recursive: true, mode: 0o700 });\n const releaseInstallLock = acquireFileLock(\n join(shared, \"install.lock\"),\n (holder) => new Error(`Another listener service install is running (pid ${holder.pid}, since ${holder.startedAt}). Try again when it has finished.`),\n );\n try {\n await installLocked();\n } finally {\n releaseInstallLock();\n }\n\n async function installLocked(): Promise<void> {\n const staging = (deps.stage ?? stageListenerApp)(join(shared, \"app.new\"));\n // Saved only once the new version is staged: a failed npm install changes nothing.\n saveProfile(profileName, next);\n const app = listenAppDir();\n const entry = installedEntry(app);\n const file = unitFile(ops, profileName);\n const previousUnit = readText(file);\n const since = Date.now();\n let swap: ReturnType<typeof swapIn> | null = null;\n try {\n swap = swapIn(staging, app);\n mkdirSync(ops.unitDir(), { recursive: true });\n if (ops.platform === \"linux\") writeFileSync(file, systemdListenUnit(profileName, entry, cwd, ops.root));\n else writeFileSync(file, launchdListenPlist(profileName, entry, cwd), { mode: 0o600 });\n // Right after a bootout, launchd can still be tearing the old job down: retry briefly.\n for (let attempt = 1; ; attempt++) {\n try {\n startUnit(ops, profileName, file);\n break;\n } catch (err) {\n if (ops.platform !== \"darwin\" || attempt >= 5) throw err;\n await new Promise((r) => setTimeout(r, 1000));\n }\n }\n if (!(await (deps.waitStarted ?? waitListenerStarted)(profileName, since))) throw new Error(\"the listener did not start\");\n } catch (err) {\n const quietly = (fn: () => void) => {\n try {\n fn();\n } catch {\n // Best effort: report the original failure.\n }\n };\n if (swap) quietly(() => swap!.restore());\n // The settings this install saved go back too, so the restored listener runs as before.\n quietly(() => saveProfile(profileName, profile));\n quietly(() => rmSync(staging, { recursive: true, force: true }));\n if (previousUnit !== null) {\n quietly(() => {\n writeFileSync(file, previousUnit, { mode: ops.platform === \"darwin\" ? 0o600 : 0o644 });\n startUnit(ops, profileName, file);\n });\n } else {\n quietly(() => uninstallListenService(profileName, true, { ops }));\n }\n const hints = listenerHints(ops, profileName).map((h) => ` ${h}`).join(\"\\n\");\n throw new Error(\n `Starting the listener service failed (${(err instanceof Error ? err.message : String(err)).split(\"\\n\")[0]}). Rolled back to ${previousUnit ? \"the previous install\" : \"no service\"}.\\n To find out why:\\n${hints}`,\n );\n }\n swap.commit();\n enableLinger(ops);\n // The other profiles' listeners share the install: move them onto the new one.\n for (const other of installedProfiles(ops).filter((o) => o !== profileName)) {\n try {\n if (ops.platform === \"linux\") systemctl(ops, \"try-restart\", unitName(other));\n else ops.exec(\"launchctl\", [\"kickstart\", \"-k\", `${launchdTarget(ops)}/${launchdLabel(other)}`], { quiet: true });\n } catch {\n // Not running: nothing to restart.\n }\n }\n }\n\n const kind = ops.platform === \"linux\" ? (ops.root ? \"systemd system service\" : \"systemd user service\") : \"launchd agent\";\n console.log(`\\n ${pc.green(\"✔\")} Installed the listener for profile \"${profileName}\" as a ${kind} (${ops.platform === \"linux\" ? unitName(profileName) : launchdLabel(profileName)}).`);\n console.log(` Runs: ${config.exec ? `--exec ${config.exec}` : \"\"}${config.exec && config.forward ? \" and \" : \"\"}${config.forward ? `--forward ${config.forward.url}` : \"\"}`);\n console.log(` Directory: ${cwd}${config.cwdIsDefault ? \" (private; pass --cwd to run in a project)\" : \"\"}`);\n console.log(` Node.js: ${process.execPath} (v${process.versions.node}); if you remove or replace it, run \\`${NPX} listen service install${p}\\` again.`);\n console.log(` Settings: ${profilePath(profileName)} (change them with \\`${NPX} listen${p} … --save\\`, then reinstall)`);\n console.log(` Log: ${logPath(profileName)}`);\n console.log(` Status: ${NPX} listen status${p}`);\n if (ops.platform === \"linux\") console.log(` Follow: journalctl ${ops.root ? \"\" : \"--user \"}-u ${unitName(profileName)} -f`);\n console.log();\n}\n\nexport function uninstallListenService(profileName: string, quiet = false, deps: ListenServiceDeps = {}): void {\n const ops = deps.ops ?? systemOps();\n assertSupported(ops);\n const name = validProfileName(profileName);\n const file = unitFile(ops, name);\n if (!existsSync(file)) {\n if (!quiet) console.log(`No listener service is installed for profile \"${name}\".`);\n return;\n }\n if (ops.platform === \"linux\") {\n try {\n systemctl(ops, \"disable\", \"--now\", unitName(name));\n } catch {\n // Already stopped.\n }\n rmSync(file);\n systemctl(ops, \"daemon-reload\");\n } else {\n try {\n ops.exec(\"launchctl\", [\"bootout\", `${launchdTarget(ops)}/${launchdLabel(name)}`], { quiet: true });\n } catch {\n // Not loaded.\n }\n rmSync(file);\n }\n // The last listener gone: remove the shared install too (only if it really is ours).\n if (!installedProfiles(ops).length) removeOwnInstall(dataDir());\n if (!quiet) console.log(`Removed the listener service for profile \"${name}\". The profile (and its key) was kept: ${profilePath(name)}`);\n}\n\nexport function listenServiceStatus(profileName: string, deps: ListenServiceDeps = {}): void {\n const ops = deps.ops ?? systemOps();\n assertSupported(ops);\n const name = validProfileName(profileName);\n if (!isServiceInstalled(name, ops)) {\n console.log(`No listener service is installed for profile \"${name}\". Install one with \\`${NPX} listen service install\\`.`);\n process.exitCode = 1;\n return;\n }\n try {\n if (ops.platform === \"linux\") systemctl(ops, \"status\", unitName(name), \"--no-pager\");\n else ops.exec(\"launchctl\", [\"print\", `${launchdTarget(ops)}/${launchdLabel(name)}`]);\n } catch {\n process.exitCode = 1;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { t as SettingsStore } from "./settings-DLUStIN7.js";
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ import { r as json } from "./db-D0gvshCc.js";
3
+ import { r as badRequest } from "./errors-FbMS3QBo.js";
4
+ //#region server/settings.ts
5
+ const USER_KEY = "settings";
6
+ var SettingsStore = class {
7
+ db;
8
+ bus;
9
+ config;
10
+ constructor(db, bus, config) {
11
+ this.db = db;
12
+ this.bus = bus;
13
+ this.config = config;
14
+ }
15
+ defaults() {
16
+ return {
17
+ publicUrl: defaultPublicUrl(this.config.host, this.config.port),
18
+ ingestMode: "poll",
19
+ pollIntervalSeconds: 30,
20
+ maxBodyChars: 2e4,
21
+ retentionDays: 30,
22
+ approvalTtlHours: 72,
23
+ webhookAllowlist: [
24
+ "localhost",
25
+ "127.0.0.0/8",
26
+ "::1"
27
+ ],
28
+ pairingEnabled: true
29
+ };
30
+ }
31
+ get() {
32
+ const row = this.db.get("SELECT value FROM settings WHERE key = ?", USER_KEY);
33
+ return {
34
+ ...this.defaults(),
35
+ ...json.parse(row?.value, {})
36
+ };
37
+ }
38
+ /** Validates, persists and broadcasts. Throws HttpError(400) on invalid input. */
39
+ update(patch) {
40
+ const next = validate({
41
+ ...this.get(),
42
+ ...stripUndefined(patch)
43
+ });
44
+ this.write(USER_KEY, next);
45
+ this.bus.emit("settings", next);
46
+ return next;
47
+ }
48
+ /** The OAuth redirect URI and MCP snippets are derived from this. Never has a trailing slash. */
49
+ publicUrl() {
50
+ return this.get().publicUrl;
51
+ }
52
+ getState(key) {
53
+ const row = this.db.get("SELECT value FROM settings WHERE key = ?", key);
54
+ return row ? json.parse(row.value, null) : null;
55
+ }
56
+ setState(key, value) {
57
+ this.write(key, value);
58
+ }
59
+ deleteState(key) {
60
+ this.db.run("DELETE FROM settings WHERE key = ?", key);
61
+ }
62
+ write(key, value) {
63
+ this.db.run("INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value", key, json.str(value));
64
+ }
65
+ };
66
+ const INGEST_MODES = ["pubsub", "poll"];
67
+ function validate(s) {
68
+ let url;
69
+ try {
70
+ url = new URL(s.publicUrl);
71
+ } catch {
72
+ throw badRequest("Public URL must be a full URL", "For example https://mail.example.com or http://localhost:8425");
73
+ }
74
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw badRequest("Public URL must use http or https");
75
+ if (url.search || url.hash) throw badRequest("Public URL must not contain a query or fragment");
76
+ const int = (v, name, min, max) => {
77
+ if (!Number.isInteger(v) || v < min || v > max) throw badRequest(`${name} must be a whole number from ${min} to ${max}`);
78
+ return v;
79
+ };
80
+ if (!INGEST_MODES.includes(s.ingestMode)) throw badRequest("Ingest mode must be pubsub or poll");
81
+ const allowlist = Array.isArray(s.webhookAllowlist) ? s.webhookAllowlist.map((h) => String(h).trim()).filter(Boolean) : null;
82
+ if (!allowlist || allowlist.length > 100 || allowlist.some((h) => h.length > 253 || /\s/.test(h))) throw badRequest("Webhook allowlist must be a list of hostnames, IPs or CIDRs");
83
+ return {
84
+ publicUrl: url.origin + url.pathname.replace(/\/+$/, ""),
85
+ ingestMode: s.ingestMode,
86
+ pollIntervalSeconds: int(s.pollIntervalSeconds, "Poll interval", 5, 3600),
87
+ maxBodyChars: int(s.maxBodyChars, "Max body characters", 0, 1e6),
88
+ retentionDays: int(s.retentionDays, "Retention", 1, 3650),
89
+ approvalTtlHours: int(s.approvalTtlHours, "Approval expiry", 1, 720),
90
+ webhookAllowlist: [...new Set(allowlist)],
91
+ pairingEnabled: s.pairingEnabled !== false
92
+ };
93
+ }
94
+ function stripUndefined(o) {
95
+ return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
96
+ }
97
+ /**
98
+ * localhost when bound to all interfaces or to 127.0.0.1/::1 (Google accepts http://localhost redirects);
99
+ * otherwise the specific address or name the operator chose with --host.
100
+ */
101
+ function defaultPublicUrl(host, port) {
102
+ const h = host.trim().toLowerCase();
103
+ if (!h || h === "0.0.0.0" || h === "::" || h === "localhost" || h === "::1" || h === "127.0.0.1") return `http://localhost:${port}`;
104
+ return `http://${h.includes(":") ? `[${h}]` : h}:${port}`;
105
+ }
106
+ //#endregion
107
+ export { defaultPublicUrl as n, SettingsStore as t };
108
+
109
+ //# sourceMappingURL=settings-DLUStIN7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-DLUStIN7.js","names":[],"sources":["../server/settings.ts"],"sourcesContent":["import type { GoogleAppType, IngestMode, PubsubState, Settings } from \"../shared/types.ts\";\nimport type { Bus } from \"./bus.ts\";\nimport type { Config } from \"./config.ts\";\nimport { type Db, json } from \"./db/index.ts\";\nimport { badRequest } from \"./util/errors.ts\";\n\n/**\n * Internal key/value state kept in the `settings` table next to the user-facing Settings.\n * Owners: google.* and pubsub → server/google, admin.* → server/auth.\n */\nexport interface InternalState {\n \"google.client\": {\n clientId: string;\n /** AES-GCM ciphertext (ctx.crypto). */\n clientSecretEnc: string | null;\n projectId: string;\n appType: GoogleAppType;\n };\n /** Pub/Sub resources and health. `mode` is not stored here: it is Settings.ingestMode. */\n pubsub: Omit<PubsubState, \"mode\"> & {\n /** Account whose OAuth token provisions and pulls the subscription (it needs IAM on the project). */\n principalAccountId: string | null;\n };\n \"admin.passwordHash\": string;\n}\n\nexport type InternalKey = keyof InternalState;\n\nconst USER_KEY = \"settings\";\n\nexport class SettingsStore {\n constructor(\n private readonly db: Db,\n private readonly bus: Bus,\n private readonly config: Config,\n ) {}\n\n defaults(): Settings {\n return {\n publicUrl: defaultPublicUrl(this.config.host, this.config.port),\n ingestMode: \"poll\",\n pollIntervalSeconds: 30,\n maxBodyChars: 20_000,\n retentionDays: 30,\n approvalTtlHours: 72,\n webhookAllowlist: [\"localhost\", \"127.0.0.0/8\", \"::1\"],\n pairingEnabled: true,\n };\n }\n\n get(): Settings {\n const row = this.db.get<{ value: string }>(\"SELECT value FROM settings WHERE key = ?\", USER_KEY);\n return { ...this.defaults(), ...json.parse<Partial<Settings>>(row?.value, {}) };\n }\n\n /** Validates, persists and broadcasts. Throws HttpError(400) on invalid input. */\n update(patch: Partial<Settings>): Settings {\n const next = validate({ ...this.get(), ...stripUndefined(patch) });\n this.write(USER_KEY, next);\n this.bus.emit(\"settings\", next);\n return next;\n }\n\n /** The OAuth redirect URI and MCP snippets are derived from this. Never has a trailing slash. */\n publicUrl(): string {\n return this.get().publicUrl;\n }\n\n getState<K extends InternalKey>(key: K): InternalState[K] | null {\n const row = this.db.get<{ value: string }>(\"SELECT value FROM settings WHERE key = ?\", key);\n return row ? json.parse<InternalState[K] | null>(row.value, null) : null;\n }\n\n setState<K extends InternalKey>(key: K, value: InternalState[K]): void {\n this.write(key, value);\n }\n\n deleteState(key: InternalKey): void {\n this.db.run(\"DELETE FROM settings WHERE key = ?\", key);\n }\n\n private write(key: string, value: unknown): void {\n this.db.run(\n \"INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value\",\n key,\n json.str(value),\n );\n }\n}\n\nconst INGEST_MODES: IngestMode[] = [\"pubsub\", \"poll\"];\n\nfunction validate(s: Settings): Settings {\n let url: URL;\n try {\n url = new URL(s.publicUrl);\n } catch {\n throw badRequest(\"Public URL must be a full URL\", \"For example https://mail.example.com or http://localhost:8425\");\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") throw badRequest(\"Public URL must use http or https\");\n if (url.search || url.hash) throw badRequest(\"Public URL must not contain a query or fragment\");\n const int = (v: number, name: string, min: number, max: number) => {\n if (!Number.isInteger(v) || v < min || v > max) throw badRequest(`${name} must be a whole number from ${min} to ${max}`);\n return v;\n };\n if (!INGEST_MODES.includes(s.ingestMode)) throw badRequest(\"Ingest mode must be pubsub or poll\");\n const allowlist = Array.isArray(s.webhookAllowlist) ? s.webhookAllowlist.map((h) => String(h).trim()).filter(Boolean) : null;\n if (!allowlist || allowlist.length > 100 || allowlist.some((h) => h.length > 253 || /\\s/.test(h))) {\n throw badRequest(\"Webhook allowlist must be a list of hostnames, IPs or CIDRs\");\n }\n return {\n publicUrl: url.origin + url.pathname.replace(/\\/+$/, \"\"),\n ingestMode: s.ingestMode,\n pollIntervalSeconds: int(s.pollIntervalSeconds, \"Poll interval\", 5, 3600),\n maxBodyChars: int(s.maxBodyChars, \"Max body characters\", 0, 1_000_000),\n retentionDays: int(s.retentionDays, \"Retention\", 1, 3650),\n approvalTtlHours: int(s.approvalTtlHours, \"Approval expiry\", 1, 24 * 30),\n webhookAllowlist: [...new Set(allowlist)],\n pairingEnabled: s.pairingEnabled !== false,\n };\n}\n\nfunction stripUndefined<T extends object>(o: T): Partial<T> {\n return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined)) as Partial<T>;\n}\n\n/**\n * localhost when bound to all interfaces or to 127.0.0.1/::1 (Google accepts http://localhost redirects);\n * otherwise the specific address or name the operator chose with --host.\n */\nexport function defaultPublicUrl(host: string, port: number): string {\n const h = host.trim().toLowerCase();\n const local = !h || h === \"0.0.0.0\" || h === \"::\" || h === \"localhost\" || h === \"::1\" || h === \"127.0.0.1\";\n if (local) return `http://localhost:${port}`;\n return `http://${h.includes(\":\") ? `[${h}]` : h}:${port}`;\n}\n"],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { randomBytes } from "node:crypto";
5
+ //#region server/util/ids.ts
6
+ const ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
7
+ /** Random base32 string. 20 chars ≈ 100 bits. */
8
+ function randomToken(length = 20) {
9
+ const bytes = randomBytes(length);
10
+ let out = "";
11
+ for (let i = 0; i < length; i++) out += ALPHABET[bytes[i] & 31];
12
+ return out;
13
+ }
14
+ const newId = (prefix) => `${prefix}_${randomToken(20)}`;
15
+ //#endregion
16
+ //#region server/auth/setup-token.ts
17
+ /**
18
+ * One-time token required to claim a fresh install over the network. It is part of the setup link
19
+ * printed on startup and is persisted (mode 0600) so the link survives restarts and
20
+ * `service install` can show it. Deleted once the instance is claimed.
21
+ */
22
+ const setupTokenPath = (dataDir) => join(dataDir, "setup-token");
23
+ function readSetupToken(dataDir) {
24
+ try {
25
+ return readFileSync(setupTokenPath(dataDir), "utf8").trim() || null;
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ function ensureSetupToken(dataDir) {
31
+ const existing = readSetupToken(dataDir);
32
+ if (existing) return existing;
33
+ const token = randomToken(24);
34
+ writeFileSync(setupTokenPath(dataDir), token + "\n", { mode: 384 });
35
+ return token;
36
+ }
37
+ /** Replaces any existing token (e.g. after `agentinbox reset-password`). */
38
+ function regenerateSetupToken(dataDir) {
39
+ deleteSetupToken(dataDir);
40
+ return ensureSetupToken(dataDir);
41
+ }
42
+ function deleteSetupToken(dataDir) {
43
+ rmSync(setupTokenPath(dataDir), { force: true });
44
+ }
45
+ //#endregion
46
+ export { setupTokenPath as a, regenerateSetupToken as i, ensureSetupToken as n, newId as o, readSetupToken as r, randomToken as s, deleteSetupToken as t };
47
+
48
+ //# sourceMappingURL=setup-token-BO89xnpb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-token-BO89xnpb.js","names":[],"sources":["../server/util/ids.ts","../server/auth/setup-token.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\n\nconst ALPHABET = \"0123456789abcdefghjkmnpqrstvwxyz\"; // Crockford base32, lowercase\n\n/** Random base32 string. 20 chars ≈ 100 bits. */\nexport function randomToken(length = 20): string {\n const bytes = randomBytes(length);\n let out = \"\";\n for (let i = 0; i < length; i++) out += ALPHABET[bytes[i]! & 31];\n return out;\n}\n\nexport type IdPrefix = \"acc\" | \"agt\" | \"grt\" | \"evt\" | \"whk\" | \"dlv\" | \"apr\" | \"par\";\n\nexport const newId = (prefix: IdPrefix): string => `${prefix}_${randomToken(20)}`;\n","import { readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { randomToken } from \"../util/ids.ts\";\n\n/**\n * One-time token required to claim a fresh install over the network. It is part of the setup link\n * printed on startup and is persisted (mode 0600) so the link survives restarts and\n * `service install` can show it. Deleted once the instance is claimed.\n */\nexport const setupTokenPath = (dataDir: string) => join(dataDir, \"setup-token\");\n\nexport function readSetupToken(dataDir: string): string | null {\n try {\n return readFileSync(setupTokenPath(dataDir), \"utf8\").trim() || null;\n } catch {\n return null;\n }\n}\n\nexport function ensureSetupToken(dataDir: string): string {\n const existing = readSetupToken(dataDir);\n if (existing) return existing;\n const token = randomToken(24);\n writeFileSync(setupTokenPath(dataDir), token + \"\\n\", { mode: 0o600 });\n return token;\n}\n\n/** Replaces any existing token (e.g. after `agentinbox reset-password`). */\nexport function regenerateSetupToken(dataDir: string): string {\n deleteSetupToken(dataDir);\n return ensureSetupToken(dataDir);\n}\n\nexport function deleteSetupToken(dataDir: string): void {\n rmSync(setupTokenPath(dataDir), { force: true });\n}\n"],"mappings":";;;;;AAEA,MAAM,WAAW;;AAGjB,SAAgB,YAAY,SAAS,IAAY;CAC/C,MAAM,QAAQ,YAAY,MAAM;CAChC,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK,OAAO,SAAS,MAAM,KAAM;CAC7D,OAAO;AACT;AAIA,MAAa,SAAS,WAA6B,GAAG,OAAO,GAAG,YAAY,EAAE;;;;;;;;ACL9E,MAAa,kBAAkB,YAAoB,KAAK,SAAS,aAAa;AAE9E,SAAgB,eAAe,SAAgC;CAC7D,IAAI;EACF,OAAO,aAAa,eAAe,OAAO,GAAG,MAAM,CAAC,CAAC,KAAK,KAAK;CACjE,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,iBAAiB,SAAyB;CACxD,MAAM,WAAW,eAAe,OAAO;CACvC,IAAI,UAAU,OAAO;CACrB,MAAM,QAAQ,YAAY,EAAE;CAC5B,cAAc,eAAe,OAAO,GAAG,QAAQ,MAAM,EAAE,MAAM,IAAM,CAAC;CACpE,OAAO;AACT;;AAGA,SAAgB,qBAAqB,SAAyB;CAC5D,iBAAiB,OAAO;CACxB,OAAO,iBAAiB,OAAO;AACjC;AAEA,SAAgB,iBAAiB,SAAuB;CACtD,OAAO,eAAe,OAAO,GAAG,EAAE,OAAO,KAAK,CAAC;AACjD"}