codeam-cli 2.39.37 → 2.39.39

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.js +143 -108
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to `codeam-cli` are documented here.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.39.38] — 2026-06-19
8
+
9
+ ### Added
10
+
11
+ - **cli:** Host-agent runs the per-agent install script before spawning
12
+
13
+ ## [2.39.37] — 2026-06-19
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** Preview always uses cloudflared + auto-retries the tunnel
18
+
7
19
  ## [2.39.36] — 2026-06-18
8
20
 
9
21
  ### Fixed
package/dist/index.js CHANGED
@@ -498,7 +498,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
498
498
  // package.json
499
499
  var package_default = {
500
500
  name: "codeam-cli",
501
- version: "2.39.37",
501
+ version: "2.39.39",
502
502
  description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
503
503
  type: "commonjs",
504
504
  main: "dist/index.js",
@@ -5908,7 +5908,7 @@ function readAnonId() {
5908
5908
  }
5909
5909
  function superProperties() {
5910
5910
  return {
5911
- cliVersion: true ? "2.39.37" : "0.0.0-dev",
5911
+ cliVersion: true ? "2.39.39" : "0.0.0-dev",
5912
5912
  nodeVersion: process.version,
5913
5913
  platform: process.platform,
5914
5914
  arch: process.arch,
@@ -7302,10 +7302,10 @@ function buildForPlatform(platform3) {
7302
7302
  var import_node_crypto4 = require("crypto");
7303
7303
 
7304
7304
  // src/agents/claude/resolver.ts
7305
- function buildClaudeLaunch(extraArgs = [], os38 = createOsStrategy()) {
7306
- const found = os38.findInPath("claude") ?? os38.findInPath("claude-code");
7305
+ function buildClaudeLaunch(extraArgs = [], os39 = createOsStrategy()) {
7306
+ const found = os39.findInPath("claude") ?? os39.findInPath("claude-code");
7307
7307
  if (!found) return null;
7308
- return os38.buildLaunch(found, extraArgs);
7308
+ return os39.buildLaunch(found, extraArgs);
7309
7309
  }
7310
7310
 
7311
7311
  // src/agents/claude/installer.ts
@@ -9754,8 +9754,8 @@ var ClaudeRuntimeStrategy = class {
9754
9754
  meta = getAgent("claude");
9755
9755
  mode = "interactive";
9756
9756
  os;
9757
- constructor(os38) {
9758
- this.os = os38;
9757
+ constructor(os39) {
9758
+ this.os = os39;
9759
9759
  }
9760
9760
  /**
9761
9761
  * Claude Code's react-ink TUI enables bracketed-paste mode at
@@ -10374,8 +10374,8 @@ function codexCredentialLocator() {
10374
10374
  function codexLoginLauncher() {
10375
10375
  return {
10376
10376
  async ensureInstalled() {
10377
- const os38 = createOsStrategy();
10378
- return os38.findInPath("codex") !== null;
10377
+ const os39 = createOsStrategy();
10378
+ return os39.findInPath("codex") !== null;
10379
10379
  },
10380
10380
  launch() {
10381
10381
  return (0, import_node_child_process3.spawn)("codex", ["login"], { stdio: "inherit" });
@@ -10398,8 +10398,8 @@ var CodexRuntimeStrategy = class {
10398
10398
  meta = getAgent("codex");
10399
10399
  mode = "interactive";
10400
10400
  os;
10401
- constructor(os38) {
10402
- this.os = os38;
10401
+ constructor(os39) {
10402
+ this.os = os39;
10403
10403
  }
10404
10404
  async prepareLaunch() {
10405
10405
  let binary = this.os.findInPath("codex");
@@ -10495,12 +10495,12 @@ var CodexRuntimeStrategy = class {
10495
10495
  });
10496
10496
  }
10497
10497
  };
10498
- function resolveNpm(os38) {
10499
- return os38.id === "win32" ? "npm.cmd" : "npm";
10498
+ function resolveNpm(os39) {
10499
+ return os39.id === "win32" ? "npm.cmd" : "npm";
10500
10500
  }
10501
- async function installCodexViaNpm(os38) {
10501
+ async function installCodexViaNpm(os39) {
10502
10502
  return new Promise((resolve7, reject) => {
10503
- const proc = (0, import_node_child_process4.spawn)(resolveNpm(os38), ["install", "-g", "@openai/codex"], {
10503
+ const proc = (0, import_node_child_process4.spawn)(resolveNpm(os39), ["install", "-g", "@openai/codex"], {
10504
10504
  stdio: "inherit"
10505
10505
  });
10506
10506
  proc.on("close", (code) => {
@@ -10517,16 +10517,16 @@ async function installCodexViaNpm(os38) {
10517
10517
  });
10518
10518
  });
10519
10519
  }
10520
- function augmentNpmGlobalBin(os38) {
10520
+ function augmentNpmGlobalBin(os39) {
10521
10521
  try {
10522
- const result = (0, import_node_child_process4.spawnSync)(resolveNpm(os38), ["prefix", "-g"], {
10522
+ const result = (0, import_node_child_process4.spawnSync)(resolveNpm(os39), ["prefix", "-g"], {
10523
10523
  stdio: ["ignore", "pipe", "ignore"]
10524
10524
  });
10525
10525
  if (result.status !== 0) return;
10526
10526
  const prefix = result.stdout.toString().trim();
10527
10527
  if (!prefix) return;
10528
- const binDir = os38.id === "win32" ? prefix : path17.join(prefix, "bin");
10529
- os38.augmentPath([binDir]);
10528
+ const binDir = os39.id === "win32" ? prefix : path17.join(prefix, "bin");
10529
+ os39.augmentPath([binDir]);
10530
10530
  } catch {
10531
10531
  }
10532
10532
  }
@@ -10610,9 +10610,9 @@ var import_node_child_process7 = require("child_process");
10610
10610
  // src/agents/coderabbit/installer.ts
10611
10611
  var import_node_child_process5 = require("child_process");
10612
10612
  var INSTALL_URL = "https://cli.coderabbit.ai/install.sh";
10613
- async function ensureCoderabbitInstalled(os38) {
10614
- if (os38.findInPath("coderabbit")) return true;
10615
- if (os38.id === "win32") {
10613
+ async function ensureCoderabbitInstalled(os39) {
10614
+ if (os39.findInPath("coderabbit")) return true;
10615
+ if (os39.id === "win32") {
10616
10616
  console.error(
10617
10617
  "\n \u2717 CodeRabbit on Windows requires WSL.\n Install the CLI inside your WSL distribution\n (curl -fsSL https://cli.coderabbit.ai/install.sh | sh)\n then re-run `codeam link coderabbit` from WSL.\n"
10618
10618
  );
@@ -10627,8 +10627,8 @@ async function ensureCoderabbitInstalled(os38) {
10627
10627
  proc.on("error", () => resolve7(false));
10628
10628
  });
10629
10629
  if (!ok) return false;
10630
- os38.augmentPath([`${os38.homeDir()}/.local/bin`, "/opt/homebrew/bin"]);
10631
- return os38.findInPath("coderabbit") !== null;
10630
+ os39.augmentPath([`${os39.homeDir()}/.local/bin`, "/opt/homebrew/bin"]);
10631
+ return os39.findInPath("coderabbit") !== null;
10632
10632
  }
10633
10633
 
10634
10634
  // src/agents/coderabbit/link.ts
@@ -10655,10 +10655,10 @@ function coderabbitCredentialLocator() {
10655
10655
  extract: extractLocalCoderabbitToken
10656
10656
  };
10657
10657
  }
10658
- function coderabbitLoginLauncher(os38) {
10658
+ function coderabbitLoginLauncher(os39) {
10659
10659
  return {
10660
10660
  async ensureInstalled() {
10661
- return ensureCoderabbitInstalled(os38);
10661
+ return ensureCoderabbitInstalled(os39);
10662
10662
  },
10663
10663
  launch() {
10664
10664
  return (0, import_node_child_process6.spawn)("coderabbit", ["login"], { stdio: "inherit" });
@@ -10704,8 +10704,8 @@ var CoderabbitRuntimeStrategy = class {
10704
10704
  meta = getAgent("coderabbit");
10705
10705
  mode = "batch";
10706
10706
  os;
10707
- constructor(os38) {
10708
- this.os = os38;
10707
+ constructor(os39) {
10708
+ this.os = os39;
10709
10709
  }
10710
10710
  getDefaultArgs() {
10711
10711
  return ["review"];
@@ -10820,10 +10820,10 @@ function cursorCredentialLocator() {
10820
10820
  extract: extractLocalCursorToken
10821
10821
  };
10822
10822
  }
10823
- function cursorLoginLauncher(os38) {
10823
+ function cursorLoginLauncher(os39) {
10824
10824
  return {
10825
10825
  async ensureInstalled() {
10826
- if (os38.findInPath("cursor-agent")) return true;
10826
+ if (os39.findInPath("cursor-agent")) return true;
10827
10827
  console.error(
10828
10828
  "\n \u2717 cursor-agent binary not on PATH.\n Install Cursor (https://cursor.com/) and ensure the CLI\n plugin is enabled, then re-run `codeam link cursor`.\n"
10829
10829
  );
@@ -10885,8 +10885,8 @@ var CursorRuntimeStrategy = class {
10885
10885
  meta = getAgent("cursor");
10886
10886
  mode = "interactive";
10887
10887
  os;
10888
- constructor(os38) {
10889
- this.os = os38;
10888
+ constructor(os39) {
10889
+ this.os = os39;
10890
10890
  }
10891
10891
  async prepareLaunch() {
10892
10892
  const binary = this.os.findInPath("cursor-agent");
@@ -11006,10 +11006,10 @@ function aiderCredentialLocator() {
11006
11006
  extract: extractLocalAiderToken
11007
11007
  };
11008
11008
  }
11009
- function aiderLoginLauncher(os38) {
11009
+ function aiderLoginLauncher(os39) {
11010
11010
  return {
11011
11011
  async ensureInstalled() {
11012
- if (os38.findInPath("aider")) return true;
11012
+ if (os39.findInPath("aider")) return true;
11013
11013
  console.error(
11014
11014
  "\n \u2717 aider binary not on PATH.\n Install Aider:\n pip install aider-chat\n then re-run `codeam link aider`.\n"
11015
11015
  );
@@ -11019,7 +11019,7 @@ function aiderLoginLauncher(os38) {
11019
11019
  console.error(
11020
11020
  "\n Aider has no interactive login flow.\n Set ANTHROPIC_API_KEY or OPENAI_API_KEY in your shell,\n or re-run `codeam link aider --api-key=<your-key>`.\n"
11021
11021
  );
11022
- return (0, import_node_child_process9.spawn)(os38.id === "win32" ? "cmd.exe" : "sh", os38.id === "win32" ? ["/c", "exit", "0"] : ["-c", "exit 0"], {
11022
+ return (0, import_node_child_process9.spawn)(os39.id === "win32" ? "cmd.exe" : "sh", os39.id === "win32" ? ["/c", "exit", "0"] : ["-c", "exit 0"], {
11023
11023
  stdio: "ignore"
11024
11024
  });
11025
11025
  }
@@ -11091,8 +11091,8 @@ var AiderRuntimeStrategy = class {
11091
11091
  meta = getAgent("aider");
11092
11092
  mode = "interactive";
11093
11093
  os;
11094
- constructor(os38) {
11095
- this.os = os38;
11094
+ constructor(os39) {
11095
+ this.os = os39;
11096
11096
  }
11097
11097
  async prepareLaunch() {
11098
11098
  const binary = this.os.findInPath("aider");
@@ -11221,8 +11221,8 @@ function geminiCredentialLocator() {
11221
11221
  function geminiLoginLauncher() {
11222
11222
  return {
11223
11223
  async ensureInstalled() {
11224
- const os38 = createOsStrategy();
11225
- return os38.findInPath("gemini") !== null;
11224
+ const os39 = createOsStrategy();
11225
+ return os39.findInPath("gemini") !== null;
11226
11226
  },
11227
11227
  launch() {
11228
11228
  return (0, import_node_child_process10.spawn)("gemini", ["auth", "login"], { stdio: "inherit" });
@@ -11255,8 +11255,8 @@ var GeminiRuntimeStrategy = class {
11255
11255
  meta = getAgent("gemini");
11256
11256
  mode = "interactive";
11257
11257
  os;
11258
- constructor(os38) {
11259
- this.os = os38;
11258
+ constructor(os39) {
11259
+ this.os = os39;
11260
11260
  }
11261
11261
  async prepareLaunch() {
11262
11262
  const binary = this.os.findInPath("gemini");
@@ -11355,18 +11355,18 @@ var GeminiRuntimeStrategy = class {
11355
11355
 
11356
11356
  // src/agents/registry.ts
11357
11357
  var runtimeBuilders = {
11358
- claude: (os38) => new ClaudeRuntimeStrategy(os38),
11359
- codex: (os38) => new CodexRuntimeStrategy(os38),
11360
- coderabbit: (os38) => new CoderabbitRuntimeStrategy(os38),
11361
- cursor: (os38) => new CursorRuntimeStrategy(os38),
11362
- aider: (os38) => new AiderRuntimeStrategy(os38),
11363
- gemini: (os38) => new GeminiRuntimeStrategy(os38)
11358
+ claude: (os39) => new ClaudeRuntimeStrategy(os39),
11359
+ codex: (os39) => new CodexRuntimeStrategy(os39),
11360
+ coderabbit: (os39) => new CoderabbitRuntimeStrategy(os39),
11361
+ cursor: (os39) => new CursorRuntimeStrategy(os39),
11362
+ aider: (os39) => new AiderRuntimeStrategy(os39),
11363
+ gemini: (os39) => new GeminiRuntimeStrategy(os39)
11364
11364
  };
11365
11365
  var deployBuilders = {
11366
11366
  claude: () => new ClaudeDeployStrategy(),
11367
11367
  codex: () => new CodexDeployStrategy()
11368
11368
  };
11369
- function createAgentStrategy(agent, os38 = createOsStrategy()) {
11369
+ function createAgentStrategy(agent, os39 = createOsStrategy()) {
11370
11370
  if (!AGENT_REGISTRY[agent]?.enabled) {
11371
11371
  throw new Error(
11372
11372
  `Agent "${agent}" is not supported in this codeam-cli version. Upgrade with 'npm i -g codeam-cli@latest'.`
@@ -11376,10 +11376,10 @@ function createAgentStrategy(agent, os38 = createOsStrategy()) {
11376
11376
  if (!build) {
11377
11377
  throw new Error(`No runtime strategy registered for agent "${agent}"`);
11378
11378
  }
11379
- return build(os38);
11379
+ return build(os39);
11380
11380
  }
11381
- function createInteractiveAgentStrategy(agent, os38 = createOsStrategy()) {
11382
- const s = createAgentStrategy(agent, os38);
11381
+ function createInteractiveAgentStrategy(agent, os39 = createOsStrategy()) {
11382
+ const s = createAgentStrategy(agent, os39);
11383
11383
  if (s.mode !== "interactive") {
11384
11384
  throw new Error(
11385
11385
  `Agent "${agent}" is a batch agent; use createAgentStrategy + .runOneShot for one-shot reviews.`
@@ -16464,37 +16464,20 @@ async function linkDryRunPreflight(ctx) {
16464
16464
  }
16465
16465
 
16466
16466
  // src/services/preview/cloudflared.ts
16467
- var import_promises = __toESM(require("dns/promises"));
16468
16467
  var import_fs = require("fs");
16469
- var import_promises2 = __toESM(require("fs/promises"));
16468
+ var import_promises = __toESM(require("fs/promises"));
16470
16469
  var import_os6 = __toESM(require("os"));
16471
16470
  var import_path4 = __toESM(require("path"));
16472
- var import_promises3 = require("stream/promises");
16471
+ var import_promises2 = require("stream/promises");
16473
16472
  var import_which = __toESM(require("which"));
16474
16473
  var CACHED_BINARY = import_path4.default.join(import_os6.default.homedir(), ".codeam", "bin", "cloudflared");
16475
- async function waitForCloudflaredReady(url, timeoutMs = 6e4) {
16476
- const hostname3 = new URL(url).hostname;
16477
- const cares = new import_promises.Resolver();
16478
- const start2 = Date.now();
16479
- while (Date.now() - start2 < timeoutMs) {
16480
- const lookup = import_promises.default.lookup(hostname3, { all: true }).then((addrs) => addrs.length > 0, () => false);
16481
- const v4 = cares.resolve4(hostname3).then((addrs) => addrs.length > 0, () => false);
16482
- const v6 = cares.resolve6(hostname3).then((addrs) => addrs.length > 0, () => false);
16483
- const results = await Promise.all([lookup, v4, v6]);
16484
- if (results.some((ok) => ok)) return;
16485
- await new Promise((r) => setTimeout(r, 1e3));
16486
- }
16487
- throw new Error(
16488
- `DNS for ${hostname3} did not resolve within ${timeoutMs}ms (Cloudflare Quick Tunnel registration may have failed).`
16489
- );
16490
- }
16491
16474
  async function resolveCloudflared(opts = {}) {
16492
16475
  try {
16493
16476
  return await (0, import_which.default)("cloudflared");
16494
16477
  } catch {
16495
16478
  }
16496
16479
  try {
16497
- await import_promises2.default.access(CACHED_BINARY);
16480
+ await import_promises.default.access(CACHED_BINARY);
16498
16481
  return CACHED_BINARY;
16499
16482
  } catch {
16500
16483
  }
@@ -16508,14 +16491,14 @@ async function resolveCloudflared(opts = {}) {
16508
16491
  }
16509
16492
  async function downloadCloudflared(target) {
16510
16493
  const url = downloadUrlForPlatform();
16511
- await import_promises2.default.mkdir(import_path4.default.dirname(target), { recursive: true });
16494
+ await import_promises.default.mkdir(import_path4.default.dirname(target), { recursive: true });
16512
16495
  const response = await fetch(url);
16513
16496
  if (!response.ok || !response.body) {
16514
16497
  throw new Error(
16515
16498
  `Failed to download cloudflared from ${url}: HTTP ${response.status}. Install manually from https://github.com/cloudflare/cloudflared/releases.`
16516
16499
  );
16517
16500
  }
16518
- await (0, import_promises3.pipeline)(
16501
+ await (0, import_promises2.pipeline)(
16519
16502
  response.body,
16520
16503
  (0, import_fs.createWriteStream)(target, { mode: 493 })
16521
16504
  );
@@ -16540,7 +16523,7 @@ var import_util3 = require("util");
16540
16523
  var execFileP4 = (0, import_util3.promisify)(import_child_process11.execFile);
16541
16524
 
16542
16525
  // src/services/preview/config-file.ts
16543
- var import_promises4 = __toESM(require("fs/promises"));
16526
+ var import_promises3 = __toESM(require("fs/promises"));
16544
16527
  var import_path5 = __toESM(require("path"));
16545
16528
  var CONFIG_DIR = ".codeam";
16546
16529
  var CONFIG_FILE = "preview.json";
@@ -16557,7 +16540,7 @@ var REQUIRED_FIELDS = [
16557
16540
  async function readPreviewConfig(cwd) {
16558
16541
  let raw;
16559
16542
  try {
16560
- raw = await import_promises4.default.readFile(configPath(cwd), "utf-8");
16543
+ raw = await import_promises3.default.readFile(configPath(cwd), "utf-8");
16561
16544
  } catch {
16562
16545
  return null;
16563
16546
  }
@@ -16576,8 +16559,8 @@ async function readPreviewConfig(cwd) {
16576
16559
  }
16577
16560
  async function writePreviewConfig(cwd, detection) {
16578
16561
  const filePath = configPath(cwd);
16579
- await import_promises4.default.mkdir(import_path5.default.dirname(filePath), { recursive: true });
16580
- await import_promises4.default.writeFile(filePath, JSON.stringify(detection, null, 2) + "\n", "utf-8");
16562
+ await import_promises3.default.mkdir(import_path5.default.dirname(filePath), { recursive: true });
16563
+ await import_promises3.default.writeFile(filePath, JSON.stringify(detection, null, 2) + "\n", "utf-8");
16581
16564
  }
16582
16565
 
16583
16566
  // src/services/preview/parser.ts
@@ -17309,11 +17292,11 @@ function resolveDoltInstallStrategy(platform3) {
17309
17292
  }
17310
17293
  var DOLT_RELEASE_BASE = "https://github.com/dolthub/dolt/releases/latest/download";
17311
17294
  function doltPlatformTuple(platform3, arch2) {
17312
- const os38 = platform3 === "win32" ? "windows" : platform3 === "darwin" ? "darwin" : "linux";
17295
+ const os39 = platform3 === "win32" ? "windows" : platform3 === "darwin" ? "darwin" : "linux";
17313
17296
  const a = arch2 === "x64" ? "amd64" : arch2 === "arm64" ? "arm64" : null;
17314
17297
  if (!a) return null;
17315
- if (os38 === "windows" && a !== "amd64") return null;
17316
- return `${os38}-${a}`;
17298
+ if (os39 === "windows" && a !== "amd64") return null;
17299
+ return `${os39}-${a}`;
17317
17300
  }
17318
17301
  function resolveDoltTarballStrategy(targetDir, platform3, arch2) {
17319
17302
  const tuple = doltPlatformTuple(platform3, arch2);
@@ -19147,33 +19130,26 @@ var previewStartH = (ctx, _cmd, parsed) => {
19147
19130
  { stdio: ["ignore", "pipe", "pipe"] }
19148
19131
  );
19149
19132
  let candidateUrl = null;
19133
+ let registered = false;
19150
19134
  const onTunnelChunk = (chunk) => {
19151
19135
  const s = chunk.toString();
19152
19136
  if (!candidateUrl) candidateUrl = parseCloudflaredUrl(s);
19137
+ if (/Registered tunnel connection/i.test(s)) registered = true;
19153
19138
  const trimmed = s.replace(/\n+$/g, "");
19154
19139
  if (trimmed.length > 0) log.info("preview", `cloudflared: ${trimmed}`);
19155
19140
  };
19156
19141
  candidate.stderr.on("data", onTunnelChunk);
19157
19142
  candidate.stdout.on("data", onTunnelChunk);
19158
- const urlDeadline = Date.now() + 45e3;
19159
- while (!candidateUrl && Date.now() < urlDeadline) {
19143
+ const deadline = Date.now() + 45e3;
19144
+ while ((!candidateUrl || !registered) && Date.now() < deadline) {
19160
19145
  await new Promise((r) => setTimeout(r, 250));
19161
19146
  }
19162
- if (!candidateUrl) {
19163
- lastTunnelErr = "cloudflared did not emit a URL within 45s";
19164
- try {
19165
- candidate.kill("SIGTERM");
19166
- } catch {
19167
- }
19168
- continue;
19169
- }
19170
- try {
19171
- await waitForCloudflaredReady(candidateUrl, 4e4);
19172
- log.info("preview", `cloudflared probe: ${candidateUrl} reachable from CLI host`);
19147
+ if (candidateUrl && registered) {
19148
+ log.info("preview", `cloudflared tunnel registered: ${candidateUrl}`);
19173
19149
  tunnel = candidate;
19174
19150
  parsedUrl = candidateUrl;
19175
- } catch (e) {
19176
- lastTunnelErr = e.message;
19151
+ } else {
19152
+ lastTunnelErr = candidateUrl ? "cloudflared did not register a tunnel connection within 45s" : "cloudflared did not emit a URL within 45s";
19177
19153
  try {
19178
19154
  candidate.kill("SIGTERM");
19179
19155
  } catch {
@@ -19428,10 +19404,10 @@ var WINDOWS_LEGACY_JUNCTIONS = [
19428
19404
  /[\\/]Start Menu([\\/]|$)/i,
19429
19405
  /[\\/]Templates([\\/]|$)/i
19430
19406
  ];
19431
- function isUnsafeWindowsWatchRoot(dir, homedir31) {
19407
+ function isUnsafeWindowsWatchRoot(dir, homedir32) {
19432
19408
  const norm = (p2) => p2.replace(/\//g, "\\").replace(/\\+$/, "").toLowerCase();
19433
19409
  const cwd = norm(dir);
19434
- const home = norm(homedir31);
19410
+ const home = norm(homedir32);
19435
19411
  if (cwd === home) return true;
19436
19412
  if (/^[a-z]:$/.test(cwd)) return true;
19437
19413
  const sysRoots = [
@@ -26484,6 +26460,7 @@ async function host(args2) {
26484
26460
 
26485
26461
  // src/commands/host-agent.ts
26486
26462
  var import_node_child_process13 = require("child_process");
26463
+ var os36 = __toESM(require("os"));
26487
26464
 
26488
26465
  // src/commands/host/workspace.ts
26489
26466
  var fs42 = __toESM(require("fs"));
@@ -26856,6 +26833,12 @@ var HostAgentSupervisor = class {
26856
26833
  CODEAM_AUTO_TOKEN: payload.autoPairToken
26857
26834
  };
26858
26835
  }
26836
+ if (payload.agentInstallScript) {
26837
+ report("installing", "installing agent CLI");
26838
+ await this.runAgentInstall(payload.agentInstallScript);
26839
+ }
26840
+ const home = process.env.HOME || os36.homedir();
26841
+ childEnv.PATH = `${home}/.local/bin:${process.env.PATH ?? ""}`;
26859
26842
  report("spawning", "starting agent");
26860
26843
  const proc = this.spawnChild(childEnv, cwd, extraArgs);
26861
26844
  const child = { deployId: payload.deployId, proc };
@@ -26891,6 +26874,58 @@ var HostAgentSupervisor = class {
26891
26874
  report("failed", message);
26892
26875
  }
26893
26876
  }
26877
+ /**
26878
+ * Run the backend-supplied per-agent CLI install script (e.g.
26879
+ * `claude.ai/install.sh`, `npm i -g @openai/codex`). Best-effort + bounded:
26880
+ * a non-zero exit / timeout is logged but never rejects, so a box that
26881
+ * can't reach an installer still deploys (the chat agent runs via the
26882
+ * bundled ACP SDK; only `claude -p` / `codex` preview detection degrades).
26883
+ * HOME is forced so the installer's `~/.local/bin` resolves on a detached
26884
+ * host-agent whose env may lack it.
26885
+ */
26886
+ runAgentInstall(script) {
26887
+ return new Promise((resolve7) => {
26888
+ const home = process.env.HOME || os36.homedir();
26889
+ const child = (0, import_node_child_process13.spawn)("sh", ["-c", script], {
26890
+ env: { ...process.env, HOME: home },
26891
+ stdio: ["ignore", "pipe", "pipe"]
26892
+ });
26893
+ const onData = (b) => {
26894
+ const line = b.toString().replace(/\n+$/g, "");
26895
+ if (line) log.info("host-agent", `agent-install: ${line}`);
26896
+ };
26897
+ child.stdout?.on("data", onData);
26898
+ child.stderr?.on("data", onData);
26899
+ let settled = false;
26900
+ const done = () => {
26901
+ if (settled) return;
26902
+ settled = true;
26903
+ resolve7();
26904
+ };
26905
+ const timer = setTimeout(() => {
26906
+ log.warn("host-agent", "agent install timed out (180s) \u2014 preview detection may be unavailable");
26907
+ try {
26908
+ child.kill("SIGTERM");
26909
+ } catch {
26910
+ }
26911
+ done();
26912
+ }, 18e4);
26913
+ child.once("exit", (code) => {
26914
+ clearTimeout(timer);
26915
+ if (code !== 0) {
26916
+ log.warn("host-agent", `agent install exited code=${code} \u2014 preview detection may be unavailable; agent still runs`);
26917
+ } else {
26918
+ log.info("host-agent", "agent CLI installed");
26919
+ }
26920
+ done();
26921
+ });
26922
+ child.once("error", (e) => {
26923
+ clearTimeout(timer);
26924
+ log.warn("host-agent", `agent install spawn error: ${e.message}`);
26925
+ done();
26926
+ });
26927
+ });
26928
+ }
26894
26929
  /**
26895
26930
  * Kill the child for the given id. The backend correlates the session it
26896
26931
  * sends to this deploy, so the id matches the deployId we keyed on. No-op
@@ -27066,9 +27101,9 @@ function checkSessions() {
27066
27101
  }
27067
27102
  }
27068
27103
  function checkAgentBinaries() {
27069
- const os38 = createOsStrategy();
27104
+ const os39 = createOsStrategy();
27070
27105
  return getEnabledAgents().map((meta) => {
27071
- const found = os38.findInPath(meta.binaryName);
27106
+ const found = os39.findInPath(meta.binaryName);
27072
27107
  return {
27073
27108
  id: `agent-${meta.id}`,
27074
27109
  label: `Agent binary: ${meta.displayName} (${meta.binaryName})`,
@@ -27132,16 +27167,16 @@ function checkChokidar() {
27132
27167
  }
27133
27168
  async function doctor(args2 = []) {
27134
27169
  const json = args2.includes("--json");
27135
- const cliVersion = true ? "2.39.37" : "0.0.0-dev";
27170
+ const cliVersion = true ? "2.39.39" : "0.0.0-dev";
27136
27171
  const apiBase2 = resolveApiBaseUrl();
27137
27172
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
27138
27173
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
27139
- const [dns2, health] = await Promise.all([
27174
+ const [dns, health] = await Promise.all([
27140
27175
  checkDns(apiBase2),
27141
27176
  checkHealth(apiBase2)
27142
27177
  ]);
27143
27178
  const checks = [
27144
- dns2,
27179
+ dns,
27145
27180
  health,
27146
27181
  checkConfigDir(),
27147
27182
  checkSessions(),
@@ -27331,7 +27366,7 @@ async function completion(args2) {
27331
27366
  // src/commands/version.ts
27332
27367
  var import_picocolors13 = __toESM(require("picocolors"));
27333
27368
  function version2() {
27334
- const v = true ? "2.39.37" : "unknown";
27369
+ const v = true ? "2.39.39" : "unknown";
27335
27370
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
27336
27371
  }
27337
27372
 
@@ -27460,7 +27495,7 @@ var _subcommandHelpKeys = Object.keys(HELPS);
27460
27495
 
27461
27496
  // src/lib/updateNotifier.ts
27462
27497
  var fs45 = __toESM(require("fs"));
27463
- var os36 = __toESM(require("os"));
27498
+ var os37 = __toESM(require("os"));
27464
27499
  var path57 = __toESM(require("path"));
27465
27500
  var https8 = __toESM(require("https"));
27466
27501
  var import_node_child_process14 = require("child_process");
@@ -27470,7 +27505,7 @@ var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
27470
27505
  var TTL_MS = 24 * 60 * 60 * 1e3;
27471
27506
  var REQUEST_TIMEOUT_MS = 1500;
27472
27507
  function cachePath() {
27473
- const dir = path57.join(os36.homedir(), ".codeam");
27508
+ const dir = path57.join(os37.homedir(), ".codeam");
27474
27509
  return path57.join(dir, "update-check.json");
27475
27510
  }
27476
27511
  function readCache() {
@@ -27617,7 +27652,7 @@ function checkForUpdates() {
27617
27652
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
27618
27653
  if (process.env.CI) return;
27619
27654
  if (!process.stdout.isTTY) return;
27620
- const current = true ? "2.39.37" : null;
27655
+ const current = true ? "2.39.39" : null;
27621
27656
  if (!current) return;
27622
27657
  const cache = readCache();
27623
27658
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -27648,10 +27683,10 @@ var EXIT_CODE_NAMES = {
27648
27683
  };
27649
27684
 
27650
27685
  // src/index.ts
27651
- var os37 = __toESM(require("os"));
27686
+ var os38 = __toESM(require("os"));
27652
27687
  if (!process.env.HOME) {
27653
27688
  try {
27654
- const home = os37.homedir();
27689
+ const home = os38.homedir();
27655
27690
  if (home) process.env.HOME = home;
27656
27691
  } catch {
27657
27692
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.39.37",
3
+ "version": "2.39.39",
4
4
  "description": "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device — async. The terminal companion for CodeAgent Mobile.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",