codeam-cli 2.35.2 → 2.35.4

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 +17 -0
  2. package/dist/index.js +172 -95
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ 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.35.3] — 2026-06-10
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Symlink bd into a WRITABLE on-PATH dir (~/.local/bin) for codespaces
12
+
13
+ ## [2.35.2] — 2026-06-10
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** Bound preview install + add port-listening ready fallback
18
+ - **cli:** Stop resume_session from killing the agent dead
19
+
20
+ ### Tests
21
+
22
+ - **cli:** Make linkBdOntoPath/cliBinDir provisioner tests OS-independent
23
+
7
24
  ## [2.35.1] — 2026-06-10
8
25
 
9
26
  ### 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.35.2",
501
+ version: "2.35.4",
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",
@@ -5898,7 +5898,7 @@ function readAnonId() {
5898
5898
  }
5899
5899
  function superProperties() {
5900
5900
  return {
5901
- cliVersion: true ? "2.35.2" : "0.0.0-dev",
5901
+ cliVersion: true ? "2.35.4" : "0.0.0-dev",
5902
5902
  nodeVersion: process.version,
5903
5903
  platform: process.platform,
5904
5904
  arch: process.arch,
@@ -7246,10 +7246,10 @@ function buildForPlatform(platform2) {
7246
7246
  var import_node_crypto4 = require("crypto");
7247
7247
 
7248
7248
  // src/agents/claude/resolver.ts
7249
- function buildClaudeLaunch(extraArgs = [], os30 = createOsStrategy()) {
7250
- const found = os30.findInPath("claude") ?? os30.findInPath("claude-code");
7249
+ function buildClaudeLaunch(extraArgs = [], os31 = createOsStrategy()) {
7250
+ const found = os31.findInPath("claude") ?? os31.findInPath("claude-code");
7251
7251
  if (!found) return null;
7252
- return os30.buildLaunch(found, extraArgs);
7252
+ return os31.buildLaunch(found, extraArgs);
7253
7253
  }
7254
7254
 
7255
7255
  // src/agents/claude/installer.ts
@@ -9992,8 +9992,8 @@ var ClaudeRuntimeStrategy = class {
9992
9992
  meta = getAgent("claude");
9993
9993
  mode = "interactive";
9994
9994
  os;
9995
- constructor(os30) {
9996
- this.os = os30;
9995
+ constructor(os31) {
9996
+ this.os = os31;
9997
9997
  }
9998
9998
  /**
9999
9999
  * Claude Code's react-ink TUI enables bracketed-paste mode at
@@ -11023,8 +11023,8 @@ function codexCredentialLocator() {
11023
11023
  function codexLoginLauncher() {
11024
11024
  return {
11025
11025
  async ensureInstalled() {
11026
- const os30 = createOsStrategy();
11027
- return os30.findInPath("codex") !== null;
11026
+ const os31 = createOsStrategy();
11027
+ return os31.findInPath("codex") !== null;
11028
11028
  },
11029
11029
  launch() {
11030
11030
  return (0, import_node_child_process3.spawn)("codex", ["login"], { stdio: "inherit" });
@@ -11047,8 +11047,8 @@ var CodexRuntimeStrategy = class {
11047
11047
  meta = getAgent("codex");
11048
11048
  mode = "interactive";
11049
11049
  os;
11050
- constructor(os30) {
11051
- this.os = os30;
11050
+ constructor(os31) {
11051
+ this.os = os31;
11052
11052
  }
11053
11053
  async prepareLaunch() {
11054
11054
  let binary = this.os.findInPath("codex");
@@ -11154,12 +11154,12 @@ var CodexRuntimeStrategy = class {
11154
11154
  });
11155
11155
  }
11156
11156
  };
11157
- function resolveNpm(os30) {
11158
- return os30.id === "win32" ? "npm.cmd" : "npm";
11157
+ function resolveNpm(os31) {
11158
+ return os31.id === "win32" ? "npm.cmd" : "npm";
11159
11159
  }
11160
- async function installCodexViaNpm(os30) {
11160
+ async function installCodexViaNpm(os31) {
11161
11161
  return new Promise((resolve7, reject) => {
11162
- const proc = (0, import_node_child_process4.spawn)(resolveNpm(os30), ["install", "-g", "@openai/codex"], {
11162
+ const proc = (0, import_node_child_process4.spawn)(resolveNpm(os31), ["install", "-g", "@openai/codex"], {
11163
11163
  stdio: "inherit"
11164
11164
  });
11165
11165
  proc.on("close", (code) => {
@@ -11176,16 +11176,16 @@ async function installCodexViaNpm(os30) {
11176
11176
  });
11177
11177
  });
11178
11178
  }
11179
- function augmentNpmGlobalBin(os30) {
11179
+ function augmentNpmGlobalBin(os31) {
11180
11180
  try {
11181
- const result = (0, import_node_child_process4.spawnSync)(resolveNpm(os30), ["prefix", "-g"], {
11181
+ const result = (0, import_node_child_process4.spawnSync)(resolveNpm(os31), ["prefix", "-g"], {
11182
11182
  stdio: ["ignore", "pipe", "ignore"]
11183
11183
  });
11184
11184
  if (result.status !== 0) return;
11185
11185
  const prefix = result.stdout.toString().trim();
11186
11186
  if (!prefix) return;
11187
- const binDir = os30.id === "win32" ? prefix : path17.join(prefix, "bin");
11188
- os30.augmentPath([binDir]);
11187
+ const binDir = os31.id === "win32" ? prefix : path17.join(prefix, "bin");
11188
+ os31.augmentPath([binDir]);
11189
11189
  } catch {
11190
11190
  }
11191
11191
  }
@@ -11269,9 +11269,9 @@ var import_node_child_process7 = require("child_process");
11269
11269
  // src/agents/coderabbit/installer.ts
11270
11270
  var import_node_child_process5 = require("child_process");
11271
11271
  var INSTALL_URL = "https://cli.coderabbit.ai/install.sh";
11272
- async function ensureCoderabbitInstalled(os30) {
11273
- if (os30.findInPath("coderabbit")) return true;
11274
- if (os30.id === "win32") {
11272
+ async function ensureCoderabbitInstalled(os31) {
11273
+ if (os31.findInPath("coderabbit")) return true;
11274
+ if (os31.id === "win32") {
11275
11275
  console.error(
11276
11276
  "\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"
11277
11277
  );
@@ -11286,8 +11286,8 @@ async function ensureCoderabbitInstalled(os30) {
11286
11286
  proc.on("error", () => resolve7(false));
11287
11287
  });
11288
11288
  if (!ok) return false;
11289
- os30.augmentPath([`${os30.homeDir()}/.local/bin`, "/opt/homebrew/bin"]);
11290
- return os30.findInPath("coderabbit") !== null;
11289
+ os31.augmentPath([`${os31.homeDir()}/.local/bin`, "/opt/homebrew/bin"]);
11290
+ return os31.findInPath("coderabbit") !== null;
11291
11291
  }
11292
11292
 
11293
11293
  // src/agents/coderabbit/link.ts
@@ -11314,10 +11314,10 @@ function coderabbitCredentialLocator() {
11314
11314
  extract: extractLocalCoderabbitToken
11315
11315
  };
11316
11316
  }
11317
- function coderabbitLoginLauncher(os30) {
11317
+ function coderabbitLoginLauncher(os31) {
11318
11318
  return {
11319
11319
  async ensureInstalled() {
11320
- return ensureCoderabbitInstalled(os30);
11320
+ return ensureCoderabbitInstalled(os31);
11321
11321
  },
11322
11322
  launch() {
11323
11323
  return (0, import_node_child_process6.spawn)("coderabbit", ["login"], { stdio: "inherit" });
@@ -11363,8 +11363,8 @@ var CoderabbitRuntimeStrategy = class {
11363
11363
  meta = getAgent("coderabbit");
11364
11364
  mode = "batch";
11365
11365
  os;
11366
- constructor(os30) {
11367
- this.os = os30;
11366
+ constructor(os31) {
11367
+ this.os = os31;
11368
11368
  }
11369
11369
  getDefaultArgs() {
11370
11370
  return ["review"];
@@ -11479,10 +11479,10 @@ function cursorCredentialLocator() {
11479
11479
  extract: extractLocalCursorToken
11480
11480
  };
11481
11481
  }
11482
- function cursorLoginLauncher(os30) {
11482
+ function cursorLoginLauncher(os31) {
11483
11483
  return {
11484
11484
  async ensureInstalled() {
11485
- if (os30.findInPath("cursor-agent")) return true;
11485
+ if (os31.findInPath("cursor-agent")) return true;
11486
11486
  console.error(
11487
11487
  "\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"
11488
11488
  );
@@ -11544,8 +11544,8 @@ var CursorRuntimeStrategy = class {
11544
11544
  meta = getAgent("cursor");
11545
11545
  mode = "interactive";
11546
11546
  os;
11547
- constructor(os30) {
11548
- this.os = os30;
11547
+ constructor(os31) {
11548
+ this.os = os31;
11549
11549
  }
11550
11550
  async prepareLaunch() {
11551
11551
  const binary = this.os.findInPath("cursor-agent");
@@ -11665,10 +11665,10 @@ function aiderCredentialLocator() {
11665
11665
  extract: extractLocalAiderToken
11666
11666
  };
11667
11667
  }
11668
- function aiderLoginLauncher(os30) {
11668
+ function aiderLoginLauncher(os31) {
11669
11669
  return {
11670
11670
  async ensureInstalled() {
11671
- if (os30.findInPath("aider")) return true;
11671
+ if (os31.findInPath("aider")) return true;
11672
11672
  console.error(
11673
11673
  "\n \u2717 aider binary not on PATH.\n Install Aider:\n pip install aider-chat\n then re-run `codeam link aider`.\n"
11674
11674
  );
@@ -11678,7 +11678,7 @@ function aiderLoginLauncher(os30) {
11678
11678
  console.error(
11679
11679
  "\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"
11680
11680
  );
11681
- return (0, import_node_child_process9.spawn)(os30.id === "win32" ? "cmd.exe" : "sh", os30.id === "win32" ? ["/c", "exit", "0"] : ["-c", "exit 0"], {
11681
+ return (0, import_node_child_process9.spawn)(os31.id === "win32" ? "cmd.exe" : "sh", os31.id === "win32" ? ["/c", "exit", "0"] : ["-c", "exit 0"], {
11682
11682
  stdio: "ignore"
11683
11683
  });
11684
11684
  }
@@ -11750,8 +11750,8 @@ var AiderRuntimeStrategy = class {
11750
11750
  meta = getAgent("aider");
11751
11751
  mode = "interactive";
11752
11752
  os;
11753
- constructor(os30) {
11754
- this.os = os30;
11753
+ constructor(os31) {
11754
+ this.os = os31;
11755
11755
  }
11756
11756
  async prepareLaunch() {
11757
11757
  const binary = this.os.findInPath("aider");
@@ -11880,8 +11880,8 @@ function geminiCredentialLocator() {
11880
11880
  function geminiLoginLauncher() {
11881
11881
  return {
11882
11882
  async ensureInstalled() {
11883
- const os30 = createOsStrategy();
11884
- return os30.findInPath("gemini") !== null;
11883
+ const os31 = createOsStrategy();
11884
+ return os31.findInPath("gemini") !== null;
11885
11885
  },
11886
11886
  launch() {
11887
11887
  return (0, import_node_child_process10.spawn)("gemini", ["auth", "login"], { stdio: "inherit" });
@@ -11914,8 +11914,8 @@ var GeminiRuntimeStrategy = class {
11914
11914
  meta = getAgent("gemini");
11915
11915
  mode = "interactive";
11916
11916
  os;
11917
- constructor(os30) {
11918
- this.os = os30;
11917
+ constructor(os31) {
11918
+ this.os = os31;
11919
11919
  }
11920
11920
  async prepareLaunch() {
11921
11921
  const binary = this.os.findInPath("gemini");
@@ -12015,18 +12015,18 @@ var GeminiRuntimeStrategy = class {
12015
12015
 
12016
12016
  // src/agents/registry.ts
12017
12017
  var runtimeBuilders = {
12018
- claude: (os30) => new ClaudeRuntimeStrategy(os30),
12019
- codex: (os30) => new CodexRuntimeStrategy(os30),
12020
- coderabbit: (os30) => new CoderabbitRuntimeStrategy(os30),
12021
- cursor: (os30) => new CursorRuntimeStrategy(os30),
12022
- aider: (os30) => new AiderRuntimeStrategy(os30),
12023
- gemini: (os30) => new GeminiRuntimeStrategy(os30)
12018
+ claude: (os31) => new ClaudeRuntimeStrategy(os31),
12019
+ codex: (os31) => new CodexRuntimeStrategy(os31),
12020
+ coderabbit: (os31) => new CoderabbitRuntimeStrategy(os31),
12021
+ cursor: (os31) => new CursorRuntimeStrategy(os31),
12022
+ aider: (os31) => new AiderRuntimeStrategy(os31),
12023
+ gemini: (os31) => new GeminiRuntimeStrategy(os31)
12024
12024
  };
12025
12025
  var deployBuilders = {
12026
12026
  claude: () => new ClaudeDeployStrategy(),
12027
12027
  codex: () => new CodexDeployStrategy()
12028
12028
  };
12029
- function createAgentStrategy(agent, os30 = createOsStrategy()) {
12029
+ function createAgentStrategy(agent, os31 = createOsStrategy()) {
12030
12030
  if (!AGENT_REGISTRY[agent]?.enabled) {
12031
12031
  throw new Error(
12032
12032
  `Agent "${agent}" is not supported in this codeam-cli version. Upgrade with 'npm i -g codeam-cli@latest'.`
@@ -12036,10 +12036,10 @@ function createAgentStrategy(agent, os30 = createOsStrategy()) {
12036
12036
  if (!build) {
12037
12037
  throw new Error(`No runtime strategy registered for agent "${agent}"`);
12038
12038
  }
12039
- return build(os30);
12039
+ return build(os31);
12040
12040
  }
12041
- function createInteractiveAgentStrategy(agent, os30 = createOsStrategy()) {
12042
- const s = createAgentStrategy(agent, os30);
12041
+ function createInteractiveAgentStrategy(agent, os31 = createOsStrategy()) {
12042
+ const s = createAgentStrategy(agent, os31);
12043
12043
  if (s.mode !== "interactive") {
12044
12044
  throw new Error(
12045
12045
  `Agent "${agent}" is a batch agent; use createAgentStrategy + .runOneShot for one-shot reviews.`
@@ -14601,9 +14601,49 @@ var RequestError = class _RequestError extends Error {
14601
14601
  }
14602
14602
  };
14603
14603
 
14604
+ // src/agents/acp/idleTimeout.ts
14605
+ function createIdleTimeout(idleMs, makeError) {
14606
+ let timer;
14607
+ let done = false;
14608
+ let reject;
14609
+ const promise = new Promise((_resolve, rej) => {
14610
+ reject = rej;
14611
+ });
14612
+ const stop = () => {
14613
+ if (timer !== void 0) {
14614
+ clearTimeout(timer);
14615
+ timer = void 0;
14616
+ }
14617
+ };
14618
+ const arm = () => {
14619
+ if (done) return;
14620
+ timer = setTimeout(() => {
14621
+ done = true;
14622
+ reject(makeError());
14623
+ }, idleMs);
14624
+ timer.unref?.();
14625
+ };
14626
+ arm();
14627
+ return {
14628
+ promise,
14629
+ bump: () => {
14630
+ if (done) return;
14631
+ stop();
14632
+ arm();
14633
+ },
14634
+ suspend: () => {
14635
+ stop();
14636
+ },
14637
+ clear: () => {
14638
+ done = true;
14639
+ stop();
14640
+ }
14641
+ };
14642
+ }
14643
+
14604
14644
  // src/agents/acp/client.ts
14605
14645
  var PROTOCOL_VERSION2 = 1;
14606
- var PROMPT_TIMEOUT_MS = 9e4;
14646
+ var PROMPT_IDLE_TIMEOUT_MS = 9e4;
14607
14647
  var CLIENT_CAPABILITIES = {
14608
14648
  fs: { readTextFile: true, writeTextFile: true },
14609
14649
  terminal: false
@@ -14617,6 +14657,11 @@ var AcpClient = class {
14617
14657
  connection = null;
14618
14658
  stopping = false;
14619
14659
  sessionId = null;
14660
+ /** Idle watchdog for the in-flight prompt. The `Client` handlers
14661
+ * (`sessionUpdate` / `requestPermission`) reach for this to keep
14662
+ * the turn alive while the adapter is demonstrably working. Null
14663
+ * between prompts. */
14664
+ promptIdle = null;
14620
14665
  /**
14621
14666
  * Spawn the adapter + perform the initial handshake (initialize
14622
14667
  * → newSession). Returns the ACP-assigned sessionId so the caller
@@ -14724,31 +14769,31 @@ var AcpClient = class {
14724
14769
  sessionId: this.sessionId,
14725
14770
  prompt: blocks
14726
14771
  });
14727
- let timeoutId;
14728
- const timeout = new Promise((_resolve, reject) => {
14729
- timeoutId = setTimeout(() => {
14730
- reject(
14731
- new Error(
14732
- `ACP prompt timed out after ${PROMPT_TIMEOUT_MS / 1e3}s \u2014 adapter never responded. Likely the underlying agent's auth or network is misconfigured; check the adapter stderr lines above (acpAdapter tag) for the actual error.`
14733
- )
14734
- );
14735
- }, PROMPT_TIMEOUT_MS);
14736
- });
14772
+ const idle = createIdleTimeout(
14773
+ PROMPT_IDLE_TIMEOUT_MS,
14774
+ () => new Error(
14775
+ `ACP prompt idle for ${PROMPT_IDLE_TIMEOUT_MS / 1e3}s \u2014 adapter sent no updates. Likely the underlying agent's auth or network is misconfigured; check the adapter stderr lines above (acpAdapter tag) for the actual error.`
14776
+ )
14777
+ );
14778
+ this.promptIdle = idle;
14737
14779
  try {
14738
- const result = await Promise.race([send, timeout]);
14780
+ const result = await Promise.race([send, idle.promise]);
14739
14781
  log.info(
14740
14782
  "acpClient",
14741
14783
  `prompt \u2190 ok stopReason=${result.stopReason ?? "?"} elapsedMs=${Date.now() - t0}`
14742
14784
  );
14743
14785
  return result;
14744
14786
  } catch (err) {
14787
+ void send.catch(() => {
14788
+ });
14745
14789
  log.warn(
14746
14790
  "acpClient",
14747
14791
  `prompt \u2190 failed elapsedMs=${Date.now() - t0} err=${err instanceof Error ? err.message : String(err)}`
14748
14792
  );
14749
14793
  throw err;
14750
14794
  } finally {
14751
- if (timeoutId !== void 0) clearTimeout(timeoutId);
14795
+ idle.clear();
14796
+ this.promptIdle = null;
14752
14797
  }
14753
14798
  }
14754
14799
  /**
@@ -14837,10 +14882,16 @@ var AcpClient = class {
14837
14882
  buildClient() {
14838
14883
  return {
14839
14884
  sessionUpdate: async (params) => {
14885
+ this.promptIdle?.bump();
14840
14886
  this.opts.onSessionUpdate(params);
14841
14887
  },
14842
- requestPermission: (params) => {
14843
- return this.opts.onRequestPermission(params);
14888
+ requestPermission: async (params) => {
14889
+ this.promptIdle?.suspend();
14890
+ try {
14891
+ return await this.opts.onRequestPermission(params);
14892
+ } finally {
14893
+ this.promptIdle?.bump();
14894
+ }
14844
14895
  },
14845
14896
  readTextFile: async (params) => {
14846
14897
  try {
@@ -15739,7 +15790,7 @@ function extractSelectPrompt(text) {
15739
15790
 
15740
15791
  // src/commands/start/handlers.ts
15741
15792
  var fs32 = __toESM(require("fs"));
15742
- var os25 = __toESM(require("os"));
15793
+ var os26 = __toESM(require("os"));
15743
15794
  var path39 = __toESM(require("path"));
15744
15795
  var import_crypto3 = require("crypto");
15745
15796
  var import_child_process17 = require("child_process");
@@ -17493,6 +17544,7 @@ function defaultBeadsHomeDir() {
17493
17544
  // src/beads/provisioner.ts
17494
17545
  var import_child_process15 = require("child_process");
17495
17546
  var fs30 = __toESM(require("fs"));
17547
+ var os25 = __toESM(require("os"));
17496
17548
  var path36 = __toESM(require("path"));
17497
17549
 
17498
17550
  // src/beads/install-bd.ts
@@ -17576,26 +17628,47 @@ var _provisionSeam = {
17576
17628
  };
17577
17629
  var _linkSeam = {
17578
17630
  platform: () => process.platform,
17631
+ homedir: () => os25.homedir(),
17632
+ isWritableDir: (dir) => {
17633
+ try {
17634
+ fs30.accessSync(dir, fs30.constants.W_OK);
17635
+ return true;
17636
+ } catch {
17637
+ return false;
17638
+ }
17639
+ },
17640
+ ensureDir: (dir) => {
17641
+ fs30.mkdirSync(dir, { recursive: true });
17642
+ },
17579
17643
  /**
17580
- * A directory ON PATH to symlink `bd` into, so the AGENT's shell + the
17581
- * SessionStart `bd prime` hook resolve `bd` by name.
17644
+ * A directory to symlink `bd` into so the AGENT's shell + Claude Code's
17645
+ * SessionStart `bd prime` hook resolve `bd` by name. The dir MUST be on the
17646
+ * persistent shell PATH those processes use AND be writable.
17582
17647
  *
17583
- * Earlier this used `dirname(realpath(process.argv[1]))` but for a GLOBAL
17584
- * npm install that resolves to the package's own `dist/` dir (e.g.
17585
- * `…/node_modules/codeam-cli/dist`), which is NOT on PATH. The symlink landed
17586
- * somewhere nothing sees, so `which bd` and the hook still failed (observed
17587
- * live in a codespace). `dirname(process.execPath)` is the dir the `node`
17588
- * binary lives in for a global install that's the npm prefix `bin/` where
17589
- * the `codeam` launcher also lives, and IS on PATH. We prefer the first
17590
- * candidate that actually appears in `$PATH`, falling back to node's bin dir.
17648
+ * History: v2.35.1 used `dirname(realpath(argv[1]))` (the package `dist/`,
17649
+ * not on PATH); v2.35.2 used `dirname(process.execPath)` correct for a
17650
+ * normal global npm install, but in a GitHub Codespace `node` lives in a
17651
+ * TRANSIENT bootstrap prefix (`/tmp/codeam-node20/bin`) that is NOT on the
17652
+ * persistent shell PATH, so the hook still failed (validated live). The
17653
+ * dir that IS on PATH and writable in a codespace is `~/.local/bin`.
17654
+ *
17655
+ * Strategy: among [node's bin, ~/.local/bin, /usr/local/bin, entry dir],
17656
+ * pick the first that is BOTH on `$PATH` AND writable. If none qualifies
17657
+ * (codespace: node prefix off-PATH, /usr/local/bin read-only), fall back to
17658
+ * `~/.local/bin` — the standard user bin, on PATH for login shells —
17659
+ * which `linkBdOntoPath` creates if missing.
17591
17660
  */
17592
17661
  cliBinDir: () => {
17593
17662
  const pathDirs = (process.env.PATH ?? "").split(path36.delimiter).filter(Boolean);
17663
+ const home = _linkSeam.homedir();
17664
+ const localBin = home ? path36.join(home, ".local", "bin") : null;
17594
17665
  const candidates = [];
17595
17666
  try {
17596
17667
  candidates.push(path36.dirname(process.execPath));
17597
17668
  } catch {
17598
17669
  }
17670
+ if (localBin) candidates.push(localBin);
17671
+ candidates.push("/usr/local/bin");
17599
17672
  const entry = process.argv[1];
17600
17673
  if (entry) {
17601
17674
  try {
@@ -17604,8 +17677,11 @@ var _linkSeam = {
17604
17677
  candidates.push(path36.dirname(entry));
17605
17678
  }
17606
17679
  }
17607
- if (candidates.length === 0) return null;
17608
- return candidates.find((d3) => pathDirs.includes(d3)) ?? candidates[0];
17680
+ const onPathWritable = candidates.find(
17681
+ (d3) => pathDirs.includes(d3) && _linkSeam.isWritableDir(d3)
17682
+ );
17683
+ if (onPathWritable) return onPathWritable;
17684
+ return localBin ?? candidates[0] ?? null;
17609
17685
  },
17610
17686
  /** Current symlink target at `linkPath`, or null when absent / not a link. */
17611
17687
  readlink: (linkPath) => {
@@ -17622,6 +17698,7 @@ function linkBdOntoPath(binaryPath) {
17622
17698
  if (_linkSeam.platform() === "win32") return;
17623
17699
  const binDir = _linkSeam.cliBinDir();
17624
17700
  if (!binDir) return;
17701
+ _linkSeam.ensureDir(binDir);
17625
17702
  const linkPath = path36.join(binDir, "bd");
17626
17703
  if (linkPath === binaryPath) return;
17627
17704
  const current = _linkSeam.readlink(linkPath);
@@ -18179,7 +18256,7 @@ function cleanupAttachmentTempFiles() {
18179
18256
  function saveFilesTemp(files) {
18180
18257
  return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
18181
18258
  const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
18182
- const tmpPath = path39.join(os25.tmpdir(), `codeam-${(0, import_crypto3.randomUUID)()}-${safeName}`);
18259
+ const tmpPath = path39.join(os26.tmpdir(), `codeam-${(0, import_crypto3.randomUUID)()}-${safeName}`);
18183
18260
  fs32.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
18184
18261
  pendingAttachmentFiles.add(tmpPath);
18185
18262
  return tmpPath;
@@ -19215,7 +19292,7 @@ async function dispatchCommand(ctx, cmd) {
19215
19292
  // src/services/file-watcher.service.ts
19216
19293
  var import_child_process18 = require("child_process");
19217
19294
  var fs33 = __toESM(require("fs"));
19218
- var os26 = __toESM(require("os"));
19295
+ var os27 = __toESM(require("os"));
19219
19296
  var path40 = __toESM(require("path"));
19220
19297
  var import_ignore = __toESM(require("ignore"));
19221
19298
 
@@ -19326,10 +19403,10 @@ var WINDOWS_LEGACY_JUNCTIONS = [
19326
19403
  /[\\/]Start Menu([\\/]|$)/i,
19327
19404
  /[\\/]Templates([\\/]|$)/i
19328
19405
  ];
19329
- function isUnsafeWindowsWatchRoot(dir, homedir22) {
19406
+ function isUnsafeWindowsWatchRoot(dir, homedir23) {
19330
19407
  const norm = (p2) => p2.replace(/\//g, "\\").replace(/\\+$/, "").toLowerCase();
19331
19408
  const cwd = norm(dir);
19332
- const home = norm(homedir22);
19409
+ const home = norm(homedir23);
19333
19410
  if (cwd === home) return true;
19334
19411
  if (/^[a-z]:$/.test(cwd)) return true;
19335
19412
  const sysRoots = [
@@ -19428,7 +19505,7 @@ var FileWatcherService = class {
19428
19505
  throw new Error("FileWatcherService has already been stopped \u2014 re-instantiate to restart.");
19429
19506
  }
19430
19507
  const isWin = process.platform === "win32";
19431
- if (isWin && isUnsafeWindowsWatchRoot(this.opts.workingDir, os26.homedir())) {
19508
+ if (isWin && isUnsafeWindowsWatchRoot(this.opts.workingDir, os27.homedir())) {
19432
19509
  log.warn(
19433
19510
  "fileWatcher",
19434
19511
  `refusing to watch ${this.opts.workingDir} \u2014 looks like a Windows user-profile or system path. Run codeam from your project folder to enable file change emission.`
@@ -22074,7 +22151,7 @@ var OutputService = class _OutputService {
22074
22151
  // src/services/history.service.ts
22075
22152
  var fs35 = __toESM(require("fs"));
22076
22153
  var path43 = __toESM(require("path"));
22077
- var os27 = __toESM(require("os"));
22154
+ var os28 = __toESM(require("os"));
22078
22155
  var https7 = __toESM(require("https"));
22079
22156
  var http7 = __toESM(require("http"));
22080
22157
  var import_zod2 = require("zod");
@@ -22236,7 +22313,7 @@ var HistoryService = class _HistoryService {
22236
22313
  return this._quotaPercent === null || Date.now() - this._quotaFetchedAt > ttlMs;
22237
22314
  }
22238
22315
  get projectDir() {
22239
- return this.runtime.resolveHistoryDir(this.cwd) ?? path43.join(os27.homedir(), ".claude", "projects", encodeCwd(this.cwd));
22316
+ return this.runtime.resolveHistoryDir(this.cwd) ?? path43.join(os28.homedir(), ".claude", "projects", encodeCwd(this.cwd));
22240
22317
  }
22241
22318
  /** Set the current Claude conversation ID (extracted from /cost command or session start) */
22242
22319
  setCurrentConversationId(id) {
@@ -23442,7 +23519,7 @@ async function autoLinkAfterPair(opts) {
23442
23519
 
23443
23520
  // src/commands/pair-auto.ts
23444
23521
  var fs36 = __toESM(require("fs"));
23445
- var os28 = __toESM(require("os"));
23522
+ var os29 = __toESM(require("os"));
23446
23523
  var import_crypto7 = require("crypto");
23447
23524
 
23448
23525
  // src/commands/start-infra-only.ts
@@ -23668,7 +23745,7 @@ async function claimOnce(token, pluginId) {
23668
23745
  pluginId,
23669
23746
  ideName: "codeam-cli (codespace)",
23670
23747
  ideVersion: process.env.npm_package_version ?? "unknown",
23671
- hostname: os28.hostname(),
23748
+ hostname: os29.hostname(),
23672
23749
  codespaceName: process.env.CODESPACE_NAME ?? "",
23673
23750
  // Current git branch of the codespace's working directory, so the
23674
23751
  // backend can populate `PairedSession.branch` for the codespace pair.
@@ -25953,9 +26030,9 @@ function checkSessions() {
25953
26030
  }
25954
26031
  }
25955
26032
  function checkAgentBinaries() {
25956
- const os30 = createOsStrategy();
26033
+ const os31 = createOsStrategy();
25957
26034
  return getEnabledAgents().map((meta) => {
25958
- const found = os30.findInPath(meta.binaryName);
26035
+ const found = os31.findInPath(meta.binaryName);
25959
26036
  return {
25960
26037
  id: `agent-${meta.id}`,
25961
26038
  label: `Agent binary: ${meta.displayName} (${meta.binaryName})`,
@@ -26019,7 +26096,7 @@ function checkChokidar() {
26019
26096
  }
26020
26097
  async function doctor(args2 = []) {
26021
26098
  const json = args2.includes("--json");
26022
- const cliVersion = true ? "2.35.2" : "0.0.0-dev";
26099
+ const cliVersion = true ? "2.35.4" : "0.0.0-dev";
26023
26100
  const apiBase = resolveApiBaseUrl();
26024
26101
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
26025
26102
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -26218,7 +26295,7 @@ async function completion(args2) {
26218
26295
  // src/commands/version.ts
26219
26296
  var import_picocolors13 = __toESM(require("picocolors"));
26220
26297
  function version2() {
26221
- const v = true ? "2.35.2" : "unknown";
26298
+ const v = true ? "2.35.4" : "unknown";
26222
26299
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
26223
26300
  }
26224
26301
 
@@ -26347,7 +26424,7 @@ var _subcommandHelpKeys = Object.keys(HELPS);
26347
26424
 
26348
26425
  // src/lib/updateNotifier.ts
26349
26426
  var fs38 = __toESM(require("fs"));
26350
- var os29 = __toESM(require("os"));
26427
+ var os30 = __toESM(require("os"));
26351
26428
  var path49 = __toESM(require("path"));
26352
26429
  var https8 = __toESM(require("https"));
26353
26430
  var import_node_child_process12 = require("child_process");
@@ -26357,7 +26434,7 @@ var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
26357
26434
  var TTL_MS = 24 * 60 * 60 * 1e3;
26358
26435
  var REQUEST_TIMEOUT_MS = 1500;
26359
26436
  function cachePath() {
26360
- const dir = path49.join(os29.homedir(), ".codeam");
26437
+ const dir = path49.join(os30.homedir(), ".codeam");
26361
26438
  return path49.join(dir, "update-check.json");
26362
26439
  }
26363
26440
  function readCache() {
@@ -26504,7 +26581,7 @@ function checkForUpdates() {
26504
26581
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
26505
26582
  if (process.env.CI) return;
26506
26583
  if (!process.stdout.isTTY) return;
26507
- const current = true ? "2.35.2" : null;
26584
+ const current = true ? "2.35.4" : null;
26508
26585
  if (!current) return;
26509
26586
  const cache = readCache();
26510
26587
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.35.2",
3
+ "version": "2.35.4",
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",