codeam-cli 2.74.2 → 2.74.3

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/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ 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.74.2] — 2026-09-04
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** A "no dev server" detection is a verdict, not a malformed answer (#704)
12
+
7
13
  ## [2.74.1] — 2026-09-04
8
14
 
9
15
  ### Fixed
package/dist/index.js CHANGED
@@ -8136,7 +8136,7 @@ function readAnonId() {
8136
8136
  }
8137
8137
  function superProperties() {
8138
8138
  return {
8139
- cliVersion: true ? "2.74.2" : "0.0.0-dev",
8139
+ cliVersion: true ? "2.74.3" : "0.0.0-dev",
8140
8140
  nodeVersion: process.version,
8141
8141
  platform: process.platform,
8142
8142
  arch: process.arch,
@@ -8378,7 +8378,7 @@ var http = __toESM(require("http"));
8378
8378
  // package.json
8379
8379
  var package_default = {
8380
8380
  name: "codeam-cli",
8381
- version: "2.74.2",
8381
+ version: "2.74.3",
8382
8382
  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.",
8383
8383
  type: "commonjs",
8384
8384
  main: "dist/index.js",
@@ -9971,7 +9971,7 @@ var CommandRelayService = class _CommandRelayService {
9971
9971
  // fresh + clear the "CLI update available" banner after a self-update
9972
9972
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
9973
9973
  // pair/reconnect). Older backends ignore the extra field.
9974
- ..."2.74.2" ? { ideVersion: "2.74.2" } : {}
9974
+ ..."2.74.3" ? { ideVersion: "2.74.3" } : {}
9975
9975
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
9976
9976
  }
9977
9977
  /**
@@ -22272,7 +22272,7 @@ async function autoUpgradeBeforeCriticalCommand() {
22272
22272
  if (process.env.NODE_ENV === "test") return;
22273
22273
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
22274
22274
  if (process.env.CI) return;
22275
- const current2 = true ? "2.74.2" : null;
22275
+ const current2 = true ? "2.74.3" : null;
22276
22276
  if (!current2) return;
22277
22277
  const cache = readCache();
22278
22278
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -22289,7 +22289,7 @@ function checkForUpdates() {
22289
22289
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
22290
22290
  if (process.env.CI) return;
22291
22291
  if (!process.stdout.isTTY) return;
22292
- const current2 = true ? "2.74.2" : null;
22292
+ const current2 = true ? "2.74.3" : null;
22293
22293
  if (!current2) return;
22294
22294
  const cache = readCache();
22295
22295
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -22312,7 +22312,7 @@ var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
22312
22312
  var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
22313
22313
  var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
22314
22314
  function currentCliVersion() {
22315
- return true ? "2.74.2" : null;
22315
+ return true ? "2.74.3" : null;
22316
22316
  }
22317
22317
  async function installedVersion(deps) {
22318
22318
  const ls = await deps.run(
@@ -25520,6 +25520,25 @@ async function ensureYarnInstalled(deps) {
25520
25520
  if (!res.ok) return res;
25521
25521
  return await deps.hasYarn() ? { ok: true, code: 0 } : { ok: false, code: res.code };
25522
25522
  }
25523
+ function isExpoTunnelCommand(command2, args2) {
25524
+ const flat = [import_path8.default.basename(command2), ...args2];
25525
+ return flat.includes("expo") && flat.includes("--tunnel");
25526
+ }
25527
+ var EXPO_NGROK_SPEC = "@expo/ngrok@^4.1.0";
25528
+ async function ensureExpoTunnelDeps(deps) {
25529
+ if (await deps.hasNgrok()) return { ok: true, code: 0, installed: false };
25530
+ const res = await deps.installNgrok();
25531
+ if (!res.ok) return { ...res, installed: false };
25532
+ return await deps.hasNgrok() ? { ok: true, code: 0, installed: true } : { ok: false, code: res.code, installed: false };
25533
+ }
25534
+ function ngrokResolvesFrom(cwd) {
25535
+ try {
25536
+ require.resolve("@expo/ngrok/package.json", { paths: [cwd] });
25537
+ return true;
25538
+ } catch {
25539
+ return false;
25540
+ }
25541
+ }
25523
25542
  function isJsInstallCommand(cmd, args2) {
25524
25543
  const known = ["npm", "pnpm", "yarn", "bun"];
25525
25544
  if (!known.includes(cmd)) return false;
@@ -26396,10 +26415,7 @@ async function runPreviewStart(args2) {
26396
26415
  };
26397
26416
  const existing = activePreviews.get(sessionId);
26398
26417
  if (existing && existing.devServer?.exitCode !== 0 && existing.devServer?.exitCode == null) {
26399
- log.info(
26400
- "preview",
26401
- `reusing running preview for session=${sessionId} url=${existing.url}`
26402
- );
26418
+ log.info("preview", `reusing running preview for session=${sessionId} url=${existing.url}`);
26403
26419
  ctx.emitProgress("READY_DETECTED", "reusing running preview");
26404
26420
  emit2(USER_EVENTS.PREVIEW_READY, {
26405
26421
  url: existing.url,
@@ -26448,6 +26464,7 @@ async function provisionDeps(ctx) {
26448
26464
  pluginAuthToken: ctx.projectEnvAuth.pluginAuthToken
26449
26465
  });
26450
26466
  }
26467
+ const needsExpoNgrok = isExpoTunnelCommand(detection.command, detection.args ?? []);
26451
26468
  const missingDeps = detectMissingNodeDeps(cwd);
26452
26469
  let preflightRan = false;
26453
26470
  if (missingDeps) {
@@ -26501,6 +26518,32 @@ async function provisionDeps(ctx) {
26501
26518
  }
26502
26519
  preflightRan = true;
26503
26520
  }
26521
+ if (needsExpoNgrok) {
26522
+ const ensured = await ensureExpoTunnelDeps({
26523
+ hasNgrok: async () => ngrokResolvesFrom(cwd),
26524
+ installNgrok: async () => {
26525
+ emitProgress(
26526
+ "SETUP_RUN",
26527
+ `npm install --no-save ${EXPO_NGROK_SPEC} (Expo tunnel needs ngrok; not found)`
26528
+ );
26529
+ const r = await runSetupCommand(
26530
+ "npm",
26531
+ ["install", "--no-save", "--no-audit", "--no-fund", EXPO_NGROK_SPEC],
26532
+ cwd,
26533
+ detection.env,
26534
+ { timeoutMs: INSTALL_TIMEOUT_MS2 }
26535
+ );
26536
+ return { ok: r.status === "ok", code: r.code };
26537
+ }
26538
+ });
26539
+ if (!ensured.ok) {
26540
+ emit2(USER_EVENTS.PREVIEW_ERROR, {
26541
+ stage: "spawn",
26542
+ message: `Expo's tunnel needs @expo/ngrok and installing it automatically failed (npm install --no-save ${EXPO_NGROK_SPEC}, exit ${ensured.code}). Install it in the project and try the preview again.`
26543
+ });
26544
+ return false;
26545
+ }
26546
+ }
26504
26547
  const normalizedSetup = (detection.setup_commands ?? []).map((entry) => {
26505
26548
  if (typeof entry === "string") {
26506
26549
  const parts = entry.trim().split(/\s+/).filter(Boolean);
@@ -26516,13 +26559,9 @@ async function provisionDeps(ctx) {
26516
26559
  for (const setup of agentSetupCommands) {
26517
26560
  emitProgress("SETUP_RUN", `${setup.cmd} ${setup.args.join(" ")}`);
26518
26561
  const timeoutMs = isJsInstallCommand(setup.cmd, setup.args) ? INSTALL_TIMEOUT_MS2 : SETUP_TIMEOUT_MS;
26519
- const result = await runSetupCommand(
26520
- setup.cmd,
26521
- setup.args,
26522
- cwd,
26523
- detection.env,
26524
- { timeoutMs }
26525
- );
26562
+ const result = await runSetupCommand(setup.cmd, setup.args, cwd, detection.env, {
26563
+ timeoutMs
26564
+ });
26526
26565
  if (result.status === "timeout") {
26527
26566
  emit2(USER_EVENTS.PREVIEW_ERROR, {
26528
26567
  stage: "ready_timeout",
@@ -26590,10 +26629,7 @@ async function startDevServer(ctx) {
26590
26629
  } else {
26591
26630
  const verdict = await canAdoptPort(detection.port);
26592
26631
  if (verdict.adopt) {
26593
- log.info(
26594
- "preview",
26595
- `adopting the dev server already serving on port ${detection.port}`
26596
- );
26632
+ log.info("preview", `adopting the dev server already serving on port ${detection.port}`);
26597
26633
  emitProgress("READY_DETECTED", `adopted the server already on port ${detection.port}`);
26598
26634
  return { devServer: null, expoUrlRef: { current: null } };
26599
26635
  }
@@ -26744,11 +26780,9 @@ async function establishTunnel(ctx, dev) {
26744
26780
  `cloudflared quick tunnel (retry ${attempt}/${MAX_TUNNEL_ATTEMPTS})`
26745
26781
  );
26746
26782
  }
26747
- const candidate = (0, import_child_process23.spawn)(
26748
- bin,
26749
- ["tunnel", "--url", `http://localhost:${tunnelPort}`],
26750
- { stdio: ["ignore", "pipe", "pipe"] }
26751
- );
26783
+ const candidate = (0, import_child_process23.spawn)(bin, ["tunnel", "--url", `http://localhost:${tunnelPort}`], {
26784
+ stdio: ["ignore", "pipe", "pipe"]
26785
+ });
26752
26786
  const outcome = await awaitTunnelRegistered(
26753
26787
  candidate,
26754
26788
  null,
@@ -47450,7 +47484,7 @@ function checkChokidar() {
47450
47484
  }
47451
47485
  async function doctor(args2 = []) {
47452
47486
  const json = args2.includes("--json");
47453
- const cliVersion = true ? "2.74.2" : "0.0.0-dev";
47487
+ const cliVersion = true ? "2.74.3" : "0.0.0-dev";
47454
47488
  const apiBase2 = resolveApiBaseUrl();
47455
47489
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
47456
47490
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -47897,7 +47931,12 @@ function runCapturing(command2, args2, timeoutMs) {
47897
47931
  return new Promise((resolve11) => {
47898
47932
  const child = (0, import_node_child_process35.spawn)(command2, args2, {
47899
47933
  env: { ...process.env, ...LAUNCHER_ENV },
47900
- stdio: ["ignore", "ignore", "pipe"]
47934
+ stdio: ["ignore", "ignore", "pipe"],
47935
+ // `npx`/`uv` are `.cmd` shims on Windows; Node ≥ 20.12 refuses to spawn a
47936
+ // `.cmd` without a shell and a bare `npx` is ENOENT — the same reason the
47937
+ // licence audit test needed it. The launchers here are ours, never user
47938
+ // input, so the shell is safe.
47939
+ shell: process.platform === "win32"
47901
47940
  });
47902
47941
  let stderr = "";
47903
47942
  child.stderr?.setEncoding("utf8");
@@ -47969,7 +48008,7 @@ async function mcpWarm(argv = [], opts = {}) {
47969
48008
  // src/commands/version.ts
47970
48009
  var import_picocolors15 = __toESM(require("picocolors"));
47971
48010
  function version2() {
47972
- const v = true ? "2.74.2" : "unknown";
48011
+ const v = true ? "2.74.3" : "unknown";
47973
48012
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
47974
48013
  }
47975
48014
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.74.2",
3
+ "version": "2.74.3",
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",