codeam-cli 2.25.0 → 2.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/index.js +801 -186
  3. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -326,6 +326,37 @@ function resolveApiBaseUrl() {
326
326
  return DEFAULT_API_BASE_URL;
327
327
  }
328
328
 
329
+ // ../../packages/shared/src/preview-prompts.ts
330
+ var PREVIEW_DETECT_PROMPT = `
331
+ Analyze the project in the current working directory and return how to start
332
+ its development server for in-app preview.
333
+
334
+ Read package.json, Procfile, Dockerfile, docker-compose.yml, manage.py, app.json,
335
+ mix.exs, Cargo.toml, go.mod, requirements.txt, Gemfile, and any other framework
336
+ markers you find at depth <= 2.
337
+
338
+ Return ONLY a JSON object on stdout (no prose, no markdown fences):
339
+
340
+ {
341
+ "framework": "<name, or 'unsupported'>",
342
+ "command": "<executable>",
343
+ "args": ["..."],
344
+ "port": <number>,
345
+ "ready_pattern": "<regex matching the server-ready stdout line>",
346
+ "env": { "HOST": "0.0.0.0" },
347
+ "setup_commands": [{"cmd":"npm","args":["install"]}],
348
+ "notes": "<one-line caveat or null>"
349
+ }
350
+
351
+ Rules:
352
+ - Pick the script the developer would run locally to see the app (typically "dev", "start", "serve").
353
+ - Prefer binding to 0.0.0.0 \u2014 most frameworks default to localhost which the tunnel cannot reach.
354
+ - For Expo: framework="Expo", command="npx", args=["expo","start","--tunnel"], port=8081, notes="Scan QR with Expo Go".
355
+ - If no dev server applies (CLI library, lambda, batch script): {"framework":"unsupported","notes":"<reason>"}.
356
+
357
+ OUTPUT JSON ONLY. NO MARKDOWN. NO COMMENTARY.
358
+ `.trim();
359
+
329
360
  // src/config.ts
330
361
  var fs = __toESM(require("fs"));
331
362
  var os = __toESM(require("os"));
@@ -441,7 +472,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
441
472
  // package.json
442
473
  var package_default = {
443
474
  name: "codeam-cli",
444
- version: "2.25.0",
475
+ version: "2.26.1",
445
476
  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.",
446
477
  type: "commonjs",
447
478
  main: "dist/index.js",
@@ -515,6 +546,7 @@ var package_default = {
515
546
  chokidar: "^3.6.0",
516
547
  picocolors: "^1.1.0",
517
548
  "qrcode-terminal": "^0.12.0",
549
+ which: "^2.0.2",
518
550
  ws: "^8.18.0",
519
551
  zod: "^4.3.6"
520
552
  },
@@ -522,6 +554,7 @@ var package_default = {
522
554
  "@codeagent/shared": "*",
523
555
  "@types/node": "^22.0.0",
524
556
  "@types/qrcode-terminal": "^0.12.0",
557
+ "@types/which": "^3.0.4",
525
558
  "@types/ws": "^8.5.0",
526
559
  "@vitest/coverage-v8": "^4.1.7",
527
560
  "node-pty": "^1.1.0",
@@ -768,6 +801,28 @@ async function postAiResult(input) {
768
801
  };
769
802
  }
770
803
  }
804
+ async function postPreviewEvent(input) {
805
+ try {
806
+ await _transport.postJsonAuthed(
807
+ `${API_BASE}/api/preview/events`,
808
+ {
809
+ sessionId: input.sessionId,
810
+ pluginId: input.pluginId,
811
+ type: input.type,
812
+ payload: input.payload ?? {}
813
+ },
814
+ input.pluginAuthToken
815
+ );
816
+ return { ok: true };
817
+ } catch (err) {
818
+ const e = err;
819
+ return {
820
+ ok: false,
821
+ status: typeof e.statusCode === "number" ? e.statusCode : 0,
822
+ message: e.message || "unknown"
823
+ };
824
+ }
825
+ }
771
826
  async function _postJsonAuthed(url, body, pluginAuthToken) {
772
827
  return new Promise((resolve5, reject) => {
773
828
  const data = JSON.stringify(body);
@@ -1060,8 +1115,8 @@ function createGetModuleFromFilename(basePath = process.argv[1] ? (0, import_pat
1060
1115
  return decodedFile;
1061
1116
  };
1062
1117
  }
1063
- function normalizeWindowsPath(path40) {
1064
- return path40.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
1118
+ function normalizeWindowsPath(path42) {
1119
+ return path42.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
1065
1120
  }
1066
1121
 
1067
1122
  // ../../node_modules/@posthog/core/dist/featureFlagUtils.mjs
@@ -3541,9 +3596,9 @@ async function addSourceContext(frames) {
3541
3596
  LRU_FILE_CONTENTS_CACHE.reduce();
3542
3597
  return frames;
3543
3598
  }
3544
- function getContextLinesFromFile(path40, ranges, output) {
3599
+ function getContextLinesFromFile(path42, ranges, output) {
3545
3600
  return new Promise((resolve5) => {
3546
- const stream = (0, import_node_fs.createReadStream)(path40);
3601
+ const stream = (0, import_node_fs.createReadStream)(path42);
3547
3602
  const lineReaded = (0, import_node_readline.createInterface)({
3548
3603
  input: stream
3549
3604
  });
@@ -3558,7 +3613,7 @@ function getContextLinesFromFile(path40, ranges, output) {
3558
3613
  let rangeStart = range[0];
3559
3614
  let rangeEnd = range[1];
3560
3615
  function onStreamError() {
3561
- LRU_FILE_CONTENTS_FS_READ_FAILED.set(path40, 1);
3616
+ LRU_FILE_CONTENTS_FS_READ_FAILED.set(path42, 1);
3562
3617
  lineReaded.close();
3563
3618
  lineReaded.removeAllListeners();
3564
3619
  destroyStreamAndResolve();
@@ -3619,8 +3674,8 @@ function clearLineContext(frame) {
3619
3674
  delete frame.context_line;
3620
3675
  delete frame.post_context;
3621
3676
  }
3622
- function shouldSkipContextLinesForFile(path40) {
3623
- return path40.startsWith("node:") || path40.endsWith(".min.js") || path40.endsWith(".min.cjs") || path40.endsWith(".min.mjs") || path40.startsWith("data:");
3677
+ function shouldSkipContextLinesForFile(path42) {
3678
+ return path42.startsWith("node:") || path42.endsWith(".min.js") || path42.endsWith(".min.cjs") || path42.endsWith(".min.mjs") || path42.startsWith("data:");
3624
3679
  }
3625
3680
  function shouldSkipContextLinesForFrame(frame) {
3626
3681
  if (void 0 !== frame.lineno && frame.lineno > MAX_CONTEXTLINES_LINENO) return true;
@@ -5774,7 +5829,7 @@ function readAnonId() {
5774
5829
  }
5775
5830
  function superProperties() {
5776
5831
  return {
5777
- cliVersion: true ? "2.25.0" : "0.0.0-dev",
5832
+ cliVersion: true ? "2.26.1" : "0.0.0-dev",
5778
5833
  nodeVersion: process.version,
5779
5834
  platform: process.platform,
5780
5835
  arch: process.arch,
@@ -7116,10 +7171,10 @@ function buildForPlatform(platform2) {
7116
7171
  var import_node_crypto4 = require("crypto");
7117
7172
 
7118
7173
  // src/agents/claude/resolver.ts
7119
- function buildClaudeLaunch(extraArgs = [], os26 = createOsStrategy()) {
7120
- const found = os26.findInPath("claude") ?? os26.findInPath("claude-code");
7174
+ function buildClaudeLaunch(extraArgs = [], os27 = createOsStrategy()) {
7175
+ const found = os27.findInPath("claude") ?? os27.findInPath("claude-code");
7121
7176
  if (!found) return null;
7122
- return os26.buildLaunch(found, extraArgs);
7177
+ return os27.buildLaunch(found, extraArgs);
7123
7178
  }
7124
7179
 
7125
7180
  // src/agents/claude/installer.ts
@@ -9802,13 +9857,13 @@ function detectStartupBanner(lines) {
9802
9857
  while (artStart > 0 && BANNER_ART_RE.test(lines[artStart - 1])) artStart--;
9803
9858
  if (metaIdx - artStart < 2) return null;
9804
9859
  const pathLine = (lines[metaIdx + 1] ?? "").trim();
9805
- const path40 = pathLine && !BANNER_ART_RE.test(pathLine) ? pathLine : "";
9860
+ const path42 = pathLine && !BANNER_ART_RE.test(pathLine) ? pathLine : "";
9806
9861
  return {
9807
9862
  title: "",
9808
9863
  subtitle: lines[metaIdx].trim(),
9809
- path: path40,
9864
+ path: path42,
9810
9865
  startIdx: artStart,
9811
- endIdx: metaIdx + (path40 ? 1 : 0)
9866
+ endIdx: metaIdx + (path42 ? 1 : 0)
9812
9867
  };
9813
9868
  }
9814
9869
 
@@ -9818,8 +9873,8 @@ var ClaudeRuntimeStrategy = class {
9818
9873
  meta = getAgent("claude");
9819
9874
  mode = "interactive";
9820
9875
  os;
9821
- constructor(os26) {
9822
- this.os = os26;
9876
+ constructor(os27) {
9877
+ this.os = os27;
9823
9878
  }
9824
9879
  /**
9825
9880
  * Claude Code's react-ink TUI enables bracketed-paste mode at
@@ -10708,8 +10763,8 @@ function codexCredentialLocator() {
10708
10763
  function codexLoginLauncher() {
10709
10764
  return {
10710
10765
  async ensureInstalled() {
10711
- const os26 = createOsStrategy();
10712
- return os26.findInPath("codex") !== null;
10766
+ const os27 = createOsStrategy();
10767
+ return os27.findInPath("codex") !== null;
10713
10768
  },
10714
10769
  launch() {
10715
10770
  return (0, import_node_child_process3.spawn)("codex", ["login"], { stdio: "inherit" });
@@ -10732,8 +10787,8 @@ var CodexRuntimeStrategy = class {
10732
10787
  meta = getAgent("codex");
10733
10788
  mode = "interactive";
10734
10789
  os;
10735
- constructor(os26) {
10736
- this.os = os26;
10790
+ constructor(os27) {
10791
+ this.os = os27;
10737
10792
  }
10738
10793
  async prepareLaunch() {
10739
10794
  let binary = this.os.findInPath("codex");
@@ -10836,12 +10891,12 @@ var CodexRuntimeStrategy = class {
10836
10891
  });
10837
10892
  }
10838
10893
  };
10839
- function resolveNpm(os26) {
10840
- return os26.id === "win32" ? "npm.cmd" : "npm";
10894
+ function resolveNpm(os27) {
10895
+ return os27.id === "win32" ? "npm.cmd" : "npm";
10841
10896
  }
10842
- async function installCodexViaNpm(os26) {
10897
+ async function installCodexViaNpm(os27) {
10843
10898
  return new Promise((resolve5, reject) => {
10844
- const proc = (0, import_node_child_process4.spawn)(resolveNpm(os26), ["install", "-g", "@openai/codex"], {
10899
+ const proc = (0, import_node_child_process4.spawn)(resolveNpm(os27), ["install", "-g", "@openai/codex"], {
10845
10900
  stdio: "inherit"
10846
10901
  });
10847
10902
  proc.on("close", (code) => {
@@ -10858,16 +10913,16 @@ async function installCodexViaNpm(os26) {
10858
10913
  });
10859
10914
  });
10860
10915
  }
10861
- function augmentNpmGlobalBin(os26) {
10916
+ function augmentNpmGlobalBin(os27) {
10862
10917
  try {
10863
- const result = (0, import_node_child_process4.spawnSync)(resolveNpm(os26), ["prefix", "-g"], {
10918
+ const result = (0, import_node_child_process4.spawnSync)(resolveNpm(os27), ["prefix", "-g"], {
10864
10919
  stdio: ["ignore", "pipe", "ignore"]
10865
10920
  });
10866
10921
  if (result.status !== 0) return;
10867
10922
  const prefix = result.stdout.toString().trim();
10868
10923
  if (!prefix) return;
10869
- const binDir = os26.id === "win32" ? prefix : path17.join(prefix, "bin");
10870
- os26.augmentPath([binDir]);
10924
+ const binDir = os27.id === "win32" ? prefix : path17.join(prefix, "bin");
10925
+ os27.augmentPath([binDir]);
10871
10926
  } catch {
10872
10927
  }
10873
10928
  }
@@ -10951,9 +11006,9 @@ var import_node_child_process7 = require("child_process");
10951
11006
  // src/agents/coderabbit/installer.ts
10952
11007
  var import_node_child_process5 = require("child_process");
10953
11008
  var INSTALL_URL = "https://cli.coderabbit.ai/install.sh";
10954
- async function ensureCoderabbitInstalled(os26) {
10955
- if (os26.findInPath("coderabbit")) return true;
10956
- if (os26.id === "win32") {
11009
+ async function ensureCoderabbitInstalled(os27) {
11010
+ if (os27.findInPath("coderabbit")) return true;
11011
+ if (os27.id === "win32") {
10957
11012
  console.error(
10958
11013
  "\n \u2717 CodeRabbit on Windows requires WSL.\n Install the CLI inside your WSL distribution\n (curl -fsSL https://cli.coderabbit.ai/install.sh | sh)\n then re-run `codeam link coderabbit` from WSL.\n"
10959
11014
  );
@@ -10968,8 +11023,8 @@ async function ensureCoderabbitInstalled(os26) {
10968
11023
  proc.on("error", () => resolve5(false));
10969
11024
  });
10970
11025
  if (!ok) return false;
10971
- os26.augmentPath([`${os26.homeDir()}/.local/bin`, "/opt/homebrew/bin"]);
10972
- return os26.findInPath("coderabbit") !== null;
11026
+ os27.augmentPath([`${os27.homeDir()}/.local/bin`, "/opt/homebrew/bin"]);
11027
+ return os27.findInPath("coderabbit") !== null;
10973
11028
  }
10974
11029
 
10975
11030
  // src/agents/coderabbit/link.ts
@@ -10996,10 +11051,10 @@ function coderabbitCredentialLocator() {
10996
11051
  extract: extractLocalCoderabbitToken
10997
11052
  };
10998
11053
  }
10999
- function coderabbitLoginLauncher(os26) {
11054
+ function coderabbitLoginLauncher(os27) {
11000
11055
  return {
11001
11056
  async ensureInstalled() {
11002
- return ensureCoderabbitInstalled(os26);
11057
+ return ensureCoderabbitInstalled(os27);
11003
11058
  },
11004
11059
  launch() {
11005
11060
  return (0, import_node_child_process6.spawn)("coderabbit", ["login"], { stdio: "inherit" });
@@ -11022,11 +11077,11 @@ function parseReview(stdout) {
11022
11077
  for (const line of lines) {
11023
11078
  const m = line.match(HUNK_LINE_RE);
11024
11079
  if (!m) continue;
11025
- const [, path40, lineNo, sevToken, message] = m;
11026
- if (!path40 || !lineNo || !message) continue;
11080
+ const [, path42, lineNo, sevToken, message] = m;
11081
+ if (!path42 || !lineNo || !message) continue;
11027
11082
  const cleanedMessage = message.trim().replace(/^[*-]\s+/, "");
11028
11083
  hunks.push({
11029
- path: path40.trim(),
11084
+ path: path42.trim(),
11030
11085
  line: Number(lineNo),
11031
11086
  severity: sevToken ? SEVERITY_MAP[sevToken.toLowerCase()] : void 0,
11032
11087
  message: cleanedMessage
@@ -11045,8 +11100,8 @@ var CoderabbitRuntimeStrategy = class {
11045
11100
  meta = getAgent("coderabbit");
11046
11101
  mode = "batch";
11047
11102
  os;
11048
- constructor(os26) {
11049
- this.os = os26;
11103
+ constructor(os27) {
11104
+ this.os = os27;
11050
11105
  }
11051
11106
  getDefaultArgs() {
11052
11107
  return ["review"];
@@ -11161,10 +11216,10 @@ function cursorCredentialLocator() {
11161
11216
  extract: extractLocalCursorToken
11162
11217
  };
11163
11218
  }
11164
- function cursorLoginLauncher(os26) {
11219
+ function cursorLoginLauncher(os27) {
11165
11220
  return {
11166
11221
  async ensureInstalled() {
11167
- if (os26.findInPath("cursor-agent")) return true;
11222
+ if (os27.findInPath("cursor-agent")) return true;
11168
11223
  console.error(
11169
11224
  "\n \u2717 cursor-agent binary not on PATH.\n Install Cursor (https://cursor.com/) and ensure the CLI\n plugin is enabled, then re-run `codeam link cursor`.\n"
11170
11225
  );
@@ -11226,8 +11281,8 @@ var CursorRuntimeStrategy = class {
11226
11281
  meta = getAgent("cursor");
11227
11282
  mode = "interactive";
11228
11283
  os;
11229
- constructor(os26) {
11230
- this.os = os26;
11284
+ constructor(os27) {
11285
+ this.os = os27;
11231
11286
  }
11232
11287
  async prepareLaunch() {
11233
11288
  const binary = this.os.findInPath("cursor-agent");
@@ -11347,10 +11402,10 @@ function aiderCredentialLocator() {
11347
11402
  extract: extractLocalAiderToken
11348
11403
  };
11349
11404
  }
11350
- function aiderLoginLauncher(os26) {
11405
+ function aiderLoginLauncher(os27) {
11351
11406
  return {
11352
11407
  async ensureInstalled() {
11353
- if (os26.findInPath("aider")) return true;
11408
+ if (os27.findInPath("aider")) return true;
11354
11409
  console.error(
11355
11410
  "\n \u2717 aider binary not on PATH.\n Install Aider:\n pip install aider-chat\n then re-run `codeam link aider`.\n"
11356
11411
  );
@@ -11360,7 +11415,7 @@ function aiderLoginLauncher(os26) {
11360
11415
  console.error(
11361
11416
  "\n Aider has no interactive login flow.\n Set ANTHROPIC_API_KEY or OPENAI_API_KEY in your shell,\n or re-run `codeam link aider --api-key=<your-key>`.\n"
11362
11417
  );
11363
- return (0, import_node_child_process9.spawn)(os26.id === "win32" ? "cmd.exe" : "sh", os26.id === "win32" ? ["/c", "exit", "0"] : ["-c", "exit 0"], {
11418
+ return (0, import_node_child_process9.spawn)(os27.id === "win32" ? "cmd.exe" : "sh", os27.id === "win32" ? ["/c", "exit", "0"] : ["-c", "exit 0"], {
11364
11419
  stdio: "ignore"
11365
11420
  });
11366
11421
  }
@@ -11432,8 +11487,8 @@ var AiderRuntimeStrategy = class {
11432
11487
  meta = getAgent("aider");
11433
11488
  mode = "interactive";
11434
11489
  os;
11435
- constructor(os26) {
11436
- this.os = os26;
11490
+ constructor(os27) {
11491
+ this.os = os27;
11437
11492
  }
11438
11493
  async prepareLaunch() {
11439
11494
  const binary = this.os.findInPath("aider");
@@ -11502,17 +11557,17 @@ var AiderRuntimeStrategy = class {
11502
11557
 
11503
11558
  // src/agents/registry.ts
11504
11559
  var runtimeBuilders = {
11505
- claude: (os26) => new ClaudeRuntimeStrategy(os26),
11506
- codex: (os26) => new CodexRuntimeStrategy(os26),
11507
- coderabbit: (os26) => new CoderabbitRuntimeStrategy(os26),
11508
- cursor: (os26) => new CursorRuntimeStrategy(os26),
11509
- aider: (os26) => new AiderRuntimeStrategy(os26)
11560
+ claude: (os27) => new ClaudeRuntimeStrategy(os27),
11561
+ codex: (os27) => new CodexRuntimeStrategy(os27),
11562
+ coderabbit: (os27) => new CoderabbitRuntimeStrategy(os27),
11563
+ cursor: (os27) => new CursorRuntimeStrategy(os27),
11564
+ aider: (os27) => new AiderRuntimeStrategy(os27)
11510
11565
  };
11511
11566
  var deployBuilders = {
11512
11567
  claude: () => new ClaudeDeployStrategy(),
11513
11568
  codex: () => new CodexDeployStrategy()
11514
11569
  };
11515
- function createAgentStrategy(agent, os26 = createOsStrategy()) {
11570
+ function createAgentStrategy(agent, os27 = createOsStrategy()) {
11516
11571
  if (!AGENT_REGISTRY[agent]?.enabled) {
11517
11572
  throw new Error(
11518
11573
  `Agent "${agent}" is not supported in this codeam-cli version. Upgrade with 'npm i -g codeam-cli@latest'.`
@@ -11522,10 +11577,10 @@ function createAgentStrategy(agent, os26 = createOsStrategy()) {
11522
11577
  if (!build) {
11523
11578
  throw new Error(`No runtime strategy registered for agent "${agent}"`);
11524
11579
  }
11525
- return build(os26);
11580
+ return build(os27);
11526
11581
  }
11527
- function createInteractiveAgentStrategy(agent, os26 = createOsStrategy()) {
11528
- const s = createAgentStrategy(agent, os26);
11582
+ function createInteractiveAgentStrategy(agent, os27 = createOsStrategy()) {
11583
+ const s = createAgentStrategy(agent, os27);
11529
11584
  if (s.mode !== "interactive") {
11530
11585
  throw new Error(
11531
11586
  `Agent "${agent}" is a batch agent; use createAgentStrategy + .runOneShot for one-shot reviews.`
@@ -13622,7 +13677,7 @@ function defaultRunGit(cwd, args2) {
13622
13677
  });
13623
13678
  }
13624
13679
  async function discoverRepos(workingDir, maxDepth = 4) {
13625
- const fs31 = await import("fs/promises");
13680
+ const fs33 = await import("fs/promises");
13626
13681
  const out2 = [];
13627
13682
  await walk(workingDir, 0);
13628
13683
  return out2;
@@ -13630,7 +13685,7 @@ async function discoverRepos(workingDir, maxDepth = 4) {
13630
13685
  if (depth > maxDepth) return;
13631
13686
  let entries = [];
13632
13687
  try {
13633
- const dirents = await fs31.readdir(dir, { withFileTypes: true });
13688
+ const dirents = await fs33.readdir(dir, { withFileTypes: true });
13634
13689
  entries = dirents.filter((d3) => !d3.name.startsWith(".") || d3.name === ".git").map((d3) => ({ name: d3.name, isDirectory: d3.isDirectory() }));
13635
13690
  } catch {
13636
13691
  return;
@@ -14532,11 +14587,11 @@ function buildKeepAlive(ctx) {
14532
14587
  }
14533
14588
 
14534
14589
  // src/commands/start/handlers.ts
14535
- var fs27 = __toESM(require("fs"));
14536
- var os23 = __toESM(require("os"));
14537
- var path33 = __toESM(require("path"));
14590
+ var fs29 = __toESM(require("fs"));
14591
+ var os24 = __toESM(require("os"));
14592
+ var path35 = __toESM(require("path"));
14538
14593
  var import_crypto5 = require("crypto");
14539
- var import_child_process14 = require("child_process");
14594
+ var import_child_process15 = require("child_process");
14540
14595
 
14541
14596
  // src/lib/payload.ts
14542
14597
  var import_zod2 = require("zod");
@@ -14628,6 +14683,28 @@ var startCommandSchema = import_zod2.z.object({
14628
14683
  added: import_zod2.z.number().int(),
14629
14684
  removed: import_zod2.z.number().int(),
14630
14685
  complexityShift: import_zod2.z.number().int()
14686
+ }).optional(),
14687
+ // `preview_start` carries the agent-detected `PreviewDetection`
14688
+ // shape from the mobile / web confirmation sheet. Mirrors
14689
+ // `@codeagent/shared`'s `PreviewDetection` byte-for-byte. Kept
14690
+ // loose (`unknown` for env / setup_commands) so a CLI version
14691
+ // running against a newer backend that adds optional fields still
14692
+ // accepts the payload; the handler validates the shape it needs
14693
+ // before spawning.
14694
+ detection: import_zod2.z.object({
14695
+ framework: import_zod2.z.string().max(64),
14696
+ command: import_zod2.z.string().min(1).max(256),
14697
+ args: import_zod2.z.array(import_zod2.z.string().max(1024)).max(64),
14698
+ port: import_zod2.z.number().int().min(1).max(65535),
14699
+ ready_pattern: import_zod2.z.string().min(1).max(4096),
14700
+ env: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string().max(8192)).optional(),
14701
+ setup_commands: import_zod2.z.array(
14702
+ import_zod2.z.object({
14703
+ cmd: import_zod2.z.string().min(1).max(256),
14704
+ args: import_zod2.z.array(import_zod2.z.string().max(1024)).max(64)
14705
+ })
14706
+ ).max(32).optional(),
14707
+ notes: import_zod2.z.string().max(4096).nullable().optional()
14631
14708
  }).optional()
14632
14709
  });
14633
14710
  function parsePayload2(schema, raw) {
@@ -15811,12 +15888,212 @@ async function linkDryRunPreflight(ctx) {
15811
15888
  process.exit(1);
15812
15889
  }
15813
15890
 
15891
+ // src/services/preview/cloudflared.ts
15892
+ var import_fs = require("fs");
15893
+ var import_promises = __toESM(require("fs/promises"));
15894
+ var import_os7 = __toESM(require("os"));
15895
+ var import_path4 = __toESM(require("path"));
15896
+ var import_promises2 = require("stream/promises");
15897
+ var import_which = __toESM(require("which"));
15898
+ var CACHED_BINARY = import_path4.default.join(import_os7.default.homedir(), ".codeam", "bin", "cloudflared");
15899
+ async function resolveCloudflared(opts = {}) {
15900
+ try {
15901
+ return await (0, import_which.default)("cloudflared");
15902
+ } catch {
15903
+ }
15904
+ try {
15905
+ await import_promises.default.access(CACHED_BINARY);
15906
+ return CACHED_BINARY;
15907
+ } catch {
15908
+ }
15909
+ if (opts.skipDownload) {
15910
+ throw new Error(
15911
+ "cloudflared not installed. Install via `brew install cloudflared` (macOS) or download from https://github.com/cloudflare/cloudflared/releases."
15912
+ );
15913
+ }
15914
+ await downloadCloudflared(CACHED_BINARY);
15915
+ return CACHED_BINARY;
15916
+ }
15917
+ async function downloadCloudflared(target) {
15918
+ const url = downloadUrlForPlatform();
15919
+ await import_promises.default.mkdir(import_path4.default.dirname(target), { recursive: true });
15920
+ const response = await fetch(url);
15921
+ if (!response.ok || !response.body) {
15922
+ throw new Error(
15923
+ `Failed to download cloudflared from ${url}: HTTP ${response.status}. Install manually from https://github.com/cloudflare/cloudflared/releases.`
15924
+ );
15925
+ }
15926
+ await (0, import_promises2.pipeline)(
15927
+ response.body,
15928
+ (0, import_fs.createWriteStream)(target, { mode: 493 })
15929
+ );
15930
+ }
15931
+ function downloadUrlForPlatform() {
15932
+ const platform2 = process.platform;
15933
+ const arch = process.arch;
15934
+ const base = "https://github.com/cloudflare/cloudflared/releases/latest/download";
15935
+ if (platform2 === "darwin" && arch === "arm64") return `${base}/cloudflared-darwin-arm64.tgz`;
15936
+ if (platform2 === "darwin" && arch === "x64") return `${base}/cloudflared-darwin-amd64.tgz`;
15937
+ if (platform2 === "linux" && arch === "x64") return `${base}/cloudflared-linux-amd64`;
15938
+ if (platform2 === "linux" && arch === "arm64") return `${base}/cloudflared-linux-arm64`;
15939
+ if (platform2 === "win32") return `${base}/cloudflared-windows-amd64.exe`;
15940
+ throw new Error(
15941
+ `cloudflared auto-install not supported on ${platform2}/${arch}. Install manually from https://github.com/cloudflare/cloudflared/releases.`
15942
+ );
15943
+ }
15944
+
15945
+ // src/services/preview/codespace.ts
15946
+ var import_child_process14 = require("child_process");
15947
+ var import_util3 = require("util");
15948
+ var execFileP4 = (0, import_util3.promisify)(import_child_process14.execFile);
15949
+ function isCodespaceSession(env = process.env) {
15950
+ return Boolean(env.CODESPACE_NAME);
15951
+ }
15952
+ function buildCodespaceUrl(codespaceName, port) {
15953
+ return `https://${codespaceName}-${port}.app.github.dev`;
15954
+ }
15955
+ async function setPortPublic(codespaceName, port) {
15956
+ await execFileP4("gh", [
15957
+ "codespace",
15958
+ "ports",
15959
+ "visibility",
15960
+ `${port}:public`,
15961
+ "-c",
15962
+ codespaceName
15963
+ ]);
15964
+ }
15965
+ async function waitForCodespacePortReady(url, timeoutMs = 15e3) {
15966
+ const start2 = Date.now();
15967
+ while (Date.now() - start2 < timeoutMs) {
15968
+ try {
15969
+ const res = await fetch(url, { method: "HEAD" });
15970
+ if (res.status < 500) return;
15971
+ } catch {
15972
+ }
15973
+ await new Promise((r) => setTimeout(r, 500));
15974
+ }
15975
+ throw new Error(`Codespace forwarded URL ${url} not reachable after ${timeoutMs}ms.`);
15976
+ }
15977
+
15978
+ // src/services/preview/config-file.ts
15979
+ var import_promises3 = __toESM(require("fs/promises"));
15980
+ var import_path5 = __toESM(require("path"));
15981
+ var CONFIG_DIR = ".codeam";
15982
+ var CONFIG_FILE = "preview.json";
15983
+ function configPath(cwd) {
15984
+ return import_path5.default.join(cwd, CONFIG_DIR, CONFIG_FILE);
15985
+ }
15986
+ var REQUIRED_FIELDS = [
15987
+ "framework",
15988
+ "command",
15989
+ "args",
15990
+ "port",
15991
+ "ready_pattern"
15992
+ ];
15993
+ async function readPreviewConfig(cwd) {
15994
+ let raw;
15995
+ try {
15996
+ raw = await import_promises3.default.readFile(configPath(cwd), "utf-8");
15997
+ } catch {
15998
+ return null;
15999
+ }
16000
+ let parsed;
16001
+ try {
16002
+ parsed = JSON.parse(raw);
16003
+ } catch {
16004
+ return null;
16005
+ }
16006
+ if (typeof parsed !== "object" || parsed === null) return null;
16007
+ const obj = parsed;
16008
+ for (const field of REQUIRED_FIELDS) {
16009
+ if (!(field in obj)) return null;
16010
+ }
16011
+ return obj;
16012
+ }
16013
+ async function writePreviewConfig(cwd, detection) {
16014
+ const filePath = configPath(cwd);
16015
+ await import_promises3.default.mkdir(import_path5.default.dirname(filePath), { recursive: true });
16016
+ await import_promises3.default.writeFile(filePath, JSON.stringify(detection, null, 2) + "\n", "utf-8");
16017
+ }
16018
+
16019
+ // src/services/preview/parser.ts
16020
+ var REQUIRED_FIELDS2 = [
16021
+ "framework",
16022
+ "command",
16023
+ "args",
16024
+ "port",
16025
+ "ready_pattern"
16026
+ ];
16027
+ function safeParseDetection(raw) {
16028
+ if (!raw) return null;
16029
+ const stripped = raw.replace(/^```(?:json)?\s*/m, "").replace(/\s*```\s*$/m, "").trim();
16030
+ let parsed;
16031
+ try {
16032
+ parsed = JSON.parse(stripped);
16033
+ } catch {
16034
+ return null;
16035
+ }
16036
+ if (typeof parsed !== "object" || parsed === null) return null;
16037
+ const obj = parsed;
16038
+ for (const field of REQUIRED_FIELDS2) {
16039
+ if (!(field in obj)) return null;
16040
+ }
16041
+ return obj;
16042
+ }
16043
+ var CLOUDFLARED_URL_RE = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i;
16044
+ function parseCloudflaredUrl(stderr) {
16045
+ const match = stderr.match(CLOUDFLARED_URL_RE);
16046
+ return match ? match[0] : null;
16047
+ }
16048
+ var EXPO_URL_RE = /exp:\/\/[^\s]+\.exp\.host/;
16049
+ function parseExpoUrl(stdout) {
16050
+ const match = stdout.match(EXPO_URL_RE);
16051
+ return match ? match[0] : null;
16052
+ }
16053
+
16054
+ // src/services/preview/index.ts
16055
+ var activePreviews = /* @__PURE__ */ new Map();
16056
+ function registerPreview(sessionId, preview) {
16057
+ activePreviews.set(sessionId, preview);
16058
+ }
16059
+ async function killPreview(sessionId) {
16060
+ const preview = activePreviews.get(sessionId);
16061
+ if (!preview) return;
16062
+ if (preview.tunnel) {
16063
+ try {
16064
+ preview.tunnel.kill("SIGTERM");
16065
+ } catch {
16066
+ }
16067
+ }
16068
+ await new Promise((r) => setTimeout(r, 100));
16069
+ try {
16070
+ preview.devServer.kill("SIGTERM");
16071
+ } catch {
16072
+ }
16073
+ const sigkillTimer = setTimeout(() => {
16074
+ try {
16075
+ preview.devServer.kill("SIGKILL");
16076
+ } catch {
16077
+ }
16078
+ try {
16079
+ preview.tunnel?.kill("SIGKILL");
16080
+ } catch {
16081
+ }
16082
+ }, 250);
16083
+ sigkillTimer.unref?.();
16084
+ activePreviews.delete(sessionId);
16085
+ }
16086
+ async function killAllPreviews() {
16087
+ const ids = Array.from(activePreviews.keys());
16088
+ await Promise.all(ids.map((id) => killPreview(id)));
16089
+ }
16090
+
15814
16091
  // src/commands/start/handlers.ts
15815
16092
  var pendingAttachmentFiles = /* @__PURE__ */ new Set();
15816
16093
  function cleanupAttachmentTempFiles() {
15817
16094
  for (const p2 of pendingAttachmentFiles) {
15818
16095
  try {
15819
- fs27.unlinkSync(p2);
16096
+ fs29.unlinkSync(p2);
15820
16097
  } catch {
15821
16098
  }
15822
16099
  }
@@ -15825,8 +16102,8 @@ function cleanupAttachmentTempFiles() {
15825
16102
  function saveFilesTemp(files) {
15826
16103
  return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
15827
16104
  const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
15828
- const tmpPath = path33.join(os23.tmpdir(), `codeam-${(0, import_crypto5.randomUUID)()}-${safeName}`);
15829
- fs27.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
16105
+ const tmpPath = path35.join(os24.tmpdir(), `codeam-${(0, import_crypto5.randomUUID)()}-${safeName}`);
16106
+ fs29.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
15830
16107
  pendingAttachmentFiles.add(tmpPath);
15831
16108
  return tmpPath;
15832
16109
  });
@@ -15846,7 +16123,7 @@ var startTask = (ctx, _cmd, parsed) => {
15846
16123
  setTimeout(() => {
15847
16124
  for (const p2 of paths) {
15848
16125
  try {
15849
- fs27.unlinkSync(p2);
16126
+ fs29.unlinkSync(p2);
15850
16127
  } catch {
15851
16128
  }
15852
16129
  pendingAttachmentFiles.delete(p2);
@@ -15971,7 +16248,7 @@ var sessionTerminated = async (ctx, cmd) => {
15971
16248
  } catch {
15972
16249
  }
15973
16250
  try {
15974
- const proc = (0, import_child_process14.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
16251
+ const proc = (0, import_child_process15.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
15975
16252
  detached: true,
15976
16253
  stdio: "ignore"
15977
16254
  });
@@ -15993,7 +16270,7 @@ var shutdownSession = async (ctx, cmd) => {
15993
16270
  }
15994
16271
  if (ctx.keepAliveCtx.inCodespace && ctx.keepAliveCtx.codespaceName) {
15995
16272
  try {
15996
- const stopProc = (0, import_child_process14.spawn)(
16273
+ const stopProc = (0, import_child_process15.spawn)(
15997
16274
  "bash",
15998
16275
  ["-lc", `sleep 1; gh codespace stop -c ${JSON.stringify(ctx.keepAliveCtx.codespaceName)} >/dev/null 2>&1 || true`],
15999
16276
  { detached: true, stdio: "ignore" }
@@ -16003,7 +16280,7 @@ var shutdownSession = async (ctx, cmd) => {
16003
16280
  }
16004
16281
  }
16005
16282
  try {
16006
- const proc = (0, import_child_process14.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
16283
+ const proc = (0, import_child_process15.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
16007
16284
  detached: true,
16008
16285
  stdio: "ignore"
16009
16286
  });
@@ -16297,6 +16574,339 @@ function parseInsightText(text) {
16297
16574
  securityNote: securityMatch?.[1]?.trim() || void 0
16298
16575
  };
16299
16576
  }
16577
+ var requestPreviewDetectH = (ctx) => {
16578
+ if (!ctx.pluginAuthToken) {
16579
+ log.info("preview", "no pluginAuthToken \u2014 skipping detect");
16580
+ return;
16581
+ }
16582
+ if (typeof ctx.runtime.generateOneShot !== "function") {
16583
+ log.info("preview", `runtime ${ctx.runtime.id} has no generateOneShot \u2014 emitting unsupported`);
16584
+ void postPreviewEvent({
16585
+ sessionId: ctx.sessionId,
16586
+ pluginId: ctx.pluginId,
16587
+ pluginAuthToken: ctx.pluginAuthToken,
16588
+ type: "preview_error",
16589
+ payload: {
16590
+ stage: "detection",
16591
+ message: `Preview detection isn't available on ${ctx.runtime.id} sessions yet \u2014 link a Claude or Codex agent.`
16592
+ }
16593
+ });
16594
+ return;
16595
+ }
16596
+ const pluginAuthToken = ctx.pluginAuthToken;
16597
+ void (async () => {
16598
+ const fromFile = await readPreviewConfig(process.cwd());
16599
+ if (fromFile) {
16600
+ log.info("preview", `detect: using .codeam/preview.json (${fromFile.framework})`);
16601
+ void postPreviewEvent({
16602
+ sessionId: ctx.sessionId,
16603
+ pluginId: ctx.pluginId,
16604
+ pluginAuthToken,
16605
+ type: "preview_detection_ready",
16606
+ payload: { detection: fromFile }
16607
+ });
16608
+ return;
16609
+ }
16610
+ void postPreviewEvent({
16611
+ sessionId: ctx.sessionId,
16612
+ pluginId: ctx.pluginId,
16613
+ pluginAuthToken,
16614
+ type: "preview_detection_pending"
16615
+ });
16616
+ log.info("preview", "detect: invoking generateOneShot");
16617
+ const startedAt = Date.now();
16618
+ const raw = await ctx.runtime.generateOneShot(PREVIEW_DETECT_PROMPT).catch((err) => {
16619
+ log.info("preview", `detect: generateOneShot threw: ${String(err)}`);
16620
+ return null;
16621
+ });
16622
+ const tookMs = Date.now() - startedAt;
16623
+ const detection = safeParseDetection(raw);
16624
+ if (!detection) {
16625
+ log.info("preview", `detect: invalid agent output after ${tookMs}ms`);
16626
+ void postPreviewEvent({
16627
+ sessionId: ctx.sessionId,
16628
+ pluginId: ctx.pluginId,
16629
+ pluginAuthToken,
16630
+ type: "preview_error",
16631
+ payload: {
16632
+ stage: "detection",
16633
+ message: "Agent returned invalid JSON. Try again, or add a .codeam/preview.json override."
16634
+ }
16635
+ });
16636
+ return;
16637
+ }
16638
+ if (detection.framework === "unsupported") {
16639
+ log.info("preview", "detect: framework=unsupported");
16640
+ void postPreviewEvent({
16641
+ sessionId: ctx.sessionId,
16642
+ pluginId: ctx.pluginId,
16643
+ pluginAuthToken,
16644
+ type: "preview_error",
16645
+ payload: {
16646
+ stage: "unsupported",
16647
+ message: detection.notes ?? "No dev server applies to this project."
16648
+ }
16649
+ });
16650
+ return;
16651
+ }
16652
+ log.info("preview", `detect: ${detection.framework} on :${detection.port} (took ${tookMs}ms)`);
16653
+ void postPreviewEvent({
16654
+ sessionId: ctx.sessionId,
16655
+ pluginId: ctx.pluginId,
16656
+ pluginAuthToken,
16657
+ type: "preview_detection_ready",
16658
+ payload: { detection }
16659
+ });
16660
+ })();
16661
+ };
16662
+ var previewStartH = (ctx, _cmd, parsed) => {
16663
+ if (!ctx.pluginAuthToken) {
16664
+ log.info("preview", "no pluginAuthToken \u2014 skipping start");
16665
+ return;
16666
+ }
16667
+ const detection = parsed.detection;
16668
+ if (!detection) {
16669
+ log.info("preview", "start: no detection in payload");
16670
+ return;
16671
+ }
16672
+ const pluginAuthToken = ctx.pluginAuthToken;
16673
+ void (async () => {
16674
+ void postPreviewEvent({
16675
+ sessionId: ctx.sessionId,
16676
+ pluginId: ctx.pluginId,
16677
+ pluginAuthToken,
16678
+ type: "preview_starting",
16679
+ payload: { framework: detection.framework, port: detection.port }
16680
+ });
16681
+ for (const setup of detection.setup_commands ?? []) {
16682
+ const exitCode = await runOnce(setup.cmd, setup.args, process.cwd(), detection.env);
16683
+ if (exitCode !== 0) {
16684
+ void postPreviewEvent({
16685
+ sessionId: ctx.sessionId,
16686
+ pluginId: ctx.pluginId,
16687
+ pluginAuthToken,
16688
+ type: "preview_error",
16689
+ payload: {
16690
+ stage: "spawn",
16691
+ message: `Setup failed (${setup.cmd} ${setup.args.join(" ")}, exit ${exitCode}).`
16692
+ }
16693
+ });
16694
+ return;
16695
+ }
16696
+ }
16697
+ const devServer = (0, import_child_process15.spawn)(detection.command, detection.args, {
16698
+ cwd: process.cwd(),
16699
+ env: { ...process.env, ...detection.env ?? {} },
16700
+ stdio: ["ignore", "pipe", "pipe"]
16701
+ });
16702
+ let readyMatched = false;
16703
+ let expoUrl = null;
16704
+ const readyRe = new RegExp(detection.ready_pattern);
16705
+ const onChunk = (chunk) => {
16706
+ const s = chunk.toString();
16707
+ if (!readyMatched && readyRe.test(s)) readyMatched = true;
16708
+ if (!expoUrl && detection.framework === "Expo") expoUrl = parseExpoUrl(s);
16709
+ };
16710
+ devServer.stdout.on("data", onChunk);
16711
+ devServer.stderr.on("data", onChunk);
16712
+ const readyDeadline = Date.now() + 12e4;
16713
+ while (!readyMatched && Date.now() < readyDeadline) {
16714
+ if (devServer.exitCode !== null) {
16715
+ void postPreviewEvent({
16716
+ sessionId: ctx.sessionId,
16717
+ pluginId: ctx.pluginId,
16718
+ pluginAuthToken,
16719
+ type: "preview_error",
16720
+ payload: {
16721
+ stage: "spawn",
16722
+ message: `Dev server exited (code ${devServer.exitCode}).`
16723
+ }
16724
+ });
16725
+ return;
16726
+ }
16727
+ await new Promise((r) => setTimeout(r, 250));
16728
+ }
16729
+ if (!readyMatched) {
16730
+ try {
16731
+ devServer.kill("SIGTERM");
16732
+ } catch {
16733
+ }
16734
+ void postPreviewEvent({
16735
+ sessionId: ctx.sessionId,
16736
+ pluginId: ctx.pluginId,
16737
+ pluginAuthToken,
16738
+ type: "preview_error",
16739
+ payload: { stage: "ready_timeout", message: "Server didn't signal ready in 120s." }
16740
+ });
16741
+ return;
16742
+ }
16743
+ let tunnel = null;
16744
+ let url;
16745
+ if (detection.framework === "Expo") {
16746
+ if (!expoUrl) {
16747
+ const expoDeadline = Date.now() + 15e3;
16748
+ while (!expoUrl && Date.now() < expoDeadline) {
16749
+ await new Promise((r) => setTimeout(r, 250));
16750
+ }
16751
+ }
16752
+ if (!expoUrl) {
16753
+ try {
16754
+ devServer.kill("SIGTERM");
16755
+ } catch {
16756
+ }
16757
+ void postPreviewEvent({
16758
+ sessionId: ctx.sessionId,
16759
+ pluginId: ctx.pluginId,
16760
+ pluginAuthToken,
16761
+ type: "preview_error",
16762
+ payload: { stage: "tunnel", message: "Expo did not report a tunnel URL." }
16763
+ });
16764
+ return;
16765
+ }
16766
+ url = expoUrl;
16767
+ } else if (isCodespaceSession()) {
16768
+ const codespaceName = process.env.CODESPACE_NAME;
16769
+ try {
16770
+ await setPortPublic(codespaceName, detection.port);
16771
+ } catch (e) {
16772
+ try {
16773
+ devServer.kill("SIGTERM");
16774
+ } catch {
16775
+ }
16776
+ void postPreviewEvent({
16777
+ sessionId: ctx.sessionId,
16778
+ pluginId: ctx.pluginId,
16779
+ pluginAuthToken,
16780
+ type: "preview_error",
16781
+ payload: { stage: "tunnel", message: `Failed to flip port public: ${e.message}` }
16782
+ });
16783
+ return;
16784
+ }
16785
+ url = buildCodespaceUrl(codespaceName, detection.port);
16786
+ try {
16787
+ await waitForCodespacePortReady(url, 15e3);
16788
+ } catch (e) {
16789
+ try {
16790
+ devServer.kill("SIGTERM");
16791
+ } catch {
16792
+ }
16793
+ void postPreviewEvent({
16794
+ sessionId: ctx.sessionId,
16795
+ pluginId: ctx.pluginId,
16796
+ pluginAuthToken,
16797
+ type: "preview_error",
16798
+ payload: { stage: "tunnel", message: e.message }
16799
+ });
16800
+ return;
16801
+ }
16802
+ } else {
16803
+ let bin;
16804
+ try {
16805
+ bin = await resolveCloudflared();
16806
+ } catch (e) {
16807
+ try {
16808
+ devServer.kill("SIGTERM");
16809
+ } catch {
16810
+ }
16811
+ void postPreviewEvent({
16812
+ sessionId: ctx.sessionId,
16813
+ pluginId: ctx.pluginId,
16814
+ pluginAuthToken,
16815
+ type: "preview_error",
16816
+ payload: { stage: "tunnel", message: e.message }
16817
+ });
16818
+ return;
16819
+ }
16820
+ tunnel = (0, import_child_process15.spawn)(bin, ["tunnel", "--url", `http://localhost:${detection.port}`], {
16821
+ stdio: ["ignore", "pipe", "pipe"]
16822
+ });
16823
+ let parsedUrl = null;
16824
+ const onTunnelChunk = (chunk) => {
16825
+ const s = chunk.toString();
16826
+ if (!parsedUrl) parsedUrl = parseCloudflaredUrl(s);
16827
+ };
16828
+ tunnel.stderr.on("data", onTunnelChunk);
16829
+ tunnel.stdout.on("data", onTunnelChunk);
16830
+ const tunnelDeadline = Date.now() + 15e3;
16831
+ while (!parsedUrl && Date.now() < tunnelDeadline) {
16832
+ await new Promise((r) => setTimeout(r, 250));
16833
+ }
16834
+ if (!parsedUrl) {
16835
+ try {
16836
+ tunnel.kill("SIGTERM");
16837
+ } catch {
16838
+ }
16839
+ try {
16840
+ devServer.kill("SIGTERM");
16841
+ } catch {
16842
+ }
16843
+ void postPreviewEvent({
16844
+ sessionId: ctx.sessionId,
16845
+ pluginId: ctx.pluginId,
16846
+ pluginAuthToken,
16847
+ type: "preview_error",
16848
+ payload: { stage: "tunnel", message: "cloudflared did not emit a URL within 15s." }
16849
+ });
16850
+ return;
16851
+ }
16852
+ url = parsedUrl;
16853
+ }
16854
+ registerPreview(ctx.sessionId, {
16855
+ sessionId: ctx.sessionId,
16856
+ devServer,
16857
+ tunnel,
16858
+ url,
16859
+ framework: detection.framework
16860
+ });
16861
+ log.info("preview", `ready: ${detection.framework} at ${url}`);
16862
+ void postPreviewEvent({
16863
+ sessionId: ctx.sessionId,
16864
+ pluginId: ctx.pluginId,
16865
+ pluginAuthToken,
16866
+ type: "preview_ready",
16867
+ payload: { url, framework: detection.framework, port: detection.port }
16868
+ });
16869
+ })();
16870
+ };
16871
+ var previewStopH = (ctx) => {
16872
+ if (!ctx.pluginAuthToken) {
16873
+ log.info("preview", "no pluginAuthToken \u2014 skipping stop");
16874
+ return;
16875
+ }
16876
+ const pluginAuthToken = ctx.pluginAuthToken;
16877
+ void (async () => {
16878
+ await killPreview(ctx.sessionId);
16879
+ log.info("preview", `stopped session=${ctx.sessionId}`);
16880
+ void postPreviewEvent({
16881
+ sessionId: ctx.sessionId,
16882
+ pluginId: ctx.pluginId,
16883
+ pluginAuthToken,
16884
+ type: "preview_stopped",
16885
+ payload: { reason: "user" }
16886
+ });
16887
+ })();
16888
+ };
16889
+ function runOnce(cmd, args2, cwd, env) {
16890
+ return new Promise((resolve5) => {
16891
+ const child = (0, import_child_process15.spawn)(cmd, args2, {
16892
+ cwd,
16893
+ env: { ...process.env, ...env ?? {} },
16894
+ stdio: "inherit"
16895
+ });
16896
+ child.once("exit", (code) => resolve5(code));
16897
+ child.once("error", () => resolve5(-1));
16898
+ });
16899
+ }
16900
+ var savePreviewConfigH = (_ctx, _cmd, parsed) => {
16901
+ const detection = parsed.detection;
16902
+ if (!detection) {
16903
+ log.info("preview", "save_preview_config: no detection in payload");
16904
+ return;
16905
+ }
16906
+ void writePreviewConfig(process.cwd(), detection).catch((err) => {
16907
+ log.info("preview", `save_preview_config failed: ${String(err)}`);
16908
+ });
16909
+ };
16300
16910
  var handlers = {
16301
16911
  start_task: startTask,
16302
16912
  provide_input: provideInput,
@@ -16331,7 +16941,11 @@ var handlers = {
16331
16941
  apply_file_review: applyFileReviewH,
16332
16942
  request_link_credentials: requestLinkCredentialsH,
16333
16943
  request_ai_summary: requestAiSummaryH,
16334
- request_ai_insight: requestAiInsightH
16944
+ request_ai_insight: requestAiInsightH,
16945
+ request_preview_detect: requestPreviewDetectH,
16946
+ preview_start: previewStartH,
16947
+ preview_stop: previewStopH,
16948
+ save_preview_config: savePreviewConfigH
16335
16949
  };
16336
16950
  async function dispatchCommand(ctx, cmd) {
16337
16951
  const parsed = parsePayload2(startCommandSchema, cmd.payload);
@@ -16505,6 +17119,7 @@ async function start(requestedAgent) {
16505
17119
  closeAllTerminals();
16506
17120
  cleanupAttachmentTempFiles();
16507
17121
  killActiveSpawnAndCaptureChildren();
17122
+ void killAllPreviews();
16508
17123
  void shutdownTelemetry();
16509
17124
  process.exit(0);
16510
17125
  }
@@ -16710,8 +17325,8 @@ async function autoLinkAfterPair(opts) {
16710
17325
  }
16711
17326
 
16712
17327
  // src/commands/pair-auto.ts
16713
- var fs28 = __toESM(require("fs"));
16714
- var os24 = __toESM(require("os"));
17328
+ var fs30 = __toESM(require("fs"));
17329
+ var os25 = __toESM(require("os"));
16715
17330
  var import_crypto7 = require("crypto");
16716
17331
 
16717
17332
  // src/commands/start-infra-only.ts
@@ -16879,12 +17494,12 @@ function readTokenFromArgs(args2) {
16879
17494
  }
16880
17495
  const fileFlag = args2.find((a) => a.startsWith("--token-file="));
16881
17496
  if (fileFlag) {
16882
- const path40 = fileFlag.slice("--token-file=".length);
17497
+ const path42 = fileFlag.slice("--token-file=".length);
16883
17498
  try {
16884
- const content = fs28.readFileSync(path40, "utf8").trim();
16885
- if (content.length === 0) fail(`--token-file ${path40} is empty`);
17499
+ const content = fs30.readFileSync(path42, "utf8").trim();
17500
+ if (content.length === 0) fail(`--token-file ${path42} is empty`);
16886
17501
  try {
16887
- fs28.unlinkSync(path40);
17502
+ fs30.unlinkSync(path42);
16888
17503
  } catch {
16889
17504
  }
16890
17505
  return content;
@@ -16910,7 +17525,7 @@ async function claimOnce(token, pluginId) {
16910
17525
  pluginId,
16911
17526
  ideName: "codeam-cli (codespace)",
16912
17527
  ideVersion: process.env.npm_package_version ?? "unknown",
16913
- hostname: os24.hostname(),
17528
+ hostname: os25.hostname(),
16914
17529
  codespaceName: process.env.CODESPACE_NAME ?? "",
16915
17530
  // Current git branch of the codespace's working directory, so the
16916
17531
  // backend can populate `PairedSession.branch` for the codespace pair.
@@ -17147,11 +17762,11 @@ async function logout() {
17147
17762
  var import_picocolors10 = __toESM(require("picocolors"));
17148
17763
 
17149
17764
  // src/services/providers/github-codespaces.ts
17150
- var import_child_process15 = require("child_process");
17151
- var import_util3 = require("util");
17765
+ var import_child_process16 = require("child_process");
17766
+ var import_util4 = require("util");
17152
17767
  var import_picocolors8 = __toESM(require("picocolors"));
17153
- var path34 = __toESM(require("path"));
17154
- var execFileP4 = (0, import_util3.promisify)(import_child_process15.execFile);
17768
+ var path36 = __toESM(require("path"));
17769
+ var execFileP5 = (0, import_util4.promisify)(import_child_process16.execFile);
17155
17770
  var MAX_BUFFER = 8 * 1024 * 1024;
17156
17771
  function resetStdinForChild() {
17157
17772
  if (process.stdin.isTTY) {
@@ -17168,11 +17783,11 @@ var GitHubCodespacesProvider = class {
17168
17783
  available = true;
17169
17784
  async authorize() {
17170
17785
  try {
17171
- await execFileP4("gh", ["--version"], { maxBuffer: MAX_BUFFER });
17786
+ await execFileP5("gh", ["--version"], { maxBuffer: MAX_BUFFER });
17172
17787
  } catch {
17173
17788
  await this.tryInstallGh();
17174
17789
  try {
17175
- await execFileP4("gh", ["--version"], { maxBuffer: MAX_BUFFER });
17790
+ await execFileP5("gh", ["--version"], { maxBuffer: MAX_BUFFER });
17176
17791
  } catch {
17177
17792
  throw new Error(
17178
17793
  [
@@ -17188,14 +17803,14 @@ var GitHubCodespacesProvider = class {
17188
17803
  }
17189
17804
  let isAuthed = false;
17190
17805
  try {
17191
- await execFileP4("gh", ["auth", "status"], { maxBuffer: MAX_BUFFER });
17806
+ await execFileP5("gh", ["auth", "status"], { maxBuffer: MAX_BUFFER });
17192
17807
  isAuthed = true;
17193
17808
  } catch {
17194
17809
  }
17195
17810
  if (!isAuthed) {
17196
17811
  resetStdinForChild();
17197
17812
  await new Promise((resolve5, reject) => {
17198
- const proc = (0, import_child_process15.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
17813
+ const proc = (0, import_child_process16.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
17199
17814
  stdio: "inherit"
17200
17815
  });
17201
17816
  proc.on("exit", (code) => {
@@ -17229,7 +17844,7 @@ var GitHubCodespacesProvider = class {
17229
17844
  wt(noteLines.join("\n"), "One more permission needed");
17230
17845
  resetStdinForChild();
17231
17846
  const refreshCode = await new Promise((resolve5, reject) => {
17232
- const proc = (0, import_child_process15.spawn)(
17847
+ const proc = (0, import_child_process16.spawn)(
17233
17848
  "gh",
17234
17849
  ["auth", "refresh", "-h", "github.com", "-s", "codespace"],
17235
17850
  { stdio: "inherit" }
@@ -17264,7 +17879,7 @@ var GitHubCodespacesProvider = class {
17264
17879
  */
17265
17880
  async getActiveGhUser() {
17266
17881
  try {
17267
- const { stdout } = await execFileP4(
17882
+ const { stdout } = await execFileP5(
17268
17883
  "gh",
17269
17884
  ["api", "user", "--jq", ".login"],
17270
17885
  { maxBuffer: MAX_BUFFER }
@@ -17284,7 +17899,7 @@ var GitHubCodespacesProvider = class {
17284
17899
  */
17285
17900
  async hasCodespaceScope() {
17286
17901
  try {
17287
- const { stdout } = await execFileP4(
17902
+ const { stdout } = await execFileP5(
17288
17903
  "gh",
17289
17904
  ["api", "-i", "user"],
17290
17905
  { maxBuffer: MAX_BUFFER }
@@ -17333,7 +17948,7 @@ var GitHubCodespacesProvider = class {
17333
17948
  let installCmd = null;
17334
17949
  if (platform2 === "darwin") {
17335
17950
  try {
17336
- await execFileP4("brew", ["--version"], { maxBuffer: MAX_BUFFER });
17951
+ await execFileP5("brew", ["--version"], { maxBuffer: MAX_BUFFER });
17337
17952
  } catch {
17338
17953
  wt(
17339
17954
  [
@@ -17352,7 +17967,7 @@ var GitHubCodespacesProvider = class {
17352
17967
  };
17353
17968
  } else if (platform2 === "win32") {
17354
17969
  try {
17355
- await execFileP4("winget", ["--version"], { maxBuffer: MAX_BUFFER });
17970
+ await execFileP5("winget", ["--version"], { maxBuffer: MAX_BUFFER });
17356
17971
  } catch {
17357
17972
  wt(
17358
17973
  [
@@ -17379,7 +17994,7 @@ var GitHubCodespacesProvider = class {
17379
17994
  O2.step(`Installing gh via ${installCmd.describe}\u2026`);
17380
17995
  resetStdinForChild();
17381
17996
  const ok = await new Promise((resolve5) => {
17382
- const proc = (0, import_child_process15.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
17997
+ const proc = (0, import_child_process16.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
17383
17998
  proc.on("exit", (code) => resolve5(code === 0));
17384
17999
  proc.on("error", () => resolve5(false));
17385
18000
  });
@@ -17406,7 +18021,7 @@ var GitHubCodespacesProvider = class {
17406
18021
  );
17407
18022
  resetStdinForChild();
17408
18023
  await new Promise((resolve5, reject) => {
17409
- const proc = (0, import_child_process15.spawn)(
18024
+ const proc = (0, import_child_process16.spawn)(
17410
18025
  "gh",
17411
18026
  ["auth", "refresh", "-h", "github.com", "-s", "repo,read:org"],
17412
18027
  { stdio: "inherit" }
@@ -17431,7 +18046,7 @@ var GitHubCodespacesProvider = class {
17431
18046
  "200"
17432
18047
  );
17433
18048
  try {
17434
- const { stdout } = await execFileP4("gh", args2, { maxBuffer: MAX_BUFFER });
18049
+ const { stdout } = await execFileP5("gh", args2, { maxBuffer: MAX_BUFFER });
17435
18050
  return JSON.parse(stdout);
17436
18051
  } catch {
17437
18052
  return [];
@@ -17440,7 +18055,7 @@ var GitHubCodespacesProvider = class {
17440
18055
  const own = await fetchRepos();
17441
18056
  let orgRepos = [];
17442
18057
  try {
17443
- const { stdout } = await execFileP4(
18058
+ const { stdout } = await execFileP5(
17444
18059
  "gh",
17445
18060
  ["api", "--paginate", "user/orgs", "--jq", ".[].login"],
17446
18061
  { maxBuffer: MAX_BUFFER }
@@ -17477,7 +18092,7 @@ var GitHubCodespacesProvider = class {
17477
18092
  */
17478
18093
  async listMachineTypes(projectId) {
17479
18094
  try {
17480
- const { stdout } = await execFileP4(
18095
+ const { stdout } = await execFileP5(
17481
18096
  "gh",
17482
18097
  ["api", `/repos/${projectId}/codespaces/machines`],
17483
18098
  { maxBuffer: MAX_BUFFER }
@@ -17508,7 +18123,7 @@ var GitHubCodespacesProvider = class {
17508
18123
  const machine = machineTypeId ?? await this.pickDefaultMachine(projectId);
17509
18124
  const args2 = ["codespace", "create", "-R", projectId, "--default-permissions"];
17510
18125
  if (machine) args2.push("-m", machine);
17511
- const { stdout } = await execFileP4(
18126
+ const { stdout } = await execFileP5(
17512
18127
  "gh",
17513
18128
  args2,
17514
18129
  { maxBuffer: MAX_BUFFER, timeout: 12e4 }
@@ -17548,7 +18163,7 @@ var GitHubCodespacesProvider = class {
17548
18163
  async waitUntilAvailable(name) {
17549
18164
  const deadline = Date.now() + 5 * 60 * 1e3;
17550
18165
  while (Date.now() < deadline) {
17551
- const { stdout } = await execFileP4(
18166
+ const { stdout } = await execFileP5(
17552
18167
  "gh",
17553
18168
  ["codespace", "list", "--json", "name,state"],
17554
18169
  { maxBuffer: MAX_BUFFER }
@@ -17566,7 +18181,7 @@ var GitHubCodespacesProvider = class {
17566
18181
  }
17567
18182
  async exec(workspaceId, command2) {
17568
18183
  try {
17569
- const { stdout, stderr } = await execFileP4(
18184
+ const { stdout, stderr } = await execFileP5(
17570
18185
  "gh",
17571
18186
  ["codespace", "ssh", "-c", workspaceId, "--", command2],
17572
18187
  { maxBuffer: MAX_BUFFER, timeout: 6e5 }
@@ -17584,7 +18199,7 @@ var GitHubCodespacesProvider = class {
17584
18199
  async streamCommand(workspaceId, command2) {
17585
18200
  resetStdinForChild();
17586
18201
  return new Promise((resolve5, reject) => {
17587
- const proc = (0, import_child_process15.spawn)(
18202
+ const proc = (0, import_child_process16.spawn)(
17588
18203
  "gh",
17589
18204
  ["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
17590
18205
  { stdio: "inherit" }
@@ -17611,11 +18226,11 @@ var GitHubCodespacesProvider = class {
17611
18226
  `mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
17612
18227
  ];
17613
18228
  await new Promise((resolve5, reject) => {
17614
- const tar = (0, import_child_process15.spawn)("tar", tarArgs, {
18229
+ const tar = (0, import_child_process16.spawn)("tar", tarArgs, {
17615
18230
  stdio: ["ignore", "pipe", "pipe"],
17616
18231
  env: tarEnv
17617
18232
  });
17618
- const ssh = (0, import_child_process15.spawn)("gh", sshArgs, {
18233
+ const ssh = (0, import_child_process16.spawn)("gh", sshArgs, {
17619
18234
  stdio: [tar.stdout, "pipe", "pipe"]
17620
18235
  });
17621
18236
  let tarErr = "";
@@ -17639,7 +18254,7 @@ var GitHubCodespacesProvider = class {
17639
18254
  });
17640
18255
  }
17641
18256
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
17642
- const remoteDir = path34.posix.dirname(remotePath);
18257
+ const remoteDir = path36.posix.dirname(remotePath);
17643
18258
  const parts = [
17644
18259
  `mkdir -p ${shellQuote(remoteDir)}`,
17645
18260
  `cat > ${shellQuote(remotePath)}`
@@ -17649,7 +18264,7 @@ var GitHubCodespacesProvider = class {
17649
18264
  }
17650
18265
  const cmd = parts.join(" && ");
17651
18266
  await new Promise((resolve5, reject) => {
17652
- const proc = (0, import_child_process15.spawn)(
18267
+ const proc = (0, import_child_process16.spawn)(
17653
18268
  "gh",
17654
18269
  ["codespace", "ssh", "-c", workspaceId, "--", cmd],
17655
18270
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -17671,7 +18286,7 @@ var GitHubCodespacesProvider = class {
17671
18286
  try {
17672
18287
  const args2 = ["codespace", "list", "--json", "name,displayName,state,lastUsedAt"];
17673
18288
  if (projectId) args2.push("--repo", projectId);
17674
- const { stdout } = await execFileP4("gh", args2, { maxBuffer: MAX_BUFFER });
18289
+ const { stdout } = await execFileP5("gh", args2, { maxBuffer: MAX_BUFFER });
17675
18290
  const list = JSON.parse(stdout);
17676
18291
  return list.map((c2) => ({
17677
18292
  id: c2.name,
@@ -17686,7 +18301,7 @@ var GitHubCodespacesProvider = class {
17686
18301
  }
17687
18302
  async startWorkspace(workspaceId) {
17688
18303
  try {
17689
- await execFileP4(
18304
+ await execFileP5(
17690
18305
  "gh",
17691
18306
  ["api", "-X", "POST", `/user/codespaces/${workspaceId}/start`],
17692
18307
  { maxBuffer: MAX_BUFFER, timeout: 6e4 }
@@ -17707,11 +18322,11 @@ function shellQuote(s) {
17707
18322
  }
17708
18323
 
17709
18324
  // src/services/providers/gitpod.ts
17710
- var import_child_process16 = require("child_process");
17711
- var import_util4 = require("util");
17712
- var path35 = __toESM(require("path"));
18325
+ var import_child_process17 = require("child_process");
18326
+ var import_util5 = require("util");
18327
+ var path37 = __toESM(require("path"));
17713
18328
  var import_picocolors9 = __toESM(require("picocolors"));
17714
- var execFileP5 = (0, import_util4.promisify)(import_child_process16.execFile);
18329
+ var execFileP6 = (0, import_util5.promisify)(import_child_process17.execFile);
17715
18330
  var MAX_BUFFER2 = 8 * 1024 * 1024;
17716
18331
  function resetStdinForChild2() {
17717
18332
  if (process.stdin.isTTY) {
@@ -17728,7 +18343,7 @@ var GitpodProvider = class {
17728
18343
  available = true;
17729
18344
  async authorize() {
17730
18345
  try {
17731
- await execFileP5("gitpod", ["--version"], { maxBuffer: MAX_BUFFER2 });
18346
+ await execFileP6("gitpod", ["--version"], { maxBuffer: MAX_BUFFER2 });
17732
18347
  } catch {
17733
18348
  throw new Error(
17734
18349
  [
@@ -17741,7 +18356,7 @@ var GitpodProvider = class {
17741
18356
  );
17742
18357
  }
17743
18358
  try {
17744
- await execFileP5("gitpod", ["whoami"], { maxBuffer: MAX_BUFFER2 });
18359
+ await execFileP6("gitpod", ["whoami"], { maxBuffer: MAX_BUFFER2 });
17745
18360
  return;
17746
18361
  } catch {
17747
18362
  }
@@ -17751,7 +18366,7 @@ var GitpodProvider = class {
17751
18366
  );
17752
18367
  resetStdinForChild2();
17753
18368
  await new Promise((resolve5, reject) => {
17754
- const proc = (0, import_child_process16.spawn)("gitpod", ["login"], { stdio: "inherit" });
18369
+ const proc = (0, import_child_process17.spawn)("gitpod", ["login"], { stdio: "inherit" });
17755
18370
  proc.on("exit", (code) => {
17756
18371
  if (code === 0) resolve5();
17757
18372
  else reject(new Error("gitpod login failed."));
@@ -17761,7 +18376,7 @@ var GitpodProvider = class {
17761
18376
  }
17762
18377
  async listProjects() {
17763
18378
  try {
17764
- const { stdout } = await execFileP5(
18379
+ const { stdout } = await execFileP6(
17765
18380
  "gitpod",
17766
18381
  ["workspace", "list", "--output", "json", "--limit", "200"],
17767
18382
  { maxBuffer: MAX_BUFFER2 }
@@ -17794,7 +18409,7 @@ var GitpodProvider = class {
17794
18409
  async createWorkspace(projectId, machineTypeId) {
17795
18410
  const args2 = ["workspace", "create", projectId, "--start", "--output", "json"];
17796
18411
  if (machineTypeId) args2.push("--class", machineTypeId);
17797
- const { stdout } = await execFileP5("gitpod", args2, {
18412
+ const { stdout } = await execFileP6("gitpod", args2, {
17798
18413
  maxBuffer: MAX_BUFFER2,
17799
18414
  timeout: 3e5
17800
18415
  });
@@ -17818,7 +18433,7 @@ var GitpodProvider = class {
17818
18433
  const deadline = Date.now() + 5 * 60 * 1e3;
17819
18434
  while (Date.now() < deadline) {
17820
18435
  try {
17821
- const { stdout } = await execFileP5(
18436
+ const { stdout } = await execFileP6(
17822
18437
  "gitpod",
17823
18438
  ["workspace", "get", workspaceId, "--output", "json"],
17824
18439
  { maxBuffer: MAX_BUFFER2 }
@@ -17836,7 +18451,7 @@ var GitpodProvider = class {
17836
18451
  }
17837
18452
  async listMachineTypes(_projectId) {
17838
18453
  try {
17839
- const { stdout } = await execFileP5(
18454
+ const { stdout } = await execFileP6(
17840
18455
  "gitpod",
17841
18456
  ["organization", "list-classes", "--output", "json"],
17842
18457
  { maxBuffer: MAX_BUFFER2 }
@@ -17854,7 +18469,7 @@ var GitpodProvider = class {
17854
18469
  async listExistingWorkspaces(projectId) {
17855
18470
  try {
17856
18471
  const args2 = ["workspace", "list", "--output", "json", "--limit", "200"];
17857
- const { stdout } = await execFileP5("gitpod", args2, { maxBuffer: MAX_BUFFER2 });
18472
+ const { stdout } = await execFileP6("gitpod", args2, { maxBuffer: MAX_BUFFER2 });
17858
18473
  const list = JSON.parse(stdout);
17859
18474
  return list.filter((w3) => !projectId || w3.contextUrl === projectId).map((w3) => ({
17860
18475
  id: w3.id,
@@ -17869,7 +18484,7 @@ var GitpodProvider = class {
17869
18484
  }
17870
18485
  async startWorkspace(workspaceId) {
17871
18486
  try {
17872
- await execFileP5(
18487
+ await execFileP6(
17873
18488
  "gitpod",
17874
18489
  ["workspace", "start", workspaceId],
17875
18490
  { maxBuffer: MAX_BUFFER2, timeout: 6e4 }
@@ -17885,7 +18500,7 @@ var GitpodProvider = class {
17885
18500
  }
17886
18501
  async exec(workspaceId, command2) {
17887
18502
  try {
17888
- const { stdout, stderr } = await execFileP5(
18503
+ const { stdout, stderr } = await execFileP6(
17889
18504
  "gitpod",
17890
18505
  ["workspace", "ssh", workspaceId, "--", command2],
17891
18506
  { maxBuffer: MAX_BUFFER2, timeout: 6e5 }
@@ -17903,7 +18518,7 @@ var GitpodProvider = class {
17903
18518
  async streamCommand(workspaceId, command2) {
17904
18519
  resetStdinForChild2();
17905
18520
  return new Promise((resolve5, reject) => {
17906
- const proc = (0, import_child_process16.spawn)(
18521
+ const proc = (0, import_child_process17.spawn)(
17907
18522
  "gitpod",
17908
18523
  ["workspace", "ssh", workspaceId, "--", "-tt", command2],
17909
18524
  { stdio: "inherit" }
@@ -17923,11 +18538,11 @@ var GitpodProvider = class {
17923
18538
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
17924
18539
  const remoteCmd = `mkdir -p ${shellQuote2(remoteDir)} && tar -xzf - -C ${shellQuote2(remoteDir)}`;
17925
18540
  await new Promise((resolve5, reject) => {
17926
- const tar = (0, import_child_process16.spawn)("tar", tarArgs, {
18541
+ const tar = (0, import_child_process17.spawn)("tar", tarArgs, {
17927
18542
  stdio: ["ignore", "pipe", "pipe"],
17928
18543
  env: tarEnv
17929
18544
  });
17930
- const ssh = (0, import_child_process16.spawn)(
18545
+ const ssh = (0, import_child_process17.spawn)(
17931
18546
  "gitpod",
17932
18547
  ["workspace", "ssh", workspaceId, "--", remoteCmd],
17933
18548
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -17949,7 +18564,7 @@ var GitpodProvider = class {
17949
18564
  });
17950
18565
  }
17951
18566
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
17952
- const remoteDir = path35.posix.dirname(remotePath);
18567
+ const remoteDir = path37.posix.dirname(remotePath);
17953
18568
  const parts = [
17954
18569
  `mkdir -p ${shellQuote2(remoteDir)}`,
17955
18570
  `cat > ${shellQuote2(remotePath)}`
@@ -17959,7 +18574,7 @@ var GitpodProvider = class {
17959
18574
  }
17960
18575
  const cmd = parts.join(" && ");
17961
18576
  await new Promise((resolve5, reject) => {
17962
- const proc = (0, import_child_process16.spawn)(
18577
+ const proc = (0, import_child_process17.spawn)(
17963
18578
  "gitpod",
17964
18579
  ["workspace", "ssh", workspaceId, "--", cmd],
17965
18580
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -17983,10 +18598,10 @@ function shellQuote2(s) {
17983
18598
  }
17984
18599
 
17985
18600
  // src/services/providers/gitlab-workspaces.ts
17986
- var import_child_process17 = require("child_process");
17987
- var import_util5 = require("util");
17988
- var path36 = __toESM(require("path"));
17989
- var execFileP6 = (0, import_util5.promisify)(import_child_process17.execFile);
18601
+ var import_child_process18 = require("child_process");
18602
+ var import_util6 = require("util");
18603
+ var path38 = __toESM(require("path"));
18604
+ var execFileP7 = (0, import_util6.promisify)(import_child_process18.execFile);
17990
18605
  var MAX_BUFFER3 = 8 * 1024 * 1024;
17991
18606
  var GITLAB_API_BASE = process.env.CODEAM_GITLAB_API_URL ?? "https://gitlab.com/api/v4";
17992
18607
  function resetStdinForChild3() {
@@ -18004,7 +18619,7 @@ var GitLabWorkspacesProvider = class {
18004
18619
  available = true;
18005
18620
  async authorize() {
18006
18621
  try {
18007
- await execFileP6("glab", ["--version"], { maxBuffer: MAX_BUFFER3 });
18622
+ await execFileP7("glab", ["--version"], { maxBuffer: MAX_BUFFER3 });
18008
18623
  } catch {
18009
18624
  throw new Error(
18010
18625
  [
@@ -18018,7 +18633,7 @@ var GitLabWorkspacesProvider = class {
18018
18633
  );
18019
18634
  }
18020
18635
  try {
18021
- await execFileP6("glab", ["auth", "status"], { maxBuffer: MAX_BUFFER3 });
18636
+ await execFileP7("glab", ["auth", "status"], { maxBuffer: MAX_BUFFER3 });
18022
18637
  return;
18023
18638
  } catch {
18024
18639
  }
@@ -18028,7 +18643,7 @@ var GitLabWorkspacesProvider = class {
18028
18643
  );
18029
18644
  resetStdinForChild3();
18030
18645
  await new Promise((resolve5, reject) => {
18031
- const proc = (0, import_child_process17.spawn)(
18646
+ const proc = (0, import_child_process18.spawn)(
18032
18647
  "glab",
18033
18648
  ["auth", "login", "--scopes", "api,read_user,read_repository"],
18034
18649
  { stdio: "inherit" }
@@ -18042,7 +18657,7 @@ var GitLabWorkspacesProvider = class {
18042
18657
  }
18043
18658
  async listProjects() {
18044
18659
  try {
18045
- const { stdout } = await execFileP6(
18660
+ const { stdout } = await execFileP7(
18046
18661
  "glab",
18047
18662
  ["repo", "list", "--per-page", "200", "--output", "json"],
18048
18663
  { maxBuffer: MAX_BUFFER3 }
@@ -18174,7 +18789,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
18174
18789
  async exec(workspaceId, command2) {
18175
18790
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
18176
18791
  try {
18177
- const { stdout, stderr } = await execFileP6(
18792
+ const { stdout, stderr } = await execFileP7(
18178
18793
  "ssh",
18179
18794
  [
18180
18795
  "-o",
@@ -18200,7 +18815,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
18200
18815
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
18201
18816
  resetStdinForChild3();
18202
18817
  return new Promise((resolve5, reject) => {
18203
- const proc = (0, import_child_process17.spawn)(
18818
+ const proc = (0, import_child_process18.spawn)(
18204
18819
  "ssh",
18205
18820
  ["-tt", "-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, command2],
18206
18821
  { stdio: "inherit" }
@@ -18221,8 +18836,8 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
18221
18836
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
18222
18837
  const remoteCmd = `mkdir -p ${shellQuote3(remoteDir)} && tar -xzf - -C ${shellQuote3(remoteDir)}`;
18223
18838
  await new Promise((resolve5, reject) => {
18224
- const tar = (0, import_child_process17.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
18225
- const ssh = (0, import_child_process17.spawn)(
18839
+ const tar = (0, import_child_process18.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
18840
+ const ssh = (0, import_child_process18.spawn)(
18226
18841
  "ssh",
18227
18842
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, remoteCmd],
18228
18843
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -18245,14 +18860,14 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
18245
18860
  }
18246
18861
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
18247
18862
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
18248
- const remoteDir = path36.posix.dirname(remotePath);
18863
+ const remoteDir = path38.posix.dirname(remotePath);
18249
18864
  const parts = [`mkdir -p ${shellQuote3(remoteDir)}`, `cat > ${shellQuote3(remotePath)}`];
18250
18865
  if (options.mode != null) {
18251
18866
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote3(remotePath)}`);
18252
18867
  }
18253
18868
  const cmd = parts.join(" && ");
18254
18869
  await new Promise((resolve5, reject) => {
18255
- const proc = (0, import_child_process17.spawn)(
18870
+ const proc = (0, import_child_process18.spawn)(
18256
18871
  "ssh",
18257
18872
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, cmd],
18258
18873
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -18277,7 +18892,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
18277
18892
  */
18278
18893
  async getGlabToken() {
18279
18894
  try {
18280
- const { stdout, stderr } = await execFileP6(
18895
+ const { stdout, stderr } = await execFileP7(
18281
18896
  "glab",
18282
18897
  ["auth", "status", "--show-token"],
18283
18898
  { maxBuffer: MAX_BUFFER3 }
@@ -18311,10 +18926,10 @@ function shellQuote3(s) {
18311
18926
  }
18312
18927
 
18313
18928
  // src/services/providers/railway.ts
18314
- var import_child_process18 = require("child_process");
18315
- var import_util6 = require("util");
18316
- var path37 = __toESM(require("path"));
18317
- var execFileP7 = (0, import_util6.promisify)(import_child_process18.execFile);
18929
+ var import_child_process19 = require("child_process");
18930
+ var import_util7 = require("util");
18931
+ var path39 = __toESM(require("path"));
18932
+ var execFileP8 = (0, import_util7.promisify)(import_child_process19.execFile);
18318
18933
  var MAX_BUFFER4 = 8 * 1024 * 1024;
18319
18934
  function resetStdinForChild4() {
18320
18935
  if (process.stdin.isTTY) {
@@ -18331,7 +18946,7 @@ var RailwayProvider = class {
18331
18946
  available = true;
18332
18947
  async authorize() {
18333
18948
  try {
18334
- await execFileP7("railway", ["--version"], { maxBuffer: MAX_BUFFER4 });
18949
+ await execFileP8("railway", ["--version"], { maxBuffer: MAX_BUFFER4 });
18335
18950
  } catch {
18336
18951
  throw new Error(
18337
18952
  [
@@ -18345,7 +18960,7 @@ var RailwayProvider = class {
18345
18960
  );
18346
18961
  }
18347
18962
  try {
18348
- await execFileP7("railway", ["whoami"], { maxBuffer: MAX_BUFFER4 });
18963
+ await execFileP8("railway", ["whoami"], { maxBuffer: MAX_BUFFER4 });
18349
18964
  return;
18350
18965
  } catch {
18351
18966
  }
@@ -18355,7 +18970,7 @@ var RailwayProvider = class {
18355
18970
  );
18356
18971
  resetStdinForChild4();
18357
18972
  await new Promise((resolve5, reject) => {
18358
- const proc = (0, import_child_process18.spawn)("railway", ["login"], { stdio: "inherit" });
18973
+ const proc = (0, import_child_process19.spawn)("railway", ["login"], { stdio: "inherit" });
18359
18974
  proc.on("exit", (code) => {
18360
18975
  if (code === 0) resolve5();
18361
18976
  else reject(new Error("railway login failed."));
@@ -18365,7 +18980,7 @@ var RailwayProvider = class {
18365
18980
  }
18366
18981
  async listProjects() {
18367
18982
  try {
18368
- const { stdout } = await execFileP7(
18983
+ const { stdout } = await execFileP8(
18369
18984
  "railway",
18370
18985
  ["list", "--json"],
18371
18986
  { maxBuffer: MAX_BUFFER4 }
@@ -18381,7 +18996,7 @@ var RailwayProvider = class {
18381
18996
  }));
18382
18997
  } catch {
18383
18998
  try {
18384
- const { stdout } = await execFileP7("railway", ["list"], { maxBuffer: MAX_BUFFER4 });
18999
+ const { stdout } = await execFileP8("railway", ["list"], { maxBuffer: MAX_BUFFER4 });
18385
19000
  const projects = [];
18386
19001
  for (const line of stdout.split("\n")) {
18387
19002
  const trimmed = line.trim();
@@ -18432,7 +19047,7 @@ var RailwayProvider = class {
18432
19047
  async listExistingWorkspaces(projectId) {
18433
19048
  if (!projectId) return [];
18434
19049
  try {
18435
- const { stdout } = await execFileP7(
19050
+ const { stdout } = await execFileP8(
18436
19051
  "railway",
18437
19052
  ["service", "list", "--project", projectId, "--json"],
18438
19053
  { maxBuffer: MAX_BUFFER4 }
@@ -18457,7 +19072,7 @@ var RailwayProvider = class {
18457
19072
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
18458
19073
  }
18459
19074
  try {
18460
- await execFileP7(
19075
+ await execFileP8(
18461
19076
  "railway",
18462
19077
  ["service", "restart", "--service", serviceId, "--project", projectId],
18463
19078
  { maxBuffer: MAX_BUFFER4, timeout: 6e4 }
@@ -18476,7 +19091,7 @@ var RailwayProvider = class {
18476
19091
  };
18477
19092
  }
18478
19093
  try {
18479
- const { stdout, stderr } = await execFileP7(
19094
+ const { stdout, stderr } = await execFileP8(
18480
19095
  "railway",
18481
19096
  ["run", "--project", projectId, "--service", serviceId, "--", "bash", "-lc", command2],
18482
19097
  { maxBuffer: MAX_BUFFER4, timeout: 6e5 }
@@ -18498,7 +19113,7 @@ var RailwayProvider = class {
18498
19113
  }
18499
19114
  resetStdinForChild4();
18500
19115
  return new Promise((resolve5, reject) => {
18501
- const proc = (0, import_child_process18.spawn)(
19116
+ const proc = (0, import_child_process19.spawn)(
18502
19117
  "railway",
18503
19118
  ["shell", "--project", projectId, "--service", serviceId, "--command", command2],
18504
19119
  { stdio: "inherit" }
@@ -18522,8 +19137,8 @@ var RailwayProvider = class {
18522
19137
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
18523
19138
  const remoteCmd = `mkdir -p ${shellQuote4(remoteDir)} && tar -xzf - -C ${shellQuote4(remoteDir)}`;
18524
19139
  await new Promise((resolve5, reject) => {
18525
- const tar = (0, import_child_process18.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
18526
- const sh = (0, import_child_process18.spawn)(
19140
+ const tar = (0, import_child_process19.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
19141
+ const sh = (0, import_child_process19.spawn)(
18527
19142
  "railway",
18528
19143
  ["shell", "--project", projectId, "--service", serviceId, "--command", remoteCmd],
18529
19144
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -18549,14 +19164,14 @@ var RailwayProvider = class {
18549
19164
  if (!projectId || !serviceId) {
18550
19165
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
18551
19166
  }
18552
- const remoteDir = path37.posix.dirname(remotePath);
19167
+ const remoteDir = path39.posix.dirname(remotePath);
18553
19168
  const parts = [`mkdir -p ${shellQuote4(remoteDir)}`, `cat > ${shellQuote4(remotePath)}`];
18554
19169
  if (options.mode != null) {
18555
19170
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote4(remotePath)}`);
18556
19171
  }
18557
19172
  const cmd = parts.join(" && ");
18558
19173
  await new Promise((resolve5, reject) => {
18559
- const proc = (0, import_child_process18.spawn)(
19174
+ const proc = (0, import_child_process19.spawn)(
18560
19175
  "railway",
18561
19176
  ["shell", "--project", projectId, "--service", serviceId, "--command", cmd],
18562
19177
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -19080,10 +19695,10 @@ async function probeCodeamPair(provider, workspace) {
19080
19695
  }
19081
19696
  async function stopWorkspaceFromLocal(target) {
19082
19697
  if (target.provider.id === "github-codespaces") {
19083
- const { execFile: execFile8 } = await import("child_process");
19084
- const { promisify: promisify9 } = await import("util");
19085
- const execFileP8 = promisify9(execFile8);
19086
- await execFileP8("gh", ["codespace", "stop", "-c", target.id], { maxBuffer: 8 * 1024 * 1024 });
19698
+ const { execFile: execFile9 } = await import("child_process");
19699
+ const { promisify: promisify10 } = await import("util");
19700
+ const execFileP9 = promisify10(execFile9);
19701
+ await execFileP9("gh", ["codespace", "stop", "-c", target.id], { maxBuffer: 8 * 1024 * 1024 });
19087
19702
  return;
19088
19703
  }
19089
19704
  }
@@ -19092,8 +19707,8 @@ async function stopWorkspaceFromLocal(target) {
19092
19707
  var import_node_dns = require("dns");
19093
19708
  var import_node_util4 = require("util");
19094
19709
  var import_node_crypto6 = require("crypto");
19095
- var fs29 = __toESM(require("fs"));
19096
- var path38 = __toESM(require("path"));
19710
+ var fs31 = __toESM(require("fs"));
19711
+ var path40 = __toESM(require("path"));
19097
19712
  var import_picocolors12 = __toESM(require("picocolors"));
19098
19713
  var dnsResolveP = (0, import_node_util4.promisify)(import_node_dns.resolve);
19099
19714
  async function checkDns(apiBase) {
@@ -19149,13 +19764,13 @@ async function checkHealth(apiBase) {
19149
19764
  }
19150
19765
  }
19151
19766
  function checkConfigDir() {
19152
- const dir = path38.join(require("os").homedir(), ".codeam");
19767
+ const dir = path40.join(require("os").homedir(), ".codeam");
19153
19768
  try {
19154
- fs29.mkdirSync(dir, { recursive: true, mode: 448 });
19155
- const probe = path38.join(dir, ".doctor-probe");
19156
- fs29.writeFileSync(probe, "ok", { mode: 384 });
19157
- const read = fs29.readFileSync(probe, "utf8");
19158
- fs29.unlinkSync(probe);
19769
+ fs31.mkdirSync(dir, { recursive: true, mode: 448 });
19770
+ const probe = path40.join(dir, ".doctor-probe");
19771
+ fs31.writeFileSync(probe, "ok", { mode: 384 });
19772
+ const read = fs31.readFileSync(probe, "utf8");
19773
+ fs31.unlinkSync(probe);
19159
19774
  if (read !== "ok") throw new Error("write/read round-trip mismatch");
19160
19775
  return {
19161
19776
  id: "config-dir",
@@ -19195,9 +19810,9 @@ function checkSessions() {
19195
19810
  }
19196
19811
  }
19197
19812
  function checkAgentBinaries() {
19198
- const os26 = createOsStrategy();
19813
+ const os27 = createOsStrategy();
19199
19814
  return getEnabledAgents().map((meta) => {
19200
- const found = os26.findInPath(meta.binaryName);
19815
+ const found = os27.findInPath(meta.binaryName);
19201
19816
  return {
19202
19817
  id: `agent-${meta.id}`,
19203
19818
  label: `Agent binary: ${meta.displayName} (${meta.binaryName})`,
@@ -19219,7 +19834,7 @@ function checkNodePty() {
19219
19834
  detail: "not required on this platform"
19220
19835
  };
19221
19836
  }
19222
- const vendoredPath = path38.join(__dirname, "vendor", "node-pty");
19837
+ const vendoredPath = path40.join(__dirname, "vendor", "node-pty");
19223
19838
  for (const target of [vendoredPath, "node-pty"]) {
19224
19839
  try {
19225
19840
  require(target);
@@ -19261,7 +19876,7 @@ function checkChokidar() {
19261
19876
  }
19262
19877
  async function doctor(args2 = []) {
19263
19878
  const json = args2.includes("--json");
19264
- const cliVersion = true ? "2.25.0" : "0.0.0-dev";
19879
+ const cliVersion = true ? "2.26.1" : "0.0.0-dev";
19265
19880
  const apiBase = resolveApiBaseUrl();
19266
19881
  const diagnosticId = (0, import_node_crypto6.randomUUID)();
19267
19882
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -19460,7 +20075,7 @@ async function completion(args2) {
19460
20075
  // src/commands/version.ts
19461
20076
  var import_picocolors13 = __toESM(require("picocolors"));
19462
20077
  function version2() {
19463
- const v = true ? "2.25.0" : "unknown";
20078
+ const v = true ? "2.26.1" : "unknown";
19464
20079
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
19465
20080
  }
19466
20081
 
@@ -19588,9 +20203,9 @@ function tryShowSubcommandHelp(cmd, args2) {
19588
20203
  var _subcommandHelpKeys = Object.keys(HELPS);
19589
20204
 
19590
20205
  // src/lib/updateNotifier.ts
19591
- var fs30 = __toESM(require("fs"));
19592
- var os25 = __toESM(require("os"));
19593
- var path39 = __toESM(require("path"));
20206
+ var fs32 = __toESM(require("fs"));
20207
+ var os26 = __toESM(require("os"));
20208
+ var path41 = __toESM(require("path"));
19594
20209
  var https7 = __toESM(require("https"));
19595
20210
  var import_picocolors16 = __toESM(require("picocolors"));
19596
20211
  var PKG_NAME = "codeam-cli";
@@ -19598,12 +20213,12 @@ var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
19598
20213
  var TTL_MS = 24 * 60 * 60 * 1e3;
19599
20214
  var REQUEST_TIMEOUT_MS = 1500;
19600
20215
  function cachePath() {
19601
- const dir = path39.join(os25.homedir(), ".codeam");
19602
- return path39.join(dir, "update-check.json");
20216
+ const dir = path41.join(os26.homedir(), ".codeam");
20217
+ return path41.join(dir, "update-check.json");
19603
20218
  }
19604
20219
  function readCache() {
19605
20220
  try {
19606
- const raw = fs30.readFileSync(cachePath(), "utf8");
20221
+ const raw = fs32.readFileSync(cachePath(), "utf8");
19607
20222
  const parsed = JSON.parse(raw);
19608
20223
  if (typeof parsed.fetchedAt !== "number" || typeof parsed.latest !== "string") return null;
19609
20224
  return parsed;
@@ -19614,10 +20229,10 @@ function readCache() {
19614
20229
  function writeCache(cache) {
19615
20230
  try {
19616
20231
  const file = cachePath();
19617
- fs30.mkdirSync(path39.dirname(file), { recursive: true });
20232
+ fs32.mkdirSync(path41.dirname(file), { recursive: true });
19618
20233
  const tmp = `${file}.${process.pid}.tmp`;
19619
- fs30.writeFileSync(tmp, JSON.stringify(cache));
19620
- fs30.renameSync(tmp, file);
20234
+ fs32.writeFileSync(tmp, JSON.stringify(cache));
20235
+ fs32.renameSync(tmp, file);
19621
20236
  } catch {
19622
20237
  }
19623
20238
  }
@@ -19688,7 +20303,7 @@ function checkForUpdates() {
19688
20303
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
19689
20304
  if (process.env.CI) return;
19690
20305
  if (!process.stdout.isTTY) return;
19691
- const current = true ? "2.25.0" : null;
20306
+ const current = true ? "2.26.1" : null;
19692
20307
  if (!current) return;
19693
20308
  const cache = readCache();
19694
20309
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;