@agenticmail/core 0.9.4 → 0.9.6

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/index.js CHANGED
@@ -78,7 +78,7 @@ var MailSender = class {
78
78
  const code = err?.responseCode ?? err?.code;
79
79
  const isTransient = typeof code === "number" && code >= 400 && code < 500 || code === "ECONNRESET" || code === "ETIMEDOUT" || code === "ESOCKET";
80
80
  if (!isTransient || attempt === MAX_RETRIES) throw err;
81
- await new Promise((resolve) => setTimeout(resolve, 1e3 * (attempt + 1)));
81
+ await new Promise((resolve2) => setTimeout(resolve2, 1e3 * (attempt + 1)));
82
82
  }
83
83
  }
84
84
  throw lastError;
@@ -1010,20 +1010,20 @@ var StalwartAdmin = class {
1010
1010
  }
1011
1011
  try {
1012
1012
  const net = await import("net");
1013
- return await new Promise((resolve) => {
1013
+ return await new Promise((resolve2) => {
1014
1014
  const smtpPort = parseInt(process.env.SMTP_PORT || "25", 10);
1015
1015
  const smtpHost = process.env.SMTP_HOST || "localhost";
1016
1016
  const socket = net.createConnection({ host: smtpHost, port: smtpPort, timeout: 3e3 }, () => {
1017
1017
  socket.destroy();
1018
- resolve(true);
1018
+ resolve2(true);
1019
1019
  });
1020
1020
  socket.on("error", () => {
1021
1021
  socket.destroy();
1022
- resolve(false);
1022
+ resolve2(false);
1023
1023
  });
1024
1024
  socket.on("timeout", () => {
1025
1025
  socket.destroy();
1026
- resolve(false);
1026
+ resolve2(false);
1027
1027
  });
1028
1028
  });
1029
1029
  } catch {
@@ -1093,14 +1093,14 @@ var StalwartAdmin = class {
1093
1093
  if (!isValidDomain(domain)) {
1094
1094
  throw new Error(`Invalid domain format: "${domain}"`);
1095
1095
  }
1096
- const { readFileSync: readFileSync7, writeFileSync: writeFileSync8 } = await import("fs");
1097
- const { homedir: homedir11 } = await import("os");
1098
- const { join: join12 } = await import("path");
1099
- const configPath = join12(homedir11(), ".agenticmail", "stalwart.toml");
1096
+ const { readFileSync: readFileSync8, writeFileSync: writeFileSync9 } = await import("fs");
1097
+ const { homedir: homedir12 } = await import("os");
1098
+ const { join: join14 } = await import("path");
1099
+ const configPath = join14(homedir12(), ".agenticmail", "stalwart.toml");
1100
1100
  try {
1101
- let config = readFileSync7(configPath, "utf-8");
1101
+ let config = readFileSync8(configPath, "utf-8");
1102
1102
  config = config.replace(/^hostname\s*=\s*"[^"]*"/m, `hostname = "${escapeTomlString(domain)}"`);
1103
- writeFileSync8(configPath, config);
1103
+ writeFileSync9(configPath, config);
1104
1104
  console.log(`[Stalwart] Updated hostname to "${domain}" in stalwart.toml`);
1105
1105
  } catch (err) {
1106
1106
  throw new Error(`Failed to set config server.hostname=${domain}`);
@@ -1109,15 +1109,15 @@ var StalwartAdmin = class {
1109
1109
  // --- DKIM ---
1110
1110
  /** Path to the host-side stalwart.toml (mounted read-only into container) */
1111
1111
  get configPath() {
1112
- const { homedir: homedir11 } = __require("os");
1113
- const { join: join12 } = __require("path");
1114
- return join12(homedir11(), ".agenticmail", "stalwart.toml");
1112
+ const { homedir: homedir12 } = __require("os");
1113
+ const { join: join14 } = __require("path");
1114
+ return join14(homedir12(), ".agenticmail", "stalwart.toml");
1115
1115
  }
1116
1116
  /** Path to host-side DKIM key directory */
1117
1117
  get dkimDir() {
1118
- const { homedir: homedir11 } = __require("os");
1119
- const { join: join12 } = __require("path");
1120
- return join12(homedir11(), ".agenticmail");
1118
+ const { homedir: homedir12 } = __require("os");
1119
+ const { join: join14 } = __require("path");
1120
+ return join14(homedir12(), ".agenticmail");
1121
1121
  }
1122
1122
  /**
1123
1123
  * Create/reuse a DKIM signing key for a domain.
@@ -1218,12 +1218,12 @@ var StalwartAdmin = class {
1218
1218
  * This bypasses the need for a PTR record on the sending IP.
1219
1219
  */
1220
1220
  async configureOutboundRelay(config) {
1221
- const { readFileSync: readFileSync7, writeFileSync: writeFileSync8 } = await import("fs");
1222
- const { homedir: homedir11 } = await import("os");
1223
- const { join: join12 } = await import("path");
1221
+ const { readFileSync: readFileSync8, writeFileSync: writeFileSync9 } = await import("fs");
1222
+ const { homedir: homedir12 } = await import("os");
1223
+ const { join: join14 } = await import("path");
1224
1224
  const routeName = config.routeName ?? "gmail";
1225
- const tomlPath = join12(homedir11(), ".agenticmail", "stalwart.toml");
1226
- let toml = readFileSync7(tomlPath, "utf-8");
1225
+ const tomlPath = join14(homedir12(), ".agenticmail", "stalwart.toml");
1226
+ let toml = readFileSync8(tomlPath, "utf-8");
1227
1227
  toml = toml.replace(/\n\[queue\.route\.gmail\][\s\S]*?(?=\n\[|$)/, "");
1228
1228
  toml = toml.replace(/\n\[queue\.strategy\][\s\S]*?(?=\n\[|$)/, "");
1229
1229
  const safeRouteName = routeName.replace(/[^a-zA-Z0-9_-]/g, "");
@@ -1243,7 +1243,7 @@ auth.secret = "${escapeTomlString(config.password)}"
1243
1243
  route = [ { if = "is_local_domain('', rcpt_domain)", then = "'local'" },
1244
1244
  { else = "'${safeRouteName}'" } ]
1245
1245
  `;
1246
- writeFileSync8(tomlPath, toml, "utf-8");
1246
+ writeFileSync9(tomlPath, toml, "utf-8");
1247
1247
  await this.restartContainer();
1248
1248
  }
1249
1249
  };
@@ -2327,7 +2327,8 @@ var OUTBOUND_TEXT_RULES = [
2327
2327
  var HIGH_RISK_EXTENSIONS = /* @__PURE__ */ new Set([".pem", ".key", ".p12", ".pfx", ".env", ".credentials", ".keystore", ".jks", ".p8"]);
2328
2328
  var MEDIUM_RISK_EXTENSIONS = /* @__PURE__ */ new Set([".db", ".sqlite", ".sqlite3", ".sql", ".csv", ".tsv", ".json", ".yml", ".yaml", ".conf", ".config", ".ini"]);
2329
2329
  function stripHtmlTags(html) {
2330
- return html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#0?39;/g, "'").replace(/&nbsp;/g, " ").replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))).replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(parseInt(dec, 10)));
2330
+ const bounded = html.length > 1048576 ? html.slice(0, 1048576) : html;
2331
+ return bounded.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#0?39;/g, "'").replace(/&nbsp;/g, " ").replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))).replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(parseInt(dec, 10)));
2331
2332
  }
2332
2333
  var TEXT_SCANNABLE_TYPES = /* @__PURE__ */ new Set([
2333
2334
  "text/plain",
@@ -3859,7 +3860,7 @@ var CloudflareClient = class {
3859
3860
  // --- Workers methods ---
3860
3861
  /** Deploy an Email Worker script (ES module format) */
3861
3862
  async deployEmailWorker(scriptName, scriptContent, envVars = {}) {
3862
- const url = `${CF_API_BASE}/accounts/${this.accountId}/workers/scripts/${scriptName}`;
3863
+ const url = `${CF_API_BASE}/accounts/${encodeURIComponent(this.accountId)}/workers/scripts/${encodeURIComponent(scriptName)}`;
3863
3864
  const bindings = Object.entries(envVars).map(([name, text2]) => ({
3864
3865
  type: "plain_text",
3865
3866
  name,
@@ -4239,9 +4240,9 @@ var TunnelManager = class {
4239
4240
  throw new Error(`Failed to download cloudflared: ${response.statusText}`);
4240
4241
  }
4241
4242
  const buffer = Buffer.from(await response.arrayBuffer());
4242
- const { writeFile: writeFile2, rename: rename2 } = await import("fs/promises");
4243
+ const { writeFile: writeFile3, rename: rename2 } = await import("fs/promises");
4243
4244
  const tmpPath = this.binPath + ".tmp";
4244
- await writeFile2(tmpPath, buffer);
4245
+ await writeFile3(tmpPath, buffer);
4245
4246
  await chmod(tmpPath, 493);
4246
4247
  await rename2(tmpPath, this.binPath);
4247
4248
  return this.binPath;
@@ -4277,7 +4278,7 @@ var TunnelManager = class {
4277
4278
  detached: false,
4278
4279
  env: { ...process.env, TUNNEL_TOKEN: tunnelToken }
4279
4280
  });
4280
- await new Promise((resolve, reject) => {
4281
+ await new Promise((resolve2, reject) => {
4281
4282
  let resolved = false;
4282
4283
  const timeout = setTimeout(() => {
4283
4284
  if (!resolved) {
@@ -4291,7 +4292,7 @@ var TunnelManager = class {
4291
4292
  resolved = true;
4292
4293
  clearTimeout(timeout);
4293
4294
  this.running = true;
4294
- resolve();
4295
+ resolve2();
4295
4296
  }
4296
4297
  };
4297
4298
  this.process.stderr?.on("data", onData);
@@ -4330,8 +4331,8 @@ var TunnelManager = class {
4330
4331
  this.process = null;
4331
4332
  p.kill("SIGTERM");
4332
4333
  await Promise.race([
4333
- new Promise((resolve) => p.on("exit", () => resolve())),
4334
- new Promise((resolve) => setTimeout(resolve, 5e3))
4334
+ new Promise((resolve2) => p.on("exit", () => resolve2())),
4335
+ new Promise((resolve2) => setTimeout(resolve2, 5e3))
4335
4336
  ]);
4336
4337
  this.running = false;
4337
4338
  }
@@ -4386,7 +4387,10 @@ function parseGoogleVoiceSms(emailBody, emailFrom) {
4386
4387
  if (!emailBody || typeof emailBody !== "string") return null;
4387
4388
  if (!emailFrom || typeof emailFrom !== "string") return null;
4388
4389
  const fromLower = emailFrom.toLowerCase();
4389
- const isGoogleVoice = fromLower.includes("voice-noreply@google.com") || fromLower.includes("@txt.voice.google.com") || fromLower.includes("voice.google.com") || fromLower.includes("google.com/voice") || fromLower.includes("google") && fromLower.includes("voice");
4390
+ const atIdx = fromLower.lastIndexOf("@");
4391
+ const domain = atIdx >= 0 ? fromLower.slice(atIdx + 1).replace(/[>"'\s].*$/, "") : "";
4392
+ const isGoogleDomain = domain === "google.com" || domain.endsWith(".google.com");
4393
+ const isGoogleVoice = isGoogleDomain && (fromLower.startsWith("voice-noreply@") || domain === "txt.voice.google.com" || domain === "voice.google.com" || domain.endsWith(".voice.google.com") || fromLower.includes("voice"));
4390
4394
  if (!isGoogleVoice) return null;
4391
4395
  let text = emailBody.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<\/div>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'").trim();
4392
4396
  let from = "";
@@ -5145,12 +5149,12 @@ var GatewayManager = class {
5145
5149
  zone = await this.cfClient.createZone(domain);
5146
5150
  }
5147
5151
  const existingRecords = await this.cfClient.listDnsRecords(zone.id);
5148
- const { homedir: homedir11 } = await import("os");
5149
- const backupDir = join4(homedir11(), ".agenticmail");
5152
+ const { homedir: homedir12 } = await import("os");
5153
+ const backupDir = join4(homedir12(), ".agenticmail");
5150
5154
  const backupPath = join4(backupDir, `dns-backup-${domain}-${Date.now()}.json`);
5151
- const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync9 } = await import("fs");
5152
- mkdirSync9(backupDir, { recursive: true });
5153
- writeFileSync8(backupPath, JSON.stringify({
5155
+ const { writeFileSync: writeFileSync9, mkdirSync: mkdirSync10 } = await import("fs");
5156
+ mkdirSync10(backupDir, { recursive: true });
5157
+ writeFileSync9(backupPath, JSON.stringify({
5154
5158
  domain,
5155
5159
  zoneId: zone.id,
5156
5160
  backedUpAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5417,6 +5421,15 @@ var GatewayManager = class {
5417
5421
  bcc: mail.bcc ? Array.isArray(mail.bcc) ? mail.bcc.join(", ") : mail.bcc : void 0,
5418
5422
  subject: mail.subject,
5419
5423
  text: mail.text || void 0,
5424
+ // The `html` field is the literal HTML body of the outbound
5425
+ // mail — by design it is whatever the sender chose to compose.
5426
+ // CodeQL `js/xss` flags this because the value flows from user
5427
+ // input, but nodemailer is the SMTP serializer, not an HTML
5428
+ // renderer; XSS would only occur if the recipient's MUA
5429
+ // executed the body, which is outside our trust boundary.
5430
+ // The outbound-guard (packages/core/src/mail/outbound-guard.ts)
5431
+ // already scores HTML bodies for suspicious patterns at the
5432
+ // pre-send step. lgtm[js/xss]
5420
5433
  html: mail.html || void 0,
5421
5434
  replyTo: mail.replyTo || from,
5422
5435
  inReplyTo: mail.inReplyTo || void 0,
@@ -5778,11 +5791,11 @@ var RelayBridge = class {
5778
5791
  this.options = options;
5779
5792
  }
5780
5793
  async start() {
5781
- return new Promise((resolve, reject) => {
5794
+ return new Promise((resolve2, reject) => {
5782
5795
  this.server = createServer((req, res) => this.handleRequest(req, res));
5783
5796
  this.server.listen(this.options.port, "127.0.0.1", () => {
5784
5797
  console.log(`[RelayBridge] Listening on 127.0.0.1:${this.options.port}`);
5785
- resolve();
5798
+ resolve2();
5786
5799
  });
5787
5800
  this.server.on("error", reject);
5788
5801
  });
@@ -5984,17 +5997,232 @@ try {
5984
5997
  } catch {
5985
5998
  }
5986
5999
 
6000
+ // src/util/safe-path.ts
6001
+ import { isAbsolute, join as join6, resolve, sep } from "path";
6002
+ var PathTraversalError = class extends Error {
6003
+ constructor(baseDir, parts) {
6004
+ super(
6005
+ `path traversal attempt: ${JSON.stringify(parts)} resolves outside ${baseDir}`
6006
+ );
6007
+ this.baseDir = baseDir;
6008
+ this.parts = parts;
6009
+ this.name = "PathTraversalError";
6010
+ }
6011
+ };
6012
+ function safeJoin(baseDir, ...partsAndOpts) {
6013
+ let opts = {};
6014
+ const parts = [];
6015
+ for (const p of partsAndOpts) {
6016
+ if (typeof p === "string") {
6017
+ parts.push(p);
6018
+ } else if (p && typeof p === "object") {
6019
+ opts = p;
6020
+ }
6021
+ }
6022
+ if (!opts.allowAbsolute) {
6023
+ for (const part of parts) {
6024
+ if (isAbsolute(part)) {
6025
+ throw new PathTraversalError(baseDir, parts);
6026
+ }
6027
+ }
6028
+ }
6029
+ const resolvedBase = resolve(baseDir);
6030
+ const resolved = resolve(resolvedBase, ...parts);
6031
+ if (resolved !== resolvedBase && !resolved.startsWith(resolvedBase + sep)) {
6032
+ throw new PathTraversalError(baseDir, parts);
6033
+ }
6034
+ return resolved;
6035
+ }
6036
+ function tryJoin(baseDir, ...parts) {
6037
+ try {
6038
+ return safeJoin(baseDir, ...parts);
6039
+ } catch (err) {
6040
+ if (err instanceof PathTraversalError) return null;
6041
+ throw err;
6042
+ }
6043
+ }
6044
+ function assertWithinBase(baseDir, candidate) {
6045
+ const resolvedBase = resolve(baseDir);
6046
+ const resolvedCandidate = resolve(candidate);
6047
+ if (resolvedCandidate !== resolvedBase && !resolvedCandidate.startsWith(resolvedBase + sep)) {
6048
+ throw new PathTraversalError(baseDir, [candidate]);
6049
+ }
6050
+ return resolvedCandidate;
6051
+ }
6052
+
6053
+ // src/util/redact.ts
6054
+ var REDACTED = "***";
6055
+ function redactSecret(value) {
6056
+ if (typeof value !== "string") return REDACTED;
6057
+ if (value.length === 0) return REDACTED;
6058
+ for (const prefix of ["mk_", "ak_", "sk-", "pk_", "tok_"]) {
6059
+ if (value.startsWith(prefix)) return `${prefix}${REDACTED}`;
6060
+ }
6061
+ return REDACTED;
6062
+ }
6063
+ var SENSITIVE_KEY_PATTERNS = [
6064
+ /key$/i,
6065
+ /secret/i,
6066
+ /password/i,
6067
+ /passwd/i,
6068
+ /token/i,
6069
+ /authorization/i,
6070
+ /bearer/i,
6071
+ /\bapikey\b/i,
6072
+ /\bapi_key\b/i,
6073
+ /\bmasterkey\b/i,
6074
+ /\bmaster_key\b/i
6075
+ ];
6076
+ function looksSensitive(key) {
6077
+ return SENSITIVE_KEY_PATTERNS.some((p) => p.test(key));
6078
+ }
6079
+ function redactObject(input, _depth = 0) {
6080
+ if (_depth > 12) return input;
6081
+ if (input === null || input === void 0) return input;
6082
+ if (typeof input !== "object") return input;
6083
+ if (Array.isArray(input)) {
6084
+ return input.map((v) => redactObject(v, _depth + 1));
6085
+ }
6086
+ const proto = Object.getPrototypeOf(input);
6087
+ if (proto !== Object.prototype && proto !== null) return input;
6088
+ const out = {};
6089
+ for (const [k, v] of Object.entries(input)) {
6090
+ if (looksSensitive(k)) {
6091
+ out[k] = typeof v === "string" ? redactSecret(v) : REDACTED;
6092
+ } else {
6093
+ out[k] = redactObject(v, _depth + 1);
6094
+ }
6095
+ }
6096
+ return out;
6097
+ }
6098
+
6099
+ // src/host-sessions.ts
6100
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync4 } from "fs";
6101
+ import { join as join7 } from "path";
6102
+ import { homedir as homedir5 } from "os";
6103
+ function storageDir() {
6104
+ return join7(homedir5(), ".agenticmail");
6105
+ }
6106
+ function storagePath() {
6107
+ return join7(storageDir(), "host-sessions.json");
6108
+ }
6109
+ var DEFAULT_SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
6110
+ function readFile() {
6111
+ const p = storagePath();
6112
+ if (!existsSync4(p)) return { version: 1, sessions: {} };
6113
+ try {
6114
+ const raw = readFileSync3(p, "utf-8");
6115
+ if (!raw.trim()) return { version: 1, sessions: {} };
6116
+ const parsed = JSON.parse(raw);
6117
+ return {
6118
+ version: 1,
6119
+ sessions: parsed.sessions && typeof parsed.sessions === "object" ? parsed.sessions : {}
6120
+ };
6121
+ } catch {
6122
+ return { version: 1, sessions: {} };
6123
+ }
6124
+ }
6125
+ function writeFile(shape) {
6126
+ const dir = storageDir();
6127
+ const p = storagePath();
6128
+ if (!existsSync4(dir)) mkdirSync4(dir, { recursive: true });
6129
+ const tmp = `${p}.agenticmail-tmp-${process.pid}`;
6130
+ writeFileSync4(tmp, JSON.stringify(shape, null, 2), "utf-8");
6131
+ renameSync(tmp, p);
6132
+ }
6133
+ function saveHostSession(host, session) {
6134
+ if (!session.sessionId) return;
6135
+ try {
6136
+ const shape = readFile();
6137
+ shape.sessions[host] = {
6138
+ ...session,
6139
+ lastSeenMs: Date.now()
6140
+ };
6141
+ writeFile(shape);
6142
+ } catch {
6143
+ }
6144
+ }
6145
+ function loadHostSession(host, maxAgeMs = DEFAULT_SESSION_MAX_AGE_MS) {
6146
+ const shape = readFile();
6147
+ const record = shape.sessions[host];
6148
+ if (!record) return null;
6149
+ if (!isSessionFresh(record, maxAgeMs)) return null;
6150
+ return record;
6151
+ }
6152
+ function isSessionFresh(session, maxAgeMs = DEFAULT_SESSION_MAX_AGE_MS) {
6153
+ if (!session || !Number.isFinite(session.lastSeenMs)) return false;
6154
+ return Date.now() - session.lastSeenMs <= maxAgeMs;
6155
+ }
6156
+ function forgetHostSession(host) {
6157
+ try {
6158
+ const shape = readFile();
6159
+ if (!shape.sessions[host]) return;
6160
+ delete shape.sessions[host];
6161
+ writeFile(shape);
6162
+ } catch {
6163
+ }
6164
+ }
6165
+ function hostSessionStoragePath() {
6166
+ return storagePath();
6167
+ }
6168
+
6169
+ // src/util/safe-url.ts
6170
+ var UnsafeApiUrlError = class extends Error {
6171
+ constructor(raw, reason) {
6172
+ super(`unsafe AgenticMail API URL ${JSON.stringify(raw)}: ${reason}`);
6173
+ this.raw = raw;
6174
+ this.reason = reason;
6175
+ this.name = "UnsafeApiUrlError";
6176
+ }
6177
+ };
6178
+ var BLOCKED_HOSTS = /* @__PURE__ */ new Set([
6179
+ "169.254.169.254",
6180
+ // AWS / Azure / GCP IPv4 metadata
6181
+ "fd00:ec2::254",
6182
+ // AWS IPv6 metadata
6183
+ "metadata.google.internal",
6184
+ // GCP DNS
6185
+ "metadata.azure.internal"
6186
+ // Azure DNS
6187
+ ]);
6188
+ function validateApiUrl(raw) {
6189
+ if (typeof raw !== "string" || raw.length === 0) {
6190
+ throw new UnsafeApiUrlError(String(raw), "must be a non-empty string");
6191
+ }
6192
+ let parsed;
6193
+ try {
6194
+ parsed = new URL(raw);
6195
+ } catch (err) {
6196
+ throw new UnsafeApiUrlError(raw, `unparseable: ${err.message}`);
6197
+ }
6198
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
6199
+ throw new UnsafeApiUrlError(raw, `unsupported protocol: ${parsed.protocol}`);
6200
+ }
6201
+ const host = parsed.hostname.toLowerCase().replace(/\.$/, "");
6202
+ if (BLOCKED_HOSTS.has(host)) {
6203
+ throw new UnsafeApiUrlError(raw, `blocked metadata host: ${host}`);
6204
+ }
6205
+ if (parsed.username || parsed.password) {
6206
+ throw new UnsafeApiUrlError(raw, "embedded credentials are not supported");
6207
+ }
6208
+ return parsed.origin;
6209
+ }
6210
+ function buildApiUrl(baseOrigin, pathAndQuery) {
6211
+ const path = pathAndQuery.startsWith("/") ? pathAndQuery : `/${pathAndQuery}`;
6212
+ return new URL(path, baseOrigin + "/").toString();
6213
+ }
6214
+
5987
6215
  // src/setup/index.ts
5988
6216
  import { randomBytes as randomBytes3 } from "crypto";
5989
- import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, chmodSync as chmodSync2 } from "fs";
5990
- import { join as join9 } from "path";
5991
- import { homedir as homedir8 } from "os";
6217
+ import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, chmodSync as chmodSync2 } from "fs";
6218
+ import { join as join11 } from "path";
6219
+ import { homedir as homedir9 } from "os";
5992
6220
 
5993
6221
  // src/setup/deps.ts
5994
6222
  import { execFileSync } from "child_process";
5995
- import { existsSync as existsSync4 } from "fs";
5996
- import { join as join6 } from "path";
5997
- import { homedir as homedir5 } from "os";
6223
+ import { existsSync as existsSync5 } from "fs";
6224
+ import { join as join8 } from "path";
6225
+ import { homedir as homedir6 } from "os";
5998
6226
  var DependencyChecker = class {
5999
6227
  async checkAll() {
6000
6228
  return Promise.all([
@@ -6044,8 +6272,8 @@ var DependencyChecker = class {
6044
6272
  }
6045
6273
  }
6046
6274
  async checkCloudflared() {
6047
- const binPath = join6(homedir5(), ".agenticmail", "bin", "cloudflared");
6048
- if (existsSync4(binPath)) {
6275
+ const binPath = join8(homedir6(), ".agenticmail", "bin", "cloudflared");
6276
+ if (existsSync5(binPath)) {
6049
6277
  let version;
6050
6278
  try {
6051
6279
  const output = execFileSync(binPath, ["--version"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
@@ -6067,14 +6295,14 @@ var DependencyChecker = class {
6067
6295
 
6068
6296
  // src/setup/installer.ts
6069
6297
  import { execFileSync as execFileSync2, execSync, spawn as spawnChild } from "child_process";
6070
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, symlinkSync } from "fs";
6071
- import { writeFile, rename, chmod as chmod2, mkdir as mkdir2, unlink } from "fs/promises";
6072
- import { join as join7 } from "path";
6073
- import { homedir as homedir6, platform as platform3, arch as arch2 } from "os";
6298
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, symlinkSync } from "fs";
6299
+ import { writeFile as writeFile2, rename, chmod as chmod2, mkdir as mkdir2, unlink } from "fs/promises";
6300
+ import { join as join9 } from "path";
6301
+ import { homedir as homedir7, platform as platform3, arch as arch2 } from "os";
6074
6302
  function runShellWithRollingOutput(cmd, opts = {}) {
6075
6303
  const maxLines = opts.maxLines ?? 20;
6076
6304
  const timeout = opts.timeout ?? 3e5;
6077
- return new Promise((resolve, reject) => {
6305
+ return new Promise((resolve2, reject) => {
6078
6306
  const child = spawnChild("sh", ["-c", cmd], {
6079
6307
  stdio: ["ignore", "pipe", "pipe"],
6080
6308
  timeout
@@ -6116,7 +6344,7 @@ function runShellWithRollingOutput(cmd, opts = {}) {
6116
6344
  }
6117
6345
  process.stdout.write(`\x1B[${displayedCount}A`);
6118
6346
  }
6119
- resolve({ exitCode: code ?? 1, fullOutput });
6347
+ resolve2({ exitCode: code ?? 1, fullOutput });
6120
6348
  });
6121
6349
  child.on("error", (err) => {
6122
6350
  if (displayedCount > 0) {
@@ -6132,7 +6360,7 @@ function runShellWithRollingOutput(cmd, opts = {}) {
6132
6360
  }
6133
6361
  function runSilent(command, args, opts = {}) {
6134
6362
  const timeout = opts.timeout ?? 3e5;
6135
- return new Promise((resolve, reject) => {
6363
+ return new Promise((resolve2, reject) => {
6136
6364
  const child = spawnChild(command, args, {
6137
6365
  stdio: ["ignore", "pipe", "pipe"],
6138
6366
  timeout
@@ -6144,7 +6372,7 @@ function runSilent(command, args, opts = {}) {
6144
6372
  child.stderr?.on("data", (d) => {
6145
6373
  fullOutput += d.toString();
6146
6374
  });
6147
- child.on("close", (code) => resolve({ exitCode: code ?? 1, fullOutput }));
6375
+ child.on("close", (code) => resolve2({ exitCode: code ?? 1, fullOutput }));
6148
6376
  child.on("error", reject);
6149
6377
  });
6150
6378
  }
@@ -6246,11 +6474,11 @@ var DependencyInstaller = class {
6246
6474
  try {
6247
6475
  const composeBin = execFileSync2("which", ["docker-compose"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
6248
6476
  if (!composeBin) return;
6249
- const pluginDir = join7(homedir6(), ".docker", "cli-plugins");
6250
- const pluginPath = join7(pluginDir, "docker-compose");
6251
- if (existsSync5(pluginPath)) return;
6477
+ const pluginDir = join9(homedir7(), ".docker", "cli-plugins");
6478
+ const pluginPath = join9(pluginDir, "docker-compose");
6479
+ if (existsSync6(pluginPath)) return;
6252
6480
  try {
6253
- mkdirSync4(pluginDir, { recursive: true });
6481
+ mkdirSync5(pluginDir, { recursive: true });
6254
6482
  } catch {
6255
6483
  }
6256
6484
  try {
@@ -6313,9 +6541,9 @@ var DependencyInstaller = class {
6313
6541
  return;
6314
6542
  }
6315
6543
  this.onProgress("__progress__:5:Installing Docker Engine...");
6316
- const tmpDir = join7(homedir6(), ".agenticmail", "tmp");
6544
+ const tmpDir = join9(homedir7(), ".agenticmail", "tmp");
6317
6545
  await mkdir2(tmpDir, { recursive: true });
6318
- const scriptPath = join7(tmpDir, "install-docker.sh");
6546
+ const scriptPath = join9(tmpDir, "install-docker.sh");
6319
6547
  const dlResult = await runShellWithRollingOutput(
6320
6548
  `curl -fsSL https://get.docker.com -o "${scriptPath}" && sudo sh "${scriptPath}"`,
6321
6549
  { timeout: 3e5 }
@@ -6383,8 +6611,8 @@ var DependencyInstaller = class {
6383
6611
  }
6384
6612
  try {
6385
6613
  const programFiles = process.env["ProgramFiles"] || "C:\\Program Files";
6386
- const dockerExe = join7(programFiles, "Docker", "Docker", "Docker Desktop.exe");
6387
- if (existsSync5(dockerExe)) {
6614
+ const dockerExe = join9(programFiles, "Docker", "Docker", "Docker Desktop.exe");
6615
+ if (existsSync6(dockerExe)) {
6388
6616
  execSync(`start "" "${dockerExe}"`, { timeout: 1e4, stdio: "ignore", shell: "cmd.exe" });
6389
6617
  }
6390
6618
  } catch {
@@ -6434,8 +6662,8 @@ var DependencyInstaller = class {
6434
6662
  this.onProgress("__progress__:70:Docker Desktop installed. Starting...");
6435
6663
  try {
6436
6664
  const programFiles = process.env["ProgramFiles"] || "C:\\Program Files";
6437
- const dockerExe = join7(programFiles, "Docker", "Docker", "Docker Desktop.exe");
6438
- if (existsSync5(dockerExe)) {
6665
+ const dockerExe = join9(programFiles, "Docker", "Docker", "Docker Desktop.exe");
6666
+ if (existsSync6(dockerExe)) {
6439
6667
  execSync(`start "" "${dockerExe}"`, { timeout: 1e4, stdio: "ignore", shell: "cmd.exe" });
6440
6668
  }
6441
6669
  } catch {
@@ -6472,7 +6700,7 @@ var DependencyInstaller = class {
6472
6700
  * Start the Stalwart mail server Docker container.
6473
6701
  */
6474
6702
  async startStalwart(composePath) {
6475
- if (!existsSync5(composePath)) {
6703
+ if (!existsSync6(composePath)) {
6476
6704
  throw new Error(`docker-compose.yml not found at: ${composePath}`);
6477
6705
  }
6478
6706
  if (!this.isDockerReady()) {
@@ -6531,16 +6759,16 @@ var DependencyInstaller = class {
6531
6759
  */
6532
6760
  async installCloudflared() {
6533
6761
  const os = platform3();
6534
- const binDir = join7(homedir6(), ".agenticmail", "bin");
6762
+ const binDir = join9(homedir7(), ".agenticmail", "bin");
6535
6763
  const binName = os === "win32" ? "cloudflared.exe" : "cloudflared";
6536
- const binPath = join7(binDir, binName);
6537
- if (existsSync5(binPath)) {
6764
+ const binPath = join9(binDir, binName);
6765
+ if (existsSync6(binPath)) {
6538
6766
  return binPath;
6539
6767
  }
6540
6768
  try {
6541
6769
  const whichCmd = os === "win32" ? "where" : "which";
6542
6770
  const sysPath = execFileSync2(whichCmd, ["cloudflared"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim().split("\n")[0];
6543
- if (sysPath && existsSync5(sysPath)) return sysPath;
6771
+ if (sysPath && existsSync6(sysPath)) return sysPath;
6544
6772
  } catch {
6545
6773
  }
6546
6774
  this.onProgress("Downloading cloudflared...");
@@ -6563,8 +6791,8 @@ var DependencyInstaller = class {
6563
6791
  }
6564
6792
  const buffer = Buffer.from(await response.arrayBuffer());
6565
6793
  if (os === "darwin") {
6566
- const tgzPath = join7(binDir, "cloudflared.tgz");
6567
- await writeFile(tgzPath, buffer);
6794
+ const tgzPath = join9(binDir, "cloudflared.tgz");
6795
+ await writeFile2(tgzPath, buffer);
6568
6796
  try {
6569
6797
  execFileSync2("tar", ["-xzf", tgzPath, "-C", binDir, "cloudflared"], { timeout: 15e3, stdio: "ignore" });
6570
6798
  await chmod2(binPath, 493);
@@ -6576,11 +6804,11 @@ var DependencyInstaller = class {
6576
6804
  }
6577
6805
  } else {
6578
6806
  const tmpPath = binPath + ".tmp";
6579
- await writeFile(tmpPath, buffer);
6807
+ await writeFile2(tmpPath, buffer);
6580
6808
  if (os !== "win32") await chmod2(tmpPath, 493);
6581
6809
  await rename(tmpPath, binPath);
6582
6810
  }
6583
- if (!existsSync5(binPath)) {
6811
+ if (!existsSync6(binPath)) {
6584
6812
  throw new Error("cloudflared download succeeded but binary not found after extraction");
6585
6813
  }
6586
6814
  this.onProgress("cloudflared installed");
@@ -6598,9 +6826,9 @@ var DependencyInstaller = class {
6598
6826
 
6599
6827
  // src/setup/service.ts
6600
6828
  import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
6601
- import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync4, unlinkSync, mkdirSync as mkdirSync5, chmodSync } from "fs";
6602
- import { join as join8 } from "path";
6603
- import { homedir as homedir7, platform as platform4 } from "os";
6829
+ import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync5, unlinkSync, mkdirSync as mkdirSync6, chmodSync } from "fs";
6830
+ import { join as join10 } from "path";
6831
+ import { homedir as homedir8, platform as platform4 } from "os";
6604
6832
  import { createRequire as createRequire2 } from "module";
6605
6833
  var PLIST_LABEL = "com.agenticmail.server";
6606
6834
  var SYSTEMD_UNIT = "agenticmail.service";
@@ -6611,9 +6839,9 @@ var ServiceManager = class {
6611
6839
  */
6612
6840
  getServicePath() {
6613
6841
  if (this.os === "darwin") {
6614
- return join8(homedir7(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
6842
+ return join10(homedir8(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
6615
6843
  } else {
6616
- return join8(homedir7(), ".config", "systemd", "user", SYSTEMD_UNIT);
6844
+ return join10(homedir8(), ".config", "systemd", "user", SYSTEMD_UNIT);
6617
6845
  }
6618
6846
  }
6619
6847
  /**
@@ -6647,40 +6875,40 @@ var ServiceManager = class {
6647
6875
  try {
6648
6876
  const req = createRequire2(import.meta.url);
6649
6877
  const resolved = req.resolve("@agenticmail/api");
6650
- if (existsSync6(resolved)) return resolved;
6878
+ if (existsSync7(resolved)) return resolved;
6651
6879
  } catch {
6652
6880
  }
6653
6881
  const parentPackages = [
6654
- join8("@agenticmail", "cli"),
6882
+ join10("@agenticmail", "cli"),
6655
6883
  // current scoped package
6656
6884
  "agenticmail"
6657
6885
  // legacy unscoped package
6658
6886
  ];
6659
6887
  const baseDirs = [
6660
6888
  // user-local install
6661
- join8(homedir7(), "node_modules")
6889
+ join10(homedir8(), "node_modules")
6662
6890
  ];
6663
6891
  try {
6664
6892
  const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
6665
- baseDirs.push(join8(prefix, "lib", "node_modules"));
6666
- baseDirs.push(join8(prefix, "node_modules"));
6893
+ baseDirs.push(join10(prefix, "lib", "node_modules"));
6894
+ baseDirs.push(join10(prefix, "node_modules"));
6667
6895
  } catch {
6668
6896
  }
6669
6897
  baseDirs.push("/opt/homebrew/lib/node_modules");
6670
6898
  baseDirs.push("/usr/local/lib/node_modules");
6671
6899
  for (const base of baseDirs) {
6672
- const sibling = join8(base, "@agenticmail", "api", "dist", "index.js");
6673
- if (existsSync6(sibling)) return sibling;
6900
+ const sibling = join10(base, "@agenticmail", "api", "dist", "index.js");
6901
+ if (existsSync7(sibling)) return sibling;
6674
6902
  for (const parent of parentPackages) {
6675
- const nested = join8(base, parent, "node_modules", "@agenticmail", "api", "dist", "index.js");
6676
- if (existsSync6(nested)) return nested;
6903
+ const nested = join10(base, parent, "node_modules", "@agenticmail", "api", "dist", "index.js");
6904
+ if (existsSync7(nested)) return nested;
6677
6905
  }
6678
6906
  }
6679
- const dataDir = join8(homedir7(), ".agenticmail");
6680
- const entryCache = join8(dataDir, "api-entry.path");
6681
- if (existsSync6(entryCache)) {
6682
- const cached = readFileSync3(entryCache, "utf-8").trim();
6683
- if (cached && existsSync6(cached)) return cached;
6907
+ const dataDir = join10(homedir8(), ".agenticmail");
6908
+ const entryCache = join10(dataDir, "api-entry.path");
6909
+ if (existsSync7(entryCache)) {
6910
+ const cached = readFileSync4(entryCache, "utf-8").trim();
6911
+ if (cached && existsSync7(cached)) return cached;
6684
6912
  }
6685
6913
  throw new Error("Could not find @agenticmail/api entry point. Run `agenticmail start` first to populate the cache.");
6686
6914
  }
@@ -6688,9 +6916,9 @@ var ServiceManager = class {
6688
6916
  * Cache the API entry path so the service can find it later.
6689
6917
  */
6690
6918
  cacheApiEntryPath(entryPath) {
6691
- const dataDir = join8(homedir7(), ".agenticmail");
6692
- if (!existsSync6(dataDir)) mkdirSync5(dataDir, { recursive: true });
6693
- writeFileSync4(join8(dataDir, "api-entry.path"), entryPath);
6919
+ const dataDir = join10(homedir8(), ".agenticmail");
6920
+ if (!existsSync7(dataDir)) mkdirSync6(dataDir, { recursive: true });
6921
+ writeFileSync5(join10(dataDir, "api-entry.path"), entryPath);
6694
6922
  }
6695
6923
  /**
6696
6924
  * Get the current package version.
@@ -6703,36 +6931,36 @@ var ServiceManager = class {
6703
6931
  try {
6704
6932
  const req = createRequire2(import.meta.url);
6705
6933
  const pkgJson = req.resolve("@agenticmail/cli/package.json");
6706
- if (existsSync6(pkgJson)) {
6707
- const pkg = JSON.parse(readFileSync3(pkgJson, "utf-8"));
6934
+ if (existsSync7(pkgJson)) {
6935
+ const pkg = JSON.parse(readFileSync4(pkgJson, "utf-8"));
6708
6936
  if (pkg.version) return pkg.version;
6709
6937
  }
6710
6938
  } catch {
6711
6939
  }
6712
6940
  try {
6713
6941
  const apiEntry = this.getApiEntryPath();
6714
- const apiPkg = join8(apiEntry, "..", "..", "package.json");
6715
- if (existsSync6(apiPkg)) {
6716
- const pkg = JSON.parse(readFileSync3(apiPkg, "utf-8"));
6942
+ const apiPkg = join10(apiEntry, "..", "..", "package.json");
6943
+ if (existsSync7(apiPkg)) {
6944
+ const pkg = JSON.parse(readFileSync4(apiPkg, "utf-8"));
6717
6945
  if (pkg.version) return pkg.version;
6718
6946
  }
6719
6947
  } catch {
6720
6948
  }
6721
6949
  const candidates = [
6722
- join8(homedir7(), "node_modules", "@agenticmail", "cli", "package.json"),
6723
- join8(homedir7(), "node_modules", "agenticmail", "package.json"),
6724
- join8(homedir7(), ".agenticmail", "package-version.json")
6950
+ join10(homedir8(), "node_modules", "@agenticmail", "cli", "package.json"),
6951
+ join10(homedir8(), "node_modules", "agenticmail", "package.json"),
6952
+ join10(homedir8(), ".agenticmail", "package-version.json")
6725
6953
  ];
6726
6954
  try {
6727
6955
  const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
6728
- candidates.push(join8(prefix, "lib", "node_modules", "@agenticmail", "cli", "package.json"));
6729
- candidates.push(join8(prefix, "lib", "node_modules", "agenticmail", "package.json"));
6956
+ candidates.push(join10(prefix, "lib", "node_modules", "@agenticmail", "cli", "package.json"));
6957
+ candidates.push(join10(prefix, "lib", "node_modules", "agenticmail", "package.json"));
6730
6958
  } catch {
6731
6959
  }
6732
6960
  for (const p of candidates) {
6733
6961
  try {
6734
- if (existsSync6(p)) {
6735
- const pkg = JSON.parse(readFileSync3(p, "utf-8"));
6962
+ if (existsSync7(p)) {
6963
+ const pkg = JSON.parse(readFileSync4(p, "utf-8"));
6736
6964
  if (pkg.version) return pkg.version;
6737
6965
  }
6738
6966
  } catch {
@@ -6745,9 +6973,9 @@ var ServiceManager = class {
6745
6973
  * This ensures AgenticMail doesn't fail on boot when Docker is still loading.
6746
6974
  */
6747
6975
  generateStartScript(nodePath, apiEntry) {
6748
- const scriptPath = join8(homedir7(), ".agenticmail", "bin", "start-server.sh");
6749
- const scriptDir = join8(homedir7(), ".agenticmail", "bin");
6750
- if (!existsSync6(scriptDir)) mkdirSync5(scriptDir, { recursive: true });
6976
+ const scriptPath = join10(homedir8(), ".agenticmail", "bin", "start-server.sh");
6977
+ const scriptDir = join10(homedir8(), ".agenticmail", "bin");
6978
+ if (!existsSync7(scriptDir)) mkdirSync6(scriptDir, { recursive: true });
6751
6979
  const script = [
6752
6980
  "#!/bin/bash",
6753
6981
  "# AgenticMail auto-start script",
@@ -6798,7 +7026,7 @@ var ServiceManager = class {
6798
7026
  `log "Starting API server: ${nodePath} ${apiEntry}"`,
6799
7027
  `exec "${nodePath}" "${apiEntry}"`
6800
7028
  ].join("\n") + "\n";
6801
- writeFileSync4(scriptPath, script, { mode: 493 });
7029
+ writeFileSync5(scriptPath, script, { mode: 493 });
6802
7030
  return scriptPath;
6803
7031
  }
6804
7032
  /**
@@ -6811,9 +7039,9 @@ var ServiceManager = class {
6811
7039
  * - Service version tracking in env vars
6812
7040
  */
6813
7041
  generatePlist(nodePath, apiEntry, configPath) {
6814
- const config = JSON.parse(readFileSync3(configPath, "utf-8"));
6815
- const logDir = join8(homedir7(), ".agenticmail", "logs");
6816
- if (!existsSync6(logDir)) mkdirSync5(logDir, { recursive: true });
7042
+ const config = JSON.parse(readFileSync4(configPath, "utf-8"));
7043
+ const logDir = join10(homedir8(), ".agenticmail", "logs");
7044
+ if (!existsSync7(logDir)) mkdirSync6(logDir, { recursive: true });
6817
7045
  const version = this.getVersion();
6818
7046
  const startScript = this.generateStartScript(nodePath, apiEntry);
6819
7047
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -6834,9 +7062,9 @@ var ServiceManager = class {
6834
7062
  <key>EnvironmentVariables</key>
6835
7063
  <dict>
6836
7064
  <key>HOME</key>
6837
- <string>${homedir7()}</string>
7065
+ <string>${homedir8()}</string>
6838
7066
  <key>AGENTICMAIL_DATA_DIR</key>
6839
- <string>${config.dataDir || join8(homedir7(), ".agenticmail")}</string>
7067
+ <string>${config.dataDir || join10(homedir8(), ".agenticmail")}</string>
6840
7068
  <key>PATH</key>
6841
7069
  <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
6842
7070
  <key>AGENTICMAIL_SERVICE_VERSION</key>
@@ -6889,8 +7117,8 @@ var ServiceManager = class {
6889
7117
  * - Proper dependency ordering
6890
7118
  */
6891
7119
  generateSystemdUnit(nodePath, apiEntry, configPath) {
6892
- const config = JSON.parse(readFileSync3(configPath, "utf-8"));
6893
- const dataDir = config.dataDir || join8(homedir7(), ".agenticmail");
7120
+ const config = JSON.parse(readFileSync4(configPath, "utf-8"));
7121
+ const dataDir = config.dataDir || join10(homedir8(), ".agenticmail");
6894
7122
  const version = this.getVersion();
6895
7123
  const startScript = this.generateStartScript(nodePath, apiEntry);
6896
7124
  return `[Unit]
@@ -6907,7 +7135,7 @@ Restart=always
6907
7135
  RestartSec=15
6908
7136
  TimeoutStartSec=660
6909
7137
  LimitNOFILE=8192
6910
- Environment=HOME=${homedir7()}
7138
+ Environment=HOME=${homedir8()}
6911
7139
  Environment=AGENTICMAIL_DATA_DIR=${dataDir}
6912
7140
  Environment=PATH=/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin
6913
7141
  Environment=AGENTICMAIL_SERVICE_VERSION=${version}
@@ -6920,8 +7148,8 @@ WantedBy=default.target
6920
7148
  * Install the auto-start service.
6921
7149
  */
6922
7150
  install() {
6923
- const configPath = join8(homedir7(), ".agenticmail", "config.json");
6924
- if (!existsSync6(configPath)) {
7151
+ const configPath = join10(homedir8(), ".agenticmail", "config.json");
7152
+ if (!existsSync7(configPath)) {
6925
7153
  return { installed: false, message: "Config not found. Run agenticmail setup first." };
6926
7154
  }
6927
7155
  const nodePath = this.getNodePath();
@@ -6933,16 +7161,16 @@ WantedBy=default.target
6933
7161
  }
6934
7162
  const servicePath = this.getServicePath();
6935
7163
  if (this.os === "darwin") {
6936
- const dir = join8(homedir7(), "Library", "LaunchAgents");
6937
- if (!existsSync6(dir)) mkdirSync5(dir, { recursive: true });
6938
- if (existsSync6(servicePath)) {
7164
+ const dir = join10(homedir8(), "Library", "LaunchAgents");
7165
+ if (!existsSync7(dir)) mkdirSync6(dir, { recursive: true });
7166
+ if (existsSync7(servicePath)) {
6939
7167
  try {
6940
7168
  execFileSync3("launchctl", ["unload", servicePath], { timeout: 1e4, stdio: "ignore" });
6941
7169
  } catch {
6942
7170
  }
6943
7171
  }
6944
7172
  const plist = this.generatePlist(nodePath, apiEntry, configPath);
6945
- writeFileSync4(servicePath, plist);
7173
+ writeFileSync5(servicePath, plist);
6946
7174
  chmodSync(servicePath, 384);
6947
7175
  try {
6948
7176
  execFileSync3("launchctl", ["load", servicePath], { timeout: 1e4, stdio: "ignore" });
@@ -6951,10 +7179,10 @@ WantedBy=default.target
6951
7179
  }
6952
7180
  return { installed: true, message: `Service installed at ${servicePath}` };
6953
7181
  } else if (this.os === "linux") {
6954
- const dir = join8(homedir7(), ".config", "systemd", "user");
6955
- if (!existsSync6(dir)) mkdirSync5(dir, { recursive: true });
7182
+ const dir = join10(homedir8(), ".config", "systemd", "user");
7183
+ if (!existsSync7(dir)) mkdirSync6(dir, { recursive: true });
6956
7184
  const unit = this.generateSystemdUnit(nodePath, apiEntry, configPath);
6957
- writeFileSync4(servicePath, unit);
7185
+ writeFileSync5(servicePath, unit);
6958
7186
  chmodSync(servicePath, 384);
6959
7187
  try {
6960
7188
  execFileSync3("systemctl", ["--user", "daemon-reload"], { timeout: 1e4, stdio: "ignore" });
@@ -6977,7 +7205,7 @@ WantedBy=default.target
6977
7205
  */
6978
7206
  uninstall() {
6979
7207
  const servicePath = this.getServicePath();
6980
- if (!existsSync6(servicePath)) {
7208
+ if (!existsSync7(servicePath)) {
6981
7209
  return { removed: false, message: "Service is not installed." };
6982
7210
  }
6983
7211
  if (this.os === "darwin") {
@@ -7015,7 +7243,7 @@ WantedBy=default.target
7015
7243
  status() {
7016
7244
  const servicePath = this.getServicePath();
7017
7245
  const plat = this.os === "darwin" ? "launchd" : this.os === "linux" ? "systemd" : "unsupported";
7018
- const installed = existsSync6(servicePath);
7246
+ const installed = existsSync7(servicePath);
7019
7247
  let running = false;
7020
7248
  if (installed) {
7021
7249
  if (this.os === "darwin") {
@@ -7070,34 +7298,34 @@ WantedBy=default.target
7070
7298
  needsRepair() {
7071
7299
  if (this.os !== "darwin" && this.os !== "linux") return null;
7072
7300
  const servicePath = this.getServicePath();
7073
- if (!existsSync6(servicePath)) return null;
7301
+ if (!existsSync7(servicePath)) return null;
7074
7302
  let serviceContent = "";
7075
7303
  try {
7076
- serviceContent = readFileSync3(servicePath, "utf-8");
7304
+ serviceContent = readFileSync4(servicePath, "utf-8");
7077
7305
  } catch {
7078
7306
  return { reason: "Service file unreadable" };
7079
7307
  }
7080
- const startScript = join8(homedir7(), ".agenticmail", "bin", "start-server.sh");
7308
+ const startScript = join10(homedir8(), ".agenticmail", "bin", "start-server.sh");
7081
7309
  if (serviceContent.includes(startScript)) {
7082
- if (!existsSync6(startScript)) {
7310
+ if (!existsSync7(startScript)) {
7083
7311
  return { reason: "start-server.sh is missing" };
7084
7312
  }
7085
7313
  let scriptContent = "";
7086
7314
  try {
7087
- scriptContent = readFileSync3(startScript, "utf-8");
7315
+ scriptContent = readFileSync4(startScript, "utf-8");
7088
7316
  } catch {
7089
7317
  return { reason: "start-server.sh unreadable" };
7090
7318
  }
7091
7319
  const apiPathMatch = scriptContent.match(/(\/[^"\s]+@agenticmail\/api\/dist\/index\.js)/);
7092
7320
  if (apiPathMatch) {
7093
7321
  const referenced = apiPathMatch[1];
7094
- if (!existsSync6(referenced)) {
7322
+ if (!existsSync7(referenced)) {
7095
7323
  return { reason: `start-server.sh references missing path: ${referenced}` };
7096
7324
  }
7097
7325
  }
7098
7326
  if (/node_modules\/agenticmail\/(?!.*@agenticmail\/cli)/.test(scriptContent)) {
7099
7327
  const stale = /(\S*node_modules\/agenticmail\/\S*)/.exec(scriptContent)?.[1];
7100
- if (stale && !existsSync6(stale)) {
7328
+ if (stale && !existsSync7(stale)) {
7101
7329
  return { reason: `start-server.sh references legacy unscoped path: ${stale}` };
7102
7330
  }
7103
7331
  }
@@ -7110,11 +7338,11 @@ WantedBy=default.target
7110
7338
  return { reason: `Service version drift (current CLI is v${currentVersion})` };
7111
7339
  }
7112
7340
  }
7113
- const entryCache = join8(homedir7(), ".agenticmail", "api-entry.path");
7114
- if (existsSync6(entryCache)) {
7341
+ const entryCache = join10(homedir8(), ".agenticmail", "api-entry.path");
7342
+ if (existsSync7(entryCache)) {
7115
7343
  try {
7116
- const cached = readFileSync3(entryCache, "utf-8").trim();
7117
- if (cached && !existsSync6(cached)) {
7344
+ const cached = readFileSync4(entryCache, "utf-8").trim();
7345
+ if (cached && !existsSync7(cached)) {
7118
7346
  return { reason: `Cached API entry path no longer exists: ${cached}` };
7119
7347
  }
7120
7348
  } catch {
@@ -7165,13 +7393,13 @@ var SetupManager = class {
7165
7393
  * falls back to monorepo location.
7166
7394
  */
7167
7395
  getComposePath() {
7168
- const standalonePath = join9(homedir8(), ".agenticmail", "docker-compose.yml");
7169
- if (existsSync7(standalonePath)) return standalonePath;
7396
+ const standalonePath = join11(homedir9(), ".agenticmail", "docker-compose.yml");
7397
+ if (existsSync8(standalonePath)) return standalonePath;
7170
7398
  const cwd = process.cwd();
7171
- const candidates = [cwd, join9(cwd, "..")];
7399
+ const candidates = [cwd, join11(cwd, "..")];
7172
7400
  for (const dir of candidates) {
7173
- const p = join9(dir, "docker-compose.yml");
7174
- if (existsSync7(p)) return p;
7401
+ const p = join11(dir, "docker-compose.yml");
7402
+ if (existsSync8(p)) return p;
7175
7403
  }
7176
7404
  return standalonePath;
7177
7405
  }
@@ -7181,19 +7409,19 @@ var SetupManager = class {
7181
7409
  * Always regenerates Docker files to keep passwords in sync.
7182
7410
  */
7183
7411
  initConfig() {
7184
- const dataDir = join9(homedir8(), ".agenticmail");
7185
- const configPath = join9(dataDir, "config.json");
7186
- const envPath = join9(dataDir, ".env");
7187
- if (existsSync7(configPath)) {
7412
+ const dataDir = join11(homedir9(), ".agenticmail");
7413
+ const configPath = join11(dataDir, "config.json");
7414
+ const envPath = join11(dataDir, ".env");
7415
+ if (existsSync8(configPath)) {
7188
7416
  try {
7189
- const existing = JSON.parse(readFileSync4(configPath, "utf-8"));
7417
+ const existing = JSON.parse(readFileSync5(configPath, "utf-8"));
7190
7418
  this.generateDockerFiles(existing);
7191
7419
  return { configPath, envPath, config: existing, isNew: false };
7192
7420
  } catch {
7193
7421
  }
7194
7422
  }
7195
- if (!existsSync7(dataDir)) {
7196
- mkdirSync6(dataDir, { recursive: true });
7423
+ if (!existsSync8(dataDir)) {
7424
+ mkdirSync7(dataDir, { recursive: true });
7197
7425
  }
7198
7426
  const masterKey = `mk_${randomBytes3(24).toString("hex")}`;
7199
7427
  const stalwartPassword = randomBytes3(16).toString("hex");
@@ -7209,7 +7437,7 @@ var SetupManager = class {
7209
7437
  api: { port: 3829, host: "127.0.0.1" },
7210
7438
  dataDir
7211
7439
  };
7212
- writeFileSync5(configPath, JSON.stringify(config, null, 2));
7440
+ writeFileSync6(configPath, JSON.stringify(config, null, 2));
7213
7441
  chmodSync2(configPath, 384);
7214
7442
  const envContent = `# Auto-generated by agenticmail setup
7215
7443
  STALWART_ADMIN_USER=admin
@@ -7225,7 +7453,7 @@ SMTP_PORT=587
7225
7453
  IMAP_HOST=localhost
7226
7454
  IMAP_PORT=143
7227
7455
  `;
7228
- writeFileSync5(envPath, envContent);
7456
+ writeFileSync6(envPath, envContent);
7229
7457
  chmodSync2(envPath, 384);
7230
7458
  this.generateDockerFiles(config);
7231
7459
  return { configPath, envPath, config, isNew: true };
@@ -7235,13 +7463,13 @@ IMAP_PORT=143
7235
7463
  * with the correct admin password from config.
7236
7464
  */
7237
7465
  generateDockerFiles(config) {
7238
- const dataDir = config.dataDir || join9(homedir8(), ".agenticmail");
7239
- if (!existsSync7(dataDir)) {
7240
- mkdirSync6(dataDir, { recursive: true });
7466
+ const dataDir = config.dataDir || join11(homedir9(), ".agenticmail");
7467
+ if (!existsSync8(dataDir)) {
7468
+ mkdirSync7(dataDir, { recursive: true });
7241
7469
  }
7242
7470
  const password = config.stalwart?.adminPassword || "changeme";
7243
- const composePath = join9(dataDir, "docker-compose.yml");
7244
- writeFileSync5(composePath, `services:
7471
+ const composePath = join11(dataDir, "docker-compose.yml");
7472
+ writeFileSync6(composePath, `services:
7245
7473
  stalwart:
7246
7474
  # Pinned to v0.15.5 \u2014 Stalwart 0.16+ moved its config to JSON
7247
7475
  # at /etc/stalwart/config.json (hardcoded into the container
@@ -7271,8 +7499,8 @@ volumes:
7271
7499
  stalwart-data:
7272
7500
  `);
7273
7501
  chmodSync2(composePath, 384);
7274
- const tomlPath = join9(dataDir, "stalwart.toml");
7275
- writeFileSync5(tomlPath, `# Stalwart Mail Server \u2014 AgenticMail Configuration
7502
+ const tomlPath = join11(dataDir, "stalwart.toml");
7503
+ writeFileSync6(tomlPath, `# Stalwart Mail Server \u2014 AgenticMail Configuration
7276
7504
 
7277
7505
  [server]
7278
7506
  hostname = "localhost"
@@ -7328,15 +7556,15 @@ secret = "${password}"
7328
7556
  * Check if config has already been initialized.
7329
7557
  */
7330
7558
  isInitialized() {
7331
- const configPath = join9(homedir8(), ".agenticmail", "config.json");
7332
- return existsSync7(configPath);
7559
+ const configPath = join11(homedir9(), ".agenticmail", "config.json");
7560
+ return existsSync8(configPath);
7333
7561
  }
7334
7562
  };
7335
7563
 
7336
7564
  // src/threading/thread-id.ts
7337
7565
  import { createHash as createHash2 } from "crypto";
7338
7566
  function stripReplyPrefixes(subject) {
7339
- let s = subject;
7567
+ let s = subject.length > 1e3 ? subject.slice(0, 1e3) : subject;
7340
7568
  for (; ; ) {
7341
7569
  const next = s.replace(/^\s*(?:re|fwd?|fw)\s*(?:\[\d+\])?\s*:\s*/i, "");
7342
7570
  if (next === s) break;
@@ -7356,8 +7584,9 @@ function normalizeSubject(subject) {
7356
7584
  }
7357
7585
  function normalizeAddress(addr) {
7358
7586
  if (!addr) return "(unknown)";
7359
- const m = addr.match(/<([^>]+)>/);
7360
- const raw = m ? m[1] : addr;
7587
+ const bounded = addr.length > 500 ? addr.slice(0, 500) : addr;
7588
+ const m = bounded.match(/<([^>]+)>/);
7589
+ const raw = m ? m[1] : bounded;
7361
7590
  return raw.trim().toLowerCase();
7362
7591
  }
7363
7592
  function threadIdFor(input) {
@@ -7367,18 +7596,18 @@ function threadIdFor(input) {
7367
7596
 
7368
7597
  // src/threading/thread-cache.ts
7369
7598
  import {
7370
- existsSync as existsSync8,
7371
- mkdirSync as mkdirSync7,
7372
- readFileSync as readFileSync5,
7373
- writeFileSync as writeFileSync6,
7599
+ existsSync as existsSync9,
7600
+ mkdirSync as mkdirSync8,
7601
+ readFileSync as readFileSync6,
7602
+ writeFileSync as writeFileSync7,
7374
7603
  readdirSync,
7375
7604
  statSync,
7376
7605
  rmSync,
7377
- renameSync
7606
+ renameSync as renameSync2
7378
7607
  } from "fs";
7379
- import { homedir as homedir9 } from "os";
7380
- import { join as join10 } from "path";
7381
- var CACHE_DIR_DEFAULT = join10(homedir9(), ".agenticmail", "thread-cache");
7608
+ import { homedir as homedir10 } from "os";
7609
+ import { join as join12 } from "path";
7610
+ var CACHE_DIR_DEFAULT = join12(homedir10(), ".agenticmail", "thread-cache");
7382
7611
  var DEFAULT_K_MESSAGES = 10;
7383
7612
  var DEFAULT_LRU_CAP = 5e3;
7384
7613
  var PREVIEW_MAX_CHARS = 240;
@@ -7391,18 +7620,18 @@ var ThreadCache = class {
7391
7620
  this.k = opts.k ?? DEFAULT_K_MESSAGES;
7392
7621
  this.lruCap = opts.lruCap ?? DEFAULT_LRU_CAP;
7393
7622
  try {
7394
- mkdirSync7(this.dir, { recursive: true });
7623
+ mkdirSync8(this.dir, { recursive: true });
7395
7624
  } catch {
7396
7625
  }
7397
7626
  }
7398
7627
  pathFor(threadId) {
7399
- return join10(this.dir, `${threadId}.json`);
7628
+ return join12(this.dir, `${threadId}.json`);
7400
7629
  }
7401
7630
  read(threadId) {
7402
7631
  const p = this.pathFor(threadId);
7403
- if (!existsSync8(p)) return null;
7632
+ if (!existsSync9(p)) return null;
7404
7633
  try {
7405
- const raw = readFileSync5(p, "utf-8");
7634
+ const raw = readFileSync6(p, "utf-8");
7406
7635
  return JSON.parse(raw);
7407
7636
  } catch {
7408
7637
  try {
@@ -7467,8 +7696,8 @@ var ThreadCache = class {
7467
7696
  writeAtomic(threadId, entry) {
7468
7697
  const p = this.pathFor(threadId);
7469
7698
  const tmp = `${p}.tmp`;
7470
- writeFileSync6(tmp, JSON.stringify(entry), "utf-8");
7471
- renameSync(tmp, p);
7699
+ writeFileSync7(tmp, JSON.stringify(entry), "utf-8");
7700
+ renameSync2(tmp, p);
7472
7701
  }
7473
7702
  /**
7474
7703
  * Best-effort LRU eviction. Runs at most every 256 writes (we
@@ -7486,7 +7715,7 @@ var ThreadCache = class {
7486
7715
  }
7487
7716
  if (files.length <= this.lruCap) return;
7488
7717
  const stats = files.map((f) => {
7489
- const p = join10(this.dir, f);
7718
+ const p = join12(this.dir, f);
7490
7719
  try {
7491
7720
  return { p, mtime: statSync(p).mtimeMs };
7492
7721
  } catch {
@@ -7517,36 +7746,36 @@ function dedupAndCap(messages, k) {
7517
7746
 
7518
7747
  // src/threading/agent-memory.ts
7519
7748
  import {
7520
- existsSync as existsSync9,
7521
- mkdirSync as mkdirSync8,
7522
- readFileSync as readFileSync6,
7523
- writeFileSync as writeFileSync7,
7749
+ existsSync as existsSync10,
7750
+ mkdirSync as mkdirSync9,
7751
+ readFileSync as readFileSync7,
7752
+ writeFileSync as writeFileSync8,
7524
7753
  rmSync as rmSync2,
7525
- renameSync as renameSync2
7754
+ renameSync as renameSync3
7526
7755
  } from "fs";
7527
- import { homedir as homedir10 } from "os";
7528
- import { join as join11 } from "path";
7529
- var MEMORY_DIR_DEFAULT = join11(homedir10(), ".agenticmail", "agent-memory");
7756
+ import { homedir as homedir11 } from "os";
7757
+ import { join as join13 } from "path";
7758
+ var MEMORY_DIR_DEFAULT = join13(homedir11(), ".agenticmail", "agent-memory");
7530
7759
  var AgentMemoryStore = class {
7531
7760
  dir;
7532
7761
  constructor(opts = {}) {
7533
7762
  this.dir = opts.memoryDir ?? MEMORY_DIR_DEFAULT;
7534
7763
  try {
7535
- mkdirSync8(this.dir, { recursive: true });
7764
+ mkdirSync9(this.dir, { recursive: true });
7536
7765
  } catch {
7537
7766
  }
7538
7767
  }
7539
7768
  dirFor(agentId) {
7540
- return join11(this.dir, sanitizeId(agentId));
7769
+ return join13(this.dir, sanitizeId(agentId));
7541
7770
  }
7542
7771
  pathFor(agentId, threadId) {
7543
- return join11(this.dirFor(agentId), `${sanitizeId(threadId)}.md`);
7772
+ return join13(this.dirFor(agentId), `${sanitizeId(threadId)}.md`);
7544
7773
  }
7545
7774
  read(agentId, threadId) {
7546
7775
  const p = this.pathFor(agentId, threadId);
7547
- if (!existsSync9(p)) return null;
7776
+ if (!existsSync10(p)) return null;
7548
7777
  try {
7549
- const raw = readFileSync6(p, "utf-8");
7778
+ const raw = readFileSync7(p, "utf-8");
7550
7779
  const parsed = parse(raw);
7551
7780
  return { ...parsed, raw };
7552
7781
  } catch {
@@ -7556,14 +7785,14 @@ var AgentMemoryStore = class {
7556
7785
  write(agentId, threadId, fields) {
7557
7786
  const agentDir = this.dirFor(agentId);
7558
7787
  try {
7559
- mkdirSync8(agentDir, { recursive: true });
7788
+ mkdirSync9(agentDir, { recursive: true });
7560
7789
  } catch {
7561
7790
  }
7562
7791
  const body = render({ ...fields, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
7563
7792
  const p = this.pathFor(agentId, threadId);
7564
7793
  const tmp = `${p}.tmp`;
7565
- writeFileSync7(tmp, body, "utf-8");
7566
- renameSync2(tmp, p);
7794
+ writeFileSync8(tmp, body, "utf-8");
7795
+ renameSync3(tmp, p);
7567
7796
  }
7568
7797
  delete(agentId, threadId) {
7569
7798
  try {
@@ -7631,6 +7860,7 @@ export {
7631
7860
  CloudflareClient,
7632
7861
  DEFAULT_AGENT_NAME,
7633
7862
  DEFAULT_AGENT_ROLE,
7863
+ DEFAULT_SESSION_MAX_AGE_MS,
7634
7864
  DNSConfigurator,
7635
7865
  DependencyChecker,
7636
7866
  DependencyInstaller,
@@ -7641,6 +7871,8 @@ export {
7641
7871
  InboxWatcher,
7642
7872
  MailReceiver,
7643
7873
  MailSender,
7874
+ PathTraversalError,
7875
+ REDACTED,
7644
7876
  RELAY_PRESETS,
7645
7877
  RelayBridge,
7646
7878
  RelayGateway,
@@ -7652,7 +7884,10 @@ export {
7652
7884
  StalwartAdmin,
7653
7885
  ThreadCache,
7654
7886
  TunnelManager,
7887
+ UnsafeApiUrlError,
7655
7888
  WARNING_THRESHOLD,
7889
+ assertWithinBase,
7890
+ buildApiUrl,
7656
7891
  buildInboundSecurityAdvisory,
7657
7892
  classifyEmailRoute,
7658
7893
  closeDatabase,
@@ -7662,21 +7897,31 @@ export {
7662
7897
  ensureDataDir,
7663
7898
  extractVerificationCode,
7664
7899
  flushTelemetry,
7900
+ forgetHostSession,
7665
7901
  getDatabase,
7902
+ hostSessionStoragePath,
7666
7903
  isInternalEmail,
7904
+ isSessionFresh,
7667
7905
  isValidPhoneNumber,
7906
+ loadHostSession,
7668
7907
  normalizeAddress,
7669
7908
  normalizePhoneNumber,
7670
7909
  normalizeSubject,
7671
7910
  parseEmail,
7672
7911
  parseGoogleVoiceSms,
7673
7912
  recordToolCall,
7913
+ redactObject,
7914
+ redactSecret,
7674
7915
  resolveConfig,
7916
+ safeJoin,
7675
7917
  sanitizeEmail,
7676
7918
  saveConfig,
7919
+ saveHostSession,
7677
7920
  scanOutboundEmail,
7678
7921
  scoreEmail,
7679
7922
  setTelemetryVersion,
7680
7923
  startRelayBridge,
7681
- threadIdFor
7924
+ threadIdFor,
7925
+ tryJoin,
7926
+ validateApiUrl
7682
7927
  };