codeam-cli 2.24.0 → 2.26.0

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 +42 -0
  2. package/dist/index.js +810 -187
  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.24.0",
475
+ version: "2.26.0",
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.24.0" : "0.0.0-dev",
5832
+ cliVersion: true ? "2.26.0" : "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);
@@ -15956,14 +16233,22 @@ var setKeepAlive = async (ctx, cmd) => {
15956
16233
  } catch {
15957
16234
  }
15958
16235
  };
15959
- var sessionTerminated = (ctx) => {
16236
+ var sessionTerminated = async (ctx, cmd) => {
15960
16237
  showInfo("Session was deleted from the app \u2014 exiting.");
16238
+ try {
16239
+ await ctx.relay.sendResult(cmd.id, "success", { ok: true });
16240
+ } catch {
16241
+ }
16242
+ try {
16243
+ removeSession(ctx.sessionId);
16244
+ } catch {
16245
+ }
15961
16246
  try {
15962
16247
  ctx.agent.kill();
15963
16248
  } catch {
15964
16249
  }
15965
16250
  try {
15966
- 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"], {
15967
16252
  detached: true,
15968
16253
  stdio: "ignore"
15969
16254
  });
@@ -15985,7 +16270,7 @@ var shutdownSession = async (ctx, cmd) => {
15985
16270
  }
15986
16271
  if (ctx.keepAliveCtx.inCodespace && ctx.keepAliveCtx.codespaceName) {
15987
16272
  try {
15988
- const stopProc = (0, import_child_process14.spawn)(
16273
+ const stopProc = (0, import_child_process15.spawn)(
15989
16274
  "bash",
15990
16275
  ["-lc", `sleep 1; gh codespace stop -c ${JSON.stringify(ctx.keepAliveCtx.codespaceName)} >/dev/null 2>&1 || true`],
15991
16276
  { detached: true, stdio: "ignore" }
@@ -15995,7 +16280,7 @@ var shutdownSession = async (ctx, cmd) => {
15995
16280
  }
15996
16281
  }
15997
16282
  try {
15998
- 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"], {
15999
16284
  detached: true,
16000
16285
  stdio: "ignore"
16001
16286
  });
@@ -16289,6 +16574,339 @@ function parseInsightText(text) {
16289
16574
  securityNote: securityMatch?.[1]?.trim() || void 0
16290
16575
  };
16291
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
+ };
16292
16910
  var handlers = {
16293
16911
  start_task: startTask,
16294
16912
  provide_input: provideInput,
@@ -16323,7 +16941,11 @@ var handlers = {
16323
16941
  apply_file_review: applyFileReviewH,
16324
16942
  request_link_credentials: requestLinkCredentialsH,
16325
16943
  request_ai_summary: requestAiSummaryH,
16326
- 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
16327
16949
  };
16328
16950
  async function dispatchCommand(ctx, cmd) {
16329
16951
  const parsed = parsePayload2(startCommandSchema, cmd.payload);
@@ -16497,6 +17119,7 @@ async function start(requestedAgent) {
16497
17119
  closeAllTerminals();
16498
17120
  cleanupAttachmentTempFiles();
16499
17121
  killActiveSpawnAndCaptureChildren();
17122
+ void killAllPreviews();
16500
17123
  void shutdownTelemetry();
16501
17124
  process.exit(0);
16502
17125
  }
@@ -16702,8 +17325,8 @@ async function autoLinkAfterPair(opts) {
16702
17325
  }
16703
17326
 
16704
17327
  // src/commands/pair-auto.ts
16705
- var fs28 = __toESM(require("fs"));
16706
- var os24 = __toESM(require("os"));
17328
+ var fs30 = __toESM(require("fs"));
17329
+ var os25 = __toESM(require("os"));
16707
17330
  var import_crypto7 = require("crypto");
16708
17331
 
16709
17332
  // src/commands/start-infra-only.ts
@@ -16871,12 +17494,12 @@ function readTokenFromArgs(args2) {
16871
17494
  }
16872
17495
  const fileFlag = args2.find((a) => a.startsWith("--token-file="));
16873
17496
  if (fileFlag) {
16874
- const path40 = fileFlag.slice("--token-file=".length);
17497
+ const path42 = fileFlag.slice("--token-file=".length);
16875
17498
  try {
16876
- const content = fs28.readFileSync(path40, "utf8").trim();
16877
- 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`);
16878
17501
  try {
16879
- fs28.unlinkSync(path40);
17502
+ fs30.unlinkSync(path42);
16880
17503
  } catch {
16881
17504
  }
16882
17505
  return content;
@@ -16902,7 +17525,7 @@ async function claimOnce(token, pluginId) {
16902
17525
  pluginId,
16903
17526
  ideName: "codeam-cli (codespace)",
16904
17527
  ideVersion: process.env.npm_package_version ?? "unknown",
16905
- hostname: os24.hostname(),
17528
+ hostname: os25.hostname(),
16906
17529
  codespaceName: process.env.CODESPACE_NAME ?? "",
16907
17530
  // Current git branch of the codespace's working directory, so the
16908
17531
  // backend can populate `PairedSession.branch` for the codespace pair.
@@ -17139,11 +17762,11 @@ async function logout() {
17139
17762
  var import_picocolors10 = __toESM(require("picocolors"));
17140
17763
 
17141
17764
  // src/services/providers/github-codespaces.ts
17142
- var import_child_process15 = require("child_process");
17143
- var import_util3 = require("util");
17765
+ var import_child_process16 = require("child_process");
17766
+ var import_util4 = require("util");
17144
17767
  var import_picocolors8 = __toESM(require("picocolors"));
17145
- var path34 = __toESM(require("path"));
17146
- 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);
17147
17770
  var MAX_BUFFER = 8 * 1024 * 1024;
17148
17771
  function resetStdinForChild() {
17149
17772
  if (process.stdin.isTTY) {
@@ -17160,11 +17783,11 @@ var GitHubCodespacesProvider = class {
17160
17783
  available = true;
17161
17784
  async authorize() {
17162
17785
  try {
17163
- await execFileP4("gh", ["--version"], { maxBuffer: MAX_BUFFER });
17786
+ await execFileP5("gh", ["--version"], { maxBuffer: MAX_BUFFER });
17164
17787
  } catch {
17165
17788
  await this.tryInstallGh();
17166
17789
  try {
17167
- await execFileP4("gh", ["--version"], { maxBuffer: MAX_BUFFER });
17790
+ await execFileP5("gh", ["--version"], { maxBuffer: MAX_BUFFER });
17168
17791
  } catch {
17169
17792
  throw new Error(
17170
17793
  [
@@ -17180,14 +17803,14 @@ var GitHubCodespacesProvider = class {
17180
17803
  }
17181
17804
  let isAuthed = false;
17182
17805
  try {
17183
- await execFileP4("gh", ["auth", "status"], { maxBuffer: MAX_BUFFER });
17806
+ await execFileP5("gh", ["auth", "status"], { maxBuffer: MAX_BUFFER });
17184
17807
  isAuthed = true;
17185
17808
  } catch {
17186
17809
  }
17187
17810
  if (!isAuthed) {
17188
17811
  resetStdinForChild();
17189
17812
  await new Promise((resolve5, reject) => {
17190
- 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"], {
17191
17814
  stdio: "inherit"
17192
17815
  });
17193
17816
  proc.on("exit", (code) => {
@@ -17221,7 +17844,7 @@ var GitHubCodespacesProvider = class {
17221
17844
  wt(noteLines.join("\n"), "One more permission needed");
17222
17845
  resetStdinForChild();
17223
17846
  const refreshCode = await new Promise((resolve5, reject) => {
17224
- const proc = (0, import_child_process15.spawn)(
17847
+ const proc = (0, import_child_process16.spawn)(
17225
17848
  "gh",
17226
17849
  ["auth", "refresh", "-h", "github.com", "-s", "codespace"],
17227
17850
  { stdio: "inherit" }
@@ -17256,7 +17879,7 @@ var GitHubCodespacesProvider = class {
17256
17879
  */
17257
17880
  async getActiveGhUser() {
17258
17881
  try {
17259
- const { stdout } = await execFileP4(
17882
+ const { stdout } = await execFileP5(
17260
17883
  "gh",
17261
17884
  ["api", "user", "--jq", ".login"],
17262
17885
  { maxBuffer: MAX_BUFFER }
@@ -17276,7 +17899,7 @@ var GitHubCodespacesProvider = class {
17276
17899
  */
17277
17900
  async hasCodespaceScope() {
17278
17901
  try {
17279
- const { stdout } = await execFileP4(
17902
+ const { stdout } = await execFileP5(
17280
17903
  "gh",
17281
17904
  ["api", "-i", "user"],
17282
17905
  { maxBuffer: MAX_BUFFER }
@@ -17325,7 +17948,7 @@ var GitHubCodespacesProvider = class {
17325
17948
  let installCmd = null;
17326
17949
  if (platform2 === "darwin") {
17327
17950
  try {
17328
- await execFileP4("brew", ["--version"], { maxBuffer: MAX_BUFFER });
17951
+ await execFileP5("brew", ["--version"], { maxBuffer: MAX_BUFFER });
17329
17952
  } catch {
17330
17953
  wt(
17331
17954
  [
@@ -17344,7 +17967,7 @@ var GitHubCodespacesProvider = class {
17344
17967
  };
17345
17968
  } else if (platform2 === "win32") {
17346
17969
  try {
17347
- await execFileP4("winget", ["--version"], { maxBuffer: MAX_BUFFER });
17970
+ await execFileP5("winget", ["--version"], { maxBuffer: MAX_BUFFER });
17348
17971
  } catch {
17349
17972
  wt(
17350
17973
  [
@@ -17371,7 +17994,7 @@ var GitHubCodespacesProvider = class {
17371
17994
  O2.step(`Installing gh via ${installCmd.describe}\u2026`);
17372
17995
  resetStdinForChild();
17373
17996
  const ok = await new Promise((resolve5) => {
17374
- 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" });
17375
17998
  proc.on("exit", (code) => resolve5(code === 0));
17376
17999
  proc.on("error", () => resolve5(false));
17377
18000
  });
@@ -17398,7 +18021,7 @@ var GitHubCodespacesProvider = class {
17398
18021
  );
17399
18022
  resetStdinForChild();
17400
18023
  await new Promise((resolve5, reject) => {
17401
- const proc = (0, import_child_process15.spawn)(
18024
+ const proc = (0, import_child_process16.spawn)(
17402
18025
  "gh",
17403
18026
  ["auth", "refresh", "-h", "github.com", "-s", "repo,read:org"],
17404
18027
  { stdio: "inherit" }
@@ -17423,7 +18046,7 @@ var GitHubCodespacesProvider = class {
17423
18046
  "200"
17424
18047
  );
17425
18048
  try {
17426
- const { stdout } = await execFileP4("gh", args2, { maxBuffer: MAX_BUFFER });
18049
+ const { stdout } = await execFileP5("gh", args2, { maxBuffer: MAX_BUFFER });
17427
18050
  return JSON.parse(stdout);
17428
18051
  } catch {
17429
18052
  return [];
@@ -17432,7 +18055,7 @@ var GitHubCodespacesProvider = class {
17432
18055
  const own = await fetchRepos();
17433
18056
  let orgRepos = [];
17434
18057
  try {
17435
- const { stdout } = await execFileP4(
18058
+ const { stdout } = await execFileP5(
17436
18059
  "gh",
17437
18060
  ["api", "--paginate", "user/orgs", "--jq", ".[].login"],
17438
18061
  { maxBuffer: MAX_BUFFER }
@@ -17469,7 +18092,7 @@ var GitHubCodespacesProvider = class {
17469
18092
  */
17470
18093
  async listMachineTypes(projectId) {
17471
18094
  try {
17472
- const { stdout } = await execFileP4(
18095
+ const { stdout } = await execFileP5(
17473
18096
  "gh",
17474
18097
  ["api", `/repos/${projectId}/codespaces/machines`],
17475
18098
  { maxBuffer: MAX_BUFFER }
@@ -17500,7 +18123,7 @@ var GitHubCodespacesProvider = class {
17500
18123
  const machine = machineTypeId ?? await this.pickDefaultMachine(projectId);
17501
18124
  const args2 = ["codespace", "create", "-R", projectId, "--default-permissions"];
17502
18125
  if (machine) args2.push("-m", machine);
17503
- const { stdout } = await execFileP4(
18126
+ const { stdout } = await execFileP5(
17504
18127
  "gh",
17505
18128
  args2,
17506
18129
  { maxBuffer: MAX_BUFFER, timeout: 12e4 }
@@ -17540,7 +18163,7 @@ var GitHubCodespacesProvider = class {
17540
18163
  async waitUntilAvailable(name) {
17541
18164
  const deadline = Date.now() + 5 * 60 * 1e3;
17542
18165
  while (Date.now() < deadline) {
17543
- const { stdout } = await execFileP4(
18166
+ const { stdout } = await execFileP5(
17544
18167
  "gh",
17545
18168
  ["codespace", "list", "--json", "name,state"],
17546
18169
  { maxBuffer: MAX_BUFFER }
@@ -17558,7 +18181,7 @@ var GitHubCodespacesProvider = class {
17558
18181
  }
17559
18182
  async exec(workspaceId, command2) {
17560
18183
  try {
17561
- const { stdout, stderr } = await execFileP4(
18184
+ const { stdout, stderr } = await execFileP5(
17562
18185
  "gh",
17563
18186
  ["codespace", "ssh", "-c", workspaceId, "--", command2],
17564
18187
  { maxBuffer: MAX_BUFFER, timeout: 6e5 }
@@ -17576,7 +18199,7 @@ var GitHubCodespacesProvider = class {
17576
18199
  async streamCommand(workspaceId, command2) {
17577
18200
  resetStdinForChild();
17578
18201
  return new Promise((resolve5, reject) => {
17579
- const proc = (0, import_child_process15.spawn)(
18202
+ const proc = (0, import_child_process16.spawn)(
17580
18203
  "gh",
17581
18204
  ["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
17582
18205
  { stdio: "inherit" }
@@ -17603,11 +18226,11 @@ var GitHubCodespacesProvider = class {
17603
18226
  `mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
17604
18227
  ];
17605
18228
  await new Promise((resolve5, reject) => {
17606
- const tar = (0, import_child_process15.spawn)("tar", tarArgs, {
18229
+ const tar = (0, import_child_process16.spawn)("tar", tarArgs, {
17607
18230
  stdio: ["ignore", "pipe", "pipe"],
17608
18231
  env: tarEnv
17609
18232
  });
17610
- const ssh = (0, import_child_process15.spawn)("gh", sshArgs, {
18233
+ const ssh = (0, import_child_process16.spawn)("gh", sshArgs, {
17611
18234
  stdio: [tar.stdout, "pipe", "pipe"]
17612
18235
  });
17613
18236
  let tarErr = "";
@@ -17631,7 +18254,7 @@ var GitHubCodespacesProvider = class {
17631
18254
  });
17632
18255
  }
17633
18256
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
17634
- const remoteDir = path34.posix.dirname(remotePath);
18257
+ const remoteDir = path36.posix.dirname(remotePath);
17635
18258
  const parts = [
17636
18259
  `mkdir -p ${shellQuote(remoteDir)}`,
17637
18260
  `cat > ${shellQuote(remotePath)}`
@@ -17641,7 +18264,7 @@ var GitHubCodespacesProvider = class {
17641
18264
  }
17642
18265
  const cmd = parts.join(" && ");
17643
18266
  await new Promise((resolve5, reject) => {
17644
- const proc = (0, import_child_process15.spawn)(
18267
+ const proc = (0, import_child_process16.spawn)(
17645
18268
  "gh",
17646
18269
  ["codespace", "ssh", "-c", workspaceId, "--", cmd],
17647
18270
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -17663,7 +18286,7 @@ var GitHubCodespacesProvider = class {
17663
18286
  try {
17664
18287
  const args2 = ["codespace", "list", "--json", "name,displayName,state,lastUsedAt"];
17665
18288
  if (projectId) args2.push("--repo", projectId);
17666
- const { stdout } = await execFileP4("gh", args2, { maxBuffer: MAX_BUFFER });
18289
+ const { stdout } = await execFileP5("gh", args2, { maxBuffer: MAX_BUFFER });
17667
18290
  const list = JSON.parse(stdout);
17668
18291
  return list.map((c2) => ({
17669
18292
  id: c2.name,
@@ -17678,7 +18301,7 @@ var GitHubCodespacesProvider = class {
17678
18301
  }
17679
18302
  async startWorkspace(workspaceId) {
17680
18303
  try {
17681
- await execFileP4(
18304
+ await execFileP5(
17682
18305
  "gh",
17683
18306
  ["api", "-X", "POST", `/user/codespaces/${workspaceId}/start`],
17684
18307
  { maxBuffer: MAX_BUFFER, timeout: 6e4 }
@@ -17699,11 +18322,11 @@ function shellQuote(s) {
17699
18322
  }
17700
18323
 
17701
18324
  // src/services/providers/gitpod.ts
17702
- var import_child_process16 = require("child_process");
17703
- var import_util4 = require("util");
17704
- 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"));
17705
18328
  var import_picocolors9 = __toESM(require("picocolors"));
17706
- var execFileP5 = (0, import_util4.promisify)(import_child_process16.execFile);
18329
+ var execFileP6 = (0, import_util5.promisify)(import_child_process17.execFile);
17707
18330
  var MAX_BUFFER2 = 8 * 1024 * 1024;
17708
18331
  function resetStdinForChild2() {
17709
18332
  if (process.stdin.isTTY) {
@@ -17720,7 +18343,7 @@ var GitpodProvider = class {
17720
18343
  available = true;
17721
18344
  async authorize() {
17722
18345
  try {
17723
- await execFileP5("gitpod", ["--version"], { maxBuffer: MAX_BUFFER2 });
18346
+ await execFileP6("gitpod", ["--version"], { maxBuffer: MAX_BUFFER2 });
17724
18347
  } catch {
17725
18348
  throw new Error(
17726
18349
  [
@@ -17733,7 +18356,7 @@ var GitpodProvider = class {
17733
18356
  );
17734
18357
  }
17735
18358
  try {
17736
- await execFileP5("gitpod", ["whoami"], { maxBuffer: MAX_BUFFER2 });
18359
+ await execFileP6("gitpod", ["whoami"], { maxBuffer: MAX_BUFFER2 });
17737
18360
  return;
17738
18361
  } catch {
17739
18362
  }
@@ -17743,7 +18366,7 @@ var GitpodProvider = class {
17743
18366
  );
17744
18367
  resetStdinForChild2();
17745
18368
  await new Promise((resolve5, reject) => {
17746
- const proc = (0, import_child_process16.spawn)("gitpod", ["login"], { stdio: "inherit" });
18369
+ const proc = (0, import_child_process17.spawn)("gitpod", ["login"], { stdio: "inherit" });
17747
18370
  proc.on("exit", (code) => {
17748
18371
  if (code === 0) resolve5();
17749
18372
  else reject(new Error("gitpod login failed."));
@@ -17753,7 +18376,7 @@ var GitpodProvider = class {
17753
18376
  }
17754
18377
  async listProjects() {
17755
18378
  try {
17756
- const { stdout } = await execFileP5(
18379
+ const { stdout } = await execFileP6(
17757
18380
  "gitpod",
17758
18381
  ["workspace", "list", "--output", "json", "--limit", "200"],
17759
18382
  { maxBuffer: MAX_BUFFER2 }
@@ -17786,7 +18409,7 @@ var GitpodProvider = class {
17786
18409
  async createWorkspace(projectId, machineTypeId) {
17787
18410
  const args2 = ["workspace", "create", projectId, "--start", "--output", "json"];
17788
18411
  if (machineTypeId) args2.push("--class", machineTypeId);
17789
- const { stdout } = await execFileP5("gitpod", args2, {
18412
+ const { stdout } = await execFileP6("gitpod", args2, {
17790
18413
  maxBuffer: MAX_BUFFER2,
17791
18414
  timeout: 3e5
17792
18415
  });
@@ -17810,7 +18433,7 @@ var GitpodProvider = class {
17810
18433
  const deadline = Date.now() + 5 * 60 * 1e3;
17811
18434
  while (Date.now() < deadline) {
17812
18435
  try {
17813
- const { stdout } = await execFileP5(
18436
+ const { stdout } = await execFileP6(
17814
18437
  "gitpod",
17815
18438
  ["workspace", "get", workspaceId, "--output", "json"],
17816
18439
  { maxBuffer: MAX_BUFFER2 }
@@ -17828,7 +18451,7 @@ var GitpodProvider = class {
17828
18451
  }
17829
18452
  async listMachineTypes(_projectId) {
17830
18453
  try {
17831
- const { stdout } = await execFileP5(
18454
+ const { stdout } = await execFileP6(
17832
18455
  "gitpod",
17833
18456
  ["organization", "list-classes", "--output", "json"],
17834
18457
  { maxBuffer: MAX_BUFFER2 }
@@ -17846,7 +18469,7 @@ var GitpodProvider = class {
17846
18469
  async listExistingWorkspaces(projectId) {
17847
18470
  try {
17848
18471
  const args2 = ["workspace", "list", "--output", "json", "--limit", "200"];
17849
- const { stdout } = await execFileP5("gitpod", args2, { maxBuffer: MAX_BUFFER2 });
18472
+ const { stdout } = await execFileP6("gitpod", args2, { maxBuffer: MAX_BUFFER2 });
17850
18473
  const list = JSON.parse(stdout);
17851
18474
  return list.filter((w3) => !projectId || w3.contextUrl === projectId).map((w3) => ({
17852
18475
  id: w3.id,
@@ -17861,7 +18484,7 @@ var GitpodProvider = class {
17861
18484
  }
17862
18485
  async startWorkspace(workspaceId) {
17863
18486
  try {
17864
- await execFileP5(
18487
+ await execFileP6(
17865
18488
  "gitpod",
17866
18489
  ["workspace", "start", workspaceId],
17867
18490
  { maxBuffer: MAX_BUFFER2, timeout: 6e4 }
@@ -17877,7 +18500,7 @@ var GitpodProvider = class {
17877
18500
  }
17878
18501
  async exec(workspaceId, command2) {
17879
18502
  try {
17880
- const { stdout, stderr } = await execFileP5(
18503
+ const { stdout, stderr } = await execFileP6(
17881
18504
  "gitpod",
17882
18505
  ["workspace", "ssh", workspaceId, "--", command2],
17883
18506
  { maxBuffer: MAX_BUFFER2, timeout: 6e5 }
@@ -17895,7 +18518,7 @@ var GitpodProvider = class {
17895
18518
  async streamCommand(workspaceId, command2) {
17896
18519
  resetStdinForChild2();
17897
18520
  return new Promise((resolve5, reject) => {
17898
- const proc = (0, import_child_process16.spawn)(
18521
+ const proc = (0, import_child_process17.spawn)(
17899
18522
  "gitpod",
17900
18523
  ["workspace", "ssh", workspaceId, "--", "-tt", command2],
17901
18524
  { stdio: "inherit" }
@@ -17915,11 +18538,11 @@ var GitpodProvider = class {
17915
18538
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
17916
18539
  const remoteCmd = `mkdir -p ${shellQuote2(remoteDir)} && tar -xzf - -C ${shellQuote2(remoteDir)}`;
17917
18540
  await new Promise((resolve5, reject) => {
17918
- const tar = (0, import_child_process16.spawn)("tar", tarArgs, {
18541
+ const tar = (0, import_child_process17.spawn)("tar", tarArgs, {
17919
18542
  stdio: ["ignore", "pipe", "pipe"],
17920
18543
  env: tarEnv
17921
18544
  });
17922
- const ssh = (0, import_child_process16.spawn)(
18545
+ const ssh = (0, import_child_process17.spawn)(
17923
18546
  "gitpod",
17924
18547
  ["workspace", "ssh", workspaceId, "--", remoteCmd],
17925
18548
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -17941,7 +18564,7 @@ var GitpodProvider = class {
17941
18564
  });
17942
18565
  }
17943
18566
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
17944
- const remoteDir = path35.posix.dirname(remotePath);
18567
+ const remoteDir = path37.posix.dirname(remotePath);
17945
18568
  const parts = [
17946
18569
  `mkdir -p ${shellQuote2(remoteDir)}`,
17947
18570
  `cat > ${shellQuote2(remotePath)}`
@@ -17951,7 +18574,7 @@ var GitpodProvider = class {
17951
18574
  }
17952
18575
  const cmd = parts.join(" && ");
17953
18576
  await new Promise((resolve5, reject) => {
17954
- const proc = (0, import_child_process16.spawn)(
18577
+ const proc = (0, import_child_process17.spawn)(
17955
18578
  "gitpod",
17956
18579
  ["workspace", "ssh", workspaceId, "--", cmd],
17957
18580
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -17975,10 +18598,10 @@ function shellQuote2(s) {
17975
18598
  }
17976
18599
 
17977
18600
  // src/services/providers/gitlab-workspaces.ts
17978
- var import_child_process17 = require("child_process");
17979
- var import_util5 = require("util");
17980
- var path36 = __toESM(require("path"));
17981
- 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);
17982
18605
  var MAX_BUFFER3 = 8 * 1024 * 1024;
17983
18606
  var GITLAB_API_BASE = process.env.CODEAM_GITLAB_API_URL ?? "https://gitlab.com/api/v4";
17984
18607
  function resetStdinForChild3() {
@@ -17996,7 +18619,7 @@ var GitLabWorkspacesProvider = class {
17996
18619
  available = true;
17997
18620
  async authorize() {
17998
18621
  try {
17999
- await execFileP6("glab", ["--version"], { maxBuffer: MAX_BUFFER3 });
18622
+ await execFileP7("glab", ["--version"], { maxBuffer: MAX_BUFFER3 });
18000
18623
  } catch {
18001
18624
  throw new Error(
18002
18625
  [
@@ -18010,7 +18633,7 @@ var GitLabWorkspacesProvider = class {
18010
18633
  );
18011
18634
  }
18012
18635
  try {
18013
- await execFileP6("glab", ["auth", "status"], { maxBuffer: MAX_BUFFER3 });
18636
+ await execFileP7("glab", ["auth", "status"], { maxBuffer: MAX_BUFFER3 });
18014
18637
  return;
18015
18638
  } catch {
18016
18639
  }
@@ -18020,7 +18643,7 @@ var GitLabWorkspacesProvider = class {
18020
18643
  );
18021
18644
  resetStdinForChild3();
18022
18645
  await new Promise((resolve5, reject) => {
18023
- const proc = (0, import_child_process17.spawn)(
18646
+ const proc = (0, import_child_process18.spawn)(
18024
18647
  "glab",
18025
18648
  ["auth", "login", "--scopes", "api,read_user,read_repository"],
18026
18649
  { stdio: "inherit" }
@@ -18034,7 +18657,7 @@ var GitLabWorkspacesProvider = class {
18034
18657
  }
18035
18658
  async listProjects() {
18036
18659
  try {
18037
- const { stdout } = await execFileP6(
18660
+ const { stdout } = await execFileP7(
18038
18661
  "glab",
18039
18662
  ["repo", "list", "--per-page", "200", "--output", "json"],
18040
18663
  { maxBuffer: MAX_BUFFER3 }
@@ -18166,7 +18789,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
18166
18789
  async exec(workspaceId, command2) {
18167
18790
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
18168
18791
  try {
18169
- const { stdout, stderr } = await execFileP6(
18792
+ const { stdout, stderr } = await execFileP7(
18170
18793
  "ssh",
18171
18794
  [
18172
18795
  "-o",
@@ -18192,7 +18815,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
18192
18815
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
18193
18816
  resetStdinForChild3();
18194
18817
  return new Promise((resolve5, reject) => {
18195
- const proc = (0, import_child_process17.spawn)(
18818
+ const proc = (0, import_child_process18.spawn)(
18196
18819
  "ssh",
18197
18820
  ["-tt", "-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, command2],
18198
18821
  { stdio: "inherit" }
@@ -18213,8 +18836,8 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
18213
18836
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
18214
18837
  const remoteCmd = `mkdir -p ${shellQuote3(remoteDir)} && tar -xzf - -C ${shellQuote3(remoteDir)}`;
18215
18838
  await new Promise((resolve5, reject) => {
18216
- const tar = (0, import_child_process17.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
18217
- 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)(
18218
18841
  "ssh",
18219
18842
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, remoteCmd],
18220
18843
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -18237,14 +18860,14 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
18237
18860
  }
18238
18861
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
18239
18862
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
18240
- const remoteDir = path36.posix.dirname(remotePath);
18863
+ const remoteDir = path38.posix.dirname(remotePath);
18241
18864
  const parts = [`mkdir -p ${shellQuote3(remoteDir)}`, `cat > ${shellQuote3(remotePath)}`];
18242
18865
  if (options.mode != null) {
18243
18866
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote3(remotePath)}`);
18244
18867
  }
18245
18868
  const cmd = parts.join(" && ");
18246
18869
  await new Promise((resolve5, reject) => {
18247
- const proc = (0, import_child_process17.spawn)(
18870
+ const proc = (0, import_child_process18.spawn)(
18248
18871
  "ssh",
18249
18872
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, cmd],
18250
18873
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -18269,7 +18892,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
18269
18892
  */
18270
18893
  async getGlabToken() {
18271
18894
  try {
18272
- const { stdout, stderr } = await execFileP6(
18895
+ const { stdout, stderr } = await execFileP7(
18273
18896
  "glab",
18274
18897
  ["auth", "status", "--show-token"],
18275
18898
  { maxBuffer: MAX_BUFFER3 }
@@ -18303,10 +18926,10 @@ function shellQuote3(s) {
18303
18926
  }
18304
18927
 
18305
18928
  // src/services/providers/railway.ts
18306
- var import_child_process18 = require("child_process");
18307
- var import_util6 = require("util");
18308
- var path37 = __toESM(require("path"));
18309
- 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);
18310
18933
  var MAX_BUFFER4 = 8 * 1024 * 1024;
18311
18934
  function resetStdinForChild4() {
18312
18935
  if (process.stdin.isTTY) {
@@ -18323,7 +18946,7 @@ var RailwayProvider = class {
18323
18946
  available = true;
18324
18947
  async authorize() {
18325
18948
  try {
18326
- await execFileP7("railway", ["--version"], { maxBuffer: MAX_BUFFER4 });
18949
+ await execFileP8("railway", ["--version"], { maxBuffer: MAX_BUFFER4 });
18327
18950
  } catch {
18328
18951
  throw new Error(
18329
18952
  [
@@ -18337,7 +18960,7 @@ var RailwayProvider = class {
18337
18960
  );
18338
18961
  }
18339
18962
  try {
18340
- await execFileP7("railway", ["whoami"], { maxBuffer: MAX_BUFFER4 });
18963
+ await execFileP8("railway", ["whoami"], { maxBuffer: MAX_BUFFER4 });
18341
18964
  return;
18342
18965
  } catch {
18343
18966
  }
@@ -18347,7 +18970,7 @@ var RailwayProvider = class {
18347
18970
  );
18348
18971
  resetStdinForChild4();
18349
18972
  await new Promise((resolve5, reject) => {
18350
- const proc = (0, import_child_process18.spawn)("railway", ["login"], { stdio: "inherit" });
18973
+ const proc = (0, import_child_process19.spawn)("railway", ["login"], { stdio: "inherit" });
18351
18974
  proc.on("exit", (code) => {
18352
18975
  if (code === 0) resolve5();
18353
18976
  else reject(new Error("railway login failed."));
@@ -18357,7 +18980,7 @@ var RailwayProvider = class {
18357
18980
  }
18358
18981
  async listProjects() {
18359
18982
  try {
18360
- const { stdout } = await execFileP7(
18983
+ const { stdout } = await execFileP8(
18361
18984
  "railway",
18362
18985
  ["list", "--json"],
18363
18986
  { maxBuffer: MAX_BUFFER4 }
@@ -18373,7 +18996,7 @@ var RailwayProvider = class {
18373
18996
  }));
18374
18997
  } catch {
18375
18998
  try {
18376
- const { stdout } = await execFileP7("railway", ["list"], { maxBuffer: MAX_BUFFER4 });
18999
+ const { stdout } = await execFileP8("railway", ["list"], { maxBuffer: MAX_BUFFER4 });
18377
19000
  const projects = [];
18378
19001
  for (const line of stdout.split("\n")) {
18379
19002
  const trimmed = line.trim();
@@ -18424,7 +19047,7 @@ var RailwayProvider = class {
18424
19047
  async listExistingWorkspaces(projectId) {
18425
19048
  if (!projectId) return [];
18426
19049
  try {
18427
- const { stdout } = await execFileP7(
19050
+ const { stdout } = await execFileP8(
18428
19051
  "railway",
18429
19052
  ["service", "list", "--project", projectId, "--json"],
18430
19053
  { maxBuffer: MAX_BUFFER4 }
@@ -18449,7 +19072,7 @@ var RailwayProvider = class {
18449
19072
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
18450
19073
  }
18451
19074
  try {
18452
- await execFileP7(
19075
+ await execFileP8(
18453
19076
  "railway",
18454
19077
  ["service", "restart", "--service", serviceId, "--project", projectId],
18455
19078
  { maxBuffer: MAX_BUFFER4, timeout: 6e4 }
@@ -18468,7 +19091,7 @@ var RailwayProvider = class {
18468
19091
  };
18469
19092
  }
18470
19093
  try {
18471
- const { stdout, stderr } = await execFileP7(
19094
+ const { stdout, stderr } = await execFileP8(
18472
19095
  "railway",
18473
19096
  ["run", "--project", projectId, "--service", serviceId, "--", "bash", "-lc", command2],
18474
19097
  { maxBuffer: MAX_BUFFER4, timeout: 6e5 }
@@ -18490,7 +19113,7 @@ var RailwayProvider = class {
18490
19113
  }
18491
19114
  resetStdinForChild4();
18492
19115
  return new Promise((resolve5, reject) => {
18493
- const proc = (0, import_child_process18.spawn)(
19116
+ const proc = (0, import_child_process19.spawn)(
18494
19117
  "railway",
18495
19118
  ["shell", "--project", projectId, "--service", serviceId, "--command", command2],
18496
19119
  { stdio: "inherit" }
@@ -18514,8 +19137,8 @@ var RailwayProvider = class {
18514
19137
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
18515
19138
  const remoteCmd = `mkdir -p ${shellQuote4(remoteDir)} && tar -xzf - -C ${shellQuote4(remoteDir)}`;
18516
19139
  await new Promise((resolve5, reject) => {
18517
- const tar = (0, import_child_process18.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
18518
- 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)(
18519
19142
  "railway",
18520
19143
  ["shell", "--project", projectId, "--service", serviceId, "--command", remoteCmd],
18521
19144
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -18541,14 +19164,14 @@ var RailwayProvider = class {
18541
19164
  if (!projectId || !serviceId) {
18542
19165
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
18543
19166
  }
18544
- const remoteDir = path37.posix.dirname(remotePath);
19167
+ const remoteDir = path39.posix.dirname(remotePath);
18545
19168
  const parts = [`mkdir -p ${shellQuote4(remoteDir)}`, `cat > ${shellQuote4(remotePath)}`];
18546
19169
  if (options.mode != null) {
18547
19170
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote4(remotePath)}`);
18548
19171
  }
18549
19172
  const cmd = parts.join(" && ");
18550
19173
  await new Promise((resolve5, reject) => {
18551
- const proc = (0, import_child_process18.spawn)(
19174
+ const proc = (0, import_child_process19.spawn)(
18552
19175
  "railway",
18553
19176
  ["shell", "--project", projectId, "--service", serviceId, "--command", cmd],
18554
19177
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -19072,10 +19695,10 @@ async function probeCodeamPair(provider, workspace) {
19072
19695
  }
19073
19696
  async function stopWorkspaceFromLocal(target) {
19074
19697
  if (target.provider.id === "github-codespaces") {
19075
- const { execFile: execFile8 } = await import("child_process");
19076
- const { promisify: promisify9 } = await import("util");
19077
- const execFileP8 = promisify9(execFile8);
19078
- 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 });
19079
19702
  return;
19080
19703
  }
19081
19704
  }
@@ -19084,8 +19707,8 @@ async function stopWorkspaceFromLocal(target) {
19084
19707
  var import_node_dns = require("dns");
19085
19708
  var import_node_util4 = require("util");
19086
19709
  var import_node_crypto6 = require("crypto");
19087
- var fs29 = __toESM(require("fs"));
19088
- var path38 = __toESM(require("path"));
19710
+ var fs31 = __toESM(require("fs"));
19711
+ var path40 = __toESM(require("path"));
19089
19712
  var import_picocolors12 = __toESM(require("picocolors"));
19090
19713
  var dnsResolveP = (0, import_node_util4.promisify)(import_node_dns.resolve);
19091
19714
  async function checkDns(apiBase) {
@@ -19141,13 +19764,13 @@ async function checkHealth(apiBase) {
19141
19764
  }
19142
19765
  }
19143
19766
  function checkConfigDir() {
19144
- const dir = path38.join(require("os").homedir(), ".codeam");
19767
+ const dir = path40.join(require("os").homedir(), ".codeam");
19145
19768
  try {
19146
- fs29.mkdirSync(dir, { recursive: true, mode: 448 });
19147
- const probe = path38.join(dir, ".doctor-probe");
19148
- fs29.writeFileSync(probe, "ok", { mode: 384 });
19149
- const read = fs29.readFileSync(probe, "utf8");
19150
- 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);
19151
19774
  if (read !== "ok") throw new Error("write/read round-trip mismatch");
19152
19775
  return {
19153
19776
  id: "config-dir",
@@ -19187,9 +19810,9 @@ function checkSessions() {
19187
19810
  }
19188
19811
  }
19189
19812
  function checkAgentBinaries() {
19190
- const os26 = createOsStrategy();
19813
+ const os27 = createOsStrategy();
19191
19814
  return getEnabledAgents().map((meta) => {
19192
- const found = os26.findInPath(meta.binaryName);
19815
+ const found = os27.findInPath(meta.binaryName);
19193
19816
  return {
19194
19817
  id: `agent-${meta.id}`,
19195
19818
  label: `Agent binary: ${meta.displayName} (${meta.binaryName})`,
@@ -19211,7 +19834,7 @@ function checkNodePty() {
19211
19834
  detail: "not required on this platform"
19212
19835
  };
19213
19836
  }
19214
- const vendoredPath = path38.join(__dirname, "vendor", "node-pty");
19837
+ const vendoredPath = path40.join(__dirname, "vendor", "node-pty");
19215
19838
  for (const target of [vendoredPath, "node-pty"]) {
19216
19839
  try {
19217
19840
  require(target);
@@ -19253,7 +19876,7 @@ function checkChokidar() {
19253
19876
  }
19254
19877
  async function doctor(args2 = []) {
19255
19878
  const json = args2.includes("--json");
19256
- const cliVersion = true ? "2.24.0" : "0.0.0-dev";
19879
+ const cliVersion = true ? "2.26.0" : "0.0.0-dev";
19257
19880
  const apiBase = resolveApiBaseUrl();
19258
19881
  const diagnosticId = (0, import_node_crypto6.randomUUID)();
19259
19882
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -19452,7 +20075,7 @@ async function completion(args2) {
19452
20075
  // src/commands/version.ts
19453
20076
  var import_picocolors13 = __toESM(require("picocolors"));
19454
20077
  function version2() {
19455
- const v = true ? "2.24.0" : "unknown";
20078
+ const v = true ? "2.26.0" : "unknown";
19456
20079
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
19457
20080
  }
19458
20081
 
@@ -19580,9 +20203,9 @@ function tryShowSubcommandHelp(cmd, args2) {
19580
20203
  var _subcommandHelpKeys = Object.keys(HELPS);
19581
20204
 
19582
20205
  // src/lib/updateNotifier.ts
19583
- var fs30 = __toESM(require("fs"));
19584
- var os25 = __toESM(require("os"));
19585
- var path39 = __toESM(require("path"));
20206
+ var fs32 = __toESM(require("fs"));
20207
+ var os26 = __toESM(require("os"));
20208
+ var path41 = __toESM(require("path"));
19586
20209
  var https7 = __toESM(require("https"));
19587
20210
  var import_picocolors16 = __toESM(require("picocolors"));
19588
20211
  var PKG_NAME = "codeam-cli";
@@ -19590,12 +20213,12 @@ var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
19590
20213
  var TTL_MS = 24 * 60 * 60 * 1e3;
19591
20214
  var REQUEST_TIMEOUT_MS = 1500;
19592
20215
  function cachePath() {
19593
- const dir = path39.join(os25.homedir(), ".codeam");
19594
- return path39.join(dir, "update-check.json");
20216
+ const dir = path41.join(os26.homedir(), ".codeam");
20217
+ return path41.join(dir, "update-check.json");
19595
20218
  }
19596
20219
  function readCache() {
19597
20220
  try {
19598
- const raw = fs30.readFileSync(cachePath(), "utf8");
20221
+ const raw = fs32.readFileSync(cachePath(), "utf8");
19599
20222
  const parsed = JSON.parse(raw);
19600
20223
  if (typeof parsed.fetchedAt !== "number" || typeof parsed.latest !== "string") return null;
19601
20224
  return parsed;
@@ -19606,10 +20229,10 @@ function readCache() {
19606
20229
  function writeCache(cache) {
19607
20230
  try {
19608
20231
  const file = cachePath();
19609
- fs30.mkdirSync(path39.dirname(file), { recursive: true });
20232
+ fs32.mkdirSync(path41.dirname(file), { recursive: true });
19610
20233
  const tmp = `${file}.${process.pid}.tmp`;
19611
- fs30.writeFileSync(tmp, JSON.stringify(cache));
19612
- fs30.renameSync(tmp, file);
20234
+ fs32.writeFileSync(tmp, JSON.stringify(cache));
20235
+ fs32.renameSync(tmp, file);
19613
20236
  } catch {
19614
20237
  }
19615
20238
  }
@@ -19680,7 +20303,7 @@ function checkForUpdates() {
19680
20303
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
19681
20304
  if (process.env.CI) return;
19682
20305
  if (!process.stdout.isTTY) return;
19683
- const current = true ? "2.24.0" : null;
20306
+ const current = true ? "2.26.0" : null;
19684
20307
  if (!current) return;
19685
20308
  const cache = readCache();
19686
20309
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;