codeam-cli 2.75.25 → 2.75.26

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 +7 -0
  2. package/dist/index.js +726 -120
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ All notable changes to `codeam-cli` are documented here.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.75.25] — 2026-09-25
8
+
9
+ ### Added
10
+
11
+ - **cli:** Send every runnable repo script with the preview detection
12
+ - **cli:** Install preview deps in the background at session start; room for monorepo builds
13
+
7
14
  ## [2.75.23] — 2026-09-25
8
15
 
9
16
  ### Fixed
package/dist/index.js CHANGED
@@ -3248,6 +3248,9 @@ var USER_EVENTS = {
3248
3248
  PREVIEW_STOPPED: "preview_stopped",
3249
3249
  PREVIEW_ERROR: "preview_error",
3250
3250
  PREVIEW_PROGRESS: "preview_progress",
3251
+ /** The agent points at an element of the running preview (`highlight_element`
3252
+ * MCP tool) — the inspector client draws the agent's mark over it. */
3253
+ PREVIEW_AGENT_HIGHLIGHT: "preview_agent_highlight",
3251
3254
  BEADS_STATE_CHANGED: "beads_state_changed",
3252
3255
  BEADS_PROVISIONING: "beads_provisioning",
3253
3256
  BEADS_TEAM_MEMORY_CHANGED: "beads_team_memory_changed",
@@ -10509,7 +10512,7 @@ function readAnonId() {
10509
10512
  }
10510
10513
  function superProperties() {
10511
10514
  return {
10512
- cliVersion: true ? "2.75.25" : "0.0.0-dev",
10515
+ cliVersion: true ? "2.75.26" : "0.0.0-dev",
10513
10516
  nodeVersion: process.version,
10514
10517
  platform: process.platform,
10515
10518
  arch: process.arch,
@@ -10751,7 +10754,7 @@ var http = __toESM(require("http"));
10751
10754
  // package.json
10752
10755
  var package_default = {
10753
10756
  name: "codeam-cli",
10754
- version: "2.75.25",
10757
+ version: "2.75.26",
10755
10758
  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.",
10756
10759
  type: "commonjs",
10757
10760
  main: "dist/index.js",
@@ -12081,7 +12084,7 @@ var CommandRelayService = class _CommandRelayService {
12081
12084
  // fresh + clear the "CLI update available" banner after a self-update
12082
12085
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
12083
12086
  // pair/reconnect). Older backends ignore the extra field.
12084
- ..."2.75.25" ? { ideVersion: "2.75.25" } : {}
12087
+ ..."2.75.26" ? { ideVersion: "2.75.26" } : {}
12085
12088
  };
12086
12089
  try {
12087
12090
  const res = await _postJson(`${API_BASE2}/api/plugin/heartbeat`, body);
@@ -24628,6 +24631,145 @@ function inspectorClientSource(opts) {
24628
24631
  redrawMarks();
24629
24632
  }
24630
24633
 
24634
+ /**
24635
+ * Las marcas del AGENTE \u2014 la herramienta highlight_element.
24636
+ *
24637
+ * Van aparte de las del usuario a proposito: no se numeran, no se pueden
24638
+ * borrar desde el composer y no dependen de que el modo inspector este
24639
+ * encendido. Son el agente SE\xD1ALANDO algo, y el usuario las ve aunque no
24640
+ * haya tocado nada: un cursor que viaja hasta el elemento, lo centra en
24641
+ * pantalla y deja una caja con su etiqueta.
24642
+ *
24643
+ * Mismo anclaje que las marcas fijadas (coordenadas de documento), asi que
24644
+ * acompa\xF1an al scroll. Un selector que no encuentra nada no pinta nada: el
24645
+ * agente ya sabe por la herramienta que puede fallar.
24646
+ */
24647
+ var AGENT = '#22d3ee';
24648
+ var agentMarks = [];
24649
+ var agentHost = null;
24650
+ var agentCursor = null;
24651
+ var agentStyle = null;
24652
+ var agentListening = false;
24653
+
24654
+ function ensureAgentStyle() {
24655
+ if (agentStyle && agentStyle.parentNode) return;
24656
+ agentStyle = document.createElement('style');
24657
+ agentStyle.setAttribute('data-codeam', 'agent-style');
24658
+ agentStyle.textContent =
24659
+ '@keyframes codeam-agent-pulse{0%{box-shadow:0 0 0 0 rgba(34,211,238,.55)}' +
24660
+ '70%{box-shadow:0 0 0 10px rgba(34,211,238,0)}100%{box-shadow:0 0 0 0 rgba(34,211,238,0)}}' +
24661
+ '@keyframes codeam-agent-in{from{opacity:0;transform:scale(1.06)}to{opacity:1;transform:scale(1)}}';
24662
+ (document.head || document.documentElement).appendChild(agentStyle);
24663
+ }
24664
+
24665
+ function ensureAgentCursor() {
24666
+ if (agentCursor && agentCursor.parentNode) return agentCursor;
24667
+ agentCursor = document.createElement('div');
24668
+ agentCursor.setAttribute('data-codeam', 'agent-cursor');
24669
+ agentCursor.style.cssText = [
24670
+ 'position:fixed', 'pointer-events:none', 'z-index:2147483647',
24671
+ 'left:' + Math.round(window.innerWidth / 2) + 'px', 'top:' + Math.round(window.innerHeight - 40) + 'px',
24672
+ 'transition:left .6s cubic-bezier(.22,1,.36,1),top .6s cubic-bezier(.22,1,.36,1),opacity .3s',
24673
+ 'opacity:0'
24674
+ ].join(';');
24675
+ agentCursor.innerHTML =
24676
+ '<svg width="22" height="22" viewBox="0 0 24 24" style="display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.5))">' +
24677
+ '<path d="M4 2l15 8.5-6.6 1.6L9 18.5z" fill="' + AGENT + '" stroke="#000" stroke-width="1.2"/></svg>' +
24678
+ '<div style="margin:2px 0 0 14px;background:' + AGENT + ';color:#000;font:700 10px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;' +
24679
+ 'padding:1px 6px;border-radius:8px;white-space:nowrap">Agent</div>';
24680
+ document.body.appendChild(agentCursor);
24681
+ return agentCursor;
24682
+ }
24683
+
24684
+ function redrawAgentMarks() {
24685
+ if (agentMarks.length === 0) {
24686
+ if (agentHost && agentHost.parentNode) agentHost.parentNode.removeChild(agentHost);
24687
+ agentHost = null;
24688
+ return;
24689
+ }
24690
+ if (!agentHost || !agentHost.parentNode) {
24691
+ agentHost = document.createElement('div');
24692
+ agentHost.setAttribute('data-codeam', 'agent-marks');
24693
+ agentHost.style.cssText = 'position:absolute;left:0;top:0;width:0;height:0;z-index:2147483646';
24694
+ document.body.appendChild(agentHost);
24695
+ }
24696
+ agentHost.innerHTML = '';
24697
+ var sx = window.scrollX || window.pageXOffset || 0;
24698
+ var sy = window.scrollY || window.pageYOffset || 0;
24699
+ for (var i = 0; i < agentMarks.length; i++) {
24700
+ var m = agentMarks[i];
24701
+ if (!m.el.isConnected) continue;
24702
+ var r = m.el.getBoundingClientRect();
24703
+ var box = document.createElement('div');
24704
+ box.setAttribute('data-codeam', 'agent-mark');
24705
+ box.style.cssText = [
24706
+ 'position:absolute', 'pointer-events:none', 'box-sizing:border-box',
24707
+ 'border:2px solid ' + AGENT, 'background:rgba(34,211,238,0.10)', 'border-radius:4px',
24708
+ 'left:' + (r.left + sx - 3) + 'px', 'top:' + (r.top + sy - 3) + 'px',
24709
+ 'width:' + (r.width + 6) + 'px', 'height:' + (r.height + 6) + 'px',
24710
+ 'animation:codeam-agent-in .25s ease-out,codeam-agent-pulse 1.6s ease-out 3'
24711
+ ].join(';');
24712
+ if (m.label) {
24713
+ var tag = document.createElement('div');
24714
+ tag.setAttribute('data-codeam', 'agent-mark-label');
24715
+ tag.style.cssText = [
24716
+ 'position:absolute', 'left:-2px', (r.top >= 22 ? 'top:-22px' : 'bottom:-22px'),
24717
+ 'background:' + AGENT, 'color:#000', 'font:700 11px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace',
24718
+ 'padding:0 6px', 'border-radius:3px', 'white-space:nowrap', 'max-width:260px',
24719
+ 'overflow:hidden', 'text-overflow:ellipsis'
24720
+ ].join(';');
24721
+ tag.textContent = m.label;
24722
+ box.appendChild(tag);
24723
+ }
24724
+ agentHost.appendChild(box);
24725
+ }
24726
+ }
24727
+
24728
+ function listenAgentLayout() {
24729
+ if (agentListening) return;
24730
+ agentListening = true;
24731
+ window.addEventListener('scroll', redrawAgentMarks, true);
24732
+ window.addEventListener('resize', redrawAgentMarks);
24733
+ }
24734
+
24735
+ function agentHighlight(selector, labelText) {
24736
+ var el = null;
24737
+ try { el = document.querySelector(String(selector)); } catch (e) { el = null; }
24738
+ if (!el || !el.getBoundingClientRect) return false;
24739
+ ensureAgentStyle();
24740
+ listenAgentLayout();
24741
+ try { el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' }); } catch (e) { /* viejo */ }
24742
+ var cursor = ensureAgentCursor();
24743
+ // Tras el scroll suave: el cursor viaja al centro del elemento y, al
24744
+ // llegar, aparece la caja. El orden es lo que se lee como \xABel agente
24745
+ // lo esta se\xF1alando\xBB y no como un recuadro que salta.
24746
+ setTimeout(function () {
24747
+ var r = el.getBoundingClientRect();
24748
+ cursor.style.opacity = '1';
24749
+ cursor.style.left = Math.round(r.left + Math.min(r.width / 2, 40)) + 'px';
24750
+ cursor.style.top = Math.round(r.top + Math.min(r.height / 2, 24)) + 'px';
24751
+ setTimeout(function () {
24752
+ agentMarks.push({ el: el, label: labelText ? String(labelText).slice(0, 80) : '' });
24753
+ redrawAgentMarks();
24754
+ }, 620);
24755
+ }, 380);
24756
+ return true;
24757
+ }
24758
+
24759
+ function agentClear() {
24760
+ agentMarks = [];
24761
+ redrawAgentMarks();
24762
+ if (agentCursor && agentCursor.parentNode) agentCursor.parentNode.removeChild(agentCursor);
24763
+ agentCursor = null;
24764
+ if (agentStyle && agentStyle.parentNode) agentStyle.parentNode.removeChild(agentStyle);
24765
+ agentStyle = null;
24766
+ if (agentListening) {
24767
+ window.removeEventListener('scroll', redrawAgentMarks, true);
24768
+ window.removeEventListener('resize', redrawAgentMarks);
24769
+ agentListening = false;
24770
+ }
24771
+ }
24772
+
24631
24773
  function enable(origin) {
24632
24774
  if (enabled) return;
24633
24775
  enabled = true;
@@ -24661,6 +24803,8 @@ function inspectorClientSource(opts) {
24661
24803
  if (data.type === 'enable') enable(e.origin);
24662
24804
  else if (data.type === 'disable') disable();
24663
24805
  else if (data.type === 'marks' && data.ids) syncMarks(data.ids);
24806
+ else if (data.type === 'agent-highlight' && data.selector) agentHighlight(data.selector, data.label);
24807
+ else if (data.type === 'agent-clear') agentClear();
24664
24808
  });
24665
24809
 
24666
24810
  /**
@@ -24679,6 +24823,11 @@ function inspectorClientSource(opts) {
24679
24823
  window.__codeamInspector = {
24680
24824
  enable: function () { enable('react-native'); },
24681
24825
  disable: disable,
24826
+ // La app movil ya llamaba \`marks\` al borrar un chip y no existia:
24827
+ // la marca se quedaba pintada en la pagina.
24828
+ marks: function (ids) { if (ids && ids.length !== undefined) syncMarks(ids); },
24829
+ highlight: function (selector, text) { return agentHighlight(selector, text); },
24830
+ clear: agentClear,
24682
24831
  };
24683
24832
  }
24684
24833
  } catch (e) {
@@ -26518,9 +26667,9 @@ async function configureBeads(action, ctx, deps) {
26518
26667
  return { enabled: false, running: false };
26519
26668
  }
26520
26669
  const p3 = await deps.probe();
26521
- const running2 = p3.serverUp && p3.bdAvailable;
26522
- deps.emit({ type: "beads_status", state: running2 ? "enabled" : "disabled", running: running2, ...p3 });
26523
- return { enabled: running2, running: running2, ...p3 };
26670
+ const running3 = p3.serverUp && p3.bdAvailable;
26671
+ deps.emit({ type: "beads_status", state: running3 ? "enabled" : "disabled", running: running3, ...p3 });
26672
+ return { enabled: running3, running: running3, ...p3 };
26524
26673
  }
26525
26674
  if (action === "disable") {
26526
26675
  deps.persist({ enabled: false });
@@ -26537,9 +26686,9 @@ async function configureBeads(action, ctx, deps) {
26537
26686
  return { enabled: false, ...p2 };
26538
26687
  }
26539
26688
  await deps.startWatcher().catch(() => void 0);
26540
- const running = true;
26541
- deps.emit({ type: "beads_status", state: "enabled", running, ...p2 });
26542
- return { enabled: true, running, ...p2 };
26689
+ const running2 = true;
26690
+ deps.emit({ type: "beads_status", state: "enabled", running: running2, ...p2 });
26691
+ return { enabled: true, running: running2, ...p2 };
26543
26692
  }
26544
26693
 
26545
26694
  // src/skills/configure.ts
@@ -27759,105 +27908,88 @@ async function runDetectOneShot(generate, timeoutMs = PREVIEW_DETECT_TIMEOUT_MS)
27759
27908
  if (timer) clearTimeout(timer);
27760
27909
  }
27761
27910
  }
27762
- var requestPreviewDetectH = (ctx) => {
27763
- if (!ctx.pluginAuthToken) {
27764
- log.info("preview", "no pluginAuthToken \u2014 skipping detect");
27765
- return;
27766
- }
27767
- if (typeof ctx.runtime.generateOneShot !== "function") {
27768
- log.info("preview", `runtime ${ctx.runtime.id} has no generateOneShot \u2014 emitting unsupported`);
27911
+ function originPayload(payload, origin) {
27912
+ if (origin !== "agent") return payload;
27913
+ return { ...payload ?? {}, origin };
27914
+ }
27915
+ async function resolvePreviewDetection(args2) {
27916
+ const { ctx, runtime, pluginAuthToken, origin } = args2;
27917
+ const emit2 = (type, payload) => {
27769
27918
  emitPreviewEvent({
27770
27919
  sessionId: ctx.sessionId,
27771
27920
  pluginId: ctx.pluginId,
27772
- pluginAuthToken: ctx.pluginAuthToken,
27773
- type: USER_EVENTS.PREVIEW_ERROR,
27774
- payload: {
27775
- stage: "detection",
27776
- message: `Preview detection isn't available on ${ctx.runtime.id} sessions yet \u2014 link a Claude or Codex agent.`
27777
- }
27921
+ pluginAuthToken,
27922
+ type,
27923
+ payload: originPayload(payload, origin)
27778
27924
  });
27779
- return;
27925
+ };
27926
+ if (typeof runtime.generateOneShot !== "function") {
27927
+ log.info("preview", `runtime ${runtime.id} has no generateOneShot \u2014 emitting unsupported`);
27928
+ emit2(USER_EVENTS.PREVIEW_ERROR, {
27929
+ stage: "detection",
27930
+ message: `Preview detection isn't available on ${runtime.id} sessions yet \u2014 link a Claude or Codex agent.`
27931
+ });
27932
+ return null;
27780
27933
  }
27781
- const pluginAuthToken = ctx.pluginAuthToken;
27782
- const generateOneShot = ctx.runtime.generateOneShot.bind(ctx.runtime);
27783
- void (async () => {
27784
- const fromFile = await readPreviewConfig(process.cwd());
27785
- if (fromFile) {
27786
- log.info("preview", `detect: using .codeam/preview.json (${fromFile.framework})`);
27787
- emitPreviewEvent({
27788
- sessionId: ctx.sessionId,
27789
- pluginId: ctx.pluginId,
27790
- pluginAuthToken,
27791
- type: USER_EVENTS.PREVIEW_DETECTION_READY,
27792
- payload: { detection: withScriptCandidates(fromFile) }
27793
- });
27794
- return;
27795
- }
27796
- emitPreviewEvent({
27797
- sessionId: ctx.sessionId,
27798
- pluginId: ctx.pluginId,
27799
- pluginAuthToken,
27800
- type: USER_EVENTS.PREVIEW_DETECTION_PENDING
27934
+ const generateOneShot = runtime.generateOneShot.bind(runtime);
27935
+ const fromFile = await readPreviewConfig(process.cwd());
27936
+ if (fromFile) {
27937
+ log.info("preview", `detect: using .codeam/preview.json (${fromFile.framework})`);
27938
+ return fromFile;
27939
+ }
27940
+ emit2(USER_EVENTS.PREVIEW_DETECTION_PENDING);
27941
+ log.info("preview", "detect: invoking generateOneShot");
27942
+ const startedAt = Date.now();
27943
+ const { raw, timedOut, stderr } = await runDetectOneShot(generateOneShot);
27944
+ const tookMs = Date.now() - startedAt;
27945
+ if (timedOut) {
27946
+ log.info("preview", `detect: timed out after ${tookMs}ms \u2014 emitting preview_error`);
27947
+ emit2(USER_EVENTS.PREVIEW_ERROR, {
27948
+ stage: "detection",
27949
+ message: `Project detection timed out after ${Math.round(PREVIEW_DETECT_TIMEOUT_MS / 1e3)} s \u2014 the agent did not answer. Try again, or add a .codeam/preview.json override.`
27801
27950
  });
27802
- log.info("preview", "detect: invoking generateOneShot");
27803
- const startedAt = Date.now();
27804
- const { raw, timedOut, stderr } = await runDetectOneShot(generateOneShot);
27805
- const tookMs = Date.now() - startedAt;
27806
- if (timedOut) {
27807
- log.info("preview", `detect: timed out after ${tookMs}ms \u2014 emitting preview_error`);
27808
- emitPreviewEvent({
27809
- sessionId: ctx.sessionId,
27810
- pluginId: ctx.pluginId,
27811
- pluginAuthToken,
27812
- type: USER_EVENTS.PREVIEW_ERROR,
27813
- payload: {
27814
- stage: "detection",
27815
- message: `Project detection timed out after ${Math.round(PREVIEW_DETECT_TIMEOUT_MS / 1e3)} s \u2014 the agent did not answer. Try again, or add a .codeam/preview.json override.`
27816
- }
27817
- });
27818
- return;
27819
- }
27820
- const detection = safeParseDetection(raw);
27821
- if (!detection) {
27822
- const failure = describeDetectionFailure(raw);
27823
- log.info(
27824
- "preview",
27825
- `detect: failed after ${tookMs}ms reason=${failure?.reason ?? "unknown"}` + (failure?.missing ? ` missing=${failure.missing.join(",")}` : "") + `
27951
+ return null;
27952
+ }
27953
+ const detection = safeParseDetection(raw);
27954
+ if (!detection) {
27955
+ const failure = describeDetectionFailure(raw);
27956
+ log.info(
27957
+ "preview",
27958
+ `detect: failed after ${tookMs}ms reason=${failure?.reason ?? "unknown"}` + (failure?.missing ? ` missing=${failure.missing.join(",")}` : "") + `
27826
27959
  --- salida cruda del agente (acotada) ---
27827
27960
  ${failure?.rawExcerpt ?? ""}`
27828
- );
27829
- emitPreviewEvent({
27830
- sessionId: ctx.sessionId,
27831
- pluginId: ctx.pluginId,
27832
- pluginAuthToken,
27833
- type: USER_EVENTS.PREVIEW_ERROR,
27834
- payload: {
27835
- stage: "detection",
27836
- // El mensaje sale del diagnostico: decir "JSON invalido" cuando el
27837
- // agente no contesto manda al usuario a mirar un JSON que no existe.
27838
- message: (failure?.reason === "no_output" ? describeOneShotAgentError(stderr) : null) ?? failure?.message ?? "Agent returned invalid JSON. Try again, or add a .codeam/preview.json override."
27839
- }
27840
- });
27841
- return;
27842
- }
27843
- if (isUnsupportedDetection(detection)) {
27844
- log.info("preview", "detect: framework=unsupported");
27845
- emitPreviewEvent({
27846
- sessionId: ctx.sessionId,
27847
- pluginId: ctx.pluginId,
27848
- pluginAuthToken,
27849
- type: USER_EVENTS.PREVIEW_ERROR,
27850
- payload: {
27851
- stage: "unsupported",
27852
- message: detection.notes ?? "No dev server applies to this project."
27853
- }
27854
- });
27855
- return;
27856
- }
27857
- log.info("preview", `detect: ${detection.framework} on :${detection.port} (took ${tookMs}ms)`);
27858
- void writePreviewConfig(process.cwd(), detection).catch((err) => {
27859
- log.info("preview", `detect: writePreviewConfig failed (non-fatal): ${String(err)}`);
27961
+ );
27962
+ emit2(USER_EVENTS.PREVIEW_ERROR, {
27963
+ stage: "detection",
27964
+ // El mensaje sale del diagnostico: decir "JSON invalido" cuando el
27965
+ // agente no contesto manda al usuario a mirar un JSON que no existe.
27966
+ message: (failure?.reason === "no_output" ? describeOneShotAgentError(stderr) : null) ?? failure?.message ?? "Agent returned invalid JSON. Try again, or add a .codeam/preview.json override."
27860
27967
  });
27968
+ return null;
27969
+ }
27970
+ if (isUnsupportedDetection(detection)) {
27971
+ log.info("preview", "detect: framework=unsupported");
27972
+ emit2(USER_EVENTS.PREVIEW_ERROR, {
27973
+ stage: "unsupported",
27974
+ message: detection.notes ?? "No dev server applies to this project."
27975
+ });
27976
+ return null;
27977
+ }
27978
+ log.info("preview", `detect: ${detection.framework} on :${detection.port} (took ${tookMs}ms)`);
27979
+ void writePreviewConfig(process.cwd(), detection).catch((err) => {
27980
+ log.info("preview", `detect: writePreviewConfig failed (non-fatal): ${String(err)}`);
27981
+ });
27982
+ return detection;
27983
+ }
27984
+ var requestPreviewDetectH = (ctx) => {
27985
+ if (!ctx.pluginAuthToken) {
27986
+ log.info("preview", "no pluginAuthToken \u2014 skipping detect");
27987
+ return;
27988
+ }
27989
+ const pluginAuthToken = ctx.pluginAuthToken;
27990
+ void (async () => {
27991
+ const detection = await resolvePreviewDetection({ ctx, runtime: ctx.runtime, pluginAuthToken });
27992
+ if (!detection) return;
27861
27993
  emitPreviewEvent({
27862
27994
  sessionId: ctx.sessionId,
27863
27995
  pluginId: ctx.pluginId,
@@ -27940,19 +28072,21 @@ var previewStartH = (ctx, _cmd, parsed) => {
27940
28072
  log.info("preview", "start: no detection in payload");
27941
28073
  return;
27942
28074
  }
27943
- startPreviewFromDetection(ctx, rawDetection, ctx.pluginAuthToken);
28075
+ void startPreviewFromDetection(ctx, rawDetection, ctx.pluginAuthToken);
27944
28076
  };
27945
- function startPreviewFromDetection(ctx, detection, pluginAuthToken) {
28077
+ function startPreviewFromDetection(ctx, detection, pluginAuthToken, opts = {}) {
27946
28078
  const emit2 = (type, payload) => {
28079
+ const tagged = originPayload(payload, opts.origin) ?? {};
28080
+ opts.onEvent?.(type, tagged);
27947
28081
  emitPreviewEvent({
27948
28082
  sessionId: ctx.sessionId,
27949
28083
  pluginId: ctx.pluginId,
27950
28084
  pluginAuthToken,
27951
28085
  type,
27952
- payload
28086
+ payload: tagged
27953
28087
  });
27954
28088
  };
27955
- runPreviewStart({
28089
+ return runPreviewStart({
27956
28090
  sessionId: ctx.sessionId,
27957
28091
  detection,
27958
28092
  cwd: process.cwd(),
@@ -27983,7 +28117,7 @@ function maybeAttachBuildHeal(ctx, pluginAuthToken) {
27983
28117
  if (!activePreviews.has(ctx.sessionId)) return;
27984
28118
  await killPreview(ctx.sessionId);
27985
28119
  await new Promise((r) => setTimeout(r, 150));
27986
- startPreviewFromDetection(ctx, preview.detection, pluginAuthToken);
28120
+ void startPreviewFromDetection(ctx, preview.detection, pluginAuthToken);
27987
28121
  })();
27988
28122
  },
27989
28123
  notify: (message) => {
@@ -28029,7 +28163,7 @@ var previewRestartH = async (ctx, cmd) => {
28029
28163
  }
28030
28164
  await killPreview(ctx.sessionId);
28031
28165
  await new Promise((r) => setTimeout(r, 150));
28032
- startPreviewFromDetection(ctx, preview.detection, ctx.pluginAuthToken);
28166
+ void startPreviewFromDetection(ctx, preview.detection, ctx.pluginAuthToken);
28033
28167
  await ctx.relay.sendResult(cmd.id, "completed", { restarted: true });
28034
28168
  };
28035
28169
  var savePreviewConfigH = (_ctx, _cmd, parsed) => {
@@ -29339,7 +29473,7 @@ async function resolveAcpAdapterWithRetry(agent, opts = {}) {
29339
29473
  }
29340
29474
 
29341
29475
  // src/agents/acp/runner.ts
29342
- var import_node_crypto10 = require("crypto");
29476
+ var import_node_crypto11 = require("crypto");
29343
29477
  var fs69 = __toESM(require("fs"));
29344
29478
  var os53 = __toESM(require("os"));
29345
29479
  var path80 = __toESM(require("path"));
@@ -39345,8 +39479,175 @@ async function mcpRouter() {
39345
39479
  });
39346
39480
  }
39347
39481
 
39482
+ // src/commands/preview-mcp.ts
39483
+ var PREVIEW_MCP_SERVER_NAME = "codeagent_preview";
39484
+ var PREVIEW_IPC_URL_ENV = "CODEAM_PREVIEW_IPC_URL";
39485
+ var PREVIEW_IPC_TOKEN_ENV = "CODEAM_PREVIEW_IPC_TOKEN";
39486
+ var START_TIMEOUT_MS = 27e4;
39487
+ var SHORT_TIMEOUT_MS = 2e4;
39488
+ var PREVIEW_TOOLS = [
39489
+ {
39490
+ name: "start_preview",
39491
+ description: "Open this project's live Preview in the user's CodeAgent app \u2014 the same thing as their Preview button: it installs dependencies if needed, starts the dev server, publishes it on a public URL and switches the user's screen to it. Use it after you change anything visual (UI, styles, layout, copy) so you and the user can check the result, or whenever the user asks to see the app. Returns the public URL (you can curl it) or the exact error. Reuses a preview that is already running. Usually leave the arguments out: the project is detected automatically. Pass command/args/port only if you know the exact dev command.",
39492
+ inputSchema: {
39493
+ type: "object",
39494
+ properties: {
39495
+ command: { type: "string", description: 'Dev server executable, e.g. "npm". Optional.' },
39496
+ args: { type: "array", items: { type: "string" }, description: 'e.g. ["run","dev"]. Optional.' },
39497
+ port: { type: "number", description: "Port the dev server listens on. Required with command." },
39498
+ framework: { type: "string", description: 'Label shown to the user, e.g. "next". Optional.' }
39499
+ },
39500
+ additionalProperties: false
39501
+ }
39502
+ },
39503
+ {
39504
+ name: "preview_status",
39505
+ description: "Current state of the Preview: running (with its URL), starting, error (with the reason) or idle. Use it when start_preview answered that the preview is still starting.",
39506
+ inputSchema: { type: "object", properties: {}, additionalProperties: false }
39507
+ },
39508
+ {
39509
+ name: "highlight_element",
39510
+ description: 'Point at an element in the running Preview: the user sees your cursor move to it and a labelled box drawn around it, scrolled into view. Use it to show the user exactly what you changed or what you are talking about ("this button", "the header spacing"). Takes a CSS selector (prefer ids, data-testid or specific classes you saw in the code). Several calls add several marks; clear_highlights removes them.',
39511
+ inputSchema: {
39512
+ type: "object",
39513
+ properties: {
39514
+ selector: { type: "string", description: 'CSS selector, e.g. "#checkout" or "[data-testid=hero-cta]".' },
39515
+ label: { type: "string", description: 'Short caption on the mark, e.g. "New CTA". Optional.' }
39516
+ },
39517
+ required: ["selector"],
39518
+ additionalProperties: false
39519
+ }
39520
+ },
39521
+ {
39522
+ name: "clear_highlights",
39523
+ description: "Remove every mark you drew in the Preview with highlight_element.",
39524
+ inputSchema: { type: "object", properties: {}, additionalProperties: false }
39525
+ },
39526
+ {
39527
+ name: "stop_preview",
39528
+ description: "Stop the Preview (dev server + public URL). Only when the user asks, or to restart it cleanly.",
39529
+ inputSchema: { type: "object", properties: {}, additionalProperties: false }
39530
+ }
39531
+ ];
39532
+ function previewMcpServer(bridge) {
39533
+ if (!bridge) return null;
39534
+ return {
39535
+ name: PREVIEW_MCP_SERVER_NAME,
39536
+ // Same runtime + entrypoint as this CLI — never trust the agent's PATH.
39537
+ command: process.execPath,
39538
+ args: [process.argv[1], "preview-mcp"],
39539
+ env: [
39540
+ { name: PREVIEW_IPC_URL_ENV, value: bridge.url },
39541
+ { name: PREVIEW_IPC_TOKEN_ENV, value: bridge.token }
39542
+ ]
39543
+ };
39544
+ }
39545
+ function httpCall(baseUrl, token) {
39546
+ return async (path96, method, body, timeoutMs) => {
39547
+ const ac = new AbortController();
39548
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
39549
+ try {
39550
+ const res = await fetch(`${baseUrl}${path96}`, {
39551
+ method,
39552
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
39553
+ ...method === "POST" ? { body: JSON.stringify(body ?? {}) } : {},
39554
+ signal: ac.signal
39555
+ });
39556
+ return await res.json();
39557
+ } finally {
39558
+ clearTimeout(timer);
39559
+ }
39560
+ };
39561
+ }
39562
+ async function runPreviewTool(name, args2, call) {
39563
+ switch (name) {
39564
+ case "start_preview":
39565
+ return describeResult(await call("/preview/start", "POST", args2, START_TIMEOUT_MS));
39566
+ case "preview_status":
39567
+ return describeResult(await call("/preview/status", "GET", void 0, SHORT_TIMEOUT_MS));
39568
+ case "highlight_element":
39569
+ return describeAck(await call("/preview/highlight", "POST", args2, SHORT_TIMEOUT_MS));
39570
+ case "clear_highlights":
39571
+ return describeAck(await call("/preview/highlight", "POST", { clear: true }, SHORT_TIMEOUT_MS));
39572
+ case "stop_preview":
39573
+ return describeAck(await call("/preview/stop", "POST", {}, SHORT_TIMEOUT_MS));
39574
+ default:
39575
+ return { text: `Unknown tool: ${name}`, isError: true };
39576
+ }
39577
+ }
39578
+ function describeResult(raw) {
39579
+ const r = raw && typeof raw === "object" ? raw : {};
39580
+ switch (r.status) {
39581
+ case "running": {
39582
+ const inspector = r.inspector ? " You can point at elements with highlight_element." : " (No element highlighting for this framework.)";
39583
+ return {
39584
+ text: `Preview ${r.reused ? "already running" : "is live"}: ${String(r.url)} (${String(r.framework)}). The user's app is showing it now.${inspector}`,
39585
+ isError: false
39586
+ };
39587
+ }
39588
+ case "starting":
39589
+ return { text: String(r.message ?? "The preview is starting."), isError: false };
39590
+ case "idle":
39591
+ return { text: "No preview is running.", isError: false };
39592
+ case "error":
39593
+ return {
39594
+ text: `Preview failed at ${String(r.stage)}: ${String(r.message)}`,
39595
+ isError: true
39596
+ };
39597
+ default:
39598
+ return { text: `Unexpected answer from the preview bridge: ${JSON.stringify(raw)}`, isError: true };
39599
+ }
39600
+ }
39601
+ function describeAck(raw) {
39602
+ const r = raw && typeof raw === "object" ? raw : {};
39603
+ return { text: String(r.message ?? r.error ?? "done"), isError: r.ok !== true };
39604
+ }
39605
+ async function previewMcp() {
39606
+ const url2 = process.env[PREVIEW_IPC_URL_ENV];
39607
+ const token = process.env[PREVIEW_IPC_TOKEN_ENV];
39608
+ const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
39609
+ const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
39610
+ const { ListToolsRequestSchema, CallToolRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
39611
+ const call = url2 && token ? httpCall(url2, token) : null;
39612
+ const server = new Server({ name: PREVIEW_MCP_SERVER_NAME, version: "1.0.0" }, { capabilities: { tools: {} } });
39613
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: PREVIEW_TOOLS }));
39614
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
39615
+ if (!call) {
39616
+ return {
39617
+ content: [{ type: "text", text: "Preview is not available in this session." }],
39618
+ isError: true
39619
+ };
39620
+ }
39621
+ try {
39622
+ const { text, isError: isError2 } = await runPreviewTool(
39623
+ req.params.name,
39624
+ req.params.arguments ?? {},
39625
+ call
39626
+ );
39627
+ return { content: [{ type: "text", text }], isError: isError2 };
39628
+ } catch (err) {
39629
+ const aborted = err instanceof Error && err.name === "AbortError";
39630
+ return {
39631
+ content: [
39632
+ {
39633
+ type: "text",
39634
+ text: aborted ? "The preview is taking long \u2014 it keeps going in the background; call preview_status shortly." : `Preview bridge unreachable: ${err instanceof Error ? err.message : String(err)}`
39635
+ }
39636
+ ],
39637
+ isError: !aborted
39638
+ };
39639
+ }
39640
+ });
39641
+ await server.connect(new StdioServerTransport());
39642
+ }
39643
+
39348
39644
  // src/integrations/provision.ts
39349
39645
  function buildMcpServersForStart(ctx) {
39646
+ const servers = buildIntegrationMcpServers(ctx);
39647
+ const preview = previewMcpServer(ctx.preview ?? null);
39648
+ return preview ? [...servers, preview] : servers;
39649
+ }
39650
+ function buildIntegrationMcpServers(ctx) {
39350
39651
  const manifest = readIntegrationsManifest();
39351
39652
  if (!manifest || manifest.integrations.length === 0) return [];
39352
39653
  if (!ctx.pluginAuthToken) {
@@ -39518,6 +39819,299 @@ function mergeWithLocalMcpServers(integrationServers) {
39518
39819
  return merged;
39519
39820
  }
39520
39821
 
39822
+ // src/commands/start/agent-preview-bridge.ts
39823
+ var http11 = __toESM(require("http"));
39824
+ var import_node_crypto10 = require("crypto");
39825
+ var START_WAIT_MS = 24e4;
39826
+ var MAX_SELECTOR = 500;
39827
+ var MAX_LABEL = 80;
39828
+ var AgentPreviewBridge = class {
39829
+ server = null;
39830
+ listening = null;
39831
+ token = (0, import_node_crypto10.randomBytes)(24).toString("hex");
39832
+ ctx = null;
39833
+ /** The bring-up in flight, so two tool calls never start two dev servers. */
39834
+ inflight = null;
39835
+ lastError = null;
39836
+ bound = null;
39837
+ /** Start the loopback server once. `null` when it could not bind — the
39838
+ * preview tools are then simply absent, never a failed session. */
39839
+ listen() {
39840
+ if (this.listening) return this.listening;
39841
+ this.listening = new Promise((resolve12) => {
39842
+ const server = http11.createServer((req, res) => {
39843
+ void this.handle(req, res);
39844
+ });
39845
+ server.on("error", (err) => {
39846
+ log.warn("agent-preview", `bridge failed to listen: ${String(err)}`);
39847
+ resolve12(null);
39848
+ });
39849
+ server.listen(0, "127.0.0.1", () => {
39850
+ const addr = server.address();
39851
+ if (!addr || typeof addr === "string") {
39852
+ resolve12(null);
39853
+ return;
39854
+ }
39855
+ server.unref();
39856
+ this.server = server;
39857
+ this.bound = { url: `http://127.0.0.1:${addr.port}`, token: this.token };
39858
+ resolve12(this.bound);
39859
+ });
39860
+ });
39861
+ return this.listening;
39862
+ }
39863
+ /** The bound address once {@link listen} resolved, else null (sync, for
39864
+ * the MCP list rebuilds that happen mid-session). */
39865
+ address() {
39866
+ return this.bound;
39867
+ }
39868
+ /** Bind the live session. Tool calls before this answer "still starting". */
39869
+ attach(ctx) {
39870
+ this.ctx = ctx;
39871
+ }
39872
+ close() {
39873
+ this.server?.close();
39874
+ this.server = null;
39875
+ }
39876
+ // ─── Operations (public for tests) ─────────────────────────────────────
39877
+ async start(override, waitMs = START_WAIT_MS) {
39878
+ const ctx = this.ctx;
39879
+ if (!ctx?.pluginAuthToken) {
39880
+ return {
39881
+ status: "error",
39882
+ stage: "session",
39883
+ message: "The session is still starting (or has no pairing token) \u2014 try again in a few seconds."
39884
+ };
39885
+ }
39886
+ if (!this.inflight) {
39887
+ const run = this.launch(ctx, ctx.pluginAuthToken, override);
39888
+ this.inflight = run;
39889
+ void run.finally(() => {
39890
+ if (this.inflight === run) this.inflight = null;
39891
+ });
39892
+ }
39893
+ const inflight = this.inflight;
39894
+ let timer;
39895
+ const pending = new Promise((resolve12) => {
39896
+ timer = setTimeout(
39897
+ () => resolve12({
39898
+ status: "starting",
39899
+ message: "The preview is still coming up (dependencies or a slow dev server). It keeps going in the background \u2014 call preview_status in a minute."
39900
+ }),
39901
+ waitMs
39902
+ );
39903
+ timer.unref?.();
39904
+ });
39905
+ try {
39906
+ return await Promise.race([inflight, pending]);
39907
+ } finally {
39908
+ if (timer) clearTimeout(timer);
39909
+ }
39910
+ }
39911
+ status() {
39912
+ const ctx = this.ctx;
39913
+ if (!ctx) return { status: "idle" };
39914
+ const active2 = serving(ctx.sessionId);
39915
+ if (active2) return running(active2, true);
39916
+ if (this.inflight) return { status: "starting", message: "The preview is starting." };
39917
+ if (this.lastError) return { status: "error", ...this.lastError };
39918
+ return { status: "idle" };
39919
+ }
39920
+ async highlight(input) {
39921
+ const ctx = this.ctx;
39922
+ if (!ctx?.pluginAuthToken) return { ok: false, message: "The session is still starting." };
39923
+ const active2 = serving(ctx.sessionId);
39924
+ if (!active2) {
39925
+ return { ok: false, message: "No preview is running \u2014 call start_preview first." };
39926
+ }
39927
+ if (!active2.inspector) {
39928
+ return {
39929
+ ok: false,
39930
+ message: `Element highlighting needs the web inspector, which ${active2.framework} previews don't have.`
39931
+ };
39932
+ }
39933
+ let payload;
39934
+ if (input.clear === true) {
39935
+ payload = { clear: true };
39936
+ } else {
39937
+ const selector = typeof input.selector === "string" ? input.selector.trim() : "";
39938
+ if (!selector || selector.length > MAX_SELECTOR) {
39939
+ return { ok: false, message: `selector must be a CSS selector of 1-${MAX_SELECTOR} characters.` };
39940
+ }
39941
+ const label = typeof input.label === "string" ? input.label.trim().slice(0, MAX_LABEL) : "";
39942
+ payload = label ? { selector, label } : { selector };
39943
+ }
39944
+ const res = await postPreviewEvent({
39945
+ sessionId: ctx.sessionId,
39946
+ pluginId: ctx.pluginId,
39947
+ pluginAuthToken: ctx.pluginAuthToken,
39948
+ type: USER_EVENTS.PREVIEW_AGENT_HIGHLIGHT,
39949
+ payload
39950
+ });
39951
+ if (!res.ok) {
39952
+ return { ok: false, message: `Could not reach the app (${res.status}): ${res.message}` };
39953
+ }
39954
+ return {
39955
+ ok: true,
39956
+ message: payload.clear === true ? "Cleared your marks from the preview." : `Marked ${String(payload.selector)} in the user's preview. If the selector matches nothing, no mark appears.`
39957
+ };
39958
+ }
39959
+ async stop() {
39960
+ const ctx = this.ctx;
39961
+ if (!ctx?.pluginAuthToken) return { ok: false, message: "The session is still starting." };
39962
+ if (!activePreviews.has(ctx.sessionId)) return { ok: true, message: "No preview was running." };
39963
+ await killPreview(ctx.sessionId);
39964
+ resetBuildHealState(ctx.sessionId);
39965
+ await postPreviewEvent({
39966
+ sessionId: ctx.sessionId,
39967
+ pluginId: ctx.pluginId,
39968
+ pluginAuthToken: ctx.pluginAuthToken,
39969
+ type: USER_EVENTS.PREVIEW_STOPPED,
39970
+ payload: { reason: "user", origin: "agent" }
39971
+ });
39972
+ return { ok: true, message: "Preview stopped." };
39973
+ }
39974
+ // ─── Internals ─────────────────────────────────────────────────────────
39975
+ async launch(ctx, pluginAuthToken, override) {
39976
+ this.lastError = null;
39977
+ const already = serving(ctx.sessionId);
39978
+ if (already) {
39979
+ await postPreviewEvent({
39980
+ sessionId: ctx.sessionId,
39981
+ pluginId: ctx.pluginId,
39982
+ pluginAuthToken,
39983
+ type: USER_EVENTS.PREVIEW_READY,
39984
+ payload: {
39985
+ url: already.url,
39986
+ framework: already.framework,
39987
+ port: already.detection.port,
39988
+ origin: "agent"
39989
+ }
39990
+ });
39991
+ return running(already, true);
39992
+ }
39993
+ void postPreviewEvent({
39994
+ sessionId: ctx.sessionId,
39995
+ pluginId: ctx.pluginId,
39996
+ pluginAuthToken,
39997
+ type: USER_EVENTS.PREVIEW_DETECTION_PENDING,
39998
+ payload: { origin: "agent" }
39999
+ });
40000
+ const detection = (override && detectionFromOverride(override)) ?? await resolvePreviewDetection({ ctx, runtime: ctx.getRuntime(), pluginAuthToken, origin: "agent" });
40001
+ if (!detection) {
40002
+ const err2 = { stage: "detection", message: "Could not work out how to start this project." };
40003
+ this.lastError = err2;
40004
+ return { status: "error", ...err2 };
40005
+ }
40006
+ let failure = null;
40007
+ await startPreviewFromDetection(ctx, detection, pluginAuthToken, {
40008
+ origin: "agent",
40009
+ onEvent: (type, payload) => {
40010
+ if (type === USER_EVENTS.PREVIEW_ERROR) {
40011
+ failure = {
40012
+ stage: String(payload.stage ?? "spawn"),
40013
+ message: String(payload.message ?? "The preview failed to start.")
40014
+ };
40015
+ }
40016
+ }
40017
+ });
40018
+ const up = serving(ctx.sessionId);
40019
+ if (up) return running(up, false);
40020
+ const err = failure ?? { stage: "spawn", message: "The preview did not come up." };
40021
+ this.lastError = err;
40022
+ return { status: "error", ...err };
40023
+ }
40024
+ async handle(req, res) {
40025
+ const reply = (code, body) => {
40026
+ res.writeHead(code, { "content-type": "application/json" });
40027
+ res.end(JSON.stringify(body));
40028
+ };
40029
+ if (!authorized(req.headers.authorization, this.token)) return reply(401, { error: "unauthorized" });
40030
+ try {
40031
+ const body = req.method === "POST" ? await readJson(req) : {};
40032
+ switch (`${req.method} ${req.url}`) {
40033
+ case "POST /preview/start":
40034
+ return reply(200, await this.start(overrideFrom(body)));
40035
+ case "GET /preview/status":
40036
+ return reply(200, this.status());
40037
+ case "POST /preview/highlight":
40038
+ return reply(200, await this.highlight(body));
40039
+ case "POST /preview/stop":
40040
+ return reply(200, await this.stop());
40041
+ default:
40042
+ return reply(404, { error: "not found" });
40043
+ }
40044
+ } catch (err) {
40045
+ log.warn("agent-preview", `bridge request failed: ${String(err)}`);
40046
+ return reply(500, { error: err instanceof Error ? err.message : String(err) });
40047
+ }
40048
+ }
40049
+ };
40050
+ function serving(sessionId) {
40051
+ const active2 = activePreviews.get(sessionId);
40052
+ if (!active2 || active2.devServer && active2.devServer.exitCode !== null) return null;
40053
+ return active2;
40054
+ }
40055
+ function running(active2, reused) {
40056
+ return {
40057
+ status: "running",
40058
+ url: active2.url,
40059
+ framework: active2.framework,
40060
+ ...reused ? { reused: true } : {},
40061
+ inspector: Boolean(active2.inspector)
40062
+ };
40063
+ }
40064
+ function detectionFromOverride(o) {
40065
+ if (!o.command || !Number.isInteger(o.port) || o.port <= 0 || o.port > 65535) return null;
40066
+ return {
40067
+ framework: o.framework?.trim() || "custom",
40068
+ command: o.command,
40069
+ args: Array.isArray(o.args) ? o.args.map(String) : [],
40070
+ port: o.port,
40071
+ // Any Local/ready/listening line; the TCP probe is the real fallback.
40072
+ ready_pattern: o.ready_pattern?.trim() || "(ready|local:|listening|started server|compiled)"
40073
+ };
40074
+ }
40075
+ function overrideFrom(body) {
40076
+ if (typeof body.command !== "string" || typeof body.port !== "number") return void 0;
40077
+ return {
40078
+ command: body.command,
40079
+ args: Array.isArray(body.args) ? body.args.map(String) : void 0,
40080
+ port: body.port,
40081
+ framework: typeof body.framework === "string" ? body.framework : void 0,
40082
+ ready_pattern: typeof body.ready_pattern === "string" ? body.ready_pattern : void 0
40083
+ };
40084
+ }
40085
+ function authorized(header, token) {
40086
+ const presented = Buffer.from((header ?? "").replace(/^Bearer\s+/i, ""));
40087
+ const expected = Buffer.from(token);
40088
+ return presented.length === expected.length && (0, import_node_crypto10.timingSafeEqual)(presented, expected);
40089
+ }
40090
+ function readJson(req) {
40091
+ return new Promise((resolve12, reject) => {
40092
+ let raw = "";
40093
+ req.setEncoding("utf8");
40094
+ req.on("data", (chunk) => {
40095
+ raw += chunk;
40096
+ if (raw.length > 64e3) {
40097
+ reject(new Error("body too large"));
40098
+ req.destroy();
40099
+ }
40100
+ });
40101
+ req.on("end", () => {
40102
+ if (!raw) return resolve12({});
40103
+ try {
40104
+ const parsed = JSON.parse(raw);
40105
+ resolve12(parsed && typeof parsed === "object" ? parsed : {});
40106
+ } catch {
40107
+ reject(new Error("invalid JSON"));
40108
+ }
40109
+ });
40110
+ req.on("error", reject);
40111
+ });
40112
+ }
40113
+ var agentPreviewBridge = new AgentPreviewBridge();
40114
+
39521
40115
  // src/integrations/detect-stack.ts
39522
40116
  var import_node_fs13 = __toESM(require("fs"));
39523
40117
  var import_node_path13 = __toESM(require("path"));
@@ -40658,7 +41252,8 @@ async function integrationsSyncH(ctx) {
40658
41252
  sessionId: opts.sessionId,
40659
41253
  pluginId: opts.pluginId,
40660
41254
  pluginAuthToken: opts.pluginAuthToken,
40661
- pollSecret: opts.pollSecret
41255
+ pollSecret: opts.pollSecret,
41256
+ preview: agentPreviewBridge.address()
40662
41257
  })
40663
41258
  );
40664
41259
  const applied = await client3.reprovisionMcp(servers);
@@ -41295,7 +41890,7 @@ var AcpHistory = class {
41295
41890
  this.summary = trimmed.length > 120 ? trimmed.slice(0, 117) + "\u2026" : trimmed;
41296
41891
  }
41297
41892
  this.messages.push({
41298
- id: (0, import_node_crypto10.randomUUID)(),
41893
+ id: (0, import_node_crypto11.randomUUID)(),
41299
41894
  role: "user",
41300
41895
  text,
41301
41896
  timestamp: Date.now(),
@@ -41311,7 +41906,7 @@ var AcpHistory = class {
41311
41906
  appendAgentReply(text, agentId = this.opts.agent) {
41312
41907
  if (text.length === 0) return;
41313
41908
  this.messages.push({
41314
- id: (0, import_node_crypto10.randomUUID)(),
41909
+ id: (0, import_node_crypto11.randomUUID)(),
41315
41910
  role: "agent",
41316
41911
  text,
41317
41912
  timestamp: Date.now(),
@@ -44265,12 +44860,20 @@ async function start(requestedAgent, presetSession) {
44265
44860
  agent: isHouseProxyEnv(process.env) ? houseRailWireId(process.env) : session.agent
44266
44861
  });
44267
44862
  }
44863
+ const previewBridge = await agentPreviewBridge.listen();
44864
+ agentPreviewBridge.attach({
44865
+ sessionId: session.id,
44866
+ pluginId,
44867
+ pluginAuthToken: session.pluginAuthToken ?? void 0,
44868
+ getRuntime: () => createInteractiveAgentStrategy(session.agent, createOsStrategy())
44869
+ });
44268
44870
  const mcpServers = mergeWithLocalMcpServers(
44269
44871
  buildMcpServersForStart({
44270
44872
  sessionId: session.id,
44271
44873
  pluginId,
44272
44874
  pluginAuthToken: session.pluginAuthToken ?? void 0,
44273
- pollSecret: session.pollSecret
44875
+ pollSecret: session.pollSecret,
44876
+ preview: previewBridge
44274
44877
  })
44275
44878
  );
44276
44879
  provisionSkillsForStart();
@@ -46853,7 +47456,7 @@ async function stopWorkspaceFromLocal(target) {
46853
47456
  var fs73 = __toESM(require("fs"));
46854
47457
  var os57 = __toESM(require("os"));
46855
47458
  var path87 = __toESM(require("path"));
46856
- var import_node_crypto11 = require("crypto");
47459
+ var import_node_crypto12 = require("crypto");
46857
47460
  function sampleCpuTimes() {
46858
47461
  let idle = 0;
46859
47462
  let total = 0;
@@ -47005,8 +47608,8 @@ function resolveHostLabel(label) {
47005
47608
  return resolved.slice(0, 80);
47006
47609
  }
47007
47610
  async function redeemEnrollToken(token, label) {
47008
- const controlPollSecret = (0, import_node_crypto11.randomBytes)(32).toString("base64url");
47009
- const pluginSecretHash = (0, import_node_crypto11.createHash)("sha256").update(controlPollSecret).digest("hex");
47611
+ const controlPollSecret = (0, import_node_crypto12.randomBytes)(32).toString("base64url");
47612
+ const pluginSecretHash = (0, import_node_crypto12.createHash)("sha256").update(controlPollSecret).digest("hex");
47010
47613
  const data = await postJson("/api/self-hosted/redeem", {
47011
47614
  token,
47012
47615
  label: resolveHostLabel(label),
@@ -47865,7 +48468,7 @@ async function autoUpgradeBeforeCriticalCommand() {
47865
48468
  if (process.env.NODE_ENV === "test") return;
47866
48469
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
47867
48470
  if (process.env.CI) return;
47868
- const current2 = true ? "2.75.25" : null;
48471
+ const current2 = true ? "2.75.26" : null;
47869
48472
  if (!current2) return;
47870
48473
  const cache = readCache();
47871
48474
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -47882,7 +48485,7 @@ function checkForUpdates() {
47882
48485
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
47883
48486
  if (process.env.CI) return;
47884
48487
  if (!process.stdout.isTTY) return;
47885
- const current2 = true ? "2.75.25" : null;
48488
+ const current2 = true ? "2.75.26" : null;
47886
48489
  if (!current2) return;
47887
48490
  const cache = readCache();
47888
48491
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -47905,7 +48508,7 @@ var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
47905
48508
  var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
47906
48509
  var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
47907
48510
  function currentCliVersion() {
47908
- return true ? "2.75.25" : null;
48511
+ return true ? "2.75.26" : null;
47909
48512
  }
47910
48513
  async function installedVersion(deps) {
47911
48514
  const ls = await deps.run(
@@ -49621,7 +50224,7 @@ async function invite() {
49621
50224
  // src/commands/doctor.ts
49622
50225
  var import_node_dns = require("dns");
49623
50226
  var import_node_util6 = require("util");
49624
- var import_node_crypto12 = require("crypto");
50227
+ var import_node_crypto13 = require("crypto");
49625
50228
  var fs79 = __toESM(require("fs"));
49626
50229
  var path93 = __toESM(require("path"));
49627
50230
  var import_picocolors14 = __toESM(require("picocolors"));
@@ -49791,9 +50394,9 @@ function checkChokidar() {
49791
50394
  }
49792
50395
  async function doctor(args2 = []) {
49793
50396
  const json = args2.includes("--json");
49794
- const cliVersion = true ? "2.75.25" : "0.0.0-dev";
50397
+ const cliVersion = true ? "2.75.26" : "0.0.0-dev";
49795
50398
  const apiBase2 = resolveApiBaseUrl();
49796
- const diagnosticId = (0, import_node_crypto12.randomUUID)();
50399
+ const diagnosticId = (0, import_node_crypto13.randomUUID)();
49797
50400
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
49798
50401
  const [dns, health] = await Promise.all([
49799
50402
  checkDns(apiBase2),
@@ -50315,7 +50918,7 @@ async function mcpWarm(argv = [], opts = {}) {
50315
50918
  // src/commands/version.ts
50316
50919
  var import_picocolors15 = __toESM(require("picocolors"));
50317
50920
  function version2() {
50318
- const v = true ? "2.75.25" : "unknown";
50921
+ const v = true ? "2.75.26" : "unknown";
50319
50922
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
50320
50923
  }
50321
50924
 
@@ -50516,6 +51119,9 @@ async function main() {
50516
51119
  if (command === "mcp-run") {
50517
51120
  return mcpRun(args);
50518
51121
  }
51122
+ if (command === "preview-mcp") {
51123
+ return previewMcp();
51124
+ }
50519
51125
  if (command === "mcp-warm") {
50520
51126
  await mcpWarm(args);
50521
51127
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.75.25",
3
+ "version": "2.75.26",
4
4
  "description": "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device — async. The terminal companion for CodeAgent Mobile.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",