chat-recall 0.6.9 → 0.7.0

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/dist/watch.js CHANGED
@@ -115,6 +115,25 @@ var init_tool_backend = __esm({
115
115
  }
116
116
  });
117
117
 
118
+ // ../engine/dist/parsers/banners.js
119
+ function stripInjectedBanners(text) {
120
+ let result = text;
121
+ for (const re of INJECTED_BANNERS)
122
+ result = result.replace(re, " ");
123
+ return result;
124
+ }
125
+ var INJECTED_BANNERS;
126
+ var init_banners = __esm({
127
+ "../engine/dist/parsers/banners.js"() {
128
+ "use strict";
129
+ INJECTED_BANNERS = [
130
+ /MCP issues detected\. ?Run \/mcp list for status\.?/g,
131
+ /Context low[^\n]*Run \/compact[^\n]*/g,
132
+ /API Error:[^\n]{0,120}/g
133
+ ];
134
+ }
135
+ });
136
+
118
137
  // ../engine/dist/core/project-path-match.js
119
138
  function canonicalProjectPath(p) {
120
139
  return (p || "").trim().replace(/\\/g, "/").replace(/[-_]/g, "/").replace(/\/{2,}/g, "/").replace(/\/+$/, "").toLowerCase();
@@ -5031,715 +5050,149 @@ var init_settings = __esm({
5031
5050
  }
5032
5051
  });
5033
5052
 
5034
- // ../engine/dist/core/secret-redactor.js
5035
- import { statSync as statSync3 } from "fs";
5036
- function secretOf(rule, m) {
5037
- const raw = rule.secret != null ? m[rule.secret] : m[0];
5038
- if (raw == null)
5039
- return null;
5040
- if (rule.where && !rule.where(raw))
5041
- return null;
5042
- return raw;
5043
- }
5044
- function looksLikeSecretToken(t) {
5045
- if (t.length < 32)
5046
- return false;
5047
- if (/^[a-f0-9]+$/i.test(t))
5048
- return true;
5049
- return /[A-Z]/.test(t) && /[a-z]/.test(t) && /[0-9]/.test(t);
5050
- }
5051
- function findContextualSecrets(text) {
5052
- if (!text)
5053
- return [];
5054
- const ranges = [];
5055
- let m;
5056
- SECRET_CONTEXT_RE.lastIndex = 0;
5057
- while ((m = SECRET_CONTEXT_RE.exec(text)) !== null) {
5058
- const winStart = m.index + m[0].length;
5059
- const window = text.slice(winStart, winStart + 80);
5060
- HIGH_ENTROPY_TOKEN_RE.lastIndex = 0;
5061
- let tm;
5062
- while ((tm = HIGH_ENTROPY_TOKEN_RE.exec(window)) !== null) {
5063
- if (!looksLikeSecretToken(tm[0]))
5064
- continue;
5065
- if (/^[a-f0-9]+$/i.test(tm[0]) && /\b(?:commit|sha-?\d*|hash|digest|checksum|uuid|guid|etag|blob|ref)\b/i.test(window.slice(0, tm.index))) {
5066
- continue;
5067
- }
5068
- ranges.push({ start: winStart + tm.index, end: winStart + tm.index + tm[0].length });
5069
- break;
5070
- }
5053
+ // ../engine/dist/core/home-approval.js
5054
+ import { existsSync as existsSync3, readdirSync as readdirSync2, realpathSync } from "fs";
5055
+ import { homedir as homedir2 } from "os";
5056
+ import { join as join3, dirname as dirname3 } from "path";
5057
+ function normalizeHomePath(p) {
5058
+ const expanded = p.startsWith("~") ? join3(homedir2(), p.slice(1)) : p;
5059
+ const trimmed = expanded.replace(/[/\\]+$/, "");
5060
+ try {
5061
+ return realpathSync(trimmed);
5062
+ } catch {
5063
+ return trimmed;
5071
5064
  }
5072
- ranges.sort((a, b) => a.start - b.start);
5065
+ }
5066
+ function legacyAutoSyncedHomes() {
5067
+ const home = homedir2();
5073
5068
  const out = [];
5074
- for (const r of ranges) {
5075
- const last = out[out.length - 1];
5076
- if (last && r.start < last.end) {
5077
- last.end = Math.max(last.end, r.end);
5078
- continue;
5069
+ const add = (dir, marker) => {
5070
+ if (!existsSync3(join3(dir, marker)))
5071
+ return;
5072
+ const n = normalizeHomePath(dir);
5073
+ if (!out.includes(n))
5074
+ out.push(n);
5075
+ };
5076
+ const bases = [
5077
+ { base: join3(home, ".claude"), marker: "projects" },
5078
+ { base: join3(home, ".gemini"), marker: "tmp" },
5079
+ { base: join3(home, ".codex"), marker: "sessions" },
5080
+ { base: join3(home, ".gemini", "antigravity-cli"), marker: "brain" },
5081
+ { base: join3(home, ".local", "share", "opencode"), marker: "opencode.db" },
5082
+ { base: join3(home, ".cursor"), marker: "chats" }
5083
+ ];
5084
+ for (const { base: base2, marker } of bases) {
5085
+ add(base2, marker);
5086
+ const parent = dirname3(base2);
5087
+ const leaf = base2.slice(parent.length + 1);
5088
+ try {
5089
+ for (const e of readdirSync2(parent, { withFileTypes: true })) {
5090
+ if (!e.isDirectory())
5091
+ continue;
5092
+ if (e.name === leaf || !e.name.startsWith(leaf + "-"))
5093
+ continue;
5094
+ if (e.name === ".claude-code")
5095
+ continue;
5096
+ add(join3(parent, e.name), marker);
5097
+ }
5098
+ } catch {
5079
5099
  }
5080
- out.push({ ...r });
5081
5100
  }
5082
5101
  return out;
5083
5102
  }
5084
- function isEnvRedactionRequested() {
5085
- const v = process.env.CHAT_RECALL_REDACT_INDEX;
5086
- if (v === void 0)
5087
- return null;
5088
- return v === "1" || v === "true" || v === "yes";
5089
- }
5090
- function settingsView() {
5091
- let mtimeMs = -1;
5103
+ function grandfatherLegacyHomes() {
5104
+ let s;
5092
5105
  try {
5093
- mtimeMs = statSync3(settingsFilePath()).mtimeMs;
5106
+ s = loadSettings();
5094
5107
  } catch {
5108
+ return { seeded: [] };
5095
5109
  }
5096
- if (cache && cache.mtimeMs === mtimeMs)
5097
- return cache;
5098
- let enabled = false;
5099
- const extra = [];
5100
- try {
5101
- const s = loadSettings();
5102
- enabled = !!s.privacy?.redactIndex;
5103
- for (const r of s.privacy?.redactionRules ?? []) {
5104
- try {
5105
- extra.push({ label: r.label, pattern: new RegExp(r.pattern, "g") });
5106
- } catch {
5107
- }
5110
+ if (s.sources.homesGrandfathered)
5111
+ return { seeded: [] };
5112
+ const legacy = legacyAutoSyncedHomes();
5113
+ const approved = new Set((s.sources.approvedHomes ?? []).map(normalizeHomePath));
5114
+ const seeded = [];
5115
+ for (const p of legacy) {
5116
+ if (!approved.has(p)) {
5117
+ approved.add(p);
5118
+ seeded.push(p);
5108
5119
  }
5120
+ }
5121
+ s.sources.approvedHomes = [...approved];
5122
+ s.sources.homesGrandfathered = true;
5123
+ try {
5124
+ saveSettings(s);
5109
5125
  } catch {
5126
+ return { seeded: [] };
5110
5127
  }
5111
- cache = { mtimeMs, enabled, rules: [...DEFAULT_REDACTION_RULES, ...extra] };
5112
- return cache;
5128
+ return { seeded };
5113
5129
  }
5114
- function compileServerRule(spec) {
5115
- const name = spec.name || "(unnamed)";
5116
- if (!spec.regex)
5117
- return { name, reason: "empty pattern" };
5118
- if (spec.regex.length > MAX_PATTERN_LEN)
5119
- return { name, reason: `pattern longer than ${MAX_PATTERN_LEN} chars` };
5120
- let re;
5130
+ function primaryHomes() {
5131
+ const home = homedir2();
5132
+ const env = process.env;
5133
+ let s;
5121
5134
  try {
5122
- const flags = "g" + (spec.flags || "").replace(/[^ims]/g, "");
5123
- re = new RegExp(spec.regex, flags);
5124
- } catch (e) {
5125
- return { name, reason: `invalid regex: ${e instanceof Error ? e.message : e}` };
5135
+ s = loadSettings().sources;
5136
+ } catch {
5137
+ s = void 0;
5126
5138
  }
5127
- if (re.test(""))
5128
- return { name, reason: "matches the empty string (would redact everything)" };
5129
- re.lastIndex = 0;
5130
- if (re.test(CANARY))
5131
- return { name, reason: "matches benign text \u2014 too broad" };
5132
- re.lastIndex = 0;
5133
- return { label: `${spec.source === "pack" ? "pack" : "tenant"}:${name}`, pattern: re };
5139
+ const opencodeDb = env.CHAT_RECALL_OPENCODE_DB || s?.opencodeDbPath || join3(home, ".local", "share", "opencode", "opencode.db");
5140
+ const candidates2 = [
5141
+ env.CHAT_RECALL_CLAUDE_HOME || s?.claudeHome || join3(home, ".claude"),
5142
+ env.CHAT_RECALL_GEMINI_HOME || s?.geminiHome || join3(home, ".gemini"),
5143
+ env.CHAT_RECALL_CODEX_HOME || s?.codexHome || join3(home, ".codex"),
5144
+ env.CHAT_RECALL_AGY_HOME || s?.agyHome || join3(home, ".gemini", "antigravity-cli"),
5145
+ env.CHAT_RECALL_CURSOR_HOME || s?.cursorHome || join3(home, ".cursor"),
5146
+ dirOf(opencodeDb)
5147
+ ];
5148
+ return candidates2.map(normalizeHomePath);
5134
5149
  }
5135
- function installServerRulePack(pack) {
5136
- const rejected = [];
5137
- const rules = [];
5138
- for (const spec of pack.rules || []) {
5139
- if (rules.length >= MAX_PACK_RULES) {
5140
- rejected.push({ name: spec.name || "(unnamed)", reason: `pack exceeds ${MAX_PACK_RULES} rules` });
5141
- continue;
5142
- }
5143
- const out = compileServerRule(spec);
5144
- if ("reason" in out)
5145
- rejected.push(out);
5146
- else
5147
- rules.push(out);
5150
+ function homeDecision(path) {
5151
+ const p = normalizeHomePath(path);
5152
+ if (primaryHomes().includes(p))
5153
+ return "primary";
5154
+ let s;
5155
+ try {
5156
+ s = loadSettings().sources;
5157
+ } catch {
5158
+ return "pending";
5148
5159
  }
5149
- const version = pack.version || "";
5150
- serverPack = { version, rules };
5151
- return { version, accepted: rules.length, rejected };
5160
+ if ((s.declinedHomes ?? []).map(normalizeHomePath).includes(p))
5161
+ return "declined";
5162
+ if ((s.approvedHomes ?? []).map(normalizeHomePath).includes(p))
5163
+ return "approved";
5164
+ if ((s.extraClaudeHomes ?? []).map(normalizeHomePath).includes(p))
5165
+ return "approved";
5166
+ return "pending";
5152
5167
  }
5153
- function serverRulePackVersion() {
5154
- return serverPack ? serverPack.version || "unversioned" : null;
5168
+ function isHomeSynced(path) {
5169
+ const d = homeDecision(path);
5170
+ return d === "primary" || d === "approved";
5155
5171
  }
5156
- function activeRules() {
5157
- const base2 = settingsView().rules;
5158
- return serverPack && serverPack.rules.length > 0 ? [...base2, ...serverPack.rules] : base2;
5172
+ function setDecision(path, decision) {
5173
+ const p = normalizeHomePath(path);
5174
+ let s;
5175
+ try {
5176
+ s = loadSettings();
5177
+ } catch {
5178
+ return false;
5179
+ }
5180
+ const approved = new Set((s.sources.approvedHomes ?? []).map(normalizeHomePath));
5181
+ const declined = new Set((s.sources.declinedHomes ?? []).map(normalizeHomePath));
5182
+ approved.delete(p);
5183
+ declined.delete(p);
5184
+ (decision === "approved" ? approved : declined).add(p);
5185
+ s.sources.approvedHomes = [...approved];
5186
+ s.sources.declinedHomes = [...declined];
5187
+ try {
5188
+ saveSettings(s);
5189
+ } catch {
5190
+ return false;
5191
+ }
5192
+ return true;
5159
5193
  }
5160
- function isRedactionEnabled() {
5161
- const env = isEnvRedactionRequested();
5162
- if (env !== null)
5163
- return env;
5164
- return settingsView().enabled;
5165
- }
5166
- function redactSecrets(text, opts = {}) {
5167
- if (!opts.force && !isRedactionEnabled())
5168
- return text;
5169
- if (!text)
5170
- return text;
5171
- const rules = opts.rules || activeRules();
5172
- let out = text;
5173
- for (const r of rules) {
5174
- out = out.replace(r.pattern, (...args) => {
5175
- const whole = args[0];
5176
- const m = args.slice(0, args.length);
5177
- const secret = secretOf(r, m);
5178
- if (secret == null)
5179
- return whole;
5180
- if (opts.count)
5181
- opts.count.redactions++;
5182
- return whole.slice(0, whole.length - secret.length) + `[REDACTED:${r.label}]`;
5183
- });
5184
- }
5185
- const ctx = findContextualSecrets(out);
5186
- for (let i = ctx.length - 1; i >= 0; i--) {
5187
- const { start, end } = ctx[i];
5188
- if (opts.count)
5189
- opts.count.redactions++;
5190
- out = out.slice(0, start) + "[REDACTED:secret-context]" + out.slice(end);
5191
- }
5192
- return out;
5193
- }
5194
- function maskSecret(s) {
5195
- if (!s)
5196
- return "";
5197
- if (s.length <= 4)
5198
- return "*".repeat(s.length);
5199
- return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
5200
- }
5201
- function globalRegexFor(pattern) {
5202
- const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
5203
- const key2 = pattern.source + "\0" + flags;
5204
- let re = globalRegexCache.get(key2);
5205
- if (!re) {
5206
- re = new RegExp(pattern.source, flags);
5207
- globalRegexCache.set(key2, re);
5208
- }
5209
- return re;
5210
- }
5211
- function makeLineResolver(text) {
5212
- const starts = [];
5213
- for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
5214
- starts.push(i);
5215
- return (index) => {
5216
- let lo = 0, hi = starts.length;
5217
- while (lo < hi) {
5218
- const mid = lo + hi >> 1;
5219
- if (starts[mid] < index)
5220
- lo = mid + 1;
5221
- else
5222
- hi = mid;
5223
- }
5224
- return lo + 1;
5225
- };
5226
- }
5227
- function scanTextForFindings(text, opts = {}) {
5228
- if (!text)
5229
- return [];
5230
- const rules = opts.rules || activeRules();
5231
- const findings = [];
5232
- const seenSpans = /* @__PURE__ */ new Set();
5233
- const lineAt = makeLineResolver(text);
5234
- for (const r of rules) {
5235
- const re = globalRegexFor(r.pattern);
5236
- re.lastIndex = 0;
5237
- let m;
5238
- while ((m = re.exec(text)) !== null) {
5239
- const secret = secretOf(r, m);
5240
- if (secret == null) {
5241
- if (re.lastIndex === m.index)
5242
- re.lastIndex++;
5243
- continue;
5244
- }
5245
- const at = m.index + (m[0].length - secret.length);
5246
- const span = `${at}:${secret.length}`;
5247
- if (!seenSpans.has(span)) {
5248
- seenSpans.add(span);
5249
- findings.push({ rule: r.label, line: lineAt(at), preview: maskSecret(secret) });
5250
- }
5251
- if (re.lastIndex === m.index)
5252
- re.lastIndex++;
5253
- }
5254
- }
5255
- for (const { start, end } of findContextualSecrets(text)) {
5256
- const line = lineAt(start);
5257
- findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
5258
- }
5259
- return findings;
5260
- }
5261
- function privacyView() {
5262
- let mtimeMs = -1;
5263
- try {
5264
- mtimeMs = statSync3(settingsFilePath()).mtimeMs;
5265
- } catch {
5266
- }
5267
- if (privacyCache && privacyCache.mtimeMs === mtimeMs)
5268
- return privacyCache;
5269
- let redactToolOutputs = false, redactFilePaths = false;
5270
- try {
5271
- const s = loadSettings();
5272
- redactToolOutputs = !!s.privacy?.redactToolOutputs;
5273
- redactFilePaths = !!s.privacy?.redactFilePaths;
5274
- } catch {
5275
- }
5276
- privacyCache = { mtimeMs, redactToolOutputs, redactFilePaths };
5277
- return privacyCache;
5278
- }
5279
- function isToolOutputRedactionEnabled() {
5280
- return privacyView().redactToolOutputs;
5281
- }
5282
- function redactToolResultBody(toolName, body) {
5283
- if (!body)
5284
- return body;
5285
- return `[redacted: ${toolName} output, ${body.length} chars]`;
5286
- }
5287
- var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache, privacyCache;
5288
- var init_secret_redactor = __esm({
5289
- "../engine/dist/core/secret-redactor.js"() {
5290
- "use strict";
5291
- init_settings();
5292
- DEFAULT_REDACTION_RULES = [
5293
- // AWS access tokens. Prefix-anchored: AKIA, ASIA, ABIA (long-term,
5294
- // STS, EC2). Always exactly 16 hex/alphanumeric after the prefix.
5295
- // NOTE: this matches only the access-key-ID, NOT the secret key value —
5296
- // the `env-secret` rule below covers AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN.
5297
- { label: "aws-access-token", pattern: /\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\b/g },
5298
- // AWS STS session tokens. These are long base64 blobs with NO `NAME=` context
5299
- // when pasted bare into prose/code, so `env-secret` misses them — but they
5300
- // reliably start with one of a handful of base64 prefixes (the encoded
5301
- // `{"...` JSON header). Prefix-anchored → low false-positive. This is the
5302
- // bare-value form that leaked (`search('IQoJEXAMPLEfakeSESSIONtokenNOTreal01234567890abcDEF==...')`).
5303
- { label: "aws-session-token", pattern: /\b(?:IQoJ|FwoG|FwoH|FQoG|FQoH|FwoD)[A-Za-z0-9/+]{40,}={0,2}/g },
5304
- // Env/shell-assignment secrets — a VAR whose name ends in KEY/SECRET/TOKEN/
5305
- // PASSWORD/CREDENTIAL, then `=`/`:`, then the value. Zero-width lookbehind so
5306
- // only the VALUE is redacted (the var name stays for context). This is the
5307
- // dominant real-world leak vector (`export` dumps, .env files, `curl -H`,
5308
- // tool output) and is exactly what leaked AWS_SECRET_ACCESS_KEY and
5309
- // AWS_SESSION_TOKEN in cleartext while the access-key-ID rule above caught
5310
- // only the (least-sensitive) ID.
5311
- // Suffix list deliberately broad: DB_PASS (our own helm chart's name!),
5312
- // NPM_AUTH, SENTRY_DSN, SESSION/COOKIE secrets and BEARER vars all leaked
5313
- // past the original KEY/SECRET/TOKEN/PASSWORD-only list. Values under 12
5314
- // chars don't redact, so flag-ish vars (BYPASS=true) stay readable.
5315
- // Context CONSUMED, secret in group 1 — see RedactionRule.secret. As a
5316
- // lookbehind this was the second most expensive rule in the collector.
5317
- { label: "env-secret", secret: 1, pattern: /\b[A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PASS|PWD|CREDENTIAL|AUTH|DSN|SESSION|COOKIE|BEARER|SIGNATURE)S?["']?[ \t]{0,3}[:=][ \t]{0,3}["']?([A-Za-z0-9/+_.~=:@-]{12,})/g },
5318
- // Credentials embedded in a connection URL: scheme://user:PASSWORD@host.
5319
- // Redacts only the password segment (keeps scheme/user/host for context).
5320
- //
5321
- // Scheme-agnostic on purpose. The old rule allowlisted eight DB schemes, so
5322
- // `https://user:pass@host`, `ssh://`, `ftp://`, `smtp://` and every other
5323
- // userinfo URL walked through in cleartext — and a `jdbc:postgresql://…`
5324
- // prefix broke the \b anchor for the one family it did cover. Any scheme
5325
- // followed by userinfo is a credential, so match the shape, not a list.
5326
- { label: "url-password", secret: 1, pattern: /\b[a-zA-Z][a-zA-Z0-9+.-]{1,31}:\/\/[^:@\s/]{1,64}:([^@\s/]{1,256})(?=@)/g },
5327
- // Bare AWS secret-access-key VALUE: EXACTLY 40 base64 chars, word-bounded,
5328
- // containing upper+lower+digit. This is the gitleaks heuristic — the mixed-case
5329
- // requirement excludes 40-char lowercase-hex git SHAs, and the exact-40 bound
5330
- // excludes longer blobs/tokens. It catches the value when it appears bare in
5331
- // prose with NO `NAME=` and NO nearby context word (e.g. inside a markdown
5332
- // table or quoted in analysis) — the residual leak path the env-secret and
5333
- // contextual rules both miss. Placed AFTER env-secret/url-password so the
5334
- // keyed/URL forms keep their more-specific labels.
5335
- // The three stacked lookaheads each re-scanned the same 40 characters, which
5336
- // made this the MOST expensive rule of all 106 the collector runs (13.1% of a
5337
- // 29.5 MB scan). The mixed-case-plus-digit test is now one JS pass.
5338
- {
5339
- label: "aws-secret-key",
5340
- secret: 1,
5341
- pattern: /(?<![A-Za-z0-9/+])([A-Za-z0-9/+]{40})(?![A-Za-z0-9/+])/g,
5342
- where: (v) => /[a-z]/.test(v) && /[A-Z]/.test(v) && /[0-9]/.test(v)
5343
- },
5344
- // GitHub personal access tokens.
5345
- { label: "github-pat", pattern: /\bghp_[A-Za-z0-9]{36}\b/g },
5346
- // GitHub fine-grained PATs.
5347
- { label: "github-fine", pattern: /\bgithub_pat_[A-Za-z0-9_]{82}\b/g },
5348
- // GitHub OAuth.
5349
- { label: "github-oauth", pattern: /\bgho_[A-Za-z0-9]{36}\b/g },
5350
- // GitHub app + refresh.
5351
- { label: "github-app", pattern: /\b(?:ghs|ghu|ghr)_[A-Za-z0-9]{36}\b/g },
5352
- // Slack bot/user/app tokens.
5353
- { label: "slack-token", pattern: /\bxox[abopr]-[A-Za-z0-9-]{10,}\b/g },
5354
- // Slack incoming webhook URL.
5355
- { label: "slack-webhook", pattern: /\bhttps:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+\b/g },
5356
- // Stripe live + test keys.
5357
- { label: "stripe-key", pattern: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g },
5358
- // Anthropic.
5359
- { label: "anthropic-key", pattern: /\bsk-ant-[a-zA-Z0-9_-]{40,}\b/g },
5360
- // OpenAI. Two shapes, deliberately kept apart. The classic key is
5361
- // `sk-` + base62 with NO hyphens; the modern scoped keys carry an explicit
5362
- // proj/svcacct/admin segment first. Allowing hyphens after a bare `sk-`
5363
- // (which is what admitted sk-proj- in the first place) also matches ordinary
5364
- // kebab-case text — `sk-fix-the-login-redirect-bug`, `.sk-button-primary`,
5365
- // `sk-2024-01-01-backup` — and this corpus is full of branch names and CSS
5366
- // classes. Requiring the segment keeps sk-proj- coverage without that.
5367
- { label: "openai-key", pattern: /\bsk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}\b|\bsk-[A-Za-z0-9]{20,}\b/g },
5368
- // Google API key (AIza…).
5369
- { label: "google-api-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
5370
- // Generic JWT — three base64url segments.
5371
- { label: "jwt", pattern: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g },
5372
- // Private key blocks (PEM). The label is a free word run rather than a fixed
5373
- // list, so ENCRYPTED PRIVATE KEY and PGP PRIVATE KEY BLOCK are covered
5374
- // without another enumeration, and \1 forces END to name the same kind as
5375
- // BEGIN.
5376
- //
5377
- // The body is [^-], NOT [\s\S]*?. That is a performance boundary, not a
5378
- // style choice. An unanchored prefix with an unbounded lazy gap re-scans to
5379
- // end-of-input from every candidate start, so text carrying many BEGIN
5380
- // markers and no matching END costs O(n^2): 500KB of that measured 810ms
5381
- // here, and this runs synchronously on the server's event loop over
5382
- // tenant-supplied text (services/secret-rescan.ts). A PEM body is base64
5383
- // plus headers and contains no '-', so excluding it makes the scan stop at
5384
- // the next delimiter instead of at EOF — same matches, linear cost.
5385
- { label: "private-key", pattern: /-----BEGIN ([A-Z0-9 ]{0,32})PRIVATE KEY(?: BLOCK)?-----[^-]{0,10000}?-----END \1PRIVATE KEY(?: BLOCK)?-----/g },
5386
- // PuTTY keys are not PEM. Match the Private-Lines payload — the actual key
5387
- // material — rather than spanning the whole file, which would need the same
5388
- // unbounded gap the rule above just removed. Line-anchored and base64-only,
5389
- // so it cannot run away.
5390
- { label: "putty-key", pattern: /^Private-Lines:[ \t]*\d{1,4}[\r\n]+(?:[A-Za-z0-9+/=]{8,120}[\r\n]+){1,400}/gm },
5391
- // npm tokens (uuid-shaped — overlap with our UUID filter; intentional).
5392
- { label: "npm-token", pattern: /\bnpm_[A-Za-z0-9]{36}\b/g },
5393
- // .npmrc auth lines: //registry.npmjs.org/:_authToken=VALUE (the key is not
5394
- // ALL-CAPS, so the env-secret rule above never matched it). The leading `:`
5395
- // is required — it is what makes this npmrc syntax rather than any shell var
5396
- // ending in _AUTH, which env-secret already owns and labels better.
5397
- // [^\s"'`]+ so the closing quote is not swallowed, and a ${VAR} placeholder
5398
- // is left alone — redacting it destroys a config example and hides nothing.
5399
- // The rule the perf profile caught the wedged daemon inside: 98.94% of samples.
5400
- { label: "npmrc-auth", secret: 1, pattern: /:_(?:authToken|password|auth)[ \t]*=[ \t]*(?!\$\{)([^\s"'`]{12,})/gi },
5401
- // HTTP Authorization headers. env-secret requires an ALL-CAPS name, and
5402
- // `Authorization` is mixed case, so both Bearer and Basic walked straight
5403
- // through — including short opaque tokens the contextual pass also misses
5404
- // (it needs 32+ chars AND mixed-case-plus-digit).
5405
- // Same two guards as npmrc-auth: stop at a quote, and skip the obvious
5406
- // placeholders that fill documentation (<YOUR_TOKEN>, ${TOKEN}, xxx…).
5407
- { label: "auth-header", secret: 1, pattern: /\bauthorization[ \t]*:[ \t]*(?:bearer|basic|token)[ \t]+(?![<$])([^\s"'`<]{8,})/gi },
5408
- // Vendor tokens with unambiguous prefixes. Each is a fixed literal, so a
5409
- // false positive is essentially impossible and the cost of listing is low.
5410
- { label: "gitlab-pat", pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g },
5411
- { label: "slack-app-token", pattern: /\bxapp-\d-[A-Z0-9]{8,}-\d{10,}-[a-f0-9]{32,}\b/g },
5412
- { label: "sendgrid-key", pattern: /\bSG\.[A-Za-z0-9_-]{20,24}\.[A-Za-z0-9_-]{40,50}\b/g },
5413
- // (?<![-\w]) so `cache-key-<md5>` is not read as a mailgun key.
5414
- { label: "mailgun-key", pattern: /(?<![-\w])key-[a-f0-9]{32}\b/g },
5415
- { label: "digitalocean-pat", pattern: /\bdop_v1_[a-f0-9]{64}\b/g },
5416
- { label: "huggingface-token", pattern: /\bhf_[A-Za-z0-9]{30,}\b/g },
5417
- { label: "shopify-token", pattern: /\bshp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}\b/g },
5418
- { label: "pypi-token", pattern: /\bpypi-AgEIcHlwaS5vcmc[A-Za-z0-9_-]{50,}\b/g },
5419
- // No twilio-sid rule: AC…/SK… are Twilio SIDs, which are public account and
5420
- // key identifiers, not the auth token. Redacting them hid nothing and
5421
- // collided with 32-hex etags and MD5 sums.
5422
- // No discord-bot-token rule either: the shape (word.word.word) matched
5423
- // ordinary dotted symbols such as a.b.ApplicationContextProviderImpl.
5424
- { label: "telegram-bot-token", pattern: /\b\d{8,10}:AA[A-Za-z0-9_-]{32,}\b/g },
5425
- { label: "doppler-token", pattern: /\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9_-]{20,}\b/g },
5426
- // Azure Storage SAS signature — the `sig=` parameter is the actual secret.
5427
- { label: "azure-sas", secret: 1, pattern: /[?&]sig=([A-Za-z0-9%/+=]{20,})/g }
5428
- ];
5429
- SECRET_CONTEXT_RE = /\b(?:secret|password|passwd|credential|access[ _-]?key|private[ _-]?key|api[ _-]?key|auth(?:oriz\w*)?|bearer|token)\b/gi;
5430
- HIGH_ENTROPY_TOKEN_RE = /[A-Za-z0-9/+_-]{32,}/g;
5431
- cache = null;
5432
- CANARY = "the quick brown fox jumps over the lazy dog 0123456789 commit 8ba1f109551bd432803012645ac136ddd475d0fb src/index.ts:42";
5433
- MAX_PACK_RULES = 200;
5434
- MAX_PATTERN_LEN = 512;
5435
- serverPack = null;
5436
- globalRegexCache = /* @__PURE__ */ new Map();
5437
- privacyCache = null;
5438
- }
5439
- });
5440
-
5441
- // ../engine/dist/parsers/chunker.js
5442
- function stripInjectedBanners(text) {
5443
- let result = text;
5444
- for (const re of INJECTED_BANNERS)
5445
- result = result.replace(re, " ");
5446
- return result;
5447
- }
5448
- function cleanText(text) {
5449
- let result = text.replace(/<system-reminder>.*?<\/system-reminder>/gs, "");
5450
- result = stripInjectedBanners(result);
5451
- result = result.replace(/\x1b\[[0-9;]*m/g, "");
5452
- result = result.replace(/\s+/g, " ");
5453
- return result.trim();
5454
- }
5455
- function windowText(text, maxChars, maxWindows = 12) {
5456
- const clean = cleanText(text);
5457
- if (clean.length <= maxChars)
5458
- return clean ? [clean] : [];
5459
- const out = [];
5460
- const overlap = 80;
5461
- const step = Math.max(1, maxChars - overlap);
5462
- for (let i = 0; i < clean.length && out.length < maxWindows; i += step) {
5463
- out.push(clean.slice(i, i + maxChars));
5464
- }
5465
- return out;
5466
- }
5467
- function chunkSession(entry, content, maxChunks = 80, maxTokensPerChunk = 500) {
5468
- const chunks = [];
5469
- const maxChars = maxTokensPerChunk * 4;
5470
- const baseData = {
5471
- sessionId: entry.sessionId,
5472
- projectPath: entry.projectPath,
5473
- created: entry.created,
5474
- modified: entry.modified,
5475
- mtime: entry.fileMtime,
5476
- firstPrompt: entry.firstPrompt || content.firstPrompt,
5477
- sourceFile: content.sessionPath
5478
- };
5479
- for (let i = 0; i < Math.min(2, content.summaries.length); i++) {
5480
- const summary = content.summaries[i];
5481
- if (!summary?.trim())
5482
- continue;
5483
- const chunkText = cleanText(summary.slice(0, maxChars));
5484
- if (chunkText.length > 8e3) {
5485
- chunks.push({
5486
- chunkId: `${entry.sessionId}_summary_${i}`,
5487
- chunkType: "summary",
5488
- text: chunkText.slice(0, 8e3),
5489
- sourceLine: 0,
5490
- ...baseData
5491
- });
5492
- } else {
5493
- chunks.push({
5494
- chunkId: `${entry.sessionId}_summary_${i}`,
5495
- chunkType: "summary",
5496
- text: chunkText,
5497
- sourceLine: 0,
5498
- ...baseData
5499
- });
5500
- }
5501
- }
5502
- if (chunks.length >= maxChunks) {
5503
- return chunks.slice(0, maxChunks);
5504
- }
5505
- const firstPrompt = entry.firstPrompt || content.firstPrompt;
5506
- if (firstPrompt?.trim()) {
5507
- const firstLine = content.userMessages[0]?.lineNumber || 0;
5508
- chunks.push({
5509
- chunkId: `${entry.sessionId}_first_prompt`,
5510
- chunkType: "first_prompt",
5511
- text: cleanText(firstPrompt.slice(0, maxChars)),
5512
- sourceLine: firstLine,
5513
- ...baseData
5514
- });
5515
- }
5516
- if (chunks.length >= maxChunks) {
5517
- return chunks.slice(0, maxChunks);
5518
- }
5519
- const userStart = firstPrompt ? 1 : 0;
5520
- for (let i = userStart; i < content.userMessages.length; i++) {
5521
- if (chunks.length >= maxChunks)
5522
- break;
5523
- const msg = content.userMessages[i];
5524
- if (!msg.text.trim())
5525
- continue;
5526
- const windows = windowText(msg.text, maxChars);
5527
- for (let w = 0; w < windows.length; w++) {
5528
- if (chunks.length >= maxChunks)
5529
- break;
5530
- chunks.push({
5531
- chunkId: `${entry.sessionId}_user_${i}_${w}`,
5532
- chunkType: "user_context",
5533
- text: windows[w],
5534
- sourceLine: msg.lineNumber,
5535
- ...baseData
5536
- });
5537
- }
5538
- }
5539
- if (chunks.length >= maxChunks) {
5540
- return chunks.slice(0, maxChunks);
5541
- }
5542
- for (let i = 0; i < content.assistantMessages.length; i++) {
5543
- if (chunks.length >= maxChunks)
5544
- break;
5545
- const msg = content.assistantMessages[i];
5546
- if (!msg.text.trim() || msg.text.length <= 50)
5547
- continue;
5548
- const windows = windowText(msg.text, maxChars);
5549
- for (let w = 0; w < windows.length; w++) {
5550
- if (chunks.length >= maxChunks)
5551
- break;
5552
- chunks.push({
5553
- chunkId: `${entry.sessionId}_assistant_${i}_${w}`,
5554
- chunkType: "assistant",
5555
- text: windows[w],
5556
- sourceLine: msg.lineNumber,
5557
- ...baseData
5558
- });
5559
- }
5560
- }
5561
- if (chunks.length >= maxChunks) {
5562
- return chunks.slice(0, maxChunks);
5563
- }
5564
- if (content.toolResults.length > 0) {
5565
- const webSearches = content.toolResults.filter((r) => r.contentType === "web_search");
5566
- const otherResults = content.toolResults.filter((r) => r.contentType !== "web_search");
5567
- const resultsToProcess = [...webSearches.slice(0, 5), ...otherResults.slice(0, 10)];
5568
- const stripBodies = isToolOutputRedactionEnabled();
5569
- for (let i = 0; i < resultsToProcess.length; i++) {
5570
- if (chunks.length >= maxChunks)
5571
- break;
5572
- const result = resultsToProcess[i];
5573
- if (result.text.trim()) {
5574
- const body = stripBodies && result.contentType !== "web_search" ? redactToolResultBody(result.contentType, result.text) : result.text.slice(0, maxChars);
5575
- chunks.push({
5576
- chunkId: `${entry.sessionId}_${result.contentType}_${i}`,
5577
- chunkType: result.contentType,
5578
- text: cleanText(body),
5579
- sourceLine: result.lineNumber,
5580
- ...baseData
5581
- });
5582
- }
5583
- }
5584
- }
5585
- return chunks.slice(0, maxChunks);
5586
- }
5587
- var INJECTED_BANNERS;
5588
- var init_chunker = __esm({
5589
- "../engine/dist/parsers/chunker.js"() {
5590
- "use strict";
5591
- init_secret_redactor();
5592
- INJECTED_BANNERS = [
5593
- /MCP issues detected\. ?Run \/mcp list for status\.?/g,
5594
- /Context low[^\n]*Run \/compact[^\n]*/g,
5595
- /API Error:[^\n]{0,120}/g
5596
- ];
5597
- }
5598
- });
5599
-
5600
- // ../engine/dist/core/home-approval.js
5601
- import { existsSync as existsSync3, readdirSync as readdirSync2, realpathSync } from "fs";
5602
- import { homedir as homedir2 } from "os";
5603
- import { join as join3, dirname as dirname3 } from "path";
5604
- function normalizeHomePath(p) {
5605
- const expanded = p.startsWith("~") ? join3(homedir2(), p.slice(1)) : p;
5606
- const trimmed = expanded.replace(/[/\\]+$/, "");
5607
- try {
5608
- return realpathSync(trimmed);
5609
- } catch {
5610
- return trimmed;
5611
- }
5612
- }
5613
- function legacyAutoSyncedHomes() {
5614
- const home = homedir2();
5615
- const out = [];
5616
- const add = (dir, marker) => {
5617
- if (!existsSync3(join3(dir, marker)))
5618
- return;
5619
- const n = normalizeHomePath(dir);
5620
- if (!out.includes(n))
5621
- out.push(n);
5622
- };
5623
- const bases = [
5624
- { base: join3(home, ".claude"), marker: "projects" },
5625
- { base: join3(home, ".gemini"), marker: "tmp" },
5626
- { base: join3(home, ".codex"), marker: "sessions" },
5627
- { base: join3(home, ".gemini", "antigravity-cli"), marker: "brain" },
5628
- { base: join3(home, ".local", "share", "opencode"), marker: "opencode.db" },
5629
- { base: join3(home, ".cursor"), marker: "chats" }
5630
- ];
5631
- for (const { base: base2, marker } of bases) {
5632
- add(base2, marker);
5633
- const parent = dirname3(base2);
5634
- const leaf = base2.slice(parent.length + 1);
5635
- try {
5636
- for (const e of readdirSync2(parent, { withFileTypes: true })) {
5637
- if (!e.isDirectory())
5638
- continue;
5639
- if (e.name === leaf || !e.name.startsWith(leaf + "-"))
5640
- continue;
5641
- if (e.name === ".claude-code")
5642
- continue;
5643
- add(join3(parent, e.name), marker);
5644
- }
5645
- } catch {
5646
- }
5647
- }
5648
- return out;
5649
- }
5650
- function grandfatherLegacyHomes() {
5651
- let s;
5652
- try {
5653
- s = loadSettings();
5654
- } catch {
5655
- return { seeded: [] };
5656
- }
5657
- if (s.sources.homesGrandfathered)
5658
- return { seeded: [] };
5659
- const legacy = legacyAutoSyncedHomes();
5660
- const approved = new Set((s.sources.approvedHomes ?? []).map(normalizeHomePath));
5661
- const seeded = [];
5662
- for (const p of legacy) {
5663
- if (!approved.has(p)) {
5664
- approved.add(p);
5665
- seeded.push(p);
5666
- }
5667
- }
5668
- s.sources.approvedHomes = [...approved];
5669
- s.sources.homesGrandfathered = true;
5670
- try {
5671
- saveSettings(s);
5672
- } catch {
5673
- return { seeded: [] };
5674
- }
5675
- return { seeded };
5676
- }
5677
- function primaryHomes() {
5678
- const home = homedir2();
5679
- const env = process.env;
5680
- let s;
5681
- try {
5682
- s = loadSettings().sources;
5683
- } catch {
5684
- s = void 0;
5685
- }
5686
- const opencodeDb = env.CHAT_RECALL_OPENCODE_DB || s?.opencodeDbPath || join3(home, ".local", "share", "opencode", "opencode.db");
5687
- const candidates2 = [
5688
- env.CHAT_RECALL_CLAUDE_HOME || s?.claudeHome || join3(home, ".claude"),
5689
- env.CHAT_RECALL_GEMINI_HOME || s?.geminiHome || join3(home, ".gemini"),
5690
- env.CHAT_RECALL_CODEX_HOME || s?.codexHome || join3(home, ".codex"),
5691
- env.CHAT_RECALL_AGY_HOME || s?.agyHome || join3(home, ".gemini", "antigravity-cli"),
5692
- env.CHAT_RECALL_CURSOR_HOME || s?.cursorHome || join3(home, ".cursor"),
5693
- dirOf(opencodeDb)
5694
- ];
5695
- return candidates2.map(normalizeHomePath);
5696
- }
5697
- function homeDecision(path) {
5698
- const p = normalizeHomePath(path);
5699
- if (primaryHomes().includes(p))
5700
- return "primary";
5701
- let s;
5702
- try {
5703
- s = loadSettings().sources;
5704
- } catch {
5705
- return "pending";
5706
- }
5707
- if ((s.declinedHomes ?? []).map(normalizeHomePath).includes(p))
5708
- return "declined";
5709
- if ((s.approvedHomes ?? []).map(normalizeHomePath).includes(p))
5710
- return "approved";
5711
- if ((s.extraClaudeHomes ?? []).map(normalizeHomePath).includes(p))
5712
- return "approved";
5713
- return "pending";
5714
- }
5715
- function isHomeSynced(path) {
5716
- const d = homeDecision(path);
5717
- return d === "primary" || d === "approved";
5718
- }
5719
- function setDecision(path, decision) {
5720
- const p = normalizeHomePath(path);
5721
- let s;
5722
- try {
5723
- s = loadSettings();
5724
- } catch {
5725
- return false;
5726
- }
5727
- const approved = new Set((s.sources.approvedHomes ?? []).map(normalizeHomePath));
5728
- const declined = new Set((s.sources.declinedHomes ?? []).map(normalizeHomePath));
5729
- approved.delete(p);
5730
- declined.delete(p);
5731
- (decision === "approved" ? approved : declined).add(p);
5732
- s.sources.approvedHomes = [...approved];
5733
- s.sources.declinedHomes = [...declined];
5734
- try {
5735
- saveSettings(s);
5736
- } catch {
5737
- return false;
5738
- }
5739
- return true;
5740
- }
5741
- function approveHome(path) {
5742
- return setDecision(path, "approved");
5194
+ function approveHome(path) {
5195
+ return setDecision(path, "approved");
5743
5196
  }
5744
5197
  function declineHome(path) {
5745
5198
  return setDecision(path, "declined");
@@ -6141,7 +5594,7 @@ var init_model_pricing = __esm({
6141
5594
  });
6142
5595
 
6143
5596
  // ../engine/dist/parsers/session.js
6144
- import { createReadStream, existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync2, statSync as statSync4, openSync, readSync, closeSync } from "fs";
5597
+ import { createReadStream, existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync2, statSync as statSync3, openSync, readSync, closeSync } from "fs";
6145
5598
  import { createInterface } from "readline";
6146
5599
  import { join as join5, basename as basename2 } from "path";
6147
5600
  function removeCodeBlocks(text) {
@@ -6183,48 +5636,10 @@ function isValuableToolResult(content, toolName = "") {
6183
5636
  if (["WebSearch", "WebFetch"].includes(toolName) || content.toLowerCase().includes("http")) {
6184
5637
  return true;
6185
5638
  }
6186
- if (content.length > 5e3) {
6187
- return false;
6188
- }
6189
- return true;
6190
- }
6191
- function parseSessionsIndex(indexPath) {
6192
- if (!existsSync5(indexPath)) {
6193
- return [];
6194
- }
6195
- const data = JSON.parse(readFileSync2(indexPath, "utf-8"));
6196
- const entries = data.entries || [];
6197
- return entries.map((e) => ({
6198
- sessionId: e.sessionId || "",
6199
- fullPath: e.fullPath || "",
6200
- fileMtime: e.fileMtime || 0,
6201
- firstPrompt: e.firstPrompt || "",
6202
- messageCount: e.messageCount || 0,
6203
- created: e.created || "",
6204
- modified: e.modified || "",
6205
- gitBranch: e.gitBranch || "",
6206
- projectPath: e.projectPath || "",
6207
- isSidechain: e.isSidechain || false
6208
- }));
6209
- }
6210
- function* iterAllSessionIndices(claudeDir) {
6211
- const dir = claudeDir || claudeBackend.projectsDir();
6212
- if (!existsSync5(dir)) {
6213
- return;
6214
- }
6215
- const entries = readdirSync5(dir, { withFileTypes: true });
6216
- for (const entry of entries) {
6217
- if (!entry.isDirectory())
6218
- continue;
6219
- const projectDir = join5(dir, entry.name);
6220
- const indexPath = join5(projectDir, "sessions-index.json");
6221
- if (existsSync5(indexPath)) {
6222
- const sessions = parseSessionsIndex(indexPath);
6223
- if (sessions.length > 0) {
6224
- yield [projectDir, sessions];
6225
- }
6226
- }
5639
+ if (content.length > 5e3) {
5640
+ return false;
6227
5641
  }
5642
+ return true;
6228
5643
  }
6229
5644
  function hasSubagentsDir(sessionPath) {
6230
5645
  const dir = sessionPath.slice(0, -6);
@@ -6236,40 +5651,6 @@ function getSubagentPaths(sessionPath) {
6236
5651
  return [];
6237
5652
  return readdirSync5(subagentsDir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => join5(subagentsDir, f));
6238
5653
  }
6239
- function getSubagentProjectPath(sessionPath, folderDerivedPath) {
6240
- const subagentPaths = getSubagentPaths(sessionPath);
6241
- if (subagentPaths.length === 0)
6242
- return folderDerivedPath;
6243
- const cwdCounts = /* @__PURE__ */ new Map();
6244
- for (const filePath of subagentPaths) {
6245
- try {
6246
- const lines = readFileSync2(filePath, "utf-8").split("\n");
6247
- for (const line of lines) {
6248
- if (!line.trim())
6249
- continue;
6250
- try {
6251
- const entry = JSON.parse(line);
6252
- if (entry.type === "user" && typeof entry.cwd === "string" && entry.cwd) {
6253
- cwdCounts.set(entry.cwd, (cwdCounts.get(entry.cwd) ?? 0) + 1);
6254
- }
6255
- } catch {
6256
- }
6257
- }
6258
- } catch {
6259
- }
6260
- }
6261
- if (cwdCounts.size === 0)
6262
- return folderDerivedPath;
6263
- let best = folderDerivedPath;
6264
- let bestCount = 0;
6265
- for (const [cwd, count] of cwdCounts) {
6266
- if (count > bestCount) {
6267
- best = cwd;
6268
- bestCount = count;
6269
- }
6270
- }
6271
- return best;
6272
- }
6273
5654
  function readCwdFromJsonl(sessionPath) {
6274
5655
  try {
6275
5656
  if (!existsSync5(sessionPath))
@@ -6298,22 +5679,6 @@ function readCwdFromJsonl(sessionPath) {
6298
5679
  }
6299
5680
  return "";
6300
5681
  }
6301
- function getSessionMtime(sessionPath) {
6302
- const subagentPaths = getSubagentPaths(sessionPath);
6303
- if (subagentPaths.length === 0) {
6304
- return statSync4(sessionPath).mtimeMs;
6305
- }
6306
- let maxMtime = statSync4(sessionPath).mtimeMs;
6307
- for (const p of subagentPaths) {
6308
- try {
6309
- const m = statSync4(p).mtimeMs;
6310
- if (m > maxMtime)
6311
- maxMtime = m;
6312
- } catch {
6313
- }
6314
- }
6315
- return maxMtime;
6316
- }
6317
5682
  async function parseSessionFile(sessionPath, maxMessages = 50) {
6318
5683
  const sessionId = basename2(sessionPath, ".jsonl");
6319
5684
  const content = {
@@ -6551,76 +5916,10 @@ ${resultText}` : resultText;
6551
5916
  });
6552
5917
  return content;
6553
5918
  }
6554
- function* getAllSessions(claudeDir) {
6555
- const seenSessionIds = /* @__PURE__ */ new Set();
6556
- const projectDirs = claudeDir ? [claudeDir] : claudeProjectDirs();
6557
- for (const dir of projectDirs) {
6558
- for (const [, entries] of iterAllSessionIndices(dir)) {
6559
- for (const entry of entries) {
6560
- if (!seenSessionIds.has(entry.sessionId) && existsSync5(entry.fullPath)) {
6561
- seenSessionIds.add(entry.sessionId);
6562
- yield [entry, entry.fullPath];
6563
- }
6564
- }
6565
- }
6566
- if (!existsSync5(dir))
6567
- continue;
6568
- const dirEntries = readdirSync5(dir, { withFileTypes: true });
6569
- for (const projDirEntry of dirEntries) {
6570
- if (!projDirEntry.isDirectory())
6571
- continue;
6572
- const projectPath = join5(dir, projDirEntry.name);
6573
- const files = readdirSync5(projectPath);
6574
- for (const file of files) {
6575
- if (!file.endsWith(".jsonl"))
6576
- continue;
6577
- if (file.startsWith("agent-"))
6578
- continue;
6579
- if (file === "sessions-index.json")
6580
- continue;
6581
- const sessionId = basename2(file, ".jsonl");
6582
- if (seenSessionIds.has(sessionId))
6583
- continue;
6584
- const fullPath = join5(projectPath, file);
6585
- try {
6586
- const stat3 = statSync4(fullPath);
6587
- const derivedProjectPath = resolveProjectDirName(projDirEntry.name);
6588
- const effectiveMtime = getSessionMtime(fullPath);
6589
- let projectPath2 = "";
6590
- if (hasSubagentsDir(fullPath)) {
6591
- projectPath2 = getSubagentProjectPath(fullPath, derivedProjectPath);
6592
- } else {
6593
- const fromJsonl = readCwdFromJsonl(fullPath);
6594
- if (fromJsonl)
6595
- projectPath2 = fromJsonl;
6596
- }
6597
- if (!projectPath2)
6598
- projectPath2 = derivedProjectPath;
6599
- const entry = {
6600
- sessionId,
6601
- fullPath,
6602
- fileMtime: effectiveMtime,
6603
- firstPrompt: "",
6604
- messageCount: 0,
6605
- created: stat3.birthtime.toISOString(),
6606
- modified: new Date(effectiveMtime).toISOString(),
6607
- gitBranch: "",
6608
- projectPath: projectPath2,
6609
- isSidechain: false
6610
- };
6611
- seenSessionIds.add(sessionId);
6612
- yield [entry, fullPath];
6613
- } catch {
6614
- continue;
6615
- }
6616
- }
6617
- }
6618
- }
6619
- }
6620
5919
  var init_session = __esm({
6621
5920
  "../engine/dist/parsers/session.js"() {
6622
5921
  "use strict";
6623
- init_chunker();
5922
+ init_banners();
6624
5923
  init_claude2();
6625
5924
  init_tool_paths();
6626
5925
  init_project_dir_name();
@@ -7286,7 +6585,7 @@ var init_raw = __esm({
7286
6585
  // ../engine/dist/transcript/shadow.js
7287
6586
  import { gzipSync as gzipSync2, gunzipSync as gunzipSync2 } from "zlib";
7288
6587
  import { createHash as createHash2 } from "crypto";
7289
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync5 } from "fs";
6588
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync4 } from "fs";
7290
6589
  import { dirname as dirname7, join as join8 } from "path";
7291
6590
  function shadowRoot() {
7292
6591
  return join8(getDataDir(), "shadow");
@@ -7494,7 +6793,7 @@ function liveFingerprint(backend, rawId) {
7494
6793
  const loc = backend.findSession(rawId);
7495
6794
  if (!loc?.path)
7496
6795
  return "";
7497
- const st = statSync5(loc.path);
6796
+ const st = statSync4(loc.path);
7498
6797
  return `${st.mtimeMs}:${st.size}`;
7499
6798
  } catch {
7500
6799
  return "";
@@ -7512,7 +6811,7 @@ function seedShadow(sessionId, container) {
7512
6811
  }
7513
6812
  function shadowSizeOnDisk(tool, rawId) {
7514
6813
  try {
7515
- return statSync5(shadowFileFor(tool, rawId)).size;
6814
+ return statSync4(shadowFileFor(tool, rawId)).size;
7516
6815
  } catch {
7517
6816
  return 0;
7518
6817
  }
@@ -7530,7 +6829,7 @@ var init_shadow = __esm({
7530
6829
  });
7531
6830
 
7532
6831
  // ../engine/dist/core/live-session-scan.js
7533
- import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync4, statSync as statSync6, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
6832
+ import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync4, statSync as statSync5, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
7534
6833
  import { join as join9, basename as basename5 } from "path";
7535
6834
  function detectTool(sessionId) {
7536
6835
  return getBackendForId(sessionId)?.id ?? "claude";
@@ -7648,7 +6947,7 @@ function readSessionGroupText(group) {
7648
6947
  const parts = [];
7649
6948
  for (const p of group.paths) {
7650
6949
  try {
7651
- const st = statSync6(p);
6950
+ const st = statSync5(p);
7652
6951
  parts.push(`${p}:${st.mtimeMs}:${st.size}`);
7653
6952
  } catch {
7654
6953
  parts.push(`${p}:missing`);
@@ -7668,7 +6967,7 @@ function readSessionGroupText(group) {
7668
6967
  continue;
7669
6968
  }
7670
6969
  try {
7671
- mtime = Math.max(mtime, statSync6(p).mtimeMs);
6970
+ mtime = Math.max(mtime, statSync5(p).mtimeMs);
7672
6971
  } catch {
7673
6972
  }
7674
6973
  text = text ? mergeLineText(text, raw).text : raw;
@@ -8377,11 +7676,11 @@ var init_first_prompt = __esm({
8377
7676
  });
8378
7677
 
8379
7678
  // ../engine/dist/core/backends/tail-read.js
8380
- import { openSync as openSync4, readSync as readSync4, closeSync as closeSync4, statSync as statSync7 } from "node:fs";
7679
+ import { openSync as openSync4, readSync as readSync4, closeSync as closeSync4, statSync as statSync6 } from "node:fs";
8381
7680
  function readTailFromOffset(path, offset) {
8382
7681
  let size = 0;
8383
7682
  try {
8384
- size = statSync7(path).size;
7683
+ size = statSync6(path).size;
8385
7684
  } catch {
8386
7685
  return { text: "", newOffset: offset };
8387
7686
  }
@@ -8698,7 +7997,7 @@ var init_generic_engine = __esm({
8698
7997
  });
8699
7998
 
8700
7999
  // ../engine/dist/core/backends/claude.js
8701
- import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync5, statSync as statSync8, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
8000
+ import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync5, statSync as statSync7, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
8702
8001
  import { join as join10, basename as basename8 } from "path";
8703
8002
  function stripReminders(text) {
8704
8003
  return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
@@ -8828,7 +8127,7 @@ var init_claude2 = __esm({
8828
8127
  let mtime = 0;
8829
8128
  for (const c of copies) {
8830
8129
  try {
8831
- mtime = Math.max(mtime, statSync8(c.path).mtimeMs);
8130
+ mtime = Math.max(mtime, statSync7(c.path).mtimeMs);
8832
8131
  } catch {
8833
8132
  }
8834
8133
  }
@@ -8904,7 +8203,7 @@ var init_claude2 = __esm({
8904
8203
  const fullPath = join10(projPath, f);
8905
8204
  let stat3;
8906
8205
  try {
8907
- stat3 = statSync8(fullPath);
8206
+ stat3 = statSync7(fullPath);
8908
8207
  } catch {
8909
8208
  continue;
8910
8209
  }
@@ -9125,7 +8424,7 @@ var init_claude2 = __esm({
9125
8424
  const events = this.readEvents(rawId);
9126
8425
  let fileMtime = 0;
9127
8426
  try {
9128
- fileMtime = statSync8(located.path).mtimeMs;
8427
+ fileMtime = statSync7(located.path).mtimeMs;
9129
8428
  } catch {
9130
8429
  }
9131
8430
  return liveScanEditsFromEvents(events, this.fileToolMap, {
@@ -9180,13 +8479,13 @@ var init_claude2 = __esm({
9180
8479
  continue;
9181
8480
  const fp = join10(projPath, f);
9182
8481
  try {
9183
- const st = statSync8(fp);
8482
+ const st = statSync7(fp);
9184
8483
  let mtime = st.mtimeMs;
9185
8484
  const subDir = join10(projPath, f.slice(0, -6), "subagents");
9186
8485
  if (existsSync10(subDir)) {
9187
8486
  for (const sub of readdirSync7(subDir)) {
9188
8487
  try {
9189
- const subSt = statSync8(join10(subDir, sub));
8488
+ const subSt = statSync7(join10(subDir, sub));
9190
8489
  if (subSt.mtimeMs > mtime)
9191
8490
  mtime = subSt.mtimeMs;
9192
8491
  } catch {
@@ -9251,7 +8550,7 @@ var init_claude2 = __esm({
9251
8550
  let total = 0;
9252
8551
  for (const located of findSessionFiles(this.toRawId(prefixedId))) {
9253
8552
  try {
9254
- total += statSync8(located.path).size;
8553
+ total += statSync7(located.path).size;
9255
8554
  } catch {
9256
8555
  }
9257
8556
  }
@@ -9864,7 +9163,7 @@ var init_opencode = __esm({
9864
9163
  });
9865
9164
 
9866
9165
  // ../engine/dist/core/backends/codex.js
9867
- import { readdirSync as readdirSync8, readFileSync as readFileSync6, statSync as statSync9, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
9166
+ import { readdirSync as readdirSync8, readFileSync as readFileSync6, statSync as statSync8, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
9868
9167
  import { basename as basename9, dirname as dirname10, join as join12 } from "path";
9869
9168
  function parseShellWrites(cmd) {
9870
9169
  const out = [];
@@ -10072,7 +9371,7 @@ var init_codex2 = __esm({
10072
9371
  return null;
10073
9372
  let mtime = 0;
10074
9373
  try {
10075
- mtime = statSync9(located.path).mtimeMs;
9374
+ mtime = statSync8(located.path).mtimeMs;
10076
9375
  } catch {
10077
9376
  }
10078
9377
  return {
@@ -10092,7 +9391,7 @@ var init_codex2 = __esm({
10092
9391
  for (const filePath of walkRollouts(root)) {
10093
9392
  let stat3;
10094
9393
  try {
10095
- stat3 = statSync9(filePath);
9394
+ stat3 = statSync8(filePath);
10096
9395
  } catch {
10097
9396
  continue;
10098
9397
  }
@@ -10288,7 +9587,7 @@ var init_codex2 = __esm({
10288
9587
  let mtime = 0;
10289
9588
  try {
10290
9589
  raw = readFileSync6(path, "utf-8");
10291
- mtime = statSync9(path).mtimeMs;
9590
+ mtime = statSync8(path).mtimeMs;
10292
9591
  } catch {
10293
9592
  return [];
10294
9593
  }
@@ -10388,7 +9687,7 @@ var init_codex2 = __esm({
10388
9687
  const events = this.readEvents(this.toRawId(id));
10389
9688
  let fileMtime = 0;
10390
9689
  try {
10391
- fileMtime = statSync9(located.path).mtimeMs;
9690
+ fileMtime = statSync8(located.path).mtimeMs;
10392
9691
  } catch {
10393
9692
  }
10394
9693
  return liveScanEditsFromEvents(events, this.fileToolMap, {
@@ -10452,7 +9751,7 @@ var init_codex2 = __esm({
10452
9751
  const p = join12(dir, entry.name);
10453
9752
  let st;
10454
9753
  try {
10455
- st = statSync9(p);
9754
+ st = statSync8(p);
10456
9755
  } catch {
10457
9756
  continue;
10458
9757
  }
@@ -10519,7 +9818,7 @@ var init_codex2 = __esm({
10519
9818
  let mtime = 0;
10520
9819
  const push = (path, name) => {
10521
9820
  try {
10522
- const st = statSync9(path);
9821
+ const st = statSync8(path);
10523
9822
  files.push({ name, bytes: readFileSync6(path) });
10524
9823
  if (st.mtimeMs > mtime)
10525
9824
  mtime = st.mtimeMs;
@@ -10561,7 +9860,7 @@ var init_codex2 = __esm({
10561
9860
  if (!loc)
10562
9861
  return 0;
10563
9862
  try {
10564
- return statSync9(loc.path).size;
9863
+ return statSync8(loc.path).size;
10565
9864
  } catch {
10566
9865
  return 0;
10567
9866
  }
@@ -10578,7 +9877,7 @@ var init_codex2 = __esm({
10578
9877
  });
10579
9878
 
10580
9879
  // ../engine/dist/core/backends/agy.js
10581
- import { existsSync as existsSync12, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync10 } from "fs";
9880
+ import { existsSync as existsSync12, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync9 } from "fs";
10582
9881
  import { basename as basename10, dirname as dirname11, join as join13 } from "path";
10583
9882
  function agyTranscriptPath(logsDir) {
10584
9883
  const full = join13(logsDir, "transcript_full.jsonl");
@@ -10771,7 +10070,7 @@ var init_agy = __esm({
10771
10070
  return null;
10772
10071
  let mtime = 0;
10773
10072
  try {
10774
- mtime = statSync10(path).mtimeMs;
10073
+ mtime = statSync9(path).mtimeMs;
10775
10074
  } catch {
10776
10075
  }
10777
10076
  const projectPath = resolveAgyProject(dirname11(path), this.homeDir());
@@ -10799,7 +10098,7 @@ var init_agy = __esm({
10799
10098
  for (const rawId of sessionDirs) {
10800
10099
  const sessionPath = join13(brainDir, rawId);
10801
10100
  try {
10802
- if (!statSync10(sessionPath).isDirectory())
10101
+ if (!statSync9(sessionPath).isDirectory())
10803
10102
  continue;
10804
10103
  } catch {
10805
10104
  continue;
@@ -10809,7 +10108,7 @@ var init_agy = __esm({
10809
10108
  continue;
10810
10109
  let stat3;
10811
10110
  try {
10812
- stat3 = statSync10(filePath);
10111
+ stat3 = statSync9(filePath);
10813
10112
  } catch {
10814
10113
  continue;
10815
10114
  }
@@ -11074,7 +10373,7 @@ var init_agy = __esm({
11074
10373
  for (const rawId of sessionDirs) {
11075
10374
  const sessionPath = join13(brainDir, rawId);
11076
10375
  try {
11077
- if (!statSync10(sessionPath).isDirectory())
10376
+ if (!statSync9(sessionPath).isDirectory())
11078
10377
  continue;
11079
10378
  } catch {
11080
10379
  continue;
@@ -11083,7 +10382,7 @@ var init_agy = __esm({
11083
10382
  if (!filePath)
11084
10383
  continue;
11085
10384
  try {
11086
- const st = statSync10(filePath);
10385
+ const st = statSync9(filePath);
11087
10386
  if (st.mtimeMs < opts.sinceMs)
11088
10387
  continue;
11089
10388
  candidates2.push({ rawId, mtime: st.mtimeMs });
@@ -11111,7 +10410,7 @@ var init_agy = __esm({
11111
10410
  try {
11112
10411
  return {
11113
10412
  tool: "agy",
11114
- mtime: statSync10(loc.path).mtimeMs,
10413
+ mtime: statSync9(loc.path).mtimeMs,
11115
10414
  files: [{ name: basename10(loc.path), bytes: readFileSync7(loc.path) }]
11116
10415
  };
11117
10416
  } catch {
@@ -11126,7 +10425,7 @@ var init_agy = __esm({
11126
10425
  if (!loc)
11127
10426
  return 0;
11128
10427
  try {
11129
- return statSync10(loc.path).size;
10428
+ return statSync9(loc.path).size;
11130
10429
  } catch {
11131
10430
  return 0;
11132
10431
  }
@@ -11143,7 +10442,7 @@ var init_agy = __esm({
11143
10442
  });
11144
10443
 
11145
10444
  // ../engine/dist/core/backends/cursor-store.js
11146
- import { existsSync as existsSync13, readdirSync as readdirSync10, readFileSync as readFileSync8, statSync as statSync11 } from "fs";
10445
+ import { existsSync as existsSync13, readdirSync as readdirSync10, readFileSync as readFileSync8, statSync as statSync10 } from "fs";
11147
10446
  import { join as join14 } from "path";
11148
10447
  function cursorProjectSlug(absPath) {
11149
10448
  return absPath.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
@@ -11160,7 +10459,7 @@ function listChatsIn(chatsRoot) {
11160
10459
  const hashPath3 = join14(chatsRoot, workspaceHash);
11161
10460
  let chatIds;
11162
10461
  try {
11163
- if (!statSync11(hashPath3).isDirectory())
10462
+ if (!statSync10(hashPath3).isDirectory())
11164
10463
  continue;
11165
10464
  chatIds = readdirSync10(hashPath3);
11166
10465
  } catch {
@@ -11182,7 +10481,7 @@ function listChatsIn(chatsRoot) {
11182
10481
  let created = meta.createdAtMs ?? 0;
11183
10482
  if (!mtime || !created) {
11184
10483
  try {
11185
- const st = statSync11(dbPath);
10484
+ const st = statSync10(dbPath);
11186
10485
  mtime = mtime || st.mtimeMs;
11187
10486
  created = created || st.birthtimeMs || st.mtimeMs;
11188
10487
  } catch {
@@ -11225,7 +10524,7 @@ function findChat(chatsRoots, chatId) {
11225
10524
  let created = meta.createdAtMs ?? 0;
11226
10525
  if (!mtime || !created) {
11227
10526
  try {
11228
- const st = statSync11(dbPath);
10527
+ const st = statSync10(dbPath);
11229
10528
  mtime = mtime || st.mtimeMs;
11230
10529
  created = created || st.birthtimeMs || st.mtimeMs;
11231
10530
  } catch {
@@ -11528,7 +10827,7 @@ var init_cursor_store = __esm({
11528
10827
  });
11529
10828
 
11530
10829
  // ../engine/dist/core/backends/cursor-ide.js
11531
- import { existsSync as existsSync14, readFileSync as readFileSync9, readdirSync as readdirSync11, statSync as statSync12 } from "fs";
10830
+ import { existsSync as existsSync14, readFileSync as readFileSync9, readdirSync as readdirSync11, statSync as statSync11 } from "fs";
11532
10831
  import { dirname as dirname12, join as join15 } from "path";
11533
10832
  function jsonOf(value) {
11534
10833
  if (value == null)
@@ -11598,7 +10897,7 @@ function workspaceIndex(workspaceRoots) {
11598
10897
  for (const id of entries) {
11599
10898
  const dir = join15(root, id);
11600
10899
  try {
11601
- if (!statSync12(dir).isDirectory())
10900
+ if (!statSync11(dir).isDirectory())
11602
10901
  continue;
11603
10902
  } catch {
11604
10903
  continue;
@@ -11944,7 +11243,7 @@ var init_cursor_ide = __esm({
11944
11243
  });
11945
11244
 
11946
11245
  // ../engine/dist/core/backends/cursor.js
11947
- import { existsSync as existsSync15, readFileSync as readFileSync10, statSync as statSync13 } from "fs";
11246
+ import { existsSync as existsSync15, readFileSync as readFileSync10, statSync as statSync12 } from "fs";
11948
11247
  import { join as join16 } from "path";
11949
11248
  var PREFIX4, CursorBackend, cursorBackend;
11950
11249
  var init_cursor = __esm({
@@ -11997,7 +11296,7 @@ var init_cursor = __esm({
11997
11296
  if (r.surface === "cli") {
11998
11297
  let mtime2 = r.chat.updatedAtMs;
11999
11298
  try {
12000
- mtime2 = statSync13(r.chat.dbPath).mtimeMs || mtime2;
11299
+ mtime2 = statSync12(r.chat.dbPath).mtimeMs || mtime2;
12001
11300
  } catch {
12002
11301
  }
12003
11302
  return {
@@ -12010,7 +11309,7 @@ var init_cursor = __esm({
12010
11309
  }
12011
11310
  let mtime = r.composer.updatedAtMs;
12012
11311
  try {
12013
- mtime = statSync13(r.composer.globalDbPath).mtimeMs || mtime;
11312
+ mtime = statSync12(r.composer.globalDbPath).mtimeMs || mtime;
12014
11313
  } catch {
12015
11314
  }
12016
11315
  return {
@@ -12256,7 +11555,7 @@ var init_cursor = __esm({
12256
11555
  if (existsSync15(metaPath)) {
12257
11556
  files.push({ name: "meta.json", bytes: readFileSync10(metaPath) });
12258
11557
  }
12259
- return { tool: "cursor", mtime: statSync13(r.chat.dbPath).mtimeMs, files };
11558
+ return { tool: "cursor", mtime: statSync12(r.chat.dbPath).mtimeMs, files };
12260
11559
  }
12261
11560
  const events = readComposerEvents(r.composer);
12262
11561
  if (events.length === 0)
@@ -12264,7 +11563,7 @@ var init_cursor = __esm({
12264
11563
  const body = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
12265
11564
  return {
12266
11565
  tool: "cursor",
12267
- mtime: statSync13(r.composer.globalDbPath).mtimeMs,
11566
+ mtime: statSync12(r.composer.globalDbPath).mtimeMs,
12268
11567
  files: [{ name: `${r.composer.composerId}.jsonl`, bytes: Buffer.from(body, "utf8") }]
12269
11568
  };
12270
11569
  } catch {
@@ -12386,33 +11685,413 @@ function reportWalkProgress(p) {
12386
11685
  lastProgressWrite = now;
12387
11686
  updateCollectorHealth({ progress: p });
12388
11687
  }
12389
- var healthPath, PROGRESS_WRITE_INTERVAL_MS, lastProgressWrite, STALE_AFTER_MS;
12390
- var init_collector_health = __esm({
12391
- "../engine/dist/core/collector-health.js"() {
12392
- "use strict";
12393
- init_paths();
12394
- healthPath = () => join17(getDataDir(), "collector-health.json");
12395
- PROGRESS_WRITE_INTERVAL_MS = 2e3;
12396
- lastProgressWrite = 0;
12397
- STALE_AFTER_MS = 30 * 6e4;
11688
+ var healthPath, PROGRESS_WRITE_INTERVAL_MS, lastProgressWrite, STALE_AFTER_MS;
11689
+ var init_collector_health = __esm({
11690
+ "../engine/dist/core/collector-health.js"() {
11691
+ "use strict";
11692
+ init_paths();
11693
+ healthPath = () => join17(getDataDir(), "collector-health.json");
11694
+ PROGRESS_WRITE_INTERVAL_MS = 2e3;
11695
+ lastProgressWrite = 0;
11696
+ STALE_AFTER_MS = 30 * 6e4;
11697
+ }
11698
+ });
11699
+
11700
+ // ../engine/dist/core/credentials-env.js
11701
+ function envCredentials(env = process.env) {
11702
+ const token = (env.CHAT_RECALL_TOKEN || "").trim();
11703
+ const server = (env.CHAT_RECALL_SERVER || "").trim();
11704
+ if (!token || !server)
11705
+ return null;
11706
+ if (!/^https?:\/\//i.test(server))
11707
+ return null;
11708
+ return { serverUrl: normalise(server), token };
11709
+ }
11710
+ var normalise;
11711
+ var init_credentials_env = __esm({
11712
+ "../engine/dist/core/credentials-env.js"() {
11713
+ "use strict";
11714
+ normalise = (u) => u.trim().replace(/\/+$/, "");
11715
+ }
11716
+ });
11717
+
11718
+ // ../engine/dist/core/secret-redactor.js
11719
+ import { statSync as statSync13 } from "fs";
11720
+ function secretOf(rule, m) {
11721
+ const raw = rule.secret != null ? m[rule.secret] : m[0];
11722
+ if (raw == null)
11723
+ return null;
11724
+ if (rule.where && !rule.where(raw))
11725
+ return null;
11726
+ return raw;
11727
+ }
11728
+ function looksLikeSecretToken(t) {
11729
+ if (t.length < 32)
11730
+ return false;
11731
+ if (/^[a-f0-9]+$/i.test(t))
11732
+ return true;
11733
+ return /[A-Z]/.test(t) && /[a-z]/.test(t) && /[0-9]/.test(t);
11734
+ }
11735
+ function findContextualSecrets(text) {
11736
+ if (!text)
11737
+ return [];
11738
+ const ranges = [];
11739
+ let m;
11740
+ SECRET_CONTEXT_RE.lastIndex = 0;
11741
+ while ((m = SECRET_CONTEXT_RE.exec(text)) !== null) {
11742
+ const winStart = m.index + m[0].length;
11743
+ const window = text.slice(winStart, winStart + 80);
11744
+ HIGH_ENTROPY_TOKEN_RE.lastIndex = 0;
11745
+ let tm;
11746
+ while ((tm = HIGH_ENTROPY_TOKEN_RE.exec(window)) !== null) {
11747
+ if (!looksLikeSecretToken(tm[0]))
11748
+ continue;
11749
+ if (/^[a-f0-9]+$/i.test(tm[0]) && /\b(?:commit|sha-?\d*|hash|digest|checksum|uuid|guid|etag|blob|ref)\b/i.test(window.slice(0, tm.index))) {
11750
+ continue;
11751
+ }
11752
+ ranges.push({ start: winStart + tm.index, end: winStart + tm.index + tm[0].length });
11753
+ break;
11754
+ }
11755
+ }
11756
+ ranges.sort((a, b) => a.start - b.start);
11757
+ const out = [];
11758
+ for (const r of ranges) {
11759
+ const last = out[out.length - 1];
11760
+ if (last && r.start < last.end) {
11761
+ last.end = Math.max(last.end, r.end);
11762
+ continue;
11763
+ }
11764
+ out.push({ ...r });
11765
+ }
11766
+ return out;
11767
+ }
11768
+ function isEnvRedactionRequested() {
11769
+ const v = process.env.CHAT_RECALL_REDACT_INDEX;
11770
+ if (v === void 0)
11771
+ return null;
11772
+ return v === "1" || v === "true" || v === "yes";
11773
+ }
11774
+ function settingsView() {
11775
+ let mtimeMs = -1;
11776
+ try {
11777
+ mtimeMs = statSync13(settingsFilePath()).mtimeMs;
11778
+ } catch {
11779
+ }
11780
+ if (cache && cache.mtimeMs === mtimeMs)
11781
+ return cache;
11782
+ let enabled = false;
11783
+ const extra = [];
11784
+ try {
11785
+ const s = loadSettings();
11786
+ enabled = !!s.privacy?.redactIndex;
11787
+ for (const r of s.privacy?.redactionRules ?? []) {
11788
+ try {
11789
+ extra.push({ label: r.label, pattern: new RegExp(r.pattern, "g") });
11790
+ } catch {
11791
+ }
11792
+ }
11793
+ } catch {
11794
+ }
11795
+ cache = { mtimeMs, enabled, rules: [...DEFAULT_REDACTION_RULES, ...extra] };
11796
+ return cache;
11797
+ }
11798
+ function compileServerRule(spec) {
11799
+ const name = spec.name || "(unnamed)";
11800
+ if (!spec.regex)
11801
+ return { name, reason: "empty pattern" };
11802
+ if (spec.regex.length > MAX_PATTERN_LEN)
11803
+ return { name, reason: `pattern longer than ${MAX_PATTERN_LEN} chars` };
11804
+ let re;
11805
+ try {
11806
+ const flags = "g" + (spec.flags || "").replace(/[^ims]/g, "");
11807
+ re = new RegExp(spec.regex, flags);
11808
+ } catch (e) {
11809
+ return { name, reason: `invalid regex: ${e instanceof Error ? e.message : e}` };
11810
+ }
11811
+ if (re.test(""))
11812
+ return { name, reason: "matches the empty string (would redact everything)" };
11813
+ re.lastIndex = 0;
11814
+ if (re.test(CANARY))
11815
+ return { name, reason: "matches benign text \u2014 too broad" };
11816
+ re.lastIndex = 0;
11817
+ return { label: `${spec.source === "pack" ? "pack" : "tenant"}:${name}`, pattern: re };
11818
+ }
11819
+ function installServerRulePack(pack) {
11820
+ const rejected = [];
11821
+ const rules = [];
11822
+ for (const spec of pack.rules || []) {
11823
+ if (rules.length >= MAX_PACK_RULES) {
11824
+ rejected.push({ name: spec.name || "(unnamed)", reason: `pack exceeds ${MAX_PACK_RULES} rules` });
11825
+ continue;
11826
+ }
11827
+ const out = compileServerRule(spec);
11828
+ if ("reason" in out)
11829
+ rejected.push(out);
11830
+ else
11831
+ rules.push(out);
11832
+ }
11833
+ const version = pack.version || "";
11834
+ serverPack = { version, rules };
11835
+ return { version, accepted: rules.length, rejected };
11836
+ }
11837
+ function serverRulePackVersion() {
11838
+ return serverPack ? serverPack.version || "unversioned" : null;
11839
+ }
11840
+ function activeRules() {
11841
+ const base2 = settingsView().rules;
11842
+ return serverPack && serverPack.rules.length > 0 ? [...base2, ...serverPack.rules] : base2;
11843
+ }
11844
+ function isRedactionEnabled() {
11845
+ const env = isEnvRedactionRequested();
11846
+ if (env !== null)
11847
+ return env;
11848
+ return settingsView().enabled;
11849
+ }
11850
+ function redactSecrets(text, opts = {}) {
11851
+ if (!opts.force && !isRedactionEnabled())
11852
+ return text;
11853
+ if (!text)
11854
+ return text;
11855
+ const rules = opts.rules || activeRules();
11856
+ let out = text;
11857
+ for (const r of rules) {
11858
+ out = out.replace(r.pattern, (...args) => {
11859
+ const whole = args[0];
11860
+ const m = args.slice(0, args.length);
11861
+ const secret = secretOf(r, m);
11862
+ if (secret == null)
11863
+ return whole;
11864
+ if (opts.count)
11865
+ opts.count.redactions++;
11866
+ return whole.slice(0, whole.length - secret.length) + `[REDACTED:${r.label}]`;
11867
+ });
11868
+ }
11869
+ const ctx = findContextualSecrets(out);
11870
+ for (let i = ctx.length - 1; i >= 0; i--) {
11871
+ const { start, end } = ctx[i];
11872
+ if (opts.count)
11873
+ opts.count.redactions++;
11874
+ out = out.slice(0, start) + "[REDACTED:secret-context]" + out.slice(end);
11875
+ }
11876
+ return out;
11877
+ }
11878
+ function maskSecret(s) {
11879
+ if (!s)
11880
+ return "";
11881
+ if (s.length <= 4)
11882
+ return "*".repeat(s.length);
11883
+ return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
11884
+ }
11885
+ function globalRegexFor(pattern) {
11886
+ const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
11887
+ const key2 = pattern.source + "\0" + flags;
11888
+ let re = globalRegexCache.get(key2);
11889
+ if (!re) {
11890
+ re = new RegExp(pattern.source, flags);
11891
+ globalRegexCache.set(key2, re);
11892
+ }
11893
+ return re;
11894
+ }
11895
+ function makeLineResolver(text) {
11896
+ const starts = [];
11897
+ for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
11898
+ starts.push(i);
11899
+ return (index) => {
11900
+ let lo = 0, hi = starts.length;
11901
+ while (lo < hi) {
11902
+ const mid = lo + hi >> 1;
11903
+ if (starts[mid] < index)
11904
+ lo = mid + 1;
11905
+ else
11906
+ hi = mid;
11907
+ }
11908
+ return lo + 1;
11909
+ };
11910
+ }
11911
+ function scanTextForFindings(text, opts = {}) {
11912
+ if (!text)
11913
+ return [];
11914
+ const rules = opts.rules || activeRules();
11915
+ const findings = [];
11916
+ const seenSpans = /* @__PURE__ */ new Set();
11917
+ const lineAt = makeLineResolver(text);
11918
+ for (const r of rules) {
11919
+ const re = globalRegexFor(r.pattern);
11920
+ re.lastIndex = 0;
11921
+ let m;
11922
+ while ((m = re.exec(text)) !== null) {
11923
+ const secret = secretOf(r, m);
11924
+ if (secret == null) {
11925
+ if (re.lastIndex === m.index)
11926
+ re.lastIndex++;
11927
+ continue;
11928
+ }
11929
+ const at = m.index + (m[0].length - secret.length);
11930
+ const span = `${at}:${secret.length}`;
11931
+ if (!seenSpans.has(span)) {
11932
+ seenSpans.add(span);
11933
+ findings.push({ rule: r.label, line: lineAt(at), preview: maskSecret(secret) });
11934
+ }
11935
+ if (re.lastIndex === m.index)
11936
+ re.lastIndex++;
11937
+ }
12398
11938
  }
12399
- });
12400
-
12401
- // ../engine/dist/core/credentials-env.js
12402
- function envCredentials(env = process.env) {
12403
- const token = (env.CHAT_RECALL_TOKEN || "").trim();
12404
- const server = (env.CHAT_RECALL_SERVER || "").trim();
12405
- if (!token || !server)
12406
- return null;
12407
- if (!/^https?:\/\//i.test(server))
12408
- return null;
12409
- return { serverUrl: normalise(server), token };
11939
+ for (const { start, end } of findContextualSecrets(text)) {
11940
+ const line = lineAt(start);
11941
+ findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
11942
+ }
11943
+ return findings;
12410
11944
  }
12411
- var normalise;
12412
- var init_credentials_env = __esm({
12413
- "../engine/dist/core/credentials-env.js"() {
11945
+ var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache;
11946
+ var init_secret_redactor = __esm({
11947
+ "../engine/dist/core/secret-redactor.js"() {
12414
11948
  "use strict";
12415
- normalise = (u) => u.trim().replace(/\/+$/, "");
11949
+ init_settings();
11950
+ DEFAULT_REDACTION_RULES = [
11951
+ // AWS access tokens. Prefix-anchored: AKIA, ASIA, ABIA (long-term,
11952
+ // STS, EC2). Always exactly 16 hex/alphanumeric after the prefix.
11953
+ // NOTE: this matches only the access-key-ID, NOT the secret key value —
11954
+ // the `env-secret` rule below covers AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN.
11955
+ { label: "aws-access-token", pattern: /\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\b/g },
11956
+ // AWS STS session tokens. These are long base64 blobs with NO `NAME=` context
11957
+ // when pasted bare into prose/code, so `env-secret` misses them — but they
11958
+ // reliably start with one of a handful of base64 prefixes (the encoded
11959
+ // `{"...` JSON header). Prefix-anchored → low false-positive. This is the
11960
+ // bare-value form that leaked (`search('IQoJEXAMPLEfakeSESSIONtokenNOTreal01234567890abcDEF==...')`).
11961
+ { label: "aws-session-token", pattern: /\b(?:IQoJ|FwoG|FwoH|FQoG|FQoH|FwoD)[A-Za-z0-9/+]{40,}={0,2}/g },
11962
+ // Env/shell-assignment secrets — a VAR whose name ends in KEY/SECRET/TOKEN/
11963
+ // PASSWORD/CREDENTIAL, then `=`/`:`, then the value. Zero-width lookbehind so
11964
+ // only the VALUE is redacted (the var name stays for context). This is the
11965
+ // dominant real-world leak vector (`export` dumps, .env files, `curl -H`,
11966
+ // tool output) and is exactly what leaked AWS_SECRET_ACCESS_KEY and
11967
+ // AWS_SESSION_TOKEN in cleartext while the access-key-ID rule above caught
11968
+ // only the (least-sensitive) ID.
11969
+ // Suffix list deliberately broad: DB_PASS (our own helm chart's name!),
11970
+ // NPM_AUTH, SENTRY_DSN, SESSION/COOKIE secrets and BEARER vars all leaked
11971
+ // past the original KEY/SECRET/TOKEN/PASSWORD-only list. Values under 12
11972
+ // chars don't redact, so flag-ish vars (BYPASS=true) stay readable.
11973
+ // Context CONSUMED, secret in group 1 — see RedactionRule.secret. As a
11974
+ // lookbehind this was the second most expensive rule in the collector.
11975
+ { label: "env-secret", secret: 1, pattern: /\b[A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PASS|PWD|CREDENTIAL|AUTH|DSN|SESSION|COOKIE|BEARER|SIGNATURE)S?["']?[ \t]{0,3}[:=][ \t]{0,3}["']?([A-Za-z0-9/+_.~=:@-]{12,})/g },
11976
+ // Credentials embedded in a connection URL: scheme://user:PASSWORD@host.
11977
+ // Redacts only the password segment (keeps scheme/user/host for context).
11978
+ //
11979
+ // Scheme-agnostic on purpose. The old rule allowlisted eight DB schemes, so
11980
+ // `https://user:pass@host`, `ssh://`, `ftp://`, `smtp://` and every other
11981
+ // userinfo URL walked through in cleartext — and a `jdbc:postgresql://…`
11982
+ // prefix broke the \b anchor for the one family it did cover. Any scheme
11983
+ // followed by userinfo is a credential, so match the shape, not a list.
11984
+ { label: "url-password", secret: 1, pattern: /\b[a-zA-Z][a-zA-Z0-9+.-]{1,31}:\/\/[^:@\s/]{1,64}:([^@\s/]{1,256})(?=@)/g },
11985
+ // Bare AWS secret-access-key VALUE: EXACTLY 40 base64 chars, word-bounded,
11986
+ // containing upper+lower+digit. This is the gitleaks heuristic — the mixed-case
11987
+ // requirement excludes 40-char lowercase-hex git SHAs, and the exact-40 bound
11988
+ // excludes longer blobs/tokens. It catches the value when it appears bare in
11989
+ // prose with NO `NAME=` and NO nearby context word (e.g. inside a markdown
11990
+ // table or quoted in analysis) — the residual leak path the env-secret and
11991
+ // contextual rules both miss. Placed AFTER env-secret/url-password so the
11992
+ // keyed/URL forms keep their more-specific labels.
11993
+ // The three stacked lookaheads each re-scanned the same 40 characters, which
11994
+ // made this the MOST expensive rule of all 106 the collector runs (13.1% of a
11995
+ // 29.5 MB scan). The mixed-case-plus-digit test is now one JS pass.
11996
+ {
11997
+ label: "aws-secret-key",
11998
+ secret: 1,
11999
+ pattern: /(?<![A-Za-z0-9/+])([A-Za-z0-9/+]{40})(?![A-Za-z0-9/+])/g,
12000
+ where: (v) => /[a-z]/.test(v) && /[A-Z]/.test(v) && /[0-9]/.test(v)
12001
+ },
12002
+ // GitHub personal access tokens.
12003
+ { label: "github-pat", pattern: /\bghp_[A-Za-z0-9]{36}\b/g },
12004
+ // GitHub fine-grained PATs.
12005
+ { label: "github-fine", pattern: /\bgithub_pat_[A-Za-z0-9_]{82}\b/g },
12006
+ // GitHub OAuth.
12007
+ { label: "github-oauth", pattern: /\bgho_[A-Za-z0-9]{36}\b/g },
12008
+ // GitHub app + refresh.
12009
+ { label: "github-app", pattern: /\b(?:ghs|ghu|ghr)_[A-Za-z0-9]{36}\b/g },
12010
+ // Slack bot/user/app tokens.
12011
+ { label: "slack-token", pattern: /\bxox[abopr]-[A-Za-z0-9-]{10,}\b/g },
12012
+ // Slack incoming webhook URL.
12013
+ { label: "slack-webhook", pattern: /\bhttps:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+\b/g },
12014
+ // Stripe live + test keys.
12015
+ { label: "stripe-key", pattern: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g },
12016
+ // Anthropic.
12017
+ { label: "anthropic-key", pattern: /\bsk-ant-[a-zA-Z0-9_-]{40,}\b/g },
12018
+ // OpenAI. Two shapes, deliberately kept apart. The classic key is
12019
+ // `sk-` + base62 with NO hyphens; the modern scoped keys carry an explicit
12020
+ // proj/svcacct/admin segment first. Allowing hyphens after a bare `sk-`
12021
+ // (which is what admitted sk-proj- in the first place) also matches ordinary
12022
+ // kebab-case text — `sk-fix-the-login-redirect-bug`, `.sk-button-primary`,
12023
+ // `sk-2024-01-01-backup` — and this corpus is full of branch names and CSS
12024
+ // classes. Requiring the segment keeps sk-proj- coverage without that.
12025
+ { label: "openai-key", pattern: /\bsk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}\b|\bsk-[A-Za-z0-9]{20,}\b/g },
12026
+ // Google API key (AIza…).
12027
+ { label: "google-api-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
12028
+ // Generic JWT — three base64url segments.
12029
+ { label: "jwt", pattern: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g },
12030
+ // Private key blocks (PEM). The label is a free word run rather than a fixed
12031
+ // list, so ENCRYPTED PRIVATE KEY and PGP PRIVATE KEY BLOCK are covered
12032
+ // without another enumeration, and \1 forces END to name the same kind as
12033
+ // BEGIN.
12034
+ //
12035
+ // The body is [^-], NOT [\s\S]*?. That is a performance boundary, not a
12036
+ // style choice. An unanchored prefix with an unbounded lazy gap re-scans to
12037
+ // end-of-input from every candidate start, so text carrying many BEGIN
12038
+ // markers and no matching END costs O(n^2): 500KB of that measured 810ms
12039
+ // here, and this runs synchronously on the server's event loop over
12040
+ // tenant-supplied text (services/secret-rescan.ts). A PEM body is base64
12041
+ // plus headers and contains no '-', so excluding it makes the scan stop at
12042
+ // the next delimiter instead of at EOF — same matches, linear cost.
12043
+ { label: "private-key", pattern: /-----BEGIN ([A-Z0-9 ]{0,32})PRIVATE KEY(?: BLOCK)?-----[^-]{0,10000}?-----END \1PRIVATE KEY(?: BLOCK)?-----/g },
12044
+ // PuTTY keys are not PEM. Match the Private-Lines payload — the actual key
12045
+ // material — rather than spanning the whole file, which would need the same
12046
+ // unbounded gap the rule above just removed. Line-anchored and base64-only,
12047
+ // so it cannot run away.
12048
+ { label: "putty-key", pattern: /^Private-Lines:[ \t]*\d{1,4}[\r\n]+(?:[A-Za-z0-9+/=]{8,120}[\r\n]+){1,400}/gm },
12049
+ // npm tokens (uuid-shaped — overlap with our UUID filter; intentional).
12050
+ { label: "npm-token", pattern: /\bnpm_[A-Za-z0-9]{36}\b/g },
12051
+ // .npmrc auth lines: //registry.npmjs.org/:_authToken=VALUE (the key is not
12052
+ // ALL-CAPS, so the env-secret rule above never matched it). The leading `:`
12053
+ // is required — it is what makes this npmrc syntax rather than any shell var
12054
+ // ending in _AUTH, which env-secret already owns and labels better.
12055
+ // [^\s"'`]+ so the closing quote is not swallowed, and a ${VAR} placeholder
12056
+ // is left alone — redacting it destroys a config example and hides nothing.
12057
+ // The rule the perf profile caught the wedged daemon inside: 98.94% of samples.
12058
+ { label: "npmrc-auth", secret: 1, pattern: /:_(?:authToken|password|auth)[ \t]*=[ \t]*(?!\$\{)([^\s"'`]{12,})/gi },
12059
+ // HTTP Authorization headers. env-secret requires an ALL-CAPS name, and
12060
+ // `Authorization` is mixed case, so both Bearer and Basic walked straight
12061
+ // through — including short opaque tokens the contextual pass also misses
12062
+ // (it needs 32+ chars AND mixed-case-plus-digit).
12063
+ // Same two guards as npmrc-auth: stop at a quote, and skip the obvious
12064
+ // placeholders that fill documentation (<YOUR_TOKEN>, ${TOKEN}, xxx…).
12065
+ { label: "auth-header", secret: 1, pattern: /\bauthorization[ \t]*:[ \t]*(?:bearer|basic|token)[ \t]+(?![<$])([^\s"'`<]{8,})/gi },
12066
+ // Vendor tokens with unambiguous prefixes. Each is a fixed literal, so a
12067
+ // false positive is essentially impossible and the cost of listing is low.
12068
+ { label: "gitlab-pat", pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g },
12069
+ { label: "slack-app-token", pattern: /\bxapp-\d-[A-Z0-9]{8,}-\d{10,}-[a-f0-9]{32,}\b/g },
12070
+ { label: "sendgrid-key", pattern: /\bSG\.[A-Za-z0-9_-]{20,24}\.[A-Za-z0-9_-]{40,50}\b/g },
12071
+ // (?<![-\w]) so `cache-key-<md5>` is not read as a mailgun key.
12072
+ { label: "mailgun-key", pattern: /(?<![-\w])key-[a-f0-9]{32}\b/g },
12073
+ { label: "digitalocean-pat", pattern: /\bdop_v1_[a-f0-9]{64}\b/g },
12074
+ { label: "huggingface-token", pattern: /\bhf_[A-Za-z0-9]{30,}\b/g },
12075
+ { label: "shopify-token", pattern: /\bshp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}\b/g },
12076
+ { label: "pypi-token", pattern: /\bpypi-AgEIcHlwaS5vcmc[A-Za-z0-9_-]{50,}\b/g },
12077
+ // No twilio-sid rule: AC…/SK… are Twilio SIDs, which are public account and
12078
+ // key identifiers, not the auth token. Redacting them hid nothing and
12079
+ // collided with 32-hex etags and MD5 sums.
12080
+ // No discord-bot-token rule either: the shape (word.word.word) matched
12081
+ // ordinary dotted symbols such as a.b.ApplicationContextProviderImpl.
12082
+ { label: "telegram-bot-token", pattern: /\b\d{8,10}:AA[A-Za-z0-9_-]{32,}\b/g },
12083
+ { label: "doppler-token", pattern: /\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9_-]{20,}\b/g },
12084
+ // Azure Storage SAS signature — the `sig=` parameter is the actual secret.
12085
+ { label: "azure-sas", secret: 1, pattern: /[?&]sig=([A-Za-z0-9%/+=]{20,})/g }
12086
+ ];
12087
+ SECRET_CONTEXT_RE = /\b(?:secret|password|passwd|credential|access[ _-]?key|private[ _-]?key|api[ _-]?key|auth(?:oriz\w*)?|bearer|token)\b/gi;
12088
+ HIGH_ENTROPY_TOKEN_RE = /[A-Za-z0-9/+_-]{32,}/g;
12089
+ cache = null;
12090
+ CANARY = "the quick brown fox jumps over the lazy dog 0123456789 commit 8ba1f109551bd432803012645ac136ddd475d0fb src/index.ts:42";
12091
+ MAX_PACK_RULES = 200;
12092
+ MAX_PATTERN_LEN = 512;
12093
+ serverPack = null;
12094
+ globalRegexCache = /* @__PURE__ */ new Map();
12416
12095
  }
12417
12096
  });
12418
12097
 
@@ -14446,111 +14125,6 @@ var init_source_registry = __esm({
14446
14125
  }
14447
14126
  });
14448
14127
 
14449
- // ../engine/dist/parsers/session-source.js
14450
- var SessionSource;
14451
- var init_session_source = __esm({
14452
- "../engine/dist/parsers/session-source.js"() {
14453
- "use strict";
14454
- init_session();
14455
- init_chunker();
14456
- init_settings();
14457
- SessionSource = class {
14458
- sourceType = "session";
14459
- claudeDir;
14460
- constructor(claudeDir) {
14461
- this.claudeDir = claudeDir;
14462
- }
14463
- async *discover() {
14464
- if (!isSourceEnabled("claude", "sessions"))
14465
- return;
14466
- for (const [entry, sessionPath] of getAllSessions(this.claudeDir)) {
14467
- yield this.entryToItem(entry, sessionPath);
14468
- }
14469
- }
14470
- async parse(item) {
14471
- const content = await parseSessionFile(item.filePath);
14472
- const entry = this.itemToEntry(item);
14473
- const sessionChunks = chunkSession(entry, content);
14474
- item.extra = {
14475
- ...item.extra || {},
14476
- ...content.metadata
14477
- };
14478
- return sessionChunks.map((sc) => ({
14479
- chunkId: sc.chunkId,
14480
- itemId: sc.sessionId,
14481
- sourceType: "session",
14482
- // Empty title is preferable to "Session abc12345" placeholder text:
14483
- // the latter pollutes search results and gets surfaced in the UI as
14484
- // a misleading heading. Downstream renderers fall back to
14485
- // project path / time when title is empty.
14486
- title: sc.firstPrompt.slice(0, 100),
14487
- text: sc.text,
14488
- chunkType: sc.chunkType,
14489
- projectPath: sc.projectPath,
14490
- filePath: sc.sourceFile,
14491
- mtime: sc.mtime
14492
- }));
14493
- }
14494
- async extractLinks(item) {
14495
- if (item.filePath) {
14496
- try {
14497
- const { detectForkPredecessor: detectForkPredecessor2 } = await Promise.resolve().then(() => (init_raw(), raw_exports));
14498
- const predecessor = detectForkPredecessor2(item.filePath);
14499
- if (predecessor) {
14500
- return [{
14501
- sourceType: "session",
14502
- sourceId: item.id,
14503
- targetType: "session",
14504
- targetId: predecessor,
14505
- linkType: "forked_from",
14506
- confidence: 1
14507
- }];
14508
- }
14509
- } catch {
14510
- }
14511
- }
14512
- return [];
14513
- }
14514
- entryToItem(entry, sessionPath) {
14515
- return {
14516
- id: entry.sessionId,
14517
- sourceType: "session",
14518
- // Empty title when there's no first prompt — the UI then knows to
14519
- // fall back to summary or project path instead of rendering the
14520
- // useless "Session abc12345" placeholder as a heading.
14521
- title: entry.firstPrompt ? entry.firstPrompt.replace(/\n/g, " ").trim().slice(0, 100) : "",
14522
- projectPath: entry.projectPath,
14523
- filePath: sessionPath,
14524
- mtime: entry.fileMtime,
14525
- contentPreview: entry.firstPrompt?.slice(0, 200),
14526
- extra: {
14527
- messageCount: entry.messageCount,
14528
- gitBranch: entry.gitBranch,
14529
- created: entry.created,
14530
- modified: entry.modified,
14531
- isSidechain: entry.isSidechain
14532
- }
14533
- };
14534
- }
14535
- itemToEntry(item) {
14536
- const extra = item.extra || {};
14537
- return {
14538
- sessionId: item.id,
14539
- fullPath: item.filePath,
14540
- fileMtime: item.mtime,
14541
- firstPrompt: item.contentPreview || "",
14542
- messageCount: extra.messageCount || 0,
14543
- created: extra.created || "",
14544
- modified: extra.modified || "",
14545
- gitBranch: extra.gitBranch || "",
14546
- projectPath: item.projectPath,
14547
- isSidechain: extra.isSidechain || false
14548
- };
14549
- }
14550
- };
14551
- }
14552
- });
14553
-
14554
14128
  // ../engine/dist/core/utils.js
14555
14129
  import { existsSync as existsSync21, readdirSync as readdirSync15 } from "fs";
14556
14130
  import { homedir as homedir6 } from "os";
@@ -17907,7 +17481,6 @@ var init_plugins_source = __esm({
17907
17481
 
17908
17482
  // ../engine/dist/parsers/all-sources.js
17909
17483
  function buildSourceRegistry(registry = new SourceRegistry()) {
17910
- registry.register(new SessionSource());
17911
17484
  registry.register(new PlanSource());
17912
17485
  registry.register(new TaskSource());
17913
17486
  registry.register(new ClaudeMdSource());
@@ -17931,7 +17504,6 @@ var init_all_sources = __esm({
17931
17504
  "../engine/dist/parsers/all-sources.js"() {
17932
17505
  "use strict";
17933
17506
  init_source_registry();
17934
- init_session_source();
17935
17507
  init_plan_source();
17936
17508
  init_task_source();
17937
17509
  init_claude_md_source();
@@ -18416,7 +17988,7 @@ var init_http = __esm({
18416
17988
  CLI_VERSION = JSON.parse(readFileSync32(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
18417
17989
  } catch {
18418
17990
  try {
18419
- if (true) CLI_VERSION = "0.6.9";
17991
+ if (true) CLI_VERSION = "0.7.0";
18420
17992
  } catch {
18421
17993
  }
18422
17994
  }
@@ -18911,7 +18483,7 @@ var init_telemetry = __esm({
18911
18483
  init_sync_client();
18912
18484
  cliVersion = () => {
18913
18485
  try {
18914
- return true ? "0.6.9" : "dev";
18486
+ return true ? "0.7.0" : "dev";
18915
18487
  } catch {
18916
18488
  return "dev";
18917
18489
  }
@@ -20858,7 +20430,7 @@ var init_sync_client = __esm({
20858
20430
  ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
20859
20431
  } catch {
20860
20432
  try {
20861
- if (true) ownVersion = "0.6.9";
20433
+ if (true) ownVersion = "0.7.0";
20862
20434
  } catch {
20863
20435
  }
20864
20436
  }
@@ -23944,7 +23516,7 @@ try {
23944
23516
  ownVersion2 = JSON.parse(readFileSync39(join48(dir, "../package.json"), "utf-8")).version || ownVersion2;
23945
23517
  } catch {
23946
23518
  try {
23947
- if (true) ownVersion2 = "0.6.9";
23519
+ if (true) ownVersion2 = "0.7.0";
23948
23520
  } catch {
23949
23521
  }
23950
23522
  }
@@ -24292,7 +23864,7 @@ async function runCollectorMigration(opts) {
24292
23864
  init_sync_ledger();
24293
23865
  init_collector_health();
24294
23866
  import { existsSync as existsSync49, readFileSync as readFileSync45, statSync as statSync44 } from "fs";
24295
- var CLI_VERSION3 = true ? "0.6.9" : "0.0.0";
23867
+ var CLI_VERSION3 = true ? "0.7.0" : "0.0.0";
24296
23868
  var DEBOUNCE_MS = 5e3;
24297
23869
  var CLAUDE_DIR = claudeBackend.projectsDir();
24298
23870
  var PLANS_DIR = claudeBackend.plansDir();