codeam-cli 2.12.1 → 2.12.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/index.js +340 -257
  3. package/package.json +1 -1
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.12.1] — 2026-05-14
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Parse Codex rollouts for full transcripts + token usage
12
+
7
13
  ## [2.11.0] — 2026-05-13
8
14
 
9
15
  ### Added
package/dist/index.js CHANGED
@@ -1392,87 +1392,6 @@ var require_src = __commonJS({
1392
1392
  // src/commands/start.ts
1393
1393
  var import_picocolors2 = __toESM(require("picocolors"));
1394
1394
 
1395
- // ../../packages/shared/src/protocol/parseChrome.ts
1396
- var SPINNER_RE = /^(?:[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]|🔴|🟠|🟡|🟢|🔵|🟣|🟤|⚫|⚪|🌀|💭|✨)\s/u;
1397
- var BULLET_TOOL_RE = /^•\s+(?:Read(?:ing)?|Edit(?:ing)?|Writ(?:e|ing)|Bash|Runn(?:ing)?|Search(?:ing)?|Glob(?:bing)?|Grep(?:ping)?|Creat(?:e|ing)|Execut(?:e|ing)|Task|Agent|NotebookEdit)\b/i;
1398
- var TREE_LINE_RE = /^└\s/;
1399
- var STATUS_LINE_RE = /^(?:\+|[🔴🟠🟡🟢🔵🟣🟤⚫⚪🌀💭✨])\s/u;
1400
- function isChromeLine(line) {
1401
- const t2 = line.replace(/️/g, "").trim();
1402
- if (!t2) return false;
1403
- if (/^[─━—═─\-]{3,}$/.test(t2)) return true;
1404
- if (SPINNER_RE.test(t2)) return true;
1405
- if (BULLET_TOOL_RE.test(t2)) return true;
1406
- if (TREE_LINE_RE.test(t2)) return true;
1407
- if (STATUS_LINE_RE.test(t2) && /\d+\s*s\s*[·•]|\bthought\s+for\b|\d+\s*tokens|\(thinking\)/i.test(t2)) return true;
1408
- if (/^↓\s*\d+\s*tokens/i.test(t2)) return true;
1409
- if (/^\bthought\s+for\s+\d+/i.test(t2)) return true;
1410
- if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) return true;
1411
- if (/high\s*[·•]\s*\/effort/i.test(t2)) return true;
1412
- if (/^[❯>]\s*$/.test(t2)) return true;
1413
- if (/^\(thinking\)\s*$/.test(t2)) return true;
1414
- if (/^\?\s.*shortcut/i.test(t2)) return true;
1415
- if (/spending limit|usage limit/i.test(t2) && t2.length < 80) return true;
1416
- if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) return true;
1417
- if (t2.replace(/\s/g, "").length === 1) return true;
1418
- if ((t2.match(/─/g)?.length ?? 0) >= 6) return true;
1419
- if (/ctrl\+?o\s+to\s+expand/i.test(t2)) return true;
1420
- const hasBoxPrefix = /^[│╭╰╮╯┌└┐┘├┤┬┴┼]/.test(t2);
1421
- const stripped = t2.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "");
1422
- if (hasBoxPrefix && /^[❯>]\s+\S/.test(stripped) && !/^[❯>]\s*\d+\./.test(stripped)) return true;
1423
- return false;
1424
- }
1425
- function parseChromeLine(line) {
1426
- const t2 = line.replace(/️/g, "").trim();
1427
- if (!t2) return null;
1428
- if (/^[─━—═─\-]{3,}$/.test(t2)) return null;
1429
- if (/^[❯>]\s*$/.test(t2)) return null;
1430
- if (t2.replace(/\s/g, "").length === 1) return null;
1431
- if ((t2.match(/─/g)?.length ?? 0) >= 6) return null;
1432
- if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) return null;
1433
- if (/high\s*[·•]\s*\/effort/i.test(t2)) return null;
1434
- if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) return null;
1435
- if (/ctrl\+?o\s+to\s+expand/i.test(t2)) return null;
1436
- if (/spending limit|usage limit/i.test(t2)) return null;
1437
- if (/^\(thinking\)\s*$/.test(t2)) {
1438
- return { tool: "thinking", label: "Thinking\u2026", status: "running" };
1439
- }
1440
- if (TREE_LINE_RE.test(t2)) return null;
1441
- if (STATUS_LINE_RE.test(t2)) {
1442
- const label = t2.slice(2).replace(/….*/s, "").trim() || "Thinking\u2026";
1443
- return { tool: "thinking", label, status: "running" };
1444
- }
1445
- let text = t2;
1446
- if (SPINNER_RE.test(t2)) {
1447
- text = t2.slice(2).trim().replace(/….*/s, "").trim();
1448
- } else if (BULLET_TOOL_RE.test(t2)) {
1449
- text = t2.slice(2).trim();
1450
- text = text.replace(/\s*\(ctrl\+?o[^)]*\)/gi, "").replace(/,\s*reading\s+\d+\s+files?\s*…?/gi, "").replace(/,\s*\d+\s+files?\s*…?/gi, "").replace(/…$/, "").trim();
1451
- }
1452
- if (!text) return null;
1453
- return classifyStep(text);
1454
- }
1455
- function classifyStep(text) {
1456
- if (/^Read(?:ing)?\s+/i.test(text)) {
1457
- const label2 = text.replace(/^Read(?:ing)?\s+/i, "").replace(/\.\.\.$/, "").trim();
1458
- return { tool: "read", label: label2, status: "running" };
1459
- }
1460
- if (/^Edit(?:ing)?\s+|^Writ(?:e|ing|ing to)\s+|^Creat(?:e|ing)\s+/i.test(text)) {
1461
- const label2 = text.replace(/^(?:Edit(?:ing)?|Writ(?:e|ing(?: to)?)|Creat(?:e|ing))\s+/i, "").replace(/\.\.\.$/, "").trim();
1462
- return { tool: "edit", label: label2, status: "running" };
1463
- }
1464
- if (/^Runn(?:ing)?\s+|^Execut(?:e|ing)\s+|^Bash(?:ing)?\s*:|^\$\s+/i.test(text)) {
1465
- const label2 = text.replace(/^(?:Runn(?:ing)?|Execut(?:e|ing)|Bash(?:ing)?:|\$)\s+/i, "").replace(/\.\.\.$/, "").trim();
1466
- return { tool: "bash", label: label2, status: "running" };
1467
- }
1468
- if (/^Search(?:ing)?\s+for\s+|^Grep(?:ping)?\s*:/i.test(text)) {
1469
- const label2 = text.replace(/^(?:Search(?:ing)?\s+for|Grep(?:ping)?:)\s+/i, "").replace(/\.\.\.$/, "").trim();
1470
- return { tool: "search", label: label2, status: "running" };
1471
- }
1472
- const label = text.replace(/\.\.\.$/, "").trim();
1473
- return { tool: "other", label, status: "running" };
1474
- }
1475
-
1476
1395
  // ../../packages/shared/src/protocol/renderToLines.ts
1477
1396
  function renderToLines(raw) {
1478
1397
  const screen = [""];
@@ -1580,162 +1499,6 @@ function renderToLines(raw) {
1580
1499
  return screen;
1581
1500
  }
1582
1501
 
1583
- // ../../packages/shared/src/protocol/selector.ts
1584
- function detectSelector(lines) {
1585
- if (lines.some((l) => /\?\s+for\s+shortcuts/i.test(l.trim()))) return null;
1586
- const clean = lines.map(
1587
- (l) => l.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "").replace(/\s*[│╭╰╮╯┌└┐┘├┤┬┴┼─━═]+\s*$/, "")
1588
- );
1589
- const hasCursor = clean.some((l) => /^[❯>]\s*\d+\./.test(l.trim()));
1590
- const looksLikeTrust = clean.some(
1591
- (l) => /\b(?:trust\s+the\s+files|trust\s+this\s+folder|safety\s+check)\b/i.test(l)
1592
- );
1593
- if (!hasCursor && !looksLikeTrust) return null;
1594
- let optionStartIdx = -1;
1595
- for (let i = 0; i < clean.length; i++) {
1596
- if (/^(?:[❯>]\s*)?\d+\.\s/.test(clean[i].trim())) {
1597
- optionStartIdx = i;
1598
- break;
1599
- }
1600
- }
1601
- if (optionStartIdx === -1) return null;
1602
- const questionParts = [];
1603
- for (let i = 0; i < optionStartIdx; i++) {
1604
- const t2 = clean[i].trim();
1605
- if (!t2) continue;
1606
- if (/^[─━—═\-]{3,}$/.test(t2)) continue;
1607
- if (/^\[.*\]$/.test(t2)) continue;
1608
- if (/^[>❯]\s/.test(t2)) continue;
1609
- if (!t2.includes(" ") && t2.length > 15) continue;
1610
- questionParts.push(t2);
1611
- }
1612
- const question = questionParts.filter((line, i, arr) => !arr.some((other, j2) => j2 !== i && other.includes(line))).join("\n").trim();
1613
- const optionLabels = /* @__PURE__ */ new Map();
1614
- const optionDescs = /* @__PURE__ */ new Map();
1615
- let currentNum = -1;
1616
- for (let i = optionStartIdx; i < clean.length; i++) {
1617
- const t2 = clean[i].trim();
1618
- if (!t2) continue;
1619
- const m = t2.match(/^(?:[❯>]\s*)?(\d+)\.\s+(.+)/);
1620
- if (m) {
1621
- const num = parseInt(m[1], 10);
1622
- if (!optionLabels.has(num)) {
1623
- optionLabels.set(num, m[2].trim());
1624
- optionDescs.set(num, []);
1625
- }
1626
- currentNum = num;
1627
- } else if (currentNum !== -1 && !/^Enter to/i.test(t2) && !/^[─━—═\-]{3,}$/.test(t2) && !/↑.*↓.*navigate/i.test(t2) && !/Esc to/i.test(t2)) {
1628
- optionDescs.get(currentNum)?.push(t2);
1629
- }
1630
- }
1631
- const keys = [...optionLabels.keys()].sort((a, b) => a - b);
1632
- if (keys.length < 2 || keys[0] !== 1) return null;
1633
- return {
1634
- question,
1635
- options: keys.map((k2) => optionLabels.get(k2)),
1636
- optionDescriptions: keys.map((k2) => (optionDescs.get(k2) ?? []).join(" ").trim()),
1637
- currentIndex: 0
1638
- };
1639
- }
1640
- function detectListSelector(lines) {
1641
- if (!lines.some((l) => /[↑↓].*navigate/i.test(l.trim()))) return null;
1642
- if (lines.some((l) => /^❯\s*\d+\./.test(l.trim()))) return null;
1643
- if (!lines.some((l) => /^\s+❯\s+\S/.test(l))) return null;
1644
- const isSelected = (line) => /^\s+❯\s+\S/.test(line);
1645
- const isUnselected = (line) => /^ \S/.test(line);
1646
- const isItem = (line) => isSelected(line) || isUnselected(line);
1647
- let optionStartIdx = -1;
1648
- for (let i = 0; i < lines.length; i++) {
1649
- if (isItem(lines[i])) {
1650
- optionStartIdx = i;
1651
- break;
1652
- }
1653
- }
1654
- if (optionStartIdx === -1) return null;
1655
- const questionParts = [];
1656
- for (let i = 0; i < optionStartIdx; i++) {
1657
- const t2 = lines[i].trim();
1658
- if (!t2) continue;
1659
- if (/^[─━—═\-]{3,}$/.test(t2)) continue;
1660
- if (/[┌└│┐┘├┤┬┴┼]/.test(t2)) {
1661
- const inner = t2.replace(/[│┌└┐┘├┤┬┴┼─]/g, "").trim();
1662
- if (inner) questionParts.push(inner);
1663
- continue;
1664
- }
1665
- if (/^[>❯]\s/.test(t2)) continue;
1666
- if (/[↑↓].*navigate/i.test(t2)) continue;
1667
- if (!t2.includes(" ") && t2.length > 15) continue;
1668
- questionParts.push(t2);
1669
- }
1670
- const question = questionParts.filter((line, i, arr) => !arr.some((other, j2) => j2 !== i && other.includes(line))).join("\n").trim();
1671
- const options = [];
1672
- let currentIndex = 0;
1673
- for (const line of lines.slice(optionStartIdx)) {
1674
- const t2 = line.trim();
1675
- if (!t2) continue;
1676
- if (/[↑↓].*navigate/i.test(t2)) break;
1677
- if (/^[─━—═\-]{3,}$/.test(t2)) continue;
1678
- if (isSelected(line)) {
1679
- currentIndex = options.length;
1680
- options.push(t2.replace(/^❯\s+/, "").trim());
1681
- } else if (isUnselected(line)) {
1682
- options.push(t2);
1683
- }
1684
- }
1685
- if (options.length < 2) return null;
1686
- return {
1687
- question,
1688
- options,
1689
- optionDescriptions: options.map(() => ""),
1690
- currentIndex
1691
- };
1692
- }
1693
-
1694
- // ../../packages/shared/src/protocol/filterChrome.ts
1695
- function filterChrome(lines) {
1696
- const result = [];
1697
- let skipEchoContinuation = false;
1698
- for (const line of lines) {
1699
- const t2 = line.trim();
1700
- if (!t2) {
1701
- skipEchoContinuation = false;
1702
- continue;
1703
- }
1704
- if (/^[─━—═─\-]{3,}$/.test(t2)) {
1705
- skipEchoContinuation = false;
1706
- continue;
1707
- }
1708
- if (/^[●⏺]\s/.test(t2)) skipEchoContinuation = false;
1709
- if (/^[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]\s/.test(t2)) continue;
1710
- if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) continue;
1711
- if (/high\s*[·•]\s*\/effort/i.test(t2)) continue;
1712
- if (/^[❯>]\s*$/.test(t2)) continue;
1713
- if (/^\(thinking\)\s*$/.test(t2)) continue;
1714
- if (/^\?\s.*shortcut/i.test(t2)) continue;
1715
- if (/spending limit|usage limit/i.test(t2) && t2.length < 80) continue;
1716
- if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) continue;
1717
- if (t2.replace(/\s/g, "").length === 1) continue;
1718
- if ((t2.match(/─/g)?.length ?? 0) >= 6) continue;
1719
- if (/ctrl\+?o\s+to\s+expand/i.test(t2)) continue;
1720
- if (/^•\s+(?:Read(?:ing)?|Edit(?:ing)?|Writ(?:e|ing)|Bash|Runn(?:ing)?|Search(?:ing)?|Glob(?:bing)?|Grep(?:ping)?|Creat(?:e|ing)|Execut(?:e|ing)|Task|Agent|NotebookEdit)\b/i.test(
1721
- t2
1722
- ))
1723
- continue;
1724
- if (/^└\s/.test(t2)) continue;
1725
- if (/^\+\s/.test(t2) && /\d+\s*s\s*[·•]|\bthought\s+for\b|\d+\s*tokens|\(thinking\)/i.test(t2)) continue;
1726
- if (/^↓\s*\d+\s*tokens/i.test(t2)) continue;
1727
- if (/^\bthought\s+for\s+\d+/i.test(t2)) continue;
1728
- const stripped = t2.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "");
1729
- if (/^[❯>]\s+\S/.test(stripped) && !/^[❯>]\s*\d+\./.test(stripped)) {
1730
- skipEchoContinuation = true;
1731
- continue;
1732
- }
1733
- if (skipEchoContinuation) continue;
1734
- result.push(line);
1735
- }
1736
- return result;
1737
- }
1738
-
1739
1502
  // ../../packages/shared/src/models/pricing.ts
1740
1503
  var MODEL_PRICING = {
1741
1504
  // ── Anthropic / Claude ────────────────────────────────────
@@ -1919,7 +1682,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
1919
1682
  // package.json
1920
1683
  var package_default = {
1921
1684
  name: "codeam-cli",
1922
- version: "2.12.1",
1685
+ version: "2.12.3",
1923
1686
  description: "Remote control Claude Code (and other AI coding agents) from your mobile phone. Pair your device, send prompts, stream responses in real-time, and approve commands \u2014 from anywhere.",
1924
1687
  type: "commonjs",
1925
1688
  main: "dist/index.js",
@@ -5410,6 +5173,239 @@ function parseHistoryFile(filePath) {
5410
5173
  return out;
5411
5174
  }
5412
5175
 
5176
+ // src/agents/claude/parsing.ts
5177
+ function filterChrome(lines) {
5178
+ const result = [];
5179
+ let skipEchoContinuation = false;
5180
+ for (const line of lines) {
5181
+ const t2 = line.trim();
5182
+ if (!t2) {
5183
+ skipEchoContinuation = false;
5184
+ continue;
5185
+ }
5186
+ if (/^[─━—═─\-]{3,}$/.test(t2)) {
5187
+ skipEchoContinuation = false;
5188
+ continue;
5189
+ }
5190
+ if (/^[●⏺]\s/.test(t2)) skipEchoContinuation = false;
5191
+ if (/^[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]\s/.test(t2)) continue;
5192
+ if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) continue;
5193
+ if (/high\s*[·•]\s*\/effort/i.test(t2)) continue;
5194
+ if (/^[❯>]\s*$/.test(t2)) continue;
5195
+ if (/^\(thinking\)\s*$/.test(t2)) continue;
5196
+ if (/^\?\s.*shortcut/i.test(t2)) continue;
5197
+ if (/spending limit|usage limit/i.test(t2) && t2.length < 80) continue;
5198
+ if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) continue;
5199
+ if (t2.replace(/\s/g, "").length === 1) continue;
5200
+ if ((t2.match(/─/g)?.length ?? 0) >= 6) continue;
5201
+ if (/ctrl\+?o\s+to\s+expand/i.test(t2)) continue;
5202
+ if (/^•\s+(?:Read(?:ing)?|Edit(?:ing)?|Writ(?:e|ing)|Bash|Runn(?:ing)?|Search(?:ing)?|Glob(?:bing)?|Grep(?:ping)?|Creat(?:e|ing)|Execut(?:e|ing)|Task|Agent|NotebookEdit)\b/i.test(
5203
+ t2
5204
+ ))
5205
+ continue;
5206
+ if (/^└\s/.test(t2)) continue;
5207
+ if (/^\+\s/.test(t2) && /\d+\s*s\s*[·•]|\bthought\s+for\b|\d+\s*tokens|\(thinking\)/i.test(t2)) continue;
5208
+ if (/^↓\s*\d+\s*tokens/i.test(t2)) continue;
5209
+ if (/^\bthought\s+for\s+\d+/i.test(t2)) continue;
5210
+ const stripped = t2.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "");
5211
+ if (/^[❯>]\s+\S/.test(stripped) && !/^[❯>]\s*\d+\./.test(stripped)) {
5212
+ skipEchoContinuation = true;
5213
+ continue;
5214
+ }
5215
+ if (skipEchoContinuation) continue;
5216
+ result.push(line);
5217
+ }
5218
+ return result;
5219
+ }
5220
+ var SPINNER_RE = /^(?:[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]|🔴|🟠|🟡|🟢|🔵|🟣|🟤|⚫|⚪|🌀|💭|✨)\s/u;
5221
+ var BULLET_TOOL_RE = /^•\s+(?:Read(?:ing)?|Edit(?:ing)?|Writ(?:e|ing)|Bash|Runn(?:ing)?|Search(?:ing)?|Glob(?:bing)?|Grep(?:ping)?|Creat(?:e|ing)|Execut(?:e|ing)|Task|Agent|NotebookEdit)\b/i;
5222
+ var TREE_LINE_RE = /^└\s/;
5223
+ var STATUS_LINE_RE = /^(?:\+|[🔴🟠🟡🟢🔵🟣🟤⚫⚪🌀💭✨])\s/u;
5224
+ function isChromeLine(line) {
5225
+ const t2 = line.replace(/️/g, "").trim();
5226
+ if (!t2) return false;
5227
+ if (/^[─━—═─\-]{3,}$/.test(t2)) return true;
5228
+ if (SPINNER_RE.test(t2)) return true;
5229
+ if (BULLET_TOOL_RE.test(t2)) return true;
5230
+ if (TREE_LINE_RE.test(t2)) return true;
5231
+ if (STATUS_LINE_RE.test(t2) && /\d+\s*s\s*[·•]|\bthought\s+for\b|\d+\s*tokens|\(thinking\)/i.test(t2)) return true;
5232
+ if (/^↓\s*\d+\s*tokens/i.test(t2)) return true;
5233
+ if (/^\bthought\s+for\s+\d+/i.test(t2)) return true;
5234
+ if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) return true;
5235
+ if (/high\s*[·•]\s*\/effort/i.test(t2)) return true;
5236
+ if (/^[❯>]\s*$/.test(t2)) return true;
5237
+ if (/^\(thinking\)\s*$/.test(t2)) return true;
5238
+ if (/^\?\s.*shortcut/i.test(t2)) return true;
5239
+ if (/spending limit|usage limit/i.test(t2) && t2.length < 80) return true;
5240
+ if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) return true;
5241
+ if (t2.replace(/\s/g, "").length === 1) return true;
5242
+ if ((t2.match(/─/g)?.length ?? 0) >= 6) return true;
5243
+ if (/ctrl\+?o\s+to\s+expand/i.test(t2)) return true;
5244
+ const hasBoxPrefix = /^[│╭╰╮╯┌└┐┘├┤┬┴┼]/.test(t2);
5245
+ const stripped = t2.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "");
5246
+ if (hasBoxPrefix && /^[❯>]\s+\S/.test(stripped) && !/^[❯>]\s*\d+\./.test(stripped)) return true;
5247
+ return false;
5248
+ }
5249
+ function parseChromeLine(line) {
5250
+ const t2 = line.replace(/️/g, "").trim();
5251
+ if (!t2) return null;
5252
+ if (/^[─━—═─\-]{3,}$/.test(t2)) return null;
5253
+ if (/^[❯>]\s*$/.test(t2)) return null;
5254
+ if (t2.replace(/\s/g, "").length === 1) return null;
5255
+ if ((t2.match(/─/g)?.length ?? 0) >= 6) return null;
5256
+ if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) return null;
5257
+ if (/high\s*[·•]\s*\/effort/i.test(t2)) return null;
5258
+ if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) return null;
5259
+ if (/ctrl\+?o\s+to\s+expand/i.test(t2)) return null;
5260
+ if (/spending limit|usage limit/i.test(t2)) return null;
5261
+ if (/^\(thinking\)\s*$/.test(t2)) {
5262
+ return { tool: "thinking", label: "Thinking\u2026", status: "running" };
5263
+ }
5264
+ if (TREE_LINE_RE.test(t2)) return null;
5265
+ if (STATUS_LINE_RE.test(t2)) {
5266
+ const label = t2.slice(2).replace(/….*/s, "").trim() || "Thinking\u2026";
5267
+ return { tool: "thinking", label, status: "running" };
5268
+ }
5269
+ let text = t2;
5270
+ if (SPINNER_RE.test(t2)) {
5271
+ text = t2.slice(2).trim().replace(/….*/s, "").trim();
5272
+ } else if (BULLET_TOOL_RE.test(t2)) {
5273
+ text = t2.slice(2).trim();
5274
+ text = text.replace(/\s*\(ctrl\+?o[^)]*\)/gi, "").replace(/,\s*reading\s+\d+\s+files?\s*…?/gi, "").replace(/,\s*\d+\s+files?\s*…?/gi, "").replace(/…$/, "").trim();
5275
+ }
5276
+ if (!text) return null;
5277
+ return classifyStep(text);
5278
+ }
5279
+ function classifyStep(text) {
5280
+ if (/^Read(?:ing)?\s+/i.test(text)) {
5281
+ const label2 = text.replace(/^Read(?:ing)?\s+/i, "").replace(/\.\.\.$/, "").trim();
5282
+ return { tool: "read", label: label2, status: "running" };
5283
+ }
5284
+ if (/^Edit(?:ing)?\s+|^Writ(?:e|ing|ing to)\s+|^Creat(?:e|ing)\s+/i.test(text)) {
5285
+ const label2 = text.replace(/^(?:Edit(?:ing)?|Writ(?:e|ing(?: to)?)|Creat(?:e|ing))\s+/i, "").replace(/\.\.\.$/, "").trim();
5286
+ return { tool: "edit", label: label2, status: "running" };
5287
+ }
5288
+ if (/^Runn(?:ing)?\s+|^Execut(?:e|ing)\s+|^Bash(?:ing)?\s*:|^\$\s+/i.test(text)) {
5289
+ const label2 = text.replace(/^(?:Runn(?:ing)?|Execut(?:e|ing)|Bash(?:ing)?:|\$)\s+/i, "").replace(/\.\.\.$/, "").trim();
5290
+ return { tool: "bash", label: label2, status: "running" };
5291
+ }
5292
+ if (/^Search(?:ing)?\s+for\s+|^Grep(?:ping)?\s*:/i.test(text)) {
5293
+ const label2 = text.replace(/^(?:Search(?:ing)?\s+for|Grep(?:ping)?:)\s+/i, "").replace(/\.\.\.$/, "").trim();
5294
+ return { tool: "search", label: label2, status: "running" };
5295
+ }
5296
+ const label = text.replace(/\.\.\.$/, "").trim();
5297
+ return { tool: "other", label, status: "running" };
5298
+ }
5299
+ function detectSelector(lines) {
5300
+ if (lines.some((l) => /\?\s+for\s+shortcuts/i.test(l.trim()))) return null;
5301
+ const clean = lines.map(
5302
+ (l) => l.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "").replace(/\s*[│╭╰╮╯┌└┐┘├┤┬┴┼─━═]+\s*$/, "")
5303
+ );
5304
+ const hasCursor = clean.some((l) => /^[❯>]\s*\d+\./.test(l.trim()));
5305
+ const looksLikeTrust = clean.some(
5306
+ (l) => /\b(?:trust\s+the\s+files|trust\s+this\s+folder|safety\s+check)\b/i.test(l)
5307
+ );
5308
+ if (!hasCursor && !looksLikeTrust) return null;
5309
+ let optionStartIdx = -1;
5310
+ for (let i = 0; i < clean.length; i++) {
5311
+ if (/^(?:[❯>]\s*)?\d+\.\s/.test(clean[i].trim())) {
5312
+ optionStartIdx = i;
5313
+ break;
5314
+ }
5315
+ }
5316
+ if (optionStartIdx === -1) return null;
5317
+ const questionParts = [];
5318
+ for (let i = 0; i < optionStartIdx; i++) {
5319
+ const t2 = clean[i].trim();
5320
+ if (!t2) continue;
5321
+ if (/^[─━—═\-]{3,}$/.test(t2)) continue;
5322
+ if (/^\[.*\]$/.test(t2)) continue;
5323
+ if (/^[>❯]\s/.test(t2)) continue;
5324
+ if (!t2.includes(" ") && t2.length > 15) continue;
5325
+ questionParts.push(t2);
5326
+ }
5327
+ const question = questionParts.filter((line, i, arr) => !arr.some((other, j2) => j2 !== i && other.includes(line))).join("\n").trim();
5328
+ const optionLabels = /* @__PURE__ */ new Map();
5329
+ const optionDescs = /* @__PURE__ */ new Map();
5330
+ let currentNum = -1;
5331
+ for (let i = optionStartIdx; i < clean.length; i++) {
5332
+ const t2 = clean[i].trim();
5333
+ if (!t2) continue;
5334
+ const m = t2.match(/^(?:[❯>]\s*)?(\d+)\.\s+(.+)/);
5335
+ if (m) {
5336
+ const num = parseInt(m[1], 10);
5337
+ if (!optionLabels.has(num)) {
5338
+ optionLabels.set(num, m[2].trim());
5339
+ optionDescs.set(num, []);
5340
+ }
5341
+ currentNum = num;
5342
+ } else if (currentNum !== -1 && !/^Enter to/i.test(t2) && !/^[─━—═\-]{3,}$/.test(t2) && !/↑.*↓.*navigate/i.test(t2) && !/Esc to/i.test(t2)) {
5343
+ optionDescs.get(currentNum)?.push(t2);
5344
+ }
5345
+ }
5346
+ const keys = [...optionLabels.keys()].sort((a, b) => a - b);
5347
+ if (keys.length < 2 || keys[0] !== 1) return null;
5348
+ return {
5349
+ question,
5350
+ options: keys.map((k2) => optionLabels.get(k2)),
5351
+ optionDescriptions: keys.map((k2) => (optionDescs.get(k2) ?? []).join(" ").trim()),
5352
+ currentIndex: 0
5353
+ };
5354
+ }
5355
+ function detectListSelector(lines) {
5356
+ if (!lines.some((l) => /[↑↓].*navigate/i.test(l.trim()))) return null;
5357
+ if (lines.some((l) => /^❯\s*\d+\./.test(l.trim()))) return null;
5358
+ if (!lines.some((l) => /^\s+❯\s+\S/.test(l))) return null;
5359
+ const isSelected = (line) => /^\s+❯\s+\S/.test(line);
5360
+ const isUnselected = (line) => /^ \S/.test(line);
5361
+ const isItem = (line) => isSelected(line) || isUnselected(line);
5362
+ let optionStartIdx = -1;
5363
+ for (let i = 0; i < lines.length; i++) {
5364
+ if (isItem(lines[i])) {
5365
+ optionStartIdx = i;
5366
+ break;
5367
+ }
5368
+ }
5369
+ if (optionStartIdx === -1) return null;
5370
+ const questionParts = [];
5371
+ for (let i = 0; i < optionStartIdx; i++) {
5372
+ const t2 = lines[i].trim();
5373
+ if (!t2) continue;
5374
+ if (/^[─━—═\-]{3,}$/.test(t2)) continue;
5375
+ if (/[┌└│┐┘├┤┬┴┼]/.test(t2)) {
5376
+ const inner = t2.replace(/[│┌└┐┘├┤┬┴┼─]/g, "").trim();
5377
+ if (inner) questionParts.push(inner);
5378
+ continue;
5379
+ }
5380
+ if (/^[>❯]\s/.test(t2)) continue;
5381
+ if (/[↑↓].*navigate/i.test(t2)) continue;
5382
+ if (!t2.includes(" ") && t2.length > 15) continue;
5383
+ questionParts.push(t2);
5384
+ }
5385
+ const question = questionParts.filter((line, i, arr) => !arr.some((other, j2) => j2 !== i && other.includes(line))).join("\n").trim();
5386
+ const options = [];
5387
+ let currentIndex = 0;
5388
+ for (const line of lines.slice(optionStartIdx)) {
5389
+ const t2 = line.trim();
5390
+ if (!t2) continue;
5391
+ if (/[↑↓].*navigate/i.test(t2)) break;
5392
+ if (/^[─━—═\-]{3,}$/.test(t2)) continue;
5393
+ if (isSelected(line)) {
5394
+ currentIndex = options.length;
5395
+ options.push(t2.replace(/^❯\s+/, "").trim());
5396
+ } else if (isUnselected(line)) {
5397
+ options.push(t2);
5398
+ }
5399
+ }
5400
+ if (options.length < 2) return null;
5401
+ return {
5402
+ question,
5403
+ options,
5404
+ optionDescriptions: options.map(() => ""),
5405
+ currentIndex
5406
+ };
5407
+ }
5408
+
5413
5409
  // src/agents/claude/runtime.ts
5414
5410
  var ClaudeRuntimeStrategy = class {
5415
5411
  id = "claude";
@@ -5449,6 +5445,17 @@ var ClaudeRuntimeStrategy = class {
5449
5445
  if (mode === "normal") return { ptyInput: "/compact\r" };
5450
5446
  return { ptyInput: "/compact\r" };
5451
5447
  }
5448
+ // ─── TUI parser strategy methods ─────────────────────────────────
5449
+ parseTuiChrome(line) {
5450
+ if (!isChromeLine(line)) return null;
5451
+ return parseChromeLine(line);
5452
+ }
5453
+ filterTuiOutput(lines) {
5454
+ return filterChrome(lines);
5455
+ }
5456
+ detectInteractivePrompt(lines) {
5457
+ return detectSelector(lines) ?? detectListSelector(lines);
5458
+ }
5452
5459
  };
5453
5460
 
5454
5461
  // src/agents/claude/deploy.ts
@@ -5788,6 +5795,51 @@ function getCurrentUsage2(historyDir) {
5788
5795
  };
5789
5796
  }
5790
5797
 
5798
+ // src/agents/codex/parsing.ts
5799
+ var BOX_DRAW_RE = /^[╭─╮│╰╯]/u;
5800
+ var CODEX_USER_ECHO_RE = /^[›>]\s+\S/u;
5801
+ var CODEX_AGENT_REPLY_RE = /^[•·]\s/u;
5802
+ var TIP_RE = /^\s*Tip:\s/i;
5803
+ var LEARN_MORE_RE = /^\s*Learn more:\s/i;
5804
+ var CODEX_STATUS_FOOTER_RE = /\bdefault\s+[·•]\s+\S+/i;
5805
+ function filterCodexChrome(lines) {
5806
+ const out = [];
5807
+ let skipEchoContinuation = false;
5808
+ for (const line of lines) {
5809
+ const t2 = line.trimEnd();
5810
+ const trimmed = t2.trimStart();
5811
+ if (!trimmed) {
5812
+ skipEchoContinuation = false;
5813
+ continue;
5814
+ }
5815
+ if (BOX_DRAW_RE.test(trimmed)) continue;
5816
+ if (/^OpenAI Codex\b/i.test(trimmed) || /^>_\s+OpenAI Codex\b/i.test(trimmed) || /^model:\s/i.test(trimmed) || /^directory:\s/i.test(trimmed)) continue;
5817
+ if (TIP_RE.test(t2) || LEARN_MORE_RE.test(t2)) continue;
5818
+ if (CODEX_STATUS_FOOTER_RE.test(trimmed)) {
5819
+ skipEchoContinuation = false;
5820
+ continue;
5821
+ }
5822
+ if (CODEX_AGENT_REPLY_RE.test(trimmed)) {
5823
+ skipEchoContinuation = false;
5824
+ out.push(t2.replace(/^(\s*)[•·]\s/, "$1"));
5825
+ continue;
5826
+ }
5827
+ if (CODEX_USER_ECHO_RE.test(trimmed)) {
5828
+ skipEchoContinuation = true;
5829
+ continue;
5830
+ }
5831
+ if (skipEchoContinuation) continue;
5832
+ out.push(t2);
5833
+ }
5834
+ return out;
5835
+ }
5836
+ function parseCodexChrome(_line) {
5837
+ return null;
5838
+ }
5839
+ function detectCodexSelector(_lines) {
5840
+ return null;
5841
+ }
5842
+
5791
5843
  // src/agents/codex/runtime.ts
5792
5844
  var CODEX_CONTEXT_WINDOW = 272e3;
5793
5845
  var CODEX_MODELS = [
@@ -5850,6 +5902,16 @@ var CodexRuntimeStrategy = class {
5850
5902
  summarizeInstruction(_mode) {
5851
5903
  return { ptyInput: "/compact\r" };
5852
5904
  }
5905
+ // ─── TUI parser strategy methods ─────────────────────────────────
5906
+ parseTuiChrome(line) {
5907
+ return parseCodexChrome(line);
5908
+ }
5909
+ filterTuiOutput(lines) {
5910
+ return filterCodexChrome(lines);
5911
+ }
5912
+ detectInteractivePrompt(lines) {
5913
+ return detectCodexSelector(lines);
5914
+ }
5853
5915
  };
5854
5916
  async function installCodexViaNpm() {
5855
5917
  return new Promise((resolve2, reject) => {
@@ -5978,9 +6040,17 @@ var ChromeStepTracker = class {
5978
6040
  this.history = [];
5979
6041
  this.sentCount = 0;
5980
6042
  }
5981
- /** Parse the rendered lines, append unseen steps to the cumulative history. */
5982
- ingest(lines) {
5983
- const visible = lines.filter((l) => isChromeLine(l)).map((l) => parseChromeLine(l)).filter((s) => s !== null);
6043
+ /**
6044
+ * Parse the rendered lines using the supplied per-agent parser,
6045
+ * appending unseen steps to the cumulative history.
6046
+ *
6047
+ * @param lines Screen lines from `renderToLines`.
6048
+ * @param parseLine Per-agent chrome parser — `runtime.parseTuiChrome`
6049
+ * bound to the active strategy, or a no-op `() => null`
6050
+ * when the agent doesn't surface tool-call chrome.
6051
+ */
6052
+ ingest(lines, parseLine2) {
6053
+ const visible = lines.map((l) => parseLine2(l)).filter((s) => s !== null);
5984
6054
  if (visible.length === 0) return;
5985
6055
  for (const step of visible) {
5986
6056
  const exists = this.history.some(
@@ -6191,18 +6261,13 @@ function hasPrintable(raw) {
6191
6261
  function renderLines(buffer) {
6192
6262
  return renderToLines(buffer);
6193
6263
  }
6194
- function detectAnySelector(lines) {
6195
- return detectSelector(lines) ?? detectListSelector(lines);
6196
- }
6197
- function extractContent(lines) {
6198
- return filterChrome(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
6199
- }
6200
6264
 
6201
6265
  // src/services/output.service.ts
6202
6266
  var OutputService = class _OutputService {
6203
6267
  pty = new PtyBuffer();
6204
6268
  steps = new ChromeStepTracker();
6205
6269
  emitter;
6270
+ runtime;
6206
6271
  lastSentContent = "";
6207
6272
  pollTimer = null;
6208
6273
  startTime = 0;
@@ -6227,7 +6292,7 @@ var OutputService = class _OutputService {
6227
6292
  static EMPTY_TIMEOUT_MS = 6e4;
6228
6293
  /** Hard turn cap — pathological no-op turns get cut after 2 minutes. */
6229
6294
  static MAX_MS = 12e4;
6230
- constructor(sessionId, pluginId, onSessionIdDetected, onRateLimitDetected, onTurnComplete, onTerminalTurnDetected, pluginAuthToken) {
6295
+ constructor(sessionId, pluginId, onSessionIdDetected, onRateLimitDetected, onTurnComplete, onTerminalTurnDetected, pluginAuthToken, runtime) {
6231
6296
  this.onSessionIdDetected = onSessionIdDetected;
6232
6297
  this.onRateLimitDetected = onRateLimitDetected;
6233
6298
  this.onTurnComplete = onTurnComplete;
@@ -6237,6 +6302,21 @@ var OutputService = class _OutputService {
6237
6302
  pluginId,
6238
6303
  pluginAuthToken
6239
6304
  });
6305
+ this.runtime = runtime ?? {
6306
+ id: "claude",
6307
+ meta: {},
6308
+ prepareLaunch: async () => ({ cmd: "", args: [] }),
6309
+ resumeLaunchArgs: () => [],
6310
+ resolveHistoryDir: () => null,
6311
+ parseHistoryFile: () => [],
6312
+ getCurrentUsage: () => null,
6313
+ fetchWeeklyUsage: async () => null,
6314
+ listModels: async () => [],
6315
+ changeModelInstruction: () => ({ type: "pty" }),
6316
+ summarizeInstruction: () => ({ ptyInput: "" }),
6317
+ filterTuiOutput: (lines) => lines,
6318
+ detectInteractivePrompt: () => null
6319
+ };
6240
6320
  }
6241
6321
  // ─── Turn lifecycle ──────────────────────────────────────────────
6242
6322
  /**
@@ -6333,13 +6413,14 @@ var OutputService = class _OutputService {
6333
6413
  }
6334
6414
  if (elapsed < _OutputService.WARMUP_MS) return;
6335
6415
  const lines = renderLines(this.pty.content);
6336
- this.steps.ingest(lines);
6416
+ const parseLine2 = this.runtime.parseTuiChrome?.bind(this.runtime) ?? (() => null);
6417
+ this.steps.ingest(lines, parseLine2);
6337
6418
  const stepsDelta = this.steps.consumeDelta();
6338
6419
  if (stepsDelta.length > 0) {
6339
6420
  this.send({ type: "chrome_steps", appendSteps: stepsDelta }).catch(() => {
6340
6421
  });
6341
6422
  }
6342
- const selector = detectAnySelector(lines);
6423
+ const selector = this.runtime.detectInteractivePrompt(lines);
6343
6424
  if (selector) {
6344
6425
  const idleMs2 = this.pty.lastPushTime > 0 ? now - this.pty.lastPushTime : elapsed;
6345
6426
  log.trace(
@@ -6364,7 +6445,7 @@ var OutputService = class _OutputService {
6364
6445
  }
6365
6446
  return;
6366
6447
  }
6367
- const content = extractContent(lines);
6448
+ const content = this.runtime.filterTuiOutput(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
6368
6449
  if (!content) {
6369
6450
  log.trace(
6370
6451
  "outputSvc",
@@ -6390,13 +6471,14 @@ var OutputService = class _OutputService {
6390
6471
  }
6391
6472
  finalize() {
6392
6473
  const lines = renderLines(this.pty.content);
6393
- this.steps.ingest(lines);
6474
+ const parseLine2 = this.runtime.parseTuiChrome?.bind(this.runtime) ?? (() => null);
6475
+ this.steps.ingest(lines, parseLine2);
6394
6476
  const stepsDelta = this.steps.consumeDelta();
6395
6477
  if (stepsDelta.length > 0) {
6396
6478
  this.send({ type: "chrome_steps", appendSteps: stepsDelta }).catch(() => {
6397
6479
  });
6398
6480
  }
6399
- const selector = detectAnySelector(lines);
6481
+ const selector = this.runtime.detectInteractivePrompt(lines);
6400
6482
  this.stopPoll();
6401
6483
  this.pty.deactivate();
6402
6484
  if (selector) {
@@ -6413,7 +6495,7 @@ var OutputService = class _OutputService {
6413
6495
  ).catch(() => {
6414
6496
  });
6415
6497
  } else {
6416
- const content = extractContent(lines);
6498
+ const content = this.runtime.filterTuiOutput(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
6417
6499
  this.send(
6418
6500
  { type: "text", content, done: true },
6419
6501
  { critical: true }
@@ -7741,7 +7823,8 @@ async function start() {
7741
7823
  const prevCount = historySvc.getCurrentMessageCount();
7742
7824
  historySvc.waitForNewUserMessage(prevCount).then((userText) => outputSvc.startTerminalTurn(userText ?? void 0)).catch(() => outputSvc.startTerminalTurn(void 0));
7743
7825
  },
7744
- session.pluginAuthToken
7826
+ session.pluginAuthToken,
7827
+ runtime
7745
7828
  );
7746
7829
  const claude = new AgentService(
7747
7830
  runtime,
@@ -10040,7 +10123,7 @@ async function stopWorkspaceFromLocal(target) {
10040
10123
  // src/commands/version.ts
10041
10124
  var import_picocolors11 = __toESM(require("picocolors"));
10042
10125
  function version() {
10043
- const v = true ? "2.12.1" : "unknown";
10126
+ const v = true ? "2.12.3" : "unknown";
10044
10127
  console.log(`${import_picocolors11.default.bold("codeam-cli")} ${import_picocolors11.default.cyan(v)}`);
10045
10128
  }
10046
10129
 
@@ -10175,7 +10258,7 @@ function checkForUpdates() {
10175
10258
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
10176
10259
  if (process.env.CI) return;
10177
10260
  if (!process.stdout.isTTY) return;
10178
- const current = true ? "2.12.1" : null;
10261
+ const current = true ? "2.12.3" : null;
10179
10262
  if (!current) return;
10180
10263
  const cache = readCache();
10181
10264
  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.12.1",
3
+ "version": "2.12.3",
4
4
  "description": "Remote control Claude Code (and other AI coding agents) from your mobile phone. Pair your device, send prompts, stream responses in real-time, and approve commands — from anywhere.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",