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.
- package/LICENSE +21 -0
- package/README.md +312 -0
- package/dist/audit-CQe-_dPQ.js +2 -0
- package/dist/audit-DETqygzG.js +65 -0
- package/dist/audit-DETqygzG.js.map +1 -0
- package/dist/auth-BniddcRD.js +447 -0
- package/dist/auth-BniddcRD.js.map +1 -0
- package/dist/auth-C2v7bEmQ.js +2 -0
- package/dist/bus-Cz0flS37.js +12 -0
- package/dist/bus-Cz0flS37.js.map +1 -0
- package/dist/bus-OKIGut72.js +2 -0
- package/dist/cli.js +141 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-CIvgMqLx.js +2 -0
- package/dist/config-v975gxv4.js +44 -0
- package/dist/config-v975gxv4.js.map +1 -0
- package/dist/crypto-DYZ4R7c2.js +74 -0
- package/dist/crypto-DYZ4R7c2.js.map +1 -0
- package/dist/db-CfoMO5zW.js +2 -0
- package/dist/db-DdagFmUY.js +247 -0
- package/dist/db-DdagFmUY.js.map +1 -0
- package/dist/errors-CJLUPllS.js +38 -0
- package/dist/errors-CJLUPllS.js.map +1 -0
- package/dist/instance-DZMV78rI.js +2 -0
- package/dist/instance-Dy2jeyZc.js +174 -0
- package/dist/instance-Dy2jeyZc.js.map +1 -0
- package/dist/server-CWqKeCGn.js +981 -0
- package/dist/server-CWqKeCGn.js.map +1 -0
- package/dist/service-63pdSmhO.js +332 -0
- package/dist/service-63pdSmhO.js.map +1 -0
- package/dist/settings-Dq2ETFsd.js +98 -0
- package/dist/settings-Dq2ETFsd.js.map +1 -0
- package/dist/settings-qtn5WTBM.js +2 -0
- package/dist/setup-token-Bkwk84l7.js +47 -0
- package/dist/setup-token-Bkwk84l7.js.map +1 -0
- package/dist/skill/SKILL.md +226 -0
- package/dist/skill-BWWtinsE.js +2 -0
- package/dist/skill-WKbeyNct.js +43 -0
- package/dist/skill-WKbeyNct.js.map +1 -0
- package/dist/urls-CNYhbeUQ.js +2 -0
- package/dist/urls-DmnonkJo.js +22 -0
- package/dist/urls-DmnonkJo.js.map +1 -0
- package/dist/web/assets/_agentId-il4y7OCG.js +1 -0
- package/dist/web/assets/_app-BIO8WIYB.js +1 -0
- package/dist/web/assets/_app-wlLL4MrD.js +63 -0
- package/dist/web/assets/_webhookId-CiwDrkY4.js +1 -0
- package/dist/web/assets/account-status-at9Bv3_T.js +1 -0
- package/dist/web/assets/accounts-B4rfPwCV.js +1 -0
- package/dist/web/assets/accounts-list-e--JWxHH.js +1 -0
- package/dist/web/assets/activity-zrckFhPk.js +1 -0
- package/dist/web/assets/agent-create-form-DaWPR6kx.js +1 -0
- package/dist/web/assets/agents-D4gPq4dt.js +1 -0
- package/dist/web/assets/approvals-DaoZ6wuf.js +2 -0
- package/dist/web/assets/arrow-left-DVJV4Zx7.js +1 -0
- package/dist/web/assets/arrow-right-4PFX6MSS.js +1 -0
- package/dist/web/assets/audit-bAdLe-px.js +1 -0
- package/dist/web/assets/audit-bits-DQHo74i6.js +1 -0
- package/dist/web/assets/bot-DVw15lBn.js +1 -0
- package/dist/web/assets/card-EQTpqntn.js +1 -0
- package/dist/web/assets/checkbox-DDZZurgK.js +1 -0
- package/dist/web/assets/chevron-down-892ylB4R.js +1 -0
- package/dist/web/assets/chevron-right-CbBKS6-T.js +1 -0
- package/dist/web/assets/chips-input-Btdc_f7b.js +1 -0
- package/dist/web/assets/circle-x-Bi4IGwky.js +1 -0
- package/dist/web/assets/common-h6596gEC.js +1 -0
- package/dist/web/assets/connect-host-Co31qIYr.js +1 -0
- package/dist/web/assets/data-table-DsKrM62X.js +1 -0
- package/dist/web/assets/dialog-CgZr27yw.js +1 -0
- package/dist/web/assets/dist-B0e3Ju3r.js +1 -0
- package/dist/web/assets/empty-CE_WZGEC.js +1 -0
- package/dist/web/assets/es2015-DPaldoxG.js +41 -0
- package/dist/web/assets/event-row-C0qRfWuV.js +1 -0
- package/dist/web/assets/events-Svyij7mp.js +1 -0
- package/dist/web/assets/eye-off-DjpaihHf.js +1 -0
- package/dist/web/assets/field-CcyMn3ey.js +1 -0
- package/dist/web/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dist/web/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dist/web/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/web/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/web/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
- package/dist/web/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
- package/dist/web/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
- package/dist/web/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/dist/web/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
- package/dist/web/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
- package/dist/web/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dist/web/assets/get-started-Cxfw97sJ.js +1 -0
- package/dist/web/assets/grants-BilXCDps.js +1 -0
- package/dist/web/assets/index-DgIgw66p.css +2 -0
- package/dist/web/assets/index-TbxC5ZgP.js +10 -0
- package/dist/web/assets/input-CMhmmHiH.js +1 -0
- package/dist/web/assets/input-group-DRs3TNaT.js +1 -0
- package/dist/web/assets/key-round-CVIZRCLM.js +1 -0
- package/dist/web/assets/labels-C_Pwium9.js +1 -0
- package/dist/web/assets/login-DUJia7lg.js +1 -0
- package/dist/web/assets/mail-CAVmuESo.js +1 -0
- package/dist/web/assets/mutations-D_GluJ0j.js +1 -0
- package/dist/web/assets/paperclip-QFjM4y6n.js +1 -0
- package/dist/web/assets/password-input-_m9Ycphv.js +1 -0
- package/dist/web/assets/pencil-Bf1JhsG0.js +1 -0
- package/dist/web/assets/play-DiPk49JH.js +1 -0
- package/dist/web/assets/plus-CETmK0SB.js +1 -0
- package/dist/web/assets/preload-helper-BFuRUfdt.js +1 -0
- package/dist/web/assets/progress-3ZOsbLSK.js +6 -0
- package/dist/web/assets/queries-a3M5_loa.js +2 -0
- package/dist/web/assets/refresh-cw-DOn9kD7g.js +1 -0
- package/dist/web/assets/search-gcYjitzV.js +1 -0
- package/dist/web/assets/select-_tULk-vs.js +1 -0
- package/dist/web/assets/send-BhQwhuJo.js +1 -0
- package/dist/web/assets/settings-Dl4wb4f0.js +1 -0
- package/dist/web/assets/settings-form-D21eKBh-.js +1 -0
- package/dist/web/assets/setup-DYnNhnfH.js +1 -0
- package/dist/web/assets/sheet--2lKU0jN.js +1 -0
- package/dist/web/assets/shield-alert-CbFgd06X.js +1 -0
- package/dist/web/assets/stamp-CH2OvjH2.js +1 -0
- package/dist/web/assets/stream-6OwrgVZw.js +1 -0
- package/dist/web/assets/tabs-CiAUEj_y.js +1 -0
- package/dist/web/assets/tag-CP72Kk6Y.js +1 -0
- package/dist/web/assets/test-connection-F2tMowbv.js +1 -0
- package/dist/web/assets/textarea-CCokBZIw.js +1 -0
- package/dist/web/assets/toggle-group-D1pC7sJr.js +1 -0
- package/dist/web/assets/trash-DxdMNg8B.js +1 -0
- package/dist/web/assets/use-sign-out-BuzlTb0Q.js +1 -0
- package/dist/web/assets/useInfiniteQuery-DE6WChlb.js +1 -0
- package/dist/web/assets/useMatch-BKUnm54_.js +1 -0
- package/dist/web/assets/useMutation-DGl4PaZ1.js +1 -0
- package/dist/web/assets/useQuery-DAhf1YDH.js +1 -0
- package/dist/web/assets/useSelector-D70eKNYs.js +1 -0
- package/dist/web/assets/webhook-BWT75M78.js +1 -0
- package/dist/web/assets/webhook-sheet-4U_6Rw8I.js +34 -0
- package/dist/web/assets/webhooks-C1JvTI88.js +1 -0
- package/dist/web/assets/with-selector-CkNyXYMW.js +1 -0
- package/dist/web/assets/x-BHjDLXNO.js +1 -0
- package/dist/web/favicon.svg +1 -0
- package/dist/web/index.html +31 -0
- package/package.json +94 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as Crypto } from "./crypto-DYZ4R7c2.js";
|
|
3
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { DatabaseSync } from "node:sqlite";
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
//#region shared/types.ts
|
|
8
|
+
/** The npm package (`npx agentinbox-cli`); the command it installs is `agentinbox`. */
|
|
9
|
+
const NPM_PACKAGE = "agentinbox-cli";
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region server/instance.ts
|
|
12
|
+
/**
|
|
13
|
+
* How to run this CLI in printed hints: `npx agentinbox-cli` when run through npx (and in service
|
|
14
|
+
* logs, read by someone who most likely installed that way), else the installed `agentinbox`.
|
|
15
|
+
*/
|
|
16
|
+
function cliCommand(args) {
|
|
17
|
+
return `${process.env.npm_command === "exec" || /[\\/]_npx[\\/]/.test(process.argv[1] ?? "") || process.env.AGENTINBOX_SERVICE === "1" ? `npx ${NPM_PACKAGE}` : "agentinbox"} ${args}`;
|
|
18
|
+
}
|
|
19
|
+
const lockPath = (dataDir) => join(dataDir, "agentinbox.lock");
|
|
20
|
+
/** Data dirs locked by this process: a lock naming our own pid is live only if it's in here. */
|
|
21
|
+
const heldByUs = /* @__PURE__ */ new Set();
|
|
22
|
+
function bootId() {
|
|
23
|
+
try {
|
|
24
|
+
return readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim() || null;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Process start time: /proc (Linux, in clock ticks since boot) or `ps` elsewhere; null if unknown. */
|
|
30
|
+
function processStartTime(pid) {
|
|
31
|
+
try {
|
|
32
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
33
|
+
return stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19] ?? null;
|
|
34
|
+
} catch {}
|
|
35
|
+
if (process.platform === "linux" || process.platform === "win32") return null;
|
|
36
|
+
try {
|
|
37
|
+
return execFileSync("ps", [
|
|
38
|
+
"-o",
|
|
39
|
+
"lstart=",
|
|
40
|
+
"-p",
|
|
41
|
+
String(pid)
|
|
42
|
+
], {
|
|
43
|
+
encoding: "utf8",
|
|
44
|
+
stdio: [
|
|
45
|
+
"ignore",
|
|
46
|
+
"pipe",
|
|
47
|
+
"ignore"
|
|
48
|
+
]
|
|
49
|
+
}).trim() || null;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function isAlive(pid) {
|
|
55
|
+
try {
|
|
56
|
+
process.kill(pid, 0);
|
|
57
|
+
return true;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
return err.code === "EPERM";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Whether the process a lock names is still the one that wrote it. Our own pid is live only if we
|
|
64
|
+
* hold the lock (in containers every start is pid 1); otherwise the pid must exist, on the same
|
|
65
|
+
* boot, with the same start time (when both are known).
|
|
66
|
+
*/
|
|
67
|
+
function isLive(info, dataDir) {
|
|
68
|
+
if (info.pid === process.pid) return heldByUs.has(dataDir);
|
|
69
|
+
if (!isAlive(info.pid)) return false;
|
|
70
|
+
const boot = bootId();
|
|
71
|
+
if (info.bootId && boot && info.bootId !== boot) return false;
|
|
72
|
+
if (info.processStart) {
|
|
73
|
+
const start = processStartTime(info.pid);
|
|
74
|
+
if (start && start !== info.processStart) return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
function readLock(dataDir) {
|
|
79
|
+
try {
|
|
80
|
+
const info = JSON.parse(readFileSync(lockPath(dataDir), "utf8"));
|
|
81
|
+
return Number.isInteger(info.pid) && info.pid > 0 ? info : null;
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** The running instance for this data directory, if any. */
|
|
87
|
+
function runningInstance(dataDir) {
|
|
88
|
+
const info = readLock(dataDir);
|
|
89
|
+
return info && isLive(info, dataDir) ? info : null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Takes `<data>/agentinbox.lock`. Two processes on one data directory would both run ingest and
|
|
93
|
+
* deliver every webhook twice, even on different ports. A lock whose process is gone (dead,
|
|
94
|
+
* rebooted, or its pid reused by something else) is replaced.
|
|
95
|
+
*/
|
|
96
|
+
function acquireInstanceLock(config) {
|
|
97
|
+
const dataDir = config.dataDir;
|
|
98
|
+
const file = lockPath(dataDir);
|
|
99
|
+
const info = {
|
|
100
|
+
pid: process.pid,
|
|
101
|
+
host: config.host,
|
|
102
|
+
port: config.port,
|
|
103
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
104
|
+
bootId: bootId(),
|
|
105
|
+
processStart: processStartTime(process.pid)
|
|
106
|
+
};
|
|
107
|
+
for (let attempt = 0;; attempt++) try {
|
|
108
|
+
writeFileSync(file, JSON.stringify(info), {
|
|
109
|
+
flag: "wx",
|
|
110
|
+
mode: 384
|
|
111
|
+
});
|
|
112
|
+
break;
|
|
113
|
+
} catch (err) {
|
|
114
|
+
if (err.code !== "EEXIST" || attempt > 0) throw err;
|
|
115
|
+
const other = runningInstance(dataDir);
|
|
116
|
+
if (other) throw new Error(`agentinbox is already running with the data directory ${dataDir} (pid ${other.pid}${other.port ? `, port ${other.port}` : ""}). Stop it first, or use another --data directory. If no such process exists, delete ${file}.`);
|
|
117
|
+
rmSync(file, { force: true });
|
|
118
|
+
}
|
|
119
|
+
heldByUs.add(dataDir);
|
|
120
|
+
const ours = () => {
|
|
121
|
+
const current = readLock(dataDir);
|
|
122
|
+
return current?.pid === process.pid && current.startedAt === info.startedAt;
|
|
123
|
+
};
|
|
124
|
+
return {
|
|
125
|
+
update(port) {
|
|
126
|
+
info.port = port;
|
|
127
|
+
if (ours()) writeFileSync(file, JSON.stringify(info), { mode: 384 });
|
|
128
|
+
},
|
|
129
|
+
release() {
|
|
130
|
+
if (ours()) rmSync(file, { force: true });
|
|
131
|
+
heldByUs.delete(dataDir);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** One ciphertext from the database, or null when nothing encrypted is stored. */
|
|
136
|
+
function sampleCiphertext(dbPath) {
|
|
137
|
+
if (!existsSync(dbPath)) return null;
|
|
138
|
+
const db = new DatabaseSync(dbPath);
|
|
139
|
+
try {
|
|
140
|
+
for (const sql of [
|
|
141
|
+
"SELECT refresh_token_enc AS v FROM accounts WHERE refresh_token_enc LIKE 'v1:%' LIMIT 1",
|
|
142
|
+
"SELECT secret_enc AS v FROM webhooks WHERE secret_enc LIKE 'v1:%' LIMIT 1",
|
|
143
|
+
"SELECT json_extract(value, '$.clientSecretEnc') AS v FROM settings WHERE key = 'google.client'"
|
|
144
|
+
]) try {
|
|
145
|
+
const v = db.prepare(sql).get()?.v;
|
|
146
|
+
if (typeof v === "string" && v.startsWith("v1:")) return v;
|
|
147
|
+
} catch {}
|
|
148
|
+
return null;
|
|
149
|
+
} finally {
|
|
150
|
+
db.close();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Refuses to start with a master key that can't read the stored secrets: a missing master.key would
|
|
155
|
+
* otherwise be regenerated, silently making every refresh token and webhook secret unreadable.
|
|
156
|
+
* `envKey` overrides AGENTINBOX_MASTER_KEY (used by `service install` to check the key it will install).
|
|
157
|
+
*/
|
|
158
|
+
function assertMasterKeyUsable(config, envKey = process.env.AGENTINBOX_MASTER_KEY) {
|
|
159
|
+
const sample = sampleCiphertext(config.dbPath);
|
|
160
|
+
if (!sample) return;
|
|
161
|
+
let raw = envKey || null;
|
|
162
|
+
const source = raw ? "AGENTINBOX_MASTER_KEY" : config.masterKeyPath;
|
|
163
|
+
if (!raw && existsSync(config.masterKeyPath)) raw = readFileSync(config.masterKeyPath, "utf8").trim();
|
|
164
|
+
if (!raw) throw new Error(`${config.dbPath} holds encrypted secrets, but there is no master key: ${config.masterKeyPath} is missing and AGENTINBOX_MASTER_KEY is not set. Restore master.key from your backup (or set AGENTINBOX_MASTER_KEY to the key used before). agentinbox won't create a new key, because the stored Google tokens and secrets could never be read again.`);
|
|
165
|
+
try {
|
|
166
|
+
new Crypto(Buffer.from(raw, "base64")).decrypt(sample);
|
|
167
|
+
} catch {
|
|
168
|
+
throw new Error(`The master key from ${source} can't decrypt the secrets stored in ${config.dbPath}. Use the key this data directory was created with (master.key from your backup, or the previous AGENTINBOX_MASTER_KEY).`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
export { lockPath as a, NPM_PACKAGE as c, cliCommand as i, assertMasterKeyUsable as n, processStartTime as o, bootId as r, runningInstance as s, acquireInstanceLock as t };
|
|
173
|
+
|
|
174
|
+
//# sourceMappingURL=instance-Dy2jeyZc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance-Dy2jeyZc.js","names":[],"sources":["../shared/types.ts","../server/instance.ts"],"sourcesContent":["// Shared contract between the server (server/) and the web console (src/).\n// Everything crossing the HTTP boundary of the console API (/api/*) is typed here.\n// Timestamps are ISO-8601 strings in UTC. IDs are prefixed opaque strings (e.g. \"agt_…\").\n\n/** The npm package (`npx agentinbox-cli`); the command it installs is `agentinbox`. */\nexport const NPM_PACKAGE = \"agentinbox-cli\";\n\n// ───────────────────────────── Domain enums ─────────────────────────────\n\n/** What an agent may do with a mailbox. `read` also grants search, labels, attachments and the event feed. */\nexport const CAPABILITIES = [\"read\", \"draft\", \"send\", \"organize\", \"trash\"] as const;\nexport type Capability = (typeof CAPABILITIES)[number];\n\n/**\n * How `send`/`reply`/`forward` behave for a grant.\n * - direct: sent immediately (still subject to allowlist + rate limits)\n * - draft_only: a Gmail draft is created instead; a human sends it from Gmail\n * - approval: a draft is created and an approval request is queued in the console;\n * approving sends the draft, rejecting deletes it\n */\nexport const SEND_MODES = [\"direct\", \"draft_only\", \"approval\"] as const;\nexport type SendMode = (typeof SEND_MODES)[number];\n\nexport const EVENT_TYPES = [\n \"message.received\", // new message landed in the mailbox (not sent by the account)\n \"message.sent\", // message sent from the account (by an agent or by a human)\n \"thread.reply_received\", // received message in a thread the account previously sent into\n \"label.added\",\n \"label.removed\",\n \"account.auth_failed\", // refresh token revoked/expired; account needs re-auth\n \"sync.gap\", // history was too old; a bounded resync ran and events may be missing\n] as const;\nexport type EventType = (typeof EVENT_TYPES)[number];\n\nexport type AccountStatus = \"active\" | \"needs_reauth\" | \"paused\" | \"error\";\nexport type IngestMode = \"pubsub\" | \"poll\";\nexport type DeliveryStatus = \"pending\" | \"succeeded\" | \"failed\" | \"dead\";\nexport type ApprovalStatus = \"pending\" | \"approved\" | \"rejected\" | \"sent\" | \"failed\" | \"expired\";\nexport type AuditOutcome = \"ok\" | \"denied\" | \"error\";\nexport type ActorType = \"agent\" | \"admin\" | \"system\";\n\n/** OAuth consent configuration the operator chose in Google Cloud. Drives setup guidance only. */\nexport type GoogleAppType = \"internal\" | \"external\";\n\n// ───────────────────────────── Setup / system ─────────────────────────────\n\nexport interface HealthResponse {\n status: \"ok\" | \"degraded\";\n version: string;\n setupComplete: boolean;\n}\n\n/** GET /api/setup/status (unauthenticated). Drives the console's first route decision: claim, login or app. */\nexport interface SetupStatus {\n claimed: boolean;\n /** The request carries a valid admin session. */\n authenticated: boolean;\n}\n\n/** Live state of the ingest pipeline (Pub/Sub pull loop or history polling). */\nexport interface IngestStatus {\n mode: IngestMode;\n running: boolean;\n lastPullAt: string | null;\n lastError: string | null;\n}\n\n/** GET /api/system: facts about this instance for the settings page and connect snippets. */\nexport interface SystemInfo {\n version: string;\n nodeVersion: string;\n dataDir: string;\n /** Running under `agentinbox service install` (systemd/launchd). */\n service: boolean;\n /** URLs this machine is reachable at (localhost first, then LAN addresses). */\n addresses: string[];\n publicUrl: string;\n ingest: IngestStatus;\n}\n\n/** GET /api/system/latest: update notice. */\nexport interface UpdateInfo {\n current: string;\n latest: string | null;\n updateAvailable: boolean;\n /** Shell command that runs (or reinstalls the service with) the latest version using this instance's options. */\n command: string;\n}\n\n/** Ordered setup checklist; the console's setup wizard and overview both render this. */\nexport interface SetupState {\n /** True once an admin password exists. Before that, only /api/setup/claim with the setup token works. */\n claimed: boolean;\n googleClientConfigured: boolean;\n /** Google project id (needed for Pub/Sub). */\n gcpProjectId: string | null;\n googleAppType: GoogleAppType | null;\n pubsub: PubsubState;\n accountsConnected: number;\n agentsCreated: number;\n /** All required steps done (claimed + client configured + ≥1 account). Pub/Sub is optional (poll fallback). */\n complete: boolean;\n}\n\nexport interface PubsubState {\n mode: IngestMode;\n topic: string | null; // projects/{p}/topics/{t}\n subscription: string | null; // projects/{p}/subscriptions/{s}\n /** Last provisioning/health error, human readable, with remediation hint. */\n lastError: string | null;\n /** Last time a Pub/Sub message was pulled (proves the pipeline works end-to-end). */\n lastMessageAt: string | null;\n}\n\nexport interface Settings {\n /** Externally reachable base URL of this instance, e.g. https://mail.example.com. Used in MCP snippets + OAuth redirect. */\n publicUrl: string;\n ingestMode: IngestMode;\n pollIntervalSeconds: number;\n /** Max characters of message body returned to agents per message (0 = unlimited). */\n maxBodyChars: number;\n /** Days to keep events, deliveries and audit entries. */\n retentionDays: number;\n /** Approval requests auto-expire after this many hours. */\n approvalTtlHours: number;\n /**\n * SSRF guard: webhook targets that resolve to private, loopback or link-local addresses are refused\n * unless their host matches one of these entries (hostnames, IPs or CIDRs, e.g. \"127.0.0.0/8\",\n * \"10.0.0.0/8\", \"n8n.internal\"). Defaults to loopback only.\n */\n webhookAllowlist: string[];\n}\n\nexport interface GoogleClientConfig {\n clientId: string;\n /** Write-only: never returned by the API. */\n clientSecret?: string;\n projectId: string;\n appType: GoogleAppType;\n}\n\n/** Returned by GET /api/google/client. */\nexport interface GoogleClientView {\n clientId: string | null;\n hasSecret: boolean;\n projectId: string | null;\n appType: GoogleAppType | null;\n /** The exact redirect URI the operator must register on the OAuth client. */\n redirectUri: string;\n /**\n * \"direct\": Google redirects straight back to this console (localhost or https publicUrl).\n * \"paste\": publicUrl is plain-http non-localhost; Google can't redirect here, so the console\n * asks the user to paste the URL they land on after consent.\n */\n callbackMode: \"direct\" | \"paste\";\n}\n\n// ───────────────────────────── Accounts ─────────────────────────────\n\nexport interface Account {\n id: string; // acc_…\n email: string;\n displayName: string | null;\n status: AccountStatus;\n lastError: string | null;\n historyId: string | null;\n watchExpiresAt: string | null;\n lastSyncAt: string | null;\n createdAt: string;\n}\n\nexport interface GmailLabel {\n id: string; // Gmail label id, e.g. \"INBOX\" or \"Label_123\"\n name: string;\n type: \"system\" | \"user\";\n}\n\n/** POST /api/accounts/connect → start OAuth. */\nexport interface PubsubTestResult {\n ok: boolean;\n message: string;\n}\n\nexport interface ConnectAccountResponse {\n authUrl: string;\n /** Echoed state; in paste mode the console POSTs the pasted URL to /api/accounts/connect/complete. */\n state: string;\n callbackMode: \"direct\" | \"paste\";\n}\n\n// ───────────────────────────── Agents & grants ─────────────────────────────\n\nexport interface Agent {\n id: string; // agt_…\n name: string;\n description: string | null;\n /** First chars of the key, for identification (e.g. \"aib_7Hk2\"). The full key is shown once at creation/rotation. */\n keyPrefix: string;\n disabled: boolean;\n lastUsedAt: string | null;\n createdAt: string;\n grants: Grant[];\n}\n\nexport interface Grant {\n id: string; // grt_…\n agentId: string;\n accountId: string;\n capabilities: Capability[];\n /** Gmail label ids the agent is confined to (thread-level: a thread is visible if any message carries one). null = whole mailbox. */\n labelScope: string[] | null;\n sendMode: SendMode;\n /** Recipient allowlist: exact addresses or \"*@domain.com\" globs. Empty = any recipient. */\n allowTo: string[];\n /** Only allow sending replies inside threads the agent can already see. */\n replyOnly: boolean;\n maxSendsPerHour: number | null;\n maxSendsPerDay: number | null;\n createdAt: string;\n}\n\nexport type GrantInput = Omit<Grant, \"id\" | \"agentId\" | \"createdAt\">;\n\nexport interface CreateAgentInput {\n name: string;\n description?: string | null;\n grants?: GrantInput[];\n}\n\n/** POST /api/agents/:id/test: runs MCP initialize + tools/list as the agent, in-process. */\nexport interface AgentTestResult {\n ok: boolean;\n tools: string[];\n message: string;\n}\n\n/** Returned once on create and on key rotation. */\nexport interface AgentWithKey {\n agent: Agent;\n apiKey: string; // aib_…\n}\n\n// ───────────────────────────── Events ─────────────────────────────\n\nexport interface Address {\n name: string | null;\n email: string;\n}\n\n/** Normalized message metadata carried in events. No bodies are stored server-side. */\nexport interface MessageSummary {\n id: string; // Gmail message id\n threadId: string;\n from: Address | null;\n to: Address[];\n cc: Address[];\n subject: string;\n snippet: string;\n date: string; // ISO\n labelIds: string[];\n hasAttachments: boolean;\n}\n\nexport interface MailEvent {\n id: string; // evt_…\n seq: number; // monotonic, used as cursor\n type: EventType;\n accountId: string;\n accountEmail: string;\n message: MessageSummary | null;\n /** For label.added / label.removed. */\n labelIds?: string[];\n /** Free-form detail for account/sync events. */\n detail?: Record<string, unknown>;\n createdAt: string;\n}\n\n// ───────────────────────────── Webhooks ─────────────────────────────\n\nexport interface WebhookFilter {\n accountIds?: string[];\n /** Match if the message has any of these label ids. */\n labelIds?: string[];\n /** Glob on sender address, e.g. \"*@stripe.com\". Any match passes. */\n from?: string[];\n /** Glob on any to/cc address. */\n to?: string[];\n /** Case-insensitive regular expression tested against the subject. */\n subject?: string;\n hasAttachments?: boolean;\n}\n\nexport interface Webhook {\n id: string; // whk_…\n agentId: string;\n name: string;\n url: string;\n eventTypes: EventType[];\n filter: WebhookFilter;\n /** Include the cleaned message body (markdown) in the payload. */\n includeBody: boolean;\n enabled: boolean;\n /** Shown once at creation and on rotation (\"whsec_…\", Standard Webhooks format). */\n secretPrefix: string;\n createdAt: string;\n stats: { succeeded24h: number; failed24h: number; lastDeliveryAt: string | null };\n}\n\nexport type WebhookInput = Omit<Webhook, \"id\" | \"secretPrefix\" | \"createdAt\" | \"stats\">;\n\nexport interface WebhookWithSecret {\n webhook: Webhook;\n secret: string;\n}\n\nexport interface Delivery {\n id: string; // dlv_…\n webhookId: string;\n eventId: string;\n eventType: EventType;\n status: DeliveryStatus;\n attempts: number;\n nextAttemptAt: string | null;\n lastStatusCode: number | null;\n lastError: string | null;\n /** First ~500 chars of the receiver's response body. */\n lastResponse: string | null;\n durationMs: number | null;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * Webhook HTTP request body. Signed per https://www.standardwebhooks.com:\n * headers webhook-id, webhook-timestamp, webhook-signature (\"v1,<base64 hmac-sha256>\").\n */\nexport interface WebhookPayload {\n type: EventType;\n timestamp: string;\n data: MailEvent & {\n /** Present only when the webhook has includeBody and the event concerns a message. */\n content?: MessageContent;\n };\n}\n\n// ───────────────────────────── Message content (returned to agents) ─────────────────────────────\n\n/** Signals attached to content. Informational: the content itself is still delivered (cleaned, not censored). */\nexport interface ContentSignals {\n /** Invisible content (display:none, zero-size/transparent text, HTML comments, zero-width/tag unicode) was removed. */\n hiddenContentRemoved: boolean;\n /** Phrases that commonly appear in prompt-injection attempts (\"ignore previous instructions\", …). */\n suspiciousInstructions: string[];\n /** SPF/DKIM/DMARC results parsed from Authentication-Results. null when the header is absent. */\n auth: { spf: string | null; dkim: string | null; dmarc: string | null } | null;\n /** Sender is outside the account's own domain. */\n external: boolean;\n}\n\nexport interface Attachment {\n id: string; // Gmail attachment id\n filename: string;\n mimeType: string;\n size: number;\n}\n\nexport interface MessageContent {\n /** Body converted to markdown (from HTML) or the plain-text part. */\n body: string;\n truncated: boolean;\n attachments: Attachment[];\n signals: ContentSignals;\n}\n\n// ───────────────────────────── Approvals ─────────────────────────────\n\nexport interface Approval {\n id: string; // apr_…\n agentId: string;\n agentName: string;\n accountId: string;\n accountEmail: string;\n kind: \"send\" | \"reply\" | \"forward\";\n status: ApprovalStatus;\n draftId: string;\n to: Address[];\n cc: Address[];\n bcc: Address[];\n subject: string;\n /** Full body as the agent wrote it (markdown/plain). */\n body: string;\n threadId: string | null;\n attachments: { filename: string; size: number }[];\n /** Why the agent says it is sending (optional `reason` tool arg). */\n reason: string | null;\n decidedAt: string | null;\n decisionNote: string | null;\n sentMessageId: string | null;\n error: string | null;\n createdAt: string;\n expiresAt: string;\n}\n\n// ───────────────────────────── Audit ─────────────────────────────\n\nexport interface AuditEntry {\n id: number;\n ts: string;\n actorType: ActorType;\n actorId: string | null;\n actorName: string | null;\n action: string; // MCP tool name or console action, e.g. \"send_message\", \"agent.create\"\n accountId: string | null;\n target: string | null; // message/thread/draft id\n outcome: AuditOutcome;\n /** For denials: the guardrail that blocked it, e.g. \"allow_to\", \"label_scope\", \"rate_limit\". */\n reason: string | null;\n durationMs: number | null;\n detail: Record<string, unknown> | null;\n}\n\n// ───────────────────────────── Overview ─────────────────────────────\n\nexport interface OverviewStats {\n accounts: { total: number; active: number; needsAttention: number };\n agents: { total: number; activeLast24h: number };\n events24h: number;\n deliveries24h: { succeeded: number; failed: number };\n pendingApprovals: number;\n toolCalls24h: number;\n denied24h: number;\n /** Hourly buckets for the last 24h, oldest first. */\n activity: { hour: string; events: number; toolCalls: number }[];\n}\n\n// ───────────────────────────── Pagination & errors ─────────────────────────────\n\nexport interface Page<T> {\n items: T[];\n /** Opaque cursor for the next page; null when exhausted. */\n nextCursor: string | null;\n}\n\n/** All non-2xx console API responses have this body. */\nexport interface ApiError {\n error: { code: string; message: string; hint?: string };\n}\n\n// ───────────────────────────── Console API route map ─────────────────────────────\n// Session: cookie `aib_session` (HttpOnly, SameSite=Lax). Mutating requests must send header\n// `x-agentinbox-csrf: 1` (simple CSRF guard; the console fetch wrapper always sets it).\n//\n// Unauthenticated:\n// GET /api/health → HealthResponse\n// GET /api/setup/status → SetupStatus\n// POST /api/setup/claim {token,password} → 204 + session cookie (only while unclaimed)\n// POST /api/auth/login {password} → 204 + session cookie (5 failures/IP → 15 min lockout)\n// POST /api/auth/logout → 204\n// GET /oauth/callback?code&state → HTML (redirects to console /accounts?connected=…)\n//\n// Authenticated (admin session):\n// GET /api/setup/state → SetupState\n// GET /api/system → SystemInfo\n// GET /api/system/latest → UpdateInfo (npm registry check, cached 30 min)\n// GET /api/settings → Settings\n// PATCH /api/settings Partial<Settings> → Settings\n// POST /api/auth/password {current,next} → 204\n// GET /api/google/client → GoogleClientView\n// PUT /api/google/client GoogleClientConfig → GoogleClientView\n// POST /api/google/pubsub/provision → PubsubState (creates topic + IAM + subscription using a connected account's token)\n// POST /api/google/pubsub/test → PubsubTestResult\n// GET /api/accounts → Account[]\n// POST /api/accounts/connect → ConnectAccountResponse\n// POST /api/accounts/connect/complete {url} → Account (paste mode)\n// POST /api/accounts/:id/resync → Account\n// POST /api/accounts/:id/pause | /resume → Account\n// DELETE /api/accounts/:id → 204 (stops watch, revokes token, deletes grants)\n// GET /api/accounts/:id/labels → GmailLabel[]\n// GET /api/agents → Agent[]\n// POST /api/agents CreateAgentInput → AgentWithKey\n// GET /api/agents/:id → Agent\n// PATCH /api/agents/:id {name?,description?,disabled?} → Agent\n// POST /api/agents/:id/rotate-key → AgentWithKey\n// DELETE /api/agents/:id → 204\n// PUT /api/agents/:id/grants/:accountId GrantInput → Grant (upsert; one grant per agent×account)\n// DELETE /api/agents/:id/grants/:accountId → 204\n// POST /api/agents/:id/test → AgentTestResult (runs MCP initialize+tools/list as the agent)\n// GET /api/webhooks?agentId → Webhook[]\n// POST /api/webhooks WebhookInput → WebhookWithSecret\n// PATCH /api/webhooks/:id Partial<WebhookInput> → Webhook\n// POST /api/webhooks/:id/rotate-secret → WebhookWithSecret\n// POST /api/webhooks/:id/test → Delivery (sends a synthetic event synchronously)\n// DELETE /api/webhooks/:id → 204\n// GET /api/deliveries?webhookId&status&cursor → Page<Delivery>\n// POST /api/deliveries/:id/replay → Delivery\n// GET /api/events?accountId&type&cursor → Page<MailEvent>\n// GET /api/events/stream → text/event-stream of ConsoleStreamMessage (SSE `data:` = JSON)\n// GET /api/approvals?status → Approval[]\n// POST /api/approvals/:id/approve {note?} → Approval\n// POST /api/approvals/:id/reject {note?} → Approval\n// GET /api/audit?agentId&outcome&cursor → Page<AuditEntry>\n// GET /api/overview → OverviewStats\n//\n// Agent-facing (bearer = agent API key):\n// POST/GET/DELETE /mcp MCP streamable HTTP (stateless)\n// GET /skill.md the agent skill (public, no secrets)\n// GET /health HealthResponse (alias of /api/health for uptime monitors; 503 when degraded)\n\nexport type ConsoleStreamMessage =\n | { kind: \"event\"; data: MailEvent }\n | { kind: \"delivery\"; data: Delivery }\n | { kind: \"approval\"; data: Approval }\n | { kind: \"audit\"; data: AuditEntry }\n | { kind: \"account\"; data: Account };\n","import { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { NPM_PACKAGE } from \"../shared/types.ts\";\nimport type { Config } from \"./config.ts\";\nimport { Crypto } from \"./crypto.ts\";\n\n/**\n * How to run this CLI in printed hints: `npx agentinbox-cli` when run through npx (and in service\n * logs, read by someone who most likely installed that way), else the installed `agentinbox`.\n */\nexport function cliCommand(args: string): string {\n const viaNpx = process.env.npm_command === \"exec\" || /[\\\\/]_npx[\\\\/]/.test(process.argv[1] ?? \"\") || process.env.AGENTINBOX_SERVICE === \"1\";\n return `${viaNpx ? `npx ${NPM_PACKAGE}` : \"agentinbox\"} ${args}`;\n}\n\n// ───────────── Single instance per data directory ─────────────\n\nexport interface InstanceInfo {\n pid: number;\n host: string;\n port: number;\n startedAt: string;\n /** Kernel boot id (Linux), so a lock from before a reboot is never mistaken for a live one. */\n bootId?: string | null;\n /** The process's own start time as the OS reports it, so a reused pid doesn't look alive. */\n processStart?: string | null;\n}\n\nexport const lockPath = (dataDir: string) => join(dataDir, \"agentinbox.lock\");\n\n/** Data dirs locked by this process: a lock naming our own pid is live only if it's in here. */\nconst heldByUs = new Set<string>();\n\nexport function bootId(): string | null {\n try {\n return readFileSync(\"/proc/sys/kernel/random/boot_id\", \"utf8\").trim() || null;\n } catch {\n return null;\n }\n}\n\n/** Process start time: /proc (Linux, in clock ticks since boot) or `ps` elsewhere; null if unknown. */\nexport function processStartTime(pid: number): string | null {\n try {\n const stat = readFileSync(`/proc/${pid}/stat`, \"utf8\");\n // Fields after the parenthesized command name; starttime is field 22, i.e. index 19 here.\n return stat.slice(stat.lastIndexOf(\")\") + 2).split(\" \")[19] ?? null;\n } catch {\n // No /proc (macOS) or no such process.\n }\n if (process.platform === \"linux\" || process.platform === \"win32\") return null;\n try {\n return execFileSync(\"ps\", [\"-o\", \"lstart=\", \"-p\", String(pid)], { encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"ignore\"] }).trim() || null;\n } catch {\n return null;\n }\n}\n\nfunction isAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * Whether the process a lock names is still the one that wrote it. Our own pid is live only if we\n * hold the lock (in containers every start is pid 1); otherwise the pid must exist, on the same\n * boot, with the same start time (when both are known).\n */\nfunction isLive(info: InstanceInfo, dataDir: string): boolean {\n if (info.pid === process.pid) return heldByUs.has(dataDir);\n if (!isAlive(info.pid)) return false;\n const boot = bootId();\n if (info.bootId && boot && info.bootId !== boot) return false;\n if (info.processStart) {\n const start = processStartTime(info.pid);\n if (start && start !== info.processStart) return false;\n }\n return true;\n}\n\nfunction readLock(dataDir: string): InstanceInfo | null {\n try {\n const info = JSON.parse(readFileSync(lockPath(dataDir), \"utf8\")) as InstanceInfo;\n return Number.isInteger(info.pid) && info.pid > 0 ? info : null;\n } catch {\n return null;\n }\n}\n\n/** The running instance for this data directory, if any. */\nexport function runningInstance(dataDir: string): InstanceInfo | null {\n const info = readLock(dataDir);\n return info && isLive(info, dataDir) ? info : null;\n}\n\nexport interface InstanceLock {\n /** Records the bound port (for `reset-password` and error messages). */\n update(port: number): void;\n release(): void;\n}\n\n/**\n * Takes `<data>/agentinbox.lock`. Two processes on one data directory would both run ingest and\n * deliver every webhook twice, even on different ports. A lock whose process is gone (dead,\n * rebooted, or its pid reused by something else) is replaced.\n */\nexport function acquireInstanceLock(config: Pick<Config, \"dataDir\" | \"host\" | \"port\">): InstanceLock {\n const dataDir = config.dataDir;\n const file = lockPath(dataDir);\n const info: InstanceInfo = {\n pid: process.pid,\n host: config.host,\n port: config.port,\n startedAt: new Date().toISOString(),\n bootId: bootId(),\n processStart: processStartTime(process.pid),\n };\n for (let attempt = 0; ; attempt++) {\n try {\n writeFileSync(file, JSON.stringify(info), { flag: \"wx\", mode: 0o600 });\n break;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\" || attempt > 0) throw err;\n const other = runningInstance(dataDir);\n if (other) {\n throw new Error(\n `agentinbox is already running with the data directory ${dataDir} (pid ${other.pid}${other.port ? `, port ${other.port}` : \"\"}). ` +\n `Stop it first, or use another --data directory. If no such process exists, delete ${file}.`,\n );\n }\n rmSync(file, { force: true });\n }\n }\n heldByUs.add(dataDir);\n const ours = () => {\n const current = readLock(dataDir);\n return current?.pid === process.pid && current.startedAt === info.startedAt;\n };\n return {\n update(port) {\n info.port = port;\n if (ours()) writeFileSync(file, JSON.stringify(info), { mode: 0o600 });\n },\n release() {\n if (ours()) rmSync(file, { force: true });\n heldByUs.delete(dataDir);\n },\n };\n}\n\n// ───────────── Master key guard ─────────────\n\n/** One ciphertext from the database, or null when nothing encrypted is stored. */\nfunction sampleCiphertext(dbPath: string): string | null {\n if (!existsSync(dbPath)) return null;\n const db = new DatabaseSync(dbPath);\n try {\n const queries = [\n \"SELECT refresh_token_enc AS v FROM accounts WHERE refresh_token_enc LIKE 'v1:%' LIMIT 1\",\n \"SELECT secret_enc AS v FROM webhooks WHERE secret_enc LIKE 'v1:%' LIMIT 1\",\n \"SELECT json_extract(value, '$.clientSecretEnc') AS v FROM settings WHERE key = 'google.client'\",\n ];\n for (const sql of queries) {\n try {\n const v = (db.prepare(sql).get() as { v?: unknown } | undefined)?.v;\n if (typeof v === \"string\" && v.startsWith(\"v1:\")) return v;\n } catch {\n // Table not created yet.\n }\n }\n return null;\n } finally {\n db.close();\n }\n}\n\n/**\n * Refuses to start with a master key that can't read the stored secrets: a missing master.key would\n * otherwise be regenerated, silently making every refresh token and webhook secret unreadable.\n * `envKey` overrides AGENTINBOX_MASTER_KEY (used by `service install` to check the key it will install).\n */\nexport function assertMasterKeyUsable(config: Pick<Config, \"dbPath\" | \"masterKeyPath\">, envKey = process.env.AGENTINBOX_MASTER_KEY): void {\n const sample = sampleCiphertext(config.dbPath);\n if (!sample) return;\n let raw: string | null = envKey || null;\n const source = raw ? \"AGENTINBOX_MASTER_KEY\" : config.masterKeyPath;\n if (!raw && existsSync(config.masterKeyPath)) raw = readFileSync(config.masterKeyPath, \"utf8\").trim();\n if (!raw) {\n throw new Error(\n `${config.dbPath} holds encrypted secrets, but there is no master key: ${config.masterKeyPath} is missing and AGENTINBOX_MASTER_KEY is not set. ` +\n \"Restore master.key from your backup (or set AGENTINBOX_MASTER_KEY to the key used before). \" +\n \"agentinbox won't create a new key, because the stored Google tokens and secrets could never be read again.\",\n );\n }\n try {\n new Crypto(Buffer.from(raw, \"base64\")).decrypt(sample);\n } catch {\n throw new Error(\n `The master key from ${source} can't decrypt the secrets stored in ${config.dbPath}. ` +\n \"Use the key this data directory was created with (master.key from your backup, or the previous AGENTINBOX_MASTER_KEY).\",\n );\n }\n}\n"],"mappings":"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"}
|