codeam-cli 2.60.69 → 2.60.70
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 +6 -0
- package/dist/index.js +240 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ 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.60.69] — 2026-07-15
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **cli:** Provision uvx via the standalone uv installer and resolve per-user bin paths for MCP launchers
|
|
12
|
+
|
|
7
13
|
## [2.60.68] — 2026-07-15
|
|
8
14
|
|
|
9
15
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -5970,7 +5970,7 @@ function readAnonId() {
|
|
|
5970
5970
|
}
|
|
5971
5971
|
function superProperties() {
|
|
5972
5972
|
return {
|
|
5973
|
-
cliVersion: true ? "2.60.
|
|
5973
|
+
cliVersion: true ? "2.60.70" : "0.0.0-dev",
|
|
5974
5974
|
nodeVersion: process.version,
|
|
5975
5975
|
platform: process.platform,
|
|
5976
5976
|
arch: process.arch,
|
|
@@ -6151,7 +6151,7 @@ var os4 = __toESM(require("os"));
|
|
|
6151
6151
|
// package.json
|
|
6152
6152
|
var package_default = {
|
|
6153
6153
|
name: "codeam-cli",
|
|
6154
|
-
version: "2.60.
|
|
6154
|
+
version: "2.60.70",
|
|
6155
6155
|
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.",
|
|
6156
6156
|
type: "commonjs",
|
|
6157
6157
|
main: "dist/index.js",
|
|
@@ -7294,7 +7294,7 @@ var CommandRelayService = class _CommandRelayService {
|
|
|
7294
7294
|
// fresh + clear the "CLI update available" banner after a self-update
|
|
7295
7295
|
// (a codespace that reinstalls @latest reconnects via heartbeat, not
|
|
7296
7296
|
// pair/reconnect). Older backends ignore the extra field.
|
|
7297
|
-
..."2.60.
|
|
7297
|
+
..."2.60.70" ? { ideVersion: "2.60.70" } : {}
|
|
7298
7298
|
}).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
|
|
7299
7299
|
}
|
|
7300
7300
|
/**
|
|
@@ -17871,7 +17871,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
17871
17871
|
if (process.env.NODE_ENV === "test") return;
|
|
17872
17872
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
17873
17873
|
if (process.env.CI) return;
|
|
17874
|
-
const current = true ? "2.60.
|
|
17874
|
+
const current = true ? "2.60.70" : null;
|
|
17875
17875
|
if (!current) return;
|
|
17876
17876
|
const cache = readCache();
|
|
17877
17877
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -17888,7 +17888,7 @@ function checkForUpdates() {
|
|
|
17888
17888
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
17889
17889
|
if (process.env.CI) return;
|
|
17890
17890
|
if (!process.stdout.isTTY) return;
|
|
17891
|
-
const current = true ? "2.60.
|
|
17891
|
+
const current = true ? "2.60.70" : null;
|
|
17892
17892
|
if (!current) return;
|
|
17893
17893
|
const cache = readCache();
|
|
17894
17894
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -17908,7 +17908,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
|
17908
17908
|
var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
|
|
17909
17909
|
var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
|
|
17910
17910
|
function currentCliVersion() {
|
|
17911
|
-
return true ? "2.60.
|
|
17911
|
+
return true ? "2.60.70" : null;
|
|
17912
17912
|
}
|
|
17913
17913
|
function runCmd(cmd, args2, timeoutMs) {
|
|
17914
17914
|
return new Promise((resolve7) => {
|
|
@@ -18136,6 +18136,78 @@ function isDeployPayload(p2) {
|
|
|
18136
18136
|
function isStopPayload(p2) {
|
|
18137
18137
|
return typeof p2.sessionId === "string";
|
|
18138
18138
|
}
|
|
18139
|
+
var FLEET_CONTAINER_NAME_RE = /^codeam-box-[a-z0-9]+$/;
|
|
18140
|
+
function isFleetContainerName(v) {
|
|
18141
|
+
return typeof v === "string" && FLEET_CONTAINER_NAME_RE.test(v);
|
|
18142
|
+
}
|
|
18143
|
+
function isFiniteNumber(v) {
|
|
18144
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
18145
|
+
}
|
|
18146
|
+
function isFleetLimits(v) {
|
|
18147
|
+
if (typeof v !== "object" || v === null) return false;
|
|
18148
|
+
const l = v;
|
|
18149
|
+
return isFiniteNumber(l.memoryMb) && isFiniteNumber(l.cpus) && isFiniteNumber(l.pidsLimit) && isFiniteNumber(l.diskGb);
|
|
18150
|
+
}
|
|
18151
|
+
function isFleetCreateBoxPayload(p2) {
|
|
18152
|
+
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);
|
|
18153
|
+
}
|
|
18154
|
+
function isFleetBoxRefPayload(p2) {
|
|
18155
|
+
if (typeof p2.boxId !== "string") return false;
|
|
18156
|
+
if (!isFleetContainerName(p2.containerName)) return false;
|
|
18157
|
+
if (p2.removeVolume !== void 0 && typeof p2.removeVolume !== "boolean") return false;
|
|
18158
|
+
return true;
|
|
18159
|
+
}
|
|
18160
|
+
function fleetUserIdFromContainerName(containerName) {
|
|
18161
|
+
return containerName.slice("codeam-box-".length);
|
|
18162
|
+
}
|
|
18163
|
+
function isMissingContainerError(stderr) {
|
|
18164
|
+
return /no such container/i.test(stderr);
|
|
18165
|
+
}
|
|
18166
|
+
function isMissingVolumeError(stderr) {
|
|
18167
|
+
return /no such volume/i.test(stderr);
|
|
18168
|
+
}
|
|
18169
|
+
function resolveFleetBoxImage() {
|
|
18170
|
+
return process.env.CODEAM_FLEET_BOX_IMAGE || "ghcr.io/edgar-durand/codeam-box:latest";
|
|
18171
|
+
}
|
|
18172
|
+
var DOCKER_RUN_TIMEOUT_MS = 12e4;
|
|
18173
|
+
var defaultDockerRunner = {
|
|
18174
|
+
run(args2, opts = {}) {
|
|
18175
|
+
return new Promise((resolve7) => {
|
|
18176
|
+
const child = (0, import_node_child_process23.spawn)("docker", args2, {
|
|
18177
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
18178
|
+
env: { ...process.env, ...opts.env }
|
|
18179
|
+
});
|
|
18180
|
+
let stdoutBuf = "";
|
|
18181
|
+
let stderrBuf = "";
|
|
18182
|
+
let settled = false;
|
|
18183
|
+
const timer = setTimeout(
|
|
18184
|
+
() => {
|
|
18185
|
+
if (settled) return;
|
|
18186
|
+
killQuiet(child);
|
|
18187
|
+
},
|
|
18188
|
+
opts.timeoutMs ?? DOCKER_RUN_TIMEOUT_MS
|
|
18189
|
+
);
|
|
18190
|
+
const done = (code) => {
|
|
18191
|
+
if (settled) return;
|
|
18192
|
+
settled = true;
|
|
18193
|
+
clearTimeout(timer);
|
|
18194
|
+
resolve7({ code, stderr: stderrBuf, stdout: stdoutBuf });
|
|
18195
|
+
};
|
|
18196
|
+
child.stdout?.on("data", (b) => {
|
|
18197
|
+
stdoutBuf += b.toString();
|
|
18198
|
+
});
|
|
18199
|
+
child.stderr?.on("data", (b) => {
|
|
18200
|
+
stderrBuf += b.toString();
|
|
18201
|
+
});
|
|
18202
|
+
child.once("error", (err) => {
|
|
18203
|
+
stderrBuf += stderrBuf ? `
|
|
18204
|
+
${err.message}` : err.message;
|
|
18205
|
+
done(null);
|
|
18206
|
+
});
|
|
18207
|
+
child.once("exit", (code) => done(code));
|
|
18208
|
+
});
|
|
18209
|
+
}
|
|
18210
|
+
};
|
|
18139
18211
|
var CONTROL_AGENT_META = {
|
|
18140
18212
|
id: "claude",
|
|
18141
18213
|
displayName: "CodeAgent Host Agent",
|
|
@@ -18177,6 +18249,7 @@ var HostAgentSupervisor = class {
|
|
|
18177
18249
|
this.teardownHeadroom = deps.teardownHeadroom ?? defaultTeardownHeadroom;
|
|
18178
18250
|
this.selfUpdate = deps.selfUpdate ?? runSelfUpdate;
|
|
18179
18251
|
this.onUpdated = deps.onUpdated ?? defaultOnUpdated;
|
|
18252
|
+
this.docker = deps.docker ?? defaultDockerRunner;
|
|
18180
18253
|
}
|
|
18181
18254
|
identity;
|
|
18182
18255
|
deps;
|
|
@@ -18213,6 +18286,8 @@ var HostAgentSupervisor = class {
|
|
|
18213
18286
|
/** Best-effort systemd de-provision used by `self_hosted_wipe`. */
|
|
18214
18287
|
disableService;
|
|
18215
18288
|
teardownHeadroom;
|
|
18289
|
+
/** Docker runner for the fleet `fleet_*` control-plane handlers. */
|
|
18290
|
+
docker;
|
|
18216
18291
|
/** Guards against firing the self-heal more than once. */
|
|
18217
18292
|
healing = false;
|
|
18218
18293
|
/**
|
|
@@ -18380,6 +18455,38 @@ var HostAgentSupervisor = class {
|
|
|
18380
18455
|
* arbitrary command surface).
|
|
18381
18456
|
*/
|
|
18382
18457
|
async handleCommand(cmd) {
|
|
18458
|
+
if (cmd.type === "fleet_create_box") {
|
|
18459
|
+
if (!isFleetCreateBoxPayload(cmd.payload)) {
|
|
18460
|
+
log.warn("host-agent", `ignoring malformed fleet_create_box id=${cmd.id}`);
|
|
18461
|
+
return;
|
|
18462
|
+
}
|
|
18463
|
+
await this.fleetCreateBox(cmd.payload);
|
|
18464
|
+
return;
|
|
18465
|
+
}
|
|
18466
|
+
if (cmd.type === "fleet_start_box") {
|
|
18467
|
+
if (!isFleetBoxRefPayload(cmd.payload)) {
|
|
18468
|
+
log.warn("host-agent", `ignoring malformed fleet_start_box id=${cmd.id}`);
|
|
18469
|
+
return;
|
|
18470
|
+
}
|
|
18471
|
+
await this.fleetStartBox(cmd.payload);
|
|
18472
|
+
return;
|
|
18473
|
+
}
|
|
18474
|
+
if (cmd.type === "fleet_stop_box") {
|
|
18475
|
+
if (!isFleetBoxRefPayload(cmd.payload)) {
|
|
18476
|
+
log.warn("host-agent", `ignoring malformed fleet_stop_box id=${cmd.id}`);
|
|
18477
|
+
return;
|
|
18478
|
+
}
|
|
18479
|
+
await this.fleetStopBox(cmd.payload);
|
|
18480
|
+
return;
|
|
18481
|
+
}
|
|
18482
|
+
if (cmd.type === "fleet_delete_box") {
|
|
18483
|
+
if (!isFleetBoxRefPayload(cmd.payload)) {
|
|
18484
|
+
log.warn("host-agent", `ignoring malformed fleet_delete_box id=${cmd.id}`);
|
|
18485
|
+
return;
|
|
18486
|
+
}
|
|
18487
|
+
await this.fleetDeleteBox(cmd.payload);
|
|
18488
|
+
return;
|
|
18489
|
+
}
|
|
18383
18490
|
if (cmd.type === "self_hosted_deploy") {
|
|
18384
18491
|
if (!isDeployPayload(cmd.payload)) {
|
|
18385
18492
|
log.warn("host-agent", `ignoring malformed self_hosted_deploy id=${cmd.id}`);
|
|
@@ -18417,6 +18524,131 @@ var HostAgentSupervisor = class {
|
|
|
18417
18524
|
}
|
|
18418
18525
|
log.trace("host-agent", `ignoring unsupported command type=${cmd.type}`);
|
|
18419
18526
|
}
|
|
18527
|
+
/**
|
|
18528
|
+
* `fleet_create_box` — `docker run` a per-user rescue box. Builds the FULL
|
|
18529
|
+
* argv from a fixed template (Global Constraints — nothing from the wire
|
|
18530
|
+
* is passed through as a raw docker argument beyond the already-validated
|
|
18531
|
+
* `containerName` and the numeric resource limits): hard isolation
|
|
18532
|
+
* (`--cap-drop ALL`, `--security-opt no-new-privileges`, resource caps,
|
|
18533
|
+
* the isolated `fleet-net` network, a SINGLE named volume mounted at
|
|
18534
|
+
* `/home/box` — named identically to the container, per the Global
|
|
18535
|
+
* Constraints — and the ops labels). NEVER `--privileged`, NEVER a
|
|
18536
|
+
* `docker.sock` mount, NEVER a host bind mount. The api origin (not a
|
|
18537
|
+
* secret) is delivered via a normal `-e KEY=value`. The enroll token IS a
|
|
18538
|
+
* secret — spec invariant #1 is "token via env, NEVER argv" — so it is
|
|
18539
|
+
* delivered as a BARE `-e CODEAM_ENROLL_TOKEN` (no `=value`) with the
|
|
18540
|
+
* actual value passed only through {@link DockerRunner.run}'s `env` map,
|
|
18541
|
+
* which the runner merges into the `docker` CLI process's OWN env; docker
|
|
18542
|
+
* then reads a bare `-e NAME` from its own env and forwards it into the
|
|
18543
|
+
* container. The value never appears in the `docker run` argv, so it's
|
|
18544
|
+
* never visible via `ps` on the shared fleet host; never logged either.
|
|
18545
|
+
*/
|
|
18546
|
+
async fleetCreateBox(payload) {
|
|
18547
|
+
const { containerName, limits } = payload;
|
|
18548
|
+
const userId = fleetUserIdFromContainerName(containerName);
|
|
18549
|
+
log.info(
|
|
18550
|
+
"host-agent",
|
|
18551
|
+
`fleet_create_box id=${payload.boxId} name=${containerName} mem=${limits.memoryMb}m cpus=${limits.cpus} pids=${limits.pidsLimit}`
|
|
18552
|
+
);
|
|
18553
|
+
const args2 = [
|
|
18554
|
+
"run",
|
|
18555
|
+
"-d",
|
|
18556
|
+
"--name",
|
|
18557
|
+
containerName,
|
|
18558
|
+
"--cap-drop",
|
|
18559
|
+
"ALL",
|
|
18560
|
+
"--security-opt",
|
|
18561
|
+
"no-new-privileges",
|
|
18562
|
+
"--memory",
|
|
18563
|
+
`${limits.memoryMb}m`,
|
|
18564
|
+
"--cpus",
|
|
18565
|
+
String(limits.cpus),
|
|
18566
|
+
"--pids-limit",
|
|
18567
|
+
String(limits.pidsLimit),
|
|
18568
|
+
"--network",
|
|
18569
|
+
"fleet-net",
|
|
18570
|
+
"--label",
|
|
18571
|
+
`com.codeagent.user-id=${userId}`,
|
|
18572
|
+
"--label",
|
|
18573
|
+
`com.codeagent.box-id=${payload.boxId}`,
|
|
18574
|
+
"--label",
|
|
18575
|
+
"com.codeagent.created-by=fleet",
|
|
18576
|
+
"-v",
|
|
18577
|
+
`${containerName}:/home/box`,
|
|
18578
|
+
// Bare `-e NAME` (no `=value`) — docker reads the value from ITS OWN
|
|
18579
|
+
// process env (supplied below via `opts.env`), never from this argv.
|
|
18580
|
+
"-e",
|
|
18581
|
+
"CODEAM_ENROLL_TOKEN",
|
|
18582
|
+
"-e",
|
|
18583
|
+
`CODEAM_API_URL=${payload.apiOrigin}`,
|
|
18584
|
+
resolveFleetBoxImage()
|
|
18585
|
+
];
|
|
18586
|
+
const res = await this.docker.run(args2, {
|
|
18587
|
+
timeoutMs: DOCKER_RUN_TIMEOUT_MS,
|
|
18588
|
+
env: { CODEAM_ENROLL_TOKEN: payload.enrollToken }
|
|
18589
|
+
});
|
|
18590
|
+
if (res.code === 0) {
|
|
18591
|
+
log.info(
|
|
18592
|
+
"host-agent",
|
|
18593
|
+
`fleet box ${containerName} created (${res.stdout.trim().slice(0, 12)})`
|
|
18594
|
+
);
|
|
18595
|
+
} else {
|
|
18596
|
+
log.warn(
|
|
18597
|
+
"host-agent",
|
|
18598
|
+
`fleet_create_box ${containerName} failed (code=${res.code}): ${res.stderr.trim().slice(-300)}`
|
|
18599
|
+
);
|
|
18600
|
+
}
|
|
18601
|
+
}
|
|
18602
|
+
/** `fleet_start_box` — wake a sleeping box (`docker start`). Idempotent: a
|
|
18603
|
+
* container the host already removed is treated as success. */
|
|
18604
|
+
async fleetStartBox(payload) {
|
|
18605
|
+
log.info("host-agent", `fleet_start_box id=${payload.boxId} name=${payload.containerName}`);
|
|
18606
|
+
const res = await this.docker.run(["start", payload.containerName]);
|
|
18607
|
+
if (res.code !== 0 && !isMissingContainerError(res.stderr)) {
|
|
18608
|
+
log.warn(
|
|
18609
|
+
"host-agent",
|
|
18610
|
+
`fleet_start_box ${payload.containerName} failed (code=${res.code}): ${res.stderr.trim().slice(-300)}`
|
|
18611
|
+
);
|
|
18612
|
+
}
|
|
18613
|
+
}
|
|
18614
|
+
/** `fleet_stop_box` — sleep an idle box (`docker stop`). MUST be
|
|
18615
|
+
* idempotent — the backend's reap sweeps may re-send a stop for a box
|
|
18616
|
+
* the host already stopped/removed; "No such container" is success. */
|
|
18617
|
+
async fleetStopBox(payload) {
|
|
18618
|
+
log.info("host-agent", `fleet_stop_box id=${payload.boxId} name=${payload.containerName}`);
|
|
18619
|
+
const res = await this.docker.run(["stop", payload.containerName]);
|
|
18620
|
+
if (res.code !== 0 && !isMissingContainerError(res.stderr)) {
|
|
18621
|
+
log.warn(
|
|
18622
|
+
"host-agent",
|
|
18623
|
+
`fleet_stop_box ${payload.containerName} failed (code=${res.code}): ${res.stderr.trim().slice(-300)}`
|
|
18624
|
+
);
|
|
18625
|
+
}
|
|
18626
|
+
}
|
|
18627
|
+
/** `fleet_delete_box` — `docker rm -f`, plus `docker volume rm` when the
|
|
18628
|
+
* backend asks for a reap (`removeVolume`). MUST be idempotent — deleting
|
|
18629
|
+
* an already-gone container/volume is success, never a failure. */
|
|
18630
|
+
async fleetDeleteBox(payload) {
|
|
18631
|
+
log.info(
|
|
18632
|
+
"host-agent",
|
|
18633
|
+
`fleet_delete_box id=${payload.boxId} name=${payload.containerName} removeVolume=${Boolean(payload.removeVolume)}`
|
|
18634
|
+
);
|
|
18635
|
+
const res = await this.docker.run(["rm", "-f", payload.containerName]);
|
|
18636
|
+
if (res.code !== 0 && !isMissingContainerError(res.stderr)) {
|
|
18637
|
+
log.warn(
|
|
18638
|
+
"host-agent",
|
|
18639
|
+
`fleet_delete_box ${payload.containerName} failed (code=${res.code}): ${res.stderr.trim().slice(-300)}`
|
|
18640
|
+
);
|
|
18641
|
+
}
|
|
18642
|
+
if (payload.removeVolume) {
|
|
18643
|
+
const volRes = await this.docker.run(["volume", "rm", payload.containerName]);
|
|
18644
|
+
if (volRes.code !== 0 && !isMissingVolumeError(volRes.stderr)) {
|
|
18645
|
+
log.warn(
|
|
18646
|
+
"host-agent",
|
|
18647
|
+
`fleet_delete_box volume rm ${payload.containerName} failed (code=${volRes.code}): ${volRes.stderr.trim().slice(-300)}`
|
|
18648
|
+
);
|
|
18649
|
+
}
|
|
18650
|
+
}
|
|
18651
|
+
}
|
|
18420
18652
|
/**
|
|
18421
18653
|
* Prepare the workspace, write the agent credential (same as codespace
|
|
18422
18654
|
* provisioning), and spawn a supervised `codeam pair-auto` child.
|
|
@@ -35337,7 +35569,7 @@ function checkChokidar() {
|
|
|
35337
35569
|
}
|
|
35338
35570
|
async function doctor(args2 = []) {
|
|
35339
35571
|
const json = args2.includes("--json");
|
|
35340
|
-
const cliVersion = true ? "2.60.
|
|
35572
|
+
const cliVersion = true ? "2.60.70" : "0.0.0-dev";
|
|
35341
35573
|
const apiBase2 = resolveApiBaseUrl();
|
|
35342
35574
|
const diagnosticId = (0, import_node_crypto12.randomUUID)();
|
|
35343
35575
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -35848,7 +36080,7 @@ async function mcpRun(args2) {
|
|
|
35848
36080
|
// src/commands/version.ts
|
|
35849
36081
|
var import_picocolors15 = __toESM(require("picocolors"));
|
|
35850
36082
|
function version2() {
|
|
35851
|
-
const v = true ? "2.60.
|
|
36083
|
+
const v = true ? "2.60.70" : "unknown";
|
|
35852
36084
|
console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
|
|
35853
36085
|
}
|
|
35854
36086
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.60.
|
|
3
|
+
"version": "2.60.70",
|
|
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",
|