codeam-cli 2.47.1 → 2.48.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/index.js +149 -23
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@ 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.48.0] — 2026-06-26
8
+
9
+ ### Added
10
+
11
+ - **shared:** EnvVar wire type for env-config
12
+ - **cli:** Dotenv parse/serialize util for env-config
13
+ - **cli:** Accept env-config vars[] in startCommandSchema
14
+ - **cli:** Env_read handler
15
+ - **cli:** Env_write handler (validate + atomic write)
16
+ - **cli:** Preview_restart handler (kill + re-spawn from stored detection)
17
+
18
+ ### Changed
19
+
20
+ - **cli:** Store detection on ActivePreview + extract startPreviewFromDetection
21
+
22
+ ## [2.47.1] — 2026-06-26
23
+
24
+ ### Chore
25
+
26
+ - **plugins:** Cloud-fallback follow-ups — test await, validated narrowing, repoSlug+learnMoreUrl tests, path-preserving probe URL, detached-HEAD parity, drop vestigial param (#393)
27
+
7
28
  ## [2.47.0] — 2026-06-26
8
29
 
9
30
  ### Added
package/dist/index.js CHANGED
@@ -1093,12 +1093,12 @@ var PromiseQueue = class {
1093
1093
  return promise;
1094
1094
  }
1095
1095
  async join() {
1096
- let promises2 = Object.values(this.promiseByIds);
1097
- let length = promises2.length;
1096
+ let promises3 = Object.values(this.promiseByIds);
1097
+ let length = promises3.length;
1098
1098
  while (length > 0) {
1099
- await Promise.all(promises2);
1100
- promises2 = Object.values(this.promiseByIds);
1101
- length = promises2.length;
1099
+ await Promise.all(promises3);
1100
+ promises3 = Object.values(this.promiseByIds);
1101
+ length = promises3.length;
1102
1102
  }
1103
1103
  }
1104
1104
  get length() {
@@ -1436,8 +1436,8 @@ function safeSetTimeout(fn, timeout) {
1436
1436
  return t2;
1437
1437
  }
1438
1438
  var isError = (x) => x instanceof Error;
1439
- function allSettled(promises2) {
1440
- return Promise.all(promises2.map((p2) => (p2 ?? Promise.resolve()).then((value) => ({
1439
+ function allSettled(promises3) {
1440
+ return Promise.all(promises3.map((p2) => (p2 ?? Promise.resolve()).then((value) => ({
1441
1441
  status: "fulfilled",
1442
1442
  value
1443
1443
  }), (reason) => ({
@@ -5397,7 +5397,7 @@ function readAnonId() {
5397
5397
  }
5398
5398
  function superProperties() {
5399
5399
  return {
5400
- cliVersion: true ? "2.47.1" : "0.0.0-dev",
5400
+ cliVersion: true ? "2.48.1" : "0.0.0-dev",
5401
5401
  nodeVersion: process.version,
5402
5402
  platform: process.platform,
5403
5403
  arch: process.arch,
@@ -5578,7 +5578,7 @@ var os4 = __toESM(require("os"));
5578
5578
  // package.json
5579
5579
  var package_default = {
5580
5580
  name: "codeam-cli",
5581
- version: "2.47.1",
5581
+ version: "2.48.1",
5582
5582
  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.",
5583
5583
  type: "commonjs",
5584
5584
  main: "dist/index.js",
@@ -7817,7 +7817,16 @@ var startCommandSchema = import_zod.z.object({
7817
7817
  })
7818
7818
  ).max(32).optional(),
7819
7819
  notes: import_zod.z.string().max(4096).nullable().optional()
7820
- }).optional()
7820
+ }).optional(),
7821
+ // `env_write` carries the full desired set of environment variables
7822
+ // for the project `.env`. Bounded so a malformed payload can't flood
7823
+ // the disk-side serializer. `env_read` / `preview_restart` send no payload.
7824
+ vars: import_zod.z.array(
7825
+ import_zod.z.object({
7826
+ key: import_zod.z.string().min(1).max(256),
7827
+ value: import_zod.z.string().max(32768)
7828
+ })
7829
+ ).max(512).optional()
7821
7830
  });
7822
7831
  function parsePayload2(schema, raw) {
7823
7832
  const result = schema.safeParse(raw);
@@ -13991,6 +14000,47 @@ async function writePreviewConfig(cwd, detection) {
13991
14000
  await import_promises3.default.writeFile(filePath, JSON.stringify(detection, null, 2) + "\n", "utf-8");
13992
14001
  }
13993
14002
 
14003
+ // src/services/preview/dotenv.ts
14004
+ var ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
14005
+ function parseDotenv(raw) {
14006
+ const order = [];
14007
+ const map = /* @__PURE__ */ new Map();
14008
+ for (const rawLine of raw.split(/\r?\n/)) {
14009
+ const line = rawLine.trim();
14010
+ if (line === "" || line.startsWith("#")) continue;
14011
+ const body = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
14012
+ const eq = body.indexOf("=");
14013
+ if (eq <= 0) continue;
14014
+ const key = body.slice(0, eq).trim();
14015
+ if (!ENV_KEY_RE.test(key)) continue;
14016
+ let value = body.slice(eq + 1).trim();
14017
+ value = unquote(value);
14018
+ if (!map.has(key)) order.push(key);
14019
+ map.set(key, value);
14020
+ }
14021
+ return order.map((key) => ({ key, value: map.get(key) }));
14022
+ }
14023
+ function unquote(value) {
14024
+ if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
14025
+ return value.slice(1, -1).replace(/\\n/g, "\n").replace(/\\"/g, '"');
14026
+ }
14027
+ if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
14028
+ return value.slice(1, -1);
14029
+ }
14030
+ return value;
14031
+ }
14032
+ function serializeDotenv(vars) {
14033
+ const lines = vars.map(({ key, value }) => `${key}=${quoteIfNeeded(value)}`);
14034
+ return `# Managed by CodeAgent
14035
+ ${lines.join("\n")}${lines.length ? "\n" : ""}`;
14036
+ }
14037
+ function quoteIfNeeded(value) {
14038
+ if (/[\s#=]/.test(value) || value.includes("\n")) {
14039
+ return `"${value.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
14040
+ }
14041
+ return value;
14042
+ }
14043
+
13994
14044
  // src/services/preview/parser.ts
13995
14045
  var REQUIRED_FIELDS2 = [
13996
14046
  "framework",
@@ -15917,6 +15967,51 @@ var listFiles = async (ctx, cmd, parsed) => {
15917
15967
  const result = await listProjectFiles({ query: parsed.query });
15918
15968
  await ctx.relay.sendResult(cmd.id, "completed", result);
15919
15969
  };
15970
+ var envReadH = async (ctx, cmd) => {
15971
+ const envPath = path40.join(process.cwd(), ".env");
15972
+ try {
15973
+ const raw = await fs34.promises.readFile(envPath, "utf8");
15974
+ await ctx.relay.sendResult(cmd.id, "completed", {
15975
+ exists: true,
15976
+ vars: parseDotenv(raw)
15977
+ });
15978
+ } catch (err) {
15979
+ if (err.code === "ENOENT") {
15980
+ await ctx.relay.sendResult(cmd.id, "completed", { exists: false, vars: [] });
15981
+ return;
15982
+ }
15983
+ await ctx.relay.sendResult(cmd.id, "failed", { error: err.message });
15984
+ }
15985
+ };
15986
+ var envWriteH = async (ctx, cmd, parsed) => {
15987
+ const vars = parsed.vars;
15988
+ if (!Array.isArray(vars)) {
15989
+ await ctx.relay.sendResult(cmd.id, "failed", { error: "Missing vars" });
15990
+ return;
15991
+ }
15992
+ const seen = /* @__PURE__ */ new Set();
15993
+ for (const v of vars) {
15994
+ if (!ENV_KEY_RE.test(v.key)) {
15995
+ await ctx.relay.sendResult(cmd.id, "failed", { error: `Invalid key: ${v.key}` });
15996
+ return;
15997
+ }
15998
+ if (seen.has(v.key)) {
15999
+ await ctx.relay.sendResult(cmd.id, "failed", { error: `Duplicate key: ${v.key}` });
16000
+ return;
16001
+ }
16002
+ seen.add(v.key);
16003
+ }
16004
+ const envPath = path40.join(process.cwd(), ".env");
16005
+ const tmpPath = path40.join(process.cwd(), ".env.codeam.tmp");
16006
+ try {
16007
+ await fs34.promises.writeFile(tmpPath, serializeDotenv(vars), "utf8");
16008
+ await fs34.promises.rename(tmpPath, envPath);
16009
+ await ctx.relay.sendResult(cmd.id, "completed", { ok: true, count: vars.length });
16010
+ } catch (err) {
16011
+ await fs34.promises.rm(tmpPath, { force: true }).catch(() => void 0);
16012
+ await ctx.relay.sendResult(cmd.id, "failed", { error: err.message });
16013
+ }
16014
+ };
15920
16015
  var terminalOpenH = async (ctx, cmd, parsed) => {
15921
16016
  const r = openTerminal({
15922
16017
  cols: typeof parsed.cols === "number" ? parsed.cols : void 0,
@@ -16372,8 +16467,9 @@ var previewStartH = (ctx, _cmd, parsed) => {
16372
16467
  log.info("preview", "start: no detection in payload");
16373
16468
  return;
16374
16469
  }
16375
- const detection = rawDetection;
16376
- const pluginAuthToken = ctx.pluginAuthToken;
16470
+ startPreviewFromDetection(ctx, rawDetection, ctx.pluginAuthToken);
16471
+ };
16472
+ function startPreviewFromDetection(ctx, detection, pluginAuthToken) {
16377
16473
  const emitProgress = (step, message) => {
16378
16474
  void postPreviewEvent({
16379
16475
  sessionId: ctx.sessionId,
@@ -16791,7 +16887,8 @@ var previewStartH = (ctx, _cmd, parsed) => {
16791
16887
  devServer,
16792
16888
  tunnel,
16793
16889
  url,
16794
- framework: detection.framework
16890
+ framework: detection.framework,
16891
+ detection
16795
16892
  });
16796
16893
  log.info("preview", `ready: ${detection.framework} at ${url}`);
16797
16894
  void postPreviewEvent({
@@ -16812,7 +16909,7 @@ var previewStartH = (ctx, _cmd, parsed) => {
16812
16909
  payload: { stage: "spawn", message: `Preview failed to start: ${message}` }
16813
16910
  });
16814
16911
  });
16815
- };
16912
+ }
16816
16913
  var previewStopH = (ctx) => {
16817
16914
  if (!ctx.pluginAuthToken) {
16818
16915
  log.info("preview", "no pluginAuthToken \u2014 skipping stop");
@@ -16831,6 +16928,21 @@ var previewStopH = (ctx) => {
16831
16928
  });
16832
16929
  })();
16833
16930
  };
16931
+ var previewRestartH = async (ctx, cmd) => {
16932
+ if (!ctx.pluginAuthToken) {
16933
+ await ctx.relay.sendResult(cmd.id, "completed", { restarted: false });
16934
+ return;
16935
+ }
16936
+ const preview = activePreviews.get(ctx.sessionId);
16937
+ if (!preview) {
16938
+ await ctx.relay.sendResult(cmd.id, "completed", { restarted: false });
16939
+ return;
16940
+ }
16941
+ await killPreview(ctx.sessionId);
16942
+ await new Promise((r) => setTimeout(r, 150));
16943
+ startPreviewFromDetection(ctx, preview.detection, ctx.pluginAuthToken);
16944
+ await ctx.relay.sendResult(cmd.id, "completed", { restarted: true });
16945
+ };
16834
16946
  var savePreviewConfigH = (_ctx, _cmd, parsed) => {
16835
16947
  const detection = parsed.detection;
16836
16948
  if (!detection) {
@@ -16880,7 +16992,10 @@ var handlers = {
16880
16992
  request_preview_detect: requestPreviewDetectH,
16881
16993
  preview_start: previewStartH,
16882
16994
  preview_stop: previewStopH,
16883
- save_preview_config: savePreviewConfigH
16995
+ preview_restart: previewRestartH,
16996
+ save_preview_config: savePreviewConfigH,
16997
+ env_read: envReadH,
16998
+ env_write: envWriteH
16884
16999
  };
16885
17000
  async function dispatchCommand(ctx, cmd) {
16886
17001
  if (cmd.type === "beads_action") {
@@ -18022,7 +18137,7 @@ async function autoUpgradeBeforeCriticalCommand() {
18022
18137
  if (process.env.NODE_ENV === "test") return;
18023
18138
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
18024
18139
  if (process.env.CI) return;
18025
- const current = true ? "2.47.1" : null;
18140
+ const current = true ? "2.48.1" : null;
18026
18141
  if (!current) return;
18027
18142
  const cache = readCache();
18028
18143
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -18039,7 +18154,7 @@ function checkForUpdates() {
18039
18154
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
18040
18155
  if (process.env.CI) return;
18041
18156
  if (!process.stdout.isTTY) return;
18042
- const current = true ? "2.47.1" : null;
18157
+ const current = true ? "2.48.1" : null;
18043
18158
  if (!current) return;
18044
18159
  const cache = readCache();
18045
18160
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -18481,7 +18596,7 @@ var defaultSpawner = (env, cwd, args2 = []) => (0, import_node_child_process14.s
18481
18596
  detached: false
18482
18597
  });
18483
18598
  function currentCliVersion() {
18484
- return true ? "2.47.1" : null;
18599
+ return true ? "2.48.1" : null;
18485
18600
  }
18486
18601
  function runCmd(cmd, args2, timeoutMs) {
18487
18602
  return new Promise((resolve7) => {
@@ -24947,6 +25062,11 @@ var StreamingState = class {
24947
25062
  */
24948
25063
  async closeWithBubble(bubble) {
24949
25064
  this.text = "";
25065
+ for (const [chunkId, chunk] of this.streamingChunks) {
25066
+ if (chunk.kind === "text") {
25067
+ this.streamingChunks.set(chunkId, { kind: "text", content: bubble });
25068
+ }
25069
+ }
24950
25070
  await Promise.all([
24951
25071
  this.publisher.publishOutput({ type: "text", content: bubble, done: true }),
24952
25072
  this.flushStreamingChunks()
@@ -25620,10 +25740,11 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
25620
25740
  }
25621
25741
  } catch (err) {
25622
25742
  const hadText = streaming.getCurrentText().trim().length > 0;
25623
- await recoverFromFailedTurn(client2, streaming);
25624
25743
  const detail = describeError(err);
25625
25744
  log.warn("acpRunner", `prompt failed: ${detail}`);
25745
+ await cancelStuckTurn(client2);
25626
25746
  if (shouldOfferOneMRecovery({ detail, recentStderr: recentStderr.join("\n"), finalText: "" })) {
25747
+ await streaming.closeAll();
25627
25748
  await oneMRecovery.offer(cmd.id, blocks);
25628
25749
  return;
25629
25750
  }
@@ -25634,9 +25755,11 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
25634
25755
  agent: opts.agent
25635
25756
  });
25636
25757
  if (bubble) {
25637
- await publisher.publishOutput({ type: "text", content: bubble, done: true });
25758
+ await streaming.closeWithBubble(bubble);
25638
25759
  history.appendAgentReply(bubble);
25639
25760
  void history.flush();
25761
+ } else {
25762
+ await streaming.closeAll();
25640
25763
  }
25641
25764
  if (bubble === AUTH_FAILURE_MESSAGE) {
25642
25765
  void reportCredentialInvalid(opts);
@@ -25947,12 +26070,15 @@ function describeError(err) {
25947
26070
  return String(err);
25948
26071
  }
25949
26072
  async function recoverFromFailedTurn(client2, streaming) {
26073
+ await cancelStuckTurn(client2);
26074
+ await streaming.closeAll();
26075
+ }
26076
+ async function cancelStuckTurn(client2) {
25950
26077
  try {
25951
26078
  await client2.cancel();
25952
26079
  } catch (err) {
25953
26080
  log.warn("acpRunner", `post-failure cancel failed: ${describeError(err)}`);
25954
26081
  }
25955
- await streaming.closeAll();
25956
26082
  }
25957
26083
  function buildBannerSubtitle(agentId, acpSessionId, model, tier) {
25958
26084
  const meta = AGENT_REGISTRY[agentId];
@@ -29849,7 +29975,7 @@ function checkChokidar() {
29849
29975
  }
29850
29976
  async function doctor(args2 = []) {
29851
29977
  const json = args2.includes("--json");
29852
- const cliVersion = true ? "2.47.1" : "0.0.0-dev";
29978
+ const cliVersion = true ? "2.48.1" : "0.0.0-dev";
29853
29979
  const apiBase2 = resolveApiBaseUrl();
29854
29980
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
29855
29981
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -30048,7 +30174,7 @@ async function completion(args2) {
30048
30174
  // src/commands/version.ts
30049
30175
  var import_picocolors15 = __toESM(require("picocolors"));
30050
30176
  function version2() {
30051
- const v = true ? "2.47.1" : "unknown";
30177
+ const v = true ? "2.48.1" : "unknown";
30052
30178
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
30053
30179
  }
30054
30180
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.47.1",
3
+ "version": "2.48.1",
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",