@agenticmail/core 0.5.56 → 0.5.59

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.cjs CHANGED
@@ -1238,7 +1238,8 @@ var InboxWatcher = class extends import_node_events.EventEmitter {
1238
1238
  this.emit("close");
1239
1239
  this._scheduleReconnect();
1240
1240
  });
1241
- this._lock = lock;
1241
+ lock.release();
1242
+ this._lock = null;
1242
1243
  } catch (err) {
1243
1244
  lock.release();
1244
1245
  throw err;
@@ -1917,7 +1918,15 @@ var AccountManager = class {
1917
1918
  }
1918
1919
  const principalName = options.name.toLowerCase();
1919
1920
  const email = `${principalName}@${domain}`;
1921
+ const existingAgent = await this.getByName(options.name);
1922
+ if (existingAgent != null) {
1923
+ throw new Error(`Account already exists: ${options.name}`);
1924
+ }
1920
1925
  await this.stalwart.ensureDomain(domain);
1926
+ try {
1927
+ await this.stalwart.deletePrincipal(principalName);
1928
+ } catch {
1929
+ }
1921
1930
  await this.stalwart.createPrincipal({
1922
1931
  type: "individual",
1923
1932
  name: principalName,
@@ -7068,6 +7077,8 @@ var import_node_child_process4 = require("child_process");
7068
7077
  var import_node_fs6 = require("fs");
7069
7078
  var import_node_path7 = require("path");
7070
7079
  var import_node_os6 = require("os");
7080
+ var import_node_module = require("module");
7081
+ var import_meta = {};
7071
7082
  var PLIST_LABEL = "com.agenticmail.server";
7072
7083
  var SYSTEMD_UNIT = "agenticmail.service";
7073
7084
  var ServiceManager = class {
@@ -7094,42 +7105,59 @@ var ServiceManager = class {
7094
7105
  }
7095
7106
  /**
7096
7107
  * Find the API server entry point.
7097
- * Searches common locations where agenticmail is installed.
7108
+ *
7109
+ * Issue #26 — Robust path resolution.
7110
+ *
7111
+ * The original implementation hard-coded `node_modules/agenticmail` (the
7112
+ * old unscoped package name). After the rename to `@agenticmail/cli`, that
7113
+ * directory no longer exists, so the resolver fell back to the stale
7114
+ * `~/.agenticmail/api-entry.path` cache and the launchd plist kept pointing
7115
+ * at a deleted path — causing the boot crash loop reported in #26.
7116
+ *
7117
+ * We now prefer `require.resolve('@agenticmail/api')` so the resolution
7118
+ * follows the actual installed location regardless of npm prefix, the
7119
+ * scoped vs unscoped package name, or the package manager (npm global,
7120
+ * pnpm, yarn global, local node_modules). Cached paths are always
7121
+ * validated against the filesystem before being returned.
7098
7122
  */
7099
7123
  getApiEntryPath() {
7100
- const searchDirs = [
7101
- // Global npm install
7102
- (0, import_node_path7.join)((0, import_node_os6.homedir)(), "node_modules", "agenticmail"),
7103
- // npx cache / global prefix
7104
- ...(() => {
7105
- try {
7106
- const prefix = (0, import_node_child_process4.execSync)("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
7107
- return [
7108
- (0, import_node_path7.join)(prefix, "lib", "node_modules", "agenticmail"),
7109
- (0, import_node_path7.join)(prefix, "node_modules", "agenticmail")
7110
- ];
7111
- } catch {
7112
- return [];
7113
- }
7114
- })(),
7115
- // Homebrew on macOS
7116
- "/opt/homebrew/lib/node_modules/agenticmail",
7117
- "/usr/local/lib/node_modules/agenticmail"
7124
+ try {
7125
+ const req = (0, import_node_module.createRequire)(import_meta.url);
7126
+ const resolved = req.resolve("@agenticmail/api");
7127
+ if ((0, import_node_fs6.existsSync)(resolved)) return resolved;
7128
+ } catch {
7129
+ }
7130
+ const parentPackages = [
7131
+ (0, import_node_path7.join)("@agenticmail", "cli"),
7132
+ // current scoped package
7133
+ "agenticmail"
7134
+ // legacy unscoped package
7118
7135
  ];
7119
- for (const base of searchDirs) {
7120
- const apiPaths = [
7121
- (0, import_node_path7.join)(base, "node_modules", "@agenticmail", "api", "dist", "index.js"),
7122
- (0, import_node_path7.join)(base, "..", "@agenticmail", "api", "dist", "index.js")
7123
- ];
7124
- for (const p of apiPaths) {
7125
- if ((0, import_node_fs6.existsSync)(p)) return p;
7136
+ const baseDirs = [
7137
+ // user-local install
7138
+ (0, import_node_path7.join)((0, import_node_os6.homedir)(), "node_modules")
7139
+ ];
7140
+ try {
7141
+ const prefix = (0, import_node_child_process4.execSync)("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
7142
+ baseDirs.push((0, import_node_path7.join)(prefix, "lib", "node_modules"));
7143
+ baseDirs.push((0, import_node_path7.join)(prefix, "node_modules"));
7144
+ } catch {
7145
+ }
7146
+ baseDirs.push("/opt/homebrew/lib/node_modules");
7147
+ baseDirs.push("/usr/local/lib/node_modules");
7148
+ for (const base of baseDirs) {
7149
+ const sibling = (0, import_node_path7.join)(base, "@agenticmail", "api", "dist", "index.js");
7150
+ if ((0, import_node_fs6.existsSync)(sibling)) return sibling;
7151
+ for (const parent of parentPackages) {
7152
+ const nested = (0, import_node_path7.join)(base, parent, "node_modules", "@agenticmail", "api", "dist", "index.js");
7153
+ if ((0, import_node_fs6.existsSync)(nested)) return nested;
7126
7154
  }
7127
7155
  }
7128
7156
  const dataDir = (0, import_node_path7.join)((0, import_node_os6.homedir)(), ".agenticmail");
7129
7157
  const entryCache = (0, import_node_path7.join)(dataDir, "api-entry.path");
7130
7158
  if ((0, import_node_fs6.existsSync)(entryCache)) {
7131
7159
  const cached = (0, import_node_fs6.readFileSync)(entryCache, "utf-8").trim();
7132
- if ((0, import_node_fs6.existsSync)(cached)) return cached;
7160
+ if (cached && (0, import_node_fs6.existsSync)(cached)) return cached;
7133
7161
  }
7134
7162
  throw new Error("Could not find @agenticmail/api entry point. Run `agenticmail start` first to populate the cache.");
7135
7163
  }
@@ -7143,25 +7171,49 @@ var ServiceManager = class {
7143
7171
  }
7144
7172
  /**
7145
7173
  * Get the current package version.
7174
+ *
7175
+ * Issue #26 — resolve the CLI package.json via require.resolve so the
7176
+ * version reflects the *currently installed* @agenticmail/cli, not a
7177
+ * leftover unscoped `agenticmail` package directory.
7146
7178
  */
7147
7179
  getVersion() {
7148
7180
  try {
7149
- const pkgPaths = [
7150
- (0, import_node_path7.join)((0, import_node_os6.homedir)(), "node_modules", "agenticmail", "package.json"),
7151
- (0, import_node_path7.join)((0, import_node_os6.homedir)(), ".agenticmail", "package-version.json")
7152
- ];
7153
- try {
7154
- const prefix = (0, import_node_child_process4.execSync)("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
7155
- pkgPaths.push((0, import_node_path7.join)(prefix, "lib", "node_modules", "agenticmail", "package.json"));
7156
- } catch {
7181
+ const req = (0, import_node_module.createRequire)(import_meta.url);
7182
+ const pkgJson = req.resolve("@agenticmail/cli/package.json");
7183
+ if ((0, import_node_fs6.existsSync)(pkgJson)) {
7184
+ const pkg = JSON.parse((0, import_node_fs6.readFileSync)(pkgJson, "utf-8"));
7185
+ if (pkg.version) return pkg.version;
7157
7186
  }
7158
- for (const p of pkgPaths) {
7187
+ } catch {
7188
+ }
7189
+ try {
7190
+ const apiEntry = this.getApiEntryPath();
7191
+ const apiPkg = (0, import_node_path7.join)(apiEntry, "..", "..", "package.json");
7192
+ if ((0, import_node_fs6.existsSync)(apiPkg)) {
7193
+ const pkg = JSON.parse((0, import_node_fs6.readFileSync)(apiPkg, "utf-8"));
7194
+ if (pkg.version) return pkg.version;
7195
+ }
7196
+ } catch {
7197
+ }
7198
+ const candidates = [
7199
+ (0, import_node_path7.join)((0, import_node_os6.homedir)(), "node_modules", "@agenticmail", "cli", "package.json"),
7200
+ (0, import_node_path7.join)((0, import_node_os6.homedir)(), "node_modules", "agenticmail", "package.json"),
7201
+ (0, import_node_path7.join)((0, import_node_os6.homedir)(), ".agenticmail", "package-version.json")
7202
+ ];
7203
+ try {
7204
+ const prefix = (0, import_node_child_process4.execSync)("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
7205
+ candidates.push((0, import_node_path7.join)(prefix, "lib", "node_modules", "@agenticmail", "cli", "package.json"));
7206
+ candidates.push((0, import_node_path7.join)(prefix, "lib", "node_modules", "agenticmail", "package.json"));
7207
+ } catch {
7208
+ }
7209
+ for (const p of candidates) {
7210
+ try {
7159
7211
  if ((0, import_node_fs6.existsSync)(p)) {
7160
7212
  const pkg = JSON.parse((0, import_node_fs6.readFileSync)(p, "utf-8"));
7161
7213
  if (pkg.version) return pkg.version;
7162
7214
  }
7215
+ } catch {
7163
7216
  }
7164
- } catch {
7165
7217
  }
7166
7218
  return "unknown";
7167
7219
  }
@@ -7467,6 +7519,86 @@ WantedBy=default.target
7467
7519
  this.uninstall();
7468
7520
  return this.install();
7469
7521
  }
7522
+ /**
7523
+ * Issue #26 — Detect a stale service installation.
7524
+ *
7525
+ * Background: when a user upgrades from the old unscoped `agenticmail`
7526
+ * package to the new `@agenticmail/cli` scoped package, the old
7527
+ * ~/Library/LaunchAgents/com.agenticmail.server.plist and
7528
+ * ~/.agenticmail/bin/start-server.sh files keep pointing at
7529
+ * /opt/homebrew/lib/node_modules/agenticmail/... — a path that no longer
7530
+ * exists post-rename. The result is a launchd crash loop.
7531
+ *
7532
+ * `needsRepair()` returns a non-null reason whenever:
7533
+ * - the service file exists but the start-server.sh it launches is
7534
+ * missing or references a node_modules path that no longer resolves;
7535
+ * - the embedded service version drifts from the running CLI version
7536
+ * (so service files get refreshed on every upgrade — including
7537
+ * in-place version bumps that don't change the install path);
7538
+ * - the cached API entry path no longer exists on disk.
7539
+ *
7540
+ * Returns null when everything checks out — callers should treat that as
7541
+ * "no action needed".
7542
+ *
7543
+ * Platform-aware: only inspects launchd artefacts on darwin and systemd
7544
+ * artefacts on linux. Returns null on unsupported platforms so this can
7545
+ * be called unconditionally from the CLI's start path.
7546
+ */
7547
+ needsRepair() {
7548
+ if (this.os !== "darwin" && this.os !== "linux") return null;
7549
+ const servicePath = this.getServicePath();
7550
+ if (!(0, import_node_fs6.existsSync)(servicePath)) return null;
7551
+ let serviceContent = "";
7552
+ try {
7553
+ serviceContent = (0, import_node_fs6.readFileSync)(servicePath, "utf-8");
7554
+ } catch {
7555
+ return { reason: "Service file unreadable" };
7556
+ }
7557
+ const startScript = (0, import_node_path7.join)((0, import_node_os6.homedir)(), ".agenticmail", "bin", "start-server.sh");
7558
+ if (serviceContent.includes(startScript)) {
7559
+ if (!(0, import_node_fs6.existsSync)(startScript)) {
7560
+ return { reason: "start-server.sh is missing" };
7561
+ }
7562
+ let scriptContent = "";
7563
+ try {
7564
+ scriptContent = (0, import_node_fs6.readFileSync)(startScript, "utf-8");
7565
+ } catch {
7566
+ return { reason: "start-server.sh unreadable" };
7567
+ }
7568
+ const apiPathMatch = scriptContent.match(/(\/[^"\s]+@agenticmail\/api\/dist\/index\.js)/);
7569
+ if (apiPathMatch) {
7570
+ const referenced = apiPathMatch[1];
7571
+ if (!(0, import_node_fs6.existsSync)(referenced)) {
7572
+ return { reason: `start-server.sh references missing path: ${referenced}` };
7573
+ }
7574
+ }
7575
+ if (/node_modules\/agenticmail\/(?!.*@agenticmail\/cli)/.test(scriptContent)) {
7576
+ const stale = /(\S*node_modules\/agenticmail\/\S*)/.exec(scriptContent)?.[1];
7577
+ if (stale && !(0, import_node_fs6.existsSync)(stale)) {
7578
+ return { reason: `start-server.sh references legacy unscoped path: ${stale}` };
7579
+ }
7580
+ }
7581
+ } else {
7582
+ return { reason: "Service file does not reference the wrapper script" };
7583
+ }
7584
+ const currentVersion = this.getVersion();
7585
+ if (currentVersion !== "unknown") {
7586
+ if (!serviceContent.includes(`v${currentVersion}`) || !serviceContent.includes(`AGENTICMAIL_SERVICE_VERSION`) || !serviceContent.includes(currentVersion)) {
7587
+ return { reason: `Service version drift (current CLI is v${currentVersion})` };
7588
+ }
7589
+ }
7590
+ const entryCache = (0, import_node_path7.join)((0, import_node_os6.homedir)(), ".agenticmail", "api-entry.path");
7591
+ if ((0, import_node_fs6.existsSync)(entryCache)) {
7592
+ try {
7593
+ const cached = (0, import_node_fs6.readFileSync)(entryCache, "utf-8").trim();
7594
+ if (cached && !(0, import_node_fs6.existsSync)(cached)) {
7595
+ return { reason: `Cached API entry path no longer exists: ${cached}` };
7596
+ }
7597
+ } catch {
7598
+ }
7599
+ }
7600
+ return null;
7601
+ }
7470
7602
  };
7471
7603
 
7472
7604
  // src/setup/index.ts
package/dist/index.d.cts CHANGED
@@ -1558,7 +1558,20 @@ declare class ServiceManager {
1558
1558
  private getNodePath;
1559
1559
  /**
1560
1560
  * Find the API server entry point.
1561
- * Searches common locations where agenticmail is installed.
1561
+ *
1562
+ * Issue #26 — Robust path resolution.
1563
+ *
1564
+ * The original implementation hard-coded `node_modules/agenticmail` (the
1565
+ * old unscoped package name). After the rename to `@agenticmail/cli`, that
1566
+ * directory no longer exists, so the resolver fell back to the stale
1567
+ * `~/.agenticmail/api-entry.path` cache and the launchd plist kept pointing
1568
+ * at a deleted path — causing the boot crash loop reported in #26.
1569
+ *
1570
+ * We now prefer `require.resolve('@agenticmail/api')` so the resolution
1571
+ * follows the actual installed location regardless of npm prefix, the
1572
+ * scoped vs unscoped package name, or the package manager (npm global,
1573
+ * pnpm, yarn global, local node_modules). Cached paths are always
1574
+ * validated against the filesystem before being returned.
1562
1575
  */
1563
1576
  private getApiEntryPath;
1564
1577
  /**
@@ -1567,6 +1580,10 @@ declare class ServiceManager {
1567
1580
  cacheApiEntryPath(entryPath: string): void;
1568
1581
  /**
1569
1582
  * Get the current package version.
1583
+ *
1584
+ * Issue #26 — resolve the CLI package.json via require.resolve so the
1585
+ * version reflects the *currently installed* @agenticmail/cli, not a
1586
+ * leftover unscoped `agenticmail` package directory.
1570
1587
  */
1571
1588
  private getVersion;
1572
1589
  /**
@@ -1619,6 +1636,34 @@ declare class ServiceManager {
1619
1636
  installed: boolean;
1620
1637
  message: string;
1621
1638
  };
1639
+ /**
1640
+ * Issue #26 — Detect a stale service installation.
1641
+ *
1642
+ * Background: when a user upgrades from the old unscoped `agenticmail`
1643
+ * package to the new `@agenticmail/cli` scoped package, the old
1644
+ * ~/Library/LaunchAgents/com.agenticmail.server.plist and
1645
+ * ~/.agenticmail/bin/start-server.sh files keep pointing at
1646
+ * /opt/homebrew/lib/node_modules/agenticmail/... — a path that no longer
1647
+ * exists post-rename. The result is a launchd crash loop.
1648
+ *
1649
+ * `needsRepair()` returns a non-null reason whenever:
1650
+ * - the service file exists but the start-server.sh it launches is
1651
+ * missing or references a node_modules path that no longer resolves;
1652
+ * - the embedded service version drifts from the running CLI version
1653
+ * (so service files get refreshed on every upgrade — including
1654
+ * in-place version bumps that don't change the install path);
1655
+ * - the cached API entry path no longer exists on disk.
1656
+ *
1657
+ * Returns null when everything checks out — callers should treat that as
1658
+ * "no action needed".
1659
+ *
1660
+ * Platform-aware: only inspects launchd artefacts on darwin and systemd
1661
+ * artefacts on linux. Returns null on unsupported platforms so this can
1662
+ * be called unconditionally from the CLI's start path.
1663
+ */
1664
+ needsRepair(): {
1665
+ reason: string;
1666
+ } | null;
1622
1667
  }
1623
1668
 
1624
1669
  interface SetupConfig {
package/dist/index.d.ts CHANGED
@@ -1558,7 +1558,20 @@ declare class ServiceManager {
1558
1558
  private getNodePath;
1559
1559
  /**
1560
1560
  * Find the API server entry point.
1561
- * Searches common locations where agenticmail is installed.
1561
+ *
1562
+ * Issue #26 — Robust path resolution.
1563
+ *
1564
+ * The original implementation hard-coded `node_modules/agenticmail` (the
1565
+ * old unscoped package name). After the rename to `@agenticmail/cli`, that
1566
+ * directory no longer exists, so the resolver fell back to the stale
1567
+ * `~/.agenticmail/api-entry.path` cache and the launchd plist kept pointing
1568
+ * at a deleted path — causing the boot crash loop reported in #26.
1569
+ *
1570
+ * We now prefer `require.resolve('@agenticmail/api')` so the resolution
1571
+ * follows the actual installed location regardless of npm prefix, the
1572
+ * scoped vs unscoped package name, or the package manager (npm global,
1573
+ * pnpm, yarn global, local node_modules). Cached paths are always
1574
+ * validated against the filesystem before being returned.
1562
1575
  */
1563
1576
  private getApiEntryPath;
1564
1577
  /**
@@ -1567,6 +1580,10 @@ declare class ServiceManager {
1567
1580
  cacheApiEntryPath(entryPath: string): void;
1568
1581
  /**
1569
1582
  * Get the current package version.
1583
+ *
1584
+ * Issue #26 — resolve the CLI package.json via require.resolve so the
1585
+ * version reflects the *currently installed* @agenticmail/cli, not a
1586
+ * leftover unscoped `agenticmail` package directory.
1570
1587
  */
1571
1588
  private getVersion;
1572
1589
  /**
@@ -1619,6 +1636,34 @@ declare class ServiceManager {
1619
1636
  installed: boolean;
1620
1637
  message: string;
1621
1638
  };
1639
+ /**
1640
+ * Issue #26 — Detect a stale service installation.
1641
+ *
1642
+ * Background: when a user upgrades from the old unscoped `agenticmail`
1643
+ * package to the new `@agenticmail/cli` scoped package, the old
1644
+ * ~/Library/LaunchAgents/com.agenticmail.server.plist and
1645
+ * ~/.agenticmail/bin/start-server.sh files keep pointing at
1646
+ * /opt/homebrew/lib/node_modules/agenticmail/... — a path that no longer
1647
+ * exists post-rename. The result is a launchd crash loop.
1648
+ *
1649
+ * `needsRepair()` returns a non-null reason whenever:
1650
+ * - the service file exists but the start-server.sh it launches is
1651
+ * missing or references a node_modules path that no longer resolves;
1652
+ * - the embedded service version drifts from the running CLI version
1653
+ * (so service files get refreshed on every upgrade — including
1654
+ * in-place version bumps that don't change the install path);
1655
+ * - the cached API entry path no longer exists on disk.
1656
+ *
1657
+ * Returns null when everything checks out — callers should treat that as
1658
+ * "no action needed".
1659
+ *
1660
+ * Platform-aware: only inspects launchd artefacts on darwin and systemd
1661
+ * artefacts on linux. Returns null on unsupported platforms so this can
1662
+ * be called unconditionally from the CLI's start path.
1663
+ */
1664
+ needsRepair(): {
1665
+ reason: string;
1666
+ } | null;
1622
1667
  }
1623
1668
 
1624
1669
  interface SetupConfig {
package/dist/index.js CHANGED
@@ -484,7 +484,8 @@ var InboxWatcher = class extends EventEmitter {
484
484
  this.emit("close");
485
485
  this._scheduleReconnect();
486
486
  });
487
- this._lock = lock;
487
+ lock.release();
488
+ this._lock = null;
488
489
  } catch (err) {
489
490
  lock.release();
490
491
  throw err;
@@ -1163,7 +1164,15 @@ var AccountManager = class {
1163
1164
  }
1164
1165
  const principalName = options.name.toLowerCase();
1165
1166
  const email = `${principalName}@${domain}`;
1167
+ const existingAgent = await this.getByName(options.name);
1168
+ if (existingAgent != null) {
1169
+ throw new Error(`Account already exists: ${options.name}`);
1170
+ }
1166
1171
  await this.stalwart.ensureDomain(domain);
1172
+ try {
1173
+ await this.stalwart.deletePrincipal(principalName);
1174
+ } catch {
1175
+ }
1167
1176
  await this.stalwart.createPrincipal({
1168
1177
  type: "individual",
1169
1178
  name: principalName,
@@ -6310,6 +6319,7 @@ import { execFileSync as execFileSync3, execSync as execSync2 } from "child_proc
6310
6319
  import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync4, unlinkSync, mkdirSync as mkdirSync5, chmodSync } from "fs";
6311
6320
  import { join as join8 } from "path";
6312
6321
  import { homedir as homedir7, platform as platform4 } from "os";
6322
+ import { createRequire } from "module";
6313
6323
  var PLIST_LABEL = "com.agenticmail.server";
6314
6324
  var SYSTEMD_UNIT = "agenticmail.service";
6315
6325
  var ServiceManager = class {
@@ -6336,42 +6346,59 @@ var ServiceManager = class {
6336
6346
  }
6337
6347
  /**
6338
6348
  * Find the API server entry point.
6339
- * Searches common locations where agenticmail is installed.
6349
+ *
6350
+ * Issue #26 — Robust path resolution.
6351
+ *
6352
+ * The original implementation hard-coded `node_modules/agenticmail` (the
6353
+ * old unscoped package name). After the rename to `@agenticmail/cli`, that
6354
+ * directory no longer exists, so the resolver fell back to the stale
6355
+ * `~/.agenticmail/api-entry.path` cache and the launchd plist kept pointing
6356
+ * at a deleted path — causing the boot crash loop reported in #26.
6357
+ *
6358
+ * We now prefer `require.resolve('@agenticmail/api')` so the resolution
6359
+ * follows the actual installed location regardless of npm prefix, the
6360
+ * scoped vs unscoped package name, or the package manager (npm global,
6361
+ * pnpm, yarn global, local node_modules). Cached paths are always
6362
+ * validated against the filesystem before being returned.
6340
6363
  */
6341
6364
  getApiEntryPath() {
6342
- const searchDirs = [
6343
- // Global npm install
6344
- join8(homedir7(), "node_modules", "agenticmail"),
6345
- // npx cache / global prefix
6346
- ...(() => {
6347
- try {
6348
- const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
6349
- return [
6350
- join8(prefix, "lib", "node_modules", "agenticmail"),
6351
- join8(prefix, "node_modules", "agenticmail")
6352
- ];
6353
- } catch {
6354
- return [];
6355
- }
6356
- })(),
6357
- // Homebrew on macOS
6358
- "/opt/homebrew/lib/node_modules/agenticmail",
6359
- "/usr/local/lib/node_modules/agenticmail"
6365
+ try {
6366
+ const req = createRequire(import.meta.url);
6367
+ const resolved = req.resolve("@agenticmail/api");
6368
+ if (existsSync6(resolved)) return resolved;
6369
+ } catch {
6370
+ }
6371
+ const parentPackages = [
6372
+ join8("@agenticmail", "cli"),
6373
+ // current scoped package
6374
+ "agenticmail"
6375
+ // legacy unscoped package
6360
6376
  ];
6361
- for (const base of searchDirs) {
6362
- const apiPaths = [
6363
- join8(base, "node_modules", "@agenticmail", "api", "dist", "index.js"),
6364
- join8(base, "..", "@agenticmail", "api", "dist", "index.js")
6365
- ];
6366
- for (const p of apiPaths) {
6367
- if (existsSync6(p)) return p;
6377
+ const baseDirs = [
6378
+ // user-local install
6379
+ join8(homedir7(), "node_modules")
6380
+ ];
6381
+ try {
6382
+ const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
6383
+ baseDirs.push(join8(prefix, "lib", "node_modules"));
6384
+ baseDirs.push(join8(prefix, "node_modules"));
6385
+ } catch {
6386
+ }
6387
+ baseDirs.push("/opt/homebrew/lib/node_modules");
6388
+ baseDirs.push("/usr/local/lib/node_modules");
6389
+ for (const base of baseDirs) {
6390
+ const sibling = join8(base, "@agenticmail", "api", "dist", "index.js");
6391
+ if (existsSync6(sibling)) return sibling;
6392
+ for (const parent of parentPackages) {
6393
+ const nested = join8(base, parent, "node_modules", "@agenticmail", "api", "dist", "index.js");
6394
+ if (existsSync6(nested)) return nested;
6368
6395
  }
6369
6396
  }
6370
6397
  const dataDir = join8(homedir7(), ".agenticmail");
6371
6398
  const entryCache = join8(dataDir, "api-entry.path");
6372
6399
  if (existsSync6(entryCache)) {
6373
6400
  const cached = readFileSync3(entryCache, "utf-8").trim();
6374
- if (existsSync6(cached)) return cached;
6401
+ if (cached && existsSync6(cached)) return cached;
6375
6402
  }
6376
6403
  throw new Error("Could not find @agenticmail/api entry point. Run `agenticmail start` first to populate the cache.");
6377
6404
  }
@@ -6385,25 +6412,49 @@ var ServiceManager = class {
6385
6412
  }
6386
6413
  /**
6387
6414
  * Get the current package version.
6415
+ *
6416
+ * Issue #26 — resolve the CLI package.json via require.resolve so the
6417
+ * version reflects the *currently installed* @agenticmail/cli, not a
6418
+ * leftover unscoped `agenticmail` package directory.
6388
6419
  */
6389
6420
  getVersion() {
6390
6421
  try {
6391
- const pkgPaths = [
6392
- join8(homedir7(), "node_modules", "agenticmail", "package.json"),
6393
- join8(homedir7(), ".agenticmail", "package-version.json")
6394
- ];
6395
- try {
6396
- const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
6397
- pkgPaths.push(join8(prefix, "lib", "node_modules", "agenticmail", "package.json"));
6398
- } catch {
6422
+ const req = createRequire(import.meta.url);
6423
+ const pkgJson = req.resolve("@agenticmail/cli/package.json");
6424
+ if (existsSync6(pkgJson)) {
6425
+ const pkg = JSON.parse(readFileSync3(pkgJson, "utf-8"));
6426
+ if (pkg.version) return pkg.version;
6399
6427
  }
6400
- for (const p of pkgPaths) {
6428
+ } catch {
6429
+ }
6430
+ try {
6431
+ const apiEntry = this.getApiEntryPath();
6432
+ const apiPkg = join8(apiEntry, "..", "..", "package.json");
6433
+ if (existsSync6(apiPkg)) {
6434
+ const pkg = JSON.parse(readFileSync3(apiPkg, "utf-8"));
6435
+ if (pkg.version) return pkg.version;
6436
+ }
6437
+ } catch {
6438
+ }
6439
+ const candidates = [
6440
+ join8(homedir7(), "node_modules", "@agenticmail", "cli", "package.json"),
6441
+ join8(homedir7(), "node_modules", "agenticmail", "package.json"),
6442
+ join8(homedir7(), ".agenticmail", "package-version.json")
6443
+ ];
6444
+ try {
6445
+ const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
6446
+ candidates.push(join8(prefix, "lib", "node_modules", "@agenticmail", "cli", "package.json"));
6447
+ candidates.push(join8(prefix, "lib", "node_modules", "agenticmail", "package.json"));
6448
+ } catch {
6449
+ }
6450
+ for (const p of candidates) {
6451
+ try {
6401
6452
  if (existsSync6(p)) {
6402
6453
  const pkg = JSON.parse(readFileSync3(p, "utf-8"));
6403
6454
  if (pkg.version) return pkg.version;
6404
6455
  }
6456
+ } catch {
6405
6457
  }
6406
- } catch {
6407
6458
  }
6408
6459
  return "unknown";
6409
6460
  }
@@ -6709,6 +6760,86 @@ WantedBy=default.target
6709
6760
  this.uninstall();
6710
6761
  return this.install();
6711
6762
  }
6763
+ /**
6764
+ * Issue #26 — Detect a stale service installation.
6765
+ *
6766
+ * Background: when a user upgrades from the old unscoped `agenticmail`
6767
+ * package to the new `@agenticmail/cli` scoped package, the old
6768
+ * ~/Library/LaunchAgents/com.agenticmail.server.plist and
6769
+ * ~/.agenticmail/bin/start-server.sh files keep pointing at
6770
+ * /opt/homebrew/lib/node_modules/agenticmail/... — a path that no longer
6771
+ * exists post-rename. The result is a launchd crash loop.
6772
+ *
6773
+ * `needsRepair()` returns a non-null reason whenever:
6774
+ * - the service file exists but the start-server.sh it launches is
6775
+ * missing or references a node_modules path that no longer resolves;
6776
+ * - the embedded service version drifts from the running CLI version
6777
+ * (so service files get refreshed on every upgrade — including
6778
+ * in-place version bumps that don't change the install path);
6779
+ * - the cached API entry path no longer exists on disk.
6780
+ *
6781
+ * Returns null when everything checks out — callers should treat that as
6782
+ * "no action needed".
6783
+ *
6784
+ * Platform-aware: only inspects launchd artefacts on darwin and systemd
6785
+ * artefacts on linux. Returns null on unsupported platforms so this can
6786
+ * be called unconditionally from the CLI's start path.
6787
+ */
6788
+ needsRepair() {
6789
+ if (this.os !== "darwin" && this.os !== "linux") return null;
6790
+ const servicePath = this.getServicePath();
6791
+ if (!existsSync6(servicePath)) return null;
6792
+ let serviceContent = "";
6793
+ try {
6794
+ serviceContent = readFileSync3(servicePath, "utf-8");
6795
+ } catch {
6796
+ return { reason: "Service file unreadable" };
6797
+ }
6798
+ const startScript = join8(homedir7(), ".agenticmail", "bin", "start-server.sh");
6799
+ if (serviceContent.includes(startScript)) {
6800
+ if (!existsSync6(startScript)) {
6801
+ return { reason: "start-server.sh is missing" };
6802
+ }
6803
+ let scriptContent = "";
6804
+ try {
6805
+ scriptContent = readFileSync3(startScript, "utf-8");
6806
+ } catch {
6807
+ return { reason: "start-server.sh unreadable" };
6808
+ }
6809
+ const apiPathMatch = scriptContent.match(/(\/[^"\s]+@agenticmail\/api\/dist\/index\.js)/);
6810
+ if (apiPathMatch) {
6811
+ const referenced = apiPathMatch[1];
6812
+ if (!existsSync6(referenced)) {
6813
+ return { reason: `start-server.sh references missing path: ${referenced}` };
6814
+ }
6815
+ }
6816
+ if (/node_modules\/agenticmail\/(?!.*@agenticmail\/cli)/.test(scriptContent)) {
6817
+ const stale = /(\S*node_modules\/agenticmail\/\S*)/.exec(scriptContent)?.[1];
6818
+ if (stale && !existsSync6(stale)) {
6819
+ return { reason: `start-server.sh references legacy unscoped path: ${stale}` };
6820
+ }
6821
+ }
6822
+ } else {
6823
+ return { reason: "Service file does not reference the wrapper script" };
6824
+ }
6825
+ const currentVersion = this.getVersion();
6826
+ if (currentVersion !== "unknown") {
6827
+ if (!serviceContent.includes(`v${currentVersion}`) || !serviceContent.includes(`AGENTICMAIL_SERVICE_VERSION`) || !serviceContent.includes(currentVersion)) {
6828
+ return { reason: `Service version drift (current CLI is v${currentVersion})` };
6829
+ }
6830
+ }
6831
+ const entryCache = join8(homedir7(), ".agenticmail", "api-entry.path");
6832
+ if (existsSync6(entryCache)) {
6833
+ try {
6834
+ const cached = readFileSync3(entryCache, "utf-8").trim();
6835
+ if (cached && !existsSync6(cached)) {
6836
+ return { reason: `Cached API entry path no longer exists: ${cached}` };
6837
+ }
6838
+ } catch {
6839
+ }
6840
+ }
6841
+ return null;
6842
+ }
6712
6843
  };
6713
6844
 
6714
6845
  // src/setup/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/core",
3
- "version": "0.5.56",
3
+ "version": "0.5.59",
4
4
  "description": "Core SDK for AgenticMail — email, SMS, and phone number access for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",