agenticmail 0.5.28 → 0.5.30

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/cli.js CHANGED
@@ -4627,8 +4627,8 @@ async function cmdSetup() {
4627
4627
  }
4628
4628
  await new Promise((r) => setTimeout(r, 300));
4629
4629
  }
4630
- const stalwart = deps.find((d) => d.name === "stalwart");
4631
- if (!stalwart?.installed) {
4630
+ const stalwartDep = deps.find((d) => d.name === "stalwart");
4631
+ if (!stalwartDep?.installed) {
4632
4632
  const spinner = new Spinner("stalwart");
4633
4633
  spinner.start();
4634
4634
  const stalwartSetup = new SetupManager((msg) => spinner.update(msg));
@@ -4640,6 +4640,11 @@ async function cmdSetup() {
4640
4640
  process.exit(1);
4641
4641
  }
4642
4642
  } else {
4643
+ ok2(`${c2.bold("Mail Server")} ${c2.dim("\u2014 already running")}`);
4644
+ await new Promise((r) => setTimeout(r, 300));
4645
+ }
4646
+ {
4647
+ let stalwartAuthOk = false;
4643
4648
  try {
4644
4649
  const authCheck = await fetch(`${result.config.stalwart.url}/api/principal`, {
4645
4650
  headers: {
@@ -4647,36 +4652,33 @@ async function cmdSetup() {
4647
4652
  },
4648
4653
  signal: AbortSignal.timeout(5e3)
4649
4654
  });
4650
- if (authCheck.status === 401) {
4651
- const spinner = new Spinner("stalwart", "Resetting mail server (stale credentials)...");
4652
- spinner.start();
4655
+ stalwartAuthOk = authCheck.status !== 401;
4656
+ } catch {
4657
+ }
4658
+ if (!stalwartAuthOk) {
4659
+ const spinner = new Spinner("stalwart", "Resetting mail server (stale credentials)...");
4660
+ spinner.start();
4661
+ try {
4662
+ const { execFileSync } = await import("child_process");
4663
+ execFileSync("docker", ["rm", "-f", "agenticmail-stalwart"], { timeout: 15e3, stdio: "ignore" });
4653
4664
  try {
4654
- const { execFileSync } = await import("child_process");
4655
- execFileSync("docker", ["rm", "-f", "agenticmail-stalwart"], { timeout: 15e3, stdio: "ignore" });
4656
- try {
4657
- const volumes = execFileSync(
4658
- "docker",
4659
- ["volume", "ls", "-q", "--filter", "name=stalwart-data"],
4660
- { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
4661
- ).toString().trim();
4662
- for (const vol of volumes.split("\n").filter(Boolean)) {
4663
- execFileSync("docker", ["volume", "rm", vol], { timeout: 1e4, stdio: "ignore" });
4664
- }
4665
- } catch {
4665
+ const volumes = execFileSync(
4666
+ "docker",
4667
+ ["volume", "ls", "-q", "--filter", "name=stalwart-data"],
4668
+ { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
4669
+ ).toString().trim();
4670
+ for (const vol of volumes.split("\n").filter(Boolean)) {
4671
+ execFileSync("docker", ["volume", "rm", vol], { timeout: 1e4, stdio: "ignore" });
4666
4672
  }
4667
- await setup.ensureStalwart();
4668
- spinner.succeed(`${c2.bold("Mail Server")} \u2014 recreated with fresh credentials`);
4669
- } catch (err) {
4670
- spinner.fail(`Couldn't reset mail server: ${err.message}`);
4671
- process.exit(1);
4673
+ } catch {
4672
4674
  }
4673
- } else {
4674
- ok2(`${c2.bold("Mail Server")} ${c2.dim("\u2014 already running")}`);
4675
+ await setup.ensureStalwart();
4676
+ spinner.succeed(`${c2.bold("Mail Server")} \u2014 recreated with fresh credentials`);
4677
+ } catch (err) {
4678
+ spinner.fail(`Couldn't reset mail server: ${err.message}`);
4679
+ process.exit(1);
4675
4680
  }
4676
- } catch {
4677
- ok2(`${c2.bold("Mail Server")} ${c2.dim("\u2014 already running")}`);
4678
4681
  }
4679
- await new Promise((r) => setTimeout(r, 300));
4680
4682
  }
4681
4683
  const cf = deps.find((d) => d.name === "cloudflared");
4682
4684
  if (!cf?.installed) {
@@ -5208,7 +5210,13 @@ function parseFriendlyError(rawText) {
5208
5210
  isAuthError: true
5209
5211
  };
5210
5212
  }
5211
- if (error.includes("Invalid API key") || error.includes("Unauthorized") || error.includes("401")) {
5213
+ if (error.includes("Stalwart API error") && (error.includes("401") || error.includes("Unauthorized"))) {
5214
+ return {
5215
+ message: "Mail server credentials mismatch \u2014 the container may have stale data. Run: agenticmail setup",
5216
+ isAuthError: false
5217
+ };
5218
+ }
5219
+ if (error.includes("Invalid API key") || error.includes("Master API key required")) {
5212
5220
  return {
5213
5221
  message: "Server authorization failed \u2014 the mail server may still be starting up. Try again in a moment.",
5214
5222
  isAuthError: false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenticmail",
3
- "version": "0.5.28",
3
+ "version": "0.5.30",
4
4
  "description": "Email and SMS infrastructure for AI agents \u2014 the first platform to give agents real email addresses and phone numbers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,6 +16,7 @@
16
16
  },
17
17
  "files": [
18
18
  "dist",
19
+ "scripts",
19
20
  "README.md",
20
21
  "REFERENCE.md",
21
22
  "LICENSE"
@@ -23,6 +24,7 @@
23
24
  "scripts": {
24
25
  "build": "tsup src/index.ts src/cli.ts --format esm --dts --clean",
25
26
  "test": "vitest run --passWithNoTests",
27
+ "preuninstall": "node scripts/uninstall.mjs",
26
28
  "prepublishOnly": "npm run build"
27
29
  },
28
30
  "dependencies": {
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Cleanup script that runs on `npm uninstall agenticmail`.
5
+ *
6
+ * 1. Unloads & removes the launchd / systemd auto-start service
7
+ * 2. Stops & removes the agenticmail-stalwart Docker container
8
+ * 3. Cleans agenticmail entries from OpenClaw config
9
+ * 4. Removes ~/.agenticmail data directory
10
+ */
11
+
12
+ import { execSync, execFileSync } from 'node:child_process';
13
+ import { readFileSync, writeFileSync, existsSync, rmSync, unlinkSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ import { homedir, platform } from 'node:os';
16
+
17
+ const home = homedir();
18
+ const os = platform();
19
+
20
+ function log(msg) { console.log(`[agenticmail] ${msg}`); }
21
+ function tryExec(cmd, opts = {}) { try { execSync(cmd, { timeout: 15_000, stdio: 'ignore', ...opts }); } catch { /* ignore */ } }
22
+
23
+ // ── 1. Unload auto-start service ─────────────────────────────────
24
+
25
+ if (os === 'darwin') {
26
+ const plist = join(home, 'Library', 'LaunchAgents', 'com.agenticmail.server.plist');
27
+ if (existsSync(plist)) {
28
+ log('Unloading launchd service...');
29
+ tryExec(`launchctl bootout gui/$(id -u) "${plist}"`);
30
+ try { unlinkSync(plist); } catch { /* ignore */ }
31
+ }
32
+ } else if (os === 'linux') {
33
+ const unit = 'agenticmail.service';
34
+ tryExec(`systemctl --user stop ${unit}`);
35
+ tryExec(`systemctl --user disable ${unit}`);
36
+ const unitPath = join(home, '.config', 'systemd', 'user', unit);
37
+ if (existsSync(unitPath)) {
38
+ try { unlinkSync(unitPath); } catch { /* ignore */ }
39
+ tryExec('systemctl --user daemon-reload');
40
+ }
41
+ }
42
+
43
+ // ── 2. Stop Docker container ──────────────────────────────────────
44
+
45
+ try {
46
+ const ps = execFileSync('docker', ['ps', '-a', '--filter', 'name=agenticmail-stalwart', '--format', '{{.Names}}'],
47
+ { timeout: 10_000, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
48
+ if (ps.includes('agenticmail-stalwart')) {
49
+ log('Stopping mail server container...');
50
+ tryExec('docker rm -f agenticmail-stalwart');
51
+ }
52
+ } catch { /* docker not available — skip */ }
53
+
54
+ // ── 3. Clean OpenClaw config ──────────────────────────────────────
55
+
56
+ const openclawConfig = join(home, '.openclaw', 'openclaw.json');
57
+ if (existsSync(openclawConfig)) {
58
+ try {
59
+ const raw = readFileSync(openclawConfig, 'utf-8');
60
+ const config = JSON.parse(raw);
61
+ let changed = false;
62
+
63
+ // Remove plugins.entries.agenticmail
64
+ if (config.plugins?.entries?.agenticmail) {
65
+ delete config.plugins.entries.agenticmail;
66
+ changed = true;
67
+ if (Object.keys(config.plugins.entries).length === 0) delete config.plugins.entries;
68
+ }
69
+
70
+ // Remove our path from plugins.load.paths
71
+ if (Array.isArray(config.plugins?.load?.paths)) {
72
+ const before = config.plugins.load.paths.length;
73
+ config.plugins.load.paths = config.plugins.load.paths.filter(
74
+ (p) => !p.includes('@agenticmail')
75
+ );
76
+ if (config.plugins.load.paths.length !== before) changed = true;
77
+ if (config.plugins.load.paths.length === 0) {
78
+ delete config.plugins.load.paths;
79
+ if (config.plugins.load && Object.keys(config.plugins.load).length === 0) delete config.plugins.load;
80
+ }
81
+ }
82
+
83
+ if (changed) {
84
+ writeFileSync(openclawConfig, JSON.stringify(config, null, 2) + '\n');
85
+ log('Cleaned OpenClaw config');
86
+ }
87
+ } catch { /* don't fail uninstall */ }
88
+ }
89
+
90
+ // ── 4. Remove ~/.agenticmail ──────────────────────────────────────
91
+
92
+ const dataDir = join(home, '.agenticmail');
93
+ if (existsSync(dataDir)) {
94
+ log('Removing ~/.agenticmail/ ...');
95
+ try { rmSync(dataDir, { recursive: true, force: true }); } catch { /* ignore */ }
96
+ }
97
+
98
+ log('Uninstall complete.');