codeam-cli 2.15.2 → 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.
Files changed (3) hide show
  1. package/README.md +7 -5
  2. package/dist/index.js +562 -95
  3. package/package.json +14 -13
package/README.md CHANGED
@@ -5,10 +5,10 @@
5
5
  [![license](https://img.shields.io/npm/l/codeam-cli.svg?color=34d399&style=flat-square)](https://github.com/edgar-durand/codeagent-mobile-clients/blob/main/LICENSE)
6
6
  [![node](https://img.shields.io/node/v/codeam-cli.svg?color=34d399&style=flat-square)](https://nodejs.org/)
7
7
 
8
- > **Remote control AI coding agents from your phone.**
9
- > Send prompts, stream responses, and approve commands in real-time — from the subway, the couch, or anywhere away from your desk.
8
+ > **The workflow-continuity bridge for AI coding agents.**
9
+ > Wrap Claude Code or Codex once, then supervise, approve, and redirect from any device async.
10
10
 
11
- `codeam-cli` is the companion CLI for [**CodeAgent Mobile**](https://codeagent-mobile.com). It wraps AI coding agents inside a pseudo-terminal, relays your mobile prompts to the agent, and streams the output back to your phone in real-time.
11
+ `codeam-cli` is the terminal bridge for [**CodeAgent Mobile**](https://codeagent-mobile.com). It wraps AI coding agents inside a pseudo-terminal and streams the entire session output, diffs, interactive selectors to your phone or web dashboard so you can stay in the loop while the agent runs for hours instead of seconds.
12
12
 
13
13
  Currently supports **[Claude Code](https://claude.ai/code)** (Anthropic) and **[OpenAI Codex](https://github.com/openai/codex)** — start either via `codeam` (Claude Code) or `codeam codex` (OpenAI Codex).
14
14
 
@@ -16,9 +16,11 @@ Currently supports **[Claude Code](https://claude.ai/code)** (Anthropic) and **[
16
16
 
17
17
  ## Why does this exist?
18
18
 
19
- Because sometimes your best ideas happen away from the keyboard. Maybe you're on a walk, on the train, in a meeting, or just want to keep a long-running task going while you step away. `codeam-cli` lets your AI agent keep working — and lets **you** keep shipping without needing to be at your machine.
19
+ AI agents went async. They write, refactor, test, and ship code on their own for hours, not seconds. Most CLI workflows still pin you to one screen while that happens.
20
20
 
21
- It works exactly like Claude Code (same terminal, same project, same files), but every prompt and every response is mirrored on your phone. You can even approve interactive selectors and confirmations from mobile.
21
+ `codeam-cli` is the supervision layer on top: run the agent locally exactly like you would today, and a paired phone / browser becomes a remote checkpoint. Approve diffs while you're away from the desk. Redirect a long-running refactor over coffee. Step into a meeting without losing the session.
22
+
23
+ Same terminal, same project, same files — just no longer chained to the desk.
22
24
 
23
25
  ---
24
26
 
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.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_process9 = 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,7 +7190,7 @@ async function jsSearchFiles(opts, cwd, cap) {
6735
7190
  }
6736
7191
 
6737
7192
  // src/services/terminal-ops.service.ts
6738
- var import_child_process8 = require("child_process");
7193
+ var import_child_process9 = require("child_process");
6739
7194
  var import_crypto = require("crypto");
6740
7195
  var import_path = __toESM(require("path"));
6741
7196
  var MAX_CONCURRENT_SESSIONS = 4;
@@ -6857,7 +7312,7 @@ function createPythonSession(id, shell, cwd, env, cols, rows) {
6857
7312
  }
6858
7313
  let child;
6859
7314
  try {
6860
- child = (0, import_child_process8.spawn)(python, ["-c", PYTHON_TERMINAL_HELPER, shell], {
7315
+ child = (0, import_child_process9.spawn)(python, ["-c", PYTHON_TERMINAL_HELPER, shell], {
6861
7316
  cwd,
6862
7317
  env: { ...env, COLUMNS: String(cols), LINES: String(rows) },
6863
7318
  stdio: ["pipe", "pipe", "pipe"]
@@ -6992,7 +7447,7 @@ function closeTerminal(sessionId) {
6992
7447
  function saveFilesTemp(files) {
6993
7448
  return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
6994
7449
  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}`);
7450
+ const tmpPath = path19.join(os13.tmpdir(), `codeam-${(0, import_crypto2.randomUUID)()}-${safeName}`);
6996
7451
  fs14.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
6997
7452
  return tmpPath;
6998
7453
  });
@@ -7124,7 +7579,7 @@ var sessionTerminated = (ctx) => {
7124
7579
  } catch {
7125
7580
  }
7126
7581
  try {
7127
- const proc = (0, import_child_process9.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"], {
7128
7583
  detached: true,
7129
7584
  stdio: "ignore"
7130
7585
  });
@@ -7146,7 +7601,7 @@ var shutdownSession = async (ctx, cmd) => {
7146
7601
  }
7147
7602
  if (ctx.keepAliveCtx.inCodespace && ctx.keepAliveCtx.codespaceName) {
7148
7603
  try {
7149
- const stopProc = (0, import_child_process9.spawn)(
7604
+ const stopProc = (0, import_child_process10.spawn)(
7150
7605
  "bash",
7151
7606
  ["-lc", `sleep 1; gh codespace stop -c ${JSON.stringify(ctx.keepAliveCtx.codespaceName)} >/dev/null 2>&1 || true`],
7152
7607
  { detached: true, stdio: "ignore" }
@@ -7156,7 +7611,7 @@ var shutdownSession = async (ctx, cmd) => {
7156
7611
  }
7157
7612
  }
7158
7613
  try {
7159
- const proc = (0, import_child_process9.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"], {
7160
7615
  detached: true,
7161
7616
  stdio: "ignore"
7162
7617
  });
@@ -7375,6 +7830,12 @@ async function start(requestedAgent) {
7375
7830
  session.pluginAuthToken,
7376
7831
  runtime
7377
7832
  );
7833
+ const fileWatcher = session.pluginAuthToken ? new FileWatcherService({
7834
+ workingDir: cwd,
7835
+ sessionId: session.id,
7836
+ pluginId,
7837
+ pluginAuthToken: session.pluginAuthToken
7838
+ }) : null;
7378
7839
  const claude = new AgentService(
7379
7840
  runtime,
7380
7841
  {
@@ -7386,6 +7847,7 @@ async function start(requestedAgent) {
7386
7847
  process.removeListener("SIGINT", sigintHandler);
7387
7848
  outputSvc.dispose();
7388
7849
  relay.stop();
7850
+ void fileWatcher?.stop();
7389
7851
  process.exit(code);
7390
7852
  }
7391
7853
  }
@@ -7415,12 +7877,17 @@ async function start(requestedAgent) {
7415
7877
  claude.kill();
7416
7878
  outputSvc.dispose();
7417
7879
  relay.stop();
7880
+ void fileWatcher?.stop();
7418
7881
  process.exit(0);
7419
7882
  }
7420
7883
  process.once("SIGINT", sigintHandler);
7421
7884
  await claude.spawn();
7422
7885
  await outputSvc.startTerminalTurn();
7423
7886
  relay.start();
7887
+ if (fileWatcher) {
7888
+ fileWatcher.start().catch(() => {
7889
+ });
7890
+ }
7424
7891
  setTimeout(() => {
7425
7892
  historySvc.load().catch(() => {
7426
7893
  });
@@ -7549,7 +8016,7 @@ async function pair(args2 = []) {
7549
8016
  var fs15 = __toESM(require("fs"));
7550
8017
  var os14 = __toESM(require("os"));
7551
8018
  var import_crypto4 = require("crypto");
7552
- 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";
7553
8020
  function fail(msg) {
7554
8021
  console.error(`
7555
8022
  ${msg}
@@ -7564,12 +8031,12 @@ function readTokenFromArgs(args2) {
7564
8031
  }
7565
8032
  const fileFlag = args2.find((a) => a.startsWith("--token-file="));
7566
8033
  if (fileFlag) {
7567
- const path24 = fileFlag.slice("--token-file=".length);
8034
+ const path25 = fileFlag.slice("--token-file=".length);
7568
8035
  try {
7569
- const content = fs15.readFileSync(path24, "utf8").trim();
7570
- 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`);
7571
8038
  try {
7572
- fs15.unlinkSync(path24);
8039
+ fs15.unlinkSync(path25);
7573
8040
  } catch {
7574
8041
  }
7575
8042
  return content;
@@ -7581,7 +8048,7 @@ function readTokenFromArgs(args2) {
7581
8048
  fail("codeam pair-auto requires --token-file=<path>, --token=<value>, or CODEAM_AUTO_TOKEN env");
7582
8049
  }
7583
8050
  async function claim(token, pluginId) {
7584
- const url = `${API_BASE5}/api/pairing/claim-auto-token`;
8051
+ const url = `${API_BASE6}/api/pairing/claim-auto-token`;
7585
8052
  const body = {
7586
8053
  token,
7587
8054
  pluginId,
@@ -7736,11 +8203,11 @@ async function logout() {
7736
8203
  var import_picocolors9 = __toESM(require("picocolors"));
7737
8204
 
7738
8205
  // src/services/providers/github-codespaces.ts
7739
- var import_child_process10 = require("child_process");
8206
+ var import_child_process11 = require("child_process");
7740
8207
  var import_util3 = require("util");
7741
8208
  var import_picocolors7 = __toESM(require("picocolors"));
7742
- var path19 = __toESM(require("path"));
7743
- var execFileP3 = (0, import_util3.promisify)(import_child_process10.execFile);
8209
+ var path20 = __toESM(require("path"));
8210
+ var execFileP3 = (0, import_util3.promisify)(import_child_process11.execFile);
7744
8211
  var MAX_BUFFER = 8 * 1024 * 1024;
7745
8212
  function resetStdinForChild() {
7746
8213
  if (process.stdin.isTTY) {
@@ -7784,7 +8251,7 @@ var GitHubCodespacesProvider = class {
7784
8251
  if (!isAuthed) {
7785
8252
  resetStdinForChild();
7786
8253
  await new Promise((resolve2, reject) => {
7787
- const proc = (0, import_child_process10.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"], {
7788
8255
  stdio: "inherit"
7789
8256
  });
7790
8257
  proc.on("exit", (code) => {
@@ -7818,7 +8285,7 @@ var GitHubCodespacesProvider = class {
7818
8285
  wt(noteLines.join("\n"), "One more permission needed");
7819
8286
  resetStdinForChild();
7820
8287
  const refreshCode = await new Promise((resolve2, reject) => {
7821
- const proc = (0, import_child_process10.spawn)(
8288
+ const proc = (0, import_child_process11.spawn)(
7822
8289
  "gh",
7823
8290
  ["auth", "refresh", "-h", "github.com", "-s", "codespace"],
7824
8291
  { stdio: "inherit" }
@@ -7968,7 +8435,7 @@ var GitHubCodespacesProvider = class {
7968
8435
  O2.step(`Installing gh via ${installCmd.describe}\u2026`);
7969
8436
  resetStdinForChild();
7970
8437
  const ok = await new Promise((resolve2) => {
7971
- const proc = (0, import_child_process10.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
8438
+ const proc = (0, import_child_process11.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
7972
8439
  proc.on("exit", (code) => resolve2(code === 0));
7973
8440
  proc.on("error", () => resolve2(false));
7974
8441
  });
@@ -7995,7 +8462,7 @@ var GitHubCodespacesProvider = class {
7995
8462
  );
7996
8463
  resetStdinForChild();
7997
8464
  await new Promise((resolve2, reject) => {
7998
- const proc = (0, import_child_process10.spawn)(
8465
+ const proc = (0, import_child_process11.spawn)(
7999
8466
  "gh",
8000
8467
  ["auth", "refresh", "-h", "github.com", "-s", "repo,read:org"],
8001
8468
  { stdio: "inherit" }
@@ -8173,7 +8640,7 @@ var GitHubCodespacesProvider = class {
8173
8640
  async streamCommand(workspaceId, command2) {
8174
8641
  resetStdinForChild();
8175
8642
  return new Promise((resolve2, reject) => {
8176
- const proc = (0, import_child_process10.spawn)(
8643
+ const proc = (0, import_child_process11.spawn)(
8177
8644
  "gh",
8178
8645
  ["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
8179
8646
  { stdio: "inherit" }
@@ -8200,11 +8667,11 @@ var GitHubCodespacesProvider = class {
8200
8667
  `mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
8201
8668
  ];
8202
8669
  await new Promise((resolve2, reject) => {
8203
- const tar = (0, import_child_process10.spawn)("tar", tarArgs, {
8670
+ const tar = (0, import_child_process11.spawn)("tar", tarArgs, {
8204
8671
  stdio: ["ignore", "pipe", "pipe"],
8205
8672
  env: tarEnv
8206
8673
  });
8207
- const ssh = (0, import_child_process10.spawn)("gh", sshArgs, {
8674
+ const ssh = (0, import_child_process11.spawn)("gh", sshArgs, {
8208
8675
  stdio: [tar.stdout, "pipe", "pipe"]
8209
8676
  });
8210
8677
  let tarErr = "";
@@ -8228,7 +8695,7 @@ var GitHubCodespacesProvider = class {
8228
8695
  });
8229
8696
  }
8230
8697
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
8231
- const remoteDir = path19.posix.dirname(remotePath);
8698
+ const remoteDir = path20.posix.dirname(remotePath);
8232
8699
  const parts = [
8233
8700
  `mkdir -p ${shellQuote(remoteDir)}`,
8234
8701
  `cat > ${shellQuote(remotePath)}`
@@ -8238,7 +8705,7 @@ var GitHubCodespacesProvider = class {
8238
8705
  }
8239
8706
  const cmd = parts.join(" && ");
8240
8707
  await new Promise((resolve2, reject) => {
8241
- const proc = (0, import_child_process10.spawn)(
8708
+ const proc = (0, import_child_process11.spawn)(
8242
8709
  "gh",
8243
8710
  ["codespace", "ssh", "-c", workspaceId, "--", cmd],
8244
8711
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8296,11 +8763,11 @@ function shellQuote(s) {
8296
8763
  }
8297
8764
 
8298
8765
  // src/services/providers/gitpod.ts
8299
- var import_child_process11 = require("child_process");
8766
+ var import_child_process12 = require("child_process");
8300
8767
  var import_util4 = require("util");
8301
- var path20 = __toESM(require("path"));
8768
+ var path21 = __toESM(require("path"));
8302
8769
  var import_picocolors8 = __toESM(require("picocolors"));
8303
- var execFileP4 = (0, import_util4.promisify)(import_child_process11.execFile);
8770
+ var execFileP4 = (0, import_util4.promisify)(import_child_process12.execFile);
8304
8771
  var MAX_BUFFER2 = 8 * 1024 * 1024;
8305
8772
  function resetStdinForChild2() {
8306
8773
  if (process.stdin.isTTY) {
@@ -8340,7 +8807,7 @@ var GitpodProvider = class {
8340
8807
  );
8341
8808
  resetStdinForChild2();
8342
8809
  await new Promise((resolve2, reject) => {
8343
- const proc = (0, import_child_process11.spawn)("gitpod", ["login"], { stdio: "inherit" });
8810
+ const proc = (0, import_child_process12.spawn)("gitpod", ["login"], { stdio: "inherit" });
8344
8811
  proc.on("exit", (code) => {
8345
8812
  if (code === 0) resolve2();
8346
8813
  else reject(new Error("gitpod login failed."));
@@ -8492,7 +8959,7 @@ var GitpodProvider = class {
8492
8959
  async streamCommand(workspaceId, command2) {
8493
8960
  resetStdinForChild2();
8494
8961
  return new Promise((resolve2, reject) => {
8495
- const proc = (0, import_child_process11.spawn)(
8962
+ const proc = (0, import_child_process12.spawn)(
8496
8963
  "gitpod",
8497
8964
  ["workspace", "ssh", workspaceId, "--", "-tt", command2],
8498
8965
  { stdio: "inherit" }
@@ -8512,11 +8979,11 @@ var GitpodProvider = class {
8512
8979
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
8513
8980
  const remoteCmd = `mkdir -p ${shellQuote2(remoteDir)} && tar -xzf - -C ${shellQuote2(remoteDir)}`;
8514
8981
  await new Promise((resolve2, reject) => {
8515
- const tar = (0, import_child_process11.spawn)("tar", tarArgs, {
8982
+ const tar = (0, import_child_process12.spawn)("tar", tarArgs, {
8516
8983
  stdio: ["ignore", "pipe", "pipe"],
8517
8984
  env: tarEnv
8518
8985
  });
8519
- const ssh = (0, import_child_process11.spawn)(
8986
+ const ssh = (0, import_child_process12.spawn)(
8520
8987
  "gitpod",
8521
8988
  ["workspace", "ssh", workspaceId, "--", remoteCmd],
8522
8989
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -8538,7 +9005,7 @@ var GitpodProvider = class {
8538
9005
  });
8539
9006
  }
8540
9007
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
8541
- const remoteDir = path20.posix.dirname(remotePath);
9008
+ const remoteDir = path21.posix.dirname(remotePath);
8542
9009
  const parts = [
8543
9010
  `mkdir -p ${shellQuote2(remoteDir)}`,
8544
9011
  `cat > ${shellQuote2(remotePath)}`
@@ -8548,7 +9015,7 @@ var GitpodProvider = class {
8548
9015
  }
8549
9016
  const cmd = parts.join(" && ");
8550
9017
  await new Promise((resolve2, reject) => {
8551
- const proc = (0, import_child_process11.spawn)(
9018
+ const proc = (0, import_child_process12.spawn)(
8552
9019
  "gitpod",
8553
9020
  ["workspace", "ssh", workspaceId, "--", cmd],
8554
9021
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8572,10 +9039,10 @@ function shellQuote2(s) {
8572
9039
  }
8573
9040
 
8574
9041
  // src/services/providers/gitlab-workspaces.ts
8575
- var import_child_process12 = require("child_process");
9042
+ var import_child_process13 = require("child_process");
8576
9043
  var import_util5 = require("util");
8577
- var path21 = __toESM(require("path"));
8578
- var execFileP5 = (0, import_util5.promisify)(import_child_process12.execFile);
9044
+ var path22 = __toESM(require("path"));
9045
+ var execFileP5 = (0, import_util5.promisify)(import_child_process13.execFile);
8579
9046
  var MAX_BUFFER3 = 8 * 1024 * 1024;
8580
9047
  var GITLAB_API_BASE = process.env.CODEAM_GITLAB_API_URL ?? "https://gitlab.com/api/v4";
8581
9048
  function resetStdinForChild3() {
@@ -8617,7 +9084,7 @@ var GitLabWorkspacesProvider = class {
8617
9084
  );
8618
9085
  resetStdinForChild3();
8619
9086
  await new Promise((resolve2, reject) => {
8620
- const proc = (0, import_child_process12.spawn)(
9087
+ const proc = (0, import_child_process13.spawn)(
8621
9088
  "glab",
8622
9089
  ["auth", "login", "--scopes", "api,read_user,read_repository"],
8623
9090
  { stdio: "inherit" }
@@ -8789,7 +9256,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8789
9256
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
8790
9257
  resetStdinForChild3();
8791
9258
  return new Promise((resolve2, reject) => {
8792
- const proc = (0, import_child_process12.spawn)(
9259
+ const proc = (0, import_child_process13.spawn)(
8793
9260
  "ssh",
8794
9261
  ["-tt", "-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, command2],
8795
9262
  { stdio: "inherit" }
@@ -8810,8 +9277,8 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8810
9277
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
8811
9278
  const remoteCmd = `mkdir -p ${shellQuote3(remoteDir)} && tar -xzf - -C ${shellQuote3(remoteDir)}`;
8812
9279
  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)(
9280
+ const tar = (0, import_child_process13.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
9281
+ const ssh = (0, import_child_process13.spawn)(
8815
9282
  "ssh",
8816
9283
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, remoteCmd],
8817
9284
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -8834,14 +9301,14 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
8834
9301
  }
8835
9302
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
8836
9303
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
8837
- const remoteDir = path21.posix.dirname(remotePath);
9304
+ const remoteDir = path22.posix.dirname(remotePath);
8838
9305
  const parts = [`mkdir -p ${shellQuote3(remoteDir)}`, `cat > ${shellQuote3(remotePath)}`];
8839
9306
  if (options.mode != null) {
8840
9307
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote3(remotePath)}`);
8841
9308
  }
8842
9309
  const cmd = parts.join(" && ");
8843
9310
  await new Promise((resolve2, reject) => {
8844
- const proc = (0, import_child_process12.spawn)(
9311
+ const proc = (0, import_child_process13.spawn)(
8845
9312
  "ssh",
8846
9313
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, cmd],
8847
9314
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -8900,10 +9367,10 @@ function shellQuote3(s) {
8900
9367
  }
8901
9368
 
8902
9369
  // src/services/providers/railway.ts
8903
- var import_child_process13 = require("child_process");
9370
+ var import_child_process14 = require("child_process");
8904
9371
  var import_util6 = require("util");
8905
- var path22 = __toESM(require("path"));
8906
- var execFileP6 = (0, import_util6.promisify)(import_child_process13.execFile);
9372
+ var path23 = __toESM(require("path"));
9373
+ var execFileP6 = (0, import_util6.promisify)(import_child_process14.execFile);
8907
9374
  var MAX_BUFFER4 = 8 * 1024 * 1024;
8908
9375
  function resetStdinForChild4() {
8909
9376
  if (process.stdin.isTTY) {
@@ -8944,7 +9411,7 @@ var RailwayProvider = class {
8944
9411
  );
8945
9412
  resetStdinForChild4();
8946
9413
  await new Promise((resolve2, reject) => {
8947
- const proc = (0, import_child_process13.spawn)("railway", ["login"], { stdio: "inherit" });
9414
+ const proc = (0, import_child_process14.spawn)("railway", ["login"], { stdio: "inherit" });
8948
9415
  proc.on("exit", (code) => {
8949
9416
  if (code === 0) resolve2();
8950
9417
  else reject(new Error("railway login failed."));
@@ -9087,7 +9554,7 @@ var RailwayProvider = class {
9087
9554
  }
9088
9555
  resetStdinForChild4();
9089
9556
  return new Promise((resolve2, reject) => {
9090
- const proc = (0, import_child_process13.spawn)(
9557
+ const proc = (0, import_child_process14.spawn)(
9091
9558
  "railway",
9092
9559
  ["shell", "--project", projectId, "--service", serviceId, "--command", command2],
9093
9560
  { stdio: "inherit" }
@@ -9111,8 +9578,8 @@ var RailwayProvider = class {
9111
9578
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
9112
9579
  const remoteCmd = `mkdir -p ${shellQuote4(remoteDir)} && tar -xzf - -C ${shellQuote4(remoteDir)}`;
9113
9580
  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)(
9581
+ const tar = (0, import_child_process14.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
9582
+ const sh = (0, import_child_process14.spawn)(
9116
9583
  "railway",
9117
9584
  ["shell", "--project", projectId, "--service", serviceId, "--command", remoteCmd],
9118
9585
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -9138,14 +9605,14 @@ var RailwayProvider = class {
9138
9605
  if (!projectId || !serviceId) {
9139
9606
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
9140
9607
  }
9141
- const remoteDir = path22.posix.dirname(remotePath);
9608
+ const remoteDir = path23.posix.dirname(remotePath);
9142
9609
  const parts = [`mkdir -p ${shellQuote4(remoteDir)}`, `cat > ${shellQuote4(remotePath)}`];
9143
9610
  if (options.mode != null) {
9144
9611
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote4(remotePath)}`);
9145
9612
  }
9146
9613
  const cmd = parts.join(" && ");
9147
9614
  await new Promise((resolve2, reject) => {
9148
- const proc = (0, import_child_process13.spawn)(
9615
+ const proc = (0, import_child_process14.spawn)(
9149
9616
  "railway",
9150
9617
  ["shell", "--project", projectId, "--service", serviceId, "--command", cmd],
9151
9618
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -9680,7 +10147,7 @@ async function stopWorkspaceFromLocal(target) {
9680
10147
  // src/commands/version.ts
9681
10148
  var import_picocolors11 = __toESM(require("picocolors"));
9682
10149
  function version() {
9683
- const v = true ? "2.15.2" : "unknown";
10150
+ const v = true ? "2.15.5" : "unknown";
9684
10151
  console.log(`${import_picocolors11.default.bold("codeam-cli")} ${import_picocolors11.default.cyan(v)}`);
9685
10152
  }
9686
10153
 
@@ -9723,16 +10190,16 @@ function help() {
9723
10190
  // src/lib/updateNotifier.ts
9724
10191
  var fs16 = __toESM(require("fs"));
9725
10192
  var os15 = __toESM(require("os"));
9726
- var path23 = __toESM(require("path"));
9727
- var https5 = __toESM(require("https"));
10193
+ var path24 = __toESM(require("path"));
10194
+ var https6 = __toESM(require("https"));
9728
10195
  var import_picocolors13 = __toESM(require("picocolors"));
9729
10196
  var PKG_NAME = "codeam-cli";
9730
10197
  var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
9731
10198
  var TTL_MS = 24 * 60 * 60 * 1e3;
9732
10199
  var REQUEST_TIMEOUT_MS = 1500;
9733
10200
  function cachePath() {
9734
- const dir = path23.join(os15.homedir(), ".codeam");
9735
- return path23.join(dir, "update-check.json");
10201
+ const dir = path24.join(os15.homedir(), ".codeam");
10202
+ return path24.join(dir, "update-check.json");
9736
10203
  }
9737
10204
  function readCache() {
9738
10205
  try {
@@ -9747,7 +10214,7 @@ function readCache() {
9747
10214
  function writeCache(cache) {
9748
10215
  try {
9749
10216
  const file = cachePath();
9750
- fs16.mkdirSync(path23.dirname(file), { recursive: true });
10217
+ fs16.mkdirSync(path24.dirname(file), { recursive: true });
9751
10218
  fs16.writeFileSync(file, JSON.stringify(cache));
9752
10219
  } catch {
9753
10220
  }
@@ -9767,7 +10234,7 @@ function compareSemver(a, b) {
9767
10234
  }
9768
10235
  function fetchLatest() {
9769
10236
  return new Promise((resolve2) => {
9770
- const req = https5.get(
10237
+ const req = https6.get(
9771
10238
  REGISTRY_URL,
9772
10239
  { headers: { Accept: "application/json" }, timeout: REQUEST_TIMEOUT_MS },
9773
10240
  (res) => {
@@ -9819,7 +10286,7 @@ function checkForUpdates() {
9819
10286
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
9820
10287
  if (process.env.CI) return;
9821
10288
  if (!process.stdout.isTTY) return;
9822
- const current = true ? "2.15.2" : null;
10289
+ const current = true ? "2.15.5" : null;
9823
10290
  if (!current) return;
9824
10291
  const cache = readCache();
9825
10292
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.15.2",
4
- "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 commandsfrom anywhere.",
3
+ "version": "2.15.5",
4
+ "description": "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device async. The terminal companion for CodeAgent Mobile.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -24,29 +24,29 @@
24
24
  "keywords": [
25
25
  "claude",
26
26
  "claude-code",
27
- "claude-code-mobile",
28
27
  "ai",
29
28
  "ai-agent",
30
29
  "ai-coding",
31
- "ai-pair-programming",
30
+ "ai-workflow",
31
+ "ai-workflow-continuity",
32
+ "async-ai-productivity",
33
+ "remote-ai-supervision",
32
34
  "cli",
33
- "remote-control",
34
- "remote-development",
35
- "mobile-ide",
36
- "mobile-coding",
37
- "pair-programming",
35
+ "developer-tools",
36
+ "devtools",
38
37
  "codeagent",
39
38
  "codeam",
40
39
  "anthropic",
40
+ "openai-codex",
41
41
  "cursor",
42
42
  "copilot",
43
43
  "jetbrains",
44
44
  "vscode",
45
+ "windsurf",
45
46
  "mcp",
46
- "code-from-phone",
47
- "afk-coding",
48
- "remote-pair-programming",
49
- "agent-control"
47
+ "remote-development",
48
+ "agent-control",
49
+ "agent-operations"
50
50
  ],
51
51
  "homepage": "https://codeagent-mobile.com",
52
52
  "bugs": {
@@ -70,6 +70,7 @@
70
70
  },
71
71
  "dependencies": {
72
72
  "@clack/prompts": "^1.2.0",
73
+ "chokidar": "^3.6.0",
73
74
  "picocolors": "^1.1.0",
74
75
  "qrcode-terminal": "^0.12.0",
75
76
  "ws": "^8.18.0",