codeam-cli 2.71.2 → 2.73.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 (2) hide show
  1. package/dist/index.js +164 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1750,7 +1750,12 @@ var INTEGRATION_REGISTRY = {
1750
1750
  label: "Token",
1751
1751
  placeholder: "Your Trello token",
1752
1752
  secret: true,
1753
- help: 'On the API-key page, click the "Token" link to authorize + generate a token (scope read,write, never-expiring).'
1753
+ // WARNING - the Power-Up API-key page also shows a "Secret", and it is
1754
+ // NOT the token: both are 64 hex characters and look identical, so it
1755
+ // is the natural thing to copy. Pasting the Secret fails with Trello's
1756
+ // misleading `invalid key`, which then sends the user to regenerate a
1757
+ // perfectly good API key (owner report, 2026-09-01). Say so here.
1758
+ help: 'On that same API-key page, click the "Token" link to authorize and generate a token (read,write, never-expiring). NOT the "Secret" on that page - it looks identical but will not work.'
1754
1759
  }
1755
1760
  ]
1756
1761
  },
@@ -8092,7 +8097,7 @@ function readAnonId() {
8092
8097
  }
8093
8098
  function superProperties() {
8094
8099
  return {
8095
- cliVersion: true ? "2.71.2" : "0.0.0-dev",
8100
+ cliVersion: true ? "2.73.0" : "0.0.0-dev",
8096
8101
  nodeVersion: process.version,
8097
8102
  platform: process.platform,
8098
8103
  arch: process.arch,
@@ -8334,7 +8339,7 @@ var http = __toESM(require("http"));
8334
8339
  // package.json
8335
8340
  var package_default = {
8336
8341
  name: "codeam-cli",
8337
- version: "2.71.2",
8342
+ version: "2.73.0",
8338
8343
  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.",
8339
8344
  type: "commonjs",
8340
8345
  main: "dist/index.js",
@@ -9902,7 +9907,7 @@ var CommandRelayService = class _CommandRelayService {
9902
9907
  if (/PLUGIN_SECRET_REQUIRED|INVALID_PLUGIN_SECRET/.test(reason)) {
9903
9908
  log.warn(
9904
9909
  "relay",
9905
- `ack REJECTED (${reason}) \u2014 this host needs to be re-paired; commands will be redelivered forever until it is`
9910
+ `ack REJECTED (${reason}) \u2014 this host must be re-paired; retrying will never fix it. Delivery still works while this client drains on receive, but the ack rail is dead and ack-mode would stall here.`
9906
9911
  );
9907
9912
  return;
9908
9913
  }
@@ -9927,7 +9932,7 @@ var CommandRelayService = class _CommandRelayService {
9927
9932
  // fresh + clear the "CLI update available" banner after a self-update
9928
9933
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
9929
9934
  // pair/reconnect). Older backends ignore the extra field.
9930
- ..."2.71.2" ? { ideVersion: "2.71.2" } : {}
9935
+ ..."2.73.0" ? { ideVersion: "2.73.0" } : {}
9931
9936
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
9932
9937
  }
9933
9938
  /**
@@ -11527,6 +11532,13 @@ var startCommandSchema = import_zod.z.object({
11527
11532
  value: import_zod.z.string().max(32768)
11528
11533
  })
11529
11534
  ).max(512).optional(),
11535
+ // `env_parse` — how a pasted `.env` blob meets the project's current one.
11536
+ // `merge` (the default when absent) keeps variables the paste does not
11537
+ // mention, because a PARTIAL paste is the common case: people copy the block
11538
+ // they care about, not the whole file. `replace` is opt-in and destructive
11539
+ // in intent, so it must be asked for explicitly. The blob itself rides the
11540
+ // existing `content` field.
11541
+ mode: import_zod.z.enum(["merge", "replace"]).optional(),
11530
11542
  // `skills_configure` — the curated `SkillId` to add/remove. `list` (and a
11531
11543
  // malformed/unknown id on add/remove) sends no `skillId` or an invalid one;
11532
11544
  // `configureSkill` itself validates against the shared registry.
@@ -22186,7 +22198,7 @@ async function autoUpgradeBeforeCriticalCommand() {
22186
22198
  if (process.env.NODE_ENV === "test") return;
22187
22199
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
22188
22200
  if (process.env.CI) return;
22189
- const current2 = true ? "2.71.2" : null;
22201
+ const current2 = true ? "2.73.0" : null;
22190
22202
  if (!current2) return;
22191
22203
  const cache = readCache();
22192
22204
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -22203,7 +22215,7 @@ function checkForUpdates() {
22203
22215
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
22204
22216
  if (process.env.CI) return;
22205
22217
  if (!process.stdout.isTTY) return;
22206
- const current2 = true ? "2.71.2" : null;
22218
+ const current2 = true ? "2.73.0" : null;
22207
22219
  if (!current2) return;
22208
22220
  const cache = readCache();
22209
22221
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -22226,7 +22238,7 @@ var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
22226
22238
  var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
22227
22239
  var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
22228
22240
  function currentCliVersion() {
22229
- return true ? "2.71.2" : null;
22241
+ return true ? "2.73.0" : null;
22230
22242
  }
22231
22243
  async function installedVersion(deps) {
22232
22244
  const ls = await deps.run(
@@ -22519,6 +22531,14 @@ function isFleetLimits(v) {
22519
22531
  function isFleetCreateBoxPayload(p2) {
22520
22532
  return typeof p2.boxId === "string" && isFleetContainerName(p2.containerName) && typeof p2.enrollToken === "string" && p2.enrollToken.length > 0 && typeof p2.apiOrigin === "string" && p2.apiOrigin.length > 0 && isFleetLimits(p2.limits);
22521
22533
  }
22534
+ function isFleetMigrateBoxImagePayload(p2) {
22535
+ return typeof p2.boxId === "string" && isFleetContainerName(p2.containerName) && typeof p2.apiOrigin === "string" && p2.apiOrigin.length > 0 && isFleetLimits(p2.limits) && // ⚠️ REJECT a token outright rather than ignoring it. A migrate that
22536
+ // carried one would bake a 15-minute credential into a container that may
22537
+ // not start for days — a terminal 4xx at boot, i.e. a box that never comes
22538
+ // back. If a future backend starts sending one, this must fail loudly here
22539
+ // rather than silently produce that box.
22540
+ p2.enrollToken === void 0;
22541
+ }
22522
22542
  function isFleetBoxRefPayload(p2) {
22523
22543
  if (typeof p2.boxId !== "string") return false;
22524
22544
  if (!isFleetContainerName(p2.containerName)) return false;
@@ -22550,6 +22570,9 @@ function isMissingVolumeError(stderr) {
22550
22570
  function resolveFleetBoxImage() {
22551
22571
  return process.env.CODEAM_FLEET_BOX_IMAGE || "ghcr.io/edgar-durand/codeam-box:latest";
22552
22572
  }
22573
+ function buildFleetBoxMigrateArgs(p2) {
22574
+ return buildFleetBoxRunArgs(p2).map((a) => a === "run" ? "create" : a).filter((a) => a !== "--pull=always" && a !== "-d");
22575
+ }
22553
22576
  function buildFleetBoxRunArgs(p2) {
22554
22577
  const userId = fleetUserIdFromContainerName(p2.containerName);
22555
22578
  const image = resolveFleetBoxImage();
@@ -22949,6 +22972,14 @@ var HostAgentSupervisor = class {
22949
22972
  await this.fleetCreateBox(cmd.payload);
22950
22973
  return;
22951
22974
  }
22975
+ if (cmd.type === "fleet_migrate_box_image") {
22976
+ if (!isFleetMigrateBoxImagePayload(cmd.payload)) {
22977
+ log.warn("host-agent", `ignoring malformed fleet_migrate_box_image id=${cmd.id}`);
22978
+ return;
22979
+ }
22980
+ await this.fleetMigrateBoxImage(cmd.payload);
22981
+ return;
22982
+ }
22952
22983
  if (cmd.type === "fleet_start_box") {
22953
22984
  if (!isFleetBoxRefPayload(cmd.payload)) {
22954
22985
  log.warn("host-agent", `ignoring malformed fleet_start_box id=${cmd.id}`);
@@ -23119,6 +23150,90 @@ var HostAgentSupervisor = class {
23119
23150
  }
23120
23151
  /** `fleet_start_box` — wake a sleeping box (`docker start`). Idempotent: a
23121
23152
  * container the host already removed is treated as success. */
23153
+ /**
23154
+ * `fleet_migrate_box_image` — re-point a SLEEPING box at the current
23155
+ * `:latest` without waking it, so the version it was pinned to becomes
23156
+ * reclaimable by the (already automated) dangling-image prune.
23157
+ *
23158
+ * WHY THIS IS NEEDED AT ALL. Boxes are created with `--pull=always` on
23159
+ * purpose, so each stays pinned to the image it was born on, and Docker
23160
+ * refuses to delete an image any container references INCLUDING stopped
23161
+ * ones — which is precisely what stops the prune from destroying a sleeping
23162
+ * user's box. So every image release strands ~7.7 GB per lagging box until it
23163
+ * wakes or is reaped on day 7 (fleet-1, 2026-09-02: 9 boxes across 4
23164
+ * versions, ~31 GB held).
23165
+ *
23166
+ * ⚠️ THE HOST RE-CHECKS THAT THE BOX IS STOPPED, even though the backend only
23167
+ * selects `status: 'SLEEPING'`. The DB's view can lag the host by a sweep —
23168
+ * a box that woke seconds ago is still SLEEPING in the row — and re-creating
23169
+ * a RUNNING container kills a live agent mid-turn. The authority on whether a
23170
+ * container is running is the daemon, so we ask it here and refuse rather
23171
+ * than trust the payload.
23172
+ *
23173
+ * ⚠️ `docker create`, and NO enroll token. `run` would start the container,
23174
+ * so migrating the sleeping set in a batch would wake all of them (9 ×
23175
+ * 1536 MB on a 16 GB host) only for the sleep sweep to re-sleep them 30
23176
+ * minutes later. And a token would be a 15-minute credential baked into a
23177
+ * container that may not start for days — a terminal 4xx at boot, i.e. a box
23178
+ * that never comes back. The sealed identity is in the named volume, which
23179
+ * `rm` WITHOUT `-v` preserves.
23180
+ *
23181
+ * Idempotent and best-effort: a box already on `:latest` is skipped, a
23182
+ * missing container is success (a concurrent reap), and nothing here may
23183
+ * throw into the relay — this is housekeeping.
23184
+ */
23185
+ async fleetMigrateBoxImage(payload) {
23186
+ const { containerName } = payload;
23187
+ const state = await this.docker.run([
23188
+ "inspect",
23189
+ "--format",
23190
+ "{{.State.Running}}",
23191
+ containerName
23192
+ ]);
23193
+ if (state.code !== 0) {
23194
+ if (isMissingContainerError(state.stderr)) return;
23195
+ log.warn(
23196
+ "host-agent",
23197
+ `fleet_migrate_box_image: inspect of ${containerName} failed (code=${state.code}) \u2014 skipping`
23198
+ );
23199
+ return;
23200
+ }
23201
+ if (state.stdout.trim() === "true") {
23202
+ log.info(
23203
+ "host-agent",
23204
+ `fleet_migrate_box_image: ${containerName} is RUNNING \u2014 skipping (never touch a live box)`
23205
+ );
23206
+ return;
23207
+ }
23208
+ if (!await this.fleetBoxImageStale(containerName)) {
23209
+ log.info("host-agent", `fleet_migrate_box_image: ${containerName} already current`);
23210
+ return;
23211
+ }
23212
+ const rm = await this.docker.run(["rm", "-f", containerName], {
23213
+ timeoutMs: DOCKER_RUN_TIMEOUT_MS
23214
+ });
23215
+ if (rm.code !== 0 && !isMissingContainerError(rm.stderr)) {
23216
+ log.warn(
23217
+ "host-agent",
23218
+ `fleet_migrate_box_image: rm of ${containerName} failed (code=${rm.code}): ${rm.stderr.trim().slice(-200)} \u2014 NOT creating a replacement`
23219
+ );
23220
+ return;
23221
+ }
23222
+ const res = await this.docker.run(buildFleetBoxMigrateArgs(payload), {
23223
+ timeoutMs: DOCKER_RUN_TIMEOUT_MS
23224
+ });
23225
+ if (res.code !== 0) {
23226
+ log.warn(
23227
+ "host-agent",
23228
+ `fleet_migrate_box_image: create of ${containerName} failed (code=${res.code}): ${res.stderr.trim().slice(-200)}`
23229
+ );
23230
+ return;
23231
+ }
23232
+ log.info(
23233
+ "host-agent",
23234
+ `fleet_migrate_box_image: ${containerName} re-pointed at :latest (still stopped)`
23235
+ );
23236
+ }
23122
23237
  async fleetStartBox(payload) {
23123
23238
  const { containerName } = payload;
23124
23239
  if (fleetRefCanRecreate(payload) && await this.fleetBoxImageStale(containerName)) {
@@ -28213,6 +28328,44 @@ var envReadH = async (ctx, cmd) => {
28213
28328
  await ctx.relay.sendResult(cmd.id, "failed", { error: err.message });
28214
28329
  }
28215
28330
  };
28331
+ var envParseH = async (ctx, cmd, parsed) => {
28332
+ const content = typeof parsed.content === "string" ? parsed.content : null;
28333
+ if (content === null) {
28334
+ await ctx.relay.sendResult(cmd.id, "failed", { error: "Missing content" });
28335
+ return;
28336
+ }
28337
+ const replace = parsed.mode === "replace";
28338
+ const incoming = /* @__PURE__ */ new Map();
28339
+ for (const v of parseDotenv(content)) incoming.set(v.key, v.value);
28340
+ const distinctAssignments = new Set(
28341
+ content.split(/\r?\n/).map((l) => l.trim()).filter((l) => l !== "" && !l.startsWith("#") && l.includes("=")).map((l) => l.startsWith("export ") ? l.slice("export ".length).trim() : l).map((l) => l.slice(0, l.indexOf("=")).trim())
28342
+ );
28343
+ const unrecognized = Math.max(0, distinctAssignments.size - incoming.size);
28344
+ const existing = /* @__PURE__ */ new Map();
28345
+ if (!replace) {
28346
+ try {
28347
+ const raw = await fs65.promises.readFile(path71.join(process.cwd(), ".env"), "utf8");
28348
+ for (const v of parseDotenv(raw)) existing.set(v.key, v.value);
28349
+ } catch {
28350
+ }
28351
+ }
28352
+ let added = 0;
28353
+ let updated = 0;
28354
+ for (const [key, value] of incoming) {
28355
+ if (existing.has(key)) {
28356
+ if (existing.get(key) !== value) updated += 1;
28357
+ } else {
28358
+ added += 1;
28359
+ }
28360
+ existing.set(key, value);
28361
+ }
28362
+ await ctx.relay.sendResult(cmd.id, "completed", {
28363
+ vars: [...existing].map(([key, value]) => ({ key, value })),
28364
+ added,
28365
+ updated,
28366
+ unrecognized
28367
+ });
28368
+ };
28216
28369
  var envWriteH = async (ctx, cmd, parsed) => {
28217
28370
  const vars = parsed.vars;
28218
28371
  if (!Array.isArray(vars)) {
@@ -29490,6 +29643,7 @@ var handlers = {
29490
29643
  save_preview_config: savePreviewConfigH,
29491
29644
  env_read: envReadH,
29492
29645
  env_write: envWriteH,
29646
+ env_parse: envParseH,
29493
29647
  skills_configure: skillsConfigureH,
29494
29648
  take_control: takeControlH,
29495
29649
  handback: handbackH,
@@ -46859,7 +47013,7 @@ function checkChokidar() {
46859
47013
  }
46860
47014
  async function doctor(args2 = []) {
46861
47015
  const json = args2.includes("--json");
46862
- const cliVersion = true ? "2.71.2" : "0.0.0-dev";
47016
+ const cliVersion = true ? "2.73.0" : "0.0.0-dev";
46863
47017
  const apiBase2 = resolveApiBaseUrl();
46864
47018
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
46865
47019
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -47250,7 +47404,7 @@ async function mcpRun(args2) {
47250
47404
  // src/commands/version.ts
47251
47405
  var import_picocolors15 = __toESM(require("picocolors"));
47252
47406
  function version2() {
47253
- const v = true ? "2.71.2" : "unknown";
47407
+ const v = true ? "2.73.0" : "unknown";
47254
47408
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
47255
47409
  }
47256
47410
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.71.2",
3
+ "version": "2.73.0",
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",