codeam-cli 2.39.12 → 2.39.14

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,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.13] — 2026-06-14
8
+
9
+ ### Added
10
+
11
+ - **cli:** Proof-of-possession secret for /status + /reconnect (SEC crit1)
12
+
13
+ ## [2.39.12] — 2026-06-13
14
+
15
+ ### Fixed
16
+
17
+ - **vsc-plugin,jetbrains-plugin:** Auto-reconnect the last session on IDE startup
18
+
7
19
  ## [2.39.11] — 2026-06-13
8
20
 
9
21
  ### Fixed
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.12",
501
+ version: "2.39.14",
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",
@@ -694,7 +694,7 @@ var _execSeam = {
694
694
  // src/services/pairing.service.ts
695
695
  var API_BASE = resolveApiBaseUrl();
696
696
  var REQUEST_CODE_TIMEOUT_MS = 1e4;
697
- async function requestCode(pluginId) {
697
+ async function requestCode(pluginId, pluginSecretHash) {
698
698
  try {
699
699
  const runtime = process.env.CODESPACES === "true" ? "github-codespaces" : "local";
700
700
  const codespaceName = process.env.CODESPACE_NAME;
@@ -706,7 +706,11 @@ async function requestCode(pluginId) {
706
706
  hostname: os2.hostname(),
707
707
  runtime,
708
708
  branch,
709
- ...codespaceName ? { codespaceName } : {}
709
+ ...codespaceName ? { codespaceName } : {},
710
+ // SEC: proof-of-possession enrollment. Backend carries this hash
711
+ // onto the session and requires the raw secret on /status +
712
+ // /reconnect. Older backends ignore the unknown field.
713
+ ...pluginSecretHash ? { pluginSecretHash } : {}
710
714
  });
711
715
  let timer;
712
716
  const timeoutSentinel = /* @__PURE__ */ Symbol("request-code-timeout");
@@ -738,11 +742,14 @@ async function requestCode(pluginId) {
738
742
  return { ok: false, reason: "network" };
739
743
  }
740
744
  }
741
- async function fetchCurrentPluginAuthToken(sessionId, pluginId) {
745
+ async function fetchCurrentPluginAuthToken(sessionId, pluginId, pollSecret) {
742
746
  try {
743
747
  const result = await _transport.postJson(
744
748
  `${API_BASE}/api/pairing/reconnect`,
745
- { sessionId, pluginId }
749
+ { sessionId, pluginId },
750
+ // SEC: prove possession so the gated /reconnect returns the token.
751
+ // Omitted for legacy sessions (no secret) → backend legacy path.
752
+ pollSecret ? { "X-Plugin-Poll-Secret": pollSecret } : void 0
746
753
  );
747
754
  const data = result?.data;
748
755
  if (!data?.paired) return null;
@@ -935,7 +942,7 @@ function makeHttpError(statusCode, retryAfterHeader, responseBody) {
935
942
  if (typeof retryAfterSeconds === "number") err.retryAfterSeconds = retryAfterSeconds;
936
943
  return err;
937
944
  }
938
- async function _postJson(url, body) {
945
+ async function _postJson(url, body, extraHeaders) {
939
946
  return new Promise((resolve7, reject) => {
940
947
  const data = JSON.stringify(body);
941
948
  const u2 = new URL(url);
@@ -949,7 +956,8 @@ async function _postJson(url, body) {
949
956
  headers: {
950
957
  "Content-Type": "application/json",
951
958
  "Content-Length": Buffer.byteLength(data),
952
- ...vercelBypassHeader()
959
+ ...vercelBypassHeader(),
960
+ ...extraHeaders ?? {}
953
961
  },
954
962
  timeout: 1e4
955
963
  },
@@ -5900,7 +5908,7 @@ function readAnonId() {
5900
5908
  }
5901
5909
  function superProperties() {
5902
5910
  return {
5903
- cliVersion: true ? "2.39.12" : "0.0.0-dev",
5911
+ cliVersion: true ? "2.39.14" : "0.0.0-dev",
5904
5912
  nodeVersion: process.version,
5905
5913
  platform: process.platform,
5906
5914
  arch: process.arch,
@@ -23938,7 +23946,11 @@ async function start(requestedAgent) {
23938
23946
  requestedAgent: requestedAgent ?? null
23939
23947
  });
23940
23948
  const cwd = process.cwd();
23941
- const refreshed = await fetchCurrentPluginAuthToken(session.id, pluginId);
23949
+ const refreshed = await fetchCurrentPluginAuthToken(
23950
+ session.id,
23951
+ pluginId,
23952
+ session.pollSecret
23953
+ );
23942
23954
  if (refreshed && refreshed !== session.pluginAuthToken) {
23943
23955
  addSession({ ...session, pluginAuthToken: refreshed });
23944
23956
  session.pluginAuthToken = refreshed;
@@ -24223,10 +24235,12 @@ async function pair(args2 = []) {
24223
24235
  const agentId = dryRun ? flagAgent ?? config.preferredAgent ?? "claude" : flagAgent ?? await promptForAgent(config.preferredAgent ?? "claude");
24224
24236
  showIntro();
24225
24237
  const pluginId = (0, import_crypto6.randomUUID)();
24238
+ const pollSecret = (0, import_crypto6.randomBytes)(32).toString("base64url");
24239
+ const pluginSecretHash = (0, import_crypto6.createHash)("sha256").update(pollSecret).digest("hex");
24226
24240
  capture("pair_started", { agentId, pluginId, dryRun });
24227
24241
  const spin = dist_exports.spinner();
24228
24242
  spin.start("Requesting pairing code...");
24229
- const result = await requestCode(pluginId);
24243
+ const result = await requestCode(pluginId, pluginSecretHash);
24230
24244
  if (!result.ok) {
24231
24245
  spin.stop("Failed");
24232
24246
  if (result.reason === "rate-limited") {
@@ -24289,6 +24303,9 @@ async function pair(args2 = []) {
24289
24303
  plan: info.plan,
24290
24304
  pairedAt: Date.now(),
24291
24305
  pluginAuthToken: info.pluginAuthToken,
24306
+ // SEC: persist the PoP secret so boot-time /reconnect can prove
24307
+ // possession and refresh the token on the gated endpoint.
24308
+ pollSecret,
24292
24309
  agent: agentId
24293
24310
  });
24294
24311
  saveCliConfig({ ...loadCliConfig(), preferredAgent: agentId });
@@ -24588,7 +24605,7 @@ function networkError(msg, cause) {
24588
24605
  if (cause !== void 0) err.cause = cause;
24589
24606
  return err;
24590
24607
  }
24591
- async function claimOnce(token, pluginId) {
24608
+ async function claimOnce(token, pluginId, pluginSecretHash) {
24592
24609
  const url = `${API_BASE10}/api/pairing/claim-auto-token`;
24593
24610
  const body = {
24594
24611
  token,
@@ -24600,7 +24617,11 @@ async function claimOnce(token, pluginId) {
24600
24617
  // Current git branch of the codespace's working directory, so the
24601
24618
  // backend can populate `PairedSession.branch` for the codespace pair.
24602
24619
  // `null` when detached HEAD / not a git repo.
24603
- branch: detectCurrentBranch()
24620
+ branch: detectCurrentBranch(),
24621
+ // SEC crit1 (#813): enroll the PoP hash so /status + /reconnect for
24622
+ // this codespace session require the raw secret. Older backends
24623
+ // ignore the unknown field.
24624
+ ...pluginSecretHash ? { pluginSecretHash } : {}
24604
24625
  };
24605
24626
  const controller = new AbortController();
24606
24627
  const timer = setTimeout(() => controller.abort(), CLAIM_TIMEOUT_MS);
@@ -24637,14 +24658,14 @@ async function claimOnce(token, pluginId) {
24637
24658
  }
24638
24659
  return ok.data;
24639
24660
  }
24640
- async function claim(token, pluginId) {
24661
+ async function claim(token, pluginId, pluginSecretHash) {
24641
24662
  try {
24642
- return await claimOnce(token, pluginId);
24663
+ return await claimOnce(token, pluginId, pluginSecretHash);
24643
24664
  } catch (err) {
24644
24665
  if (err.code !== "NETWORK") throw err;
24645
24666
  await new Promise((r) => setTimeout(r, RETRY_BACKOFF_MS3));
24646
24667
  try {
24647
- return await claimOnce(token, pluginId);
24668
+ return await claimOnce(token, pluginId, pluginSecretHash);
24648
24669
  } catch (retryErr) {
24649
24670
  const netErr = retryErr;
24650
24671
  fail(`Auto-pair failed (NETWORK): ${netErr.message}`);
@@ -24700,9 +24721,11 @@ async function pairAuto(args2) {
24700
24721
  }
24701
24722
  const token = readTokenFromArgs(args2);
24702
24723
  const pluginId = (0, import_crypto7.randomUUID)();
24724
+ const pollSecret = (0, import_crypto7.randomBytes)(32).toString("base64url");
24725
+ const pluginSecretHash = (0, import_crypto7.createHash)("sha256").update(pollSecret).digest("hex");
24703
24726
  capture("pair_auto_started", { pluginId });
24704
24727
  console.log(" Claiming pairing token\u2026");
24705
- const claimed = await claim(token, pluginId);
24728
+ const claimed = await claim(token, pluginId, pluginSecretHash);
24706
24729
  if (!isKnownAgentId(claimed.agent)) {
24707
24730
  fail(
24708
24731
  `agent "${claimed.agent}" is not supported in this codeam-cli version. Upgrade with 'npm i -g codeam-cli@latest'.`
@@ -24716,6 +24739,8 @@ async function pairAuto(args2) {
24716
24739
  plan: claimed.user.plan,
24717
24740
  pairedAt: Date.now(),
24718
24741
  pluginAuthToken: claimed.pluginAuthToken,
24742
+ // SEC crit1 (#813): persist so boot-time /reconnect proves possession.
24743
+ pollSecret,
24719
24744
  agent: claimed.agent
24720
24745
  });
24721
24746
  identifyUser({
@@ -26992,7 +27017,7 @@ function checkChokidar() {
26992
27017
  }
26993
27018
  async function doctor(args2 = []) {
26994
27019
  const json = args2.includes("--json");
26995
- const cliVersion = true ? "2.39.12" : "0.0.0-dev";
27020
+ const cliVersion = true ? "2.39.14" : "0.0.0-dev";
26996
27021
  const apiBase = resolveApiBaseUrl();
26997
27022
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
26998
27023
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -27191,7 +27216,7 @@ async function completion(args2) {
27191
27216
  // src/commands/version.ts
27192
27217
  var import_picocolors13 = __toESM(require("picocolors"));
27193
27218
  function version2() {
27194
- const v = true ? "2.39.12" : "unknown";
27219
+ const v = true ? "2.39.14" : "unknown";
27195
27220
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
27196
27221
  }
27197
27222
 
@@ -27477,7 +27502,7 @@ function checkForUpdates() {
27477
27502
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
27478
27503
  if (process.env.CI) return;
27479
27504
  if (!process.stdout.isTTY) return;
27480
- const current = true ? "2.39.12" : null;
27505
+ const current = true ? "2.39.14" : null;
27481
27506
  if (!current) return;
27482
27507
  const cache = readCache();
27483
27508
  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.12",
3
+ "version": "2.39.14",
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",