@agenticmail/core 0.5.58 → 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
@@ -1918,13 +1918,14 @@ var AccountManager = class {
1918
1918
  }
1919
1919
  const principalName = options.name.toLowerCase();
1920
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
+ }
1921
1925
  await this.stalwart.ensureDomain(domain);
1922
- const existsInSqlite = await this.getByName(options.name) != null;
1923
- if (!existsInSqlite) {
1924
- try {
1925
- await this.stalwart.deletePrincipal(principalName);
1926
- } catch {
1927
- }
1926
+ try {
1927
+ await this.stalwart.deletePrincipal(principalName);
1928
+ } catch {
1928
1929
  }
1929
1930
  await this.stalwart.createPrincipal({
1930
1931
  type: "individual",
@@ -7076,6 +7077,8 @@ var import_node_child_process4 = require("child_process");
7076
7077
  var import_node_fs6 = require("fs");
7077
7078
  var import_node_path7 = require("path");
7078
7079
  var import_node_os6 = require("os");
7080
+ var import_node_module = require("module");
7081
+ var import_meta = {};
7079
7082
  var PLIST_LABEL = "com.agenticmail.server";
7080
7083
  var SYSTEMD_UNIT = "agenticmail.service";
7081
7084
  var ServiceManager = class {
@@ -7102,42 +7105,59 @@ var ServiceManager = class {
7102
7105
  }
7103
7106
  /**
7104
7107
  * Find the API server entry point.
7105
- * 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.
7106
7122
  */
7107
7123
  getApiEntryPath() {
7108
- const searchDirs = [
7109
- // Global npm install
7110
- (0, import_node_path7.join)((0, import_node_os6.homedir)(), "node_modules", "agenticmail"),
7111
- // npx cache / global prefix
7112
- ...(() => {
7113
- try {
7114
- const prefix = (0, import_node_child_process4.execSync)("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
7115
- return [
7116
- (0, import_node_path7.join)(prefix, "lib", "node_modules", "agenticmail"),
7117
- (0, import_node_path7.join)(prefix, "node_modules", "agenticmail")
7118
- ];
7119
- } catch {
7120
- return [];
7121
- }
7122
- })(),
7123
- // Homebrew on macOS
7124
- "/opt/homebrew/lib/node_modules/agenticmail",
7125
- "/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
7126
7135
  ];
7127
- for (const base of searchDirs) {
7128
- const apiPaths = [
7129
- (0, import_node_path7.join)(base, "node_modules", "@agenticmail", "api", "dist", "index.js"),
7130
- (0, import_node_path7.join)(base, "..", "@agenticmail", "api", "dist", "index.js")
7131
- ];
7132
- for (const p of apiPaths) {
7133
- 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;
7134
7154
  }
7135
7155
  }
7136
7156
  const dataDir = (0, import_node_path7.join)((0, import_node_os6.homedir)(), ".agenticmail");
7137
7157
  const entryCache = (0, import_node_path7.join)(dataDir, "api-entry.path");
7138
7158
  if ((0, import_node_fs6.existsSync)(entryCache)) {
7139
7159
  const cached = (0, import_node_fs6.readFileSync)(entryCache, "utf-8").trim();
7140
- if ((0, import_node_fs6.existsSync)(cached)) return cached;
7160
+ if (cached && (0, import_node_fs6.existsSync)(cached)) return cached;
7141
7161
  }
7142
7162
  throw new Error("Could not find @agenticmail/api entry point. Run `agenticmail start` first to populate the cache.");
7143
7163
  }
@@ -7151,25 +7171,49 @@ var ServiceManager = class {
7151
7171
  }
7152
7172
  /**
7153
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.
7154
7178
  */
7155
7179
  getVersion() {
7156
7180
  try {
7157
- const pkgPaths = [
7158
- (0, import_node_path7.join)((0, import_node_os6.homedir)(), "node_modules", "agenticmail", "package.json"),
7159
- (0, import_node_path7.join)((0, import_node_os6.homedir)(), ".agenticmail", "package-version.json")
7160
- ];
7161
- try {
7162
- const prefix = (0, import_node_child_process4.execSync)("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
7163
- pkgPaths.push((0, import_node_path7.join)(prefix, "lib", "node_modules", "agenticmail", "package.json"));
7164
- } 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;
7165
7186
  }
7166
- 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 {
7167
7211
  if ((0, import_node_fs6.existsSync)(p)) {
7168
7212
  const pkg = JSON.parse((0, import_node_fs6.readFileSync)(p, "utf-8"));
7169
7213
  if (pkg.version) return pkg.version;
7170
7214
  }
7215
+ } catch {
7171
7216
  }
7172
- } catch {
7173
7217
  }
7174
7218
  return "unknown";
7175
7219
  }
@@ -7475,6 +7519,86 @@ WantedBy=default.target
7475
7519
  this.uninstall();
7476
7520
  return this.install();
7477
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
+ }
7478
7602
  };
7479
7603
 
7480
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
@@ -1164,13 +1164,14 @@ var AccountManager = class {
1164
1164
  }
1165
1165
  const principalName = options.name.toLowerCase();
1166
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
+ }
1167
1171
  await this.stalwart.ensureDomain(domain);
1168
- const existsInSqlite = await this.getByName(options.name) != null;
1169
- if (!existsInSqlite) {
1170
- try {
1171
- await this.stalwart.deletePrincipal(principalName);
1172
- } catch {
1173
- }
1172
+ try {
1173
+ await this.stalwart.deletePrincipal(principalName);
1174
+ } catch {
1174
1175
  }
1175
1176
  await this.stalwart.createPrincipal({
1176
1177
  type: "individual",
@@ -6318,6 +6319,7 @@ import { execFileSync as execFileSync3, execSync as execSync2 } from "child_proc
6318
6319
  import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync4, unlinkSync, mkdirSync as mkdirSync5, chmodSync } from "fs";
6319
6320
  import { join as join8 } from "path";
6320
6321
  import { homedir as homedir7, platform as platform4 } from "os";
6322
+ import { createRequire } from "module";
6321
6323
  var PLIST_LABEL = "com.agenticmail.server";
6322
6324
  var SYSTEMD_UNIT = "agenticmail.service";
6323
6325
  var ServiceManager = class {
@@ -6344,42 +6346,59 @@ var ServiceManager = class {
6344
6346
  }
6345
6347
  /**
6346
6348
  * Find the API server entry point.
6347
- * 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.
6348
6363
  */
6349
6364
  getApiEntryPath() {
6350
- const searchDirs = [
6351
- // Global npm install
6352
- join8(homedir7(), "node_modules", "agenticmail"),
6353
- // npx cache / global prefix
6354
- ...(() => {
6355
- try {
6356
- const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
6357
- return [
6358
- join8(prefix, "lib", "node_modules", "agenticmail"),
6359
- join8(prefix, "node_modules", "agenticmail")
6360
- ];
6361
- } catch {
6362
- return [];
6363
- }
6364
- })(),
6365
- // Homebrew on macOS
6366
- "/opt/homebrew/lib/node_modules/agenticmail",
6367
- "/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
6368
6376
  ];
6369
- for (const base of searchDirs) {
6370
- const apiPaths = [
6371
- join8(base, "node_modules", "@agenticmail", "api", "dist", "index.js"),
6372
- join8(base, "..", "@agenticmail", "api", "dist", "index.js")
6373
- ];
6374
- for (const p of apiPaths) {
6375
- 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;
6376
6395
  }
6377
6396
  }
6378
6397
  const dataDir = join8(homedir7(), ".agenticmail");
6379
6398
  const entryCache = join8(dataDir, "api-entry.path");
6380
6399
  if (existsSync6(entryCache)) {
6381
6400
  const cached = readFileSync3(entryCache, "utf-8").trim();
6382
- if (existsSync6(cached)) return cached;
6401
+ if (cached && existsSync6(cached)) return cached;
6383
6402
  }
6384
6403
  throw new Error("Could not find @agenticmail/api entry point. Run `agenticmail start` first to populate the cache.");
6385
6404
  }
@@ -6393,25 +6412,49 @@ var ServiceManager = class {
6393
6412
  }
6394
6413
  /**
6395
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.
6396
6419
  */
6397
6420
  getVersion() {
6398
6421
  try {
6399
- const pkgPaths = [
6400
- join8(homedir7(), "node_modules", "agenticmail", "package.json"),
6401
- join8(homedir7(), ".agenticmail", "package-version.json")
6402
- ];
6403
- try {
6404
- const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
6405
- pkgPaths.push(join8(prefix, "lib", "node_modules", "agenticmail", "package.json"));
6406
- } 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;
6407
6427
  }
6408
- 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 {
6409
6452
  if (existsSync6(p)) {
6410
6453
  const pkg = JSON.parse(readFileSync3(p, "utf-8"));
6411
6454
  if (pkg.version) return pkg.version;
6412
6455
  }
6456
+ } catch {
6413
6457
  }
6414
- } catch {
6415
6458
  }
6416
6459
  return "unknown";
6417
6460
  }
@@ -6717,6 +6760,86 @@ WantedBy=default.target
6717
6760
  this.uninstall();
6718
6761
  return this.install();
6719
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
+ }
6720
6843
  };
6721
6844
 
6722
6845
  // src/setup/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/core",
3
- "version": "0.5.58",
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",