@agenticmail/core 0.4.0 → 0.5.0

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
@@ -1424,14 +1424,15 @@ declare class DependencyInstaller {
1424
1424
  */
1425
1425
  installDocker(): Promise<void>;
1426
1426
  /**
1427
- * Attempt to start the Docker daemon.
1428
- * On macOS: opens Docker Desktop app.
1429
- * On Linux: tries systemctl.
1427
+ * Attempt to start the Docker daemon using multiple strategies.
1428
+ * On macOS: tries Docker Desktop app, then docker CLI commands.
1429
+ * On Linux: tries systemctl, then dockerd direct, then snap.
1430
1430
  */
1431
1431
  private startDockerDaemon;
1432
1432
  /**
1433
- * Wait for Docker daemon to be ready (up to 3 minutes).
1434
- * Docker Desktop can take 1-2+ minutes on first launch.
1433
+ * Wait for Docker daemon to be ready, with automatic retry strategies.
1434
+ * Tries multiple approaches to start Docker if the first one fails.
1435
+ * Reports progress as a percentage (0-100).
1435
1436
  */
1436
1437
  private waitForDocker;
1437
1438
  /**
package/dist/index.js CHANGED
@@ -5893,11 +5893,129 @@ var DependencyChecker = class {
5893
5893
  };
5894
5894
 
5895
5895
  // src/setup/installer.ts
5896
- import { execFileSync as execFileSync2, execSync } from "child_process";
5896
+ import { execFileSync as execFileSync2, execSync, spawn as spawnChild } from "child_process";
5897
5897
  import { existsSync as existsSync4 } 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";
5901
+ function runWithRollingOutput(command, args, opts = {}) {
5902
+ const maxLines = opts.maxLines ?? 20;
5903
+ const timeout = opts.timeout ?? 3e5;
5904
+ return new Promise((resolve, reject) => {
5905
+ const child = spawnChild(command, args, {
5906
+ stdio: ["ignore", "pipe", "pipe"],
5907
+ timeout
5908
+ });
5909
+ const lines = [];
5910
+ let displayedCount = 0;
5911
+ let fullOutput = "";
5912
+ const processData = (data) => {
5913
+ const text = data.toString();
5914
+ fullOutput += text;
5915
+ const newLines = text.split("\n");
5916
+ for (const line of newLines) {
5917
+ const trimmed = line.trimEnd();
5918
+ if (!trimmed) continue;
5919
+ lines.push(trimmed);
5920
+ if (displayedCount > 0) {
5921
+ const toClear = Math.min(displayedCount, maxLines);
5922
+ process.stdout.write(`\x1B[${toClear}A`);
5923
+ for (let i = 0; i < toClear; i++) {
5924
+ process.stdout.write("\x1B[2K\n");
5925
+ }
5926
+ process.stdout.write(`\x1B[${toClear}A`);
5927
+ }
5928
+ const visible = lines.slice(-maxLines);
5929
+ for (const vLine of visible) {
5930
+ process.stdout.write(` \x1B[90m${vLine.slice(0, 100)}\x1B[0m
5931
+ `);
5932
+ }
5933
+ displayedCount = visible.length;
5934
+ }
5935
+ };
5936
+ child.stdout?.on("data", processData);
5937
+ child.stderr?.on("data", processData);
5938
+ child.on("close", (code) => {
5939
+ if (displayedCount > 0) {
5940
+ process.stdout.write(`\x1B[${displayedCount}A`);
5941
+ for (let i = 0; i < displayedCount; i++) {
5942
+ process.stdout.write("\x1B[2K\n");
5943
+ }
5944
+ process.stdout.write(`\x1B[${displayedCount}A`);
5945
+ }
5946
+ resolve({ exitCode: code ?? 1, fullOutput });
5947
+ });
5948
+ child.on("error", (err) => {
5949
+ if (displayedCount > 0) {
5950
+ process.stdout.write(`\x1B[${displayedCount}A`);
5951
+ for (let i = 0; i < displayedCount; i++) {
5952
+ process.stdout.write("\x1B[2K\n");
5953
+ }
5954
+ process.stdout.write(`\x1B[${displayedCount}A`);
5955
+ }
5956
+ reject(err);
5957
+ });
5958
+ });
5959
+ }
5960
+ function runShellWithRollingOutput(cmd, opts = {}) {
5961
+ const maxLines = opts.maxLines ?? 20;
5962
+ const timeout = opts.timeout ?? 3e5;
5963
+ return new Promise((resolve, reject) => {
5964
+ const child = spawnChild("sh", ["-c", cmd], {
5965
+ stdio: ["ignore", "pipe", "pipe"],
5966
+ timeout
5967
+ });
5968
+ const lines = [];
5969
+ let displayedCount = 0;
5970
+ let fullOutput = "";
5971
+ const processData = (data) => {
5972
+ const text = data.toString();
5973
+ fullOutput += text;
5974
+ const newLines = text.split("\n");
5975
+ for (const line of newLines) {
5976
+ const trimmed = line.trimEnd();
5977
+ if (!trimmed) continue;
5978
+ lines.push(trimmed);
5979
+ if (displayedCount > 0) {
5980
+ const toClear = Math.min(displayedCount, maxLines);
5981
+ process.stdout.write(`\x1B[${toClear}A`);
5982
+ for (let i = 0; i < toClear; i++) {
5983
+ process.stdout.write("\x1B[2K\n");
5984
+ }
5985
+ process.stdout.write(`\x1B[${toClear}A`);
5986
+ }
5987
+ const visible = lines.slice(-maxLines);
5988
+ for (const vLine of visible) {
5989
+ process.stdout.write(` \x1B[90m${vLine.slice(0, 100)}\x1B[0m
5990
+ `);
5991
+ }
5992
+ displayedCount = visible.length;
5993
+ }
5994
+ };
5995
+ child.stdout?.on("data", processData);
5996
+ child.stderr?.on("data", processData);
5997
+ child.on("close", (code) => {
5998
+ if (displayedCount > 0) {
5999
+ process.stdout.write(`\x1B[${displayedCount}A`);
6000
+ for (let i = 0; i < displayedCount; i++) {
6001
+ process.stdout.write("\x1B[2K\n");
6002
+ }
6003
+ process.stdout.write(`\x1B[${displayedCount}A`);
6004
+ }
6005
+ resolve({ exitCode: code ?? 1, fullOutput });
6006
+ });
6007
+ child.on("error", (err) => {
6008
+ if (displayedCount > 0) {
6009
+ process.stdout.write(`\x1B[${displayedCount}A`);
6010
+ for (let i = 0; i < displayedCount; i++) {
6011
+ process.stdout.write("\x1B[2K\n");
6012
+ }
6013
+ process.stdout.write(`\x1B[${displayedCount}A`);
6014
+ }
6015
+ reject(err);
6016
+ });
6017
+ });
6018
+ }
5901
6019
  var DependencyInstaller = class {
5902
6020
  onProgress;
5903
6021
  constructor(onProgress) {
@@ -5935,14 +6053,20 @@ var DependencyInstaller = class {
5935
6053
  } catch {
5936
6054
  throw new Error("Homebrew is required to install Docker on macOS. Install it from https://brew.sh then try again.");
5937
6055
  }
5938
- execFileSync2("brew", ["install", "--cask", "docker"], { timeout: 3e5, stdio: "inherit" });
6056
+ const brewResult = await runWithRollingOutput("brew", ["install", "--cask", "docker"], { timeout: 3e5 });
6057
+ if (brewResult.exitCode !== 0) {
6058
+ throw new Error("Failed to install Docker via Homebrew. Try: brew install --cask docker");
6059
+ }
5939
6060
  this.onProgress("Docker installed. Starting Docker Desktop...");
5940
6061
  this.startDockerDaemon();
5941
6062
  await this.waitForDocker();
5942
6063
  } else if (os === "linux") {
5943
6064
  this.onProgress("Installing Docker...");
5944
6065
  try {
5945
- execSync("curl -fsSL https://get.docker.com | sh", { timeout: 3e5, stdio: "inherit" });
6066
+ const result = await runShellWithRollingOutput("curl -fsSL https://get.docker.com | sh", { timeout: 3e5 });
6067
+ if (result.exitCode !== 0) {
6068
+ throw new Error("Install script failed");
6069
+ }
5946
6070
  } catch {
5947
6071
  throw new Error("Failed to install Docker. Install it manually: https://docs.docker.com/get-docker/");
5948
6072
  }
@@ -5952,48 +6076,123 @@ var DependencyInstaller = class {
5952
6076
  }
5953
6077
  }
5954
6078
  /**
5955
- * Attempt to start the Docker daemon.
5956
- * On macOS: opens Docker Desktop app.
5957
- * On Linux: tries systemctl.
6079
+ * Attempt to start the Docker daemon using multiple strategies.
6080
+ * On macOS: tries Docker Desktop app, then docker CLI commands.
6081
+ * On Linux: tries systemctl, then dockerd direct, then snap.
5958
6082
  */
5959
- startDockerDaemon() {
6083
+ startDockerDaemon(strategy) {
5960
6084
  const os = platform2();
5961
6085
  if (os === "darwin") {
5962
- try {
5963
- execFileSync2("open", ["-a", "Docker"], { timeout: 1e4, stdio: "ignore" });
5964
- } catch {
6086
+ switch (strategy) {
6087
+ case "cli":
6088
+ try {
6089
+ execSync("docker context use default 2>/dev/null; docker info", { timeout: 5e3, stdio: "ignore" });
6090
+ } catch {
6091
+ }
6092
+ break;
6093
+ case "reopen":
6094
+ try {
6095
+ execSync(`osascript -e 'quit app "Docker"'`, { timeout: 5e3, stdio: "ignore" });
6096
+ } catch {
6097
+ }
6098
+ try {
6099
+ execFileSync2("sleep", ["2"], { timeout: 5e3, stdio: "ignore" });
6100
+ } catch {
6101
+ }
6102
+ try {
6103
+ execFileSync2("open", ["-a", "Docker"], { timeout: 1e4, stdio: "ignore" });
6104
+ } catch {
6105
+ }
6106
+ break;
6107
+ case "background":
6108
+ try {
6109
+ const appBin = "/Applications/Docker.app/Contents/MacOS/Docker";
6110
+ if (existsSync4(appBin)) {
6111
+ execSync(`"${appBin}" &`, { timeout: 5e3, stdio: "ignore", shell: "sh" });
6112
+ }
6113
+ } catch {
6114
+ }
6115
+ break;
6116
+ default:
6117
+ try {
6118
+ execFileSync2("open", ["-a", "Docker"], { timeout: 1e4, stdio: "ignore" });
6119
+ } catch {
6120
+ }
5965
6121
  }
5966
6122
  } else if (os === "linux") {
5967
- try {
5968
- execFileSync2("sudo", ["systemctl", "start", "docker"], { timeout: 15e3, stdio: "ignore" });
5969
- } catch {
6123
+ switch (strategy) {
6124
+ case "snap":
6125
+ try {
6126
+ execFileSync2("sudo", ["snap", "start", "docker"], { timeout: 15e3, stdio: "ignore" });
6127
+ } catch {
6128
+ }
6129
+ break;
6130
+ case "service":
6131
+ try {
6132
+ execFileSync2("sudo", ["service", "docker", "start"], { timeout: 15e3, stdio: "ignore" });
6133
+ } catch {
6134
+ }
6135
+ break;
6136
+ default:
6137
+ try {
6138
+ execFileSync2("sudo", ["systemctl", "start", "docker"], { timeout: 15e3, stdio: "ignore" });
6139
+ } catch {
6140
+ }
5970
6141
  }
5971
6142
  }
5972
6143
  }
5973
6144
  /**
5974
- * Wait for Docker daemon to be ready (up to 3 minutes).
5975
- * Docker Desktop can take 1-2+ minutes on first launch.
6145
+ * Wait for Docker daemon to be ready, with automatic retry strategies.
6146
+ * Tries multiple approaches to start Docker if the first one fails.
6147
+ * Reports progress as a percentage (0-100).
5976
6148
  */
5977
6149
  async waitForDocker() {
5978
- this.onProgress("Waiting for Docker to start (this can take a minute)...");
5979
- const maxWait = 18e4;
6150
+ const os = platform2();
6151
+ const strategies = os === "darwin" ? ["default", "cli", "reopen", "background"] : ["default", "service", "snap"];
6152
+ const totalTime = 24e4;
6153
+ const perStrategyTime = Math.floor(totalTime / strategies.length);
5980
6154
  const start = Date.now();
5981
- let attempts = 0;
5982
- while (Date.now() - start < maxWait) {
6155
+ let strategyIdx = 0;
6156
+ this.onProgress("__progress__:0:Starting Docker...");
6157
+ while (Date.now() - start < totalTime) {
5983
6158
  try {
5984
6159
  execFileSync2("docker", ["info"], { timeout: 5e3, stdio: "ignore" });
6160
+ this.onProgress("__progress__:100:Docker is ready!");
5985
6161
  return;
5986
6162
  } catch {
5987
6163
  }
5988
- attempts++;
5989
- if (attempts % 5 === 0) {
5990
- const elapsed = Math.round((Date.now() - start) / 1e3);
5991
- this.onProgress(`Still waiting for Docker to start (${elapsed}s)...`);
6164
+ const elapsed = Date.now() - start;
6165
+ const pct = Math.min(95, Math.round(elapsed / totalTime * 100));
6166
+ const currentStrategyElapsed = elapsed - strategyIdx * perStrategyTime;
6167
+ if (currentStrategyElapsed >= perStrategyTime && strategyIdx < strategies.length - 1) {
6168
+ strategyIdx++;
6169
+ const strategy = strategies[strategyIdx];
6170
+ const msgs = {
6171
+ cli: "Trying Docker CLI...",
6172
+ reopen: "Restarting Docker Desktop...",
6173
+ background: "Trying direct launch...",
6174
+ service: "Trying service command...",
6175
+ snap: "Trying snap..."
6176
+ };
6177
+ this.onProgress(`__progress__:${pct}:${msgs[strategy] || "Trying another approach..."}`);
6178
+ this.startDockerDaemon(strategy);
6179
+ } else {
6180
+ const msgs = [
6181
+ "Starting Docker...",
6182
+ "Waiting for Docker engine...",
6183
+ "Docker is loading...",
6184
+ "Almost there...",
6185
+ "Still starting up...",
6186
+ "First launch takes a bit longer...",
6187
+ "Hang tight..."
6188
+ ];
6189
+ const msgIdx = Math.floor(elapsed / 1e4) % msgs.length;
6190
+ this.onProgress(`__progress__:${pct}:${msgs[msgIdx]}`);
5992
6191
  }
5993
6192
  await new Promise((r) => setTimeout(r, 3e3));
5994
6193
  }
5995
6194
  throw new Error(
5996
- "Docker daemon did not start in time. Open Docker Desktop manually, wait for it to finish loading, then run this again."
6195
+ "DOCKER_MANUAL_START"
5997
6196
  );
5998
6197
  }
5999
6198
  /**
@@ -6010,14 +6209,17 @@ var DependencyInstaller = class {
6010
6209
  this.startDockerDaemon();
6011
6210
  await this.waitForDocker();
6012
6211
  }
6013
- this.onProgress("Starting Stalwart mail server...");
6014
- execFileSync2("docker", ["compose", "-f", composePath, "up", "-d"], {
6015
- timeout: 12e4,
6016
- stdio: ["ignore", "pipe", "pipe"]
6017
- });
6212
+ this.onProgress("__progress__:10:Pulling mail server image...");
6213
+ const composeResult = await runWithRollingOutput("docker", ["compose", "-f", composePath, "up", "-d"], { timeout: 12e4 });
6214
+ if (composeResult.exitCode !== 0) {
6215
+ throw new Error("Failed to start mail server container. Check Docker is running.");
6216
+ }
6217
+ this.onProgress("__progress__:60:Waiting for mail server to start...");
6018
6218
  const maxWait = 3e4;
6019
6219
  const start = Date.now();
6020
6220
  while (Date.now() - start < maxWait) {
6221
+ const pct = 60 + Math.round((Date.now() - start) / maxWait * 35);
6222
+ this.onProgress(`__progress__:${Math.min(95, pct)}:Starting mail server...`);
6021
6223
  try {
6022
6224
  const output = execFileSync2(
6023
6225
  "docker",
@@ -6025,6 +6227,7 @@ var DependencyInstaller = class {
6025
6227
  { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
6026
6228
  ).toString().trim();
6027
6229
  if (output.toLowerCase().includes("up")) {
6230
+ this.onProgress("__progress__:100:Mail server ready!");
6028
6231
  return;
6029
6232
  }
6030
6233
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/core",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Core SDK for AgenticMail — programmatic email for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",