codeam-cli 2.39.16 → 2.39.18

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.16] — 2026-06-14
8
+
9
+ ### Added
10
+
11
+ - **jetbrains-plugin:** Proof-of-possession secret for /status + /reconnect (SEC crit1)
12
+
13
+ ## [2.39.15] — 2026-06-14
14
+
15
+ ### Added
16
+
17
+ - **vsc-plugin:** Proof-of-possession secret for /status + /reconnect (SEC crit1)
18
+
7
19
  ## [2.39.14] — 2026-06-14
8
20
 
9
21
  ### 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.16",
501
+ version: "2.39.18",
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",
@@ -989,7 +989,7 @@ async function _postJson(url, body, extraHeaders) {
989
989
  req.end();
990
990
  });
991
991
  }
992
- async function _getJson(url) {
992
+ async function _getJson(url, extraHeaders) {
993
993
  return new Promise((resolve7, reject) => {
994
994
  const u2 = new URL(url);
995
995
  const transport = u2.protocol === "https:" ? https : http;
@@ -999,7 +999,7 @@ async function _getJson(url) {
999
999
  port: u2.port || (u2.protocol === "https:" ? 443 : 80),
1000
1000
  path: u2.pathname + u2.search,
1001
1001
  method: "GET",
1002
- headers: { ...vercelBypassHeader() },
1002
+ headers: { ...vercelBypassHeader(), ...extraHeaders ?? {} },
1003
1003
  timeout: 1e4
1004
1004
  },
1005
1005
  (res) => {
@@ -5908,7 +5908,7 @@ function readAnonId() {
5908
5908
  }
5909
5909
  function superProperties() {
5910
5910
  return {
5911
- cliVersion: true ? "2.39.16" : "0.0.0-dev",
5911
+ cliVersion: true ? "2.39.18" : "0.0.0-dev",
5912
5912
  nodeVersion: process.version,
5913
5913
  platform: process.platform,
5914
5914
  arch: process.arch,
@@ -6111,7 +6111,12 @@ var CommandRelayService = class {
6111
6111
  port: url.port || (url.protocol === "https:" ? 443 : 80),
6112
6112
  path: `${url.pathname}${url.search}`,
6113
6113
  method: "GET",
6114
- headers: { Accept: "text/event-stream", "Cache-Control": "no-cache", ...vercelBypassHeader() },
6114
+ headers: {
6115
+ Accept: "text/event-stream",
6116
+ "Cache-Control": "no-cache",
6117
+ ...vercelBypassHeader(),
6118
+ ...this.pollSecretHeader()
6119
+ },
6115
6120
  timeout: 35e3
6116
6121
  },
6117
6122
  (res) => {
@@ -6264,9 +6269,27 @@ var CommandRelayService = class {
6264
6269
  this.pollTimer = setTimeout(() => this.pollLoop(), delay);
6265
6270
  }
6266
6271
  }
6272
+ // SEC crit1 (#8): the command-delivery endpoints are gated on the
6273
+ // per-pairing pollSecret when the backend enforces it. Look it up from
6274
+ // the persisted session (keyed by this relay's pluginId) and replay it
6275
+ // as X-Plugin-Poll-Secret. Empty {} for legacy sessions / older
6276
+ // backends (which ignore it).
6277
+ pollSecretHeader() {
6278
+ try {
6279
+ const secret = loadCliConfig().sessions.find(
6280
+ (s) => s.pluginId === this.pluginId
6281
+ )?.pollSecret;
6282
+ return secret ? { "X-Plugin-Poll-Secret": secret } : {};
6283
+ } catch {
6284
+ return {};
6285
+ }
6286
+ }
6267
6287
  async pollOnce() {
6268
6288
  try {
6269
- const data = await _getJson(`${API_BASE2}/api/commands/pending?pluginId=${this.pluginId}`);
6289
+ const data = await _getJson(
6290
+ `${API_BASE2}/api/commands/pending?pluginId=${this.pluginId}`,
6291
+ this.pollSecretHeader()
6292
+ );
6270
6293
  const commands = data?.data;
6271
6294
  this.pollFailures = 0;
6272
6295
  if (!Array.isArray(commands) || commands.length === 0) {
@@ -16661,7 +16684,7 @@ var http4 = __toESM(require("http"));
16661
16684
  var API_BASE3 = resolveApiBaseUrl();
16662
16685
  var PAIR_TIMEOUT_MS = 5 * 60 * 1e3;
16663
16686
  var dispatchers = /* @__PURE__ */ new Set();
16664
- function subscribeToPairCompletion(pluginId, onPaired, onTimeout) {
16687
+ function subscribeToPairCompletion(pluginId, onPaired, onTimeout, pollSecret) {
16665
16688
  let stopped = false;
16666
16689
  let req = null;
16667
16690
  let timeoutTimer = null;
@@ -16696,7 +16719,8 @@ function subscribeToPairCompletion(pluginId, onPaired, onTimeout) {
16696
16719
  headers: {
16697
16720
  Accept: "text/event-stream",
16698
16721
  "Cache-Control": "no-cache",
16699
- ...vercelBypassHeader()
16722
+ ...vercelBypassHeader(),
16723
+ ...pollSecret ? { "X-Plugin-Poll-Secret": pollSecret } : {}
16700
16724
  },
16701
16725
  timeout: 35e3
16702
16726
  },
@@ -23022,7 +23046,7 @@ function parseJsonl(filePath) {
23022
23046
  }
23023
23047
  return messages;
23024
23048
  }
23025
- function post(endpoint, body) {
23049
+ function post(endpoint, body, pluginAuthToken) {
23026
23050
  return new Promise((resolve7) => {
23027
23051
  const payload = JSON.stringify(body);
23028
23052
  const u2 = new URL(`${API_BASE8}${endpoint}`);
@@ -23036,7 +23060,11 @@ function post(endpoint, body) {
23036
23060
  headers: {
23037
23061
  "Content-Type": "application/json",
23038
23062
  "Content-Length": Buffer.byteLength(payload),
23039
- ...vercelBypassHeader()
23063
+ ...vercelBypassHeader(),
23064
+ // SEC crit1 (#819): authenticate conversation-history writes so
23065
+ // the backend can verify the (sessionId, pluginId) ownership.
23066
+ // Older backends ignore the header.
23067
+ ...pluginAuthToken ? { "X-Plugin-Auth-Token": pluginAuthToken } : {}
23040
23068
  },
23041
23069
  timeout: 15e3
23042
23070
  },
@@ -23065,6 +23093,7 @@ var HistoryService = class _HistoryService {
23065
23093
  this.pluginId = pluginId;
23066
23094
  this.cwd = cwd;
23067
23095
  this.runtime = runtime;
23096
+ this.pluginAuthToken = options?.pluginAuthToken;
23068
23097
  this.bootTimeMs = options?.bootTimeMs ?? Date.now();
23069
23098
  }
23070
23099
  pluginId;
@@ -23101,6 +23130,7 @@ var HistoryService = class _HistoryService {
23101
23130
  */
23102
23131
  static BIRTHTIME_GRACE_MS = 5e3;
23103
23132
  runtime;
23133
+ pluginAuthToken;
23104
23134
  /** Store rate limit reset info detected from Claude Code output */
23105
23135
  setRateLimitReset(reset) {
23106
23136
  this._rateLimitReset = reset;
@@ -23340,11 +23370,15 @@ var HistoryService = class _HistoryService {
23340
23370
  }
23341
23371
  const sessions3 = this.runtime.listResumableSessions(this.cwd);
23342
23372
  if (sessions3.length === 0) return;
23343
- await post("/api/sessions/list", {
23344
- pluginId: this.pluginId,
23345
- agentId: this.runtime.id,
23346
- sessions: sessions3
23347
- });
23373
+ await post(
23374
+ "/api/sessions/list",
23375
+ {
23376
+ pluginId: this.pluginId,
23377
+ agentId: this.runtime.id,
23378
+ sessions: sessions3
23379
+ },
23380
+ this.pluginAuthToken
23381
+ );
23348
23382
  }
23349
23383
  /**
23350
23384
  * Read a specific session's full conversation and POST it to the API in batches.
@@ -23374,10 +23408,10 @@ var HistoryService = class _HistoryService {
23374
23408
  batchIndex: i,
23375
23409
  totalBatches
23376
23410
  };
23377
- let ok = await post("/api/sessions/conversation", body);
23411
+ let ok = await post("/api/sessions/conversation", body, this.pluginAuthToken);
23378
23412
  for (let attempt = 0; !ok && attempt < RETRY_DELAYS.length; attempt++) {
23379
23413
  await new Promise((r) => setTimeout(r, RETRY_DELAYS[attempt]));
23380
- ok = await post("/api/sessions/conversation", body);
23414
+ ok = await post("/api/sessions/conversation", body, this.pluginAuthToken);
23381
23415
  }
23382
23416
  if (!ok) {
23383
23417
  throw new Error(`Failed to upload conversation batch ${i + 1}/${totalBatches} after all retries`);
@@ -23428,7 +23462,7 @@ var HistoryService = class _HistoryService {
23428
23462
  messages: newMessages,
23429
23463
  mode: "append"
23430
23464
  };
23431
- const ok = await post("/api/sessions/conversation", body);
23465
+ const ok = await post("/api/sessions/conversation", body, this.pluginAuthToken);
23432
23466
  if (ok) {
23433
23467
  const last = newMessages[newMessages.length - 1];
23434
23468
  this.lastUploadedUuid.set(sessionId, last.id);
@@ -24018,7 +24052,9 @@ async function start(requestedAgent) {
24018
24052
  showInfo("CODEAM_ACP_DISABLED is set \u2014 running the legacy PTY pipeline.");
24019
24053
  }
24020
24054
  const runtime = createRuntimeStrategy(session.agent);
24021
- const historySvc = new HistoryService(runtime, pluginId, cwd);
24055
+ const historySvc = new HistoryService(runtime, pluginId, cwd, {
24056
+ pluginAuthToken: session.pluginAuthToken
24057
+ });
24022
24058
  const keepAliveCtx = {
24023
24059
  inCodespace: process.env.CODESPACES === "true",
24024
24060
  codespaceName: process.env.CODESPACE_NAME
@@ -24340,7 +24376,10 @@ async function pair(args2 = []) {
24340
24376
  capture("pair_timed_out", { agentId, pluginId });
24341
24377
  showError("Pairing timed out after 5 minutes. Run codeam pair to try again.");
24342
24378
  process.exit(1);
24343
- }
24379
+ },
24380
+ // SEC crit1 (#8): replay this pairing's secret on the pre-pair
24381
+ // subscription so it passes the command-endpoint gate when enforced.
24382
+ pollSecret
24344
24383
  );
24345
24384
  process.once("SIGINT", sigintHandler);
24346
24385
  });
@@ -27017,7 +27056,7 @@ function checkChokidar() {
27017
27056
  }
27018
27057
  async function doctor(args2 = []) {
27019
27058
  const json = args2.includes("--json");
27020
- const cliVersion = true ? "2.39.16" : "0.0.0-dev";
27059
+ const cliVersion = true ? "2.39.18" : "0.0.0-dev";
27021
27060
  const apiBase = resolveApiBaseUrl();
27022
27061
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
27023
27062
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -27216,7 +27255,7 @@ async function completion(args2) {
27216
27255
  // src/commands/version.ts
27217
27256
  var import_picocolors13 = __toESM(require("picocolors"));
27218
27257
  function version2() {
27219
- const v = true ? "2.39.16" : "unknown";
27258
+ const v = true ? "2.39.18" : "unknown";
27220
27259
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
27221
27260
  }
27222
27261
 
@@ -27502,7 +27541,7 @@ function checkForUpdates() {
27502
27541
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
27503
27542
  if (process.env.CI) return;
27504
27543
  if (!process.stdout.isTTY) return;
27505
- const current = true ? "2.39.16" : null;
27544
+ const current = true ? "2.39.18" : null;
27506
27545
  if (!current) return;
27507
27546
  const cache = readCache();
27508
27547
  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.16",
3
+ "version": "2.39.18",
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",