codeam-cli 2.74.1 → 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,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.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
+
13
+ ## [2.74.1] — 2026-09-04
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** Npx integration servers start with npm audit off; bound mcp-warm; 25-min fleet-box step (#701)
18
+
7
19
  ## [2.74.0] — 2026-09-03
8
20
 
9
21
  ### Added
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.1" : "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.1",
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.1" ? { ideVersion: "2.74.1" } : {}
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.1" : 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.1" : 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.1" : null;
22315
+ return true ? "2.74.3" : null;
22316
22316
  }
22317
22317
  async function installedVersion(deps) {
22318
22318
  const ls = await deps.run(
@@ -24956,11 +24956,23 @@ function safeParseDetection(raw) {
24956
24956
  if (!raw) return null;
24957
24957
  const parsed = parseDetectionObject(raw);
24958
24958
  if (!parsed) return null;
24959
+ if (isUnsupportedAnswer(parsed)) {
24960
+ return {
24961
+ framework: "unsupported",
24962
+ notes: typeof parsed.notes === "string" ? parsed.notes : void 0
24963
+ };
24964
+ }
24959
24965
  for (const field of REQUIRED_FIELDS2) {
24960
24966
  if (!(field in parsed)) return null;
24961
24967
  }
24962
24968
  return parsed;
24963
24969
  }
24970
+ function isUnsupportedAnswer(parsed) {
24971
+ return parsed.framework === "unsupported";
24972
+ }
24973
+ function isUnsupportedDetection(d3) {
24974
+ return d3.framework === "unsupported";
24975
+ }
24964
24976
  function parseDetectionObject(raw) {
24965
24977
  let parsed = tryParseObject(raw.trim());
24966
24978
  if (!parsed) {
@@ -25044,6 +25056,7 @@ function describeDetectionFailure(raw) {
25044
25056
  rawExcerpt
25045
25057
  };
25046
25058
  }
25059
+ if (isUnsupportedAnswer(parsed)) return null;
25047
25060
  const missing = REQUIRED_FIELDS2.filter((f) => !(f in parsed)).map(String);
25048
25061
  if (missing.length > 0) {
25049
25062
  return {
@@ -25507,6 +25520,25 @@ async function ensureYarnInstalled(deps) {
25507
25520
  if (!res.ok) return res;
25508
25521
  return await deps.hasYarn() ? { ok: true, code: 0 } : { ok: false, code: res.code };
25509
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
+ }
25510
25542
  function isJsInstallCommand(cmd, args2) {
25511
25543
  const known = ["npm", "pnpm", "yarn", "bun"];
25512
25544
  if (!known.includes(cmd)) return false;
@@ -26383,10 +26415,7 @@ async function runPreviewStart(args2) {
26383
26415
  };
26384
26416
  const existing = activePreviews.get(sessionId);
26385
26417
  if (existing && existing.devServer?.exitCode !== 0 && existing.devServer?.exitCode == null) {
26386
- log.info(
26387
- "preview",
26388
- `reusing running preview for session=${sessionId} url=${existing.url}`
26389
- );
26418
+ log.info("preview", `reusing running preview for session=${sessionId} url=${existing.url}`);
26390
26419
  ctx.emitProgress("READY_DETECTED", "reusing running preview");
26391
26420
  emit2(USER_EVENTS.PREVIEW_READY, {
26392
26421
  url: existing.url,
@@ -26435,6 +26464,7 @@ async function provisionDeps(ctx) {
26435
26464
  pluginAuthToken: ctx.projectEnvAuth.pluginAuthToken
26436
26465
  });
26437
26466
  }
26467
+ const needsExpoNgrok = isExpoTunnelCommand(detection.command, detection.args ?? []);
26438
26468
  const missingDeps = detectMissingNodeDeps(cwd);
26439
26469
  let preflightRan = false;
26440
26470
  if (missingDeps) {
@@ -26488,6 +26518,32 @@ async function provisionDeps(ctx) {
26488
26518
  }
26489
26519
  preflightRan = true;
26490
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
+ }
26491
26547
  const normalizedSetup = (detection.setup_commands ?? []).map((entry) => {
26492
26548
  if (typeof entry === "string") {
26493
26549
  const parts = entry.trim().split(/\s+/).filter(Boolean);
@@ -26503,13 +26559,9 @@ async function provisionDeps(ctx) {
26503
26559
  for (const setup of agentSetupCommands) {
26504
26560
  emitProgress("SETUP_RUN", `${setup.cmd} ${setup.args.join(" ")}`);
26505
26561
  const timeoutMs = isJsInstallCommand(setup.cmd, setup.args) ? INSTALL_TIMEOUT_MS2 : SETUP_TIMEOUT_MS;
26506
- const result = await runSetupCommand(
26507
- setup.cmd,
26508
- setup.args,
26509
- cwd,
26510
- detection.env,
26511
- { timeoutMs }
26512
- );
26562
+ const result = await runSetupCommand(setup.cmd, setup.args, cwd, detection.env, {
26563
+ timeoutMs
26564
+ });
26513
26565
  if (result.status === "timeout") {
26514
26566
  emit2(USER_EVENTS.PREVIEW_ERROR, {
26515
26567
  stage: "ready_timeout",
@@ -26577,10 +26629,7 @@ async function startDevServer(ctx) {
26577
26629
  } else {
26578
26630
  const verdict = await canAdoptPort(detection.port);
26579
26631
  if (verdict.adopt) {
26580
- log.info(
26581
- "preview",
26582
- `adopting the dev server already serving on port ${detection.port}`
26583
- );
26632
+ log.info("preview", `adopting the dev server already serving on port ${detection.port}`);
26584
26633
  emitProgress("READY_DETECTED", `adopted the server already on port ${detection.port}`);
26585
26634
  return { devServer: null, expoUrlRef: { current: null } };
26586
26635
  }
@@ -26731,11 +26780,9 @@ async function establishTunnel(ctx, dev) {
26731
26780
  `cloudflared quick tunnel (retry ${attempt}/${MAX_TUNNEL_ATTEMPTS})`
26732
26781
  );
26733
26782
  }
26734
- const candidate = (0, import_child_process23.spawn)(
26735
- bin,
26736
- ["tunnel", "--url", `http://localhost:${tunnelPort}`],
26737
- { stdio: ["ignore", "pipe", "pipe"] }
26738
- );
26783
+ const candidate = (0, import_child_process23.spawn)(bin, ["tunnel", "--url", `http://localhost:${tunnelPort}`], {
26784
+ stdio: ["ignore", "pipe", "pipe"]
26785
+ });
26739
26786
  const outcome = await awaitTunnelRegistered(
26740
26787
  candidate,
26741
26788
  null,
@@ -29462,7 +29509,7 @@ ${failure?.rawExcerpt ?? ""}`
29462
29509
  });
29463
29510
  return;
29464
29511
  }
29465
- if (detection.framework === "unsupported") {
29512
+ if (isUnsupportedDetection(detection)) {
29466
29513
  log.info("preview", "detect: framework=unsupported");
29467
29514
  emitPreviewEvent({
29468
29515
  sessionId: ctx.sessionId,
@@ -29530,7 +29577,7 @@ function prewarmPreviewDetection(runtime) {
29530
29577
  log.info("preview", `prewarm: detection failed reason=${failure?.reason ?? "unknown"}`);
29531
29578
  return;
29532
29579
  }
29533
- if (detection.framework === "unsupported") return;
29580
+ if (isUnsupportedDetection(detection)) return;
29534
29581
  await writePreviewConfig(cwd, detection);
29535
29582
  log.info(
29536
29583
  "preview",
@@ -47437,7 +47484,7 @@ function checkChokidar() {
47437
47484
  }
47438
47485
  async function doctor(args2 = []) {
47439
47486
  const json = args2.includes("--json");
47440
- const cliVersion = true ? "2.74.1" : "0.0.0-dev";
47487
+ const cliVersion = true ? "2.74.3" : "0.0.0-dev";
47441
47488
  const apiBase2 = resolveApiBaseUrl();
47442
47489
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
47443
47490
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -47884,7 +47931,12 @@ function runCapturing(command2, args2, timeoutMs) {
47884
47931
  return new Promise((resolve11) => {
47885
47932
  const child = (0, import_node_child_process35.spawn)(command2, args2, {
47886
47933
  env: { ...process.env, ...LAUNCHER_ENV },
47887
- 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"
47888
47940
  });
47889
47941
  let stderr = "";
47890
47942
  child.stderr?.setEncoding("utf8");
@@ -47956,7 +48008,7 @@ async function mcpWarm(argv = [], opts = {}) {
47956
48008
  // src/commands/version.ts
47957
48009
  var import_picocolors15 = __toESM(require("picocolors"));
47958
48010
  function version2() {
47959
- const v = true ? "2.74.1" : "unknown";
48011
+ const v = true ? "2.74.3" : "unknown";
47960
48012
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
47961
48013
  }
47962
48014
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.74.1",
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",