codeam-cli 2.76.20 → 2.76.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ 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.76.21] — 2026-10-10
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Refresh the manifest on a toolRouter change; stop start specs calling prod (#940)
12
+
13
+ ## [2.76.20] — 2026-10-10
14
+
15
+ ### Added
16
+
17
+ - **shared:** Own the Composer, feature-gate and compression-savings wire types (#934)
18
+
19
+ ### Fixed
20
+
21
+ - **cli:** Wake recreates a fleet box whose container is gone but volume remains (#933)
22
+ - **cli:** Provision aider credentials on self-hosted and Box deploys (#935)
23
+ - **both-plugins:** Open the command SSE on the stream host, not apiBaseUrl (#936)
24
+ - **cli:** Keep the typed blocker tag on failure bubbles in pushed history (#937)
25
+ - **cli:** Point USERPROFILE at the tmp home in the re-link credential test (#938)
26
+
7
27
  ## [2.76.19] — 2026-10-09
8
28
 
9
29
  ### Fixed
package/dist/index.js CHANGED
@@ -10617,7 +10617,7 @@ function readAnonId() {
10617
10617
  }
10618
10618
  function superProperties() {
10619
10619
  return {
10620
- cliVersion: true ? "2.76.20" : "0.0.0-dev",
10620
+ cliVersion: true ? "2.76.22" : "0.0.0-dev",
10621
10621
  nodeVersion: process.version,
10622
10622
  platform: process.platform,
10623
10623
  arch: process.arch,
@@ -10866,7 +10866,7 @@ var http = __toESM(require("http"));
10866
10866
  // package.json
10867
10867
  var package_default = {
10868
10868
  name: "codeam-cli",
10869
- version: "2.76.20",
10869
+ version: "2.76.22",
10870
10870
  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.",
10871
10871
  type: "commonjs",
10872
10872
  main: "dist/index.js",
@@ -11145,7 +11145,7 @@ async function postAiResult(input) {
11145
11145
  async function fetchNamedPreviewTunnel(input) {
11146
11146
  const res = await _transport.postJsonAuthed(
11147
11147
  `${API_BASE}/api/preview/tunnel`,
11148
- { pluginId: input.pluginId },
11148
+ { sessionId: input.sessionId, pluginId: input.pluginId },
11149
11149
  input.pluginAuthToken
11150
11150
  );
11151
11151
  const body = res;
@@ -12229,7 +12229,7 @@ var CommandRelayService = class _CommandRelayService {
12229
12229
  // fresh + clear the "CLI update available" banner after a self-update
12230
12230
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
12231
12231
  // pair/reconnect). Older backends ignore the extra field.
12232
- ..."2.76.20" ? { ideVersion: "2.76.20" } : {}
12232
+ ..."2.76.22" ? { ideVersion: "2.76.22" } : {}
12233
12233
  };
12234
12234
  try {
12235
12235
  const res = await _postJson(`${API_BASE2}/api/plugin/heartbeat`, body, this.pollSecretHeader());
@@ -23815,6 +23815,7 @@ function watchForBuildClobber(deps) {
23815
23815
  if (stopped) return;
23816
23816
  const current2 = readMarker2(markerPath5);
23817
23817
  if (current2 === lastKnownMarker) return;
23818
+ if (current2 === null) return;
23818
23819
  lastKnownMarker = current2;
23819
23820
  const count = (healRestartCounts.get(deps.sessionId) ?? 0) + 1;
23820
23821
  healRestartCounts.set(deps.sessionId, count);
@@ -24896,7 +24897,7 @@ function acquire(inst) {
24896
24897
  async function setupStandalone(ctx, rec) {
24897
24898
  const named = await resolveNamedTunnel(
24898
24899
  { sessionId: ctx.sessionId, pluginId: ctx.pluginId, pluginAuthToken: ctx.pluginAuthToken ?? "" },
24899
- (c2) => fetchNamedPreviewTunnel({ pluginId: c2.pluginId, pluginAuthToken: c2.pluginAuthToken })
24900
+ fetchNamedPreviewTunnel
24900
24901
  );
24901
24902
  if (!named || rec.cancelled) throw new Error("EXPORT_TUNNEL_UNAVAILABLE");
24902
24903
  const server = http8.createServer((req, res) => {
@@ -27273,7 +27274,7 @@ async function bringUpPublicUrl(ctx, tunnelPort) {
27273
27274
  pluginId: ctx.projectEnvAuth?.pluginId ?? "",
27274
27275
  pluginAuthToken: ctx.projectEnvAuth?.pluginAuthToken ?? ""
27275
27276
  },
27276
- (c2) => fetchNamedPreviewTunnel({ pluginId: c2.pluginId, pluginAuthToken: c2.pluginAuthToken })
27277
+ fetchNamedPreviewTunnel
27277
27278
  );
27278
27279
  const namedToken = named?.token;
27279
27280
  const namedHostname = named?.hostname;
@@ -30049,7 +30050,7 @@ ${stdout}`);
30049
30050
  }
30050
30051
  }
30051
30052
  function originPayload(payload, origin) {
30052
- if (origin !== "agent") return payload;
30053
+ if (origin === void 0 || origin === "user") return payload;
30053
30054
  return { ...payload ?? {}, origin };
30054
30055
  }
30055
30056
  async function resolvePreviewDetection(args2) {
@@ -30271,7 +30272,7 @@ async function restorePreviewAfterRestart(ctx, pluginAuthToken, deps = {}) {
30271
30272
  `restoring the preview that was serving before the restart (${detection.framework})`
30272
30273
  );
30273
30274
  void (deps.start ?? startPreviewFromDetection)(ctx, detection, pluginAuthToken, {
30274
- origin: "user"
30275
+ origin: "auto"
30275
30276
  });
30276
30277
  return true;
30277
30278
  }
@@ -30334,7 +30335,9 @@ function maybeAttachBuildHeal(ctx, pluginAuthToken) {
30334
30335
  if (!activePreviews.has(ctx.sessionId)) return;
30335
30336
  await killPreview(ctx.sessionId);
30336
30337
  await new Promise((r) => setTimeout(r, 150));
30337
- void startPreviewFromDetection(ctx, preview.detection, pluginAuthToken);
30338
+ void startPreviewFromDetection(ctx, preview.detection, pluginAuthToken, {
30339
+ origin: "auto"
30340
+ });
30338
30341
  })();
30339
30342
  },
30340
30343
  notify: (message) => {
@@ -36881,6 +36884,7 @@ var PROTOCOL_VERSION3 = 1;
36881
36884
  var FATAL_STARTUP_RE = /IneligibleTierError|UNSUPPORTED_CLIENT|no longer supported for Gemini Code Assist|not eligible for Gemini Code Assist|Error authenticating|ProjectIdRequiredError/i;
36882
36885
  var NEWSESSION_TIMEOUT_MS = 12e4;
36883
36886
  var MCP_STARTUP_TIMEOUT_MS = NEWSESSION_TIMEOUT_MS - 3e4;
36887
+ var AGENT_ENV_WITHHELD = ["PREVIEW_TUNNEL_TOKEN", "PREVIEW_TUNNEL_HOSTNAME"];
36884
36888
  var LOADSESSION_TIMEOUT_MS = 6e4;
36885
36889
  var PROMPT_IDLE_TIMEOUT_MS = 9e4;
36886
36890
  var PROMPT_ACTIVE_IDLE_TIMEOUT_MS = 6e5;
@@ -37066,6 +37070,7 @@ var AcpClient = class {
37066
37070
  ...this.opts.extraEnv ?? {},
37067
37071
  PATH: augmentedPath
37068
37072
  };
37073
+ for (const key of AGENT_ENV_WITHHELD) delete childEnv[key];
37069
37074
  log.info(
37070
37075
  "acpClient",
37071
37076
  `provider routing ${formatProviderRouting(describeProviderRouting(childEnv, { cwd }), adapter.command)}`
@@ -39383,14 +39388,11 @@ function buildOnboardingWelcome(cwd) {
39383
39388
  "- **Smart Composer** \u2014 help crafting and structuring your prompts.",
39384
39389
  "- **Team Spaces** \u2014 share this session with your team, or hand it off to someone else.",
39385
39390
  "",
39386
- "Found a bug or have feedback? Reach us at:",
39387
- // EXPLICIT markdown links, not bare URLs. Relying on the app's autolinker
39388
- // rendered the GitHub bullet as an EMPTY link (the label was consumed, the
39389
- // href never attached) — a labelled link renders correctly everywhere and
39390
- // reads better than a raw URL on a phone-width bubble.
39391
- "- [GitHub issues](https://github.com/edgar-durand/codeagent-mobile-clients/issues)",
39392
- "- [Discord community](https://discord.gg/Np2pbMrV9f)",
39393
- "",
39391
+ // ⚠️ No support links here. The first message a new user reads is the
39392
+ // invitation to start; "Found a bug? GitHub issues / Discord" pointed a
39393
+ // non-developer at a developer bug tracker before they had sent a single
39394
+ // task (replay 2026-10-04, PRO user in Malaysia, bead codeagent-sak0).
39395
+ // Help lives in the app (Profile › Help → FAQ + Contact support).
39394
39396
  `Ready when you are \u2014 try **"explain ${repo}"** or **"what should I work on first?"**`
39395
39397
  ].join("\n");
39396
39398
  }
@@ -41822,7 +41824,7 @@ var SHORT_TIMEOUT_MS = 2e4;
41822
41824
  var PREVIEW_TOOLS = [
41823
41825
  {
41824
41826
  name: "start_preview",
41825
- 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.",
41827
+ 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. Only give the user a preview link when this tool says the preview is live \u2014 after an error or a "still starting" answer there is NO working link. Reuses a preview that is already running (code changes hot-reload; never stop and start it to refresh). Usually leave the arguments out: the project is detected automatically. Pass command/args/port only if you know the exact dev command.`,
41826
41828
  inputSchema: {
41827
41829
  type: "object",
41828
41830
  properties: {
@@ -41859,7 +41861,7 @@ var PREVIEW_TOOLS = [
41859
41861
  },
41860
41862
  {
41861
41863
  name: "stop_preview",
41862
- description: "Stop the Preview (dev server + public URL). Only when the user asks, or to restart it cleanly.",
41864
+ description: "Stop the Preview (dev server + public URL). Only when the user asks. Never to apply code changes \u2014 the dev server hot-reloads them; to run a different dev command, call start_preview with that command instead.",
41863
41865
  inputSchema: { type: "object", properties: {}, additionalProperties: false }
41864
41866
  }
41865
41867
  ];
@@ -41919,12 +41921,17 @@ function describeResult(raw) {
41919
41921
  };
41920
41922
  }
41921
41923
  case "starting":
41922
- return { text: String(r.message ?? "The preview is starting."), isError: false };
41924
+ return {
41925
+ text: `${String(r.message ?? "The preview is starting.")} It is not serving yet, so there is no link to give the user until preview_status says it is running.`,
41926
+ isError: false
41927
+ };
41923
41928
  case "idle":
41924
41929
  return { text: "No preview is running.", isError: false };
41925
41930
  case "error":
41926
41931
  return {
41927
- text: `Preview failed at ${String(r.stage)}: ${String(r.message)}`,
41932
+ // The agent once answered a failed spawn with "the preview is open, here
41933
+ // is the link" (2026-10-05). Say plainly that nothing is serving.
41934
+ text: `Preview failed at ${String(r.stage)}: ${String(r.message)}. The preview is NOT running \u2014 do not give the user a preview link; tell them what failed.`,
41928
41935
  isError: true
41929
41936
  };
41930
41937
  default:
@@ -42253,6 +42260,8 @@ function mergeWithLocalMcpServers(integrationServers) {
42253
42260
  var http12 = __toESM(require("http"));
42254
42261
  var import_node_crypto14 = require("crypto");
42255
42262
  var START_WAIT_MS = 24e4;
42263
+ var MAX_AGENT_RESTARTS = 3;
42264
+ var AGENT_RESTART_WINDOW_MS = 10 * 6e4;
42256
42265
  var MAX_SELECTOR = 500;
42257
42266
  var MAX_LABEL = 80;
42258
42267
  var AgentPreviewBridge = class {
@@ -42264,6 +42273,12 @@ var AgentPreviewBridge = class {
42264
42273
  inflight = null;
42265
42274
  lastError = null;
42266
42275
  bound = null;
42276
+ /** When the agent tore the preview down, per session (see MAX_AGENT_RESTARTS). */
42277
+ restarts = /* @__PURE__ */ new Map();
42278
+ now;
42279
+ constructor(opts = {}) {
42280
+ this.now = opts.now ?? Date.now;
42281
+ }
42267
42282
  /** Start the loopback server once. `null` when it could not bind — the
42268
42283
  * preview tools are then simply absent, never a failed session. */
42269
42284
  listen() {
@@ -42390,6 +42405,7 @@ var AgentPreviewBridge = class {
42390
42405
  const ctx = this.ctx;
42391
42406
  if (!ctx?.pluginAuthToken) return { ok: false, message: "The session is still starting." };
42392
42407
  if (!activePreviews.has(ctx.sessionId)) return { ok: true, message: "No preview was running." };
42408
+ if (!this.takeRestart(ctx.sessionId)) return { ok: false, message: RESTART_CAPPED_MESSAGE };
42393
42409
  await killPreview(ctx.sessionId);
42394
42410
  resetBuildHealState(ctx.sessionId);
42395
42411
  await postPreviewEvent({
@@ -42411,7 +42427,15 @@ var AgentPreviewBridge = class {
42411
42427
  // ─── Internals ─────────────────────────────────────────────────────────
42412
42428
  async launch(ctx, pluginAuthToken, override) {
42413
42429
  this.lastError = null;
42414
- const already = serving(ctx.sessionId);
42430
+ let already = serving(ctx.sessionId);
42431
+ const wanted = override && detectionFromOverride(override);
42432
+ if (already && wanted && !sameCommand(already.detection, wanted)) {
42433
+ if (!this.takeRestart(ctx.sessionId)) {
42434
+ return { status: "error", stage: "restart_limit", message: RESTART_CAPPED_MESSAGE };
42435
+ }
42436
+ await killPreview(ctx.sessionId);
42437
+ already = null;
42438
+ }
42415
42439
  if (already) {
42416
42440
  await postPreviewEvent({
42417
42441
  sessionId: ctx.sessionId,
@@ -42458,6 +42482,21 @@ var AgentPreviewBridge = class {
42458
42482
  this.lastError = err;
42459
42483
  return { status: "error", ...err };
42460
42484
  }
42485
+ /** Records one agent-driven teardown; false once the window's cap is spent. */
42486
+ takeRestart(sessionId) {
42487
+ const now = this.now();
42488
+ const recent = (this.restarts.get(sessionId) ?? []).filter(
42489
+ (t2) => now - t2 < AGENT_RESTART_WINDOW_MS
42490
+ );
42491
+ if (recent.length >= MAX_AGENT_RESTARTS) {
42492
+ this.restarts.set(sessionId, recent);
42493
+ log.info("agent-preview", `restart cap reached for session=${sessionId}`);
42494
+ return false;
42495
+ }
42496
+ recent.push(now);
42497
+ this.restarts.set(sessionId, recent);
42498
+ return true;
42499
+ }
42461
42500
  async handle(req, res) {
42462
42501
  const reply = (code, body) => {
42463
42502
  res.writeHead(code, { "content-type": "application/json" });
@@ -42500,6 +42539,10 @@ function running(active2, reused) {
42500
42539
  inspector: Boolean(active2.inspector)
42501
42540
  };
42502
42541
  }
42542
+ var RESTART_CAPPED_MESSAGE = `The preview was already restarted ${MAX_AGENT_RESTARTS} times in the last ${AGENT_RESTART_WINDOW_MS / 6e4} minutes, so it stays as it is. Dev servers hot-reload: your code changes show up without a restart. If it is genuinely broken, tell the user what you saw.`;
42543
+ function sameCommand(a, b) {
42544
+ return a.command === b.command && a.port === b.port && JSON.stringify(a.args ?? []) === JSON.stringify(b.args ?? []);
42545
+ }
42503
42546
  function detectionFromOverride(o) {
42504
42547
  if (!o.command || !Number.isInteger(o.port) || o.port <= 0 || o.port > 65535) return null;
42505
42548
  return {
@@ -46382,7 +46425,7 @@ function sanitizeRetiredProxyConfig(homeDir2 = os62.homedir()) {
46382
46425
  // src/integrations/refresh-manifest.ts
46383
46426
  function fingerprint(m) {
46384
46427
  const norm = [...m.integrations].map((e) => ({ id: e.id, delivery: e.delivery })).sort((a, b) => a.id.localeCompare(b.id));
46385
- return JSON.stringify(norm);
46428
+ return JSON.stringify({ integrations: norm, toolRouter: m.toolRouter === true });
46386
46429
  }
46387
46430
  async function refreshIntegrationsManifest(ctx, fetchImpl = fetch, timeoutMs = 8e3) {
46388
46431
  const result = await fetchAndPersist(ctx, fetchImpl, timeoutMs);
@@ -51224,7 +51267,7 @@ async function autoUpgradeBeforeCriticalCommand() {
51224
51267
  if (process.env.NODE_ENV === "test") return;
51225
51268
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
51226
51269
  if (process.env.CI) return;
51227
- const current2 = true ? "2.76.20" : null;
51270
+ const current2 = true ? "2.76.22" : null;
51228
51271
  if (!current2) return;
51229
51272
  const cache = readCache();
51230
51273
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -51241,7 +51284,7 @@ function checkForUpdates() {
51241
51284
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
51242
51285
  if (process.env.CI) return;
51243
51286
  if (!process.stdout.isTTY) return;
51244
- const current2 = true ? "2.76.20" : null;
51287
+ const current2 = true ? "2.76.22" : null;
51245
51288
  if (!current2) return;
51246
51289
  const cache = readCache();
51247
51290
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -51523,7 +51566,7 @@ var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
51523
51566
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
51524
51567
  var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
51525
51568
  function currentCliVersion() {
51526
- return true ? "2.76.20" : null;
51569
+ return true ? "2.76.22" : null;
51527
51570
  }
51528
51571
  async function installedVersion(deps) {
51529
51572
  const ls = await deps.run(
@@ -51734,7 +51777,7 @@ function signalHostReady(send = process.send?.bind(process)) {
51734
51777
  }
51735
51778
  function defaultDeps() {
51736
51779
  return {
51737
- ownVersion: true ? "2.76.20" : null,
51780
+ ownVersion: true ? "2.76.22" : null,
51738
51781
  root: cliVersionsRoot(),
51739
51782
  args: process.argv.slice(3),
51740
51783
  execPath: process.execPath,
@@ -54245,7 +54288,7 @@ function checkChokidar() {
54245
54288
  }
54246
54289
  async function doctor(args2 = []) {
54247
54290
  const json = args2.includes("--json");
54248
- const cliVersion = true ? "2.76.20" : "0.0.0-dev";
54291
+ const cliVersion = true ? "2.76.22" : "0.0.0-dev";
54249
54292
  const apiBase2 = resolveApiBaseUrl();
54250
54293
  const diagnosticId = (0, import_node_crypto17.randomUUID)();
54251
54294
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -54781,7 +54824,7 @@ async function mcpWarm(argv = [], opts = {}) {
54781
54824
  // src/commands/version.ts
54782
54825
  var import_picocolors15 = __toESM(require("picocolors"));
54783
54826
  function version2() {
54784
- const v = true ? "2.76.20" : "unknown";
54827
+ const v = true ? "2.76.22" : "unknown";
54785
54828
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
54786
54829
  }
54787
54830
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.76.20",
3
+ "version": "2.76.22",
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",