codeam-cli 2.65.17 → 2.66.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ 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.66.0] — 2026-08-21
8
+
9
+ ### Added
10
+
11
+ - **cli:** Handle fleet_prune_host — reclaim Docker residue on the fleet host
12
+
13
+ ### Documentation
14
+
15
+ - **cli:** Document the least-privilege Headroom installer resolution
16
+
17
+ ## [2.65.17] — 2026-08-21
18
+
19
+ ### Fixed
20
+
21
+ - **cli:** Install Headroom without root by resolving uv before sudo (#648)
22
+
7
23
  ## [2.65.16] — 2026-08-20
8
24
 
9
25
  ### Added
package/dist/index.js CHANGED
@@ -8081,7 +8081,7 @@ function readAnonId() {
8081
8081
  }
8082
8082
  function superProperties() {
8083
8083
  return {
8084
- cliVersion: true ? "2.65.17" : "0.0.0-dev",
8084
+ cliVersion: true ? "2.66.1" : "0.0.0-dev",
8085
8085
  nodeVersion: process.version,
8086
8086
  platform: process.platform,
8087
8087
  arch: process.arch,
@@ -8323,7 +8323,7 @@ var http = __toESM(require("http"));
8323
8323
  // package.json
8324
8324
  var package_default = {
8325
8325
  name: "codeam-cli",
8326
- version: "2.65.17",
8326
+ version: "2.66.1",
8327
8327
  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.",
8328
8328
  type: "commonjs",
8329
8329
  main: "dist/index.js",
@@ -9845,7 +9845,7 @@ var CommandRelayService = class _CommandRelayService {
9845
9845
  // fresh + clear the "CLI update available" banner after a self-update
9846
9846
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
9847
9847
  // pair/reconnect). Older backends ignore the extra field.
9848
- ..."2.65.17" ? { ideVersion: "2.65.17" } : {}
9848
+ ..."2.66.1" ? { ideVersion: "2.66.1" } : {}
9849
9849
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
9850
9850
  }
9851
9851
  /**
@@ -22070,7 +22070,7 @@ async function autoUpgradeBeforeCriticalCommand() {
22070
22070
  if (process.env.NODE_ENV === "test") return;
22071
22071
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
22072
22072
  if (process.env.CI) return;
22073
- const current2 = true ? "2.65.17" : null;
22073
+ const current2 = true ? "2.66.1" : null;
22074
22074
  if (!current2) return;
22075
22075
  const cache = readCache();
22076
22076
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -22087,7 +22087,7 @@ function checkForUpdates() {
22087
22087
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
22088
22088
  if (process.env.CI) return;
22089
22089
  if (!process.stdout.isTTY) return;
22090
- const current2 = true ? "2.65.17" : null;
22090
+ const current2 = true ? "2.66.1" : null;
22091
22091
  if (!current2) return;
22092
22092
  const cache = readCache();
22093
22093
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -22106,8 +22106,23 @@ var SELF_UPDATE_PKG = "codeam-cli";
22106
22106
  var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
22107
22107
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
22108
22108
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
22109
+ var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
22110
+ var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
22109
22111
  function currentCliVersion() {
22110
- return true ? "2.65.17" : null;
22112
+ return true ? "2.66.1" : null;
22113
+ }
22114
+ async function installedVersion(deps) {
22115
+ const ls = await deps.run(
22116
+ "npm",
22117
+ ["ls", "-g", "--depth=0", "--json", SELF_UPDATE_PKG],
22118
+ SELF_UPDATE_LS_TIMEOUT_MS
22119
+ );
22120
+ try {
22121
+ const parsed = JSON.parse(ls.stdout);
22122
+ return parsed.dependencies?.[SELF_UPDATE_PKG]?.version ?? null;
22123
+ } catch {
22124
+ return null;
22125
+ }
22111
22126
  }
22112
22127
  function runCmd(cmd, args2, timeoutMs) {
22113
22128
  return new Promise((resolve10) => {
@@ -22118,13 +22133,28 @@ function runCmd(cmd, args2, timeoutMs) {
22118
22133
  });
22119
22134
  }
22120
22135
  async function runSelfUpdate() {
22136
+ return runSelfUpdateWith({
22137
+ run: runCmd,
22138
+ currentVersion: currentCliVersion,
22139
+ isRoot: () => process.getuid?.() === 0
22140
+ });
22141
+ }
22142
+ async function runSelfUpdateWith(deps) {
22121
22143
  try {
22122
- const current2 = currentCliVersion();
22144
+ const current2 = deps.currentVersion();
22123
22145
  if (!current2) {
22124
22146
  log.trace("host-agent", "self-update: no __CLI_VERSION__ \u2014 skipping");
22125
22147
  return { status: "skipped" };
22126
22148
  }
22127
- const view = await runCmd(
22149
+ const onDisk = await installedVersion(deps);
22150
+ if (onDisk && compareSemver(onDisk, current2) > 0) {
22151
+ log.info(
22152
+ "host-agent",
22153
+ `self-update: ${onDisk} already installed on disk (running ${current2}) \u2014 restarting onto it`
22154
+ );
22155
+ return { status: "updated", version: onDisk };
22156
+ }
22157
+ const view = await deps.run(
22128
22158
  "npm",
22129
22159
  ["view", SELF_UPDATE_PKG, "version"],
22130
22160
  SELF_UPDATE_VIEW_TIMEOUT_MS
@@ -22143,11 +22173,18 @@ async function runSelfUpdate() {
22143
22173
  }
22144
22174
  log.info("host-agent", `self-update: ${current2} \u2192 ${latest} available \u2014 installing`);
22145
22175
  const installArgs = ["install", "-g", `${SELF_UPDATE_PKG}@latest`];
22146
- let install = await runCmd("npm", installArgs, SELF_UPDATE_INSTALL_TIMEOUT_MS);
22147
- const isRoot = process.getuid?.() === 0;
22148
- if (install.code !== 0 && !isRoot && /EACCES/i.test(install.stderr)) {
22149
- log.info("host-agent", "self-update: install hit EACCES \u2014 retrying with sudo");
22150
- install = await runCmd("sudo", ["npm", ...installArgs], SELF_UPDATE_INSTALL_TIMEOUT_MS);
22176
+ let install = await deps.run("npm", installArgs, SELF_UPDATE_INSTALL_TIMEOUT_MS);
22177
+ if (install.code !== 0 && !deps.isRoot() && /EACCES/i.test(install.stderr)) {
22178
+ const canSudo = await deps.run("sudo", ["-n", "true"], SUDO_PREFLIGHT_TIMEOUT_MS);
22179
+ if (canSudo.code === 0) {
22180
+ log.info("host-agent", "self-update: install hit EACCES \u2014 retrying with sudo");
22181
+ install = await deps.run("sudo", ["npm", ...installArgs], SELF_UPDATE_INSTALL_TIMEOUT_MS);
22182
+ } else {
22183
+ log.warn(
22184
+ "host-agent",
22185
+ "self-update: the npm global prefix is not writable by this user and passwordless sudo is unavailable \u2014 install codeam-cli under a user-writable prefix, or let a privileged agent on this host install it (this box restarts onto it automatically)"
22186
+ );
22187
+ }
22151
22188
  }
22152
22189
  if (install.code !== 0) {
22153
22190
  log.warn(
@@ -22156,7 +22193,7 @@ async function runSelfUpdate() {
22156
22193
  );
22157
22194
  return { status: "skipped" };
22158
22195
  }
22159
- const after = await runCmd(
22196
+ const after = await deps.run(
22160
22197
  "npm",
22161
22198
  ["view", SELF_UPDATE_PKG, "version"],
22162
22199
  SELF_UPDATE_VIEW_TIMEOUT_MS
@@ -22385,6 +22422,11 @@ function fleetUserIdFromContainerName(containerName) {
22385
22422
  function isMissingContainerError(stderr) {
22386
22423
  return /no such container/i.test(stderr);
22387
22424
  }
22425
+ function isFleetPruneHostPayload(v) {
22426
+ if (typeof v !== "object" || v === null) return false;
22427
+ const p2 = v;
22428
+ return typeof p2.images === "boolean" && typeof p2.buildCache === "boolean";
22429
+ }
22388
22430
  function isMissingVolumeError(stderr) {
22389
22431
  return /no such volume/i.test(stderr);
22390
22432
  }
@@ -22800,6 +22842,14 @@ var HostAgentSupervisor = class {
22800
22842
  await this.fleetDeleteBox(cmd.payload);
22801
22843
  return;
22802
22844
  }
22845
+ if (cmd.type === "fleet_prune_host") {
22846
+ if (!isFleetPruneHostPayload(cmd.payload)) {
22847
+ log.warn("host-agent", `ignoring malformed fleet_prune_host id=${cmd.id}`);
22848
+ return;
22849
+ }
22850
+ await this.fleetPruneHost(cmd.payload);
22851
+ return;
22852
+ }
22803
22853
  if (cmd.type === "self_hosted_deploy") {
22804
22854
  if (!isDeployPayload(cmd.payload)) {
22805
22855
  log.warn("host-agent", `ignoring malformed self_hosted_deploy id=${cmd.id}`);
@@ -23011,6 +23061,39 @@ var HostAgentSupervisor = class {
23011
23061
  /** `fleet_stop_box` — sleep an idle box (`docker stop`). MUST be
23012
23062
  * idempotent — the backend's reap sweeps may re-send a stop for a box
23013
23063
  * the host already stopped/removed; "No such container" is success. */
23064
+ /**
23065
+ * `fleet_prune_host` — reclaim Docker residue. Best-effort and idempotent: a
23066
+ * prune with nothing to collect exits 0 with "Total reclaimed space: 0B".
23067
+ *
23068
+ * ⚠️ Only ever `image prune` (dangling) and `builder prune`. NEVER
23069
+ * `container prune` (a stopped fleet container is a SLEEPING BOX) and NEVER
23070
+ * `volume prune` (a box's volume is the user's workspace and outlives its
23071
+ * container by design). See `FleetPruneHostPayload`.
23072
+ */
23073
+ async fleetPruneHost(payload) {
23074
+ log.info(
23075
+ "host-agent",
23076
+ `fleet_prune_host images=${payload.images} buildCache=${payload.buildCache}`
23077
+ );
23078
+ const steps = [];
23079
+ if (payload.images) steps.push({ label: "image", args: ["image", "prune", "-f"] });
23080
+ if (payload.buildCache) steps.push({ label: "builder", args: ["builder", "prune", "-f"] });
23081
+ for (const step of steps) {
23082
+ const res = await this.docker.run(step.args);
23083
+ if (res.code !== 0) {
23084
+ log.warn(
23085
+ "host-agent",
23086
+ `fleet_prune_host ${step.label} failed (code=${res.code}): ${res.stderr.trim().slice(-300)}`
23087
+ );
23088
+ continue;
23089
+ }
23090
+ const reclaimed = /Total reclaimed space:\s*(.+)/i.exec(res.stdout || "");
23091
+ log.info(
23092
+ "host-agent",
23093
+ `fleet_prune_host ${step.label} ok${reclaimed ? ` reclaimed=${reclaimed[1].trim()}` : ""}`
23094
+ );
23095
+ }
23096
+ }
23014
23097
  async fleetStopBox(payload) {
23015
23098
  log.info("host-agent", `fleet_stop_box id=${payload.boxId} name=${payload.containerName}`);
23016
23099
  const res = await this.docker.run(["stop", payload.containerName]);
@@ -45588,7 +45671,7 @@ function checkChokidar() {
45588
45671
  }
45589
45672
  async function doctor(args2 = []) {
45590
45673
  const json = args2.includes("--json");
45591
- const cliVersion = true ? "2.65.17" : "0.0.0-dev";
45674
+ const cliVersion = true ? "2.66.1" : "0.0.0-dev";
45592
45675
  const apiBase2 = resolveApiBaseUrl();
45593
45676
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
45594
45677
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -45979,7 +46062,7 @@ async function mcpRun(args2) {
45979
46062
  // src/commands/version.ts
45980
46063
  var import_picocolors15 = __toESM(require("picocolors"));
45981
46064
  function version2() {
45982
- const v = true ? "2.65.17" : "unknown";
46065
+ const v = true ? "2.66.1" : "unknown";
45983
46066
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
45984
46067
  }
45985
46068
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.65.17",
3
+ "version": "2.66.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",