@agenticmail/enterprise 0.5.375 → 0.5.376
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/dist/agent-heartbeat-3JIAQFMW.js +510 -0
- package/dist/{agent-tools-CUJHKMUN.js → agent-tools-GJUIPPHV.js} +1 -1
- package/dist/browser-tool-HG5THI5H.js +4002 -0
- package/dist/{chunk-BBGCFJWI.js → chunk-BC4VT5GK.js} +19 -19
- package/dist/{chunk-6MR5ICED.js → chunk-MI4NMRKF.js} +39 -17
- package/dist/{chunk-WTYMDT72.js → chunk-OPPT7QRL.js} +2 -2
- package/dist/{chunk-LVKRN4IR.js → chunk-VPWGFA5K.js} +15 -15
- package/dist/cli-agent-HWRINZSE.js +2483 -0
- package/dist/cli-serve-IOSXZHIK.js +286 -0
- package/dist/cli-update-6ZZTT5UR.js +246 -0
- package/dist/cli.js +11 -3
- package/dist/dashboard/app.js +41 -1
- package/dist/index.js +18 -18
- package/dist/routes-NIQHEAV2.js +92 -0
- package/dist/runtime-SUDXHYMB.js +45 -0
- package/dist/server-Q52YXWZD.js +28 -0
- package/dist/setup-C22ILWUJ.js +20 -0
- package/logs/cloudflared-error.log +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import "./chunk-KFQGP6VL.js";
|
|
2
|
+
|
|
3
|
+
// src/cli-serve.ts
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
function loadEnvFile() {
|
|
8
|
+
const candidates = [
|
|
9
|
+
join(process.cwd(), ".env"),
|
|
10
|
+
join(homedir(), ".agenticmail", ".env")
|
|
11
|
+
];
|
|
12
|
+
for (const envPath of candidates) {
|
|
13
|
+
if (!existsSync(envPath)) continue;
|
|
14
|
+
try {
|
|
15
|
+
const content = readFileSync(envPath, "utf8");
|
|
16
|
+
for (const line of content.split("\n")) {
|
|
17
|
+
const trimmed = line.trim();
|
|
18
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
19
|
+
const eq = trimmed.indexOf("=");
|
|
20
|
+
if (eq < 0) continue;
|
|
21
|
+
const key = trimmed.slice(0, eq).trim();
|
|
22
|
+
let val = trimmed.slice(eq + 1).trim();
|
|
23
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
24
|
+
val = val.slice(1, -1);
|
|
25
|
+
}
|
|
26
|
+
if (!process.env[key]) process.env[key] = val;
|
|
27
|
+
}
|
|
28
|
+
console.log(`Loaded config from ${envPath}`);
|
|
29
|
+
return;
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function ensureSecrets() {
|
|
35
|
+
const { randomUUID } = await import("crypto");
|
|
36
|
+
const envDir = join(homedir(), ".agenticmail");
|
|
37
|
+
const envPath = join(envDir, ".env");
|
|
38
|
+
let dirty = false;
|
|
39
|
+
if (!process.env.JWT_SECRET) {
|
|
40
|
+
process.env.JWT_SECRET = randomUUID() + randomUUID();
|
|
41
|
+
dirty = true;
|
|
42
|
+
console.log("[startup] Generated new JWT_SECRET (existing sessions will need to re-login)");
|
|
43
|
+
}
|
|
44
|
+
if (!process.env.AGENTICMAIL_VAULT_KEY) {
|
|
45
|
+
process.env.AGENTICMAIL_VAULT_KEY = randomUUID() + randomUUID();
|
|
46
|
+
dirty = true;
|
|
47
|
+
console.log("[startup] Generated new AGENTICMAIL_VAULT_KEY");
|
|
48
|
+
console.log("[startup] \u26A0\uFE0F Previously encrypted credentials will need to be re-entered in the dashboard");
|
|
49
|
+
}
|
|
50
|
+
if (dirty) {
|
|
51
|
+
try {
|
|
52
|
+
if (!existsSync(envDir)) {
|
|
53
|
+
const { mkdirSync } = await import("fs");
|
|
54
|
+
mkdirSync(envDir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
const { appendFileSync } = await import("fs");
|
|
57
|
+
const lines = [];
|
|
58
|
+
let existing = "";
|
|
59
|
+
if (existsSync(envPath)) {
|
|
60
|
+
existing = readFileSync(envPath, "utf8");
|
|
61
|
+
}
|
|
62
|
+
if (!existing.includes("JWT_SECRET=")) {
|
|
63
|
+
lines.push(`JWT_SECRET=${process.env.JWT_SECRET}`);
|
|
64
|
+
}
|
|
65
|
+
if (!existing.includes("AGENTICMAIL_VAULT_KEY=")) {
|
|
66
|
+
lines.push(`AGENTICMAIL_VAULT_KEY=${process.env.AGENTICMAIL_VAULT_KEY}`);
|
|
67
|
+
}
|
|
68
|
+
if (lines.length) {
|
|
69
|
+
appendFileSync(envPath, "\n" + lines.join("\n") + "\n", { mode: 384 });
|
|
70
|
+
console.log(`[startup] Saved secrets to ${envPath}`);
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.warn(`[startup] Could not save secrets to ${envPath}: ${e.message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function runServe(_args) {
|
|
78
|
+
loadEnvFile();
|
|
79
|
+
const DATABASE_URL = process.env.DATABASE_URL;
|
|
80
|
+
const PORT = parseInt(process.env.PORT || "8080", 10);
|
|
81
|
+
await ensureSecrets();
|
|
82
|
+
const JWT_SECRET = process.env.JWT_SECRET;
|
|
83
|
+
const _VAULT_KEY = process.env.AGENTICMAIL_VAULT_KEY;
|
|
84
|
+
if (!DATABASE_URL) {
|
|
85
|
+
console.error("ERROR: DATABASE_URL is required.");
|
|
86
|
+
console.error("");
|
|
87
|
+
console.error("Set it via environment variable or .env file:");
|
|
88
|
+
console.error(" DATABASE_URL=postgresql://user:pass@host:5432/db npx @agenticmail/enterprise start");
|
|
89
|
+
console.error("");
|
|
90
|
+
console.error("Or create a .env file (in cwd or ~/.agenticmail/.env):");
|
|
91
|
+
console.error(" DATABASE_URL=postgresql://user:pass@host:5432/db");
|
|
92
|
+
console.error(" JWT_SECRET=your-secret-here");
|
|
93
|
+
console.error(" PORT=3200");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
const { createAdapter, smartDbConfig } = await import("./factory-XRYYBBCW.js");
|
|
97
|
+
const { createServer } = await import("./server-Q52YXWZD.js");
|
|
98
|
+
const db = await createAdapter(smartDbConfig(DATABASE_URL));
|
|
99
|
+
await db.migrate();
|
|
100
|
+
const server = createServer({
|
|
101
|
+
port: PORT,
|
|
102
|
+
db,
|
|
103
|
+
jwtSecret: JWT_SECRET,
|
|
104
|
+
corsOrigins: ["*"]
|
|
105
|
+
});
|
|
106
|
+
await server.start();
|
|
107
|
+
console.log(`AgenticMail Enterprise server running on :${PORT}`);
|
|
108
|
+
try {
|
|
109
|
+
const { startBackgroundUpdateCheck } = await import("./cli-update-6ZZTT5UR.js");
|
|
110
|
+
startBackgroundUpdateCheck();
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const { startPreventSleep } = await import("./screen-unlock-4RPZBHOI.js");
|
|
115
|
+
const adminDb = server.getAdminDb?.() || server.adminDb;
|
|
116
|
+
if (adminDb) {
|
|
117
|
+
const settings = await adminDb.getSettings?.().catch(() => null);
|
|
118
|
+
const screenAccess = settings?.securityConfig?.screenAccess;
|
|
119
|
+
if (screenAccess?.enabled && screenAccess?.preventSleep) {
|
|
120
|
+
startPreventSleep();
|
|
121
|
+
console.log("[startup] Prevent-sleep enabled \u2014 system will stay awake while agents are active");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
await setupSystemPersistence();
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.warn("[startup] System persistence setup skipped: " + e.message);
|
|
130
|
+
}
|
|
131
|
+
const tunnelToken = process.env.CLOUDFLARED_TOKEN;
|
|
132
|
+
if (tunnelToken) {
|
|
133
|
+
try {
|
|
134
|
+
const { execSync, spawn } = await import("child_process");
|
|
135
|
+
try {
|
|
136
|
+
execSync(process.platform === "win32" ? "where cloudflared" : "which cloudflared", { timeout: 3e3 });
|
|
137
|
+
} catch {
|
|
138
|
+
console.log("[startup] cloudflared not found \u2014 skipping tunnel auto-start");
|
|
139
|
+
console.log("[startup] Install cloudflared to enable tunnel: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
if (process.platform === "win32") {
|
|
144
|
+
const tasklist = execSync('tasklist /FI "IMAGENAME eq cloudflared.exe" /NH', { encoding: "utf8", timeout: 5e3 });
|
|
145
|
+
if (tasklist.includes("cloudflared.exe")) {
|
|
146
|
+
console.log("[startup] cloudflared tunnel already running");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
execSync('pgrep -f "cloudflared.*tunnel.*run"', { timeout: 3e3 });
|
|
151
|
+
console.log("[startup] cloudflared tunnel already running");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
const subdomain = process.env.AGENTICMAIL_SUBDOMAIN || process.env.AGENTICMAIL_DOMAIN || "";
|
|
157
|
+
console.log(`[startup] Starting cloudflared tunnel${subdomain ? ` for ${subdomain}.agenticmail.io` : ""}...`);
|
|
158
|
+
let cfBin = "cloudflared";
|
|
159
|
+
if (process.platform === "win32") {
|
|
160
|
+
try {
|
|
161
|
+
cfBin = execSync("where cloudflared", { encoding: "utf8", timeout: 3e3 }).trim().split("\n")[0].trim();
|
|
162
|
+
} catch {
|
|
163
|
+
const candidate = `${process.env.LOCALAPPDATA || ""}\\cloudflared\\cloudflared.exe`;
|
|
164
|
+
try {
|
|
165
|
+
(await import("fs")).statSync(candidate);
|
|
166
|
+
cfBin = candidate;
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const child = spawn(cfBin, ["tunnel", "--no-autoupdate", "run", "--token", tunnelToken], {
|
|
172
|
+
detached: true,
|
|
173
|
+
stdio: "ignore"
|
|
174
|
+
});
|
|
175
|
+
child.unref();
|
|
176
|
+
console.log("[startup] cloudflared tunnel started (pid " + child.pid + ")");
|
|
177
|
+
} catch (e) {
|
|
178
|
+
console.warn("[startup] Could not auto-start cloudflared: " + e.message);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function setupSystemPersistence() {
|
|
183
|
+
const { execSync, spawnSync } = await import("child_process");
|
|
184
|
+
const { existsSync: exists, writeFileSync, mkdirSync } = await import("fs");
|
|
185
|
+
const { join: pathJoin } = await import("path");
|
|
186
|
+
const platform = process.platform;
|
|
187
|
+
if (!process.env.PM2_HOME && !process.env.pm_id) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const markerDir = pathJoin(homedir(), ".agenticmail");
|
|
191
|
+
const markerFile = pathJoin(markerDir, ".persistence-configured");
|
|
192
|
+
if (exists(markerFile)) {
|
|
193
|
+
try {
|
|
194
|
+
execSync("pm2 save --silent", { timeout: 1e4, stdio: "ignore" });
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
console.log("[startup] Configuring system persistence (one-time setup)...");
|
|
200
|
+
try {
|
|
201
|
+
if (platform === "darwin") {
|
|
202
|
+
const result = spawnSync("pm2", ["startup", "launchd", "--silent"], {
|
|
203
|
+
timeout: 15e3,
|
|
204
|
+
stdio: "pipe",
|
|
205
|
+
encoding: "utf-8"
|
|
206
|
+
});
|
|
207
|
+
const output = (result.stdout || "") + (result.stderr || "");
|
|
208
|
+
const sudoMatch = output.match(/sudo\s+env\s+.*pm2\s+startup.*/);
|
|
209
|
+
if (sudoMatch) {
|
|
210
|
+
console.log("[startup] PM2 startup requires sudo. Run this once:");
|
|
211
|
+
console.log(" " + sudoMatch[0]);
|
|
212
|
+
} else {
|
|
213
|
+
console.log("[startup] PM2 startup configured (launchd)");
|
|
214
|
+
}
|
|
215
|
+
const plistPath = pathJoin(homedir(), "Library", "LaunchAgents", `pm2.${process.env.USER || "user"}.plist`);
|
|
216
|
+
if (exists(plistPath)) {
|
|
217
|
+
try {
|
|
218
|
+
execSync(`launchctl load -w "${plistPath}"`, { timeout: 5e3, stdio: "ignore" });
|
|
219
|
+
} catch {
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} else if (platform === "linux") {
|
|
223
|
+
const result = spawnSync("pm2", ["startup", "systemd", "--silent"], {
|
|
224
|
+
timeout: 15e3,
|
|
225
|
+
stdio: "pipe",
|
|
226
|
+
encoding: "utf-8"
|
|
227
|
+
});
|
|
228
|
+
const output = (result.stdout || "") + (result.stderr || "");
|
|
229
|
+
const sudoMatch = output.match(/sudo\s+env\s+.*pm2\s+startup.*/);
|
|
230
|
+
if (sudoMatch) {
|
|
231
|
+
try {
|
|
232
|
+
execSync(sudoMatch[0], { timeout: 15e3, stdio: "ignore" });
|
|
233
|
+
console.log("[startup] PM2 startup configured (systemd)");
|
|
234
|
+
} catch {
|
|
235
|
+
console.log("[startup] PM2 startup requires root. Run this once:");
|
|
236
|
+
console.log(" " + sudoMatch[0]);
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
console.log("[startup] PM2 startup configured (systemd)");
|
|
240
|
+
}
|
|
241
|
+
} else if (platform === "win32") {
|
|
242
|
+
try {
|
|
243
|
+
execSync("npm list -g pm2-windows-startup", { timeout: 1e4, stdio: "ignore" });
|
|
244
|
+
} catch {
|
|
245
|
+
console.log("[startup] Installing pm2-windows-startup...");
|
|
246
|
+
try {
|
|
247
|
+
execSync("npm install -g pm2-windows-startup", { timeout: 6e4, stdio: "ignore" });
|
|
248
|
+
execSync("pm2-startup install", { timeout: 15e3, stdio: "ignore" });
|
|
249
|
+
console.log("[startup] PM2 startup configured (Windows Service)");
|
|
250
|
+
} catch (e) {
|
|
251
|
+
console.warn("[startup] Could not install pm2-windows-startup: " + e.message);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
} catch (e) {
|
|
256
|
+
console.warn("[startup] PM2 startup setup: " + e.message);
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const moduleList = execSync("pm2 ls --silent 2>/dev/null || true", { timeout: 1e4, encoding: "utf-8" });
|
|
260
|
+
if (!moduleList.includes("pm2-logrotate")) {
|
|
261
|
+
console.log("[startup] Installing pm2-logrotate...");
|
|
262
|
+
execSync("pm2 install pm2-logrotate --silent", { timeout: 6e4, stdio: "ignore" });
|
|
263
|
+
execSync("pm2 set pm2-logrotate:max_size 10M --silent", { timeout: 5e3, stdio: "ignore" });
|
|
264
|
+
execSync("pm2 set pm2-logrotate:retain 5 --silent", { timeout: 5e3, stdio: "ignore" });
|
|
265
|
+
execSync("pm2 set pm2-logrotate:compress true --silent", { timeout: 5e3, stdio: "ignore" });
|
|
266
|
+
console.log("[startup] Log rotation configured (10MB, 5 files)");
|
|
267
|
+
}
|
|
268
|
+
} catch {
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
execSync("pm2 save --silent", { timeout: 1e4, stdio: "ignore" });
|
|
272
|
+
console.log("[startup] Process list saved");
|
|
273
|
+
} catch {
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
if (!exists(markerDir)) mkdirSync(markerDir, { recursive: true });
|
|
277
|
+
writeFileSync(markerFile, (/* @__PURE__ */ new Date()).toISOString() + `
|
|
278
|
+
platform=${platform}
|
|
279
|
+
`, { mode: 384 });
|
|
280
|
+
console.log("[startup] System persistence configured successfully");
|
|
281
|
+
} catch {
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
export {
|
|
285
|
+
runServe
|
|
286
|
+
};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import "./chunk-KFQGP6VL.js";
|
|
2
|
+
|
|
3
|
+
// src/cli-update.ts
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { homedir, platform } from "os";
|
|
8
|
+
var PKG_NAME = "@agenticmail/enterprise";
|
|
9
|
+
function getCurrentVersion() {
|
|
10
|
+
try {
|
|
11
|
+
const pkgPath = join(import.meta.dirname || __dirname, "..", "package.json");
|
|
12
|
+
if (existsSync(pkgPath)) {
|
|
13
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
14
|
+
}
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const out = execSync(`npm ls -g ${PKG_NAME} --json 2>/dev/null`, { encoding: "utf-8", timeout: 1e4 });
|
|
19
|
+
const data = JSON.parse(out);
|
|
20
|
+
return data.dependencies?.[PKG_NAME]?.version || "unknown";
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
return "unknown";
|
|
24
|
+
}
|
|
25
|
+
async function getLatestVersion() {
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(`https://registry.npmjs.org/${PKG_NAME}/latest`, {
|
|
28
|
+
headers: { "Accept": "application/json" },
|
|
29
|
+
signal: AbortSignal.timeout(1e4)
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
32
|
+
const data = await res.json();
|
|
33
|
+
return data.version;
|
|
34
|
+
} catch {
|
|
35
|
+
try {
|
|
36
|
+
return execSync(`npm view ${PKG_NAME} version 2>/dev/null`, { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return "unknown";
|
|
41
|
+
}
|
|
42
|
+
async function checkForUpdate() {
|
|
43
|
+
const current = getCurrentVersion();
|
|
44
|
+
const latest = await getLatestVersion();
|
|
45
|
+
const updateAvailable = latest !== "unknown" && current !== "unknown" && latest !== current;
|
|
46
|
+
const info = {
|
|
47
|
+
current,
|
|
48
|
+
latest,
|
|
49
|
+
updateAvailable,
|
|
50
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
51
|
+
};
|
|
52
|
+
try {
|
|
53
|
+
const cacheDir = join(homedir(), ".agenticmail");
|
|
54
|
+
const cachePath = join(cacheDir, "update-check.json");
|
|
55
|
+
writeFileSync(cachePath, JSON.stringify(info, null, 2));
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
return info;
|
|
59
|
+
}
|
|
60
|
+
function getCachedUpdateCheck() {
|
|
61
|
+
try {
|
|
62
|
+
const cachePath = join(homedir(), ".agenticmail", "update-check.json");
|
|
63
|
+
if (existsSync(cachePath)) {
|
|
64
|
+
return JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
async function performUpdate(options) {
|
|
71
|
+
const current = getCurrentVersion();
|
|
72
|
+
console.log(`
|
|
73
|
+
\u{1F380} AgenticMail Enterprise Update
|
|
74
|
+
`);
|
|
75
|
+
console.log(` Current version: ${current}`);
|
|
76
|
+
const latest = await getLatestVersion();
|
|
77
|
+
console.log(` Latest version: ${latest}`);
|
|
78
|
+
if (latest === current) {
|
|
79
|
+
console.log(`
|
|
80
|
+
\u2705 Already up to date!
|
|
81
|
+
`);
|
|
82
|
+
return { success: true, from: current, to: current, message: "Already up to date" };
|
|
83
|
+
}
|
|
84
|
+
if (latest === "unknown") {
|
|
85
|
+
console.log(`
|
|
86
|
+
\u274C Could not determine latest version
|
|
87
|
+
`);
|
|
88
|
+
return { success: false, from: current, to: "unknown", message: "Could not determine latest version" };
|
|
89
|
+
}
|
|
90
|
+
console.log(`
|
|
91
|
+
\u{1F4E6} Installing ${PKG_NAME}@${latest}...`);
|
|
92
|
+
try {
|
|
93
|
+
execSync(`npm install -g ${PKG_NAME}@${latest}`, {
|
|
94
|
+
stdio: "inherit",
|
|
95
|
+
timeout: 12e4
|
|
96
|
+
});
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error(`
|
|
99
|
+
\u274C Update failed: ${err.message}
|
|
100
|
+
`);
|
|
101
|
+
return { success: false, from: current, to: latest, message: `npm install failed: ${err.message}` };
|
|
102
|
+
}
|
|
103
|
+
const newVersion = getCurrentVersion();
|
|
104
|
+
console.log(`
|
|
105
|
+
\u2705 Updated to v${newVersion}`);
|
|
106
|
+
if (options?.restart !== false) {
|
|
107
|
+
console.log(` \u{1F504} Restarting services...`);
|
|
108
|
+
try {
|
|
109
|
+
const jlist = execSync('pm2 jlist 2>/dev/null || echo "[]"', { encoding: "utf-8", timeout: 1e4 });
|
|
110
|
+
const procs = JSON.parse(jlist);
|
|
111
|
+
const amProcs = procs.filter((p) => {
|
|
112
|
+
const script = p.pm2_env?.pm_exec_path || "";
|
|
113
|
+
return script.includes("agenticmail") || script.includes("enterprise");
|
|
114
|
+
});
|
|
115
|
+
if (amProcs.length > 0) {
|
|
116
|
+
const names = amProcs.map((p) => p.name).join(" ");
|
|
117
|
+
console.log(` Restarting: ${names}`);
|
|
118
|
+
execSync(`pm2 restart ${names}`, { stdio: "inherit", timeout: 3e4 });
|
|
119
|
+
execSync("pm2 save", { stdio: "ignore", timeout: 1e4 });
|
|
120
|
+
console.log(` \u2705 All services restarted`);
|
|
121
|
+
} else {
|
|
122
|
+
console.log(` \u26A0\uFE0F No PM2 processes found \u2014 restart manually if needed`);
|
|
123
|
+
}
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.log(` \u26A0\uFE0F Could not restart PM2: ${err.message}`);
|
|
126
|
+
console.log(` Run manually: pm2 restart enterprise && pm2 save`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
console.log("");
|
|
130
|
+
return { success: true, from: current, to: newVersion, message: `Updated from ${current} to ${newVersion}` };
|
|
131
|
+
}
|
|
132
|
+
function setupAutoUpdateCron() {
|
|
133
|
+
console.log(`
|
|
134
|
+
\u{1F380} Auto-Update Cron Setup
|
|
135
|
+
`);
|
|
136
|
+
const isWindows = platform() === "win32";
|
|
137
|
+
if (isWindows) {
|
|
138
|
+
const taskName = "AgenticMailEnterprise-AutoUpdate";
|
|
139
|
+
const cmd = `npm install -g ${PKG_NAME}@latest && pm2 restart enterprise && pm2 save`;
|
|
140
|
+
console.log(` Creating Windows Task Scheduler entry...`);
|
|
141
|
+
console.log(` Task: ${taskName}`);
|
|
142
|
+
console.log(` Schedule: Every 6 hours
|
|
143
|
+
`);
|
|
144
|
+
try {
|
|
145
|
+
execSync(
|
|
146
|
+
`schtasks /create /tn "${taskName}" /tr "cmd /c ${cmd}" /sc HOURLY /mo 6 /f`,
|
|
147
|
+
{ stdio: "inherit" }
|
|
148
|
+
);
|
|
149
|
+
console.log(` \u2705 Auto-update scheduled!`);
|
|
150
|
+
console.log(` To remove: schtasks /delete /tn "${taskName}" /f
|
|
151
|
+
`);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
console.error(` \u274C Failed: ${err.message}`);
|
|
154
|
+
console.log(` Manual alternative:`);
|
|
155
|
+
console.log(` schtasks /create /tn "${taskName}" /tr "cmd /c ${cmd}" /sc HOURLY /mo 6
|
|
156
|
+
`);
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
const npmPath = execSync("which npm", { encoding: "utf-8" }).trim();
|
|
160
|
+
const pm2Path = execSync("which pm2 2>/dev/null || echo pm2", { encoding: "utf-8" }).trim();
|
|
161
|
+
const cronLine = `0 */6 * * * ${npmPath} install -g ${PKG_NAME}@latest && ${pm2Path} restart enterprise && ${pm2Path} save 2>/dev/null`;
|
|
162
|
+
const cronTag = "# agenticmail-auto-update";
|
|
163
|
+
console.log(` Adding cron job (every 6 hours):
|
|
164
|
+
`);
|
|
165
|
+
console.log(` ${cronLine}
|
|
166
|
+
`);
|
|
167
|
+
try {
|
|
168
|
+
const existing = execSync('crontab -l 2>/dev/null || echo ""', { encoding: "utf-8" });
|
|
169
|
+
if (existing.includes(cronTag)) {
|
|
170
|
+
console.log(` \u26A0\uFE0F Auto-update cron already installed. Replacing...`);
|
|
171
|
+
const filtered = existing.split("\n").filter((l) => !l.includes(cronTag) && !l.includes("agenticmail")).join("\n");
|
|
172
|
+
const newCron = `${filtered.trimEnd()}
|
|
173
|
+
${cronLine} ${cronTag}
|
|
174
|
+
`;
|
|
175
|
+
execSync(`echo "${newCron}" | crontab -`, { timeout: 5e3 });
|
|
176
|
+
} else {
|
|
177
|
+
const newCron = `${existing.trimEnd()}
|
|
178
|
+
${cronLine} ${cronTag}
|
|
179
|
+
`;
|
|
180
|
+
execSync(`echo "${newCron}" | crontab -`, { timeout: 5e3 });
|
|
181
|
+
}
|
|
182
|
+
console.log(` \u2705 Auto-update cron installed!`);
|
|
183
|
+
console.log(` To remove: crontab -e and delete the agenticmail line
|
|
184
|
+
`);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
console.error(` \u274C Failed to install cron: ${err.message}`);
|
|
187
|
+
console.log(` Manual alternative:`);
|
|
188
|
+
console.log(` crontab -e`);
|
|
189
|
+
console.log(` Add: ${cronLine}
|
|
190
|
+
`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function startBackgroundUpdateCheck() {
|
|
195
|
+
setTimeout(async () => {
|
|
196
|
+
try {
|
|
197
|
+
const info = await checkForUpdate();
|
|
198
|
+
if (info.updateAvailable) {
|
|
199
|
+
console.log(`[update] \u{1F380} New version available: v${info.latest} (current: v${info.current})`);
|
|
200
|
+
console.log(`[update] Run: agenticmail-enterprise update`);
|
|
201
|
+
}
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
}, 3e4);
|
|
205
|
+
setInterval(async () => {
|
|
206
|
+
try {
|
|
207
|
+
const info = await checkForUpdate();
|
|
208
|
+
if (info.updateAvailable) {
|
|
209
|
+
console.log(`[update] \u{1F380} New version available: v${info.latest} (current: v${info.current})`);
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
}, 6 * 60 * 6e4);
|
|
214
|
+
}
|
|
215
|
+
async function runUpdate(args) {
|
|
216
|
+
if (args.includes("--cron")) {
|
|
217
|
+
setupAutoUpdateCron();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (args.includes("--check")) {
|
|
221
|
+
const info = await checkForUpdate();
|
|
222
|
+
if (info.updateAvailable) {
|
|
223
|
+
console.log(`
|
|
224
|
+
\u{1F380} Update available: v${info.current} \u2192 v${info.latest}`);
|
|
225
|
+
console.log(` Run: agenticmail-enterprise update
|
|
226
|
+
`);
|
|
227
|
+
} else {
|
|
228
|
+
console.log(`
|
|
229
|
+
\u2705 Up to date (v${info.current})
|
|
230
|
+
`);
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const noRestart = args.includes("--no-restart");
|
|
235
|
+
await performUpdate({ restart: !noRestart });
|
|
236
|
+
}
|
|
237
|
+
export {
|
|
238
|
+
checkForUpdate,
|
|
239
|
+
getCachedUpdateCheck,
|
|
240
|
+
getCurrentVersion,
|
|
241
|
+
getLatestVersion,
|
|
242
|
+
performUpdate,
|
|
243
|
+
runUpdate,
|
|
244
|
+
setupAutoUpdateCron,
|
|
245
|
+
startBackgroundUpdateCheck
|
|
246
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -38,6 +38,10 @@ Commands:
|
|
|
38
38
|
recover Recover a domain/subdomain on a new machine
|
|
39
39
|
reset-password Reset admin password directly in the database
|
|
40
40
|
verify-domain Check DNS verification for your domain
|
|
41
|
+
update / upgrade Update to the latest version
|
|
42
|
+
--check Check for updates without installing
|
|
43
|
+
--cron Set up automatic updates (cron/task scheduler)
|
|
44
|
+
--no-restart Update without restarting PM2 services
|
|
41
45
|
|
|
42
46
|
Domain Recovery & Verification:
|
|
43
47
|
npx @agenticmail/enterprise recover
|
|
@@ -55,16 +59,20 @@ Skill Development:
|
|
|
55
59
|
npx @agenticmail/enterprise submit-skill ./community-skills/my-skill/
|
|
56
60
|
`);
|
|
57
61
|
break;
|
|
62
|
+
case "update":
|
|
63
|
+
case "upgrade":
|
|
64
|
+
import("./cli-update-6ZZTT5UR.js").then((m) => m.runUpdate(args.slice(1))).catch(fatal);
|
|
65
|
+
break;
|
|
58
66
|
case "serve":
|
|
59
67
|
case "start":
|
|
60
|
-
import("./cli-serve-
|
|
68
|
+
import("./cli-serve-IOSXZHIK.js").then((m) => m.runServe(args.slice(1))).catch(fatal);
|
|
61
69
|
break;
|
|
62
70
|
case "agent":
|
|
63
|
-
import("./cli-agent-
|
|
71
|
+
import("./cli-agent-HWRINZSE.js").then((m) => m.runAgent(args.slice(1))).catch(fatal);
|
|
64
72
|
break;
|
|
65
73
|
case "setup":
|
|
66
74
|
default:
|
|
67
|
-
import("./setup-
|
|
75
|
+
import("./setup-C22ILWUJ.js").then((m) => m.runSetupWizard()).catch(fatal);
|
|
68
76
|
break;
|
|
69
77
|
}
|
|
70
78
|
function fatal(err) {
|
package/dist/dashboard/app.js
CHANGED
|
@@ -135,6 +135,8 @@ function App() {
|
|
|
135
135
|
const [permissions, setPermissions] = useState('*'); // '*' = full access, or { pageId: true | ['tab1','tab2'] }
|
|
136
136
|
const [mustResetPassword, setMustResetPassword] = useState(false);
|
|
137
137
|
const [show2faReminder, setShow2faReminder] = useState(false);
|
|
138
|
+
const [updateInfo, setUpdateInfo] = useState(null);
|
|
139
|
+
const [updating, setUpdating] = useState(false);
|
|
138
140
|
const [impersonating, _setImpersonating] = useState(function() {
|
|
139
141
|
try { var s = localStorage.getItem('em_impersonating'); return s ? JSON.parse(s) : null; } catch { return null; }
|
|
140
142
|
});
|
|
@@ -329,7 +331,14 @@ function App() {
|
|
|
329
331
|
if (!authChecked) return h('div', { style: { minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--bg-primary)', color: 'var(--text-muted)' } }, 'Loading...');
|
|
330
332
|
if (needsSetup === true && !authed) return h(OnboardingWizard, { onComplete: () => { setNeedsSetup(false); setAuthed(true); authCall('/me').then(d => { setUser(d.user || d); }).catch(() => {}); } });
|
|
331
333
|
if (!authed) return h(LoginPage, { onLogin: async (d) => {
|
|
332
|
-
if (d?.user) {
|
|
334
|
+
if (d?.user) {
|
|
335
|
+
setUser(d.user);
|
|
336
|
+
if (!d.user.totpEnabled) setShow2faReminder(true);
|
|
337
|
+
// Check for updates (admin only)
|
|
338
|
+
if (d.user.role === 'admin' || d.user.role === 'owner') {
|
|
339
|
+
apiCall('/admin/system/update-check').then(u => { if (u?.updateAvailable) setUpdateInfo(u); }).catch(() => {});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
333
342
|
if (d?.mustResetPassword) setMustResetPassword(true);
|
|
334
343
|
// Init encryption before enabling dashboard
|
|
335
344
|
try {
|
|
@@ -544,6 +553,37 @@ function App() {
|
|
|
544
553
|
h('button', { className: 'btn btn-warning btn-sm', onClick: () => { setPage('settings'); setShow2faReminder(false); history.pushState(null, '', '/dashboard/settings'); } }, 'Set Up 2FA'),
|
|
545
554
|
h('button', { className: 'btn btn-ghost btn-sm', onClick: () => setShow2faReminder(false), style: { padding: '2px 6px', minWidth: 0 } }, '\u00d7')
|
|
546
555
|
),
|
|
556
|
+
// Update available banner
|
|
557
|
+
updateInfo && h('div', { style: { display: 'flex', alignItems: 'center', gap: 12, padding: '10px 16px', margin: '0 0 16px', background: 'rgba(16,185,129,0.1)', border: '1px solid rgba(16,185,129,0.4)', borderRadius: 8, fontSize: 13 } },
|
|
558
|
+
h('span', { style: { fontSize: 18 } }, '\uD83C\uDF80'),
|
|
559
|
+
h('div', { style: { flex: 1 } },
|
|
560
|
+
h('strong', null, 'Update Available'),
|
|
561
|
+
h('span', { style: { color: 'var(--text-secondary)', marginLeft: 6 } },
|
|
562
|
+
'v' + updateInfo.current + ' \u2192 v' + updateInfo.latest
|
|
563
|
+
)
|
|
564
|
+
),
|
|
565
|
+
h('button', {
|
|
566
|
+
className: 'btn btn-sm',
|
|
567
|
+
disabled: updating,
|
|
568
|
+
style: { background: 'rgba(16,185,129,0.9)', color: '#fff', border: 'none' },
|
|
569
|
+
onClick: async () => {
|
|
570
|
+
setUpdating(true);
|
|
571
|
+
try {
|
|
572
|
+
const r = await apiCall('/admin/system/update', { method: 'POST' });
|
|
573
|
+
if (r?.success) {
|
|
574
|
+
setUpdateInfo(null);
|
|
575
|
+
showToast && showToast('Updated to v' + r.to + ' — services restarting...', 'success');
|
|
576
|
+
} else {
|
|
577
|
+
showToast && showToast('Update failed: ' + (r?.message || 'unknown'), 'error');
|
|
578
|
+
}
|
|
579
|
+
} catch (e) {
|
|
580
|
+
showToast && showToast('Update failed: ' + e.message, 'error');
|
|
581
|
+
}
|
|
582
|
+
setUpdating(false);
|
|
583
|
+
}
|
|
584
|
+
}, updating ? 'Updating...' : 'Update Now'),
|
|
585
|
+
h('button', { className: 'btn btn-ghost btn-sm', onClick: () => setUpdateInfo(null), style: { padding: '2px 6px', minWidth: 0 } }, '\u00d7')
|
|
586
|
+
),
|
|
547
587
|
selectedAgentId
|
|
548
588
|
? h(AgentDetailPage, { agentId: selectedAgentId, onBack: () => { _setSelectedAgentId(null); _setPage('agents'); history.pushState(null, '', '/dashboard/agents'); } })
|
|
549
589
|
: page === 'agents'
|