codeam-cli 2.12.0 → 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 +454 -299
  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.0",
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,28 +5173,261 @@ function parseHistoryFile(filePath) {
5410
5173
  return out;
5411
5174
  }
5412
5175
 
5413
- // src/agents/claude/runtime.ts
5414
- var ClaudeRuntimeStrategy = class {
5415
- id = "claude";
5416
- meta = getAgent("claude");
5417
- async prepareLaunch() {
5418
- const launch = buildClaudeLaunch();
5419
- if (!launch) throw new Error("claude binary not found in PATH");
5420
- return { cmd: launch.cmd, args: launch.args };
5421
- }
5422
- resumeLaunchArgs(sessionId) {
5423
- return ["--resume", sessionId, "--dangerously-skip-permissions"];
5424
- }
5425
- resolveHistoryDir(cwd) {
5426
- return resolveHistoryDir(cwd);
5427
- }
5428
- parseHistoryFile(filePath) {
5429
- return parseHistoryFile(filePath);
5430
- }
5431
- getCurrentUsage(historyDir) {
5432
- return getCurrentUsage(historyDir);
5433
- }
5434
- async fetchWeeklyUsage() {
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
+
5409
+ // src/agents/claude/runtime.ts
5410
+ var ClaudeRuntimeStrategy = class {
5411
+ id = "claude";
5412
+ meta = getAgent("claude");
5413
+ async prepareLaunch() {
5414
+ const launch = buildClaudeLaunch();
5415
+ if (!launch) throw new Error("claude binary not found in PATH");
5416
+ return { cmd: launch.cmd, args: launch.args };
5417
+ }
5418
+ resumeLaunchArgs(sessionId) {
5419
+ return ["--resume", sessionId, "--dangerously-skip-permissions"];
5420
+ }
5421
+ resolveHistoryDir(cwd) {
5422
+ return resolveHistoryDir(cwd);
5423
+ }
5424
+ parseHistoryFile(filePath) {
5425
+ return parseHistoryFile(filePath);
5426
+ }
5427
+ getCurrentUsage(historyDir) {
5428
+ return getCurrentUsage(historyDir);
5429
+ }
5430
+ async fetchWeeklyUsage() {
5435
5431
  return fetchClaudeQuota();
5436
5432
  }
5437
5433
  async listModels() {
@@ -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
@@ -5676,38 +5683,155 @@ var import_node_path2 = __toESM(require("path"));
5676
5683
  var import_node_os = __toESM(require("os"));
5677
5684
  function resolveHistoryDir2(_cwd, homeOverride) {
5678
5685
  const home = homeOverride ?? import_node_os.default.homedir();
5679
- const codexDir = import_node_path2.default.join(home, ".codex");
5680
- if (!import_node_fs2.default.existsSync(import_node_path2.default.join(codexDir, "history.jsonl"))) return null;
5681
- return codexDir;
5686
+ const sessionsRoot = import_node_path2.default.join(home, ".codex", "sessions");
5687
+ if (!import_node_fs2.default.existsSync(sessionsRoot)) return null;
5688
+ const now = /* @__PURE__ */ new Date();
5689
+ const yyyy = String(now.getUTCFullYear());
5690
+ const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
5691
+ const dd = String(now.getUTCDate()).padStart(2, "0");
5692
+ const todayDir = import_node_path2.default.join(sessionsRoot, yyyy, mm, dd);
5693
+ if (!import_node_fs2.default.existsSync(todayDir)) return null;
5694
+ return todayDir;
5695
+ }
5696
+ function parseLine(line) {
5697
+ try {
5698
+ const parsed = JSON.parse(line);
5699
+ if (parsed && typeof parsed === "object" && typeof parsed.timestamp === "string" && typeof parsed.type === "string") {
5700
+ return parsed;
5701
+ }
5702
+ } catch {
5703
+ }
5704
+ return null;
5682
5705
  }
5683
- function isCodexRecord(v) {
5684
- if (!v || typeof v !== "object") return false;
5685
- const r = v;
5686
- return typeof r.session_id === "string" && typeof r.ts === "number" && typeof r.text === "string";
5706
+ function extractMessageText(content) {
5707
+ if (!Array.isArray(content)) return "";
5708
+ const parts = [];
5709
+ for (const block of content) {
5710
+ if (!block || typeof block !== "object") continue;
5711
+ if (typeof block.text === "string" && block.text.length > 0) {
5712
+ if (block.type === "input_text" || block.type === "output_text") {
5713
+ parts.push(block.text);
5714
+ }
5715
+ }
5716
+ }
5717
+ return parts.join("\n");
5718
+ }
5719
+ function mapRole(codexRole) {
5720
+ if (codexRole === "user") return "user";
5721
+ if (codexRole === "assistant") return "agent";
5722
+ return "system";
5687
5723
  }
5688
5724
  function parseHistoryFile2(filePath) {
5689
5725
  const raw = import_node_fs2.default.readFileSync(filePath, "utf8");
5726
+ const lines = raw.split("\n").filter(Boolean);
5727
+ for (const line of lines) {
5728
+ const rec = parseLine(line);
5729
+ if (!rec) continue;
5730
+ if (rec.type === "session_meta") {
5731
+ const meta = rec.payload;
5732
+ if (meta && typeof meta.cwd === "string") {
5733
+ let resolvedMeta;
5734
+ let resolvedCurrent;
5735
+ try {
5736
+ resolvedMeta = import_node_fs2.default.realpathSync(meta.cwd);
5737
+ resolvedCurrent = import_node_fs2.default.realpathSync(process.cwd());
5738
+ } catch {
5739
+ resolvedMeta = import_node_path2.default.resolve(meta.cwd);
5740
+ resolvedCurrent = import_node_path2.default.resolve(process.cwd());
5741
+ }
5742
+ if (resolvedMeta !== resolvedCurrent) {
5743
+ return [];
5744
+ }
5745
+ }
5746
+ break;
5747
+ }
5748
+ }
5690
5749
  const out = [];
5691
5750
  let idx = 0;
5692
- for (const line of raw.split("\n").filter(Boolean)) {
5693
- let rec;
5694
- try {
5695
- rec = JSON.parse(line);
5696
- } catch {
5697
- continue;
5698
- }
5699
- if (!isCodexRecord(rec)) continue;
5751
+ for (const line of lines) {
5752
+ const rec = parseLine(line);
5753
+ if (!rec) continue;
5754
+ if (rec.type !== "response_item") continue;
5755
+ const payload = rec.payload;
5756
+ const msg = payload?.Message;
5757
+ if (!msg) continue;
5758
+ const text = extractMessageText(msg.content);
5759
+ if (!text) continue;
5700
5760
  out.push({
5701
- id: `${rec.session_id}:${idx}`,
5702
- role: "user",
5703
- text: rec.text,
5704
- timestamp: new Date(rec.ts * 1e3).toISOString()
5761
+ id: `rollout:${idx}`,
5762
+ role: mapRole(msg.role),
5763
+ text,
5764
+ timestamp: rec.timestamp
5705
5765
  });
5706
5766
  idx++;
5707
5767
  }
5708
5768
  return out;
5709
5769
  }
5710
- function getCurrentUsage2(_historyDir) {
5770
+ function getCurrentUsage2(historyDir) {
5771
+ if (!import_node_fs2.default.existsSync(historyDir)) return null;
5772
+ const files = import_node_fs2.default.readdirSync(historyDir).filter((f) => f.startsWith("rollout-") && f.endsWith(".jsonl")).map((f) => ({ name: f, full: import_node_path2.default.join(historyDir, f) })).map((e) => ({ ...e, mtime: import_node_fs2.default.statSync(e.full).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
5773
+ if (files.length === 0) return null;
5774
+ const latest = files[0].full;
5775
+ const raw = import_node_fs2.default.readFileSync(latest, "utf8");
5776
+ let lastTokenCount = null;
5777
+ for (const line of raw.split("\n").filter(Boolean)) {
5778
+ const rec = parseLine(line);
5779
+ if (!rec || rec.type !== "event_msg") continue;
5780
+ const payload = rec.payload;
5781
+ const info = payload?.TokenCount?.info;
5782
+ const total2 = info?.total_token_usage?.total_tokens;
5783
+ const window = info?.model_context_window;
5784
+ if (typeof total2 === "number" && typeof window === "number" && window > 0) {
5785
+ lastTokenCount = { total: total2, window };
5786
+ }
5787
+ }
5788
+ if (!lastTokenCount) return null;
5789
+ const used = lastTokenCount.total;
5790
+ const total = lastTokenCount.window;
5791
+ return {
5792
+ used,
5793
+ total,
5794
+ percent: Math.min(100, Math.round(used / total * 100))
5795
+ };
5796
+ }
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) {
5711
5835
  return null;
5712
5836
  }
5713
5837
 
@@ -5773,6 +5897,16 @@ var CodexRuntimeStrategy = class {
5773
5897
  summarizeInstruction(_mode) {
5774
5898
  return { ptyInput: "/compact\r" };
5775
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
+ }
5776
5910
  };
5777
5911
  async function installCodexViaNpm() {
5778
5912
  return new Promise((resolve2, reject) => {
@@ -5901,9 +6035,17 @@ var ChromeStepTracker = class {
5901
6035
  this.history = [];
5902
6036
  this.sentCount = 0;
5903
6037
  }
5904
- /** Parse the rendered lines, append unseen steps to the cumulative history. */
5905
- ingest(lines) {
5906
- 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);
5907
6049
  if (visible.length === 0) return;
5908
6050
  for (const step of visible) {
5909
6051
  const exists = this.history.some(
@@ -6114,18 +6256,13 @@ function hasPrintable(raw) {
6114
6256
  function renderLines(buffer) {
6115
6257
  return renderToLines(buffer);
6116
6258
  }
6117
- function detectAnySelector(lines) {
6118
- return detectSelector(lines) ?? detectListSelector(lines);
6119
- }
6120
- function extractContent(lines) {
6121
- return filterChrome(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
6122
- }
6123
6259
 
6124
6260
  // src/services/output.service.ts
6125
6261
  var OutputService = class _OutputService {
6126
6262
  pty = new PtyBuffer();
6127
6263
  steps = new ChromeStepTracker();
6128
6264
  emitter;
6265
+ runtime;
6129
6266
  lastSentContent = "";
6130
6267
  pollTimer = null;
6131
6268
  startTime = 0;
@@ -6150,7 +6287,7 @@ var OutputService = class _OutputService {
6150
6287
  static EMPTY_TIMEOUT_MS = 6e4;
6151
6288
  /** Hard turn cap — pathological no-op turns get cut after 2 minutes. */
6152
6289
  static MAX_MS = 12e4;
6153
- constructor(sessionId, pluginId, onSessionIdDetected, onRateLimitDetected, onTurnComplete, onTerminalTurnDetected, pluginAuthToken) {
6290
+ constructor(sessionId, pluginId, onSessionIdDetected, onRateLimitDetected, onTurnComplete, onTerminalTurnDetected, pluginAuthToken, runtime) {
6154
6291
  this.onSessionIdDetected = onSessionIdDetected;
6155
6292
  this.onRateLimitDetected = onRateLimitDetected;
6156
6293
  this.onTurnComplete = onTurnComplete;
@@ -6160,6 +6297,21 @@ var OutputService = class _OutputService {
6160
6297
  pluginId,
6161
6298
  pluginAuthToken
6162
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
+ };
6163
6315
  }
6164
6316
  // ─── Turn lifecycle ──────────────────────────────────────────────
6165
6317
  /**
@@ -6256,13 +6408,14 @@ var OutputService = class _OutputService {
6256
6408
  }
6257
6409
  if (elapsed < _OutputService.WARMUP_MS) return;
6258
6410
  const lines = renderLines(this.pty.content);
6259
- this.steps.ingest(lines);
6411
+ const parseLine2 = this.runtime.parseTuiChrome?.bind(this.runtime) ?? (() => null);
6412
+ this.steps.ingest(lines, parseLine2);
6260
6413
  const stepsDelta = this.steps.consumeDelta();
6261
6414
  if (stepsDelta.length > 0) {
6262
6415
  this.send({ type: "chrome_steps", appendSteps: stepsDelta }).catch(() => {
6263
6416
  });
6264
6417
  }
6265
- const selector = detectAnySelector(lines);
6418
+ const selector = this.runtime.detectInteractivePrompt(lines);
6266
6419
  if (selector) {
6267
6420
  const idleMs2 = this.pty.lastPushTime > 0 ? now - this.pty.lastPushTime : elapsed;
6268
6421
  log.trace(
@@ -6287,7 +6440,7 @@ var OutputService = class _OutputService {
6287
6440
  }
6288
6441
  return;
6289
6442
  }
6290
- const content = extractContent(lines);
6443
+ const content = this.runtime.filterTuiOutput(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
6291
6444
  if (!content) {
6292
6445
  log.trace(
6293
6446
  "outputSvc",
@@ -6313,13 +6466,14 @@ var OutputService = class _OutputService {
6313
6466
  }
6314
6467
  finalize() {
6315
6468
  const lines = renderLines(this.pty.content);
6316
- this.steps.ingest(lines);
6469
+ const parseLine2 = this.runtime.parseTuiChrome?.bind(this.runtime) ?? (() => null);
6470
+ this.steps.ingest(lines, parseLine2);
6317
6471
  const stepsDelta = this.steps.consumeDelta();
6318
6472
  if (stepsDelta.length > 0) {
6319
6473
  this.send({ type: "chrome_steps", appendSteps: stepsDelta }).catch(() => {
6320
6474
  });
6321
6475
  }
6322
- const selector = detectAnySelector(lines);
6476
+ const selector = this.runtime.detectInteractivePrompt(lines);
6323
6477
  this.stopPoll();
6324
6478
  this.pty.deactivate();
6325
6479
  if (selector) {
@@ -6336,7 +6490,7 @@ var OutputService = class _OutputService {
6336
6490
  ).catch(() => {
6337
6491
  });
6338
6492
  } else {
6339
- const content = extractContent(lines);
6493
+ const content = this.runtime.filterTuiOutput(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
6340
6494
  this.send(
6341
6495
  { type: "text", content, done: true },
6342
6496
  { critical: true }
@@ -7664,7 +7818,8 @@ async function start() {
7664
7818
  const prevCount = historySvc.getCurrentMessageCount();
7665
7819
  historySvc.waitForNewUserMessage(prevCount).then((userText) => outputSvc.startTerminalTurn(userText ?? void 0)).catch(() => outputSvc.startTerminalTurn(void 0));
7666
7820
  },
7667
- session.pluginAuthToken
7821
+ session.pluginAuthToken,
7822
+ runtime
7668
7823
  );
7669
7824
  const claude = new AgentService(
7670
7825
  runtime,
@@ -9963,7 +10118,7 @@ async function stopWorkspaceFromLocal(target) {
9963
10118
  // src/commands/version.ts
9964
10119
  var import_picocolors11 = __toESM(require("picocolors"));
9965
10120
  function version() {
9966
- const v = true ? "2.12.0" : "unknown";
10121
+ const v = true ? "2.12.2" : "unknown";
9967
10122
  console.log(`${import_picocolors11.default.bold("codeam-cli")} ${import_picocolors11.default.cyan(v)}`);
9968
10123
  }
9969
10124
 
@@ -10098,7 +10253,7 @@ function checkForUpdates() {
10098
10253
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
10099
10254
  if (process.env.CI) return;
10100
10255
  if (!process.stdout.isTTY) return;
10101
- const current = true ? "2.12.0" : null;
10256
+ const current = true ? "2.12.2" : null;
10102
10257
  if (!current) return;
10103
10258
  const cache = readCache();
10104
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.0",
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",