claude-bridge-cli 2.1.0 → 2.2.1

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.
Files changed (2) hide show
  1. package/lib/bridge.js +59 -3
  2. package/package.json +1 -1
package/lib/bridge.js CHANGED
@@ -7,6 +7,10 @@ const path = require("node:path");
7
7
  const crypto = require("node:crypto");
8
8
  const os = require("node:os");
9
9
 
10
+ // Extension builds seen calling this bridge: {version: last_seen_epoch}.
11
+ // Diagnostics only — surfaced on /health (see the x-ext-version note below).
12
+ const EXT_SEEN = {};
13
+
10
14
  const running = new Map();
11
15
 
12
16
  function homeDir() {
@@ -1075,8 +1079,46 @@ function stopSession(sessionId) {
1075
1079
 
1076
1080
  // ── HTTP server ──
1077
1081
 
1082
+ // ── Transcript-retention guard ───────────────────────────────────────────────
1083
+ // Claude Code's `cleanupPeriodDays` DEFAULTS to 30: transcripts older than 30
1084
+ // days are silently DELETED at every CLI startup. On 2026-08-31 this erased
1085
+ // two months-old sessions ("Keycloak-IDP", "IDP-Lucid") from a machine whose
1086
+ // settings had no override — the owner "didn't delete it"; the tool did.
1087
+ // Every bridge start therefore pins a long retention into ~/.claude/settings.json
1088
+ // (create-if-absent, other keys preserved, .bak written on first change).
1089
+ // Opt out with CLAUDE_BRIDGE_NO_RETENTION_PIN=1. NOTE: 0 is NOT a safe value
1090
+ // (older CLIs treated it as "disable transcript writes") — use a big number.
1091
+ function ensureTranscriptRetention() {
1092
+ if (process.env.CLAUDE_BRIDGE_NO_RETENTION_PIN === "1") return;
1093
+ try {
1094
+ const dir = path.join(homeDir(), ".claude");
1095
+ const file = path.join(dir, "settings.json");
1096
+ let settings = {};
1097
+ let existed = false;
1098
+ try {
1099
+ settings = JSON.parse(fs.readFileSync(file, "utf8"));
1100
+ existed = true;
1101
+ } catch (_e) { /* absent or unparseable-empty — treat as new */ }
1102
+ if (typeof settings !== "object" || settings === null) settings = {};
1103
+ const cur = settings.cleanupPeriodDays;
1104
+ if (typeof cur === "number" && cur >= 3650) return; // already pinned
1105
+ if (existed) {
1106
+ try { fs.copyFileSync(file, file + ".bak-retention"); } catch (_e) {}
1107
+ }
1108
+ settings.cleanupPeriodDays = 3650;
1109
+ fs.mkdirSync(dir, { recursive: true });
1110
+ fs.writeFileSync(file, JSON.stringify(settings, null, 2) + "\n");
1111
+ console.log(`[retention] pinned cleanupPeriodDays=3650 in ${file}`
1112
+ + (typeof cur === "number" ? ` (was ${cur})` : " (was absent → default 30)"));
1113
+ } catch (e) {
1114
+ // Never block the bridge on this — but say so, silence here is the bug.
1115
+ console.error("[retention] could not pin cleanupPeriodDays:", e.message);
1116
+ }
1117
+ }
1118
+
1078
1119
  function startBridge(config) {
1079
1120
  const dd = dataDir();
1121
+ ensureTranscriptRetention();
1080
1122
 
1081
1123
  const server = http.createServer(async (req, res) => {
1082
1124
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -1095,12 +1137,26 @@ function startBridge(config) {
1095
1137
  return;
1096
1138
  }
1097
1139
 
1140
+ // Record the extension build making this call. An extension does not
1141
+ // auto-update, so without this "I still see the bug" and "the fix isn't
1142
+ // loaded yet" are indistinguishable from the host side. Diagnostics only.
1143
+ try {
1144
+ const ev = String(req.headers["x-ext-version"] || "").trim().slice(0, 32);
1145
+ if (ev) EXT_SEEN[ev] = Math.floor(Date.now() / 1000);
1146
+ } catch (_e) {}
1147
+
1098
1148
  const url = new URL(req.url, `http://${req.headers.host}`);
1099
1149
  let m;
1100
1150
 
1101
1151
  // Health
1102
1152
  if (url.pathname === "/health") {
1103
- send(200, { ok: true }); return;
1153
+ send(200, {
1154
+ ok: true,
1155
+ ext_versions_seen: Object.fromEntries(
1156
+ Object.entries(EXT_SEEN).sort((a, b) => b[1] - a[1]).slice(0, 8)
1157
+ ),
1158
+ });
1159
+ return;
1104
1160
  }
1105
1161
 
1106
1162
  // Ask
@@ -1190,7 +1246,7 @@ function startBridge(config) {
1190
1246
  const q = url.searchParams.get("q") || "";
1191
1247
  const limit = parseInt(url.searchParams.get("limit") || "30", 10);
1192
1248
  const results = searchSessions(q, limit);
1193
- send(200, { results });
1249
+ send(200, { q, results, hits: results }); // `hits` = Python-bridge parity (the extension reads it)
1194
1250
  return;
1195
1251
  }
1196
1252
 
@@ -1500,4 +1556,4 @@ function readBody(req) {
1500
1556
  });
1501
1557
  }
1502
1558
 
1503
- module.exports = { startBridge };
1559
+ module.exports = { startBridge, ensureTranscriptRetention };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-bridge-cli",
3
- "version": "2.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "Use Claude Code from your browser. Runs a local server that connects your browser tools to the Claude CLI.",
5
5
  "main": "lib/bridge.js",
6
6
  "bin": {