agentinbox-cli 0.0.0 → 0.1.0-nightly.1

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 (136) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +312 -0
  3. package/dist/audit-CQe-_dPQ.js +2 -0
  4. package/dist/audit-DETqygzG.js +65 -0
  5. package/dist/audit-DETqygzG.js.map +1 -0
  6. package/dist/auth-BniddcRD.js +447 -0
  7. package/dist/auth-BniddcRD.js.map +1 -0
  8. package/dist/auth-C2v7bEmQ.js +2 -0
  9. package/dist/bus-Cz0flS37.js +12 -0
  10. package/dist/bus-Cz0flS37.js.map +1 -0
  11. package/dist/bus-OKIGut72.js +2 -0
  12. package/dist/cli.js +141 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/config-CIvgMqLx.js +2 -0
  15. package/dist/config-v975gxv4.js +44 -0
  16. package/dist/config-v975gxv4.js.map +1 -0
  17. package/dist/crypto-DYZ4R7c2.js +74 -0
  18. package/dist/crypto-DYZ4R7c2.js.map +1 -0
  19. package/dist/db-CfoMO5zW.js +2 -0
  20. package/dist/db-DdagFmUY.js +247 -0
  21. package/dist/db-DdagFmUY.js.map +1 -0
  22. package/dist/errors-CJLUPllS.js +38 -0
  23. package/dist/errors-CJLUPllS.js.map +1 -0
  24. package/dist/instance-DZMV78rI.js +2 -0
  25. package/dist/instance-Dy2jeyZc.js +174 -0
  26. package/dist/instance-Dy2jeyZc.js.map +1 -0
  27. package/dist/server-CWqKeCGn.js +981 -0
  28. package/dist/server-CWqKeCGn.js.map +1 -0
  29. package/dist/service-63pdSmhO.js +332 -0
  30. package/dist/service-63pdSmhO.js.map +1 -0
  31. package/dist/settings-Dq2ETFsd.js +98 -0
  32. package/dist/settings-Dq2ETFsd.js.map +1 -0
  33. package/dist/settings-qtn5WTBM.js +2 -0
  34. package/dist/setup-token-Bkwk84l7.js +47 -0
  35. package/dist/setup-token-Bkwk84l7.js.map +1 -0
  36. package/dist/skill/SKILL.md +226 -0
  37. package/dist/skill-BWWtinsE.js +2 -0
  38. package/dist/skill-WKbeyNct.js +43 -0
  39. package/dist/skill-WKbeyNct.js.map +1 -0
  40. package/dist/urls-CNYhbeUQ.js +2 -0
  41. package/dist/urls-DmnonkJo.js +22 -0
  42. package/dist/urls-DmnonkJo.js.map +1 -0
  43. package/dist/web/assets/_agentId-il4y7OCG.js +1 -0
  44. package/dist/web/assets/_app-BIO8WIYB.js +1 -0
  45. package/dist/web/assets/_app-wlLL4MrD.js +63 -0
  46. package/dist/web/assets/_webhookId-CiwDrkY4.js +1 -0
  47. package/dist/web/assets/account-status-at9Bv3_T.js +1 -0
  48. package/dist/web/assets/accounts-B4rfPwCV.js +1 -0
  49. package/dist/web/assets/accounts-list-e--JWxHH.js +1 -0
  50. package/dist/web/assets/activity-zrckFhPk.js +1 -0
  51. package/dist/web/assets/agent-create-form-DaWPR6kx.js +1 -0
  52. package/dist/web/assets/agents-D4gPq4dt.js +1 -0
  53. package/dist/web/assets/approvals-DaoZ6wuf.js +2 -0
  54. package/dist/web/assets/arrow-left-DVJV4Zx7.js +1 -0
  55. package/dist/web/assets/arrow-right-4PFX6MSS.js +1 -0
  56. package/dist/web/assets/audit-bAdLe-px.js +1 -0
  57. package/dist/web/assets/audit-bits-DQHo74i6.js +1 -0
  58. package/dist/web/assets/bot-DVw15lBn.js +1 -0
  59. package/dist/web/assets/card-EQTpqntn.js +1 -0
  60. package/dist/web/assets/checkbox-DDZZurgK.js +1 -0
  61. package/dist/web/assets/chevron-down-892ylB4R.js +1 -0
  62. package/dist/web/assets/chevron-right-CbBKS6-T.js +1 -0
  63. package/dist/web/assets/chips-input-Btdc_f7b.js +1 -0
  64. package/dist/web/assets/circle-x-Bi4IGwky.js +1 -0
  65. package/dist/web/assets/common-h6596gEC.js +1 -0
  66. package/dist/web/assets/connect-host-Co31qIYr.js +1 -0
  67. package/dist/web/assets/data-table-DsKrM62X.js +1 -0
  68. package/dist/web/assets/dialog-CgZr27yw.js +1 -0
  69. package/dist/web/assets/dist-B0e3Ju3r.js +1 -0
  70. package/dist/web/assets/empty-CE_WZGEC.js +1 -0
  71. package/dist/web/assets/es2015-DPaldoxG.js +41 -0
  72. package/dist/web/assets/event-row-C0qRfWuV.js +1 -0
  73. package/dist/web/assets/events-Svyij7mp.js +1 -0
  74. package/dist/web/assets/eye-off-DjpaihHf.js +1 -0
  75. package/dist/web/assets/field-CcyMn3ey.js +1 -0
  76. package/dist/web/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  77. package/dist/web/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  78. package/dist/web/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  79. package/dist/web/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  80. package/dist/web/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  81. package/dist/web/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  82. package/dist/web/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  83. package/dist/web/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  84. package/dist/web/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  85. package/dist/web/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  86. package/dist/web/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  87. package/dist/web/assets/get-started-Cxfw97sJ.js +1 -0
  88. package/dist/web/assets/grants-BilXCDps.js +1 -0
  89. package/dist/web/assets/index-DgIgw66p.css +2 -0
  90. package/dist/web/assets/index-TbxC5ZgP.js +10 -0
  91. package/dist/web/assets/input-CMhmmHiH.js +1 -0
  92. package/dist/web/assets/input-group-DRs3TNaT.js +1 -0
  93. package/dist/web/assets/key-round-CVIZRCLM.js +1 -0
  94. package/dist/web/assets/labels-C_Pwium9.js +1 -0
  95. package/dist/web/assets/login-DUJia7lg.js +1 -0
  96. package/dist/web/assets/mail-CAVmuESo.js +1 -0
  97. package/dist/web/assets/mutations-D_GluJ0j.js +1 -0
  98. package/dist/web/assets/paperclip-QFjM4y6n.js +1 -0
  99. package/dist/web/assets/password-input-_m9Ycphv.js +1 -0
  100. package/dist/web/assets/pencil-Bf1JhsG0.js +1 -0
  101. package/dist/web/assets/play-DiPk49JH.js +1 -0
  102. package/dist/web/assets/plus-CETmK0SB.js +1 -0
  103. package/dist/web/assets/preload-helper-BFuRUfdt.js +1 -0
  104. package/dist/web/assets/progress-3ZOsbLSK.js +6 -0
  105. package/dist/web/assets/queries-a3M5_loa.js +2 -0
  106. package/dist/web/assets/refresh-cw-DOn9kD7g.js +1 -0
  107. package/dist/web/assets/search-gcYjitzV.js +1 -0
  108. package/dist/web/assets/select-_tULk-vs.js +1 -0
  109. package/dist/web/assets/send-BhQwhuJo.js +1 -0
  110. package/dist/web/assets/settings-Dl4wb4f0.js +1 -0
  111. package/dist/web/assets/settings-form-D21eKBh-.js +1 -0
  112. package/dist/web/assets/setup-DYnNhnfH.js +1 -0
  113. package/dist/web/assets/sheet--2lKU0jN.js +1 -0
  114. package/dist/web/assets/shield-alert-CbFgd06X.js +1 -0
  115. package/dist/web/assets/stamp-CH2OvjH2.js +1 -0
  116. package/dist/web/assets/stream-6OwrgVZw.js +1 -0
  117. package/dist/web/assets/tabs-CiAUEj_y.js +1 -0
  118. package/dist/web/assets/tag-CP72Kk6Y.js +1 -0
  119. package/dist/web/assets/test-connection-F2tMowbv.js +1 -0
  120. package/dist/web/assets/textarea-CCokBZIw.js +1 -0
  121. package/dist/web/assets/toggle-group-D1pC7sJr.js +1 -0
  122. package/dist/web/assets/trash-DxdMNg8B.js +1 -0
  123. package/dist/web/assets/use-sign-out-BuzlTb0Q.js +1 -0
  124. package/dist/web/assets/useInfiniteQuery-DE6WChlb.js +1 -0
  125. package/dist/web/assets/useMatch-BKUnm54_.js +1 -0
  126. package/dist/web/assets/useMutation-DGl4PaZ1.js +1 -0
  127. package/dist/web/assets/useQuery-DAhf1YDH.js +1 -0
  128. package/dist/web/assets/useSelector-D70eKNYs.js +1 -0
  129. package/dist/web/assets/webhook-BWT75M78.js +1 -0
  130. package/dist/web/assets/webhook-sheet-4U_6Rw8I.js +34 -0
  131. package/dist/web/assets/webhooks-C1JvTI88.js +1 -0
  132. package/dist/web/assets/with-selector-CkNyXYMW.js +1 -0
  133. package/dist/web/assets/x-BHjDLXNO.js +1 -0
  134. package/dist/web/favicon.svg +1 -0
  135. package/dist/web/index.html +31 -0
  136. package/package.json +94 -1
package/dist/cli.js ADDED
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join, resolve } from "node:path";
5
+ import { Command, InvalidArgumentError } from "commander";
6
+ import pc from "picocolors";
7
+ //#region server/cli.ts
8
+ const emitWarning = process.emitWarning.bind(process);
9
+ process.emitWarning = ((warning, ...rest) => {
10
+ const message = typeof warning === "string" ? warning : warning.message;
11
+ if (((typeof rest[0] === "string" ? rest[0] : rest[0]?.type) === "ExperimentalWarning" || warning.name === "ExperimentalWarning") && /SQLite/i.test(message)) return;
12
+ return emitWarning(warning, ...rest);
13
+ });
14
+ /** package.json sits one level above both dist/cli.js and server/cli.ts. */
15
+ function packageVersion() {
16
+ try {
17
+ return JSON.parse(readFileSync(join(import.meta.dirname, "..", "package.json"), "utf8")).version;
18
+ } catch {
19
+ return "0.0.0";
20
+ }
21
+ }
22
+ const version = packageVersion();
23
+ const validPort = (value) => /^\d+$/.test(value) && Number(value) <= 65535;
24
+ function parsePort(value) {
25
+ if (!validPort(value)) throw new InvalidArgumentError("Port must be a number from 0 to 65535.");
26
+ return Number(value);
27
+ }
28
+ /** --port, else a validated PORT env (a typo there would otherwise become NaN deep inside). */
29
+ function portOption(flag) {
30
+ if (flag !== void 0) return flag;
31
+ const env = process.env.PORT?.trim();
32
+ if (!env) return void 0;
33
+ if (!validPort(env)) throw new Error(`PORT="${process.env.PORT}" is not a valid port. Use a number from 0 to 65535.`);
34
+ return Number(env);
35
+ }
36
+ const withServerOptions = (cmd) => cmd.option("-p, --port <port>", "port to listen on (default 8425, env PORT)", parsePort).option("--host <host>", "bind address (default 0.0.0.0, env HOST)").option("-d, --data <dir>", "data directory (default ~/.agentinbox, env AGENTINBOX_DATA)");
37
+ const configInput = (o) => ({
38
+ port: portOption(o.port),
39
+ host: o.host,
40
+ dataDir: o.data
41
+ });
42
+ const program = new Command("agentinbox").description("Self-hosted Gmail broker for AI agents: MCP tools, webhooks, guardrails and a web console.").version(version, "-v, --version", "print the version").helpOption("-h, --help", "show help").addHelpText("after", "\nWithout installing: npx agentinbox-cli [command]. To install the command: npm i -g agentinbox-cli").showSuggestionAfterError();
43
+ withServerOptions(program.command("start", { isDefault: true }).description("run agentinbox in the foreground (default)")).option("--no-open", "don't open a browser on first run").option("--dev", "development: API only, the console runs on Vite (:5173)").action(async (o) => {
44
+ const { runForeground } = await import("./server-CWqKeCGn.js");
45
+ await runForeground({
46
+ ...configInput(o),
47
+ dev: !!o.dev,
48
+ open: o.open,
49
+ version
50
+ });
51
+ });
52
+ const service = program.command("service").description("run agentinbox as a background service (systemd / launchd)");
53
+ withServerOptions(service.command("install").description("install and start the service; re-run to update")).action(async (o) => {
54
+ const { loadConfig } = await import("./config-CIvgMqLx.js");
55
+ const { installService } = await import("./service-63pdSmhO.js");
56
+ await installService(loadConfig(configInput(o), version));
57
+ });
58
+ service.command("uninstall").description("stop and remove the service (keeps your data)").option("-d, --data <dir>", "data directory (default ~/.agentinbox, env AGENTINBOX_DATA)").action(async (o) => {
59
+ const { loadConfig } = await import("./config-CIvgMqLx.js");
60
+ const { uninstallService } = await import("./service-63pdSmhO.js");
61
+ uninstallService(loadConfig(configInput(o), version));
62
+ });
63
+ service.command("status").description("show service status").action(async () => {
64
+ const { serviceStatus } = await import("./service-63pdSmhO.js");
65
+ serviceStatus();
66
+ });
67
+ const skill = program.command("skill").description("the agentinbox agent skill (SKILL.md)");
68
+ skill.command("print").description("print SKILL.md to stdout").option("--url <url>", "agentinbox server URL to fill in (env AGENTINBOX_URL)").action(async (o) => {
69
+ const { renderSkill } = await import("./skill-BWWtinsE.js");
70
+ process.stdout.write(renderSkill(serverUrl(o.url)));
71
+ });
72
+ skill.command("install").description("install SKILL.md for an agent (no running server needed)").option("-t, --target <target>", "claude, codex, agents, or a skills directory", "claude").option("--url <url>", "agentinbox server URL agents connect to (env AGENTINBOX_URL)").action(async (o) => {
73
+ const { renderSkill, skillPath, writeSkill } = await import("./skill-BWWtinsE.js");
74
+ const url = serverUrl(o.url);
75
+ const path = skillPath(o.target);
76
+ writeSkill(path, renderSkill(url));
77
+ console.log(`Installed the agentinbox skill to ${pc.cyan(path)} (server ${url}).`);
78
+ if (!o.url && !process.env.AGENTINBOX_URL) console.log(pc.dim("Agents on other machines need the server's address: re-run with --url https://your-server."));
79
+ });
80
+ withServerOptions(program.command("reset-password").description("remove the admin password and print a new setup link")).action(async (o) => {
81
+ const dataDir = resolve(o.data ?? process.env.AGENTINBOX_DATA ?? join(homedir(), ".agentinbox"));
82
+ const dbPath = join(dataDir, "agentinbox.db");
83
+ if (!existsSync(dbPath)) throw new Error(`No agentinbox data in ${dataDir}. If agentinbox runs as a system service, run this with sudo; otherwise pass the data directory it uses with --data.`);
84
+ const { loadConfig } = await import("./config-CIvgMqLx.js");
85
+ const { Db } = await import("./db-CfoMO5zW.js");
86
+ const { createBus } = await import("./bus-OKIGut72.js");
87
+ const { SettingsStore } = await import("./settings-qtn5WTBM.js");
88
+ const { AuditLog } = await import("./audit-CQe-_dPQ.js");
89
+ const { currentSetupToken, isClaimed, resetAdminPassword } = await import("./auth-C2v7bEmQ.js");
90
+ const { runningInstance } = await import("./instance-DZMV78rI.js");
91
+ const { consoleUrls } = await import("./urls-CNYhbeUQ.js");
92
+ const running = runningInstance(dataDir);
93
+ const config = loadConfig({
94
+ dataDir,
95
+ port: portOption(o.port) ?? running?.port,
96
+ host: o.host ?? (process.env.HOST || running?.host)
97
+ }, version);
98
+ const db = new Db(dbPath);
99
+ try {
100
+ const bus = createBus();
101
+ const core = {
102
+ config,
103
+ db,
104
+ settings: new SettingsStore(db, bus, config),
105
+ audit: new AuditLog(db, bus)
106
+ };
107
+ const claimed = isClaimed(core);
108
+ const token = claimed ? resetAdminPassword(core) : currentSetupToken(core);
109
+ console.log(claimed ? "\n The admin password was removed and all console sessions were signed out.\n Open this link to set a new password:\n" : "\n This instance hasn't been claimed yet, so there is no password to reset.\n Open this link to claim it:\n");
110
+ const urls = consoleUrls(config.host, config.port);
111
+ const publicUrl = core.settings.publicUrl();
112
+ if (!urls.includes(publicUrl)) urls.push(publicUrl);
113
+ for (const url of urls) console.log(` ${pc.cyan(`${url}/setup?token=${token}`)}`);
114
+ console.log(pc.dim(`\n ${running ? "The running agentinbox picks this up immediately; no restart needed." : "Start agentinbox to use it."}\n`));
115
+ } finally {
116
+ db.close();
117
+ }
118
+ });
119
+ /** The URL written into SKILL.md: --url, AGENTINBOX_URL, or this machine's default. */
120
+ function serverUrl(flag) {
121
+ const raw = flag ?? process.env.AGENTINBOX_URL ?? `http://localhost:${process.env.PORT || 8425}`;
122
+ let url;
123
+ try {
124
+ url = new URL(raw);
125
+ } catch {
126
+ throw new Error(`Invalid --url "${raw}". Use a full URL such as https://mail.example.com.`);
127
+ }
128
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("--url must start with http:// or https://");
129
+ return (url.origin + url.pathname).replace(/\/+$/, "");
130
+ }
131
+ try {
132
+ await program.parseAsync(process.argv);
133
+ } catch (err) {
134
+ console.error(pc.red(err instanceof Error ? err.message : String(err)));
135
+ if (process.env.AGENTINBOX_DEBUG && err instanceof Error) console.error(err.stack);
136
+ process.exit(1);
137
+ }
138
+ //#endregion
139
+ export {};
140
+
141
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../server/cli.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { Command, InvalidArgumentError } from \"commander\";\nimport pc from \"picocolors\";\n\n// node:sqlite is stable enough for us but still warns on Node 22. This runs before any server\n// module loads: everything that touches node:sqlite is imported dynamically below.\nconst emitWarning = process.emitWarning.bind(process);\nprocess.emitWarning = ((warning: string | Error, ...rest: unknown[]) => {\n const message = typeof warning === \"string\" ? warning : warning.message;\n const type = typeof rest[0] === \"string\" ? rest[0] : (rest[0] as { type?: string } | undefined)?.type;\n if ((type === \"ExperimentalWarning\" || (warning as Error).name === \"ExperimentalWarning\") && /SQLite/i.test(message)) return;\n return (emitWarning as (...args: unknown[]) => void)(warning, ...rest);\n}) as typeof process.emitWarning;\n\n/** package.json sits one level above both dist/cli.js and server/cli.ts. */\nfunction packageVersion(): string {\n try {\n return (JSON.parse(readFileSync(join(import.meta.dirname, \"..\", \"package.json\"), \"utf8\")) as { version: string }).version;\n } catch {\n return \"0.0.0\";\n }\n}\n\nconst version = packageVersion();\n\ninterface ServerFlags {\n port?: number;\n host?: string;\n data?: string;\n}\n\nconst validPort = (value: string) => /^\\d+$/.test(value) && Number(value) <= 65535;\n\nfunction parsePort(value: string): number {\n if (!validPort(value)) throw new InvalidArgumentError(\"Port must be a number from 0 to 65535.\");\n return Number(value);\n}\n\n/** --port, else a validated PORT env (a typo there would otherwise become NaN deep inside). */\nfunction portOption(flag: number | undefined): number | undefined {\n if (flag !== undefined) return flag;\n const env = process.env.PORT?.trim();\n if (!env) return undefined;\n if (!validPort(env)) throw new Error(`PORT=\"${process.env.PORT}\" is not a valid port. Use a number from 0 to 65535.`);\n return Number(env);\n}\n\nconst withServerOptions = (cmd: Command) =>\n cmd\n .option(\"-p, --port <port>\", \"port to listen on (default 8425, env PORT)\", parsePort)\n .option(\"--host <host>\", \"bind address (default 0.0.0.0, env HOST)\")\n .option(\"-d, --data <dir>\", \"data directory (default ~/.agentinbox, env AGENTINBOX_DATA)\");\n\nconst configInput = (o: ServerFlags) => ({ port: portOption(o.port), host: o.host, dataDir: o.data });\n\nconst program = new Command(\"agentinbox\")\n .description(\"Self-hosted Gmail broker for AI agents: MCP tools, webhooks, guardrails and a web console.\")\n .version(version, \"-v, --version\", \"print the version\")\n .helpOption(\"-h, --help\", \"show help\")\n .addHelpText(\"after\", \"\\nWithout installing: npx agentinbox-cli [command]. To install the command: npm i -g agentinbox-cli\")\n .showSuggestionAfterError();\n\nwithServerOptions(program.command(\"start\", { isDefault: true }).description(\"run agentinbox in the foreground (default)\"))\n .option(\"--no-open\", \"don't open a browser on first run\")\n .option(\"--dev\", \"development: API only, the console runs on Vite (:5173)\")\n .action(async (o: ServerFlags & { open: boolean; dev?: boolean }) => {\n const { runForeground } = await import(\"./server.ts\");\n await runForeground({ ...configInput(o), dev: !!o.dev, open: o.open, version });\n });\n\nconst service = program.command(\"service\").description(\"run agentinbox as a background service (systemd / launchd)\");\n\nwithServerOptions(service.command(\"install\").description(\"install and start the service; re-run to update\")).action(async (o: ServerFlags) => {\n const { loadConfig } = await import(\"./config.ts\");\n const { installService } = await import(\"./service.ts\");\n await installService(loadConfig(configInput(o), version));\n});\n\nservice\n .command(\"uninstall\")\n .description(\"stop and remove the service (keeps your data)\")\n .option(\"-d, --data <dir>\", \"data directory (default ~/.agentinbox, env AGENTINBOX_DATA)\")\n .action(async (o: ServerFlags) => {\n const { loadConfig } = await import(\"./config.ts\");\n const { uninstallService } = await import(\"./service.ts\");\n uninstallService(loadConfig(configInput(o), version));\n });\n\nservice\n .command(\"status\")\n .description(\"show service status\")\n .action(async () => {\n const { serviceStatus } = await import(\"./service.ts\");\n serviceStatus();\n });\n\nconst skill = program.command(\"skill\").description(\"the agentinbox agent skill (SKILL.md)\");\n\nskill\n .command(\"print\")\n .description(\"print SKILL.md to stdout\")\n .option(\"--url <url>\", \"agentinbox server URL to fill in (env AGENTINBOX_URL)\")\n .action(async (o: { url?: string }) => {\n const { renderSkill } = await import(\"./skill/index.ts\");\n process.stdout.write(renderSkill(serverUrl(o.url)));\n });\n\nskill\n .command(\"install\")\n .description(\"install SKILL.md for an agent (no running server needed)\")\n .option(\"-t, --target <target>\", \"claude, codex, agents, or a skills directory\", \"claude\")\n .option(\"--url <url>\", \"agentinbox server URL agents connect to (env AGENTINBOX_URL)\")\n .action(async (o: { target: string; url?: string }) => {\n const { renderSkill, skillPath, writeSkill } = await import(\"./skill/index.ts\");\n const url = serverUrl(o.url);\n const path = skillPath(o.target);\n writeSkill(path, renderSkill(url));\n console.log(`Installed the agentinbox skill to ${pc.cyan(path)} (server ${url}).`);\n if (!o.url && !process.env.AGENTINBOX_URL) {\n console.log(pc.dim(\"Agents on other machines need the server's address: re-run with --url https://your-server.\"));\n }\n });\n\nwithServerOptions(program.command(\"reset-password\").description(\"remove the admin password and print a new setup link\")).action(\n async (o: ServerFlags) => {\n // Never create a data directory or master key here: a wrong --data (or a service running as\n // another user) must not silently produce a fresh instance.\n const dataDir = resolve(o.data ?? process.env.AGENTINBOX_DATA ?? join(homedir(), \".agentinbox\"));\n const dbPath = join(dataDir, \"agentinbox.db\");\n if (!existsSync(dbPath)) {\n throw new Error(\n `No agentinbox data in ${dataDir}. If agentinbox runs as a system service, run this with sudo; ` +\n \"otherwise pass the data directory it uses with --data.\",\n );\n }\n const { loadConfig } = await import(\"./config.ts\");\n const { Db } = await import(\"./db/index.ts\");\n const { createBus } = await import(\"./bus.ts\");\n const { SettingsStore } = await import(\"./settings.ts\");\n const { AuditLog } = await import(\"./audit.ts\");\n const { currentSetupToken, isClaimed, resetAdminPassword } = await import(\"./auth/index.ts\");\n const { runningInstance } = await import(\"./instance.ts\");\n const { consoleUrls } = await import(\"./urls.ts\");\n\n const running = runningInstance(dataDir);\n const config = loadConfig(\n { dataDir, port: portOption(o.port) ?? running?.port, host: o.host ?? (process.env.HOST || running?.host) },\n version,\n );\n const db = new Db(dbPath);\n try {\n const bus = createBus();\n const core = { config, db, settings: new SettingsStore(db, bus, config), audit: new AuditLog(db, bus) };\n const claimed = isClaimed(core);\n const token = claimed ? resetAdminPassword(core) : currentSetupToken(core)!;\n console.log(\n claimed\n ? \"\\n The admin password was removed and all console sessions were signed out.\\n Open this link to set a new password:\\n\"\n : \"\\n This instance hasn't been claimed yet, so there is no password to reset.\\n Open this link to claim it:\\n\",\n );\n const urls = consoleUrls(config.host, config.port);\n const publicUrl = core.settings.publicUrl();\n if (!urls.includes(publicUrl)) urls.push(publicUrl);\n for (const url of urls) console.log(` ${pc.cyan(`${url}/setup?token=${token}`)}`);\n console.log(pc.dim(`\\n ${running ? \"The running agentinbox picks this up immediately; no restart needed.\" : \"Start agentinbox to use it.\"}\\n`));\n } finally {\n db.close();\n }\n },\n);\n\n/** The URL written into SKILL.md: --url, AGENTINBOX_URL, or this machine's default. */\nfunction serverUrl(flag: string | undefined): string {\n const raw = flag ?? process.env.AGENTINBOX_URL ?? `http://localhost:${process.env.PORT || 8425}`;\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new Error(`Invalid --url \"${raw}\". Use a full URL such as https://mail.example.com.`);\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") throw new Error(\"--url must start with http:// or https://\");\n return (url.origin + url.pathname).replace(/\\/+$/, \"\");\n}\n\ntry {\n await program.parseAsync(process.argv);\n} catch (err) {\n console.error(pc.red(err instanceof Error ? err.message : String(err)));\n if (process.env.AGENTINBOX_DEBUG && err instanceof Error) console.error(err.stack);\n process.exit(1);\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { n as loadConfig } from "./config-v975gxv4.js";
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ import { chmodSync, mkdirSync, statSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join, resolve } from "node:path";
5
+ //#region server/config.ts
6
+ const DEFAULT_PORT = 8425;
7
+ function loadConfig(input = {}, version = "0.0.0") {
8
+ const dataDir = resolve(input.dataDir ?? process.env.AGENTINBOX_DATA ?? join(homedir(), ".agentinbox"));
9
+ mkdirSync(dataDir, {
10
+ recursive: true,
11
+ mode: 448
12
+ });
13
+ restrictDir(dataDir);
14
+ return {
15
+ dataDir,
16
+ dbPath: join(dataDir, "agentinbox.db"),
17
+ masterKeyPath: join(dataDir, "master.key"),
18
+ host: input.host ?? process.env.HOST ?? "0.0.0.0",
19
+ port: input.port ?? (process.env.PORT ? Number(process.env.PORT) : 8425),
20
+ dev: input.dev ?? false,
21
+ trustProxy: process.env.TRUST_PROXY === "1",
22
+ service: process.env.AGENTINBOX_SERVICE === "1",
23
+ version,
24
+ google: {
25
+ oauthBase: process.env.AGENTINBOX_GOOGLE_OAUTH_BASE ?? "https://oauth2.googleapis.com",
26
+ authBase: process.env.AGENTINBOX_GOOGLE_AUTH_BASE ?? "https://accounts.google.com",
27
+ gmailBase: process.env.AGENTINBOX_GOOGLE_GMAIL_BASE ?? "https://gmail.googleapis.com",
28
+ pubsubBase: process.env.AGENTINBOX_GOOGLE_PUBSUB_BASE ?? "https://pubsub.googleapis.com"
29
+ }
30
+ };
31
+ }
32
+ /** An existing data dir may be group/world accessible (umask, manual mkdir): tighten it to owner-only. */
33
+ function restrictDir(dir) {
34
+ try {
35
+ if (process.platform !== "win32" && statSync(dir).mode & 63) {
36
+ chmodSync(dir, 448);
37
+ process.stderr.write(`agentinbox: restricted ${dir} to owner-only access (it was readable by other users).\n`);
38
+ }
39
+ } catch {}
40
+ }
41
+ //#endregion
42
+ export { loadConfig as n, DEFAULT_PORT as t };
43
+
44
+ //# sourceMappingURL=config-v975gxv4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-v975gxv4.js","names":[],"sources":["../server/config.ts"],"sourcesContent":["import { chmodSync, mkdirSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\n\nexport interface Config {\n dataDir: string;\n dbPath: string;\n masterKeyPath: string;\n host: string;\n port: number;\n /** Dev mode: console served by Vite on :5173, server only serves /api, /mcp, /oauth. */\n dev: boolean;\n /** Honor X-Forwarded-For / X-Forwarded-Proto (behind a reverse proxy). */\n trustProxy: boolean;\n /** Running under `agentinbox service install` (set via AGENTINBOX_SERVICE=1 in the unit/plist). */\n service: boolean;\n version: string;\n /**\n * Base URLs for Google APIs. Overridable so tests can point at test/fake-google.ts.\n * Defaults: https://oauth2.googleapis.com, https://accounts.google.com, https://gmail.googleapis.com, https://pubsub.googleapis.com\n */\n google: { oauthBase: string; authBase: string; gmailBase: string; pubsubBase: string };\n}\n\nexport interface ConfigInput {\n dataDir?: string;\n host?: string;\n port?: number;\n dev?: boolean;\n}\n\nexport const DEFAULT_PORT = 8425;\n\nexport function loadConfig(input: ConfigInput = {}, version = \"0.0.0\"): Config {\n const dataDir = resolve(input.dataDir ?? process.env.AGENTINBOX_DATA ?? join(homedir(), \".agentinbox\"));\n mkdirSync(dataDir, { recursive: true, mode: 0o700 });\n restrictDir(dataDir);\n return {\n dataDir,\n dbPath: join(dataDir, \"agentinbox.db\"),\n masterKeyPath: join(dataDir, \"master.key\"),\n host: input.host ?? process.env.HOST ?? \"0.0.0.0\",\n port: input.port ?? (process.env.PORT ? Number(process.env.PORT) : DEFAULT_PORT),\n dev: input.dev ?? false,\n trustProxy: process.env.TRUST_PROXY === \"1\",\n service: process.env.AGENTINBOX_SERVICE === \"1\",\n version,\n google: {\n oauthBase: process.env.AGENTINBOX_GOOGLE_OAUTH_BASE ?? \"https://oauth2.googleapis.com\",\n authBase: process.env.AGENTINBOX_GOOGLE_AUTH_BASE ?? \"https://accounts.google.com\",\n gmailBase: process.env.AGENTINBOX_GOOGLE_GMAIL_BASE ?? \"https://gmail.googleapis.com\",\n pubsubBase: process.env.AGENTINBOX_GOOGLE_PUBSUB_BASE ?? \"https://pubsub.googleapis.com\",\n },\n };\n}\n\n/** An existing data dir may be group/world accessible (umask, manual mkdir): tighten it to owner-only. */\nfunction restrictDir(dir: string): void {\n try {\n if (process.platform !== \"win32\" && statSync(dir).mode & 0o077) {\n chmodSync(dir, 0o700);\n process.stderr.write(`agentinbox: restricted ${dir} to owner-only access (it was readable by other users).\\n`);\n }\n } catch {\n // Not ours to change; the files inside are still created owner-only.\n }\n}\n"],"mappings":";;;;;AA+BA,MAAa,eAAe;AAE5B,SAAgB,WAAW,QAAqB,CAAC,GAAG,UAAU,SAAiB;CAC7E,MAAM,UAAU,QAAQ,MAAM,WAAW,QAAQ,IAAI,mBAAmB,KAAK,QAAQ,GAAG,aAAa,CAAC;CACtG,UAAU,SAAS;EAAE,WAAW;EAAM,MAAM;CAAM,CAAC;CACnD,YAAY,OAAO;CACnB,OAAO;EACL;EACA,QAAQ,KAAK,SAAS,eAAe;EACrC,eAAe,KAAK,SAAS,YAAY;EACzC,MAAM,MAAM,QAAQ,QAAQ,IAAI,QAAQ;EACxC,MAAM,MAAM,SAAS,QAAQ,IAAI,OAAO,OAAO,QAAQ,IAAI,IAAI,IAAA;EAC/D,KAAK,MAAM,OAAO;EAClB,YAAY,QAAQ,IAAI,gBAAgB;EACxC,SAAS,QAAQ,IAAI,uBAAuB;EAC5C;EACA,QAAQ;GACN,WAAW,QAAQ,IAAI,gCAAgC;GACvD,UAAU,QAAQ,IAAI,+BAA+B;GACrD,WAAW,QAAQ,IAAI,gCAAgC;GACvD,YAAY,QAAQ,IAAI,iCAAiC;EAC3D;CACF;AACF;;AAGA,SAAS,YAAY,KAAmB;CACtC,IAAI;EACF,IAAI,QAAQ,aAAa,WAAW,SAAS,GAAG,CAAC,CAAC,OAAO,IAAO;GAC9D,UAAU,KAAK,GAAK;GACpB,QAAQ,OAAO,MAAM,0BAA0B,IAAI,0DAA0D;EAC/G;CACF,QAAQ,CAER;AACF"}
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { createCipheriv, createDecipheriv, createHash, randomBytes, scrypt, timingSafeEqual } from "node:crypto";
4
+ //#region server/crypto.ts
5
+ /**
6
+ * Secrets at rest (refresh tokens, webhook secrets, Google client secret) are AES-256-GCM encrypted.
7
+ * Key source: AGENTINBOX_MASTER_KEY (base64, 32 bytes) or `<dataDir>/master.key` (generated, mode 0600).
8
+ * Ciphertext format: "v1:" + base64(iv[12] | tag[16] | ciphertext).
9
+ */
10
+ var Crypto = class Crypto {
11
+ key;
12
+ constructor(key) {
13
+ if (key.length !== 32) throw new Error("master key must be 32 bytes");
14
+ this.key = key;
15
+ }
16
+ static load(keyFile) {
17
+ const fromEnv = process.env.AGENTINBOX_MASTER_KEY;
18
+ if (fromEnv) return new Crypto(Buffer.from(fromEnv, "base64"));
19
+ if (!existsSync(keyFile)) writeFileSync(keyFile, randomBytes(32).toString("base64") + "\n", { mode: 384 });
20
+ chmodSync(keyFile, 384);
21
+ return new Crypto(Buffer.from(readFileSync(keyFile, "utf8").trim(), "base64"));
22
+ }
23
+ encrypt(plain) {
24
+ const iv = randomBytes(12);
25
+ const cipher = createCipheriv("aes-256-gcm", this.key, iv);
26
+ const ct = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
27
+ return "v1:" + Buffer.concat([
28
+ iv,
29
+ cipher.getAuthTag(),
30
+ ct
31
+ ]).toString("base64");
32
+ }
33
+ decrypt(enc) {
34
+ if (!enc.startsWith("v1:")) throw new Error("unknown ciphertext version");
35
+ const buf = Buffer.from(enc.slice(3), "base64");
36
+ const decipher = createDecipheriv("aes-256-gcm", this.key, buf.subarray(0, 12));
37
+ decipher.setAuthTag(buf.subarray(12, 28));
38
+ return Buffer.concat([decipher.update(buf.subarray(28)), decipher.final()]).toString("utf8");
39
+ }
40
+ };
41
+ /** Fast hash for high-entropy tokens (API keys, session tokens). */
42
+ const sha256 = (s) => createHash("sha256").update(s).digest("hex");
43
+ const scryptAsync = (password, salt, length, opts) => new Promise((resolve, reject) => scrypt(password, salt, length, opts, (err, key) => err ? reject(err) : resolve(key)));
44
+ /** Like hashPassword, but runs scrypt on the thread pool instead of blocking the event loop. */
45
+ async function hashPasswordAsync(password) {
46
+ const salt = randomBytes(16);
47
+ const hash = await scryptAsync(password, salt, 32, {
48
+ N: 16384,
49
+ r: 8,
50
+ p: 1
51
+ });
52
+ return `scrypt$16384$8$1$${salt.toString("base64")}$${hash.toString("base64")}`;
53
+ }
54
+ /** Like verifyPassword, but runs scrypt on the thread pool instead of blocking the event loop. */
55
+ async function verifyPasswordAsync(password, stored) {
56
+ const [alg, n, r, p, salt, hash] = stored.split("$");
57
+ if (alg !== "scrypt" || !salt || !hash) return false;
58
+ const expected = Buffer.from(hash, "base64");
59
+ const actual = await scryptAsync(password, Buffer.from(salt, "base64"), expected.length, {
60
+ N: Number(n),
61
+ r: Number(r),
62
+ p: Number(p)
63
+ });
64
+ return timingSafeEqual(actual, expected);
65
+ }
66
+ function safeEqual(a, b) {
67
+ const ab = Buffer.from(a);
68
+ const bb = Buffer.from(b);
69
+ return ab.length === bb.length && timingSafeEqual(ab, bb);
70
+ }
71
+ //#endregion
72
+ export { verifyPasswordAsync as a, sha256 as i, hashPasswordAsync as n, safeEqual as r, Crypto as t };
73
+
74
+ //# sourceMappingURL=crypto-DYZ4R7c2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto-DYZ4R7c2.js","names":[],"sources":["../server/crypto.ts"],"sourcesContent":["import { createCipheriv, createDecipheriv, createHash, randomBytes, type ScryptOptions, scrypt, scryptSync, timingSafeEqual } from \"node:crypto\";\nimport { chmodSync, existsSync, readFileSync, writeFileSync } from \"node:fs\";\n\n/**\n * Secrets at rest (refresh tokens, webhook secrets, Google client secret) are AES-256-GCM encrypted.\n * Key source: AGENTINBOX_MASTER_KEY (base64, 32 bytes) or `<dataDir>/master.key` (generated, mode 0600).\n * Ciphertext format: \"v1:\" + base64(iv[12] | tag[16] | ciphertext).\n */\nexport class Crypto {\n private readonly key: Buffer;\n\n constructor(key: Buffer) {\n if (key.length !== 32) throw new Error(\"master key must be 32 bytes\");\n this.key = key;\n }\n\n static load(keyFile: string): Crypto {\n const fromEnv = process.env.AGENTINBOX_MASTER_KEY;\n if (fromEnv) return new Crypto(Buffer.from(fromEnv, \"base64\"));\n if (!existsSync(keyFile)) {\n writeFileSync(keyFile, randomBytes(32).toString(\"base64\") + \"\\n\", { mode: 0o600 });\n }\n chmodSync(keyFile, 0o600);\n return new Crypto(Buffer.from(readFileSync(keyFile, \"utf8\").trim(), \"base64\"));\n }\n\n encrypt(plain: string): string {\n const iv = randomBytes(12);\n const cipher = createCipheriv(\"aes-256-gcm\", this.key, iv);\n const ct = Buffer.concat([cipher.update(plain, \"utf8\"), cipher.final()]);\n return \"v1:\" + Buffer.concat([iv, cipher.getAuthTag(), ct]).toString(\"base64\");\n }\n\n decrypt(enc: string): string {\n if (!enc.startsWith(\"v1:\")) throw new Error(\"unknown ciphertext version\");\n const buf = Buffer.from(enc.slice(3), \"base64\");\n const decipher = createDecipheriv(\"aes-256-gcm\", this.key, buf.subarray(0, 12));\n decipher.setAuthTag(buf.subarray(12, 28));\n return Buffer.concat([decipher.update(buf.subarray(28)), decipher.final()]).toString(\"utf8\");\n }\n}\n\n/** Fast hash for high-entropy tokens (API keys, session tokens). */\nexport const sha256 = (s: string): string => createHash(\"sha256\").update(s).digest(\"hex\");\n\n/** Password hashing for the admin password. Format: \"scrypt$N$r$p$salt$hash\". */\nexport function hashPassword(password: string): string {\n const salt = randomBytes(16);\n const hash = scryptSync(password, salt, 32, { N: 16384, r: 8, p: 1 });\n return `scrypt$16384$8$1$${salt.toString(\"base64\")}$${hash.toString(\"base64\")}`;\n}\n\nexport function verifyPassword(password: string, stored: string): boolean {\n const [alg, n, r, p, salt, hash] = stored.split(\"$\");\n if (alg !== \"scrypt\" || !salt || !hash) return false;\n const expected = Buffer.from(hash, \"base64\");\n const actual = scryptSync(password, Buffer.from(salt, \"base64\"), expected.length, {\n N: Number(n),\n r: Number(r),\n p: Number(p),\n });\n return timingSafeEqual(actual, expected);\n}\n\nconst scryptAsync = (password: string, salt: Buffer, length: number, opts: ScryptOptions) =>\n new Promise<Buffer>((resolve, reject) => scrypt(password, salt, length, opts, (err, key) => (err ? reject(err) : resolve(key))));\n\n/** Like hashPassword, but runs scrypt on the thread pool instead of blocking the event loop. */\nexport async function hashPasswordAsync(password: string): Promise<string> {\n const salt = randomBytes(16);\n const hash = await scryptAsync(password, salt, 32, { N: 16384, r: 8, p: 1 });\n return `scrypt$16384$8$1$${salt.toString(\"base64\")}$${hash.toString(\"base64\")}`;\n}\n\n/** Like verifyPassword, but runs scrypt on the thread pool instead of blocking the event loop. */\nexport async function verifyPasswordAsync(password: string, stored: string): Promise<boolean> {\n const [alg, n, r, p, salt, hash] = stored.split(\"$\");\n if (alg !== \"scrypt\" || !salt || !hash) return false;\n const expected = Buffer.from(hash, \"base64\");\n const actual = await scryptAsync(password, Buffer.from(salt, \"base64\"), expected.length, { N: Number(n), r: Number(r), p: Number(p) });\n return timingSafeEqual(actual, expected);\n}\n\nexport function safeEqual(a: string, b: string): boolean {\n const ab = Buffer.from(a);\n const bb = Buffer.from(b);\n return ab.length === bb.length && timingSafeEqual(ab, bb);\n}\n"],"mappings":";;;;;;;;;AAQA,IAAa,SAAb,MAAa,OAAO;CAClB;CAEA,YAAY,KAAa;EACvB,IAAI,IAAI,WAAW,IAAI,MAAM,IAAI,MAAM,6BAA6B;EACpE,KAAK,MAAM;CACb;CAEA,OAAO,KAAK,SAAyB;EACnC,MAAM,UAAU,QAAQ,IAAI;EAC5B,IAAI,SAAS,OAAO,IAAI,OAAO,OAAO,KAAK,SAAS,QAAQ,CAAC;EAC7D,IAAI,CAAC,WAAW,OAAO,GACrB,cAAc,SAAS,YAAY,EAAE,CAAC,CAAC,SAAS,QAAQ,IAAI,MAAM,EAAE,MAAM,IAAM,CAAC;EAEnF,UAAU,SAAS,GAAK;EACxB,OAAO,IAAI,OAAO,OAAO,KAAK,aAAa,SAAS,MAAM,CAAC,CAAC,KAAK,GAAG,QAAQ,CAAC;CAC/E;CAEA,QAAQ,OAAuB;EAC7B,MAAM,KAAK,YAAY,EAAE;EACzB,MAAM,SAAS,eAAe,eAAe,KAAK,KAAK,EAAE;EACzD,MAAM,KAAK,OAAO,OAAO,CAAC,OAAO,OAAO,OAAO,MAAM,GAAG,OAAO,MAAM,CAAC,CAAC;EACvE,OAAO,QAAQ,OAAO,OAAO;GAAC;GAAI,OAAO,WAAW;GAAG;EAAE,CAAC,CAAC,CAAC,SAAS,QAAQ;CAC/E;CAEA,QAAQ,KAAqB;EAC3B,IAAI,CAAC,IAAI,WAAW,KAAK,GAAG,MAAM,IAAI,MAAM,4BAA4B;EACxE,MAAM,MAAM,OAAO,KAAK,IAAI,MAAM,CAAC,GAAG,QAAQ;EAC9C,MAAM,WAAW,iBAAiB,eAAe,KAAK,KAAK,IAAI,SAAS,GAAG,EAAE,CAAC;EAC9E,SAAS,WAAW,IAAI,SAAS,IAAI,EAAE,CAAC;EACxC,OAAO,OAAO,OAAO,CAAC,SAAS,OAAO,IAAI,SAAS,EAAE,CAAC,GAAG,SAAS,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM;CAC7F;AACF;;AAGA,MAAa,UAAU,MAAsB,WAAW,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,KAAK;AAqBxF,MAAM,eAAe,UAAkB,MAAc,QAAgB,SACnE,IAAI,SAAiB,SAAS,WAAW,OAAO,UAAU,MAAM,QAAQ,OAAO,KAAK,QAAS,MAAM,OAAO,GAAG,IAAI,QAAQ,GAAG,CAAE,CAAC;;AAGjI,eAAsB,kBAAkB,UAAmC;CACzE,MAAM,OAAO,YAAY,EAAE;CAC3B,MAAM,OAAO,MAAM,YAAY,UAAU,MAAM,IAAI;EAAE,GAAG;EAAO,GAAG;EAAG,GAAG;CAAE,CAAC;CAC3E,OAAO,oBAAoB,KAAK,SAAS,QAAQ,EAAE,GAAG,KAAK,SAAS,QAAQ;AAC9E;;AAGA,eAAsB,oBAAoB,UAAkB,QAAkC;CAC5F,MAAM,CAAC,KAAK,GAAG,GAAG,GAAG,MAAM,QAAQ,OAAO,MAAM,GAAG;CACnD,IAAI,QAAQ,YAAY,CAAC,QAAQ,CAAC,MAAM,OAAO;CAC/C,MAAM,WAAW,OAAO,KAAK,MAAM,QAAQ;CAC3C,MAAM,SAAS,MAAM,YAAY,UAAU,OAAO,KAAK,MAAM,QAAQ,GAAG,SAAS,QAAQ;EAAE,GAAG,OAAO,CAAC;EAAG,GAAG,OAAO,CAAC;EAAG,GAAG,OAAO,CAAC;CAAE,CAAC;CACrI,OAAO,gBAAgB,QAAQ,QAAQ;AACzC;AAEA,SAAgB,UAAU,GAAW,GAAoB;CACvD,MAAM,KAAK,OAAO,KAAK,CAAC;CACxB,MAAM,KAAK,OAAO,KAAK,CAAC;CACxB,OAAO,GAAG,WAAW,GAAG,UAAU,gBAAgB,IAAI,EAAE;AAC1D"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { t as Db } from "./db-DdagFmUY.js";
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env node
2
+ import { chmodSync } from "node:fs";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ //#region server/db/schema.ts
5
+ const MIGRATIONS = [`
6
+ CREATE TABLE settings (
7
+ key TEXT PRIMARY KEY,
8
+ value TEXT NOT NULL
9
+ );
10
+
11
+ CREATE TABLE admin_sessions (
12
+ token_hash TEXT PRIMARY KEY,
13
+ created_at TEXT NOT NULL,
14
+ expires_at TEXT NOT NULL,
15
+ ip TEXT,
16
+ user_agent TEXT
17
+ );
18
+
19
+ CREATE TABLE accounts (
20
+ id TEXT PRIMARY KEY,
21
+ email TEXT NOT NULL UNIQUE,
22
+ display_name TEXT,
23
+ status TEXT NOT NULL DEFAULT 'active',
24
+ last_error TEXT,
25
+ refresh_token_enc TEXT NOT NULL,
26
+ access_token_enc TEXT,
27
+ access_expires_at TEXT,
28
+ scopes TEXT NOT NULL DEFAULT '[]',
29
+ history_id TEXT,
30
+ watch_expires_at TEXT,
31
+ last_sync_at TEXT,
32
+ created_at TEXT NOT NULL
33
+ );
34
+
35
+ -- Pending OAuth flows (state → PKCE verifier). Short-lived.
36
+ CREATE TABLE oauth_states (
37
+ state TEXT PRIMARY KEY,
38
+ code_verifier TEXT NOT NULL,
39
+ redirect_uri TEXT NOT NULL,
40
+ reauth_account_id TEXT,
41
+ created_at TEXT NOT NULL
42
+ );
43
+
44
+ CREATE TABLE agents (
45
+ id TEXT PRIMARY KEY,
46
+ name TEXT NOT NULL,
47
+ description TEXT,
48
+ key_prefix TEXT NOT NULL,
49
+ key_hash TEXT NOT NULL UNIQUE,
50
+ disabled INTEGER NOT NULL DEFAULT 0,
51
+ last_used_at TEXT,
52
+ created_at TEXT NOT NULL
53
+ );
54
+
55
+ CREATE TABLE grants (
56
+ id TEXT PRIMARY KEY,
57
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
58
+ account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
59
+ capabilities TEXT NOT NULL, -- JSON Capability[]
60
+ label_scope TEXT, -- JSON string[] | NULL (= whole mailbox)
61
+ send_mode TEXT NOT NULL DEFAULT 'draft_only',
62
+ allow_to TEXT NOT NULL DEFAULT '[]', -- JSON string[]
63
+ reply_only INTEGER NOT NULL DEFAULT 0,
64
+ max_sends_per_hour INTEGER,
65
+ max_sends_per_day INTEGER,
66
+ created_at TEXT NOT NULL,
67
+ UNIQUE (agent_id, account_id)
68
+ );
69
+
70
+ -- Agent event-feed cursor for the MCP pull tools (list_events / wait_for_events / ack_events).
71
+ CREATE TABLE agent_cursors (
72
+ agent_id TEXT PRIMARY KEY REFERENCES agents(id) ON DELETE CASCADE,
73
+ acked_seq INTEGER NOT NULL DEFAULT 0,
74
+ updated_at TEXT NOT NULL
75
+ );
76
+
77
+ CREATE TABLE events (
78
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
79
+ id TEXT NOT NULL UNIQUE,
80
+ type TEXT NOT NULL,
81
+ account_id TEXT NOT NULL,
82
+ gmail_message_id TEXT,
83
+ gmail_thread_id TEXT,
84
+ label_ids TEXT NOT NULL DEFAULT '[]', -- JSON: message labels at event time (used for scope + filters)
85
+ message TEXT, -- JSON MessageSummary | NULL
86
+ changed_label_ids TEXT, -- JSON string[] for label.added/removed
87
+ detail TEXT, -- JSON
88
+ dedupe_key TEXT NOT NULL UNIQUE, -- e.g. "acc_x:msgid:message.received" — Pub/Sub is at-least-once
89
+ created_at TEXT NOT NULL
90
+ );
91
+ CREATE INDEX events_account_seq ON events(account_id, seq);
92
+ CREATE INDEX events_thread ON events(account_id, gmail_thread_id);
93
+ CREATE INDEX events_created ON events(created_at);
94
+
95
+ -- Threads the account has sent into; drives thread.reply_received.
96
+ CREATE TABLE sent_threads (
97
+ account_id TEXT NOT NULL,
98
+ gmail_thread_id TEXT NOT NULL,
99
+ last_sent_at TEXT NOT NULL,
100
+ PRIMARY KEY (account_id, gmail_thread_id)
101
+ );
102
+
103
+ CREATE TABLE webhooks (
104
+ id TEXT PRIMARY KEY,
105
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
106
+ name TEXT NOT NULL,
107
+ url TEXT NOT NULL,
108
+ secret_enc TEXT NOT NULL,
109
+ secret_prefix TEXT NOT NULL,
110
+ event_types TEXT NOT NULL, -- JSON EventType[]
111
+ filter TEXT NOT NULL DEFAULT '{}', -- JSON WebhookFilter
112
+ include_body INTEGER NOT NULL DEFAULT 0,
113
+ enabled INTEGER NOT NULL DEFAULT 1,
114
+ created_at TEXT NOT NULL
115
+ );
116
+
117
+ CREATE TABLE deliveries (
118
+ id TEXT PRIMARY KEY,
119
+ webhook_id TEXT NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
120
+ event_seq INTEGER NOT NULL,
121
+ status TEXT NOT NULL DEFAULT 'pending',
122
+ attempts INTEGER NOT NULL DEFAULT 0,
123
+ next_attempt_at TEXT,
124
+ last_status_code INTEGER,
125
+ last_error TEXT,
126
+ last_response TEXT,
127
+ duration_ms INTEGER,
128
+ created_at TEXT NOT NULL,
129
+ updated_at TEXT NOT NULL,
130
+ UNIQUE (webhook_id, event_seq)
131
+ );
132
+ CREATE INDEX deliveries_due ON deliveries(status, next_attempt_at);
133
+ CREATE INDEX deliveries_webhook ON deliveries(webhook_id, created_at);
134
+
135
+ CREATE TABLE approvals (
136
+ id TEXT PRIMARY KEY,
137
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
138
+ account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
139
+ kind TEXT NOT NULL,
140
+ status TEXT NOT NULL DEFAULT 'pending',
141
+ draft_id TEXT NOT NULL,
142
+ payload TEXT NOT NULL, -- JSON {to,cc,bcc,subject,body,threadId,attachments,reason}
143
+ decided_at TEXT,
144
+ decision_note TEXT,
145
+ sent_message_id TEXT,
146
+ error TEXT,
147
+ created_at TEXT NOT NULL,
148
+ expires_at TEXT NOT NULL
149
+ );
150
+ CREATE INDEX approvals_status ON approvals(status, created_at);
151
+
152
+ CREATE TABLE audit_log (
153
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
154
+ ts TEXT NOT NULL,
155
+ actor_type TEXT NOT NULL,
156
+ actor_id TEXT,
157
+ action TEXT NOT NULL,
158
+ account_id TEXT,
159
+ target TEXT,
160
+ outcome TEXT NOT NULL,
161
+ reason TEXT,
162
+ duration_ms INTEGER,
163
+ detail TEXT
164
+ );
165
+ CREATE INDEX audit_ts ON audit_log(ts);
166
+ CREATE INDEX audit_actor ON audit_log(actor_id, ts);
167
+ `];
168
+ //#endregion
169
+ //#region server/db/index.ts
170
+ /** Thin synchronous wrapper over node:sqlite. All repos go through this. */
171
+ var Db = class {
172
+ raw;
173
+ depth = 0;
174
+ constructor(path) {
175
+ this.raw = new DatabaseSync(path);
176
+ this.raw.exec("PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000;");
177
+ this.migrate();
178
+ if (path !== ":memory:") for (const file of [
179
+ path,
180
+ `${path}-wal`,
181
+ `${path}-shm`
182
+ ]) try {
183
+ chmodSync(file, 384);
184
+ } catch {}
185
+ }
186
+ get(sql, ...params) {
187
+ return this.raw.prepare(sql).get(...params);
188
+ }
189
+ all(sql, ...params) {
190
+ return this.raw.prepare(sql).all(...params);
191
+ }
192
+ run(sql, ...params) {
193
+ const r = this.raw.prepare(sql).run(...params);
194
+ return {
195
+ changes: Number(r.changes),
196
+ lastInsertRowid: Number(r.lastInsertRowid)
197
+ };
198
+ }
199
+ /** Nested calls join the outer transaction (savepoints). */
200
+ tx(fn) {
201
+ const sp = `sp${this.depth++}`;
202
+ this.raw.exec(`SAVEPOINT ${sp}`);
203
+ try {
204
+ const out = fn();
205
+ this.raw.exec(`RELEASE ${sp}`);
206
+ return out;
207
+ } catch (err) {
208
+ this.raw.exec(`ROLLBACK TO ${sp}; RELEASE ${sp}`);
209
+ throw err;
210
+ } finally {
211
+ this.depth--;
212
+ }
213
+ }
214
+ close() {
215
+ this.raw.close();
216
+ }
217
+ migrate() {
218
+ this.raw.exec("CREATE TABLE IF NOT EXISTS _migrations (version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)");
219
+ const current = this.get("SELECT MAX(version) AS v FROM _migrations")?.v ?? 0;
220
+ MIGRATIONS.forEach((sql, i) => {
221
+ const version = i + 1;
222
+ if (version <= current) return;
223
+ this.tx(() => {
224
+ this.raw.exec(sql);
225
+ this.run("INSERT INTO _migrations (version, applied_at) VALUES (?, ?)", version, (/* @__PURE__ */ new Date()).toISOString());
226
+ });
227
+ });
228
+ }
229
+ };
230
+ const json = {
231
+ parse(v, fallback) {
232
+ if (typeof v !== "string" || v === "") return fallback;
233
+ try {
234
+ return JSON.parse(v);
235
+ } catch {
236
+ return fallback;
237
+ }
238
+ },
239
+ str(v) {
240
+ return JSON.stringify(v);
241
+ }
242
+ };
243
+ const now = () => (/* @__PURE__ */ new Date()).toISOString();
244
+ //#endregion
245
+ export { json as n, now as r, Db as t };
246
+
247
+ //# sourceMappingURL=db-DdagFmUY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-DdagFmUY.js","names":[],"sources":["../server/db/schema.ts","../server/db/index.ts"],"sourcesContent":["// Ordered migrations. Append only — never edit a shipped migration.\n// Conventions: ids are prefixed TEXT, timestamps are ISO-8601 TEXT (UTC), JSON is TEXT,\n// booleans are INTEGER 0/1, secrets are AES-GCM ciphertext TEXT (see server/crypto.ts).\n\nexport const MIGRATIONS: string[] = [\n /* 1: initial schema */ `\n CREATE TABLE settings (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n );\n\n CREATE TABLE admin_sessions (\n token_hash TEXT PRIMARY KEY,\n created_at TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n ip TEXT,\n user_agent TEXT\n );\n\n CREATE TABLE accounts (\n id TEXT PRIMARY KEY,\n email TEXT NOT NULL UNIQUE,\n display_name TEXT,\n status TEXT NOT NULL DEFAULT 'active',\n last_error TEXT,\n refresh_token_enc TEXT NOT NULL,\n access_token_enc TEXT,\n access_expires_at TEXT,\n scopes TEXT NOT NULL DEFAULT '[]',\n history_id TEXT,\n watch_expires_at TEXT,\n last_sync_at TEXT,\n created_at TEXT NOT NULL\n );\n\n -- Pending OAuth flows (state → PKCE verifier). Short-lived.\n CREATE TABLE oauth_states (\n state TEXT PRIMARY KEY,\n code_verifier TEXT NOT NULL,\n redirect_uri TEXT NOT NULL,\n reauth_account_id TEXT,\n created_at TEXT NOT NULL\n );\n\n CREATE TABLE agents (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n description TEXT,\n key_prefix TEXT NOT NULL,\n key_hash TEXT NOT NULL UNIQUE,\n disabled INTEGER NOT NULL DEFAULT 0,\n last_used_at TEXT,\n created_at TEXT NOT NULL\n );\n\n CREATE TABLE grants (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,\n account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,\n capabilities TEXT NOT NULL, -- JSON Capability[]\n label_scope TEXT, -- JSON string[] | NULL (= whole mailbox)\n send_mode TEXT NOT NULL DEFAULT 'draft_only',\n allow_to TEXT NOT NULL DEFAULT '[]', -- JSON string[]\n reply_only INTEGER NOT NULL DEFAULT 0,\n max_sends_per_hour INTEGER,\n max_sends_per_day INTEGER,\n created_at TEXT NOT NULL,\n UNIQUE (agent_id, account_id)\n );\n\n -- Agent event-feed cursor for the MCP pull tools (list_events / wait_for_events / ack_events).\n CREATE TABLE agent_cursors (\n agent_id TEXT PRIMARY KEY REFERENCES agents(id) ON DELETE CASCADE,\n acked_seq INTEGER NOT NULL DEFAULT 0,\n updated_at TEXT NOT NULL\n );\n\n CREATE TABLE events (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n type TEXT NOT NULL,\n account_id TEXT NOT NULL,\n gmail_message_id TEXT,\n gmail_thread_id TEXT,\n label_ids TEXT NOT NULL DEFAULT '[]', -- JSON: message labels at event time (used for scope + filters)\n message TEXT, -- JSON MessageSummary | NULL\n changed_label_ids TEXT, -- JSON string[] for label.added/removed\n detail TEXT, -- JSON\n dedupe_key TEXT NOT NULL UNIQUE, -- e.g. \"acc_x:msgid:message.received\" — Pub/Sub is at-least-once\n created_at TEXT NOT NULL\n );\n CREATE INDEX events_account_seq ON events(account_id, seq);\n CREATE INDEX events_thread ON events(account_id, gmail_thread_id);\n CREATE INDEX events_created ON events(created_at);\n\n -- Threads the account has sent into; drives thread.reply_received.\n CREATE TABLE sent_threads (\n account_id TEXT NOT NULL,\n gmail_thread_id TEXT NOT NULL,\n last_sent_at TEXT NOT NULL,\n PRIMARY KEY (account_id, gmail_thread_id)\n );\n\n CREATE TABLE webhooks (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n url TEXT NOT NULL,\n secret_enc TEXT NOT NULL,\n secret_prefix TEXT NOT NULL,\n event_types TEXT NOT NULL, -- JSON EventType[]\n filter TEXT NOT NULL DEFAULT '{}', -- JSON WebhookFilter\n include_body INTEGER NOT NULL DEFAULT 0,\n enabled INTEGER NOT NULL DEFAULT 1,\n created_at TEXT NOT NULL\n );\n\n CREATE TABLE deliveries (\n id TEXT PRIMARY KEY,\n webhook_id TEXT NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,\n event_seq INTEGER NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n attempts INTEGER NOT NULL DEFAULT 0,\n next_attempt_at TEXT,\n last_status_code INTEGER,\n last_error TEXT,\n last_response TEXT,\n duration_ms INTEGER,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n UNIQUE (webhook_id, event_seq)\n );\n CREATE INDEX deliveries_due ON deliveries(status, next_attempt_at);\n CREATE INDEX deliveries_webhook ON deliveries(webhook_id, created_at);\n\n CREATE TABLE approvals (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,\n account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,\n kind TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n draft_id TEXT NOT NULL,\n payload TEXT NOT NULL, -- JSON {to,cc,bcc,subject,body,threadId,attachments,reason}\n decided_at TEXT,\n decision_note TEXT,\n sent_message_id TEXT,\n error TEXT,\n created_at TEXT NOT NULL,\n expires_at TEXT NOT NULL\n );\n CREATE INDEX approvals_status ON approvals(status, created_at);\n\n CREATE TABLE audit_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts TEXT NOT NULL,\n actor_type TEXT NOT NULL,\n actor_id TEXT,\n action TEXT NOT NULL,\n account_id TEXT,\n target TEXT,\n outcome TEXT NOT NULL,\n reason TEXT,\n duration_ms INTEGER,\n detail TEXT\n );\n CREATE INDEX audit_ts ON audit_log(ts);\n CREATE INDEX audit_actor ON audit_log(actor_id, ts);\n `,\n];\n","import { chmodSync } from \"node:fs\";\nimport { DatabaseSync, type SQLInputValue } from \"node:sqlite\";\nimport { MIGRATIONS } from \"./schema.ts\";\n\nexport type Param = SQLInputValue;\nexport type Row = Record<string, unknown>;\n\n/** Thin synchronous wrapper over node:sqlite. All repos go through this. */\nexport class Db {\n readonly raw: DatabaseSync;\n private depth = 0;\n\n constructor(path: string) {\n this.raw = new DatabaseSync(path);\n this.raw.exec(\"PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000;\");\n this.migrate();\n // The database holds (encrypted) tokens and the audit log: owner-only, whatever the umask.\n if (path !== \":memory:\") {\n for (const file of [path, `${path}-wal`, `${path}-shm`]) {\n try {\n chmodSync(file, 0o600);\n } catch {\n // Not created (yet) or not ours to change.\n }\n }\n }\n }\n\n get<T = Row>(sql: string, ...params: Param[]): T | undefined {\n return this.raw.prepare(sql).get(...params) as T | undefined;\n }\n\n all<T = Row>(sql: string, ...params: Param[]): T[] {\n return this.raw.prepare(sql).all(...params) as T[];\n }\n\n run(sql: string, ...params: Param[]): { changes: number; lastInsertRowid: number } {\n const r = this.raw.prepare(sql).run(...params);\n return { changes: Number(r.changes), lastInsertRowid: Number(r.lastInsertRowid) };\n }\n\n /** Nested calls join the outer transaction (savepoints). */\n tx<T>(fn: () => T): T {\n const sp = `sp${this.depth++}`;\n this.raw.exec(`SAVEPOINT ${sp}`);\n try {\n const out = fn();\n this.raw.exec(`RELEASE ${sp}`);\n return out;\n } catch (err) {\n this.raw.exec(`ROLLBACK TO ${sp}; RELEASE ${sp}`);\n throw err;\n } finally {\n this.depth--;\n }\n }\n\n close(): void {\n this.raw.close();\n }\n\n private migrate(): void {\n this.raw.exec(\"CREATE TABLE IF NOT EXISTS _migrations (version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)\");\n const row = this.get<{ v: number | null }>(\"SELECT MAX(version) AS v FROM _migrations\");\n const current = row?.v ?? 0;\n MIGRATIONS.forEach((sql, i) => {\n const version = i + 1;\n if (version <= current) return;\n this.tx(() => {\n this.raw.exec(sql);\n this.run(\"INSERT INTO _migrations (version, applied_at) VALUES (?, ?)\", version, new Date().toISOString());\n });\n });\n }\n}\n\nexport const json = {\n parse<T>(v: unknown, fallback: T): T {\n if (typeof v !== \"string\" || v === \"\") return fallback;\n try {\n return JSON.parse(v) as T;\n } catch {\n return fallback;\n }\n },\n str(v: unknown): string {\n return JSON.stringify(v);\n },\n};\n\nexport const bool = (v: unknown): boolean => v === 1 || v === 1n || v === true;\nexport const now = (): string => new Date().toISOString();\n"],"mappings":";;;;AAIA,MAAa,aAAuB,CACV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmK1B;;;;AChKA,IAAa,KAAb,MAAgB;CACd;CACA,QAAgB;CAEhB,YAAY,MAAc;EACxB,KAAK,MAAM,IAAI,aAAa,IAAI;EAChC,KAAK,IAAI,KAAK,kFAAkF;EAChG,KAAK,QAAQ;EAEb,IAAI,SAAS,YACX,KAAK,MAAM,QAAQ;GAAC;GAAM,GAAG,KAAK;GAAO,GAAG,KAAK;EAAK,GACpD,IAAI;GACF,UAAU,MAAM,GAAK;EACvB,QAAQ,CAER;CAGN;CAEA,IAAa,KAAa,GAAG,QAAgC;EAC3D,OAAO,KAAK,IAAI,QAAQ,GAAG,CAAC,CAAC,IAAI,GAAG,MAAM;CAC5C;CAEA,IAAa,KAAa,GAAG,QAAsB;EACjD,OAAO,KAAK,IAAI,QAAQ,GAAG,CAAC,CAAC,IAAI,GAAG,MAAM;CAC5C;CAEA,IAAI,KAAa,GAAG,QAA+D;EACjF,MAAM,IAAI,KAAK,IAAI,QAAQ,GAAG,CAAC,CAAC,IAAI,GAAG,MAAM;EAC7C,OAAO;GAAE,SAAS,OAAO,EAAE,OAAO;GAAG,iBAAiB,OAAO,EAAE,eAAe;EAAE;CAClF;;CAGA,GAAM,IAAgB;EACpB,MAAM,KAAK,KAAK,KAAK;EACrB,KAAK,IAAI,KAAK,aAAa,IAAI;EAC/B,IAAI;GACF,MAAM,MAAM,GAAG;GACf,KAAK,IAAI,KAAK,WAAW,IAAI;GAC7B,OAAO;EACT,SAAS,KAAK;GACZ,KAAK,IAAI,KAAK,eAAe,GAAG,YAAY,IAAI;GAChD,MAAM;EACR,UAAU;GACR,KAAK;EACP;CACF;CAEA,QAAc;EACZ,KAAK,IAAI,MAAM;CACjB;CAEA,UAAwB;EACtB,KAAK,IAAI,KAAK,gGAAgG;EAE9G,MAAM,UADM,KAAK,IAA0B,2CACzB,CAAC,EAAE,KAAK;EAC1B,WAAW,SAAS,KAAK,MAAM;GAC7B,MAAM,UAAU,IAAI;GACpB,IAAI,WAAW,SAAS;GACxB,KAAK,SAAS;IACZ,KAAK,IAAI,KAAK,GAAG;IACjB,KAAK,IAAI,+DAA+D,0BAAS,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC;GAC3G,CAAC;EACH,CAAC;CACH;AACF;AAEA,MAAa,OAAO;CAClB,MAAS,GAAY,UAAgB;EACnC,IAAI,OAAO,MAAM,YAAY,MAAM,IAAI,OAAO;EAC9C,IAAI;GACF,OAAO,KAAK,MAAM,CAAC;EACrB,QAAQ;GACN,OAAO;EACT;CACF;CACA,IAAI,GAAoB;EACtB,OAAO,KAAK,UAAU,CAAC;CACzB;AACF;AAGA,MAAa,6BAAoB,IAAI,KAAK,EAAA,CAAE,YAAY"}
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ //#region server/util/errors.ts
3
+ /** Throw from anywhere in request handling; the API layer turns it into an ApiError response. */
4
+ var HttpError = class extends Error {
5
+ status;
6
+ code;
7
+ hint;
8
+ constructor(status, code, message, hint) {
9
+ super(message);
10
+ this.status = status;
11
+ this.code = code;
12
+ this.hint = hint;
13
+ }
14
+ toJSON() {
15
+ return { error: {
16
+ code: this.code,
17
+ message: this.message,
18
+ ...this.hint ? { hint: this.hint } : {}
19
+ } };
20
+ }
21
+ };
22
+ const badRequest = (message, hint) => new HttpError(400, "bad_request", message, hint);
23
+ const conflict = (message, hint) => new HttpError(409, "conflict", message, hint);
24
+ /** Thrown by policy checks. `reason` is the guardrail name recorded in the audit log. */
25
+ var PolicyDenied = class extends Error {
26
+ reason;
27
+ constructor(reason, message) {
28
+ super(message);
29
+ this.reason = reason;
30
+ }
31
+ };
32
+ function errorMessage(err) {
33
+ return err instanceof Error ? err.message : String(err);
34
+ }
35
+ //#endregion
36
+ export { errorMessage as a, conflict as i, PolicyDenied as n, badRequest as r, HttpError as t };
37
+
38
+ //# sourceMappingURL=errors-CJLUPllS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-CJLUPllS.js","names":[],"sources":["../server/util/errors.ts"],"sourcesContent":["import type { ApiError } from \"../../shared/types.ts\";\n\n/** Throw from anywhere in request handling; the API layer turns it into an ApiError response. */\nexport class HttpError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n readonly hint?: string,\n ) {\n super(message);\n }\n\n toJSON(): ApiError {\n return { error: { code: this.code, message: this.message, ...(this.hint ? { hint: this.hint } : {}) } };\n }\n}\n\nexport const badRequest = (message: string, hint?: string) => new HttpError(400, \"bad_request\", message, hint);\nexport const notFound = (what: string) => new HttpError(404, \"not_found\", `${what} not found`);\nexport const conflict = (message: string, hint?: string) => new HttpError(409, \"conflict\", message, hint);\n\n/** Thrown by policy checks. `reason` is the guardrail name recorded in the audit log. */\nexport class PolicyDenied extends Error {\n constructor(\n readonly reason: string,\n message: string,\n ) {\n super(message);\n }\n}\n\nexport function errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n"],"mappings":";;;AAGA,IAAa,YAAb,cAA+B,MAAM;CAExB;CACA;CAEA;CAJX,YACE,QACA,MACA,SACA,MACA;EACA,MAAM,OAAO;EALJ,KAAA,SAAA;EACA,KAAA,OAAA;EAEA,KAAA,OAAA;CAGX;CAEA,SAAmB;EACjB,OAAO,EAAE,OAAO;GAAE,MAAM,KAAK;GAAM,SAAS,KAAK;GAAS,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;EAAG,EAAE;CACxG;AACF;AAEA,MAAa,cAAc,SAAiB,SAAkB,IAAI,UAAU,KAAK,eAAe,SAAS,IAAI;AAE7G,MAAa,YAAY,SAAiB,SAAkB,IAAI,UAAU,KAAK,YAAY,SAAS,IAAI;;AAGxG,IAAa,eAAb,cAAkC,MAAM;CAE3B;CADX,YACE,QACA,SACA;EACA,MAAM,OAAO;EAHJ,KAAA,SAAA;CAIX;AACF;AAEA,SAAgB,aAAa,KAAsB;CACjD,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { s as runningInstance } from "./instance-Dy2jeyZc.js";