codeam-cli 2.39.31 → 2.39.32

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,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.39.31] — 2026-06-18
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Host-agent re-enrollment + self-heal on deleted host
12
+
7
13
  ## [2.39.30] — 2026-06-18
8
14
 
9
15
  ### Added
package/dist/index.js CHANGED
@@ -498,7 +498,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
498
498
  // package.json
499
499
  var package_default = {
500
500
  name: "codeam-cli",
501
- version: "2.39.31",
501
+ version: "2.39.32",
502
502
  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.",
503
503
  type: "commonjs",
504
504
  main: "dist/index.js",
@@ -5908,7 +5908,7 @@ function readAnonId() {
5908
5908
  }
5909
5909
  function superProperties() {
5910
5910
  return {
5911
- cliVersion: true ? "2.39.31" : "0.0.0-dev",
5911
+ cliVersion: true ? "2.39.32" : "0.0.0-dev",
5912
5912
  nodeVersion: process.version,
5913
5913
  platform: process.platform,
5914
5914
  arch: process.arch,
@@ -26393,6 +26393,30 @@ async function reportProgress(auth, step, message) {
26393
26393
  } catch {
26394
26394
  }
26395
26395
  }
26396
+ async function reportDeployProgress(auth, deployId, step, message, sessionId) {
26397
+ try {
26398
+ const controller = new AbortController();
26399
+ const timer = setTimeout(() => controller.abort(), PROGRESS_TIMEOUT_MS);
26400
+ timer.unref?.();
26401
+ try {
26402
+ await fetch(`${apiBase()}/api/self-hosted/deploy-progress`, {
26403
+ method: "POST",
26404
+ headers: { "Content-Type": "application/json", ...vercelBypassHeader() },
26405
+ body: JSON.stringify({
26406
+ ...auth,
26407
+ deployId,
26408
+ step,
26409
+ message,
26410
+ ...sessionId ? { sessionId } : {}
26411
+ }),
26412
+ signal: controller.signal
26413
+ });
26414
+ } finally {
26415
+ clearTimeout(timer);
26416
+ }
26417
+ } catch {
26418
+ }
26419
+ }
26396
26420
 
26397
26421
  // src/commands/host.ts
26398
26422
  function readTokenFlag(args2) {
@@ -26455,12 +26479,46 @@ function isAbsolutePathTarget(target) {
26455
26479
  function selfHostedWorkspaceRoot() {
26456
26480
  return path54.join(os33.homedir(), ".codeam", "self-hosted");
26457
26481
  }
26458
- function repoCloneUrl(repoRef) {
26482
+ function nonInteractiveGitEnv() {
26483
+ return {
26484
+ ...process.env,
26485
+ GIT_TERMINAL_PROMPT: "0",
26486
+ GIT_ASKPASS: "",
26487
+ GCM_INTERACTIVE: "never"
26488
+ };
26489
+ }
26490
+ function githubOwnerRepo(repoRef) {
26491
+ const trimmed = repoRef.trim();
26492
+ const shorthand = /^([^/\s]+)\/([^/\s]+?)(?:\.git)?$/.exec(trimmed);
26493
+ if (shorthand && !/^https?:\/\//.test(trimmed) && !trimmed.startsWith("git@")) {
26494
+ return { owner: shorthand[1], repo: shorthand[2] };
26495
+ }
26496
+ const httpsMatch = /^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/.exec(trimmed);
26497
+ if (httpsMatch) return { owner: httpsMatch[1], repo: httpsMatch[2] };
26498
+ return null;
26499
+ }
26500
+ function repoCloneUrl(repoRef, cloneToken) {
26459
26501
  const trimmed = repoRef.trim();
26502
+ if (cloneToken) {
26503
+ const gh = githubOwnerRepo(trimmed);
26504
+ if (gh) {
26505
+ return `https://x-access-token:${cloneToken}@github.com/${gh.owner}/${gh.repo}.git`;
26506
+ }
26507
+ }
26460
26508
  if (/^https?:\/\//.test(trimmed) || trimmed.startsWith("git@")) return trimmed;
26461
26509
  return `https://github.com/${trimmed.replace(/\.git$/, "")}.git`;
26462
26510
  }
26463
- async function prepareWorkspace(repoOrPath, deployId) {
26511
+ function maskCloneUrl(url) {
26512
+ return url.replace(/(https?:\/\/)[^@/]+@/, "$1***@");
26513
+ }
26514
+ function maskToken(text, cloneToken) {
26515
+ const masked = maskCloneUrl(text);
26516
+ if (cloneToken && cloneToken.length > 0) {
26517
+ return masked.split(cloneToken).join("***");
26518
+ }
26519
+ return masked;
26520
+ }
26521
+ async function prepareWorkspace(repoOrPath, deployId, cloneToken) {
26464
26522
  if (isAbsolutePathTarget(repoOrPath)) {
26465
26523
  if (!fs42.existsSync(repoOrPath)) {
26466
26524
  throw new Error(`deploy target path does not exist: ${repoOrPath}`);
@@ -26472,10 +26530,17 @@ async function prepareWorkspace(repoOrPath, deployId) {
26472
26530
  return dest;
26473
26531
  }
26474
26532
  fs42.mkdirSync(selfHostedWorkspaceRoot(), { recursive: true, mode: 448 });
26475
- await execFileP9("git", ["clone", "--depth", "1", repoCloneUrl(repoOrPath), dest], {
26476
- timeout: 12e4,
26477
- maxBuffer: 16 * 1024 * 1024
26478
- });
26533
+ const cloneUrl = repoCloneUrl(repoOrPath, cloneToken);
26534
+ try {
26535
+ await execFileP9("git", ["clone", "--depth", "1", cloneUrl, dest], {
26536
+ timeout: 12e4,
26537
+ maxBuffer: 16 * 1024 * 1024,
26538
+ env: nonInteractiveGitEnv()
26539
+ });
26540
+ } catch (err) {
26541
+ const reason = err instanceof Error ? err.message : String(err);
26542
+ throw new Error(`git clone failed for ${maskCloneUrl(cloneUrl)}: ${maskToken(reason, cloneToken)}`);
26543
+ }
26479
26544
  return dest;
26480
26545
  }
26481
26546
 
@@ -26560,6 +26625,9 @@ function isDeployPayload(p2) {
26560
26625
  if (typeof p2.deployId !== "string" || typeof p2.repoOrPath !== "string" || typeof p2.agentId !== "string" || typeof p2.autoPairToken !== "string") {
26561
26626
  return false;
26562
26627
  }
26628
+ if (p2.cloneToken !== void 0 && typeof p2.cloneToken !== "string") {
26629
+ return false;
26630
+ }
26563
26631
  const hasHouse = isHouseProxy(p2.houseProxy);
26564
26632
  const hasSealed = typeof p2.sealedAgentAuth === "string";
26565
26633
  return hasHouse || hasSealed;
@@ -26578,7 +26646,7 @@ var CONTROL_AGENT_META = {
26578
26646
  var defaultSpawner = (env, cwd, args2 = []) => (0, import_node_child_process13.spawn)(process.execPath, [process.argv[1], "pair-auto", ...args2], {
26579
26647
  cwd,
26580
26648
  env: { ...process.env, ...env },
26581
- stdio: "ignore",
26649
+ stdio: ["ignore", "pipe", "pipe"],
26582
26650
  detached: false
26583
26651
  });
26584
26652
  var defaultOnIdentityRejected = () => {
@@ -26731,43 +26799,86 @@ var HostAgentSupervisor = class {
26731
26799
  "host-agent",
26732
26800
  `deploy id=${payload.deployId.slice(0, 8)} agent=${payload.agentId} target=${payload.repoOrPath}`
26733
26801
  );
26734
- const cwd = await prepareWorkspace(payload.repoOrPath, payload.deployId);
26735
- let childEnv;
26736
- let extraArgs = [];
26737
- if (payload.houseProxy) {
26738
- const { baseUrl, token, agentKind } = payload.houseProxy;
26739
- childEnv = {
26740
- ANTHROPIC_BASE_URL: baseUrl,
26741
- ANTHROPIC_AUTH_TOKEN: token,
26742
- ANTHROPIC_MODEL: "MiniMax-M3",
26743
- ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMax-M3",
26744
- ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMax-M3",
26745
- ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMax-M3",
26746
- CLAUDE_CODE_AUTO_COMPACT_WINDOW: "512000",
26747
- API_TIMEOUT_MS: "3000000",
26748
- CODEAM_AUTO_TOKEN: payload.autoPairToken
26749
- };
26750
- extraArgs = [`--agent=${agentKind || "claude"}`];
26751
- } else {
26752
- const auth = await this.resolveAgentAuth(this.identity, payload.sealedAgentAuth);
26753
- const credEnv = provisionAgentCredentials(payload.agentId, auth, void 0);
26754
- childEnv = {
26755
- ...credEnv,
26756
- CODEAM_AUTO_TOKEN: payload.autoPairToken
26802
+ const report = (step, message) => {
26803
+ void reportDeployProgress(
26804
+ { hostId: this.identity.hostId, hostToken: this.identity.hostToken },
26805
+ payload.deployId,
26806
+ step,
26807
+ message
26808
+ );
26809
+ };
26810
+ try {
26811
+ report("preparing", "preparing workspace");
26812
+ if (!isAbsolutePathTarget(payload.repoOrPath)) {
26813
+ report("cloning", "cloning repository");
26814
+ }
26815
+ const cwd = await prepareWorkspace(payload.repoOrPath, payload.deployId, payload.cloneToken);
26816
+ let childEnv;
26817
+ let extraArgs = [];
26818
+ if (payload.houseProxy) {
26819
+ const { baseUrl, token, agentKind } = payload.houseProxy;
26820
+ childEnv = {
26821
+ ANTHROPIC_BASE_URL: baseUrl,
26822
+ ANTHROPIC_AUTH_TOKEN: token,
26823
+ ANTHROPIC_MODEL: "MiniMax-M3",
26824
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMax-M3",
26825
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMax-M3",
26826
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMax-M3",
26827
+ CLAUDE_CODE_AUTO_COMPACT_WINDOW: "512000",
26828
+ API_TIMEOUT_MS: "3000000",
26829
+ CODEAM_AUTO_TOKEN: payload.autoPairToken
26830
+ };
26831
+ extraArgs = [`--agent=${agentKind || "claude"}`];
26832
+ } else {
26833
+ const auth = await this.resolveAgentAuth(this.identity, payload.sealedAgentAuth);
26834
+ const credEnv = provisionAgentCredentials(payload.agentId, auth, void 0);
26835
+ childEnv = {
26836
+ ...credEnv,
26837
+ CODEAM_AUTO_TOKEN: payload.autoPairToken
26838
+ };
26839
+ }
26840
+ report("spawning", "starting agent");
26841
+ const proc = this.spawnChild(childEnv, cwd, extraArgs);
26842
+ const child = { deployId: payload.deployId, proc };
26843
+ this.children.set(payload.deployId, child);
26844
+ let tail = "";
26845
+ const appendTail = (buf) => {
26846
+ tail = (tail + buf.toString("utf8")).slice(-2e3);
26757
26847
  };
26758
- }
26759
- const proc = this.spawnChild(childEnv, cwd, extraArgs);
26760
- const child = { deployId: payload.deployId, sessionId: payload.deployId, proc };
26761
- this.children.set(payload.deployId, child);
26762
- proc.once("exit", () => {
26763
- if (this.children.get(payload.deployId)?.proc === proc) {
26848
+ proc.stdout?.on("data", appendTail);
26849
+ proc.stderr?.on("data", appendTail);
26850
+ report("agent_starting", "agent process started");
26851
+ proc.once("exit", (code) => {
26852
+ const tracked = this.children.get(payload.deployId)?.proc === proc;
26853
+ if (tracked) {
26854
+ this.children.delete(payload.deployId);
26855
+ }
26856
+ if (tracked && typeof code === "number" && code !== 0) {
26857
+ const detail = tail.trim().slice(-500);
26858
+ report("failed", detail ? `agent exited (${code}): ${detail}` : `agent exited (${code})`);
26859
+ }
26860
+ });
26861
+ } catch (err) {
26862
+ const message = err instanceof Error ? err.message : String(err);
26863
+ log.warn("host-agent", `deploy ${payload.deployId.slice(0, 8)} failed: ${message}`);
26864
+ const existing = this.children.get(payload.deployId);
26865
+ if (existing) {
26866
+ try {
26867
+ existing.proc.kill("SIGTERM");
26868
+ } catch {
26869
+ }
26764
26870
  this.children.delete(payload.deployId);
26765
26871
  }
26766
- });
26872
+ report("failed", message);
26873
+ }
26767
26874
  }
26768
- /** Kill the child matching `sessionId` (or its deployId). No-op if absent. */
26875
+ /**
26876
+ * Kill the child for the given id. The backend correlates the session it
26877
+ * sends to this deploy, so the id matches the deployId we keyed on. No-op
26878
+ * if absent.
26879
+ */
26769
26880
  stopChild(sessionId) {
26770
- const child = this.children.get(sessionId) ?? this.findBySessionId(sessionId);
26881
+ const child = this.children.get(sessionId);
26771
26882
  if (!child) {
26772
26883
  log.trace("host-agent", `stop: no child for sessionId=${sessionId}`);
26773
26884
  return;
@@ -26779,12 +26890,6 @@ var HostAgentSupervisor = class {
26779
26890
  }
26780
26891
  this.children.delete(child.deployId);
26781
26892
  }
26782
- findBySessionId(sessionId) {
26783
- for (const child of this.children.values()) {
26784
- if (child.sessionId === sessionId) return child;
26785
- }
26786
- return void 0;
26787
- }
26788
26893
  };
26789
26894
  async function resolveHostIdentity(enrollToken) {
26790
26895
  const existing = loadHostIdentity();
@@ -27008,7 +27113,7 @@ function checkChokidar() {
27008
27113
  }
27009
27114
  async function doctor(args2 = []) {
27010
27115
  const json = args2.includes("--json");
27011
- const cliVersion = true ? "2.39.31" : "0.0.0-dev";
27116
+ const cliVersion = true ? "2.39.32" : "0.0.0-dev";
27012
27117
  const apiBase2 = resolveApiBaseUrl();
27013
27118
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
27014
27119
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -27207,7 +27312,7 @@ async function completion(args2) {
27207
27312
  // src/commands/version.ts
27208
27313
  var import_picocolors13 = __toESM(require("picocolors"));
27209
27314
  function version2() {
27210
- const v = true ? "2.39.31" : "unknown";
27315
+ const v = true ? "2.39.32" : "unknown";
27211
27316
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
27212
27317
  }
27213
27318
 
@@ -27493,7 +27598,7 @@ function checkForUpdates() {
27493
27598
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
27494
27599
  if (process.env.CI) return;
27495
27600
  if (!process.stdout.isTTY) return;
27496
- const current = true ? "2.39.31" : null;
27601
+ const current = true ? "2.39.32" : null;
27497
27602
  if (!current) return;
27498
27603
  const cache = readCache();
27499
27604
  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.31",
3
+ "version": "2.39.32",
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",