@agenticmail/core 0.5.4 → 0.5.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.d.ts CHANGED
@@ -1412,22 +1412,41 @@ declare class DependencyChecker {
1412
1412
  type InstallProgress = (message: string) => void;
1413
1413
  /**
1414
1414
  * DependencyInstaller handles installing all external dependencies.
1415
- * Everything is auto-installed — no optional deps.
1415
+ * Everything is auto-installed — no optional deps, no manual steps.
1416
1416
  */
1417
1417
  declare class DependencyInstaller {
1418
1418
  private onProgress;
1419
1419
  constructor(onProgress?: InstallProgress);
1420
1420
  /**
1421
1421
  * Ensure Docker is installed AND the daemon is running.
1422
- * macOS: Downloads official DMG and runs Docker's CLI installer (no Homebrew needed).
1423
- * Linux: Uses Docker's official convenience script (https://get.docker.com).
1424
- * Starts the daemon if Docker CLI is present but daemon is stopped.
1422
+ * Fully automatic installs, accepts license, starts daemon, waits for ready.
1423
+ * Never asks the user to do anything manually.
1424
+ *
1425
+ * Flow:
1426
+ * 1. docker CLI + daemon running? → done
1427
+ * 2. docker CLI exists but daemon stopped? → start daemon, wait
1428
+ * 3. Docker.app exists (in /Applications or Caskroom)? → run installer --accept-license, start, wait
1429
+ * 4. Nothing installed? → install via Homebrew or DMG download, then start, wait
1425
1430
  */
1426
1431
  installDocker(): Promise<void>;
1432
+ /** Check if `docker info` succeeds (CLI + daemon both working). */
1433
+ private isDockerReady;
1434
+ /** Check if `docker` CLI is in PATH (daemon may be stopped). */
1435
+ private isDockerCliInstalled;
1436
+ /**
1437
+ * Find Docker.app on macOS.
1438
+ * Checks /Applications first, then Homebrew Caskroom.
1439
+ * Validates symlinks aren't broken.
1440
+ */
1441
+ private findDockerApp;
1442
+ /**
1443
+ * Docker.app exists but CLI isn't available.
1444
+ * Run the built-in installer (--accept-license), start Docker Desktop, wait.
1445
+ */
1446
+ private setupExistingDockerApp;
1427
1447
  /**
1428
- * Install Docker Desktop on macOS using the official DMG installer.
1429
- * Downloads the DMG, mounts it, runs the silent CLI installer, then starts Docker Desktop.
1430
- * This is Docker's recommended command-line installation method.
1448
+ * Full Docker Desktop install on macOS.
1449
+ * Tries Homebrew first (cleaner, handles updates), falls back to DMG download.
1431
1450
  */
1432
1451
  private installDockerMac;
1433
1452
  /**
@@ -1436,14 +1455,14 @@ declare class DependencyInstaller {
1436
1455
  */
1437
1456
  private installDockerLinux;
1438
1457
  /**
1439
- * Attempt to start the Docker daemon using multiple strategies.
1440
- * On macOS: tries Docker Desktop app, then docker CLI commands.
1441
- * On Linux: tries systemctl, then dockerd direct, then snap.
1458
+ * Start the Docker daemon using multiple strategies.
1459
+ * On macOS: tries Docker Desktop app via `open`, direct binary launch, etc.
1460
+ * On Linux: tries systemctl, service, snap.
1442
1461
  */
1443
1462
  private startDockerDaemon;
1444
1463
  /**
1445
- * Wait for Docker daemon to be ready, with automatic retry strategies.
1446
- * Tries multiple approaches to start Docker if the first one fails.
1464
+ * Wait for Docker daemon to be ready.
1465
+ * Cycles through multiple start strategies automatically.
1447
1466
  * Reports progress as a percentage (0-100).
1448
1467
  */
1449
1468
  private waitForDocker;
package/dist/index.js CHANGED
@@ -5894,7 +5894,7 @@ var DependencyChecker = class {
5894
5894
 
5895
5895
  // src/setup/installer.ts
5896
5896
  import { execFileSync as execFileSync2, execSync, spawn as spawnChild } from "child_process";
5897
- import { existsSync as existsSync4 } from "fs";
5897
+ import { existsSync as existsSync4, readdirSync, lstatSync, readlinkSync } from "fs";
5898
5898
  import { writeFile, rename, chmod as chmod2, mkdir as mkdir2, unlink } from "fs/promises";
5899
5899
  import { join as join6 } from "path";
5900
5900
  import { homedir as homedir5, platform as platform2, arch as arch2 } from "os";
@@ -6016,6 +6016,31 @@ function runShellWithRollingOutput(cmd, opts = {}) {
6016
6016
  });
6017
6017
  });
6018
6018
  }
6019
+ function existsReal(p) {
6020
+ try {
6021
+ const stat = lstatSync(p);
6022
+ if (stat.isSymbolicLink()) {
6023
+ try {
6024
+ const target = readlinkSync(p);
6025
+ const resolved = target.startsWith("/") ? target : join6(p, "..", target);
6026
+ return existsSync4(resolved);
6027
+ } catch {
6028
+ return false;
6029
+ }
6030
+ }
6031
+ return true;
6032
+ } catch {
6033
+ return false;
6034
+ }
6035
+ }
6036
+ function hasHomebrew() {
6037
+ try {
6038
+ execFileSync2("brew", ["--version"], { timeout: 5e3, stdio: "ignore" });
6039
+ return true;
6040
+ } catch {
6041
+ return false;
6042
+ }
6043
+ }
6019
6044
  var DependencyInstaller = class {
6020
6045
  onProgress;
6021
6046
  constructor(onProgress) {
@@ -6024,27 +6049,28 @@ var DependencyInstaller = class {
6024
6049
  }
6025
6050
  /**
6026
6051
  * Ensure Docker is installed AND the daemon is running.
6027
- * macOS: Downloads official DMG and runs Docker's CLI installer (no Homebrew needed).
6028
- * Linux: Uses Docker's official convenience script (https://get.docker.com).
6029
- * Starts the daemon if Docker CLI is present but daemon is stopped.
6052
+ * Fully automatic installs, accepts license, starts daemon, waits for ready.
6053
+ * Never asks the user to do anything manually.
6054
+ *
6055
+ * Flow:
6056
+ * 1. docker CLI + daemon running? → done
6057
+ * 2. docker CLI exists but daemon stopped? → start daemon, wait
6058
+ * 3. Docker.app exists (in /Applications or Caskroom)? → run installer --accept-license, start, wait
6059
+ * 4. Nothing installed? → install via Homebrew or DMG download, then start, wait
6030
6060
  */
6031
6061
  async installDocker() {
6032
- let cliInstalled = false;
6033
- try {
6034
- execFileSync2("docker", ["--version"], { timeout: 5e3, stdio: "ignore" });
6035
- cliInstalled = true;
6036
- } catch {
6062
+ if (this.isDockerReady()) return;
6063
+ if (this.isDockerCliInstalled()) {
6064
+ this.onProgress("__progress__:10:Docker installed \u2014 starting the engine...");
6065
+ this.startDockerDaemon();
6066
+ await this.waitForDocker();
6067
+ return;
6037
6068
  }
6038
- if (cliInstalled) {
6039
- try {
6040
- execFileSync2("docker", ["info"], { timeout: 1e4, stdio: "ignore" });
6041
- return;
6042
- } catch {
6043
- this.onProgress("Docker found but not running \u2014 starting it now...");
6044
- this.startDockerDaemon();
6045
- await this.waitForDocker();
6046
- return;
6047
- }
6069
+ const dockerApp = this.findDockerApp();
6070
+ if (dockerApp) {
6071
+ this.onProgress("__progress__:10:Docker Desktop found \u2014 setting it up...");
6072
+ await this.setupExistingDockerApp(dockerApp);
6073
+ return;
6048
6074
  }
6049
6075
  const os = platform2();
6050
6076
  if (os === "darwin") {
@@ -6052,15 +6078,102 @@ var DependencyInstaller = class {
6052
6078
  } else if (os === "linux") {
6053
6079
  await this.installDockerLinux();
6054
6080
  } else {
6055
- throw new Error(`Automatic Docker installation not supported on ${os}. Install it manually: https://docs.docker.com/get-docker/`);
6081
+ throw new Error(
6082
+ `Docker auto-install isn't supported on ${os} yet. Install it from https://docs.docker.com/get-docker/ and try again.`
6083
+ );
6084
+ }
6085
+ }
6086
+ /** Check if `docker info` succeeds (CLI + daemon both working). */
6087
+ isDockerReady() {
6088
+ try {
6089
+ execFileSync2("docker", ["info"], { timeout: 1e4, stdio: "ignore" });
6090
+ return true;
6091
+ } catch {
6092
+ return false;
6056
6093
  }
6057
6094
  }
6095
+ /** Check if `docker` CLI is in PATH (daemon may be stopped). */
6096
+ isDockerCliInstalled() {
6097
+ try {
6098
+ execFileSync2("docker", ["--version"], { timeout: 5e3, stdio: "ignore" });
6099
+ return true;
6100
+ } catch {
6101
+ return false;
6102
+ }
6103
+ }
6104
+ /**
6105
+ * Find Docker.app on macOS.
6106
+ * Checks /Applications first, then Homebrew Caskroom.
6107
+ * Validates symlinks aren't broken.
6108
+ */
6109
+ findDockerApp() {
6110
+ if (existsReal("/Applications/Docker.app")) {
6111
+ return "/Applications/Docker.app";
6112
+ }
6113
+ const caskroomBases = [
6114
+ "/opt/homebrew/Caskroom",
6115
+ "/usr/local/Caskroom"
6116
+ ];
6117
+ const caskNames = ["docker-desktop", "docker"];
6118
+ for (const base of caskroomBases) {
6119
+ for (const name of caskNames) {
6120
+ const caskroom = join6(base, name);
6121
+ if (!existsSync4(caskroom)) continue;
6122
+ try {
6123
+ const versions = readdirSync(caskroom).filter((d) => !d.startsWith("."));
6124
+ for (const ver of versions) {
6125
+ const appPath = join6(caskroom, ver, "Docker.app");
6126
+ if (existsReal(appPath)) return appPath;
6127
+ }
6128
+ } catch {
6129
+ }
6130
+ }
6131
+ }
6132
+ return null;
6133
+ }
6058
6134
  /**
6059
- * Install Docker Desktop on macOS using the official DMG installer.
6060
- * Downloads the DMG, mounts it, runs the silent CLI installer, then starts Docker Desktop.
6061
- * This is Docker's recommended command-line installation method.
6135
+ * Docker.app exists but CLI isn't available.
6136
+ * Run the built-in installer (--accept-license), start Docker Desktop, wait.
6137
+ */
6138
+ async setupExistingDockerApp(appPath) {
6139
+ const installBin = join6(appPath, "Contents", "MacOS", "install");
6140
+ if (existsSync4(installBin)) {
6141
+ this.onProgress("__progress__:20:Installing Docker CLI tools...");
6142
+ const user = process.env.USER || execSync("whoami", { timeout: 5e3 }).toString().trim();
6143
+ try {
6144
+ await runWithRollingOutput(installBin, ["--accept-license", `--user=${user}`], {
6145
+ timeout: 12e4
6146
+ });
6147
+ } catch {
6148
+ }
6149
+ }
6150
+ this.onProgress("__progress__:40:Starting Docker Desktop...");
6151
+ this.startDockerDaemon();
6152
+ await this.waitForDocker();
6153
+ }
6154
+ /**
6155
+ * Full Docker Desktop install on macOS.
6156
+ * Tries Homebrew first (cleaner, handles updates), falls back to DMG download.
6062
6157
  */
6063
6158
  async installDockerMac() {
6159
+ if (hasHomebrew()) {
6160
+ this.onProgress("__progress__:5:Installing Docker Desktop via Homebrew...");
6161
+ const brewResult = await runWithRollingOutput(
6162
+ "brew",
6163
+ ["install", "--cask", "docker"],
6164
+ { timeout: 6e5 }
6165
+ // can be slow on first install
6166
+ );
6167
+ if (brewResult.exitCode === 0) {
6168
+ this.onProgress("__progress__:50:Docker Desktop installed!");
6169
+ const appPath = this.findDockerApp();
6170
+ if (appPath) {
6171
+ await this.setupExistingDockerApp(appPath);
6172
+ return;
6173
+ }
6174
+ }
6175
+ this.onProgress("__progress__:10:Homebrew install didn't work, downloading directly...");
6176
+ }
6064
6177
  const cpu = arch2();
6065
6178
  const archName = cpu === "arm64" ? "arm64" : "amd64";
6066
6179
  const dmgUrl = `https://desktop.docker.com/mac/main/${archName}/Docker.dmg`;
@@ -6094,18 +6207,16 @@ var DependencyInstaller = class {
6094
6207
  ["--accept-license", `--user=${user}`],
6095
6208
  { timeout: 12e4 }
6096
6209
  );
6097
- if (installResult.exitCode !== 0) {
6098
- if (!existsSync4("/Applications/Docker.app")) {
6099
- throw new Error("Installer exited with errors");
6100
- }
6210
+ if (installResult.exitCode !== 0 && !existsSync4("/Applications/Docker.app")) {
6211
+ throw new Error("Installer exited with errors");
6101
6212
  }
6102
- } catch (err) {
6213
+ } catch {
6103
6214
  if (!existsSync4("/Applications/Docker.app")) {
6104
6215
  this.onProgress("__progress__:60:Trying alternative install method...");
6105
6216
  try {
6106
6217
  execSync('cp -R "/Volumes/Docker/Docker.app" /Applications/', { timeout: 6e4, stdio: "ignore" });
6107
6218
  } catch {
6108
- throw new Error("Failed to install Docker Desktop. Try dragging Docker.app to Applications manually.");
6219
+ throw new Error("Failed to install Docker Desktop. You may need to run this with admin privileges.");
6109
6220
  }
6110
6221
  }
6111
6222
  }
@@ -6117,9 +6228,14 @@ var DependencyInstaller = class {
6117
6228
  await unlink(dmgPath);
6118
6229
  } catch {
6119
6230
  }
6120
- this.onProgress("__progress__:70:Starting Docker Desktop...");
6121
- this.startDockerDaemon();
6122
- await this.waitForDocker();
6231
+ const installed = this.findDockerApp();
6232
+ if (installed) {
6233
+ await this.setupExistingDockerApp(installed);
6234
+ } else {
6235
+ this.onProgress("__progress__:70:Starting Docker Desktop...");
6236
+ this.startDockerDaemon();
6237
+ await this.waitForDocker();
6238
+ }
6123
6239
  }
6124
6240
  /**
6125
6241
  * Install Docker Engine on Linux using Docker's official convenience script.
@@ -6160,13 +6276,14 @@ var DependencyInstaller = class {
6160
6276
  await this.waitForDocker();
6161
6277
  }
6162
6278
  /**
6163
- * Attempt to start the Docker daemon using multiple strategies.
6164
- * On macOS: tries Docker Desktop app, then docker CLI commands.
6165
- * On Linux: tries systemctl, then dockerd direct, then snap.
6279
+ * Start the Docker daemon using multiple strategies.
6280
+ * On macOS: tries Docker Desktop app via `open`, direct binary launch, etc.
6281
+ * On Linux: tries systemctl, service, snap.
6166
6282
  */
6167
6283
  startDockerDaemon(strategy) {
6168
6284
  const os = platform2();
6169
6285
  if (os === "darwin") {
6286
+ const dockerApp = this.findDockerApp();
6170
6287
  switch (strategy) {
6171
6288
  case "cli":
6172
6289
  try {
@@ -6183,24 +6300,56 @@ var DependencyInstaller = class {
6183
6300
  execFileSync2("sleep", ["2"], { timeout: 5e3, stdio: "ignore" });
6184
6301
  } catch {
6185
6302
  }
6186
- try {
6187
- execFileSync2("open", ["-a", "Docker"], { timeout: 1e4, stdio: "ignore" });
6188
- } catch {
6303
+ if (dockerApp) {
6304
+ try {
6305
+ execFileSync2("open", [dockerApp], { timeout: 1e4, stdio: "ignore" });
6306
+ } catch {
6307
+ }
6308
+ } else {
6309
+ try {
6310
+ execFileSync2("open", ["-a", "Docker"], { timeout: 1e4, stdio: "ignore" });
6311
+ } catch {
6312
+ }
6189
6313
  }
6190
6314
  break;
6191
- case "background":
6315
+ case "background": {
6316
+ const appBin = dockerApp ? join6(dockerApp, "Contents", "MacOS", "Docker") : "/Applications/Docker.app/Contents/MacOS/Docker";
6192
6317
  try {
6193
- const appBin = "/Applications/Docker.app/Contents/MacOS/Docker";
6194
6318
  if (existsSync4(appBin)) {
6195
6319
  execSync(`"${appBin}" &`, { timeout: 5e3, stdio: "ignore", shell: "sh" });
6196
6320
  }
6197
6321
  } catch {
6198
6322
  }
6199
6323
  break;
6324
+ }
6325
+ case "install": {
6326
+ const installBin = dockerApp ? join6(dockerApp, "Contents", "MacOS", "install") : "/Applications/Docker.app/Contents/MacOS/install";
6327
+ if (existsSync4(installBin)) {
6328
+ const user = process.env.USER || "nobody";
6329
+ try {
6330
+ execSync(`"${installBin}" --accept-license --user=${user}`, { timeout: 6e4, stdio: "ignore" });
6331
+ } catch {
6332
+ }
6333
+ }
6334
+ if (dockerApp) {
6335
+ try {
6336
+ execFileSync2("open", [dockerApp], { timeout: 1e4, stdio: "ignore" });
6337
+ } catch {
6338
+ }
6339
+ }
6340
+ break;
6341
+ }
6200
6342
  default:
6201
- try {
6202
- execFileSync2("open", ["-a", "Docker"], { timeout: 1e4, stdio: "ignore" });
6203
- } catch {
6343
+ if (dockerApp) {
6344
+ try {
6345
+ execFileSync2("open", [dockerApp], { timeout: 1e4, stdio: "ignore" });
6346
+ } catch {
6347
+ }
6348
+ } else {
6349
+ try {
6350
+ execFileSync2("open", ["-a", "Docker"], { timeout: 1e4, stdio: "ignore" });
6351
+ } catch {
6352
+ }
6204
6353
  }
6205
6354
  }
6206
6355
  } else if (os === "linux") {
@@ -6226,14 +6375,14 @@ var DependencyInstaller = class {
6226
6375
  }
6227
6376
  }
6228
6377
  /**
6229
- * Wait for Docker daemon to be ready, with automatic retry strategies.
6230
- * Tries multiple approaches to start Docker if the first one fails.
6378
+ * Wait for Docker daemon to be ready.
6379
+ * Cycles through multiple start strategies automatically.
6231
6380
  * Reports progress as a percentage (0-100).
6232
6381
  */
6233
6382
  async waitForDocker() {
6234
6383
  const os = platform2();
6235
- const strategies = os === "darwin" ? ["default", "cli", "reopen", "background"] : ["default", "service", "snap"];
6236
- const totalTime = 24e4;
6384
+ const strategies = os === "darwin" ? ["default", "cli", "reopen", "background", "install"] : ["default", "service", "snap"];
6385
+ const totalTime = 3e5;
6237
6386
  const perStrategyTime = Math.floor(totalTime / strategies.length);
6238
6387
  const start = Date.now();
6239
6388
  let strategyIdx = 0;
@@ -6255,6 +6404,7 @@ var DependencyInstaller = class {
6255
6404
  cli: "Trying Docker CLI...",
6256
6405
  reopen: "Restarting Docker Desktop...",
6257
6406
  background: "Trying direct launch...",
6407
+ install: "Re-running Docker installer...",
6258
6408
  service: "Trying service command...",
6259
6409
  snap: "Trying snap..."
6260
6410
  };
@@ -6275,7 +6425,9 @@ var DependencyInstaller = class {
6275
6425
  }
6276
6426
  await new Promise((r) => setTimeout(r, 3e3));
6277
6427
  }
6278
- throw new Error("DOCKER_MANUAL_START");
6428
+ throw new Error(
6429
+ "Docker could not be started after trying all available methods. This usually means Docker Desktop needs a one-time manual launch to complete its setup. Open Docker from your Applications folder, then run this command again."
6430
+ );
6279
6431
  }
6280
6432
  /**
6281
6433
  * Start the Stalwart mail server Docker container.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/core",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Core SDK for AgenticMail \u2014 email, SMS, and phone number access for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",