codeam-cli 2.10.7 → 2.11.0

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 +22 -0
  2. package/dist/index.js +1358 -924
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -519,9 +519,9 @@ var require_windowsPtyAgent = __commonJS({
519
519
  "use strict";
520
520
  Object.defineProperty(exports2, "__esModule", { value: true });
521
521
  exports2.argsToCommandLine = exports2.WindowsPtyAgent = void 0;
522
- var fs13 = require("fs");
523
- var os12 = require("os");
524
- var path19 = require("path");
522
+ var fs15 = require("fs");
523
+ var os14 = require("os");
524
+ var path21 = require("path");
525
525
  var child_process_1 = require("child_process");
526
526
  var net_1 = require("net");
527
527
  var windowsConoutConnection_1 = require_windowsConoutConnection();
@@ -557,7 +557,7 @@ var require_windowsPtyAgent = __commonJS({
557
557
  }
558
558
  }
559
559
  this._ptyNative = this._useConpty ? conptyNative : winptyNative;
560
- cwd = path19.resolve(cwd);
560
+ cwd = path21.resolve(cwd);
561
561
  var commandLine = argsToCommandLine(file, args2);
562
562
  var term;
563
563
  if (this._useConpty) {
@@ -578,7 +578,7 @@ var require_windowsPtyAgent = __commonJS({
578
578
  this._outSocket.on("connect", function() {
579
579
  _this._outSocket.emit("ready_datapipe");
580
580
  });
581
- var inSocketFD = fs13.openSync(term.conin, "w");
581
+ var inSocketFD = fs15.openSync(term.conin, "w");
582
582
  this._inSocket = new net_1.Socket({
583
583
  fd: inSocketFD,
584
584
  readable: false,
@@ -679,7 +679,7 @@ var require_windowsPtyAgent = __commonJS({
679
679
  WindowsPtyAgent2.prototype._getConsoleProcessList = function() {
680
680
  var _this = this;
681
681
  return new Promise(function(resolve2) {
682
- var agent = child_process_1.fork(path19.join(__dirname, "conpty_console_list_agent"), [_this._innerPid.toString()]);
682
+ var agent = child_process_1.fork(path21.join(__dirname, "conpty_console_list_agent"), [_this._innerPid.toString()]);
683
683
  agent.on("message", function(message) {
684
684
  clearTimeout(timeout);
685
685
  resolve2(message.consoleProcessList);
@@ -702,7 +702,7 @@ var require_windowsPtyAgent = __commonJS({
702
702
  configurable: true
703
703
  });
704
704
  WindowsPtyAgent2.prototype._getWindowsBuildNumber = function() {
705
- var osVersion = /(\d+)\.(\d+)\.(\d+)/g.exec(os12.release());
705
+ var osVersion = /(\d+)\.(\d+)\.(\d+)/g.exec(os14.release());
706
706
  var buildNumber = 0;
707
707
  if (osVersion && osVersion.length === 4) {
708
708
  buildNumber = parseInt(osVersion[3]);
@@ -1012,15 +1012,15 @@ var require_unixTerminal = __commonJS({
1012
1012
  })();
1013
1013
  Object.defineProperty(exports2, "__esModule", { value: true });
1014
1014
  exports2.UnixTerminal = void 0;
1015
- var fs13 = require("fs");
1016
- var path19 = require("path");
1015
+ var fs15 = require("fs");
1016
+ var path21 = require("path");
1017
1017
  var tty = require("tty");
1018
1018
  var terminal_1 = require_terminal();
1019
1019
  var utils_1 = require_utils();
1020
1020
  var native = utils_1.loadNativeModule("pty");
1021
1021
  var pty = native.module;
1022
1022
  var helperPath = native.dir + "/spawn-helper";
1023
- helperPath = path19.resolve(__dirname, helperPath);
1023
+ helperPath = path21.resolve(__dirname, helperPath);
1024
1024
  helperPath = helperPath.replace("app.asar", "app.asar.unpacked");
1025
1025
  helperPath = helperPath.replace("node_modules.asar", "node_modules.asar.unpacked");
1026
1026
  var DEFAULT_FILE = "sh";
@@ -1275,7 +1275,7 @@ var require_unixTerminal = __commonJS({
1275
1275
  return;
1276
1276
  }
1277
1277
  var task = this._writeQueue[0];
1278
- fs13.write(this._fd, task.buffer, task.offset, function(err, written) {
1278
+ fs15.write(this._fd, task.buffer, task.offset, function(err, written) {
1279
1279
  if (err) {
1280
1280
  if ("code" in err && err.code === "EAGAIN") {
1281
1281
  _this._writeImmediate = setImmediate(function() {
@@ -1392,6 +1392,419 @@ 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
+ // ../../packages/shared/src/protocol/renderToLines.ts
1477
+ function renderToLines(raw) {
1478
+ const screen = [""];
1479
+ let row = 0;
1480
+ let col = 0;
1481
+ function ensureRow() {
1482
+ while (screen.length <= row) screen.push("");
1483
+ }
1484
+ function writeChar(ch) {
1485
+ ensureRow();
1486
+ if (col < screen[row].length) {
1487
+ screen[row] = screen[row].slice(0, col) + ch + screen[row].slice(col + 1);
1488
+ } else {
1489
+ while (screen[row].length < col) screen[row] += " ";
1490
+ screen[row] += ch;
1491
+ }
1492
+ col++;
1493
+ }
1494
+ let i = 0;
1495
+ while (i < raw.length) {
1496
+ const ch = raw[i];
1497
+ if (ch === "\x1B") {
1498
+ i++;
1499
+ if (i >= raw.length) break;
1500
+ if (raw[i] === "[") {
1501
+ i++;
1502
+ let param = "";
1503
+ while (i < raw.length && !/[@-~]/.test(raw[i])) param += raw[i++];
1504
+ const cmd = raw[i] ?? "";
1505
+ const n = parseInt(param) || 1;
1506
+ if (cmd === "A") {
1507
+ row = Math.max(0, row - n);
1508
+ } else if (cmd === "B") {
1509
+ row += n;
1510
+ ensureRow();
1511
+ } else if (cmd === "C") {
1512
+ col += n;
1513
+ } else if (cmd === "D") {
1514
+ col = Math.max(0, col - n);
1515
+ } else if (cmd === "G") {
1516
+ col = Math.max(0, n - 1);
1517
+ } else if (cmd === "H" || cmd === "f") {
1518
+ const p2 = param.split(";");
1519
+ row = Math.max(0, (parseInt(p2[0] ?? "1") || 1) - 1);
1520
+ col = Math.max(0, (parseInt(p2[1] ?? "1") || 1) - 1);
1521
+ ensureRow();
1522
+ } else if (cmd === "J") {
1523
+ if (param === "2" || param === "3") {
1524
+ screen.length = 1;
1525
+ screen[0] = "";
1526
+ row = 0;
1527
+ col = 0;
1528
+ } else if (param === "1") {
1529
+ for (let r = 0; r < row; r++) screen[r] = "";
1530
+ screen[row] = " ".repeat(col) + screen[row].slice(col);
1531
+ } else {
1532
+ screen[row] = screen[row].slice(0, col);
1533
+ screen.splice(row + 1);
1534
+ }
1535
+ } else if (cmd === "K") {
1536
+ ensureRow();
1537
+ if (param === "" || param === "0") screen[row] = screen[row].slice(0, col);
1538
+ else if (param === "1") screen[row] = " ".repeat(col) + screen[row].slice(col);
1539
+ else if (param === "2") screen[row] = "";
1540
+ } else if (cmd === "h" && (param === "?1049" || param === "?47")) {
1541
+ screen.length = 1;
1542
+ screen[0] = "";
1543
+ row = 0;
1544
+ col = 0;
1545
+ } else if (cmd === "l" && (param === "?1049" || param === "?47")) {
1546
+ screen.length = 1;
1547
+ screen[0] = "";
1548
+ row = 0;
1549
+ col = 0;
1550
+ }
1551
+ } else if (raw[i] === "]") {
1552
+ i++;
1553
+ while (i < raw.length) {
1554
+ if (raw[i] === "\x07") break;
1555
+ if (raw[i] === "\x1B" && i + 1 < raw.length && raw[i + 1] === "\\") {
1556
+ i++;
1557
+ break;
1558
+ }
1559
+ i++;
1560
+ }
1561
+ }
1562
+ } else if (ch === "\r") {
1563
+ if (i + 1 < raw.length && raw[i + 1] === "\n") {
1564
+ row++;
1565
+ col = 0;
1566
+ ensureRow();
1567
+ i++;
1568
+ } else {
1569
+ col = 0;
1570
+ }
1571
+ } else if (ch === "\n") {
1572
+ row++;
1573
+ col = 0;
1574
+ ensureRow();
1575
+ } else if (ch >= " " || ch === " ") {
1576
+ writeChar(ch);
1577
+ }
1578
+ i++;
1579
+ }
1580
+ return screen;
1581
+ }
1582
+
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
+ // ../../packages/shared/src/models/pricing.ts
1740
+ var MODEL_PRICING = {
1741
+ "claude-sonnet-4": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
1742
+ "claude-opus-4": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
1743
+ "claude-3-5-sonnet": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
1744
+ "claude-3-5-haiku": { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
1745
+ "claude-3-haiku": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 }
1746
+ };
1747
+ var MODEL_CONTEXT_WINDOW = {
1748
+ "claude-opus-4": 1e6,
1749
+ "claude-sonnet-4": 1e6,
1750
+ "claude-3-5-sonnet": 2e5,
1751
+ "claude-3-5-haiku": 2e5,
1752
+ "claude-3-haiku": 2e5
1753
+ };
1754
+ var DEFAULT_CONTEXT_WINDOW = 2e5;
1755
+ function getPricing(model) {
1756
+ for (const [prefix, pricing] of Object.entries(MODEL_PRICING)) {
1757
+ if (model.startsWith(prefix)) return pricing;
1758
+ }
1759
+ return MODEL_PRICING["claude-sonnet-4"];
1760
+ }
1761
+ function getContextWindow(model) {
1762
+ if (!model) return DEFAULT_CONTEXT_WINDOW;
1763
+ for (const [prefix, size] of Object.entries(MODEL_CONTEXT_WINDOW)) {
1764
+ if (model.startsWith(prefix)) return size;
1765
+ }
1766
+ return DEFAULT_CONTEXT_WINDOW;
1767
+ }
1768
+
1769
+ // ../../packages/shared/src/agents/registry.ts
1770
+ var AGENT_REGISTRY = {
1771
+ claude: {
1772
+ id: "claude",
1773
+ displayName: "Claude Code",
1774
+ binaryName: "claude",
1775
+ enabled: true,
1776
+ supportedAuthKinds: ["oauth_token", "api_key"],
1777
+ preferredAuthKind: "oauth_token"
1778
+ },
1779
+ codex: {
1780
+ id: "codex",
1781
+ displayName: "Codex CLI",
1782
+ binaryName: "codex",
1783
+ enabled: false,
1784
+ supportedAuthKinds: ["oauth_token", "api_key"],
1785
+ preferredAuthKind: "oauth_token"
1786
+ },
1787
+ copilot: {
1788
+ id: "copilot",
1789
+ displayName: "GitHub Copilot CLI",
1790
+ binaryName: "gh",
1791
+ enabled: false,
1792
+ supportedAuthKinds: ["oauth_token"],
1793
+ preferredAuthKind: "oauth_token"
1794
+ }
1795
+ };
1796
+ function getEnabledAgents() {
1797
+ return Object.values(AGENT_REGISTRY).filter((m) => m.enabled);
1798
+ }
1799
+ function getAgent(id) {
1800
+ const meta = AGENT_REGISTRY[id];
1801
+ if (!meta) throw new Error(`Unknown agent id: ${id}`);
1802
+ return meta;
1803
+ }
1804
+ function isKnownAgentId(id) {
1805
+ return id === "claude" || id === "codex" || id === "copilot";
1806
+ }
1807
+
1395
1808
  // src/config.ts
1396
1809
  var fs = __toESM(require("fs"));
1397
1810
  var os = __toESM(require("os"));
@@ -1402,6 +1815,9 @@ var EMPTY_CONFIG = () => ({
1402
1815
  activeSessionId: null,
1403
1816
  sessions: []
1404
1817
  });
1818
+ function migrateSession(s) {
1819
+ return { ...s, agent: s.agent ?? "claude" };
1820
+ }
1405
1821
  function makeConfig(baseDir) {
1406
1822
  const dir = path.join(baseDir ?? os.homedir(), ".codeam");
1407
1823
  const file = path.join(dir, "config.json");
@@ -1411,7 +1827,8 @@ function makeConfig(baseDir) {
1411
1827
  return {
1412
1828
  pluginId: typeof raw.pluginId === "string" ? raw.pluginId : crypto.randomUUID(),
1413
1829
  activeSessionId: typeof raw.activeSessionId === "string" ? raw.activeSessionId : null,
1414
- sessions: Array.isArray(raw.sessions) ? raw.sessions : []
1830
+ sessions: Array.isArray(raw.sessions) ? raw.sessions.map(migrateSession) : [],
1831
+ preferredAgent: typeof raw.preferredAgent === "string" ? raw.preferredAgent : void 0
1415
1832
  };
1416
1833
  } catch {
1417
1834
  return EMPTY_CONFIG();
@@ -1465,10 +1882,16 @@ function makeConfig(baseDir) {
1465
1882
  } catch {
1466
1883
  }
1467
1884
  }
1468
- return { getConfig: getConfig2, ensurePluginId: ensurePluginId2, addSession: addSession2, removeSession: removeSession2, setActiveSession: setActiveSession2, getActiveSession: getActiveSession2, clearAll: clearAll2 };
1885
+ function saveCliConfig2(c2) {
1886
+ save(c2);
1887
+ }
1888
+ function loadCliConfig2() {
1889
+ return load();
1890
+ }
1891
+ return { getConfig: getConfig2, ensurePluginId: ensurePluginId2, addSession: addSession2, removeSession: removeSession2, setActiveSession: setActiveSession2, getActiveSession: getActiveSession2, clearAll: clearAll2, saveCliConfig: saveCliConfig2, loadCliConfig: loadCliConfig2 };
1469
1892
  }
1470
1893
  var _default = makeConfig();
1471
- var { getConfig, ensurePluginId, addSession, removeSession, setActiveSession, getActiveSession, clearAll } = _default;
1894
+ var { getConfig, ensurePluginId, addSession, removeSession, setActiveSession, getActiveSession, clearAll, saveCliConfig, loadCliConfig } = _default;
1472
1895
 
1473
1896
  // src/ui/banner.ts
1474
1897
  var import_picocolors = __toESM(require("picocolors"));
@@ -1477,7 +1900,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
1477
1900
  // package.json
1478
1901
  var package_default = {
1479
1902
  name: "codeam-cli",
1480
- version: "2.10.7",
1903
+ version: "2.11.0",
1481
1904
  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.",
1482
1905
  type: "commonjs",
1483
1906
  main: "dist/index.js",
@@ -1848,6 +2271,13 @@ var CommandRelayService = class {
1848
2271
  /** Polling backoff state (only used on the fallback). */
1849
2272
  pollTimer = null;
1850
2273
  pollFailures = 0;
2274
+ // Successive polls that returned no commands. Drives an idle
2275
+ // backoff so a CLI sitting on the polling fallback (SSE was
2276
+ // unavailable) doesn't keep hammering the API every 2 s when
2277
+ // there's nothing to do. Reset to 0 whenever a poll DELIVERS a
2278
+ // command, so the first real work after a quiet period still
2279
+ // reaches the CLI quickly.
2280
+ pollEmptyStreak = 0;
1851
2281
  /** Reconnect backoff state for the SSE stream. */
1852
2282
  sseFailures = 0;
1853
2283
  sseReconnectTimer = null;
@@ -1976,7 +2406,9 @@ var CommandRelayService = class {
1976
2406
  if (!this._running) return;
1977
2407
  await this.pollOnce();
1978
2408
  if (this._running) {
1979
- const delay = computePollDelay({ baseMs: 2e3, failures: this.pollFailures });
2409
+ const idleExp = Math.min(this.pollEmptyStreak, 4);
2410
+ const effectiveFailures = Math.max(this.pollFailures, idleExp);
2411
+ const delay = computePollDelay({ baseMs: 2e3, failures: effectiveFailures });
1980
2412
  this.pollTimer = setTimeout(() => this.pollLoop(), delay);
1981
2413
  }
1982
2414
  }
@@ -1985,7 +2417,11 @@ var CommandRelayService = class {
1985
2417
  const data = await _getJson(`${API_BASE2}/api/commands/pending?pluginId=${this.pluginId}`);
1986
2418
  const commands = data?.data;
1987
2419
  this.pollFailures = 0;
1988
- if (!Array.isArray(commands) || commands.length === 0) return;
2420
+ if (!Array.isArray(commands) || commands.length === 0) {
2421
+ this.pollEmptyStreak += 1;
2422
+ return;
2423
+ }
2424
+ this.pollEmptyStreak = 0;
1989
2425
  log.trace("relay", `poll received ${commands.length} command(s)`);
1990
2426
  await this.dispatchCommands(commands);
1991
2427
  } catch (err) {
@@ -2380,7 +2816,7 @@ var WindowsConPtyStrategy = class _WindowsConPtyStrategy {
2380
2816
  resizeHandler = null;
2381
2817
  /**
2382
2818
  * Factory that returns a working ConPTY strategy or `null` if
2383
- * node-pty can't load. The caller (claude.service.ts) decides
2819
+ * node-pty can't load. The caller (agent.service.ts) decides
2384
2820
  * whether to fall back to the legacy pipe strategy.
2385
2821
  */
2386
2822
  static tryCreate(opts) {
@@ -4485,9 +4921,10 @@ function buildClaudeLaunch(extraArgs = []) {
4485
4921
  return { cmd: found, args: extraArgs };
4486
4922
  }
4487
4923
 
4488
- // src/services/claude.service.ts
4489
- var ClaudeService = class {
4490
- constructor(opts) {
4924
+ // src/services/agent.service.ts
4925
+ var AgentService = class {
4926
+ constructor(runtime, opts) {
4927
+ this.runtime = runtime;
4491
4928
  this.opts = opts;
4492
4929
  this.strategyOpts = {
4493
4930
  onData: (d3) => {
@@ -4501,6 +4938,7 @@ var ClaudeService = class {
4501
4938
  onExit: opts.onExit
4502
4939
  };
4503
4940
  }
4941
+ runtime;
4504
4942
  opts;
4505
4943
  // Strategy is selected lazily inside spawn() so we can fall back from
4506
4944
  // ConPTY → legacy pipe at runtime if the native binding fails to load.
@@ -4532,10 +4970,20 @@ var ClaudeService = class {
4532
4970
  this.pendingInputs.length = 0;
4533
4971
  }
4534
4972
  async spawn() {
4535
- let launch = buildClaudeLaunch();
4536
- if (!launch) {
4973
+ let launch;
4974
+ try {
4975
+ launch = await this.runtime.prepareLaunch();
4976
+ } catch {
4537
4977
  const installed = await ensureClaudeInstalled();
4538
- if (installed) launch = buildClaudeLaunch();
4978
+ if (installed) {
4979
+ try {
4980
+ launch = await this.runtime.prepareLaunch();
4981
+ } catch {
4982
+ launch = null;
4983
+ }
4984
+ } else {
4985
+ launch = null;
4986
+ }
4539
4987
  if (!launch) {
4540
4988
  const cmd = process.platform === "win32" ? "irm https://claude.ai/install.ps1 | iex" : "curl -fsSL https://claude.ai/install.sh | bash";
4541
4989
  console.error(
@@ -4648,6 +5096,20 @@ var ClaudeService = class {
4648
5096
  s.write("\r");
4649
5097
  }, steps * ARROW_MS + ENTER_MS);
4650
5098
  }
5099
+ /**
5100
+ * Write raw bytes to the PTY without any auto-appended `\r` or delay.
5101
+ * Use this when the caller already owns the full input (e.g. the
5102
+ * `ptyInput` returned by `RuntimeStrategy.changeModelInstruction()`
5103
+ * already contains the trailing `\r`).
5104
+ */
5105
+ sendRawPtyInput(text) {
5106
+ if (!this.strategy) {
5107
+ log.trace("claude", "sendRawPtyInput dropped (no strategy)");
5108
+ return;
5109
+ }
5110
+ log.trace("claude", `sendRawPtyInput len=${text.length}`);
5111
+ this.strategy.write(text);
5112
+ }
4651
5113
  /** Send Escape key to Claude (cancels interactive prompts). */
4652
5114
  sendEscape() {
4653
5115
  this.strategy?.write("\x1B");
@@ -4662,390 +5124,559 @@ var ClaudeService = class {
4662
5124
  /**
4663
5125
  * Kill the current Claude process and relaunch it resuming the given session.
4664
5126
  * Pass auto=true to add --dangerously-skip-permissions (no confirmation prompts).
5127
+ *
5128
+ * For agents that use CLI flags (Claude: --resume <id>), `resumeLaunchArgs`
5129
+ * returns a non-empty array and we pass those directly to the binary.
5130
+ * For agents that use a post-spawn PTY instruction (e.g. Codex), `resumeLaunchArgs`
5131
+ * returns [] and `postSpawnInstruction` types the resume command into the PTY.
4665
5132
  */
4666
5133
  restart(sessionId, auto = false) {
4667
5134
  if (!this.strategy) return;
4668
- const extraArgs = ["--resume", sessionId];
4669
- if (auto) extraArgs.push("--dangerously-skip-permissions");
4670
- const launch = buildClaudeLaunch(extraArgs);
5135
+ const resumeArgs = auto ? this.runtime.resumeLaunchArgs(sessionId) : ["--resume", sessionId];
5136
+ const launch = buildClaudeLaunch(resumeArgs);
4671
5137
  if (!launch) return;
4672
5138
  this.strategy.kill();
4673
5139
  this.strategy.spawn(launch.cmd, this.opts.cwd, launch.args);
5140
+ if (resumeArgs.length === 0 && this.runtime.postSpawnInstruction) {
5141
+ const { ptyInput } = this.runtime.postSpawnInstruction(sessionId);
5142
+ setTimeout(() => {
5143
+ this.strategy?.write(ptyInput);
5144
+ }, 500);
5145
+ }
4674
5146
  }
4675
5147
  };
4676
5148
 
4677
- // ../../packages/shared/src/protocol/parseChrome.ts
4678
- var SPINNER_RE = /^(?:[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]|🔴|🟠|🟡|🟢|🔵|🟣|🟤|⚫|⚪|🌀|💭|✨)\s/u;
4679
- 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;
4680
- var TREE_LINE_RE = /^└\s/;
4681
- var STATUS_LINE_RE = /^(?:\+|[🔴🟠🟡🟢🔵🟣🟤⚫⚪🌀💭✨])\s/u;
4682
- function isChromeLine(line) {
4683
- const t2 = line.replace(/️/g, "").trim();
4684
- if (!t2) return false;
4685
- if (/^[─━—═─\-]{3,}$/.test(t2)) return true;
4686
- if (SPINNER_RE.test(t2)) return true;
4687
- if (BULLET_TOOL_RE.test(t2)) return true;
4688
- if (TREE_LINE_RE.test(t2)) return true;
4689
- if (STATUS_LINE_RE.test(t2) && /\d+\s*s\s*[·•]|\bthought\s+for\b|\d+\s*tokens|\(thinking\)/i.test(t2)) return true;
4690
- if (/^↓\s*\d+\s*tokens/i.test(t2)) return true;
4691
- if (/^\bthought\s+for\s+\d+/i.test(t2)) return true;
4692
- if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) return true;
4693
- if (/high\s*[·•]\s*\/effort/i.test(t2)) return true;
4694
- if (/^[❯>]\s*$/.test(t2)) return true;
4695
- if (/^\(thinking\)\s*$/.test(t2)) return true;
4696
- if (/^\?\s.*shortcut/i.test(t2)) return true;
4697
- if (/spending limit|usage limit/i.test(t2) && t2.length < 80) return true;
4698
- if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) return true;
4699
- if (t2.replace(/\s/g, "").length === 1) return true;
4700
- if ((t2.match(/─/g)?.length ?? 0) >= 6) return true;
4701
- if (/ctrl\+?o\s+to\s+expand/i.test(t2)) return true;
4702
- const hasBoxPrefix = /^[│╭╰╮╯┌└┐┘├┤┬┴┼]/.test(t2);
4703
- const stripped = t2.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "");
4704
- if (hasBoxPrefix && /^[❯>]\s+\S/.test(stripped) && !/^[❯>]\s*\d+\./.test(stripped)) return true;
4705
- return false;
4706
- }
4707
- function parseChromeLine(line) {
4708
- const t2 = line.replace(/️/g, "").trim();
4709
- if (!t2) return null;
4710
- if (/^[─━—═─\-]{3,}$/.test(t2)) return null;
4711
- if (/^[❯>]\s*$/.test(t2)) return null;
4712
- if (t2.replace(/\s/g, "").length === 1) return null;
4713
- if ((t2.match(/─/g)?.length ?? 0) >= 6) return null;
4714
- if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) return null;
4715
- if (/high\s*[·•]\s*\/effort/i.test(t2)) return null;
4716
- if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) return null;
4717
- if (/ctrl\+?o\s+to\s+expand/i.test(t2)) return null;
4718
- if (/spending limit|usage limit/i.test(t2)) return null;
4719
- if (/^\(thinking\)\s*$/.test(t2)) {
4720
- return { tool: "thinking", label: "Thinking\u2026", status: "running" };
4721
- }
4722
- if (TREE_LINE_RE.test(t2)) return null;
4723
- if (STATUS_LINE_RE.test(t2)) {
4724
- const label = t2.slice(2).replace(/….*/s, "").trim() || "Thinking\u2026";
4725
- return { tool: "thinking", label, status: "running" };
4726
- }
4727
- let text = t2;
4728
- if (SPINNER_RE.test(t2)) {
4729
- text = t2.slice(2).trim().replace(/….*/s, "").trim();
4730
- } else if (BULLET_TOOL_RE.test(t2)) {
4731
- text = t2.slice(2).trim();
4732
- 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();
4733
- }
4734
- if (!text) return null;
4735
- return classifyStep(text);
4736
- }
4737
- function classifyStep(text) {
4738
- if (/^Read(?:ing)?\s+/i.test(text)) {
4739
- const label2 = text.replace(/^Read(?:ing)?\s+/i, "").replace(/\.\.\.$/, "").trim();
4740
- return { tool: "read", label: label2, status: "running" };
4741
- }
4742
- if (/^Edit(?:ing)?\s+|^Writ(?:e|ing|ing to)\s+|^Creat(?:e|ing)\s+/i.test(text)) {
4743
- const label2 = text.replace(/^(?:Edit(?:ing)?|Writ(?:e|ing(?: to)?)|Creat(?:e|ing))\s+/i, "").replace(/\.\.\.$/, "").trim();
4744
- return { tool: "edit", label: label2, status: "running" };
4745
- }
4746
- if (/^Runn(?:ing)?\s+|^Execut(?:e|ing)\s+|^Bash(?:ing)?\s*:|^\$\s+/i.test(text)) {
4747
- const label2 = text.replace(/^(?:Runn(?:ing)?|Execut(?:e|ing)|Bash(?:ing)?:|\$)\s+/i, "").replace(/\.\.\.$/, "").trim();
4748
- return { tool: "bash", label: label2, status: "running" };
4749
- }
4750
- if (/^Search(?:ing)?\s+for\s+|^Grep(?:ping)?\s*:/i.test(text)) {
4751
- const label2 = text.replace(/^(?:Search(?:ing)?\s+for|Grep(?:ping)?:)\s+/i, "").replace(/\.\.\.$/, "").trim();
4752
- return { tool: "search", label: label2, status: "running" };
4753
- }
4754
- const label = text.replace(/\.\.\.$/, "").trim();
4755
- return { tool: "other", label, status: "running" };
4756
- }
4757
-
4758
- // ../../packages/shared/src/protocol/renderToLines.ts
4759
- function renderToLines(raw) {
4760
- const screen = [""];
4761
- let row = 0;
4762
- let col = 0;
4763
- function ensureRow() {
4764
- while (screen.length <= row) screen.push("");
4765
- }
4766
- function writeChar(ch) {
4767
- ensureRow();
4768
- if (col < screen[row].length) {
4769
- screen[row] = screen[row].slice(0, col) + ch + screen[row].slice(col + 1);
4770
- } else {
4771
- while (screen[row].length < col) screen[row] += " ";
4772
- screen[row] += ch;
5149
+ // src/agents/claude/quota.ts
5150
+ var fs5 = __toESM(require("fs"));
5151
+ var os6 = __toESM(require("os"));
5152
+ var path8 = __toESM(require("path"));
5153
+ var import_child_process4 = require("child_process");
5154
+ var HELPER_SCRIPT = `import os,pty,sys,select,signal,struct,fcntl,termios,errno
5155
+ m,s=pty.openpty()
5156
+ try:
5157
+ fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',30,120,0,0))
5158
+ except Exception:pass
5159
+ pid=os.fork()
5160
+ if pid==0:
5161
+ os.close(m);os.setsid()
5162
+ try:fcntl.ioctl(s,termios.TIOCSCTTY,0)
5163
+ except Exception:pass
5164
+ for fd in[0,1,2]:os.dup2(s,fd)
5165
+ if s>2:os.close(s)
5166
+ os.execvp(sys.argv[1],sys.argv[1:])
5167
+ sys.exit(127)
5168
+ os.close(s)
5169
+ done=[False]
5170
+ def onchld(n,f):
5171
+ try:os.waitpid(pid,os.WNOHANG)
5172
+ except Exception:pass
5173
+ done[0]=True
5174
+ signal.signal(signal.SIGCHLD,onchld)
5175
+ i=sys.stdin.fileno();o=sys.stdout.fileno()
5176
+ while not done[0]:
5177
+ try:r,_,_=select.select([i,m],[],[],0.1)
5178
+ except OSError as e:
5179
+ if e.errno==errno.EINTR:continue
5180
+ break
5181
+ if i in r:
5182
+ try:
5183
+ d=os.read(i,4096)
5184
+ if d:os.write(m,d)
5185
+ else:break
5186
+ except OSError:break
5187
+ if m in r:
5188
+ try:
5189
+ d=os.read(m,4096)
5190
+ if d:os.write(o,d)
5191
+ except OSError:done[0]=True
5192
+ try:os.kill(pid,signal.SIGTERM)
5193
+ except Exception:pass
5194
+ try:
5195
+ _,st=os.waitpid(pid,0)
5196
+ sys.exit((st>>8)&0xFF)
5197
+ except Exception:sys.exit(0)
5198
+ `;
5199
+ function parseUsageOutput(raw) {
5200
+ const clean = raw.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, " ");
5201
+ const weekMatch = clean.match(/(\d+)%\s*used/i) || clean.match(/(\d+)\s*%/);
5202
+ if (!weekMatch) return null;
5203
+ const percent = parseInt(weekMatch[1], 10);
5204
+ const resetMatch = clean.match(/resets\s+(.+?)(?:\s*[\(\)]|$)/im);
5205
+ const resetAt = resetMatch?.[1]?.trim() || void 0;
5206
+ return { percent, resetAt };
5207
+ }
5208
+ async function fetchClaudeQuota() {
5209
+ return new Promise((resolve2) => {
5210
+ const claudeCmd = findInPath("claude") ? "claude" : "claude-code";
5211
+ if (!claudeCmd) {
5212
+ resolve2(null);
5213
+ return;
4773
5214
  }
4774
- col++;
4775
- }
4776
- let i = 0;
4777
- while (i < raw.length) {
4778
- const ch = raw[i];
4779
- if (ch === "\x1B") {
4780
- i++;
4781
- if (i >= raw.length) break;
4782
- if (raw[i] === "[") {
4783
- i++;
4784
- let param = "";
4785
- while (i < raw.length && !/[@-~]/.test(raw[i])) param += raw[i++];
4786
- const cmd = raw[i] ?? "";
4787
- const n = parseInt(param) || 1;
4788
- if (cmd === "A") {
4789
- row = Math.max(0, row - n);
4790
- } else if (cmd === "B") {
4791
- row += n;
4792
- ensureRow();
4793
- } else if (cmd === "C") {
4794
- col += n;
4795
- } else if (cmd === "D") {
4796
- col = Math.max(0, col - n);
4797
- } else if (cmd === "G") {
4798
- col = Math.max(0, n - 1);
4799
- } else if (cmd === "H" || cmd === "f") {
4800
- const p2 = param.split(";");
4801
- row = Math.max(0, (parseInt(p2[0] ?? "1") || 1) - 1);
4802
- col = Math.max(0, (parseInt(p2[1] ?? "1") || 1) - 1);
4803
- ensureRow();
4804
- } else if (cmd === "J") {
4805
- if (param === "2" || param === "3") {
4806
- screen.length = 1;
4807
- screen[0] = "";
4808
- row = 0;
4809
- col = 0;
4810
- } else if (param === "1") {
4811
- for (let r = 0; r < row; r++) screen[r] = "";
4812
- screen[row] = " ".repeat(col) + screen[row].slice(col);
4813
- } else {
4814
- screen[row] = screen[row].slice(0, col);
4815
- screen.splice(row + 1);
4816
- }
4817
- } else if (cmd === "K") {
4818
- ensureRow();
4819
- if (param === "" || param === "0") screen[row] = screen[row].slice(0, col);
4820
- else if (param === "1") screen[row] = " ".repeat(col) + screen[row].slice(col);
4821
- else if (param === "2") screen[row] = "";
4822
- } else if (cmd === "h" && (param === "?1049" || param === "?47")) {
4823
- screen.length = 1;
4824
- screen[0] = "";
4825
- row = 0;
4826
- col = 0;
4827
- } else if (cmd === "l" && (param === "?1049" || param === "?47")) {
4828
- screen.length = 1;
4829
- screen[0] = "";
4830
- row = 0;
4831
- col = 0;
5215
+ const helperPath = path8.join(os6.tmpdir(), "codeam-quota-helper.py");
5216
+ fs5.writeFileSync(helperPath, HELPER_SCRIPT, { mode: 420 });
5217
+ const python = findInPath("python3") ?? findInPath("python");
5218
+ if (!python) {
5219
+ resolve2(null);
5220
+ return;
5221
+ }
5222
+ const proc = (0, import_child_process4.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
5223
+ stdio: ["pipe", "pipe", "ignore"],
5224
+ cwd: process.cwd(),
5225
+ env: { ...process.env, TERM: "dumb", COLUMNS: "120", LINES: "30" }
5226
+ });
5227
+ let output = "";
5228
+ let resolved = false;
5229
+ proc.stdout?.on("data", (chunk) => {
5230
+ output += chunk.toString("utf8");
5231
+ });
5232
+ setTimeout(() => {
5233
+ proc.stdin?.write("/usage\r");
5234
+ setTimeout(() => {
5235
+ if (resolved) return;
5236
+ resolved = true;
5237
+ const result = parseUsageOutput(output);
5238
+ try {
5239
+ proc.kill();
5240
+ } catch {
4832
5241
  }
4833
- } else if (raw[i] === "]") {
4834
- i++;
4835
- while (i < raw.length) {
4836
- if (raw[i] === "\x07") break;
4837
- if (raw[i] === "\x1B" && i + 1 < raw.length && raw[i + 1] === "\\") {
4838
- i++;
4839
- break;
4840
- }
4841
- i++;
5242
+ try {
5243
+ fs5.unlinkSync(helperPath);
5244
+ } catch {
4842
5245
  }
5246
+ resolve2(result);
5247
+ }, 5e3);
5248
+ }, 8e3);
5249
+ setTimeout(() => {
5250
+ if (!resolved) {
5251
+ resolved = true;
5252
+ resolve2(null);
4843
5253
  }
4844
- } else if (ch === "\r") {
4845
- if (i + 1 < raw.length && raw[i + 1] === "\n") {
4846
- row++;
4847
- col = 0;
4848
- ensureRow();
4849
- i++;
4850
- } else {
4851
- col = 0;
5254
+ try {
5255
+ proc.kill();
5256
+ } catch {
4852
5257
  }
4853
- } else if (ch === "\n") {
4854
- row++;
4855
- col = 0;
4856
- ensureRow();
4857
- } else if (ch >= " " || ch === " ") {
4858
- writeChar(ch);
4859
- }
4860
- i++;
4861
- }
4862
- return screen;
5258
+ }, 2e4);
5259
+ });
4863
5260
  }
4864
5261
 
4865
- // ../../packages/shared/src/protocol/selector.ts
4866
- function detectSelector(lines) {
4867
- if (lines.some((l) => /\?\s+for\s+shortcuts/i.test(l.trim()))) return null;
4868
- const clean = lines.map(
4869
- (l) => l.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "").replace(/\s*[│╭╰╮╯┌└┐┘├┤┬┴┼─━═]+\s*$/, "")
4870
- );
4871
- const hasCursor = clean.some((l) => /^[❯>]\s*\d+\./.test(l.trim()));
4872
- const looksLikeTrust = clean.some(
4873
- (l) => /\b(?:trust\s+the\s+files|trust\s+this\s+folder|safety\s+check)\b/i.test(l)
4874
- );
4875
- if (!hasCursor && !looksLikeTrust) return null;
4876
- let optionStartIdx = -1;
4877
- for (let i = 0; i < clean.length; i++) {
4878
- if (/^(?:[❯>]\s*)?\d+\.\s/.test(clean[i].trim())) {
4879
- optionStartIdx = i;
4880
- break;
5262
+ // src/agents/claude/history.ts
5263
+ var fs6 = __toESM(require("fs"));
5264
+ var path9 = __toESM(require("path"));
5265
+ var os7 = __toESM(require("os"));
5266
+ function encodeCwd(cwd) {
5267
+ return cwd.replace(/[\\/:]/g, "-");
5268
+ }
5269
+ function resolveHistoryDir(cwd, projectsRoot) {
5270
+ const root = projectsRoot ?? path9.join(os7.homedir(), ".claude", "projects");
5271
+ const primary = path9.join(root, encodeCwd(cwd));
5272
+ if (fs6.existsSync(primary)) return primary;
5273
+ try {
5274
+ const entries = fs6.readdirSync(root, { withFileTypes: true });
5275
+ const wanted = encodeCwd(cwd);
5276
+ for (const e of entries) {
5277
+ if (!e.isDirectory()) continue;
5278
+ const candidate = e.name.replace(/-+/g, "-");
5279
+ if (candidate === wanted.replace(/-+/g, "-")) {
5280
+ return path9.join(root, e.name);
5281
+ }
4881
5282
  }
5283
+ } catch {
4882
5284
  }
4883
- if (optionStartIdx === -1) return null;
4884
- const questionParts = [];
4885
- for (let i = 0; i < optionStartIdx; i++) {
4886
- const t2 = clean[i].trim();
4887
- if (!t2) continue;
4888
- if (/^[─━—═\-]{3,}$/.test(t2)) continue;
4889
- if (/^\[.*\]$/.test(t2)) continue;
4890
- if (/^[>❯]\s/.test(t2)) continue;
4891
- if (!t2.includes(" ") && t2.length > 15) continue;
4892
- questionParts.push(t2);
5285
+ return null;
5286
+ }
5287
+ function getCurrentUsage(historyDir, bootTimeMs = 0) {
5288
+ const GRACE_MS = 5e3;
5289
+ const cutoff = bootTimeMs > 0 ? bootTimeMs - GRACE_MS : 0;
5290
+ let entries;
5291
+ try {
5292
+ entries = fs6.readdirSync(historyDir, { withFileTypes: true });
5293
+ } catch {
5294
+ return null;
4893
5295
  }
4894
- const question = questionParts.filter((line, i, arr) => !arr.some((other, j2) => j2 !== i && other.includes(line))).join("\n").trim();
4895
- const optionLabels = /* @__PURE__ */ new Map();
4896
- const optionDescs = /* @__PURE__ */ new Map();
4897
- let currentNum = -1;
4898
- for (let i = optionStartIdx; i < clean.length; i++) {
4899
- const t2 = clean[i].trim();
4900
- if (!t2) continue;
4901
- const m = t2.match(/^(?:[❯>]\s*)?(\d+)\.\s+(.+)/);
4902
- if (m) {
4903
- const num = parseInt(m[1], 10);
4904
- if (!optionLabels.has(num)) {
4905
- optionLabels.set(num, m[2].trim());
4906
- optionDescs.set(num, []);
4907
- }
4908
- currentNum = num;
4909
- } else if (currentNum !== -1 && !/^Enter to/i.test(t2) && !/^[─━—═\-]{3,}$/.test(t2) && !/↑.*↓.*navigate/i.test(t2) && !/Esc to/i.test(t2)) {
4910
- optionDescs.get(currentNum)?.push(t2);
5296
+ const files = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
5297
+ try {
5298
+ const stat3 = fs6.statSync(path9.join(historyDir, e.name));
5299
+ return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
5300
+ } catch {
5301
+ return { name: e.name, mtime: 0, birthtime: 0 };
5302
+ }
5303
+ }).filter((f) => f.birthtime >= cutoff).sort((a, b) => b.mtime - a.mtime);
5304
+ if (files.length === 0) return null;
5305
+ const filePath = path9.join(historyDir, files[0].name);
5306
+ let raw;
5307
+ try {
5308
+ raw = fs6.readFileSync(filePath, "utf8");
5309
+ } catch {
5310
+ return null;
5311
+ }
5312
+ let lastUsage = null;
5313
+ let lastModel = null;
5314
+ for (const line of raw.split("\n").filter(Boolean)) {
5315
+ try {
5316
+ const record = JSON.parse(line);
5317
+ if (record["type"] !== "assistant") continue;
5318
+ const msg = record["message"];
5319
+ if (msg?.["model"] === "<synthetic>") continue;
5320
+ const usage = msg?.["usage"];
5321
+ if (usage && (usage["input_tokens"] !== void 0 || usage["prompt_tokens"] !== void 0)) {
5322
+ lastUsage = usage;
5323
+ }
5324
+ if (msg?.["model"]) lastModel = msg["model"];
5325
+ } catch {
4911
5326
  }
4912
5327
  }
4913
- const keys = [...optionLabels.keys()].sort((a, b) => a - b);
4914
- if (keys.length < 2 || keys[0] !== 1) return null;
5328
+ const total = getContextWindow(lastModel);
5329
+ if (!lastUsage) {
5330
+ if (!lastModel) return null;
5331
+ return { used: 0, total, percent: 0, model: lastModel };
5332
+ }
5333
+ const inputTokens = (lastUsage["input_tokens"] ?? lastUsage["prompt_tokens"] ?? 0) + (lastUsage["cache_read_input_tokens"] ?? 0) + (lastUsage["cache_creation_input_tokens"] ?? 0);
5334
+ const percent = Math.min(100, Math.round(inputTokens / total * 100));
4915
5335
  return {
4916
- question,
4917
- options: keys.map((k2) => optionLabels.get(k2)),
4918
- optionDescriptions: keys.map((k2) => (optionDescs.get(k2) ?? []).join(" ").trim()),
4919
- currentIndex: 0
5336
+ used: inputTokens,
5337
+ total,
5338
+ percent,
5339
+ model: lastModel ?? void 0
4920
5340
  };
4921
5341
  }
4922
- function detectListSelector(lines) {
4923
- if (!lines.some((l) => /[↑↓].*navigate/i.test(l.trim()))) return null;
4924
- if (lines.some((l) => /^❯\s*\d+\./.test(l.trim()))) return null;
4925
- if (!lines.some((l) => /^\s+❯\s+\S/.test(l))) return null;
4926
- const isSelected = (line) => /^\s+❯\s+\S/.test(line);
4927
- const isUnselected = (line) => /^ \S/.test(line);
4928
- const isItem = (line) => isSelected(line) || isUnselected(line);
4929
- let optionStartIdx = -1;
4930
- for (let i = 0; i < lines.length; i++) {
4931
- if (isItem(lines[i])) {
4932
- optionStartIdx = i;
4933
- break;
4934
- }
5342
+ function extractText(content) {
5343
+ if (typeof content === "string") return content;
5344
+ if (Array.isArray(content)) {
5345
+ return content.filter((b) => b["type"] === "text").map((b) => b["text"]).join("\n");
4935
5346
  }
4936
- if (optionStartIdx === -1) return null;
4937
- const questionParts = [];
4938
- for (let i = 0; i < optionStartIdx; i++) {
4939
- const t2 = lines[i].trim();
4940
- if (!t2) continue;
4941
- if (/^[─━—═\-]{3,}$/.test(t2)) continue;
4942
- if (/[┌└│┐┘├┤┬┴┼]/.test(t2)) {
4943
- const inner = t2.replace(/[│┌└┐┘├┤┬┴┼─]/g, "").trim();
4944
- if (inner) questionParts.push(inner);
5347
+ return "";
5348
+ }
5349
+ function parseHistoryFile(filePath) {
5350
+ const out = [];
5351
+ let raw;
5352
+ try {
5353
+ raw = fs6.readFileSync(filePath, "utf8");
5354
+ } catch {
5355
+ return out;
5356
+ }
5357
+ for (const line of raw.split("\n").filter(Boolean)) {
5358
+ let rec;
5359
+ try {
5360
+ rec = JSON.parse(line);
5361
+ } catch {
4945
5362
  continue;
4946
5363
  }
4947
- if (/^[>❯]\s/.test(t2)) continue;
4948
- if (/[↑↓].*navigate/i.test(t2)) continue;
4949
- if (!t2.includes(" ") && t2.length > 15) continue;
4950
- questionParts.push(t2);
5364
+ if (typeof rec !== "object" || rec === null) continue;
5365
+ const r = rec;
5366
+ if (r["isMeta"]) continue;
5367
+ const type = r["type"];
5368
+ if (type !== "user" && type !== "assistant") continue;
5369
+ const msg = r["message"];
5370
+ if (!msg) continue;
5371
+ const text = extractText(msg["content"]).trim();
5372
+ if (!text) continue;
5373
+ const ts = r["timestamp"];
5374
+ const timestamp = typeof ts === "string" ? ts : typeof ts === "number" ? new Date(ts).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
5375
+ const uuid = typeof r["uuid"] === "string" ? r["uuid"] : `${Date.now()}-${Math.random()}`;
5376
+ const usage = msg["usage"];
5377
+ out.push({
5378
+ id: uuid,
5379
+ role: type === "user" ? "user" : "agent",
5380
+ text,
5381
+ timestamp,
5382
+ modelId: typeof msg["model"] === "string" ? msg["model"] : void 0,
5383
+ usage: usage ? {
5384
+ input: usage["input_tokens"] ?? 0,
5385
+ output: usage["output_tokens"] ?? 0,
5386
+ cacheRead: usage["cache_read_input_tokens"],
5387
+ cacheCreation: usage["cache_creation_input_tokens"]
5388
+ } : void 0
5389
+ });
5390
+ }
5391
+ return out;
5392
+ }
5393
+
5394
+ // src/agents/claude/runtime.ts
5395
+ var ClaudeRuntimeStrategy = class {
5396
+ id = "claude";
5397
+ meta = getAgent("claude");
5398
+ async prepareLaunch() {
5399
+ const launch = buildClaudeLaunch();
5400
+ if (!launch) throw new Error("claude binary not found in PATH");
5401
+ return { cmd: launch.cmd, args: launch.args };
5402
+ }
5403
+ resumeLaunchArgs(sessionId) {
5404
+ return ["--resume", sessionId, "--dangerously-skip-permissions"];
5405
+ }
5406
+ resolveHistoryDir(cwd) {
5407
+ return resolveHistoryDir(cwd);
5408
+ }
5409
+ parseHistoryFile(filePath) {
5410
+ return parseHistoryFile(filePath);
5411
+ }
5412
+ getCurrentUsage(historyDir) {
5413
+ return getCurrentUsage(historyDir);
5414
+ }
5415
+ async fetchWeeklyUsage() {
5416
+ return fetchClaudeQuota();
5417
+ }
5418
+ async listModels() {
5419
+ return [
5420
+ { id: "claude-opus-4-7", label: "Claude Opus 4.7", contextWindow: 2e5 },
5421
+ { id: "claude-opus-4-6", label: "Claude Opus 4.6", contextWindow: 2e5 },
5422
+ { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", contextWindow: 2e5 },
5423
+ { id: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", contextWindow: 2e5 }
5424
+ ];
5425
+ }
5426
+ changeModelInstruction(modelId) {
5427
+ return { type: "pty", ptyInput: `/model ${modelId}\r` };
5428
+ }
5429
+ summarizeInstruction(mode) {
5430
+ if (mode === "normal") return { ptyInput: "/compact\r" };
5431
+ return { ptyInput: "/compact\r" };
5432
+ }
5433
+ };
5434
+
5435
+ // src/agents/claude/deploy.ts
5436
+ var fs8 = __toESM(require("fs"));
5437
+ var os9 = __toESM(require("os"));
5438
+ var path11 = __toESM(require("path"));
5439
+
5440
+ // src/agents/claude/credentials.ts
5441
+ var import_child_process5 = require("child_process");
5442
+ var fs7 = __toESM(require("fs"));
5443
+ var os8 = __toESM(require("os"));
5444
+ var path10 = __toESM(require("path"));
5445
+ var import_util = require("util");
5446
+ var execFileP = (0, import_util.promisify)(import_child_process5.execFile);
5447
+ async function detectLocalClaudeCredentials() {
5448
+ const localClaudeDir = path10.join(os8.homedir(), ".claude");
5449
+ const flat = path10.join(localClaudeDir, ".credentials.json");
5450
+ if (fs7.existsSync(flat)) {
5451
+ return { source: "flat-file", description: "~/.claude/.credentials.json" };
5452
+ }
5453
+ if (os8.platform() === "darwin") {
5454
+ try {
5455
+ await execFileP(
5456
+ "security",
5457
+ ["find-generic-password", "-s", "Claude Code-credentials"],
5458
+ { maxBuffer: 1024 * 1024 }
5459
+ );
5460
+ return { source: "macos-keychain", description: "macOS Keychain" };
5461
+ } catch {
5462
+ return { source: "none", description: "" };
5463
+ }
4951
5464
  }
4952
- const question = questionParts.filter((line, i, arr) => !arr.some((other, j2) => j2 !== i && other.includes(line))).join("\n").trim();
4953
- const options = [];
4954
- let currentIndex = 0;
4955
- for (const line of lines.slice(optionStartIdx)) {
4956
- const t2 = line.trim();
4957
- if (!t2) continue;
4958
- if (/[↑↓].*navigate/i.test(t2)) break;
4959
- if (/^[─━—═\-]{3,}$/.test(t2)) continue;
4960
- if (isSelected(line)) {
4961
- currentIndex = options.length;
4962
- options.push(t2.replace(/^❯\s+/, "").trim());
4963
- } else if (isUnselected(line)) {
4964
- options.push(t2);
5465
+ return { source: "none", description: "" };
5466
+ }
5467
+ async function bridgeClaudeCredentials(provider, workspaceId) {
5468
+ const localClaudeDir = path10.join(os8.homedir(), ".claude");
5469
+ const fileBased = path10.join(localClaudeDir, ".credentials.json");
5470
+ if (fs7.existsSync(fileBased)) {
5471
+ return { source: "flat-file", description: "~/.claude/.credentials.json" };
5472
+ }
5473
+ if (process.platform === "darwin") {
5474
+ try {
5475
+ const { stdout } = await execFileP(
5476
+ "security",
5477
+ ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
5478
+ { maxBuffer: 1024 * 1024 }
5479
+ );
5480
+ const json = stdout.trim();
5481
+ if (json.length === 0) return { source: "none", description: "" };
5482
+ await provider.uploadFile(
5483
+ workspaceId,
5484
+ "/home/codespace/.claude/.credentials.json",
5485
+ json,
5486
+ { mode: 384 }
5487
+ );
5488
+ return { source: "macos-keychain", description: "macOS Keychain" };
5489
+ } catch {
5490
+ return { source: "none", description: "" };
4965
5491
  }
4966
5492
  }
4967
- if (options.length < 2) return null;
4968
- return {
4969
- question,
4970
- options,
4971
- optionDescriptions: options.map(() => ""),
4972
- currentIndex
4973
- };
5493
+ return { source: "none", description: "" };
5494
+ }
5495
+ async function runRemoteClaudeLogin(provider, workspaceId) {
5496
+ wt(
5497
+ [
5498
+ "A login URL will print below. Open it in your local browser, sign in,",
5499
+ "and paste any code Claude asks for back into this terminal."
5500
+ ].join("\n"),
5501
+ "Authenticating Claude on workspace"
5502
+ );
5503
+ const result = await provider.streamCommand(
5504
+ workspaceId,
5505
+ 'bash -lc "claude login || claude /login || true"'
5506
+ );
5507
+ if (result.code !== 0) {
5508
+ wt(
5509
+ "claude login exited non-zero. You can re-run it manually inside the codespace later.",
5510
+ "Heads up"
5511
+ );
5512
+ }
5513
+ }
5514
+ async function verifyClaudeAuth(provider, workspaceId) {
5515
+ const result = await provider.exec(
5516
+ workspaceId,
5517
+ 'bash -lc "claude auth status 2>/dev/null || true"'
5518
+ );
5519
+ if (result.code !== 0) return false;
5520
+ const jsonStart = result.stdout.indexOf("{");
5521
+ if (jsonStart < 0) return false;
5522
+ try {
5523
+ const parsed = JSON.parse(result.stdout.slice(jsonStart));
5524
+ return parsed.loggedIn === true;
5525
+ } catch {
5526
+ return false;
5527
+ }
4974
5528
  }
4975
5529
 
4976
- // ../../packages/shared/src/protocol/filterChrome.ts
4977
- function filterChrome(lines) {
4978
- const result = [];
4979
- let skipEchoContinuation = false;
4980
- for (const line of lines) {
4981
- const t2 = line.trim();
4982
- if (!t2) {
4983
- skipEchoContinuation = false;
4984
- continue;
5530
+ // src/agents/claude/deploy.ts
5531
+ var ClaudeDeployStrategy = class {
5532
+ id = "claude";
5533
+ async detectLocalCredentials() {
5534
+ return detectLocalClaudeCredentials();
5535
+ }
5536
+ async bridgeLocalCredentials(provider, workspaceId) {
5537
+ return bridgeClaudeCredentials(provider, workspaceId);
5538
+ }
5539
+ async runRemoteLogin(provider, workspaceId) {
5540
+ return runRemoteClaudeLogin(provider, workspaceId);
5541
+ }
5542
+ /**
5543
+ * Steps 5-9 of the deploy flow:
5544
+ * - Install Claude CLI on the workspace
5545
+ * - Copy local ~/.claude config dir (excluding heavy state)
5546
+ * - Ship ~/.claude.json (only when creds were bridged — privacy)
5547
+ * - Verify auth, fallback to runRemoteLogin
5548
+ */
5549
+ async setupOnWorkspace(provider, workspaceId, opts) {
5550
+ const claudeStep = fe();
5551
+ claudeStep.start("Installing Claude CLI on workspace\u2026");
5552
+ const installResult = await provider.exec(
5553
+ workspaceId,
5554
+ "curl -fsSL https://claude.ai/install.sh | bash"
5555
+ );
5556
+ if (installResult.code !== 0) {
5557
+ claudeStep.stop("\u2717 Claude CLI install failed");
5558
+ pt(installResult.stderr.slice(0, 1e3));
5559
+ process.exit(1);
4985
5560
  }
4986
- if (/^[─━—═─\-]{3,}$/.test(t2)) {
4987
- skipEchoContinuation = false;
4988
- continue;
5561
+ claudeStep.stop("\u2713 Claude CLI installed");
5562
+ const localClaudeDir = path11.join(os9.homedir(), ".claude");
5563
+ const haveLocalClaude = fs8.existsSync(localClaudeDir) && fs8.statSync(localClaudeDir).isDirectory();
5564
+ if (haveLocalClaude) {
5565
+ const copyStep = fe();
5566
+ copyStep.start("Copying local Claude config to workspace\u2026");
5567
+ try {
5568
+ await provider.uploadDirectory(
5569
+ workspaceId,
5570
+ localClaudeDir,
5571
+ "/home/codespace/.claude",
5572
+ {
5573
+ exclude: [
5574
+ "./projects",
5575
+ // per-project conversation history (often 700MB+)
5576
+ "./file-history",
5577
+ // per-project file diffs
5578
+ "./downloads",
5579
+ // downloaded artifacts
5580
+ "./image-cache",
5581
+ // cached images
5582
+ "./paste-cache",
5583
+ // clipboard/paste cache
5584
+ "./backups",
5585
+ // local backups
5586
+ "./shell-snapshots",
5587
+ // shell history snapshots
5588
+ "./telemetry",
5589
+ // analytics dumps
5590
+ "./statsig",
5591
+ // feature-flag cache
5592
+ "./cache",
5593
+ // generic cache dir
5594
+ "./history.jsonl",
5595
+ // global REPL history
5596
+ "./ide",
5597
+ // local IDE bridge state
5598
+ "./todos",
5599
+ // local todo state
5600
+ "./tasks",
5601
+ // local task state
5602
+ // Don't overwrite the credentials we already staged in
5603
+ // step 4 — the local dir on macOS doesn't have a flat
5604
+ // credentials file anyway, but on Linux it would, and a
5605
+ // re-write here would be redundant.
5606
+ "./.credentials.json"
5607
+ ]
5608
+ }
5609
+ );
5610
+ copyStep.stop("\u2713 Claude config uploaded");
5611
+ } catch (err) {
5612
+ copyStep.stop("\u26A0 Could not upload Claude config (continuing)");
5613
+ void err;
5614
+ }
4989
5615
  }
4990
- if (/^[●⏺]\s/.test(t2)) skipEchoContinuation = false;
4991
- if (/^[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]\s/.test(t2)) continue;
4992
- if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) continue;
4993
- if (/high\s*[·•]\s*\/effort/i.test(t2)) continue;
4994
- if (/^[❯>]\s*$/.test(t2)) continue;
4995
- if (/^\(thinking\)\s*$/.test(t2)) continue;
4996
- if (/^\?\s.*shortcut/i.test(t2)) continue;
4997
- if (/spending limit|usage limit/i.test(t2) && t2.length < 80) continue;
4998
- if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) continue;
4999
- if (t2.replace(/\s/g, "").length === 1) continue;
5000
- if ((t2.match(/─/g)?.length ?? 0) >= 6) continue;
5001
- if (/ctrl\+?o\s+to\s+expand/i.test(t2)) continue;
5002
- 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(
5003
- t2
5004
- ))
5005
- continue;
5006
- if (/^└\s/.test(t2)) continue;
5007
- if (/^\+\s/.test(t2) && /\d+\s*s\s*[·•]|\bthought\s+for\b|\d+\s*tokens|\(thinking\)/i.test(t2)) continue;
5008
- if (/^↓\s*\d+\s*tokens/i.test(t2)) continue;
5009
- if (/^\bthought\s+for\s+\d+/i.test(t2)) continue;
5010
- const stripped = t2.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "");
5011
- if (/^[❯>]\s+\S/.test(stripped) && !/^[❯>]\s*\d+\./.test(stripped)) {
5012
- skipEchoContinuation = true;
5013
- continue;
5616
+ if (opts.bridged !== "none") {
5617
+ const localClaudeJson = path11.join(os9.homedir(), ".claude.json");
5618
+ if (fs8.existsSync(localClaudeJson)) {
5619
+ try {
5620
+ const contents = fs8.readFileSync(localClaudeJson);
5621
+ await provider.uploadFile(
5622
+ workspaceId,
5623
+ "/home/codespace/.claude.json",
5624
+ contents,
5625
+ { mode: 384 }
5626
+ );
5627
+ } catch (err) {
5628
+ void err;
5629
+ }
5630
+ }
5631
+ }
5632
+ const verifyStep = fe();
5633
+ verifyStep.start("Verifying Claude auth on workspace\u2026");
5634
+ const verified = await verifyClaudeAuth(provider, workspaceId);
5635
+ if (verified) {
5636
+ verifyStep.stop("\u2713 Claude is logged in \u2014 no re-auth needed");
5637
+ } else {
5638
+ verifyStep.stop("\xB7 Claude not yet authenticated \u2014 running login flow");
5639
+ await runRemoteClaudeLogin(provider, workspaceId);
5640
+ const reverified = await verifyClaudeAuth(provider, workspaceId);
5641
+ if (!reverified) {
5642
+ wt(
5643
+ "Claude auth could not be confirmed. You may need to run `claude /login` manually inside the codespace.",
5644
+ "Heads up"
5645
+ );
5646
+ }
5014
5647
  }
5015
- if (skipEchoContinuation) continue;
5016
- result.push(line);
5017
5648
  }
5018
- return result;
5019
- }
5649
+ };
5020
5650
 
5021
- // ../../packages/shared/src/models/pricing.ts
5022
- var MODEL_PRICING = {
5023
- "claude-sonnet-4": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
5024
- "claude-opus-4": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
5025
- "claude-3-5-sonnet": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
5026
- "claude-3-5-haiku": { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
5027
- "claude-3-haiku": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 }
5651
+ // src/agents/registry.ts
5652
+ var runtimeBuilders = {
5653
+ claude: () => new ClaudeRuntimeStrategy()
5654
+ // codex and copilot added in Phase 2 / later
5028
5655
  };
5029
- var MODEL_CONTEXT_WINDOW = {
5030
- "claude-opus-4": 1e6,
5031
- "claude-sonnet-4": 1e6,
5032
- "claude-3-5-sonnet": 2e5,
5033
- "claude-3-5-haiku": 2e5,
5034
- "claude-3-haiku": 2e5
5656
+ var deployBuilders = {
5657
+ claude: () => new ClaudeDeployStrategy()
5035
5658
  };
5036
- var DEFAULT_CONTEXT_WINDOW = 2e5;
5037
- function getPricing(model) {
5038
- for (const [prefix, pricing] of Object.entries(MODEL_PRICING)) {
5039
- if (model.startsWith(prefix)) return pricing;
5659
+ function createRuntimeStrategy(agent) {
5660
+ if (!AGENT_REGISTRY[agent]?.enabled) {
5661
+ throw new Error(
5662
+ `Agent "${agent}" is not supported in this codeam-cli version. Upgrade with 'npm i -g codeam-cli@latest'.`
5663
+ );
5040
5664
  }
5041
- return MODEL_PRICING["claude-sonnet-4"];
5665
+ const build = runtimeBuilders[agent];
5666
+ if (!build) {
5667
+ throw new Error(`No runtime strategy registered for agent "${agent}"`);
5668
+ }
5669
+ return build();
5042
5670
  }
5043
- function getContextWindow(model) {
5044
- if (!model) return DEFAULT_CONTEXT_WINDOW;
5045
- for (const [prefix, size] of Object.entries(MODEL_CONTEXT_WINDOW)) {
5046
- if (model.startsWith(prefix)) return size;
5671
+ function createDeployStrategy(agent) {
5672
+ if (!AGENT_REGISTRY[agent]?.enabled) {
5673
+ throw new Error(`Agent "${agent}" is not supported in this codeam-cli version.`);
5047
5674
  }
5048
- return DEFAULT_CONTEXT_WINDOW;
5675
+ const build = deployBuilders[agent];
5676
+ if (!build) {
5677
+ throw new Error(`No deploy strategy registered for agent "${agent}"`);
5678
+ }
5679
+ return build();
5049
5680
  }
5050
5681
 
5051
5682
  // src/services/output/chrome-tracker.ts
@@ -5504,19 +6135,8 @@ var OutputService = class _OutputService {
5504
6135
  tryExtractSessionId(text) {
5505
6136
  if (!this.onSessionIdDetected) return;
5506
6137
  const printable = text.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, "");
5507
- const patterns = [
5508
- /Resuming session[:\s]+([a-f0-9-]{36})/i,
5509
- /Session[:\s]+([a-f0-9-]{36})/i,
5510
- /Conversation[:\s]+([a-f0-9-]{36})/i,
5511
- /Session\s+ID[:\s]+([a-f0-9-]{36})/i
5512
- ];
5513
- for (const pattern of patterns) {
5514
- const match = printable.match(pattern);
5515
- if (match) {
5516
- this.onSessionIdDetected(match[1]);
5517
- return;
5518
- }
5519
- }
6138
+ const match = printable.match(/Resuming session[:\s]+([a-f0-9-]{36})/i);
6139
+ if (match) this.onSessionIdDetected(match[1]);
5520
6140
  }
5521
6141
  tryDetectRateLimit(text) {
5522
6142
  if (!this.onRateLimitDetected) return;
@@ -5529,9 +6149,9 @@ var OutputService = class _OutputService {
5529
6149
  };
5530
6150
 
5531
6151
  // src/services/history.service.ts
5532
- var fs5 = __toESM(require("fs"));
5533
- var path8 = __toESM(require("path"));
5534
- var os6 = __toESM(require("os"));
6152
+ var fs9 = __toESM(require("fs"));
6153
+ var path12 = __toESM(require("path"));
6154
+ var os10 = __toESM(require("os"));
5535
6155
  var https4 = __toESM(require("https"));
5536
6156
  var http4 = __toESM(require("http"));
5537
6157
  var import_zod = require("zod");
@@ -5546,28 +6166,7 @@ var historyRecordSchema = import_zod.z.object({
5546
6166
  }).passthrough().optional()
5547
6167
  }).passthrough();
5548
6168
  var API_BASE4 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
5549
- function encodeCwd(cwd) {
5550
- return cwd.replace(/[\\/:]/g, "-");
5551
- }
5552
- function findProjectDir(cwd) {
5553
- const projectsRoot = path8.join(os6.homedir(), ".claude", "projects");
5554
- const primary = path8.join(projectsRoot, encodeCwd(cwd));
5555
- if (fs5.existsSync(primary)) return primary;
5556
- try {
5557
- const entries = fs5.readdirSync(projectsRoot, { withFileTypes: true });
5558
- const wanted = encodeCwd(cwd);
5559
- for (const e of entries) {
5560
- if (!e.isDirectory()) continue;
5561
- const candidate = e.name.replace(/-+/g, "-");
5562
- if (candidate === wanted.replace(/-+/g, "-")) {
5563
- return path8.join(projectsRoot, e.name);
5564
- }
5565
- }
5566
- } catch {
5567
- }
5568
- return null;
5569
- }
5570
- function extractText(content) {
6169
+ function extractText2(content) {
5571
6170
  if (typeof content === "string") return content;
5572
6171
  if (Array.isArray(content)) {
5573
6172
  return content.filter((b) => b["type"] === "text").map((b) => b["text"]).join("\n");
@@ -5579,7 +6178,7 @@ function parseJsonl(filePath) {
5579
6178
  const messages = [];
5580
6179
  let raw;
5581
6180
  try {
5582
- raw = fs5.readFileSync(filePath, "utf8");
6181
+ raw = fs9.readFileSync(filePath, "utf8");
5583
6182
  } catch (err) {
5584
6183
  if (err.code !== "ENOENT") {
5585
6184
  log.warn("history:parseJsonl", `read failed for ${filePath}`, err);
@@ -5606,10 +6205,10 @@ function parseJsonl(filePath) {
5606
6205
  const uuid = record.uuid ?? `${Date.now()}-${Math.random()}`;
5607
6206
  const msg = record.message;
5608
6207
  if (record.type === "user" && msg) {
5609
- const text = extractText(msg.content).trim();
6208
+ const text = extractText2(msg.content).trim();
5610
6209
  if (text) messages.push({ id: uuid, role: "user", text, timestamp });
5611
6210
  } else if (record.type === "assistant" && msg) {
5612
- const text = extractText(msg.content).trim();
6211
+ const text = extractText2(msg.content).trim();
5613
6212
  if (text) messages.push({ id: uuid, role: "agent", text, timestamp });
5614
6213
  }
5615
6214
  }
@@ -5652,10 +6251,12 @@ function post(endpoint, body) {
5652
6251
  req.end();
5653
6252
  });
5654
6253
  }
5655
- var HistoryService = class {
5656
- constructor(pluginId, cwd) {
6254
+ var HistoryService = class _HistoryService {
6255
+ constructor(runtime, pluginId, cwd, options) {
5657
6256
  this.pluginId = pluginId;
5658
6257
  this.cwd = cwd;
6258
+ this.runtime = runtime;
6259
+ this.bootTimeMs = options?.bootTimeMs ?? Date.now();
5659
6260
  }
5660
6261
  pluginId;
5661
6262
  cwd;
@@ -5671,6 +6272,26 @@ var HistoryService = class {
5671
6272
  * resume re-uploads the full transcript on first call.
5672
6273
  */
5673
6274
  lastUploadedUuid = /* @__PURE__ */ new Map();
6275
+ /**
6276
+ * Captured at construction time so every JSONL discovery (detect,
6277
+ * usage stats) can ignore files that already existed in the
6278
+ * project's `~/.claude/projects/<cwd>/` dir *before* this CLI
6279
+ * run started. Without this filter, an old conversation — or
6280
+ * a *parallel* Claude session actively writing to the same
6281
+ * project — wins the mtime sort and we publish its content
6282
+ * to the mobile app as if it were the fresh pair's chat.
6283
+ */
6284
+ bootTimeMs;
6285
+ /**
6286
+ * Small grace window subtracted from `bootTimeMs` when filtering
6287
+ * by `birthtime`. Covers clock skew + filesystem timestamp
6288
+ * rounding (HFS+ floors to 1 s; some Linux filesystems round to
6289
+ * the nearest second). 5 s is comfortably wider than any
6290
+ * filesystem rounding while still excluding everything from a
6291
+ * previous pair / previous Claude run.
6292
+ */
6293
+ static BIRTHTIME_GRACE_MS = 5e3;
6294
+ runtime;
5674
6295
  /** Store rate limit reset info detected from Claude Code output */
5675
6296
  setRateLimitReset(reset) {
5676
6297
  this._rateLimitReset = reset;
@@ -5691,7 +6312,7 @@ var HistoryService = class {
5691
6312
  return this._quotaPercent === null || Date.now() - this._quotaFetchedAt > ttlMs;
5692
6313
  }
5693
6314
  get projectDir() {
5694
- return findProjectDir(this.cwd) ?? path8.join(os6.homedir(), ".claude", "projects", encodeCwd(this.cwd));
6315
+ return this.runtime.resolveHistoryDir(this.cwd) ?? path12.join(os10.homedir(), ".claude", "projects", encodeCwd(this.cwd));
5695
6316
  }
5696
6317
  /** Set the current Claude conversation ID (extracted from /cost command or session start) */
5697
6318
  setCurrentConversationId(id) {
@@ -5703,7 +6324,7 @@ var HistoryService = class {
5703
6324
  /** Return the current message count in the active conversation. */
5704
6325
  getCurrentMessageCount() {
5705
6326
  if (!this.currentConversationId) return 0;
5706
- const filePath = path8.join(this.projectDir, `${this.currentConversationId}.jsonl`);
6327
+ const filePath = path12.join(this.projectDir, `${this.currentConversationId}.jsonl`);
5707
6328
  return parseJsonl(filePath).length;
5708
6329
  }
5709
6330
  /**
@@ -5714,7 +6335,7 @@ var HistoryService = class {
5714
6335
  const deadline = Date.now() + timeoutMs;
5715
6336
  while (Date.now() < deadline) {
5716
6337
  if (!this.currentConversationId) return null;
5717
- const filePath = path8.join(this.projectDir, `${this.currentConversationId}.jsonl`);
6338
+ const filePath = path12.join(this.projectDir, `${this.currentConversationId}.jsonl`);
5718
6339
  const messages = parseJsonl(filePath);
5719
6340
  if (messages.length > previousCount) {
5720
6341
  for (let i = messages.length - 1; i >= previousCount; i--) {
@@ -5725,37 +6346,50 @@ var HistoryService = class {
5725
6346
  }
5726
6347
  return null;
5727
6348
  }
5728
- /** Detect the active conversation by finding the most recently modified JSONL file */
6349
+ /**
6350
+ * Detect the active conversation by finding the most recently
6351
+ * modified JSONL file that was **created during this CLI run**.
6352
+ * The birthtime filter is critical: without it, an old
6353
+ * conversation in the same project dir — or a parallel Claude
6354
+ * session actively writing to a sibling JSONL — wins the mtime
6355
+ * sort, and we publish that other run's content to mobile as if
6356
+ * it were the fresh pair's chat. With the filter, only files
6357
+ * Claude created on or after `bootTimeMs` are eligible, so a
6358
+ * fresh pair stays empty until the user actually types a turn.
6359
+ */
5729
6360
  detectCurrentConversation() {
5730
6361
  const dir = this.projectDir;
6362
+ const cutoff = this.bootTimeMs - _HistoryService.BIRTHTIME_GRACE_MS;
5731
6363
  try {
5732
- const files = fs5.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
6364
+ const files = fs9.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
5733
6365
  try {
5734
- return { name: e.name, mtime: fs5.statSync(path8.join(dir, e.name)).mtimeMs };
6366
+ const stat3 = fs9.statSync(path12.join(dir, e.name));
6367
+ return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
5735
6368
  } catch {
5736
- return { name: e.name, mtime: 0 };
6369
+ return { name: e.name, mtime: 0, birthtime: 0 };
5737
6370
  }
5738
- }).sort((a, b) => b.mtime - a.mtime);
6371
+ }).filter((f) => f.birthtime >= cutoff).sort((a, b) => b.mtime - a.mtime);
5739
6372
  if (files.length > 0) {
5740
- this.currentConversationId = path8.basename(files[0].name, ".jsonl");
6373
+ this.currentConversationId = path12.basename(files[0].name, ".jsonl");
5741
6374
  }
5742
6375
  } catch {
5743
6376
  }
5744
6377
  }
5745
- /** Extract conversation ID from Claude output (e.g., from session resume messages) */
6378
+ /**
6379
+ * Extract conversation ID from Claude output. Limited to the
6380
+ * unambiguous "Resuming session: <uuid>" pattern — the older
6381
+ * generic `Session: <uuid>` / `Conversation: <uuid>` patterns
6382
+ * were too greedy and matched any incidental UUID-bearing line
6383
+ * Claude printed (debug logs, status info, etc.), causing the
6384
+ * CLI to "detect" the wrong conversation on a fresh pair.
6385
+ * Resume is the only flow that legitimately needs to bind via
6386
+ * output text; everything else sets `currentConversationId`
6387
+ * via `setCurrentConversationId()` or the birthtime-filtered
6388
+ * `detectCurrentConversation()`.
6389
+ */
5746
6390
  tryExtractConversationIdFromOutput(output) {
5747
- const patterns = [
5748
- /Resuming session[:\s]+([a-f0-9-]{36})/i,
5749
- /session[:\s]+([a-f0-9-]{36})/i,
5750
- /conversation[:\s]+([a-f0-9-]{36})/i
5751
- ];
5752
- for (const pattern of patterns) {
5753
- const match = output.match(pattern);
5754
- if (match) {
5755
- this.currentConversationId = match[1];
5756
- return;
5757
- }
5758
- }
6391
+ const match = output.match(/Resuming session[:\s]+([a-f0-9-]{36})/i);
6392
+ if (match) this.currentConversationId = match[1];
5759
6393
  }
5760
6394
  /**
5761
6395
  * Read the most recently modified JSONL session file and extract the
@@ -5767,28 +6401,30 @@ var HistoryService = class {
5767
6401
  */
5768
6402
  getCurrentUsage() {
5769
6403
  const dir = this.projectDir;
6404
+ const cutoff = this.bootTimeMs - _HistoryService.BIRTHTIME_GRACE_MS;
5770
6405
  let entries;
5771
6406
  try {
5772
- entries = fs5.readdirSync(dir, { withFileTypes: true });
6407
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
5773
6408
  } catch {
5774
6409
  return null;
5775
6410
  }
5776
6411
  const files = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
5777
6412
  try {
5778
- return { name: e.name, mtime: fs5.statSync(path8.join(dir, e.name)).mtimeMs };
6413
+ const stat3 = fs9.statSync(path12.join(dir, e.name));
6414
+ return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
5779
6415
  } catch {
5780
- return { name: e.name, mtime: 0 };
6416
+ return { name: e.name, mtime: 0, birthtime: 0 };
5781
6417
  }
5782
- }).sort((a, b) => b.mtime - a.mtime);
6418
+ }).filter((f) => f.birthtime >= cutoff).sort((a, b) => b.mtime - a.mtime);
5783
6419
  if (files.length === 0) return null;
5784
6420
  const targetFile = this.currentConversationId ? `${this.currentConversationId}.jsonl` : files[0].name;
5785
6421
  if (!files.some((f) => f.name === targetFile)) return null;
5786
- return this.extractUsageFromFile(path8.join(dir, targetFile));
6422
+ return this.extractUsageFromFile(path12.join(dir, targetFile));
5787
6423
  }
5788
6424
  extractUsageFromFile(filePath) {
5789
6425
  let raw;
5790
6426
  try {
5791
- raw = fs5.readFileSync(filePath, "utf8");
6427
+ raw = fs9.readFileSync(filePath, "utf8");
5792
6428
  } catch {
5793
6429
  return null;
5794
6430
  }
@@ -5833,9 +6469,9 @@ var HistoryService = class {
5833
6469
  let totalCost = 0;
5834
6470
  let files;
5835
6471
  try {
5836
- files = fs5.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).filter((f) => {
6472
+ files = fs9.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).filter((f) => {
5837
6473
  try {
5838
- return fs5.statSync(path8.join(projectDir, f)).mtimeMs >= monthStartMs;
6474
+ return fs9.statSync(path12.join(projectDir, f)).mtimeMs >= monthStartMs;
5839
6475
  } catch {
5840
6476
  return false;
5841
6477
  }
@@ -5846,7 +6482,7 @@ var HistoryService = class {
5846
6482
  for (const file of files) {
5847
6483
  let raw;
5848
6484
  try {
5849
- raw = fs5.readFileSync(path8.join(projectDir, file), "utf8");
6485
+ raw = fs9.readFileSync(path12.join(projectDir, file), "utf8");
5850
6486
  } catch {
5851
6487
  continue;
5852
6488
  }
@@ -5881,30 +6517,30 @@ var HistoryService = class {
5881
6517
  const dir = this.projectDir;
5882
6518
  let entries;
5883
6519
  try {
5884
- entries = fs5.readdirSync(dir, { withFileTypes: true });
6520
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
5885
6521
  } catch {
5886
6522
  return;
5887
6523
  }
5888
6524
  const sessions2 = [];
5889
6525
  for (const entry of entries) {
5890
6526
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
5891
- const id = path8.basename(entry.name, ".jsonl");
5892
- const filePath = path8.join(dir, entry.name);
6527
+ const id = path12.basename(entry.name, ".jsonl");
6528
+ const filePath = path12.join(dir, entry.name);
5893
6529
  let mtime = Date.now();
5894
6530
  try {
5895
- mtime = fs5.statSync(filePath).mtimeMs;
6531
+ mtime = fs9.statSync(filePath).mtimeMs;
5896
6532
  } catch {
5897
6533
  }
5898
6534
  let summary = "";
5899
6535
  try {
5900
- const raw = fs5.readFileSync(filePath, "utf8");
6536
+ const raw = fs9.readFileSync(filePath, "utf8");
5901
6537
  for (const line of raw.split("\n")) {
5902
6538
  if (!line.trim()) continue;
5903
6539
  try {
5904
6540
  const record = JSON.parse(line);
5905
6541
  if (record["type"] === "user") {
5906
6542
  const msg = record["message"];
5907
- const text = extractText(msg?.["content"]).trim();
6543
+ const text = extractText2(msg?.["content"]).trim();
5908
6544
  if (text) {
5909
6545
  summary = text.slice(0, 120);
5910
6546
  break;
@@ -5930,7 +6566,7 @@ var HistoryService = class {
5930
6566
  * showing an empty conversation.
5931
6567
  */
5932
6568
  async loadConversation(sessionId) {
5933
- const filePath = path8.join(this.projectDir, `${sessionId}.jsonl`);
6569
+ const filePath = path12.join(this.projectDir, `${sessionId}.jsonl`);
5934
6570
  const messages = parseJsonl(filePath);
5935
6571
  if (messages.length === 0) return;
5936
6572
  const totalBatches = Math.ceil(messages.length / CONVERSATION_BATCH_SIZE);
@@ -5973,7 +6609,7 @@ var HistoryService = class {
5973
6609
  if (!this.currentConversationId) return 0;
5974
6610
  }
5975
6611
  const sessionId = this.currentConversationId;
5976
- const filePath = path8.join(this.projectDir, `${sessionId}.jsonl`);
6612
+ const filePath = path12.join(this.projectDir, `${sessionId}.jsonl`);
5977
6613
  const messages = parseJsonl(filePath);
5978
6614
  if (messages.length === 0) return 0;
5979
6615
  const marker = this.lastUploadedUuid.get(sessionId);
@@ -6001,119 +6637,28 @@ var HistoryService = class {
6001
6637
  }
6002
6638
  };
6003
6639
 
6004
- // src/commands/start/quota-fetcher.ts
6005
- var fs6 = __toESM(require("fs"));
6006
- var os7 = __toESM(require("os"));
6007
- var path9 = __toESM(require("path"));
6008
- var import_child_process4 = require("child_process");
6009
- var inProgress = false;
6010
- var HELPER_SCRIPT = `import os,pty,sys,select,signal,struct,fcntl,termios,errno
6011
- m,s=pty.openpty()
6012
- try:
6013
- fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',30,120,0,0))
6014
- except Exception:pass
6015
- pid=os.fork()
6016
- if pid==0:
6017
- os.close(m);os.setsid()
6018
- try:fcntl.ioctl(s,termios.TIOCSCTTY,0)
6019
- except Exception:pass
6020
- for fd in[0,1,2]:os.dup2(s,fd)
6021
- if s>2:os.close(s)
6022
- os.execvp(sys.argv[1],sys.argv[1:])
6023
- sys.exit(127)
6024
- os.close(s)
6025
- done=[False]
6026
- def onchld(n,f):
6027
- try:os.waitpid(pid,os.WNOHANG)
6028
- except Exception:pass
6029
- done[0]=True
6030
- signal.signal(signal.SIGCHLD,onchld)
6031
- i=sys.stdin.fileno();o=sys.stdout.fileno()
6032
- while not done[0]:
6033
- try:r,_,_=select.select([i,m],[],[],0.1)
6034
- except OSError as e:
6035
- if e.errno==errno.EINTR:continue
6036
- break
6037
- if i in r:
6038
- try:
6039
- d=os.read(i,4096)
6040
- if d:os.write(m,d)
6041
- else:break
6042
- except OSError:break
6043
- if m in r:
6044
- try:
6045
- d=os.read(m,4096)
6046
- if d:os.write(o,d)
6047
- except OSError:done[0]=True
6048
- try:os.kill(pid,signal.SIGTERM)
6049
- except Exception:pass
6050
- try:
6051
- _,st=os.waitpid(pid,0)
6052
- sys.exit((st>>8)&0xFF)
6053
- except Exception:sys.exit(0)
6054
- `;
6055
- function fetchQuotaUsage(historySvc) {
6640
+ // src/commands/start/quota-fetcher.ts
6641
+ var inProgress = false;
6642
+ function fetchQuotaUsage(runtime, historySvc) {
6056
6643
  if (inProgress) return;
6057
6644
  inProgress = true;
6058
- const claudeCmd = findInPath("claude") ? "claude" : "claude-code";
6059
- if (!claudeCmd) {
6060
- inProgress = false;
6061
- return;
6062
- }
6063
- const helperPath = path9.join(os7.tmpdir(), "codeam-quota-helper.py");
6064
- fs6.writeFileSync(helperPath, HELPER_SCRIPT, { mode: 420 });
6065
- const python = findInPath("python3") ?? findInPath("python");
6066
- if (!python) {
6067
- inProgress = false;
6068
- return;
6069
- }
6070
- const proc = (0, import_child_process4.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
6071
- stdio: ["pipe", "pipe", "ignore"],
6072
- cwd: process.cwd(),
6073
- env: { ...process.env, TERM: "dumb", COLUMNS: "120", LINES: "30" }
6074
- });
6075
- let output = "";
6076
- proc.stdout?.on("data", (chunk) => {
6077
- output += chunk.toString("utf8");
6078
- });
6079
- setTimeout(() => {
6080
- proc.stdin?.write("/usage\r");
6081
- setTimeout(() => {
6082
- const clean = output.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, " ").replace(/\s+/g, " ");
6083
- const weekMatch = clean.match(/(\d+)%\s*used/i) || clean.match(/(\d+)\s*%/);
6084
- if (weekMatch) historySvc.setQuotaPercent(parseInt(weekMatch[1], 10));
6085
- const resetMatch = clean.match(/resets\s+(.+?)(?:\s*\(|$)/im);
6086
- if (resetMatch) historySvc.setRateLimitReset(resetMatch[1].trim());
6087
- try {
6088
- proc.kill();
6089
- } catch {
6090
- }
6091
- try {
6092
- fs6.unlinkSync(helperPath);
6093
- } catch {
6094
- }
6095
- inProgress = false;
6096
- }, 5e3);
6097
- }, 8e3);
6098
- proc.on("exit", () => {
6645
+ runtime.fetchWeeklyUsage().then((result) => {
6646
+ if (!result) return;
6647
+ historySvc.setQuotaPercent(result.percent);
6648
+ if (result.resetAt) historySvc.setRateLimitReset(result.resetAt);
6649
+ }).finally(() => {
6099
6650
  inProgress = false;
6100
6651
  });
6101
- setTimeout(() => {
6102
- try {
6103
- proc.kill();
6104
- } catch {
6105
- }
6106
- }, 2e4);
6107
6652
  }
6108
6653
 
6109
6654
  // src/commands/start/keep-alive.ts
6110
- var import_child_process5 = require("child_process");
6655
+ var import_child_process6 = require("child_process");
6111
6656
  function buildKeepAlive(ctx) {
6112
6657
  let timer = null;
6113
6658
  async function setIdleTimeout(minutes) {
6114
6659
  if (!ctx.inCodespace || !ctx.codespaceName) return;
6115
6660
  await new Promise((resolve2) => {
6116
- const proc = (0, import_child_process5.spawn)(
6661
+ const proc = (0, import_child_process6.spawn)(
6117
6662
  "gh",
6118
6663
  [
6119
6664
  "api",
@@ -6150,11 +6695,11 @@ function buildKeepAlive(ctx) {
6150
6695
  }
6151
6696
 
6152
6697
  // src/commands/start/handlers.ts
6153
- var fs9 = __toESM(require("fs"));
6154
- var os8 = __toESM(require("os"));
6155
- var path12 = __toESM(require("path"));
6698
+ var fs12 = __toESM(require("fs"));
6699
+ var os11 = __toESM(require("os"));
6700
+ var path15 = __toESM(require("path"));
6156
6701
  var import_crypto = require("crypto");
6157
- var import_child_process7 = require("child_process");
6702
+ var import_child_process8 = require("child_process");
6158
6703
 
6159
6704
  // src/lib/payload.ts
6160
6705
  var import_zod2 = require("zod");
@@ -6191,8 +6736,8 @@ function parsePayload(schema, raw) {
6191
6736
  }
6192
6737
 
6193
6738
  // src/services/file-ops.service.ts
6194
- var fs7 = __toESM(require("fs/promises"));
6195
- var path10 = __toESM(require("path"));
6739
+ var fs10 = __toESM(require("fs/promises"));
6740
+ var path13 = __toESM(require("path"));
6196
6741
  var MAX_FILE_BYTES = 5 * 1024 * 1024;
6197
6742
  var MAX_WALK_DEPTH = 6;
6198
6743
  var MAX_VISITED_DIRS = 5e3;
@@ -6227,12 +6772,12 @@ var SUBDIR_IGNORE = /* @__PURE__ */ new Set([
6227
6772
  "__pycache__"
6228
6773
  ]);
6229
6774
  function isUnder(parent, candidate) {
6230
- const rel = path10.relative(parent, candidate);
6231
- return rel === "" || !rel.startsWith("..") && !path10.isAbsolute(rel);
6775
+ const rel = path13.relative(parent, candidate);
6776
+ return rel === "" || !rel.startsWith("..") && !path13.isAbsolute(rel);
6232
6777
  }
6233
6778
  async function isExistingFile(absPath) {
6234
6779
  try {
6235
- const stat3 = await fs7.stat(absPath);
6780
+ const stat3 = await fs10.stat(absPath);
6236
6781
  return stat3.isFile();
6237
6782
  } catch {
6238
6783
  return false;
@@ -6245,13 +6790,13 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
6245
6790
  ctx.visited++;
6246
6791
  let entries = [];
6247
6792
  try {
6248
- entries = await fs7.readdir(dir, { withFileTypes: true });
6793
+ entries = await fs10.readdir(dir, { withFileTypes: true });
6249
6794
  } catch {
6250
6795
  return;
6251
6796
  }
6252
6797
  for (const e of entries) {
6253
6798
  if (!e.isFile()) continue;
6254
- const full = path10.join(dir, e.name);
6799
+ const full = path13.join(dir, e.name);
6255
6800
  if (needleVariants.some((needle) => full.endsWith(needle))) {
6256
6801
  ctx.matches.push(full);
6257
6802
  if (ctx.matches.length >= ctx.cap) return;
@@ -6261,21 +6806,21 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
6261
6806
  if (!e.isDirectory()) continue;
6262
6807
  if (SUBDIR_IGNORE.has(e.name)) continue;
6263
6808
  if (e.name.startsWith(".") && SUBDIR_IGNORE.has(e.name)) continue;
6264
- await walkForSuffix(path10.join(dir, e.name), needleVariants, depth + 1, ctx);
6809
+ await walkForSuffix(path13.join(dir, e.name), needleVariants, depth + 1, ctx);
6265
6810
  if (ctx.matches.length >= ctx.cap) return;
6266
6811
  }
6267
6812
  }
6268
6813
  async function findFile(rawPath) {
6269
6814
  const cwd = process.cwd();
6270
- if (path10.isAbsolute(rawPath)) {
6271
- const abs = path10.normalize(rawPath);
6815
+ if (path13.isAbsolute(rawPath)) {
6816
+ const abs = path13.normalize(rawPath);
6272
6817
  if (isUnder(cwd, abs) && await isExistingFile(abs)) return abs;
6273
6818
  }
6274
- const direct = path10.resolve(cwd, rawPath);
6819
+ const direct = path13.resolve(cwd, rawPath);
6275
6820
  if (isUnder(cwd, direct) && await isExistingFile(direct)) return direct;
6276
- const normalized = path10.normalize(rawPath).replace(/^[./\\]+/, "");
6821
+ const normalized = path13.normalize(rawPath).replace(/^[./\\]+/, "");
6277
6822
  const needles = [
6278
- `${path10.sep}${normalized}`,
6823
+ `${path13.sep}${normalized}`,
6279
6824
  `/${normalized}`
6280
6825
  ].filter((v, i, a) => a.indexOf(v) === i);
6281
6826
  const ctx = { visited: 0, matches: [], cap: 16 };
@@ -6289,7 +6834,7 @@ async function findWriteTarget(rawPath) {
6289
6834
  const found = await findFile(rawPath);
6290
6835
  if (found) return found;
6291
6836
  const cwd = process.cwd();
6292
- const fallback = path10.isAbsolute(rawPath) ? path10.normalize(rawPath) : path10.resolve(cwd, rawPath);
6837
+ const fallback = path13.isAbsolute(rawPath) ? path13.normalize(rawPath) : path13.resolve(cwd, rawPath);
6293
6838
  if (!isUnder(cwd, fallback)) return null;
6294
6839
  return fallback;
6295
6840
  }
@@ -6306,11 +6851,11 @@ async function readProjectFile(rawPath) {
6306
6851
  if (!abs) {
6307
6852
  return { error: `File not found in the project tree: ${rawPath}` };
6308
6853
  }
6309
- const stat3 = await fs7.stat(abs);
6854
+ const stat3 = await fs10.stat(abs);
6310
6855
  if (stat3.size > MAX_FILE_BYTES) {
6311
6856
  return { error: `File too large (${(stat3.size / 1024 / 1024).toFixed(1)} MB > ${MAX_FILE_BYTES / 1024 / 1024} MB).` };
6312
6857
  }
6313
- const buf = await fs7.readFile(abs);
6858
+ const buf = await fs10.readFile(abs);
6314
6859
  if (looksBinary(buf)) {
6315
6860
  return { error: "Binary file \u2014 refusing to open in a code editor." };
6316
6861
  }
@@ -6329,8 +6874,8 @@ async function writeProjectFile(rawPath, content) {
6329
6874
  if (Buffer.byteLength(content, "utf-8") > MAX_FILE_BYTES) {
6330
6875
  return { error: "Content too large." };
6331
6876
  }
6332
- await fs7.mkdir(path10.dirname(abs), { recursive: true });
6333
- await fs7.writeFile(abs, content, "utf-8");
6877
+ await fs10.mkdir(path13.dirname(abs), { recursive: true });
6878
+ await fs10.writeFile(abs, content, "utf-8");
6334
6879
  return { ok: true };
6335
6880
  } catch (e) {
6336
6881
  const msg = e instanceof Error ? e.message : "Write failed";
@@ -6339,11 +6884,11 @@ async function writeProjectFile(rawPath, content) {
6339
6884
  }
6340
6885
 
6341
6886
  // src/services/project-ops.service.ts
6342
- var import_child_process6 = require("child_process");
6343
- var import_util = require("util");
6344
- var fs8 = __toESM(require("fs/promises"));
6345
- var path11 = __toESM(require("path"));
6346
- var execFileP = (0, import_util.promisify)(import_child_process6.execFile);
6887
+ var import_child_process7 = require("child_process");
6888
+ var import_util2 = require("util");
6889
+ var fs11 = __toESM(require("fs/promises"));
6890
+ var path14 = __toESM(require("path"));
6891
+ var execFileP2 = (0, import_util2.promisify)(import_child_process7.execFile);
6347
6892
  var PROJECT_IGNORE = /* @__PURE__ */ new Set([
6348
6893
  "node_modules",
6349
6894
  ".git",
@@ -6390,7 +6935,7 @@ async function listProjectFiles(opts = {}) {
6390
6935
  }
6391
6936
  let entries = [];
6392
6937
  try {
6393
- entries = await fs8.readdir(dir, { withFileTypes: true });
6938
+ entries = await fs11.readdir(dir, { withFileTypes: true });
6394
6939
  } catch {
6395
6940
  return;
6396
6941
  }
@@ -6400,18 +6945,18 @@ async function listProjectFiles(opts = {}) {
6400
6945
  return;
6401
6946
  }
6402
6947
  if (PROJECT_IGNORE.has(e.name)) continue;
6403
- const full = path11.join(dir, e.name);
6948
+ const full = path14.join(dir, e.name);
6404
6949
  if (e.isDirectory()) {
6405
6950
  if (depth >= 12) continue;
6406
6951
  await walk(full, depth + 1);
6407
6952
  } else if (e.isFile()) {
6408
- const rel = path11.relative(root, full);
6953
+ const rel = path14.relative(root, full);
6409
6954
  if (q2 && !rel.toLowerCase().includes(q2) && !e.name.toLowerCase().includes(q2)) {
6410
6955
  continue;
6411
6956
  }
6412
6957
  let size = 0;
6413
6958
  try {
6414
- const st3 = await fs8.stat(full);
6959
+ const st3 = await fs11.stat(full);
6415
6960
  size = st3.size;
6416
6961
  } catch {
6417
6962
  }
@@ -6425,7 +6970,7 @@ async function listProjectFiles(opts = {}) {
6425
6970
  }
6426
6971
  async function git(args2, cwd) {
6427
6972
  try {
6428
- const { stdout, stderr } = await execFileP("git", args2, {
6973
+ const { stdout, stderr } = await execFileP2("git", args2, {
6429
6974
  cwd: cwd ?? process.cwd(),
6430
6975
  maxBuffer: MAX_GIT_OUTPUT,
6431
6976
  timeout: 3e4
@@ -6513,8 +7058,8 @@ async function gitStatus(cwd) {
6513
7058
  let hasMergeInProgress = false;
6514
7059
  try {
6515
7060
  const gitDir = (await git(["rev-parse", "--git-dir"], root)).stdout.trim();
6516
- const mergeHead = path11.isAbsolute(gitDir) ? path11.join(gitDir, "MERGE_HEAD") : path11.join(root, gitDir, "MERGE_HEAD");
6517
- await fs8.access(mergeHead);
7061
+ const mergeHead = path14.isAbsolute(gitDir) ? path14.join(gitDir, "MERGE_HEAD") : path14.join(root, gitDir, "MERGE_HEAD");
7062
+ await fs11.access(mergeHead);
6518
7063
  hasMergeInProgress = true;
6519
7064
  } catch {
6520
7065
  }
@@ -6591,8 +7136,8 @@ async function gitResolve(file, side, cwd) {
6591
7136
  function saveFilesTemp(files) {
6592
7137
  return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
6593
7138
  const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
6594
- const tmpPath = path12.join(os8.tmpdir(), `codeam-${(0, import_crypto.randomUUID)()}-${safeName}`);
6595
- fs9.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
7139
+ const tmpPath = path15.join(os11.tmpdir(), `codeam-${(0, import_crypto.randomUUID)()}-${safeName}`);
7140
+ fs12.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
6596
7141
  return tmpPath;
6597
7142
  });
6598
7143
  }
@@ -6611,7 +7156,7 @@ var startTask = (ctx, _cmd, parsed) => {
6611
7156
  setTimeout(() => {
6612
7157
  for (const p2 of paths) {
6613
7158
  try {
6614
- fs9.unlinkSync(p2);
7159
+ fs12.unlinkSync(p2);
6615
7160
  } catch {
6616
7161
  }
6617
7162
  }
@@ -6671,14 +7216,35 @@ var getConversation = async (ctx, cmd) => {
6671
7216
  }
6672
7217
  };
6673
7218
  var listModels = async (ctx, cmd) => {
6674
- const models = [
6675
- { id: "claude-opus-4-7", label: "Claude Opus 4.7", description: "Most capable", family: "claude", vendor: "anthropic", isDefault: false },
6676
- { id: "claude-opus-4-6", label: "Claude Opus 4.6", description: "Top tier", family: "claude", vendor: "anthropic", isDefault: false },
6677
- { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", description: "Balanced", family: "claude", vendor: "anthropic", isDefault: true },
6678
- { id: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", description: "Fastest", family: "claude", vendor: "anthropic", isDefault: false }
6679
- ];
7219
+ const models = await ctx.runtime.listModels();
6680
7220
  await ctx.relay.sendResult(cmd.id, "completed", { models });
6681
7221
  };
7222
+ var changeModel = async (ctx, cmd) => {
7223
+ const params = cmd.payload;
7224
+ if (typeof params.modelId !== "string" || !params.modelId) {
7225
+ await ctx.relay.sendResult(cmd.id, "failed", { error: "modelId required" });
7226
+ return;
7227
+ }
7228
+ const instr = ctx.runtime.changeModelInstruction(params.modelId);
7229
+ if (instr.type === "pty") {
7230
+ if (!instr.ptyInput) {
7231
+ await ctx.relay.sendResult(cmd.id, "failed", { error: "no pty input for this agent" });
7232
+ return;
7233
+ }
7234
+ ctx.claude.sendRawPtyInput(instr.ptyInput);
7235
+ } else if (instr.type === "restart") {
7236
+ await ctx.relay.sendResult(cmd.id, "failed", { error: "restart-mode change_model not supported in Phase 1" });
7237
+ return;
7238
+ }
7239
+ await ctx.relay.sendResult(cmd.id, "completed", {});
7240
+ };
7241
+ var summarize = async (ctx, cmd) => {
7242
+ const params = cmd.payload;
7243
+ const mode = params.mode === "auto" ? "auto" : "normal";
7244
+ const instr = ctx.runtime.summarizeInstruction(mode);
7245
+ ctx.claude.sendRawPtyInput(instr.ptyInput);
7246
+ await ctx.relay.sendResult(cmd.id, "completed", {});
7247
+ };
6682
7248
  var setKeepAlive = async (ctx, cmd) => {
6683
7249
  const enabled = !!cmd.payload.enabled;
6684
7250
  ctx.setKeepAlive(enabled);
@@ -6702,7 +7268,7 @@ var sessionTerminated = (ctx) => {
6702
7268
  } catch {
6703
7269
  }
6704
7270
  try {
6705
- const proc = (0, import_child_process7.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
7271
+ const proc = (0, import_child_process8.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
6706
7272
  detached: true,
6707
7273
  stdio: "ignore"
6708
7274
  });
@@ -6724,7 +7290,7 @@ var shutdownSession = async (ctx, cmd) => {
6724
7290
  }
6725
7291
  if (ctx.keepAliveCtx.inCodespace && ctx.keepAliveCtx.codespaceName) {
6726
7292
  try {
6727
- const stopProc = (0, import_child_process7.spawn)(
7293
+ const stopProc = (0, import_child_process8.spawn)(
6728
7294
  "bash",
6729
7295
  ["-lc", `sleep 1; gh codespace stop -c ${JSON.stringify(ctx.keepAliveCtx.codespaceName)} >/dev/null 2>&1 || true`],
6730
7296
  { detached: true, stdio: "ignore" }
@@ -6734,7 +7300,7 @@ var shutdownSession = async (ctx, cmd) => {
6734
7300
  }
6735
7301
  }
6736
7302
  try {
6737
- const proc = (0, import_child_process7.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
7303
+ const proc = (0, import_child_process8.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
6738
7304
  detached: true,
6739
7305
  stdio: "ignore"
6740
7306
  });
@@ -6815,6 +7381,8 @@ var handlers = {
6815
7381
  get_context: getContext,
6816
7382
  get_conversation: getConversation,
6817
7383
  list_models: listModels,
7384
+ change_model: changeModel,
7385
+ summarize,
6818
7386
  set_keep_alive: setKeepAlive,
6819
7387
  session_terminated: sessionTerminated,
6820
7388
  shutdown_session: shutdownSession,
@@ -6851,11 +7419,16 @@ async function start() {
6851
7419
  `);
6852
7420
  process.exit(0);
6853
7421
  }
7422
+ if (!session.agent) {
7423
+ throw new Error("Active session has no agent \u2014 re-pair with `codeam pair`.");
7424
+ }
6854
7425
  const pluginId = session.pluginId ?? ensurePluginId();
6855
7426
  showInfo(`${session.userName} \xB7 ${import_picocolors2.default.cyan(session.plan)}`);
6856
- showInfo("Launching Claude Code...\n");
7427
+ showInfo(`Launching ${AGENT_REGISTRY[session.agent].displayName}...
7428
+ `);
6857
7429
  const cwd = process.cwd();
6858
- const historySvc = new HistoryService(pluginId, cwd);
7430
+ const runtime = createRuntimeStrategy(session.agent);
7431
+ const historySvc = new HistoryService(runtime, pluginId, cwd);
6859
7432
  const keepAliveCtx = {
6860
7433
  inCodespace: process.env.CODESPACES === "true",
6861
7434
  codespaceName: process.env.CODESPACE_NAME
@@ -6867,7 +7440,7 @@ async function start() {
6867
7440
  (conversationId) => historySvc.setCurrentConversationId(conversationId),
6868
7441
  (reset) => historySvc.setRateLimitReset(reset),
6869
7442
  () => {
6870
- if (historySvc.isQuotaStale()) fetchQuotaUsage(historySvc);
7443
+ if (historySvc.isQuotaStale()) fetchQuotaUsage(runtime, historySvc);
6871
7444
  setTimeout(() => {
6872
7445
  historySvc.uploadDelta().catch(() => {
6873
7446
  });
@@ -6879,22 +7452,26 @@ async function start() {
6879
7452
  },
6880
7453
  session.pluginAuthToken
6881
7454
  );
6882
- const claude = new ClaudeService({
6883
- cwd,
6884
- onData(raw) {
6885
- outputSvc.push(raw);
6886
- },
6887
- onExit(code) {
6888
- process.removeListener("SIGINT", sigintHandler);
6889
- outputSvc.dispose();
6890
- relay.stop();
6891
- process.exit(code);
7455
+ const claude = new AgentService(
7456
+ runtime,
7457
+ {
7458
+ cwd,
7459
+ onData(raw) {
7460
+ outputSvc.push(raw);
7461
+ },
7462
+ onExit(code) {
7463
+ process.removeListener("SIGINT", sigintHandler);
7464
+ outputSvc.dispose();
7465
+ relay.stop();
7466
+ process.exit(code);
7467
+ }
6892
7468
  }
6893
- });
7469
+ );
6894
7470
  const ctx = {
6895
7471
  outputSvc,
6896
7472
  claude,
6897
7473
  historySvc,
7474
+ runtime,
6898
7475
  relay: void 0,
6899
7476
  setKeepAlive: setKeepAlive2,
6900
7477
  keepAliveCtx
@@ -6917,7 +7494,7 @@ async function start() {
6917
7494
  historySvc.load().catch(() => {
6918
7495
  });
6919
7496
  }, 2e3);
6920
- setTimeout(() => fetchQuotaUsage(historySvc), 5e3);
7497
+ setTimeout(() => fetchQuotaUsage(runtime, historySvc), 5e3);
6921
7498
  }
6922
7499
 
6923
7500
  // src/commands/pair.ts
@@ -6944,8 +7521,45 @@ async function selectSession(sessions2, activeId) {
6944
7521
  return result;
6945
7522
  }
6946
7523
 
7524
+ // src/utils/agent-prompt.ts
7525
+ function parseAgentFlag(args2) {
7526
+ const flag = args2.find((a) => a.startsWith("--agent="));
7527
+ if (!flag) return null;
7528
+ const value = flag.slice("--agent=".length);
7529
+ if (!isKnownAgentId(value)) {
7530
+ throw new Error(
7531
+ `invalid agent "${value}"; valid: ${Object.keys(AGENT_REGISTRY).join(", ")}`
7532
+ );
7533
+ }
7534
+ if (!AGENT_REGISTRY[value].enabled) {
7535
+ throw new Error(
7536
+ `${AGENT_REGISTRY[value].displayName} is not available in this codeam-cli version`
7537
+ );
7538
+ }
7539
+ return value;
7540
+ }
7541
+ async function promptForAgent(initialValue) {
7542
+ const enabled = getEnabledAgents();
7543
+ if (enabled.length === 1) {
7544
+ return enabled[0].id;
7545
+ }
7546
+ const chosen = await _t({
7547
+ message: "Pick an agent:",
7548
+ options: enabled.map((m) => ({ value: m.id, label: m.displayName })),
7549
+ initialValue: initialValue ?? enabled[0].id
7550
+ });
7551
+ if (q(chosen)) {
7552
+ pt("Cancelled.");
7553
+ process.exit(0);
7554
+ }
7555
+ return chosen;
7556
+ }
7557
+
6947
7558
  // src/commands/pair.ts
6948
- async function pair() {
7559
+ async function pair(args2 = []) {
7560
+ const config = loadCliConfig();
7561
+ const flagAgent = parseAgentFlag(args2);
7562
+ const agentId = flagAgent ?? await promptForAgent(config.preferredAgent ?? "claude");
6949
7563
  showIntro();
6950
7564
  const pluginId = (0, import_crypto2.randomUUID)();
6951
7565
  const spin = dist_exports.spinner();
@@ -6981,8 +7595,10 @@ async function pair() {
6981
7595
  userEmail: info.userEmail,
6982
7596
  plan: info.plan,
6983
7597
  pairedAt: Date.now(),
6984
- pluginAuthToken: info.pluginAuthToken
7598
+ pluginAuthToken: info.pluginAuthToken,
7599
+ agent: agentId
6985
7600
  });
7601
+ saveCliConfig({ ...loadCliConfig(), preferredAgent: agentId });
6986
7602
  showSuccess(`Paired with ${info.userName} (${info.plan})`);
6987
7603
  console.log("");
6988
7604
  resolve2();
@@ -6999,8 +7615,8 @@ async function pair() {
6999
7615
  }
7000
7616
 
7001
7617
  // src/commands/pair-auto.ts
7002
- var fs10 = __toESM(require("fs"));
7003
- var os9 = __toESM(require("os"));
7618
+ var fs13 = __toESM(require("fs"));
7619
+ var os12 = __toESM(require("os"));
7004
7620
  var import_crypto3 = require("crypto");
7005
7621
  var API_BASE5 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
7006
7622
  function fail(msg) {
@@ -7017,12 +7633,12 @@ function readTokenFromArgs(args2) {
7017
7633
  }
7018
7634
  const fileFlag = args2.find((a) => a.startsWith("--token-file="));
7019
7635
  if (fileFlag) {
7020
- const path19 = fileFlag.slice("--token-file=".length);
7636
+ const path21 = fileFlag.slice("--token-file=".length);
7021
7637
  try {
7022
- const content = fs10.readFileSync(path19, "utf8").trim();
7023
- if (content.length === 0) fail(`--token-file ${path19} is empty`);
7638
+ const content = fs13.readFileSync(path21, "utf8").trim();
7639
+ if (content.length === 0) fail(`--token-file ${path21} is empty`);
7024
7640
  try {
7025
- fs10.unlinkSync(path19);
7641
+ fs13.unlinkSync(path21);
7026
7642
  } catch {
7027
7643
  }
7028
7644
  return content;
@@ -7040,7 +7656,7 @@ async function claim(token, pluginId) {
7040
7656
  pluginId,
7041
7657
  ideName: "codeam-cli (codespace)",
7042
7658
  ideVersion: process.env.npm_package_version ?? "unknown",
7043
- hostname: os9.hostname(),
7659
+ hostname: os12.hostname(),
7044
7660
  codespaceName: process.env.CODESPACE_NAME ?? ""
7045
7661
  };
7046
7662
  const res = await fetch(url, {
@@ -7066,6 +7682,11 @@ async function pairAuto(args2) {
7066
7682
  const pluginId = (0, import_crypto3.randomUUID)();
7067
7683
  console.log(" Claiming pairing token\u2026");
7068
7684
  const claimed = await claim(token, pluginId);
7685
+ if (!isKnownAgentId(claimed.agent)) {
7686
+ fail(
7687
+ `agent "${claimed.agent}" is not supported in this codeam-cli version. Upgrade with 'npm i -g codeam-cli@latest'.`
7688
+ );
7689
+ }
7069
7690
  addSession({
7070
7691
  id: claimed.sessionId,
7071
7692
  pluginId,
@@ -7073,7 +7694,8 @@ async function pairAuto(args2) {
7073
7694
  userEmail: claimed.user.email,
7074
7695
  plan: claimed.user.plan,
7075
7696
  pairedAt: Date.now(),
7076
- pluginAuthToken: claimed.pluginAuthToken
7697
+ pluginAuthToken: claimed.pluginAuthToken,
7698
+ agent: claimed.agent
7077
7699
  });
7078
7700
  console.log(` Paired with ${claimed.user.name} (${claimed.user.plan})`);
7079
7701
  console.log(" Starting agent loop\u2026");
@@ -7180,19 +7802,14 @@ async function logout() {
7180
7802
  }
7181
7803
 
7182
7804
  // src/commands/deploy.ts
7183
- var import_child_process12 = require("child_process");
7184
- var fs11 = __toESM(require("fs"));
7185
- var os10 = __toESM(require("os"));
7186
- var path17 = __toESM(require("path"));
7187
- var import_util6 = require("util");
7188
7805
  var import_picocolors9 = __toESM(require("picocolors"));
7189
7806
 
7190
7807
  // src/services/providers/github-codespaces.ts
7191
- var import_child_process8 = require("child_process");
7192
- var import_util2 = require("util");
7808
+ var import_child_process9 = require("child_process");
7809
+ var import_util3 = require("util");
7193
7810
  var import_picocolors7 = __toESM(require("picocolors"));
7194
- var path13 = __toESM(require("path"));
7195
- var execFileP2 = (0, import_util2.promisify)(import_child_process8.execFile);
7811
+ var path16 = __toESM(require("path"));
7812
+ var execFileP3 = (0, import_util3.promisify)(import_child_process9.execFile);
7196
7813
  var MAX_BUFFER = 8 * 1024 * 1024;
7197
7814
  function resetStdinForChild() {
7198
7815
  if (process.stdin.isTTY) {
@@ -7209,11 +7826,11 @@ var GitHubCodespacesProvider = class {
7209
7826
  available = true;
7210
7827
  async authorize() {
7211
7828
  try {
7212
- await execFileP2("gh", ["--version"], { maxBuffer: MAX_BUFFER });
7829
+ await execFileP3("gh", ["--version"], { maxBuffer: MAX_BUFFER });
7213
7830
  } catch {
7214
7831
  await this.tryInstallGh();
7215
7832
  try {
7216
- await execFileP2("gh", ["--version"], { maxBuffer: MAX_BUFFER });
7833
+ await execFileP3("gh", ["--version"], { maxBuffer: MAX_BUFFER });
7217
7834
  } catch {
7218
7835
  throw new Error(
7219
7836
  [
@@ -7229,14 +7846,14 @@ var GitHubCodespacesProvider = class {
7229
7846
  }
7230
7847
  let isAuthed = false;
7231
7848
  try {
7232
- await execFileP2("gh", ["auth", "status"], { maxBuffer: MAX_BUFFER });
7849
+ await execFileP3("gh", ["auth", "status"], { maxBuffer: MAX_BUFFER });
7233
7850
  isAuthed = true;
7234
7851
  } catch {
7235
7852
  }
7236
7853
  if (!isAuthed) {
7237
7854
  resetStdinForChild();
7238
7855
  await new Promise((resolve2, reject) => {
7239
- const proc = (0, import_child_process8.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
7856
+ const proc = (0, import_child_process9.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
7240
7857
  stdio: "inherit"
7241
7858
  });
7242
7859
  proc.on("exit", (code) => {
@@ -7270,7 +7887,7 @@ var GitHubCodespacesProvider = class {
7270
7887
  wt(noteLines.join("\n"), "One more permission needed");
7271
7888
  resetStdinForChild();
7272
7889
  const refreshCode = await new Promise((resolve2, reject) => {
7273
- const proc = (0, import_child_process8.spawn)(
7890
+ const proc = (0, import_child_process9.spawn)(
7274
7891
  "gh",
7275
7892
  ["auth", "refresh", "-h", "github.com", "-s", "codespace"],
7276
7893
  { stdio: "inherit" }
@@ -7305,7 +7922,7 @@ var GitHubCodespacesProvider = class {
7305
7922
  */
7306
7923
  async getActiveGhUser() {
7307
7924
  try {
7308
- const { stdout } = await execFileP2(
7925
+ const { stdout } = await execFileP3(
7309
7926
  "gh",
7310
7927
  ["api", "user", "--jq", ".login"],
7311
7928
  { maxBuffer: MAX_BUFFER }
@@ -7325,7 +7942,7 @@ var GitHubCodespacesProvider = class {
7325
7942
  */
7326
7943
  async hasCodespaceScope() {
7327
7944
  try {
7328
- const { stdout } = await execFileP2(
7945
+ const { stdout } = await execFileP3(
7329
7946
  "gh",
7330
7947
  ["api", "-i", "user"],
7331
7948
  { maxBuffer: MAX_BUFFER }
@@ -7355,12 +7972,12 @@ var GitHubCodespacesProvider = class {
7355
7972
  * install error if it's still missing.
7356
7973
  */
7357
7974
  async tryInstallGh() {
7358
- const platform = process.platform;
7975
+ const platform2 = process.platform;
7359
7976
  wt(
7360
7977
  `GitHub CLI (${import_picocolors7.default.cyan("gh")}) is required for Codespaces deploys but isn't on your PATH.`,
7361
7978
  "Heads up"
7362
7979
  );
7363
- if (platform === "linux") {
7980
+ if (platform2 === "linux") {
7364
7981
  wt(
7365
7982
  [
7366
7983
  "On Linux, please install gh from the official guide:",
@@ -7372,9 +7989,9 @@ var GitHubCodespacesProvider = class {
7372
7989
  return;
7373
7990
  }
7374
7991
  let installCmd = null;
7375
- if (platform === "darwin") {
7992
+ if (platform2 === "darwin") {
7376
7993
  try {
7377
- await execFileP2("brew", ["--version"], { maxBuffer: MAX_BUFFER });
7994
+ await execFileP3("brew", ["--version"], { maxBuffer: MAX_BUFFER });
7378
7995
  } catch {
7379
7996
  wt(
7380
7997
  [
@@ -7391,9 +8008,9 @@ var GitHubCodespacesProvider = class {
7391
8008
  args: ["install", "gh"],
7392
8009
  describe: "brew install gh"
7393
8010
  };
7394
- } else if (platform === "win32") {
8011
+ } else if (platform2 === "win32") {
7395
8012
  try {
7396
- await execFileP2("winget", ["--version"], { maxBuffer: MAX_BUFFER });
8013
+ await execFileP3("winget", ["--version"], { maxBuffer: MAX_BUFFER });
7397
8014
  } catch {
7398
8015
  wt(
7399
8016
  [
@@ -7420,7 +8037,7 @@ var GitHubCodespacesProvider = class {
7420
8037
  O2.step(`Installing gh via ${installCmd.describe}\u2026`);
7421
8038
  resetStdinForChild();
7422
8039
  const ok = await new Promise((resolve2) => {
7423
- const proc = (0, import_child_process8.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
8040
+ const proc = (0, import_child_process9.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
7424
8041
  proc.on("exit", (code) => resolve2(code === 0));
7425
8042
  proc.on("error", () => resolve2(false));
7426
8043
  });
@@ -7447,7 +8064,7 @@ var GitHubCodespacesProvider = class {
7447
8064
  );
7448
8065
  resetStdinForChild();
7449
8066
  await new Promise((resolve2, reject) => {
7450
- const proc = (0, import_child_process8.spawn)(
8067
+ const proc = (0, import_child_process9.spawn)(
7451
8068
  "gh",
7452
8069
  ["auth", "refresh", "-h", "github.com", "-s", "repo,read:org"],
7453
8070
  { stdio: "inherit" }
@@ -7472,7 +8089,7 @@ var GitHubCodespacesProvider = class {
7472
8089
  "200"
7473
8090
  );
7474
8091
  try {
7475
- const { stdout } = await execFileP2("gh", args2, { maxBuffer: MAX_BUFFER });
8092
+ const { stdout } = await execFileP3("gh", args2, { maxBuffer: MAX_BUFFER });
7476
8093
  return JSON.parse(stdout);
7477
8094
  } catch {
7478
8095
  return [];
@@ -7481,7 +8098,7 @@ var GitHubCodespacesProvider = class {
7481
8098
  const own = await fetchRepos();
7482
8099
  let orgRepos = [];
7483
8100
  try {
7484
- const { stdout } = await execFileP2(
8101
+ const { stdout } = await execFileP3(
7485
8102
  "gh",
7486
8103
  ["api", "--paginate", "user/orgs", "--jq", ".[].login"],
7487
8104
  { maxBuffer: MAX_BUFFER }
@@ -7518,7 +8135,7 @@ var GitHubCodespacesProvider = class {
7518
8135
  */
7519
8136
  async listMachineTypes(projectId) {
7520
8137
  try {
7521
- const { stdout } = await execFileP2(
8138
+ const { stdout } = await execFileP3(
7522
8139
  "gh",
7523
8140
  ["api", `/repos/${projectId}/codespaces/machines`],
7524
8141
  { maxBuffer: MAX_BUFFER }
@@ -7549,7 +8166,7 @@ var GitHubCodespacesProvider = class {
7549
8166
  const machine = machineTypeId ?? await this.pickDefaultMachine(projectId);
7550
8167
  const args2 = ["codespace", "create", "-R", projectId, "--default-permissions"];
7551
8168
  if (machine) args2.push("-m", machine);
7552
- const { stdout } = await execFileP2(
8169
+ const { stdout } = await execFileP3(
7553
8170
  "gh",
7554
8171
  args2,
7555
8172
  { maxBuffer: MAX_BUFFER, timeout: 12e4 }
@@ -7589,7 +8206,7 @@ var GitHubCodespacesProvider = class {
7589
8206
  async waitUntilAvailable(name) {
7590
8207
  const deadline = Date.now() + 5 * 60 * 1e3;
7591
8208
  while (Date.now() < deadline) {
7592
- const { stdout } = await execFileP2(
8209
+ const { stdout } = await execFileP3(
7593
8210
  "gh",
7594
8211
  ["codespace", "list", "--json", "name,state"],
7595
8212
  { maxBuffer: MAX_BUFFER }
@@ -7607,7 +8224,7 @@ var GitHubCodespacesProvider = class {
7607
8224
  }
7608
8225
  async exec(workspaceId, command2) {
7609
8226
  try {
7610
- const { stdout, stderr } = await execFileP2(
8227
+ const { stdout, stderr } = await execFileP3(
7611
8228
  "gh",
7612
8229
  ["codespace", "ssh", "-c", workspaceId, "--", command2],
7613
8230
  { maxBuffer: MAX_BUFFER, timeout: 6e5 }
@@ -7625,7 +8242,7 @@ var GitHubCodespacesProvider = class {
7625
8242
  async streamCommand(workspaceId, command2) {
7626
8243
  resetStdinForChild();
7627
8244
  return new Promise((resolve2, reject) => {
7628
- const proc = (0, import_child_process8.spawn)(
8245
+ const proc = (0, import_child_process9.spawn)(
7629
8246
  "gh",
7630
8247
  ["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
7631
8248
  { stdio: "inherit" }
@@ -7652,11 +8269,11 @@ var GitHubCodespacesProvider = class {
7652
8269
  `mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
7653
8270
  ];
7654
8271
  await new Promise((resolve2, reject) => {
7655
- const tar = (0, import_child_process8.spawn)("tar", tarArgs, {
8272
+ const tar = (0, import_child_process9.spawn)("tar", tarArgs, {
7656
8273
  stdio: ["ignore", "pipe", "pipe"],
7657
8274
  env: tarEnv
7658
8275
  });
7659
- const ssh = (0, import_child_process8.spawn)("gh", sshArgs, {
8276
+ const ssh = (0, import_child_process9.spawn)("gh", sshArgs, {
7660
8277
  stdio: [tar.stdout, "pipe", "pipe"]
7661
8278
  });
7662
8279
  let tarErr = "";
@@ -7680,7 +8297,7 @@ var GitHubCodespacesProvider = class {
7680
8297
  });
7681
8298
  }
7682
8299
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
7683
- const remoteDir = path13.posix.dirname(remotePath);
8300
+ const remoteDir = path16.posix.dirname(remotePath);
7684
8301
  const parts = [
7685
8302
  `mkdir -p ${shellQuote(remoteDir)}`,
7686
8303
  `cat > ${shellQuote(remotePath)}`
@@ -7690,7 +8307,7 @@ var GitHubCodespacesProvider = class {
7690
8307
  }
7691
8308
  const cmd = parts.join(" && ");
7692
8309
  await new Promise((resolve2, reject) => {
7693
- const proc = (0, import_child_process8.spawn)(
8310
+ const proc = (0, import_child_process9.spawn)(
7694
8311
  "gh",
7695
8312
  ["codespace", "ssh", "-c", workspaceId, "--", cmd],
7696
8313
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -7712,7 +8329,7 @@ var GitHubCodespacesProvider = class {
7712
8329
  try {
7713
8330
  const args2 = ["codespace", "list", "--json", "name,displayName,state,lastUsedAt"];
7714
8331
  if (projectId) args2.push("--repo", projectId);
7715
- const { stdout } = await execFileP2("gh", args2, { maxBuffer: MAX_BUFFER });
8332
+ const { stdout } = await execFileP3("gh", args2, { maxBuffer: MAX_BUFFER });
7716
8333
  const list = JSON.parse(stdout);
7717
8334
  return list.map((c2) => ({
7718
8335
  id: c2.name,
@@ -7727,7 +8344,7 @@ var GitHubCodespacesProvider = class {
7727
8344
  }
7728
8345
  async startWorkspace(workspaceId) {
7729
8346
  try {
7730
- await execFileP2(
8347
+ await execFileP3(
7731
8348
  "gh",
7732
8349
  ["api", "-X", "POST", `/user/codespaces/${workspaceId}/start`],
7733
8350
  { maxBuffer: MAX_BUFFER, timeout: 6e4 }
@@ -7748,11 +8365,11 @@ function shellQuote(s) {
7748
8365
  }
7749
8366
 
7750
8367
  // src/services/providers/gitpod.ts
7751
- var import_child_process9 = require("child_process");
7752
- var import_util3 = require("util");
7753
- var path14 = __toESM(require("path"));
8368
+ var import_child_process10 = require("child_process");
8369
+ var import_util4 = require("util");
8370
+ var path17 = __toESM(require("path"));
7754
8371
  var import_picocolors8 = __toESM(require("picocolors"));
7755
- var execFileP3 = (0, import_util3.promisify)(import_child_process9.execFile);
8372
+ var execFileP4 = (0, import_util4.promisify)(import_child_process10.execFile);
7756
8373
  var MAX_BUFFER2 = 8 * 1024 * 1024;
7757
8374
  function resetStdinForChild2() {
7758
8375
  if (process.stdin.isTTY) {
@@ -7769,7 +8386,7 @@ var GitpodProvider = class {
7769
8386
  available = true;
7770
8387
  async authorize() {
7771
8388
  try {
7772
- await execFileP3("gitpod", ["--version"], { maxBuffer: MAX_BUFFER2 });
8389
+ await execFileP4("gitpod", ["--version"], { maxBuffer: MAX_BUFFER2 });
7773
8390
  } catch {
7774
8391
  throw new Error(
7775
8392
  [
@@ -7782,7 +8399,7 @@ var GitpodProvider = class {
7782
8399
  );
7783
8400
  }
7784
8401
  try {
7785
- await execFileP3("gitpod", ["whoami"], { maxBuffer: MAX_BUFFER2 });
8402
+ await execFileP4("gitpod", ["whoami"], { maxBuffer: MAX_BUFFER2 });
7786
8403
  return;
7787
8404
  } catch {
7788
8405
  }
@@ -7792,7 +8409,7 @@ var GitpodProvider = class {
7792
8409
  );
7793
8410
  resetStdinForChild2();
7794
8411
  await new Promise((resolve2, reject) => {
7795
- const proc = (0, import_child_process9.spawn)("gitpod", ["login"], { stdio: "inherit" });
8412
+ const proc = (0, import_child_process10.spawn)("gitpod", ["login"], { stdio: "inherit" });
7796
8413
  proc.on("exit", (code) => {
7797
8414
  if (code === 0) resolve2();
7798
8415
  else reject(new Error("gitpod login failed."));
@@ -7802,7 +8419,7 @@ var GitpodProvider = class {
7802
8419
  }
7803
8420
  async listProjects() {
7804
8421
  try {
7805
- const { stdout } = await execFileP3(
8422
+ const { stdout } = await execFileP4(
7806
8423
  "gitpod",
7807
8424
  ["workspace", "list", "--output", "json", "--limit", "200"],
7808
8425
  { maxBuffer: MAX_BUFFER2 }
@@ -7835,7 +8452,7 @@ var GitpodProvider = class {
7835
8452
  async createWorkspace(projectId, machineTypeId) {
7836
8453
  const args2 = ["workspace", "create", projectId, "--start", "--output", "json"];
7837
8454
  if (machineTypeId) args2.push("--class", machineTypeId);
7838
- const { stdout } = await execFileP3("gitpod", args2, {
8455
+ const { stdout } = await execFileP4("gitpod", args2, {
7839
8456
  maxBuffer: MAX_BUFFER2,
7840
8457
  timeout: 3e5
7841
8458
  });
@@ -7859,7 +8476,7 @@ var GitpodProvider = class {
7859
8476
  const deadline = Date.now() + 5 * 60 * 1e3;
7860
8477
  while (Date.now() < deadline) {
7861
8478
  try {
7862
- const { stdout } = await execFileP3(
8479
+ const { stdout } = await execFileP4(
7863
8480
  "gitpod",
7864
8481
  ["workspace", "get", workspaceId, "--output", "json"],
7865
8482
  { maxBuffer: MAX_BUFFER2 }
@@ -7877,7 +8494,7 @@ var GitpodProvider = class {
7877
8494
  }
7878
8495
  async listMachineTypes(_projectId) {
7879
8496
  try {
7880
- const { stdout } = await execFileP3(
8497
+ const { stdout } = await execFileP4(
7881
8498
  "gitpod",
7882
8499
  ["organization", "list-classes", "--output", "json"],
7883
8500
  { maxBuffer: MAX_BUFFER2 }
@@ -7895,7 +8512,7 @@ var GitpodProvider = class {
7895
8512
  async listExistingWorkspaces(projectId) {
7896
8513
  try {
7897
8514
  const args2 = ["workspace", "list", "--output", "json", "--limit", "200"];
7898
- const { stdout } = await execFileP3("gitpod", args2, { maxBuffer: MAX_BUFFER2 });
8515
+ const { stdout } = await execFileP4("gitpod", args2, { maxBuffer: MAX_BUFFER2 });
7899
8516
  const list = JSON.parse(stdout);
7900
8517
  return list.filter((w3) => !projectId || w3.contextUrl === projectId).map((w3) => ({
7901
8518
  id: w3.id,
@@ -7910,7 +8527,7 @@ var GitpodProvider = class {
7910
8527
  }
7911
8528
  async startWorkspace(workspaceId) {
7912
8529
  try {
7913
- await execFileP3(
8530
+ await execFileP4(
7914
8531
  "gitpod",
7915
8532
  ["workspace", "start", workspaceId],
7916
8533
  { maxBuffer: MAX_BUFFER2, timeout: 6e4 }
@@ -7926,7 +8543,7 @@ var GitpodProvider = class {
7926
8543
  }
7927
8544
  async exec(workspaceId, command2) {
7928
8545
  try {
7929
- const { stdout, stderr } = await execFileP3(
8546
+ const { stdout, stderr } = await execFileP4(
7930
8547
  "gitpod",
7931
8548
  ["workspace", "ssh", workspaceId, "--", command2],
7932
8549
  { maxBuffer: MAX_BUFFER2, timeout: 6e5 }
@@ -7944,7 +8561,7 @@ var GitpodProvider = class {
7944
8561
  async streamCommand(workspaceId, command2) {
7945
8562
  resetStdinForChild2();
7946
8563
  return new Promise((resolve2, reject) => {
7947
- const proc = (0, import_child_process9.spawn)(
8564
+ const proc = (0, import_child_process10.spawn)(
7948
8565
  "gitpod",
7949
8566
  ["workspace", "ssh", workspaceId, "--", "-tt", command2],
7950
8567
  { stdio: "inherit" }
@@ -7964,11 +8581,11 @@ var GitpodProvider = class {
7964
8581
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
7965
8582
  const remoteCmd = `mkdir -p ${shellQuote2(remoteDir)} && tar -xzf - -C ${shellQuote2(remoteDir)}`;
7966
8583
  await new Promise((resolve2, reject) => {
7967
- const tar = (0, import_child_process9.spawn)("tar", tarArgs, {
8584
+ const tar = (0, import_child_process10.spawn)("tar", tarArgs, {
7968
8585
  stdio: ["ignore", "pipe", "pipe"],
7969
8586
  env: tarEnv
7970
8587
  });
7971
- const ssh = (0, import_child_process9.spawn)(
8588
+ const ssh = (0, import_child_process10.spawn)(
7972
8589
  "gitpod",
7973
8590
  ["workspace", "ssh", workspaceId, "--", remoteCmd],
7974
8591
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -7990,7 +8607,7 @@ var GitpodProvider = class {
7990
8607
  });
7991
8608
  }
7992
8609
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
7993
- const remoteDir = path14.posix.dirname(remotePath);
8610
+ const remoteDir = path17.posix.dirname(remotePath);
7994
8611
  const parts = [
7995
8612
  `mkdir -p ${shellQuote2(remoteDir)}`,
7996
8613
  `cat > ${shellQuote2(remotePath)}`
@@ -8000,7 +8617,7 @@ var GitpodProvider = class {
8000
8617
  }
8001
8618
  const cmd = parts.join(" && ");
8002
8619
  await new Promise((resolve2, reject) => {
8003
- const proc = (0, import_child_process9.spawn)(
8620
+ const proc = (0, import_child_process10.spawn)(
8004
8621
  "gitpod",
8005
8622
  ["workspace", "ssh", workspaceId, "--", cmd],
8006
8623
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8024,10 +8641,10 @@ function shellQuote2(s) {
8024
8641
  }
8025
8642
 
8026
8643
  // src/services/providers/gitlab-workspaces.ts
8027
- var import_child_process10 = require("child_process");
8028
- var import_util4 = require("util");
8029
- var path15 = __toESM(require("path"));
8030
- var execFileP4 = (0, import_util4.promisify)(import_child_process10.execFile);
8644
+ var import_child_process11 = require("child_process");
8645
+ var import_util5 = require("util");
8646
+ var path18 = __toESM(require("path"));
8647
+ var execFileP5 = (0, import_util5.promisify)(import_child_process11.execFile);
8031
8648
  var MAX_BUFFER3 = 8 * 1024 * 1024;
8032
8649
  var GITLAB_API_BASE = process.env.CODEAM_GITLAB_API_URL ?? "https://gitlab.com/api/v4";
8033
8650
  function resetStdinForChild3() {
@@ -8045,7 +8662,7 @@ var GitLabWorkspacesProvider = class {
8045
8662
  available = true;
8046
8663
  async authorize() {
8047
8664
  try {
8048
- await execFileP4("glab", ["--version"], { maxBuffer: MAX_BUFFER3 });
8665
+ await execFileP5("glab", ["--version"], { maxBuffer: MAX_BUFFER3 });
8049
8666
  } catch {
8050
8667
  throw new Error(
8051
8668
  [
@@ -8059,7 +8676,7 @@ var GitLabWorkspacesProvider = class {
8059
8676
  );
8060
8677
  }
8061
8678
  try {
8062
- await execFileP4("glab", ["auth", "status"], { maxBuffer: MAX_BUFFER3 });
8679
+ await execFileP5("glab", ["auth", "status"], { maxBuffer: MAX_BUFFER3 });
8063
8680
  return;
8064
8681
  } catch {
8065
8682
  }
@@ -8069,7 +8686,7 @@ var GitLabWorkspacesProvider = class {
8069
8686
  );
8070
8687
  resetStdinForChild3();
8071
8688
  await new Promise((resolve2, reject) => {
8072
- const proc = (0, import_child_process10.spawn)(
8689
+ const proc = (0, import_child_process11.spawn)(
8073
8690
  "glab",
8074
8691
  ["auth", "login", "--scopes", "api,read_user,read_repository"],
8075
8692
  { stdio: "inherit" }
@@ -8083,7 +8700,7 @@ var GitLabWorkspacesProvider = class {
8083
8700
  }
8084
8701
  async listProjects() {
8085
8702
  try {
8086
- const { stdout } = await execFileP4(
8703
+ const { stdout } = await execFileP5(
8087
8704
  "glab",
8088
8705
  ["repo", "list", "--per-page", "200", "--output", "json"],
8089
8706
  { maxBuffer: MAX_BUFFER3 }
@@ -8215,7 +8832,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8215
8832
  async exec(workspaceId, command2) {
8216
8833
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
8217
8834
  try {
8218
- const { stdout, stderr } = await execFileP4(
8835
+ const { stdout, stderr } = await execFileP5(
8219
8836
  "ssh",
8220
8837
  [
8221
8838
  "-o",
@@ -8241,7 +8858,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8241
8858
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
8242
8859
  resetStdinForChild3();
8243
8860
  return new Promise((resolve2, reject) => {
8244
- const proc = (0, import_child_process10.spawn)(
8861
+ const proc = (0, import_child_process11.spawn)(
8245
8862
  "ssh",
8246
8863
  ["-tt", "-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, command2],
8247
8864
  { stdio: "inherit" }
@@ -8262,8 +8879,8 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8262
8879
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
8263
8880
  const remoteCmd = `mkdir -p ${shellQuote3(remoteDir)} && tar -xzf - -C ${shellQuote3(remoteDir)}`;
8264
8881
  await new Promise((resolve2, reject) => {
8265
- const tar = (0, import_child_process10.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
8266
- const ssh = (0, import_child_process10.spawn)(
8882
+ const tar = (0, import_child_process11.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
8883
+ const ssh = (0, import_child_process11.spawn)(
8267
8884
  "ssh",
8268
8885
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, remoteCmd],
8269
8886
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -8286,14 +8903,14 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8286
8903
  }
8287
8904
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
8288
8905
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
8289
- const remoteDir = path15.posix.dirname(remotePath);
8906
+ const remoteDir = path18.posix.dirname(remotePath);
8290
8907
  const parts = [`mkdir -p ${shellQuote3(remoteDir)}`, `cat > ${shellQuote3(remotePath)}`];
8291
8908
  if (options.mode != null) {
8292
8909
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote3(remotePath)}`);
8293
8910
  }
8294
8911
  const cmd = parts.join(" && ");
8295
8912
  await new Promise((resolve2, reject) => {
8296
- const proc = (0, import_child_process10.spawn)(
8913
+ const proc = (0, import_child_process11.spawn)(
8297
8914
  "ssh",
8298
8915
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, cmd],
8299
8916
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8318,7 +8935,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8318
8935
  */
8319
8936
  async getGlabToken() {
8320
8937
  try {
8321
- const { stdout, stderr } = await execFileP4(
8938
+ const { stdout, stderr } = await execFileP5(
8322
8939
  "glab",
8323
8940
  ["auth", "status", "--show-token"],
8324
8941
  { maxBuffer: MAX_BUFFER3 }
@@ -8352,10 +8969,10 @@ function shellQuote3(s) {
8352
8969
  }
8353
8970
 
8354
8971
  // src/services/providers/railway.ts
8355
- var import_child_process11 = require("child_process");
8356
- var import_util5 = require("util");
8357
- var path16 = __toESM(require("path"));
8358
- var execFileP5 = (0, import_util5.promisify)(import_child_process11.execFile);
8972
+ var import_child_process12 = require("child_process");
8973
+ var import_util6 = require("util");
8974
+ var path19 = __toESM(require("path"));
8975
+ var execFileP6 = (0, import_util6.promisify)(import_child_process12.execFile);
8359
8976
  var MAX_BUFFER4 = 8 * 1024 * 1024;
8360
8977
  function resetStdinForChild4() {
8361
8978
  if (process.stdin.isTTY) {
@@ -8372,7 +8989,7 @@ var RailwayProvider = class {
8372
8989
  available = true;
8373
8990
  async authorize() {
8374
8991
  try {
8375
- await execFileP5("railway", ["--version"], { maxBuffer: MAX_BUFFER4 });
8992
+ await execFileP6("railway", ["--version"], { maxBuffer: MAX_BUFFER4 });
8376
8993
  } catch {
8377
8994
  throw new Error(
8378
8995
  [
@@ -8386,7 +9003,7 @@ var RailwayProvider = class {
8386
9003
  );
8387
9004
  }
8388
9005
  try {
8389
- await execFileP5("railway", ["whoami"], { maxBuffer: MAX_BUFFER4 });
9006
+ await execFileP6("railway", ["whoami"], { maxBuffer: MAX_BUFFER4 });
8390
9007
  return;
8391
9008
  } catch {
8392
9009
  }
@@ -8396,7 +9013,7 @@ var RailwayProvider = class {
8396
9013
  );
8397
9014
  resetStdinForChild4();
8398
9015
  await new Promise((resolve2, reject) => {
8399
- const proc = (0, import_child_process11.spawn)("railway", ["login"], { stdio: "inherit" });
9016
+ const proc = (0, import_child_process12.spawn)("railway", ["login"], { stdio: "inherit" });
8400
9017
  proc.on("exit", (code) => {
8401
9018
  if (code === 0) resolve2();
8402
9019
  else reject(new Error("railway login failed."));
@@ -8406,7 +9023,7 @@ var RailwayProvider = class {
8406
9023
  }
8407
9024
  async listProjects() {
8408
9025
  try {
8409
- const { stdout } = await execFileP5(
9026
+ const { stdout } = await execFileP6(
8410
9027
  "railway",
8411
9028
  ["list", "--json"],
8412
9029
  { maxBuffer: MAX_BUFFER4 }
@@ -8422,7 +9039,7 @@ var RailwayProvider = class {
8422
9039
  }));
8423
9040
  } catch {
8424
9041
  try {
8425
- const { stdout } = await execFileP5("railway", ["list"], { maxBuffer: MAX_BUFFER4 });
9042
+ const { stdout } = await execFileP6("railway", ["list"], { maxBuffer: MAX_BUFFER4 });
8426
9043
  const projects = [];
8427
9044
  for (const line of stdout.split("\n")) {
8428
9045
  const trimmed = line.trim();
@@ -8473,7 +9090,7 @@ var RailwayProvider = class {
8473
9090
  async listExistingWorkspaces(projectId) {
8474
9091
  if (!projectId) return [];
8475
9092
  try {
8476
- const { stdout } = await execFileP5(
9093
+ const { stdout } = await execFileP6(
8477
9094
  "railway",
8478
9095
  ["service", "list", "--project", projectId, "--json"],
8479
9096
  { maxBuffer: MAX_BUFFER4 }
@@ -8498,7 +9115,7 @@ var RailwayProvider = class {
8498
9115
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
8499
9116
  }
8500
9117
  try {
8501
- await execFileP5(
9118
+ await execFileP6(
8502
9119
  "railway",
8503
9120
  ["service", "restart", "--service", serviceId, "--project", projectId],
8504
9121
  { maxBuffer: MAX_BUFFER4, timeout: 6e4 }
@@ -8517,7 +9134,7 @@ var RailwayProvider = class {
8517
9134
  };
8518
9135
  }
8519
9136
  try {
8520
- const { stdout, stderr } = await execFileP5(
9137
+ const { stdout, stderr } = await execFileP6(
8521
9138
  "railway",
8522
9139
  ["run", "--project", projectId, "--service", serviceId, "--", "bash", "-lc", command2],
8523
9140
  { maxBuffer: MAX_BUFFER4, timeout: 6e5 }
@@ -8539,7 +9156,7 @@ var RailwayProvider = class {
8539
9156
  }
8540
9157
  resetStdinForChild4();
8541
9158
  return new Promise((resolve2, reject) => {
8542
- const proc = (0, import_child_process11.spawn)(
9159
+ const proc = (0, import_child_process12.spawn)(
8543
9160
  "railway",
8544
9161
  ["shell", "--project", projectId, "--service", serviceId, "--command", command2],
8545
9162
  { stdio: "inherit" }
@@ -8563,8 +9180,8 @@ var RailwayProvider = class {
8563
9180
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
8564
9181
  const remoteCmd = `mkdir -p ${shellQuote4(remoteDir)} && tar -xzf - -C ${shellQuote4(remoteDir)}`;
8565
9182
  await new Promise((resolve2, reject) => {
8566
- const tar = (0, import_child_process11.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
8567
- const sh = (0, import_child_process11.spawn)(
9183
+ const tar = (0, import_child_process12.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
9184
+ const sh = (0, import_child_process12.spawn)(
8568
9185
  "railway",
8569
9186
  ["shell", "--project", projectId, "--service", serviceId, "--command", remoteCmd],
8570
9187
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -8590,14 +9207,14 @@ var RailwayProvider = class {
8590
9207
  if (!projectId || !serviceId) {
8591
9208
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
8592
9209
  }
8593
- const remoteDir = path16.posix.dirname(remotePath);
9210
+ const remoteDir = path19.posix.dirname(remotePath);
8594
9211
  const parts = [`mkdir -p ${shellQuote4(remoteDir)}`, `cat > ${shellQuote4(remotePath)}`];
8595
9212
  if (options.mode != null) {
8596
9213
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote4(remotePath)}`);
8597
9214
  }
8598
9215
  const cmd = parts.join(" && ");
8599
9216
  await new Promise((resolve2, reject) => {
8600
- const proc = (0, import_child_process11.spawn)(
9217
+ const proc = (0, import_child_process12.spawn)(
8601
9218
  "railway",
8602
9219
  ["shell", "--project", projectId, "--service", serviceId, "--command", cmd],
8603
9220
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8629,8 +9246,7 @@ var PROVIDERS = [
8629
9246
  ];
8630
9247
 
8631
9248
  // src/commands/deploy.ts
8632
- var execFileP6 = (0, import_util6.promisify)(import_child_process12.execFile);
8633
- async function deploy() {
9249
+ async function deploy(args2 = []) {
8634
9250
  console.log();
8635
9251
  mt(import_picocolors9.default.bgMagenta(import_picocolors9.default.white(" codeam deploy ")));
8636
9252
  const provider = await pickProvider();
@@ -8782,13 +9398,14 @@ async function deploy() {
8782
9398
  process.exit(1);
8783
9399
  }
8784
9400
  }
8785
- const localClaudeDir = path17.join(os10.homedir(), ".claude");
8786
- const localCredsKind = await detectLocalClaudeCredentials(localClaudeDir);
9401
+ const cfg = loadCliConfig();
9402
+ const agentId = parseAgentFlag(args2) ?? await promptForAgent(cfg.preferredAgent ?? "claude");
9403
+ const strategy = createDeployStrategy(agentId);
9404
+ const localCreds = await strategy.detectLocalCredentials();
8787
9405
  let bridged = "none";
8788
- if (localCredsKind !== "none") {
8789
- const sourceLabel = localCredsKind === "flat-file" ? "~/.claude/.credentials.json" : "macOS Keychain";
9406
+ if (localCreds.source !== "none") {
8790
9407
  const useLocal = await ot2({
8791
- message: `Copy your local Claude credentials (${sourceLabel}) to the workspace?`,
9408
+ message: `Copy your local ${AGENT_REGISTRY[agentId].displayName} credentials (${localCreds.description}) to the workspace?`,
8792
9409
  active: "Yes \u2014 same account, no re-auth",
8793
9410
  inactive: "No \u2014 log in with a different account",
8794
9411
  initialValue: true
@@ -8799,118 +9416,13 @@ async function deploy() {
8799
9416
  }
8800
9417
  if (useLocal) {
8801
9418
  const credStep = fe();
8802
- credStep.start("Bridging Claude credentials\u2026");
8803
- bridged = await bridgeClaudeCredentials(provider, workspace.id, localClaudeDir);
8804
- switch (bridged) {
8805
- case "flat-file":
8806
- credStep.stop("\u2713 Local credentials staged");
8807
- break;
8808
- case "macos-keychain":
8809
- credStep.stop("\u2713 Credentials extracted from macOS Keychain and staged");
8810
- break;
8811
- case "none":
8812
- credStep.stop("\u26A0 Could not extract local credentials \u2014 falling back to remote login");
8813
- break;
8814
- }
8815
- }
8816
- }
8817
- const claudeStep = fe();
8818
- claudeStep.start("Installing Claude CLI on workspace\u2026");
8819
- const installResult = await provider.exec(
8820
- workspace.id,
8821
- "curl -fsSL https://claude.ai/install.sh | bash"
8822
- );
8823
- if (installResult.code !== 0) {
8824
- claudeStep.stop("\u2717 Claude CLI install failed");
8825
- pt(installResult.stderr.slice(0, 1e3));
8826
- process.exit(1);
8827
- }
8828
- claudeStep.stop("\u2713 Claude CLI installed");
8829
- const haveLocalClaude = fs11.existsSync(localClaudeDir) && fs11.statSync(localClaudeDir).isDirectory();
8830
- if (haveLocalClaude) {
8831
- const copyStep = fe();
8832
- copyStep.start("Copying local Claude config to workspace\u2026");
8833
- try {
8834
- await provider.uploadDirectory(
8835
- workspace.id,
8836
- localClaudeDir,
8837
- "/home/codespace/.claude",
8838
- {
8839
- exclude: [
8840
- "./projects",
8841
- // per-project conversation history (often 700MB+)
8842
- "./file-history",
8843
- // per-project file diffs
8844
- "./downloads",
8845
- // downloaded artifacts
8846
- "./image-cache",
8847
- // cached images
8848
- "./paste-cache",
8849
- // clipboard/paste cache
8850
- "./backups",
8851
- // local backups
8852
- "./shell-snapshots",
8853
- // shell history snapshots
8854
- "./telemetry",
8855
- // analytics dumps
8856
- "./statsig",
8857
- // feature-flag cache
8858
- "./cache",
8859
- // generic cache dir
8860
- "./history.jsonl",
8861
- // global REPL history
8862
- "./ide",
8863
- // local IDE bridge state
8864
- "./todos",
8865
- // local todo state
8866
- "./tasks",
8867
- // local task state
8868
- // Don't overwrite the credentials we already staged in
8869
- // step 4 — the local dir on macOS doesn't have a flat
8870
- // credentials file anyway, but on Linux it would, and a
8871
- // re-write here would be redundant.
8872
- "./.credentials.json"
8873
- ]
8874
- }
8875
- );
8876
- copyStep.stop("\u2713 Claude config uploaded");
8877
- } catch (err) {
8878
- copyStep.stop("\u26A0 Could not upload Claude config (continuing)");
8879
- void err;
8880
- }
8881
- }
8882
- if (bridged !== "none") {
8883
- const localClaudeJson = path17.join(os10.homedir(), ".claude.json");
8884
- if (fs11.existsSync(localClaudeJson)) {
8885
- try {
8886
- const contents = fs11.readFileSync(localClaudeJson);
8887
- await provider.uploadFile(
8888
- workspace.id,
8889
- "/home/codespace/.claude.json",
8890
- contents,
8891
- { mode: 384 }
8892
- );
8893
- } catch (err) {
8894
- void err;
8895
- }
8896
- }
8897
- }
8898
- const verifyStep = fe();
8899
- verifyStep.start("Verifying Claude auth on workspace\u2026");
8900
- const verified = await verifyClaudeAuth(provider, workspace.id);
8901
- if (verified) {
8902
- verifyStep.stop("\u2713 Claude is logged in \u2014 no re-auth needed");
8903
- } else {
8904
- verifyStep.stop("\xB7 Claude not yet authenticated \u2014 running login flow");
8905
- await runRemoteClaudeLogin(provider, workspace.id);
8906
- const reverified = await verifyClaudeAuth(provider, workspace.id);
8907
- if (!reverified) {
8908
- wt(
8909
- "Claude auth could not be confirmed. You may need to run `claude /login` manually inside the codespace.",
8910
- "Heads up"
8911
- );
9419
+ credStep.start(`Bridging ${AGENT_REGISTRY[agentId].displayName} credentials\u2026`);
9420
+ const result = await strategy.bridgeLocalCredentials(provider, workspace.id);
9421
+ bridged = result.source;
9422
+ credStep.stop(`\u2713 Credentials staged (${bridged})`);
8912
9423
  }
8913
9424
  }
9425
+ await strategy.setupOnWorkspace(provider, workspace.id, { bridged });
8914
9426
  const cliStep = fe();
8915
9427
  cliStep.start("Installing codeam-cli on workspace\u2026");
8916
9428
  const cliInstall = await provider.exec(workspace.id, "npm install -g codeam-cli@latest");
@@ -8925,8 +9437,8 @@ async function deploy() {
8925
9437
  `Workspace: ${import_picocolors9.default.cyan(workspace.displayName ?? workspace.id)}`,
8926
9438
  workspace.webUrl ? `Web: ${import_picocolors9.default.cyan(workspace.webUrl)}` : "",
8927
9439
  "",
8928
- "Starting `codeam pair` on the workspace.",
8929
- "Scan the QR code below with the CodeAgent Mobile app to finish pairing.",
9440
+ `Starting \`codeam pair\` on the workspace (agent: ${AGENT_REGISTRY[agentId].displayName}).`,
9441
+ "Scan the QR code from your phone to pair.",
8930
9442
  import_picocolors9.default.dim("(Once paired, this terminal disconnects automatically; the session stays alive on the codespace.)")
8931
9443
  ].filter(Boolean).join("\n"),
8932
9444
  "Almost there"
@@ -8936,10 +9448,9 @@ async function deploy() {
8936
9448
  "LOG=~/.codeam-deploy/session.log",
8937
9449
  ': > "$LOG"',
8938
9450
  // The default `gh codespace ssh` cwd is the repo root
8939
- // (/workspaces/<repo>), which is exactly where Claude needs to
9451
+ // (/workspaces/<repo>), which is exactly where the agent needs to
8940
9452
  // run so it can read/edit project files. Pass that to PM2 via
8941
- // --cwd so the relay's child Claude inherits the right
8942
- // working directory.
9453
+ // --cwd so the relay's child agent inherits the right directory.
8943
9454
  'PROJECT_DIR="$(pwd)"',
8944
9455
  // Install PM2 if it isn't already on PATH. Idempotent.
8945
9456
  "if ! command -v pm2 >/dev/null 2>&1; then",
@@ -8951,11 +9462,11 @@ async function deploy() {
8951
9462
  // Start codeam pair under PM2. `--merge-logs` writes stdout
8952
9463
  // and stderr to the same file so we only need one tail.
8953
9464
  // --max-restarts 3 keeps PM2 from looping forever if codeam pair
8954
- // can't start (e.g. backend unreachable) — three attempts is
8955
- // enough for transient flakes, anything more wastes time.
8956
- // No `--time` (would prefix every line with a timestamp and
8957
- // break the QR rendering); no `--no-pmx` either (default off).
8958
- 'pm2 start codeam --name codeam-pair --cwd "$PROJECT_DIR" --max-restarts 3 -o "$LOG" -e "$LOG" --merge-logs -- pair >/dev/null 2>&1',
9465
+ // can't start (e.g. backend unreachable) — three attempts is enough
9466
+ // for transient flakes.
9467
+ // No `--time` (would prefix every line with a timestamp); no
9468
+ // `--no-pmx` either (default off).
9469
+ `pm2 start codeam --name codeam-pair --cwd "$PROJECT_DIR" --max-restarts 3 -o "$LOG" -e "$LOG" --merge-logs -- pair --agent=${agentId} >/dev/null 2>&1`,
8959
9470
  // Give PM2 a moment to spawn the process before we start polling
8960
9471
  // status — otherwise the very first jlist can race the spawn.
8961
9472
  "sleep 2",
@@ -9053,83 +9564,6 @@ async function deploy() {
9053
9564
  function shellQuoteSingle(s) {
9054
9565
  return `'${s.replace(/'/g, `'\\''`)}'`;
9055
9566
  }
9056
- async function runRemoteClaudeLogin(provider, workspaceId) {
9057
- wt(
9058
- [
9059
- "A login URL will print below. Open it in your local browser, sign in,",
9060
- "and paste any code Claude asks for back into this terminal."
9061
- ].join("\n"),
9062
- "Authenticating Claude on workspace"
9063
- );
9064
- const result = await provider.streamCommand(
9065
- workspaceId,
9066
- 'bash -lc "claude login || claude /login || true"'
9067
- );
9068
- if (result.code !== 0) {
9069
- wt(
9070
- "claude login exited non-zero. You can re-run it manually inside the codespace later.",
9071
- "Heads up"
9072
- );
9073
- }
9074
- }
9075
- async function detectLocalClaudeCredentials(localClaudeDir) {
9076
- if (fs11.existsSync(path17.join(localClaudeDir, ".credentials.json"))) {
9077
- return "flat-file";
9078
- }
9079
- if (process.platform === "darwin") {
9080
- try {
9081
- await execFileP6(
9082
- "security",
9083
- ["find-generic-password", "-s", "Claude Code-credentials"],
9084
- { maxBuffer: 1024 * 1024 }
9085
- );
9086
- return "macos-keychain";
9087
- } catch {
9088
- return "none";
9089
- }
9090
- }
9091
- return "none";
9092
- }
9093
- async function verifyClaudeAuth(provider, workspaceId) {
9094
- const result = await provider.exec(
9095
- workspaceId,
9096
- 'bash -lc "claude auth status 2>/dev/null || true"'
9097
- );
9098
- if (result.code !== 0) return false;
9099
- const jsonStart = result.stdout.indexOf("{");
9100
- if (jsonStart < 0) return false;
9101
- try {
9102
- const parsed = JSON.parse(result.stdout.slice(jsonStart));
9103
- return parsed.loggedIn === true;
9104
- } catch {
9105
- return false;
9106
- }
9107
- }
9108
- async function bridgeClaudeCredentials(provider, workspaceId, localClaudeDir) {
9109
- const fileBased = path17.join(localClaudeDir, ".credentials.json");
9110
- if (fs11.existsSync(fileBased)) return "flat-file";
9111
- if (process.platform === "darwin") {
9112
- try {
9113
- const { stdout } = await execFileP6(
9114
- "security",
9115
- ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
9116
- { maxBuffer: 1024 * 1024 }
9117
- );
9118
- const json = stdout.trim();
9119
- if (json.length === 0) return "none";
9120
- await provider.uploadFile(
9121
- workspaceId,
9122
- "/home/codespace/.claude/.credentials.json",
9123
- json,
9124
- { mode: 384 }
9125
- );
9126
- return "macos-keychain";
9127
- } catch {
9128
- return "none";
9129
- }
9130
- }
9131
- return "none";
9132
- }
9133
9567
  function formatLastUsed(iso) {
9134
9568
  if (!iso) return "";
9135
9569
  const t2 = Date.parse(iso);
@@ -9315,7 +9749,7 @@ async function stopWorkspaceFromLocal(target) {
9315
9749
  // src/commands/version.ts
9316
9750
  var import_picocolors11 = __toESM(require("picocolors"));
9317
9751
  function version() {
9318
- const v = true ? "2.10.7" : "unknown";
9752
+ const v = true ? "2.11.0" : "unknown";
9319
9753
  console.log(`${import_picocolors11.default.bold("codeam-cli")} ${import_picocolors11.default.cyan(v)}`);
9320
9754
  }
9321
9755
 
@@ -9352,9 +9786,9 @@ function help() {
9352
9786
  }
9353
9787
 
9354
9788
  // src/lib/updateNotifier.ts
9355
- var fs12 = __toESM(require("fs"));
9356
- var os11 = __toESM(require("os"));
9357
- var path18 = __toESM(require("path"));
9789
+ var fs14 = __toESM(require("fs"));
9790
+ var os13 = __toESM(require("os"));
9791
+ var path20 = __toESM(require("path"));
9358
9792
  var https5 = __toESM(require("https"));
9359
9793
  var import_picocolors13 = __toESM(require("picocolors"));
9360
9794
  var PKG_NAME = "codeam-cli";
@@ -9362,12 +9796,12 @@ var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
9362
9796
  var TTL_MS = 24 * 60 * 60 * 1e3;
9363
9797
  var REQUEST_TIMEOUT_MS = 1500;
9364
9798
  function cachePath() {
9365
- const dir = path18.join(os11.homedir(), ".codeam");
9366
- return path18.join(dir, "update-check.json");
9799
+ const dir = path20.join(os13.homedir(), ".codeam");
9800
+ return path20.join(dir, "update-check.json");
9367
9801
  }
9368
9802
  function readCache() {
9369
9803
  try {
9370
- const raw = fs12.readFileSync(cachePath(), "utf8");
9804
+ const raw = fs14.readFileSync(cachePath(), "utf8");
9371
9805
  const parsed = JSON.parse(raw);
9372
9806
  if (typeof parsed.fetchedAt !== "number" || typeof parsed.latest !== "string") return null;
9373
9807
  return parsed;
@@ -9378,8 +9812,8 @@ function readCache() {
9378
9812
  function writeCache(cache) {
9379
9813
  try {
9380
9814
  const file = cachePath();
9381
- fs12.mkdirSync(path18.dirname(file), { recursive: true });
9382
- fs12.writeFileSync(file, JSON.stringify(cache));
9815
+ fs14.mkdirSync(path20.dirname(file), { recursive: true });
9816
+ fs14.writeFileSync(file, JSON.stringify(cache));
9383
9817
  } catch {
9384
9818
  }
9385
9819
  }
@@ -9450,7 +9884,7 @@ function checkForUpdates() {
9450
9884
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
9451
9885
  if (process.env.CI) return;
9452
9886
  if (!process.stdout.isTTY) return;
9453
- const current = true ? "2.10.7" : null;
9887
+ const current = true ? "2.11.0" : null;
9454
9888
  if (!current) return;
9455
9889
  const cache = readCache();
9456
9890
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -9479,7 +9913,7 @@ async function main() {
9479
9913
  case "help":
9480
9914
  return help();
9481
9915
  case "pair":
9482
- return pair();
9916
+ return pair(args);
9483
9917
  case "pair-auto":
9484
9918
  return pairAuto(args);
9485
9919
  case "sessions":
@@ -9491,7 +9925,7 @@ async function main() {
9491
9925
  case "deploy":
9492
9926
  if (args[0] === "ls" || args[0] === "list") return deployList();
9493
9927
  if (args[0] === "stop" || args[0] === "remove") return deployStop();
9494
- return deploy();
9928
+ return deploy(args);
9495
9929
  default:
9496
9930
  return start();
9497
9931
  }