codeam-cli 2.15.5 → 2.15.6
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 +11 -0
- package/dist/index.js +544 -70
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ All notable changes to `codeam-cli` are documented here.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.15.5] — 2026-05-20
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **shared:** Add FileChangedEvent + PendingReviewHunkEvent wire types
|
|
12
|
+
- **cli:** Emit file-change + review-hunk events during paired sessions
|
|
13
|
+
|
|
14
|
+
### CI
|
|
15
|
+
|
|
16
|
+
- **release:** Make CLI npm publish idempotent
|
|
17
|
+
|
|
7
18
|
## [2.15.1] — 2026-05-17
|
|
8
19
|
|
|
9
20
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -386,7 +386,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
|
386
386
|
// package.json
|
|
387
387
|
var package_default = {
|
|
388
388
|
name: "codeam-cli",
|
|
389
|
-
version: "2.15.
|
|
389
|
+
version: "2.15.6",
|
|
390
390
|
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.",
|
|
391
391
|
type: "commonjs",
|
|
392
392
|
main: "dist/index.js",
|
|
@@ -521,6 +521,34 @@ function vercelBypassHeader() {
|
|
|
521
521
|
return token ? { "x-vercel-protection-bypass": token } : {};
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
+
// src/lib/git-branch.ts
|
|
525
|
+
var import_child_process = require("child_process");
|
|
526
|
+
function detectCurrentBranch(cwd = process.cwd()) {
|
|
527
|
+
try {
|
|
528
|
+
const raw = _execSeam.exec("git branch --show-current", {
|
|
529
|
+
cwd,
|
|
530
|
+
// 1 s ceiling is comfortably above normal git latency (<50 ms
|
|
531
|
+
// on a healthy repo) and well below the pair POST's 10 s budget.
|
|
532
|
+
timeout: 1e3,
|
|
533
|
+
// Swallow stderr — non-git directories print "fatal: not a git
|
|
534
|
+
// repository" to stderr and we don't want that on the CLI's
|
|
535
|
+
// own stderr while pairing.
|
|
536
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
537
|
+
encoding: "utf8"
|
|
538
|
+
});
|
|
539
|
+
const trimmed = raw.trim();
|
|
540
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
541
|
+
} catch {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
var _execSeam = {
|
|
546
|
+
exec: (cmd, opts) => {
|
|
547
|
+
const out = (0, import_child_process.execSync)(cmd, opts);
|
|
548
|
+
return typeof out === "string" ? out : out.toString("utf8");
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
524
552
|
// src/lib/poll-delay.ts
|
|
525
553
|
var MAX_DELAY_MS = 3e4;
|
|
526
554
|
function computePollDelay({ baseMs, failures }) {
|
|
@@ -535,12 +563,14 @@ async function requestCode(pluginId) {
|
|
|
535
563
|
try {
|
|
536
564
|
const runtime = process.env.CODESPACES === "true" ? "github-codespaces" : "local";
|
|
537
565
|
const codespaceName = process.env.CODESPACE_NAME;
|
|
566
|
+
const branch = detectCurrentBranch();
|
|
538
567
|
const result = await _transport.postJson(`${API_BASE}/api/pairing/code`, {
|
|
539
568
|
pluginId,
|
|
540
569
|
ideName: "Terminal (codeam-cli)",
|
|
541
570
|
ideVersion: package_default.version,
|
|
542
571
|
hostname: os2.hostname(),
|
|
543
572
|
runtime,
|
|
573
|
+
branch,
|
|
544
574
|
...codespaceName ? { codespaceName } : {}
|
|
545
575
|
});
|
|
546
576
|
const data = result?.data;
|
|
@@ -983,7 +1013,7 @@ var CommandRelayService = class {
|
|
|
983
1013
|
};
|
|
984
1014
|
|
|
985
1015
|
// src/services/pty/unix.strategy.ts
|
|
986
|
-
var
|
|
1016
|
+
var import_child_process2 = require("child_process");
|
|
987
1017
|
var fs4 = __toESM(require("fs"));
|
|
988
1018
|
var os4 = __toESM(require("os"));
|
|
989
1019
|
var path4 = __toESM(require("path"));
|
|
@@ -1087,7 +1117,7 @@ var UnixPtyStrategy = class {
|
|
|
1087
1117
|
const rows = process.stdout.rows || 50;
|
|
1088
1118
|
this.helperPath = path4.join(os4.tmpdir(), "codeam-pty-helper.py");
|
|
1089
1119
|
fs4.writeFileSync(this.helperPath, PYTHON_PTY_HELPER, { mode: 420 });
|
|
1090
|
-
this.proc = (0,
|
|
1120
|
+
this.proc = (0, import_child_process2.spawn)(python, [this.helperPath, cmd, ...args2], {
|
|
1091
1121
|
stdio: ["pipe", "pipe", "inherit"],
|
|
1092
1122
|
cwd,
|
|
1093
1123
|
env: {
|
|
@@ -1152,7 +1182,7 @@ var UnixPtyStrategy = class {
|
|
|
1152
1182
|
* are NOT interpreted by /bin/sh.
|
|
1153
1183
|
*/
|
|
1154
1184
|
spawnDirect(cmd, cwd, args2 = []) {
|
|
1155
|
-
this.proc = (0,
|
|
1185
|
+
this.proc = (0, import_child_process2.spawn)(cmd, args2, {
|
|
1156
1186
|
stdio: ["pipe", "inherit", "inherit"],
|
|
1157
1187
|
cwd,
|
|
1158
1188
|
env: process.env,
|
|
@@ -1224,7 +1254,7 @@ var UnixPtyStrategy = class {
|
|
|
1224
1254
|
};
|
|
1225
1255
|
|
|
1226
1256
|
// src/services/pty/windows.strategy.ts
|
|
1227
|
-
var
|
|
1257
|
+
var import_child_process3 = require("child_process");
|
|
1228
1258
|
var WindowsPtyStrategy = class {
|
|
1229
1259
|
constructor(opts) {
|
|
1230
1260
|
this.opts = opts;
|
|
@@ -1232,7 +1262,7 @@ var WindowsPtyStrategy = class {
|
|
|
1232
1262
|
opts;
|
|
1233
1263
|
proc = null;
|
|
1234
1264
|
spawn(cmd, cwd, args2 = []) {
|
|
1235
|
-
this.proc = (0,
|
|
1265
|
+
this.proc = (0, import_child_process3.spawn)(cmd, args2, {
|
|
1236
1266
|
stdio: ["pipe", "pipe", "inherit"],
|
|
1237
1267
|
cwd,
|
|
1238
1268
|
env: {
|
|
@@ -1408,7 +1438,7 @@ var WindowsConPtyStrategy = class _WindowsConPtyStrategy {
|
|
|
1408
1438
|
};
|
|
1409
1439
|
|
|
1410
1440
|
// src/services/claude-installer.ts
|
|
1411
|
-
var
|
|
1441
|
+
var import_child_process4 = require("child_process");
|
|
1412
1442
|
var path6 = __toESM(require("path"));
|
|
1413
1443
|
var os5 = __toESM(require("os"));
|
|
1414
1444
|
|
|
@@ -3361,7 +3391,7 @@ function runInstaller() {
|
|
|
3361
3391
|
"irm https://claude.ai/install.ps1 | iex"
|
|
3362
3392
|
] : ["-c", "curl -fsSL https://claude.ai/install.sh | bash"];
|
|
3363
3393
|
return new Promise((resolve2) => {
|
|
3364
|
-
const proc = (0,
|
|
3394
|
+
const proc = (0, import_child_process4.spawn)(cmd, args2, { stdio: "inherit" });
|
|
3365
3395
|
proc.on("error", (err) => {
|
|
3366
3396
|
console.error(`
|
|
3367
3397
|
\u2717 Installer failed to launch: ${err.message}`);
|
|
@@ -3660,7 +3690,7 @@ var AgentService = class {
|
|
|
3660
3690
|
var fs5 = __toESM(require("fs"));
|
|
3661
3691
|
var os6 = __toESM(require("os"));
|
|
3662
3692
|
var path8 = __toESM(require("path"));
|
|
3663
|
-
var
|
|
3693
|
+
var import_child_process5 = require("child_process");
|
|
3664
3694
|
var HELPER_SCRIPT = `import os,pty,sys,select,signal,struct,fcntl,termios,errno
|
|
3665
3695
|
m,s=pty.openpty()
|
|
3666
3696
|
try:
|
|
@@ -3729,7 +3759,7 @@ async function fetchClaudeQuota() {
|
|
|
3729
3759
|
resolve2(null);
|
|
3730
3760
|
return;
|
|
3731
3761
|
}
|
|
3732
|
-
const proc = (0,
|
|
3762
|
+
const proc = (0, import_child_process5.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
|
|
3733
3763
|
stdio: ["pipe", "pipe", "ignore"],
|
|
3734
3764
|
cwd: process.cwd(),
|
|
3735
3765
|
env: { ...process.env, TERM: "dumb", COLUMNS: "120", LINES: "30" }
|
|
@@ -4192,12 +4222,12 @@ var os9 = __toESM(require("os"));
|
|
|
4192
4222
|
var path11 = __toESM(require("path"));
|
|
4193
4223
|
|
|
4194
4224
|
// src/agents/claude/credentials.ts
|
|
4195
|
-
var
|
|
4225
|
+
var import_child_process6 = require("child_process");
|
|
4196
4226
|
var fs7 = __toESM(require("fs"));
|
|
4197
4227
|
var os8 = __toESM(require("os"));
|
|
4198
4228
|
var path10 = __toESM(require("path"));
|
|
4199
4229
|
var import_util = require("util");
|
|
4200
|
-
var execFileP = (0, import_util.promisify)(
|
|
4230
|
+
var execFileP = (0, import_util.promisify)(import_child_process6.execFile);
|
|
4201
4231
|
async function detectLocalClaudeCredentials() {
|
|
4202
4232
|
const localClaudeDir = path10.join(os8.homedir(), ".claude");
|
|
4203
4233
|
const flat = path10.join(localClaudeDir, ".credentials.json");
|
|
@@ -6127,7 +6157,7 @@ var HistoryService = class _HistoryService {
|
|
|
6127
6157
|
};
|
|
6128
6158
|
|
|
6129
6159
|
// src/services/file-watcher.service.ts
|
|
6130
|
-
var
|
|
6160
|
+
var import_child_process7 = require("child_process");
|
|
6131
6161
|
var path15 = __toESM(require("path"));
|
|
6132
6162
|
|
|
6133
6163
|
// src/services/file-watcher/diff-parser.ts
|
|
@@ -6552,7 +6582,7 @@ async function _runGitImpl(cwd, args2, opts = {}) {
|
|
|
6552
6582
|
return new Promise((resolve2) => {
|
|
6553
6583
|
let proc;
|
|
6554
6584
|
try {
|
|
6555
|
-
proc = (0,
|
|
6585
|
+
proc = (0, import_child_process7.spawn)("git", args2, { cwd, env: process.env });
|
|
6556
6586
|
} catch {
|
|
6557
6587
|
resolve2(null);
|
|
6558
6588
|
return;
|
|
@@ -6580,6 +6610,432 @@ function _runGit(cwd, args2, opts = {}) {
|
|
|
6580
6610
|
return _gitSeam.run(cwd, args2, opts);
|
|
6581
6611
|
}
|
|
6582
6612
|
|
|
6613
|
+
// src/services/streaming-emitter.service.ts
|
|
6614
|
+
var import_crypto = require("crypto");
|
|
6615
|
+
|
|
6616
|
+
// src/services/streaming/transport.ts
|
|
6617
|
+
var http6 = __toESM(require("http"));
|
|
6618
|
+
var https6 = __toESM(require("https"));
|
|
6619
|
+
var _transport4 = {
|
|
6620
|
+
post: _post3,
|
|
6621
|
+
get: _get
|
|
6622
|
+
};
|
|
6623
|
+
function _post3(url, headers, payload) {
|
|
6624
|
+
return new Promise((resolve2, reject) => {
|
|
6625
|
+
let settled = false;
|
|
6626
|
+
const u2 = new URL(url);
|
|
6627
|
+
const lib = u2.protocol === "https:" ? https6 : http6;
|
|
6628
|
+
const req = lib.request(
|
|
6629
|
+
{
|
|
6630
|
+
hostname: u2.hostname,
|
|
6631
|
+
port: u2.port || (u2.protocol === "https:" ? 443 : 80),
|
|
6632
|
+
path: u2.pathname + u2.search,
|
|
6633
|
+
method: "POST",
|
|
6634
|
+
headers: {
|
|
6635
|
+
...headers,
|
|
6636
|
+
...vercelBypassHeader(),
|
|
6637
|
+
"Content-Length": Buffer.byteLength(payload)
|
|
6638
|
+
},
|
|
6639
|
+
timeout: 8e3
|
|
6640
|
+
},
|
|
6641
|
+
(res) => {
|
|
6642
|
+
let body = "";
|
|
6643
|
+
res.on("data", (c2) => {
|
|
6644
|
+
body += c2.toString();
|
|
6645
|
+
});
|
|
6646
|
+
res.on("end", () => {
|
|
6647
|
+
if (settled) return;
|
|
6648
|
+
settled = true;
|
|
6649
|
+
resolve2({ statusCode: res.statusCode ?? 0, body });
|
|
6650
|
+
});
|
|
6651
|
+
}
|
|
6652
|
+
);
|
|
6653
|
+
req.on("error", (err) => {
|
|
6654
|
+
if (settled) return;
|
|
6655
|
+
settled = true;
|
|
6656
|
+
reject(err);
|
|
6657
|
+
});
|
|
6658
|
+
req.on("timeout", () => {
|
|
6659
|
+
req.destroy();
|
|
6660
|
+
});
|
|
6661
|
+
req.write(payload);
|
|
6662
|
+
req.end();
|
|
6663
|
+
});
|
|
6664
|
+
}
|
|
6665
|
+
function _get(url, headers) {
|
|
6666
|
+
return new Promise((resolve2, reject) => {
|
|
6667
|
+
let settled = false;
|
|
6668
|
+
const u2 = new URL(url);
|
|
6669
|
+
const lib = u2.protocol === "https:" ? https6 : http6;
|
|
6670
|
+
const req = lib.request(
|
|
6671
|
+
{
|
|
6672
|
+
hostname: u2.hostname,
|
|
6673
|
+
port: u2.port || (u2.protocol === "https:" ? 443 : 80),
|
|
6674
|
+
path: u2.pathname + u2.search,
|
|
6675
|
+
method: "GET",
|
|
6676
|
+
headers: {
|
|
6677
|
+
...headers,
|
|
6678
|
+
...vercelBypassHeader()
|
|
6679
|
+
},
|
|
6680
|
+
timeout: 8e3
|
|
6681
|
+
},
|
|
6682
|
+
(res) => {
|
|
6683
|
+
let body = "";
|
|
6684
|
+
res.on("data", (c2) => {
|
|
6685
|
+
body += c2.toString();
|
|
6686
|
+
});
|
|
6687
|
+
res.on("end", () => {
|
|
6688
|
+
if (settled) return;
|
|
6689
|
+
settled = true;
|
|
6690
|
+
resolve2({ statusCode: res.statusCode ?? 0, body });
|
|
6691
|
+
});
|
|
6692
|
+
}
|
|
6693
|
+
);
|
|
6694
|
+
req.on("error", (err) => {
|
|
6695
|
+
if (settled) return;
|
|
6696
|
+
settled = true;
|
|
6697
|
+
reject(err);
|
|
6698
|
+
});
|
|
6699
|
+
req.on("timeout", () => {
|
|
6700
|
+
req.destroy();
|
|
6701
|
+
});
|
|
6702
|
+
req.end();
|
|
6703
|
+
});
|
|
6704
|
+
}
|
|
6705
|
+
|
|
6706
|
+
// src/services/streaming-emitter.service.ts
|
|
6707
|
+
var API_BASE6 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
6708
|
+
var TICK_MS = 50;
|
|
6709
|
+
var ANSWER_POLL_MS = 1500;
|
|
6710
|
+
var SELECTOR_STABLE_MS = 800;
|
|
6711
|
+
var MAX_RETRIES2 = 1;
|
|
6712
|
+
var RETRY_BACKOFF_MS2 = 200;
|
|
6713
|
+
var StreamingEmitterService = class {
|
|
6714
|
+
constructor(opts) {
|
|
6715
|
+
this.opts = opts;
|
|
6716
|
+
this.apiBase = opts.apiBaseUrl ?? API_BASE6;
|
|
6717
|
+
this.headers = {
|
|
6718
|
+
"Content-Type": "application/json",
|
|
6719
|
+
"X-Codeam-Protocol-Version": "2.0.0",
|
|
6720
|
+
"X-Plugin-Auth-Token": opts.pluginAuthToken
|
|
6721
|
+
};
|
|
6722
|
+
}
|
|
6723
|
+
opts;
|
|
6724
|
+
apiBase;
|
|
6725
|
+
headers;
|
|
6726
|
+
rawBuffer = "";
|
|
6727
|
+
active = false;
|
|
6728
|
+
tickTimer = null;
|
|
6729
|
+
answerPollTimer = null;
|
|
6730
|
+
/** Open chunk we're appending bytes to; cleared on kind change or stop. */
|
|
6731
|
+
activeChunk = null;
|
|
6732
|
+
/** Outstanding answer we're polling the backend for. Null when idle. */
|
|
6733
|
+
pendingAnswer = null;
|
|
6734
|
+
/**
|
|
6735
|
+
* Memoised selector signature so we don't fire `awaiting-answer` on
|
|
6736
|
+
* every tick while the user thinks. Cleared once an answer resolves.
|
|
6737
|
+
*/
|
|
6738
|
+
lastSelectorSignature = null;
|
|
6739
|
+
selectorFirstSeenAt = 0;
|
|
6740
|
+
// ─── Lifecycle ─────────────────────────────────────────────────────
|
|
6741
|
+
start() {
|
|
6742
|
+
if (this.active) return;
|
|
6743
|
+
this.active = true;
|
|
6744
|
+
this.tickTimer = setInterval(() => this.tick(), TICK_MS);
|
|
6745
|
+
this.answerPollTimer = setInterval(() => {
|
|
6746
|
+
void this.pollPendingAnswer();
|
|
6747
|
+
}, ANSWER_POLL_MS);
|
|
6748
|
+
log.trace("streamingEmitter", `started session=${this.opts.sessionId.slice(0, 8)}`);
|
|
6749
|
+
}
|
|
6750
|
+
/**
|
|
6751
|
+
* Stop the emitter. Finalises the open chunk (if any) so the
|
|
6752
|
+
* backend doesn't leave a half-streamed message in the SSE
|
|
6753
|
+
* buffer. Idempotent.
|
|
6754
|
+
*/
|
|
6755
|
+
async stop() {
|
|
6756
|
+
if (!this.active) return;
|
|
6757
|
+
this.active = false;
|
|
6758
|
+
if (this.tickTimer) {
|
|
6759
|
+
clearInterval(this.tickTimer);
|
|
6760
|
+
this.tickTimer = null;
|
|
6761
|
+
}
|
|
6762
|
+
if (this.answerPollTimer) {
|
|
6763
|
+
clearInterval(this.answerPollTimer);
|
|
6764
|
+
this.answerPollTimer = null;
|
|
6765
|
+
}
|
|
6766
|
+
if (this.activeChunk) {
|
|
6767
|
+
const finalContent = this.activeChunk.currentContent;
|
|
6768
|
+
const chunk = this.activeChunk;
|
|
6769
|
+
this.activeChunk = null;
|
|
6770
|
+
await this.postChunk({
|
|
6771
|
+
chunkId: chunk.chunkId,
|
|
6772
|
+
kind: chunk.kind,
|
|
6773
|
+
content: finalContent,
|
|
6774
|
+
isFinal: true
|
|
6775
|
+
});
|
|
6776
|
+
}
|
|
6777
|
+
log.trace("streamingEmitter", `stopped session=${this.opts.sessionId.slice(0, 8)}`);
|
|
6778
|
+
}
|
|
6779
|
+
// ─── Input pump ────────────────────────────────────────────────────
|
|
6780
|
+
/**
|
|
6781
|
+
* Forward a chunk of raw PTY bytes into the emitter. Safe to call
|
|
6782
|
+
* before `start()` — bytes accumulate in the internal buffer and the
|
|
6783
|
+
* first tick processes the backlog.
|
|
6784
|
+
*/
|
|
6785
|
+
push(raw) {
|
|
6786
|
+
if (raw.length === 0) return;
|
|
6787
|
+
this.rawBuffer += raw;
|
|
6788
|
+
}
|
|
6789
|
+
// ─── Tick ──────────────────────────────────────────────────────────
|
|
6790
|
+
/**
|
|
6791
|
+
* Visible for tests. Runs one classify-and-emit pass against the
|
|
6792
|
+
* current PTY buffer.
|
|
6793
|
+
*/
|
|
6794
|
+
/* @internal */
|
|
6795
|
+
_tickForTest() {
|
|
6796
|
+
this.tick();
|
|
6797
|
+
}
|
|
6798
|
+
tick() {
|
|
6799
|
+
if (!this.active) return;
|
|
6800
|
+
const lines = (this.opts.runtime.renderToLines ?? renderToLines)(this.rawBuffer);
|
|
6801
|
+
const selector = this.opts.runtime.detectInteractivePrompt(lines);
|
|
6802
|
+
if (selector) {
|
|
6803
|
+
this.maybeEmitAwaitingAnswer(selector);
|
|
6804
|
+
return;
|
|
6805
|
+
}
|
|
6806
|
+
this.lastSelectorSignature = null;
|
|
6807
|
+
this.selectorFirstSeenAt = 0;
|
|
6808
|
+
const groups = this.classifyLines(lines, this.opts.runtime);
|
|
6809
|
+
if (groups.length === 0) return;
|
|
6810
|
+
const last = groups[groups.length - 1];
|
|
6811
|
+
if (this.activeChunk && this.activeChunk.kind === last.kind) {
|
|
6812
|
+
this.activeChunk.currentContent = last.content;
|
|
6813
|
+
this.maybeFlushActive(false);
|
|
6814
|
+
return;
|
|
6815
|
+
}
|
|
6816
|
+
if (this.activeChunk) {
|
|
6817
|
+
const closing = this.activeChunk;
|
|
6818
|
+
this.activeChunk = null;
|
|
6819
|
+
void this.postChunk({
|
|
6820
|
+
chunkId: closing.chunkId,
|
|
6821
|
+
kind: closing.kind,
|
|
6822
|
+
content: closing.currentContent,
|
|
6823
|
+
isFinal: true
|
|
6824
|
+
});
|
|
6825
|
+
}
|
|
6826
|
+
this.activeChunk = {
|
|
6827
|
+
chunkId: (0, import_crypto.randomUUID)(),
|
|
6828
|
+
kind: last.kind,
|
|
6829
|
+
emittedContent: "",
|
|
6830
|
+
currentContent: last.content,
|
|
6831
|
+
lastEmitAt: 0
|
|
6832
|
+
};
|
|
6833
|
+
this.maybeFlushActive(false);
|
|
6834
|
+
}
|
|
6835
|
+
maybeFlushActive(force) {
|
|
6836
|
+
const chunk = this.activeChunk;
|
|
6837
|
+
if (!chunk) return;
|
|
6838
|
+
const now = Date.now();
|
|
6839
|
+
if (!force && now - chunk.lastEmitAt < TICK_MS) return;
|
|
6840
|
+
if (chunk.currentContent === chunk.emittedContent) return;
|
|
6841
|
+
chunk.emittedContent = chunk.currentContent;
|
|
6842
|
+
chunk.lastEmitAt = now;
|
|
6843
|
+
void this.postChunk({
|
|
6844
|
+
chunkId: chunk.chunkId,
|
|
6845
|
+
kind: chunk.kind,
|
|
6846
|
+
content: chunk.currentContent,
|
|
6847
|
+
isFinal: false
|
|
6848
|
+
});
|
|
6849
|
+
}
|
|
6850
|
+
// ─── Line classification ───────────────────────────────────────────
|
|
6851
|
+
/**
|
|
6852
|
+
* Walk the rendered screen lines and bucket them into kind-groups.
|
|
6853
|
+
*
|
|
6854
|
+
* Visible for tests so we can assert discrimination against fixture
|
|
6855
|
+
* inputs without standing up the full POST loop.
|
|
6856
|
+
*/
|
|
6857
|
+
/* @internal */
|
|
6858
|
+
_classifyForTest(lines) {
|
|
6859
|
+
return this.classifyLines(lines, this.opts.runtime);
|
|
6860
|
+
}
|
|
6861
|
+
classifyLines(lines, runtime) {
|
|
6862
|
+
const parseLine2 = runtime.parseTuiChrome?.bind(runtime);
|
|
6863
|
+
const groups = [];
|
|
6864
|
+
let current = null;
|
|
6865
|
+
const flush = () => {
|
|
6866
|
+
if (!current) return;
|
|
6867
|
+
const text = current.lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
6868
|
+
if (text.length > 0) groups.push({ kind: current.kind, content: text });
|
|
6869
|
+
current = null;
|
|
6870
|
+
};
|
|
6871
|
+
for (const rawLine of lines) {
|
|
6872
|
+
const kind = classifyLine(rawLine, parseLine2, runtime);
|
|
6873
|
+
if (kind === null) continue;
|
|
6874
|
+
if (!current || current.kind !== kind) {
|
|
6875
|
+
flush();
|
|
6876
|
+
current = { kind, lines: [rawLine] };
|
|
6877
|
+
} else {
|
|
6878
|
+
current.lines.push(rawLine);
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
flush();
|
|
6882
|
+
return groups;
|
|
6883
|
+
}
|
|
6884
|
+
// ─── Awaiting-answer + answer subscription ─────────────────────────
|
|
6885
|
+
maybeEmitAwaitingAnswer(selector) {
|
|
6886
|
+
const signature = `${selector.question}::${selector.options.join("|")}`;
|
|
6887
|
+
const now = Date.now();
|
|
6888
|
+
if (this.lastSelectorSignature !== signature) {
|
|
6889
|
+
this.lastSelectorSignature = signature;
|
|
6890
|
+
this.selectorFirstSeenAt = now;
|
|
6891
|
+
return;
|
|
6892
|
+
}
|
|
6893
|
+
if (this.pendingAnswer) return;
|
|
6894
|
+
if (now - this.selectorFirstSeenAt < SELECTOR_STABLE_MS) return;
|
|
6895
|
+
const questionId = (0, import_crypto.randomUUID)();
|
|
6896
|
+
this.pendingAnswer = {
|
|
6897
|
+
questionId,
|
|
6898
|
+
options: selector.options.length > 0 ? selector.options : void 0,
|
|
6899
|
+
fromIndex: selector.currentIndex
|
|
6900
|
+
};
|
|
6901
|
+
const event = {
|
|
6902
|
+
questionId,
|
|
6903
|
+
prompt: selector.question,
|
|
6904
|
+
...selector.options.length > 0 ? { options: selector.options } : {}
|
|
6905
|
+
};
|
|
6906
|
+
void this.postAwaitingAnswer(event);
|
|
6907
|
+
}
|
|
6908
|
+
async pollPendingAnswer() {
|
|
6909
|
+
if (!this.active) return;
|
|
6910
|
+
if (!this.pendingAnswer) return;
|
|
6911
|
+
const questionId = this.pendingAnswer.questionId;
|
|
6912
|
+
try {
|
|
6913
|
+
const { statusCode, body } = await _transport4.get(
|
|
6914
|
+
`${this.apiBase}/api/sessions/${encodeURIComponent(this.opts.sessionId)}/pending-answer?questionId=${encodeURIComponent(questionId)}`,
|
|
6915
|
+
this.headers
|
|
6916
|
+
);
|
|
6917
|
+
if (statusCode === 204 || statusCode === 404) {
|
|
6918
|
+
return;
|
|
6919
|
+
}
|
|
6920
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
6921
|
+
log.warn("streamingEmitter", `pending-answer status=${statusCode} body=${body.slice(0, 200)}`);
|
|
6922
|
+
return;
|
|
6923
|
+
}
|
|
6924
|
+
const parsed = parsePendingAnswerResponse(body);
|
|
6925
|
+
if (!parsed || parsed.questionId !== questionId) return;
|
|
6926
|
+
const pending = this.pendingAnswer;
|
|
6927
|
+
this.pendingAnswer = null;
|
|
6928
|
+
this.lastSelectorSignature = null;
|
|
6929
|
+
this.selectorFirstSeenAt = 0;
|
|
6930
|
+
this.deliverAnswer(parsed, pending);
|
|
6931
|
+
} catch (err) {
|
|
6932
|
+
log.trace("streamingEmitter", "pending-answer poll failed", err);
|
|
6933
|
+
}
|
|
6934
|
+
}
|
|
6935
|
+
deliverAnswer(answer, pending) {
|
|
6936
|
+
if (pending.options && pending.options.length > 0) {
|
|
6937
|
+
let targetIndex = answer.optionIndex ?? -1;
|
|
6938
|
+
if (targetIndex < 0) {
|
|
6939
|
+
targetIndex = pending.options.findIndex((o) => o === answer.answer);
|
|
6940
|
+
}
|
|
6941
|
+
if (targetIndex < 0 || targetIndex >= pending.options.length) {
|
|
6942
|
+
log.warn(
|
|
6943
|
+
"streamingEmitter",
|
|
6944
|
+
`answer label "${answer.answer}" not found in selector options \u2014 defaulting to 0`
|
|
6945
|
+
);
|
|
6946
|
+
targetIndex = 0;
|
|
6947
|
+
}
|
|
6948
|
+
this.opts.ptyInput.selectOption(targetIndex, pending.fromIndex ?? 0);
|
|
6949
|
+
return;
|
|
6950
|
+
}
|
|
6951
|
+
this.opts.ptyInput.sendCommand(answer.answer);
|
|
6952
|
+
}
|
|
6953
|
+
// ─── POSTs ─────────────────────────────────────────────────────────
|
|
6954
|
+
async postChunk(event) {
|
|
6955
|
+
await this.postWithRetries(
|
|
6956
|
+
`${this.apiBase}/api/sessions/${encodeURIComponent(this.opts.sessionId)}/streaming-chunk`,
|
|
6957
|
+
event
|
|
6958
|
+
);
|
|
6959
|
+
}
|
|
6960
|
+
async postAwaitingAnswer(event) {
|
|
6961
|
+
await this.postWithRetries(
|
|
6962
|
+
`${this.apiBase}/api/sessions/${encodeURIComponent(this.opts.sessionId)}/awaiting-answer`,
|
|
6963
|
+
event
|
|
6964
|
+
);
|
|
6965
|
+
}
|
|
6966
|
+
async postWithRetries(url, body) {
|
|
6967
|
+
const payload = JSON.stringify(body);
|
|
6968
|
+
for (let attempt = 0; attempt <= MAX_RETRIES2; attempt += 1) {
|
|
6969
|
+
try {
|
|
6970
|
+
const { statusCode, body: resBody } = await _transport4.post(url, this.headers, payload);
|
|
6971
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
6972
|
+
log.trace("streamingEmitter", `post ok url=${url} status=${statusCode}`);
|
|
6973
|
+
return;
|
|
6974
|
+
}
|
|
6975
|
+
if (statusCode === 410 || statusCode === 404) {
|
|
6976
|
+
log.warn("streamingEmitter", `session dead (status=${statusCode}) \u2014 disabling emitter`);
|
|
6977
|
+
this.active = false;
|
|
6978
|
+
if (this.tickTimer) {
|
|
6979
|
+
clearInterval(this.tickTimer);
|
|
6980
|
+
this.tickTimer = null;
|
|
6981
|
+
}
|
|
6982
|
+
if (this.answerPollTimer) {
|
|
6983
|
+
clearInterval(this.answerPollTimer);
|
|
6984
|
+
this.answerPollTimer = null;
|
|
6985
|
+
}
|
|
6986
|
+
return;
|
|
6987
|
+
}
|
|
6988
|
+
log.warn(
|
|
6989
|
+
"streamingEmitter",
|
|
6990
|
+
`post failed url=${url} status=${statusCode} attempt=${attempt + 1} body=${resBody.slice(0, 200)}`
|
|
6991
|
+
);
|
|
6992
|
+
} catch (err) {
|
|
6993
|
+
log.warn("streamingEmitter", `post error url=${url} attempt=${attempt + 1}`, err);
|
|
6994
|
+
}
|
|
6995
|
+
if (attempt < MAX_RETRIES2) {
|
|
6996
|
+
await sleep2(RETRY_BACKOFF_MS2 * (attempt + 1));
|
|
6997
|
+
}
|
|
6998
|
+
}
|
|
6999
|
+
}
|
|
7000
|
+
};
|
|
7001
|
+
function sleep2(ms) {
|
|
7002
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
7003
|
+
}
|
|
7004
|
+
var TREE_CONTINUATION_RE = /^\s*└/;
|
|
7005
|
+
function classifyLine(line, parseChrome, runtime) {
|
|
7006
|
+
if (line.trim().length === 0) return null;
|
|
7007
|
+
if (TREE_CONTINUATION_RE.test(line)) return "tool_result";
|
|
7008
|
+
const step = parseChrome?.(line) ?? null;
|
|
7009
|
+
if (step) {
|
|
7010
|
+
if (step.tool === "thinking") return "thinking";
|
|
7011
|
+
return "tool_use";
|
|
7012
|
+
}
|
|
7013
|
+
const filtered = runtime.filterTuiOutput([line]);
|
|
7014
|
+
if (filtered.length === 0) return null;
|
|
7015
|
+
return "text";
|
|
7016
|
+
}
|
|
7017
|
+
function parsePendingAnswerResponse(body) {
|
|
7018
|
+
if (!body) return null;
|
|
7019
|
+
let parsed;
|
|
7020
|
+
try {
|
|
7021
|
+
parsed = JSON.parse(body);
|
|
7022
|
+
} catch {
|
|
7023
|
+
return null;
|
|
7024
|
+
}
|
|
7025
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
7026
|
+
const root = parsed;
|
|
7027
|
+
const candidate = root.data && typeof root.data === "object" ? root.data : root;
|
|
7028
|
+
const questionId = candidate.questionId;
|
|
7029
|
+
const answer = candidate.answer;
|
|
7030
|
+
const optionIndex = candidate.optionIndex;
|
|
7031
|
+
if (typeof questionId !== "string" || typeof answer !== "string") return null;
|
|
7032
|
+
const result = { questionId, answer };
|
|
7033
|
+
if (typeof optionIndex === "number" && Number.isInteger(optionIndex)) {
|
|
7034
|
+
result.optionIndex = optionIndex;
|
|
7035
|
+
}
|
|
7036
|
+
return result;
|
|
7037
|
+
}
|
|
7038
|
+
|
|
6583
7039
|
// src/commands/start/quota-fetcher.ts
|
|
6584
7040
|
var inProgress = false;
|
|
6585
7041
|
function fetchQuotaUsage(runtime, historySvc) {
|
|
@@ -6595,13 +7051,13 @@ function fetchQuotaUsage(runtime, historySvc) {
|
|
|
6595
7051
|
}
|
|
6596
7052
|
|
|
6597
7053
|
// src/commands/start/keep-alive.ts
|
|
6598
|
-
var
|
|
7054
|
+
var import_child_process8 = require("child_process");
|
|
6599
7055
|
function buildKeepAlive(ctx) {
|
|
6600
7056
|
let timer = null;
|
|
6601
7057
|
async function setIdleTimeout(minutes) {
|
|
6602
7058
|
if (!ctx.inCodespace || !ctx.codespaceName) return;
|
|
6603
7059
|
await new Promise((resolve2) => {
|
|
6604
|
-
const proc = (0,
|
|
7060
|
+
const proc = (0, import_child_process8.spawn)(
|
|
6605
7061
|
"gh",
|
|
6606
7062
|
[
|
|
6607
7063
|
"api",
|
|
@@ -6641,8 +7097,8 @@ function buildKeepAlive(ctx) {
|
|
|
6641
7097
|
var fs14 = __toESM(require("fs"));
|
|
6642
7098
|
var os13 = __toESM(require("os"));
|
|
6643
7099
|
var path19 = __toESM(require("path"));
|
|
6644
|
-
var
|
|
6645
|
-
var
|
|
7100
|
+
var import_crypto3 = require("crypto");
|
|
7101
|
+
var import_child_process11 = require("child_process");
|
|
6646
7102
|
|
|
6647
7103
|
// src/lib/payload.ts
|
|
6648
7104
|
var import_zod2 = require("zod");
|
|
@@ -6846,11 +7302,11 @@ async function writeProjectFile(rawPath, content) {
|
|
|
6846
7302
|
}
|
|
6847
7303
|
|
|
6848
7304
|
// src/services/project-ops.service.ts
|
|
6849
|
-
var
|
|
7305
|
+
var import_child_process9 = require("child_process");
|
|
6850
7306
|
var import_util2 = require("util");
|
|
6851
7307
|
var fs13 = __toESM(require("fs/promises"));
|
|
6852
7308
|
var path17 = __toESM(require("path"));
|
|
6853
|
-
var execFileP2 = (0, import_util2.promisify)(
|
|
7309
|
+
var execFileP2 = (0, import_util2.promisify)(import_child_process9.execFile);
|
|
6854
7310
|
var PROJECT_IGNORE = /* @__PURE__ */ new Set([
|
|
6855
7311
|
"node_modules",
|
|
6856
7312
|
".git",
|
|
@@ -7190,8 +7646,8 @@ async function jsSearchFiles(opts, cwd, cap) {
|
|
|
7190
7646
|
}
|
|
7191
7647
|
|
|
7192
7648
|
// src/services/terminal-ops.service.ts
|
|
7193
|
-
var
|
|
7194
|
-
var
|
|
7649
|
+
var import_child_process10 = require("child_process");
|
|
7650
|
+
var import_crypto2 = require("crypto");
|
|
7195
7651
|
var import_path = __toESM(require("path"));
|
|
7196
7652
|
var MAX_CONCURRENT_SESSIONS = 4;
|
|
7197
7653
|
var nodePtyModule;
|
|
@@ -7312,7 +7768,7 @@ function createPythonSession(id, shell, cwd, env, cols, rows) {
|
|
|
7312
7768
|
}
|
|
7313
7769
|
let child;
|
|
7314
7770
|
try {
|
|
7315
|
-
child = (0,
|
|
7771
|
+
child = (0, import_child_process10.spawn)(python, ["-c", PYTHON_TERMINAL_HELPER, shell], {
|
|
7316
7772
|
cwd,
|
|
7317
7773
|
env: { ...env, COLUMNS: String(cols), LINES: String(rows) },
|
|
7318
7774
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7367,7 +7823,7 @@ function openTerminal(opts) {
|
|
|
7367
7823
|
};
|
|
7368
7824
|
const cols = Math.max(1, Math.min(opts.cols ?? 80, 500));
|
|
7369
7825
|
const rows = Math.max(1, Math.min(opts.rows ?? 24, 200));
|
|
7370
|
-
const id = (0,
|
|
7826
|
+
const id = (0, import_crypto2.randomUUID)();
|
|
7371
7827
|
const ptyMod = loadNodePty2();
|
|
7372
7828
|
if (ptyMod) {
|
|
7373
7829
|
try {
|
|
@@ -7447,7 +7903,7 @@ function closeTerminal(sessionId) {
|
|
|
7447
7903
|
function saveFilesTemp(files) {
|
|
7448
7904
|
return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
|
|
7449
7905
|
const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
7450
|
-
const tmpPath = path19.join(os13.tmpdir(), `codeam-${(0,
|
|
7906
|
+
const tmpPath = path19.join(os13.tmpdir(), `codeam-${(0, import_crypto3.randomUUID)()}-${safeName}`);
|
|
7451
7907
|
fs14.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
|
|
7452
7908
|
return tmpPath;
|
|
7453
7909
|
});
|
|
@@ -7579,7 +8035,7 @@ var sessionTerminated = (ctx) => {
|
|
|
7579
8035
|
} catch {
|
|
7580
8036
|
}
|
|
7581
8037
|
try {
|
|
7582
|
-
const proc = (0,
|
|
8038
|
+
const proc = (0, import_child_process11.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
|
|
7583
8039
|
detached: true,
|
|
7584
8040
|
stdio: "ignore"
|
|
7585
8041
|
});
|
|
@@ -7601,7 +8057,7 @@ var shutdownSession = async (ctx, cmd) => {
|
|
|
7601
8057
|
}
|
|
7602
8058
|
if (ctx.keepAliveCtx.inCodespace && ctx.keepAliveCtx.codespaceName) {
|
|
7603
8059
|
try {
|
|
7604
|
-
const stopProc = (0,
|
|
8060
|
+
const stopProc = (0, import_child_process11.spawn)(
|
|
7605
8061
|
"bash",
|
|
7606
8062
|
["-lc", `sleep 1; gh codespace stop -c ${JSON.stringify(ctx.keepAliveCtx.codespaceName)} >/dev/null 2>&1 || true`],
|
|
7607
8063
|
{ detached: true, stdio: "ignore" }
|
|
@@ -7611,7 +8067,7 @@ var shutdownSession = async (ctx, cmd) => {
|
|
|
7611
8067
|
}
|
|
7612
8068
|
}
|
|
7613
8069
|
try {
|
|
7614
|
-
const proc = (0,
|
|
8070
|
+
const proc = (0, import_child_process11.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
|
|
7615
8071
|
detached: true,
|
|
7616
8072
|
stdio: "ignore"
|
|
7617
8073
|
});
|
|
@@ -7836,22 +8292,34 @@ async function start(requestedAgent) {
|
|
|
7836
8292
|
pluginId,
|
|
7837
8293
|
pluginAuthToken: session.pluginAuthToken
|
|
7838
8294
|
}) : null;
|
|
8295
|
+
let streamingEmitter = null;
|
|
7839
8296
|
const claude = new AgentService(
|
|
7840
8297
|
runtime,
|
|
7841
8298
|
{
|
|
7842
8299
|
cwd,
|
|
7843
8300
|
onData(raw) {
|
|
7844
8301
|
outputSvc.push(raw);
|
|
8302
|
+
streamingEmitter?.push(raw);
|
|
7845
8303
|
},
|
|
7846
8304
|
onExit(code) {
|
|
7847
8305
|
process.removeListener("SIGINT", sigintHandler);
|
|
7848
8306
|
outputSvc.dispose();
|
|
7849
8307
|
relay.stop();
|
|
7850
8308
|
void fileWatcher?.stop();
|
|
8309
|
+
void streamingEmitter?.stop();
|
|
7851
8310
|
process.exit(code);
|
|
7852
8311
|
}
|
|
7853
8312
|
}
|
|
7854
8313
|
);
|
|
8314
|
+
if (session.pluginAuthToken) {
|
|
8315
|
+
streamingEmitter = new StreamingEmitterService({
|
|
8316
|
+
sessionId: session.id,
|
|
8317
|
+
pluginId,
|
|
8318
|
+
pluginAuthToken: session.pluginAuthToken,
|
|
8319
|
+
runtime,
|
|
8320
|
+
ptyInput: claude
|
|
8321
|
+
});
|
|
8322
|
+
}
|
|
7855
8323
|
const ctx = {
|
|
7856
8324
|
outputSvc,
|
|
7857
8325
|
claude,
|
|
@@ -7878,6 +8346,7 @@ async function start(requestedAgent) {
|
|
|
7878
8346
|
outputSvc.dispose();
|
|
7879
8347
|
relay.stop();
|
|
7880
8348
|
void fileWatcher?.stop();
|
|
8349
|
+
void streamingEmitter?.stop();
|
|
7881
8350
|
process.exit(0);
|
|
7882
8351
|
}
|
|
7883
8352
|
process.once("SIGINT", sigintHandler);
|
|
@@ -7888,6 +8357,7 @@ async function start(requestedAgent) {
|
|
|
7888
8357
|
fileWatcher.start().catch(() => {
|
|
7889
8358
|
});
|
|
7890
8359
|
}
|
|
8360
|
+
streamingEmitter?.start();
|
|
7891
8361
|
setTimeout(() => {
|
|
7892
8362
|
historySvc.load().catch(() => {
|
|
7893
8363
|
});
|
|
@@ -7896,7 +8366,7 @@ async function start(requestedAgent) {
|
|
|
7896
8366
|
}
|
|
7897
8367
|
|
|
7898
8368
|
// src/commands/pair.ts
|
|
7899
|
-
var
|
|
8369
|
+
var import_crypto4 = require("crypto");
|
|
7900
8370
|
var import_picocolors3 = __toESM(require("picocolors"));
|
|
7901
8371
|
|
|
7902
8372
|
// src/ui/prompts.ts
|
|
@@ -7959,7 +8429,7 @@ async function pair(args2 = []) {
|
|
|
7959
8429
|
const flagAgent = parseAgentFlag(args2);
|
|
7960
8430
|
const agentId = flagAgent ?? await promptForAgent(config.preferredAgent ?? "claude");
|
|
7961
8431
|
showIntro();
|
|
7962
|
-
const pluginId = (0,
|
|
8432
|
+
const pluginId = (0, import_crypto4.randomUUID)();
|
|
7963
8433
|
const spin = dist_exports.spinner();
|
|
7964
8434
|
spin.start("Requesting pairing code...");
|
|
7965
8435
|
const result = await requestCode(pluginId);
|
|
@@ -8015,8 +8485,8 @@ async function pair(args2 = []) {
|
|
|
8015
8485
|
// src/commands/pair-auto.ts
|
|
8016
8486
|
var fs15 = __toESM(require("fs"));
|
|
8017
8487
|
var os14 = __toESM(require("os"));
|
|
8018
|
-
var
|
|
8019
|
-
var
|
|
8488
|
+
var import_crypto5 = require("crypto");
|
|
8489
|
+
var API_BASE7 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
8020
8490
|
function fail(msg) {
|
|
8021
8491
|
console.error(`
|
|
8022
8492
|
${msg}
|
|
@@ -8048,14 +8518,18 @@ function readTokenFromArgs(args2) {
|
|
|
8048
8518
|
fail("codeam pair-auto requires --token-file=<path>, --token=<value>, or CODEAM_AUTO_TOKEN env");
|
|
8049
8519
|
}
|
|
8050
8520
|
async function claim(token, pluginId) {
|
|
8051
|
-
const url = `${
|
|
8521
|
+
const url = `${API_BASE7}/api/pairing/claim-auto-token`;
|
|
8052
8522
|
const body = {
|
|
8053
8523
|
token,
|
|
8054
8524
|
pluginId,
|
|
8055
8525
|
ideName: "codeam-cli (codespace)",
|
|
8056
8526
|
ideVersion: process.env.npm_package_version ?? "unknown",
|
|
8057
8527
|
hostname: os14.hostname(),
|
|
8058
|
-
codespaceName: process.env.CODESPACE_NAME ?? ""
|
|
8528
|
+
codespaceName: process.env.CODESPACE_NAME ?? "",
|
|
8529
|
+
// Current git branch of the codespace's working directory, so the
|
|
8530
|
+
// backend can populate `PairedSession.branch` for the codespace pair.
|
|
8531
|
+
// `null` when detached HEAD / not a git repo.
|
|
8532
|
+
branch: detectCurrentBranch()
|
|
8059
8533
|
};
|
|
8060
8534
|
const res = await fetch(url, {
|
|
8061
8535
|
method: "POST",
|
|
@@ -8077,7 +8551,7 @@ async function claim(token, pluginId) {
|
|
|
8077
8551
|
}
|
|
8078
8552
|
async function pairAuto(args2) {
|
|
8079
8553
|
const token = readTokenFromArgs(args2);
|
|
8080
|
-
const pluginId = (0,
|
|
8554
|
+
const pluginId = (0, import_crypto5.randomUUID)();
|
|
8081
8555
|
console.log(" Claiming pairing token\u2026");
|
|
8082
8556
|
const claimed = await claim(token, pluginId);
|
|
8083
8557
|
if (!isKnownAgentId(claimed.agent)) {
|
|
@@ -8203,11 +8677,11 @@ async function logout() {
|
|
|
8203
8677
|
var import_picocolors9 = __toESM(require("picocolors"));
|
|
8204
8678
|
|
|
8205
8679
|
// src/services/providers/github-codespaces.ts
|
|
8206
|
-
var
|
|
8680
|
+
var import_child_process12 = require("child_process");
|
|
8207
8681
|
var import_util3 = require("util");
|
|
8208
8682
|
var import_picocolors7 = __toESM(require("picocolors"));
|
|
8209
8683
|
var path20 = __toESM(require("path"));
|
|
8210
|
-
var execFileP3 = (0, import_util3.promisify)(
|
|
8684
|
+
var execFileP3 = (0, import_util3.promisify)(import_child_process12.execFile);
|
|
8211
8685
|
var MAX_BUFFER = 8 * 1024 * 1024;
|
|
8212
8686
|
function resetStdinForChild() {
|
|
8213
8687
|
if (process.stdin.isTTY) {
|
|
@@ -8251,7 +8725,7 @@ var GitHubCodespacesProvider = class {
|
|
|
8251
8725
|
if (!isAuthed) {
|
|
8252
8726
|
resetStdinForChild();
|
|
8253
8727
|
await new Promise((resolve2, reject) => {
|
|
8254
|
-
const proc = (0,
|
|
8728
|
+
const proc = (0, import_child_process12.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
|
|
8255
8729
|
stdio: "inherit"
|
|
8256
8730
|
});
|
|
8257
8731
|
proc.on("exit", (code) => {
|
|
@@ -8285,7 +8759,7 @@ var GitHubCodespacesProvider = class {
|
|
|
8285
8759
|
wt(noteLines.join("\n"), "One more permission needed");
|
|
8286
8760
|
resetStdinForChild();
|
|
8287
8761
|
const refreshCode = await new Promise((resolve2, reject) => {
|
|
8288
|
-
const proc = (0,
|
|
8762
|
+
const proc = (0, import_child_process12.spawn)(
|
|
8289
8763
|
"gh",
|
|
8290
8764
|
["auth", "refresh", "-h", "github.com", "-s", "codespace"],
|
|
8291
8765
|
{ stdio: "inherit" }
|
|
@@ -8435,7 +8909,7 @@ var GitHubCodespacesProvider = class {
|
|
|
8435
8909
|
O2.step(`Installing gh via ${installCmd.describe}\u2026`);
|
|
8436
8910
|
resetStdinForChild();
|
|
8437
8911
|
const ok = await new Promise((resolve2) => {
|
|
8438
|
-
const proc = (0,
|
|
8912
|
+
const proc = (0, import_child_process12.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
|
|
8439
8913
|
proc.on("exit", (code) => resolve2(code === 0));
|
|
8440
8914
|
proc.on("error", () => resolve2(false));
|
|
8441
8915
|
});
|
|
@@ -8462,7 +8936,7 @@ var GitHubCodespacesProvider = class {
|
|
|
8462
8936
|
);
|
|
8463
8937
|
resetStdinForChild();
|
|
8464
8938
|
await new Promise((resolve2, reject) => {
|
|
8465
|
-
const proc = (0,
|
|
8939
|
+
const proc = (0, import_child_process12.spawn)(
|
|
8466
8940
|
"gh",
|
|
8467
8941
|
["auth", "refresh", "-h", "github.com", "-s", "repo,read:org"],
|
|
8468
8942
|
{ stdio: "inherit" }
|
|
@@ -8640,7 +9114,7 @@ var GitHubCodespacesProvider = class {
|
|
|
8640
9114
|
async streamCommand(workspaceId, command2) {
|
|
8641
9115
|
resetStdinForChild();
|
|
8642
9116
|
return new Promise((resolve2, reject) => {
|
|
8643
|
-
const proc = (0,
|
|
9117
|
+
const proc = (0, import_child_process12.spawn)(
|
|
8644
9118
|
"gh",
|
|
8645
9119
|
["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
|
|
8646
9120
|
{ stdio: "inherit" }
|
|
@@ -8667,11 +9141,11 @@ var GitHubCodespacesProvider = class {
|
|
|
8667
9141
|
`mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
|
|
8668
9142
|
];
|
|
8669
9143
|
await new Promise((resolve2, reject) => {
|
|
8670
|
-
const tar = (0,
|
|
9144
|
+
const tar = (0, import_child_process12.spawn)("tar", tarArgs, {
|
|
8671
9145
|
stdio: ["ignore", "pipe", "pipe"],
|
|
8672
9146
|
env: tarEnv
|
|
8673
9147
|
});
|
|
8674
|
-
const ssh = (0,
|
|
9148
|
+
const ssh = (0, import_child_process12.spawn)("gh", sshArgs, {
|
|
8675
9149
|
stdio: [tar.stdout, "pipe", "pipe"]
|
|
8676
9150
|
});
|
|
8677
9151
|
let tarErr = "";
|
|
@@ -8705,7 +9179,7 @@ var GitHubCodespacesProvider = class {
|
|
|
8705
9179
|
}
|
|
8706
9180
|
const cmd = parts.join(" && ");
|
|
8707
9181
|
await new Promise((resolve2, reject) => {
|
|
8708
|
-
const proc = (0,
|
|
9182
|
+
const proc = (0, import_child_process12.spawn)(
|
|
8709
9183
|
"gh",
|
|
8710
9184
|
["codespace", "ssh", "-c", workspaceId, "--", cmd],
|
|
8711
9185
|
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -8763,11 +9237,11 @@ function shellQuote(s) {
|
|
|
8763
9237
|
}
|
|
8764
9238
|
|
|
8765
9239
|
// src/services/providers/gitpod.ts
|
|
8766
|
-
var
|
|
9240
|
+
var import_child_process13 = require("child_process");
|
|
8767
9241
|
var import_util4 = require("util");
|
|
8768
9242
|
var path21 = __toESM(require("path"));
|
|
8769
9243
|
var import_picocolors8 = __toESM(require("picocolors"));
|
|
8770
|
-
var execFileP4 = (0, import_util4.promisify)(
|
|
9244
|
+
var execFileP4 = (0, import_util4.promisify)(import_child_process13.execFile);
|
|
8771
9245
|
var MAX_BUFFER2 = 8 * 1024 * 1024;
|
|
8772
9246
|
function resetStdinForChild2() {
|
|
8773
9247
|
if (process.stdin.isTTY) {
|
|
@@ -8807,7 +9281,7 @@ var GitpodProvider = class {
|
|
|
8807
9281
|
);
|
|
8808
9282
|
resetStdinForChild2();
|
|
8809
9283
|
await new Promise((resolve2, reject) => {
|
|
8810
|
-
const proc = (0,
|
|
9284
|
+
const proc = (0, import_child_process13.spawn)("gitpod", ["login"], { stdio: "inherit" });
|
|
8811
9285
|
proc.on("exit", (code) => {
|
|
8812
9286
|
if (code === 0) resolve2();
|
|
8813
9287
|
else reject(new Error("gitpod login failed."));
|
|
@@ -8959,7 +9433,7 @@ var GitpodProvider = class {
|
|
|
8959
9433
|
async streamCommand(workspaceId, command2) {
|
|
8960
9434
|
resetStdinForChild2();
|
|
8961
9435
|
return new Promise((resolve2, reject) => {
|
|
8962
|
-
const proc = (0,
|
|
9436
|
+
const proc = (0, import_child_process13.spawn)(
|
|
8963
9437
|
"gitpod",
|
|
8964
9438
|
["workspace", "ssh", workspaceId, "--", "-tt", command2],
|
|
8965
9439
|
{ stdio: "inherit" }
|
|
@@ -8979,11 +9453,11 @@ var GitpodProvider = class {
|
|
|
8979
9453
|
const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
|
|
8980
9454
|
const remoteCmd = `mkdir -p ${shellQuote2(remoteDir)} && tar -xzf - -C ${shellQuote2(remoteDir)}`;
|
|
8981
9455
|
await new Promise((resolve2, reject) => {
|
|
8982
|
-
const tar = (0,
|
|
9456
|
+
const tar = (0, import_child_process13.spawn)("tar", tarArgs, {
|
|
8983
9457
|
stdio: ["ignore", "pipe", "pipe"],
|
|
8984
9458
|
env: tarEnv
|
|
8985
9459
|
});
|
|
8986
|
-
const ssh = (0,
|
|
9460
|
+
const ssh = (0, import_child_process13.spawn)(
|
|
8987
9461
|
"gitpod",
|
|
8988
9462
|
["workspace", "ssh", workspaceId, "--", remoteCmd],
|
|
8989
9463
|
{ stdio: [tar.stdout, "pipe", "pipe"] }
|
|
@@ -9015,7 +9489,7 @@ var GitpodProvider = class {
|
|
|
9015
9489
|
}
|
|
9016
9490
|
const cmd = parts.join(" && ");
|
|
9017
9491
|
await new Promise((resolve2, reject) => {
|
|
9018
|
-
const proc = (0,
|
|
9492
|
+
const proc = (0, import_child_process13.spawn)(
|
|
9019
9493
|
"gitpod",
|
|
9020
9494
|
["workspace", "ssh", workspaceId, "--", cmd],
|
|
9021
9495
|
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -9039,10 +9513,10 @@ function shellQuote2(s) {
|
|
|
9039
9513
|
}
|
|
9040
9514
|
|
|
9041
9515
|
// src/services/providers/gitlab-workspaces.ts
|
|
9042
|
-
var
|
|
9516
|
+
var import_child_process14 = require("child_process");
|
|
9043
9517
|
var import_util5 = require("util");
|
|
9044
9518
|
var path22 = __toESM(require("path"));
|
|
9045
|
-
var execFileP5 = (0, import_util5.promisify)(
|
|
9519
|
+
var execFileP5 = (0, import_util5.promisify)(import_child_process14.execFile);
|
|
9046
9520
|
var MAX_BUFFER3 = 8 * 1024 * 1024;
|
|
9047
9521
|
var GITLAB_API_BASE = process.env.CODEAM_GITLAB_API_URL ?? "https://gitlab.com/api/v4";
|
|
9048
9522
|
function resetStdinForChild3() {
|
|
@@ -9084,7 +9558,7 @@ var GitLabWorkspacesProvider = class {
|
|
|
9084
9558
|
);
|
|
9085
9559
|
resetStdinForChild3();
|
|
9086
9560
|
await new Promise((resolve2, reject) => {
|
|
9087
|
-
const proc = (0,
|
|
9561
|
+
const proc = (0, import_child_process14.spawn)(
|
|
9088
9562
|
"glab",
|
|
9089
9563
|
["auth", "login", "--scopes", "api,read_user,read_repository"],
|
|
9090
9564
|
{ stdio: "inherit" }
|
|
@@ -9256,7 +9730,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
|
|
|
9256
9730
|
const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
|
|
9257
9731
|
resetStdinForChild3();
|
|
9258
9732
|
return new Promise((resolve2, reject) => {
|
|
9259
|
-
const proc = (0,
|
|
9733
|
+
const proc = (0, import_child_process14.spawn)(
|
|
9260
9734
|
"ssh",
|
|
9261
9735
|
["-tt", "-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, command2],
|
|
9262
9736
|
{ stdio: "inherit" }
|
|
@@ -9277,8 +9751,8 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
|
|
|
9277
9751
|
const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
|
|
9278
9752
|
const remoteCmd = `mkdir -p ${shellQuote3(remoteDir)} && tar -xzf - -C ${shellQuote3(remoteDir)}`;
|
|
9279
9753
|
await new Promise((resolve2, reject) => {
|
|
9280
|
-
const tar = (0,
|
|
9281
|
-
const ssh = (0,
|
|
9754
|
+
const tar = (0, import_child_process14.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
|
|
9755
|
+
const ssh = (0, import_child_process14.spawn)(
|
|
9282
9756
|
"ssh",
|
|
9283
9757
|
["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, remoteCmd],
|
|
9284
9758
|
{ stdio: [tar.stdout, "pipe", "pipe"] }
|
|
@@ -9308,7 +9782,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
|
|
|
9308
9782
|
}
|
|
9309
9783
|
const cmd = parts.join(" && ");
|
|
9310
9784
|
await new Promise((resolve2, reject) => {
|
|
9311
|
-
const proc = (0,
|
|
9785
|
+
const proc = (0, import_child_process14.spawn)(
|
|
9312
9786
|
"ssh",
|
|
9313
9787
|
["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, cmd],
|
|
9314
9788
|
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -9367,10 +9841,10 @@ function shellQuote3(s) {
|
|
|
9367
9841
|
}
|
|
9368
9842
|
|
|
9369
9843
|
// src/services/providers/railway.ts
|
|
9370
|
-
var
|
|
9844
|
+
var import_child_process15 = require("child_process");
|
|
9371
9845
|
var import_util6 = require("util");
|
|
9372
9846
|
var path23 = __toESM(require("path"));
|
|
9373
|
-
var execFileP6 = (0, import_util6.promisify)(
|
|
9847
|
+
var execFileP6 = (0, import_util6.promisify)(import_child_process15.execFile);
|
|
9374
9848
|
var MAX_BUFFER4 = 8 * 1024 * 1024;
|
|
9375
9849
|
function resetStdinForChild4() {
|
|
9376
9850
|
if (process.stdin.isTTY) {
|
|
@@ -9411,7 +9885,7 @@ var RailwayProvider = class {
|
|
|
9411
9885
|
);
|
|
9412
9886
|
resetStdinForChild4();
|
|
9413
9887
|
await new Promise((resolve2, reject) => {
|
|
9414
|
-
const proc = (0,
|
|
9888
|
+
const proc = (0, import_child_process15.spawn)("railway", ["login"], { stdio: "inherit" });
|
|
9415
9889
|
proc.on("exit", (code) => {
|
|
9416
9890
|
if (code === 0) resolve2();
|
|
9417
9891
|
else reject(new Error("railway login failed."));
|
|
@@ -9554,7 +10028,7 @@ var RailwayProvider = class {
|
|
|
9554
10028
|
}
|
|
9555
10029
|
resetStdinForChild4();
|
|
9556
10030
|
return new Promise((resolve2, reject) => {
|
|
9557
|
-
const proc = (0,
|
|
10031
|
+
const proc = (0, import_child_process15.spawn)(
|
|
9558
10032
|
"railway",
|
|
9559
10033
|
["shell", "--project", projectId, "--service", serviceId, "--command", command2],
|
|
9560
10034
|
{ stdio: "inherit" }
|
|
@@ -9578,8 +10052,8 @@ var RailwayProvider = class {
|
|
|
9578
10052
|
const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
|
|
9579
10053
|
const remoteCmd = `mkdir -p ${shellQuote4(remoteDir)} && tar -xzf - -C ${shellQuote4(remoteDir)}`;
|
|
9580
10054
|
await new Promise((resolve2, reject) => {
|
|
9581
|
-
const tar = (0,
|
|
9582
|
-
const sh = (0,
|
|
10055
|
+
const tar = (0, import_child_process15.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
|
|
10056
|
+
const sh = (0, import_child_process15.spawn)(
|
|
9583
10057
|
"railway",
|
|
9584
10058
|
["shell", "--project", projectId, "--service", serviceId, "--command", remoteCmd],
|
|
9585
10059
|
{ stdio: [tar.stdout, "pipe", "pipe"] }
|
|
@@ -9612,7 +10086,7 @@ var RailwayProvider = class {
|
|
|
9612
10086
|
}
|
|
9613
10087
|
const cmd = parts.join(" && ");
|
|
9614
10088
|
await new Promise((resolve2, reject) => {
|
|
9615
|
-
const proc = (0,
|
|
10089
|
+
const proc = (0, import_child_process15.spawn)(
|
|
9616
10090
|
"railway",
|
|
9617
10091
|
["shell", "--project", projectId, "--service", serviceId, "--command", cmd],
|
|
9618
10092
|
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -10147,7 +10621,7 @@ async function stopWorkspaceFromLocal(target) {
|
|
|
10147
10621
|
// src/commands/version.ts
|
|
10148
10622
|
var import_picocolors11 = __toESM(require("picocolors"));
|
|
10149
10623
|
function version() {
|
|
10150
|
-
const v = true ? "2.15.
|
|
10624
|
+
const v = true ? "2.15.6" : "unknown";
|
|
10151
10625
|
console.log(`${import_picocolors11.default.bold("codeam-cli")} ${import_picocolors11.default.cyan(v)}`);
|
|
10152
10626
|
}
|
|
10153
10627
|
|
|
@@ -10191,7 +10665,7 @@ function help() {
|
|
|
10191
10665
|
var fs16 = __toESM(require("fs"));
|
|
10192
10666
|
var os15 = __toESM(require("os"));
|
|
10193
10667
|
var path24 = __toESM(require("path"));
|
|
10194
|
-
var
|
|
10668
|
+
var https7 = __toESM(require("https"));
|
|
10195
10669
|
var import_picocolors13 = __toESM(require("picocolors"));
|
|
10196
10670
|
var PKG_NAME = "codeam-cli";
|
|
10197
10671
|
var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
|
|
@@ -10234,7 +10708,7 @@ function compareSemver(a, b) {
|
|
|
10234
10708
|
}
|
|
10235
10709
|
function fetchLatest() {
|
|
10236
10710
|
return new Promise((resolve2) => {
|
|
10237
|
-
const req =
|
|
10711
|
+
const req = https7.get(
|
|
10238
10712
|
REGISTRY_URL,
|
|
10239
10713
|
{ headers: { Accept: "application/json" }, timeout: REQUEST_TIMEOUT_MS },
|
|
10240
10714
|
(res) => {
|
|
@@ -10286,7 +10760,7 @@ function checkForUpdates() {
|
|
|
10286
10760
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
10287
10761
|
if (process.env.CI) return;
|
|
10288
10762
|
if (!process.stdout.isTTY) return;
|
|
10289
|
-
const current = true ? "2.15.
|
|
10763
|
+
const current = true ? "2.15.6" : null;
|
|
10290
10764
|
if (!current) return;
|
|
10291
10765
|
const cache = readCache();
|
|
10292
10766
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.6",
|
|
4
4
|
"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 — async. The terminal companion for CodeAgent Mobile.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|