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.
- package/CHANGELOG.md +6 -0
- package/dist/index.js +454 -299
- 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.
|
|
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/
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
const
|
|
5419
|
-
if (!
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
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
|
|
5680
|
-
if (!import_node_fs2.default.existsSync(
|
|
5681
|
-
|
|
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
|
|
5684
|
-
if (!
|
|
5685
|
-
const
|
|
5686
|
-
|
|
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
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
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:
|
|
5702
|
-
role:
|
|
5703
|
-
text
|
|
5704
|
-
timestamp:
|
|
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(
|
|
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
|
-
/**
|
|
5905
|
-
|
|
5906
|
-
|
|
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.
|
|
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 =
|
|
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 =
|
|
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.
|
|
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 =
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|