agentinbox-cli 0.0.0 → 0.1.0-nightly.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +337 -0
- package/dist/admin-CXkmdTYo.js +196 -0
- package/dist/admin-CXkmdTYo.js.map +1 -0
- package/dist/audit-bItbJYFP.js +65 -0
- package/dist/audit-bItbJYFP.js.map +1 -0
- package/dist/audit-gVdDzkAE.js +2 -0
- package/dist/auth-0NrMvHdE.js +550 -0
- package/dist/auth-0NrMvHdE.js.map +1 -0
- package/dist/auth-CoHvbWgO.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 +220 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands-DZ60j8_y.js +103 -0
- package/dist/commands-DZ60j8_y.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/connect-BHC2trBi.js +292 -0
- package/dist/connect-BHC2trBi.js.map +1 -0
- package/dist/content-BXblC_Qs.js +1162 -0
- package/dist/content-BXblC_Qs.js.map +1 -0
- package/dist/content-worker.js +28 -0
- package/dist/content-worker.js.map +1 -0
- package/dist/crypto-DYZ4R7c2.js +74 -0
- package/dist/crypto-DYZ4R7c2.js.map +1 -0
- package/dist/db-B6gLQGBK.js +2 -0
- package/dist/db-D0gvshCc.js +282 -0
- package/dist/db-D0gvshCc.js.map +1 -0
- package/dist/errors-FbMS3QBo.js +39 -0
- package/dist/errors-FbMS3QBo.js.map +1 -0
- package/dist/filter-DGVz6-P1.js +140 -0
- package/dist/filter-DGVz6-P1.js.map +1 -0
- package/dist/instance-BBzEsYZO.js +175 -0
- package/dist/instance-BBzEsYZO.js.map +1 -0
- package/dist/instance-DLNTj9nm.js +2 -0
- package/dist/listen-D8DkEhqM.js +1724 -0
- package/dist/listen-D8DkEhqM.js.map +1 -0
- package/dist/listen-DT-omyD6.js +2 -0
- package/dist/mcpheader-By2dIyZq.js +2 -0
- package/dist/mcpheader-D9rrI-aa.js +65 -0
- package/dist/mcpheader-D9rrI-aa.js.map +1 -0
- package/dist/mcpsetup-7KxroOCp.js +2 -0
- package/dist/mcpsetup-DJ7ebFGJ.js +266 -0
- package/dist/mcpsetup-DJ7ebFGJ.js.map +1 -0
- package/dist/paths-BCBp8vrG.js +2 -0
- package/dist/paths-TLp8hiMt.js +40 -0
- package/dist/paths-TLp8hiMt.js.map +1 -0
- package/dist/profile-CgGgJ4Dm.js +181 -0
- package/dist/profile-CgGgJ4Dm.js.map +1 -0
- package/dist/profile-RbN2FRz7.js +2 -0
- package/dist/recipes-7d_Xws4M.js +2 -0
- package/dist/recipes-UalOc_LB.js +100 -0
- package/dist/recipes-UalOc_LB.js.map +1 -0
- package/dist/send-Dk-t8ia-.js +108 -0
- package/dist/send-Dk-t8ia-.js.map +1 -0
- package/dist/server-Hunjpt8n.js +8103 -0
- package/dist/server-Hunjpt8n.js.map +1 -0
- package/dist/service-BAgZDh22.js +2 -0
- package/dist/service-DYdD92CC.js +2 -0
- package/dist/service-D_ZEs3Zk.js +508 -0
- package/dist/service-D_ZEs3Zk.js.map +1 -0
- package/dist/service-whbugeQp.js +295 -0
- package/dist/service-whbugeQp.js.map +1 -0
- package/dist/settings-CeEsv4CE.js +2 -0
- package/dist/settings-DLUStIN7.js +109 -0
- package/dist/settings-DLUStIN7.js.map +1 -0
- package/dist/setup-token-BO89xnpb.js +48 -0
- package/dist/setup-token-BO89xnpb.js.map +1 -0
- package/dist/skill/SKILL.md +277 -0
- package/dist/skill-BJFn2bHK.js +2 -0
- package/dist/skill-WKbeyNct.js +43 -0
- package/dist/skill-WKbeyNct.js.map +1 -0
- package/dist/types-3eEsBFVj.js +37 -0
- package/dist/types-3eEsBFVj.js.map +1 -0
- package/dist/ui-B8Sd4Y7Y.js +81 -0
- package/dist/ui-B8Sd4Y7Y.js.map +1 -0
- package/dist/urls-Dp8pBGMU.js +2 -0
- package/dist/urls-U_LfYV61.js +27 -0
- package/dist/urls-U_LfYV61.js.map +1 -0
- package/dist/web/assets/_agentId-DAj08NDq.js +1 -0
- package/dist/web/assets/_app-B6q_HS9y.js +1 -0
- package/dist/web/assets/_app-CAQ6Mu1s.js +63 -0
- package/dist/web/assets/_webhookId-CMgBUN2Z.js +1 -0
- package/dist/web/assets/account-status-omoUlgBI.js +1 -0
- package/dist/web/assets/accounts-DyLfACj_.js +1 -0
- package/dist/web/assets/accounts-list-DiXACW0-.js +1 -0
- package/dist/web/assets/activity-zrckFhPk.js +1 -0
- package/dist/web/assets/agent-create-form-DKwaY1Dg.js +1 -0
- package/dist/web/assets/agents-Drvjccgt.js +1 -0
- package/dist/web/assets/approvals-4kd0_J8X.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-76H45orR.js +1 -0
- package/dist/web/assets/audit-bits-B1We-fQ3.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--xgMZpg2.js +1 -0
- package/dist/web/assets/circle-x-Bi4IGwky.js +1 -0
- package/dist/web/assets/common-PG6weTqi.js +1 -0
- package/dist/web/assets/connect-host-DdkOJnMu.js +1 -0
- package/dist/web/assets/data-table-Ca9aQZKR.js +1 -0
- package/dist/web/assets/dialog-CxNRCZdE.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-2xoyzgbP.js +1 -0
- package/dist/web/assets/events-Bizq3DPC.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-DXUVmyNX.js +1 -0
- package/dist/web/assets/grants-BhXj1Pew.js +1 -0
- package/dist/web/assets/index-Bd4Y5wSp.js +10 -0
- package/dist/web/assets/index-akvt_1UI.css +2 -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-aCr-cPqJ.js +1 -0
- package/dist/web/assets/mail-CAVmuESo.js +1 -0
- package/dist/web/assets/mutations-DVlbDx0M.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-C8tJBHb5.js +1 -0
- package/dist/web/assets/send-BhQwhuJo.js +1 -0
- package/dist/web/assets/settings-H7ExHD9L.js +1 -0
- package/dist/web/assets/settings-form-CNUionsI.js +1 -0
- package/dist/web/assets/setup-DLbH5Dvl.js +1 -0
- package/dist/web/assets/sheet-BQKpA2xa.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-C2Tr2d5O.js +1 -0
- package/dist/web/assets/tabs-ySiz7Mni.js +1 -0
- package/dist/web/assets/tag-CP72Kk6Y.js +1 -0
- package/dist/web/assets/test-connection-Bd2Gac0O.js +1 -0
- package/dist/web/assets/textarea-CCokBZIw.js +1 -0
- package/dist/web/assets/toggle-group-ne0pVDg6.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-DeWvLw1a.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-Cy_jHIqG.js +34 -0
- package/dist/web/assets/webhooks-CTAj5Ovp.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 +95 -1
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as Crypto } from "./crypto-DYZ4R7c2.js";
|
|
3
|
+
import { r as readSetupToken } from "./setup-token-BO89xnpb.js";
|
|
4
|
+
import { r as NPM_PACKAGE } from "./types-3eEsBFVj.js";
|
|
5
|
+
import { i as cliCommand, n as assertMasterKeyUsable } from "./instance-BBzEsYZO.js";
|
|
6
|
+
import { i as loopbackUrl, t as consoleUrls } from "./urls-U_LfYV61.js";
|
|
7
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { homedir, tmpdir, userInfo } from "node:os";
|
|
9
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
10
|
+
import { execFileSync } from "node:child_process";
|
|
11
|
+
//#region server/service.ts
|
|
12
|
+
/** Installs agentinbox as a background service: systemd on Linux, launchd on macOS. */
|
|
13
|
+
const SERVICE_NAME = "agentinbox";
|
|
14
|
+
const LAUNCHD_LABEL = "com.unlaboredlabs.agentinbox";
|
|
15
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
16
|
+
/** Package root: one level above the running module (dist/cli.js or server/service.ts). */
|
|
17
|
+
const ROOT = resolve(import.meta.dirname, "..");
|
|
18
|
+
/**
|
|
19
|
+
* Settings that come from the environment rather than flags. They are kept in `<data>/service.env`
|
|
20
|
+
* (mode 0600) so re-running `service install` from a shell without them (the documented update
|
|
21
|
+
* path) keeps them. Set one to an empty value to drop it.
|
|
22
|
+
*/
|
|
23
|
+
const SERVICE_ENV_KEYS = [
|
|
24
|
+
"AGENTINBOX_MASTER_KEY",
|
|
25
|
+
"TRUST_PROXY",
|
|
26
|
+
"TRUST_PROXY_HOPS",
|
|
27
|
+
"TRUST_PROXY_FROM",
|
|
28
|
+
"AGENTINBOX_LOG_LEVEL"
|
|
29
|
+
];
|
|
30
|
+
const serviceEnvPath = (dataDir) => join(dataDir, "service.env");
|
|
31
|
+
/** Plain variables written into the unit/plist. Secrets never go into the systemd unit. */
|
|
32
|
+
function plainEnv(spec) {
|
|
33
|
+
return {
|
|
34
|
+
AGENTINBOX_DATA: spec.dataDir,
|
|
35
|
+
PORT: String(spec.port),
|
|
36
|
+
HOST: spec.host,
|
|
37
|
+
AGENTINBOX_SERVICE: "1"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** systemd word quoting for ExecStart/Environment: double quotes, escaped backslashes, quotes and specifiers. */
|
|
41
|
+
const sdQuote = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/%/g, "%%")}"`;
|
|
42
|
+
/**
|
|
43
|
+
* The systemd unit. Everything from service.env (including AGENTINBOX_MASTER_KEY) is loaded with
|
|
44
|
+
* EnvironmentFile=, so the world-readable unit holds no secrets. EnvironmentFile takes an
|
|
45
|
+
* unquoted absolute path (systemd ignores quoted ones), which is why data dirs with whitespace are refused.
|
|
46
|
+
*/
|
|
47
|
+
function renderSystemdUnit(spec) {
|
|
48
|
+
const vars = Object.entries(plainEnv(spec)).map(([k, v]) => `Environment=${sdQuote(`${k}=${v}`)}`).join("\n");
|
|
49
|
+
const envFile = Object.keys(spec.extraEnv).length ? `\nEnvironmentFile=${serviceEnvPath(spec.dataDir).replace(/%/g, "%%")}` : "";
|
|
50
|
+
return `[Unit]
|
|
51
|
+
Description=agentinbox (Gmail broker for AI agents)${spec.system ? "\nAfter=network-online.target\nWants=network-online.target" : ""}
|
|
52
|
+
|
|
53
|
+
[Service]
|
|
54
|
+
ExecStart=${sdQuote(spec.execPath)} ${sdQuote(spec.entry)} start --no-open
|
|
55
|
+
${vars}${envFile}
|
|
56
|
+
Restart=on-failure
|
|
57
|
+
RestartSec=3
|
|
58
|
+
TimeoutStopSec=20
|
|
59
|
+
|
|
60
|
+
[Install]
|
|
61
|
+
WantedBy=${spec.system ? "multi-user.target" : "default.target"}
|
|
62
|
+
`;
|
|
63
|
+
}
|
|
64
|
+
/** The launchd plist. Written with mode 0600, since it may carry AGENTINBOX_MASTER_KEY. */
|
|
65
|
+
/** Escapes text for a plist <string>. */
|
|
66
|
+
const xmlEscape = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
67
|
+
function renderLaunchdPlist(spec) {
|
|
68
|
+
const esc = xmlEscape;
|
|
69
|
+
const vars = Object.entries({
|
|
70
|
+
...plainEnv(spec),
|
|
71
|
+
...spec.extraEnv
|
|
72
|
+
}).map(([k, v]) => ` <key>${esc(k)}</key><string>${esc(v)}</string>`).join("\n");
|
|
73
|
+
const log = esc(join(spec.dataDir, "agentinbox.log"));
|
|
74
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
75
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
76
|
+
<plist version="1.0">
|
|
77
|
+
<dict>
|
|
78
|
+
<key>Label</key><string>${LAUNCHD_LABEL}</string>
|
|
79
|
+
<key>ProgramArguments</key>
|
|
80
|
+
<array>
|
|
81
|
+
<string>${esc(spec.execPath)}</string>
|
|
82
|
+
<string>${esc(spec.entry)}</string>
|
|
83
|
+
<string>start</string>
|
|
84
|
+
<string>--no-open</string>
|
|
85
|
+
</array>
|
|
86
|
+
<key>EnvironmentVariables</key>
|
|
87
|
+
<dict>
|
|
88
|
+
${vars}
|
|
89
|
+
</dict>
|
|
90
|
+
<key>RunAtLoad</key><true/>
|
|
91
|
+
<key>KeepAlive</key><true/>
|
|
92
|
+
<key>StandardOutPath</key><string>${log}</string>
|
|
93
|
+
<key>StandardErrorPath</key><string>${log}</string>
|
|
94
|
+
</dict>
|
|
95
|
+
</plist>
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
/** Parses service.env (KEY=value lines). */
|
|
99
|
+
function parseEnvFile(text) {
|
|
100
|
+
const out = {};
|
|
101
|
+
for (const line of text.split("\n")) {
|
|
102
|
+
const m = /^\s*([A-Z_][A-Z0-9_]*)=(.*)$/.exec(line);
|
|
103
|
+
if (m) out[m[1]] = m[2].trim();
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The service's env-derived settings: the current shell wins, previous install fills the gaps,
|
|
109
|
+
* an explicitly empty value removes the setting.
|
|
110
|
+
*/
|
|
111
|
+
function mergeServiceEnv(previous, current) {
|
|
112
|
+
const out = {};
|
|
113
|
+
for (const key of SERVICE_ENV_KEYS) {
|
|
114
|
+
const value = key in current ? current[key] : previous[key];
|
|
115
|
+
if (value) out[key] = key === "TRUST_PROXY_FROM" ? value.split(/[\s,]+/).filter(Boolean).join(",") : value;
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
/** Fails early (before anything is touched) when the master key can't work. */
|
|
120
|
+
function validateMasterKey(value) {
|
|
121
|
+
if (!value) return;
|
|
122
|
+
try {
|
|
123
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(value.trim())) throw new Error("not base64");
|
|
124
|
+
new Crypto(Buffer.from(value, "base64"));
|
|
125
|
+
} catch {
|
|
126
|
+
throw new Error("AGENTINBOX_MASTER_KEY must be 32 random bytes in base64 (e.g. `openssl rand -base64 32`).");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function readPreviousEnv(dataDir) {
|
|
130
|
+
try {
|
|
131
|
+
return parseEnvFile(readFileSync(serviceEnvPath(dataDir), "utf8"));
|
|
132
|
+
} catch {
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function writeServiceEnv(dataDir, env) {
|
|
137
|
+
const file = serviceEnvPath(dataDir);
|
|
138
|
+
if (!Object.keys(env).length) {
|
|
139
|
+
rmSync(file, { force: true });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
for (const [k, v] of Object.entries(env)) if (/[\r\n]/.test(v)) throw new Error(`${k} must be a single line.`);
|
|
143
|
+
writeFileSync(file, Object.entries(env).map(([k, v]) => `${k}=${v}\n`).join(""), { mode: 384 });
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Installs the package into `staging` (a fresh private npm prefix), so a failed npm install leaves the
|
|
147
|
+
* current install untouched. From an installed package that is the latest release; from a source
|
|
148
|
+
* checkout, the local build. Returns `staging`. The one install path for the daemon and the listeners.
|
|
149
|
+
*/
|
|
150
|
+
function stagePackage(staging, opts) {
|
|
151
|
+
const fromSource = basename(dirname(ROOT)) !== "node_modules";
|
|
152
|
+
let spec = `${NPM_PACKAGE}@latest`;
|
|
153
|
+
let tmp;
|
|
154
|
+
if (fromSource) {
|
|
155
|
+
if (!existsSync(join(ROOT, "dist/cli.js")) || opts.requireWeb && !existsSync(join(ROOT, "dist/web/index.html"))) throw new Error("Run `pnpm build` before installing the service from a source checkout.");
|
|
156
|
+
tmp = mkdtempSync(join(tmpdir(), "agentinbox-pack-"));
|
|
157
|
+
const out = execFileSync(npm, [
|
|
158
|
+
"pack",
|
|
159
|
+
"--ignore-scripts",
|
|
160
|
+
"--pack-destination",
|
|
161
|
+
tmp,
|
|
162
|
+
"--loglevel=error"
|
|
163
|
+
], {
|
|
164
|
+
cwd: ROOT,
|
|
165
|
+
encoding: "utf8"
|
|
166
|
+
});
|
|
167
|
+
spec = join(tmp, out.trim().split("\n").pop());
|
|
168
|
+
}
|
|
169
|
+
console.log(fromSource ? ` Installing this build into ${opts.where}…` : ` Installing the latest release into ${opts.where}…`);
|
|
170
|
+
rmSync(staging, {
|
|
171
|
+
recursive: true,
|
|
172
|
+
force: true
|
|
173
|
+
});
|
|
174
|
+
mkdirSync(staging, { recursive: true });
|
|
175
|
+
writeFileSync(join(staging, "package.json"), "{ \"private\": true }\n");
|
|
176
|
+
try {
|
|
177
|
+
execFileSync(npm, [
|
|
178
|
+
"install",
|
|
179
|
+
spec,
|
|
180
|
+
"--omit=dev",
|
|
181
|
+
"--prefer-online",
|
|
182
|
+
"--no-audit",
|
|
183
|
+
"--no-fund",
|
|
184
|
+
"--loglevel=error"
|
|
185
|
+
], {
|
|
186
|
+
cwd: staging,
|
|
187
|
+
stdio: "inherit"
|
|
188
|
+
});
|
|
189
|
+
if (!existsSync(installedEntry(staging))) throw new Error("the installed package has no dist/cli.js");
|
|
190
|
+
} catch (err) {
|
|
191
|
+
rmSync(staging, {
|
|
192
|
+
recursive: true,
|
|
193
|
+
force: true
|
|
194
|
+
});
|
|
195
|
+
throw new Error(`Installing agentinbox failed, so the existing service was left as it was. ${err instanceof Error ? err.message : ""}`.trim());
|
|
196
|
+
} finally {
|
|
197
|
+
if (tmp) rmSync(tmp, {
|
|
198
|
+
recursive: true,
|
|
199
|
+
force: true
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return staging;
|
|
203
|
+
}
|
|
204
|
+
/** The CLI entry inside an install made by stagePackage. */
|
|
205
|
+
const installedEntry = (app) => join(app, "node_modules", NPM_PACKAGE, "dist/cli.js");
|
|
206
|
+
/** Installs into `<dir>/app.new` (see stagePackage). Returns the staging directory. */
|
|
207
|
+
const stageApp = (dir) => stagePackage(join(dir, "app.new"), {
|
|
208
|
+
where: "the data directory",
|
|
209
|
+
requireWeb: true
|
|
210
|
+
});
|
|
211
|
+
/**
|
|
212
|
+
* Moves a staged install into place as `app` (the current one becomes `app.old`). `restore()` puts
|
|
213
|
+
* the previous install back; `commit()` deletes it. If the move itself fails, nothing changes.
|
|
214
|
+
*/
|
|
215
|
+
function swapIn(staging, app) {
|
|
216
|
+
const old = `${app}.old`;
|
|
217
|
+
rmSync(old, {
|
|
218
|
+
recursive: true,
|
|
219
|
+
force: true
|
|
220
|
+
});
|
|
221
|
+
if (existsSync(app)) renameSync(app, old);
|
|
222
|
+
try {
|
|
223
|
+
renameSync(staging, app);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
if (existsSync(old)) renameSync(old, app);
|
|
226
|
+
throw err;
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
restore() {
|
|
230
|
+
rmSync(app, {
|
|
231
|
+
recursive: true,
|
|
232
|
+
force: true
|
|
233
|
+
});
|
|
234
|
+
if (existsSync(old)) renameSync(old, app);
|
|
235
|
+
},
|
|
236
|
+
commit() {
|
|
237
|
+
rmSync(old, {
|
|
238
|
+
recursive: true,
|
|
239
|
+
force: true
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/** Deletes `<dir>/app`, `app.new` and `app.old`, but only where they really are our own installs. */
|
|
245
|
+
function removeOwnInstall(dir) {
|
|
246
|
+
for (const name of [
|
|
247
|
+
"app",
|
|
248
|
+
"app.new",
|
|
249
|
+
"app.old"
|
|
250
|
+
]) {
|
|
251
|
+
const path = join(dir, name);
|
|
252
|
+
if (existsSync(join(path, "node_modules", "agentinbox-cli"))) rmSync(path, {
|
|
253
|
+
recursive: true,
|
|
254
|
+
force: true
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const systemOps = () => {
|
|
259
|
+
const root = process.getuid?.() === 0;
|
|
260
|
+
const ops = {
|
|
261
|
+
platform: process.platform,
|
|
262
|
+
root,
|
|
263
|
+
exec: (cmd, args, opts) => void execFileSync(cmd, args, { stdio: opts?.quiet ? "ignore" : "inherit" }),
|
|
264
|
+
capture: (cmd, args) => execFileSync(cmd, args, {
|
|
265
|
+
encoding: "utf8",
|
|
266
|
+
stdio: [
|
|
267
|
+
"ignore",
|
|
268
|
+
"pipe",
|
|
269
|
+
"pipe"
|
|
270
|
+
]
|
|
271
|
+
}),
|
|
272
|
+
stage: stageApp,
|
|
273
|
+
unitDir: () => {
|
|
274
|
+
if (process.platform === "darwin") return join(homedir(), "Library/LaunchAgents");
|
|
275
|
+
if (root) return "/etc/systemd/system";
|
|
276
|
+
unitDirCache ??= join(userManagerConfigHome(ops) ?? join(homedir(), ".config"), "systemd/user");
|
|
277
|
+
return unitDirCache;
|
|
278
|
+
},
|
|
279
|
+
waitReady: waitForHealth,
|
|
280
|
+
uid: process.getuid?.() ?? 0,
|
|
281
|
+
user: userInfo().username
|
|
282
|
+
};
|
|
283
|
+
let unitDirCache;
|
|
284
|
+
return ops;
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* XDG_CONFIG_HOME as the systemd user manager sees it (where `systemctl --user` looks for units),
|
|
288
|
+
* which can differ from this shell's. Null when unset there or the manager can't be asked.
|
|
289
|
+
*/
|
|
290
|
+
function userManagerConfigHome(ops) {
|
|
291
|
+
try {
|
|
292
|
+
const env = ops.capture("systemctl", ["--user", "show-environment"]);
|
|
293
|
+
return /^XDG_CONFIG_HOME=(.+)$/m.exec(env)?.[1]?.trim() || null;
|
|
294
|
+
} catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const unitFile = (ops) => join(ops.unitDir(), ops.platform === "darwin" ? `${LAUNCHD_LABEL}.plist` : `${SERVICE_NAME}.service`);
|
|
299
|
+
const systemctl = (ops, ...args) => ops.exec("systemctl", ops.root ? args : ["--user", ...args]);
|
|
300
|
+
const launchdTarget = (ops) => `gui/${ops.uid}`;
|
|
301
|
+
function assertSupported(ops) {
|
|
302
|
+
if (ops.platform !== "linux" && ops.platform !== "darwin") throw new Error(`Service install supports Linux (systemd) and macOS (launchd). Elsewhere, install the command (\`npm i -g ${NPM_PACKAGE}\`) and run \`agentinbox start\` under your own supervisor.`);
|
|
303
|
+
}
|
|
304
|
+
const readText = (file) => {
|
|
305
|
+
try {
|
|
306
|
+
return readFileSync(file, "utf8");
|
|
307
|
+
} catch {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
/** Where to look when the service doesn't start. */
|
|
312
|
+
function serviceHints(ops, env = process.env) {
|
|
313
|
+
if (ops.platform === "darwin") return [`launchctl print ${launchdTarget(ops)}/${LAUNCHD_LABEL}`, "and the log file agentinbox.log in the data directory"];
|
|
314
|
+
const scope = ops.root ? "" : " --user";
|
|
315
|
+
const hints = [`systemctl${scope} status ${SERVICE_NAME}`, `journalctl${scope} -u ${SERVICE_NAME} -e`];
|
|
316
|
+
if (!ops.root && !env.XDG_RUNTIME_DIR) hints.push(`XDG_RUNTIME_DIR is not set, so there may be no systemd user session (common over \`su\` or \`sudo -u\`). Log in as ${ops.user} directly (e.g. over ssh), or run \`sudo loginctl enable-linger ${ops.user}\` and then \`export XDG_RUNTIME_DIR=/run/user/${ops.uid}\` before installing.`);
|
|
317
|
+
return hints;
|
|
318
|
+
}
|
|
319
|
+
const firstLine = (err) => (err instanceof Error ? err.message : String(err)).split("\n")[0];
|
|
320
|
+
/** Fails before anything is touched when systemd can't be reached (e.g. no user bus over su). */
|
|
321
|
+
function preflight(ops) {
|
|
322
|
+
if (ops.platform !== "linux") return;
|
|
323
|
+
try {
|
|
324
|
+
systemctl(ops, "daemon-reload");
|
|
325
|
+
} catch (err) {
|
|
326
|
+
const hints = serviceHints(ops).map((h) => ` ${h}`).join("\n");
|
|
327
|
+
throw new Error(`Can't talk to systemd${ops.root ? "" : " (user session)"}: ${firstLine(err)}. Nothing was changed.\n${hints}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async function installService(config, ops = systemOps()) {
|
|
331
|
+
assertSupported(ops);
|
|
332
|
+
if (ops.platform === "linux" && /\s/.test(config.dataDir)) throw new Error(`The data directory "${config.dataDir}" contains whitespace, which systemd can't load settings from. Choose another one with --data.`);
|
|
333
|
+
const extraEnv = mergeServiceEnv(readPreviousEnv(config.dataDir), process.env);
|
|
334
|
+
validateMasterKey(extraEnv.AGENTINBOX_MASTER_KEY);
|
|
335
|
+
assertMasterKeyUsable(config, extraEnv.AGENTINBOX_MASTER_KEY ?? "");
|
|
336
|
+
preflight(ops);
|
|
337
|
+
mkdirSync(config.dataDir, {
|
|
338
|
+
recursive: true,
|
|
339
|
+
mode: 448
|
|
340
|
+
});
|
|
341
|
+
const staging = ops.stage(config.dataDir);
|
|
342
|
+
const file = unitFile(ops);
|
|
343
|
+
const previousUnit = readText(file);
|
|
344
|
+
const previousEnv = readText(serviceEnvPath(config.dataDir));
|
|
345
|
+
const app = join(config.dataDir, "app");
|
|
346
|
+
const entry = installedEntry(app);
|
|
347
|
+
const spec = {
|
|
348
|
+
execPath: process.execPath,
|
|
349
|
+
entry,
|
|
350
|
+
dataDir: config.dataDir,
|
|
351
|
+
port: config.port,
|
|
352
|
+
host: config.host,
|
|
353
|
+
extraEnv,
|
|
354
|
+
system: ops.root
|
|
355
|
+
};
|
|
356
|
+
let swap = null;
|
|
357
|
+
try {
|
|
358
|
+
uninstallService(config, ops, {
|
|
359
|
+
quiet: true,
|
|
360
|
+
keepApp: true
|
|
361
|
+
});
|
|
362
|
+
swap = swapIn(staging, app);
|
|
363
|
+
writeServiceEnv(config.dataDir, extraEnv);
|
|
364
|
+
mkdirSync(ops.unitDir(), { recursive: true });
|
|
365
|
+
if (ops.platform === "linux") {
|
|
366
|
+
writeFileSync(file, renderSystemdUnit(spec));
|
|
367
|
+
systemctl(ops, "daemon-reload");
|
|
368
|
+
systemctl(ops, "enable", "--now", SERVICE_NAME);
|
|
369
|
+
} else {
|
|
370
|
+
writeFileSync(file, renderLaunchdPlist(spec), { mode: 384 });
|
|
371
|
+
ops.exec("launchctl", [
|
|
372
|
+
"bootstrap",
|
|
373
|
+
launchdTarget(ops),
|
|
374
|
+
file
|
|
375
|
+
]);
|
|
376
|
+
}
|
|
377
|
+
if (!await ops.waitReady(config)) throw new Error(`it did not answer on port ${config.port}`);
|
|
378
|
+
} catch (err) {
|
|
379
|
+
rollback();
|
|
380
|
+
const hints = serviceHints(ops).map((h) => ` ${h}`).join("\n");
|
|
381
|
+
throw new Error(`Starting the service failed (${firstLine(err)}). Rolled back to ${previousUnit ? "the previous install" : "no service"}.\n To find out why:\n${hints}`);
|
|
382
|
+
}
|
|
383
|
+
swap.commit();
|
|
384
|
+
function rollback() {
|
|
385
|
+
const quietly = (fn) => {
|
|
386
|
+
try {
|
|
387
|
+
fn();
|
|
388
|
+
} catch {}
|
|
389
|
+
};
|
|
390
|
+
quietly(() => uninstallService(config, ops, {
|
|
391
|
+
quiet: true,
|
|
392
|
+
keepApp: true
|
|
393
|
+
}));
|
|
394
|
+
quietly(() => rmSync(file, { force: true }));
|
|
395
|
+
if (ops.platform === "linux") quietly(() => systemctl(ops, "daemon-reload"));
|
|
396
|
+
quietly(() => rmSync(staging, {
|
|
397
|
+
recursive: true,
|
|
398
|
+
force: true
|
|
399
|
+
}));
|
|
400
|
+
if (swap) quietly(() => swap.restore());
|
|
401
|
+
quietly(() => previousEnv === null ? rmSync(serviceEnvPath(config.dataDir), { force: true }) : writeFileSync(serviceEnvPath(config.dataDir), previousEnv, { mode: 384 }));
|
|
402
|
+
if (previousUnit !== null) quietly(() => {
|
|
403
|
+
writeFileSync(file, previousUnit, { mode: ops.platform === "darwin" ? 384 : 420 });
|
|
404
|
+
if (ops.platform === "linux") {
|
|
405
|
+
systemctl(ops, "daemon-reload");
|
|
406
|
+
systemctl(ops, "enable", "--now", SERVICE_NAME);
|
|
407
|
+
} else ops.exec("launchctl", [
|
|
408
|
+
"bootstrap",
|
|
409
|
+
launchdTarget(ops),
|
|
410
|
+
file
|
|
411
|
+
]);
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
enableLinger(ops);
|
|
415
|
+
const kept = Object.keys(extraEnv).filter((k) => !(k in process.env));
|
|
416
|
+
console.log(`\n Installed ${SERVICE_NAME} as a ${ops.platform === "linux" ? "systemd" : "launchd"} service.`);
|
|
417
|
+
console.log(` It runs the Node.js at ${process.execPath} (v${process.versions.node}). If you remove or replace that Node, run \`${cliCommand("service install")}\` again.`);
|
|
418
|
+
if (kept.length) console.log(` Kept ${kept.join(", ")} from the previous install.`);
|
|
419
|
+
printLinks(config);
|
|
420
|
+
}
|
|
421
|
+
/** Keeps user services running after logout and starts them at boot (systemd user units only). */
|
|
422
|
+
function enableLinger(ops) {
|
|
423
|
+
if (ops.platform !== "linux" || ops.root) return;
|
|
424
|
+
try {
|
|
425
|
+
ops.exec("loginctl", ["enable-linger", ops.user], { quiet: true });
|
|
426
|
+
} catch {
|
|
427
|
+
console.warn(` Could not enable lingering. Run \`sudo loginctl enable-linger ${ops.user}\` so the service starts at boot.`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
/** AGENTINBOX_DATA from an installed unit or plist. */
|
|
431
|
+
function unitDataDir(text) {
|
|
432
|
+
const systemd = /^Environment="AGENTINBOX_DATA=((?:[^"\\]|\\.)*)"/m.exec(text);
|
|
433
|
+
if (systemd) return systemd[1].replace(/\\(.)/g, "$1").replace(/%%/g, "%");
|
|
434
|
+
const plist = /<key>AGENTINBOX_DATA<\/key><string>([^<]*)<\/string>/.exec(text);
|
|
435
|
+
if (plist) return plist[1].replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Stops and removes the service. The data directory is the one the installed unit uses (falling
|
|
440
|
+
* back to `config.dataDir`), and only directories that really are the service's own install are
|
|
441
|
+
* deleted; nothing is created.
|
|
442
|
+
*/
|
|
443
|
+
function uninstallService(config, ops = systemOps(), opts = {}) {
|
|
444
|
+
assertSupported(ops);
|
|
445
|
+
const file = unitFile(ops);
|
|
446
|
+
const unitText = readText(file);
|
|
447
|
+
const dataDir = unitText && unitDataDir(unitText) || config.dataDir;
|
|
448
|
+
if (unitText === null) {
|
|
449
|
+
if (!opts.quiet) console.log("The agentinbox service is not installed.");
|
|
450
|
+
} else {
|
|
451
|
+
if (ops.platform === "linux") {
|
|
452
|
+
try {
|
|
453
|
+
systemctl(ops, "disable", "--now", SERVICE_NAME);
|
|
454
|
+
} catch {}
|
|
455
|
+
rmSync(file);
|
|
456
|
+
systemctl(ops, "daemon-reload");
|
|
457
|
+
} else {
|
|
458
|
+
try {
|
|
459
|
+
ops.exec("launchctl", ["bootout", `${launchdTarget(ops)}/${LAUNCHD_LABEL}`], { quiet: true });
|
|
460
|
+
} catch {}
|
|
461
|
+
rmSync(file);
|
|
462
|
+
}
|
|
463
|
+
if (!opts.quiet) console.log(`Removed the ${SERVICE_NAME} service. Your data in ${dataDir} was kept.`);
|
|
464
|
+
}
|
|
465
|
+
if (!opts.keepApp) removeOwnInstall(dataDir);
|
|
466
|
+
}
|
|
467
|
+
function serviceStatus(ops = systemOps()) {
|
|
468
|
+
assertSupported(ops);
|
|
469
|
+
if (!existsSync(unitFile(ops))) {
|
|
470
|
+
console.log(`The agentinbox service is not installed. Install it with \`${cliCommand("service install")}\`.`);
|
|
471
|
+
process.exitCode = 1;
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
try {
|
|
475
|
+
if (ops.platform === "linux") systemctl(ops, "status", SERVICE_NAME, "--no-pager");
|
|
476
|
+
else ops.exec("launchctl", ["print", `${launchdTarget(ops)}/${LAUNCHD_LABEL}`]);
|
|
477
|
+
} catch {
|
|
478
|
+
process.exitCode = 1;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/** A real HealthResponse (not the "starting" placeholder or something else on the port). */
|
|
482
|
+
function isHealthResponse(body) {
|
|
483
|
+
const b = body;
|
|
484
|
+
return !!b && (b.status === "ok" || b.status === "degraded") && typeof b.version === "string";
|
|
485
|
+
}
|
|
486
|
+
/** Polls /api/health for up to ~45 s. 503 with a HealthResponse means "up but degraded", which is up. */
|
|
487
|
+
async function waitForHealth(config) {
|
|
488
|
+
const base = loopbackUrl(config.host, config.port);
|
|
489
|
+
for (let i = 0; i < 90; i++) {
|
|
490
|
+
try {
|
|
491
|
+
const res = await fetch(`${base}/api/health`, { signal: AbortSignal.timeout(1e3) });
|
|
492
|
+
if ((res.status === 200 || res.status === 503) && isHealthResponse(await res.json().catch(() => null))) return true;
|
|
493
|
+
} catch {}
|
|
494
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
495
|
+
}
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
function printLinks(config) {
|
|
499
|
+
const token = readSetupToken(config.dataDir);
|
|
500
|
+
console.log(` Running at:\n`);
|
|
501
|
+
for (const url of consoleUrls(config.host, config.port)) console.log(` ${url}${token ? `/setup?token=${token}` : ""}`);
|
|
502
|
+
if (token) console.log(`\n Open the link above to claim the instance and finish setup.`);
|
|
503
|
+
console.log();
|
|
504
|
+
}
|
|
505
|
+
//#endregion
|
|
506
|
+
export { xmlEscape as A, swapIn as C, unitDataDir as D, uninstallService as E, userManagerConfigHome as O, stagePackage as S, systemctl as T, sdQuote as _, enableLinger as a, serviceStatus as b, isHealthResponse as c, parseEnvFile as d, preflight as f, renderSystemdUnit as g, renderLaunchdPlist as h, assertSupported as i, validateMasterKey as k, launchdTarget as l, removeOwnInstall as m, SERVICE_ENV_KEYS as n, installService as o, readText as p, SERVICE_NAME as r, installedEntry as s, LAUNCHD_LABEL as t, mergeServiceEnv as u, serviceEnvPath as v, systemOps as w, stageApp as x, serviceHints as y };
|
|
507
|
+
|
|
508
|
+
//# sourceMappingURL=service-D_ZEs3Zk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-D_ZEs3Zk.js","names":["PACKAGE"],"sources":["../server/service.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir, tmpdir, userInfo } from \"node:os\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { NPM_PACKAGE as PACKAGE } from \"../shared/types.ts\";\nimport { readSetupToken } from \"./auth/setup-token.ts\";\nimport type { Config } from \"./config.ts\";\nimport { Crypto } from \"./crypto.ts\";\nimport { assertMasterKeyUsable, cliCommand } from \"./instance.ts\";\nimport { consoleUrls, loopbackUrl } from \"./urls.ts\";\n\n/** Installs agentinbox as a background service: systemd on Linux, launchd on macOS. */\n\nexport const SERVICE_NAME = \"agentinbox\";\nexport const LAUNCHD_LABEL = \"com.unlaboredlabs.agentinbox\";\nconst npm = process.platform === \"win32\" ? \"npm.cmd\" : \"npm\";\n/** Package root: one level above the running module (dist/cli.js or server/service.ts). */\nconst ROOT = resolve(import.meta.dirname, \"..\");\n\n/**\n * Settings that come from the environment rather than flags. They are kept in `<data>/service.env`\n * (mode 0600) so re-running `service install` from a shell without them (the documented update\n * path) keeps them. Set one to an empty value to drop it.\n */\nexport const SERVICE_ENV_KEYS = [\"AGENTINBOX_MASTER_KEY\", \"TRUST_PROXY\", \"TRUST_PROXY_HOPS\", \"TRUST_PROXY_FROM\", \"AGENTINBOX_LOG_LEVEL\"] as const;\n\ntype ServiceConfig = Pick<Config, \"dataDir\" | \"port\" | \"host\" | \"dbPath\" | \"masterKeyPath\">;\n\nexport const serviceEnvPath = (dataDir: string) => join(dataDir, \"service.env\");\n\n// ───────────── Pure rendering (unit-tested) ─────────────\n\nexport interface ServiceSpec {\n /** Node binary. */\n execPath: string;\n /** The installed dist/cli.js. */\n entry: string;\n dataDir: string;\n port: number;\n host: string;\n /** Extra environment from service.env (SERVICE_ENV_KEYS). */\n extraEnv: Record<string, string>;\n /** systemd: system unit (root) rather than a user unit. */\n system?: boolean;\n}\n\n/** Plain variables written into the unit/plist. Secrets never go into the systemd unit. */\nfunction plainEnv(spec: ServiceSpec): Record<string, string> {\n return { AGENTINBOX_DATA: spec.dataDir, PORT: String(spec.port), HOST: spec.host, AGENTINBOX_SERVICE: \"1\" };\n}\n\n/** systemd word quoting for ExecStart/Environment: double quotes, escaped backslashes, quotes and specifiers. */\nexport const sdQuote = (s: string) => `\"${s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/%/g, \"%%\")}\"`;\n\n/**\n * The systemd unit. Everything from service.env (including AGENTINBOX_MASTER_KEY) is loaded with\n * EnvironmentFile=, so the world-readable unit holds no secrets. EnvironmentFile takes an\n * unquoted absolute path (systemd ignores quoted ones), which is why data dirs with whitespace are refused.\n */\nexport function renderSystemdUnit(spec: ServiceSpec): string {\n const vars = Object.entries(plainEnv(spec))\n .map(([k, v]) => `Environment=${sdQuote(`${k}=${v}`)}`)\n .join(\"\\n\");\n const envFile = Object.keys(spec.extraEnv).length ? `\\nEnvironmentFile=${serviceEnvPath(spec.dataDir).replace(/%/g, \"%%\")}` : \"\";\n // network-online.target only exists for system units; a user unit waiting on it never starts it.\n const network = spec.system ? \"\\nAfter=network-online.target\\nWants=network-online.target\" : \"\";\n return `[Unit]\nDescription=agentinbox (Gmail broker for AI agents)${network}\n\n[Service]\nExecStart=${sdQuote(spec.execPath)} ${sdQuote(spec.entry)} start --no-open\n${vars}${envFile}\nRestart=on-failure\nRestartSec=3\nTimeoutStopSec=20\n\n[Install]\nWantedBy=${spec.system ? \"multi-user.target\" : \"default.target\"}\n`;\n}\n\n/** The launchd plist. Written with mode 0600, since it may carry AGENTINBOX_MASTER_KEY. */\n/** Escapes text for a plist <string>. */\nexport const xmlEscape = (s: string) => s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n\nexport function renderLaunchdPlist(spec: ServiceSpec): string {\n const esc = xmlEscape;\n const vars = Object.entries({ ...plainEnv(spec), ...spec.extraEnv })\n .map(([k, v]) => ` <key>${esc(k)}</key><string>${esc(v)}</string>`)\n .join(\"\\n\");\n const log = esc(join(spec.dataDir, \"agentinbox.log\"));\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key><string>${LAUNCHD_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>${esc(spec.execPath)}</string>\n <string>${esc(spec.entry)}</string>\n <string>start</string>\n <string>--no-open</string>\n </array>\n <key>EnvironmentVariables</key>\n <dict>\n${vars}\n </dict>\n <key>RunAtLoad</key><true/>\n <key>KeepAlive</key><true/>\n <key>StandardOutPath</key><string>${log}</string>\n <key>StandardErrorPath</key><string>${log}</string>\n</dict>\n</plist>\n`;\n}\n\n/** Parses service.env (KEY=value lines). */\nexport function parseEnvFile(text: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const line of text.split(\"\\n\")) {\n const m = /^\\s*([A-Z_][A-Z0-9_]*)=(.*)$/.exec(line);\n if (m) out[m[1]!] = m[2]!.trim();\n }\n return out;\n}\n\n/**\n * The service's env-derived settings: the current shell wins, previous install fills the gaps,\n * an explicitly empty value removes the setting.\n */\nexport function mergeServiceEnv(previous: Record<string, string>, current: NodeJS.ProcessEnv): Record<string, string> {\n const out: Record<string, string> = {};\n for (const key of SERVICE_ENV_KEYS) {\n const value = key in current ? current[key] : previous[key];\n // A single comma-separated token, so systemd's EnvironmentFile parsing can't split it.\n if (value) out[key] = key === \"TRUST_PROXY_FROM\" ? value.split(/[\\s,]+/).filter(Boolean).join(\",\") : value;\n }\n return out;\n}\n\n/** Fails early (before anything is touched) when the master key can't work. */\nexport function validateMasterKey(value: string | undefined): void {\n if (!value) return;\n try {\n if (!/^[A-Za-z0-9+/]+={0,2}$/.test(value.trim())) throw new Error(\"not base64\");\n new Crypto(Buffer.from(value, \"base64\"));\n } catch {\n throw new Error(\"AGENTINBOX_MASTER_KEY must be 32 random bytes in base64 (e.g. `openssl rand -base64 32`).\");\n }\n}\n\n// ───────────── Install ─────────────\n\nfunction readPreviousEnv(dataDir: string): Record<string, string> {\n try {\n return parseEnvFile(readFileSync(serviceEnvPath(dataDir), \"utf8\"));\n } catch {\n return {};\n }\n}\n\nfunction writeServiceEnv(dataDir: string, env: Record<string, string>): void {\n const file = serviceEnvPath(dataDir);\n if (!Object.keys(env).length) {\n rmSync(file, { force: true });\n return;\n }\n for (const [k, v] of Object.entries(env)) {\n if (/[\\r\\n]/.test(v)) throw new Error(`${k} must be a single line.`);\n }\n writeFileSync(file, Object.entries(env).map(([k, v]) => `${k}=${v}\\n`).join(\"\"), { mode: 0o600 });\n}\n\n/**\n * Installs the package into `staging` (a fresh private npm prefix), so a failed npm install leaves the\n * current install untouched. From an installed package that is the latest release; from a source\n * checkout, the local build. Returns `staging`. The one install path for the daemon and the listeners.\n */\nexport function stagePackage(staging: string, opts: { where: string; requireWeb: boolean }): string {\n const fromSource = basename(dirname(ROOT)) !== \"node_modules\";\n let spec = `${PACKAGE}@latest`;\n let tmp: string | undefined;\n if (fromSource) {\n if (!existsSync(join(ROOT, \"dist/cli.js\")) || (opts.requireWeb && !existsSync(join(ROOT, \"dist/web/index.html\")))) {\n throw new Error(\"Run `pnpm build` before installing the service from a source checkout.\");\n }\n tmp = mkdtempSync(join(tmpdir(), \"agentinbox-pack-\"));\n const out = execFileSync(npm, [\"pack\", \"--ignore-scripts\", \"--pack-destination\", tmp, \"--loglevel=error\"], { cwd: ROOT, encoding: \"utf8\" });\n spec = join(tmp, out.trim().split(\"\\n\").pop()!);\n }\n\n console.log(fromSource ? ` Installing this build into ${opts.where}…` : ` Installing the latest release into ${opts.where}…`);\n rmSync(staging, { recursive: true, force: true });\n mkdirSync(staging, { recursive: true });\n writeFileSync(join(staging, \"package.json\"), '{ \"private\": true }\\n');\n try {\n execFileSync(npm, [\"install\", spec, \"--omit=dev\", \"--prefer-online\", \"--no-audit\", \"--no-fund\", \"--loglevel=error\"], { cwd: staging, stdio: \"inherit\" });\n if (!existsSync(installedEntry(staging))) throw new Error(\"the installed package has no dist/cli.js\");\n } catch (err) {\n rmSync(staging, { recursive: true, force: true });\n throw new Error(`Installing agentinbox failed, so the existing service was left as it was. ${err instanceof Error ? err.message : \"\"}`.trim());\n } finally {\n if (tmp) rmSync(tmp, { recursive: true, force: true });\n }\n return staging;\n}\n\n/** The CLI entry inside an install made by stagePackage. */\nexport const installedEntry = (app: string) => join(app, \"node_modules\", PACKAGE, \"dist/cli.js\");\n\n/** Installs into `<dir>/app.new` (see stagePackage). Returns the staging directory. */\nexport const stageApp = (dir: string): string => stagePackage(join(dir, \"app.new\"), { where: \"the data directory\", requireWeb: true });\n\n/**\n * Moves a staged install into place as `app` (the current one becomes `app.old`). `restore()` puts\n * the previous install back; `commit()` deletes it. If the move itself fails, nothing changes.\n */\nexport function swapIn(staging: string, app: string): { restore(): void; commit(): void } {\n const old = `${app}.old`;\n rmSync(old, { recursive: true, force: true });\n if (existsSync(app)) renameSync(app, old);\n try {\n renameSync(staging, app);\n } catch (err) {\n if (existsSync(old)) renameSync(old, app);\n throw err;\n }\n return {\n restore() {\n rmSync(app, { recursive: true, force: true });\n if (existsSync(old)) renameSync(old, app);\n },\n commit() {\n rmSync(old, { recursive: true, force: true });\n },\n };\n}\n\n/** Deletes `<dir>/app`, `app.new` and `app.old`, but only where they really are our own installs. */\nexport function removeOwnInstall(dir: string): void {\n for (const name of [\"app\", \"app.new\", \"app.old\"]) {\n const path = join(dir, name);\n if (existsSync(join(path, \"node_modules\", PACKAGE))) rmSync(path, { recursive: true, force: true });\n }\n}\n\n// ───────────── System integration (injectable for tests) ─────────────\n\nexport interface ServiceOps {\n platform: NodeJS.Platform;\n /** systemd: install a system unit (running as root) rather than a user unit. */\n root: boolean;\n /** Runs a command, throwing on a non-zero exit. */\n exec(cmd: string, args: string[], opts?: { quiet?: boolean }): void;\n /** Runs a command and returns its stdout, throwing on a non-zero exit. */\n capture(cmd: string, args: string[]): string;\n /** Installs the package into a staging directory and returns it (see stageApp). */\n stage(dataDir: string): string;\n /** Directory for the systemd unit or the launchd plist. */\n unitDir(): string;\n /** Resolves true once /api/health returns a real HealthResponse, false if it never does. */\n waitReady(config: ServiceConfig): Promise<boolean>;\n uid: number;\n user: string;\n}\n\nexport const systemOps = (): ServiceOps => {\n const root = process.getuid?.() === 0;\n const ops: ServiceOps = {\n platform: process.platform,\n root,\n exec: (cmd, args, opts) => void execFileSync(cmd, args, { stdio: opts?.quiet ? \"ignore\" : \"inherit\" }),\n capture: (cmd, args) => execFileSync(cmd, args, { encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"pipe\"] }),\n stage: stageApp,\n unitDir: () => {\n if (process.platform === \"darwin\") return join(homedir(), \"Library/LaunchAgents\");\n if (root) return \"/etc/systemd/system\";\n unitDirCache ??= join(userManagerConfigHome(ops) ?? join(homedir(), \".config\"), \"systemd/user\");\n return unitDirCache;\n },\n waitReady: waitForHealth,\n uid: process.getuid?.() ?? 0,\n user: userInfo().username,\n };\n let unitDirCache: string | undefined;\n return ops;\n};\n\n/**\n * XDG_CONFIG_HOME as the systemd user manager sees it (where `systemctl --user` looks for units),\n * which can differ from this shell's. Null when unset there or the manager can't be asked.\n */\nexport function userManagerConfigHome(ops: Pick<ServiceOps, \"capture\">): string | null {\n try {\n const env = ops.capture(\"systemctl\", [\"--user\", \"show-environment\"]);\n return /^XDG_CONFIG_HOME=(.+)$/m.exec(env)?.[1]?.trim() || null;\n } catch {\n return null;\n }\n}\n\nconst unitFile = (ops: ServiceOps) => join(ops.unitDir(), ops.platform === \"darwin\" ? `${LAUNCHD_LABEL}.plist` : `${SERVICE_NAME}.service`);\nexport const systemctl = (ops: ServiceOps, ...args: string[]) => ops.exec(\"systemctl\", ops.root ? args : [\"--user\", ...args]);\nexport const launchdTarget = (ops: ServiceOps) => `gui/${ops.uid}`;\n\nexport function assertSupported(ops: ServiceOps): void {\n if (ops.platform !== \"linux\" && ops.platform !== \"darwin\") {\n throw new Error(\n `Service install supports Linux (systemd) and macOS (launchd). Elsewhere, install the command (\\`npm i -g ${PACKAGE}\\`) and run \\`agentinbox start\\` under your own supervisor.`,\n );\n }\n}\n\nexport const readText = (file: string) => {\n try {\n return readFileSync(file, \"utf8\");\n } catch {\n return null;\n }\n};\n\n/** Where to look when the service doesn't start. */\nexport function serviceHints(ops: ServiceOps, env: NodeJS.ProcessEnv = process.env): string[] {\n if (ops.platform === \"darwin\") {\n return [`launchctl print ${launchdTarget(ops)}/${LAUNCHD_LABEL}`, \"and the log file agentinbox.log in the data directory\"];\n }\n const scope = ops.root ? \"\" : \" --user\";\n const hints = [`systemctl${scope} status ${SERVICE_NAME}`, `journalctl${scope} -u ${SERVICE_NAME} -e`];\n if (!ops.root && !env.XDG_RUNTIME_DIR) {\n hints.push(\n \"XDG_RUNTIME_DIR is not set, so there may be no systemd user session (common over `su` or `sudo -u`). \" +\n `Log in as ${ops.user} directly (e.g. over ssh), or run \\`sudo loginctl enable-linger ${ops.user}\\` and then ` +\n `\\`export XDG_RUNTIME_DIR=/run/user/${ops.uid}\\` before installing.`,\n );\n }\n return hints;\n}\n\nconst firstLine = (err: unknown) => (err instanceof Error ? err.message : String(err)).split(\"\\n\")[0];\n\n/** Fails before anything is touched when systemd can't be reached (e.g. no user bus over su). */\nexport function preflight(ops: ServiceOps): void {\n if (ops.platform !== \"linux\") return;\n try {\n systemctl(ops, \"daemon-reload\");\n } catch (err) {\n const hints = serviceHints(ops).map((h) => ` ${h}`).join(\"\\n\");\n throw new Error(`Can't talk to systemd${ops.root ? \"\" : \" (user session)\"}: ${firstLine(err)}. Nothing was changed.\\n${hints}`);\n }\n}\n\nexport async function installService(config: ServiceConfig, ops: ServiceOps = systemOps()): Promise<void> {\n assertSupported(ops);\n if (ops.platform === \"linux\" && /\\s/.test(config.dataDir)) {\n throw new Error(`The data directory \"${config.dataDir}\" contains whitespace, which systemd can't load settings from. Choose another one with --data.`);\n }\n const extraEnv = mergeServiceEnv(readPreviousEnv(config.dataDir), process.env);\n validateMasterKey(extraEnv.AGENTINBOX_MASTER_KEY);\n // The service must be able to read what's already stored, with exactly the key it will get.\n assertMasterKeyUsable(config, extraEnv.AGENTINBOX_MASTER_KEY ?? \"\");\n preflight(ops);\n mkdirSync(config.dataDir, { recursive: true, mode: 0o700 });\n\n const staging = ops.stage(config.dataDir);\n const file = unitFile(ops);\n const previousUnit = readText(file);\n const previousEnv = readText(serviceEnvPath(config.dataDir));\n const app = join(config.dataDir, \"app\");\n const entry = installedEntry(app);\n const spec: ServiceSpec = { execPath: process.execPath, entry, dataDir: config.dataDir, port: config.port, host: config.host, extraEnv, system: ops.root };\n let swap: ReturnType<typeof swapIn> | null = null;\n\n try {\n uninstallService(config, ops, { quiet: true, keepApp: true });\n swap = swapIn(staging, app);\n writeServiceEnv(config.dataDir, extraEnv);\n mkdirSync(ops.unitDir(), { recursive: true });\n if (ops.platform === \"linux\") {\n writeFileSync(file, renderSystemdUnit(spec));\n systemctl(ops, \"daemon-reload\");\n systemctl(ops, \"enable\", \"--now\", SERVICE_NAME);\n } else {\n // 0600: the plist may carry AGENTINBOX_MASTER_KEY.\n writeFileSync(file, renderLaunchdPlist(spec), { mode: 0o600 });\n ops.exec(\"launchctl\", [\"bootstrap\", launchdTarget(ops), file]);\n }\n if (!(await ops.waitReady(config))) throw new Error(`it did not answer on port ${config.port}`);\n } catch (err) {\n rollback();\n const hints = serviceHints(ops).map((h) => ` ${h}`).join(\"\\n\");\n throw new Error(\n `Starting the service failed (${firstLine(err)}). Rolled back to ${previousUnit ? \"the previous install\" : \"no service\"}.\\n To find out why:\\n${hints}`,\n );\n }\n swap!.commit();\n\n function rollback(): void {\n const quietly = (fn: () => void) => {\n try {\n fn();\n } catch {\n // Best effort: report the original failure.\n }\n };\n quietly(() => uninstallService(config, ops, { quiet: true, keepApp: true }));\n quietly(() => rmSync(file, { force: true }));\n if (ops.platform === \"linux\") quietly(() => systemctl(ops, \"daemon-reload\"));\n quietly(() => rmSync(staging, { recursive: true, force: true }));\n if (swap) quietly(() => swap!.restore());\n quietly(() => (previousEnv === null ? rmSync(serviceEnvPath(config.dataDir), { force: true }) : writeFileSync(serviceEnvPath(config.dataDir), previousEnv, { mode: 0o600 })));\n if (previousUnit !== null) {\n quietly(() => {\n writeFileSync(file, previousUnit, { mode: ops.platform === \"darwin\" ? 0o600 : 0o644 });\n if (ops.platform === \"linux\") {\n systemctl(ops, \"daemon-reload\");\n systemctl(ops, \"enable\", \"--now\", SERVICE_NAME);\n } else {\n ops.exec(\"launchctl\", [\"bootstrap\", launchdTarget(ops), file]);\n }\n });\n }\n }\n\n enableLinger(ops);\n\n const kept = Object.keys(extraEnv).filter((k) => !(k in process.env));\n console.log(`\\n Installed ${SERVICE_NAME} as a ${ops.platform === \"linux\" ? \"systemd\" : \"launchd\"} service.`);\n console.log(\n ` It runs the Node.js at ${process.execPath} (v${process.versions.node}). If you remove or replace that Node, run \\`${cliCommand(\"service install\")}\\` again.`,\n );\n if (kept.length) console.log(` Kept ${kept.join(\", \")} from the previous install.`);\n printLinks(config);\n}\n\n/** Keeps user services running after logout and starts them at boot (systemd user units only). */\nexport function enableLinger(ops: ServiceOps): void {\n if (ops.platform !== \"linux\" || ops.root) return;\n try {\n ops.exec(\"loginctl\", [\"enable-linger\", ops.user], { quiet: true });\n } catch {\n console.warn(` Could not enable lingering. Run \\`sudo loginctl enable-linger ${ops.user}\\` so the service starts at boot.`);\n }\n}\n\n/** AGENTINBOX_DATA from an installed unit or plist. */\nexport function unitDataDir(text: string): string | null {\n const systemd = /^Environment=\"AGENTINBOX_DATA=((?:[^\"\\\\]|\\\\.)*)\"/m.exec(text);\n if (systemd) return systemd[1]!.replace(/\\\\(.)/g, \"$1\").replace(/%%/g, \"%\");\n const plist = /<key>AGENTINBOX_DATA<\\/key><string>([^<]*)<\\/string>/.exec(text);\n if (plist) return plist[1]!.replace(/</g, \"<\").replace(/>/g, \">\").replace(/&/g, \"&\");\n return null;\n}\n\n/**\n * Stops and removes the service. The data directory is the one the installed unit uses (falling\n * back to `config.dataDir`), and only directories that really are the service's own install are\n * deleted; nothing is created.\n */\nexport function uninstallService(\n config: Pick<ServiceConfig, \"dataDir\">,\n ops: ServiceOps = systemOps(),\n opts: { quiet?: boolean; keepApp?: boolean } = {},\n): void {\n assertSupported(ops);\n const file = unitFile(ops);\n const unitText = readText(file);\n const dataDir = (unitText && unitDataDir(unitText)) || config.dataDir;\n if (unitText === null) {\n if (!opts.quiet) console.log(\"The agentinbox service is not installed.\");\n } else {\n if (ops.platform === \"linux\") {\n try {\n systemctl(ops, \"disable\", \"--now\", SERVICE_NAME);\n } catch {\n // Already stopped.\n }\n rmSync(file);\n systemctl(ops, \"daemon-reload\");\n } else {\n try {\n ops.exec(\"launchctl\", [\"bootout\", `${launchdTarget(ops)}/${LAUNCHD_LABEL}`], { quiet: true });\n } catch {\n // Not loaded.\n }\n rmSync(file);\n }\n if (!opts.quiet) console.log(`Removed the ${SERVICE_NAME} service. Your data in ${dataDir} was kept.`);\n }\n // The service's own install; the data (database, keys) stays.\n if (!opts.keepApp) removeOwnInstall(dataDir);\n}\n\nexport function serviceStatus(ops: ServiceOps = systemOps()): void {\n assertSupported(ops);\n if (!existsSync(unitFile(ops))) {\n console.log(`The agentinbox service is not installed. Install it with \\`${cliCommand(\"service install\")}\\`.`);\n process.exitCode = 1;\n return;\n }\n try {\n if (ops.platform === \"linux\") systemctl(ops, \"status\", SERVICE_NAME, \"--no-pager\");\n else ops.exec(\"launchctl\", [\"print\", `${launchdTarget(ops)}/${LAUNCHD_LABEL}`]);\n } catch {\n process.exitCode = 1;\n }\n}\n\n/** A real HealthResponse (not the \"starting\" placeholder or something else on the port). */\nexport function isHealthResponse(body: unknown): boolean {\n const b = body as { status?: unknown; version?: unknown } | null;\n return !!b && (b.status === \"ok\" || b.status === \"degraded\") && typeof b.version === \"string\";\n}\n\n/** Polls /api/health for up to ~45 s. 503 with a HealthResponse means \"up but degraded\", which is up. */\nasync function waitForHealth(config: ServiceConfig): Promise<boolean> {\n const base = loopbackUrl(config.host, config.port);\n for (let i = 0; i < 90; i++) {\n try {\n const res = await fetch(`${base}/api/health`, { signal: AbortSignal.timeout(1000) });\n if ((res.status === 200 || res.status === 503) && isHealthResponse(await res.json().catch(() => null))) return true;\n } catch {\n // Not up yet.\n }\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n}\n\nfunction printLinks(config: ServiceConfig): void {\n const token = readSetupToken(config.dataDir);\n console.log(` Running at:\\n`);\n for (const url of consoleUrls(config.host, config.port)) console.log(` ${url}${token ? `/setup?token=${token}` : \"\"}`);\n if (token) console.log(`\\n Open the link above to claim the instance and finish setup.`);\n console.log();\n}\n"],"mappings":"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"}
|