codeam-cli 2.16.2 → 2.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/dist/index.js +604 -453
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -424,7 +424,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
|
424
424
|
// package.json
|
|
425
425
|
var package_default = {
|
|
426
426
|
name: "codeam-cli",
|
|
427
|
-
version: "2.
|
|
427
|
+
version: "2.17.1",
|
|
428
428
|
description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
|
|
429
429
|
type: "commonjs",
|
|
430
430
|
main: "dist/index.js",
|
|
@@ -1537,9 +1537,253 @@ var WindowsConPtyStrategy = class _WindowsConPtyStrategy {
|
|
|
1537
1537
|
};
|
|
1538
1538
|
};
|
|
1539
1539
|
|
|
1540
|
+
// src/services/claude-resolver.ts
|
|
1541
|
+
var path6 = __toESM(require("path"));
|
|
1542
|
+
init_types();
|
|
1543
|
+
function buildClaudeLaunch(extraArgs = []) {
|
|
1544
|
+
const found = findInPath("claude") ?? findInPath("claude-code");
|
|
1545
|
+
if (!found) return null;
|
|
1546
|
+
if (process.platform === "win32") {
|
|
1547
|
+
const ext = path6.extname(found).toLowerCase();
|
|
1548
|
+
if (ext === ".cmd" || ext === ".bat") {
|
|
1549
|
+
return { cmd: "cmd.exe", args: ["/c", found, ...extraArgs] };
|
|
1550
|
+
}
|
|
1551
|
+
if (ext === ".ps1") {
|
|
1552
|
+
return {
|
|
1553
|
+
cmd: "powershell.exe",
|
|
1554
|
+
args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", found, ...extraArgs]
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return { cmd: found, args: extraArgs };
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
// src/services/agent.service.ts
|
|
1562
|
+
var AgentService = class {
|
|
1563
|
+
constructor(runtime, opts) {
|
|
1564
|
+
this.runtime = runtime;
|
|
1565
|
+
this.opts = opts;
|
|
1566
|
+
this.strategyOpts = {
|
|
1567
|
+
onData: (d3) => {
|
|
1568
|
+
if (!this.claudeReady && d3.length > 0) {
|
|
1569
|
+
this.claudeReady = true;
|
|
1570
|
+
setTimeout(() => this.drainPending(), 250);
|
|
1571
|
+
}
|
|
1572
|
+
(opts.onData ?? (() => {
|
|
1573
|
+
}))(d3);
|
|
1574
|
+
},
|
|
1575
|
+
onExit: opts.onExit
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
runtime;
|
|
1579
|
+
opts;
|
|
1580
|
+
// Strategy is selected lazily inside spawn() so we can fall back from
|
|
1581
|
+
// ConPTY → legacy pipe at runtime if the native binding fails to load.
|
|
1582
|
+
// Methods called before spawn() (e.g. early kill/SIGINT) no-op safely.
|
|
1583
|
+
strategy = null;
|
|
1584
|
+
strategyOpts;
|
|
1585
|
+
/**
|
|
1586
|
+
* Set once the PTY emits its FIRST batch of output — proxy for
|
|
1587
|
+
* "Claude has rendered its input box and is ready to read keystrokes."
|
|
1588
|
+
* Before this, remote `sendCommand`s are buffered (`pendingInputs`)
|
|
1589
|
+
* and replayed in order on first data. Without this guard, the very
|
|
1590
|
+
* first prompt right after `codeam pair` on Windows lands while
|
|
1591
|
+
* Claude's React Ink tree is still mounting — the input bytes are
|
|
1592
|
+
* accepted by the PTY but never make it to the input field, and
|
|
1593
|
+
* the prompt silently vanishes.
|
|
1594
|
+
*/
|
|
1595
|
+
claudeReady = false;
|
|
1596
|
+
pendingInputs = [];
|
|
1597
|
+
drainPending() {
|
|
1598
|
+
if (!this.strategy || this.pendingInputs.length === 0) return;
|
|
1599
|
+
const s = this.strategy;
|
|
1600
|
+
log.trace("claude", `drain pending=${this.pendingInputs.length}`);
|
|
1601
|
+
let offset = 0;
|
|
1602
|
+
for (const text of this.pendingInputs) {
|
|
1603
|
+
setTimeout(() => s.write(text), offset);
|
|
1604
|
+
setTimeout(() => s.write("\r"), offset + 50);
|
|
1605
|
+
offset += 200;
|
|
1606
|
+
}
|
|
1607
|
+
this.pendingInputs.length = 0;
|
|
1608
|
+
}
|
|
1609
|
+
async spawn() {
|
|
1610
|
+
let launch;
|
|
1611
|
+
try {
|
|
1612
|
+
launch = await this.runtime.prepareLaunch();
|
|
1613
|
+
} catch (err) {
|
|
1614
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1615
|
+
console.error(
|
|
1616
|
+
`
|
|
1617
|
+
\u2717 ${this.runtime.meta.displayName} could not be launched.
|
|
1618
|
+
${msg}
|
|
1619
|
+
`
|
|
1620
|
+
);
|
|
1621
|
+
process.exit(1);
|
|
1622
|
+
}
|
|
1623
|
+
if (process.platform === "win32") {
|
|
1624
|
+
log.trace("claude", `spawn (win32) cmd=${launch.cmd} args=${launch.args.join(" ")}`);
|
|
1625
|
+
const conpty = WindowsConPtyStrategy.tryCreate(this.strategyOpts);
|
|
1626
|
+
if (conpty) {
|
|
1627
|
+
try {
|
|
1628
|
+
conpty.spawn(launch.cmd, this.opts.cwd, launch.args);
|
|
1629
|
+
this.strategy = conpty;
|
|
1630
|
+
log.trace("claude", "ConPTY spawn ok");
|
|
1631
|
+
return;
|
|
1632
|
+
} catch (err) {
|
|
1633
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1634
|
+
try {
|
|
1635
|
+
conpty.dispose();
|
|
1636
|
+
} catch {
|
|
1637
|
+
}
|
|
1638
|
+
console.error(`
|
|
1639
|
+
\u26A0 ConPTY launch failed (${msg.split("\n")[0]})`);
|
|
1640
|
+
console.error(" Falling back to pipe mode (limited interactivity)\u2026\n");
|
|
1641
|
+
}
|
|
1642
|
+
} else {
|
|
1643
|
+
console.error(
|
|
1644
|
+
'\n \u26A0 Windows: node-pty unavailable, falling back to pipe mode.\n Claude may exit with "no stdin data" / "--print" errors.\n Reinstall the CLI to fetch the prebuilt ConPTY binary, or run inside WSL.\n'
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
const pipe = new WindowsPtyStrategy(this.strategyOpts);
|
|
1648
|
+
pipe.spawn(launch.cmd, this.opts.cwd, launch.args);
|
|
1649
|
+
this.strategy = pipe;
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
const unix = new UnixPtyStrategy(this.strategyOpts);
|
|
1653
|
+
unix.spawn(launch.cmd, this.opts.cwd, launch.args);
|
|
1654
|
+
this.strategy = unix;
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Send a command to Claude's stdin (remote control from mobile).
|
|
1658
|
+
*
|
|
1659
|
+
* Why two separate writes with a delay?
|
|
1660
|
+
* Same batching problem as selectOption: all bytes arriving in one write()
|
|
1661
|
+
* call are processed by readline in one synchronous run. React Ink batches
|
|
1662
|
+
* the resulting state updates, so when '\r' fires the input's value is still
|
|
1663
|
+
* the pre-batch (empty/previous) state → Enter submits nothing and the text
|
|
1664
|
+
* stays visible-but-unsubmitted in the input field.
|
|
1665
|
+
*
|
|
1666
|
+
* Sending '\r' in a separate write() ~50 ms later guarantees it arrives on
|
|
1667
|
+
* a fresh event-loop tick, after React has flushed the text into input state.
|
|
1668
|
+
*
|
|
1669
|
+
* The delay scales with line count: Smart Composer outputs are ~500–1500
|
|
1670
|
+
* chars with embedded `\n`s (Task / Context / Steps blocks). Ink's input
|
|
1671
|
+
* field re-flows multi-line content per render and the 50 ms baseline that
|
|
1672
|
+
* worked for single-line prompts isn't enough headroom — the text lands in
|
|
1673
|
+
* the field but `\r` submits stale state and the prompt sits there until
|
|
1674
|
+
* the user hits Enter manually. Adding ~40 ms per extra line (capped at
|
|
1675
|
+
* 300 ms) keeps short prompts snappy while giving multi-line composer
|
|
1676
|
+
* outputs the time they need to settle.
|
|
1677
|
+
*/
|
|
1678
|
+
sendCommand(text) {
|
|
1679
|
+
if (!this.strategy) {
|
|
1680
|
+
log.trace("claude", "sendCommand dropped (no strategy)");
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
if (!this.claudeReady) {
|
|
1684
|
+
log.trace("claude", `sendCommand buffered (not ready) text=${text.length}B`);
|
|
1685
|
+
this.pendingInputs.push(text);
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
const s = this.strategy;
|
|
1689
|
+
log.trace("claude", `sendCommand text=${text.length}B`);
|
|
1690
|
+
s.write(text);
|
|
1691
|
+
const lineCount = text.split("\n").length;
|
|
1692
|
+
const delay = Math.min(300, 50 + (lineCount - 1) * 40);
|
|
1693
|
+
setTimeout(() => s.write("\r"), delay);
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Navigate a React Ink selector to the given 0-based target index and confirm.
|
|
1697
|
+
*
|
|
1698
|
+
* `fromIndex` is the current highlighted position (defaults to 0 for
|
|
1699
|
+
* numbered selectors which always start at the first option). For list-style
|
|
1700
|
+
* selectors (e.g. /mcp), the CLI sends `currentIndex` in the select_prompt
|
|
1701
|
+
* chunk so the client can pass it back here as `fromIndex`, enabling both
|
|
1702
|
+
* up-arrow and down-arrow navigation without always rewinding to position 0.
|
|
1703
|
+
*
|
|
1704
|
+
* Why not sendCommand(arrows + Enter) in one write()?
|
|
1705
|
+
* All bytes arrive as one chunk → readline fires all keypress events in the
|
|
1706
|
+
* same synchronous run → React Ink batches the state updates → each arrow
|
|
1707
|
+
* sees selectedIndex=0 → final state is still 0 or 1 → wrong option selected.
|
|
1708
|
+
*
|
|
1709
|
+
* Fix: send each arrow in a separate write(), ARROW_MS apart, so React has
|
|
1710
|
+
* time to process and re-render between each keystroke. Enter is sent
|
|
1711
|
+
* ENTER_MS after the last arrow.
|
|
1712
|
+
*/
|
|
1713
|
+
selectOption(targetIndex, fromIndex = 0) {
|
|
1714
|
+
if (!this.strategy) return;
|
|
1715
|
+
const s = this.strategy;
|
|
1716
|
+
const delta = targetIndex - fromIndex;
|
|
1717
|
+
const steps = Math.abs(delta);
|
|
1718
|
+
const arrow = delta >= 0 ? "\x1B[B" : "\x1B[A";
|
|
1719
|
+
const ARROW_MS = 80;
|
|
1720
|
+
const ENTER_MS = 200;
|
|
1721
|
+
if (steps === 0) {
|
|
1722
|
+
s.write("\r");
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
for (let i = 0; i < steps; i++) {
|
|
1726
|
+
setTimeout(() => {
|
|
1727
|
+
s.write(arrow);
|
|
1728
|
+
}, i * ARROW_MS);
|
|
1729
|
+
}
|
|
1730
|
+
setTimeout(() => {
|
|
1731
|
+
s.write("\r");
|
|
1732
|
+
}, steps * ARROW_MS + ENTER_MS);
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Write raw bytes to the PTY without any auto-appended `\r` or delay.
|
|
1736
|
+
* Use this when the caller already owns the full input (e.g. the
|
|
1737
|
+
* `ptyInput` returned by `RuntimeStrategy.changeModelInstruction()`
|
|
1738
|
+
* already contains the trailing `\r`).
|
|
1739
|
+
*/
|
|
1740
|
+
sendRawPtyInput(text) {
|
|
1741
|
+
if (!this.strategy) {
|
|
1742
|
+
log.trace("claude", "sendRawPtyInput dropped (no strategy)");
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
log.trace("claude", `sendRawPtyInput len=${text.length}`);
|
|
1746
|
+
this.strategy.write(text);
|
|
1747
|
+
}
|
|
1748
|
+
/** Send Escape key to Claude (cancels interactive prompts). */
|
|
1749
|
+
sendEscape() {
|
|
1750
|
+
this.strategy?.write("\x1B");
|
|
1751
|
+
}
|
|
1752
|
+
/** Send Ctrl+C to Claude. */
|
|
1753
|
+
interrupt() {
|
|
1754
|
+
this.strategy?.write("");
|
|
1755
|
+
}
|
|
1756
|
+
kill() {
|
|
1757
|
+
this.strategy?.kill();
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Kill the current Claude process and relaunch it resuming the given session.
|
|
1761
|
+
* Pass auto=true to add --dangerously-skip-permissions (no confirmation prompts).
|
|
1762
|
+
*
|
|
1763
|
+
* For agents that use CLI flags (Claude: --resume <id>), `resumeLaunchArgs`
|
|
1764
|
+
* returns a non-empty array and we pass those directly to the binary.
|
|
1765
|
+
* For agents that use a post-spawn PTY instruction (e.g. Codex), `resumeLaunchArgs`
|
|
1766
|
+
* returns [] and `postSpawnInstruction` types the resume command into the PTY.
|
|
1767
|
+
*/
|
|
1768
|
+
restart(sessionId, auto = false) {
|
|
1769
|
+
if (!this.strategy) return;
|
|
1770
|
+
const resumeArgs = auto ? this.runtime.resumeLaunchArgs(sessionId) : ["--resume", sessionId];
|
|
1771
|
+
const launch = buildClaudeLaunch(resumeArgs);
|
|
1772
|
+
if (!launch) return;
|
|
1773
|
+
this.strategy.kill();
|
|
1774
|
+
this.strategy.spawn(launch.cmd, this.opts.cwd, launch.args);
|
|
1775
|
+
if (resumeArgs.length === 0 && this.runtime.postSpawnInstruction) {
|
|
1776
|
+
const { ptyInput } = this.runtime.postSpawnInstruction(sessionId);
|
|
1777
|
+
setTimeout(() => {
|
|
1778
|
+
this.strategy?.write(ptyInput);
|
|
1779
|
+
}, 500);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
|
|
1540
1784
|
// src/services/claude-installer.ts
|
|
1541
1785
|
var import_child_process4 = require("child_process");
|
|
1542
|
-
var
|
|
1786
|
+
var path7 = __toESM(require("path"));
|
|
1543
1787
|
var os5 = __toESM(require("os"));
|
|
1544
1788
|
|
|
1545
1789
|
// ../../node_modules/@clack/prompts/dist/index.mjs
|
|
@@ -3406,387 +3650,130 @@ ${h.value}` : h.value;
|
|
|
3406
3650
|
$2(false), h.result = f, c2 && g();
|
|
3407
3651
|
};
|
|
3408
3652
|
return { message(h, f) {
|
|
3409
|
-
m(a[0], h, f);
|
|
3410
|
-
}, group(h) {
|
|
3411
|
-
const f = { header: h, value: "", full: "" };
|
|
3412
|
-
return a.push(f), { message(v, T2) {
|
|
3413
|
-
m(f, v, T2);
|
|
3414
|
-
}, error(v) {
|
|
3415
|
-
S2(f, { status: "error", message: v });
|
|
3416
|
-
}, success(v) {
|
|
3417
|
-
S2(f, { status: "success", message: v });
|
|
3418
|
-
} };
|
|
3419
|
-
}, error(h, f) {
|
|
3420
|
-
$2(true), O2.error(h, { output: i, secondarySymbol: r, spacing: 1 }), f?.showLog !== false && p2(), a.splice(1, a.length - 1), a[0].value = "", a[0].full = "";
|
|
3421
|
-
}, success(h, f) {
|
|
3422
|
-
$2(true), O2.success(h, { output: i, secondarySymbol: r, spacing: 1 }), f?.showLog === true && p2(), a.splice(1, a.length - 1), a[0].value = "", a[0].full = "";
|
|
3423
|
-
} };
|
|
3424
|
-
};
|
|
3425
|
-
var Ot = (e) => new at({ validate: e.validate, placeholder: e.placeholder, defaultValue: e.defaultValue, initialValue: e.initialValue, output: e.output, signal: e.signal, input: e.input, render() {
|
|
3426
|
-
const i = e?.withGuide ?? u.withGuide, s = `${`${i ? `${(0, import_node_util2.styleText)("gray", d2)}
|
|
3427
|
-
` : ""}${V2(this.state)} `}${e.message}
|
|
3428
|
-
`, r = e.placeholder ? (0, import_node_util2.styleText)("inverse", e.placeholder[0]) + (0, import_node_util2.styleText)("dim", e.placeholder.slice(1)) : (0, import_node_util2.styleText)(["inverse", "hidden"], "_"), u2 = this.userInput ? this.userInputWithCursor : r, n = this.value ?? "";
|
|
3429
|
-
switch (this.state) {
|
|
3430
|
-
case "error": {
|
|
3431
|
-
const o = this.error ? ` ${(0, import_node_util2.styleText)("yellow", this.error)}` : "", c2 = i ? `${(0, import_node_util2.styleText)("yellow", d2)} ` : "", a = i ? (0, import_node_util2.styleText)("yellow", E2) : "";
|
|
3432
|
-
return `${s.trim()}
|
|
3433
|
-
${c2}${u2}
|
|
3434
|
-
${a}${o}
|
|
3435
|
-
`;
|
|
3436
|
-
}
|
|
3437
|
-
case "submit": {
|
|
3438
|
-
const o = n ? ` ${(0, import_node_util2.styleText)("dim", n)}` : "", c2 = i ? (0, import_node_util2.styleText)("gray", d2) : "";
|
|
3439
|
-
return `${s}${c2}${o}`;
|
|
3440
|
-
}
|
|
3441
|
-
case "cancel": {
|
|
3442
|
-
const o = n ? ` ${(0, import_node_util2.styleText)(["strikethrough", "dim"], n)}` : "", c2 = i ? (0, import_node_util2.styleText)("gray", d2) : "";
|
|
3443
|
-
return `${s}${c2}${o}${n.trim() ? `
|
|
3444
|
-
${c2}` : ""}`;
|
|
3445
|
-
}
|
|
3446
|
-
default: {
|
|
3447
|
-
const o = i ? `${(0, import_node_util2.styleText)("cyan", d2)} ` : "", c2 = i ? (0, import_node_util2.styleText)("cyan", E2) : "";
|
|
3448
|
-
return `${s}${o}${u2}
|
|
3449
|
-
${c2}
|
|
3450
|
-
`;
|
|
3451
|
-
}
|
|
3452
|
-
}
|
|
3453
|
-
} }).prompt();
|
|
3454
|
-
|
|
3455
|
-
// src/services/claude-installer.ts
|
|
3456
|
-
init_types();
|
|
3457
|
-
function probeInstallDirs() {
|
|
3458
|
-
const home = os5.homedir();
|
|
3459
|
-
if (process.platform === "win32") {
|
|
3460
|
-
return [
|
|
3461
|
-
path6.join(home, ".claude", "local"),
|
|
3462
|
-
path6.join(home, "AppData", "Local", "AnthropicClaude"),
|
|
3463
|
-
path6.join(home, "AppData", "Local", "Programs", "AnthropicClaude")
|
|
3464
|
-
];
|
|
3465
|
-
}
|
|
3466
|
-
return [
|
|
3467
|
-
path6.join(home, ".local", "bin"),
|
|
3468
|
-
path6.join(home, ".claude", "local"),
|
|
3469
|
-
"/usr/local/bin"
|
|
3470
|
-
];
|
|
3471
|
-
}
|
|
3472
|
-
function isAvailable() {
|
|
3473
|
-
return findInPath("claude") !== null || findInPath("claude-code") !== null;
|
|
3474
|
-
}
|
|
3475
|
-
function augmentPath() {
|
|
3476
|
-
const dirs = probeInstallDirs();
|
|
3477
|
-
const sep2 = path6.delimiter;
|
|
3478
|
-
const current = process.env.PATH ?? "";
|
|
3479
|
-
const existing = new Set(current.split(sep2).filter(Boolean));
|
|
3480
|
-
const additions = dirs.filter((d3) => !existing.has(d3));
|
|
3481
|
-
if (additions.length === 0) return;
|
|
3482
|
-
process.env.PATH = additions.join(sep2) + sep2 + current;
|
|
3483
|
-
}
|
|
3484
|
-
function runInstaller() {
|
|
3485
|
-
const isWindows = process.platform === "win32";
|
|
3486
|
-
const cmd = isWindows ? "powershell.exe" : "bash";
|
|
3487
|
-
const args2 = isWindows ? [
|
|
3488
|
-
"-NoProfile",
|
|
3489
|
-
"-ExecutionPolicy",
|
|
3490
|
-
"Bypass",
|
|
3491
|
-
"-Command",
|
|
3492
|
-
"irm https://claude.ai/install.ps1 | iex"
|
|
3493
|
-
] : ["-c", "curl -fsSL https://claude.ai/install.sh | bash"];
|
|
3494
|
-
return new Promise((resolve3) => {
|
|
3495
|
-
const proc = (0, import_child_process4.spawn)(cmd, args2, { stdio: "inherit" });
|
|
3496
|
-
proc.on("error", (err) => {
|
|
3497
|
-
console.error(`
|
|
3498
|
-
\u2717 Installer failed to launch: ${err.message}`);
|
|
3499
|
-
resolve3(false);
|
|
3500
|
-
});
|
|
3501
|
-
proc.on("exit", (code) => {
|
|
3502
|
-
resolve3(code === 0);
|
|
3503
|
-
});
|
|
3504
|
-
});
|
|
3505
|
-
}
|
|
3506
|
-
async function ensureClaudeInstalled() {
|
|
3507
|
-
if (isAvailable()) return true;
|
|
3508
|
-
const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
3509
|
-
if (isInteractive) {
|
|
3510
|
-
const proceed = await ot2({
|
|
3511
|
-
message: "Claude Code is not installed on this machine. Install it now using the official installer?",
|
|
3512
|
-
initialValue: true
|
|
3513
|
-
});
|
|
3514
|
-
if (q(proceed) || proceed === false) return false;
|
|
3515
|
-
} else {
|
|
3516
|
-
console.log("\n Claude Code not found \u2014 running the official installer...\n");
|
|
3517
|
-
}
|
|
3518
|
-
console.log();
|
|
3519
|
-
const ok = await runInstaller();
|
|
3520
|
-
if (!ok) {
|
|
3521
|
-
console.error("\n \u2717 Claude Code installation failed. See the installer output above.");
|
|
3522
|
-
return false;
|
|
3523
|
-
}
|
|
3524
|
-
augmentPath();
|
|
3525
|
-
if (!isAvailable()) {
|
|
3526
|
-
console.error(
|
|
3527
|
-
"\n \u26A0 Claude Code installed but the binary is still not on PATH for this process.\n This usually means the installer registered a new directory that only takes\n effect in a fresh shell. Restart your terminal and run `codeam pair` again."
|
|
3528
|
-
);
|
|
3529
|
-
return false;
|
|
3530
|
-
}
|
|
3531
|
-
return true;
|
|
3532
|
-
}
|
|
3533
|
-
|
|
3534
|
-
// src/services/claude-resolver.ts
|
|
3535
|
-
var path7 = __toESM(require("path"));
|
|
3536
|
-
init_types();
|
|
3537
|
-
function buildClaudeLaunch(extraArgs = []) {
|
|
3538
|
-
const found = findInPath("claude") ?? findInPath("claude-code");
|
|
3539
|
-
if (!found) return null;
|
|
3540
|
-
if (process.platform === "win32") {
|
|
3541
|
-
const ext = path7.extname(found).toLowerCase();
|
|
3542
|
-
if (ext === ".cmd" || ext === ".bat") {
|
|
3543
|
-
return { cmd: "cmd.exe", args: ["/c", found, ...extraArgs] };
|
|
3544
|
-
}
|
|
3545
|
-
if (ext === ".ps1") {
|
|
3546
|
-
return {
|
|
3547
|
-
cmd: "powershell.exe",
|
|
3548
|
-
args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", found, ...extraArgs]
|
|
3549
|
-
};
|
|
3550
|
-
}
|
|
3551
|
-
}
|
|
3552
|
-
return { cmd: found, args: extraArgs };
|
|
3553
|
-
}
|
|
3554
|
-
|
|
3555
|
-
// src/services/agent.service.ts
|
|
3556
|
-
var AgentService = class {
|
|
3557
|
-
constructor(runtime, opts) {
|
|
3558
|
-
this.runtime = runtime;
|
|
3559
|
-
this.opts = opts;
|
|
3560
|
-
this.strategyOpts = {
|
|
3561
|
-
onData: (d3) => {
|
|
3562
|
-
if (!this.claudeReady && d3.length > 0) {
|
|
3563
|
-
this.claudeReady = true;
|
|
3564
|
-
setTimeout(() => this.drainPending(), 250);
|
|
3565
|
-
}
|
|
3566
|
-
(opts.onData ?? (() => {
|
|
3567
|
-
}))(d3);
|
|
3568
|
-
},
|
|
3569
|
-
onExit: opts.onExit
|
|
3570
|
-
};
|
|
3571
|
-
}
|
|
3572
|
-
runtime;
|
|
3573
|
-
opts;
|
|
3574
|
-
// Strategy is selected lazily inside spawn() so we can fall back from
|
|
3575
|
-
// ConPTY → legacy pipe at runtime if the native binding fails to load.
|
|
3576
|
-
// Methods called before spawn() (e.g. early kill/SIGINT) no-op safely.
|
|
3577
|
-
strategy = null;
|
|
3578
|
-
strategyOpts;
|
|
3579
|
-
/**
|
|
3580
|
-
* Set once the PTY emits its FIRST batch of output — proxy for
|
|
3581
|
-
* "Claude has rendered its input box and is ready to read keystrokes."
|
|
3582
|
-
* Before this, remote `sendCommand`s are buffered (`pendingInputs`)
|
|
3583
|
-
* and replayed in order on first data. Without this guard, the very
|
|
3584
|
-
* first prompt right after `codeam pair` on Windows lands while
|
|
3585
|
-
* Claude's React Ink tree is still mounting — the input bytes are
|
|
3586
|
-
* accepted by the PTY but never make it to the input field, and
|
|
3587
|
-
* the prompt silently vanishes.
|
|
3588
|
-
*/
|
|
3589
|
-
claudeReady = false;
|
|
3590
|
-
pendingInputs = [];
|
|
3591
|
-
drainPending() {
|
|
3592
|
-
if (!this.strategy || this.pendingInputs.length === 0) return;
|
|
3593
|
-
const s = this.strategy;
|
|
3594
|
-
log.trace("claude", `drain pending=${this.pendingInputs.length}`);
|
|
3595
|
-
let offset = 0;
|
|
3596
|
-
for (const text of this.pendingInputs) {
|
|
3597
|
-
setTimeout(() => s.write(text), offset);
|
|
3598
|
-
setTimeout(() => s.write("\r"), offset + 50);
|
|
3599
|
-
offset += 200;
|
|
3600
|
-
}
|
|
3601
|
-
this.pendingInputs.length = 0;
|
|
3602
|
-
}
|
|
3603
|
-
async spawn() {
|
|
3604
|
-
let launch;
|
|
3605
|
-
try {
|
|
3606
|
-
launch = await this.runtime.prepareLaunch();
|
|
3607
|
-
} catch {
|
|
3608
|
-
const installed = await ensureClaudeInstalled();
|
|
3609
|
-
if (installed) {
|
|
3610
|
-
try {
|
|
3611
|
-
launch = await this.runtime.prepareLaunch();
|
|
3612
|
-
} catch {
|
|
3613
|
-
launch = null;
|
|
3614
|
-
}
|
|
3615
|
-
} else {
|
|
3616
|
-
launch = null;
|
|
3617
|
-
}
|
|
3618
|
-
if (!launch) {
|
|
3619
|
-
const cmd = process.platform === "win32" ? "irm https://claude.ai/install.ps1 | iex" : "curl -fsSL https://claude.ai/install.sh | bash";
|
|
3620
|
-
console.error(
|
|
3621
|
-
`
|
|
3622
|
-
\u2717 claude is required to continue. Install it manually with:
|
|
3623
|
-
${cmd}
|
|
3624
|
-
Then restart your terminal and run \`codeam pair\` again.
|
|
3625
|
-
`
|
|
3626
|
-
);
|
|
3627
|
-
process.exit(1);
|
|
3628
|
-
}
|
|
3629
|
-
}
|
|
3630
|
-
if (process.platform === "win32") {
|
|
3631
|
-
log.trace("claude", `spawn (win32) cmd=${launch.cmd} args=${launch.args.join(" ")}`);
|
|
3632
|
-
const conpty = WindowsConPtyStrategy.tryCreate(this.strategyOpts);
|
|
3633
|
-
if (conpty) {
|
|
3634
|
-
try {
|
|
3635
|
-
conpty.spawn(launch.cmd, this.opts.cwd, launch.args);
|
|
3636
|
-
this.strategy = conpty;
|
|
3637
|
-
log.trace("claude", "ConPTY spawn ok");
|
|
3638
|
-
return;
|
|
3639
|
-
} catch (err) {
|
|
3640
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3641
|
-
try {
|
|
3642
|
-
conpty.dispose();
|
|
3643
|
-
} catch {
|
|
3644
|
-
}
|
|
3645
|
-
console.error(`
|
|
3646
|
-
\u26A0 ConPTY launch failed (${msg.split("\n")[0]})`);
|
|
3647
|
-
console.error(" Falling back to pipe mode (limited interactivity)\u2026\n");
|
|
3648
|
-
}
|
|
3649
|
-
} else {
|
|
3650
|
-
console.error(
|
|
3651
|
-
'\n \u26A0 Windows: node-pty unavailable, falling back to pipe mode.\n Claude may exit with "no stdin data" / "--print" errors.\n Reinstall the CLI to fetch the prebuilt ConPTY binary, or run inside WSL.\n'
|
|
3652
|
-
);
|
|
3653
|
-
}
|
|
3654
|
-
const pipe = new WindowsPtyStrategy(this.strategyOpts);
|
|
3655
|
-
pipe.spawn(launch.cmd, this.opts.cwd, launch.args);
|
|
3656
|
-
this.strategy = pipe;
|
|
3657
|
-
return;
|
|
3658
|
-
}
|
|
3659
|
-
const unix = new UnixPtyStrategy(this.strategyOpts);
|
|
3660
|
-
unix.spawn(launch.cmd, this.opts.cwd, launch.args);
|
|
3661
|
-
this.strategy = unix;
|
|
3662
|
-
}
|
|
3663
|
-
/**
|
|
3664
|
-
* Send a command to Claude's stdin (remote control from mobile).
|
|
3665
|
-
*
|
|
3666
|
-
* Why two separate writes with a delay?
|
|
3667
|
-
* Same batching problem as selectOption: all bytes arriving in one write()
|
|
3668
|
-
* call are processed by readline in one synchronous run. React Ink batches
|
|
3669
|
-
* the resulting state updates, so when '\r' fires the input's value is still
|
|
3670
|
-
* the pre-batch (empty/previous) state → Enter submits nothing and the text
|
|
3671
|
-
* stays visible-but-unsubmitted in the input field.
|
|
3672
|
-
*
|
|
3673
|
-
* Sending '\r' in a separate write() ~50 ms later guarantees it arrives on
|
|
3674
|
-
* a fresh event-loop tick, after React has flushed the text into input state.
|
|
3675
|
-
*
|
|
3676
|
-
* The delay scales with line count: Smart Composer outputs are ~500–1500
|
|
3677
|
-
* chars with embedded `\n`s (Task / Context / Steps blocks). Ink's input
|
|
3678
|
-
* field re-flows multi-line content per render and the 50 ms baseline that
|
|
3679
|
-
* worked for single-line prompts isn't enough headroom — the text lands in
|
|
3680
|
-
* the field but `\r` submits stale state and the prompt sits there until
|
|
3681
|
-
* the user hits Enter manually. Adding ~40 ms per extra line (capped at
|
|
3682
|
-
* 300 ms) keeps short prompts snappy while giving multi-line composer
|
|
3683
|
-
* outputs the time they need to settle.
|
|
3684
|
-
*/
|
|
3685
|
-
sendCommand(text) {
|
|
3686
|
-
if (!this.strategy) {
|
|
3687
|
-
log.trace("claude", "sendCommand dropped (no strategy)");
|
|
3688
|
-
return;
|
|
3689
|
-
}
|
|
3690
|
-
if (!this.claudeReady) {
|
|
3691
|
-
log.trace("claude", `sendCommand buffered (not ready) text=${text.length}B`);
|
|
3692
|
-
this.pendingInputs.push(text);
|
|
3693
|
-
return;
|
|
3694
|
-
}
|
|
3695
|
-
const s = this.strategy;
|
|
3696
|
-
log.trace("claude", `sendCommand text=${text.length}B`);
|
|
3697
|
-
s.write(text);
|
|
3698
|
-
const lineCount = text.split("\n").length;
|
|
3699
|
-
const delay = Math.min(300, 50 + (lineCount - 1) * 40);
|
|
3700
|
-
setTimeout(() => s.write("\r"), delay);
|
|
3701
|
-
}
|
|
3702
|
-
/**
|
|
3703
|
-
* Navigate a React Ink selector to the given 0-based target index and confirm.
|
|
3704
|
-
*
|
|
3705
|
-
* `fromIndex` is the current highlighted position (defaults to 0 for
|
|
3706
|
-
* numbered selectors which always start at the first option). For list-style
|
|
3707
|
-
* selectors (e.g. /mcp), the CLI sends `currentIndex` in the select_prompt
|
|
3708
|
-
* chunk so the client can pass it back here as `fromIndex`, enabling both
|
|
3709
|
-
* up-arrow and down-arrow navigation without always rewinding to position 0.
|
|
3710
|
-
*
|
|
3711
|
-
* Why not sendCommand(arrows + Enter) in one write()?
|
|
3712
|
-
* All bytes arrive as one chunk → readline fires all keypress events in the
|
|
3713
|
-
* same synchronous run → React Ink batches the state updates → each arrow
|
|
3714
|
-
* sees selectedIndex=0 → final state is still 0 or 1 → wrong option selected.
|
|
3715
|
-
*
|
|
3716
|
-
* Fix: send each arrow in a separate write(), ARROW_MS apart, so React has
|
|
3717
|
-
* time to process and re-render between each keystroke. Enter is sent
|
|
3718
|
-
* ENTER_MS after the last arrow.
|
|
3719
|
-
*/
|
|
3720
|
-
selectOption(targetIndex, fromIndex = 0) {
|
|
3721
|
-
if (!this.strategy) return;
|
|
3722
|
-
const s = this.strategy;
|
|
3723
|
-
const delta = targetIndex - fromIndex;
|
|
3724
|
-
const steps = Math.abs(delta);
|
|
3725
|
-
const arrow = delta >= 0 ? "\x1B[B" : "\x1B[A";
|
|
3726
|
-
const ARROW_MS = 80;
|
|
3727
|
-
const ENTER_MS = 200;
|
|
3728
|
-
if (steps === 0) {
|
|
3729
|
-
s.write("\r");
|
|
3730
|
-
return;
|
|
3653
|
+
m(a[0], h, f);
|
|
3654
|
+
}, group(h) {
|
|
3655
|
+
const f = { header: h, value: "", full: "" };
|
|
3656
|
+
return a.push(f), { message(v, T2) {
|
|
3657
|
+
m(f, v, T2);
|
|
3658
|
+
}, error(v) {
|
|
3659
|
+
S2(f, { status: "error", message: v });
|
|
3660
|
+
}, success(v) {
|
|
3661
|
+
S2(f, { status: "success", message: v });
|
|
3662
|
+
} };
|
|
3663
|
+
}, error(h, f) {
|
|
3664
|
+
$2(true), O2.error(h, { output: i, secondarySymbol: r, spacing: 1 }), f?.showLog !== false && p2(), a.splice(1, a.length - 1), a[0].value = "", a[0].full = "";
|
|
3665
|
+
}, success(h, f) {
|
|
3666
|
+
$2(true), O2.success(h, { output: i, secondarySymbol: r, spacing: 1 }), f?.showLog === true && p2(), a.splice(1, a.length - 1), a[0].value = "", a[0].full = "";
|
|
3667
|
+
} };
|
|
3668
|
+
};
|
|
3669
|
+
var Ot = (e) => new at({ validate: e.validate, placeholder: e.placeholder, defaultValue: e.defaultValue, initialValue: e.initialValue, output: e.output, signal: e.signal, input: e.input, render() {
|
|
3670
|
+
const i = e?.withGuide ?? u.withGuide, s = `${`${i ? `${(0, import_node_util2.styleText)("gray", d2)}
|
|
3671
|
+
` : ""}${V2(this.state)} `}${e.message}
|
|
3672
|
+
`, r = e.placeholder ? (0, import_node_util2.styleText)("inverse", e.placeholder[0]) + (0, import_node_util2.styleText)("dim", e.placeholder.slice(1)) : (0, import_node_util2.styleText)(["inverse", "hidden"], "_"), u2 = this.userInput ? this.userInputWithCursor : r, n = this.value ?? "";
|
|
3673
|
+
switch (this.state) {
|
|
3674
|
+
case "error": {
|
|
3675
|
+
const o = this.error ? ` ${(0, import_node_util2.styleText)("yellow", this.error)}` : "", c2 = i ? `${(0, import_node_util2.styleText)("yellow", d2)} ` : "", a = i ? (0, import_node_util2.styleText)("yellow", E2) : "";
|
|
3676
|
+
return `${s.trim()}
|
|
3677
|
+
${c2}${u2}
|
|
3678
|
+
${a}${o}
|
|
3679
|
+
`;
|
|
3731
3680
|
}
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
}, i * ARROW_MS);
|
|
3681
|
+
case "submit": {
|
|
3682
|
+
const o = n ? ` ${(0, import_node_util2.styleText)("dim", n)}` : "", c2 = i ? (0, import_node_util2.styleText)("gray", d2) : "";
|
|
3683
|
+
return `${s}${c2}${o}`;
|
|
3736
3684
|
}
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
sendRawPtyInput(text) {
|
|
3748
|
-
if (!this.strategy) {
|
|
3749
|
-
log.trace("claude", "sendRawPtyInput dropped (no strategy)");
|
|
3750
|
-
return;
|
|
3685
|
+
case "cancel": {
|
|
3686
|
+
const o = n ? ` ${(0, import_node_util2.styleText)(["strikethrough", "dim"], n)}` : "", c2 = i ? (0, import_node_util2.styleText)("gray", d2) : "";
|
|
3687
|
+
return `${s}${c2}${o}${n.trim() ? `
|
|
3688
|
+
${c2}` : ""}`;
|
|
3689
|
+
}
|
|
3690
|
+
default: {
|
|
3691
|
+
const o = i ? `${(0, import_node_util2.styleText)("cyan", d2)} ` : "", c2 = i ? (0, import_node_util2.styleText)("cyan", E2) : "";
|
|
3692
|
+
return `${s}${o}${u2}
|
|
3693
|
+
${c2}
|
|
3694
|
+
`;
|
|
3751
3695
|
}
|
|
3752
|
-
log.trace("claude", `sendRawPtyInput len=${text.length}`);
|
|
3753
|
-
this.strategy.write(text);
|
|
3754
3696
|
}
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3697
|
+
} }).prompt();
|
|
3698
|
+
|
|
3699
|
+
// src/services/claude-installer.ts
|
|
3700
|
+
init_types();
|
|
3701
|
+
function probeInstallDirs() {
|
|
3702
|
+
const home = os5.homedir();
|
|
3703
|
+
if (process.platform === "win32") {
|
|
3704
|
+
return [
|
|
3705
|
+
path7.join(home, ".claude", "local"),
|
|
3706
|
+
path7.join(home, "AppData", "Local", "AnthropicClaude"),
|
|
3707
|
+
path7.join(home, "AppData", "Local", "Programs", "AnthropicClaude")
|
|
3708
|
+
];
|
|
3758
3709
|
}
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3710
|
+
return [
|
|
3711
|
+
path7.join(home, ".local", "bin"),
|
|
3712
|
+
path7.join(home, ".claude", "local"),
|
|
3713
|
+
"/usr/local/bin"
|
|
3714
|
+
];
|
|
3715
|
+
}
|
|
3716
|
+
function isAvailable() {
|
|
3717
|
+
return findInPath("claude") !== null || findInPath("claude-code") !== null;
|
|
3718
|
+
}
|
|
3719
|
+
function augmentPath() {
|
|
3720
|
+
const dirs = probeInstallDirs();
|
|
3721
|
+
const sep2 = path7.delimiter;
|
|
3722
|
+
const current = process.env.PATH ?? "";
|
|
3723
|
+
const existing = new Set(current.split(sep2).filter(Boolean));
|
|
3724
|
+
const additions = dirs.filter((d3) => !existing.has(d3));
|
|
3725
|
+
if (additions.length === 0) return;
|
|
3726
|
+
process.env.PATH = additions.join(sep2) + sep2 + current;
|
|
3727
|
+
}
|
|
3728
|
+
function runInstaller() {
|
|
3729
|
+
const isWindows = process.platform === "win32";
|
|
3730
|
+
const cmd = isWindows ? "powershell.exe" : "bash";
|
|
3731
|
+
const args2 = isWindows ? [
|
|
3732
|
+
"-NoProfile",
|
|
3733
|
+
"-ExecutionPolicy",
|
|
3734
|
+
"Bypass",
|
|
3735
|
+
"-Command",
|
|
3736
|
+
"irm https://claude.ai/install.ps1 | iex"
|
|
3737
|
+
] : ["-c", "curl -fsSL https://claude.ai/install.sh | bash"];
|
|
3738
|
+
return new Promise((resolve3) => {
|
|
3739
|
+
const proc = (0, import_child_process4.spawn)(cmd, args2, { stdio: "inherit" });
|
|
3740
|
+
proc.on("error", (err) => {
|
|
3741
|
+
console.error(`
|
|
3742
|
+
\u2717 Installer failed to launch: ${err.message}`);
|
|
3743
|
+
resolve3(false);
|
|
3744
|
+
});
|
|
3745
|
+
proc.on("exit", (code) => {
|
|
3746
|
+
resolve3(code === 0);
|
|
3747
|
+
});
|
|
3748
|
+
});
|
|
3749
|
+
}
|
|
3750
|
+
async function ensureClaudeInstalled() {
|
|
3751
|
+
if (isAvailable()) return true;
|
|
3752
|
+
const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
3753
|
+
if (isInteractive) {
|
|
3754
|
+
const proceed = await ot2({
|
|
3755
|
+
message: "Claude Code is not installed on this machine. Install it now using the official installer?",
|
|
3756
|
+
initialValue: true
|
|
3757
|
+
});
|
|
3758
|
+
if (q(proceed) || proceed === false) return false;
|
|
3759
|
+
} else {
|
|
3760
|
+
console.log("\n Claude Code not found \u2014 running the official installer...\n");
|
|
3762
3761
|
}
|
|
3763
|
-
|
|
3764
|
-
|
|
3762
|
+
console.log();
|
|
3763
|
+
const ok = await runInstaller();
|
|
3764
|
+
if (!ok) {
|
|
3765
|
+
console.error("\n \u2717 Claude Code installation failed. See the installer output above.");
|
|
3766
|
+
return false;
|
|
3765
3767
|
}
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
* For agents that use a post-spawn PTY instruction (e.g. Codex), `resumeLaunchArgs`
|
|
3773
|
-
* returns [] and `postSpawnInstruction` types the resume command into the PTY.
|
|
3774
|
-
*/
|
|
3775
|
-
restart(sessionId, auto = false) {
|
|
3776
|
-
if (!this.strategy) return;
|
|
3777
|
-
const resumeArgs = auto ? this.runtime.resumeLaunchArgs(sessionId) : ["--resume", sessionId];
|
|
3778
|
-
const launch = buildClaudeLaunch(resumeArgs);
|
|
3779
|
-
if (!launch) return;
|
|
3780
|
-
this.strategy.kill();
|
|
3781
|
-
this.strategy.spawn(launch.cmd, this.opts.cwd, launch.args);
|
|
3782
|
-
if (resumeArgs.length === 0 && this.runtime.postSpawnInstruction) {
|
|
3783
|
-
const { ptyInput } = this.runtime.postSpawnInstruction(sessionId);
|
|
3784
|
-
setTimeout(() => {
|
|
3785
|
-
this.strategy?.write(ptyInput);
|
|
3786
|
-
}, 500);
|
|
3787
|
-
}
|
|
3768
|
+
augmentPath();
|
|
3769
|
+
if (!isAvailable()) {
|
|
3770
|
+
console.error(
|
|
3771
|
+
"\n \u26A0 Claude Code installed but the binary is still not on PATH for this process.\n This usually means the installer registered a new directory that only takes\n effect in a fresh shell. Restart your terminal and run `codeam pair` again."
|
|
3772
|
+
);
|
|
3773
|
+
return false;
|
|
3788
3774
|
}
|
|
3789
|
-
|
|
3775
|
+
return true;
|
|
3776
|
+
}
|
|
3790
3777
|
|
|
3791
3778
|
// src/agents/claude/quota.ts
|
|
3792
3779
|
var fs5 = __toESM(require("fs"));
|
|
@@ -4272,8 +4259,19 @@ var ClaudeRuntimeStrategy = class {
|
|
|
4272
4259
|
id = "claude";
|
|
4273
4260
|
meta = getAgent("claude");
|
|
4274
4261
|
async prepareLaunch() {
|
|
4275
|
-
|
|
4276
|
-
if (!launch)
|
|
4262
|
+
let launch = buildClaudeLaunch();
|
|
4263
|
+
if (!launch) {
|
|
4264
|
+
const installed = await ensureClaudeInstalled();
|
|
4265
|
+
if (installed) launch = buildClaudeLaunch();
|
|
4266
|
+
}
|
|
4267
|
+
if (!launch) {
|
|
4268
|
+
const cmd = process.platform === "win32" ? "irm https://claude.ai/install.ps1 | iex" : "curl -fsSL https://claude.ai/install.sh | bash";
|
|
4269
|
+
throw new Error(
|
|
4270
|
+
`Claude Code is required to continue. Install it manually with:
|
|
4271
|
+
${cmd}
|
|
4272
|
+
Then restart your terminal and run \`codeam pair\` again.`
|
|
4273
|
+
);
|
|
4274
|
+
}
|
|
4277
4275
|
return { cmd: launch.cmd, args: launch.args };
|
|
4278
4276
|
}
|
|
4279
4277
|
resumeLaunchArgs(sessionId) {
|
|
@@ -4537,6 +4535,7 @@ var ClaudeDeployStrategy = class {
|
|
|
4537
4535
|
|
|
4538
4536
|
// src/agents/codex/runtime.ts
|
|
4539
4537
|
var import_node_child_process = require("child_process");
|
|
4538
|
+
var path13 = __toESM(require("path"));
|
|
4540
4539
|
init_types();
|
|
4541
4540
|
|
|
4542
4541
|
// src/agents/codex/history.ts
|
|
@@ -5062,16 +5061,26 @@ var CodexRuntimeStrategy = class {
|
|
|
5062
5061
|
id = "codex";
|
|
5063
5062
|
meta = getAgent("codex");
|
|
5064
5063
|
async prepareLaunch() {
|
|
5065
|
-
|
|
5066
|
-
if (
|
|
5064
|
+
let binary = findInPath("codex");
|
|
5065
|
+
if (binary) return { cmd: binary, args: [] };
|
|
5066
|
+
console.log("\n Codex CLI not found \u2014 installing via `npm install -g @openai/codex`...\n");
|
|
5067
|
+
try {
|
|
5067
5068
|
await installCodexViaNpm();
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5069
|
+
} catch (err) {
|
|
5070
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5071
|
+
throw new Error(
|
|
5072
|
+
`Failed to install Codex CLI automatically (${msg}).
|
|
5073
|
+
Install it manually with:
|
|
5074
|
+
npm install -g @openai/codex
|
|
5075
|
+
Then run \`codeam pair\` again.`
|
|
5076
|
+
);
|
|
5077
|
+
}
|
|
5078
|
+
augmentNpmGlobalBin();
|
|
5079
|
+
binary = findInPath("codex");
|
|
5080
|
+
if (!binary) {
|
|
5081
|
+
throw new Error(
|
|
5082
|
+
"Codex CLI was installed but the binary is not visible on PATH for this process.\n Restart your terminal and run `codeam pair` again."
|
|
5083
|
+
);
|
|
5075
5084
|
}
|
|
5076
5085
|
return { cmd: binary, args: [] };
|
|
5077
5086
|
}
|
|
@@ -5141,9 +5150,35 @@ async function installCodexViaNpm() {
|
|
|
5141
5150
|
if (code === 0) resolve3();
|
|
5142
5151
|
else reject(new Error(`npm install -g @openai/codex exited ${code}`));
|
|
5143
5152
|
});
|
|
5144
|
-
proc.on("error",
|
|
5153
|
+
proc.on("error", (err) => {
|
|
5154
|
+
const code = err.code;
|
|
5155
|
+
if (code === "ENOENT") {
|
|
5156
|
+
reject(new Error("`npm` is not on PATH. Install Node.js first, then retry."));
|
|
5157
|
+
} else {
|
|
5158
|
+
reject(err);
|
|
5159
|
+
}
|
|
5160
|
+
});
|
|
5145
5161
|
});
|
|
5146
5162
|
}
|
|
5163
|
+
function augmentNpmGlobalBin() {
|
|
5164
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
5165
|
+
try {
|
|
5166
|
+
const result = (0, import_node_child_process.spawnSync)(npm, ["prefix", "-g"], {
|
|
5167
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5168
|
+
});
|
|
5169
|
+
if (result.status !== 0) return;
|
|
5170
|
+
const prefix = result.stdout.toString().trim();
|
|
5171
|
+
if (!prefix) return;
|
|
5172
|
+
const binDir = process.platform === "win32" ? prefix : path13.join(prefix, "bin");
|
|
5173
|
+
const sep2 = path13.delimiter;
|
|
5174
|
+
const current = process.env.PATH ?? "";
|
|
5175
|
+
const existing = new Set(current.split(sep2).filter(Boolean));
|
|
5176
|
+
if (!existing.has(binDir)) {
|
|
5177
|
+
process.env.PATH = binDir + sep2 + current;
|
|
5178
|
+
}
|
|
5179
|
+
} catch {
|
|
5180
|
+
}
|
|
5181
|
+
}
|
|
5147
5182
|
|
|
5148
5183
|
// src/agents/codex/credentials.ts
|
|
5149
5184
|
var import_node_fs3 = __toESM(require("fs"));
|
|
@@ -5772,7 +5807,7 @@ var OutputService = class _OutputService {
|
|
|
5772
5807
|
|
|
5773
5808
|
// src/services/history.service.ts
|
|
5774
5809
|
var fs11 = __toESM(require("fs"));
|
|
5775
|
-
var
|
|
5810
|
+
var path15 = __toESM(require("path"));
|
|
5776
5811
|
var os12 = __toESM(require("os"));
|
|
5777
5812
|
var https4 = __toESM(require("https"));
|
|
5778
5813
|
var http4 = __toESM(require("http"));
|
|
@@ -5935,7 +5970,7 @@ var HistoryService = class _HistoryService {
|
|
|
5935
5970
|
return this._quotaPercent === null || Date.now() - this._quotaFetchedAt > ttlMs;
|
|
5936
5971
|
}
|
|
5937
5972
|
get projectDir() {
|
|
5938
|
-
return this.runtime.resolveHistoryDir(this.cwd) ??
|
|
5973
|
+
return this.runtime.resolveHistoryDir(this.cwd) ?? path15.join(os12.homedir(), ".claude", "projects", encodeCwd(this.cwd));
|
|
5939
5974
|
}
|
|
5940
5975
|
/** Set the current Claude conversation ID (extracted from /cost command or session start) */
|
|
5941
5976
|
setCurrentConversationId(id) {
|
|
@@ -5947,7 +5982,7 @@ var HistoryService = class _HistoryService {
|
|
|
5947
5982
|
/** Return the current message count in the active conversation. */
|
|
5948
5983
|
getCurrentMessageCount() {
|
|
5949
5984
|
if (!this.currentConversationId) return 0;
|
|
5950
|
-
const filePath =
|
|
5985
|
+
const filePath = path15.join(this.projectDir, `${this.currentConversationId}.jsonl`);
|
|
5951
5986
|
return parseJsonl(filePath).length;
|
|
5952
5987
|
}
|
|
5953
5988
|
/**
|
|
@@ -5958,7 +5993,7 @@ var HistoryService = class _HistoryService {
|
|
|
5958
5993
|
const deadline = Date.now() + timeoutMs;
|
|
5959
5994
|
while (Date.now() < deadline) {
|
|
5960
5995
|
if (!this.currentConversationId) return null;
|
|
5961
|
-
const filePath =
|
|
5996
|
+
const filePath = path15.join(this.projectDir, `${this.currentConversationId}.jsonl`);
|
|
5962
5997
|
const messages = parseJsonl(filePath);
|
|
5963
5998
|
if (messages.length > previousCount) {
|
|
5964
5999
|
for (let i = messages.length - 1; i >= previousCount; i--) {
|
|
@@ -5986,14 +6021,14 @@ var HistoryService = class _HistoryService {
|
|
|
5986
6021
|
try {
|
|
5987
6022
|
const files = fs11.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
|
|
5988
6023
|
try {
|
|
5989
|
-
const stat3 = fs11.statSync(
|
|
6024
|
+
const stat3 = fs11.statSync(path15.join(dir, e.name));
|
|
5990
6025
|
return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
|
|
5991
6026
|
} catch {
|
|
5992
6027
|
return { name: e.name, mtime: 0, birthtime: 0 };
|
|
5993
6028
|
}
|
|
5994
6029
|
}).filter((f) => f.birthtime >= cutoff).sort((a, b) => b.mtime - a.mtime);
|
|
5995
6030
|
if (files.length > 0) {
|
|
5996
|
-
this.currentConversationId =
|
|
6031
|
+
this.currentConversationId = path15.basename(files[0].name, ".jsonl");
|
|
5997
6032
|
}
|
|
5998
6033
|
} catch {
|
|
5999
6034
|
}
|
|
@@ -6033,7 +6068,7 @@ var HistoryService = class _HistoryService {
|
|
|
6033
6068
|
}
|
|
6034
6069
|
const files = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
|
|
6035
6070
|
try {
|
|
6036
|
-
const stat3 = fs11.statSync(
|
|
6071
|
+
const stat3 = fs11.statSync(path15.join(dir, e.name));
|
|
6037
6072
|
return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
|
|
6038
6073
|
} catch {
|
|
6039
6074
|
return { name: e.name, mtime: 0, birthtime: 0 };
|
|
@@ -6042,7 +6077,7 @@ var HistoryService = class _HistoryService {
|
|
|
6042
6077
|
if (files.length === 0) return null;
|
|
6043
6078
|
const targetFile = this.currentConversationId ? `${this.currentConversationId}.jsonl` : files[0].name;
|
|
6044
6079
|
if (!files.some((f) => f.name === targetFile)) return null;
|
|
6045
|
-
return this.extractUsageFromFile(
|
|
6080
|
+
return this.extractUsageFromFile(path15.join(dir, targetFile));
|
|
6046
6081
|
}
|
|
6047
6082
|
extractUsageFromFile(filePath) {
|
|
6048
6083
|
let raw;
|
|
@@ -6094,7 +6129,7 @@ var HistoryService = class _HistoryService {
|
|
|
6094
6129
|
try {
|
|
6095
6130
|
files = fs11.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).filter((f) => {
|
|
6096
6131
|
try {
|
|
6097
|
-
return fs11.statSync(
|
|
6132
|
+
return fs11.statSync(path15.join(projectDir, f)).mtimeMs >= monthStartMs;
|
|
6098
6133
|
} catch {
|
|
6099
6134
|
return false;
|
|
6100
6135
|
}
|
|
@@ -6105,7 +6140,7 @@ var HistoryService = class _HistoryService {
|
|
|
6105
6140
|
for (const file of files) {
|
|
6106
6141
|
let raw;
|
|
6107
6142
|
try {
|
|
6108
|
-
raw = fs11.readFileSync(
|
|
6143
|
+
raw = fs11.readFileSync(path15.join(projectDir, file), "utf8");
|
|
6109
6144
|
} catch {
|
|
6110
6145
|
continue;
|
|
6111
6146
|
}
|
|
@@ -6147,8 +6182,8 @@ var HistoryService = class _HistoryService {
|
|
|
6147
6182
|
const sessions3 = [];
|
|
6148
6183
|
for (const entry of entries) {
|
|
6149
6184
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
6150
|
-
const id =
|
|
6151
|
-
const filePath =
|
|
6185
|
+
const id = path15.basename(entry.name, ".jsonl");
|
|
6186
|
+
const filePath = path15.join(dir, entry.name);
|
|
6152
6187
|
let mtime = Date.now();
|
|
6153
6188
|
try {
|
|
6154
6189
|
mtime = fs11.statSync(filePath).mtimeMs;
|
|
@@ -6189,7 +6224,7 @@ var HistoryService = class _HistoryService {
|
|
|
6189
6224
|
* showing an empty conversation.
|
|
6190
6225
|
*/
|
|
6191
6226
|
async loadConversation(sessionId) {
|
|
6192
|
-
const filePath =
|
|
6227
|
+
const filePath = path15.join(this.projectDir, `${sessionId}.jsonl`);
|
|
6193
6228
|
const messages = parseJsonl(filePath);
|
|
6194
6229
|
if (messages.length === 0) return;
|
|
6195
6230
|
const totalBatches = Math.ceil(messages.length / CONVERSATION_BATCH_SIZE);
|
|
@@ -6232,7 +6267,7 @@ var HistoryService = class _HistoryService {
|
|
|
6232
6267
|
if (!this.currentConversationId) return 0;
|
|
6233
6268
|
}
|
|
6234
6269
|
const sessionId = this.currentConversationId;
|
|
6235
|
-
const filePath =
|
|
6270
|
+
const filePath = path15.join(this.projectDir, `${sessionId}.jsonl`);
|
|
6236
6271
|
const messages = parseJsonl(filePath);
|
|
6237
6272
|
if (messages.length === 0) return 0;
|
|
6238
6273
|
const marker = this.lastUploadedUuid.get(sessionId);
|
|
@@ -6263,7 +6298,7 @@ var HistoryService = class _HistoryService {
|
|
|
6263
6298
|
// src/services/file-watcher.service.ts
|
|
6264
6299
|
var import_child_process7 = require("child_process");
|
|
6265
6300
|
var os13 = __toESM(require("os"));
|
|
6266
|
-
var
|
|
6301
|
+
var path16 = __toESM(require("path"));
|
|
6267
6302
|
|
|
6268
6303
|
// src/services/file-watcher/diff-parser.ts
|
|
6269
6304
|
var HUNK_HEADER_RE = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
|
|
@@ -6587,7 +6622,7 @@ var FileWatcherService = class {
|
|
|
6587
6622
|
}
|
|
6588
6623
|
async emitForFile(absPath, changeType) {
|
|
6589
6624
|
if (this.stopped) return;
|
|
6590
|
-
const relPath =
|
|
6625
|
+
const relPath = path16.relative(this.opts.workingDir, absPath);
|
|
6591
6626
|
if (!relPath || relPath.startsWith("..")) {
|
|
6592
6627
|
return;
|
|
6593
6628
|
}
|
|
@@ -7262,7 +7297,7 @@ function buildKeepAlive(ctx) {
|
|
|
7262
7297
|
// src/commands/start/handlers.ts
|
|
7263
7298
|
var fs14 = __toESM(require("fs"));
|
|
7264
7299
|
var os14 = __toESM(require("os"));
|
|
7265
|
-
var
|
|
7300
|
+
var path20 = __toESM(require("path"));
|
|
7266
7301
|
var import_crypto3 = require("crypto");
|
|
7267
7302
|
var import_child_process11 = require("child_process");
|
|
7268
7303
|
|
|
@@ -7321,7 +7356,7 @@ function parsePayload(schema, raw) {
|
|
|
7321
7356
|
|
|
7322
7357
|
// src/services/file-ops.service.ts
|
|
7323
7358
|
var fs12 = __toESM(require("fs/promises"));
|
|
7324
|
-
var
|
|
7359
|
+
var path17 = __toESM(require("path"));
|
|
7325
7360
|
var MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
7326
7361
|
var MAX_WALK_DEPTH = 6;
|
|
7327
7362
|
var MAX_VISITED_DIRS = 5e3;
|
|
@@ -7356,8 +7391,8 @@ var SUBDIR_IGNORE = /* @__PURE__ */ new Set([
|
|
|
7356
7391
|
"__pycache__"
|
|
7357
7392
|
]);
|
|
7358
7393
|
function isUnder(parent, candidate) {
|
|
7359
|
-
const rel =
|
|
7360
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
7394
|
+
const rel = path17.relative(parent, candidate);
|
|
7395
|
+
return rel === "" || !rel.startsWith("..") && !path17.isAbsolute(rel);
|
|
7361
7396
|
}
|
|
7362
7397
|
async function isExistingFile(absPath) {
|
|
7363
7398
|
try {
|
|
@@ -7380,7 +7415,7 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
|
|
|
7380
7415
|
}
|
|
7381
7416
|
for (const e of entries) {
|
|
7382
7417
|
if (!e.isFile()) continue;
|
|
7383
|
-
const full =
|
|
7418
|
+
const full = path17.join(dir, e.name);
|
|
7384
7419
|
if (needleVariants.some((needle) => full.endsWith(needle))) {
|
|
7385
7420
|
ctx.matches.push(full);
|
|
7386
7421
|
if (ctx.matches.length >= ctx.cap) return;
|
|
@@ -7390,21 +7425,21 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
|
|
|
7390
7425
|
if (!e.isDirectory()) continue;
|
|
7391
7426
|
if (SUBDIR_IGNORE.has(e.name)) continue;
|
|
7392
7427
|
if (e.name.startsWith(".") && SUBDIR_IGNORE.has(e.name)) continue;
|
|
7393
|
-
await walkForSuffix(
|
|
7428
|
+
await walkForSuffix(path17.join(dir, e.name), needleVariants, depth + 1, ctx);
|
|
7394
7429
|
if (ctx.matches.length >= ctx.cap) return;
|
|
7395
7430
|
}
|
|
7396
7431
|
}
|
|
7397
7432
|
async function findFile(rawPath) {
|
|
7398
7433
|
const cwd = process.cwd();
|
|
7399
|
-
if (
|
|
7400
|
-
const abs =
|
|
7434
|
+
if (path17.isAbsolute(rawPath)) {
|
|
7435
|
+
const abs = path17.normalize(rawPath);
|
|
7401
7436
|
if (isUnder(cwd, abs) && await isExistingFile(abs)) return abs;
|
|
7402
7437
|
}
|
|
7403
|
-
const direct =
|
|
7438
|
+
const direct = path17.resolve(cwd, rawPath);
|
|
7404
7439
|
if (isUnder(cwd, direct) && await isExistingFile(direct)) return direct;
|
|
7405
|
-
const normalized =
|
|
7440
|
+
const normalized = path17.normalize(rawPath).replace(/^[./\\]+/, "");
|
|
7406
7441
|
const needles = [
|
|
7407
|
-
`${
|
|
7442
|
+
`${path17.sep}${normalized}`,
|
|
7408
7443
|
`/${normalized}`
|
|
7409
7444
|
].filter((v, i, a) => a.indexOf(v) === i);
|
|
7410
7445
|
const ctx = { visited: 0, matches: [], cap: 16 };
|
|
@@ -7418,7 +7453,7 @@ async function findWriteTarget(rawPath) {
|
|
|
7418
7453
|
const found = await findFile(rawPath);
|
|
7419
7454
|
if (found) return found;
|
|
7420
7455
|
const cwd = process.cwd();
|
|
7421
|
-
const fallback =
|
|
7456
|
+
const fallback = path17.isAbsolute(rawPath) ? path17.normalize(rawPath) : path17.resolve(cwd, rawPath);
|
|
7422
7457
|
if (!isUnder(cwd, fallback)) return null;
|
|
7423
7458
|
return fallback;
|
|
7424
7459
|
}
|
|
@@ -7458,7 +7493,7 @@ async function writeProjectFile(rawPath, content) {
|
|
|
7458
7493
|
if (Buffer.byteLength(content, "utf-8") > MAX_FILE_BYTES) {
|
|
7459
7494
|
return { error: "Content too large." };
|
|
7460
7495
|
}
|
|
7461
|
-
await fs12.mkdir(
|
|
7496
|
+
await fs12.mkdir(path17.dirname(abs), { recursive: true });
|
|
7462
7497
|
await fs12.writeFile(abs, content, "utf-8");
|
|
7463
7498
|
return { ok: true };
|
|
7464
7499
|
} catch (e) {
|
|
@@ -7471,7 +7506,7 @@ async function writeProjectFile(rawPath, content) {
|
|
|
7471
7506
|
var import_child_process9 = require("child_process");
|
|
7472
7507
|
var import_util2 = require("util");
|
|
7473
7508
|
var fs13 = __toESM(require("fs/promises"));
|
|
7474
|
-
var
|
|
7509
|
+
var path18 = __toESM(require("path"));
|
|
7475
7510
|
var execFileP2 = (0, import_util2.promisify)(import_child_process9.execFile);
|
|
7476
7511
|
var PROJECT_IGNORE = /* @__PURE__ */ new Set([
|
|
7477
7512
|
"node_modules",
|
|
@@ -7529,12 +7564,12 @@ async function listProjectFiles(opts = {}) {
|
|
|
7529
7564
|
return;
|
|
7530
7565
|
}
|
|
7531
7566
|
if (PROJECT_IGNORE.has(e.name)) continue;
|
|
7532
|
-
const full =
|
|
7567
|
+
const full = path18.join(dir, e.name);
|
|
7533
7568
|
if (e.isDirectory()) {
|
|
7534
7569
|
if (depth >= 12) continue;
|
|
7535
7570
|
await walk(full, depth + 1);
|
|
7536
7571
|
} else if (e.isFile()) {
|
|
7537
|
-
const rel =
|
|
7572
|
+
const rel = path18.relative(root, full);
|
|
7538
7573
|
if (q2 && !rel.toLowerCase().includes(q2) && !e.name.toLowerCase().includes(q2)) {
|
|
7539
7574
|
continue;
|
|
7540
7575
|
}
|
|
@@ -7642,7 +7677,7 @@ async function gitStatus(cwd) {
|
|
|
7642
7677
|
let hasMergeInProgress = false;
|
|
7643
7678
|
try {
|
|
7644
7679
|
const gitDir = (await git(["rev-parse", "--git-dir"], root)).stdout.trim();
|
|
7645
|
-
const mergeHead =
|
|
7680
|
+
const mergeHead = path18.isAbsolute(gitDir) ? path18.join(gitDir, "MERGE_HEAD") : path18.join(root, gitDir, "MERGE_HEAD");
|
|
7646
7681
|
await fs13.access(mergeHead);
|
|
7647
7682
|
hasMergeInProgress = true;
|
|
7648
7683
|
} catch {
|
|
@@ -7789,7 +7824,7 @@ async function jsSearchFiles(opts, cwd, cap) {
|
|
|
7789
7824
|
}
|
|
7790
7825
|
let content = "";
|
|
7791
7826
|
try {
|
|
7792
|
-
content = await fs13.readFile(
|
|
7827
|
+
content = await fs13.readFile(path18.join(cwd, f.path), "utf8");
|
|
7793
7828
|
} catch {
|
|
7794
7829
|
continue;
|
|
7795
7830
|
}
|
|
@@ -8069,7 +8104,7 @@ function closeTerminal(sessionId) {
|
|
|
8069
8104
|
function saveFilesTemp(files) {
|
|
8070
8105
|
return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
|
|
8071
8106
|
const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
8072
|
-
const tmpPath =
|
|
8107
|
+
const tmpPath = path20.join(os14.tmpdir(), `codeam-${(0, import_crypto3.randomUUID)()}-${safeName}`);
|
|
8073
8108
|
fs14.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
|
|
8074
8109
|
return tmpPath;
|
|
8075
8110
|
});
|
|
@@ -8592,8 +8627,9 @@ async function promptForAgent(initialValue) {
|
|
|
8592
8627
|
// src/commands/pair.ts
|
|
8593
8628
|
async function pair(args2 = []) {
|
|
8594
8629
|
const config = loadCliConfig();
|
|
8630
|
+
const dryRun = args2.includes("--dry-run");
|
|
8595
8631
|
const flagAgent = parseAgentFlag(args2);
|
|
8596
|
-
const agentId = flagAgent ?? await promptForAgent(config.preferredAgent ?? "claude");
|
|
8632
|
+
const agentId = dryRun ? flagAgent ?? config.preferredAgent ?? "claude" : flagAgent ?? await promptForAgent(config.preferredAgent ?? "claude");
|
|
8597
8633
|
showIntro();
|
|
8598
8634
|
const pluginId = (0, import_crypto4.randomUUID)();
|
|
8599
8635
|
const spin = dist_exports.spinner();
|
|
@@ -8605,6 +8641,20 @@ async function pair(args2 = []) {
|
|
|
8605
8641
|
process.exit(1);
|
|
8606
8642
|
}
|
|
8607
8643
|
spin.stop("Got pairing code");
|
|
8644
|
+
if (dryRun) {
|
|
8645
|
+
const codeOk = typeof result.code === "string" && result.code.trim().length > 0;
|
|
8646
|
+
const expiresOk = typeof result.expiresAt === "number" && result.expiresAt > 0;
|
|
8647
|
+
if (!codeOk || !expiresOk) {
|
|
8648
|
+
showError(
|
|
8649
|
+
`Pair dry-run: backend response shape unexpected (codeType=${typeof result.code}, codeEmpty=${!codeOk}, expiresType=${typeof result.expiresAt}, expiresPositive=${expiresOk}).`
|
|
8650
|
+
);
|
|
8651
|
+
process.exit(1);
|
|
8652
|
+
}
|
|
8653
|
+
showSuccess(
|
|
8654
|
+
`Pair dry-run OK \u2014 backend reachable, response shape valid (codeLength=${result.code.length}, expiresAt=${result.expiresAt}, agent=${agentId}).`
|
|
8655
|
+
);
|
|
8656
|
+
process.exit(0);
|
|
8657
|
+
}
|
|
8608
8658
|
showPairingCode(result.code);
|
|
8609
8659
|
console.log(import_picocolors3.default.dim(" Scan the QR code or enter the code in CodeAgent Mobile."));
|
|
8610
8660
|
console.log("");
|
|
@@ -8675,12 +8725,12 @@ function readTokenFromArgs(args2) {
|
|
|
8675
8725
|
}
|
|
8676
8726
|
const fileFlag = args2.find((a) => a.startsWith("--token-file="));
|
|
8677
8727
|
if (fileFlag) {
|
|
8678
|
-
const
|
|
8728
|
+
const path29 = fileFlag.slice("--token-file=".length);
|
|
8679
8729
|
try {
|
|
8680
|
-
const content = fs15.readFileSync(
|
|
8681
|
-
if (content.length === 0) fail(`--token-file ${
|
|
8730
|
+
const content = fs15.readFileSync(path29, "utf8").trim();
|
|
8731
|
+
if (content.length === 0) fail(`--token-file ${path29} is empty`);
|
|
8682
8732
|
try {
|
|
8683
|
-
fs15.unlinkSync(
|
|
8733
|
+
fs15.unlinkSync(path29);
|
|
8684
8734
|
} catch {
|
|
8685
8735
|
}
|
|
8686
8736
|
return content;
|
|
@@ -8854,7 +8904,7 @@ var import_picocolors9 = __toESM(require("picocolors"));
|
|
|
8854
8904
|
var import_child_process12 = require("child_process");
|
|
8855
8905
|
var import_util3 = require("util");
|
|
8856
8906
|
var import_picocolors7 = __toESM(require("picocolors"));
|
|
8857
|
-
var
|
|
8907
|
+
var path21 = __toESM(require("path"));
|
|
8858
8908
|
var execFileP3 = (0, import_util3.promisify)(import_child_process12.execFile);
|
|
8859
8909
|
var MAX_BUFFER = 8 * 1024 * 1024;
|
|
8860
8910
|
function resetStdinForChild() {
|
|
@@ -9343,7 +9393,7 @@ var GitHubCodespacesProvider = class {
|
|
|
9343
9393
|
});
|
|
9344
9394
|
}
|
|
9345
9395
|
async uploadFile(workspaceId, remotePath, contents, options = {}) {
|
|
9346
|
-
const remoteDir =
|
|
9396
|
+
const remoteDir = path21.posix.dirname(remotePath);
|
|
9347
9397
|
const parts = [
|
|
9348
9398
|
`mkdir -p ${shellQuote(remoteDir)}`,
|
|
9349
9399
|
`cat > ${shellQuote(remotePath)}`
|
|
@@ -9413,7 +9463,7 @@ function shellQuote(s) {
|
|
|
9413
9463
|
// src/services/providers/gitpod.ts
|
|
9414
9464
|
var import_child_process13 = require("child_process");
|
|
9415
9465
|
var import_util4 = require("util");
|
|
9416
|
-
var
|
|
9466
|
+
var path22 = __toESM(require("path"));
|
|
9417
9467
|
var import_picocolors8 = __toESM(require("picocolors"));
|
|
9418
9468
|
var execFileP4 = (0, import_util4.promisify)(import_child_process13.execFile);
|
|
9419
9469
|
var MAX_BUFFER2 = 8 * 1024 * 1024;
|
|
@@ -9653,7 +9703,7 @@ var GitpodProvider = class {
|
|
|
9653
9703
|
});
|
|
9654
9704
|
}
|
|
9655
9705
|
async uploadFile(workspaceId, remotePath, contents, options = {}) {
|
|
9656
|
-
const remoteDir =
|
|
9706
|
+
const remoteDir = path22.posix.dirname(remotePath);
|
|
9657
9707
|
const parts = [
|
|
9658
9708
|
`mkdir -p ${shellQuote2(remoteDir)}`,
|
|
9659
9709
|
`cat > ${shellQuote2(remotePath)}`
|
|
@@ -9689,7 +9739,7 @@ function shellQuote2(s) {
|
|
|
9689
9739
|
// src/services/providers/gitlab-workspaces.ts
|
|
9690
9740
|
var import_child_process14 = require("child_process");
|
|
9691
9741
|
var import_util5 = require("util");
|
|
9692
|
-
var
|
|
9742
|
+
var path23 = __toESM(require("path"));
|
|
9693
9743
|
var execFileP5 = (0, import_util5.promisify)(import_child_process14.execFile);
|
|
9694
9744
|
var MAX_BUFFER3 = 8 * 1024 * 1024;
|
|
9695
9745
|
var GITLAB_API_BASE = process.env.CODEAM_GITLAB_API_URL ?? "https://gitlab.com/api/v4";
|
|
@@ -9949,7 +9999,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
|
|
|
9949
9999
|
}
|
|
9950
10000
|
async uploadFile(workspaceId, remotePath, contents, options = {}) {
|
|
9951
10001
|
const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
|
|
9952
|
-
const remoteDir =
|
|
10002
|
+
const remoteDir = path23.posix.dirname(remotePath);
|
|
9953
10003
|
const parts = [`mkdir -p ${shellQuote3(remoteDir)}`, `cat > ${shellQuote3(remotePath)}`];
|
|
9954
10004
|
if (options.mode != null) {
|
|
9955
10005
|
parts.push(`chmod ${options.mode.toString(8)} ${shellQuote3(remotePath)}`);
|
|
@@ -10017,7 +10067,7 @@ function shellQuote3(s) {
|
|
|
10017
10067
|
// src/services/providers/railway.ts
|
|
10018
10068
|
var import_child_process15 = require("child_process");
|
|
10019
10069
|
var import_util6 = require("util");
|
|
10020
|
-
var
|
|
10070
|
+
var path24 = __toESM(require("path"));
|
|
10021
10071
|
var execFileP6 = (0, import_util6.promisify)(import_child_process15.execFile);
|
|
10022
10072
|
var MAX_BUFFER4 = 8 * 1024 * 1024;
|
|
10023
10073
|
function resetStdinForChild4() {
|
|
@@ -10253,7 +10303,7 @@ var RailwayProvider = class {
|
|
|
10253
10303
|
if (!projectId || !serviceId) {
|
|
10254
10304
|
throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
|
|
10255
10305
|
}
|
|
10256
|
-
const remoteDir =
|
|
10306
|
+
const remoteDir = path24.posix.dirname(remotePath);
|
|
10257
10307
|
const parts = [`mkdir -p ${shellQuote4(remoteDir)}`, `cat > ${shellQuote4(remotePath)}`];
|
|
10258
10308
|
if (options.mode != null) {
|
|
10259
10309
|
parts.push(`chmod ${options.mode.toString(8)} ${shellQuote4(remotePath)}`);
|
|
@@ -10796,7 +10846,7 @@ async function stopWorkspaceFromLocal(target) {
|
|
|
10796
10846
|
var import_node_child_process3 = require("child_process");
|
|
10797
10847
|
var import_node_crypto = require("crypto");
|
|
10798
10848
|
var fs18 = __toESM(require("fs"));
|
|
10799
|
-
var
|
|
10849
|
+
var path27 = __toESM(require("path"));
|
|
10800
10850
|
var import_chokidar = __toESM(require("chokidar"));
|
|
10801
10851
|
var import_picocolors11 = __toESM(require("picocolors"));
|
|
10802
10852
|
|
|
@@ -10804,7 +10854,7 @@ var import_picocolors11 = __toESM(require("picocolors"));
|
|
|
10804
10854
|
var import_node_child_process2 = require("child_process");
|
|
10805
10855
|
var fs16 = __toESM(require("fs"));
|
|
10806
10856
|
var os16 = __toESM(require("os"));
|
|
10807
|
-
var
|
|
10857
|
+
var path25 = __toESM(require("path"));
|
|
10808
10858
|
var import_node_util3 = require("util");
|
|
10809
10859
|
var execFileP7 = (0, import_node_util3.promisify)(import_node_child_process2.execFile);
|
|
10810
10860
|
var KEYCHAIN_SERVICE_NAMES = [
|
|
@@ -10816,8 +10866,8 @@ var KEYCHAIN_SERVICE_NAMES = [
|
|
|
10816
10866
|
function claudeCredentialsPaths() {
|
|
10817
10867
|
const home = os16.homedir();
|
|
10818
10868
|
return [
|
|
10819
|
-
|
|
10820
|
-
|
|
10869
|
+
path25.join(home, ".claude", ".credentials.json"),
|
|
10870
|
+
path25.join(home, ".config", "claude", ".credentials.json")
|
|
10821
10871
|
];
|
|
10822
10872
|
}
|
|
10823
10873
|
async function extractLocalClaudeToken() {
|
|
@@ -10850,9 +10900,9 @@ async function extractLocalClaudeToken() {
|
|
|
10850
10900
|
// src/agents/codex/local-token.ts
|
|
10851
10901
|
var fs17 = __toESM(require("fs"));
|
|
10852
10902
|
var os17 = __toESM(require("os"));
|
|
10853
|
-
var
|
|
10903
|
+
var path26 = __toESM(require("path"));
|
|
10854
10904
|
function codexCredentialsPath() {
|
|
10855
|
-
return
|
|
10905
|
+
return path26.join(os17.homedir(), ".codex", "auth.json");
|
|
10856
10906
|
}
|
|
10857
10907
|
async function extractLocalCodexToken() {
|
|
10858
10908
|
const file = codexCredentialsPath();
|
|
@@ -10920,11 +10970,12 @@ function parseLinkArgs(args2) {
|
|
|
10920
10970
|
);
|
|
10921
10971
|
}
|
|
10922
10972
|
const reuseExisting = args2.includes("--reuse-existing");
|
|
10973
|
+
const dryRun = args2.includes("--dry-run");
|
|
10923
10974
|
const apiKeyArg = args2.find((a) => a.startsWith("--api-key="));
|
|
10924
10975
|
const apiKey = apiKeyArg ? apiKeyArg.slice("--api-key=".length) : null;
|
|
10925
10976
|
const tokenFileArg = args2.find((a) => a.startsWith("--token-file="));
|
|
10926
10977
|
const tokenFile = tokenFileArg ? tokenFileArg.slice("--token-file=".length) : null;
|
|
10927
|
-
return { agent: normalised, reuseExisting, apiKey, tokenFile };
|
|
10978
|
+
return { agent: normalised, reuseExisting, apiKey, tokenFile, dryRun };
|
|
10928
10979
|
}
|
|
10929
10980
|
async function link(args2 = []) {
|
|
10930
10981
|
const parsed = parseLinkArgs(args2);
|
|
@@ -10934,6 +10985,10 @@ async function link(args2 = []) {
|
|
|
10934
10985
|
import_picocolors11.default.bold(` Link ${meta.displayName}`) + import_picocolors11.default.dim(` \xB7 ${meta.vendor}`)
|
|
10935
10986
|
);
|
|
10936
10987
|
console.log("");
|
|
10988
|
+
if (parsed.dryRun) {
|
|
10989
|
+
await linkDryRunPreflight(meta);
|
|
10990
|
+
return;
|
|
10991
|
+
}
|
|
10937
10992
|
const pluginId = (0, import_node_crypto.randomUUID)();
|
|
10938
10993
|
const spin = dist_exports.spinner();
|
|
10939
10994
|
spin.start("Requesting pairing code...");
|
|
@@ -11001,7 +11056,7 @@ async function link(args2 = []) {
|
|
|
11001
11056
|
return;
|
|
11002
11057
|
}
|
|
11003
11058
|
if (parsed.tokenFile) {
|
|
11004
|
-
const credential = fs18.readFileSync(
|
|
11059
|
+
const credential = fs18.readFileSync(path27.resolve(parsed.tokenFile), "utf8").trim();
|
|
11005
11060
|
if (!credential) {
|
|
11006
11061
|
showError(`--token-file ${parsed.tokenFile} is empty.`);
|
|
11007
11062
|
process.exit(1);
|
|
@@ -11153,11 +11208,42 @@ async function uploadAndSucceed(meta, paired, pluginId, token) {
|
|
|
11153
11208
|
);
|
|
11154
11209
|
console.log("");
|
|
11155
11210
|
}
|
|
11211
|
+
async function linkDryRunPreflight(meta) {
|
|
11212
|
+
const spin = dist_exports.spinner();
|
|
11213
|
+
spin.start(`Probing ${meta.publicId} link endpoint...`);
|
|
11214
|
+
const result = await postLinkCredential({
|
|
11215
|
+
agentId: meta.publicId,
|
|
11216
|
+
sessionId: "dryrun-session",
|
|
11217
|
+
pluginId: "dryrun-plugin",
|
|
11218
|
+
pluginAuthToken: "dryrun-token",
|
|
11219
|
+
method: "oauth",
|
|
11220
|
+
credential: "dryrun-credential"
|
|
11221
|
+
});
|
|
11222
|
+
if (result.ok) {
|
|
11223
|
+
spin.stop("Unexpected 2xx");
|
|
11224
|
+
showError(
|
|
11225
|
+
"Link dry-run: backend accepted a stub credential (2xx). PluginAuthGuard appears to be disabled \u2014 investigate api-v2."
|
|
11226
|
+
);
|
|
11227
|
+
process.exit(1);
|
|
11228
|
+
}
|
|
11229
|
+
if (result.status === 401) {
|
|
11230
|
+
spin.stop("Endpoint OK");
|
|
11231
|
+
showSuccess(
|
|
11232
|
+
`Link dry-run OK \u2014 /api/plugin/agents/${meta.publicId}/link reachable and auth-gated (401 as expected).`
|
|
11233
|
+
);
|
|
11234
|
+
process.exit(0);
|
|
11235
|
+
}
|
|
11236
|
+
spin.stop("Failed");
|
|
11237
|
+
showError(
|
|
11238
|
+
`Link dry-run: unexpected response from /api/plugin/agents/${meta.publicId}/link (status=${result.status}, message=${result.message}). Expected 401.`
|
|
11239
|
+
);
|
|
11240
|
+
process.exit(1);
|
|
11241
|
+
}
|
|
11156
11242
|
|
|
11157
11243
|
// src/commands/version.ts
|
|
11158
11244
|
var import_picocolors12 = __toESM(require("picocolors"));
|
|
11159
11245
|
function version() {
|
|
11160
|
-
const v = true ? "2.
|
|
11246
|
+
const v = true ? "2.17.1" : "unknown";
|
|
11161
11247
|
console.log(`${import_picocolors12.default.bold("codeam-cli")} ${import_picocolors12.default.cyan(v)}`);
|
|
11162
11248
|
}
|
|
11163
11249
|
|
|
@@ -11198,19 +11284,81 @@ function help() {
|
|
|
11198
11284
|
process.stdout.write(lines.join("\n") + "\n");
|
|
11199
11285
|
}
|
|
11200
11286
|
|
|
11287
|
+
// src/commands/subcommand-help.ts
|
|
11288
|
+
var import_picocolors14 = __toESM(require("picocolors"));
|
|
11289
|
+
var HELPS = {
|
|
11290
|
+
pair: () => print([
|
|
11291
|
+
` ${import_picocolors14.default.bold("codeam pair")} ${import_picocolors14.default.dim("\u2014 pair a mobile device with this CLI")}`,
|
|
11292
|
+
"",
|
|
11293
|
+
` ${import_picocolors14.default.cyan("codeam pair")} ${import_picocolors14.default.dim("interactive pairing (prompts for the agent)")}`,
|
|
11294
|
+
` ${import_picocolors14.default.cyan("codeam pair --agent <id>")} ${import_picocolors14.default.dim("pair non-interactively (agent: claude | codex)")}`,
|
|
11295
|
+
` ${import_picocolors14.default.cyan("codeam pair --dry-run")} ${import_picocolors14.default.dim("request a pairing code, validate the response, exit")}`
|
|
11296
|
+
]),
|
|
11297
|
+
"pair-auto": () => print([
|
|
11298
|
+
` ${import_picocolors14.default.bold("codeam pair-auto")} ${import_picocolors14.default.dim("\u2014 non-interactive variant of pair for scripted setups")}`,
|
|
11299
|
+
"",
|
|
11300
|
+
` ${import_picocolors14.default.cyan("codeam pair-auto --agent <id>")} ${import_picocolors14.default.dim("pair using the supplied agent id; exit on success or timeout")}`
|
|
11301
|
+
]),
|
|
11302
|
+
link: () => print([
|
|
11303
|
+
` ${import_picocolors14.default.bold("codeam link <agent>")} ${import_picocolors14.default.dim("\u2014 upload a local agent token (Claude or Codex) to your vault")}`,
|
|
11304
|
+
"",
|
|
11305
|
+
` ${import_picocolors14.default.cyan("codeam link claude")}`,
|
|
11306
|
+
` ${import_picocolors14.default.cyan("codeam link codex")}`,
|
|
11307
|
+
"",
|
|
11308
|
+
` ${import_picocolors14.default.white("--api-key=<key>")} ${import_picocolors14.default.dim("paste an API key directly (skip the local auth flow)")}`,
|
|
11309
|
+
` ${import_picocolors14.default.white("--reuse-existing")} ${import_picocolors14.default.dim("upload existing creds without re-launching the agent login")}`,
|
|
11310
|
+
` ${import_picocolors14.default.white("--token-file=<path>")} ${import_picocolors14.default.dim("manual credential blob path for unusual vendor locations")}`,
|
|
11311
|
+
` ${import_picocolors14.default.white("--dry-run")} ${import_picocolors14.default.dim("probe the /api/plugin/agents/<agent>/link endpoint and exit")}`
|
|
11312
|
+
]),
|
|
11313
|
+
sessions: () => print([
|
|
11314
|
+
` ${import_picocolors14.default.bold("codeam sessions")} ${import_picocolors14.default.dim("\u2014 list, switch, or delete paired mobile sessions")}`,
|
|
11315
|
+
"",
|
|
11316
|
+
` ${import_picocolors14.default.cyan("codeam sessions")} ${import_picocolors14.default.dim("list all paired sessions on this machine")}`,
|
|
11317
|
+
` ${import_picocolors14.default.cyan("codeam sessions switch")} ${import_picocolors14.default.dim("interactively switch the active session")}`,
|
|
11318
|
+
` ${import_picocolors14.default.cyan("codeam sessions delete <id>")} ${import_picocolors14.default.dim("remove a specific paired session")}`
|
|
11319
|
+
]),
|
|
11320
|
+
deploy: () => print([
|
|
11321
|
+
` ${import_picocolors14.default.bold("codeam deploy")} ${import_picocolors14.default.dim("\u2014 provision a cloud workspace (GitHub Codespaces) and pair it")}`,
|
|
11322
|
+
"",
|
|
11323
|
+
` ${import_picocolors14.default.cyan("codeam deploy")} ${import_picocolors14.default.dim("start a new deploy (prompts for repo + agent)")}`,
|
|
11324
|
+
` ${import_picocolors14.default.cyan("codeam deploy ls | list")} ${import_picocolors14.default.dim("list deployed cloud workspaces")}`,
|
|
11325
|
+
` ${import_picocolors14.default.cyan("codeam deploy stop | remove")} ${import_picocolors14.default.dim("pick a workspace and stop its codeam-pair session")}`
|
|
11326
|
+
]),
|
|
11327
|
+
status: () => print([
|
|
11328
|
+
` ${import_picocolors14.default.bold("codeam status")} ${import_picocolors14.default.dim("\u2014 show the active session, agent, and connection info")}`
|
|
11329
|
+
]),
|
|
11330
|
+
logout: () => print([
|
|
11331
|
+
` ${import_picocolors14.default.bold("codeam logout")} ${import_picocolors14.default.dim("\u2014 remove every paired session from this machine")}`
|
|
11332
|
+
])
|
|
11333
|
+
};
|
|
11334
|
+
function print(lines) {
|
|
11335
|
+
process.stdout.write(["", ...lines, ""].join("\n") + "\n");
|
|
11336
|
+
}
|
|
11337
|
+
function isHelpFlag(arg) {
|
|
11338
|
+
return arg === "--help" || arg === "-h";
|
|
11339
|
+
}
|
|
11340
|
+
function tryShowSubcommandHelp(cmd, args2) {
|
|
11341
|
+
if (!isHelpFlag(args2[0])) return false;
|
|
11342
|
+
const renderer = HELPS[cmd];
|
|
11343
|
+
if (!renderer) return false;
|
|
11344
|
+
renderer();
|
|
11345
|
+
return true;
|
|
11346
|
+
}
|
|
11347
|
+
var _subcommandHelpKeys = Object.keys(HELPS);
|
|
11348
|
+
|
|
11201
11349
|
// src/lib/updateNotifier.ts
|
|
11202
11350
|
var fs19 = __toESM(require("fs"));
|
|
11203
11351
|
var os18 = __toESM(require("os"));
|
|
11204
|
-
var
|
|
11352
|
+
var path28 = __toESM(require("path"));
|
|
11205
11353
|
var https7 = __toESM(require("https"));
|
|
11206
|
-
var
|
|
11354
|
+
var import_picocolors15 = __toESM(require("picocolors"));
|
|
11207
11355
|
var PKG_NAME = "codeam-cli";
|
|
11208
11356
|
var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
|
|
11209
11357
|
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
11210
11358
|
var REQUEST_TIMEOUT_MS = 1500;
|
|
11211
11359
|
function cachePath() {
|
|
11212
|
-
const dir =
|
|
11213
|
-
return
|
|
11360
|
+
const dir = path28.join(os18.homedir(), ".codeam");
|
|
11361
|
+
return path28.join(dir, "update-check.json");
|
|
11214
11362
|
}
|
|
11215
11363
|
function readCache() {
|
|
11216
11364
|
try {
|
|
@@ -11225,7 +11373,7 @@ function readCache() {
|
|
|
11225
11373
|
function writeCache(cache) {
|
|
11226
11374
|
try {
|
|
11227
11375
|
const file = cachePath();
|
|
11228
|
-
fs19.mkdirSync(
|
|
11376
|
+
fs19.mkdirSync(path28.dirname(file), { recursive: true });
|
|
11229
11377
|
fs19.writeFileSync(file, JSON.stringify(cache));
|
|
11230
11378
|
} catch {
|
|
11231
11379
|
}
|
|
@@ -11282,11 +11430,11 @@ function fetchLatest() {
|
|
|
11282
11430
|
}
|
|
11283
11431
|
function notifyIfStale(currentVersion, latest) {
|
|
11284
11432
|
if (compareSemver(latest, currentVersion) <= 0) return;
|
|
11285
|
-
const arrow =
|
|
11286
|
-
const cmd =
|
|
11433
|
+
const arrow = import_picocolors15.default.dim("\u2192");
|
|
11434
|
+
const cmd = import_picocolors15.default.cyan("npm install -g codeam-cli");
|
|
11287
11435
|
const lines = [
|
|
11288
11436
|
"",
|
|
11289
|
-
` ${
|
|
11437
|
+
` ${import_picocolors15.default.yellow("\u25CF")} ${import_picocolors15.default.bold("Update available")} ${import_picocolors15.default.dim(currentVersion)} ${arrow} ${import_picocolors15.default.green(latest)}`,
|
|
11290
11438
|
` Run ${cmd} to upgrade.`,
|
|
11291
11439
|
""
|
|
11292
11440
|
];
|
|
@@ -11297,7 +11445,7 @@ function checkForUpdates() {
|
|
|
11297
11445
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
11298
11446
|
if (process.env.CI) return;
|
|
11299
11447
|
if (!process.stdout.isTTY) return;
|
|
11300
|
-
const current = true ? "2.
|
|
11448
|
+
const current = true ? "2.17.1" : null;
|
|
11301
11449
|
if (!current) return;
|
|
11302
11450
|
const cache = readCache();
|
|
11303
11451
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -11316,6 +11464,9 @@ var [, , command, ...args] = process.argv;
|
|
|
11316
11464
|
async function main() {
|
|
11317
11465
|
const isMetaCommand = command === "--version" || command === "-v" || command === "version" || command === "--help" || command === "-h" || command === "help";
|
|
11318
11466
|
if (!isMetaCommand) checkForUpdates();
|
|
11467
|
+
if (typeof command === "string" && tryShowSubcommandHelp(command, args)) {
|
|
11468
|
+
return;
|
|
11469
|
+
}
|
|
11319
11470
|
switch (command) {
|
|
11320
11471
|
case "--version":
|
|
11321
11472
|
case "-v":
|