codeam-cli 2.12.1 → 2.12.2

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 +335 -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.2",
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,46 @@ 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
+ function filterCodexChrome(lines) {
5805
+ const out = [];
5806
+ let skipEchoContinuation = false;
5807
+ for (const line of lines) {
5808
+ const t2 = line.trimEnd();
5809
+ const trimmed = t2.trimStart();
5810
+ if (!trimmed) {
5811
+ skipEchoContinuation = false;
5812
+ continue;
5813
+ }
5814
+ if (BOX_DRAW_RE.test(trimmed)) continue;
5815
+ 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;
5816
+ if (TIP_RE.test(t2) || LEARN_MORE_RE.test(t2)) continue;
5817
+ if (CODEX_AGENT_REPLY_RE.test(trimmed)) {
5818
+ skipEchoContinuation = false;
5819
+ out.push(t2.replace(/^(\s*)•\s/, "$1"));
5820
+ continue;
5821
+ }
5822
+ if (CODEX_USER_ECHO_RE.test(trimmed)) {
5823
+ skipEchoContinuation = true;
5824
+ continue;
5825
+ }
5826
+ if (skipEchoContinuation) continue;
5827
+ out.push(t2);
5828
+ }
5829
+ return out;
5830
+ }
5831
+ function parseCodexChrome(_line) {
5832
+ return null;
5833
+ }
5834
+ function detectCodexSelector(_lines) {
5835
+ return null;
5836
+ }
5837
+
5791
5838
  // src/agents/codex/runtime.ts
5792
5839
  var CODEX_CONTEXT_WINDOW = 272e3;
5793
5840
  var CODEX_MODELS = [
@@ -5850,6 +5897,16 @@ var CodexRuntimeStrategy = class {
5850
5897
  summarizeInstruction(_mode) {
5851
5898
  return { ptyInput: "/compact\r" };
5852
5899
  }
5900
+ // ─── TUI parser strategy methods ─────────────────────────────────
5901
+ parseTuiChrome(line) {
5902
+ return parseCodexChrome(line);
5903
+ }
5904
+ filterTuiOutput(lines) {
5905
+ return filterCodexChrome(lines);
5906
+ }
5907
+ detectInteractivePrompt(lines) {
5908
+ return detectCodexSelector(lines);
5909
+ }
5853
5910
  };
5854
5911
  async function installCodexViaNpm() {
5855
5912
  return new Promise((resolve2, reject) => {
@@ -5978,9 +6035,17 @@ var ChromeStepTracker = class {
5978
6035
  this.history = [];
5979
6036
  this.sentCount = 0;
5980
6037
  }
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);
6038
+ /**
6039
+ * Parse the rendered lines using the supplied per-agent parser,
6040
+ * appending unseen steps to the cumulative history.
6041
+ *
6042
+ * @param lines Screen lines from `renderToLines`.
6043
+ * @param parseLine Per-agent chrome parser — `runtime.parseTuiChrome`
6044
+ * bound to the active strategy, or a no-op `() => null`
6045
+ * when the agent doesn't surface tool-call chrome.
6046
+ */
6047
+ ingest(lines, parseLine2) {
6048
+ const visible = lines.map((l) => parseLine2(l)).filter((s) => s !== null);
5984
6049
  if (visible.length === 0) return;
5985
6050
  for (const step of visible) {
5986
6051
  const exists = this.history.some(
@@ -6191,18 +6256,13 @@ function hasPrintable(raw) {
6191
6256
  function renderLines(buffer) {
6192
6257
  return renderToLines(buffer);
6193
6258
  }
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
6259
 
6201
6260
  // src/services/output.service.ts
6202
6261
  var OutputService = class _OutputService {
6203
6262
  pty = new PtyBuffer();
6204
6263
  steps = new ChromeStepTracker();
6205
6264
  emitter;
6265
+ runtime;
6206
6266
  lastSentContent = "";
6207
6267
  pollTimer = null;
6208
6268
  startTime = 0;
@@ -6227,7 +6287,7 @@ var OutputService = class _OutputService {
6227
6287
  static EMPTY_TIMEOUT_MS = 6e4;
6228
6288
  /** Hard turn cap — pathological no-op turns get cut after 2 minutes. */
6229
6289
  static MAX_MS = 12e4;
6230
- constructor(sessionId, pluginId, onSessionIdDetected, onRateLimitDetected, onTurnComplete, onTerminalTurnDetected, pluginAuthToken) {
6290
+ constructor(sessionId, pluginId, onSessionIdDetected, onRateLimitDetected, onTurnComplete, onTerminalTurnDetected, pluginAuthToken, runtime) {
6231
6291
  this.onSessionIdDetected = onSessionIdDetected;
6232
6292
  this.onRateLimitDetected = onRateLimitDetected;
6233
6293
  this.onTurnComplete = onTurnComplete;
@@ -6237,6 +6297,21 @@ var OutputService = class _OutputService {
6237
6297
  pluginId,
6238
6298
  pluginAuthToken
6239
6299
  });
6300
+ this.runtime = runtime ?? {
6301
+ id: "claude",
6302
+ meta: {},
6303
+ prepareLaunch: async () => ({ cmd: "", args: [] }),
6304
+ resumeLaunchArgs: () => [],
6305
+ resolveHistoryDir: () => null,
6306
+ parseHistoryFile: () => [],
6307
+ getCurrentUsage: () => null,
6308
+ fetchWeeklyUsage: async () => null,
6309
+ listModels: async () => [],
6310
+ changeModelInstruction: () => ({ type: "pty" }),
6311
+ summarizeInstruction: () => ({ ptyInput: "" }),
6312
+ filterTuiOutput: (lines) => lines,
6313
+ detectInteractivePrompt: () => null
6314
+ };
6240
6315
  }
6241
6316
  // ─── Turn lifecycle ──────────────────────────────────────────────
6242
6317
  /**
@@ -6333,13 +6408,14 @@ var OutputService = class _OutputService {
6333
6408
  }
6334
6409
  if (elapsed < _OutputService.WARMUP_MS) return;
6335
6410
  const lines = renderLines(this.pty.content);
6336
- this.steps.ingest(lines);
6411
+ const parseLine2 = this.runtime.parseTuiChrome?.bind(this.runtime) ?? (() => null);
6412
+ this.steps.ingest(lines, parseLine2);
6337
6413
  const stepsDelta = this.steps.consumeDelta();
6338
6414
  if (stepsDelta.length > 0) {
6339
6415
  this.send({ type: "chrome_steps", appendSteps: stepsDelta }).catch(() => {
6340
6416
  });
6341
6417
  }
6342
- const selector = detectAnySelector(lines);
6418
+ const selector = this.runtime.detectInteractivePrompt(lines);
6343
6419
  if (selector) {
6344
6420
  const idleMs2 = this.pty.lastPushTime > 0 ? now - this.pty.lastPushTime : elapsed;
6345
6421
  log.trace(
@@ -6364,7 +6440,7 @@ var OutputService = class _OutputService {
6364
6440
  }
6365
6441
  return;
6366
6442
  }
6367
- const content = extractContent(lines);
6443
+ const content = this.runtime.filterTuiOutput(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
6368
6444
  if (!content) {
6369
6445
  log.trace(
6370
6446
  "outputSvc",
@@ -6390,13 +6466,14 @@ var OutputService = class _OutputService {
6390
6466
  }
6391
6467
  finalize() {
6392
6468
  const lines = renderLines(this.pty.content);
6393
- this.steps.ingest(lines);
6469
+ const parseLine2 = this.runtime.parseTuiChrome?.bind(this.runtime) ?? (() => null);
6470
+ this.steps.ingest(lines, parseLine2);
6394
6471
  const stepsDelta = this.steps.consumeDelta();
6395
6472
  if (stepsDelta.length > 0) {
6396
6473
  this.send({ type: "chrome_steps", appendSteps: stepsDelta }).catch(() => {
6397
6474
  });
6398
6475
  }
6399
- const selector = detectAnySelector(lines);
6476
+ const selector = this.runtime.detectInteractivePrompt(lines);
6400
6477
  this.stopPoll();
6401
6478
  this.pty.deactivate();
6402
6479
  if (selector) {
@@ -6413,7 +6490,7 @@ var OutputService = class _OutputService {
6413
6490
  ).catch(() => {
6414
6491
  });
6415
6492
  } else {
6416
- const content = extractContent(lines);
6493
+ const content = this.runtime.filterTuiOutput(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
6417
6494
  this.send(
6418
6495
  { type: "text", content, done: true },
6419
6496
  { critical: true }
@@ -7741,7 +7818,8 @@ async function start() {
7741
7818
  const prevCount = historySvc.getCurrentMessageCount();
7742
7819
  historySvc.waitForNewUserMessage(prevCount).then((userText) => outputSvc.startTerminalTurn(userText ?? void 0)).catch(() => outputSvc.startTerminalTurn(void 0));
7743
7820
  },
7744
- session.pluginAuthToken
7821
+ session.pluginAuthToken,
7822
+ runtime
7745
7823
  );
7746
7824
  const claude = new AgentService(
7747
7825
  runtime,
@@ -10040,7 +10118,7 @@ async function stopWorkspaceFromLocal(target) {
10040
10118
  // src/commands/version.ts
10041
10119
  var import_picocolors11 = __toESM(require("picocolors"));
10042
10120
  function version() {
10043
- const v = true ? "2.12.1" : "unknown";
10121
+ const v = true ? "2.12.2" : "unknown";
10044
10122
  console.log(`${import_picocolors11.default.bold("codeam-cli")} ${import_picocolors11.default.cyan(v)}`);
10045
10123
  }
10046
10124
 
@@ -10175,7 +10253,7 @@ function checkForUpdates() {
10175
10253
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
10176
10254
  if (process.env.CI) return;
10177
10255
  if (!process.stdout.isTTY) return;
10178
- const current = true ? "2.12.1" : null;
10256
+ const current = true ? "2.12.2" : null;
10179
10257
  if (!current) return;
10180
10258
  const cache = readCache();
10181
10259
  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.2",
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",