codeam-cli 2.15.2 → 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/dist/index.js CHANGED
@@ -386,8 +386,8 @@ 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.2",
390
- description: "Remote control Claude Code (and other AI coding agents) from your mobile phone. Pair your device, send prompts, stream responses in real-time, and approve commands \u2014 from anywhere.",
389
+ version: "2.15.6",
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",
393
393
  bin: {
@@ -410,29 +410,29 @@ var package_default = {
410
410
  keywords: [
411
411
  "claude",
412
412
  "claude-code",
413
- "claude-code-mobile",
414
413
  "ai",
415
414
  "ai-agent",
416
415
  "ai-coding",
417
- "ai-pair-programming",
416
+ "ai-workflow",
417
+ "ai-workflow-continuity",
418
+ "async-ai-productivity",
419
+ "remote-ai-supervision",
418
420
  "cli",
419
- "remote-control",
420
- "remote-development",
421
- "mobile-ide",
422
- "mobile-coding",
423
- "pair-programming",
421
+ "developer-tools",
422
+ "devtools",
424
423
  "codeagent",
425
424
  "codeam",
426
425
  "anthropic",
426
+ "openai-codex",
427
427
  "cursor",
428
428
  "copilot",
429
429
  "jetbrains",
430
430
  "vscode",
431
+ "windsurf",
431
432
  "mcp",
432
- "code-from-phone",
433
- "afk-coding",
434
- "remote-pair-programming",
435
- "agent-control"
433
+ "remote-development",
434
+ "agent-control",
435
+ "agent-operations"
436
436
  ],
437
437
  homepage: "https://codeagent-mobile.com",
438
438
  bugs: {
@@ -456,6 +456,7 @@ var package_default = {
456
456
  },
457
457
  dependencies: {
458
458
  "@clack/prompts": "^1.2.0",
459
+ chokidar: "^3.6.0",
459
460
  picocolors: "^1.1.0",
460
461
  "qrcode-terminal": "^0.12.0",
461
462
  ws: "^8.18.0",
@@ -520,6 +521,34 @@ function vercelBypassHeader() {
520
521
  return token ? { "x-vercel-protection-bypass": token } : {};
521
522
  }
522
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
+
523
552
  // src/lib/poll-delay.ts
524
553
  var MAX_DELAY_MS = 3e4;
525
554
  function computePollDelay({ baseMs, failures }) {
@@ -534,12 +563,14 @@ async function requestCode(pluginId) {
534
563
  try {
535
564
  const runtime = process.env.CODESPACES === "true" ? "github-codespaces" : "local";
536
565
  const codespaceName = process.env.CODESPACE_NAME;
566
+ const branch = detectCurrentBranch();
537
567
  const result = await _transport.postJson(`${API_BASE}/api/pairing/code`, {
538
568
  pluginId,
539
569
  ideName: "Terminal (codeam-cli)",
540
570
  ideVersion: package_default.version,
541
571
  hostname: os2.hostname(),
542
572
  runtime,
573
+ branch,
543
574
  ...codespaceName ? { codespaceName } : {}
544
575
  });
545
576
  const data = result?.data;
@@ -982,7 +1013,7 @@ var CommandRelayService = class {
982
1013
  };
983
1014
 
984
1015
  // src/services/pty/unix.strategy.ts
985
- var import_child_process = require("child_process");
1016
+ var import_child_process2 = require("child_process");
986
1017
  var fs4 = __toESM(require("fs"));
987
1018
  var os4 = __toESM(require("os"));
988
1019
  var path4 = __toESM(require("path"));
@@ -1086,7 +1117,7 @@ var UnixPtyStrategy = class {
1086
1117
  const rows = process.stdout.rows || 50;
1087
1118
  this.helperPath = path4.join(os4.tmpdir(), "codeam-pty-helper.py");
1088
1119
  fs4.writeFileSync(this.helperPath, PYTHON_PTY_HELPER, { mode: 420 });
1089
- this.proc = (0, import_child_process.spawn)(python, [this.helperPath, cmd, ...args2], {
1120
+ this.proc = (0, import_child_process2.spawn)(python, [this.helperPath, cmd, ...args2], {
1090
1121
  stdio: ["pipe", "pipe", "inherit"],
1091
1122
  cwd,
1092
1123
  env: {
@@ -1151,7 +1182,7 @@ var UnixPtyStrategy = class {
1151
1182
  * are NOT interpreted by /bin/sh.
1152
1183
  */
1153
1184
  spawnDirect(cmd, cwd, args2 = []) {
1154
- this.proc = (0, import_child_process.spawn)(cmd, args2, {
1185
+ this.proc = (0, import_child_process2.spawn)(cmd, args2, {
1155
1186
  stdio: ["pipe", "inherit", "inherit"],
1156
1187
  cwd,
1157
1188
  env: process.env,
@@ -1223,7 +1254,7 @@ var UnixPtyStrategy = class {
1223
1254
  };
1224
1255
 
1225
1256
  // src/services/pty/windows.strategy.ts
1226
- var import_child_process2 = require("child_process");
1257
+ var import_child_process3 = require("child_process");
1227
1258
  var WindowsPtyStrategy = class {
1228
1259
  constructor(opts) {
1229
1260
  this.opts = opts;
@@ -1231,7 +1262,7 @@ var WindowsPtyStrategy = class {
1231
1262
  opts;
1232
1263
  proc = null;
1233
1264
  spawn(cmd, cwd, args2 = []) {
1234
- this.proc = (0, import_child_process2.spawn)(cmd, args2, {
1265
+ this.proc = (0, import_child_process3.spawn)(cmd, args2, {
1235
1266
  stdio: ["pipe", "pipe", "inherit"],
1236
1267
  cwd,
1237
1268
  env: {
@@ -1407,7 +1438,7 @@ var WindowsConPtyStrategy = class _WindowsConPtyStrategy {
1407
1438
  };
1408
1439
 
1409
1440
  // src/services/claude-installer.ts
1410
- var import_child_process3 = require("child_process");
1441
+ var import_child_process4 = require("child_process");
1411
1442
  var path6 = __toESM(require("path"));
1412
1443
  var os5 = __toESM(require("os"));
1413
1444
 
@@ -3360,7 +3391,7 @@ function runInstaller() {
3360
3391
  "irm https://claude.ai/install.ps1 | iex"
3361
3392
  ] : ["-c", "curl -fsSL https://claude.ai/install.sh | bash"];
3362
3393
  return new Promise((resolve2) => {
3363
- const proc = (0, import_child_process3.spawn)(cmd, args2, { stdio: "inherit" });
3394
+ const proc = (0, import_child_process4.spawn)(cmd, args2, { stdio: "inherit" });
3364
3395
  proc.on("error", (err) => {
3365
3396
  console.error(`
3366
3397
  \u2717 Installer failed to launch: ${err.message}`);
@@ -3659,7 +3690,7 @@ var AgentService = class {
3659
3690
  var fs5 = __toESM(require("fs"));
3660
3691
  var os6 = __toESM(require("os"));
3661
3692
  var path8 = __toESM(require("path"));
3662
- var import_child_process4 = require("child_process");
3693
+ var import_child_process5 = require("child_process");
3663
3694
  var HELPER_SCRIPT = `import os,pty,sys,select,signal,struct,fcntl,termios,errno
3664
3695
  m,s=pty.openpty()
3665
3696
  try:
@@ -3728,7 +3759,7 @@ async function fetchClaudeQuota() {
3728
3759
  resolve2(null);
3729
3760
  return;
3730
3761
  }
3731
- const proc = (0, import_child_process4.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
3762
+ const proc = (0, import_child_process5.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
3732
3763
  stdio: ["pipe", "pipe", "ignore"],
3733
3764
  cwd: process.cwd(),
3734
3765
  env: { ...process.env, TERM: "dumb", COLUMNS: "120", LINES: "30" }
@@ -4191,12 +4222,12 @@ var os9 = __toESM(require("os"));
4191
4222
  var path11 = __toESM(require("path"));
4192
4223
 
4193
4224
  // src/agents/claude/credentials.ts
4194
- var import_child_process5 = require("child_process");
4225
+ var import_child_process6 = require("child_process");
4195
4226
  var fs7 = __toESM(require("fs"));
4196
4227
  var os8 = __toESM(require("os"));
4197
4228
  var path10 = __toESM(require("path"));
4198
4229
  var import_util = require("util");
4199
- var execFileP = (0, import_util.promisify)(import_child_process5.execFile);
4230
+ var execFileP = (0, import_util.promisify)(import_child_process6.execFile);
4200
4231
  async function detectLocalClaudeCredentials() {
4201
4232
  const localClaudeDir = path10.join(os8.homedir(), ".claude");
4202
4233
  const flat = path10.join(localClaudeDir, ".credentials.json");
@@ -6125,6 +6156,886 @@ var HistoryService = class _HistoryService {
6125
6156
  }
6126
6157
  };
6127
6158
 
6159
+ // src/services/file-watcher.service.ts
6160
+ var import_child_process7 = require("child_process");
6161
+ var path15 = __toESM(require("path"));
6162
+
6163
+ // src/services/file-watcher/diff-parser.ts
6164
+ var HUNK_HEADER_RE = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
6165
+ function parseUnifiedDiff(diff) {
6166
+ if (!diff || diff.trim().length === 0) {
6167
+ return {
6168
+ fileStatus: "modified",
6169
+ hunks: [],
6170
+ totalLinesAdded: 0,
6171
+ totalLinesRemoved: 0
6172
+ };
6173
+ }
6174
+ const rawLines = diff.split(/\r?\n/);
6175
+ const fileStatus = detectFileStatus(rawLines);
6176
+ const hunks = [];
6177
+ let current = null;
6178
+ let oldLine = 0;
6179
+ let newLine = 0;
6180
+ let totalAdded = 0;
6181
+ let totalRemoved = 0;
6182
+ for (const raw of rawLines) {
6183
+ if (raw.startsWith("@@")) {
6184
+ const match = raw.match(HUNK_HEADER_RE);
6185
+ if (!match) continue;
6186
+ if (current) hunks.push(current);
6187
+ oldLine = parseInt(match[1], 10);
6188
+ newLine = parseInt(match[2], 10);
6189
+ current = {
6190
+ header: raw,
6191
+ lines: [],
6192
+ linesAdded: 0,
6193
+ linesRemoved: 0
6194
+ };
6195
+ continue;
6196
+ }
6197
+ if (current === null) continue;
6198
+ if (raw.startsWith("\\ No newline")) continue;
6199
+ if (raw.startsWith("+")) {
6200
+ current.lines.push({ type: "add", lineNumber: newLine, text: raw.slice(1) });
6201
+ current.linesAdded += 1;
6202
+ totalAdded += 1;
6203
+ newLine += 1;
6204
+ continue;
6205
+ }
6206
+ if (raw.startsWith("-")) {
6207
+ current.lines.push({ type: "remove", lineNumber: oldLine, text: raw.slice(1) });
6208
+ current.linesRemoved += 1;
6209
+ totalRemoved += 1;
6210
+ oldLine += 1;
6211
+ continue;
6212
+ }
6213
+ if (raw.startsWith(" ")) {
6214
+ current.lines.push({ type: "context", lineNumber: newLine, text: raw.slice(1) });
6215
+ newLine += 1;
6216
+ oldLine += 1;
6217
+ continue;
6218
+ }
6219
+ }
6220
+ if (current) hunks.push(current);
6221
+ return {
6222
+ fileStatus,
6223
+ hunks,
6224
+ totalLinesAdded: totalAdded,
6225
+ totalLinesRemoved: totalRemoved
6226
+ };
6227
+ }
6228
+ function detectFileStatus(rawLines) {
6229
+ for (const line of rawLines) {
6230
+ if (line.startsWith("@@")) break;
6231
+ if (line.startsWith("new file mode")) return "added";
6232
+ if (line.startsWith("deleted file mode")) return "deleted";
6233
+ if (line.startsWith("rename from ") || line.startsWith("rename to ")) {
6234
+ return "renamed";
6235
+ }
6236
+ if (line.startsWith("--- /dev/null")) return "added";
6237
+ if (line.startsWith("+++ /dev/null")) return "deleted";
6238
+ }
6239
+ return "modified";
6240
+ }
6241
+
6242
+ // src/services/file-watcher/transport.ts
6243
+ var http5 = __toESM(require("http"));
6244
+ var https5 = __toESM(require("https"));
6245
+ var _transport3 = {
6246
+ post: _post2
6247
+ };
6248
+ function _post2(url, headers, payload) {
6249
+ return new Promise((resolve2, reject) => {
6250
+ let settled = false;
6251
+ const u2 = new URL(url);
6252
+ const lib = u2.protocol === "https:" ? https5 : http5;
6253
+ const req = lib.request(
6254
+ {
6255
+ hostname: u2.hostname,
6256
+ port: u2.port || (u2.protocol === "https:" ? 443 : 80),
6257
+ path: u2.pathname + u2.search,
6258
+ method: "POST",
6259
+ headers: {
6260
+ ...headers,
6261
+ ...vercelBypassHeader(),
6262
+ "Content-Length": Buffer.byteLength(payload)
6263
+ },
6264
+ timeout: 8e3
6265
+ },
6266
+ (res) => {
6267
+ let body = "";
6268
+ res.on("data", (c2) => {
6269
+ body += c2.toString();
6270
+ });
6271
+ res.on("end", () => {
6272
+ if (settled) return;
6273
+ settled = true;
6274
+ resolve2({ statusCode: res.statusCode ?? 0, body });
6275
+ });
6276
+ }
6277
+ );
6278
+ req.on("error", (err) => {
6279
+ if (settled) return;
6280
+ settled = true;
6281
+ reject(err);
6282
+ });
6283
+ req.on("timeout", () => {
6284
+ req.destroy();
6285
+ });
6286
+ req.write(payload);
6287
+ req.end();
6288
+ });
6289
+ }
6290
+
6291
+ // src/services/file-watcher.service.ts
6292
+ var API_BASE5 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
6293
+ var DEBOUNCE_MS = 250;
6294
+ var MAX_RETRIES = 2;
6295
+ var RETRY_BACKOFF_MS = 300;
6296
+ var FileWatcherService = class {
6297
+ constructor(opts) {
6298
+ this.opts = opts;
6299
+ this.apiBase = opts.apiBaseUrl ?? API_BASE5;
6300
+ }
6301
+ opts;
6302
+ watcher = null;
6303
+ pending = /* @__PURE__ */ new Map();
6304
+ apiBase;
6305
+ stopped = false;
6306
+ /**
6307
+ * Start watching `opts.workingDir`. Idempotent (second call is a
6308
+ * no-op). Resolves once chokidar's initial scan completes; that
6309
+ * way the `start.ts` orchestrator can sequence "agent up → watcher
6310
+ * ready" deterministically if it wants to, though today it
6311
+ * doesn't await this.
6312
+ */
6313
+ async start() {
6314
+ if (this.watcher) return;
6315
+ if (this.stopped) {
6316
+ throw new Error("FileWatcherService has already been stopped \u2014 re-instantiate to restart.");
6317
+ }
6318
+ let chokidar;
6319
+ try {
6320
+ chokidar = require("chokidar");
6321
+ } catch (err) {
6322
+ log.warn(
6323
+ "fileWatcher",
6324
+ `chokidar unavailable \u2014 file change emission disabled`,
6325
+ err
6326
+ );
6327
+ return;
6328
+ }
6329
+ const watcher = chokidar.watch(this.opts.workingDir, {
6330
+ ignored: [
6331
+ /(^|[\\/])\../,
6332
+ // dot-files & dot-dirs (.git, .next, .expo, .DS_Store, …)
6333
+ /node_modules/,
6334
+ /dist/,
6335
+ /build/,
6336
+ /out/,
6337
+ /coverage/,
6338
+ /\.turbo/,
6339
+ /\.cache/,
6340
+ /\.parcel-cache/,
6341
+ // Build outputs that aren't a typical "dist" target
6342
+ /target\//,
6343
+ /__pycache__/
6344
+ ],
6345
+ ignoreInitial: true,
6346
+ // we only care about post-start changes
6347
+ persistent: true,
6348
+ awaitWriteFinish: {
6349
+ // Coalesces rapid sequential writes (npm install spam, build
6350
+ // tools emitting bursts). Lower than chokidar's default so
6351
+ // the user sees their Files screen update within 0.5 s of
6352
+ // saving.
6353
+ stabilityThreshold: 150,
6354
+ pollInterval: 50
6355
+ }
6356
+ });
6357
+ watcher.on("add", (filePath) => this.schedule(filePath, "add"));
6358
+ watcher.on("change", (filePath) => this.schedule(filePath, "change"));
6359
+ watcher.on("unlink", (filePath) => this.schedule(filePath, "unlink"));
6360
+ this.watcher = watcher;
6361
+ log.info(
6362
+ "fileWatcher",
6363
+ `watching ${this.opts.workingDir} for session=${this.opts.sessionId.slice(0, 8)}`
6364
+ );
6365
+ }
6366
+ /**
6367
+ * Stop watching. Idempotent — safe to call multiple times. After
6368
+ * stop, the instance is dead; create a new one to resume. (This
6369
+ * matches the `OutputService` / `CommandRelayService` style.)
6370
+ */
6371
+ async stop() {
6372
+ if (this.stopped) return;
6373
+ this.stopped = true;
6374
+ for (const entry of this.pending.values()) {
6375
+ clearTimeout(entry.timer);
6376
+ }
6377
+ this.pending.clear();
6378
+ if (this.watcher) {
6379
+ try {
6380
+ await this.watcher.close();
6381
+ } catch (err) {
6382
+ log.warn("fileWatcher", "error closing chokidar", err);
6383
+ }
6384
+ this.watcher = null;
6385
+ }
6386
+ log.info("fileWatcher", `stopped (session=${this.opts.sessionId.slice(0, 8)})`);
6387
+ }
6388
+ /**
6389
+ * Coalesce rapid writes per-file. Each fresh event resets the
6390
+ * 250 ms debounce timer. When the timer fires, we compute the
6391
+ * diff once and emit.
6392
+ *
6393
+ * `unlink` events bypass the diff path — we emit a synthetic
6394
+ * deletion directly because `git diff <path>` for a removed file
6395
+ * produces a diff that's already encoded as a deletion in
6396
+ * `parseUnifiedDiff`, but in practice the file is gone and the
6397
+ * synthetic path is simpler and avoids a race with git's index.
6398
+ */
6399
+ schedule(absPath, changeType) {
6400
+ if (this.stopped) return;
6401
+ const existing = this.pending.get(absPath);
6402
+ if (existing) clearTimeout(existing.timer);
6403
+ const timer = setTimeout(() => {
6404
+ this.pending.delete(absPath);
6405
+ void this.emitForFile(absPath, changeType);
6406
+ }, DEBOUNCE_MS);
6407
+ this.pending.set(absPath, {
6408
+ lastEventAt: Date.now(),
6409
+ timer,
6410
+ changeType
6411
+ });
6412
+ }
6413
+ /**
6414
+ * Visible for tests — lets vitest pump a synthetic file event
6415
+ * through the debounce + diff + emit pipeline without spinning up
6416
+ * a real chokidar watcher.
6417
+ */
6418
+ /* @internal */
6419
+ _scheduleForTest(absPath, changeType) {
6420
+ this.schedule(absPath, changeType);
6421
+ }
6422
+ async emitForFile(absPath, changeType) {
6423
+ if (this.stopped) return;
6424
+ const relPath = path15.relative(this.opts.workingDir, absPath);
6425
+ if (!relPath || relPath.startsWith("..")) {
6426
+ return;
6427
+ }
6428
+ let diffText = "";
6429
+ let fileStatus = "modified";
6430
+ if (changeType === "unlink") {
6431
+ const diff = await this.gitDiff(relPath);
6432
+ if (diff !== null && diff.trim().length > 0) {
6433
+ diffText = diff;
6434
+ } else {
6435
+ await this.postFileChanged({
6436
+ sessionId: this.opts.sessionId,
6437
+ pluginId: this.opts.pluginId,
6438
+ filePath: relPath,
6439
+ fileStatus: "deleted",
6440
+ linesAdded: 0,
6441
+ linesRemoved: 0,
6442
+ hunkCount: 0
6443
+ });
6444
+ return;
6445
+ }
6446
+ fileStatus = "deleted";
6447
+ } else {
6448
+ const diff = await this.gitDiff(relPath);
6449
+ if (diff === null) {
6450
+ log.warn(
6451
+ "fileWatcher",
6452
+ `git diff failed for ${relPath} \u2014 emitting file-changed only`
6453
+ );
6454
+ await this.postFileChanged({
6455
+ sessionId: this.opts.sessionId,
6456
+ pluginId: this.opts.pluginId,
6457
+ filePath: relPath,
6458
+ fileStatus: changeType === "add" ? "added" : "modified",
6459
+ linesAdded: 0,
6460
+ linesRemoved: 0,
6461
+ hunkCount: 0
6462
+ });
6463
+ return;
6464
+ }
6465
+ diffText = diff;
6466
+ }
6467
+ const parsed = parseUnifiedDiff(diffText);
6468
+ const finalStatus = parsed.fileStatus !== "modified" ? parsed.fileStatus : changeType === "add" ? "added" : changeType === "unlink" ? "deleted" : fileStatus;
6469
+ const reviewStatus = parsed.hunks.length > 0 ? "awaiting_review" : void 0;
6470
+ await this.postFileChanged({
6471
+ sessionId: this.opts.sessionId,
6472
+ pluginId: this.opts.pluginId,
6473
+ filePath: relPath,
6474
+ fileStatus: finalStatus,
6475
+ linesAdded: parsed.totalLinesAdded,
6476
+ linesRemoved: parsed.totalLinesRemoved,
6477
+ hunkCount: parsed.hunks.length,
6478
+ reviewStatus
6479
+ });
6480
+ for (const hunk of parsed.hunks) {
6481
+ await this.postReviewHunk({
6482
+ sessionId: this.opts.sessionId,
6483
+ pluginId: this.opts.pluginId,
6484
+ filePath: relPath,
6485
+ fileStatus: finalStatus,
6486
+ hunkHeader: hunk.header,
6487
+ lines: hunk.lines,
6488
+ linesAdded: hunk.linesAdded,
6489
+ linesRemoved: hunk.linesRemoved
6490
+ });
6491
+ }
6492
+ }
6493
+ /**
6494
+ * Compute the unified diff for a single path relative to the
6495
+ * working dir. Returns `null` when git is unavailable or the cwd
6496
+ * is not a repo. Returns `''` when there's no diff (a touch that
6497
+ * didn't change content).
6498
+ *
6499
+ * For tracked files we use `git diff --no-color -- <path>` which
6500
+ * compares the worktree against HEAD's blob.
6501
+ * For untracked files (`git ls-files --error-unmatch` exits non-
6502
+ * zero) we use `git diff --no-color --no-index /dev/null <path>`,
6503
+ * which produces an "added"-shaped diff against an empty source.
6504
+ */
6505
+ async gitDiff(relPath) {
6506
+ const tracked = await runGit(
6507
+ this.opts.workingDir,
6508
+ ["diff", "--no-color", "--", relPath]
6509
+ );
6510
+ if (tracked === null) return null;
6511
+ if (tracked.trim().length > 0) return tracked;
6512
+ const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
6513
+ const untracked = await runGit(
6514
+ this.opts.workingDir,
6515
+ ["diff", "--no-color", "--no-index", "--", devNull, relPath],
6516
+ { allowNonZeroExit: true }
6517
+ );
6518
+ return untracked ?? "";
6519
+ }
6520
+ async postFileChanged(body) {
6521
+ await this.postWithRetries(`${this.apiBase}/api/files/changed`, body);
6522
+ }
6523
+ async postReviewHunk(body) {
6524
+ await this.postWithRetries(`${this.apiBase}/api/review/hunks`, body);
6525
+ }
6526
+ async postWithRetries(url, body) {
6527
+ const payload = JSON.stringify(body);
6528
+ const headers = {
6529
+ "Content-Type": "application/json",
6530
+ "X-Codeam-Protocol-Version": "2.0.0",
6531
+ "X-Plugin-Auth-Token": this.opts.pluginAuthToken
6532
+ };
6533
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {
6534
+ try {
6535
+ const { statusCode, body: resBody } = await _transport3.post(url, headers, payload);
6536
+ if (statusCode >= 200 && statusCode < 300) {
6537
+ log.trace(
6538
+ "fileWatcher",
6539
+ `post ok url=${url} status=${statusCode} path=${body.filePath}`
6540
+ );
6541
+ return;
6542
+ }
6543
+ if (statusCode === 410 || statusCode === 404) {
6544
+ log.warn(
6545
+ "fileWatcher",
6546
+ `session dead (status=${statusCode}) \u2014 dropping ${body.filePath}`
6547
+ );
6548
+ this.stopped = true;
6549
+ return;
6550
+ }
6551
+ log.warn(
6552
+ "fileWatcher",
6553
+ `post failed url=${url} status=${statusCode} attempt=${attempt + 1} body=${resBody.slice(0, 200)}`
6554
+ );
6555
+ } catch (err) {
6556
+ log.warn(
6557
+ "fileWatcher",
6558
+ `post error url=${url} attempt=${attempt + 1}`,
6559
+ err
6560
+ );
6561
+ }
6562
+ if (attempt < MAX_RETRIES) {
6563
+ await sleep(RETRY_BACKOFF_MS * (attempt + 1));
6564
+ }
6565
+ }
6566
+ log.warn(
6567
+ "fileWatcher",
6568
+ `giving up after ${MAX_RETRIES + 1} attempts \u2014 path=${body.filePath}`
6569
+ );
6570
+ }
6571
+ };
6572
+ function sleep(ms) {
6573
+ return new Promise((r) => setTimeout(r, ms));
6574
+ }
6575
+ async function runGit(cwd, args2, opts = {}) {
6576
+ return _runGit(cwd, args2, opts);
6577
+ }
6578
+ var _gitSeam = {
6579
+ run: _runGitImpl
6580
+ };
6581
+ async function _runGitImpl(cwd, args2, opts = {}) {
6582
+ return new Promise((resolve2) => {
6583
+ let proc;
6584
+ try {
6585
+ proc = (0, import_child_process7.spawn)("git", args2, { cwd, env: process.env });
6586
+ } catch {
6587
+ resolve2(null);
6588
+ return;
6589
+ }
6590
+ let stdout = "";
6591
+ let stderr = "";
6592
+ proc.stdout?.on("data", (c2) => {
6593
+ stdout += c2.toString();
6594
+ });
6595
+ proc.stderr?.on("data", (c2) => {
6596
+ stderr += c2.toString();
6597
+ });
6598
+ proc.on("error", () => resolve2(null));
6599
+ proc.on("close", (code) => {
6600
+ if (code === 0 || opts.allowNonZeroExit) {
6601
+ resolve2(stdout);
6602
+ } else {
6603
+ log.trace("fileWatcher", `git ${args2.join(" ")} exited ${code} stderr=${stderr.slice(0, 200)}`);
6604
+ resolve2(null);
6605
+ }
6606
+ });
6607
+ });
6608
+ }
6609
+ function _runGit(cwd, args2, opts = {}) {
6610
+ return _gitSeam.run(cwd, args2, opts);
6611
+ }
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
+
6128
7039
  // src/commands/start/quota-fetcher.ts
6129
7040
  var inProgress = false;
6130
7041
  function fetchQuotaUsage(runtime, historySvc) {
@@ -6140,13 +7051,13 @@ function fetchQuotaUsage(runtime, historySvc) {
6140
7051
  }
6141
7052
 
6142
7053
  // src/commands/start/keep-alive.ts
6143
- var import_child_process6 = require("child_process");
7054
+ var import_child_process8 = require("child_process");
6144
7055
  function buildKeepAlive(ctx) {
6145
7056
  let timer = null;
6146
7057
  async function setIdleTimeout(minutes) {
6147
7058
  if (!ctx.inCodespace || !ctx.codespaceName) return;
6148
7059
  await new Promise((resolve2) => {
6149
- const proc = (0, import_child_process6.spawn)(
7060
+ const proc = (0, import_child_process8.spawn)(
6150
7061
  "gh",
6151
7062
  [
6152
7063
  "api",
@@ -6185,9 +7096,9 @@ function buildKeepAlive(ctx) {
6185
7096
  // src/commands/start/handlers.ts
6186
7097
  var fs14 = __toESM(require("fs"));
6187
7098
  var os13 = __toESM(require("os"));
6188
- var path18 = __toESM(require("path"));
6189
- var import_crypto2 = require("crypto");
6190
- var import_child_process9 = require("child_process");
7099
+ var path19 = __toESM(require("path"));
7100
+ var import_crypto3 = require("crypto");
7101
+ var import_child_process11 = require("child_process");
6191
7102
 
6192
7103
  // src/lib/payload.ts
6193
7104
  var import_zod2 = require("zod");
@@ -6244,7 +7155,7 @@ function parsePayload(schema, raw) {
6244
7155
 
6245
7156
  // src/services/file-ops.service.ts
6246
7157
  var fs12 = __toESM(require("fs/promises"));
6247
- var path15 = __toESM(require("path"));
7158
+ var path16 = __toESM(require("path"));
6248
7159
  var MAX_FILE_BYTES = 5 * 1024 * 1024;
6249
7160
  var MAX_WALK_DEPTH = 6;
6250
7161
  var MAX_VISITED_DIRS = 5e3;
@@ -6279,8 +7190,8 @@ var SUBDIR_IGNORE = /* @__PURE__ */ new Set([
6279
7190
  "__pycache__"
6280
7191
  ]);
6281
7192
  function isUnder(parent, candidate) {
6282
- const rel = path15.relative(parent, candidate);
6283
- return rel === "" || !rel.startsWith("..") && !path15.isAbsolute(rel);
7193
+ const rel = path16.relative(parent, candidate);
7194
+ return rel === "" || !rel.startsWith("..") && !path16.isAbsolute(rel);
6284
7195
  }
6285
7196
  async function isExistingFile(absPath) {
6286
7197
  try {
@@ -6303,7 +7214,7 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
6303
7214
  }
6304
7215
  for (const e of entries) {
6305
7216
  if (!e.isFile()) continue;
6306
- const full = path15.join(dir, e.name);
7217
+ const full = path16.join(dir, e.name);
6307
7218
  if (needleVariants.some((needle) => full.endsWith(needle))) {
6308
7219
  ctx.matches.push(full);
6309
7220
  if (ctx.matches.length >= ctx.cap) return;
@@ -6313,21 +7224,21 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
6313
7224
  if (!e.isDirectory()) continue;
6314
7225
  if (SUBDIR_IGNORE.has(e.name)) continue;
6315
7226
  if (e.name.startsWith(".") && SUBDIR_IGNORE.has(e.name)) continue;
6316
- await walkForSuffix(path15.join(dir, e.name), needleVariants, depth + 1, ctx);
7227
+ await walkForSuffix(path16.join(dir, e.name), needleVariants, depth + 1, ctx);
6317
7228
  if (ctx.matches.length >= ctx.cap) return;
6318
7229
  }
6319
7230
  }
6320
7231
  async function findFile(rawPath) {
6321
7232
  const cwd = process.cwd();
6322
- if (path15.isAbsolute(rawPath)) {
6323
- const abs = path15.normalize(rawPath);
7233
+ if (path16.isAbsolute(rawPath)) {
7234
+ const abs = path16.normalize(rawPath);
6324
7235
  if (isUnder(cwd, abs) && await isExistingFile(abs)) return abs;
6325
7236
  }
6326
- const direct = path15.resolve(cwd, rawPath);
7237
+ const direct = path16.resolve(cwd, rawPath);
6327
7238
  if (isUnder(cwd, direct) && await isExistingFile(direct)) return direct;
6328
- const normalized = path15.normalize(rawPath).replace(/^[./\\]+/, "");
7239
+ const normalized = path16.normalize(rawPath).replace(/^[./\\]+/, "");
6329
7240
  const needles = [
6330
- `${path15.sep}${normalized}`,
7241
+ `${path16.sep}${normalized}`,
6331
7242
  `/${normalized}`
6332
7243
  ].filter((v, i, a) => a.indexOf(v) === i);
6333
7244
  const ctx = { visited: 0, matches: [], cap: 16 };
@@ -6341,7 +7252,7 @@ async function findWriteTarget(rawPath) {
6341
7252
  const found = await findFile(rawPath);
6342
7253
  if (found) return found;
6343
7254
  const cwd = process.cwd();
6344
- const fallback = path15.isAbsolute(rawPath) ? path15.normalize(rawPath) : path15.resolve(cwd, rawPath);
7255
+ const fallback = path16.isAbsolute(rawPath) ? path16.normalize(rawPath) : path16.resolve(cwd, rawPath);
6345
7256
  if (!isUnder(cwd, fallback)) return null;
6346
7257
  return fallback;
6347
7258
  }
@@ -6381,7 +7292,7 @@ async function writeProjectFile(rawPath, content) {
6381
7292
  if (Buffer.byteLength(content, "utf-8") > MAX_FILE_BYTES) {
6382
7293
  return { error: "Content too large." };
6383
7294
  }
6384
- await fs12.mkdir(path15.dirname(abs), { recursive: true });
7295
+ await fs12.mkdir(path16.dirname(abs), { recursive: true });
6385
7296
  await fs12.writeFile(abs, content, "utf-8");
6386
7297
  return { ok: true };
6387
7298
  } catch (e) {
@@ -6391,11 +7302,11 @@ async function writeProjectFile(rawPath, content) {
6391
7302
  }
6392
7303
 
6393
7304
  // src/services/project-ops.service.ts
6394
- var import_child_process7 = require("child_process");
7305
+ var import_child_process9 = require("child_process");
6395
7306
  var import_util2 = require("util");
6396
7307
  var fs13 = __toESM(require("fs/promises"));
6397
- var path16 = __toESM(require("path"));
6398
- var execFileP2 = (0, import_util2.promisify)(import_child_process7.execFile);
7308
+ var path17 = __toESM(require("path"));
7309
+ var execFileP2 = (0, import_util2.promisify)(import_child_process9.execFile);
6399
7310
  var PROJECT_IGNORE = /* @__PURE__ */ new Set([
6400
7311
  "node_modules",
6401
7312
  ".git",
@@ -6452,12 +7363,12 @@ async function listProjectFiles(opts = {}) {
6452
7363
  return;
6453
7364
  }
6454
7365
  if (PROJECT_IGNORE.has(e.name)) continue;
6455
- const full = path16.join(dir, e.name);
7366
+ const full = path17.join(dir, e.name);
6456
7367
  if (e.isDirectory()) {
6457
7368
  if (depth >= 12) continue;
6458
7369
  await walk(full, depth + 1);
6459
7370
  } else if (e.isFile()) {
6460
- const rel = path16.relative(root, full);
7371
+ const rel = path17.relative(root, full);
6461
7372
  if (q2 && !rel.toLowerCase().includes(q2) && !e.name.toLowerCase().includes(q2)) {
6462
7373
  continue;
6463
7374
  }
@@ -6565,7 +7476,7 @@ async function gitStatus(cwd) {
6565
7476
  let hasMergeInProgress = false;
6566
7477
  try {
6567
7478
  const gitDir = (await git(["rev-parse", "--git-dir"], root)).stdout.trim();
6568
- const mergeHead = path16.isAbsolute(gitDir) ? path16.join(gitDir, "MERGE_HEAD") : path16.join(root, gitDir, "MERGE_HEAD");
7479
+ const mergeHead = path17.isAbsolute(gitDir) ? path17.join(gitDir, "MERGE_HEAD") : path17.join(root, gitDir, "MERGE_HEAD");
6569
7480
  await fs13.access(mergeHead);
6570
7481
  hasMergeInProgress = true;
6571
7482
  } catch {
@@ -6712,7 +7623,7 @@ async function jsSearchFiles(opts, cwd, cap) {
6712
7623
  }
6713
7624
  let content = "";
6714
7625
  try {
6715
- content = await fs13.readFile(path16.join(cwd, f.path), "utf8");
7626
+ content = await fs13.readFile(path17.join(cwd, f.path), "utf8");
6716
7627
  } catch {
6717
7628
  continue;
6718
7629
  }
@@ -6735,8 +7646,8 @@ async function jsSearchFiles(opts, cwd, cap) {
6735
7646
  }
6736
7647
 
6737
7648
  // src/services/terminal-ops.service.ts
6738
- var import_child_process8 = require("child_process");
6739
- var import_crypto = require("crypto");
7649
+ var import_child_process10 = require("child_process");
7650
+ var import_crypto2 = require("crypto");
6740
7651
  var import_path = __toESM(require("path"));
6741
7652
  var MAX_CONCURRENT_SESSIONS = 4;
6742
7653
  var nodePtyModule;
@@ -6857,7 +7768,7 @@ function createPythonSession(id, shell, cwd, env, cols, rows) {
6857
7768
  }
6858
7769
  let child;
6859
7770
  try {
6860
- child = (0, import_child_process8.spawn)(python, ["-c", PYTHON_TERMINAL_HELPER, shell], {
7771
+ child = (0, import_child_process10.spawn)(python, ["-c", PYTHON_TERMINAL_HELPER, shell], {
6861
7772
  cwd,
6862
7773
  env: { ...env, COLUMNS: String(cols), LINES: String(rows) },
6863
7774
  stdio: ["pipe", "pipe", "pipe"]
@@ -6912,7 +7823,7 @@ function openTerminal(opts) {
6912
7823
  };
6913
7824
  const cols = Math.max(1, Math.min(opts.cols ?? 80, 500));
6914
7825
  const rows = Math.max(1, Math.min(opts.rows ?? 24, 200));
6915
- const id = (0, import_crypto.randomUUID)();
7826
+ const id = (0, import_crypto2.randomUUID)();
6916
7827
  const ptyMod = loadNodePty2();
6917
7828
  if (ptyMod) {
6918
7829
  try {
@@ -6992,7 +7903,7 @@ function closeTerminal(sessionId) {
6992
7903
  function saveFilesTemp(files) {
6993
7904
  return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
6994
7905
  const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
6995
- const tmpPath = path18.join(os13.tmpdir(), `codeam-${(0, import_crypto2.randomUUID)()}-${safeName}`);
7906
+ const tmpPath = path19.join(os13.tmpdir(), `codeam-${(0, import_crypto3.randomUUID)()}-${safeName}`);
6996
7907
  fs14.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
6997
7908
  return tmpPath;
6998
7909
  });
@@ -7124,7 +8035,7 @@ var sessionTerminated = (ctx) => {
7124
8035
  } catch {
7125
8036
  }
7126
8037
  try {
7127
- const proc = (0, import_child_process9.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
8038
+ const proc = (0, import_child_process11.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
7128
8039
  detached: true,
7129
8040
  stdio: "ignore"
7130
8041
  });
@@ -7146,7 +8057,7 @@ var shutdownSession = async (ctx, cmd) => {
7146
8057
  }
7147
8058
  if (ctx.keepAliveCtx.inCodespace && ctx.keepAliveCtx.codespaceName) {
7148
8059
  try {
7149
- const stopProc = (0, import_child_process9.spawn)(
8060
+ const stopProc = (0, import_child_process11.spawn)(
7150
8061
  "bash",
7151
8062
  ["-lc", `sleep 1; gh codespace stop -c ${JSON.stringify(ctx.keepAliveCtx.codespaceName)} >/dev/null 2>&1 || true`],
7152
8063
  { detached: true, stdio: "ignore" }
@@ -7156,7 +8067,7 @@ var shutdownSession = async (ctx, cmd) => {
7156
8067
  }
7157
8068
  }
7158
8069
  try {
7159
- const proc = (0, import_child_process9.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
8070
+ const proc = (0, import_child_process11.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
7160
8071
  detached: true,
7161
8072
  stdio: "ignore"
7162
8073
  });
@@ -7375,21 +8286,40 @@ async function start(requestedAgent) {
7375
8286
  session.pluginAuthToken,
7376
8287
  runtime
7377
8288
  );
8289
+ const fileWatcher = session.pluginAuthToken ? new FileWatcherService({
8290
+ workingDir: cwd,
8291
+ sessionId: session.id,
8292
+ pluginId,
8293
+ pluginAuthToken: session.pluginAuthToken
8294
+ }) : null;
8295
+ let streamingEmitter = null;
7378
8296
  const claude = new AgentService(
7379
8297
  runtime,
7380
8298
  {
7381
8299
  cwd,
7382
8300
  onData(raw) {
7383
8301
  outputSvc.push(raw);
8302
+ streamingEmitter?.push(raw);
7384
8303
  },
7385
8304
  onExit(code) {
7386
8305
  process.removeListener("SIGINT", sigintHandler);
7387
8306
  outputSvc.dispose();
7388
8307
  relay.stop();
8308
+ void fileWatcher?.stop();
8309
+ void streamingEmitter?.stop();
7389
8310
  process.exit(code);
7390
8311
  }
7391
8312
  }
7392
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
+ }
7393
8323
  const ctx = {
7394
8324
  outputSvc,
7395
8325
  claude,
@@ -7415,12 +8345,19 @@ async function start(requestedAgent) {
7415
8345
  claude.kill();
7416
8346
  outputSvc.dispose();
7417
8347
  relay.stop();
8348
+ void fileWatcher?.stop();
8349
+ void streamingEmitter?.stop();
7418
8350
  process.exit(0);
7419
8351
  }
7420
8352
  process.once("SIGINT", sigintHandler);
7421
8353
  await claude.spawn();
7422
8354
  await outputSvc.startTerminalTurn();
7423
8355
  relay.start();
8356
+ if (fileWatcher) {
8357
+ fileWatcher.start().catch(() => {
8358
+ });
8359
+ }
8360
+ streamingEmitter?.start();
7424
8361
  setTimeout(() => {
7425
8362
  historySvc.load().catch(() => {
7426
8363
  });
@@ -7429,7 +8366,7 @@ async function start(requestedAgent) {
7429
8366
  }
7430
8367
 
7431
8368
  // src/commands/pair.ts
7432
- var import_crypto3 = require("crypto");
8369
+ var import_crypto4 = require("crypto");
7433
8370
  var import_picocolors3 = __toESM(require("picocolors"));
7434
8371
 
7435
8372
  // src/ui/prompts.ts
@@ -7492,7 +8429,7 @@ async function pair(args2 = []) {
7492
8429
  const flagAgent = parseAgentFlag(args2);
7493
8430
  const agentId = flagAgent ?? await promptForAgent(config.preferredAgent ?? "claude");
7494
8431
  showIntro();
7495
- const pluginId = (0, import_crypto3.randomUUID)();
8432
+ const pluginId = (0, import_crypto4.randomUUID)();
7496
8433
  const spin = dist_exports.spinner();
7497
8434
  spin.start("Requesting pairing code...");
7498
8435
  const result = await requestCode(pluginId);
@@ -7548,8 +8485,8 @@ async function pair(args2 = []) {
7548
8485
  // src/commands/pair-auto.ts
7549
8486
  var fs15 = __toESM(require("fs"));
7550
8487
  var os14 = __toESM(require("os"));
7551
- var import_crypto4 = require("crypto");
7552
- var API_BASE5 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
8488
+ var import_crypto5 = require("crypto");
8489
+ var API_BASE7 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
7553
8490
  function fail(msg) {
7554
8491
  console.error(`
7555
8492
  ${msg}
@@ -7564,12 +8501,12 @@ function readTokenFromArgs(args2) {
7564
8501
  }
7565
8502
  const fileFlag = args2.find((a) => a.startsWith("--token-file="));
7566
8503
  if (fileFlag) {
7567
- const path24 = fileFlag.slice("--token-file=".length);
8504
+ const path25 = fileFlag.slice("--token-file=".length);
7568
8505
  try {
7569
- const content = fs15.readFileSync(path24, "utf8").trim();
7570
- if (content.length === 0) fail(`--token-file ${path24} is empty`);
8506
+ const content = fs15.readFileSync(path25, "utf8").trim();
8507
+ if (content.length === 0) fail(`--token-file ${path25} is empty`);
7571
8508
  try {
7572
- fs15.unlinkSync(path24);
8509
+ fs15.unlinkSync(path25);
7573
8510
  } catch {
7574
8511
  }
7575
8512
  return content;
@@ -7581,14 +8518,18 @@ function readTokenFromArgs(args2) {
7581
8518
  fail("codeam pair-auto requires --token-file=<path>, --token=<value>, or CODEAM_AUTO_TOKEN env");
7582
8519
  }
7583
8520
  async function claim(token, pluginId) {
7584
- const url = `${API_BASE5}/api/pairing/claim-auto-token`;
8521
+ const url = `${API_BASE7}/api/pairing/claim-auto-token`;
7585
8522
  const body = {
7586
8523
  token,
7587
8524
  pluginId,
7588
8525
  ideName: "codeam-cli (codespace)",
7589
8526
  ideVersion: process.env.npm_package_version ?? "unknown",
7590
8527
  hostname: os14.hostname(),
7591
- 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()
7592
8533
  };
7593
8534
  const res = await fetch(url, {
7594
8535
  method: "POST",
@@ -7610,7 +8551,7 @@ async function claim(token, pluginId) {
7610
8551
  }
7611
8552
  async function pairAuto(args2) {
7612
8553
  const token = readTokenFromArgs(args2);
7613
- const pluginId = (0, import_crypto4.randomUUID)();
8554
+ const pluginId = (0, import_crypto5.randomUUID)();
7614
8555
  console.log(" Claiming pairing token\u2026");
7615
8556
  const claimed = await claim(token, pluginId);
7616
8557
  if (!isKnownAgentId(claimed.agent)) {
@@ -7736,11 +8677,11 @@ async function logout() {
7736
8677
  var import_picocolors9 = __toESM(require("picocolors"));
7737
8678
 
7738
8679
  // src/services/providers/github-codespaces.ts
7739
- var import_child_process10 = require("child_process");
8680
+ var import_child_process12 = require("child_process");
7740
8681
  var import_util3 = require("util");
7741
8682
  var import_picocolors7 = __toESM(require("picocolors"));
7742
- var path19 = __toESM(require("path"));
7743
- var execFileP3 = (0, import_util3.promisify)(import_child_process10.execFile);
8683
+ var path20 = __toESM(require("path"));
8684
+ var execFileP3 = (0, import_util3.promisify)(import_child_process12.execFile);
7744
8685
  var MAX_BUFFER = 8 * 1024 * 1024;
7745
8686
  function resetStdinForChild() {
7746
8687
  if (process.stdin.isTTY) {
@@ -7784,7 +8725,7 @@ var GitHubCodespacesProvider = class {
7784
8725
  if (!isAuthed) {
7785
8726
  resetStdinForChild();
7786
8727
  await new Promise((resolve2, reject) => {
7787
- const proc = (0, import_child_process10.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
8728
+ const proc = (0, import_child_process12.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
7788
8729
  stdio: "inherit"
7789
8730
  });
7790
8731
  proc.on("exit", (code) => {
@@ -7818,7 +8759,7 @@ var GitHubCodespacesProvider = class {
7818
8759
  wt(noteLines.join("\n"), "One more permission needed");
7819
8760
  resetStdinForChild();
7820
8761
  const refreshCode = await new Promise((resolve2, reject) => {
7821
- const proc = (0, import_child_process10.spawn)(
8762
+ const proc = (0, import_child_process12.spawn)(
7822
8763
  "gh",
7823
8764
  ["auth", "refresh", "-h", "github.com", "-s", "codespace"],
7824
8765
  { stdio: "inherit" }
@@ -7968,7 +8909,7 @@ var GitHubCodespacesProvider = class {
7968
8909
  O2.step(`Installing gh via ${installCmd.describe}\u2026`);
7969
8910
  resetStdinForChild();
7970
8911
  const ok = await new Promise((resolve2) => {
7971
- const proc = (0, import_child_process10.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
8912
+ const proc = (0, import_child_process12.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
7972
8913
  proc.on("exit", (code) => resolve2(code === 0));
7973
8914
  proc.on("error", () => resolve2(false));
7974
8915
  });
@@ -7995,7 +8936,7 @@ var GitHubCodespacesProvider = class {
7995
8936
  );
7996
8937
  resetStdinForChild();
7997
8938
  await new Promise((resolve2, reject) => {
7998
- const proc = (0, import_child_process10.spawn)(
8939
+ const proc = (0, import_child_process12.spawn)(
7999
8940
  "gh",
8000
8941
  ["auth", "refresh", "-h", "github.com", "-s", "repo,read:org"],
8001
8942
  { stdio: "inherit" }
@@ -8173,7 +9114,7 @@ var GitHubCodespacesProvider = class {
8173
9114
  async streamCommand(workspaceId, command2) {
8174
9115
  resetStdinForChild();
8175
9116
  return new Promise((resolve2, reject) => {
8176
- const proc = (0, import_child_process10.spawn)(
9117
+ const proc = (0, import_child_process12.spawn)(
8177
9118
  "gh",
8178
9119
  ["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
8179
9120
  { stdio: "inherit" }
@@ -8200,11 +9141,11 @@ var GitHubCodespacesProvider = class {
8200
9141
  `mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
8201
9142
  ];
8202
9143
  await new Promise((resolve2, reject) => {
8203
- const tar = (0, import_child_process10.spawn)("tar", tarArgs, {
9144
+ const tar = (0, import_child_process12.spawn)("tar", tarArgs, {
8204
9145
  stdio: ["ignore", "pipe", "pipe"],
8205
9146
  env: tarEnv
8206
9147
  });
8207
- const ssh = (0, import_child_process10.spawn)("gh", sshArgs, {
9148
+ const ssh = (0, import_child_process12.spawn)("gh", sshArgs, {
8208
9149
  stdio: [tar.stdout, "pipe", "pipe"]
8209
9150
  });
8210
9151
  let tarErr = "";
@@ -8228,7 +9169,7 @@ var GitHubCodespacesProvider = class {
8228
9169
  });
8229
9170
  }
8230
9171
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
8231
- const remoteDir = path19.posix.dirname(remotePath);
9172
+ const remoteDir = path20.posix.dirname(remotePath);
8232
9173
  const parts = [
8233
9174
  `mkdir -p ${shellQuote(remoteDir)}`,
8234
9175
  `cat > ${shellQuote(remotePath)}`
@@ -8238,7 +9179,7 @@ var GitHubCodespacesProvider = class {
8238
9179
  }
8239
9180
  const cmd = parts.join(" && ");
8240
9181
  await new Promise((resolve2, reject) => {
8241
- const proc = (0, import_child_process10.spawn)(
9182
+ const proc = (0, import_child_process12.spawn)(
8242
9183
  "gh",
8243
9184
  ["codespace", "ssh", "-c", workspaceId, "--", cmd],
8244
9185
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8296,11 +9237,11 @@ function shellQuote(s) {
8296
9237
  }
8297
9238
 
8298
9239
  // src/services/providers/gitpod.ts
8299
- var import_child_process11 = require("child_process");
9240
+ var import_child_process13 = require("child_process");
8300
9241
  var import_util4 = require("util");
8301
- var path20 = __toESM(require("path"));
9242
+ var path21 = __toESM(require("path"));
8302
9243
  var import_picocolors8 = __toESM(require("picocolors"));
8303
- var execFileP4 = (0, import_util4.promisify)(import_child_process11.execFile);
9244
+ var execFileP4 = (0, import_util4.promisify)(import_child_process13.execFile);
8304
9245
  var MAX_BUFFER2 = 8 * 1024 * 1024;
8305
9246
  function resetStdinForChild2() {
8306
9247
  if (process.stdin.isTTY) {
@@ -8340,7 +9281,7 @@ var GitpodProvider = class {
8340
9281
  );
8341
9282
  resetStdinForChild2();
8342
9283
  await new Promise((resolve2, reject) => {
8343
- const proc = (0, import_child_process11.spawn)("gitpod", ["login"], { stdio: "inherit" });
9284
+ const proc = (0, import_child_process13.spawn)("gitpod", ["login"], { stdio: "inherit" });
8344
9285
  proc.on("exit", (code) => {
8345
9286
  if (code === 0) resolve2();
8346
9287
  else reject(new Error("gitpod login failed."));
@@ -8492,7 +9433,7 @@ var GitpodProvider = class {
8492
9433
  async streamCommand(workspaceId, command2) {
8493
9434
  resetStdinForChild2();
8494
9435
  return new Promise((resolve2, reject) => {
8495
- const proc = (0, import_child_process11.spawn)(
9436
+ const proc = (0, import_child_process13.spawn)(
8496
9437
  "gitpod",
8497
9438
  ["workspace", "ssh", workspaceId, "--", "-tt", command2],
8498
9439
  { stdio: "inherit" }
@@ -8512,11 +9453,11 @@ var GitpodProvider = class {
8512
9453
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
8513
9454
  const remoteCmd = `mkdir -p ${shellQuote2(remoteDir)} && tar -xzf - -C ${shellQuote2(remoteDir)}`;
8514
9455
  await new Promise((resolve2, reject) => {
8515
- const tar = (0, import_child_process11.spawn)("tar", tarArgs, {
9456
+ const tar = (0, import_child_process13.spawn)("tar", tarArgs, {
8516
9457
  stdio: ["ignore", "pipe", "pipe"],
8517
9458
  env: tarEnv
8518
9459
  });
8519
- const ssh = (0, import_child_process11.spawn)(
9460
+ const ssh = (0, import_child_process13.spawn)(
8520
9461
  "gitpod",
8521
9462
  ["workspace", "ssh", workspaceId, "--", remoteCmd],
8522
9463
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -8538,7 +9479,7 @@ var GitpodProvider = class {
8538
9479
  });
8539
9480
  }
8540
9481
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
8541
- const remoteDir = path20.posix.dirname(remotePath);
9482
+ const remoteDir = path21.posix.dirname(remotePath);
8542
9483
  const parts = [
8543
9484
  `mkdir -p ${shellQuote2(remoteDir)}`,
8544
9485
  `cat > ${shellQuote2(remotePath)}`
@@ -8548,7 +9489,7 @@ var GitpodProvider = class {
8548
9489
  }
8549
9490
  const cmd = parts.join(" && ");
8550
9491
  await new Promise((resolve2, reject) => {
8551
- const proc = (0, import_child_process11.spawn)(
9492
+ const proc = (0, import_child_process13.spawn)(
8552
9493
  "gitpod",
8553
9494
  ["workspace", "ssh", workspaceId, "--", cmd],
8554
9495
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8572,10 +9513,10 @@ function shellQuote2(s) {
8572
9513
  }
8573
9514
 
8574
9515
  // src/services/providers/gitlab-workspaces.ts
8575
- var import_child_process12 = require("child_process");
9516
+ var import_child_process14 = require("child_process");
8576
9517
  var import_util5 = require("util");
8577
- var path21 = __toESM(require("path"));
8578
- var execFileP5 = (0, import_util5.promisify)(import_child_process12.execFile);
9518
+ var path22 = __toESM(require("path"));
9519
+ var execFileP5 = (0, import_util5.promisify)(import_child_process14.execFile);
8579
9520
  var MAX_BUFFER3 = 8 * 1024 * 1024;
8580
9521
  var GITLAB_API_BASE = process.env.CODEAM_GITLAB_API_URL ?? "https://gitlab.com/api/v4";
8581
9522
  function resetStdinForChild3() {
@@ -8617,7 +9558,7 @@ var GitLabWorkspacesProvider = class {
8617
9558
  );
8618
9559
  resetStdinForChild3();
8619
9560
  await new Promise((resolve2, reject) => {
8620
- const proc = (0, import_child_process12.spawn)(
9561
+ const proc = (0, import_child_process14.spawn)(
8621
9562
  "glab",
8622
9563
  ["auth", "login", "--scopes", "api,read_user,read_repository"],
8623
9564
  { stdio: "inherit" }
@@ -8789,7 +9730,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8789
9730
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
8790
9731
  resetStdinForChild3();
8791
9732
  return new Promise((resolve2, reject) => {
8792
- const proc = (0, import_child_process12.spawn)(
9733
+ const proc = (0, import_child_process14.spawn)(
8793
9734
  "ssh",
8794
9735
  ["-tt", "-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, command2],
8795
9736
  { stdio: "inherit" }
@@ -8810,8 +9751,8 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8810
9751
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
8811
9752
  const remoteCmd = `mkdir -p ${shellQuote3(remoteDir)} && tar -xzf - -C ${shellQuote3(remoteDir)}`;
8812
9753
  await new Promise((resolve2, reject) => {
8813
- const tar = (0, import_child_process12.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
8814
- const ssh = (0, import_child_process12.spawn)(
9754
+ const tar = (0, import_child_process14.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
9755
+ const ssh = (0, import_child_process14.spawn)(
8815
9756
  "ssh",
8816
9757
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, remoteCmd],
8817
9758
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -8834,14 +9775,14 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8834
9775
  }
8835
9776
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
8836
9777
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
8837
- const remoteDir = path21.posix.dirname(remotePath);
9778
+ const remoteDir = path22.posix.dirname(remotePath);
8838
9779
  const parts = [`mkdir -p ${shellQuote3(remoteDir)}`, `cat > ${shellQuote3(remotePath)}`];
8839
9780
  if (options.mode != null) {
8840
9781
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote3(remotePath)}`);
8841
9782
  }
8842
9783
  const cmd = parts.join(" && ");
8843
9784
  await new Promise((resolve2, reject) => {
8844
- const proc = (0, import_child_process12.spawn)(
9785
+ const proc = (0, import_child_process14.spawn)(
8845
9786
  "ssh",
8846
9787
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, cmd],
8847
9788
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8900,10 +9841,10 @@ function shellQuote3(s) {
8900
9841
  }
8901
9842
 
8902
9843
  // src/services/providers/railway.ts
8903
- var import_child_process13 = require("child_process");
9844
+ var import_child_process15 = require("child_process");
8904
9845
  var import_util6 = require("util");
8905
- var path22 = __toESM(require("path"));
8906
- var execFileP6 = (0, import_util6.promisify)(import_child_process13.execFile);
9846
+ var path23 = __toESM(require("path"));
9847
+ var execFileP6 = (0, import_util6.promisify)(import_child_process15.execFile);
8907
9848
  var MAX_BUFFER4 = 8 * 1024 * 1024;
8908
9849
  function resetStdinForChild4() {
8909
9850
  if (process.stdin.isTTY) {
@@ -8944,7 +9885,7 @@ var RailwayProvider = class {
8944
9885
  );
8945
9886
  resetStdinForChild4();
8946
9887
  await new Promise((resolve2, reject) => {
8947
- const proc = (0, import_child_process13.spawn)("railway", ["login"], { stdio: "inherit" });
9888
+ const proc = (0, import_child_process15.spawn)("railway", ["login"], { stdio: "inherit" });
8948
9889
  proc.on("exit", (code) => {
8949
9890
  if (code === 0) resolve2();
8950
9891
  else reject(new Error("railway login failed."));
@@ -9087,7 +10028,7 @@ var RailwayProvider = class {
9087
10028
  }
9088
10029
  resetStdinForChild4();
9089
10030
  return new Promise((resolve2, reject) => {
9090
- const proc = (0, import_child_process13.spawn)(
10031
+ const proc = (0, import_child_process15.spawn)(
9091
10032
  "railway",
9092
10033
  ["shell", "--project", projectId, "--service", serviceId, "--command", command2],
9093
10034
  { stdio: "inherit" }
@@ -9111,8 +10052,8 @@ var RailwayProvider = class {
9111
10052
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
9112
10053
  const remoteCmd = `mkdir -p ${shellQuote4(remoteDir)} && tar -xzf - -C ${shellQuote4(remoteDir)}`;
9113
10054
  await new Promise((resolve2, reject) => {
9114
- const tar = (0, import_child_process13.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
9115
- const sh = (0, import_child_process13.spawn)(
10055
+ const tar = (0, import_child_process15.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
10056
+ const sh = (0, import_child_process15.spawn)(
9116
10057
  "railway",
9117
10058
  ["shell", "--project", projectId, "--service", serviceId, "--command", remoteCmd],
9118
10059
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -9138,14 +10079,14 @@ var RailwayProvider = class {
9138
10079
  if (!projectId || !serviceId) {
9139
10080
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
9140
10081
  }
9141
- const remoteDir = path22.posix.dirname(remotePath);
10082
+ const remoteDir = path23.posix.dirname(remotePath);
9142
10083
  const parts = [`mkdir -p ${shellQuote4(remoteDir)}`, `cat > ${shellQuote4(remotePath)}`];
9143
10084
  if (options.mode != null) {
9144
10085
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote4(remotePath)}`);
9145
10086
  }
9146
10087
  const cmd = parts.join(" && ");
9147
10088
  await new Promise((resolve2, reject) => {
9148
- const proc = (0, import_child_process13.spawn)(
10089
+ const proc = (0, import_child_process15.spawn)(
9149
10090
  "railway",
9150
10091
  ["shell", "--project", projectId, "--service", serviceId, "--command", cmd],
9151
10092
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -9680,7 +10621,7 @@ async function stopWorkspaceFromLocal(target) {
9680
10621
  // src/commands/version.ts
9681
10622
  var import_picocolors11 = __toESM(require("picocolors"));
9682
10623
  function version() {
9683
- const v = true ? "2.15.2" : "unknown";
10624
+ const v = true ? "2.15.6" : "unknown";
9684
10625
  console.log(`${import_picocolors11.default.bold("codeam-cli")} ${import_picocolors11.default.cyan(v)}`);
9685
10626
  }
9686
10627
 
@@ -9723,16 +10664,16 @@ function help() {
9723
10664
  // src/lib/updateNotifier.ts
9724
10665
  var fs16 = __toESM(require("fs"));
9725
10666
  var os15 = __toESM(require("os"));
9726
- var path23 = __toESM(require("path"));
9727
- var https5 = __toESM(require("https"));
10667
+ var path24 = __toESM(require("path"));
10668
+ var https7 = __toESM(require("https"));
9728
10669
  var import_picocolors13 = __toESM(require("picocolors"));
9729
10670
  var PKG_NAME = "codeam-cli";
9730
10671
  var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
9731
10672
  var TTL_MS = 24 * 60 * 60 * 1e3;
9732
10673
  var REQUEST_TIMEOUT_MS = 1500;
9733
10674
  function cachePath() {
9734
- const dir = path23.join(os15.homedir(), ".codeam");
9735
- return path23.join(dir, "update-check.json");
10675
+ const dir = path24.join(os15.homedir(), ".codeam");
10676
+ return path24.join(dir, "update-check.json");
9736
10677
  }
9737
10678
  function readCache() {
9738
10679
  try {
@@ -9747,7 +10688,7 @@ function readCache() {
9747
10688
  function writeCache(cache) {
9748
10689
  try {
9749
10690
  const file = cachePath();
9750
- fs16.mkdirSync(path23.dirname(file), { recursive: true });
10691
+ fs16.mkdirSync(path24.dirname(file), { recursive: true });
9751
10692
  fs16.writeFileSync(file, JSON.stringify(cache));
9752
10693
  } catch {
9753
10694
  }
@@ -9767,7 +10708,7 @@ function compareSemver(a, b) {
9767
10708
  }
9768
10709
  function fetchLatest() {
9769
10710
  return new Promise((resolve2) => {
9770
- const req = https5.get(
10711
+ const req = https7.get(
9771
10712
  REGISTRY_URL,
9772
10713
  { headers: { Accept: "application/json" }, timeout: REQUEST_TIMEOUT_MS },
9773
10714
  (res) => {
@@ -9819,7 +10760,7 @@ function checkForUpdates() {
9819
10760
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
9820
10761
  if (process.env.CI) return;
9821
10762
  if (!process.stdout.isTTY) return;
9822
- const current = true ? "2.15.2" : null;
10763
+ const current = true ? "2.15.6" : null;
9823
10764
  if (!current) return;
9824
10765
  const cache = readCache();
9825
10766
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;