codeam-cli 2.39.51 → 2.39.53
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 +12 -0
- package/dist/index.js +252 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ 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.39.52] — 2026-06-20
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **cli:** Set up Headroom on self-hosted deploys
|
|
12
|
+
|
|
13
|
+
## [2.39.51] — 2026-06-20
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **cli:** Reuse running preview on re-open instead of re-spawn (EADDRINUSE)
|
|
18
|
+
|
|
7
19
|
## [2.39.50] — 2026-06-19
|
|
8
20
|
|
|
9
21
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -5388,7 +5388,7 @@ function readAnonId() {
|
|
|
5388
5388
|
}
|
|
5389
5389
|
function superProperties() {
|
|
5390
5390
|
return {
|
|
5391
|
-
cliVersion: true ? "2.39.
|
|
5391
|
+
cliVersion: true ? "2.39.53" : "0.0.0-dev",
|
|
5392
5392
|
nodeVersion: process.version,
|
|
5393
5393
|
platform: process.platform,
|
|
5394
5394
|
arch: process.arch,
|
|
@@ -5547,7 +5547,7 @@ var os4 = __toESM(require("os"));
|
|
|
5547
5547
|
// package.json
|
|
5548
5548
|
var package_default = {
|
|
5549
5549
|
name: "codeam-cli",
|
|
5550
|
-
version: "2.39.
|
|
5550
|
+
version: "2.39.53",
|
|
5551
5551
|
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.",
|
|
5552
5552
|
type: "commonjs",
|
|
5553
5553
|
main: "dist/index.js",
|
|
@@ -17245,6 +17245,15 @@ function isDeployPayload(p2) {
|
|
|
17245
17245
|
if (p2.cloneToken !== void 0 && typeof p2.cloneToken !== "string") {
|
|
17246
17246
|
return false;
|
|
17247
17247
|
}
|
|
17248
|
+
if (p2.headroomEnabled !== void 0 && typeof p2.headroomEnabled !== "boolean") {
|
|
17249
|
+
return false;
|
|
17250
|
+
}
|
|
17251
|
+
if (p2.headroomAgent !== void 0 && typeof p2.headroomAgent !== "string") {
|
|
17252
|
+
return false;
|
|
17253
|
+
}
|
|
17254
|
+
if (p2.headroomSavingsIngestUrl !== void 0 && typeof p2.headroomSavingsIngestUrl !== "string") {
|
|
17255
|
+
return false;
|
|
17256
|
+
}
|
|
17248
17257
|
const hasHouse = isHouseProxy(p2.houseProxy);
|
|
17249
17258
|
const hasSealed = typeof p2.sealedAgentAuth === "string";
|
|
17250
17259
|
return hasHouse || hasSealed;
|
|
@@ -17260,6 +17269,228 @@ var CONTROL_AGENT_META = {
|
|
|
17260
17269
|
supportedAuthKinds: ["oauth_token"],
|
|
17261
17270
|
preferredAuthKind: "oauth_token"
|
|
17262
17271
|
};
|
|
17272
|
+
var PEP668_MARKER = "externally-managed-environment";
|
|
17273
|
+
var PM_INSTALL_TIMEOUT_MS = 18e4;
|
|
17274
|
+
var PIP_INSTALL_TIMEOUT_MS = 12e4;
|
|
17275
|
+
var defaultHeadroomRunner = {
|
|
17276
|
+
which(cmd) {
|
|
17277
|
+
try {
|
|
17278
|
+
(0, import_node_child_process12.execFileSync)("which", [cmd], { stdio: "ignore" });
|
|
17279
|
+
return true;
|
|
17280
|
+
} catch {
|
|
17281
|
+
return false;
|
|
17282
|
+
}
|
|
17283
|
+
},
|
|
17284
|
+
run(cmd, args2, opts = {}) {
|
|
17285
|
+
return new Promise((resolve7) => {
|
|
17286
|
+
const child = (0, import_node_child_process12.spawn)(cmd, args2, { stdio: ["ignore", "pipe", "pipe"] });
|
|
17287
|
+
let stderrBuf = "";
|
|
17288
|
+
let settled = false;
|
|
17289
|
+
const done = (code) => {
|
|
17290
|
+
if (settled) return;
|
|
17291
|
+
settled = true;
|
|
17292
|
+
resolve7({ code, stderr: stderrBuf });
|
|
17293
|
+
};
|
|
17294
|
+
child.stdout?.on("data", (b) => {
|
|
17295
|
+
const line = b.toString().replace(/\n+$/, "");
|
|
17296
|
+
if (line) log.info("host-agent", `headroom[${cmd}]: ${line}`);
|
|
17297
|
+
});
|
|
17298
|
+
child.stderr?.on("data", (b) => {
|
|
17299
|
+
const chunk = b.toString();
|
|
17300
|
+
stderrBuf += chunk;
|
|
17301
|
+
const line = chunk.replace(/\n+$/, "");
|
|
17302
|
+
if (line) log.info("host-agent", `headroom[${cmd}]: ${line}`);
|
|
17303
|
+
});
|
|
17304
|
+
const timeoutMs = opts.timeoutMs;
|
|
17305
|
+
let timer;
|
|
17306
|
+
if (timeoutMs !== void 0) {
|
|
17307
|
+
timer = setTimeout(() => {
|
|
17308
|
+
log.warn("host-agent", `headroom[${cmd}] timed out after ${timeoutMs / 1e3}s \u2014 aborting`);
|
|
17309
|
+
try {
|
|
17310
|
+
child.kill("SIGTERM");
|
|
17311
|
+
} catch {
|
|
17312
|
+
}
|
|
17313
|
+
done(null);
|
|
17314
|
+
}, timeoutMs);
|
|
17315
|
+
}
|
|
17316
|
+
child.once("exit", (code) => {
|
|
17317
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
17318
|
+
done(code);
|
|
17319
|
+
});
|
|
17320
|
+
child.once("error", (e) => {
|
|
17321
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
17322
|
+
log.trace("host-agent", `headroom[${cmd}] spawn error: ${e.message}`);
|
|
17323
|
+
done(null);
|
|
17324
|
+
});
|
|
17325
|
+
});
|
|
17326
|
+
}
|
|
17327
|
+
};
|
|
17328
|
+
var PACKAGE_MANAGERS = [
|
|
17329
|
+
"apt-get",
|
|
17330
|
+
"apk",
|
|
17331
|
+
"dnf",
|
|
17332
|
+
"yum",
|
|
17333
|
+
"pacman",
|
|
17334
|
+
"zypper"
|
|
17335
|
+
];
|
|
17336
|
+
var PROVISION_RECIPES = {
|
|
17337
|
+
"apt-get": {
|
|
17338
|
+
update: ["apt-get", "update"],
|
|
17339
|
+
install: ["apt-get", "install", "-y", "python3", "python3-pip", "python3-venv", "ca-certificates", "curl"]
|
|
17340
|
+
},
|
|
17341
|
+
apk: {
|
|
17342
|
+
install: ["apk", "add", "--no-cache", "python3", "py3-pip", "ca-certificates", "curl"]
|
|
17343
|
+
},
|
|
17344
|
+
dnf: {
|
|
17345
|
+
install: ["dnf", "install", "-y", "python3", "python3-pip", "ca-certificates", "curl"]
|
|
17346
|
+
},
|
|
17347
|
+
yum: {
|
|
17348
|
+
install: ["yum", "install", "-y", "python3", "python3-pip", "ca-certificates", "curl"]
|
|
17349
|
+
},
|
|
17350
|
+
pacman: {
|
|
17351
|
+
install: ["pacman", "-Sy", "--noconfirm", "python", "python-pip", "ca-certificates", "curl"]
|
|
17352
|
+
},
|
|
17353
|
+
zypper: {
|
|
17354
|
+
install: ["zypper", "--non-interactive", "install", "python3", "python3-pip", "ca-certificates", "curl"]
|
|
17355
|
+
}
|
|
17356
|
+
};
|
|
17357
|
+
function detectPackageManager(runner) {
|
|
17358
|
+
for (const pm of PACKAGE_MANAGERS) {
|
|
17359
|
+
if (runner.which(pm)) return pm;
|
|
17360
|
+
}
|
|
17361
|
+
return null;
|
|
17362
|
+
}
|
|
17363
|
+
async function ensurePip(runner) {
|
|
17364
|
+
if (runner.which("pip") || runner.which("pip3")) {
|
|
17365
|
+
return true;
|
|
17366
|
+
}
|
|
17367
|
+
const pm = detectPackageManager(runner);
|
|
17368
|
+
if (!pm) {
|
|
17369
|
+
log.warn(
|
|
17370
|
+
"host-agent",
|
|
17371
|
+
"pip is absent and no known package manager (apt-get/apk/dnf/yum/pacman/zypper) found \u2014 skipping Headroom"
|
|
17372
|
+
);
|
|
17373
|
+
return false;
|
|
17374
|
+
}
|
|
17375
|
+
const isRoot = process.getuid?.() === 0;
|
|
17376
|
+
const escalate = (argv) => isRoot ? { cmd: argv[0], args: argv.slice(1) } : { cmd: "sudo", args: argv };
|
|
17377
|
+
const recipe = PROVISION_RECIPES[pm];
|
|
17378
|
+
log.info(
|
|
17379
|
+
"host-agent",
|
|
17380
|
+
`pip absent \u2014 provisioning bare box (python3+pip+ca-certificates+curl) via ${pm}`
|
|
17381
|
+
);
|
|
17382
|
+
try {
|
|
17383
|
+
if (recipe.update) {
|
|
17384
|
+
const { cmd: cmd2, args: args3 } = escalate(recipe.update);
|
|
17385
|
+
const updateResult = await runner.run(cmd2, args3, { timeoutMs: PM_INSTALL_TIMEOUT_MS });
|
|
17386
|
+
if (updateResult.code !== 0) {
|
|
17387
|
+
log.warn(
|
|
17388
|
+
"host-agent",
|
|
17389
|
+
`${pm} update exited ${String(updateResult.code)} \u2014 attempting install anyway`
|
|
17390
|
+
);
|
|
17391
|
+
}
|
|
17392
|
+
}
|
|
17393
|
+
const { cmd, args: args2 } = escalate(recipe.install);
|
|
17394
|
+
const installResult = await runner.run(cmd, args2, { timeoutMs: PM_INSTALL_TIMEOUT_MS });
|
|
17395
|
+
if (installResult.code !== 0) {
|
|
17396
|
+
log.warn(
|
|
17397
|
+
"host-agent",
|
|
17398
|
+
`${pm} bare-box provision failed (code=${String(installResult.code)}) \u2014 skipping Headroom`
|
|
17399
|
+
);
|
|
17400
|
+
return false;
|
|
17401
|
+
}
|
|
17402
|
+
} catch (e) {
|
|
17403
|
+
log.warn(
|
|
17404
|
+
"host-agent",
|
|
17405
|
+
`bare-box provision threw unexpectedly: ${e instanceof Error ? e.message : String(e)} \u2014 skipping Headroom`
|
|
17406
|
+
);
|
|
17407
|
+
return false;
|
|
17408
|
+
}
|
|
17409
|
+
log.info("host-agent", `bare box provisioned via ${pm} (python3+pip+ca-certificates+curl)`);
|
|
17410
|
+
return true;
|
|
17411
|
+
}
|
|
17412
|
+
async function setupHeadroomForSelfHosted(agent, runner = defaultHeadroomRunner) {
|
|
17413
|
+
const PIP_PACKAGES = [
|
|
17414
|
+
"headroom-ai",
|
|
17415
|
+
"fastapi",
|
|
17416
|
+
"uvicorn",
|
|
17417
|
+
"httpx[http2]",
|
|
17418
|
+
"websockets",
|
|
17419
|
+
"zstandard"
|
|
17420
|
+
];
|
|
17421
|
+
const pipAvailable = await ensurePip(runner);
|
|
17422
|
+
if (!pipAvailable) {
|
|
17423
|
+
return false;
|
|
17424
|
+
}
|
|
17425
|
+
const installOk = await (async () => {
|
|
17426
|
+
const baseArgs = ["-m", "pip", "install", "--quiet", ...PIP_PACKAGES];
|
|
17427
|
+
const firstResult = await runner.run("python3", baseArgs, {
|
|
17428
|
+
timeoutMs: PIP_INSTALL_TIMEOUT_MS
|
|
17429
|
+
});
|
|
17430
|
+
if (firstResult.code === 0) {
|
|
17431
|
+
log.info("host-agent", "headroom pip install succeeded");
|
|
17432
|
+
return true;
|
|
17433
|
+
}
|
|
17434
|
+
if (firstResult.stderr.includes(PEP668_MARKER)) {
|
|
17435
|
+
log.info(
|
|
17436
|
+
"host-agent",
|
|
17437
|
+
"PEP 668 externally-managed-environment detected \u2014 retrying with --break-system-packages"
|
|
17438
|
+
);
|
|
17439
|
+
const retryResult = await runner.run(
|
|
17440
|
+
"python3",
|
|
17441
|
+
[...baseArgs, "--break-system-packages"],
|
|
17442
|
+
{ timeoutMs: PIP_INSTALL_TIMEOUT_MS }
|
|
17443
|
+
);
|
|
17444
|
+
if (retryResult.code === 0) {
|
|
17445
|
+
log.info("host-agent", "headroom pip install succeeded (--break-system-packages)");
|
|
17446
|
+
return true;
|
|
17447
|
+
}
|
|
17448
|
+
log.warn(
|
|
17449
|
+
"host-agent",
|
|
17450
|
+
`headroom pip install failed even with --break-system-packages (code=${String(retryResult.code)}) \u2014 skipping Headroom`
|
|
17451
|
+
);
|
|
17452
|
+
return false;
|
|
17453
|
+
}
|
|
17454
|
+
log.warn(
|
|
17455
|
+
"host-agent",
|
|
17456
|
+
`headroom pip install exited code=${String(firstResult.code)} \u2014 skipping Headroom`
|
|
17457
|
+
);
|
|
17458
|
+
return false;
|
|
17459
|
+
})();
|
|
17460
|
+
if (!installOk) {
|
|
17461
|
+
return false;
|
|
17462
|
+
}
|
|
17463
|
+
if (!runner.which("headroom")) {
|
|
17464
|
+
log.warn("host-agent", "headroom not found on PATH after install \u2014 skipping init");
|
|
17465
|
+
return false;
|
|
17466
|
+
}
|
|
17467
|
+
const initOk = await new Promise((resolve7) => {
|
|
17468
|
+
(0, import_node_child_process12.execFile)("headroom", ["init", "--global", agent], (initErr, stdout, stderr) => {
|
|
17469
|
+
if (initErr) {
|
|
17470
|
+
const detail = (stderr || initErr.message).replace(/\n+$/g, "");
|
|
17471
|
+
log.warn("host-agent", `headroom init failed (best-effort): ${detail}`);
|
|
17472
|
+
resolve7(false);
|
|
17473
|
+
} else {
|
|
17474
|
+
if (stdout.trim()) log.info("host-agent", `headroom init: ${stdout.trim()}`);
|
|
17475
|
+
log.info("host-agent", "headroom init --global succeeded");
|
|
17476
|
+
resolve7(true);
|
|
17477
|
+
}
|
|
17478
|
+
});
|
|
17479
|
+
});
|
|
17480
|
+
if (!initOk) {
|
|
17481
|
+
return false;
|
|
17482
|
+
}
|
|
17483
|
+
try {
|
|
17484
|
+
const proxy = (0, import_node_child_process12.spawn)("headroom", ["proxy", "--port", "8787"], {
|
|
17485
|
+
stdio: "ignore",
|
|
17486
|
+
detached: true
|
|
17487
|
+
});
|
|
17488
|
+
proxy.unref();
|
|
17489
|
+
} catch (e) {
|
|
17490
|
+
log.warn("host-agent", `headroom proxy warm-start failed (best-effort): ${e instanceof Error ? e.message : String(e)}`);
|
|
17491
|
+
}
|
|
17492
|
+
return true;
|
|
17493
|
+
}
|
|
17263
17494
|
var defaultSpawner = (env, cwd, args2 = []) => (0, import_node_child_process12.spawn)(process.execPath, [process.argv[1], "pair-auto", ...args2], {
|
|
17264
17495
|
cwd,
|
|
17265
17496
|
env: { ...process.env, ...env },
|
|
@@ -17283,6 +17514,7 @@ var HostAgentSupervisor = class {
|
|
|
17283
17514
|
this.deps = deps;
|
|
17284
17515
|
this.spawnChild = deps.spawnChild ?? defaultSpawner;
|
|
17285
17516
|
this.resolveAgentAuth = deps.resolveAgentAuth ?? unsealAgentAuth;
|
|
17517
|
+
this.setupHeadroom = deps.setupHeadroom ?? setupHeadroomForSelfHosted;
|
|
17286
17518
|
this.metrics = deps.metricsCollector ?? new MetricsCollector();
|
|
17287
17519
|
this.onIdentityRejected = deps.onIdentityRejected ?? defaultOnIdentityRejected;
|
|
17288
17520
|
this.disableService = deps.disableService ?? defaultDisableService;
|
|
@@ -17292,6 +17524,7 @@ var HostAgentSupervisor = class {
|
|
|
17292
17524
|
children = /* @__PURE__ */ new Map();
|
|
17293
17525
|
spawnChild;
|
|
17294
17526
|
resolveAgentAuth;
|
|
17527
|
+
setupHeadroom;
|
|
17295
17528
|
relay = null;
|
|
17296
17529
|
heartbeatTimer = null;
|
|
17297
17530
|
/** Guards the one-shot 'connected' telemetry on the first heartbeat. */
|
|
@@ -17464,6 +17697,18 @@ var HostAgentSupervisor = class {
|
|
|
17464
17697
|
childEnv.PREVIEW_TUNNEL_TOKEN = payload.previewTunnelToken;
|
|
17465
17698
|
childEnv.PREVIEW_TUNNEL_HOSTNAME = payload.previewHostname;
|
|
17466
17699
|
}
|
|
17700
|
+
if (payload.headroomEnabled && payload.headroomAgent && payload.headroomSavingsIngestUrl) {
|
|
17701
|
+
report("headroom", "setting up Headroom proxy");
|
|
17702
|
+
const headroomOk = await this.setupHeadroom(payload.headroomAgent);
|
|
17703
|
+
if (headroomOk) {
|
|
17704
|
+
childEnv.HEADROOM_ENABLED = "1";
|
|
17705
|
+
childEnv.HEADROOM_AGENT = payload.headroomAgent;
|
|
17706
|
+
childEnv.HEADROOM_SAVINGS_INGEST_URL = payload.headroomSavingsIngestUrl;
|
|
17707
|
+
log.info("host-agent", "Headroom proxy ready; HEADROOM_* env injected into child");
|
|
17708
|
+
} else {
|
|
17709
|
+
log.warn("host-agent", "Headroom setup failed (best-effort) \u2014 child will run without Headroom");
|
|
17710
|
+
}
|
|
17711
|
+
}
|
|
17467
17712
|
report("spawning", "starting agent");
|
|
17468
17713
|
const proc = this.spawnChild(childEnv, cwd, extraArgs);
|
|
17469
17714
|
const child = { deployId: payload.deployId, proc };
|
|
@@ -27373,9 +27618,9 @@ async function probeCodeamPair(provider, workspace) {
|
|
|
27373
27618
|
}
|
|
27374
27619
|
async function stopWorkspaceFromLocal(target) {
|
|
27375
27620
|
if (target.provider.id === "github-codespaces") {
|
|
27376
|
-
const { execFile:
|
|
27621
|
+
const { execFile: execFile12 } = await import("child_process");
|
|
27377
27622
|
const { promisify: promisify11 } = await import("util");
|
|
27378
|
-
const execFileP10 = promisify11(
|
|
27623
|
+
const execFileP10 = promisify11(execFile12);
|
|
27379
27624
|
await execFileP10("gh", ["codespace", "stop", "-c", target.id], { maxBuffer: 8 * 1024 * 1024 });
|
|
27380
27625
|
return;
|
|
27381
27626
|
}
|
|
@@ -27599,7 +27844,7 @@ function checkChokidar() {
|
|
|
27599
27844
|
}
|
|
27600
27845
|
async function doctor(args2 = []) {
|
|
27601
27846
|
const json = args2.includes("--json");
|
|
27602
|
-
const cliVersion = true ? "2.39.
|
|
27847
|
+
const cliVersion = true ? "2.39.53" : "0.0.0-dev";
|
|
27603
27848
|
const apiBase2 = resolveApiBaseUrl();
|
|
27604
27849
|
const diagnosticId = (0, import_node_crypto8.randomUUID)();
|
|
27605
27850
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -27798,7 +28043,7 @@ async function completion(args2) {
|
|
|
27798
28043
|
// src/commands/version.ts
|
|
27799
28044
|
var import_picocolors13 = __toESM(require("picocolors"));
|
|
27800
28045
|
function version2() {
|
|
27801
|
-
const v = true ? "2.39.
|
|
28046
|
+
const v = true ? "2.39.53" : "unknown";
|
|
27802
28047
|
console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
|
|
27803
28048
|
}
|
|
27804
28049
|
|
|
@@ -28084,7 +28329,7 @@ function checkForUpdates() {
|
|
|
28084
28329
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
28085
28330
|
if (process.env.CI) return;
|
|
28086
28331
|
if (!process.stdout.isTTY) return;
|
|
28087
|
-
const current = true ? "2.39.
|
|
28332
|
+
const current = true ? "2.39.53" : null;
|
|
28088
28333
|
if (!current) return;
|
|
28089
28334
|
const cache = readCache();
|
|
28090
28335
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.39.
|
|
3
|
+
"version": "2.39.53",
|
|
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",
|