codeam-cli 2.61.98 → 2.61.99

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 +10 -0
  2. package/dist/index.js +176 -33
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ 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.61.98] — 2026-08-12
8
+
9
+ ### Fixed
10
+
11
+ - **jetbrains-plugin:** Pairing poll flooded /api/pairing/status and ignored 429s
12
+
13
+ ### Tests
14
+
15
+ - **cli:** Integration test for the command-relay SSE primary channel (#608)
16
+
7
17
  ## [2.61.97] — 2026-08-10
8
18
 
9
19
  ### Added
package/dist/index.js CHANGED
@@ -2790,6 +2790,7 @@ function resolveApiBaseUrl() {
2790
2790
  }
2791
2791
 
2792
2792
  // ../../packages/shared/src/headroom/manifest.ts
2793
+ var HEADROOM_PROXY_PORT = 8787;
2793
2794
  var HEADROOM_PIP_COMPANIONS = [
2794
2795
  "fastapi",
2795
2796
  "uvicorn",
@@ -3635,31 +3636,31 @@ var BucketedRateLimiter = class {
3635
3636
  this._refillRate = clampToRange(options.refillRate, 0, this._bucketSize, options._logger);
3636
3637
  this._refillInterval = clampToRange(options.refillInterval, 0, ONE_DAY_IN_MS, options._logger);
3637
3638
  }
3638
- _applyRefill(bucket, now) {
3639
- const elapsedMs = now - bucket.lastAccess;
3639
+ _applyRefill(bucket2, now) {
3640
+ const elapsedMs = now - bucket2.lastAccess;
3640
3641
  const refillIntervals = Math.floor(elapsedMs / this._refillInterval);
3641
3642
  if (refillIntervals > 0) {
3642
3643
  const tokensToAdd = refillIntervals * this._refillRate;
3643
- bucket.tokens = Math.min(bucket.tokens + tokensToAdd, this._bucketSize);
3644
- bucket.lastAccess = bucket.lastAccess + refillIntervals * this._refillInterval;
3644
+ bucket2.tokens = Math.min(bucket2.tokens + tokensToAdd, this._bucketSize);
3645
+ bucket2.lastAccess = bucket2.lastAccess + refillIntervals * this._refillInterval;
3645
3646
  }
3646
3647
  }
3647
3648
  consumeRateLimit(key) {
3648
3649
  const now = Date.now();
3649
3650
  const keyStr = String(key);
3650
- let bucket = this._buckets[keyStr];
3651
- if (bucket) this._applyRefill(bucket, now);
3651
+ let bucket2 = this._buckets[keyStr];
3652
+ if (bucket2) this._applyRefill(bucket2, now);
3652
3653
  else {
3653
- bucket = {
3654
+ bucket2 = {
3654
3655
  tokens: this._bucketSize,
3655
3656
  lastAccess: now
3656
3657
  };
3657
- this._buckets[keyStr] = bucket;
3658
+ this._buckets[keyStr] = bucket2;
3658
3659
  }
3659
- if (0 === bucket.tokens) return true;
3660
- bucket.tokens--;
3661
- if (0 === bucket.tokens) this._onBucketRateLimited?.(key);
3662
- return 0 === bucket.tokens;
3660
+ if (0 === bucket2.tokens) return true;
3661
+ bucket2.tokens--;
3662
+ if (0 === bucket2.tokens) this._onBucketRateLimited?.(key);
3663
+ return 0 === bucket2.tokens;
3663
3664
  }
3664
3665
  stop() {
3665
3666
  this._buckets = {};
@@ -8021,7 +8022,7 @@ function readAnonId() {
8021
8022
  }
8022
8023
  function superProperties() {
8023
8024
  return {
8024
- cliVersion: true ? "2.61.98" : "0.0.0-dev",
8025
+ cliVersion: true ? "2.61.99" : "0.0.0-dev",
8025
8026
  nodeVersion: process.version,
8026
8027
  platform: process.platform,
8027
8028
  arch: process.arch,
@@ -8202,7 +8203,7 @@ var os4 = __toESM(require("os"));
8202
8203
  // package.json
8203
8204
  var package_default = {
8204
8205
  name: "codeam-cli",
8205
- version: "2.61.98",
8206
+ version: "2.61.99",
8206
8207
  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.",
8207
8208
  type: "commonjs",
8208
8209
  main: "dist/index.js",
@@ -9519,7 +9520,7 @@ var CommandRelayService = class _CommandRelayService {
9519
9520
  // fresh + clear the "CLI update available" banner after a self-update
9520
9521
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
9521
9522
  // pair/reconnect). Older backends ignore the extra field.
9522
- ..."2.61.98" ? { ideVersion: "2.61.98" } : {}
9523
+ ..."2.61.99" ? { ideVersion: "2.61.99" } : {}
9523
9524
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
9524
9525
  }
9525
9526
  /**
@@ -16195,9 +16196,9 @@ function findingsFrom(value) {
16195
16196
  }
16196
16197
  const grouped = flat && !Array.isArray(flat) ? flat : root;
16197
16198
  for (const sev of ["critical", "warning", "warn", "error", "info", "note"]) {
16198
- const bucket = grouped[sev];
16199
- if (Array.isArray(bucket)) {
16200
- for (const f of bucket) {
16199
+ const bucket2 = grouped[sev];
16200
+ if (Array.isArray(bucket2)) {
16201
+ for (const f of bucket2) {
16201
16202
  const h = toHunk(f, sev);
16202
16203
  if (h) out2.push(h);
16203
16204
  }
@@ -17427,8 +17428,8 @@ function resolveHistoryFile5(cwd, sessionId) {
17427
17428
  const p2 = path34.join(fromIndex, WIRE_REL);
17428
17429
  if (fs30.existsSync(p2)) return p2;
17429
17430
  }
17430
- const bucket = path34.join(kimiHome(), "sessions", workDirKey(cwd));
17431
- const computed = path34.join(bucket, sessionId, WIRE_REL);
17431
+ const bucket2 = path34.join(kimiHome(), "sessions", workDirKey(cwd));
17432
+ const computed = path34.join(bucket2, sessionId, WIRE_REL);
17432
17433
  if (fs30.existsSync(computed)) return computed;
17433
17434
  const scanned = scanBucketsForSession(sessionId);
17434
17435
  if (scanned) return scanned;
@@ -17467,20 +17468,20 @@ function scanBucketsForSession(sessionId) {
17467
17468
  return null;
17468
17469
  }
17469
17470
  async function discoverSessionId2(cwd, opts) {
17470
- const bucket = path34.join(kimiHome(), "sessions", workDirKey(cwd));
17471
+ const bucket2 = path34.join(kimiHome(), "sessions", workDirKey(cwd));
17471
17472
  const floor = opts.sinceMs - 2e3;
17472
17473
  const deadline = Date.now() + (opts.timeoutMs ?? 15e3);
17473
17474
  for (; ; ) {
17474
- const id = newestSessionSince(bucket, floor);
17475
+ const id = newestSessionSince(bucket2, floor);
17475
17476
  if (id) return id;
17476
17477
  if (Date.now() >= deadline) return null;
17477
17478
  await sleep3(250);
17478
17479
  }
17479
17480
  }
17480
- function newestSessionSince(bucket, floorMs) {
17481
+ function newestSessionSince(bucket2, floorMs) {
17481
17482
  let entries;
17482
17483
  try {
17483
- entries = fs30.readdirSync(bucket, { withFileTypes: true });
17484
+ entries = fs30.readdirSync(bucket2, { withFileTypes: true });
17484
17485
  } catch {
17485
17486
  return null;
17486
17487
  }
@@ -17490,7 +17491,7 @@ function newestSessionSince(bucket, floorMs) {
17490
17491
  if (!e.isDirectory() || !e.name.startsWith("session_")) continue;
17491
17492
  let mtime;
17492
17493
  try {
17493
- mtime = fs30.statSync(path34.join(bucket, e.name)).mtimeMs;
17494
+ mtime = fs30.statSync(path34.join(bucket2, e.name)).mtimeMs;
17494
17495
  } catch {
17495
17496
  continue;
17496
17497
  }
@@ -19862,10 +19863,10 @@ var ZERO = {
19862
19863
  compressionPct: 0
19863
19864
  };
19864
19865
  function read(stats, inputPricePerMillion) {
19865
- const totals = stats.agent_usage?.totals;
19866
+ const totals2 = stats.agent_usage?.totals;
19866
19867
  const compression = stats.summary?.compression;
19867
- const rawTokensEst = totals?.before_tokens ?? compression?.total_tokens_before_with_cli_filtering ?? 0;
19868
- const sentTokensEst = totals?.after_tokens ?? rawTokensEst - (compression?.total_tokens_removed ?? 0);
19868
+ const rawTokensEst = totals2?.before_tokens ?? compression?.total_tokens_before_with_cli_filtering ?? 0;
19869
+ const sentTokensEst = totals2?.after_tokens ?? rawTokensEst - (compression?.total_tokens_removed ?? 0);
19869
19870
  const compressionTokens = compression?.total_tokens_saved_with_cli_filtering ?? compression?.total_tokens_removed ?? Math.max(0, rawTokensEst - sentTokensEst);
19870
19871
  const compressionSavingsUsd = stats.summary?.cost?.breakdown?.compression_savings_usd ?? Math.max(0, compressionTokens) / 1e6 * inputPricePerMillion;
19871
19872
  return {
@@ -20722,7 +20723,7 @@ async function autoUpgradeBeforeCriticalCommand() {
20722
20723
  if (process.env.NODE_ENV === "test") return;
20723
20724
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
20724
20725
  if (process.env.CI) return;
20725
- const current2 = true ? "2.61.98" : null;
20726
+ const current2 = true ? "2.61.99" : null;
20726
20727
  if (!current2) return;
20727
20728
  const cache = readCache();
20728
20729
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -20739,7 +20740,7 @@ function checkForUpdates() {
20739
20740
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
20740
20741
  if (process.env.CI) return;
20741
20742
  if (!process.stdout.isTTY) return;
20742
- const current2 = true ? "2.61.98" : null;
20743
+ const current2 = true ? "2.61.99" : null;
20743
20744
  if (!current2) return;
20744
20745
  const cache = readCache();
20745
20746
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -20759,7 +20760,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
20759
20760
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
20760
20761
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
20761
20762
  function currentCliVersion() {
20762
- return true ? "2.61.98" : null;
20763
+ return true ? "2.61.99" : null;
20763
20764
  }
20764
20765
  function runCmd(cmd, args2, timeoutMs) {
20765
20766
  return new Promise((resolve9) => {
@@ -22362,6 +22363,129 @@ function makeRealApplyBudgetDeps() {
22362
22363
  };
22363
22364
  }
22364
22365
 
22366
+ // src/services/headroom/usage-report.ts
22367
+ var HOURLY_CAP = 48;
22368
+ var SERIES_CAP = {
22369
+ hourly: HOURLY_CAP,
22370
+ daily: 90,
22371
+ weekly: 52,
22372
+ monthly: 24
22373
+ };
22374
+ var REQUEST_TIMEOUT_MS2 = 5e3;
22375
+ async function fetchWithTimeout2(url2) {
22376
+ const controller = new AbortController();
22377
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
22378
+ try {
22379
+ return await fetch(url2, { signal: controller.signal });
22380
+ } finally {
22381
+ clearTimeout(timer);
22382
+ }
22383
+ }
22384
+ function num(v) {
22385
+ return typeof v === "number" && Number.isFinite(v) ? v : 0;
22386
+ }
22387
+ function totals(raw) {
22388
+ const o = raw ?? {};
22389
+ return {
22390
+ requests: num(o.requests),
22391
+ tokens_saved: num(o.tokens_saved),
22392
+ compression_savings_usd: num(o.compression_savings_usd),
22393
+ total_input_tokens: num(o.total_input_tokens),
22394
+ total_input_cost_usd: num(o.total_input_cost_usd)
22395
+ };
22396
+ }
22397
+ function slices(raw) {
22398
+ const out2 = {};
22399
+ if (typeof raw !== "object" || raw === null) return out2;
22400
+ for (const [key, value] of Object.entries(raw)) {
22401
+ const v = value ?? {};
22402
+ out2[key] = {
22403
+ tokens_saved: num(v.tokens_saved),
22404
+ compression_savings_usd_delta: num(v.compression_savings_usd_delta),
22405
+ total_input_tokens_delta: num(v.total_input_tokens_delta),
22406
+ total_input_cost_usd_delta: num(v.total_input_cost_usd_delta)
22407
+ };
22408
+ }
22409
+ return out2;
22410
+ }
22411
+ function bucket(raw) {
22412
+ if (typeof raw !== "object" || raw === null) return null;
22413
+ const o = raw;
22414
+ if (typeof o.timestamp !== "string") return null;
22415
+ return {
22416
+ timestamp: o.timestamp,
22417
+ tokens_saved: num(o.tokens_saved),
22418
+ compression_savings_usd_delta: num(o.compression_savings_usd_delta),
22419
+ total_tokens_saved: num(o.total_tokens_saved),
22420
+ compression_savings_usd: num(o.compression_savings_usd),
22421
+ total_input_tokens_delta: num(o.total_input_tokens_delta),
22422
+ total_input_tokens: num(o.total_input_tokens),
22423
+ total_input_cost_usd_delta: num(o.total_input_cost_usd_delta),
22424
+ total_input_cost_usd: num(o.total_input_cost_usd),
22425
+ by_provider: slices(o.by_provider),
22426
+ by_model: slices(o.by_model)
22427
+ };
22428
+ }
22429
+ function toUsageReport(raw, proxyVersion) {
22430
+ const root = raw ?? {};
22431
+ const rawSeries = root.series ?? {};
22432
+ const series = {};
22433
+ for (const key of Object.keys(SERIES_CAP)) {
22434
+ const arr = rawSeries[key];
22435
+ if (!Array.isArray(arr)) continue;
22436
+ const kept = arr.slice(-SERIES_CAP[key]).map(bucket).filter((b) => b !== null);
22437
+ if (kept.length > 0) series[key] = kept;
22438
+ }
22439
+ const displaySession = root.display_session ?? null;
22440
+ const retention = root.retention ?? null;
22441
+ const report = {
22442
+ schemaVersion: num(root.schema_version),
22443
+ generatedAt: typeof root.generated_at === "string" ? root.generated_at : (/* @__PURE__ */ new Date()).toISOString(),
22444
+ lifetime: totals(root.lifetime),
22445
+ series
22446
+ };
22447
+ if (proxyVersion) report.proxyVersion = proxyVersion;
22448
+ if (displaySession) {
22449
+ report.currentSession = {
22450
+ ...totals(displaySession),
22451
+ savings_percent: num(displaySession.savings_percent),
22452
+ started_at: typeof displaySession.started_at === "string" ? displaySession.started_at : null,
22453
+ last_activity_at: typeof displaySession.last_activity_at === "string" ? displaySession.last_activity_at : null
22454
+ };
22455
+ }
22456
+ if (retention) {
22457
+ report.retention = {
22458
+ max_history_points: num(retention.max_history_points),
22459
+ max_history_age_days: num(retention.max_history_age_days)
22460
+ };
22461
+ }
22462
+ return report;
22463
+ }
22464
+ async function defaultFetchJson(url2) {
22465
+ const res = await fetchWithTimeout2(url2);
22466
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
22467
+ return await res.json();
22468
+ }
22469
+ async function readUsageReport(deps = {}) {
22470
+ const port = deps.port ?? HEADROOM_PROXY_PORT;
22471
+ const base = `http://127.0.0.1:${port}`;
22472
+ const get2 = deps.fetchJson ?? ((path89) => defaultFetchJson(`${base}${path89}`));
22473
+ let history;
22474
+ try {
22475
+ history = await get2("/stats-history");
22476
+ } catch (err) {
22477
+ log.info("headroomUsage", `stats-history unavailable: ${err.message}`);
22478
+ return null;
22479
+ }
22480
+ let proxyVersion;
22481
+ try {
22482
+ const health = await get2("/health");
22483
+ if (health && typeof health.version === "string") proxyVersion = health.version;
22484
+ } catch {
22485
+ }
22486
+ return toUsageReport(history, proxyVersion);
22487
+ }
22488
+
22365
22489
  // src/agents/acp/guardrail-config.ts
22366
22490
  var fs47 = __toESM(require("fs"));
22367
22491
  var path50 = __toESM(require("path"));
@@ -25943,6 +26067,24 @@ var vcsAgentReviewH = async (ctx, cmd, parsed) => {
25943
26067
  }
25944
26068
  })();
25945
26069
  };
26070
+ var headroomUsageH = async (ctx, cmd) => {
26071
+ try {
26072
+ const report = await readUsageReport();
26073
+ if (!report) {
26074
+ await ctx.relay.sendResult(cmd.id, "completed", {
26075
+ available: false,
26076
+ error: "Headroom is not running in this session."
26077
+ });
26078
+ return;
26079
+ }
26080
+ await ctx.relay.sendResult(cmd.id, "completed", { available: true, report });
26081
+ } catch (err) {
26082
+ await ctx.relay.sendResult(cmd.id, "completed", {
26083
+ available: false,
26084
+ error: err.message
26085
+ });
26086
+ }
26087
+ };
25946
26088
  var headroomBudgetH = async (ctx, cmd) => {
25947
26089
  const payload = cmd.payload;
25948
26090
  let rawAgentId = ctx.agentId || (typeof payload.agentId === "string" ? payload.agentId : "");
@@ -26731,6 +26873,7 @@ var handlers = {
26731
26873
  take_control: takeControlH,
26732
26874
  handback: handbackH,
26733
26875
  headroom_configure: headroomConfigureH,
26876
+ headroom_usage: headroomUsageH,
26734
26877
  coderabbit_configure: coderabbitConfigureH,
26735
26878
  vcs_agent_review: vcsAgentReviewH,
26736
26879
  headroom_budget: headroomBudgetH,
@@ -42598,7 +42741,7 @@ function checkChokidar() {
42598
42741
  }
42599
42742
  async function doctor(args2 = []) {
42600
42743
  const json = args2.includes("--json");
42601
- const cliVersion = true ? "2.61.98" : "0.0.0-dev";
42744
+ const cliVersion = true ? "2.61.99" : "0.0.0-dev";
42602
42745
  const apiBase2 = resolveApiBaseUrl();
42603
42746
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
42604
42747
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -42989,7 +43132,7 @@ async function mcpRun(args2) {
42989
43132
  // src/commands/version.ts
42990
43133
  var import_picocolors15 = __toESM(require("picocolors"));
42991
43134
  function version2() {
42992
- const v = true ? "2.61.98" : "unknown";
43135
+ const v = true ? "2.61.99" : "unknown";
42993
43136
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
42994
43137
  }
42995
43138
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.61.98",
3
+ "version": "2.61.99",
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",