codeam-cli 2.15.1 → 2.15.5

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.1",
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.5",
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",
@@ -6125,6 +6126,460 @@ var HistoryService = class _HistoryService {
6125
6126
  }
6126
6127
  };
6127
6128
 
6129
+ // src/services/file-watcher.service.ts
6130
+ var import_child_process6 = require("child_process");
6131
+ var path15 = __toESM(require("path"));
6132
+
6133
+ // src/services/file-watcher/diff-parser.ts
6134
+ var HUNK_HEADER_RE = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
6135
+ function parseUnifiedDiff(diff) {
6136
+ if (!diff || diff.trim().length === 0) {
6137
+ return {
6138
+ fileStatus: "modified",
6139
+ hunks: [],
6140
+ totalLinesAdded: 0,
6141
+ totalLinesRemoved: 0
6142
+ };
6143
+ }
6144
+ const rawLines = diff.split(/\r?\n/);
6145
+ const fileStatus = detectFileStatus(rawLines);
6146
+ const hunks = [];
6147
+ let current = null;
6148
+ let oldLine = 0;
6149
+ let newLine = 0;
6150
+ let totalAdded = 0;
6151
+ let totalRemoved = 0;
6152
+ for (const raw of rawLines) {
6153
+ if (raw.startsWith("@@")) {
6154
+ const match = raw.match(HUNK_HEADER_RE);
6155
+ if (!match) continue;
6156
+ if (current) hunks.push(current);
6157
+ oldLine = parseInt(match[1], 10);
6158
+ newLine = parseInt(match[2], 10);
6159
+ current = {
6160
+ header: raw,
6161
+ lines: [],
6162
+ linesAdded: 0,
6163
+ linesRemoved: 0
6164
+ };
6165
+ continue;
6166
+ }
6167
+ if (current === null) continue;
6168
+ if (raw.startsWith("\\ No newline")) continue;
6169
+ if (raw.startsWith("+")) {
6170
+ current.lines.push({ type: "add", lineNumber: newLine, text: raw.slice(1) });
6171
+ current.linesAdded += 1;
6172
+ totalAdded += 1;
6173
+ newLine += 1;
6174
+ continue;
6175
+ }
6176
+ if (raw.startsWith("-")) {
6177
+ current.lines.push({ type: "remove", lineNumber: oldLine, text: raw.slice(1) });
6178
+ current.linesRemoved += 1;
6179
+ totalRemoved += 1;
6180
+ oldLine += 1;
6181
+ continue;
6182
+ }
6183
+ if (raw.startsWith(" ")) {
6184
+ current.lines.push({ type: "context", lineNumber: newLine, text: raw.slice(1) });
6185
+ newLine += 1;
6186
+ oldLine += 1;
6187
+ continue;
6188
+ }
6189
+ }
6190
+ if (current) hunks.push(current);
6191
+ return {
6192
+ fileStatus,
6193
+ hunks,
6194
+ totalLinesAdded: totalAdded,
6195
+ totalLinesRemoved: totalRemoved
6196
+ };
6197
+ }
6198
+ function detectFileStatus(rawLines) {
6199
+ for (const line of rawLines) {
6200
+ if (line.startsWith("@@")) break;
6201
+ if (line.startsWith("new file mode")) return "added";
6202
+ if (line.startsWith("deleted file mode")) return "deleted";
6203
+ if (line.startsWith("rename from ") || line.startsWith("rename to ")) {
6204
+ return "renamed";
6205
+ }
6206
+ if (line.startsWith("--- /dev/null")) return "added";
6207
+ if (line.startsWith("+++ /dev/null")) return "deleted";
6208
+ }
6209
+ return "modified";
6210
+ }
6211
+
6212
+ // src/services/file-watcher/transport.ts
6213
+ var http5 = __toESM(require("http"));
6214
+ var https5 = __toESM(require("https"));
6215
+ var _transport3 = {
6216
+ post: _post2
6217
+ };
6218
+ function _post2(url, headers, payload) {
6219
+ return new Promise((resolve2, reject) => {
6220
+ let settled = false;
6221
+ const u2 = new URL(url);
6222
+ const lib = u2.protocol === "https:" ? https5 : http5;
6223
+ const req = lib.request(
6224
+ {
6225
+ hostname: u2.hostname,
6226
+ port: u2.port || (u2.protocol === "https:" ? 443 : 80),
6227
+ path: u2.pathname + u2.search,
6228
+ method: "POST",
6229
+ headers: {
6230
+ ...headers,
6231
+ ...vercelBypassHeader(),
6232
+ "Content-Length": Buffer.byteLength(payload)
6233
+ },
6234
+ timeout: 8e3
6235
+ },
6236
+ (res) => {
6237
+ let body = "";
6238
+ res.on("data", (c2) => {
6239
+ body += c2.toString();
6240
+ });
6241
+ res.on("end", () => {
6242
+ if (settled) return;
6243
+ settled = true;
6244
+ resolve2({ statusCode: res.statusCode ?? 0, body });
6245
+ });
6246
+ }
6247
+ );
6248
+ req.on("error", (err) => {
6249
+ if (settled) return;
6250
+ settled = true;
6251
+ reject(err);
6252
+ });
6253
+ req.on("timeout", () => {
6254
+ req.destroy();
6255
+ });
6256
+ req.write(payload);
6257
+ req.end();
6258
+ });
6259
+ }
6260
+
6261
+ // src/services/file-watcher.service.ts
6262
+ var API_BASE5 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
6263
+ var DEBOUNCE_MS = 250;
6264
+ var MAX_RETRIES = 2;
6265
+ var RETRY_BACKOFF_MS = 300;
6266
+ var FileWatcherService = class {
6267
+ constructor(opts) {
6268
+ this.opts = opts;
6269
+ this.apiBase = opts.apiBaseUrl ?? API_BASE5;
6270
+ }
6271
+ opts;
6272
+ watcher = null;
6273
+ pending = /* @__PURE__ */ new Map();
6274
+ apiBase;
6275
+ stopped = false;
6276
+ /**
6277
+ * Start watching `opts.workingDir`. Idempotent (second call is a
6278
+ * no-op). Resolves once chokidar's initial scan completes; that
6279
+ * way the `start.ts` orchestrator can sequence "agent up → watcher
6280
+ * ready" deterministically if it wants to, though today it
6281
+ * doesn't await this.
6282
+ */
6283
+ async start() {
6284
+ if (this.watcher) return;
6285
+ if (this.stopped) {
6286
+ throw new Error("FileWatcherService has already been stopped \u2014 re-instantiate to restart.");
6287
+ }
6288
+ let chokidar;
6289
+ try {
6290
+ chokidar = require("chokidar");
6291
+ } catch (err) {
6292
+ log.warn(
6293
+ "fileWatcher",
6294
+ `chokidar unavailable \u2014 file change emission disabled`,
6295
+ err
6296
+ );
6297
+ return;
6298
+ }
6299
+ const watcher = chokidar.watch(this.opts.workingDir, {
6300
+ ignored: [
6301
+ /(^|[\\/])\../,
6302
+ // dot-files & dot-dirs (.git, .next, .expo, .DS_Store, …)
6303
+ /node_modules/,
6304
+ /dist/,
6305
+ /build/,
6306
+ /out/,
6307
+ /coverage/,
6308
+ /\.turbo/,
6309
+ /\.cache/,
6310
+ /\.parcel-cache/,
6311
+ // Build outputs that aren't a typical "dist" target
6312
+ /target\//,
6313
+ /__pycache__/
6314
+ ],
6315
+ ignoreInitial: true,
6316
+ // we only care about post-start changes
6317
+ persistent: true,
6318
+ awaitWriteFinish: {
6319
+ // Coalesces rapid sequential writes (npm install spam, build
6320
+ // tools emitting bursts). Lower than chokidar's default so
6321
+ // the user sees their Files screen update within 0.5 s of
6322
+ // saving.
6323
+ stabilityThreshold: 150,
6324
+ pollInterval: 50
6325
+ }
6326
+ });
6327
+ watcher.on("add", (filePath) => this.schedule(filePath, "add"));
6328
+ watcher.on("change", (filePath) => this.schedule(filePath, "change"));
6329
+ watcher.on("unlink", (filePath) => this.schedule(filePath, "unlink"));
6330
+ this.watcher = watcher;
6331
+ log.info(
6332
+ "fileWatcher",
6333
+ `watching ${this.opts.workingDir} for session=${this.opts.sessionId.slice(0, 8)}`
6334
+ );
6335
+ }
6336
+ /**
6337
+ * Stop watching. Idempotent — safe to call multiple times. After
6338
+ * stop, the instance is dead; create a new one to resume. (This
6339
+ * matches the `OutputService` / `CommandRelayService` style.)
6340
+ */
6341
+ async stop() {
6342
+ if (this.stopped) return;
6343
+ this.stopped = true;
6344
+ for (const entry of this.pending.values()) {
6345
+ clearTimeout(entry.timer);
6346
+ }
6347
+ this.pending.clear();
6348
+ if (this.watcher) {
6349
+ try {
6350
+ await this.watcher.close();
6351
+ } catch (err) {
6352
+ log.warn("fileWatcher", "error closing chokidar", err);
6353
+ }
6354
+ this.watcher = null;
6355
+ }
6356
+ log.info("fileWatcher", `stopped (session=${this.opts.sessionId.slice(0, 8)})`);
6357
+ }
6358
+ /**
6359
+ * Coalesce rapid writes per-file. Each fresh event resets the
6360
+ * 250 ms debounce timer. When the timer fires, we compute the
6361
+ * diff once and emit.
6362
+ *
6363
+ * `unlink` events bypass the diff path — we emit a synthetic
6364
+ * deletion directly because `git diff <path>` for a removed file
6365
+ * produces a diff that's already encoded as a deletion in
6366
+ * `parseUnifiedDiff`, but in practice the file is gone and the
6367
+ * synthetic path is simpler and avoids a race with git's index.
6368
+ */
6369
+ schedule(absPath, changeType) {
6370
+ if (this.stopped) return;
6371
+ const existing = this.pending.get(absPath);
6372
+ if (existing) clearTimeout(existing.timer);
6373
+ const timer = setTimeout(() => {
6374
+ this.pending.delete(absPath);
6375
+ void this.emitForFile(absPath, changeType);
6376
+ }, DEBOUNCE_MS);
6377
+ this.pending.set(absPath, {
6378
+ lastEventAt: Date.now(),
6379
+ timer,
6380
+ changeType
6381
+ });
6382
+ }
6383
+ /**
6384
+ * Visible for tests — lets vitest pump a synthetic file event
6385
+ * through the debounce + diff + emit pipeline without spinning up
6386
+ * a real chokidar watcher.
6387
+ */
6388
+ /* @internal */
6389
+ _scheduleForTest(absPath, changeType) {
6390
+ this.schedule(absPath, changeType);
6391
+ }
6392
+ async emitForFile(absPath, changeType) {
6393
+ if (this.stopped) return;
6394
+ const relPath = path15.relative(this.opts.workingDir, absPath);
6395
+ if (!relPath || relPath.startsWith("..")) {
6396
+ return;
6397
+ }
6398
+ let diffText = "";
6399
+ let fileStatus = "modified";
6400
+ if (changeType === "unlink") {
6401
+ const diff = await this.gitDiff(relPath);
6402
+ if (diff !== null && diff.trim().length > 0) {
6403
+ diffText = diff;
6404
+ } else {
6405
+ await this.postFileChanged({
6406
+ sessionId: this.opts.sessionId,
6407
+ pluginId: this.opts.pluginId,
6408
+ filePath: relPath,
6409
+ fileStatus: "deleted",
6410
+ linesAdded: 0,
6411
+ linesRemoved: 0,
6412
+ hunkCount: 0
6413
+ });
6414
+ return;
6415
+ }
6416
+ fileStatus = "deleted";
6417
+ } else {
6418
+ const diff = await this.gitDiff(relPath);
6419
+ if (diff === null) {
6420
+ log.warn(
6421
+ "fileWatcher",
6422
+ `git diff failed for ${relPath} \u2014 emitting file-changed only`
6423
+ );
6424
+ await this.postFileChanged({
6425
+ sessionId: this.opts.sessionId,
6426
+ pluginId: this.opts.pluginId,
6427
+ filePath: relPath,
6428
+ fileStatus: changeType === "add" ? "added" : "modified",
6429
+ linesAdded: 0,
6430
+ linesRemoved: 0,
6431
+ hunkCount: 0
6432
+ });
6433
+ return;
6434
+ }
6435
+ diffText = diff;
6436
+ }
6437
+ const parsed = parseUnifiedDiff(diffText);
6438
+ const finalStatus = parsed.fileStatus !== "modified" ? parsed.fileStatus : changeType === "add" ? "added" : changeType === "unlink" ? "deleted" : fileStatus;
6439
+ const reviewStatus = parsed.hunks.length > 0 ? "awaiting_review" : void 0;
6440
+ await this.postFileChanged({
6441
+ sessionId: this.opts.sessionId,
6442
+ pluginId: this.opts.pluginId,
6443
+ filePath: relPath,
6444
+ fileStatus: finalStatus,
6445
+ linesAdded: parsed.totalLinesAdded,
6446
+ linesRemoved: parsed.totalLinesRemoved,
6447
+ hunkCount: parsed.hunks.length,
6448
+ reviewStatus
6449
+ });
6450
+ for (const hunk of parsed.hunks) {
6451
+ await this.postReviewHunk({
6452
+ sessionId: this.opts.sessionId,
6453
+ pluginId: this.opts.pluginId,
6454
+ filePath: relPath,
6455
+ fileStatus: finalStatus,
6456
+ hunkHeader: hunk.header,
6457
+ lines: hunk.lines,
6458
+ linesAdded: hunk.linesAdded,
6459
+ linesRemoved: hunk.linesRemoved
6460
+ });
6461
+ }
6462
+ }
6463
+ /**
6464
+ * Compute the unified diff for a single path relative to the
6465
+ * working dir. Returns `null` when git is unavailable or the cwd
6466
+ * is not a repo. Returns `''` when there's no diff (a touch that
6467
+ * didn't change content).
6468
+ *
6469
+ * For tracked files we use `git diff --no-color -- <path>` which
6470
+ * compares the worktree against HEAD's blob.
6471
+ * For untracked files (`git ls-files --error-unmatch` exits non-
6472
+ * zero) we use `git diff --no-color --no-index /dev/null <path>`,
6473
+ * which produces an "added"-shaped diff against an empty source.
6474
+ */
6475
+ async gitDiff(relPath) {
6476
+ const tracked = await runGit(
6477
+ this.opts.workingDir,
6478
+ ["diff", "--no-color", "--", relPath]
6479
+ );
6480
+ if (tracked === null) return null;
6481
+ if (tracked.trim().length > 0) return tracked;
6482
+ const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
6483
+ const untracked = await runGit(
6484
+ this.opts.workingDir,
6485
+ ["diff", "--no-color", "--no-index", "--", devNull, relPath],
6486
+ { allowNonZeroExit: true }
6487
+ );
6488
+ return untracked ?? "";
6489
+ }
6490
+ async postFileChanged(body) {
6491
+ await this.postWithRetries(`${this.apiBase}/api/files/changed`, body);
6492
+ }
6493
+ async postReviewHunk(body) {
6494
+ await this.postWithRetries(`${this.apiBase}/api/review/hunks`, body);
6495
+ }
6496
+ async postWithRetries(url, body) {
6497
+ const payload = JSON.stringify(body);
6498
+ const headers = {
6499
+ "Content-Type": "application/json",
6500
+ "X-Codeam-Protocol-Version": "2.0.0",
6501
+ "X-Plugin-Auth-Token": this.opts.pluginAuthToken
6502
+ };
6503
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {
6504
+ try {
6505
+ const { statusCode, body: resBody } = await _transport3.post(url, headers, payload);
6506
+ if (statusCode >= 200 && statusCode < 300) {
6507
+ log.trace(
6508
+ "fileWatcher",
6509
+ `post ok url=${url} status=${statusCode} path=${body.filePath}`
6510
+ );
6511
+ return;
6512
+ }
6513
+ if (statusCode === 410 || statusCode === 404) {
6514
+ log.warn(
6515
+ "fileWatcher",
6516
+ `session dead (status=${statusCode}) \u2014 dropping ${body.filePath}`
6517
+ );
6518
+ this.stopped = true;
6519
+ return;
6520
+ }
6521
+ log.warn(
6522
+ "fileWatcher",
6523
+ `post failed url=${url} status=${statusCode} attempt=${attempt + 1} body=${resBody.slice(0, 200)}`
6524
+ );
6525
+ } catch (err) {
6526
+ log.warn(
6527
+ "fileWatcher",
6528
+ `post error url=${url} attempt=${attempt + 1}`,
6529
+ err
6530
+ );
6531
+ }
6532
+ if (attempt < MAX_RETRIES) {
6533
+ await sleep(RETRY_BACKOFF_MS * (attempt + 1));
6534
+ }
6535
+ }
6536
+ log.warn(
6537
+ "fileWatcher",
6538
+ `giving up after ${MAX_RETRIES + 1} attempts \u2014 path=${body.filePath}`
6539
+ );
6540
+ }
6541
+ };
6542
+ function sleep(ms) {
6543
+ return new Promise((r) => setTimeout(r, ms));
6544
+ }
6545
+ async function runGit(cwd, args2, opts = {}) {
6546
+ return _runGit(cwd, args2, opts);
6547
+ }
6548
+ var _gitSeam = {
6549
+ run: _runGitImpl
6550
+ };
6551
+ async function _runGitImpl(cwd, args2, opts = {}) {
6552
+ return new Promise((resolve2) => {
6553
+ let proc;
6554
+ try {
6555
+ proc = (0, import_child_process6.spawn)("git", args2, { cwd, env: process.env });
6556
+ } catch {
6557
+ resolve2(null);
6558
+ return;
6559
+ }
6560
+ let stdout = "";
6561
+ let stderr = "";
6562
+ proc.stdout?.on("data", (c2) => {
6563
+ stdout += c2.toString();
6564
+ });
6565
+ proc.stderr?.on("data", (c2) => {
6566
+ stderr += c2.toString();
6567
+ });
6568
+ proc.on("error", () => resolve2(null));
6569
+ proc.on("close", (code) => {
6570
+ if (code === 0 || opts.allowNonZeroExit) {
6571
+ resolve2(stdout);
6572
+ } else {
6573
+ log.trace("fileWatcher", `git ${args2.join(" ")} exited ${code} stderr=${stderr.slice(0, 200)}`);
6574
+ resolve2(null);
6575
+ }
6576
+ });
6577
+ });
6578
+ }
6579
+ function _runGit(cwd, args2, opts = {}) {
6580
+ return _gitSeam.run(cwd, args2, opts);
6581
+ }
6582
+
6128
6583
  // src/commands/start/quota-fetcher.ts
6129
6584
  var inProgress = false;
6130
6585
  function fetchQuotaUsage(runtime, historySvc) {
@@ -6140,13 +6595,13 @@ function fetchQuotaUsage(runtime, historySvc) {
6140
6595
  }
6141
6596
 
6142
6597
  // src/commands/start/keep-alive.ts
6143
- var import_child_process6 = require("child_process");
6598
+ var import_child_process7 = require("child_process");
6144
6599
  function buildKeepAlive(ctx) {
6145
6600
  let timer = null;
6146
6601
  async function setIdleTimeout(minutes) {
6147
6602
  if (!ctx.inCodespace || !ctx.codespaceName) return;
6148
6603
  await new Promise((resolve2) => {
6149
- const proc = (0, import_child_process6.spawn)(
6604
+ const proc = (0, import_child_process7.spawn)(
6150
6605
  "gh",
6151
6606
  [
6152
6607
  "api",
@@ -6185,9 +6640,9 @@ function buildKeepAlive(ctx) {
6185
6640
  // src/commands/start/handlers.ts
6186
6641
  var fs14 = __toESM(require("fs"));
6187
6642
  var os13 = __toESM(require("os"));
6188
- var path18 = __toESM(require("path"));
6643
+ var path19 = __toESM(require("path"));
6189
6644
  var import_crypto2 = require("crypto");
6190
- var import_child_process8 = require("child_process");
6645
+ var import_child_process10 = require("child_process");
6191
6646
 
6192
6647
  // src/lib/payload.ts
6193
6648
  var import_zod2 = require("zod");
@@ -6244,7 +6699,7 @@ function parsePayload(schema, raw) {
6244
6699
 
6245
6700
  // src/services/file-ops.service.ts
6246
6701
  var fs12 = __toESM(require("fs/promises"));
6247
- var path15 = __toESM(require("path"));
6702
+ var path16 = __toESM(require("path"));
6248
6703
  var MAX_FILE_BYTES = 5 * 1024 * 1024;
6249
6704
  var MAX_WALK_DEPTH = 6;
6250
6705
  var MAX_VISITED_DIRS = 5e3;
@@ -6279,8 +6734,8 @@ var SUBDIR_IGNORE = /* @__PURE__ */ new Set([
6279
6734
  "__pycache__"
6280
6735
  ]);
6281
6736
  function isUnder(parent, candidate) {
6282
- const rel = path15.relative(parent, candidate);
6283
- return rel === "" || !rel.startsWith("..") && !path15.isAbsolute(rel);
6737
+ const rel = path16.relative(parent, candidate);
6738
+ return rel === "" || !rel.startsWith("..") && !path16.isAbsolute(rel);
6284
6739
  }
6285
6740
  async function isExistingFile(absPath) {
6286
6741
  try {
@@ -6303,7 +6758,7 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
6303
6758
  }
6304
6759
  for (const e of entries) {
6305
6760
  if (!e.isFile()) continue;
6306
- const full = path15.join(dir, e.name);
6761
+ const full = path16.join(dir, e.name);
6307
6762
  if (needleVariants.some((needle) => full.endsWith(needle))) {
6308
6763
  ctx.matches.push(full);
6309
6764
  if (ctx.matches.length >= ctx.cap) return;
@@ -6313,21 +6768,21 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
6313
6768
  if (!e.isDirectory()) continue;
6314
6769
  if (SUBDIR_IGNORE.has(e.name)) continue;
6315
6770
  if (e.name.startsWith(".") && SUBDIR_IGNORE.has(e.name)) continue;
6316
- await walkForSuffix(path15.join(dir, e.name), needleVariants, depth + 1, ctx);
6771
+ await walkForSuffix(path16.join(dir, e.name), needleVariants, depth + 1, ctx);
6317
6772
  if (ctx.matches.length >= ctx.cap) return;
6318
6773
  }
6319
6774
  }
6320
6775
  async function findFile(rawPath) {
6321
6776
  const cwd = process.cwd();
6322
- if (path15.isAbsolute(rawPath)) {
6323
- const abs = path15.normalize(rawPath);
6777
+ if (path16.isAbsolute(rawPath)) {
6778
+ const abs = path16.normalize(rawPath);
6324
6779
  if (isUnder(cwd, abs) && await isExistingFile(abs)) return abs;
6325
6780
  }
6326
- const direct = path15.resolve(cwd, rawPath);
6781
+ const direct = path16.resolve(cwd, rawPath);
6327
6782
  if (isUnder(cwd, direct) && await isExistingFile(direct)) return direct;
6328
- const normalized = path15.normalize(rawPath).replace(/^[./\\]+/, "");
6783
+ const normalized = path16.normalize(rawPath).replace(/^[./\\]+/, "");
6329
6784
  const needles = [
6330
- `${path15.sep}${normalized}`,
6785
+ `${path16.sep}${normalized}`,
6331
6786
  `/${normalized}`
6332
6787
  ].filter((v, i, a) => a.indexOf(v) === i);
6333
6788
  const ctx = { visited: 0, matches: [], cap: 16 };
@@ -6341,7 +6796,7 @@ async function findWriteTarget(rawPath) {
6341
6796
  const found = await findFile(rawPath);
6342
6797
  if (found) return found;
6343
6798
  const cwd = process.cwd();
6344
- const fallback = path15.isAbsolute(rawPath) ? path15.normalize(rawPath) : path15.resolve(cwd, rawPath);
6799
+ const fallback = path16.isAbsolute(rawPath) ? path16.normalize(rawPath) : path16.resolve(cwd, rawPath);
6345
6800
  if (!isUnder(cwd, fallback)) return null;
6346
6801
  return fallback;
6347
6802
  }
@@ -6381,7 +6836,7 @@ async function writeProjectFile(rawPath, content) {
6381
6836
  if (Buffer.byteLength(content, "utf-8") > MAX_FILE_BYTES) {
6382
6837
  return { error: "Content too large." };
6383
6838
  }
6384
- await fs12.mkdir(path15.dirname(abs), { recursive: true });
6839
+ await fs12.mkdir(path16.dirname(abs), { recursive: true });
6385
6840
  await fs12.writeFile(abs, content, "utf-8");
6386
6841
  return { ok: true };
6387
6842
  } catch (e) {
@@ -6391,11 +6846,11 @@ async function writeProjectFile(rawPath, content) {
6391
6846
  }
6392
6847
 
6393
6848
  // src/services/project-ops.service.ts
6394
- var import_child_process7 = require("child_process");
6849
+ var import_child_process8 = require("child_process");
6395
6850
  var import_util2 = require("util");
6396
6851
  var fs13 = __toESM(require("fs/promises"));
6397
- var path16 = __toESM(require("path"));
6398
- var execFileP2 = (0, import_util2.promisify)(import_child_process7.execFile);
6852
+ var path17 = __toESM(require("path"));
6853
+ var execFileP2 = (0, import_util2.promisify)(import_child_process8.execFile);
6399
6854
  var PROJECT_IGNORE = /* @__PURE__ */ new Set([
6400
6855
  "node_modules",
6401
6856
  ".git",
@@ -6452,12 +6907,12 @@ async function listProjectFiles(opts = {}) {
6452
6907
  return;
6453
6908
  }
6454
6909
  if (PROJECT_IGNORE.has(e.name)) continue;
6455
- const full = path16.join(dir, e.name);
6910
+ const full = path17.join(dir, e.name);
6456
6911
  if (e.isDirectory()) {
6457
6912
  if (depth >= 12) continue;
6458
6913
  await walk(full, depth + 1);
6459
6914
  } else if (e.isFile()) {
6460
- const rel = path16.relative(root, full);
6915
+ const rel = path17.relative(root, full);
6461
6916
  if (q2 && !rel.toLowerCase().includes(q2) && !e.name.toLowerCase().includes(q2)) {
6462
6917
  continue;
6463
6918
  }
@@ -6565,7 +7020,7 @@ async function gitStatus(cwd) {
6565
7020
  let hasMergeInProgress = false;
6566
7021
  try {
6567
7022
  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");
7023
+ const mergeHead = path17.isAbsolute(gitDir) ? path17.join(gitDir, "MERGE_HEAD") : path17.join(root, gitDir, "MERGE_HEAD");
6569
7024
  await fs13.access(mergeHead);
6570
7025
  hasMergeInProgress = true;
6571
7026
  } catch {
@@ -6712,7 +7167,7 @@ async function jsSearchFiles(opts, cwd, cap) {
6712
7167
  }
6713
7168
  let content = "";
6714
7169
  try {
6715
- content = await fs13.readFile(path16.join(cwd, f.path), "utf8");
7170
+ content = await fs13.readFile(path17.join(cwd, f.path), "utf8");
6716
7171
  } catch {
6717
7172
  continue;
6718
7173
  }
@@ -6735,6 +7190,7 @@ async function jsSearchFiles(opts, cwd, cap) {
6735
7190
  }
6736
7191
 
6737
7192
  // src/services/terminal-ops.service.ts
7193
+ var import_child_process9 = require("child_process");
6738
7194
  var import_crypto = require("crypto");
6739
7195
  var import_path = __toESM(require("path"));
6740
7196
  var MAX_CONCURRENT_SESSIONS = 4;
@@ -6768,6 +7224,135 @@ function defaultShell() {
6768
7224
  }
6769
7225
  return process.env.SHELL ?? "/bin/bash";
6770
7226
  }
7227
+ var PYTHON_TERMINAL_HELPER = `import os,pty,sys,select,signal,struct,fcntl,termios,errno,re
7228
+ m,s=pty.openpty()
7229
+ try:
7230
+ cols=int(os.environ.get('COLUMNS','80'))
7231
+ rows=int(os.environ.get('LINES','24'))
7232
+ fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',rows,cols,0,0))
7233
+ except Exception:pass
7234
+ pid=os.fork()
7235
+ if pid==0:
7236
+ os.close(m)
7237
+ os.setsid()
7238
+ try:fcntl.ioctl(s,termios.TIOCSCTTY,0)
7239
+ except Exception:pass
7240
+ for fd in[0,1,2]:os.dup2(s,fd)
7241
+ if s>2:os.close(s)
7242
+ os.execvp(sys.argv[1],sys.argv[1:])
7243
+ sys.exit(127)
7244
+ os.close(s)
7245
+ done=[False]
7246
+ def onchld(n,f):
7247
+ try:os.waitpid(pid,os.WNOHANG)
7248
+ except Exception:pass
7249
+ done[0]=True
7250
+ signal.signal(signal.SIGCHLD,onchld)
7251
+ signal.signal(signal.SIGHUP,signal.SIG_IGN)
7252
+ i=sys.stdin.fileno()
7253
+ o=sys.stdout.fileno()
7254
+ in_buf=b''
7255
+ resize_re=re.compile(rb'\\x00CW (\\d+) (\\d+)\\n')
7256
+ while not done[0]:
7257
+ try:r,_,_=select.select([i,m],[],[],0.1)
7258
+ except OSError as e:
7259
+ if e.errno==errno.EINTR:continue
7260
+ break
7261
+ if i in r:
7262
+ try:
7263
+ d=os.read(i,4096)
7264
+ if not d:break
7265
+ in_buf+=d
7266
+ while True:
7267
+ mo=resize_re.search(in_buf)
7268
+ if not mo:break
7269
+ try:
7270
+ rows=int(mo.group(1));cols=int(mo.group(2))
7271
+ fcntl.ioctl(m,termios.TIOCSWINSZ,struct.pack('HHHH',rows,cols,0,0))
7272
+ except Exception:pass
7273
+ in_buf=in_buf[:mo.start()]+in_buf[mo.end():]
7274
+ if in_buf:
7275
+ # Don't forward a dangling NUL that might be the
7276
+ # start of an incomplete resize marker \u2014 hold it
7277
+ # until the next read so the regex matches.
7278
+ nul=in_buf.rfind(b'\\x00')
7279
+ if nul>=0 and len(in_buf)-nul<32:
7280
+ tail=in_buf[nul:];body=in_buf[:nul]
7281
+ if body:os.write(m,body)
7282
+ in_buf=tail
7283
+ else:
7284
+ os.write(m,in_buf);in_buf=b''
7285
+ except OSError:break
7286
+ if m in r:
7287
+ try:
7288
+ d=os.read(m,4096)
7289
+ if d:os.write(o,d)
7290
+ except OSError:done[0]=True
7291
+ try:os.kill(pid,signal.SIGTERM)
7292
+ except Exception:pass
7293
+ try:
7294
+ _,st=os.waitpid(pid,0)
7295
+ sys.exit((st>>8)&0xFF)
7296
+ except Exception:sys.exit(0)
7297
+ `;
7298
+ function findPython3() {
7299
+ for (const name of ["python3", "python"]) {
7300
+ try {
7301
+ const out = require("child_process").spawnSync("which", [name], { encoding: "utf8" });
7302
+ if (out.status === 0 && out.stdout?.trim()) return out.stdout.trim();
7303
+ } catch {
7304
+ }
7305
+ }
7306
+ return null;
7307
+ }
7308
+ function createPythonSession(id, shell, cwd, env, cols, rows) {
7309
+ const python = findPython3();
7310
+ if (!python) {
7311
+ return { error: "python3 not found on PATH \u2014 required for terminal sessions on Linux/macOS without node-pty." };
7312
+ }
7313
+ let child;
7314
+ try {
7315
+ child = (0, import_child_process9.spawn)(python, ["-c", PYTHON_TERMINAL_HELPER, shell], {
7316
+ cwd,
7317
+ env: { ...env, COLUMNS: String(cols), LINES: String(rows) },
7318
+ stdio: ["pipe", "pipe", "pipe"]
7319
+ });
7320
+ } catch (e) {
7321
+ return { error: e instanceof Error ? e.message : "python spawn failed" };
7322
+ }
7323
+ child.stdout.on("data", (buf) => {
7324
+ onDataHandler?.({ sessionId: id, data: buf.toString("utf8") });
7325
+ });
7326
+ child.stderr.on("data", (buf) => {
7327
+ onDataHandler?.({ sessionId: id, data: buf.toString("utf8") });
7328
+ });
7329
+ child.on("exit", (code) => {
7330
+ onExitHandler?.({ sessionId: id, exitCode: code ?? 0 });
7331
+ sessions.delete(id);
7332
+ });
7333
+ return {
7334
+ id,
7335
+ write(data) {
7336
+ try {
7337
+ child.stdin.write(data);
7338
+ } catch {
7339
+ }
7340
+ },
7341
+ resize(cs, rs) {
7342
+ try {
7343
+ child.stdin.write(`\0CW ${rs} ${cs}
7344
+ `);
7345
+ } catch {
7346
+ }
7347
+ },
7348
+ kill() {
7349
+ try {
7350
+ child.kill("SIGTERM");
7351
+ } catch {
7352
+ }
7353
+ }
7354
+ };
7355
+ }
6771
7356
  function openTerminal(opts) {
6772
7357
  if (sessions.size >= MAX_CONCURRENT_SESSIONS) {
6773
7358
  return { error: `Too many open terminals (max ${MAX_CONCURRENT_SESSIONS})` };
@@ -6777,46 +7362,61 @@ function openTerminal(opts) {
6777
7362
  const env = {
6778
7363
  ...process.env,
6779
7364
  TERM: "xterm-256color",
6780
- COLORTERM: "truecolor"
7365
+ COLORTERM: "truecolor",
7366
+ FORCE_COLOR: "1"
6781
7367
  };
6782
- env.FORCE_COLOR = "1";
7368
+ const cols = Math.max(1, Math.min(opts.cols ?? 80, 500));
7369
+ const rows = Math.max(1, Math.min(opts.rows ?? 24, 200));
7370
+ const id = (0, import_crypto.randomUUID)();
6783
7371
  const ptyMod = loadNodePty2();
6784
- if (!ptyMod) {
7372
+ if (ptyMod) {
7373
+ try {
7374
+ const term = ptyMod.spawn(shell, [], {
7375
+ name: "xterm-256color",
7376
+ cols,
7377
+ rows,
7378
+ cwd,
7379
+ env,
7380
+ useConpty: process.platform === "win32" ? true : void 0
7381
+ });
7382
+ const dataListener = term.onData((data) => {
7383
+ onDataHandler?.({ sessionId: id, data });
7384
+ });
7385
+ const exitListener = term.onExit(({ exitCode }) => {
7386
+ onExitHandler?.({ sessionId: id, exitCode });
7387
+ sessions.delete(id);
7388
+ });
7389
+ sessions.set(id, {
7390
+ id,
7391
+ write: (d3) => term.write(d3),
7392
+ resize: (cs, rs) => term.resize(cs, rs),
7393
+ kill: () => {
7394
+ dataListener.dispose();
7395
+ exitListener.dispose();
7396
+ term.kill();
7397
+ }
7398
+ });
7399
+ return { sessionId: id };
7400
+ } catch (e) {
7401
+ if (process.platform === "win32") {
7402
+ return { error: e instanceof Error ? e.message : "spawn failed" };
7403
+ }
7404
+ }
7405
+ } else if (process.platform === "win32") {
6785
7406
  return {
6786
7407
  error: `node-pty native module unavailable on ${process.platform}-${process.arch}; terminal feature disabled for this platform`
6787
7408
  };
6788
7409
  }
6789
- try {
6790
- const term = ptyMod.spawn(shell, [], {
6791
- name: "xterm-256color",
6792
- cols: Math.max(1, Math.min(opts.cols ?? 80, 500)),
6793
- rows: Math.max(1, Math.min(opts.rows ?? 24, 200)),
6794
- cwd,
6795
- env,
6796
- // Windows-specific: ConPTY is the default on Win 10 1809+
6797
- // and is what we want. node-pty falls back to winpty
6798
- // automatically on older builds.
6799
- useConpty: process.platform === "win32" ? true : void 0
6800
- });
6801
- const id = (0, import_crypto.randomUUID)();
6802
- const dataListener = term.onData((data) => {
6803
- onDataHandler?.({ sessionId: id, data });
6804
- });
6805
- const exitListener = term.onExit(({ exitCode }) => {
6806
- onExitHandler?.({ sessionId: id, exitCode });
6807
- sessions.delete(id);
6808
- });
6809
- sessions.set(id, { id, pty: term, dataListener, exitListener });
6810
- return { sessionId: id };
6811
- } catch (e) {
6812
- return { error: e instanceof Error ? e.message : "spawn failed" };
6813
- }
7410
+ const sess = createPythonSession(id, shell, cwd, env, cols, rows);
7411
+ if ("error" in sess) return { error: sess.error };
7412
+ sessions.set(id, sess);
7413
+ return { sessionId: id };
6814
7414
  }
6815
7415
  function writeTerminal(sessionId, data) {
6816
7416
  const s = sessions.get(sessionId);
6817
7417
  if (!s) return { ok: false, error: "No such session" };
6818
7418
  try {
6819
- s.pty.write(data);
7419
+ s.write(data);
6820
7420
  return { ok: true };
6821
7421
  } catch (e) {
6822
7422
  return { ok: false, error: e instanceof Error ? e.message : "write failed" };
@@ -6826,7 +7426,7 @@ function resizeTerminal(sessionId, cols, rows) {
6826
7426
  const s = sessions.get(sessionId);
6827
7427
  if (!s) return { ok: false, error: "No such session" };
6828
7428
  try {
6829
- s.pty.resize(Math.max(1, Math.min(cols, 500)), Math.max(1, Math.min(rows, 200)));
7429
+ s.resize?.(Math.max(1, Math.min(cols, 500)), Math.max(1, Math.min(rows, 200)));
6830
7430
  return { ok: true };
6831
7431
  } catch (e) {
6832
7432
  return { ok: false, error: e instanceof Error ? e.message : "resize failed" };
@@ -6836,9 +7436,7 @@ function closeTerminal(sessionId) {
6836
7436
  const s = sessions.get(sessionId);
6837
7437
  if (!s) return { ok: true };
6838
7438
  try {
6839
- s.dataListener.dispose();
6840
- s.exitListener.dispose();
6841
- s.pty.kill();
7439
+ s.kill();
6842
7440
  } catch {
6843
7441
  }
6844
7442
  sessions.delete(sessionId);
@@ -6849,7 +7447,7 @@ function closeTerminal(sessionId) {
6849
7447
  function saveFilesTemp(files) {
6850
7448
  return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
6851
7449
  const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
6852
- const tmpPath = path18.join(os13.tmpdir(), `codeam-${(0, import_crypto2.randomUUID)()}-${safeName}`);
7450
+ const tmpPath = path19.join(os13.tmpdir(), `codeam-${(0, import_crypto2.randomUUID)()}-${safeName}`);
6853
7451
  fs14.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
6854
7452
  return tmpPath;
6855
7453
  });
@@ -6981,7 +7579,7 @@ var sessionTerminated = (ctx) => {
6981
7579
  } catch {
6982
7580
  }
6983
7581
  try {
6984
- const proc = (0, import_child_process8.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
7582
+ const proc = (0, import_child_process10.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
6985
7583
  detached: true,
6986
7584
  stdio: "ignore"
6987
7585
  });
@@ -7003,7 +7601,7 @@ var shutdownSession = async (ctx, cmd) => {
7003
7601
  }
7004
7602
  if (ctx.keepAliveCtx.inCodespace && ctx.keepAliveCtx.codespaceName) {
7005
7603
  try {
7006
- const stopProc = (0, import_child_process8.spawn)(
7604
+ const stopProc = (0, import_child_process10.spawn)(
7007
7605
  "bash",
7008
7606
  ["-lc", `sleep 1; gh codespace stop -c ${JSON.stringify(ctx.keepAliveCtx.codespaceName)} >/dev/null 2>&1 || true`],
7009
7607
  { detached: true, stdio: "ignore" }
@@ -7013,7 +7611,7 @@ var shutdownSession = async (ctx, cmd) => {
7013
7611
  }
7014
7612
  }
7015
7613
  try {
7016
- const proc = (0, import_child_process8.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
7614
+ const proc = (0, import_child_process10.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
7017
7615
  detached: true,
7018
7616
  stdio: "ignore"
7019
7617
  });
@@ -7232,6 +7830,12 @@ async function start(requestedAgent) {
7232
7830
  session.pluginAuthToken,
7233
7831
  runtime
7234
7832
  );
7833
+ const fileWatcher = session.pluginAuthToken ? new FileWatcherService({
7834
+ workingDir: cwd,
7835
+ sessionId: session.id,
7836
+ pluginId,
7837
+ pluginAuthToken: session.pluginAuthToken
7838
+ }) : null;
7235
7839
  const claude = new AgentService(
7236
7840
  runtime,
7237
7841
  {
@@ -7243,6 +7847,7 @@ async function start(requestedAgent) {
7243
7847
  process.removeListener("SIGINT", sigintHandler);
7244
7848
  outputSvc.dispose();
7245
7849
  relay.stop();
7850
+ void fileWatcher?.stop();
7246
7851
  process.exit(code);
7247
7852
  }
7248
7853
  }
@@ -7272,12 +7877,17 @@ async function start(requestedAgent) {
7272
7877
  claude.kill();
7273
7878
  outputSvc.dispose();
7274
7879
  relay.stop();
7880
+ void fileWatcher?.stop();
7275
7881
  process.exit(0);
7276
7882
  }
7277
7883
  process.once("SIGINT", sigintHandler);
7278
7884
  await claude.spawn();
7279
7885
  await outputSvc.startTerminalTurn();
7280
7886
  relay.start();
7887
+ if (fileWatcher) {
7888
+ fileWatcher.start().catch(() => {
7889
+ });
7890
+ }
7281
7891
  setTimeout(() => {
7282
7892
  historySvc.load().catch(() => {
7283
7893
  });
@@ -7406,7 +8016,7 @@ async function pair(args2 = []) {
7406
8016
  var fs15 = __toESM(require("fs"));
7407
8017
  var os14 = __toESM(require("os"));
7408
8018
  var import_crypto4 = require("crypto");
7409
- var API_BASE5 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
8019
+ var API_BASE6 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
7410
8020
  function fail(msg) {
7411
8021
  console.error(`
7412
8022
  ${msg}
@@ -7421,12 +8031,12 @@ function readTokenFromArgs(args2) {
7421
8031
  }
7422
8032
  const fileFlag = args2.find((a) => a.startsWith("--token-file="));
7423
8033
  if (fileFlag) {
7424
- const path24 = fileFlag.slice("--token-file=".length);
8034
+ const path25 = fileFlag.slice("--token-file=".length);
7425
8035
  try {
7426
- const content = fs15.readFileSync(path24, "utf8").trim();
7427
- if (content.length === 0) fail(`--token-file ${path24} is empty`);
8036
+ const content = fs15.readFileSync(path25, "utf8").trim();
8037
+ if (content.length === 0) fail(`--token-file ${path25} is empty`);
7428
8038
  try {
7429
- fs15.unlinkSync(path24);
8039
+ fs15.unlinkSync(path25);
7430
8040
  } catch {
7431
8041
  }
7432
8042
  return content;
@@ -7438,7 +8048,7 @@ function readTokenFromArgs(args2) {
7438
8048
  fail("codeam pair-auto requires --token-file=<path>, --token=<value>, or CODEAM_AUTO_TOKEN env");
7439
8049
  }
7440
8050
  async function claim(token, pluginId) {
7441
- const url = `${API_BASE5}/api/pairing/claim-auto-token`;
8051
+ const url = `${API_BASE6}/api/pairing/claim-auto-token`;
7442
8052
  const body = {
7443
8053
  token,
7444
8054
  pluginId,
@@ -7593,11 +8203,11 @@ async function logout() {
7593
8203
  var import_picocolors9 = __toESM(require("picocolors"));
7594
8204
 
7595
8205
  // src/services/providers/github-codespaces.ts
7596
- var import_child_process9 = require("child_process");
8206
+ var import_child_process11 = require("child_process");
7597
8207
  var import_util3 = require("util");
7598
8208
  var import_picocolors7 = __toESM(require("picocolors"));
7599
- var path19 = __toESM(require("path"));
7600
- var execFileP3 = (0, import_util3.promisify)(import_child_process9.execFile);
8209
+ var path20 = __toESM(require("path"));
8210
+ var execFileP3 = (0, import_util3.promisify)(import_child_process11.execFile);
7601
8211
  var MAX_BUFFER = 8 * 1024 * 1024;
7602
8212
  function resetStdinForChild() {
7603
8213
  if (process.stdin.isTTY) {
@@ -7641,7 +8251,7 @@ var GitHubCodespacesProvider = class {
7641
8251
  if (!isAuthed) {
7642
8252
  resetStdinForChild();
7643
8253
  await new Promise((resolve2, reject) => {
7644
- const proc = (0, import_child_process9.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
8254
+ const proc = (0, import_child_process11.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
7645
8255
  stdio: "inherit"
7646
8256
  });
7647
8257
  proc.on("exit", (code) => {
@@ -7675,7 +8285,7 @@ var GitHubCodespacesProvider = class {
7675
8285
  wt(noteLines.join("\n"), "One more permission needed");
7676
8286
  resetStdinForChild();
7677
8287
  const refreshCode = await new Promise((resolve2, reject) => {
7678
- const proc = (0, import_child_process9.spawn)(
8288
+ const proc = (0, import_child_process11.spawn)(
7679
8289
  "gh",
7680
8290
  ["auth", "refresh", "-h", "github.com", "-s", "codespace"],
7681
8291
  { stdio: "inherit" }
@@ -7825,7 +8435,7 @@ var GitHubCodespacesProvider = class {
7825
8435
  O2.step(`Installing gh via ${installCmd.describe}\u2026`);
7826
8436
  resetStdinForChild();
7827
8437
  const ok = await new Promise((resolve2) => {
7828
- const proc = (0, import_child_process9.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
8438
+ const proc = (0, import_child_process11.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
7829
8439
  proc.on("exit", (code) => resolve2(code === 0));
7830
8440
  proc.on("error", () => resolve2(false));
7831
8441
  });
@@ -7852,7 +8462,7 @@ var GitHubCodespacesProvider = class {
7852
8462
  );
7853
8463
  resetStdinForChild();
7854
8464
  await new Promise((resolve2, reject) => {
7855
- const proc = (0, import_child_process9.spawn)(
8465
+ const proc = (0, import_child_process11.spawn)(
7856
8466
  "gh",
7857
8467
  ["auth", "refresh", "-h", "github.com", "-s", "repo,read:org"],
7858
8468
  { stdio: "inherit" }
@@ -8030,7 +8640,7 @@ var GitHubCodespacesProvider = class {
8030
8640
  async streamCommand(workspaceId, command2) {
8031
8641
  resetStdinForChild();
8032
8642
  return new Promise((resolve2, reject) => {
8033
- const proc = (0, import_child_process9.spawn)(
8643
+ const proc = (0, import_child_process11.spawn)(
8034
8644
  "gh",
8035
8645
  ["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
8036
8646
  { stdio: "inherit" }
@@ -8057,11 +8667,11 @@ var GitHubCodespacesProvider = class {
8057
8667
  `mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
8058
8668
  ];
8059
8669
  await new Promise((resolve2, reject) => {
8060
- const tar = (0, import_child_process9.spawn)("tar", tarArgs, {
8670
+ const tar = (0, import_child_process11.spawn)("tar", tarArgs, {
8061
8671
  stdio: ["ignore", "pipe", "pipe"],
8062
8672
  env: tarEnv
8063
8673
  });
8064
- const ssh = (0, import_child_process9.spawn)("gh", sshArgs, {
8674
+ const ssh = (0, import_child_process11.spawn)("gh", sshArgs, {
8065
8675
  stdio: [tar.stdout, "pipe", "pipe"]
8066
8676
  });
8067
8677
  let tarErr = "";
@@ -8085,7 +8695,7 @@ var GitHubCodespacesProvider = class {
8085
8695
  });
8086
8696
  }
8087
8697
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
8088
- const remoteDir = path19.posix.dirname(remotePath);
8698
+ const remoteDir = path20.posix.dirname(remotePath);
8089
8699
  const parts = [
8090
8700
  `mkdir -p ${shellQuote(remoteDir)}`,
8091
8701
  `cat > ${shellQuote(remotePath)}`
@@ -8095,7 +8705,7 @@ var GitHubCodespacesProvider = class {
8095
8705
  }
8096
8706
  const cmd = parts.join(" && ");
8097
8707
  await new Promise((resolve2, reject) => {
8098
- const proc = (0, import_child_process9.spawn)(
8708
+ const proc = (0, import_child_process11.spawn)(
8099
8709
  "gh",
8100
8710
  ["codespace", "ssh", "-c", workspaceId, "--", cmd],
8101
8711
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8153,11 +8763,11 @@ function shellQuote(s) {
8153
8763
  }
8154
8764
 
8155
8765
  // src/services/providers/gitpod.ts
8156
- var import_child_process10 = require("child_process");
8766
+ var import_child_process12 = require("child_process");
8157
8767
  var import_util4 = require("util");
8158
- var path20 = __toESM(require("path"));
8768
+ var path21 = __toESM(require("path"));
8159
8769
  var import_picocolors8 = __toESM(require("picocolors"));
8160
- var execFileP4 = (0, import_util4.promisify)(import_child_process10.execFile);
8770
+ var execFileP4 = (0, import_util4.promisify)(import_child_process12.execFile);
8161
8771
  var MAX_BUFFER2 = 8 * 1024 * 1024;
8162
8772
  function resetStdinForChild2() {
8163
8773
  if (process.stdin.isTTY) {
@@ -8197,7 +8807,7 @@ var GitpodProvider = class {
8197
8807
  );
8198
8808
  resetStdinForChild2();
8199
8809
  await new Promise((resolve2, reject) => {
8200
- const proc = (0, import_child_process10.spawn)("gitpod", ["login"], { stdio: "inherit" });
8810
+ const proc = (0, import_child_process12.spawn)("gitpod", ["login"], { stdio: "inherit" });
8201
8811
  proc.on("exit", (code) => {
8202
8812
  if (code === 0) resolve2();
8203
8813
  else reject(new Error("gitpod login failed."));
@@ -8349,7 +8959,7 @@ var GitpodProvider = class {
8349
8959
  async streamCommand(workspaceId, command2) {
8350
8960
  resetStdinForChild2();
8351
8961
  return new Promise((resolve2, reject) => {
8352
- const proc = (0, import_child_process10.spawn)(
8962
+ const proc = (0, import_child_process12.spawn)(
8353
8963
  "gitpod",
8354
8964
  ["workspace", "ssh", workspaceId, "--", "-tt", command2],
8355
8965
  { stdio: "inherit" }
@@ -8369,11 +8979,11 @@ var GitpodProvider = class {
8369
8979
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
8370
8980
  const remoteCmd = `mkdir -p ${shellQuote2(remoteDir)} && tar -xzf - -C ${shellQuote2(remoteDir)}`;
8371
8981
  await new Promise((resolve2, reject) => {
8372
- const tar = (0, import_child_process10.spawn)("tar", tarArgs, {
8982
+ const tar = (0, import_child_process12.spawn)("tar", tarArgs, {
8373
8983
  stdio: ["ignore", "pipe", "pipe"],
8374
8984
  env: tarEnv
8375
8985
  });
8376
- const ssh = (0, import_child_process10.spawn)(
8986
+ const ssh = (0, import_child_process12.spawn)(
8377
8987
  "gitpod",
8378
8988
  ["workspace", "ssh", workspaceId, "--", remoteCmd],
8379
8989
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -8395,7 +9005,7 @@ var GitpodProvider = class {
8395
9005
  });
8396
9006
  }
8397
9007
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
8398
- const remoteDir = path20.posix.dirname(remotePath);
9008
+ const remoteDir = path21.posix.dirname(remotePath);
8399
9009
  const parts = [
8400
9010
  `mkdir -p ${shellQuote2(remoteDir)}`,
8401
9011
  `cat > ${shellQuote2(remotePath)}`
@@ -8405,7 +9015,7 @@ var GitpodProvider = class {
8405
9015
  }
8406
9016
  const cmd = parts.join(" && ");
8407
9017
  await new Promise((resolve2, reject) => {
8408
- const proc = (0, import_child_process10.spawn)(
9018
+ const proc = (0, import_child_process12.spawn)(
8409
9019
  "gitpod",
8410
9020
  ["workspace", "ssh", workspaceId, "--", cmd],
8411
9021
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8429,10 +9039,10 @@ function shellQuote2(s) {
8429
9039
  }
8430
9040
 
8431
9041
  // src/services/providers/gitlab-workspaces.ts
8432
- var import_child_process11 = require("child_process");
9042
+ var import_child_process13 = require("child_process");
8433
9043
  var import_util5 = require("util");
8434
- var path21 = __toESM(require("path"));
8435
- var execFileP5 = (0, import_util5.promisify)(import_child_process11.execFile);
9044
+ var path22 = __toESM(require("path"));
9045
+ var execFileP5 = (0, import_util5.promisify)(import_child_process13.execFile);
8436
9046
  var MAX_BUFFER3 = 8 * 1024 * 1024;
8437
9047
  var GITLAB_API_BASE = process.env.CODEAM_GITLAB_API_URL ?? "https://gitlab.com/api/v4";
8438
9048
  function resetStdinForChild3() {
@@ -8474,7 +9084,7 @@ var GitLabWorkspacesProvider = class {
8474
9084
  );
8475
9085
  resetStdinForChild3();
8476
9086
  await new Promise((resolve2, reject) => {
8477
- const proc = (0, import_child_process11.spawn)(
9087
+ const proc = (0, import_child_process13.spawn)(
8478
9088
  "glab",
8479
9089
  ["auth", "login", "--scopes", "api,read_user,read_repository"],
8480
9090
  { stdio: "inherit" }
@@ -8646,7 +9256,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8646
9256
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
8647
9257
  resetStdinForChild3();
8648
9258
  return new Promise((resolve2, reject) => {
8649
- const proc = (0, import_child_process11.spawn)(
9259
+ const proc = (0, import_child_process13.spawn)(
8650
9260
  "ssh",
8651
9261
  ["-tt", "-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, command2],
8652
9262
  { stdio: "inherit" }
@@ -8667,8 +9277,8 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8667
9277
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
8668
9278
  const remoteCmd = `mkdir -p ${shellQuote3(remoteDir)} && tar -xzf - -C ${shellQuote3(remoteDir)}`;
8669
9279
  await new Promise((resolve2, reject) => {
8670
- const tar = (0, import_child_process11.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
8671
- const ssh = (0, import_child_process11.spawn)(
9280
+ const tar = (0, import_child_process13.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
9281
+ const ssh = (0, import_child_process13.spawn)(
8672
9282
  "ssh",
8673
9283
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, remoteCmd],
8674
9284
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -8691,14 +9301,14 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8691
9301
  }
8692
9302
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
8693
9303
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
8694
- const remoteDir = path21.posix.dirname(remotePath);
9304
+ const remoteDir = path22.posix.dirname(remotePath);
8695
9305
  const parts = [`mkdir -p ${shellQuote3(remoteDir)}`, `cat > ${shellQuote3(remotePath)}`];
8696
9306
  if (options.mode != null) {
8697
9307
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote3(remotePath)}`);
8698
9308
  }
8699
9309
  const cmd = parts.join(" && ");
8700
9310
  await new Promise((resolve2, reject) => {
8701
- const proc = (0, import_child_process11.spawn)(
9311
+ const proc = (0, import_child_process13.spawn)(
8702
9312
  "ssh",
8703
9313
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, cmd],
8704
9314
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8757,10 +9367,10 @@ function shellQuote3(s) {
8757
9367
  }
8758
9368
 
8759
9369
  // src/services/providers/railway.ts
8760
- var import_child_process12 = require("child_process");
9370
+ var import_child_process14 = require("child_process");
8761
9371
  var import_util6 = require("util");
8762
- var path22 = __toESM(require("path"));
8763
- var execFileP6 = (0, import_util6.promisify)(import_child_process12.execFile);
9372
+ var path23 = __toESM(require("path"));
9373
+ var execFileP6 = (0, import_util6.promisify)(import_child_process14.execFile);
8764
9374
  var MAX_BUFFER4 = 8 * 1024 * 1024;
8765
9375
  function resetStdinForChild4() {
8766
9376
  if (process.stdin.isTTY) {
@@ -8801,7 +9411,7 @@ var RailwayProvider = class {
8801
9411
  );
8802
9412
  resetStdinForChild4();
8803
9413
  await new Promise((resolve2, reject) => {
8804
- const proc = (0, import_child_process12.spawn)("railway", ["login"], { stdio: "inherit" });
9414
+ const proc = (0, import_child_process14.spawn)("railway", ["login"], { stdio: "inherit" });
8805
9415
  proc.on("exit", (code) => {
8806
9416
  if (code === 0) resolve2();
8807
9417
  else reject(new Error("railway login failed."));
@@ -8944,7 +9554,7 @@ var RailwayProvider = class {
8944
9554
  }
8945
9555
  resetStdinForChild4();
8946
9556
  return new Promise((resolve2, reject) => {
8947
- const proc = (0, import_child_process12.spawn)(
9557
+ const proc = (0, import_child_process14.spawn)(
8948
9558
  "railway",
8949
9559
  ["shell", "--project", projectId, "--service", serviceId, "--command", command2],
8950
9560
  { stdio: "inherit" }
@@ -8968,8 +9578,8 @@ var RailwayProvider = class {
8968
9578
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
8969
9579
  const remoteCmd = `mkdir -p ${shellQuote4(remoteDir)} && tar -xzf - -C ${shellQuote4(remoteDir)}`;
8970
9580
  await new Promise((resolve2, reject) => {
8971
- const tar = (0, import_child_process12.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
8972
- const sh = (0, import_child_process12.spawn)(
9581
+ const tar = (0, import_child_process14.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
9582
+ const sh = (0, import_child_process14.spawn)(
8973
9583
  "railway",
8974
9584
  ["shell", "--project", projectId, "--service", serviceId, "--command", remoteCmd],
8975
9585
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -8995,14 +9605,14 @@ var RailwayProvider = class {
8995
9605
  if (!projectId || !serviceId) {
8996
9606
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
8997
9607
  }
8998
- const remoteDir = path22.posix.dirname(remotePath);
9608
+ const remoteDir = path23.posix.dirname(remotePath);
8999
9609
  const parts = [`mkdir -p ${shellQuote4(remoteDir)}`, `cat > ${shellQuote4(remotePath)}`];
9000
9610
  if (options.mode != null) {
9001
9611
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote4(remotePath)}`);
9002
9612
  }
9003
9613
  const cmd = parts.join(" && ");
9004
9614
  await new Promise((resolve2, reject) => {
9005
- const proc = (0, import_child_process12.spawn)(
9615
+ const proc = (0, import_child_process14.spawn)(
9006
9616
  "railway",
9007
9617
  ["shell", "--project", projectId, "--service", serviceId, "--command", cmd],
9008
9618
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -9537,7 +10147,7 @@ async function stopWorkspaceFromLocal(target) {
9537
10147
  // src/commands/version.ts
9538
10148
  var import_picocolors11 = __toESM(require("picocolors"));
9539
10149
  function version() {
9540
- const v = true ? "2.15.1" : "unknown";
10150
+ const v = true ? "2.15.5" : "unknown";
9541
10151
  console.log(`${import_picocolors11.default.bold("codeam-cli")} ${import_picocolors11.default.cyan(v)}`);
9542
10152
  }
9543
10153
 
@@ -9580,16 +10190,16 @@ function help() {
9580
10190
  // src/lib/updateNotifier.ts
9581
10191
  var fs16 = __toESM(require("fs"));
9582
10192
  var os15 = __toESM(require("os"));
9583
- var path23 = __toESM(require("path"));
9584
- var https5 = __toESM(require("https"));
10193
+ var path24 = __toESM(require("path"));
10194
+ var https6 = __toESM(require("https"));
9585
10195
  var import_picocolors13 = __toESM(require("picocolors"));
9586
10196
  var PKG_NAME = "codeam-cli";
9587
10197
  var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
9588
10198
  var TTL_MS = 24 * 60 * 60 * 1e3;
9589
10199
  var REQUEST_TIMEOUT_MS = 1500;
9590
10200
  function cachePath() {
9591
- const dir = path23.join(os15.homedir(), ".codeam");
9592
- return path23.join(dir, "update-check.json");
10201
+ const dir = path24.join(os15.homedir(), ".codeam");
10202
+ return path24.join(dir, "update-check.json");
9593
10203
  }
9594
10204
  function readCache() {
9595
10205
  try {
@@ -9604,7 +10214,7 @@ function readCache() {
9604
10214
  function writeCache(cache) {
9605
10215
  try {
9606
10216
  const file = cachePath();
9607
- fs16.mkdirSync(path23.dirname(file), { recursive: true });
10217
+ fs16.mkdirSync(path24.dirname(file), { recursive: true });
9608
10218
  fs16.writeFileSync(file, JSON.stringify(cache));
9609
10219
  } catch {
9610
10220
  }
@@ -9624,7 +10234,7 @@ function compareSemver(a, b) {
9624
10234
  }
9625
10235
  function fetchLatest() {
9626
10236
  return new Promise((resolve2) => {
9627
- const req = https5.get(
10237
+ const req = https6.get(
9628
10238
  REGISTRY_URL,
9629
10239
  { headers: { Accept: "application/json" }, timeout: REQUEST_TIMEOUT_MS },
9630
10240
  (res) => {
@@ -9676,7 +10286,7 @@ function checkForUpdates() {
9676
10286
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
9677
10287
  if (process.env.CI) return;
9678
10288
  if (!process.stdout.isTTY) return;
9679
- const current = true ? "2.15.1" : null;
10289
+ const current = true ? "2.15.5" : null;
9680
10290
  if (!current) return;
9681
10291
  const cache = readCache();
9682
10292
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;