codeam-cli 2.60.21 → 2.60.23

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 +12 -0
  2. package/dist/index.js +207 -33
  3. package/package.json +1 -1
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.60.22] — 2026-07-09
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Baton — late-bind Codex's session id instead of blocking start()
12
+
13
+ ## [2.60.21] — 2026-07-09
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** Baton — discover Codex's self-minted session id (first-turn aware)
18
+
7
19
  ## [2.60.20] — 2026-07-09
8
20
 
9
21
  ### Fixed
package/dist/index.js CHANGED
@@ -5680,7 +5680,7 @@ function readAnonId() {
5680
5680
  }
5681
5681
  function superProperties() {
5682
5682
  return {
5683
- cliVersion: true ? "2.60.21" : "0.0.0-dev",
5683
+ cliVersion: true ? "2.60.23" : "0.0.0-dev",
5684
5684
  nodeVersion: process.version,
5685
5685
  platform: process.platform,
5686
5686
  arch: process.arch,
@@ -5861,7 +5861,7 @@ var os4 = __toESM(require("os"));
5861
5861
  // package.json
5862
5862
  var package_default = {
5863
5863
  name: "codeam-cli",
5864
- version: "2.60.21",
5864
+ version: "2.60.23",
5865
5865
  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.",
5866
5866
  type: "commonjs",
5867
5867
  main: "dist/index.js",
@@ -6932,7 +6932,7 @@ var CommandRelayService = class {
6932
6932
  // fresh + clear the "CLI update available" banner after a self-update
6933
6933
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
6934
6934
  // pair/reconnect). Older backends ignore the extra field.
6935
- ..."2.60.21" ? { ideVersion: "2.60.21" } : {}
6935
+ ..."2.60.23" ? { ideVersion: "2.60.23" } : {}
6936
6936
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
6937
6937
  }
6938
6938
  /**
@@ -13420,7 +13420,7 @@ function coderabbitLoginLauncher(os49) {
13420
13420
  return ensureCoderabbitInstalled(os49);
13421
13421
  },
13422
13422
  launch() {
13423
- return (0, import_node_child_process7.spawn)("coderabbit", ["login"], { stdio: "inherit" });
13423
+ return (0, import_node_child_process7.spawn)("coderabbit", ["auth", "login"], { stdio: "inherit" });
13424
13424
  }
13425
13425
  };
13426
13426
  }
@@ -13429,31 +13429,166 @@ function coderabbitLoginLauncher(os49) {
13429
13429
  var HUNK_LINE_RE = /^([^\s:][^:]*?):(\d+)[\s:—-]+(?:\[?(info|warn|warning|error|critical)\]?[:\s—-]+)?(.+)$/i;
13430
13430
  var SEVERITY_MAP = {
13431
13431
  info: "info",
13432
+ note: "info",
13433
+ low: "info",
13434
+ minor: "info",
13432
13435
  warn: "warn",
13433
13436
  warning: "warn",
13437
+ medium: "warn",
13438
+ major: "warn",
13434
13439
  error: "error",
13435
- critical: "error"
13440
+ critical: "error",
13441
+ high: "error",
13442
+ blocker: "error"
13436
13443
  };
13437
- function parseReview(stdout) {
13444
+ function normSeverity(v) {
13445
+ if (typeof v !== "string") return void 0;
13446
+ return SEVERITY_MAP[v.trim().toLowerCase()];
13447
+ }
13448
+ function pick(obj, keys) {
13449
+ for (const k2 of keys) {
13450
+ if (obj[k2] !== void 0 && obj[k2] !== null) return obj[k2];
13451
+ }
13452
+ return void 0;
13453
+ }
13454
+ function asString(v) {
13455
+ return typeof v === "string" && v.trim().length > 0 ? v : void 0;
13456
+ }
13457
+ function pickLine(obj) {
13458
+ const direct = pick(obj, ["line", "start_line", "startLine", "line_number", "lineNumber"]);
13459
+ if (typeof direct === "number") return direct;
13460
+ if (typeof direct === "string" && /^\d+$/.test(direct)) return Number(direct);
13461
+ const range = pick(obj, ["line_range", "lineRange", "range", "lines"]);
13462
+ if (Array.isArray(range) && typeof range[0] === "number") return range[0];
13463
+ if (range && typeof range === "object") {
13464
+ const start2 = pick(range, ["start", "begin", "from"]);
13465
+ if (typeof start2 === "number") return start2;
13466
+ if (start2 && typeof start2 === "object") {
13467
+ const l = start2.line;
13468
+ if (typeof l === "number") return l;
13469
+ }
13470
+ }
13471
+ return void 0;
13472
+ }
13473
+ function toHunk(raw, groupSeverity) {
13474
+ if (!raw || typeof raw !== "object") return null;
13475
+ const o = raw;
13476
+ const path70 = asString(pick(o, ["file_path", "filePath", "file", "path", "filename"])) ?? asString(pick(o, ["location"])?.path);
13477
+ if (!path70) return null;
13478
+ const message = asString(pick(o, ["comment", "message", "body", "description", "summary", "markdown", "text"])) ?? "";
13479
+ const title = asString(pick(o, ["title", "rule", "category", "check"]));
13480
+ const severity = normSeverity(pick(o, ["severity", "level", "priority", "impact"])) ?? normSeverity(groupSeverity);
13481
+ const locObj = pick(o, ["location"]) ?? o;
13482
+ return {
13483
+ path: path70.trim(),
13484
+ line: pickLine(o) ?? pickLine(locObj),
13485
+ severity,
13486
+ message: (title && message ? `${title}: ${message}` : title || message).trim() || "(no message)"
13487
+ };
13488
+ }
13489
+ function findingsFrom(value) {
13490
+ const out2 = [];
13491
+ if (!value || typeof value !== "object") return out2;
13492
+ const root = value;
13493
+ const flat = pick(root, ["findings", "comments", "issues", "results", "review_comments", "reviewComments"]);
13494
+ if (Array.isArray(flat)) {
13495
+ for (const f of flat) {
13496
+ const h = toHunk(f);
13497
+ if (h) out2.push(h);
13498
+ }
13499
+ }
13500
+ const grouped = flat && !Array.isArray(flat) ? flat : root;
13501
+ for (const sev of ["critical", "warning", "warn", "error", "info", "note"]) {
13502
+ const bucket = grouped[sev];
13503
+ if (Array.isArray(bucket)) {
13504
+ for (const f of bucket) {
13505
+ const h = toHunk(f, sev);
13506
+ if (h) out2.push(h);
13507
+ }
13508
+ }
13509
+ }
13510
+ return out2;
13511
+ }
13512
+ function errorFrom(obj) {
13513
+ if (obj.type !== "error") return void 0;
13514
+ return asString(pick(obj, ["message", "error"]));
13515
+ }
13516
+ function isControlEvent(obj) {
13517
+ return obj.type === "review_context" || obj.type === "status" || obj.type === "progress" || obj.type === "error";
13518
+ }
13519
+ function parseStructured(stdout) {
13520
+ const trimmed = stdout.trim();
13521
+ if (!trimmed || trimmed[0] !== "{" && trimmed[0] !== "[") return null;
13522
+ try {
13523
+ const doc = JSON.parse(trimmed);
13524
+ const hunks2 = findingsFrom(Array.isArray(doc) ? { findings: doc } : doc);
13525
+ const summary2 = asString(
13526
+ pick(Array.isArray(doc) ? {} : doc, ["summary", "markdown", "report", "overview"])
13527
+ );
13528
+ if (hunks2.length > 0 || summary2) return { hunks: hunks2, summary: summary2 };
13529
+ } catch {
13530
+ }
13438
13531
  const hunks = [];
13439
- const lines = stdout.split(/\r?\n/);
13440
- for (const line of lines) {
13532
+ let summary;
13533
+ let error;
13534
+ let sawJson = false;
13535
+ for (const line of trimmed.split(/\r?\n/)) {
13536
+ const l = line.trim();
13537
+ if (!l || l[0] !== "{" && l[0] !== "[") continue;
13538
+ try {
13539
+ const obj = JSON.parse(l);
13540
+ sawJson = true;
13541
+ const rec = Array.isArray(obj) ? {} : obj;
13542
+ error ??= errorFrom(rec);
13543
+ if (isControlEvent(rec)) continue;
13544
+ const found = findingsFrom(Array.isArray(obj) ? { findings: obj } : obj);
13545
+ if (found.length > 0) hunks.push(...found);
13546
+ else {
13547
+ const single = toHunk(obj);
13548
+ if (single) hunks.push(single);
13549
+ }
13550
+ summary ??= asString(pick(rec, ["summary", "markdown", "report"]));
13551
+ } catch {
13552
+ }
13553
+ }
13554
+ return sawJson ? { hunks, summary, error } : null;
13555
+ }
13556
+ function parsePlain(stdout) {
13557
+ const hunks = [];
13558
+ for (const line of stdout.split(/\r?\n/)) {
13441
13559
  const m = line.match(HUNK_LINE_RE);
13442
13560
  if (!m) continue;
13443
13561
  const [, path70, lineNo, sevToken, message] = m;
13444
13562
  if (!path70 || !lineNo || !message) continue;
13445
- const cleanedMessage = message.trim().replace(/^[*-]\s+/, "");
13446
13563
  hunks.push({
13447
13564
  path: path70.trim(),
13448
13565
  line: Number(lineNo),
13449
13566
  severity: sevToken ? SEVERITY_MAP[sevToken.toLowerCase()] : void 0,
13450
- message: cleanedMessage
13567
+ message: message.trim().replace(/^[*-]\s+/, "")
13451
13568
  });
13452
13569
  }
13570
+ return hunks;
13571
+ }
13572
+ function severityCounts(hunks) {
13573
+ const c2 = { error: 0, warn: 0, info: 0 };
13574
+ for (const h of hunks) if (h.severity) c2[h.severity] += 1;
13575
+ return c2;
13576
+ }
13577
+ function parseReview(stdout) {
13578
+ const structured = parseStructured(stdout);
13579
+ const hunks = structured && structured.hunks.length > 0 ? structured.hunks : parsePlain(stdout);
13580
+ const counts = severityCounts(hunks);
13581
+ const markdown = (structured?.summary ?? (hunks.length === 0 && structured?.error ? structured.error : stdout)).trim();
13453
13582
  return {
13454
- markdown: stdout.trim(),
13583
+ markdown,
13455
13584
  hunks,
13456
- stats: { linesReviewed: lines.length, hunkCount: hunks.length }
13585
+ stats: {
13586
+ findingCount: hunks.length,
13587
+ critical: counts.error,
13588
+ warning: counts.warn,
13589
+ info: counts.info,
13590
+ ...structured?.error ? { error: structured.error } : {}
13591
+ }
13457
13592
  };
13458
13593
  }
13459
13594
 
@@ -13467,7 +13602,7 @@ var CoderabbitRuntimeStrategy = class {
13467
13602
  this.os = os49;
13468
13603
  }
13469
13604
  getDefaultArgs() {
13470
- return ["review"];
13605
+ return ["review", "--agent"];
13471
13606
  }
13472
13607
  async prepareInvocation(input) {
13473
13608
  const binary = this.os.findInPath(this.meta.binaryName);
@@ -13476,14 +13611,12 @@ var CoderabbitRuntimeStrategy = class {
13476
13611
  `CodeRabbit binary "${this.meta.binaryName}" not on PATH. Run \`codeam link coderabbit\` to install it first.`
13477
13612
  );
13478
13613
  }
13479
- const args2 = this.getDefaultArgs();
13480
- if (input.prRef) args2.push("--pr", input.prRef);
13481
- if (input.files && input.files.length > 0) {
13482
- args2.push(...input.files);
13483
- }
13484
- if (input.prompt) {
13485
- args2.push("--message", input.prompt);
13486
- }
13614
+ const args2 = input.structured === false ? ["review", "--plain"] : this.getDefaultArgs();
13615
+ if (input.changeSet) args2.push("--type", input.changeSet);
13616
+ if (input.base) args2.push("--base", input.base);
13617
+ if (input.baseCommit) args2.push("--base-commit", input.baseCommit);
13618
+ if (input.dir) args2.push("--dir", input.dir);
13619
+ if (input.apiKey) args2.push("--api-key", input.apiKey);
13487
13620
  if (input.extraArgs && input.extraArgs.length > 0) {
13488
13621
  args2.push(...input.extraArgs);
13489
13622
  }
@@ -13791,10 +13924,20 @@ var CursorRuntimeStrategy = class {
13791
13924
  * failure. `cursor-agent create-chat` prints a bare UUID and exits 0 (verified
13792
13925
  * on cursor-agent 2026.06.24). Synchronous + bounded so prepareLaunch stays a
13793
13926
  * single deterministic step; a timeout/parse failure just disables pre-mint.
13927
+ *
13928
+ * ⚠️ Windows: `findInPath` resolves `cursor-agent.cmd` (or `.ps1`) via PATHEXT,
13929
+ * and Windows CANNOT spawn a `.cmd`/`.bat`/`.ps1` directly — it needs
13930
+ * `cmd.exe /c` / `powershell -File`. Spawning `binary` raw (as this used to)
13931
+ * failed with EINVAL on Windows → null id → no pre-mint → the baton threw
13932
+ * "agent did not expose a session id after spawn" (Windows-only). Route the
13933
+ * spawn through `os.buildLaunch`, exactly like the TUI launch above, so the
13934
+ * command is wrapped correctly per-platform. On macOS/Linux buildLaunch returns
13935
+ * the bare `{cmd: binary, args: ['create-chat']}` → behaviour unchanged.
13794
13936
  */
13795
13937
  mintChatId(binary) {
13796
13938
  try {
13797
- const r = (0, import_node_child_process10.spawnSync)(binary, ["create-chat"], {
13939
+ const launch = this.os.buildLaunch(binary, ["create-chat"]);
13940
+ const r = (0, import_node_child_process10.spawnSync)(launch.cmd, launch.args, {
13798
13941
  encoding: "utf8",
13799
13942
  timeout: 2e4,
13800
13943
  stdio: ["ignore", "pipe", "pipe"]
@@ -16733,7 +16876,7 @@ async function autoUpgradeBeforeCriticalCommand() {
16733
16876
  if (process.env.NODE_ENV === "test") return;
16734
16877
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
16735
16878
  if (process.env.CI) return;
16736
- const current = true ? "2.60.21" : null;
16879
+ const current = true ? "2.60.23" : null;
16737
16880
  if (!current) return;
16738
16881
  const cache = readCache();
16739
16882
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -16750,7 +16893,7 @@ function checkForUpdates() {
16750
16893
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
16751
16894
  if (process.env.CI) return;
16752
16895
  if (!process.stdout.isTTY) return;
16753
- const current = true ? "2.60.21" : null;
16896
+ const current = true ? "2.60.23" : null;
16754
16897
  if (!current) return;
16755
16898
  const cache = readCache();
16756
16899
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -16770,7 +16913,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
16770
16913
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
16771
16914
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
16772
16915
  function currentCliVersion() {
16773
- return true ? "2.60.21" : null;
16916
+ return true ? "2.60.23" : null;
16774
16917
  }
16775
16918
  function runCmd(cmd, args2, timeoutMs) {
16776
16919
  return new Promise((resolve7) => {
@@ -30118,6 +30261,20 @@ var BatonController = class {
30118
30261
  this._active = "local_tui";
30119
30262
  this.setState("LOCAL_DRIVE");
30120
30263
  }
30264
+ /**
30265
+ * Late-bind the conversation id for an agent (Codex) that minted it only on the
30266
+ * user's first turn, so `begin()` returned null. Fires from the native driver's
30267
+ * background discovery. Idempotent + guarded: only binds while we're still in
30268
+ * the INITIAL, id-less LOCAL_DRIVE — if the user already took control (which
30269
+ * started its own session) or an id is already set, it's a no-op, so a stray
30270
+ * native id can never clobber the live conversation. Re-publishes LOCAL_DRIVE so
30271
+ * the read-only mirror arms on the now-known id.
30272
+ */
30273
+ rebindConversation(conversationId) {
30274
+ if (this._conversationId !== null || this._state !== "LOCAL_DRIVE") return;
30275
+ this._conversationId = conversationId;
30276
+ this.setState("LOCAL_DRIVE");
30277
+ }
30121
30278
  async takeControl() {
30122
30279
  await this.switchDriver(
30123
30280
  "LOCAL_DRIVE",
@@ -30179,7 +30336,7 @@ function parkTerminalForReadonly(term = {}) {
30179
30336
  }
30180
30337
 
30181
30338
  // src/baton/native-tui-driver.ts
30182
- var NativeTuiDriver = class {
30339
+ var NativeTuiDriver = class _NativeTuiDriver {
30183
30340
  constructor(deps) {
30184
30341
  this.deps = deps;
30185
30342
  this.agent = deps.agent;
@@ -30208,6 +30365,9 @@ var NativeTuiDriver = class {
30208
30365
  idleMs;
30209
30366
  now;
30210
30367
  lastOutput;
30368
+ /** How long `start()` waits for a boot-time session store (Kimi) before
30369
+ * falling back to background/late-bind discovery (Codex, first-turn store). */
30370
+ static QUICK_DISCOVER_MS = 8e3;
30211
30371
  outputSvc;
30212
30372
  historySvc;
30213
30373
  setKeepAlive;
@@ -30222,11 +30382,20 @@ var NativeTuiDriver = class {
30222
30382
  await this.agent.spawn();
30223
30383
  const preMinted = this.agent.spawnedSessionId;
30224
30384
  if (preMinted) return preMinted;
30225
- const discovered = await this.deps.runtime.discoverSessionId?.(this.deps.opts.cwd, {
30226
- sinceMs: spawnedAt
30385
+ const discover = this.deps.runtime.discoverSessionId;
30386
+ if (!discover) {
30387
+ throw new Error("NativeTuiDriver: agent did not expose a session id after spawn");
30388
+ }
30389
+ const quick = await discover(this.deps.opts.cwd, {
30390
+ sinceMs: spawnedAt,
30391
+ timeoutMs: _NativeTuiDriver.QUICK_DISCOVER_MS
30227
30392
  });
30228
- if (discovered) return discovered;
30229
- throw new Error("NativeTuiDriver: agent did not expose a session id after spawn");
30393
+ if (quick) return quick;
30394
+ void discover(this.deps.opts.cwd, { sinceMs: spawnedAt }).then((id) => {
30395
+ if (id) this.deps.onLateBind?.(id);
30396
+ }).catch(() => {
30397
+ });
30398
+ return null;
30230
30399
  }
30231
30400
  async stop() {
30232
30401
  this.agent.kill();
@@ -30640,7 +30809,12 @@ async function runBatonSession(opts) {
30640
30809
  cwd: opts.cwd
30641
30810
  },
30642
30811
  getRelay: () => relay,
30643
- getBeads: opts.getBeads ?? (() => null)
30812
+ getBeads: opts.getBeads ?? (() => null),
30813
+ // Late-bind for first-turn-minted ids (Codex): the background discovery
30814
+ // calls this once the user's first terminal turn creates the transcript.
30815
+ // Safe forward-ref — `controller` is initialised long before `begin()` runs,
30816
+ // and onLateBind only fires from inside `begin()`'s spawn.
30817
+ onLateBind: (id) => controller.rebindConversation(id)
30644
30818
  });
30645
30819
  let mirror = null;
30646
30820
  let firstLocalDrive = true;
@@ -33632,7 +33806,7 @@ function checkChokidar() {
33632
33806
  }
33633
33807
  async function doctor(args2 = []) {
33634
33808
  const json = args2.includes("--json");
33635
- const cliVersion = true ? "2.60.21" : "0.0.0-dev";
33809
+ const cliVersion = true ? "2.60.23" : "0.0.0-dev";
33636
33810
  const apiBase2 = resolveApiBaseUrl();
33637
33811
  const diagnosticId = (0, import_node_crypto12.randomUUID)();
33638
33812
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -33831,7 +34005,7 @@ async function completion(args2) {
33831
34005
  // src/commands/version.ts
33832
34006
  var import_picocolors15 = __toESM(require("picocolors"));
33833
34007
  function version2() {
33834
- const v = true ? "2.60.21" : "unknown";
34008
+ const v = true ? "2.60.23" : "unknown";
33835
34009
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
33836
34010
  }
33837
34011
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.60.21",
3
+ "version": "2.60.23",
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",