codeam-cli 2.39.50 → 2.39.52

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.39.51] — 2026-06-20
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Reuse running preview on re-open instead of re-spawn (EADDRINUSE)
12
+
13
+ ## [2.39.50] — 2026-06-19
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** Parse real headroom /stats shape so savings reach the dashboard
18
+
7
19
  ## [2.39.49] — 2026-06-19
8
20
 
9
21
  ### Fixed
package/dist/index.js CHANGED
@@ -5388,7 +5388,7 @@ function readAnonId() {
5388
5388
  }
5389
5389
  function superProperties() {
5390
5390
  return {
5391
- cliVersion: true ? "2.39.50" : "0.0.0-dev",
5391
+ cliVersion: true ? "2.39.52" : "0.0.0-dev",
5392
5392
  nodeVersion: process.version,
5393
5393
  platform: process.platform,
5394
5394
  arch: process.arch,
@@ -5547,7 +5547,7 @@ var os4 = __toESM(require("os"));
5547
5547
  // package.json
5548
5548
  var package_default = {
5549
5549
  name: "codeam-cli",
5550
- version: "2.39.50",
5550
+ version: "2.39.52",
5551
5551
  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.",
5552
5552
  type: "commonjs",
5553
5553
  main: "dist/index.js",
@@ -16073,6 +16073,22 @@ var previewStartH = (ctx, _cmd, parsed) => {
16073
16073
  });
16074
16074
  };
16075
16075
  void (async () => {
16076
+ const existing = activePreviews.get(ctx.sessionId);
16077
+ if (existing && existing.devServer.exitCode === null) {
16078
+ log.info(
16079
+ "preview",
16080
+ `reusing running preview for session=${ctx.sessionId} url=${existing.url}`
16081
+ );
16082
+ emitProgress("READY_DETECTED", "reusing running preview");
16083
+ void postPreviewEvent({
16084
+ sessionId: ctx.sessionId,
16085
+ pluginId: ctx.pluginId,
16086
+ pluginAuthToken,
16087
+ type: "preview_ready",
16088
+ payload: { url: existing.url, framework: existing.framework, port: detection.port }
16089
+ });
16090
+ return;
16091
+ }
16076
16092
  void postPreviewEvent({
16077
16093
  sessionId: ctx.sessionId,
16078
16094
  pluginId: ctx.pluginId,
@@ -16193,6 +16209,22 @@ var previewStartH = (ctx, _cmd, parsed) => {
16193
16209
  }
16194
16210
  }
16195
16211
  if (await isPortListening(detection.port)) {
16212
+ const raceExisting = activePreviews.get(ctx.sessionId);
16213
+ if (raceExisting && raceExisting.devServer.exitCode === null) {
16214
+ log.info(
16215
+ "preview",
16216
+ `port race: reusing running preview for session=${ctx.sessionId} url=${raceExisting.url}`
16217
+ );
16218
+ emitProgress("READY_DETECTED", "reusing running preview");
16219
+ void postPreviewEvent({
16220
+ sessionId: ctx.sessionId,
16221
+ pluginId: ctx.pluginId,
16222
+ pluginAuthToken,
16223
+ type: "preview_ready",
16224
+ payload: { url: raceExisting.url, framework: raceExisting.framework, port: detection.port }
16225
+ });
16226
+ return;
16227
+ }
16196
16228
  void postPreviewEvent({
16197
16229
  sessionId: ctx.sessionId,
16198
16230
  pluginId: ctx.pluginId,
@@ -17213,6 +17245,15 @@ function isDeployPayload(p2) {
17213
17245
  if (p2.cloneToken !== void 0 && typeof p2.cloneToken !== "string") {
17214
17246
  return false;
17215
17247
  }
17248
+ if (p2.headroomEnabled !== void 0 && typeof p2.headroomEnabled !== "boolean") {
17249
+ return false;
17250
+ }
17251
+ if (p2.headroomAgent !== void 0 && typeof p2.headroomAgent !== "string") {
17252
+ return false;
17253
+ }
17254
+ if (p2.headroomSavingsIngestUrl !== void 0 && typeof p2.headroomSavingsIngestUrl !== "string") {
17255
+ return false;
17256
+ }
17216
17257
  const hasHouse = isHouseProxy(p2.houseProxy);
17217
17258
  const hasSealed = typeof p2.sealedAgentAuth === "string";
17218
17259
  return hasHouse || hasSealed;
@@ -17228,6 +17269,95 @@ var CONTROL_AGENT_META = {
17228
17269
  supportedAuthKinds: ["oauth_token"],
17229
17270
  preferredAuthKind: "oauth_token"
17230
17271
  };
17272
+ async function setupHeadroomForSelfHosted(agent) {
17273
+ const INSTALL_TIMEOUT_MS2 = 12e4;
17274
+ const PIP_PACKAGES = [
17275
+ "headroom-ai",
17276
+ "fastapi",
17277
+ "uvicorn",
17278
+ "httpx[http2]",
17279
+ "websockets",
17280
+ "zstandard"
17281
+ ];
17282
+ const installOk = await new Promise((resolve7) => {
17283
+ const tryPip = (cmd) => new Promise((res) => {
17284
+ const child = (0, import_node_child_process12.spawn)(cmd, ["install", "--quiet", ...PIP_PACKAGES], {
17285
+ stdio: ["ignore", "pipe", "pipe"]
17286
+ });
17287
+ let settled = false;
17288
+ const done = (ok) => {
17289
+ if (settled) return;
17290
+ settled = true;
17291
+ res(ok);
17292
+ };
17293
+ const onData = (b) => {
17294
+ const line = b.toString().replace(/\n+$/g, "");
17295
+ if (line) log.info("host-agent", `headroom-install: ${line}`);
17296
+ };
17297
+ child.stdout?.on("data", onData);
17298
+ child.stderr?.on("data", onData);
17299
+ const timer = setTimeout(() => {
17300
+ log.warn("host-agent", `headroom pip install timed out (${INSTALL_TIMEOUT_MS2 / 1e3}s) \u2014 skipping Headroom`);
17301
+ try {
17302
+ child.kill("SIGTERM");
17303
+ } catch {
17304
+ }
17305
+ done(false);
17306
+ }, INSTALL_TIMEOUT_MS2);
17307
+ child.once("exit", (code) => {
17308
+ clearTimeout(timer);
17309
+ if (code === 0) {
17310
+ log.info("host-agent", "headroom pip install succeeded");
17311
+ } else {
17312
+ log.warn("host-agent", `headroom pip install exited code=${String(code)} \u2014 skipping Headroom`);
17313
+ }
17314
+ done(code === 0);
17315
+ });
17316
+ child.once("error", (e) => {
17317
+ clearTimeout(timer);
17318
+ done(false);
17319
+ log.trace("host-agent", `headroom pip spawn error (${cmd}): ${e.message}`);
17320
+ });
17321
+ });
17322
+ tryPip("pip").then((ok) => ok ? resolve7(true) : tryPip("pip3").then(resolve7)).catch(() => resolve7(false));
17323
+ });
17324
+ if (!installOk) {
17325
+ return false;
17326
+ }
17327
+ const initOk = await new Promise((resolve7) => {
17328
+ (0, import_node_child_process12.execFile)("which", ["headroom"], (whichErr) => {
17329
+ if (whichErr) {
17330
+ log.warn("host-agent", "headroom not found on PATH after install \u2014 skipping init");
17331
+ resolve7(false);
17332
+ return;
17333
+ }
17334
+ (0, import_node_child_process12.execFile)("headroom", ["init", "--global", agent], (initErr, stdout, stderr) => {
17335
+ if (initErr) {
17336
+ const detail = (stderr || initErr.message).replace(/\n+$/g, "");
17337
+ log.warn("host-agent", `headroom init failed (best-effort): ${detail}`);
17338
+ resolve7(false);
17339
+ } else {
17340
+ if (stdout.trim()) log.info("host-agent", `headroom init: ${stdout.trim()}`);
17341
+ log.info("host-agent", "headroom init --global succeeded");
17342
+ resolve7(true);
17343
+ }
17344
+ });
17345
+ });
17346
+ });
17347
+ if (!initOk) {
17348
+ return false;
17349
+ }
17350
+ try {
17351
+ const proxy = (0, import_node_child_process12.spawn)("headroom", ["proxy", "--port", "8787"], {
17352
+ stdio: "ignore",
17353
+ detached: true
17354
+ });
17355
+ proxy.unref();
17356
+ } catch (e) {
17357
+ log.warn("host-agent", `headroom proxy warm-start failed (best-effort): ${e instanceof Error ? e.message : String(e)}`);
17358
+ }
17359
+ return true;
17360
+ }
17231
17361
  var defaultSpawner = (env, cwd, args2 = []) => (0, import_node_child_process12.spawn)(process.execPath, [process.argv[1], "pair-auto", ...args2], {
17232
17362
  cwd,
17233
17363
  env: { ...process.env, ...env },
@@ -17251,6 +17381,7 @@ var HostAgentSupervisor = class {
17251
17381
  this.deps = deps;
17252
17382
  this.spawnChild = deps.spawnChild ?? defaultSpawner;
17253
17383
  this.resolveAgentAuth = deps.resolveAgentAuth ?? unsealAgentAuth;
17384
+ this.setupHeadroom = deps.setupHeadroom ?? setupHeadroomForSelfHosted;
17254
17385
  this.metrics = deps.metricsCollector ?? new MetricsCollector();
17255
17386
  this.onIdentityRejected = deps.onIdentityRejected ?? defaultOnIdentityRejected;
17256
17387
  this.disableService = deps.disableService ?? defaultDisableService;
@@ -17260,6 +17391,7 @@ var HostAgentSupervisor = class {
17260
17391
  children = /* @__PURE__ */ new Map();
17261
17392
  spawnChild;
17262
17393
  resolveAgentAuth;
17394
+ setupHeadroom;
17263
17395
  relay = null;
17264
17396
  heartbeatTimer = null;
17265
17397
  /** Guards the one-shot 'connected' telemetry on the first heartbeat. */
@@ -17432,6 +17564,18 @@ var HostAgentSupervisor = class {
17432
17564
  childEnv.PREVIEW_TUNNEL_TOKEN = payload.previewTunnelToken;
17433
17565
  childEnv.PREVIEW_TUNNEL_HOSTNAME = payload.previewHostname;
17434
17566
  }
17567
+ if (payload.headroomEnabled && payload.headroomAgent && payload.headroomSavingsIngestUrl) {
17568
+ report("headroom", "setting up Headroom proxy");
17569
+ const headroomOk = await this.setupHeadroom(payload.headroomAgent);
17570
+ if (headroomOk) {
17571
+ childEnv.HEADROOM_ENABLED = "1";
17572
+ childEnv.HEADROOM_AGENT = payload.headroomAgent;
17573
+ childEnv.HEADROOM_SAVINGS_INGEST_URL = payload.headroomSavingsIngestUrl;
17574
+ log.info("host-agent", "Headroom proxy ready; HEADROOM_* env injected into child");
17575
+ } else {
17576
+ log.warn("host-agent", "Headroom setup failed (best-effort) \u2014 child will run without Headroom");
17577
+ }
17578
+ }
17435
17579
  report("spawning", "starting agent");
17436
17580
  const proc = this.spawnChild(childEnv, cwd, extraArgs);
17437
17581
  const child = { deployId: payload.deployId, proc };
@@ -27341,9 +27485,9 @@ async function probeCodeamPair(provider, workspace) {
27341
27485
  }
27342
27486
  async function stopWorkspaceFromLocal(target) {
27343
27487
  if (target.provider.id === "github-codespaces") {
27344
- const { execFile: execFile11 } = await import("child_process");
27488
+ const { execFile: execFile12 } = await import("child_process");
27345
27489
  const { promisify: promisify11 } = await import("util");
27346
- const execFileP10 = promisify11(execFile11);
27490
+ const execFileP10 = promisify11(execFile12);
27347
27491
  await execFileP10("gh", ["codespace", "stop", "-c", target.id], { maxBuffer: 8 * 1024 * 1024 });
27348
27492
  return;
27349
27493
  }
@@ -27567,7 +27711,7 @@ function checkChokidar() {
27567
27711
  }
27568
27712
  async function doctor(args2 = []) {
27569
27713
  const json = args2.includes("--json");
27570
- const cliVersion = true ? "2.39.50" : "0.0.0-dev";
27714
+ const cliVersion = true ? "2.39.52" : "0.0.0-dev";
27571
27715
  const apiBase2 = resolveApiBaseUrl();
27572
27716
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
27573
27717
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -27766,7 +27910,7 @@ async function completion(args2) {
27766
27910
  // src/commands/version.ts
27767
27911
  var import_picocolors13 = __toESM(require("picocolors"));
27768
27912
  function version2() {
27769
- const v = true ? "2.39.50" : "unknown";
27913
+ const v = true ? "2.39.52" : "unknown";
27770
27914
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
27771
27915
  }
27772
27916
 
@@ -28052,7 +28196,7 @@ function checkForUpdates() {
28052
28196
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
28053
28197
  if (process.env.CI) return;
28054
28198
  if (!process.stdout.isTTY) return;
28055
- const current = true ? "2.39.50" : null;
28199
+ const current = true ? "2.39.52" : null;
28056
28200
  if (!current) return;
28057
28201
  const cache = readCache();
28058
28202
  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.39.50",
3
+ "version": "2.39.52",
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",