codeam-cli 2.10.8 → 2.12.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 +53 -0
  2. package/dist/index.js +1509 -898
  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 fs17 = require("fs");
523
+ var os16 = require("os");
524
+ var path23 = 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 = path23.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 = fs17.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(path23.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(os16.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 fs17 = require("fs");
1016
+ var path23 = 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 = path23.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
+ fs17.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() {
@@ -1313,10 +1313,10 @@ var require_lib = __commonJS({
1313
1313
  } else {
1314
1314
  terminalCtor = require_unixTerminal().UnixTerminal;
1315
1315
  }
1316
- function spawn10(file, args2, opt) {
1316
+ function spawn11(file, args2, opt) {
1317
1317
  return new terminalCtor(file, args2, opt);
1318
1318
  }
1319
- exports2.spawn = spawn10;
1319
+ exports2.spawn = spawn11;
1320
1320
  function fork(file, args2, opt) {
1321
1321
  return new terminalCtor(file, args2, opt);
1322
1322
  }
@@ -1392,6 +1392,438 @@ 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
+ // ── Anthropic / Claude ────────────────────────────────────
1742
+ "claude-sonnet-4": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
1743
+ "claude-opus-4": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
1744
+ "claude-3-5-sonnet": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
1745
+ "claude-3-5-haiku": { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
1746
+ "claude-3-haiku": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 },
1747
+ // ── Codex / OpenAI ────────────────────────────────────────
1748
+ // Phase 2 placeholder pricing: 0 across the board until OpenAI publishes
1749
+ // confirmed rates for the GPT-5.x catalog. Sync from
1750
+ // developers.openai.com/pricing when available.
1751
+ "gpt-5.5": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1752
+ "gpt-5.4": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1753
+ "gpt-5.4-mini": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1754
+ "gpt-5.3-codex": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1755
+ "gpt-5.2": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1756
+ "codex-auto-review": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
1757
+ };
1758
+ var MODEL_CONTEXT_WINDOW = {
1759
+ // ── Anthropic / Claude ────────────────────────────────────
1760
+ "claude-opus-4": 1e6,
1761
+ "claude-sonnet-4": 1e6,
1762
+ "claude-3-5-sonnet": 2e5,
1763
+ "claude-3-5-haiku": 2e5,
1764
+ "claude-3-haiku": 2e5,
1765
+ // ── Codex / OpenAI ────────────────────────────────────────
1766
+ "gpt-5.5": 272e3,
1767
+ "gpt-5.4": 272e3,
1768
+ "gpt-5.4-mini": 272e3,
1769
+ "gpt-5.3-codex": 272e3,
1770
+ "gpt-5.2": 272e3,
1771
+ "codex-auto-review": 272e3
1772
+ };
1773
+ var DEFAULT_CONTEXT_WINDOW = 2e5;
1774
+ function getPricing(model) {
1775
+ for (const [prefix, pricing] of Object.entries(MODEL_PRICING)) {
1776
+ if (model.startsWith(prefix)) return pricing;
1777
+ }
1778
+ return MODEL_PRICING["claude-sonnet-4"];
1779
+ }
1780
+ function getContextWindow(model) {
1781
+ if (!model) return DEFAULT_CONTEXT_WINDOW;
1782
+ for (const [prefix, size] of Object.entries(MODEL_CONTEXT_WINDOW)) {
1783
+ if (model.startsWith(prefix)) return size;
1784
+ }
1785
+ return DEFAULT_CONTEXT_WINDOW;
1786
+ }
1787
+
1788
+ // ../../packages/shared/src/agents/registry.ts
1789
+ var AGENT_REGISTRY = {
1790
+ claude: {
1791
+ id: "claude",
1792
+ displayName: "Claude Code",
1793
+ binaryName: "claude",
1794
+ enabled: true,
1795
+ supportedAuthKinds: ["oauth_token", "api_key"],
1796
+ preferredAuthKind: "oauth_token"
1797
+ },
1798
+ codex: {
1799
+ id: "codex",
1800
+ displayName: "Codex CLI",
1801
+ binaryName: "codex",
1802
+ enabled: true,
1803
+ supportedAuthKinds: ["oauth_token", "api_key"],
1804
+ preferredAuthKind: "oauth_token"
1805
+ },
1806
+ copilot: {
1807
+ id: "copilot",
1808
+ displayName: "GitHub Copilot CLI",
1809
+ binaryName: "gh",
1810
+ enabled: false,
1811
+ supportedAuthKinds: ["oauth_token"],
1812
+ preferredAuthKind: "oauth_token"
1813
+ }
1814
+ };
1815
+ function getEnabledAgents() {
1816
+ return Object.values(AGENT_REGISTRY).filter((m) => m.enabled);
1817
+ }
1818
+ function getAgent(id) {
1819
+ const meta = AGENT_REGISTRY[id];
1820
+ if (!meta) throw new Error(`Unknown agent id: ${id}`);
1821
+ return meta;
1822
+ }
1823
+ function isKnownAgentId(id) {
1824
+ return id === "claude" || id === "codex" || id === "copilot";
1825
+ }
1826
+
1395
1827
  // src/config.ts
1396
1828
  var fs = __toESM(require("fs"));
1397
1829
  var os = __toESM(require("os"));
@@ -1402,6 +1834,9 @@ var EMPTY_CONFIG = () => ({
1402
1834
  activeSessionId: null,
1403
1835
  sessions: []
1404
1836
  });
1837
+ function migrateSession(s) {
1838
+ return { ...s, agent: s.agent ?? "claude" };
1839
+ }
1405
1840
  function makeConfig(baseDir) {
1406
1841
  const dir = path.join(baseDir ?? os.homedir(), ".codeam");
1407
1842
  const file = path.join(dir, "config.json");
@@ -1411,7 +1846,8 @@ function makeConfig(baseDir) {
1411
1846
  return {
1412
1847
  pluginId: typeof raw.pluginId === "string" ? raw.pluginId : crypto.randomUUID(),
1413
1848
  activeSessionId: typeof raw.activeSessionId === "string" ? raw.activeSessionId : null,
1414
- sessions: Array.isArray(raw.sessions) ? raw.sessions : []
1849
+ sessions: Array.isArray(raw.sessions) ? raw.sessions.map(migrateSession) : [],
1850
+ preferredAgent: typeof raw.preferredAgent === "string" ? raw.preferredAgent : void 0
1415
1851
  };
1416
1852
  } catch {
1417
1853
  return EMPTY_CONFIG();
@@ -1465,10 +1901,16 @@ function makeConfig(baseDir) {
1465
1901
  } catch {
1466
1902
  }
1467
1903
  }
1468
- return { getConfig: getConfig2, ensurePluginId: ensurePluginId2, addSession: addSession2, removeSession: removeSession2, setActiveSession: setActiveSession2, getActiveSession: getActiveSession2, clearAll: clearAll2 };
1904
+ function saveCliConfig2(c2) {
1905
+ save(c2);
1906
+ }
1907
+ function loadCliConfig2() {
1908
+ return load();
1909
+ }
1910
+ return { getConfig: getConfig2, ensurePluginId: ensurePluginId2, addSession: addSession2, removeSession: removeSession2, setActiveSession: setActiveSession2, getActiveSession: getActiveSession2, clearAll: clearAll2, saveCliConfig: saveCliConfig2, loadCliConfig: loadCliConfig2 };
1469
1911
  }
1470
1912
  var _default = makeConfig();
1471
- var { getConfig, ensurePluginId, addSession, removeSession, setActiveSession, getActiveSession, clearAll } = _default;
1913
+ var { getConfig, ensurePluginId, addSession, removeSession, setActiveSession, getActiveSession, clearAll, saveCliConfig, loadCliConfig } = _default;
1472
1914
 
1473
1915
  // src/ui/banner.ts
1474
1916
  var import_picocolors = __toESM(require("picocolors"));
@@ -1477,7 +1919,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
1477
1919
  // package.json
1478
1920
  var package_default = {
1479
1921
  name: "codeam-cli",
1480
- version: "2.10.8",
1922
+ version: "2.12.0",
1481
1923
  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
1924
  type: "commonjs",
1483
1925
  main: "dist/index.js",
@@ -2393,7 +2835,7 @@ var WindowsConPtyStrategy = class _WindowsConPtyStrategy {
2393
2835
  resizeHandler = null;
2394
2836
  /**
2395
2837
  * Factory that returns a working ConPTY strategy or `null` if
2396
- * node-pty can't load. The caller (claude.service.ts) decides
2838
+ * node-pty can't load. The caller (agent.service.ts) decides
2397
2839
  * whether to fall back to the legacy pipe strategy.
2398
2840
  */
2399
2841
  static tryCreate(opts) {
@@ -4498,9 +4940,10 @@ function buildClaudeLaunch(extraArgs = []) {
4498
4940
  return { cmd: found, args: extraArgs };
4499
4941
  }
4500
4942
 
4501
- // src/services/claude.service.ts
4502
- var ClaudeService = class {
4503
- constructor(opts) {
4943
+ // src/services/agent.service.ts
4944
+ var AgentService = class {
4945
+ constructor(runtime, opts) {
4946
+ this.runtime = runtime;
4504
4947
  this.opts = opts;
4505
4948
  this.strategyOpts = {
4506
4949
  onData: (d3) => {
@@ -4514,6 +4957,7 @@ var ClaudeService = class {
4514
4957
  onExit: opts.onExit
4515
4958
  };
4516
4959
  }
4960
+ runtime;
4517
4961
  opts;
4518
4962
  // Strategy is selected lazily inside spawn() so we can fall back from
4519
4963
  // ConPTY → legacy pipe at runtime if the native binding fails to load.
@@ -4545,10 +4989,20 @@ var ClaudeService = class {
4545
4989
  this.pendingInputs.length = 0;
4546
4990
  }
4547
4991
  async spawn() {
4548
- let launch = buildClaudeLaunch();
4549
- if (!launch) {
4992
+ let launch;
4993
+ try {
4994
+ launch = await this.runtime.prepareLaunch();
4995
+ } catch {
4550
4996
  const installed = await ensureClaudeInstalled();
4551
- if (installed) launch = buildClaudeLaunch();
4997
+ if (installed) {
4998
+ try {
4999
+ launch = await this.runtime.prepareLaunch();
5000
+ } catch {
5001
+ launch = null;
5002
+ }
5003
+ } else {
5004
+ launch = null;
5005
+ }
4552
5006
  if (!launch) {
4553
5007
  const cmd = process.platform === "win32" ? "irm https://claude.ai/install.ps1 | iex" : "curl -fsSL https://claude.ai/install.sh | bash";
4554
5008
  console.error(
@@ -4661,6 +5115,20 @@ var ClaudeService = class {
4661
5115
  s.write("\r");
4662
5116
  }, steps * ARROW_MS + ENTER_MS);
4663
5117
  }
5118
+ /**
5119
+ * Write raw bytes to the PTY without any auto-appended `\r` or delay.
5120
+ * Use this when the caller already owns the full input (e.g. the
5121
+ * `ptyInput` returned by `RuntimeStrategy.changeModelInstruction()`
5122
+ * already contains the trailing `\r`).
5123
+ */
5124
+ sendRawPtyInput(text) {
5125
+ if (!this.strategy) {
5126
+ log.trace("claude", "sendRawPtyInput dropped (no strategy)");
5127
+ return;
5128
+ }
5129
+ log.trace("claude", `sendRawPtyInput len=${text.length}`);
5130
+ this.strategy.write(text);
5131
+ }
4664
5132
  /** Send Escape key to Claude (cancels interactive prompts). */
4665
5133
  sendEscape() {
4666
5134
  this.strategy?.write("\x1B");
@@ -4675,390 +5143,754 @@ var ClaudeService = class {
4675
5143
  /**
4676
5144
  * Kill the current Claude process and relaunch it resuming the given session.
4677
5145
  * Pass auto=true to add --dangerously-skip-permissions (no confirmation prompts).
5146
+ *
5147
+ * For agents that use CLI flags (Claude: --resume <id>), `resumeLaunchArgs`
5148
+ * returns a non-empty array and we pass those directly to the binary.
5149
+ * For agents that use a post-spawn PTY instruction (e.g. Codex), `resumeLaunchArgs`
5150
+ * returns [] and `postSpawnInstruction` types the resume command into the PTY.
4678
5151
  */
4679
5152
  restart(sessionId, auto = false) {
4680
5153
  if (!this.strategy) return;
4681
- const extraArgs = ["--resume", sessionId];
4682
- if (auto) extraArgs.push("--dangerously-skip-permissions");
4683
- const launch = buildClaudeLaunch(extraArgs);
5154
+ const resumeArgs = auto ? this.runtime.resumeLaunchArgs(sessionId) : ["--resume", sessionId];
5155
+ const launch = buildClaudeLaunch(resumeArgs);
4684
5156
  if (!launch) return;
4685
5157
  this.strategy.kill();
4686
5158
  this.strategy.spawn(launch.cmd, this.opts.cwd, launch.args);
5159
+ if (resumeArgs.length === 0 && this.runtime.postSpawnInstruction) {
5160
+ const { ptyInput } = this.runtime.postSpawnInstruction(sessionId);
5161
+ setTimeout(() => {
5162
+ this.strategy?.write(ptyInput);
5163
+ }, 500);
5164
+ }
4687
5165
  }
4688
5166
  };
4689
5167
 
4690
- // ../../packages/shared/src/protocol/parseChrome.ts
4691
- var SPINNER_RE = /^(?:[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]|🔴|🟠|🟡|🟢|🔵|🟣|🟤|⚫|⚪|🌀|💭|✨)\s/u;
4692
- 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;
4693
- var TREE_LINE_RE = /^└\s/;
4694
- var STATUS_LINE_RE = /^(?:\+|[🔴🟠🟡🟢🔵🟣🟤⚫⚪🌀💭✨])\s/u;
4695
- function isChromeLine(line) {
4696
- const t2 = line.replace(/️/g, "").trim();
4697
- if (!t2) return false;
4698
- if (/^[─━—═─\-]{3,}$/.test(t2)) return true;
4699
- if (SPINNER_RE.test(t2)) return true;
4700
- if (BULLET_TOOL_RE.test(t2)) return true;
4701
- if (TREE_LINE_RE.test(t2)) return true;
4702
- if (STATUS_LINE_RE.test(t2) && /\d+\s*s\s*[·•]|\bthought\s+for\b|\d+\s*tokens|\(thinking\)/i.test(t2)) return true;
4703
- if (/^↓\s*\d+\s*tokens/i.test(t2)) return true;
4704
- if (/^\bthought\s+for\s+\d+/i.test(t2)) return true;
4705
- if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) return true;
4706
- if (/high\s*[·•]\s*\/effort/i.test(t2)) return true;
4707
- if (/^[❯>]\s*$/.test(t2)) return true;
4708
- if (/^\(thinking\)\s*$/.test(t2)) return true;
4709
- if (/^\?\s.*shortcut/i.test(t2)) return true;
4710
- if (/spending limit|usage limit/i.test(t2) && t2.length < 80) return true;
4711
- if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) return true;
4712
- if (t2.replace(/\s/g, "").length === 1) return true;
4713
- if ((t2.match(/─/g)?.length ?? 0) >= 6) return true;
4714
- if (/ctrl\+?o\s+to\s+expand/i.test(t2)) return true;
4715
- const hasBoxPrefix = /^[│╭╰╮╯┌└┐┘├┤┬┴┼]/.test(t2);
4716
- const stripped = t2.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "");
4717
- if (hasBoxPrefix && /^[❯>]\s+\S/.test(stripped) && !/^[❯>]\s*\d+\./.test(stripped)) return true;
4718
- return false;
5168
+ // src/agents/claude/quota.ts
5169
+ var fs5 = __toESM(require("fs"));
5170
+ var os6 = __toESM(require("os"));
5171
+ var path8 = __toESM(require("path"));
5172
+ var import_child_process4 = require("child_process");
5173
+ var HELPER_SCRIPT = `import os,pty,sys,select,signal,struct,fcntl,termios,errno
5174
+ m,s=pty.openpty()
5175
+ try:
5176
+ fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',30,120,0,0))
5177
+ except Exception:pass
5178
+ pid=os.fork()
5179
+ if pid==0:
5180
+ os.close(m);os.setsid()
5181
+ try:fcntl.ioctl(s,termios.TIOCSCTTY,0)
5182
+ except Exception:pass
5183
+ for fd in[0,1,2]:os.dup2(s,fd)
5184
+ if s>2:os.close(s)
5185
+ os.execvp(sys.argv[1],sys.argv[1:])
5186
+ sys.exit(127)
5187
+ os.close(s)
5188
+ done=[False]
5189
+ def onchld(n,f):
5190
+ try:os.waitpid(pid,os.WNOHANG)
5191
+ except Exception:pass
5192
+ done[0]=True
5193
+ signal.signal(signal.SIGCHLD,onchld)
5194
+ i=sys.stdin.fileno();o=sys.stdout.fileno()
5195
+ while not done[0]:
5196
+ try:r,_,_=select.select([i,m],[],[],0.1)
5197
+ except OSError as e:
5198
+ if e.errno==errno.EINTR:continue
5199
+ break
5200
+ if i in r:
5201
+ try:
5202
+ d=os.read(i,4096)
5203
+ if d:os.write(m,d)
5204
+ else:break
5205
+ except OSError:break
5206
+ if m in r:
5207
+ try:
5208
+ d=os.read(m,4096)
5209
+ if d:os.write(o,d)
5210
+ except OSError:done[0]=True
5211
+ try:os.kill(pid,signal.SIGTERM)
5212
+ except Exception:pass
5213
+ try:
5214
+ _,st=os.waitpid(pid,0)
5215
+ sys.exit((st>>8)&0xFF)
5216
+ except Exception:sys.exit(0)
5217
+ `;
5218
+ function parseUsageOutput(raw) {
5219
+ const clean = raw.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, " ");
5220
+ const weekMatch = clean.match(/(\d+)%\s*used/i) || clean.match(/(\d+)\s*%/);
5221
+ if (!weekMatch) return null;
5222
+ const percent = parseInt(weekMatch[1], 10);
5223
+ const resetMatch = clean.match(/resets\s+(.+?)(?:\s*[\(\)]|$)/im);
5224
+ const resetAt = resetMatch?.[1]?.trim() || void 0;
5225
+ return { percent, resetAt };
5226
+ }
5227
+ async function fetchClaudeQuota() {
5228
+ return new Promise((resolve2) => {
5229
+ const claudeCmd = findInPath("claude") ? "claude" : "claude-code";
5230
+ if (!claudeCmd) {
5231
+ resolve2(null);
5232
+ return;
5233
+ }
5234
+ const helperPath = path8.join(os6.tmpdir(), "codeam-quota-helper.py");
5235
+ fs5.writeFileSync(helperPath, HELPER_SCRIPT, { mode: 420 });
5236
+ const python = findInPath("python3") ?? findInPath("python");
5237
+ if (!python) {
5238
+ resolve2(null);
5239
+ return;
5240
+ }
5241
+ const proc = (0, import_child_process4.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
5242
+ stdio: ["pipe", "pipe", "ignore"],
5243
+ cwd: process.cwd(),
5244
+ env: { ...process.env, TERM: "dumb", COLUMNS: "120", LINES: "30" }
5245
+ });
5246
+ let output = "";
5247
+ let resolved = false;
5248
+ proc.stdout?.on("data", (chunk) => {
5249
+ output += chunk.toString("utf8");
5250
+ });
5251
+ setTimeout(() => {
5252
+ proc.stdin?.write("/usage\r");
5253
+ setTimeout(() => {
5254
+ if (resolved) return;
5255
+ resolved = true;
5256
+ const result = parseUsageOutput(output);
5257
+ try {
5258
+ proc.kill();
5259
+ } catch {
5260
+ }
5261
+ try {
5262
+ fs5.unlinkSync(helperPath);
5263
+ } catch {
5264
+ }
5265
+ resolve2(result);
5266
+ }, 5e3);
5267
+ }, 8e3);
5268
+ setTimeout(() => {
5269
+ if (!resolved) {
5270
+ resolved = true;
5271
+ resolve2(null);
5272
+ }
5273
+ try {
5274
+ proc.kill();
5275
+ } catch {
5276
+ }
5277
+ }, 2e4);
5278
+ });
4719
5279
  }
4720
- function parseChromeLine(line) {
4721
- const t2 = line.replace(/️/g, "").trim();
4722
- if (!t2) return null;
4723
- if (/^[─━—═─\-]{3,}$/.test(t2)) return null;
4724
- if (/^[❯>]\s*$/.test(t2)) return null;
4725
- if (t2.replace(/\s/g, "").length === 1) return null;
4726
- if ((t2.match(/─/g)?.length ?? 0) >= 6) return null;
4727
- if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) return null;
4728
- if (/high\s*[·•]\s*\/effort/i.test(t2)) return null;
4729
- if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) return null;
4730
- if (/ctrl\+?o\s+to\s+expand/i.test(t2)) return null;
4731
- if (/spending limit|usage limit/i.test(t2)) return null;
4732
- if (/^\(thinking\)\s*$/.test(t2)) {
4733
- return { tool: "thinking", label: "Thinking\u2026", status: "running" };
5280
+
5281
+ // src/agents/claude/history.ts
5282
+ var fs6 = __toESM(require("fs"));
5283
+ var path9 = __toESM(require("path"));
5284
+ var os7 = __toESM(require("os"));
5285
+ function encodeCwd(cwd) {
5286
+ return cwd.replace(/[\\/:]/g, "-");
5287
+ }
5288
+ function resolveHistoryDir(cwd, projectsRoot) {
5289
+ const root = projectsRoot ?? path9.join(os7.homedir(), ".claude", "projects");
5290
+ const primary = path9.join(root, encodeCwd(cwd));
5291
+ if (fs6.existsSync(primary)) return primary;
5292
+ try {
5293
+ const entries = fs6.readdirSync(root, { withFileTypes: true });
5294
+ const wanted = encodeCwd(cwd);
5295
+ for (const e of entries) {
5296
+ if (!e.isDirectory()) continue;
5297
+ const candidate = e.name.replace(/-+/g, "-");
5298
+ if (candidate === wanted.replace(/-+/g, "-")) {
5299
+ return path9.join(root, e.name);
5300
+ }
5301
+ }
5302
+ } catch {
5303
+ }
5304
+ return null;
5305
+ }
5306
+ function getCurrentUsage(historyDir, bootTimeMs = 0) {
5307
+ const GRACE_MS = 5e3;
5308
+ const cutoff = bootTimeMs > 0 ? bootTimeMs - GRACE_MS : 0;
5309
+ let entries;
5310
+ try {
5311
+ entries = fs6.readdirSync(historyDir, { withFileTypes: true });
5312
+ } catch {
5313
+ return null;
5314
+ }
5315
+ const files = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
5316
+ try {
5317
+ const stat3 = fs6.statSync(path9.join(historyDir, e.name));
5318
+ return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
5319
+ } catch {
5320
+ return { name: e.name, mtime: 0, birthtime: 0 };
5321
+ }
5322
+ }).filter((f) => f.birthtime >= cutoff).sort((a, b) => b.mtime - a.mtime);
5323
+ if (files.length === 0) return null;
5324
+ const filePath = path9.join(historyDir, files[0].name);
5325
+ let raw;
5326
+ try {
5327
+ raw = fs6.readFileSync(filePath, "utf8");
5328
+ } catch {
5329
+ return null;
5330
+ }
5331
+ let lastUsage = null;
5332
+ let lastModel = null;
5333
+ for (const line of raw.split("\n").filter(Boolean)) {
5334
+ try {
5335
+ const record = JSON.parse(line);
5336
+ if (record["type"] !== "assistant") continue;
5337
+ const msg = record["message"];
5338
+ if (msg?.["model"] === "<synthetic>") continue;
5339
+ const usage = msg?.["usage"];
5340
+ if (usage && (usage["input_tokens"] !== void 0 || usage["prompt_tokens"] !== void 0)) {
5341
+ lastUsage = usage;
5342
+ }
5343
+ if (msg?.["model"]) lastModel = msg["model"];
5344
+ } catch {
5345
+ }
5346
+ }
5347
+ const total = getContextWindow(lastModel);
5348
+ if (!lastUsage) {
5349
+ if (!lastModel) return null;
5350
+ return { used: 0, total, percent: 0, model: lastModel };
5351
+ }
5352
+ const inputTokens = (lastUsage["input_tokens"] ?? lastUsage["prompt_tokens"] ?? 0) + (lastUsage["cache_read_input_tokens"] ?? 0) + (lastUsage["cache_creation_input_tokens"] ?? 0);
5353
+ const percent = Math.min(100, Math.round(inputTokens / total * 100));
5354
+ return {
5355
+ used: inputTokens,
5356
+ total,
5357
+ percent,
5358
+ model: lastModel ?? void 0
5359
+ };
5360
+ }
5361
+ function extractText(content) {
5362
+ if (typeof content === "string") return content;
5363
+ if (Array.isArray(content)) {
5364
+ return content.filter((b) => b["type"] === "text").map((b) => b["text"]).join("\n");
5365
+ }
5366
+ return "";
5367
+ }
5368
+ function parseHistoryFile(filePath) {
5369
+ const out = [];
5370
+ let raw;
5371
+ try {
5372
+ raw = fs6.readFileSync(filePath, "utf8");
5373
+ } catch {
5374
+ return out;
5375
+ }
5376
+ for (const line of raw.split("\n").filter(Boolean)) {
5377
+ let rec;
5378
+ try {
5379
+ rec = JSON.parse(line);
5380
+ } catch {
5381
+ continue;
5382
+ }
5383
+ if (typeof rec !== "object" || rec === null) continue;
5384
+ const r = rec;
5385
+ if (r["isMeta"]) continue;
5386
+ const type = r["type"];
5387
+ if (type !== "user" && type !== "assistant") continue;
5388
+ const msg = r["message"];
5389
+ if (!msg) continue;
5390
+ const text = extractText(msg["content"]).trim();
5391
+ if (!text) continue;
5392
+ const ts = r["timestamp"];
5393
+ const timestamp = typeof ts === "string" ? ts : typeof ts === "number" ? new Date(ts).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
5394
+ const uuid = typeof r["uuid"] === "string" ? r["uuid"] : `${Date.now()}-${Math.random()}`;
5395
+ const usage = msg["usage"];
5396
+ out.push({
5397
+ id: uuid,
5398
+ role: type === "user" ? "user" : "agent",
5399
+ text,
5400
+ timestamp,
5401
+ modelId: typeof msg["model"] === "string" ? msg["model"] : void 0,
5402
+ usage: usage ? {
5403
+ input: usage["input_tokens"] ?? 0,
5404
+ output: usage["output_tokens"] ?? 0,
5405
+ cacheRead: usage["cache_read_input_tokens"],
5406
+ cacheCreation: usage["cache_creation_input_tokens"]
5407
+ } : void 0
5408
+ });
4734
5409
  }
4735
- if (TREE_LINE_RE.test(t2)) return null;
4736
- if (STATUS_LINE_RE.test(t2)) {
4737
- const label = t2.slice(2).replace(/….*/s, "").trim() || "Thinking\u2026";
4738
- return { tool: "thinking", label, status: "running" };
5410
+ return out;
5411
+ }
5412
+
5413
+ // src/agents/claude/runtime.ts
5414
+ var ClaudeRuntimeStrategy = class {
5415
+ id = "claude";
5416
+ meta = getAgent("claude");
5417
+ async prepareLaunch() {
5418
+ const launch = buildClaudeLaunch();
5419
+ if (!launch) throw new Error("claude binary not found in PATH");
5420
+ return { cmd: launch.cmd, args: launch.args };
4739
5421
  }
4740
- let text = t2;
4741
- if (SPINNER_RE.test(t2)) {
4742
- text = t2.slice(2).trim().replace(/….*/s, "").trim();
4743
- } else if (BULLET_TOOL_RE.test(t2)) {
4744
- text = t2.slice(2).trim();
4745
- 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();
5422
+ resumeLaunchArgs(sessionId) {
5423
+ return ["--resume", sessionId, "--dangerously-skip-permissions"];
4746
5424
  }
4747
- if (!text) return null;
4748
- return classifyStep(text);
4749
- }
4750
- function classifyStep(text) {
4751
- if (/^Read(?:ing)?\s+/i.test(text)) {
4752
- const label2 = text.replace(/^Read(?:ing)?\s+/i, "").replace(/\.\.\.$/, "").trim();
4753
- return { tool: "read", label: label2, status: "running" };
5425
+ resolveHistoryDir(cwd) {
5426
+ return resolveHistoryDir(cwd);
4754
5427
  }
4755
- if (/^Edit(?:ing)?\s+|^Writ(?:e|ing|ing to)\s+|^Creat(?:e|ing)\s+/i.test(text)) {
4756
- const label2 = text.replace(/^(?:Edit(?:ing)?|Writ(?:e|ing(?: to)?)|Creat(?:e|ing))\s+/i, "").replace(/\.\.\.$/, "").trim();
4757
- return { tool: "edit", label: label2, status: "running" };
5428
+ parseHistoryFile(filePath) {
5429
+ return parseHistoryFile(filePath);
4758
5430
  }
4759
- if (/^Runn(?:ing)?\s+|^Execut(?:e|ing)\s+|^Bash(?:ing)?\s*:|^\$\s+/i.test(text)) {
4760
- const label2 = text.replace(/^(?:Runn(?:ing)?|Execut(?:e|ing)|Bash(?:ing)?:|\$)\s+/i, "").replace(/\.\.\.$/, "").trim();
4761
- return { tool: "bash", label: label2, status: "running" };
5431
+ getCurrentUsage(historyDir) {
5432
+ return getCurrentUsage(historyDir);
4762
5433
  }
4763
- if (/^Search(?:ing)?\s+for\s+|^Grep(?:ping)?\s*:/i.test(text)) {
4764
- const label2 = text.replace(/^(?:Search(?:ing)?\s+for|Grep(?:ping)?:)\s+/i, "").replace(/\.\.\.$/, "").trim();
4765
- return { tool: "search", label: label2, status: "running" };
5434
+ async fetchWeeklyUsage() {
5435
+ return fetchClaudeQuota();
4766
5436
  }
4767
- const label = text.replace(/\.\.\.$/, "").trim();
4768
- return { tool: "other", label, status: "running" };
4769
- }
4770
-
4771
- // ../../packages/shared/src/protocol/renderToLines.ts
4772
- function renderToLines(raw) {
4773
- const screen = [""];
4774
- let row = 0;
4775
- let col = 0;
4776
- function ensureRow() {
4777
- while (screen.length <= row) screen.push("");
5437
+ async listModels() {
5438
+ return [
5439
+ { id: "claude-opus-4-7", label: "Claude Opus 4.7", contextWindow: 2e5 },
5440
+ { id: "claude-opus-4-6", label: "Claude Opus 4.6", contextWindow: 2e5 },
5441
+ { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", contextWindow: 2e5 },
5442
+ { id: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", contextWindow: 2e5 }
5443
+ ];
4778
5444
  }
4779
- function writeChar(ch) {
4780
- ensureRow();
4781
- if (col < screen[row].length) {
4782
- screen[row] = screen[row].slice(0, col) + ch + screen[row].slice(col + 1);
4783
- } else {
4784
- while (screen[row].length < col) screen[row] += " ";
4785
- screen[row] += ch;
5445
+ changeModelInstruction(modelId) {
5446
+ return { type: "pty", ptyInput: `/model ${modelId}\r` };
5447
+ }
5448
+ summarizeInstruction(mode) {
5449
+ if (mode === "normal") return { ptyInput: "/compact\r" };
5450
+ return { ptyInput: "/compact\r" };
5451
+ }
5452
+ };
5453
+
5454
+ // src/agents/claude/deploy.ts
5455
+ var fs8 = __toESM(require("fs"));
5456
+ var os9 = __toESM(require("os"));
5457
+ var path11 = __toESM(require("path"));
5458
+
5459
+ // src/agents/claude/credentials.ts
5460
+ var import_child_process5 = require("child_process");
5461
+ var fs7 = __toESM(require("fs"));
5462
+ var os8 = __toESM(require("os"));
5463
+ var path10 = __toESM(require("path"));
5464
+ var import_util = require("util");
5465
+ var execFileP = (0, import_util.promisify)(import_child_process5.execFile);
5466
+ async function detectLocalClaudeCredentials() {
5467
+ const localClaudeDir = path10.join(os8.homedir(), ".claude");
5468
+ const flat = path10.join(localClaudeDir, ".credentials.json");
5469
+ if (fs7.existsSync(flat)) {
5470
+ return { source: "flat-file", description: "~/.claude/.credentials.json" };
5471
+ }
5472
+ if (os8.platform() === "darwin") {
5473
+ try {
5474
+ await execFileP(
5475
+ "security",
5476
+ ["find-generic-password", "-s", "Claude Code-credentials"],
5477
+ { maxBuffer: 1024 * 1024 }
5478
+ );
5479
+ return { source: "macos-keychain", description: "macOS Keychain" };
5480
+ } catch {
5481
+ return { source: "none", description: "" };
4786
5482
  }
4787
- col++;
4788
5483
  }
4789
- let i = 0;
4790
- while (i < raw.length) {
4791
- const ch = raw[i];
4792
- if (ch === "\x1B") {
4793
- i++;
4794
- if (i >= raw.length) break;
4795
- if (raw[i] === "[") {
4796
- i++;
4797
- let param = "";
4798
- while (i < raw.length && !/[@-~]/.test(raw[i])) param += raw[i++];
4799
- const cmd = raw[i] ?? "";
4800
- const n = parseInt(param) || 1;
4801
- if (cmd === "A") {
4802
- row = Math.max(0, row - n);
4803
- } else if (cmd === "B") {
4804
- row += n;
4805
- ensureRow();
4806
- } else if (cmd === "C") {
4807
- col += n;
4808
- } else if (cmd === "D") {
4809
- col = Math.max(0, col - n);
4810
- } else if (cmd === "G") {
4811
- col = Math.max(0, n - 1);
4812
- } else if (cmd === "H" || cmd === "f") {
4813
- const p2 = param.split(";");
4814
- row = Math.max(0, (parseInt(p2[0] ?? "1") || 1) - 1);
4815
- col = Math.max(0, (parseInt(p2[1] ?? "1") || 1) - 1);
4816
- ensureRow();
4817
- } else if (cmd === "J") {
4818
- if (param === "2" || param === "3") {
4819
- screen.length = 1;
4820
- screen[0] = "";
4821
- row = 0;
4822
- col = 0;
4823
- } else if (param === "1") {
4824
- for (let r = 0; r < row; r++) screen[r] = "";
4825
- screen[row] = " ".repeat(col) + screen[row].slice(col);
4826
- } else {
4827
- screen[row] = screen[row].slice(0, col);
4828
- screen.splice(row + 1);
4829
- }
4830
- } else if (cmd === "K") {
4831
- ensureRow();
4832
- if (param === "" || param === "0") screen[row] = screen[row].slice(0, col);
4833
- else if (param === "1") screen[row] = " ".repeat(col) + screen[row].slice(col);
4834
- else if (param === "2") screen[row] = "";
4835
- } else if (cmd === "h" && (param === "?1049" || param === "?47")) {
4836
- screen.length = 1;
4837
- screen[0] = "";
4838
- row = 0;
4839
- col = 0;
4840
- } else if (cmd === "l" && (param === "?1049" || param === "?47")) {
4841
- screen.length = 1;
4842
- screen[0] = "";
4843
- row = 0;
4844
- col = 0;
4845
- }
4846
- } else if (raw[i] === "]") {
4847
- i++;
4848
- while (i < raw.length) {
4849
- if (raw[i] === "\x07") break;
4850
- if (raw[i] === "\x1B" && i + 1 < raw.length && raw[i + 1] === "\\") {
4851
- i++;
4852
- break;
4853
- }
4854
- i++;
4855
- }
4856
- }
4857
- } else if (ch === "\r") {
4858
- if (i + 1 < raw.length && raw[i + 1] === "\n") {
4859
- row++;
4860
- col = 0;
4861
- ensureRow();
4862
- i++;
4863
- } else {
4864
- col = 0;
4865
- }
4866
- } else if (ch === "\n") {
4867
- row++;
4868
- col = 0;
4869
- ensureRow();
4870
- } else if (ch >= " " || ch === " ") {
4871
- writeChar(ch);
5484
+ return { source: "none", description: "" };
5485
+ }
5486
+ async function bridgeClaudeCredentials(provider, workspaceId) {
5487
+ const localClaudeDir = path10.join(os8.homedir(), ".claude");
5488
+ const fileBased = path10.join(localClaudeDir, ".credentials.json");
5489
+ if (fs7.existsSync(fileBased)) {
5490
+ return { source: "flat-file", description: "~/.claude/.credentials.json" };
5491
+ }
5492
+ if (process.platform === "darwin") {
5493
+ try {
5494
+ const { stdout } = await execFileP(
5495
+ "security",
5496
+ ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
5497
+ { maxBuffer: 1024 * 1024 }
5498
+ );
5499
+ const json = stdout.trim();
5500
+ if (json.length === 0) return { source: "none", description: "" };
5501
+ await provider.uploadFile(
5502
+ workspaceId,
5503
+ "/home/codespace/.claude/.credentials.json",
5504
+ json,
5505
+ { mode: 384 }
5506
+ );
5507
+ return { source: "macos-keychain", description: "macOS Keychain" };
5508
+ } catch {
5509
+ return { source: "none", description: "" };
4872
5510
  }
4873
- i++;
4874
5511
  }
4875
- return screen;
5512
+ return { source: "none", description: "" };
4876
5513
  }
4877
-
4878
- // ../../packages/shared/src/protocol/selector.ts
4879
- function detectSelector(lines) {
4880
- if (lines.some((l) => /\?\s+for\s+shortcuts/i.test(l.trim()))) return null;
4881
- const clean = lines.map(
4882
- (l) => l.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "").replace(/\s*[│╭╰╮╯┌└┐┘├┤┬┴┼─━═]+\s*$/, "")
5514
+ async function runRemoteClaudeLogin(provider, workspaceId) {
5515
+ wt(
5516
+ [
5517
+ "A login URL will print below. Open it in your local browser, sign in,",
5518
+ "and paste any code Claude asks for back into this terminal."
5519
+ ].join("\n"),
5520
+ "Authenticating Claude on workspace"
4883
5521
  );
4884
- const hasCursor = clean.some((l) => /^[❯>]\s*\d+\./.test(l.trim()));
4885
- const looksLikeTrust = clean.some(
4886
- (l) => /\b(?:trust\s+the\s+files|trust\s+this\s+folder|safety\s+check)\b/i.test(l)
5522
+ const result = await provider.streamCommand(
5523
+ workspaceId,
5524
+ 'bash -lc "claude login || claude /login || true"'
4887
5525
  );
4888
- if (!hasCursor && !looksLikeTrust) return null;
4889
- let optionStartIdx = -1;
4890
- for (let i = 0; i < clean.length; i++) {
4891
- if (/^(?:[❯>]\s*)?\d+\.\s/.test(clean[i].trim())) {
4892
- optionStartIdx = i;
4893
- break;
4894
- }
5526
+ if (result.code !== 0) {
5527
+ wt(
5528
+ "claude login exited non-zero. You can re-run it manually inside the codespace later.",
5529
+ "Heads up"
5530
+ );
4895
5531
  }
4896
- if (optionStartIdx === -1) return null;
4897
- const questionParts = [];
4898
- for (let i = 0; i < optionStartIdx; i++) {
4899
- const t2 = clean[i].trim();
4900
- if (!t2) continue;
4901
- if (/^[─━—═\-]{3,}$/.test(t2)) continue;
4902
- if (/^\[.*\]$/.test(t2)) continue;
4903
- if (/^[>❯]\s/.test(t2)) continue;
4904
- if (!t2.includes(" ") && t2.length > 15) continue;
4905
- questionParts.push(t2);
5532
+ }
5533
+ async function verifyClaudeAuth(provider, workspaceId) {
5534
+ const result = await provider.exec(
5535
+ workspaceId,
5536
+ 'bash -lc "claude auth status 2>/dev/null || true"'
5537
+ );
5538
+ if (result.code !== 0) return false;
5539
+ const jsonStart = result.stdout.indexOf("{");
5540
+ if (jsonStart < 0) return false;
5541
+ try {
5542
+ const parsed = JSON.parse(result.stdout.slice(jsonStart));
5543
+ return parsed.loggedIn === true;
5544
+ } catch {
5545
+ return false;
4906
5546
  }
4907
- const question = questionParts.filter((line, i, arr) => !arr.some((other, j2) => j2 !== i && other.includes(line))).join("\n").trim();
4908
- const optionLabels = /* @__PURE__ */ new Map();
4909
- const optionDescs = /* @__PURE__ */ new Map();
4910
- let currentNum = -1;
4911
- for (let i = optionStartIdx; i < clean.length; i++) {
4912
- const t2 = clean[i].trim();
4913
- if (!t2) continue;
4914
- const m = t2.match(/^(?:[❯>]\s*)?(\d+)\.\s+(.+)/);
4915
- if (m) {
4916
- const num = parseInt(m[1], 10);
4917
- if (!optionLabels.has(num)) {
4918
- optionLabels.set(num, m[2].trim());
4919
- optionDescs.set(num, []);
5547
+ }
5548
+
5549
+ // src/agents/claude/deploy.ts
5550
+ var ClaudeDeployStrategy = class {
5551
+ id = "claude";
5552
+ async detectLocalCredentials() {
5553
+ return detectLocalClaudeCredentials();
5554
+ }
5555
+ async bridgeLocalCredentials(provider, workspaceId) {
5556
+ return bridgeClaudeCredentials(provider, workspaceId);
5557
+ }
5558
+ async runRemoteLogin(provider, workspaceId) {
5559
+ return runRemoteClaudeLogin(provider, workspaceId);
5560
+ }
5561
+ /**
5562
+ * Steps 5-9 of the deploy flow:
5563
+ * - Install Claude CLI on the workspace
5564
+ * - Copy local ~/.claude config dir (excluding heavy state)
5565
+ * - Ship ~/.claude.json (only when creds were bridged — privacy)
5566
+ * - Verify auth, fallback to runRemoteLogin
5567
+ */
5568
+ async setupOnWorkspace(provider, workspaceId, opts) {
5569
+ const claudeStep = fe();
5570
+ claudeStep.start("Installing Claude CLI on workspace\u2026");
5571
+ const installResult = await provider.exec(
5572
+ workspaceId,
5573
+ "curl -fsSL https://claude.ai/install.sh | bash"
5574
+ );
5575
+ if (installResult.code !== 0) {
5576
+ claudeStep.stop("\u2717 Claude CLI install failed");
5577
+ pt(installResult.stderr.slice(0, 1e3));
5578
+ process.exit(1);
5579
+ }
5580
+ claudeStep.stop("\u2713 Claude CLI installed");
5581
+ const localClaudeDir = path11.join(os9.homedir(), ".claude");
5582
+ const haveLocalClaude = fs8.existsSync(localClaudeDir) && fs8.statSync(localClaudeDir).isDirectory();
5583
+ if (haveLocalClaude) {
5584
+ const copyStep = fe();
5585
+ copyStep.start("Copying local Claude config to workspace\u2026");
5586
+ try {
5587
+ await provider.uploadDirectory(
5588
+ workspaceId,
5589
+ localClaudeDir,
5590
+ "/home/codespace/.claude",
5591
+ {
5592
+ exclude: [
5593
+ "./projects",
5594
+ // per-project conversation history (often 700MB+)
5595
+ "./file-history",
5596
+ // per-project file diffs
5597
+ "./downloads",
5598
+ // downloaded artifacts
5599
+ "./image-cache",
5600
+ // cached images
5601
+ "./paste-cache",
5602
+ // clipboard/paste cache
5603
+ "./backups",
5604
+ // local backups
5605
+ "./shell-snapshots",
5606
+ // shell history snapshots
5607
+ "./telemetry",
5608
+ // analytics dumps
5609
+ "./statsig",
5610
+ // feature-flag cache
5611
+ "./cache",
5612
+ // generic cache dir
5613
+ "./history.jsonl",
5614
+ // global REPL history
5615
+ "./ide",
5616
+ // local IDE bridge state
5617
+ "./todos",
5618
+ // local todo state
5619
+ "./tasks",
5620
+ // local task state
5621
+ // Don't overwrite the credentials we already staged in
5622
+ // step 4 — the local dir on macOS doesn't have a flat
5623
+ // credentials file anyway, but on Linux it would, and a
5624
+ // re-write here would be redundant.
5625
+ "./.credentials.json"
5626
+ ]
5627
+ }
5628
+ );
5629
+ copyStep.stop("\u2713 Claude config uploaded");
5630
+ } catch (err) {
5631
+ copyStep.stop("\u26A0 Could not upload Claude config (continuing)");
5632
+ void err;
5633
+ }
5634
+ }
5635
+ if (opts.bridged !== "none") {
5636
+ const localClaudeJson = path11.join(os9.homedir(), ".claude.json");
5637
+ if (fs8.existsSync(localClaudeJson)) {
5638
+ try {
5639
+ const contents = fs8.readFileSync(localClaudeJson);
5640
+ await provider.uploadFile(
5641
+ workspaceId,
5642
+ "/home/codespace/.claude.json",
5643
+ contents,
5644
+ { mode: 384 }
5645
+ );
5646
+ } catch (err) {
5647
+ void err;
5648
+ }
4920
5649
  }
4921
- currentNum = num;
4922
- } else if (currentNum !== -1 && !/^Enter to/i.test(t2) && !/^[─━—═\-]{3,}$/.test(t2) && !/↑.*↓.*navigate/i.test(t2) && !/Esc to/i.test(t2)) {
4923
- optionDescs.get(currentNum)?.push(t2);
4924
5650
  }
4925
- }
4926
- const keys = [...optionLabels.keys()].sort((a, b) => a - b);
4927
- if (keys.length < 2 || keys[0] !== 1) return null;
4928
- return {
4929
- question,
4930
- options: keys.map((k2) => optionLabels.get(k2)),
4931
- optionDescriptions: keys.map((k2) => (optionDescs.get(k2) ?? []).join(" ").trim()),
4932
- currentIndex: 0
4933
- };
4934
- }
4935
- function detectListSelector(lines) {
4936
- if (!lines.some((l) => /[↑↓].*navigate/i.test(l.trim()))) return null;
4937
- if (lines.some((l) => /^❯\s*\d+\./.test(l.trim()))) return null;
4938
- if (!lines.some((l) => /^\s+❯\s+\S/.test(l))) return null;
4939
- const isSelected = (line) => /^\s+❯\s+\S/.test(line);
4940
- const isUnselected = (line) => /^ \S/.test(line);
4941
- const isItem = (line) => isSelected(line) || isUnselected(line);
4942
- let optionStartIdx = -1;
4943
- for (let i = 0; i < lines.length; i++) {
4944
- if (isItem(lines[i])) {
4945
- optionStartIdx = i;
4946
- break;
5651
+ const verifyStep = fe();
5652
+ verifyStep.start("Verifying Claude auth on workspace\u2026");
5653
+ const verified = await verifyClaudeAuth(provider, workspaceId);
5654
+ if (verified) {
5655
+ verifyStep.stop("\u2713 Claude is logged in \u2014 no re-auth needed");
5656
+ } else {
5657
+ verifyStep.stop("\xB7 Claude not yet authenticated \u2014 running login flow");
5658
+ await runRemoteClaudeLogin(provider, workspaceId);
5659
+ const reverified = await verifyClaudeAuth(provider, workspaceId);
5660
+ if (!reverified) {
5661
+ wt(
5662
+ "Claude auth could not be confirmed. You may need to run `claude /login` manually inside the codespace.",
5663
+ "Heads up"
5664
+ );
5665
+ }
4947
5666
  }
4948
5667
  }
4949
- if (optionStartIdx === -1) return null;
4950
- const questionParts = [];
4951
- for (let i = 0; i < optionStartIdx; i++) {
4952
- const t2 = lines[i].trim();
4953
- if (!t2) continue;
4954
- if (/^[─━—═\-]{3,}$/.test(t2)) continue;
4955
- if (/[┌└│┐┘├┤┬┴┼]/.test(t2)) {
4956
- const inner = t2.replace(/[│┌└┐┘├┤┬┴┼─]/g, "").trim();
4957
- if (inner) questionParts.push(inner);
5668
+ };
5669
+
5670
+ // src/agents/codex/runtime.ts
5671
+ var import_node_child_process = require("child_process");
5672
+
5673
+ // src/agents/codex/history.ts
5674
+ var import_node_fs2 = __toESM(require("fs"));
5675
+ var import_node_path2 = __toESM(require("path"));
5676
+ var import_node_os = __toESM(require("os"));
5677
+ function resolveHistoryDir2(_cwd, homeOverride) {
5678
+ const home = homeOverride ?? import_node_os.default.homedir();
5679
+ const codexDir = import_node_path2.default.join(home, ".codex");
5680
+ if (!import_node_fs2.default.existsSync(import_node_path2.default.join(codexDir, "history.jsonl"))) return null;
5681
+ return codexDir;
5682
+ }
5683
+ function isCodexRecord(v) {
5684
+ if (!v || typeof v !== "object") return false;
5685
+ const r = v;
5686
+ return typeof r.session_id === "string" && typeof r.ts === "number" && typeof r.text === "string";
5687
+ }
5688
+ function parseHistoryFile2(filePath) {
5689
+ const raw = import_node_fs2.default.readFileSync(filePath, "utf8");
5690
+ const out = [];
5691
+ let idx = 0;
5692
+ for (const line of raw.split("\n").filter(Boolean)) {
5693
+ let rec;
5694
+ try {
5695
+ rec = JSON.parse(line);
5696
+ } catch {
4958
5697
  continue;
4959
5698
  }
4960
- if (/^[>❯]\s/.test(t2)) continue;
4961
- if (/[↑↓].*navigate/i.test(t2)) continue;
4962
- if (!t2.includes(" ") && t2.length > 15) continue;
4963
- questionParts.push(t2);
5699
+ if (!isCodexRecord(rec)) continue;
5700
+ out.push({
5701
+ id: `${rec.session_id}:${idx}`,
5702
+ role: "user",
5703
+ text: rec.text,
5704
+ timestamp: new Date(rec.ts * 1e3).toISOString()
5705
+ });
5706
+ idx++;
4964
5707
  }
4965
- const question = questionParts.filter((line, i, arr) => !arr.some((other, j2) => j2 !== i && other.includes(line))).join("\n").trim();
4966
- const options = [];
4967
- let currentIndex = 0;
4968
- for (const line of lines.slice(optionStartIdx)) {
4969
- const t2 = line.trim();
4970
- if (!t2) continue;
4971
- if (/[↑↓].*navigate/i.test(t2)) break;
4972
- if (/^[─━—═\-]{3,}$/.test(t2)) continue;
4973
- if (isSelected(line)) {
4974
- currentIndex = options.length;
4975
- options.push(t2.replace(/^❯\s+/, "").trim());
4976
- } else if (isUnselected(line)) {
4977
- options.push(t2);
5708
+ return out;
5709
+ }
5710
+ function getCurrentUsage2(_historyDir) {
5711
+ return null;
5712
+ }
5713
+
5714
+ // src/agents/codex/runtime.ts
5715
+ var CODEX_CONTEXT_WINDOW = 272e3;
5716
+ var CODEX_MODELS = [
5717
+ { id: "gpt-5.5", label: "GPT-5.5", contextWindow: CODEX_CONTEXT_WINDOW },
5718
+ { id: "gpt-5.4", label: "GPT-5.4", contextWindow: CODEX_CONTEXT_WINDOW },
5719
+ { id: "gpt-5.4-mini", label: "GPT-5.4 Mini", contextWindow: CODEX_CONTEXT_WINDOW },
5720
+ { id: "gpt-5.3-codex", label: "GPT-5.3 Codex", contextWindow: CODEX_CONTEXT_WINDOW },
5721
+ { id: "gpt-5.2", label: "GPT-5.2", contextWindow: CODEX_CONTEXT_WINDOW },
5722
+ { id: "codex-auto-review", label: "Codex Auto Review", contextWindow: CODEX_CONTEXT_WINDOW }
5723
+ ];
5724
+ var CodexRuntimeStrategy = class {
5725
+ id = "codex";
5726
+ meta = getAgent("codex");
5727
+ async prepareLaunch() {
5728
+ const binary = findInPath("codex");
5729
+ if (!binary) {
5730
+ await installCodexViaNpm();
5731
+ const afterInstall = findInPath("codex");
5732
+ if (!afterInstall) {
5733
+ throw new Error(
5734
+ "Could not find 'codex' on PATH after running 'npm install -g @openai/codex'. Install it manually and retry."
5735
+ );
5736
+ }
5737
+ return { cmd: afterInstall, args: [] };
4978
5738
  }
5739
+ return { cmd: binary, args: [] };
4979
5740
  }
4980
- if (options.length < 2) return null;
4981
- return {
4982
- question,
4983
- options,
4984
- optionDescriptions: options.map(() => ""),
4985
- currentIndex
4986
- };
5741
+ /** `codex resume <SESSION_ID>` subcommand, not flag. */
5742
+ resumeLaunchArgs(sessionId) {
5743
+ return ["resume", sessionId];
5744
+ }
5745
+ resolveHistoryDir(cwd) {
5746
+ return resolveHistoryDir2(cwd);
5747
+ }
5748
+ parseHistoryFile(filePath) {
5749
+ return parseHistoryFile2(filePath);
5750
+ }
5751
+ getCurrentUsage(historyDir) {
5752
+ return getCurrentUsage2(historyDir);
5753
+ }
5754
+ /**
5755
+ * Codex's quota lives behind the `account/get_account_rate_limits` RPC,
5756
+ * not a TUI slash command. Phase 2 ships with this stubbed to null so the
5757
+ * mobile shows "—" for weekly usage on Codex sessions. A follow-up will
5758
+ * invoke the RPC directly.
5759
+ */
5760
+ async fetchWeeklyUsage() {
5761
+ return null;
5762
+ }
5763
+ async listModels() {
5764
+ return CODEX_MODELS;
5765
+ }
5766
+ changeModelInstruction(modelId) {
5767
+ return { type: "pty", ptyInput: `/model ${modelId}\r` };
5768
+ }
5769
+ /**
5770
+ * Codex has no auto-compact (`auto_compact_token_limit: null` for every
5771
+ * model). Both modes fall through to the manual `/compact` slash command.
5772
+ */
5773
+ summarizeInstruction(_mode) {
5774
+ return { ptyInput: "/compact\r" };
5775
+ }
5776
+ };
5777
+ async function installCodexViaNpm() {
5778
+ return new Promise((resolve2, reject) => {
5779
+ const npm = process.platform === "win32" ? "npm.cmd" : "npm";
5780
+ const proc = (0, import_node_child_process.spawn)(npm, ["install", "-g", "@openai/codex"], {
5781
+ stdio: "inherit"
5782
+ });
5783
+ proc.on("close", (code) => {
5784
+ if (code === 0) resolve2();
5785
+ else reject(new Error(`npm install -g @openai/codex exited ${code}`));
5786
+ });
5787
+ proc.on("error", reject);
5788
+ });
4987
5789
  }
4988
5790
 
4989
- // ../../packages/shared/src/protocol/filterChrome.ts
4990
- function filterChrome(lines) {
4991
- const result = [];
4992
- let skipEchoContinuation = false;
4993
- for (const line of lines) {
4994
- const t2 = line.trim();
4995
- if (!t2) {
4996
- skipEchoContinuation = false;
4997
- continue;
4998
- }
4999
- if (/^[─━—═─\-]{3,}$/.test(t2)) {
5000
- skipEchoContinuation = false;
5001
- continue;
5791
+ // src/agents/codex/credentials.ts
5792
+ var import_node_fs3 = __toESM(require("fs"));
5793
+ var import_node_path3 = __toESM(require("path"));
5794
+ var import_node_os2 = __toESM(require("os"));
5795
+ async function detectLocalCodexCredentials() {
5796
+ const home = process.env.HOME ?? import_node_os2.default.homedir();
5797
+ const authJson = import_node_path3.default.join(home, ".codex", "auth.json");
5798
+ if (import_node_fs3.default.existsSync(authJson)) {
5799
+ return { source: "flat-file", description: "~/.codex/auth.json" };
5800
+ }
5801
+ if (process.env.OPENAI_API_KEY) {
5802
+ return { source: "env-var", description: "OPENAI_API_KEY env var" };
5803
+ }
5804
+ return { source: "none", description: "" };
5805
+ }
5806
+ async function bridgeLocalCodexCredentials(provider, workspaceId) {
5807
+ const home = process.env.HOME ?? import_node_os2.default.homedir();
5808
+ const authJson = import_node_path3.default.join(home, ".codex", "auth.json");
5809
+ if (import_node_fs3.default.existsSync(authJson)) {
5810
+ const contents = import_node_fs3.default.readFileSync(authJson);
5811
+ await provider.exec(workspaceId, "mkdir -p ~/.codex && chmod 700 ~/.codex");
5812
+ await provider.uploadFile(workspaceId, "/home/codespace/.codex/auth.json", contents, { mode: 384 });
5813
+ return { source: "flat-file", description: "~/.codex/auth.json" };
5814
+ }
5815
+ const key = process.env.OPENAI_API_KEY;
5816
+ if (key) {
5817
+ const escaped = key.replace(/'/g, "'\\''");
5818
+ const line = `export OPENAI_API_KEY='${escaped}'`;
5819
+ await provider.exec(workspaceId, `grep -qxF "${line}" ~/.bashrc || echo "${line}" >> ~/.bashrc`);
5820
+ return { source: "env-var", description: "OPENAI_API_KEY env var" };
5821
+ }
5822
+ return { source: "none", description: "" };
5823
+ }
5824
+ async function runRemoteCodexLogin(provider, workspaceId) {
5825
+ await provider.streamCommand(workspaceId, 'bash -lc "codex login"');
5826
+ }
5827
+ async function verifyCodexAuth(provider, workspaceId) {
5828
+ const r = await provider.exec(workspaceId, 'bash -lc "codex --version"');
5829
+ return r.code === 0;
5830
+ }
5831
+
5832
+ // src/agents/codex/deploy.ts
5833
+ var CodexDeployStrategy = class {
5834
+ id = "codex";
5835
+ async detectLocalCredentials() {
5836
+ return detectLocalCodexCredentials();
5837
+ }
5838
+ async bridgeLocalCredentials(provider, workspaceId) {
5839
+ return bridgeLocalCodexCredentials(provider, workspaceId);
5840
+ }
5841
+ async runRemoteLogin(provider, workspaceId) {
5842
+ return runRemoteCodexLogin(provider, workspaceId);
5843
+ }
5844
+ /**
5845
+ * Steps 5–7 of the existing local-deploy flow, adapted for Codex:
5846
+ * 1. Install codex via npm
5847
+ * 2. (skipped — bridge already happened before this method runs, or the
5848
+ * fallback runs via runRemoteLogin)
5849
+ * 3. Verify auth; fallback to remote login if verify fails AND no
5850
+ * credentials were bridged.
5851
+ */
5852
+ async setupOnWorkspace(provider, workspaceId, opts) {
5853
+ const installResult = await provider.exec(workspaceId, "npm install -g @openai/codex");
5854
+ if (installResult.code !== 0) {
5855
+ throw new Error(`codex install failed: ${installResult.stderr.slice(0, 500)}`);
5002
5856
  }
5003
- if (/^[●⏺]\s/.test(t2)) skipEchoContinuation = false;
5004
- if (/^[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]\s/.test(t2)) continue;
5005
- if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) continue;
5006
- if (/high\s*[·•]\s*\/effort/i.test(t2)) continue;
5007
- if (/^[❯>]\s*$/.test(t2)) continue;
5008
- if (/^\(thinking\)\s*$/.test(t2)) continue;
5009
- if (/^\?\s.*shortcut/i.test(t2)) continue;
5010
- if (/spending limit|usage limit/i.test(t2) && t2.length < 80) continue;
5011
- if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) continue;
5012
- if (t2.replace(/\s/g, "").length === 1) continue;
5013
- if ((t2.match(/─/g)?.length ?? 0) >= 6) continue;
5014
- if (/ctrl\+?o\s+to\s+expand/i.test(t2)) continue;
5015
- 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(
5016
- t2
5017
- ))
5018
- continue;
5019
- if (/^└\s/.test(t2)) continue;
5020
- if (/^\+\s/.test(t2) && /\d+\s*s\s*[·•]|\bthought\s+for\b|\d+\s*tokens|\(thinking\)/i.test(t2)) continue;
5021
- if (/^↓\s*\d+\s*tokens/i.test(t2)) continue;
5022
- if (/^\bthought\s+for\s+\d+/i.test(t2)) continue;
5023
- const stripped = t2.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "");
5024
- if (/^[❯>]\s+\S/.test(stripped) && !/^[❯>]\s*\d+\./.test(stripped)) {
5025
- skipEchoContinuation = true;
5026
- continue;
5857
+ const verified = await verifyCodexAuth(provider, workspaceId);
5858
+ if (!verified && opts.bridged === "none") {
5859
+ await runRemoteCodexLogin(provider, workspaceId);
5027
5860
  }
5028
- if (skipEchoContinuation) continue;
5029
- result.push(line);
5030
5861
  }
5031
- return result;
5032
- }
5862
+ };
5033
5863
 
5034
- // ../../packages/shared/src/models/pricing.ts
5035
- var MODEL_PRICING = {
5036
- "claude-sonnet-4": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
5037
- "claude-opus-4": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
5038
- "claude-3-5-sonnet": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
5039
- "claude-3-5-haiku": { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
5040
- "claude-3-haiku": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 }
5864
+ // src/agents/registry.ts
5865
+ var runtimeBuilders = {
5866
+ claude: () => new ClaudeRuntimeStrategy(),
5867
+ codex: () => new CodexRuntimeStrategy()
5041
5868
  };
5042
- var MODEL_CONTEXT_WINDOW = {
5043
- "claude-opus-4": 1e6,
5044
- "claude-sonnet-4": 1e6,
5045
- "claude-3-5-sonnet": 2e5,
5046
- "claude-3-5-haiku": 2e5,
5047
- "claude-3-haiku": 2e5
5869
+ var deployBuilders = {
5870
+ claude: () => new ClaudeDeployStrategy(),
5871
+ codex: () => new CodexDeployStrategy()
5048
5872
  };
5049
- var DEFAULT_CONTEXT_WINDOW = 2e5;
5050
- function getPricing(model) {
5051
- for (const [prefix, pricing] of Object.entries(MODEL_PRICING)) {
5052
- if (model.startsWith(prefix)) return pricing;
5873
+ function createRuntimeStrategy(agent) {
5874
+ if (!AGENT_REGISTRY[agent]?.enabled) {
5875
+ throw new Error(
5876
+ `Agent "${agent}" is not supported in this codeam-cli version. Upgrade with 'npm i -g codeam-cli@latest'.`
5877
+ );
5053
5878
  }
5054
- return MODEL_PRICING["claude-sonnet-4"];
5879
+ const build = runtimeBuilders[agent];
5880
+ if (!build) {
5881
+ throw new Error(`No runtime strategy registered for agent "${agent}"`);
5882
+ }
5883
+ return build();
5055
5884
  }
5056
- function getContextWindow(model) {
5057
- if (!model) return DEFAULT_CONTEXT_WINDOW;
5058
- for (const [prefix, size] of Object.entries(MODEL_CONTEXT_WINDOW)) {
5059
- if (model.startsWith(prefix)) return size;
5885
+ function createDeployStrategy(agent) {
5886
+ if (!AGENT_REGISTRY[agent]?.enabled) {
5887
+ throw new Error(`Agent "${agent}" is not supported in this codeam-cli version.`);
5060
5888
  }
5061
- return DEFAULT_CONTEXT_WINDOW;
5889
+ const build = deployBuilders[agent];
5890
+ if (!build) {
5891
+ throw new Error(`No deploy strategy registered for agent "${agent}"`);
5892
+ }
5893
+ return build();
5062
5894
  }
5063
5895
 
5064
5896
  // src/services/output/chrome-tracker.ts
@@ -5531,9 +6363,9 @@ var OutputService = class _OutputService {
5531
6363
  };
5532
6364
 
5533
6365
  // src/services/history.service.ts
5534
- var fs5 = __toESM(require("fs"));
5535
- var path8 = __toESM(require("path"));
5536
- var os6 = __toESM(require("os"));
6366
+ var fs11 = __toESM(require("fs"));
6367
+ var path14 = __toESM(require("path"));
6368
+ var os12 = __toESM(require("os"));
5537
6369
  var https4 = __toESM(require("https"));
5538
6370
  var http4 = __toESM(require("http"));
5539
6371
  var import_zod = require("zod");
@@ -5548,28 +6380,7 @@ var historyRecordSchema = import_zod.z.object({
5548
6380
  }).passthrough().optional()
5549
6381
  }).passthrough();
5550
6382
  var API_BASE4 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
5551
- function encodeCwd(cwd) {
5552
- return cwd.replace(/[\\/:]/g, "-");
5553
- }
5554
- function findProjectDir(cwd) {
5555
- const projectsRoot = path8.join(os6.homedir(), ".claude", "projects");
5556
- const primary = path8.join(projectsRoot, encodeCwd(cwd));
5557
- if (fs5.existsSync(primary)) return primary;
5558
- try {
5559
- const entries = fs5.readdirSync(projectsRoot, { withFileTypes: true });
5560
- const wanted = encodeCwd(cwd);
5561
- for (const e of entries) {
5562
- if (!e.isDirectory()) continue;
5563
- const candidate = e.name.replace(/-+/g, "-");
5564
- if (candidate === wanted.replace(/-+/g, "-")) {
5565
- return path8.join(projectsRoot, e.name);
5566
- }
5567
- }
5568
- } catch {
5569
- }
5570
- return null;
5571
- }
5572
- function extractText(content) {
6383
+ function extractText2(content) {
5573
6384
  if (typeof content === "string") return content;
5574
6385
  if (Array.isArray(content)) {
5575
6386
  return content.filter((b) => b["type"] === "text").map((b) => b["text"]).join("\n");
@@ -5581,7 +6392,7 @@ function parseJsonl(filePath) {
5581
6392
  const messages = [];
5582
6393
  let raw;
5583
6394
  try {
5584
- raw = fs5.readFileSync(filePath, "utf8");
6395
+ raw = fs11.readFileSync(filePath, "utf8");
5585
6396
  } catch (err) {
5586
6397
  if (err.code !== "ENOENT") {
5587
6398
  log.warn("history:parseJsonl", `read failed for ${filePath}`, err);
@@ -5608,10 +6419,10 @@ function parseJsonl(filePath) {
5608
6419
  const uuid = record.uuid ?? `${Date.now()}-${Math.random()}`;
5609
6420
  const msg = record.message;
5610
6421
  if (record.type === "user" && msg) {
5611
- const text = extractText(msg.content).trim();
6422
+ const text = extractText2(msg.content).trim();
5612
6423
  if (text) messages.push({ id: uuid, role: "user", text, timestamp });
5613
6424
  } else if (record.type === "assistant" && msg) {
5614
- const text = extractText(msg.content).trim();
6425
+ const text = extractText2(msg.content).trim();
5615
6426
  if (text) messages.push({ id: uuid, role: "agent", text, timestamp });
5616
6427
  }
5617
6428
  }
@@ -5655,9 +6466,10 @@ function post(endpoint, body) {
5655
6466
  });
5656
6467
  }
5657
6468
  var HistoryService = class _HistoryService {
5658
- constructor(pluginId, cwd, options) {
6469
+ constructor(runtime, pluginId, cwd, options) {
5659
6470
  this.pluginId = pluginId;
5660
6471
  this.cwd = cwd;
6472
+ this.runtime = runtime;
5661
6473
  this.bootTimeMs = options?.bootTimeMs ?? Date.now();
5662
6474
  }
5663
6475
  pluginId;
@@ -5693,6 +6505,7 @@ var HistoryService = class _HistoryService {
5693
6505
  * previous pair / previous Claude run.
5694
6506
  */
5695
6507
  static BIRTHTIME_GRACE_MS = 5e3;
6508
+ runtime;
5696
6509
  /** Store rate limit reset info detected from Claude Code output */
5697
6510
  setRateLimitReset(reset) {
5698
6511
  this._rateLimitReset = reset;
@@ -5713,7 +6526,7 @@ var HistoryService = class _HistoryService {
5713
6526
  return this._quotaPercent === null || Date.now() - this._quotaFetchedAt > ttlMs;
5714
6527
  }
5715
6528
  get projectDir() {
5716
- return findProjectDir(this.cwd) ?? path8.join(os6.homedir(), ".claude", "projects", encodeCwd(this.cwd));
6529
+ return this.runtime.resolveHistoryDir(this.cwd) ?? path14.join(os12.homedir(), ".claude", "projects", encodeCwd(this.cwd));
5717
6530
  }
5718
6531
  /** Set the current Claude conversation ID (extracted from /cost command or session start) */
5719
6532
  setCurrentConversationId(id) {
@@ -5725,7 +6538,7 @@ var HistoryService = class _HistoryService {
5725
6538
  /** Return the current message count in the active conversation. */
5726
6539
  getCurrentMessageCount() {
5727
6540
  if (!this.currentConversationId) return 0;
5728
- const filePath = path8.join(this.projectDir, `${this.currentConversationId}.jsonl`);
6541
+ const filePath = path14.join(this.projectDir, `${this.currentConversationId}.jsonl`);
5729
6542
  return parseJsonl(filePath).length;
5730
6543
  }
5731
6544
  /**
@@ -5736,7 +6549,7 @@ var HistoryService = class _HistoryService {
5736
6549
  const deadline = Date.now() + timeoutMs;
5737
6550
  while (Date.now() < deadline) {
5738
6551
  if (!this.currentConversationId) return null;
5739
- const filePath = path8.join(this.projectDir, `${this.currentConversationId}.jsonl`);
6552
+ const filePath = path14.join(this.projectDir, `${this.currentConversationId}.jsonl`);
5740
6553
  const messages = parseJsonl(filePath);
5741
6554
  if (messages.length > previousCount) {
5742
6555
  for (let i = messages.length - 1; i >= previousCount; i--) {
@@ -5762,16 +6575,16 @@ var HistoryService = class _HistoryService {
5762
6575
  const dir = this.projectDir;
5763
6576
  const cutoff = this.bootTimeMs - _HistoryService.BIRTHTIME_GRACE_MS;
5764
6577
  try {
5765
- const files = fs5.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
6578
+ const files = fs11.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
5766
6579
  try {
5767
- const stat3 = fs5.statSync(path8.join(dir, e.name));
6580
+ const stat3 = fs11.statSync(path14.join(dir, e.name));
5768
6581
  return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
5769
6582
  } catch {
5770
6583
  return { name: e.name, mtime: 0, birthtime: 0 };
5771
6584
  }
5772
6585
  }).filter((f) => f.birthtime >= cutoff).sort((a, b) => b.mtime - a.mtime);
5773
6586
  if (files.length > 0) {
5774
- this.currentConversationId = path8.basename(files[0].name, ".jsonl");
6587
+ this.currentConversationId = path14.basename(files[0].name, ".jsonl");
5775
6588
  }
5776
6589
  } catch {
5777
6590
  }
@@ -5805,13 +6618,13 @@ var HistoryService = class _HistoryService {
5805
6618
  const cutoff = this.bootTimeMs - _HistoryService.BIRTHTIME_GRACE_MS;
5806
6619
  let entries;
5807
6620
  try {
5808
- entries = fs5.readdirSync(dir, { withFileTypes: true });
6621
+ entries = fs11.readdirSync(dir, { withFileTypes: true });
5809
6622
  } catch {
5810
6623
  return null;
5811
6624
  }
5812
6625
  const files = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
5813
6626
  try {
5814
- const stat3 = fs5.statSync(path8.join(dir, e.name));
6627
+ const stat3 = fs11.statSync(path14.join(dir, e.name));
5815
6628
  return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
5816
6629
  } catch {
5817
6630
  return { name: e.name, mtime: 0, birthtime: 0 };
@@ -5820,12 +6633,12 @@ var HistoryService = class _HistoryService {
5820
6633
  if (files.length === 0) return null;
5821
6634
  const targetFile = this.currentConversationId ? `${this.currentConversationId}.jsonl` : files[0].name;
5822
6635
  if (!files.some((f) => f.name === targetFile)) return null;
5823
- return this.extractUsageFromFile(path8.join(dir, targetFile));
6636
+ return this.extractUsageFromFile(path14.join(dir, targetFile));
5824
6637
  }
5825
6638
  extractUsageFromFile(filePath) {
5826
6639
  let raw;
5827
6640
  try {
5828
- raw = fs5.readFileSync(filePath, "utf8");
6641
+ raw = fs11.readFileSync(filePath, "utf8");
5829
6642
  } catch {
5830
6643
  return null;
5831
6644
  }
@@ -5870,9 +6683,9 @@ var HistoryService = class _HistoryService {
5870
6683
  let totalCost = 0;
5871
6684
  let files;
5872
6685
  try {
5873
- files = fs5.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).filter((f) => {
6686
+ files = fs11.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).filter((f) => {
5874
6687
  try {
5875
- return fs5.statSync(path8.join(projectDir, f)).mtimeMs >= monthStartMs;
6688
+ return fs11.statSync(path14.join(projectDir, f)).mtimeMs >= monthStartMs;
5876
6689
  } catch {
5877
6690
  return false;
5878
6691
  }
@@ -5883,7 +6696,7 @@ var HistoryService = class _HistoryService {
5883
6696
  for (const file of files) {
5884
6697
  let raw;
5885
6698
  try {
5886
- raw = fs5.readFileSync(path8.join(projectDir, file), "utf8");
6699
+ raw = fs11.readFileSync(path14.join(projectDir, file), "utf8");
5887
6700
  } catch {
5888
6701
  continue;
5889
6702
  }
@@ -5918,30 +6731,30 @@ var HistoryService = class _HistoryService {
5918
6731
  const dir = this.projectDir;
5919
6732
  let entries;
5920
6733
  try {
5921
- entries = fs5.readdirSync(dir, { withFileTypes: true });
6734
+ entries = fs11.readdirSync(dir, { withFileTypes: true });
5922
6735
  } catch {
5923
6736
  return;
5924
6737
  }
5925
6738
  const sessions2 = [];
5926
6739
  for (const entry of entries) {
5927
6740
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
5928
- const id = path8.basename(entry.name, ".jsonl");
5929
- const filePath = path8.join(dir, entry.name);
6741
+ const id = path14.basename(entry.name, ".jsonl");
6742
+ const filePath = path14.join(dir, entry.name);
5930
6743
  let mtime = Date.now();
5931
6744
  try {
5932
- mtime = fs5.statSync(filePath).mtimeMs;
6745
+ mtime = fs11.statSync(filePath).mtimeMs;
5933
6746
  } catch {
5934
6747
  }
5935
6748
  let summary = "";
5936
6749
  try {
5937
- const raw = fs5.readFileSync(filePath, "utf8");
6750
+ const raw = fs11.readFileSync(filePath, "utf8");
5938
6751
  for (const line of raw.split("\n")) {
5939
6752
  if (!line.trim()) continue;
5940
6753
  try {
5941
6754
  const record = JSON.parse(line);
5942
6755
  if (record["type"] === "user") {
5943
6756
  const msg = record["message"];
5944
- const text = extractText(msg?.["content"]).trim();
6757
+ const text = extractText2(msg?.["content"]).trim();
5945
6758
  if (text) {
5946
6759
  summary = text.slice(0, 120);
5947
6760
  break;
@@ -5967,7 +6780,7 @@ var HistoryService = class _HistoryService {
5967
6780
  * showing an empty conversation.
5968
6781
  */
5969
6782
  async loadConversation(sessionId) {
5970
- const filePath = path8.join(this.projectDir, `${sessionId}.jsonl`);
6783
+ const filePath = path14.join(this.projectDir, `${sessionId}.jsonl`);
5971
6784
  const messages = parseJsonl(filePath);
5972
6785
  if (messages.length === 0) return;
5973
6786
  const totalBatches = Math.ceil(messages.length / CONVERSATION_BATCH_SIZE);
@@ -6010,7 +6823,7 @@ var HistoryService = class _HistoryService {
6010
6823
  if (!this.currentConversationId) return 0;
6011
6824
  }
6012
6825
  const sessionId = this.currentConversationId;
6013
- const filePath = path8.join(this.projectDir, `${sessionId}.jsonl`);
6826
+ const filePath = path14.join(this.projectDir, `${sessionId}.jsonl`);
6014
6827
  const messages = parseJsonl(filePath);
6015
6828
  if (messages.length === 0) return 0;
6016
6829
  const marker = this.lastUploadedUuid.get(sessionId);
@@ -6027,130 +6840,39 @@ var HistoryService = class _HistoryService {
6027
6840
  sessionId,
6028
6841
  messages: newMessages,
6029
6842
  mode: "append"
6030
- };
6031
- const ok = await post("/api/sessions/claude-conversation", body);
6032
- if (ok) {
6033
- const last = newMessages[newMessages.length - 1];
6034
- this.lastUploadedUuid.set(sessionId, last.id);
6035
- return newMessages.length;
6036
- }
6037
- return 0;
6038
- }
6039
- };
6040
-
6041
- // src/commands/start/quota-fetcher.ts
6042
- var fs6 = __toESM(require("fs"));
6043
- var os7 = __toESM(require("os"));
6044
- var path9 = __toESM(require("path"));
6045
- var import_child_process4 = require("child_process");
6046
- var inProgress = false;
6047
- var HELPER_SCRIPT = `import os,pty,sys,select,signal,struct,fcntl,termios,errno
6048
- m,s=pty.openpty()
6049
- try:
6050
- fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',30,120,0,0))
6051
- except Exception:pass
6052
- pid=os.fork()
6053
- if pid==0:
6054
- os.close(m);os.setsid()
6055
- try:fcntl.ioctl(s,termios.TIOCSCTTY,0)
6056
- except Exception:pass
6057
- for fd in[0,1,2]:os.dup2(s,fd)
6058
- if s>2:os.close(s)
6059
- os.execvp(sys.argv[1],sys.argv[1:])
6060
- sys.exit(127)
6061
- os.close(s)
6062
- done=[False]
6063
- def onchld(n,f):
6064
- try:os.waitpid(pid,os.WNOHANG)
6065
- except Exception:pass
6066
- done[0]=True
6067
- signal.signal(signal.SIGCHLD,onchld)
6068
- i=sys.stdin.fileno();o=sys.stdout.fileno()
6069
- while not done[0]:
6070
- try:r,_,_=select.select([i,m],[],[],0.1)
6071
- except OSError as e:
6072
- if e.errno==errno.EINTR:continue
6073
- break
6074
- if i in r:
6075
- try:
6076
- d=os.read(i,4096)
6077
- if d:os.write(m,d)
6078
- else:break
6079
- except OSError:break
6080
- if m in r:
6081
- try:
6082
- d=os.read(m,4096)
6083
- if d:os.write(o,d)
6084
- except OSError:done[0]=True
6085
- try:os.kill(pid,signal.SIGTERM)
6086
- except Exception:pass
6087
- try:
6088
- _,st=os.waitpid(pid,0)
6089
- sys.exit((st>>8)&0xFF)
6090
- except Exception:sys.exit(0)
6091
- `;
6092
- function fetchQuotaUsage(historySvc) {
6843
+ };
6844
+ const ok = await post("/api/sessions/claude-conversation", body);
6845
+ if (ok) {
6846
+ const last = newMessages[newMessages.length - 1];
6847
+ this.lastUploadedUuid.set(sessionId, last.id);
6848
+ return newMessages.length;
6849
+ }
6850
+ return 0;
6851
+ }
6852
+ };
6853
+
6854
+ // src/commands/start/quota-fetcher.ts
6855
+ var inProgress = false;
6856
+ function fetchQuotaUsage(runtime, historySvc) {
6093
6857
  if (inProgress) return;
6094
6858
  inProgress = true;
6095
- const claudeCmd = findInPath("claude") ? "claude" : "claude-code";
6096
- if (!claudeCmd) {
6097
- inProgress = false;
6098
- return;
6099
- }
6100
- const helperPath = path9.join(os7.tmpdir(), "codeam-quota-helper.py");
6101
- fs6.writeFileSync(helperPath, HELPER_SCRIPT, { mode: 420 });
6102
- const python = findInPath("python3") ?? findInPath("python");
6103
- if (!python) {
6104
- inProgress = false;
6105
- return;
6106
- }
6107
- const proc = (0, import_child_process4.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
6108
- stdio: ["pipe", "pipe", "ignore"],
6109
- cwd: process.cwd(),
6110
- env: { ...process.env, TERM: "dumb", COLUMNS: "120", LINES: "30" }
6111
- });
6112
- let output = "";
6113
- proc.stdout?.on("data", (chunk) => {
6114
- output += chunk.toString("utf8");
6115
- });
6116
- setTimeout(() => {
6117
- proc.stdin?.write("/usage\r");
6118
- setTimeout(() => {
6119
- const clean = output.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, " ").replace(/\s+/g, " ");
6120
- const weekMatch = clean.match(/(\d+)%\s*used/i) || clean.match(/(\d+)\s*%/);
6121
- if (weekMatch) historySvc.setQuotaPercent(parseInt(weekMatch[1], 10));
6122
- const resetMatch = clean.match(/resets\s+(.+?)(?:\s*\(|$)/im);
6123
- if (resetMatch) historySvc.setRateLimitReset(resetMatch[1].trim());
6124
- try {
6125
- proc.kill();
6126
- } catch {
6127
- }
6128
- try {
6129
- fs6.unlinkSync(helperPath);
6130
- } catch {
6131
- }
6132
- inProgress = false;
6133
- }, 5e3);
6134
- }, 8e3);
6135
- proc.on("exit", () => {
6859
+ runtime.fetchWeeklyUsage().then((result) => {
6860
+ if (!result) return;
6861
+ historySvc.setQuotaPercent(result.percent);
6862
+ if (result.resetAt) historySvc.setRateLimitReset(result.resetAt);
6863
+ }).finally(() => {
6136
6864
  inProgress = false;
6137
6865
  });
6138
- setTimeout(() => {
6139
- try {
6140
- proc.kill();
6141
- } catch {
6142
- }
6143
- }, 2e4);
6144
6866
  }
6145
6867
 
6146
6868
  // src/commands/start/keep-alive.ts
6147
- var import_child_process5 = require("child_process");
6869
+ var import_child_process6 = require("child_process");
6148
6870
  function buildKeepAlive(ctx) {
6149
6871
  let timer = null;
6150
6872
  async function setIdleTimeout(minutes) {
6151
6873
  if (!ctx.inCodespace || !ctx.codespaceName) return;
6152
6874
  await new Promise((resolve2) => {
6153
- const proc = (0, import_child_process5.spawn)(
6875
+ const proc = (0, import_child_process6.spawn)(
6154
6876
  "gh",
6155
6877
  [
6156
6878
  "api",
@@ -6187,11 +6909,11 @@ function buildKeepAlive(ctx) {
6187
6909
  }
6188
6910
 
6189
6911
  // src/commands/start/handlers.ts
6190
- var fs9 = __toESM(require("fs"));
6191
- var os8 = __toESM(require("os"));
6192
- var path12 = __toESM(require("path"));
6912
+ var fs14 = __toESM(require("fs"));
6913
+ var os13 = __toESM(require("os"));
6914
+ var path17 = __toESM(require("path"));
6193
6915
  var import_crypto = require("crypto");
6194
- var import_child_process7 = require("child_process");
6916
+ var import_child_process8 = require("child_process");
6195
6917
 
6196
6918
  // src/lib/payload.ts
6197
6919
  var import_zod2 = require("zod");
@@ -6228,8 +6950,8 @@ function parsePayload(schema, raw) {
6228
6950
  }
6229
6951
 
6230
6952
  // src/services/file-ops.service.ts
6231
- var fs7 = __toESM(require("fs/promises"));
6232
- var path10 = __toESM(require("path"));
6953
+ var fs12 = __toESM(require("fs/promises"));
6954
+ var path15 = __toESM(require("path"));
6233
6955
  var MAX_FILE_BYTES = 5 * 1024 * 1024;
6234
6956
  var MAX_WALK_DEPTH = 6;
6235
6957
  var MAX_VISITED_DIRS = 5e3;
@@ -6264,12 +6986,12 @@ var SUBDIR_IGNORE = /* @__PURE__ */ new Set([
6264
6986
  "__pycache__"
6265
6987
  ]);
6266
6988
  function isUnder(parent, candidate) {
6267
- const rel = path10.relative(parent, candidate);
6268
- return rel === "" || !rel.startsWith("..") && !path10.isAbsolute(rel);
6989
+ const rel = path15.relative(parent, candidate);
6990
+ return rel === "" || !rel.startsWith("..") && !path15.isAbsolute(rel);
6269
6991
  }
6270
6992
  async function isExistingFile(absPath) {
6271
6993
  try {
6272
- const stat3 = await fs7.stat(absPath);
6994
+ const stat3 = await fs12.stat(absPath);
6273
6995
  return stat3.isFile();
6274
6996
  } catch {
6275
6997
  return false;
@@ -6282,13 +7004,13 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
6282
7004
  ctx.visited++;
6283
7005
  let entries = [];
6284
7006
  try {
6285
- entries = await fs7.readdir(dir, { withFileTypes: true });
7007
+ entries = await fs12.readdir(dir, { withFileTypes: true });
6286
7008
  } catch {
6287
7009
  return;
6288
7010
  }
6289
7011
  for (const e of entries) {
6290
7012
  if (!e.isFile()) continue;
6291
- const full = path10.join(dir, e.name);
7013
+ const full = path15.join(dir, e.name);
6292
7014
  if (needleVariants.some((needle) => full.endsWith(needle))) {
6293
7015
  ctx.matches.push(full);
6294
7016
  if (ctx.matches.length >= ctx.cap) return;
@@ -6298,21 +7020,21 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
6298
7020
  if (!e.isDirectory()) continue;
6299
7021
  if (SUBDIR_IGNORE.has(e.name)) continue;
6300
7022
  if (e.name.startsWith(".") && SUBDIR_IGNORE.has(e.name)) continue;
6301
- await walkForSuffix(path10.join(dir, e.name), needleVariants, depth + 1, ctx);
7023
+ await walkForSuffix(path15.join(dir, e.name), needleVariants, depth + 1, ctx);
6302
7024
  if (ctx.matches.length >= ctx.cap) return;
6303
7025
  }
6304
7026
  }
6305
7027
  async function findFile(rawPath) {
6306
7028
  const cwd = process.cwd();
6307
- if (path10.isAbsolute(rawPath)) {
6308
- const abs = path10.normalize(rawPath);
7029
+ if (path15.isAbsolute(rawPath)) {
7030
+ const abs = path15.normalize(rawPath);
6309
7031
  if (isUnder(cwd, abs) && await isExistingFile(abs)) return abs;
6310
7032
  }
6311
- const direct = path10.resolve(cwd, rawPath);
7033
+ const direct = path15.resolve(cwd, rawPath);
6312
7034
  if (isUnder(cwd, direct) && await isExistingFile(direct)) return direct;
6313
- const normalized = path10.normalize(rawPath).replace(/^[./\\]+/, "");
7035
+ const normalized = path15.normalize(rawPath).replace(/^[./\\]+/, "");
6314
7036
  const needles = [
6315
- `${path10.sep}${normalized}`,
7037
+ `${path15.sep}${normalized}`,
6316
7038
  `/${normalized}`
6317
7039
  ].filter((v, i, a) => a.indexOf(v) === i);
6318
7040
  const ctx = { visited: 0, matches: [], cap: 16 };
@@ -6326,7 +7048,7 @@ async function findWriteTarget(rawPath) {
6326
7048
  const found = await findFile(rawPath);
6327
7049
  if (found) return found;
6328
7050
  const cwd = process.cwd();
6329
- const fallback = path10.isAbsolute(rawPath) ? path10.normalize(rawPath) : path10.resolve(cwd, rawPath);
7051
+ const fallback = path15.isAbsolute(rawPath) ? path15.normalize(rawPath) : path15.resolve(cwd, rawPath);
6330
7052
  if (!isUnder(cwd, fallback)) return null;
6331
7053
  return fallback;
6332
7054
  }
@@ -6343,11 +7065,11 @@ async function readProjectFile(rawPath) {
6343
7065
  if (!abs) {
6344
7066
  return { error: `File not found in the project tree: ${rawPath}` };
6345
7067
  }
6346
- const stat3 = await fs7.stat(abs);
7068
+ const stat3 = await fs12.stat(abs);
6347
7069
  if (stat3.size > MAX_FILE_BYTES) {
6348
7070
  return { error: `File too large (${(stat3.size / 1024 / 1024).toFixed(1)} MB > ${MAX_FILE_BYTES / 1024 / 1024} MB).` };
6349
7071
  }
6350
- const buf = await fs7.readFile(abs);
7072
+ const buf = await fs12.readFile(abs);
6351
7073
  if (looksBinary(buf)) {
6352
7074
  return { error: "Binary file \u2014 refusing to open in a code editor." };
6353
7075
  }
@@ -6366,8 +7088,8 @@ async function writeProjectFile(rawPath, content) {
6366
7088
  if (Buffer.byteLength(content, "utf-8") > MAX_FILE_BYTES) {
6367
7089
  return { error: "Content too large." };
6368
7090
  }
6369
- await fs7.mkdir(path10.dirname(abs), { recursive: true });
6370
- await fs7.writeFile(abs, content, "utf-8");
7091
+ await fs12.mkdir(path15.dirname(abs), { recursive: true });
7092
+ await fs12.writeFile(abs, content, "utf-8");
6371
7093
  return { ok: true };
6372
7094
  } catch (e) {
6373
7095
  const msg = e instanceof Error ? e.message : "Write failed";
@@ -6376,11 +7098,11 @@ async function writeProjectFile(rawPath, content) {
6376
7098
  }
6377
7099
 
6378
7100
  // src/services/project-ops.service.ts
6379
- var import_child_process6 = require("child_process");
6380
- var import_util = require("util");
6381
- var fs8 = __toESM(require("fs/promises"));
6382
- var path11 = __toESM(require("path"));
6383
- var execFileP = (0, import_util.promisify)(import_child_process6.execFile);
7101
+ var import_child_process7 = require("child_process");
7102
+ var import_util2 = require("util");
7103
+ var fs13 = __toESM(require("fs/promises"));
7104
+ var path16 = __toESM(require("path"));
7105
+ var execFileP2 = (0, import_util2.promisify)(import_child_process7.execFile);
6384
7106
  var PROJECT_IGNORE = /* @__PURE__ */ new Set([
6385
7107
  "node_modules",
6386
7108
  ".git",
@@ -6427,7 +7149,7 @@ async function listProjectFiles(opts = {}) {
6427
7149
  }
6428
7150
  let entries = [];
6429
7151
  try {
6430
- entries = await fs8.readdir(dir, { withFileTypes: true });
7152
+ entries = await fs13.readdir(dir, { withFileTypes: true });
6431
7153
  } catch {
6432
7154
  return;
6433
7155
  }
@@ -6437,18 +7159,18 @@ async function listProjectFiles(opts = {}) {
6437
7159
  return;
6438
7160
  }
6439
7161
  if (PROJECT_IGNORE.has(e.name)) continue;
6440
- const full = path11.join(dir, e.name);
7162
+ const full = path16.join(dir, e.name);
6441
7163
  if (e.isDirectory()) {
6442
7164
  if (depth >= 12) continue;
6443
7165
  await walk(full, depth + 1);
6444
7166
  } else if (e.isFile()) {
6445
- const rel = path11.relative(root, full);
7167
+ const rel = path16.relative(root, full);
6446
7168
  if (q2 && !rel.toLowerCase().includes(q2) && !e.name.toLowerCase().includes(q2)) {
6447
7169
  continue;
6448
7170
  }
6449
7171
  let size = 0;
6450
7172
  try {
6451
- const st3 = await fs8.stat(full);
7173
+ const st3 = await fs13.stat(full);
6452
7174
  size = st3.size;
6453
7175
  } catch {
6454
7176
  }
@@ -6462,7 +7184,7 @@ async function listProjectFiles(opts = {}) {
6462
7184
  }
6463
7185
  async function git(args2, cwd) {
6464
7186
  try {
6465
- const { stdout, stderr } = await execFileP("git", args2, {
7187
+ const { stdout, stderr } = await execFileP2("git", args2, {
6466
7188
  cwd: cwd ?? process.cwd(),
6467
7189
  maxBuffer: MAX_GIT_OUTPUT,
6468
7190
  timeout: 3e4
@@ -6550,8 +7272,8 @@ async function gitStatus(cwd) {
6550
7272
  let hasMergeInProgress = false;
6551
7273
  try {
6552
7274
  const gitDir = (await git(["rev-parse", "--git-dir"], root)).stdout.trim();
6553
- const mergeHead = path11.isAbsolute(gitDir) ? path11.join(gitDir, "MERGE_HEAD") : path11.join(root, gitDir, "MERGE_HEAD");
6554
- await fs8.access(mergeHead);
7275
+ const mergeHead = path16.isAbsolute(gitDir) ? path16.join(gitDir, "MERGE_HEAD") : path16.join(root, gitDir, "MERGE_HEAD");
7276
+ await fs13.access(mergeHead);
6555
7277
  hasMergeInProgress = true;
6556
7278
  } catch {
6557
7279
  }
@@ -6628,8 +7350,8 @@ async function gitResolve(file, side, cwd) {
6628
7350
  function saveFilesTemp(files) {
6629
7351
  return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
6630
7352
  const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
6631
- const tmpPath = path12.join(os8.tmpdir(), `codeam-${(0, import_crypto.randomUUID)()}-${safeName}`);
6632
- fs9.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
7353
+ const tmpPath = path17.join(os13.tmpdir(), `codeam-${(0, import_crypto.randomUUID)()}-${safeName}`);
7354
+ fs14.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
6633
7355
  return tmpPath;
6634
7356
  });
6635
7357
  }
@@ -6648,7 +7370,7 @@ var startTask = (ctx, _cmd, parsed) => {
6648
7370
  setTimeout(() => {
6649
7371
  for (const p2 of paths) {
6650
7372
  try {
6651
- fs9.unlinkSync(p2);
7373
+ fs14.unlinkSync(p2);
6652
7374
  } catch {
6653
7375
  }
6654
7376
  }
@@ -6708,14 +7430,35 @@ var getConversation = async (ctx, cmd) => {
6708
7430
  }
6709
7431
  };
6710
7432
  var listModels = async (ctx, cmd) => {
6711
- const models = [
6712
- { id: "claude-opus-4-7", label: "Claude Opus 4.7", description: "Most capable", family: "claude", vendor: "anthropic", isDefault: false },
6713
- { id: "claude-opus-4-6", label: "Claude Opus 4.6", description: "Top tier", family: "claude", vendor: "anthropic", isDefault: false },
6714
- { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", description: "Balanced", family: "claude", vendor: "anthropic", isDefault: true },
6715
- { id: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", description: "Fastest", family: "claude", vendor: "anthropic", isDefault: false }
6716
- ];
7433
+ const models = await ctx.runtime.listModels();
6717
7434
  await ctx.relay.sendResult(cmd.id, "completed", { models });
6718
7435
  };
7436
+ var changeModel = async (ctx, cmd) => {
7437
+ const params = cmd.payload;
7438
+ if (typeof params.modelId !== "string" || !params.modelId) {
7439
+ await ctx.relay.sendResult(cmd.id, "failed", { error: "modelId required" });
7440
+ return;
7441
+ }
7442
+ const instr = ctx.runtime.changeModelInstruction(params.modelId);
7443
+ if (instr.type === "pty") {
7444
+ if (!instr.ptyInput) {
7445
+ await ctx.relay.sendResult(cmd.id, "failed", { error: "no pty input for this agent" });
7446
+ return;
7447
+ }
7448
+ ctx.claude.sendRawPtyInput(instr.ptyInput);
7449
+ } else if (instr.type === "restart") {
7450
+ await ctx.relay.sendResult(cmd.id, "failed", { error: "restart-mode change_model not supported in Phase 1" });
7451
+ return;
7452
+ }
7453
+ await ctx.relay.sendResult(cmd.id, "completed", {});
7454
+ };
7455
+ var summarize = async (ctx, cmd) => {
7456
+ const params = cmd.payload;
7457
+ const mode = params.mode === "auto" ? "auto" : "normal";
7458
+ const instr = ctx.runtime.summarizeInstruction(mode);
7459
+ ctx.claude.sendRawPtyInput(instr.ptyInput);
7460
+ await ctx.relay.sendResult(cmd.id, "completed", {});
7461
+ };
6719
7462
  var setKeepAlive = async (ctx, cmd) => {
6720
7463
  const enabled = !!cmd.payload.enabled;
6721
7464
  ctx.setKeepAlive(enabled);
@@ -6739,7 +7482,7 @@ var sessionTerminated = (ctx) => {
6739
7482
  } catch {
6740
7483
  }
6741
7484
  try {
6742
- const proc = (0, import_child_process7.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
7485
+ const proc = (0, import_child_process8.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
6743
7486
  detached: true,
6744
7487
  stdio: "ignore"
6745
7488
  });
@@ -6761,7 +7504,7 @@ var shutdownSession = async (ctx, cmd) => {
6761
7504
  }
6762
7505
  if (ctx.keepAliveCtx.inCodespace && ctx.keepAliveCtx.codespaceName) {
6763
7506
  try {
6764
- const stopProc = (0, import_child_process7.spawn)(
7507
+ const stopProc = (0, import_child_process8.spawn)(
6765
7508
  "bash",
6766
7509
  ["-lc", `sleep 1; gh codespace stop -c ${JSON.stringify(ctx.keepAliveCtx.codespaceName)} >/dev/null 2>&1 || true`],
6767
7510
  { detached: true, stdio: "ignore" }
@@ -6771,7 +7514,7 @@ var shutdownSession = async (ctx, cmd) => {
6771
7514
  }
6772
7515
  }
6773
7516
  try {
6774
- const proc = (0, import_child_process7.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
7517
+ const proc = (0, import_child_process8.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
6775
7518
  detached: true,
6776
7519
  stdio: "ignore"
6777
7520
  });
@@ -6852,6 +7595,8 @@ var handlers = {
6852
7595
  get_context: getContext,
6853
7596
  get_conversation: getConversation,
6854
7597
  list_models: listModels,
7598
+ change_model: changeModel,
7599
+ summarize,
6855
7600
  set_keep_alive: setKeepAlive,
6856
7601
  session_terminated: sessionTerminated,
6857
7602
  shutdown_session: shutdownSession,
@@ -6888,11 +7633,16 @@ async function start() {
6888
7633
  `);
6889
7634
  process.exit(0);
6890
7635
  }
7636
+ if (!session.agent) {
7637
+ throw new Error("Active session has no agent \u2014 re-pair with `codeam pair`.");
7638
+ }
6891
7639
  const pluginId = session.pluginId ?? ensurePluginId();
6892
7640
  showInfo(`${session.userName} \xB7 ${import_picocolors2.default.cyan(session.plan)}`);
6893
- showInfo("Launching Claude Code...\n");
7641
+ showInfo(`Launching ${AGENT_REGISTRY[session.agent].displayName}...
7642
+ `);
6894
7643
  const cwd = process.cwd();
6895
- const historySvc = new HistoryService(pluginId, cwd);
7644
+ const runtime = createRuntimeStrategy(session.agent);
7645
+ const historySvc = new HistoryService(runtime, pluginId, cwd);
6896
7646
  const keepAliveCtx = {
6897
7647
  inCodespace: process.env.CODESPACES === "true",
6898
7648
  codespaceName: process.env.CODESPACE_NAME
@@ -6904,7 +7654,7 @@ async function start() {
6904
7654
  (conversationId) => historySvc.setCurrentConversationId(conversationId),
6905
7655
  (reset) => historySvc.setRateLimitReset(reset),
6906
7656
  () => {
6907
- if (historySvc.isQuotaStale()) fetchQuotaUsage(historySvc);
7657
+ if (historySvc.isQuotaStale()) fetchQuotaUsage(runtime, historySvc);
6908
7658
  setTimeout(() => {
6909
7659
  historySvc.uploadDelta().catch(() => {
6910
7660
  });
@@ -6916,22 +7666,26 @@ async function start() {
6916
7666
  },
6917
7667
  session.pluginAuthToken
6918
7668
  );
6919
- const claude = new ClaudeService({
6920
- cwd,
6921
- onData(raw) {
6922
- outputSvc.push(raw);
6923
- },
6924
- onExit(code) {
6925
- process.removeListener("SIGINT", sigintHandler);
6926
- outputSvc.dispose();
6927
- relay.stop();
6928
- process.exit(code);
7669
+ const claude = new AgentService(
7670
+ runtime,
7671
+ {
7672
+ cwd,
7673
+ onData(raw) {
7674
+ outputSvc.push(raw);
7675
+ },
7676
+ onExit(code) {
7677
+ process.removeListener("SIGINT", sigintHandler);
7678
+ outputSvc.dispose();
7679
+ relay.stop();
7680
+ process.exit(code);
7681
+ }
6929
7682
  }
6930
- });
7683
+ );
6931
7684
  const ctx = {
6932
7685
  outputSvc,
6933
7686
  claude,
6934
7687
  historySvc,
7688
+ runtime,
6935
7689
  relay: void 0,
6936
7690
  setKeepAlive: setKeepAlive2,
6937
7691
  keepAliveCtx
@@ -6954,7 +7708,7 @@ async function start() {
6954
7708
  historySvc.load().catch(() => {
6955
7709
  });
6956
7710
  }, 2e3);
6957
- setTimeout(() => fetchQuotaUsage(historySvc), 5e3);
7711
+ setTimeout(() => fetchQuotaUsage(runtime, historySvc), 5e3);
6958
7712
  }
6959
7713
 
6960
7714
  // src/commands/pair.ts
@@ -6981,8 +7735,45 @@ async function selectSession(sessions2, activeId) {
6981
7735
  return result;
6982
7736
  }
6983
7737
 
7738
+ // src/utils/agent-prompt.ts
7739
+ function parseAgentFlag(args2) {
7740
+ const flag = args2.find((a) => a.startsWith("--agent="));
7741
+ if (!flag) return null;
7742
+ const value = flag.slice("--agent=".length);
7743
+ if (!isKnownAgentId(value)) {
7744
+ throw new Error(
7745
+ `invalid agent "${value}"; valid: ${Object.keys(AGENT_REGISTRY).join(", ")}`
7746
+ );
7747
+ }
7748
+ if (!AGENT_REGISTRY[value].enabled) {
7749
+ throw new Error(
7750
+ `${AGENT_REGISTRY[value].displayName} is not available in this codeam-cli version`
7751
+ );
7752
+ }
7753
+ return value;
7754
+ }
7755
+ async function promptForAgent(initialValue) {
7756
+ const enabled = getEnabledAgents();
7757
+ if (enabled.length === 1) {
7758
+ return enabled[0].id;
7759
+ }
7760
+ const chosen = await _t({
7761
+ message: "Pick an agent:",
7762
+ options: enabled.map((m) => ({ value: m.id, label: m.displayName })),
7763
+ initialValue: initialValue ?? enabled[0].id
7764
+ });
7765
+ if (q(chosen)) {
7766
+ pt("Cancelled.");
7767
+ process.exit(0);
7768
+ }
7769
+ return chosen;
7770
+ }
7771
+
6984
7772
  // src/commands/pair.ts
6985
- async function pair() {
7773
+ async function pair(args2 = []) {
7774
+ const config = loadCliConfig();
7775
+ const flagAgent = parseAgentFlag(args2);
7776
+ const agentId = flagAgent ?? await promptForAgent(config.preferredAgent ?? "claude");
6986
7777
  showIntro();
6987
7778
  const pluginId = (0, import_crypto2.randomUUID)();
6988
7779
  const spin = dist_exports.spinner();
@@ -7018,8 +7809,10 @@ async function pair() {
7018
7809
  userEmail: info.userEmail,
7019
7810
  plan: info.plan,
7020
7811
  pairedAt: Date.now(),
7021
- pluginAuthToken: info.pluginAuthToken
7812
+ pluginAuthToken: info.pluginAuthToken,
7813
+ agent: agentId
7022
7814
  });
7815
+ saveCliConfig({ ...loadCliConfig(), preferredAgent: agentId });
7023
7816
  showSuccess(`Paired with ${info.userName} (${info.plan})`);
7024
7817
  console.log("");
7025
7818
  resolve2();
@@ -7036,8 +7829,8 @@ async function pair() {
7036
7829
  }
7037
7830
 
7038
7831
  // src/commands/pair-auto.ts
7039
- var fs10 = __toESM(require("fs"));
7040
- var os9 = __toESM(require("os"));
7832
+ var fs15 = __toESM(require("fs"));
7833
+ var os14 = __toESM(require("os"));
7041
7834
  var import_crypto3 = require("crypto");
7042
7835
  var API_BASE5 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
7043
7836
  function fail(msg) {
@@ -7054,12 +7847,12 @@ function readTokenFromArgs(args2) {
7054
7847
  }
7055
7848
  const fileFlag = args2.find((a) => a.startsWith("--token-file="));
7056
7849
  if (fileFlag) {
7057
- const path19 = fileFlag.slice("--token-file=".length);
7850
+ const path23 = fileFlag.slice("--token-file=".length);
7058
7851
  try {
7059
- const content = fs10.readFileSync(path19, "utf8").trim();
7060
- if (content.length === 0) fail(`--token-file ${path19} is empty`);
7852
+ const content = fs15.readFileSync(path23, "utf8").trim();
7853
+ if (content.length === 0) fail(`--token-file ${path23} is empty`);
7061
7854
  try {
7062
- fs10.unlinkSync(path19);
7855
+ fs15.unlinkSync(path23);
7063
7856
  } catch {
7064
7857
  }
7065
7858
  return content;
@@ -7077,7 +7870,7 @@ async function claim(token, pluginId) {
7077
7870
  pluginId,
7078
7871
  ideName: "codeam-cli (codespace)",
7079
7872
  ideVersion: process.env.npm_package_version ?? "unknown",
7080
- hostname: os9.hostname(),
7873
+ hostname: os14.hostname(),
7081
7874
  codespaceName: process.env.CODESPACE_NAME ?? ""
7082
7875
  };
7083
7876
  const res = await fetch(url, {
@@ -7103,6 +7896,11 @@ async function pairAuto(args2) {
7103
7896
  const pluginId = (0, import_crypto3.randomUUID)();
7104
7897
  console.log(" Claiming pairing token\u2026");
7105
7898
  const claimed = await claim(token, pluginId);
7899
+ if (!isKnownAgentId(claimed.agent)) {
7900
+ fail(
7901
+ `agent "${claimed.agent}" is not supported in this codeam-cli version. Upgrade with 'npm i -g codeam-cli@latest'.`
7902
+ );
7903
+ }
7106
7904
  addSession({
7107
7905
  id: claimed.sessionId,
7108
7906
  pluginId,
@@ -7110,7 +7908,8 @@ async function pairAuto(args2) {
7110
7908
  userEmail: claimed.user.email,
7111
7909
  plan: claimed.user.plan,
7112
7910
  pairedAt: Date.now(),
7113
- pluginAuthToken: claimed.pluginAuthToken
7911
+ pluginAuthToken: claimed.pluginAuthToken,
7912
+ agent: claimed.agent
7114
7913
  });
7115
7914
  console.log(` Paired with ${claimed.user.name} (${claimed.user.plan})`);
7116
7915
  console.log(" Starting agent loop\u2026");
@@ -7217,19 +8016,14 @@ async function logout() {
7217
8016
  }
7218
8017
 
7219
8018
  // src/commands/deploy.ts
7220
- var import_child_process12 = require("child_process");
7221
- var fs11 = __toESM(require("fs"));
7222
- var os10 = __toESM(require("os"));
7223
- var path17 = __toESM(require("path"));
7224
- var import_util6 = require("util");
7225
8019
  var import_picocolors9 = __toESM(require("picocolors"));
7226
8020
 
7227
8021
  // src/services/providers/github-codespaces.ts
7228
- var import_child_process8 = require("child_process");
7229
- var import_util2 = require("util");
8022
+ var import_child_process9 = require("child_process");
8023
+ var import_util3 = require("util");
7230
8024
  var import_picocolors7 = __toESM(require("picocolors"));
7231
- var path13 = __toESM(require("path"));
7232
- var execFileP2 = (0, import_util2.promisify)(import_child_process8.execFile);
8025
+ var path18 = __toESM(require("path"));
8026
+ var execFileP3 = (0, import_util3.promisify)(import_child_process9.execFile);
7233
8027
  var MAX_BUFFER = 8 * 1024 * 1024;
7234
8028
  function resetStdinForChild() {
7235
8029
  if (process.stdin.isTTY) {
@@ -7246,11 +8040,11 @@ var GitHubCodespacesProvider = class {
7246
8040
  available = true;
7247
8041
  async authorize() {
7248
8042
  try {
7249
- await execFileP2("gh", ["--version"], { maxBuffer: MAX_BUFFER });
8043
+ await execFileP3("gh", ["--version"], { maxBuffer: MAX_BUFFER });
7250
8044
  } catch {
7251
8045
  await this.tryInstallGh();
7252
8046
  try {
7253
- await execFileP2("gh", ["--version"], { maxBuffer: MAX_BUFFER });
8047
+ await execFileP3("gh", ["--version"], { maxBuffer: MAX_BUFFER });
7254
8048
  } catch {
7255
8049
  throw new Error(
7256
8050
  [
@@ -7266,14 +8060,14 @@ var GitHubCodespacesProvider = class {
7266
8060
  }
7267
8061
  let isAuthed = false;
7268
8062
  try {
7269
- await execFileP2("gh", ["auth", "status"], { maxBuffer: MAX_BUFFER });
8063
+ await execFileP3("gh", ["auth", "status"], { maxBuffer: MAX_BUFFER });
7270
8064
  isAuthed = true;
7271
8065
  } catch {
7272
8066
  }
7273
8067
  if (!isAuthed) {
7274
8068
  resetStdinForChild();
7275
8069
  await new Promise((resolve2, reject) => {
7276
- const proc = (0, import_child_process8.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
8070
+ const proc = (0, import_child_process9.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
7277
8071
  stdio: "inherit"
7278
8072
  });
7279
8073
  proc.on("exit", (code) => {
@@ -7307,7 +8101,7 @@ var GitHubCodespacesProvider = class {
7307
8101
  wt(noteLines.join("\n"), "One more permission needed");
7308
8102
  resetStdinForChild();
7309
8103
  const refreshCode = await new Promise((resolve2, reject) => {
7310
- const proc = (0, import_child_process8.spawn)(
8104
+ const proc = (0, import_child_process9.spawn)(
7311
8105
  "gh",
7312
8106
  ["auth", "refresh", "-h", "github.com", "-s", "codespace"],
7313
8107
  { stdio: "inherit" }
@@ -7342,7 +8136,7 @@ var GitHubCodespacesProvider = class {
7342
8136
  */
7343
8137
  async getActiveGhUser() {
7344
8138
  try {
7345
- const { stdout } = await execFileP2(
8139
+ const { stdout } = await execFileP3(
7346
8140
  "gh",
7347
8141
  ["api", "user", "--jq", ".login"],
7348
8142
  { maxBuffer: MAX_BUFFER }
@@ -7362,7 +8156,7 @@ var GitHubCodespacesProvider = class {
7362
8156
  */
7363
8157
  async hasCodespaceScope() {
7364
8158
  try {
7365
- const { stdout } = await execFileP2(
8159
+ const { stdout } = await execFileP3(
7366
8160
  "gh",
7367
8161
  ["api", "-i", "user"],
7368
8162
  { maxBuffer: MAX_BUFFER }
@@ -7392,12 +8186,12 @@ var GitHubCodespacesProvider = class {
7392
8186
  * install error if it's still missing.
7393
8187
  */
7394
8188
  async tryInstallGh() {
7395
- const platform = process.platform;
8189
+ const platform2 = process.platform;
7396
8190
  wt(
7397
8191
  `GitHub CLI (${import_picocolors7.default.cyan("gh")}) is required for Codespaces deploys but isn't on your PATH.`,
7398
8192
  "Heads up"
7399
8193
  );
7400
- if (platform === "linux") {
8194
+ if (platform2 === "linux") {
7401
8195
  wt(
7402
8196
  [
7403
8197
  "On Linux, please install gh from the official guide:",
@@ -7409,9 +8203,9 @@ var GitHubCodespacesProvider = class {
7409
8203
  return;
7410
8204
  }
7411
8205
  let installCmd = null;
7412
- if (platform === "darwin") {
8206
+ if (platform2 === "darwin") {
7413
8207
  try {
7414
- await execFileP2("brew", ["--version"], { maxBuffer: MAX_BUFFER });
8208
+ await execFileP3("brew", ["--version"], { maxBuffer: MAX_BUFFER });
7415
8209
  } catch {
7416
8210
  wt(
7417
8211
  [
@@ -7428,9 +8222,9 @@ var GitHubCodespacesProvider = class {
7428
8222
  args: ["install", "gh"],
7429
8223
  describe: "brew install gh"
7430
8224
  };
7431
- } else if (platform === "win32") {
8225
+ } else if (platform2 === "win32") {
7432
8226
  try {
7433
- await execFileP2("winget", ["--version"], { maxBuffer: MAX_BUFFER });
8227
+ await execFileP3("winget", ["--version"], { maxBuffer: MAX_BUFFER });
7434
8228
  } catch {
7435
8229
  wt(
7436
8230
  [
@@ -7457,7 +8251,7 @@ var GitHubCodespacesProvider = class {
7457
8251
  O2.step(`Installing gh via ${installCmd.describe}\u2026`);
7458
8252
  resetStdinForChild();
7459
8253
  const ok = await new Promise((resolve2) => {
7460
- const proc = (0, import_child_process8.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
8254
+ const proc = (0, import_child_process9.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
7461
8255
  proc.on("exit", (code) => resolve2(code === 0));
7462
8256
  proc.on("error", () => resolve2(false));
7463
8257
  });
@@ -7484,7 +8278,7 @@ var GitHubCodespacesProvider = class {
7484
8278
  );
7485
8279
  resetStdinForChild();
7486
8280
  await new Promise((resolve2, reject) => {
7487
- const proc = (0, import_child_process8.spawn)(
8281
+ const proc = (0, import_child_process9.spawn)(
7488
8282
  "gh",
7489
8283
  ["auth", "refresh", "-h", "github.com", "-s", "repo,read:org"],
7490
8284
  { stdio: "inherit" }
@@ -7509,7 +8303,7 @@ var GitHubCodespacesProvider = class {
7509
8303
  "200"
7510
8304
  );
7511
8305
  try {
7512
- const { stdout } = await execFileP2("gh", args2, { maxBuffer: MAX_BUFFER });
8306
+ const { stdout } = await execFileP3("gh", args2, { maxBuffer: MAX_BUFFER });
7513
8307
  return JSON.parse(stdout);
7514
8308
  } catch {
7515
8309
  return [];
@@ -7518,7 +8312,7 @@ var GitHubCodespacesProvider = class {
7518
8312
  const own = await fetchRepos();
7519
8313
  let orgRepos = [];
7520
8314
  try {
7521
- const { stdout } = await execFileP2(
8315
+ const { stdout } = await execFileP3(
7522
8316
  "gh",
7523
8317
  ["api", "--paginate", "user/orgs", "--jq", ".[].login"],
7524
8318
  { maxBuffer: MAX_BUFFER }
@@ -7555,7 +8349,7 @@ var GitHubCodespacesProvider = class {
7555
8349
  */
7556
8350
  async listMachineTypes(projectId) {
7557
8351
  try {
7558
- const { stdout } = await execFileP2(
8352
+ const { stdout } = await execFileP3(
7559
8353
  "gh",
7560
8354
  ["api", `/repos/${projectId}/codespaces/machines`],
7561
8355
  { maxBuffer: MAX_BUFFER }
@@ -7586,7 +8380,7 @@ var GitHubCodespacesProvider = class {
7586
8380
  const machine = machineTypeId ?? await this.pickDefaultMachine(projectId);
7587
8381
  const args2 = ["codespace", "create", "-R", projectId, "--default-permissions"];
7588
8382
  if (machine) args2.push("-m", machine);
7589
- const { stdout } = await execFileP2(
8383
+ const { stdout } = await execFileP3(
7590
8384
  "gh",
7591
8385
  args2,
7592
8386
  { maxBuffer: MAX_BUFFER, timeout: 12e4 }
@@ -7626,7 +8420,7 @@ var GitHubCodespacesProvider = class {
7626
8420
  async waitUntilAvailable(name) {
7627
8421
  const deadline = Date.now() + 5 * 60 * 1e3;
7628
8422
  while (Date.now() < deadline) {
7629
- const { stdout } = await execFileP2(
8423
+ const { stdout } = await execFileP3(
7630
8424
  "gh",
7631
8425
  ["codespace", "list", "--json", "name,state"],
7632
8426
  { maxBuffer: MAX_BUFFER }
@@ -7644,7 +8438,7 @@ var GitHubCodespacesProvider = class {
7644
8438
  }
7645
8439
  async exec(workspaceId, command2) {
7646
8440
  try {
7647
- const { stdout, stderr } = await execFileP2(
8441
+ const { stdout, stderr } = await execFileP3(
7648
8442
  "gh",
7649
8443
  ["codespace", "ssh", "-c", workspaceId, "--", command2],
7650
8444
  { maxBuffer: MAX_BUFFER, timeout: 6e5 }
@@ -7662,7 +8456,7 @@ var GitHubCodespacesProvider = class {
7662
8456
  async streamCommand(workspaceId, command2) {
7663
8457
  resetStdinForChild();
7664
8458
  return new Promise((resolve2, reject) => {
7665
- const proc = (0, import_child_process8.spawn)(
8459
+ const proc = (0, import_child_process9.spawn)(
7666
8460
  "gh",
7667
8461
  ["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
7668
8462
  { stdio: "inherit" }
@@ -7689,11 +8483,11 @@ var GitHubCodespacesProvider = class {
7689
8483
  `mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
7690
8484
  ];
7691
8485
  await new Promise((resolve2, reject) => {
7692
- const tar = (0, import_child_process8.spawn)("tar", tarArgs, {
8486
+ const tar = (0, import_child_process9.spawn)("tar", tarArgs, {
7693
8487
  stdio: ["ignore", "pipe", "pipe"],
7694
8488
  env: tarEnv
7695
8489
  });
7696
- const ssh = (0, import_child_process8.spawn)("gh", sshArgs, {
8490
+ const ssh = (0, import_child_process9.spawn)("gh", sshArgs, {
7697
8491
  stdio: [tar.stdout, "pipe", "pipe"]
7698
8492
  });
7699
8493
  let tarErr = "";
@@ -7717,7 +8511,7 @@ var GitHubCodespacesProvider = class {
7717
8511
  });
7718
8512
  }
7719
8513
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
7720
- const remoteDir = path13.posix.dirname(remotePath);
8514
+ const remoteDir = path18.posix.dirname(remotePath);
7721
8515
  const parts = [
7722
8516
  `mkdir -p ${shellQuote(remoteDir)}`,
7723
8517
  `cat > ${shellQuote(remotePath)}`
@@ -7727,7 +8521,7 @@ var GitHubCodespacesProvider = class {
7727
8521
  }
7728
8522
  const cmd = parts.join(" && ");
7729
8523
  await new Promise((resolve2, reject) => {
7730
- const proc = (0, import_child_process8.spawn)(
8524
+ const proc = (0, import_child_process9.spawn)(
7731
8525
  "gh",
7732
8526
  ["codespace", "ssh", "-c", workspaceId, "--", cmd],
7733
8527
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -7749,7 +8543,7 @@ var GitHubCodespacesProvider = class {
7749
8543
  try {
7750
8544
  const args2 = ["codespace", "list", "--json", "name,displayName,state,lastUsedAt"];
7751
8545
  if (projectId) args2.push("--repo", projectId);
7752
- const { stdout } = await execFileP2("gh", args2, { maxBuffer: MAX_BUFFER });
8546
+ const { stdout } = await execFileP3("gh", args2, { maxBuffer: MAX_BUFFER });
7753
8547
  const list = JSON.parse(stdout);
7754
8548
  return list.map((c2) => ({
7755
8549
  id: c2.name,
@@ -7764,7 +8558,7 @@ var GitHubCodespacesProvider = class {
7764
8558
  }
7765
8559
  async startWorkspace(workspaceId) {
7766
8560
  try {
7767
- await execFileP2(
8561
+ await execFileP3(
7768
8562
  "gh",
7769
8563
  ["api", "-X", "POST", `/user/codespaces/${workspaceId}/start`],
7770
8564
  { maxBuffer: MAX_BUFFER, timeout: 6e4 }
@@ -7785,11 +8579,11 @@ function shellQuote(s) {
7785
8579
  }
7786
8580
 
7787
8581
  // src/services/providers/gitpod.ts
7788
- var import_child_process9 = require("child_process");
7789
- var import_util3 = require("util");
7790
- var path14 = __toESM(require("path"));
8582
+ var import_child_process10 = require("child_process");
8583
+ var import_util4 = require("util");
8584
+ var path19 = __toESM(require("path"));
7791
8585
  var import_picocolors8 = __toESM(require("picocolors"));
7792
- var execFileP3 = (0, import_util3.promisify)(import_child_process9.execFile);
8586
+ var execFileP4 = (0, import_util4.promisify)(import_child_process10.execFile);
7793
8587
  var MAX_BUFFER2 = 8 * 1024 * 1024;
7794
8588
  function resetStdinForChild2() {
7795
8589
  if (process.stdin.isTTY) {
@@ -7806,7 +8600,7 @@ var GitpodProvider = class {
7806
8600
  available = true;
7807
8601
  async authorize() {
7808
8602
  try {
7809
- await execFileP3("gitpod", ["--version"], { maxBuffer: MAX_BUFFER2 });
8603
+ await execFileP4("gitpod", ["--version"], { maxBuffer: MAX_BUFFER2 });
7810
8604
  } catch {
7811
8605
  throw new Error(
7812
8606
  [
@@ -7819,7 +8613,7 @@ var GitpodProvider = class {
7819
8613
  );
7820
8614
  }
7821
8615
  try {
7822
- await execFileP3("gitpod", ["whoami"], { maxBuffer: MAX_BUFFER2 });
8616
+ await execFileP4("gitpod", ["whoami"], { maxBuffer: MAX_BUFFER2 });
7823
8617
  return;
7824
8618
  } catch {
7825
8619
  }
@@ -7829,7 +8623,7 @@ var GitpodProvider = class {
7829
8623
  );
7830
8624
  resetStdinForChild2();
7831
8625
  await new Promise((resolve2, reject) => {
7832
- const proc = (0, import_child_process9.spawn)("gitpod", ["login"], { stdio: "inherit" });
8626
+ const proc = (0, import_child_process10.spawn)("gitpod", ["login"], { stdio: "inherit" });
7833
8627
  proc.on("exit", (code) => {
7834
8628
  if (code === 0) resolve2();
7835
8629
  else reject(new Error("gitpod login failed."));
@@ -7839,7 +8633,7 @@ var GitpodProvider = class {
7839
8633
  }
7840
8634
  async listProjects() {
7841
8635
  try {
7842
- const { stdout } = await execFileP3(
8636
+ const { stdout } = await execFileP4(
7843
8637
  "gitpod",
7844
8638
  ["workspace", "list", "--output", "json", "--limit", "200"],
7845
8639
  { maxBuffer: MAX_BUFFER2 }
@@ -7872,7 +8666,7 @@ var GitpodProvider = class {
7872
8666
  async createWorkspace(projectId, machineTypeId) {
7873
8667
  const args2 = ["workspace", "create", projectId, "--start", "--output", "json"];
7874
8668
  if (machineTypeId) args2.push("--class", machineTypeId);
7875
- const { stdout } = await execFileP3("gitpod", args2, {
8669
+ const { stdout } = await execFileP4("gitpod", args2, {
7876
8670
  maxBuffer: MAX_BUFFER2,
7877
8671
  timeout: 3e5
7878
8672
  });
@@ -7896,7 +8690,7 @@ var GitpodProvider = class {
7896
8690
  const deadline = Date.now() + 5 * 60 * 1e3;
7897
8691
  while (Date.now() < deadline) {
7898
8692
  try {
7899
- const { stdout } = await execFileP3(
8693
+ const { stdout } = await execFileP4(
7900
8694
  "gitpod",
7901
8695
  ["workspace", "get", workspaceId, "--output", "json"],
7902
8696
  { maxBuffer: MAX_BUFFER2 }
@@ -7914,7 +8708,7 @@ var GitpodProvider = class {
7914
8708
  }
7915
8709
  async listMachineTypes(_projectId) {
7916
8710
  try {
7917
- const { stdout } = await execFileP3(
8711
+ const { stdout } = await execFileP4(
7918
8712
  "gitpod",
7919
8713
  ["organization", "list-classes", "--output", "json"],
7920
8714
  { maxBuffer: MAX_BUFFER2 }
@@ -7932,7 +8726,7 @@ var GitpodProvider = class {
7932
8726
  async listExistingWorkspaces(projectId) {
7933
8727
  try {
7934
8728
  const args2 = ["workspace", "list", "--output", "json", "--limit", "200"];
7935
- const { stdout } = await execFileP3("gitpod", args2, { maxBuffer: MAX_BUFFER2 });
8729
+ const { stdout } = await execFileP4("gitpod", args2, { maxBuffer: MAX_BUFFER2 });
7936
8730
  const list = JSON.parse(stdout);
7937
8731
  return list.filter((w3) => !projectId || w3.contextUrl === projectId).map((w3) => ({
7938
8732
  id: w3.id,
@@ -7947,7 +8741,7 @@ var GitpodProvider = class {
7947
8741
  }
7948
8742
  async startWorkspace(workspaceId) {
7949
8743
  try {
7950
- await execFileP3(
8744
+ await execFileP4(
7951
8745
  "gitpod",
7952
8746
  ["workspace", "start", workspaceId],
7953
8747
  { maxBuffer: MAX_BUFFER2, timeout: 6e4 }
@@ -7963,7 +8757,7 @@ var GitpodProvider = class {
7963
8757
  }
7964
8758
  async exec(workspaceId, command2) {
7965
8759
  try {
7966
- const { stdout, stderr } = await execFileP3(
8760
+ const { stdout, stderr } = await execFileP4(
7967
8761
  "gitpod",
7968
8762
  ["workspace", "ssh", workspaceId, "--", command2],
7969
8763
  { maxBuffer: MAX_BUFFER2, timeout: 6e5 }
@@ -7981,7 +8775,7 @@ var GitpodProvider = class {
7981
8775
  async streamCommand(workspaceId, command2) {
7982
8776
  resetStdinForChild2();
7983
8777
  return new Promise((resolve2, reject) => {
7984
- const proc = (0, import_child_process9.spawn)(
8778
+ const proc = (0, import_child_process10.spawn)(
7985
8779
  "gitpod",
7986
8780
  ["workspace", "ssh", workspaceId, "--", "-tt", command2],
7987
8781
  { stdio: "inherit" }
@@ -8001,11 +8795,11 @@ var GitpodProvider = class {
8001
8795
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
8002
8796
  const remoteCmd = `mkdir -p ${shellQuote2(remoteDir)} && tar -xzf - -C ${shellQuote2(remoteDir)}`;
8003
8797
  await new Promise((resolve2, reject) => {
8004
- const tar = (0, import_child_process9.spawn)("tar", tarArgs, {
8798
+ const tar = (0, import_child_process10.spawn)("tar", tarArgs, {
8005
8799
  stdio: ["ignore", "pipe", "pipe"],
8006
8800
  env: tarEnv
8007
8801
  });
8008
- const ssh = (0, import_child_process9.spawn)(
8802
+ const ssh = (0, import_child_process10.spawn)(
8009
8803
  "gitpod",
8010
8804
  ["workspace", "ssh", workspaceId, "--", remoteCmd],
8011
8805
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -8027,7 +8821,7 @@ var GitpodProvider = class {
8027
8821
  });
8028
8822
  }
8029
8823
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
8030
- const remoteDir = path14.posix.dirname(remotePath);
8824
+ const remoteDir = path19.posix.dirname(remotePath);
8031
8825
  const parts = [
8032
8826
  `mkdir -p ${shellQuote2(remoteDir)}`,
8033
8827
  `cat > ${shellQuote2(remotePath)}`
@@ -8037,7 +8831,7 @@ var GitpodProvider = class {
8037
8831
  }
8038
8832
  const cmd = parts.join(" && ");
8039
8833
  await new Promise((resolve2, reject) => {
8040
- const proc = (0, import_child_process9.spawn)(
8834
+ const proc = (0, import_child_process10.spawn)(
8041
8835
  "gitpod",
8042
8836
  ["workspace", "ssh", workspaceId, "--", cmd],
8043
8837
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8061,10 +8855,10 @@ function shellQuote2(s) {
8061
8855
  }
8062
8856
 
8063
8857
  // src/services/providers/gitlab-workspaces.ts
8064
- var import_child_process10 = require("child_process");
8065
- var import_util4 = require("util");
8066
- var path15 = __toESM(require("path"));
8067
- var execFileP4 = (0, import_util4.promisify)(import_child_process10.execFile);
8858
+ var import_child_process11 = require("child_process");
8859
+ var import_util5 = require("util");
8860
+ var path20 = __toESM(require("path"));
8861
+ var execFileP5 = (0, import_util5.promisify)(import_child_process11.execFile);
8068
8862
  var MAX_BUFFER3 = 8 * 1024 * 1024;
8069
8863
  var GITLAB_API_BASE = process.env.CODEAM_GITLAB_API_URL ?? "https://gitlab.com/api/v4";
8070
8864
  function resetStdinForChild3() {
@@ -8082,7 +8876,7 @@ var GitLabWorkspacesProvider = class {
8082
8876
  available = true;
8083
8877
  async authorize() {
8084
8878
  try {
8085
- await execFileP4("glab", ["--version"], { maxBuffer: MAX_BUFFER3 });
8879
+ await execFileP5("glab", ["--version"], { maxBuffer: MAX_BUFFER3 });
8086
8880
  } catch {
8087
8881
  throw new Error(
8088
8882
  [
@@ -8096,7 +8890,7 @@ var GitLabWorkspacesProvider = class {
8096
8890
  );
8097
8891
  }
8098
8892
  try {
8099
- await execFileP4("glab", ["auth", "status"], { maxBuffer: MAX_BUFFER3 });
8893
+ await execFileP5("glab", ["auth", "status"], { maxBuffer: MAX_BUFFER3 });
8100
8894
  return;
8101
8895
  } catch {
8102
8896
  }
@@ -8106,7 +8900,7 @@ var GitLabWorkspacesProvider = class {
8106
8900
  );
8107
8901
  resetStdinForChild3();
8108
8902
  await new Promise((resolve2, reject) => {
8109
- const proc = (0, import_child_process10.spawn)(
8903
+ const proc = (0, import_child_process11.spawn)(
8110
8904
  "glab",
8111
8905
  ["auth", "login", "--scopes", "api,read_user,read_repository"],
8112
8906
  { stdio: "inherit" }
@@ -8120,7 +8914,7 @@ var GitLabWorkspacesProvider = class {
8120
8914
  }
8121
8915
  async listProjects() {
8122
8916
  try {
8123
- const { stdout } = await execFileP4(
8917
+ const { stdout } = await execFileP5(
8124
8918
  "glab",
8125
8919
  ["repo", "list", "--per-page", "200", "--output", "json"],
8126
8920
  { maxBuffer: MAX_BUFFER3 }
@@ -8252,7 +9046,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8252
9046
  async exec(workspaceId, command2) {
8253
9047
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
8254
9048
  try {
8255
- const { stdout, stderr } = await execFileP4(
9049
+ const { stdout, stderr } = await execFileP5(
8256
9050
  "ssh",
8257
9051
  [
8258
9052
  "-o",
@@ -8278,7 +9072,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8278
9072
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
8279
9073
  resetStdinForChild3();
8280
9074
  return new Promise((resolve2, reject) => {
8281
- const proc = (0, import_child_process10.spawn)(
9075
+ const proc = (0, import_child_process11.spawn)(
8282
9076
  "ssh",
8283
9077
  ["-tt", "-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, command2],
8284
9078
  { stdio: "inherit" }
@@ -8299,8 +9093,8 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8299
9093
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
8300
9094
  const remoteCmd = `mkdir -p ${shellQuote3(remoteDir)} && tar -xzf - -C ${shellQuote3(remoteDir)}`;
8301
9095
  await new Promise((resolve2, reject) => {
8302
- const tar = (0, import_child_process10.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
8303
- const ssh = (0, import_child_process10.spawn)(
9096
+ const tar = (0, import_child_process11.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
9097
+ const ssh = (0, import_child_process11.spawn)(
8304
9098
  "ssh",
8305
9099
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, remoteCmd],
8306
9100
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -8323,14 +9117,14 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8323
9117
  }
8324
9118
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
8325
9119
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
8326
- const remoteDir = path15.posix.dirname(remotePath);
9120
+ const remoteDir = path20.posix.dirname(remotePath);
8327
9121
  const parts = [`mkdir -p ${shellQuote3(remoteDir)}`, `cat > ${shellQuote3(remotePath)}`];
8328
9122
  if (options.mode != null) {
8329
9123
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote3(remotePath)}`);
8330
9124
  }
8331
9125
  const cmd = parts.join(" && ");
8332
9126
  await new Promise((resolve2, reject) => {
8333
- const proc = (0, import_child_process10.spawn)(
9127
+ const proc = (0, import_child_process11.spawn)(
8334
9128
  "ssh",
8335
9129
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, cmd],
8336
9130
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8355,7 +9149,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8355
9149
  */
8356
9150
  async getGlabToken() {
8357
9151
  try {
8358
- const { stdout, stderr } = await execFileP4(
9152
+ const { stdout, stderr } = await execFileP5(
8359
9153
  "glab",
8360
9154
  ["auth", "status", "--show-token"],
8361
9155
  { maxBuffer: MAX_BUFFER3 }
@@ -8389,10 +9183,10 @@ function shellQuote3(s) {
8389
9183
  }
8390
9184
 
8391
9185
  // src/services/providers/railway.ts
8392
- var import_child_process11 = require("child_process");
8393
- var import_util5 = require("util");
8394
- var path16 = __toESM(require("path"));
8395
- var execFileP5 = (0, import_util5.promisify)(import_child_process11.execFile);
9186
+ var import_child_process12 = require("child_process");
9187
+ var import_util6 = require("util");
9188
+ var path21 = __toESM(require("path"));
9189
+ var execFileP6 = (0, import_util6.promisify)(import_child_process12.execFile);
8396
9190
  var MAX_BUFFER4 = 8 * 1024 * 1024;
8397
9191
  function resetStdinForChild4() {
8398
9192
  if (process.stdin.isTTY) {
@@ -8409,7 +9203,7 @@ var RailwayProvider = class {
8409
9203
  available = true;
8410
9204
  async authorize() {
8411
9205
  try {
8412
- await execFileP5("railway", ["--version"], { maxBuffer: MAX_BUFFER4 });
9206
+ await execFileP6("railway", ["--version"], { maxBuffer: MAX_BUFFER4 });
8413
9207
  } catch {
8414
9208
  throw new Error(
8415
9209
  [
@@ -8423,7 +9217,7 @@ var RailwayProvider = class {
8423
9217
  );
8424
9218
  }
8425
9219
  try {
8426
- await execFileP5("railway", ["whoami"], { maxBuffer: MAX_BUFFER4 });
9220
+ await execFileP6("railway", ["whoami"], { maxBuffer: MAX_BUFFER4 });
8427
9221
  return;
8428
9222
  } catch {
8429
9223
  }
@@ -8433,7 +9227,7 @@ var RailwayProvider = class {
8433
9227
  );
8434
9228
  resetStdinForChild4();
8435
9229
  await new Promise((resolve2, reject) => {
8436
- const proc = (0, import_child_process11.spawn)("railway", ["login"], { stdio: "inherit" });
9230
+ const proc = (0, import_child_process12.spawn)("railway", ["login"], { stdio: "inherit" });
8437
9231
  proc.on("exit", (code) => {
8438
9232
  if (code === 0) resolve2();
8439
9233
  else reject(new Error("railway login failed."));
@@ -8443,7 +9237,7 @@ var RailwayProvider = class {
8443
9237
  }
8444
9238
  async listProjects() {
8445
9239
  try {
8446
- const { stdout } = await execFileP5(
9240
+ const { stdout } = await execFileP6(
8447
9241
  "railway",
8448
9242
  ["list", "--json"],
8449
9243
  { maxBuffer: MAX_BUFFER4 }
@@ -8459,7 +9253,7 @@ var RailwayProvider = class {
8459
9253
  }));
8460
9254
  } catch {
8461
9255
  try {
8462
- const { stdout } = await execFileP5("railway", ["list"], { maxBuffer: MAX_BUFFER4 });
9256
+ const { stdout } = await execFileP6("railway", ["list"], { maxBuffer: MAX_BUFFER4 });
8463
9257
  const projects = [];
8464
9258
  for (const line of stdout.split("\n")) {
8465
9259
  const trimmed = line.trim();
@@ -8510,7 +9304,7 @@ var RailwayProvider = class {
8510
9304
  async listExistingWorkspaces(projectId) {
8511
9305
  if (!projectId) return [];
8512
9306
  try {
8513
- const { stdout } = await execFileP5(
9307
+ const { stdout } = await execFileP6(
8514
9308
  "railway",
8515
9309
  ["service", "list", "--project", projectId, "--json"],
8516
9310
  { maxBuffer: MAX_BUFFER4 }
@@ -8535,7 +9329,7 @@ var RailwayProvider = class {
8535
9329
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
8536
9330
  }
8537
9331
  try {
8538
- await execFileP5(
9332
+ await execFileP6(
8539
9333
  "railway",
8540
9334
  ["service", "restart", "--service", serviceId, "--project", projectId],
8541
9335
  { maxBuffer: MAX_BUFFER4, timeout: 6e4 }
@@ -8554,7 +9348,7 @@ var RailwayProvider = class {
8554
9348
  };
8555
9349
  }
8556
9350
  try {
8557
- const { stdout, stderr } = await execFileP5(
9351
+ const { stdout, stderr } = await execFileP6(
8558
9352
  "railway",
8559
9353
  ["run", "--project", projectId, "--service", serviceId, "--", "bash", "-lc", command2],
8560
9354
  { maxBuffer: MAX_BUFFER4, timeout: 6e5 }
@@ -8576,7 +9370,7 @@ var RailwayProvider = class {
8576
9370
  }
8577
9371
  resetStdinForChild4();
8578
9372
  return new Promise((resolve2, reject) => {
8579
- const proc = (0, import_child_process11.spawn)(
9373
+ const proc = (0, import_child_process12.spawn)(
8580
9374
  "railway",
8581
9375
  ["shell", "--project", projectId, "--service", serviceId, "--command", command2],
8582
9376
  { stdio: "inherit" }
@@ -8600,8 +9394,8 @@ var RailwayProvider = class {
8600
9394
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
8601
9395
  const remoteCmd = `mkdir -p ${shellQuote4(remoteDir)} && tar -xzf - -C ${shellQuote4(remoteDir)}`;
8602
9396
  await new Promise((resolve2, reject) => {
8603
- const tar = (0, import_child_process11.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
8604
- const sh = (0, import_child_process11.spawn)(
9397
+ const tar = (0, import_child_process12.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
9398
+ const sh = (0, import_child_process12.spawn)(
8605
9399
  "railway",
8606
9400
  ["shell", "--project", projectId, "--service", serviceId, "--command", remoteCmd],
8607
9401
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -8627,14 +9421,14 @@ var RailwayProvider = class {
8627
9421
  if (!projectId || !serviceId) {
8628
9422
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
8629
9423
  }
8630
- const remoteDir = path16.posix.dirname(remotePath);
9424
+ const remoteDir = path21.posix.dirname(remotePath);
8631
9425
  const parts = [`mkdir -p ${shellQuote4(remoteDir)}`, `cat > ${shellQuote4(remotePath)}`];
8632
9426
  if (options.mode != null) {
8633
9427
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote4(remotePath)}`);
8634
9428
  }
8635
9429
  const cmd = parts.join(" && ");
8636
9430
  await new Promise((resolve2, reject) => {
8637
- const proc = (0, import_child_process11.spawn)(
9431
+ const proc = (0, import_child_process12.spawn)(
8638
9432
  "railway",
8639
9433
  ["shell", "--project", projectId, "--service", serviceId, "--command", cmd],
8640
9434
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8666,8 +9460,7 @@ var PROVIDERS = [
8666
9460
  ];
8667
9461
 
8668
9462
  // src/commands/deploy.ts
8669
- var execFileP6 = (0, import_util6.promisify)(import_child_process12.execFile);
8670
- async function deploy() {
9463
+ async function deploy(args2 = []) {
8671
9464
  console.log();
8672
9465
  mt(import_picocolors9.default.bgMagenta(import_picocolors9.default.white(" codeam deploy ")));
8673
9466
  const provider = await pickProvider();
@@ -8819,13 +9612,14 @@ async function deploy() {
8819
9612
  process.exit(1);
8820
9613
  }
8821
9614
  }
8822
- const localClaudeDir = path17.join(os10.homedir(), ".claude");
8823
- const localCredsKind = await detectLocalClaudeCredentials(localClaudeDir);
9615
+ const cfg = loadCliConfig();
9616
+ const agentId = parseAgentFlag(args2) ?? await promptForAgent(cfg.preferredAgent ?? "claude");
9617
+ const strategy = createDeployStrategy(agentId);
9618
+ const localCreds = await strategy.detectLocalCredentials();
8824
9619
  let bridged = "none";
8825
- if (localCredsKind !== "none") {
8826
- const sourceLabel = localCredsKind === "flat-file" ? "~/.claude/.credentials.json" : "macOS Keychain";
9620
+ if (localCreds.source !== "none") {
8827
9621
  const useLocal = await ot2({
8828
- message: `Copy your local Claude credentials (${sourceLabel}) to the workspace?`,
9622
+ message: `Copy your local ${AGENT_REGISTRY[agentId].displayName} credentials (${localCreds.description}) to the workspace?`,
8829
9623
  active: "Yes \u2014 same account, no re-auth",
8830
9624
  inactive: "No \u2014 log in with a different account",
8831
9625
  initialValue: true
@@ -8836,118 +9630,13 @@ async function deploy() {
8836
9630
  }
8837
9631
  if (useLocal) {
8838
9632
  const credStep = fe();
8839
- credStep.start("Bridging Claude credentials\u2026");
8840
- bridged = await bridgeClaudeCredentials(provider, workspace.id, localClaudeDir);
8841
- switch (bridged) {
8842
- case "flat-file":
8843
- credStep.stop("\u2713 Local credentials staged");
8844
- break;
8845
- case "macos-keychain":
8846
- credStep.stop("\u2713 Credentials extracted from macOS Keychain and staged");
8847
- break;
8848
- case "none":
8849
- credStep.stop("\u26A0 Could not extract local credentials \u2014 falling back to remote login");
8850
- break;
8851
- }
8852
- }
8853
- }
8854
- const claudeStep = fe();
8855
- claudeStep.start("Installing Claude CLI on workspace\u2026");
8856
- const installResult = await provider.exec(
8857
- workspace.id,
8858
- "curl -fsSL https://claude.ai/install.sh | bash"
8859
- );
8860
- if (installResult.code !== 0) {
8861
- claudeStep.stop("\u2717 Claude CLI install failed");
8862
- pt(installResult.stderr.slice(0, 1e3));
8863
- process.exit(1);
8864
- }
8865
- claudeStep.stop("\u2713 Claude CLI installed");
8866
- const haveLocalClaude = fs11.existsSync(localClaudeDir) && fs11.statSync(localClaudeDir).isDirectory();
8867
- if (haveLocalClaude) {
8868
- const copyStep = fe();
8869
- copyStep.start("Copying local Claude config to workspace\u2026");
8870
- try {
8871
- await provider.uploadDirectory(
8872
- workspace.id,
8873
- localClaudeDir,
8874
- "/home/codespace/.claude",
8875
- {
8876
- exclude: [
8877
- "./projects",
8878
- // per-project conversation history (often 700MB+)
8879
- "./file-history",
8880
- // per-project file diffs
8881
- "./downloads",
8882
- // downloaded artifacts
8883
- "./image-cache",
8884
- // cached images
8885
- "./paste-cache",
8886
- // clipboard/paste cache
8887
- "./backups",
8888
- // local backups
8889
- "./shell-snapshots",
8890
- // shell history snapshots
8891
- "./telemetry",
8892
- // analytics dumps
8893
- "./statsig",
8894
- // feature-flag cache
8895
- "./cache",
8896
- // generic cache dir
8897
- "./history.jsonl",
8898
- // global REPL history
8899
- "./ide",
8900
- // local IDE bridge state
8901
- "./todos",
8902
- // local todo state
8903
- "./tasks",
8904
- // local task state
8905
- // Don't overwrite the credentials we already staged in
8906
- // step 4 — the local dir on macOS doesn't have a flat
8907
- // credentials file anyway, but on Linux it would, and a
8908
- // re-write here would be redundant.
8909
- "./.credentials.json"
8910
- ]
8911
- }
8912
- );
8913
- copyStep.stop("\u2713 Claude config uploaded");
8914
- } catch (err) {
8915
- copyStep.stop("\u26A0 Could not upload Claude config (continuing)");
8916
- void err;
8917
- }
8918
- }
8919
- if (bridged !== "none") {
8920
- const localClaudeJson = path17.join(os10.homedir(), ".claude.json");
8921
- if (fs11.existsSync(localClaudeJson)) {
8922
- try {
8923
- const contents = fs11.readFileSync(localClaudeJson);
8924
- await provider.uploadFile(
8925
- workspace.id,
8926
- "/home/codespace/.claude.json",
8927
- contents,
8928
- { mode: 384 }
8929
- );
8930
- } catch (err) {
8931
- void err;
8932
- }
8933
- }
8934
- }
8935
- const verifyStep = fe();
8936
- verifyStep.start("Verifying Claude auth on workspace\u2026");
8937
- const verified = await verifyClaudeAuth(provider, workspace.id);
8938
- if (verified) {
8939
- verifyStep.stop("\u2713 Claude is logged in \u2014 no re-auth needed");
8940
- } else {
8941
- verifyStep.stop("\xB7 Claude not yet authenticated \u2014 running login flow");
8942
- await runRemoteClaudeLogin(provider, workspace.id);
8943
- const reverified = await verifyClaudeAuth(provider, workspace.id);
8944
- if (!reverified) {
8945
- wt(
8946
- "Claude auth could not be confirmed. You may need to run `claude /login` manually inside the codespace.",
8947
- "Heads up"
8948
- );
9633
+ credStep.start(`Bridging ${AGENT_REGISTRY[agentId].displayName} credentials\u2026`);
9634
+ const result = await strategy.bridgeLocalCredentials(provider, workspace.id);
9635
+ bridged = result.source;
9636
+ credStep.stop(`\u2713 Credentials staged (${bridged})`);
8949
9637
  }
8950
9638
  }
9639
+ await strategy.setupOnWorkspace(provider, workspace.id, { bridged });
8951
9640
  const cliStep = fe();
8952
9641
  cliStep.start("Installing codeam-cli on workspace\u2026");
8953
9642
  const cliInstall = await provider.exec(workspace.id, "npm install -g codeam-cli@latest");
@@ -8962,8 +9651,8 @@ async function deploy() {
8962
9651
  `Workspace: ${import_picocolors9.default.cyan(workspace.displayName ?? workspace.id)}`,
8963
9652
  workspace.webUrl ? `Web: ${import_picocolors9.default.cyan(workspace.webUrl)}` : "",
8964
9653
  "",
8965
- "Starting `codeam pair` on the workspace.",
8966
- "Scan the QR code below with the CodeAgent Mobile app to finish pairing.",
9654
+ `Starting \`codeam pair\` on the workspace (agent: ${AGENT_REGISTRY[agentId].displayName}).`,
9655
+ "Scan the QR code from your phone to pair.",
8967
9656
  import_picocolors9.default.dim("(Once paired, this terminal disconnects automatically; the session stays alive on the codespace.)")
8968
9657
  ].filter(Boolean).join("\n"),
8969
9658
  "Almost there"
@@ -8973,10 +9662,9 @@ async function deploy() {
8973
9662
  "LOG=~/.codeam-deploy/session.log",
8974
9663
  ': > "$LOG"',
8975
9664
  // The default `gh codespace ssh` cwd is the repo root
8976
- // (/workspaces/<repo>), which is exactly where Claude needs to
9665
+ // (/workspaces/<repo>), which is exactly where the agent needs to
8977
9666
  // run so it can read/edit project files. Pass that to PM2 via
8978
- // --cwd so the relay's child Claude inherits the right
8979
- // working directory.
9667
+ // --cwd so the relay's child agent inherits the right directory.
8980
9668
  'PROJECT_DIR="$(pwd)"',
8981
9669
  // Install PM2 if it isn't already on PATH. Idempotent.
8982
9670
  "if ! command -v pm2 >/dev/null 2>&1; then",
@@ -8988,11 +9676,11 @@ async function deploy() {
8988
9676
  // Start codeam pair under PM2. `--merge-logs` writes stdout
8989
9677
  // and stderr to the same file so we only need one tail.
8990
9678
  // --max-restarts 3 keeps PM2 from looping forever if codeam pair
8991
- // can't start (e.g. backend unreachable) — three attempts is
8992
- // enough for transient flakes, anything more wastes time.
8993
- // No `--time` (would prefix every line with a timestamp and
8994
- // break the QR rendering); no `--no-pmx` either (default off).
8995
- 'pm2 start codeam --name codeam-pair --cwd "$PROJECT_DIR" --max-restarts 3 -o "$LOG" -e "$LOG" --merge-logs -- pair >/dev/null 2>&1',
9679
+ // can't start (e.g. backend unreachable) — three attempts is enough
9680
+ // for transient flakes.
9681
+ // No `--time` (would prefix every line with a timestamp); no
9682
+ // `--no-pmx` either (default off).
9683
+ `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`,
8996
9684
  // Give PM2 a moment to spawn the process before we start polling
8997
9685
  // status — otherwise the very first jlist can race the spawn.
8998
9686
  "sleep 2",
@@ -9090,83 +9778,6 @@ async function deploy() {
9090
9778
  function shellQuoteSingle(s) {
9091
9779
  return `'${s.replace(/'/g, `'\\''`)}'`;
9092
9780
  }
9093
- async function runRemoteClaudeLogin(provider, workspaceId) {
9094
- wt(
9095
- [
9096
- "A login URL will print below. Open it in your local browser, sign in,",
9097
- "and paste any code Claude asks for back into this terminal."
9098
- ].join("\n"),
9099
- "Authenticating Claude on workspace"
9100
- );
9101
- const result = await provider.streamCommand(
9102
- workspaceId,
9103
- 'bash -lc "claude login || claude /login || true"'
9104
- );
9105
- if (result.code !== 0) {
9106
- wt(
9107
- "claude login exited non-zero. You can re-run it manually inside the codespace later.",
9108
- "Heads up"
9109
- );
9110
- }
9111
- }
9112
- async function detectLocalClaudeCredentials(localClaudeDir) {
9113
- if (fs11.existsSync(path17.join(localClaudeDir, ".credentials.json"))) {
9114
- return "flat-file";
9115
- }
9116
- if (process.platform === "darwin") {
9117
- try {
9118
- await execFileP6(
9119
- "security",
9120
- ["find-generic-password", "-s", "Claude Code-credentials"],
9121
- { maxBuffer: 1024 * 1024 }
9122
- );
9123
- return "macos-keychain";
9124
- } catch {
9125
- return "none";
9126
- }
9127
- }
9128
- return "none";
9129
- }
9130
- async function verifyClaudeAuth(provider, workspaceId) {
9131
- const result = await provider.exec(
9132
- workspaceId,
9133
- 'bash -lc "claude auth status 2>/dev/null || true"'
9134
- );
9135
- if (result.code !== 0) return false;
9136
- const jsonStart = result.stdout.indexOf("{");
9137
- if (jsonStart < 0) return false;
9138
- try {
9139
- const parsed = JSON.parse(result.stdout.slice(jsonStart));
9140
- return parsed.loggedIn === true;
9141
- } catch {
9142
- return false;
9143
- }
9144
- }
9145
- async function bridgeClaudeCredentials(provider, workspaceId, localClaudeDir) {
9146
- const fileBased = path17.join(localClaudeDir, ".credentials.json");
9147
- if (fs11.existsSync(fileBased)) return "flat-file";
9148
- if (process.platform === "darwin") {
9149
- try {
9150
- const { stdout } = await execFileP6(
9151
- "security",
9152
- ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
9153
- { maxBuffer: 1024 * 1024 }
9154
- );
9155
- const json = stdout.trim();
9156
- if (json.length === 0) return "none";
9157
- await provider.uploadFile(
9158
- workspaceId,
9159
- "/home/codespace/.claude/.credentials.json",
9160
- json,
9161
- { mode: 384 }
9162
- );
9163
- return "macos-keychain";
9164
- } catch {
9165
- return "none";
9166
- }
9167
- }
9168
- return "none";
9169
- }
9170
9781
  function formatLastUsed(iso) {
9171
9782
  if (!iso) return "";
9172
9783
  const t2 = Date.parse(iso);
@@ -9352,7 +9963,7 @@ async function stopWorkspaceFromLocal(target) {
9352
9963
  // src/commands/version.ts
9353
9964
  var import_picocolors11 = __toESM(require("picocolors"));
9354
9965
  function version() {
9355
- const v = true ? "2.10.8" : "unknown";
9966
+ const v = true ? "2.12.0" : "unknown";
9356
9967
  console.log(`${import_picocolors11.default.bold("codeam-cli")} ${import_picocolors11.default.cyan(v)}`);
9357
9968
  }
9358
9969
 
@@ -9389,9 +10000,9 @@ function help() {
9389
10000
  }
9390
10001
 
9391
10002
  // src/lib/updateNotifier.ts
9392
- var fs12 = __toESM(require("fs"));
9393
- var os11 = __toESM(require("os"));
9394
- var path18 = __toESM(require("path"));
10003
+ var fs16 = __toESM(require("fs"));
10004
+ var os15 = __toESM(require("os"));
10005
+ var path22 = __toESM(require("path"));
9395
10006
  var https5 = __toESM(require("https"));
9396
10007
  var import_picocolors13 = __toESM(require("picocolors"));
9397
10008
  var PKG_NAME = "codeam-cli";
@@ -9399,12 +10010,12 @@ var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
9399
10010
  var TTL_MS = 24 * 60 * 60 * 1e3;
9400
10011
  var REQUEST_TIMEOUT_MS = 1500;
9401
10012
  function cachePath() {
9402
- const dir = path18.join(os11.homedir(), ".codeam");
9403
- return path18.join(dir, "update-check.json");
10013
+ const dir = path22.join(os15.homedir(), ".codeam");
10014
+ return path22.join(dir, "update-check.json");
9404
10015
  }
9405
10016
  function readCache() {
9406
10017
  try {
9407
- const raw = fs12.readFileSync(cachePath(), "utf8");
10018
+ const raw = fs16.readFileSync(cachePath(), "utf8");
9408
10019
  const parsed = JSON.parse(raw);
9409
10020
  if (typeof parsed.fetchedAt !== "number" || typeof parsed.latest !== "string") return null;
9410
10021
  return parsed;
@@ -9415,8 +10026,8 @@ function readCache() {
9415
10026
  function writeCache(cache) {
9416
10027
  try {
9417
10028
  const file = cachePath();
9418
- fs12.mkdirSync(path18.dirname(file), { recursive: true });
9419
- fs12.writeFileSync(file, JSON.stringify(cache));
10029
+ fs16.mkdirSync(path22.dirname(file), { recursive: true });
10030
+ fs16.writeFileSync(file, JSON.stringify(cache));
9420
10031
  } catch {
9421
10032
  }
9422
10033
  }
@@ -9487,7 +10098,7 @@ function checkForUpdates() {
9487
10098
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
9488
10099
  if (process.env.CI) return;
9489
10100
  if (!process.stdout.isTTY) return;
9490
- const current = true ? "2.10.8" : null;
10101
+ const current = true ? "2.12.0" : null;
9491
10102
  if (!current) return;
9492
10103
  const cache = readCache();
9493
10104
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -9516,7 +10127,7 @@ async function main() {
9516
10127
  case "help":
9517
10128
  return help();
9518
10129
  case "pair":
9519
- return pair();
10130
+ return pair(args);
9520
10131
  case "pair-auto":
9521
10132
  return pairAuto(args);
9522
10133
  case "sessions":
@@ -9528,7 +10139,7 @@ async function main() {
9528
10139
  case "deploy":
9529
10140
  if (args[0] === "ls" || args[0] === "list") return deployList();
9530
10141
  if (args[0] === "stop" || args[0] === "remove") return deployStop();
9531
- return deploy();
10142
+ return deploy(args);
9532
10143
  default:
9533
10144
  return start();
9534
10145
  }