chat-recall 0.5.5 → 0.5.6

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/cli.js CHANGED
@@ -2034,7 +2034,7 @@ var require_thread_stream = __commonJS({
2034
2034
  var { version: version2 } = require_package();
2035
2035
  var { EventEmitter } = __require("events");
2036
2036
  var { Worker } = __require("worker_threads");
2037
- var { join: join54 } = __require("path");
2037
+ var { join: join57 } = __require("path");
2038
2038
  var { pathToFileURL } = __require("url");
2039
2039
  var { wait } = require_wait();
2040
2040
  var {
@@ -2077,7 +2077,7 @@ var require_thread_stream = __commonJS({
2077
2077
  function createWorker(stream, opts) {
2078
2078
  const { filename, workerData } = opts;
2079
2079
  const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
2080
- const toExecute = bundlerOverrides["thread-stream-worker"] || join54(__dirname, "lib", "worker.js");
2080
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join57(__dirname, "lib", "worker.js");
2081
2081
  const worker = new Worker(toExecute, {
2082
2082
  ...opts.workerOpts,
2083
2083
  trackUnmanagedFds: false,
@@ -2480,7 +2480,7 @@ var require_transport = __commonJS({
2480
2480
  "use strict";
2481
2481
  var { createRequire: createRequire3 } = __require("module");
2482
2482
  var getCallers = require_caller();
2483
- var { join: join54, isAbsolute: isAbsolute2, sep: sep2 } = __require("node:path");
2483
+ var { join: join57, isAbsolute: isAbsolute3, sep: sep3 } = __require("node:path");
2484
2484
  var sleep2 = require_atomic_sleep();
2485
2485
  var onExit = require_on_exit_leak_free();
2486
2486
  var ThreadStream = require_thread_stream();
@@ -2543,7 +2543,7 @@ var require_transport = __commonJS({
2543
2543
  throw new Error("only one of target or targets can be specified");
2544
2544
  }
2545
2545
  if (targets) {
2546
- target = bundlerOverrides["pino-worker"] || join54(__dirname, "worker.js");
2546
+ target = bundlerOverrides["pino-worker"] || join57(__dirname, "worker.js");
2547
2547
  options.targets = targets.filter((dest) => dest.target).map((dest) => {
2548
2548
  return {
2549
2549
  ...dest,
@@ -2561,7 +2561,7 @@ var require_transport = __commonJS({
2561
2561
  });
2562
2562
  });
2563
2563
  } else if (pipeline) {
2564
- target = bundlerOverrides["pino-worker"] || join54(__dirname, "worker.js");
2564
+ target = bundlerOverrides["pino-worker"] || join57(__dirname, "worker.js");
2565
2565
  options.pipelines = [pipeline.map((dest) => {
2566
2566
  return {
2567
2567
  ...dest,
@@ -2579,16 +2579,16 @@ var require_transport = __commonJS({
2579
2579
  return buildStream(fixTarget(target), options, worker, sync);
2580
2580
  function fixTarget(origin) {
2581
2581
  origin = bundlerOverrides[origin] || origin;
2582
- if (isAbsolute2(origin) || origin.indexOf("file://") === 0) {
2582
+ if (isAbsolute3(origin) || origin.indexOf("file://") === 0) {
2583
2583
  return origin;
2584
2584
  }
2585
2585
  if (origin === "pino/file") {
2586
- return join54(__dirname, "..", "file.js");
2586
+ return join57(__dirname, "..", "file.js");
2587
2587
  }
2588
2588
  let fixTarget2;
2589
2589
  for (const filePath of callers) {
2590
2590
  try {
2591
- const context3 = filePath === "node:repl" ? process.cwd() + sep2 : filePath;
2591
+ const context3 = filePath === "node:repl" ? process.cwd() + sep3 : filePath;
2592
2592
  fixTarget2 = createRequire3(context3).resolve(origin);
2593
2593
  break;
2594
2594
  } catch (err) {
@@ -3572,7 +3572,7 @@ var require_safe_stable_stringify = __commonJS({
3572
3572
  return circularValue;
3573
3573
  }
3574
3574
  let res = "";
3575
- let join54 = ",";
3575
+ let join57 = ",";
3576
3576
  const originalIndentation = indentation;
3577
3577
  if (Array.isArray(value)) {
3578
3578
  if (value.length === 0) {
@@ -3586,7 +3586,7 @@ var require_safe_stable_stringify = __commonJS({
3586
3586
  indentation += spacer;
3587
3587
  res += `
3588
3588
  ${indentation}`;
3589
- join54 = `,
3589
+ join57 = `,
3590
3590
  ${indentation}`;
3591
3591
  }
3592
3592
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -3594,13 +3594,13 @@ ${indentation}`;
3594
3594
  for (; i < maximumValuesToStringify - 1; i++) {
3595
3595
  const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
3596
3596
  res += tmp2 !== void 0 ? tmp2 : "null";
3597
- res += join54;
3597
+ res += join57;
3598
3598
  }
3599
3599
  const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
3600
3600
  res += tmp !== void 0 ? tmp : "null";
3601
3601
  if (value.length - 1 > maximumBreadth) {
3602
3602
  const removedKeys = value.length - maximumBreadth - 1;
3603
- res += `${join54}"... ${getItemCount(removedKeys)} not stringified"`;
3603
+ res += `${join57}"... ${getItemCount(removedKeys)} not stringified"`;
3604
3604
  }
3605
3605
  if (spacer !== "") {
3606
3606
  res += `
@@ -3621,7 +3621,7 @@ ${originalIndentation}`;
3621
3621
  let separator = "";
3622
3622
  if (spacer !== "") {
3623
3623
  indentation += spacer;
3624
- join54 = `,
3624
+ join57 = `,
3625
3625
  ${indentation}`;
3626
3626
  whitespace = " ";
3627
3627
  }
@@ -3635,13 +3635,13 @@ ${indentation}`;
3635
3635
  const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
3636
3636
  if (tmp !== void 0) {
3637
3637
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
3638
- separator = join54;
3638
+ separator = join57;
3639
3639
  }
3640
3640
  }
3641
3641
  if (keyLength > maximumBreadth) {
3642
3642
  const removedKeys = keyLength - maximumBreadth;
3643
3643
  res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
3644
- separator = join54;
3644
+ separator = join57;
3645
3645
  }
3646
3646
  if (spacer !== "" && separator.length > 1) {
3647
3647
  res = `
@@ -3682,7 +3682,7 @@ ${originalIndentation}`;
3682
3682
  }
3683
3683
  const originalIndentation = indentation;
3684
3684
  let res = "";
3685
- let join54 = ",";
3685
+ let join57 = ",";
3686
3686
  if (Array.isArray(value)) {
3687
3687
  if (value.length === 0) {
3688
3688
  return "[]";
@@ -3695,7 +3695,7 @@ ${originalIndentation}`;
3695
3695
  indentation += spacer;
3696
3696
  res += `
3697
3697
  ${indentation}`;
3698
- join54 = `,
3698
+ join57 = `,
3699
3699
  ${indentation}`;
3700
3700
  }
3701
3701
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -3703,13 +3703,13 @@ ${indentation}`;
3703
3703
  for (; i < maximumValuesToStringify - 1; i++) {
3704
3704
  const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
3705
3705
  res += tmp2 !== void 0 ? tmp2 : "null";
3706
- res += join54;
3706
+ res += join57;
3707
3707
  }
3708
3708
  const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
3709
3709
  res += tmp !== void 0 ? tmp : "null";
3710
3710
  if (value.length - 1 > maximumBreadth) {
3711
3711
  const removedKeys = value.length - maximumBreadth - 1;
3712
- res += `${join54}"... ${getItemCount(removedKeys)} not stringified"`;
3712
+ res += `${join57}"... ${getItemCount(removedKeys)} not stringified"`;
3713
3713
  }
3714
3714
  if (spacer !== "") {
3715
3715
  res += `
@@ -3722,7 +3722,7 @@ ${originalIndentation}`;
3722
3722
  let whitespace = "";
3723
3723
  if (spacer !== "") {
3724
3724
  indentation += spacer;
3725
- join54 = `,
3725
+ join57 = `,
3726
3726
  ${indentation}`;
3727
3727
  whitespace = " ";
3728
3728
  }
@@ -3731,7 +3731,7 @@ ${indentation}`;
3731
3731
  const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
3732
3732
  if (tmp !== void 0) {
3733
3733
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
3734
- separator = join54;
3734
+ separator = join57;
3735
3735
  }
3736
3736
  }
3737
3737
  if (spacer !== "" && separator.length > 1) {
@@ -3789,20 +3789,20 @@ ${originalIndentation}`;
3789
3789
  indentation += spacer;
3790
3790
  let res2 = `
3791
3791
  ${indentation}`;
3792
- const join55 = `,
3792
+ const join58 = `,
3793
3793
  ${indentation}`;
3794
3794
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
3795
3795
  let i = 0;
3796
3796
  for (; i < maximumValuesToStringify - 1; i++) {
3797
3797
  const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
3798
3798
  res2 += tmp2 !== void 0 ? tmp2 : "null";
3799
- res2 += join55;
3799
+ res2 += join58;
3800
3800
  }
3801
3801
  const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
3802
3802
  res2 += tmp !== void 0 ? tmp : "null";
3803
3803
  if (value.length - 1 > maximumBreadth) {
3804
3804
  const removedKeys = value.length - maximumBreadth - 1;
3805
- res2 += `${join55}"... ${getItemCount(removedKeys)} not stringified"`;
3805
+ res2 += `${join58}"... ${getItemCount(removedKeys)} not stringified"`;
3806
3806
  }
3807
3807
  res2 += `
3808
3808
  ${originalIndentation}`;
@@ -3818,16 +3818,16 @@ ${originalIndentation}`;
3818
3818
  return '"[Object]"';
3819
3819
  }
3820
3820
  indentation += spacer;
3821
- const join54 = `,
3821
+ const join57 = `,
3822
3822
  ${indentation}`;
3823
3823
  let res = "";
3824
3824
  let separator = "";
3825
3825
  let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
3826
3826
  if (isTypedArrayWithEntries(value)) {
3827
- res += stringifyTypedArray(value, join54, maximumBreadth);
3827
+ res += stringifyTypedArray(value, join57, maximumBreadth);
3828
3828
  keys = keys.slice(value.length);
3829
3829
  maximumPropertiesToStringify -= value.length;
3830
- separator = join54;
3830
+ separator = join57;
3831
3831
  }
3832
3832
  if (deterministic) {
3833
3833
  keys = sort(keys, comparator);
@@ -3838,13 +3838,13 @@ ${indentation}`;
3838
3838
  const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
3839
3839
  if (tmp !== void 0) {
3840
3840
  res += `${separator}${strEscape(key2)}: ${tmp}`;
3841
- separator = join54;
3841
+ separator = join57;
3842
3842
  }
3843
3843
  }
3844
3844
  if (keyLength > maximumBreadth) {
3845
3845
  const removedKeys = keyLength - maximumBreadth;
3846
3846
  res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
3847
- separator = join54;
3847
+ separator = join57;
3848
3848
  }
3849
3849
  if (separator !== "") {
3850
3850
  res = `
@@ -4552,7 +4552,10 @@ var init_paths = __esm({
4552
4552
  // ../engine/dist/core/settings.js
4553
4553
  var settings_exports = {};
4554
4554
  __export(settings_exports, {
4555
+ PERSONAL_DIR_NAMES: () => PERSONAL_DIR_NAMES,
4555
4556
  PERSONAL_DIR_PATTERNS: () => PERSONAL_DIR_PATTERNS,
4557
+ _resetSecretsCacheForTests: () => _resetSecretsCacheForTests,
4558
+ _resetSettingsCacheForTests: () => _resetSettingsCacheForTests,
4556
4559
  _resetSourceSettingsCache: () => _resetSourceSettingsCache,
4557
4560
  applySettingsToEnv: () => applySettingsToEnv,
4558
4561
  isPersonalPath: () => isPersonalPath,
@@ -4671,7 +4674,8 @@ function defaultSync() {
4671
4674
  function isPersonalPath(projectPath, includeProjects = []) {
4672
4675
  if (includeProjects.some((inc) => inc && projectPath.includes(inc)))
4673
4676
  return false;
4674
- return PERSONAL_DIR_PATTERNS.some((pat) => projectPath.includes(pat));
4677
+ const norm = (projectPath.replace(/\\/g, "/") + "/").toLowerCase();
4678
+ return PERSONAL_DIR_NAMES.some((n) => norm.includes(`/${n.toLowerCase()}/`));
4675
4679
  }
4676
4680
  function defaultTeam() {
4677
4681
  return {
@@ -4791,11 +4795,24 @@ function _resetSourceSettingsCache() {
4791
4795
  function secretsPath() {
4792
4796
  return join2(getDataDir(), "secrets.env");
4793
4797
  }
4798
+ function _resetSecretsCacheForTests() {
4799
+ _secretsCache = null;
4800
+ }
4794
4801
  function loadSecrets() {
4795
4802
  const out = {};
4796
4803
  const p = secretsPath();
4797
4804
  if (!existsSync2(p))
4798
4805
  return out;
4806
+ let mtimeMs = -1, size = -1;
4807
+ try {
4808
+ const st = statSync2(p);
4809
+ mtimeMs = st.mtimeMs;
4810
+ size = st.size;
4811
+ } catch {
4812
+ }
4813
+ if (_secretsCache && _secretsCache.mtimeMs === mtimeMs && _secretsCache.size === size) {
4814
+ return { ..._secretsCache.value };
4815
+ }
4799
4816
  try {
4800
4817
  for (const line of readFileSync(p, "utf-8").split("\n")) {
4801
4818
  const t = line.trim();
@@ -4813,9 +4830,12 @@ function loadSecrets() {
4813
4830
  }
4814
4831
  } catch {
4815
4832
  }
4833
+ if (mtimeMs >= 0)
4834
+ _secretsCache = { mtimeMs, size, value: { ...out } };
4816
4835
  return out;
4817
4836
  }
4818
4837
  function writeSecrets(secrets) {
4838
+ _secretsCache = null;
4819
4839
  const p = secretsPath();
4820
4840
  mkdirSync2(dirname2(p), { recursive: true });
4821
4841
  const body = "# chat-recall secrets \u2014 NOT committed/synced. Mode 0600.\n" + Object.entries(secrets).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
@@ -4825,10 +4845,34 @@ function writeSecrets(secrets) {
4825
4845
  } catch {
4826
4846
  }
4827
4847
  }
4848
+ function applySecretOverlay(result) {
4849
+ const secrets = loadSecrets();
4850
+ for (const { block, field, env } of SECRET_FIELDS) {
4851
+ const blk = result[block];
4852
+ const value = process.env[env] || secrets[env] || blk[field];
4853
+ if (value)
4854
+ blk[field] = value;
4855
+ }
4856
+ return result;
4857
+ }
4858
+ function _resetSettingsCacheForTests() {
4859
+ _settingsCache = null;
4860
+ }
4828
4861
  function loadSettings() {
4829
4862
  const path = settingsPath();
4830
4863
  if (!existsSync2(path))
4831
4864
  return freshDefaults();
4865
+ let mtimeMs = -1, size = -1;
4866
+ try {
4867
+ const st = statSync2(path);
4868
+ mtimeMs = st.mtimeMs;
4869
+ size = st.size;
4870
+ } catch {
4871
+ }
4872
+ if (_settingsCache && _settingsCache.mtimeMs === mtimeMs && _settingsCache.size === size) {
4873
+ const cached = structuredClone(_settingsCache.value);
4874
+ return applySecretOverlay(cached);
4875
+ }
4832
4876
  try {
4833
4877
  const parsed = JSON.parse(readFileSync(path, "utf-8"));
4834
4878
  const base2 = freshDefaults();
@@ -4841,19 +4885,15 @@ function loadSettings() {
4841
4885
  sync: mergeSync(base2.sync, parsed.sync),
4842
4886
  team: mergeTeam(base2.team, parsed.team)
4843
4887
  };
4844
- const secrets = loadSecrets();
4845
- for (const { block, field, env } of SECRET_FIELDS) {
4846
- const blk = result[block];
4847
- const value = process.env[env] || secrets[env] || blk[field];
4848
- if (value)
4849
- blk[field] = value;
4850
- }
4851
- return result;
4888
+ if (mtimeMs >= 0)
4889
+ _settingsCache = { mtimeMs, size, value: structuredClone(result) };
4890
+ return applySecretOverlay(result);
4852
4891
  } catch {
4853
4892
  return freshDefaults();
4854
4893
  }
4855
4894
  }
4856
4895
  function saveSettings(settings2) {
4896
+ _settingsCache = null;
4857
4897
  const secrets = loadSecrets();
4858
4898
  const stripped = {
4859
4899
  ...settings2,
@@ -4967,7 +5007,7 @@ function mergeSettings(current, update) {
4967
5007
  const team2 = mergeTeam(current.team, update.team);
4968
5008
  return { v: SCHEMA_VERSION, embedding: e, summary: sm, sources: sources3, privacy, sync, team: team2 };
4969
5009
  }
4970
- var CLI_DETECTION_ORDER, SCHEMA_VERSION, PERSONAL_DIR_PATTERNS, _sourceSettingsCache, SECRET_FIELDS;
5010
+ var CLI_DETECTION_ORDER, SCHEMA_VERSION, PERSONAL_DIR_NAMES, PERSONAL_DIR_PATTERNS, _sourceSettingsCache, SECRET_FIELDS, _secretsCache, _settingsCache;
4971
5011
  var init_settings = __esm({
4972
5012
  "../engine/dist/core/settings.js"() {
4973
5013
  "use strict";
@@ -4981,17 +5021,18 @@ var init_settings = __esm({
4981
5021
  { preset: "aichat", bin: "aichat", cmd: "aichat --no-stream" }
4982
5022
  ];
4983
5023
  SCHEMA_VERSION = 3;
4984
- PERSONAL_DIR_PATTERNS = [
4985
- "/Pictures/",
4986
- "/Music/",
4987
- "/Movies/",
4988
- "/Videos/",
4989
- "/Photos Library",
4990
- "/Documents/",
4991
- "/Desktop/",
4992
- "/Downloads/",
4993
- "/Library/"
5024
+ PERSONAL_DIR_NAMES = [
5025
+ "Pictures",
5026
+ "Music",
5027
+ "Movies",
5028
+ "Videos",
5029
+ "Photos Library",
5030
+ "Documents",
5031
+ "Desktop",
5032
+ "Downloads",
5033
+ "Library"
4994
5034
  ];
5035
+ PERSONAL_DIR_PATTERNS = PERSONAL_DIR_NAMES.map((n) => `/${n}/`);
4995
5036
  _sourceSettingsCache = null;
4996
5037
  SECRET_FIELDS = [
4997
5038
  { block: "summary", field: "apiKey", env: "SUMMARY_API_KEY" },
@@ -5002,6 +5043,8 @@ var init_settings = __esm({
5002
5043
  { block: "embedding", field: "ollamaCloudApiKey", env: "OLLAMA_API_KEY" },
5003
5044
  { block: "embedding", field: "openaiCompatApiKey", env: "OPENAI_COMPAT_API_KEY" }
5004
5045
  ];
5046
+ _secretsCache = null;
5047
+ _settingsCache = null;
5005
5048
  }
5006
5049
  });
5007
5050
 
@@ -5101,14 +5144,14 @@ function primaryHomes() {
5101
5144
  s = void 0;
5102
5145
  }
5103
5146
  const opencodeDb = env.CHAT_RECALL_OPENCODE_DB || s?.opencodeDbPath || join3(home, ".local", "share", "opencode", "opencode.db");
5104
- const candidates = [
5147
+ const candidates2 = [
5105
5148
  env.CHAT_RECALL_CLAUDE_HOME || s?.claudeHome || join3(home, ".claude"),
5106
5149
  env.CHAT_RECALL_GEMINI_HOME || s?.geminiHome || join3(home, ".gemini"),
5107
5150
  env.CHAT_RECALL_CODEX_HOME || s?.codexHome || join3(home, ".codex"),
5108
5151
  env.CHAT_RECALL_AGY_HOME || s?.agyHome || join3(home, ".gemini", "antigravity-cli"),
5109
5152
  dirOf(opencodeDb)
5110
5153
  ];
5111
- return candidates.map(normalizeHomePath);
5154
+ return candidates2.map(normalizeHomePath);
5112
5155
  }
5113
5156
  function homeDecision(path) {
5114
5157
  const p = normalizeHomePath(path);
@@ -5522,28 +5565,55 @@ function maskSecret(s) {
5522
5565
  return "*".repeat(s.length);
5523
5566
  return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
5524
5567
  }
5568
+ function globalRegexFor(pattern) {
5569
+ const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
5570
+ const key = pattern.source + "\0" + flags;
5571
+ let re = globalRegexCache.get(key);
5572
+ if (!re) {
5573
+ re = new RegExp(pattern.source, flags);
5574
+ globalRegexCache.set(key, re);
5575
+ }
5576
+ return re;
5577
+ }
5578
+ function makeLineResolver(text) {
5579
+ const starts = [];
5580
+ for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
5581
+ starts.push(i);
5582
+ return (index) => {
5583
+ let lo = 0, hi = starts.length;
5584
+ while (lo < hi) {
5585
+ const mid = lo + hi >> 1;
5586
+ if (starts[mid] < index)
5587
+ lo = mid + 1;
5588
+ else
5589
+ hi = mid;
5590
+ }
5591
+ return lo + 1;
5592
+ };
5593
+ }
5525
5594
  function scanTextForFindings(text, opts = {}) {
5526
5595
  if (!text)
5527
5596
  return [];
5528
5597
  const rules = opts.rules || activeRules();
5529
5598
  const findings = [];
5530
5599
  const seenSpans = /* @__PURE__ */ new Set();
5600
+ const lineAt = makeLineResolver(text);
5531
5601
  for (const r of rules) {
5532
- const re = new RegExp(r.pattern.source, r.pattern.flags.includes("g") ? r.pattern.flags : r.pattern.flags + "g");
5602
+ const re = globalRegexFor(r.pattern);
5603
+ re.lastIndex = 0;
5533
5604
  let m;
5534
5605
  while ((m = re.exec(text)) !== null) {
5535
5606
  const span = `${m.index}:${m[0].length}`;
5536
5607
  if (!seenSpans.has(span)) {
5537
5608
  seenSpans.add(span);
5538
- const line = text.slice(0, m.index).split("\n").length;
5539
- findings.push({ rule: r.label, line, preview: maskSecret(m[0]) });
5609
+ findings.push({ rule: r.label, line: lineAt(m.index), preview: maskSecret(m[0]) });
5540
5610
  }
5541
5611
  if (re.lastIndex === m.index)
5542
5612
  re.lastIndex++;
5543
5613
  }
5544
5614
  }
5545
5615
  for (const { start, end } of findContextualSecrets(text)) {
5546
- const line = text.slice(0, start).split("\n").length;
5616
+ const line = lineAt(start);
5547
5617
  findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
5548
5618
  }
5549
5619
  return findings;
@@ -5596,7 +5666,7 @@ function redactToolResultBody(toolName, body) {
5596
5666
  return body;
5597
5667
  return `[redacted: ${toolName} output, ${body.length} chars]`;
5598
5668
  }
5599
- var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, privacyCache, PATH_REGEX;
5669
+ var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache, privacyCache, PATH_REGEX;
5600
5670
  var init_secret_redactor = __esm({
5601
5671
  "../engine/dist/core/secret-redactor.js"() {
5602
5672
  "use strict";
@@ -5734,6 +5804,7 @@ var init_secret_redactor = __esm({
5734
5804
  MAX_PACK_RULES = 200;
5735
5805
  MAX_PATTERN_LEN = 512;
5736
5806
  serverPack = null;
5807
+ globalRegexCache = /* @__PURE__ */ new Map();
5737
5808
  privacyCache = null;
5738
5809
  PATH_REGEX = /(?<![A-Za-z0-9_])(?:\/[^\s'"`<>]+|[A-Za-z]:\\[^\s'"`<>]+)/g;
5739
5810
  }
@@ -6494,8 +6565,8 @@ function decodeDirName(dirName) {
6494
6565
  }
6495
6566
  for (let len = 2; len <= segments.length - i; len++) {
6496
6567
  const joinedSegments = segments.slice(i, i + len);
6497
- for (const sep2 of ["-", "_", "."]) {
6498
- const candidate = join5(currentPath, joinedSegments.join(sep2));
6568
+ for (const sep3 of ["-", "_", "."]) {
6569
+ const candidate = join5(currentPath, joinedSegments.join(sep3));
6499
6570
  if (existsSync5(candidate)) {
6500
6571
  currentPath = candidate;
6501
6572
  i += len;
@@ -6865,8 +6936,8 @@ var init_claude = __esm({
6865
6936
 
6866
6937
  // ../engine/dist/transcript/gemini.js
6867
6938
  async function parseGeminiTranscript(sessionPath) {
6868
- const { readFileSync: readFileSync45 } = await import("fs");
6869
- return parseGeminiTranscriptText(readFileSync45(sessionPath, "utf-8"));
6939
+ const { readFileSync: readFileSync46 } = await import("fs");
6940
+ return parseGeminiTranscriptText(readFileSync46(sessionPath, "utf-8"));
6870
6941
  }
6871
6942
  function parseGeminiTranscriptText(text) {
6872
6943
  let content;
@@ -7322,11 +7393,11 @@ async function archiveRawSession(store, sessionId) {
7322
7393
  return await store.putRawSession(sessionId, exp.tool, Math.floor(exp.mtime), gz, size);
7323
7394
  }
7324
7395
  function detectForkPredecessor(sessionPath) {
7325
- const { readFileSync: readFileSync45, readdirSync: readdirSync33 } = requireFs();
7326
- const { dirname: dirname23, join: join54, basename: basename28 } = requirePath();
7396
+ const { readFileSync: readFileSync46, readdirSync: readdirSync33 } = requireFs();
7397
+ const { dirname: dirname25, join: join57, basename: basename28 } = requirePath();
7327
7398
  let text;
7328
7399
  try {
7329
- text = readFileSync45(sessionPath, "utf-8");
7400
+ text = readFileSync46(sessionPath, "utf-8");
7330
7401
  } catch {
7331
7402
  return null;
7332
7403
  }
@@ -7348,7 +7419,7 @@ function detectForkPredecessor(sessionPath) {
7348
7419
  return null;
7349
7420
  if (text.includes(`"uuid":"${headParent}"`))
7350
7421
  return null;
7351
- const dir = dirname23(sessionPath);
7422
+ const dir = dirname25(sessionPath);
7352
7423
  const self = basename28(sessionPath);
7353
7424
  let entries;
7354
7425
  try {
@@ -7360,7 +7431,7 @@ function detectForkPredecessor(sessionPath) {
7360
7431
  if (!f.endsWith(".jsonl") || f === self || f === "sessions-index.json")
7361
7432
  continue;
7362
7433
  try {
7363
- if (readFileSync45(join54(dir, f), "utf-8").includes(`"uuid":"${headParent}"`)) {
7434
+ if (readFileSync46(join57(dir, f), "utf-8").includes(`"uuid":"${headParent}"`)) {
7364
7435
  return basename28(f, ".jsonl");
7365
7436
  }
7366
7437
  } catch {
@@ -8161,6 +8232,20 @@ var init_tail_read = __esm({
8161
8232
  }
8162
8233
  });
8163
8234
 
8235
+ // ../engine/dist/core/flat-string.js
8236
+ function flatString(s) {
8237
+ if (!s)
8238
+ return "";
8239
+ if (s.length < 13)
8240
+ return s;
8241
+ return Buffer.from(s, "utf8").toString("utf8");
8242
+ }
8243
+ var init_flat_string = __esm({
8244
+ "../engine/dist/core/flat-string.js"() {
8245
+ "use strict";
8246
+ }
8247
+ });
8248
+
8164
8249
  // ../engine/dist/core/backends/gemini.js
8165
8250
  import { createHash as createHash3 } from "crypto";
8166
8251
  import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync3, statSync as statSync6 } from "fs";
@@ -8202,6 +8287,7 @@ var init_gemini2 = __esm({
8202
8287
  init_session_git();
8203
8288
  init_generic_engine();
8204
8289
  init_tail_read();
8290
+ init_flat_string();
8205
8291
  PREFIX = "gemini_";
8206
8292
  FULL_PARSE_MAX = 4 * 1024 * 1024;
8207
8293
  GeminiBackend = class {
@@ -8311,7 +8397,7 @@ var init_gemini2 = __esm({
8311
8397
  sessionId = readSessionId(fullPath, format);
8312
8398
  const messages = readGeminiMessages(fullPath, format);
8313
8399
  const firstUser = messages.find((m) => m.type === "user");
8314
- firstPrompt = (typeof firstUser?.text === "string" ? firstUser.text : flatten(firstUser?.content)).slice(0, 200);
8400
+ firstPrompt = flatString((typeof firstUser?.text === "string" ? firstUser.text : flatten(firstUser?.content)).slice(0, 200));
8315
8401
  messageCount = messages.length;
8316
8402
  } else if (format === "jsonl") {
8317
8403
  const lines = readHeadSync(fullPath, 1048576).split("\n");
@@ -8333,7 +8419,7 @@ var init_gemini2 = __esm({
8333
8419
  }
8334
8420
  if (m?.type !== "user")
8335
8421
  continue;
8336
- firstPrompt = (typeof m.text === "string" ? m.text : flatten(m.content)).slice(0, 200);
8422
+ firstPrompt = flatString((typeof m.text === "string" ? m.text : flatten(m.content)).slice(0, 200));
8337
8423
  break;
8338
8424
  }
8339
8425
  }
@@ -8509,7 +8595,7 @@ var init_gemini2 = __esm({
8509
8595
  if (!existsSync7(tmpRoot))
8510
8596
  return [];
8511
8597
  const projMap = this.loadProjectMap();
8512
- const candidates = [];
8598
+ const candidates2 = [];
8513
8599
  for (const proj of readdirSync5(tmpRoot, { withFileTypes: true })) {
8514
8600
  if (!proj.isDirectory())
8515
8601
  continue;
@@ -8540,13 +8626,13 @@ var init_gemini2 = __esm({
8540
8626
  if (st.mtimeMs < opts.sinceMs)
8541
8627
  continue;
8542
8628
  const trimLen = isJsonl ? 6 : 5;
8543
- candidates.push({ rawIdHint: f.slice(0, -trimLen), mtime: st.mtimeMs });
8629
+ candidates2.push({ rawIdHint: f.slice(0, -trimLen), mtime: st.mtimeMs });
8544
8630
  } catch {
8545
8631
  }
8546
8632
  }
8547
8633
  }
8548
- candidates.sort((a, b) => b.mtime - a.mtime);
8549
- const limited = opts.limitSessions ? candidates.slice(0, opts.limitSessions) : candidates;
8634
+ candidates2.sort((a, b) => b.mtime - a.mtime);
8635
+ const limited = opts.limitSessions ? candidates2.slice(0, opts.limitSessions) : candidates2;
8550
8636
  const edits = [];
8551
8637
  for (const c of limited) {
8552
8638
  const scan = this.liveScanEdits(c.rawIdHint);
@@ -8678,6 +8764,7 @@ var init_opencode = __esm({
8678
8764
  init_session_outcome();
8679
8765
  init_session_git();
8680
8766
  init_generic_engine();
8767
+ init_flat_string();
8681
8768
  PREFIX2 = "opencode_";
8682
8769
  OpencodeBackend = class {
8683
8770
  id = "opencode";
@@ -8845,7 +8932,7 @@ var init_opencode = __esm({
8845
8932
  if (firstUser) {
8846
8933
  try {
8847
8934
  const parsed = JSON.parse(firstUser.data);
8848
- firstPrompt = String(parsed?.text || "").slice(0, 200);
8935
+ firstPrompt = flatString(String(parsed?.text || "").slice(0, 200));
8849
8936
  } catch {
8850
8937
  }
8851
8938
  }
@@ -9336,6 +9423,7 @@ var init_codex2 = __esm({
9336
9423
  init_session_git();
9337
9424
  init_generic_engine();
9338
9425
  init_tail_read();
9426
+ init_flat_string();
9339
9427
  PREFIX3 = "codex_";
9340
9428
  CodexBackend = class {
9341
9429
  id = "codex";
@@ -9448,7 +9536,7 @@ var init_codex2 = __esm({
9448
9536
  if (ev.type === "event_msg" && ev.payload?.type === "user_message") {
9449
9537
  messageCount++;
9450
9538
  if (!firstPrompt && ev.payload.message) {
9451
- firstPrompt = String(ev.payload.message).slice(0, 200);
9539
+ firstPrompt = flatString(String(ev.payload.message).slice(0, 200));
9452
9540
  }
9453
9541
  } else if (ev.type === "response_item" && ev.payload?.type === "message") {
9454
9542
  messageCount++;
@@ -10021,6 +10109,7 @@ var init_agy = __esm({
10021
10109
  init_session_outcome();
10022
10110
  init_session_git();
10023
10111
  init_generic_engine();
10112
+ init_flat_string();
10024
10113
  PREFIX4 = "agy_";
10025
10114
  AgyBackend = class {
10026
10115
  id = "agy";
@@ -10121,7 +10210,7 @@ var init_agy = __esm({
10121
10210
  const content = obj.content;
10122
10211
  const match = content.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
10123
10212
  const rawText = match ? match[1].trim() : content.trim();
10124
- firstPrompt = rawText.slice(0, 200);
10213
+ firstPrompt = flatString(rawText.slice(0, 200));
10125
10214
  break;
10126
10215
  }
10127
10216
  }
@@ -10355,7 +10444,7 @@ var init_agy = __esm({
10355
10444
  if (brainRoots.length === 0)
10356
10445
  return [];
10357
10446
  const brainDir = brainRoots[0];
10358
- const candidates = [];
10447
+ const candidates2 = [];
10359
10448
  let sessionDirs;
10360
10449
  try {
10361
10450
  sessionDirs = readdirSync7(brainDir);
@@ -10377,12 +10466,12 @@ var init_agy = __esm({
10377
10466
  const st = statSync8(filePath);
10378
10467
  if (st.mtimeMs < opts.sinceMs)
10379
10468
  continue;
10380
- candidates.push({ rawId, mtime: st.mtimeMs });
10469
+ candidates2.push({ rawId, mtime: st.mtimeMs });
10381
10470
  } catch {
10382
10471
  }
10383
10472
  }
10384
- candidates.sort((a, b) => b.mtime - a.mtime);
10385
- const limited = opts.limitSessions ? candidates.slice(0, opts.limitSessions) : candidates;
10473
+ candidates2.sort((a, b) => b.mtime - a.mtime);
10474
+ const limited = opts.limitSessions ? candidates2.slice(0, opts.limitSessions) : candidates2;
10386
10475
  const edits = [];
10387
10476
  for (const c of limited) {
10388
10477
  const scan = this.liveScanEdits(c.rawId);
@@ -10654,18 +10743,43 @@ function updateShadow(sessionId, exp) {
10654
10743
  path
10655
10744
  };
10656
10745
  }
10657
- async function snapshotShadow(sessionId) {
10746
+ async function snapshotShadow(sessionId, opts = {}) {
10658
10747
  await Promise.resolve().then(() => (init_backends(), backends_exports));
10659
10748
  const { getBackendForId: getBackendForId2, getBackend: getBackend2 } = await Promise.resolve().then(() => (init_tool_backend(), tool_backend_exports));
10660
10749
  const backend = getBackendForId2(sessionId) ?? getBackend2("claude");
10661
10750
  const rawId = backend.toRawId(sessionId);
10751
+ const fp = liveFingerprint(backend, rawId);
10752
+ if (!opts.force && fp && lastLiveFingerprint.get(rawId) === fp) {
10753
+ return {
10754
+ status: "skipped",
10755
+ sessionId,
10756
+ tool: backend.id,
10757
+ container: null,
10758
+ recovered: 0,
10759
+ path: shadowFileFor(backend.id, rawId)
10760
+ };
10761
+ }
10662
10762
  let exp = null;
10663
10763
  try {
10664
10764
  exp = backend.exportRawSession(rawId);
10665
10765
  } catch {
10666
10766
  exp = null;
10667
10767
  }
10668
- return updateShadow(rawId, exp);
10768
+ const result = updateShadow(rawId, exp);
10769
+ if (fp && result.status !== "unavailable")
10770
+ lastLiveFingerprint.set(rawId, fp);
10771
+ return result;
10772
+ }
10773
+ function liveFingerprint(backend, rawId) {
10774
+ try {
10775
+ const loc = backend.findSession(rawId);
10776
+ if (!loc?.path)
10777
+ return "";
10778
+ const st = statSync9(loc.path);
10779
+ return `${st.mtimeMs}:${st.size}`;
10780
+ } catch {
10781
+ return "";
10782
+ }
10669
10783
  }
10670
10784
  function seedShadow(sessionId, container) {
10671
10785
  const tool = container.tool;
@@ -10684,7 +10798,7 @@ function shadowSizeOnDisk(tool, rawId) {
10684
10798
  return 0;
10685
10799
  }
10686
10800
  }
10687
- var WHOLE_FILE_TOOLS, SINGLETON_META;
10801
+ var WHOLE_FILE_TOOLS, SINGLETON_META, lastLiveFingerprint;
10688
10802
  var init_shadow = __esm({
10689
10803
  "../engine/dist/transcript/shadow.js"() {
10690
10804
  "use strict";
@@ -10692,6 +10806,27 @@ var init_shadow = __esm({
10692
10806
  init_raw();
10693
10807
  WHOLE_FILE_TOOLS = /* @__PURE__ */ new Set(["gemini"]);
10694
10808
  SINGLETON_META = /* @__PURE__ */ new Set(["mode", "permission-mode", "ai-title", "last-prompt", "pr-link", "summary"]);
10809
+ lastLiveFingerprint = /* @__PURE__ */ new Map();
10810
+ }
10811
+ });
10812
+
10813
+ // ../engine/dist/core/project-dir-name.js
10814
+ function looksWindowsEncoded(dirName) {
10815
+ return /^[A-Za-z]--/.test(dirName);
10816
+ }
10817
+ function decodeProjectDirName(dirName) {
10818
+ if (!dirName)
10819
+ return "";
10820
+ if (looksWindowsEncoded(dirName)) {
10821
+ const drive = dirName[0].toUpperCase();
10822
+ const rest = dirName.slice(3).replace(/-/g, "\\");
10823
+ return `${drive}:\\${rest}`;
10824
+ }
10825
+ return "/" + dirName.replace(/^-/, "").replace(/-/g, "/");
10826
+ }
10827
+ var init_project_dir_name = __esm({
10828
+ "../engine/dist/core/project-dir-name.js"() {
10829
+ "use strict";
10695
10830
  }
10696
10831
  });
10697
10832
 
@@ -10718,7 +10853,7 @@ function findSessionFile(sessionId) {
10718
10853
  path: candidate,
10719
10854
  projectDir: entry.name,
10720
10855
  // Project path is encoded by replacing slashes with dashes.
10721
- projectPath: entry.name.replace(/-/g, "/").replace(/^\//, "/")
10856
+ projectPath: decodeProjectDirName(entry.name)
10722
10857
  };
10723
10858
  }
10724
10859
  }
@@ -10747,7 +10882,7 @@ function findSessionFiles(sessionId) {
10747
10882
  out.push({
10748
10883
  path: candidate,
10749
10884
  projectDir: entry.name,
10750
- projectPath: entry.name.replace(/-/g, "/").replace(/^\//, "/")
10885
+ projectPath: decodeProjectDirName(entry.name)
10751
10886
  });
10752
10887
  }
10753
10888
  }
@@ -10771,7 +10906,31 @@ function resolveSessionContentGroups(sessionId) {
10771
10906
  const names = [...groups.keys()].sort((a, b) => a === "main" ? -1 : b === "main" ? 1 : a.localeCompare(b));
10772
10907
  return names.map((name) => ({ name, paths: groups.get(name) }));
10773
10908
  }
10909
+ function withSessionReadCache(fn) {
10910
+ readScopeDepth++;
10911
+ try {
10912
+ return fn();
10913
+ } finally {
10914
+ if (--readScopeDepth === 0)
10915
+ readScope = null;
10916
+ }
10917
+ }
10774
10918
  function readSessionGroupText(group) {
10919
+ let key = "";
10920
+ if (readScopeDepth > 0) {
10921
+ const parts = [];
10922
+ for (const p of group.paths) {
10923
+ try {
10924
+ const st = statSync10(p);
10925
+ parts.push(`${p}:${st.mtimeMs}:${st.size}`);
10926
+ } catch {
10927
+ parts.push(`${p}:missing`);
10928
+ }
10929
+ }
10930
+ key = parts.join("|");
10931
+ if (readScope && readScope.key === key)
10932
+ return readScope.value;
10933
+ }
10775
10934
  let text = "";
10776
10935
  let mtime = 0;
10777
10936
  for (const p of group.paths) {
@@ -10787,7 +10946,10 @@ function readSessionGroupText(group) {
10787
10946
  }
10788
10947
  text = text ? mergeLineText(text, raw).text : raw;
10789
10948
  }
10790
- return { text, mtime };
10949
+ const out = { text, mtime };
10950
+ if (readScopeDepth > 0 && key)
10951
+ readScope = { key, value: out };
10952
+ return out;
10791
10953
  }
10792
10954
  function resolveSessionContentPaths(sessionFile) {
10793
10955
  if (!hasSubagentsDir(sessionFile))
@@ -11010,7 +11172,7 @@ function findCodexSessionFileIn(root, sessionId) {
11010
11172
  }
11011
11173
  return scanDir(root);
11012
11174
  }
11013
- var geminiProjectMapCache, MAX_EVENT_LINE_BYTES;
11175
+ var readScope, readScopeDepth, geminiProjectMapCache, MAX_EVENT_LINE_BYTES;
11014
11176
  var init_live_session_scan = __esm({
11015
11177
  "../engine/dist/core/live-session-scan.js"() {
11016
11178
  "use strict";
@@ -11019,79 +11181,117 @@ var init_live_session_scan = __esm({
11019
11181
  init_tool_backend();
11020
11182
  init_tool_paths();
11021
11183
  init_backends();
11184
+ init_project_dir_name();
11185
+ readScope = null;
11186
+ readScopeDepth = 0;
11022
11187
  geminiProjectMapCache = null;
11023
11188
  MAX_EVENT_LINE_BYTES = 8 * 1024 * 1024;
11024
11189
  }
11025
11190
  });
11026
11191
 
11027
11192
  // ../engine/dist/core/first-prompt.js
11028
- import { createReadStream as createReadStream2, existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
11193
+ import { createReadStream as createReadStream2, existsSync as existsSync12, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
11194
+ import { StringDecoder } from "string_decoder";
11029
11195
  function stripBanners2(s) {
11030
11196
  let out = s;
11031
11197
  for (const re of BANNERS)
11032
11198
  out = out.replace(re, " ");
11033
11199
  return out.replace(/[ \t]{2,}/g, " ").trim();
11034
11200
  }
11201
+ function promptFromLine(raw, maxLength) {
11202
+ if (!raw.trim())
11203
+ return null;
11204
+ let obj;
11205
+ try {
11206
+ obj = JSON.parse(raw);
11207
+ } catch {
11208
+ return null;
11209
+ }
11210
+ if (obj.type !== "user")
11211
+ return null;
11212
+ if (obj.isSidechain === true)
11213
+ return null;
11214
+ const message = obj.message;
11215
+ if (!message)
11216
+ return null;
11217
+ const content = message.content;
11218
+ let text = "";
11219
+ if (typeof content === "string") {
11220
+ text = content;
11221
+ } else if (Array.isArray(content)) {
11222
+ const parts = [];
11223
+ for (const block of content) {
11224
+ if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
11225
+ parts.push(block.text);
11226
+ }
11227
+ }
11228
+ text = parts.join("\n");
11229
+ }
11230
+ if (!text)
11231
+ return null;
11232
+ if (text.includes("<system-reminder>"))
11233
+ return null;
11234
+ if (text.trim() === "[Request interrupted by user]")
11235
+ return null;
11236
+ const cleaned = stripBanners2(text);
11237
+ if (cleaned.length < 10)
11238
+ return null;
11239
+ return flatString(cleaned.slice(0, maxLength));
11240
+ }
11035
11241
  function extractFirstUserPromptSync(filePath, options = {}) {
11036
11242
  const maxLines = options.maxLines ?? 500;
11037
11243
  const maxLength = options.maxLength ?? 1e3;
11038
- if (!existsSync12(filePath))
11039
- return "";
11040
- let raw;
11244
+ const maxBytes = options.maxBytes ?? 4 * 1024 * 1024;
11245
+ let fd;
11041
11246
  try {
11042
- raw = readFileSync8(filePath, "utf-8");
11247
+ fd = openSync5(filePath, "r");
11043
11248
  } catch {
11044
11249
  return "";
11045
11250
  }
11046
- const lines = raw.split("\n");
11047
- const upTo = Math.min(lines.length, maxLines);
11048
- for (let i = 0; i < upTo; i++) {
11049
- const line = lines[i];
11050
- if (!line.trim())
11051
- continue;
11052
- let obj;
11251
+ try {
11252
+ const CHUNK = 64 * 1024;
11253
+ const buf = Buffer.allocUnsafe(CHUNK);
11254
+ const decoder = new StringDecoder("utf-8");
11255
+ let carry = "";
11256
+ let pos = 0;
11257
+ let lineNo = 0;
11258
+ for (; ; ) {
11259
+ const n = readSync5(fd, buf, 0, CHUNK, pos);
11260
+ if (n <= 0)
11261
+ break;
11262
+ pos += n;
11263
+ carry += decoder.write(buf.subarray(0, n));
11264
+ const parts = carry.split("\n");
11265
+ carry = parts.pop() ?? "";
11266
+ for (const line of parts) {
11267
+ if (++lineNo > maxLines)
11268
+ return "";
11269
+ const found = promptFromLine(line, maxLength);
11270
+ if (found !== null)
11271
+ return found;
11272
+ }
11273
+ if (pos >= maxBytes)
11274
+ return "";
11275
+ }
11276
+ carry += decoder.end();
11277
+ if (carry && lineNo < maxLines) {
11278
+ const found = promptFromLine(carry, maxLength);
11279
+ if (found !== null)
11280
+ return found;
11281
+ }
11282
+ return "";
11283
+ } finally {
11053
11284
  try {
11054
- obj = JSON.parse(line);
11285
+ closeSync5(fd);
11055
11286
  } catch {
11056
- continue;
11057
11287
  }
11058
- if (obj.type !== "user")
11059
- continue;
11060
- if (obj.isSidechain === true)
11061
- continue;
11062
- const message = obj.message;
11063
- if (!message)
11064
- continue;
11065
- const content = message.content;
11066
- let text = "";
11067
- if (typeof content === "string") {
11068
- text = content;
11069
- } else if (Array.isArray(content)) {
11070
- const parts = [];
11071
- for (const block of content) {
11072
- if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
11073
- parts.push(block.text);
11074
- }
11075
- }
11076
- text = parts.join("\n");
11077
- }
11078
- if (!text)
11079
- continue;
11080
- if (text.includes("<system-reminder>"))
11081
- continue;
11082
- if (text.trim() === "[Request interrupted by user]")
11083
- continue;
11084
- const cleaned = stripBanners2(text);
11085
- if (cleaned.length < 10)
11086
- continue;
11087
- return cleaned.slice(0, maxLength);
11088
11288
  }
11089
- return "";
11090
11289
  }
11091
11290
  var BANNERS;
11092
11291
  var init_first_prompt = __esm({
11093
11292
  "../engine/dist/core/first-prompt.js"() {
11094
11293
  "use strict";
11294
+ init_flat_string();
11095
11295
  BANNERS = [
11096
11296
  /MCP issues detected\. ?Run \/mcp list for status\.?/g,
11097
11297
  /Context low[^\n]*Run \/compact[^\n]*/g,
@@ -11101,7 +11301,7 @@ var init_first_prompt = __esm({
11101
11301
  });
11102
11302
 
11103
11303
  // ../engine/dist/core/backends/claude.js
11104
- import { existsSync as existsSync13, readdirSync as readdirSync9, readFileSync as readFileSync9, statSync as statSync11, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
11304
+ import { existsSync as existsSync13, readdirSync as readdirSync9, readFileSync as readFileSync8, statSync as statSync11, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
11105
11305
  import { join as join14, basename as basename11 } from "path";
11106
11306
  function stripReminders(text) {
11107
11307
  return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
@@ -11120,6 +11320,7 @@ var init_claude2 = __esm({
11120
11320
  init_session_git();
11121
11321
  init_first_prompt();
11122
11322
  init_tail_read();
11323
+ init_project_dir_name();
11123
11324
  init_generic_engine();
11124
11325
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11125
11326
  ClaudeBackend = class {
@@ -11196,13 +11397,13 @@ var init_claude2 = __esm({
11196
11397
  return null;
11197
11398
  let head;
11198
11399
  try {
11199
- const fd = openSync5(located.path, "r");
11400
+ const fd = openSync6(located.path, "r");
11200
11401
  try {
11201
11402
  const buf = Buffer.alloc(65536);
11202
- const n = readSync5(fd, buf, 0, buf.length, 0);
11403
+ const n = readSync6(fd, buf, 0, buf.length, 0);
11203
11404
  head = buf.toString("utf-8", 0, n);
11204
11405
  } finally {
11205
- closeSync5(fd);
11406
+ closeSync6(fd);
11206
11407
  }
11207
11408
  } catch {
11208
11409
  return null;
@@ -11261,11 +11462,11 @@ var init_claude2 = __esm({
11261
11462
  if (filter && !proj.name.toLowerCase().includes(filter))
11262
11463
  continue;
11263
11464
  const projPath = join14(root, proj.name);
11264
- const projectPath = proj.name.replace(/-/g, "/").replace(/^\//, "/");
11465
+ const projectPath = decodeProjectDirName(proj.name);
11265
11466
  const indexPath = join14(projPath, "sessions-index.json");
11266
11467
  if (existsSync13(indexPath)) {
11267
11468
  try {
11268
- const indexData = JSON.parse(readFileSync9(indexPath, "utf-8"));
11469
+ const indexData = JSON.parse(readFileSync8(indexPath, "utf-8"));
11269
11470
  const entries = indexData.entries || [];
11270
11471
  for (const entry of entries) {
11271
11472
  if (!entry.fullPath || !existsSync13(entry.fullPath))
@@ -11557,7 +11758,7 @@ var init_claude2 = __esm({
11557
11758
  * with quiet main + active subagents still surfaces.
11558
11759
  */
11559
11760
  collectRecentEdits(opts) {
11560
- const candidates = [];
11761
+ const candidates2 = [];
11561
11762
  for (const root of claudeProjectDirs()) {
11562
11763
  if (!existsSync13(root))
11563
11764
  continue;
@@ -11593,14 +11794,14 @@ var init_claude2 = __esm({
11593
11794
  }
11594
11795
  if (mtime < opts.sinceMs)
11595
11796
  continue;
11596
- candidates.push({ rawId: basename11(f, ".jsonl"), mtime });
11797
+ candidates2.push({ rawId: basename11(f, ".jsonl"), mtime });
11597
11798
  } catch {
11598
11799
  }
11599
11800
  }
11600
11801
  }
11601
11802
  }
11602
- candidates.sort((a, b) => b.mtime - a.mtime);
11603
- const limited = opts.limitSessions ? candidates.slice(0, opts.limitSessions) : candidates;
11803
+ candidates2.sort((a, b) => b.mtime - a.mtime);
11804
+ const limited = opts.limitSessions ? candidates2.slice(0, opts.limitSessions) : candidates2;
11604
11805
  const edits = [];
11605
11806
  for (const c of limited) {
11606
11807
  const scan = this.liveScanEdits(c.rawId);
@@ -11699,7 +11900,7 @@ __export(project_resolver_exports, {
11699
11900
  });
11700
11901
  import { execSync } from "child_process";
11701
11902
  import { createHash as createHash6 } from "crypto";
11702
- import { existsSync as existsSync14, readFileSync as readFileSync10, realpathSync as realpathSync2, readdirSync as readdirSync10, statSync as statSync12 } from "fs";
11903
+ import { existsSync as existsSync14, readFileSync as readFileSync9, realpathSync as realpathSync2, readdirSync as readdirSync10, statSync as statSync12 } from "fs";
11703
11904
  import { basename as basename12, dirname as dirname12, isAbsolute, join as join15, resolve, sep } from "path";
11704
11905
  function getProjectsConfigPath() {
11705
11906
  return join15(getDataDir(), "projects.json");
@@ -11717,7 +11918,7 @@ function loadProjectsConfig() {
11717
11918
  if (cachedConfig && cachedConfig.mtime === mtime)
11718
11919
  return cachedConfig.cfg;
11719
11920
  try {
11720
- const raw = readFileSync10(path, "utf-8");
11921
+ const raw = readFileSync9(path, "utf-8");
11721
11922
  const cfg = JSON.parse(raw);
11722
11923
  cachedConfig = { mtime, cfg };
11723
11924
  return cfg;
@@ -11818,8 +12019,8 @@ function resolveBotWorktree(realPath) {
11818
12019
  };
11819
12020
  }
11820
12021
  function matchDeclaredProject(realPath, projects) {
11821
- const candidates = [...projects].sort((a, b) => b.root.length - a.root.length);
11822
- for (const proj of candidates) {
12022
+ const candidates2 = [...projects].sort((a, b) => b.root.length - a.root.length);
12023
+ for (const proj of candidates2) {
11823
12024
  const root = safeRealpath(proj.root);
11824
12025
  if (!isInside(realPath, root))
11825
12026
  continue;
@@ -12044,6 +12245,24 @@ var init_project_resolver = __esm({
12044
12245
  }
12045
12246
  });
12046
12247
 
12248
+ // ../engine/dist/core/credentials-env.js
12249
+ function envCredentials(env = process.env) {
12250
+ const token = (env.CHAT_RECALL_TOKEN || "").trim();
12251
+ const server = (env.CHAT_RECALL_SERVER || "").trim();
12252
+ if (!token || !server)
12253
+ return null;
12254
+ if (!/^https?:\/\//i.test(server))
12255
+ return null;
12256
+ return { serverUrl: normalise(server), token };
12257
+ }
12258
+ var normalise;
12259
+ var init_credentials_env = __esm({
12260
+ "../engine/dist/core/credentials-env.js"() {
12261
+ "use strict";
12262
+ normalise = (u) => u.trim().replace(/\/+$/, "");
12263
+ }
12264
+ });
12265
+
12047
12266
  // ../engine/dist/core/source-discovery.js
12048
12267
  import { createHash as createHash7 } from "crypto";
12049
12268
  import { existsSync as existsSync15, readdirSync as readdirSync11, statSync as statSync13 } from "fs";
@@ -12159,7 +12378,7 @@ __export(home_discovery_exports, {
12159
12378
  sessionIdsInHome: () => sessionIdsInHome
12160
12379
  });
12161
12380
  import { execFileSync as execFileSync2 } from "child_process";
12162
- import { existsSync as existsSync16, readdirSync as readdirSync12, readFileSync as readFileSync11, realpathSync as realpathSync3, statSync as statSync14 } from "fs";
12381
+ import { existsSync as existsSync16, readdirSync as readdirSync12, readFileSync as readFileSync10, realpathSync as realpathSync3, statSync as statSync14 } from "fs";
12163
12382
  import { homedir as homedir5, platform as osPlatform } from "os";
12164
12383
  import { join as join17 } from "path";
12165
12384
  function candidateRoots(plat = osPlatform(), env = process.env) {
@@ -12347,7 +12566,7 @@ function declaredHomes(env = process.env, plat = osPlatform()) {
12347
12566
  for (const rc of rcs) {
12348
12567
  let text;
12349
12568
  try {
12350
- text = readFileSync11(rc, "utf-8");
12569
+ text = readFileSync10(rc, "utf-8");
12351
12570
  } catch {
12352
12571
  continue;
12353
12572
  }
@@ -12372,7 +12591,7 @@ function runningToolHomes(plat = osPlatform()) {
12372
12591
  for (const pid of pids) {
12373
12592
  let env;
12374
12593
  try {
12375
- env = readFileSync11(`/proc/${pid}/environ`, "utf-8");
12594
+ env = readFileSync10(`/proc/${pid}/environ`, "utf-8");
12376
12595
  } catch {
12377
12596
  continue;
12378
12597
  }
@@ -12506,7 +12725,7 @@ __export(detector_install_exports, {
12506
12725
  });
12507
12726
  import { spawnSync } from "child_process";
12508
12727
  import { createHash as createHash8 } from "crypto";
12509
- import { existsSync as existsSync17, mkdirSync as mkdirSync4, mkdtempSync, chmodSync as chmodSync2, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync12, statSync as statSync15, copyFileSync as copyFileSync2 } from "fs";
12728
+ import { existsSync as existsSync17, mkdirSync as mkdirSync4, mkdtempSync, chmodSync as chmodSync2, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync11, statSync as statSync15, copyFileSync as copyFileSync2 } from "fs";
12510
12729
  import { join as join18 } from "path";
12511
12730
  function managedDetectorPath(name) {
12512
12731
  return join18(getDetectorBinDir(), name + exeSuffix());
@@ -12515,11 +12734,11 @@ function receiptPath(name) {
12515
12734
  return join18(getDetectorBinDir(), `${name}.receipt.json`);
12516
12735
  }
12517
12736
  function sha256File(path) {
12518
- return createHash8("sha256").update(readFileSync12(path)).digest("hex");
12737
+ return createHash8("sha256").update(readFileSync11(path)).digest("hex");
12519
12738
  }
12520
12739
  function readReceipt(name) {
12521
12740
  try {
12522
- return JSON.parse(readFileSync12(receiptPath(name), "utf-8"));
12741
+ return JSON.parse(readFileSync11(receiptPath(name), "utf-8"));
12523
12742
  } catch {
12524
12743
  return null;
12525
12744
  }
@@ -12730,7 +12949,7 @@ __export(secret_scanner_exports, {
12730
12949
  scanTenantRules: () => scanTenantRules
12731
12950
  });
12732
12951
  import { spawnSync as spawnSync2 } from "child_process";
12733
- import { existsSync as existsSync18, unlinkSync, readFileSync as readFileSync13, mkdtempSync as mkdtempSync2, rmSync as rmSync3 } from "fs";
12952
+ import { existsSync as existsSync18, unlinkSync, readFileSync as readFileSync12, mkdtempSync as mkdtempSync2, rmSync as rmSync3 } from "fs";
12734
12953
  import { tmpdir } from "os";
12735
12954
  import { join as join19 } from "path";
12736
12955
  function mask(s) {
@@ -12794,7 +13013,7 @@ function runGitleaks(binary, filePath) {
12794
13013
  return [];
12795
13014
  let parsed = [];
12796
13015
  try {
12797
- parsed = JSON.parse(readFileSync13(reportPath, "utf-8")) || [];
13016
+ parsed = JSON.parse(readFileSync12(reportPath, "utf-8")) || [];
12798
13017
  } catch {
12799
13018
  }
12800
13019
  try {
@@ -12858,7 +13077,7 @@ function runGitleaksDir(binary, dir) {
12858
13077
  }
12859
13078
  let parsed = [];
12860
13079
  try {
12861
- parsed = JSON.parse(readFileSync13(reportPath, "utf-8")) || [];
13080
+ parsed = JSON.parse(readFileSync12(reportPath, "utf-8")) || [];
12862
13081
  } catch {
12863
13082
  }
12864
13083
  try {
@@ -12949,7 +13168,7 @@ function scanFileForSecrets(filePath, opts = {}) {
12949
13168
  out.push({ detector: "trufflehog", ...f });
12950
13169
  if (opts.tenantRules && opts.tenantRules.length > 0) {
12951
13170
  try {
12952
- const text = readFileSync13(filePath, "utf-8");
13171
+ const text = readFileSync12(filePath, "utf-8");
12953
13172
  out.push(...scanTenantRules(text, opts.tenantRules));
12954
13173
  } catch {
12955
13174
  }
@@ -12977,7 +13196,7 @@ async function scanSessionForSecrets(opts) {
12977
13196
  try {
12978
13197
  const tenantRules = (await opts.store.listSecretRules()).filter((r) => r.enabled).map((r) => ({ name: r.name, regex: r.regex, severity: r.severity }));
12979
13198
  if (tenantRules.length > 0) {
12980
- const text = readFileSync13(opts.filePath, "utf-8");
13199
+ const text = readFileSync12(opts.filePath, "utf-8");
12981
13200
  const lines = text.split("\n");
12982
13201
  for (const r of tenantRules) {
12983
13202
  let re;
@@ -13135,10 +13354,10 @@ var init_types = __esm({
13135
13354
 
13136
13355
  // ../engine/dist/transcript/opencode.js
13137
13356
  async function parseOpenCodeSubagents(parentSessionId) {
13138
- const { existsSync: existsSync50 } = await import("fs");
13357
+ const { existsSync: existsSync52 } = await import("fs");
13139
13358
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
13140
13359
  const dbPath = opencodeBackend2.dbPath();
13141
- if (!existsSync50(dbPath))
13360
+ if (!existsSync52(dbPath))
13142
13361
  return [];
13143
13362
  const realParent = parentSessionId.replace("opencode_", "");
13144
13363
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -13179,10 +13398,10 @@ async function parseOpenCodeSubagents(parentSessionId) {
13179
13398
  }
13180
13399
  }
13181
13400
  async function parseOpenCodeTranscript(sessionId) {
13182
- const { existsSync: existsSync50 } = await import("fs");
13401
+ const { existsSync: existsSync52 } = await import("fs");
13183
13402
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
13184
13403
  const dbPath = opencodeBackend2.dbPath();
13185
- if (!existsSync50(dbPath))
13404
+ if (!existsSync52(dbPath))
13186
13405
  return [];
13187
13406
  const realId = sessionId.replace("opencode_", "");
13188
13407
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -14223,7 +14442,7 @@ var init_utils = __esm({
14223
14442
  });
14224
14443
 
14225
14444
  // ../engine/dist/parsers/plan-source.js
14226
- import { existsSync as existsSync20, readdirSync as readdirSync14, readFileSync as readFileSync14, statSync as statSync18 } from "fs";
14445
+ import { existsSync as existsSync20, readdirSync as readdirSync14, readFileSync as readFileSync13, statSync as statSync18 } from "fs";
14227
14446
  import { homedir as homedir7 } from "os";
14228
14447
  import { join as join21, basename as basename13 } from "path";
14229
14448
  import { createHash as createHash9 } from "crypto";
@@ -14265,6 +14484,7 @@ var init_plan_source = __esm({
14265
14484
  init_claude2();
14266
14485
  init_agy();
14267
14486
  init_settings();
14487
+ init_project_dir_name();
14268
14488
  MAX_CHUNK_CHARS = 2e3;
14269
14489
  UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14270
14490
  PlanSource = class {
@@ -14289,7 +14509,7 @@ var init_plan_source = __esm({
14289
14509
  for (const entry of entries) {
14290
14510
  if (!entry.isDirectory() || entry.name.startsWith("."))
14291
14511
  continue;
14292
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
14512
+ const projectPath = decodeProjectDirName(entry.name);
14293
14513
  if (existsSync20(projectPath)) {
14294
14514
  this._knownProjectDirs.push(projectPath);
14295
14515
  }
@@ -14327,7 +14547,7 @@ var init_plan_source = __esm({
14327
14547
  const stat3 = statSync18(filePath);
14328
14548
  const planName = basename13(file, ".md");
14329
14549
  const isAgentPlan = planName.includes("-agent-");
14330
- const content = readFileSync14(filePath, "utf-8");
14550
+ const content = readFileSync13(filePath, "utf-8");
14331
14551
  const fm = parseFrontmatter(content);
14332
14552
  const body = fm.body;
14333
14553
  const firstLine = body.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || planName;
@@ -14369,7 +14589,7 @@ var init_plan_source = __esm({
14369
14589
  const filePath = join21(plansDir, file);
14370
14590
  try {
14371
14591
  const stat3 = statSync18(filePath);
14372
- const content = readFileSync14(filePath, "utf-8");
14592
+ const content = readFileSync13(filePath, "utf-8");
14373
14593
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
14374
14594
  const planName = `${opencodeBackend.idPrefix}plan_${basename13(file, ".md")}`;
14375
14595
  const projectPath = this.extractProjectPath(content);
@@ -14399,7 +14619,7 @@ var init_plan_source = __esm({
14399
14619
  const projectsPath = geminiBackend.projectsJson();
14400
14620
  if (existsSync20(projectsPath)) {
14401
14621
  try {
14402
- const data = JSON.parse(readFileSync14(projectsPath, "utf-8"));
14622
+ const data = JSON.parse(readFileSync13(projectsPath, "utf-8"));
14403
14623
  for (const path of Object.keys(data.projects || {})) {
14404
14624
  projMap.set(createHash9("sha256").update(path).digest("hex"), path);
14405
14625
  }
@@ -14435,7 +14655,7 @@ var init_plan_source = __esm({
14435
14655
  const filePath = join21(plansDir, file);
14436
14656
  try {
14437
14657
  const stat3 = statSync18(filePath);
14438
- const content = readFileSync14(filePath, "utf-8");
14658
+ const content = readFileSync13(filePath, "utf-8");
14439
14659
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
14440
14660
  const planName = `${geminiBackend.idPrefix}plan_${sess}_${basename13(file, ".md")}`;
14441
14661
  yield {
@@ -14514,7 +14734,7 @@ var init_plan_source = __esm({
14514
14734
  async parse(item) {
14515
14735
  if (!existsSync20(item.filePath))
14516
14736
  return [];
14517
- const content = readFileSync14(item.filePath, "utf-8");
14737
+ const content = readFileSync13(item.filePath, "utf-8");
14518
14738
  const chunks = [];
14519
14739
  const { body } = parseFrontmatter(content);
14520
14740
  const sections = splitByHeaders(body);
@@ -14658,7 +14878,7 @@ var init_plan_source = __esm({
14658
14878
  const filePath = join21(sessionPath, file);
14659
14879
  try {
14660
14880
  const stat3 = statSync18(filePath);
14661
- const content = readFileSync14(filePath, "utf-8");
14881
+ const content = readFileSync13(filePath, "utf-8");
14662
14882
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
14663
14883
  const sessionLoc = agyBackend.findSession(rawId);
14664
14884
  const projectPath = sessionLoc?.projectPath || this.extractProjectPath(content);
@@ -14687,7 +14907,7 @@ var init_plan_source = __esm({
14687
14907
  });
14688
14908
 
14689
14909
  // ../engine/dist/parsers/task-source.js
14690
- import { existsSync as existsSync21, readdirSync as readdirSync15, readFileSync as readFileSync15, statSync as statSync19 } from "fs";
14910
+ import { existsSync as existsSync21, readdirSync as readdirSync15, readFileSync as readFileSync14, statSync as statSync19 } from "fs";
14691
14911
  import { join as join22 } from "path";
14692
14912
  var MAX_CHUNK_CHARS2, TaskSource;
14693
14913
  var init_task_source = __esm({
@@ -14737,7 +14957,7 @@ var init_task_source = __esm({
14737
14957
  continue;
14738
14958
  let parsed;
14739
14959
  try {
14740
- const raw = readFileSync15(filePath, "utf-8");
14960
+ const raw = readFileSync14(filePath, "utf-8");
14741
14961
  parsed = JSON.parse(raw);
14742
14962
  if (!Array.isArray(parsed) || parsed.length === 0)
14743
14963
  continue;
@@ -14875,7 +15095,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14875
15095
  return [];
14876
15096
  let parsed;
14877
15097
  try {
14878
- parsed = JSON.parse(readFileSync15(item.filePath, "utf-8"));
15098
+ parsed = JSON.parse(readFileSync14(item.filePath, "utf-8"));
14879
15099
  } catch {
14880
15100
  return [];
14881
15101
  }
@@ -14907,7 +15127,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14907
15127
  });
14908
15128
  for (const file of sorted) {
14909
15129
  try {
14910
- const content = readFileSync15(join22(taskDir, file), "utf-8");
15130
+ const content = readFileSync14(join22(taskDir, file), "utf-8");
14911
15131
  const task = JSON.parse(content);
14912
15132
  if (task.subject) {
14913
15133
  tasks2.push(task);
@@ -14923,7 +15143,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14923
15143
  });
14924
15144
 
14925
15145
  // ../engine/dist/parsers/claude-md-source.js
14926
- import { existsSync as existsSync22, readFileSync as readFileSync16, readdirSync as readdirSync16, statSync as statSync20 } from "fs";
15146
+ import { existsSync as existsSync22, readFileSync as readFileSync15, readdirSync as readdirSync16, statSync as statSync20 } from "fs";
14927
15147
  import { homedir as homedir8 } from "os";
14928
15148
  import { join as join23, basename as basename14, dirname as dirname13 } from "path";
14929
15149
  import { createHash as createHash10 } from "crypto";
@@ -14944,7 +15164,7 @@ function discoverProjectDirs(claudeDir) {
14944
15164
  for (const entry of entries) {
14945
15165
  if (!entry.isDirectory() || entry.name.startsWith("."))
14946
15166
  continue;
14947
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
15167
+ const projectPath = decodeProjectDirName(entry.name);
14948
15168
  addProject(projectPath);
14949
15169
  }
14950
15170
  } catch {
@@ -14967,7 +15187,7 @@ function discoverProjectDirs(claudeDir) {
14967
15187
  const gemProjects = geminiBackend.projectsJson();
14968
15188
  if (existsSync22(gemProjects)) {
14969
15189
  try {
14970
- const data = JSON.parse(readFileSync16(gemProjects, "utf-8"));
15190
+ const data = JSON.parse(readFileSync15(gemProjects, "utf-8"));
14971
15191
  for (const path of Object.keys(data.projects || {}))
14972
15192
  addProject(path);
14973
15193
  } catch {
@@ -15001,6 +15221,7 @@ var init_claude_md_source = __esm({
15001
15221
  init_gemini2();
15002
15222
  init_settings();
15003
15223
  init_opencode();
15224
+ init_project_dir_name();
15004
15225
  MAX_CHUNK_CHARS3 = 2e3;
15005
15226
  NOTE_FILES = [
15006
15227
  { filename: "CLAUDE.md", tool: "claude" },
@@ -15081,7 +15302,7 @@ var init_claude_md_source = __esm({
15081
15302
  async parse(item) {
15082
15303
  if (!existsSync22(item.filePath))
15083
15304
  return [];
15084
- const content = readFileSync16(item.filePath, "utf-8");
15305
+ const content = readFileSync15(item.filePath, "utf-8");
15085
15306
  const chunks = [];
15086
15307
  const sections = splitByHeaders(content);
15087
15308
  for (let i = 0; i < sections.length; i++) {
@@ -15123,7 +15344,7 @@ var init_claude_md_source = __esm({
15123
15344
  const projectDir = dirname13(filePath);
15124
15345
  const projectName = basename14(projectDir);
15125
15346
  const projectPath = projectDir;
15126
- const content = readFileSync16(filePath, "utf-8");
15347
+ const content = readFileSync15(filePath, "utf-8");
15127
15348
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim()?.slice(0, 200) || `${filename} for ${projectName}`;
15128
15349
  const id = tool === "claude" ? projectName : `${projectName}_${tool}`;
15129
15350
  return {
@@ -15148,7 +15369,7 @@ var init_claude_md_source = __esm({
15148
15369
  });
15149
15370
 
15150
15371
  // ../engine/dist/parsers/agent-memory-source.js
15151
- import { existsSync as existsSync23, readFileSync as readFileSync17, readdirSync as readdirSync17, statSync as statSync21 } from "fs";
15372
+ import { existsSync as existsSync23, readFileSync as readFileSync16, readdirSync as readdirSync17, statSync as statSync21 } from "fs";
15152
15373
  import { homedir as homedir9 } from "os";
15153
15374
  import { join as join24, basename as basename15 } from "path";
15154
15375
  function parseFrontmatter2(content) {
@@ -15176,7 +15397,7 @@ function parseFrontmatter2(content) {
15176
15397
  return { fm, body };
15177
15398
  }
15178
15399
  function projectHashToPath(hashDir) {
15179
- return hashDir.replace(/^-/, "/").replace(/-/g, "/");
15400
+ return decodeProjectDirName(hashDir);
15180
15401
  }
15181
15402
  function discoverMemoryDirs() {
15182
15403
  const out = [];
@@ -15218,6 +15439,7 @@ var init_agent_memory_source = __esm({
15218
15439
  init_utils();
15219
15440
  init_claude2();
15220
15441
  init_settings();
15442
+ init_project_dir_name();
15221
15443
  MAX_CHUNK_CHARS4 = 2e3;
15222
15444
  MAX_CHUNKS = 12;
15223
15445
  AgentMemorySource = class {
@@ -15248,7 +15470,7 @@ var init_agent_memory_source = __esm({
15248
15470
  async parse(item) {
15249
15471
  if (!existsSync23(item.filePath))
15250
15472
  return [];
15251
- const content = readFileSync17(item.filePath, "utf-8");
15473
+ const content = readFileSync16(item.filePath, "utf-8");
15252
15474
  const { body } = parseFrontmatter2(content);
15253
15475
  const chunks = [];
15254
15476
  const sections = splitByHeaders(body);
@@ -15301,7 +15523,7 @@ var init_agent_memory_source = __esm({
15301
15523
  fileToItem(filePath, filename, projectPath, _projectHash) {
15302
15524
  try {
15303
15525
  const stat3 = statSync21(filePath);
15304
- const content = readFileSync17(filePath, "utf-8");
15526
+ const content = readFileSync16(filePath, "utf-8");
15305
15527
  const { fm } = parseFrontmatter2(content);
15306
15528
  const stem = filename.replace(/\.md$/, "");
15307
15529
  const idBase = fm.name || stem;
@@ -15516,7 +15738,7 @@ var init_history_source = __esm({
15516
15738
  });
15517
15739
 
15518
15740
  // ../engine/dist/parsers/paste-source.js
15519
- import { existsSync as existsSync25, readdirSync as readdirSync19, readFileSync as readFileSync18, statSync as statSync23 } from "fs";
15741
+ import { existsSync as existsSync25, readdirSync as readdirSync19, readFileSync as readFileSync17, statSync as statSync23 } from "fs";
15520
15742
  import { homedir as homedir11 } from "os";
15521
15743
  import { join as join26, basename as basename16 } from "path";
15522
15744
  var MAX_CHUNK_CHARS6, MAX_FILE_SIZE, PasteSource;
@@ -15572,7 +15794,7 @@ var init_paste_source = __esm({
15572
15794
  if (stat3.size < 10)
15573
15795
  continue;
15574
15796
  const hash = basename16(file, ".txt");
15575
- const content = readFileSync18(filePath, "utf-8");
15797
+ const content = readFileSync17(filePath, "utf-8");
15576
15798
  const firstLine = content.split("\n").find((l) => l.trim().length > 5)?.trim()?.slice(0, 100) || `Paste ${hash.slice(0, 8)}`;
15577
15799
  yield {
15578
15800
  id: hash,
@@ -15593,7 +15815,7 @@ var init_paste_source = __esm({
15593
15815
  async parse(item) {
15594
15816
  if (!existsSync25(item.filePath))
15595
15817
  return [];
15596
- const content = readFileSync18(item.filePath, "utf-8");
15818
+ const content = readFileSync17(item.filePath, "utf-8");
15597
15819
  if (content.trim().length < 20)
15598
15820
  return [];
15599
15821
  return [{
@@ -15616,7 +15838,7 @@ var init_paste_source = __esm({
15616
15838
  });
15617
15839
 
15618
15840
  // ../engine/dist/parsers/gemini-source.js
15619
- import { existsSync as existsSync26, readdirSync as readdirSync20, readFileSync as readFileSync19, statSync as statSync24 } from "fs";
15841
+ import { existsSync as existsSync26, readdirSync as readdirSync20, readFileSync as readFileSync18, statSync as statSync24 } from "fs";
15620
15842
  import { join as join27, basename as basename17 } from "path";
15621
15843
  import { createHash as createHash11 } from "crypto";
15622
15844
  function loadGeminiProjectMap2() {
@@ -15625,7 +15847,7 @@ function loadGeminiProjectMap2() {
15625
15847
  if (!existsSync26(projectsPath))
15626
15848
  return map;
15627
15849
  try {
15628
- const data = JSON.parse(readFileSync19(projectsPath, "utf-8"));
15850
+ const data = JSON.parse(readFileSync18(projectsPath, "utf-8"));
15629
15851
  const projects = data.projects || {};
15630
15852
  for (const [path] of Object.entries(projects)) {
15631
15853
  const hash = createHash11("sha256").update(path).digest("hex");
@@ -15670,7 +15892,7 @@ var init_gemini_source = __esm({
15670
15892
  const filePath = join27(chatsDir, file);
15671
15893
  try {
15672
15894
  const stat3 = statSync24(filePath);
15673
- const content = JSON.parse(readFileSync19(filePath, "utf-8"));
15895
+ const content = JSON.parse(readFileSync18(filePath, "utf-8"));
15674
15896
  const sessionId = content.sessionId || basename17(file, ".json");
15675
15897
  if (seen.has(sessionId))
15676
15898
  continue;
@@ -15751,7 +15973,7 @@ var init_gemini_source = __esm({
15751
15973
  async parse(item) {
15752
15974
  if (!existsSync26(item.filePath))
15753
15975
  return [];
15754
- const content = JSON.parse(readFileSync19(item.filePath, "utf-8"));
15976
+ const content = JSON.parse(readFileSync18(item.filePath, "utf-8"));
15755
15977
  const messages = content.messages || [];
15756
15978
  const chunks = [];
15757
15979
  const getText = (m) => {
@@ -15799,7 +16021,7 @@ var init_gemini_source = __esm({
15799
16021
  });
15800
16022
 
15801
16023
  // ../engine/dist/parsers/gemini-brain-source.js
15802
- import { existsSync as existsSync27, readdirSync as readdirSync21, readFileSync as readFileSync20, statSync as statSync25 } from "fs";
16024
+ import { existsSync as existsSync27, readdirSync as readdirSync21, readFileSync as readFileSync19, statSync as statSync25 } from "fs";
15803
16025
  import { join as join28 } from "path";
15804
16026
  var MAX_CHUNK_CHARS8, GeminiBrainSource;
15805
16027
  var init_gemini_brain_source = __esm({
@@ -15832,14 +16054,14 @@ var init_gemini_brain_source = __esm({
15832
16054
  const filePath = join28(sessionDir, file);
15833
16055
  try {
15834
16056
  const stat3 = statSync25(filePath);
15835
- const content = readFileSync20(filePath, "utf-8");
16057
+ const content = readFileSync19(filePath, "utf-8");
15836
16058
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim() || "";
15837
16059
  const heading = content.split("\n").find((l) => l.startsWith("#"))?.replace(/^#+\s*/, "").trim() || file;
15838
16060
  const metaPath = `${filePath}.metadata.json`;
15839
16061
  let metaSummary = "";
15840
16062
  if (existsSync27(metaPath)) {
15841
16063
  try {
15842
- const meta = JSON.parse(readFileSync20(metaPath, "utf-8"));
16064
+ const meta = JSON.parse(readFileSync19(metaPath, "utf-8"));
15843
16065
  metaSummary = meta.summary || "";
15844
16066
  } catch {
15845
16067
  }
@@ -15870,7 +16092,7 @@ var init_gemini_brain_source = __esm({
15870
16092
  async parse(item) {
15871
16093
  if (!existsSync27(item.filePath))
15872
16094
  return [];
15873
- const content = readFileSync20(item.filePath, "utf-8");
16095
+ const content = readFileSync19(item.filePath, "utf-8");
15874
16096
  const chunks = [];
15875
16097
  const sections = splitByHeaders(content);
15876
16098
  for (let i = 0; i < sections.length; i++) {
@@ -16213,7 +16435,7 @@ var init_opencode_todo_source = __esm({
16213
16435
  });
16214
16436
 
16215
16437
  // ../engine/dist/parsers/codex-session-source.js
16216
- import { existsSync as existsSync30, readdirSync as readdirSync22, readFileSync as readFileSync21, statSync as statSync26 } from "fs";
16438
+ import { existsSync as existsSync30, readdirSync as readdirSync22, readFileSync as readFileSync20, statSync as statSync26 } from "fs";
16217
16439
  import { join as join29, basename as basename18 } from "path";
16218
16440
  function getCodexSessionsDir() {
16219
16441
  return codexBackend.sessionsDir();
@@ -16241,7 +16463,7 @@ function extractReasoningText(event) {
16241
16463
  return "";
16242
16464
  }
16243
16465
  function readJsonlLines(filePath) {
16244
- const text = readFileSync21(filePath, "utf-8");
16466
+ const text = readFileSync20(filePath, "utf-8");
16245
16467
  const lines = text.split("\n");
16246
16468
  const events = [];
16247
16469
  for (const line of lines) {
@@ -16276,7 +16498,7 @@ var init_codex_session_source = __esm({
16276
16498
  for (const filePath of jsonlFiles) {
16277
16499
  try {
16278
16500
  const stat3 = statSync26(filePath);
16279
- const lines = readFileSync21(filePath, "utf-8").split("\n");
16501
+ const lines = readFileSync20(filePath, "utf-8").split("\n");
16280
16502
  const firstLine = lines.find((l) => l.trim());
16281
16503
  if (!firstLine)
16282
16504
  continue;
@@ -16449,7 +16671,7 @@ var init_codex_session_source = __esm({
16449
16671
  });
16450
16672
 
16451
16673
  // ../engine/dist/parsers/diary-source.js
16452
- import { existsSync as existsSync31, mkdirSync as mkdirSync5, readdirSync as readdirSync23, readFileSync as readFileSync22, writeFileSync as writeFileSync4, statSync as statSync27 } from "fs";
16674
+ import { existsSync as existsSync31, mkdirSync as mkdirSync5, readdirSync as readdirSync23, readFileSync as readFileSync21, writeFileSync as writeFileSync4, statSync as statSync27 } from "fs";
16453
16675
  import { join as join30, basename as basename19 } from "path";
16454
16676
  var MAX_CHUNK_CHARS11, DiarySource;
16455
16677
  var init_diary_source = __esm({
@@ -16475,7 +16697,7 @@ var init_diary_source = __esm({
16475
16697
  const filePath = join30(agentPath2, file);
16476
16698
  try {
16477
16699
  const stat3 = statSync27(filePath);
16478
- const raw = readFileSync22(filePath, "utf-8");
16700
+ const raw = readFileSync21(filePath, "utf-8");
16479
16701
  const entry = JSON.parse(raw);
16480
16702
  const entryId = basename19(file, ".json");
16481
16703
  const title = `[${entry.agent}] ${entry.topic}: ${entry.content.slice(0, 80)}`;
@@ -16502,7 +16724,7 @@ var init_diary_source = __esm({
16502
16724
  async parse(item) {
16503
16725
  if (!existsSync31(item.filePath))
16504
16726
  return [];
16505
- const raw = readFileSync22(item.filePath, "utf-8");
16727
+ const raw = readFileSync21(item.filePath, "utf-8");
16506
16728
  const entry = JSON.parse(raw);
16507
16729
  if (!entry.content || entry.content.trim().length < 10)
16508
16730
  return [];
@@ -16565,7 +16787,7 @@ var init_diary_source = __esm({
16565
16787
  const entries = [];
16566
16788
  for (const file of files) {
16567
16789
  try {
16568
- const raw = readFileSync22(join30(agentDir, file), "utf-8");
16790
+ const raw = readFileSync21(join30(agentDir, file), "utf-8");
16569
16791
  entries.push(JSON.parse(raw));
16570
16792
  } catch {
16571
16793
  continue;
@@ -16578,7 +16800,7 @@ var init_diary_source = __esm({
16578
16800
  });
16579
16801
 
16580
16802
  // ../engine/dist/parsers/skills-source.js
16581
- import { existsSync as existsSync32, readdirSync as readdirSync24, readFileSync as readFileSync23, statSync as statSync28 } from "fs";
16803
+ import { existsSync as existsSync32, readdirSync as readdirSync24, readFileSync as readFileSync22, statSync as statSync28 } from "fs";
16582
16804
  import { homedir as homedir12 } from "os";
16583
16805
  import { join as join31 } from "path";
16584
16806
  function resolveRoots() {
@@ -16671,7 +16893,7 @@ var init_skills_source = __esm({
16671
16893
  continue;
16672
16894
  let content;
16673
16895
  try {
16674
- content = readFileSync23(skillMd, "utf-8");
16896
+ content = readFileSync22(skillMd, "utf-8");
16675
16897
  } catch {
16676
16898
  continue;
16677
16899
  }
@@ -16731,7 +16953,7 @@ var init_skills_source = __esm({
16731
16953
  async parse(item) {
16732
16954
  if (!existsSync32(item.filePath))
16733
16955
  return [];
16734
- const content = readFileSync23(item.filePath, "utf-8");
16956
+ const content = readFileSync22(item.filePath, "utf-8");
16735
16957
  if (!content.trim())
16736
16958
  return [];
16737
16959
  return [{
@@ -16754,14 +16976,14 @@ var init_skills_source = __esm({
16754
16976
  });
16755
16977
 
16756
16978
  // ../engine/dist/parsers/mcps-source.js
16757
- import { existsSync as existsSync33, readdirSync as readdirSync25, readFileSync as readFileSync24, statSync as statSync29 } from "fs";
16979
+ import { existsSync as existsSync33, readdirSync as readdirSync25, readFileSync as readFileSync23, statSync as statSync29 } from "fs";
16758
16980
  import { homedir as homedir13 } from "os";
16759
16981
  import { join as join32 } from "path";
16760
16982
  function readJson(path) {
16761
16983
  if (!existsSync33(path))
16762
16984
  return null;
16763
16985
  try {
16764
- const raw = readFileSync24(path, "utf-8");
16986
+ const raw = readFileSync23(path, "utf-8");
16765
16987
  const stripped = path.endsWith(".jsonc") ? raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/,(\s*[}\]])/g, "$1") : raw;
16766
16988
  return JSON.parse(stripped);
16767
16989
  } catch {
@@ -16874,7 +17096,7 @@ var init_mcps_source = __esm({
16874
17096
  return;
16875
17097
  let content;
16876
17098
  try {
16877
- content = readFileSync24(path, "utf-8");
17099
+ content = readFileSync23(path, "utf-8");
16878
17100
  } catch {
16879
17101
  return;
16880
17102
  }
@@ -17043,7 +17265,7 @@ var init_toolkit_format = __esm({
17043
17265
  });
17044
17266
 
17045
17267
  // ../engine/dist/parsers/slash-commands-source.js
17046
- import { existsSync as existsSync34, readdirSync as readdirSync26, readFileSync as readFileSync25, statSync as statSync30 } from "fs";
17268
+ import { existsSync as existsSync34, readdirSync as readdirSync26, readFileSync as readFileSync24, statSync as statSync30 } from "fs";
17047
17269
  import { join as join33, basename as basename20 } from "path";
17048
17270
  function parseCommand(content, fallbackName, format) {
17049
17271
  if (format === "toml") {
@@ -17071,6 +17293,7 @@ var init_slash_commands_source = __esm({
17071
17293
  init_codex2();
17072
17294
  init_settings();
17073
17295
  init_toolkit_format();
17296
+ init_project_dir_name();
17074
17297
  MAX_CHUNK_CHARS13 = 2e3;
17075
17298
  SlashCommandsSource = class {
17076
17299
  sourceType = "command";
@@ -17084,7 +17307,7 @@ var init_slash_commands_source = __esm({
17084
17307
  for (const entry of readdirSync26(projectsRoot, { withFileTypes: true })) {
17085
17308
  if (!entry.isDirectory() || entry.name.startsWith("."))
17086
17309
  continue;
17087
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
17310
+ const projectPath = decodeProjectDirName(entry.name);
17088
17311
  const cmdDir = join33(projectPath, ".claude", "commands");
17089
17312
  if (existsSync34(cmdDir))
17090
17313
  roots.push({ path: cmdDir, tool: "claude", scope: "project", projectPath, format: "md" });
@@ -17117,7 +17340,7 @@ var init_slash_commands_source = __esm({
17117
17340
  const filePath = join33(root.path, file);
17118
17341
  try {
17119
17342
  const stat3 = statSync30(filePath);
17120
- const content = readFileSync25(filePath, "utf-8");
17343
+ const content = readFileSync24(filePath, "utf-8");
17121
17344
  const parsed = parseCommand(content, basename20(file, ext), root.format);
17122
17345
  yield {
17123
17346
  id: `${root.tool}_cmd_${root.scope}_${parsed.name}`,
@@ -17143,7 +17366,7 @@ var init_slash_commands_source = __esm({
17143
17366
  async parse(item) {
17144
17367
  if (!existsSync34(item.filePath))
17145
17368
  return [];
17146
- const content = readFileSync25(item.filePath, "utf-8");
17369
+ const content = readFileSync24(item.filePath, "utf-8");
17147
17370
  if (!content.trim())
17148
17371
  return [];
17149
17372
  return [{
@@ -17166,7 +17389,7 @@ var init_slash_commands_source = __esm({
17166
17389
  });
17167
17390
 
17168
17391
  // ../engine/dist/parsers/subagents-source.js
17169
- import { existsSync as existsSync35, readdirSync as readdirSync27, readFileSync as readFileSync26, statSync as statSync31 } from "fs";
17392
+ import { existsSync as existsSync35, readdirSync as readdirSync27, readFileSync as readFileSync25, statSync as statSync31 } from "fs";
17170
17393
  import { join as join34, basename as basename21 } from "path";
17171
17394
  function parseAgent(content, fallbackName, format) {
17172
17395
  if (format === "toml") {
@@ -17196,6 +17419,7 @@ var init_subagents_source = __esm({
17196
17419
  init_codex2();
17197
17420
  init_settings();
17198
17421
  init_toolkit_format();
17422
+ init_project_dir_name();
17199
17423
  MAX_CHUNK_CHARS14 = 2e3;
17200
17424
  SubagentsSource = class {
17201
17425
  sourceType = "agent";
@@ -17209,7 +17433,7 @@ var init_subagents_source = __esm({
17209
17433
  for (const entry of readdirSync27(projectsRoot, { withFileTypes: true })) {
17210
17434
  if (!entry.isDirectory() || entry.name.startsWith("."))
17211
17435
  continue;
17212
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
17436
+ const projectPath = decodeProjectDirName(entry.name);
17213
17437
  const agentDir = join34(projectPath, ".claude", "agents");
17214
17438
  if (existsSync35(agentDir))
17215
17439
  roots.push({ path: agentDir, tool: "claude", scope: "project", projectPath, format: "md" });
@@ -17242,7 +17466,7 @@ var init_subagents_source = __esm({
17242
17466
  const filePath = join34(root.path, file);
17243
17467
  try {
17244
17468
  const stat3 = statSync31(filePath);
17245
- const content = readFileSync26(filePath, "utf-8");
17469
+ const content = readFileSync25(filePath, "utf-8");
17246
17470
  const parsed = parseAgent(content, basename21(file, ext), root.format);
17247
17471
  yield {
17248
17472
  id: `${root.tool}_agent_${root.scope}_${parsed.name}`,
@@ -17269,7 +17493,7 @@ var init_subagents_source = __esm({
17269
17493
  async parse(item) {
17270
17494
  if (!existsSync35(item.filePath))
17271
17495
  return [];
17272
- const content = readFileSync26(item.filePath, "utf-8");
17496
+ const content = readFileSync25(item.filePath, "utf-8");
17273
17497
  if (!content.trim())
17274
17498
  return [];
17275
17499
  return [{
@@ -17292,13 +17516,13 @@ var init_subagents_source = __esm({
17292
17516
  });
17293
17517
 
17294
17518
  // ../engine/dist/parsers/hooks-source.js
17295
- import { existsSync as existsSync36, readFileSync as readFileSync27, readdirSync as readdirSync28, statSync as statSync32 } from "fs";
17519
+ import { existsSync as existsSync36, readFileSync as readFileSync26, readdirSync as readdirSync28, statSync as statSync32 } from "fs";
17296
17520
  import { join as join35 } from "path";
17297
17521
  function readJson2(path) {
17298
17522
  if (!existsSync36(path))
17299
17523
  return null;
17300
17524
  try {
17301
- return JSON.parse(readFileSync27(path, "utf-8"));
17525
+ return JSON.parse(readFileSync26(path, "utf-8"));
17302
17526
  } catch {
17303
17527
  return null;
17304
17528
  }
@@ -17309,6 +17533,7 @@ var init_hooks_source = __esm({
17309
17533
  "use strict";
17310
17534
  init_claude2();
17311
17535
  init_settings();
17536
+ init_project_dir_name();
17312
17537
  HooksSource = class {
17313
17538
  sourceType = "hook";
17314
17539
  async *discover() {
@@ -17321,7 +17546,7 @@ var init_hooks_source = __esm({
17321
17546
  for (const entry of readdirSync28(projectsRoot, { withFileTypes: true })) {
17322
17547
  if (!entry.isDirectory() || entry.name.startsWith("."))
17323
17548
  continue;
17324
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
17549
+ const projectPath = decodeProjectDirName(entry.name);
17325
17550
  const settingsPath2 = join35(projectPath, ".claude", "settings.json");
17326
17551
  if (existsSync36(settingsPath2)) {
17327
17552
  yield* this.fromSettings(settingsPath2, "project", projectPath);
@@ -17414,14 +17639,14 @@ var init_hooks_source = __esm({
17414
17639
  });
17415
17640
 
17416
17641
  // ../engine/dist/parsers/plugins-source.js
17417
- import { existsSync as existsSync37, readFileSync as readFileSync28, readdirSync as readdirSync29, statSync as statSync33 } from "fs";
17642
+ import { existsSync as existsSync37, readFileSync as readFileSync27, readdirSync as readdirSync29, statSync as statSync33 } from "fs";
17418
17643
  import { homedir as homedir14 } from "os";
17419
17644
  import { join as join36 } from "path";
17420
17645
  function readJson3(path) {
17421
17646
  if (!existsSync37(path))
17422
17647
  return null;
17423
17648
  try {
17424
- return JSON.parse(readFileSync28(path, "utf-8"));
17649
+ return JSON.parse(readFileSync27(path, "utf-8"));
17425
17650
  } catch {
17426
17651
  return null;
17427
17652
  }
@@ -17678,7 +17903,7 @@ var init_extractor_version = __esm({
17678
17903
  });
17679
17904
 
17680
17905
  // src/sync-ledger.ts
17681
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync5, renameSync as renameSync4, mkdirSync as mkdirSync6 } from "node:fs";
17906
+ import { readFileSync as readFileSync28, writeFileSync as writeFileSync5, renameSync as renameSync4, mkdirSync as mkdirSync6 } from "node:fs";
17682
17907
  import { join as join37, dirname as dirname14 } from "node:path";
17683
17908
  function toRow(entry) {
17684
17909
  if (entry === void 0) return void 0;
@@ -17690,24 +17915,77 @@ function ledgerFilePath() {
17690
17915
  function load() {
17691
17916
  if (cache3) return cache3;
17692
17917
  try {
17693
- const parsed = JSON.parse(readFileSync29(ledgerPath(), "utf-8"));
17918
+ const parsed = JSON.parse(readFileSync28(ledgerPath(), "utf-8"));
17694
17919
  cache3 = parsed && typeof parsed === "object" ? parsed : {};
17695
17920
  } catch {
17696
17921
  cache3 = {};
17697
17922
  }
17698
17923
  return cache3;
17699
17924
  }
17700
- function persist(data) {
17925
+ function persistNow(data) {
17701
17926
  const path = ledgerPath();
17702
17927
  mkdirSync6(dirname14(path), { recursive: true });
17703
17928
  const tmp = `${path}.tmp`;
17704
17929
  writeFileSync5(tmp, JSON.stringify(data));
17705
17930
  renameSync4(tmp, path);
17931
+ pendingWrite = false;
17932
+ }
17933
+ function persist(data) {
17934
+ pendingWrite = true;
17935
+ if (flushTimer) return;
17936
+ flushTimer = setTimeout(() => {
17937
+ flushTimer = null;
17938
+ if (pendingWrite) {
17939
+ try {
17940
+ persistNow(data);
17941
+ } catch {
17942
+ }
17943
+ }
17944
+ }, LEDGER_FLUSH_MS);
17945
+ flushTimer.unref?.();
17946
+ }
17947
+ function pruneLedgerTargets(configuredServers) {
17948
+ const data = load();
17949
+ const keep = new Set(configuredServers);
17950
+ const orphans = {};
17951
+ for (const server of Object.keys(data)) {
17952
+ if (!keep.has(server)) {
17953
+ orphans[server] = data[server];
17954
+ delete data[server];
17955
+ }
17956
+ }
17957
+ const names = Object.keys(orphans);
17958
+ if (names.length === 0) return 0;
17959
+ const path = ledgerPath();
17960
+ const side = `${path}.orphans.json`;
17961
+ try {
17962
+ let prior = {};
17963
+ try {
17964
+ prior = JSON.parse(readFileSync28(side, "utf-8"));
17965
+ } catch {
17966
+ }
17967
+ writeFileSync5(side, JSON.stringify({ ...prior, ...orphans }));
17968
+ } catch {
17969
+ for (const n of names) data[n] = orphans[n];
17970
+ return 0;
17971
+ }
17972
+ const rows = names.reduce((n, s) => n + Object.keys(orphans[s]).length, 0);
17973
+ console.error(`[sync] ledger: parked ${rows} row(s) for ${names.length} server(s) no longer configured (${names.join(", ")}) in ${side}`);
17974
+ persistNow(data);
17975
+ return names.length;
17976
+ }
17977
+ function flushLedger() {
17978
+ if (flushTimer) {
17979
+ clearTimeout(flushTimer);
17980
+ flushTimer = null;
17981
+ }
17982
+ if (!pendingWrite || !cache3) return;
17983
+ persistNow(cache3);
17706
17984
  }
17707
17985
  function loadItemVersionsAll() {
17708
17986
  if (itemVersionsCache) return itemVersionsCache;
17709
17987
  try {
17710
- const parsed = JSON.parse(readFileSync29(itemVersionsPath(), "utf-8"));
17988
+ const parsed = JSON.parse(readFileSync28(itemVersionsPath(), "utf-8"));
17711
17989
  itemVersionsCache = parsed && typeof parsed === "object" ? parsed : {};
17712
17990
  } catch {
17713
17991
  itemVersionsCache = {};
@@ -17789,7 +18067,7 @@ function markFieldCoverage(server, field, rows) {
17789
18067
  function loadReconcile() {
17790
18068
  if (reconcileCache) return reconcileCache;
17791
18069
  try {
17792
- const parsed = JSON.parse(readFileSync29(reconcilePath(), "utf-8"));
18070
+ const parsed = JSON.parse(readFileSync28(reconcilePath(), "utf-8"));
17793
18071
  reconcileCache = parsed && typeof parsed === "object" ? parsed : {};
17794
18072
  } catch {
17795
18073
  reconcileCache = {};
@@ -17837,7 +18115,7 @@ function syncMode(row, mtime, fileSize, extractorVersion, isAppendOnly) {
17837
18115
  if (row.m >= Math.floor(mtime)) return "skip";
17838
18116
  return "full";
17839
18117
  }
17840
- var ledgerPath, cache3, itemVersionsPath, itemVersionsCache, unackedCursorAttempts, reconcilePath, reconcileCache;
18118
+ var ledgerPath, cache3, LEDGER_FLUSH_MS, pendingWrite, flushTimer, itemVersionsPath, itemVersionsCache, unackedCursorAttempts, reconcilePath, reconcileCache;
17841
18119
  var init_sync_ledger = __esm({
17842
18120
  "src/sync-ledger.ts"() {
17843
18121
  "use strict";
@@ -17845,6 +18123,9 @@ var init_sync_ledger = __esm({
17845
18123
  init_extractor_version();
17846
18124
  ledgerPath = () => join37(getDataDir(), "sync-ledger.json");
17847
18125
  cache3 = null;
18126
+ LEDGER_FLUSH_MS = 2e3;
18127
+ pendingWrite = false;
18128
+ flushTimer = null;
17848
18129
  itemVersionsPath = () => join37(getDataDir(), "item-versions.json");
17849
18130
  itemVersionsCache = null;
17850
18131
  unackedCursorAttempts = 0;
@@ -17879,7 +18160,7 @@ var init_sync_fields = __esm({
17879
18160
  });
17880
18161
 
17881
18162
  // ../engine/dist/core/index-lock.js
17882
- import { existsSync as existsSync38, mkdirSync as mkdirSync7, openSync as openSync6, writeFileSync as writeFileSync6, readFileSync as readFileSync30, rmSync as rmSync4, statSync as statSync34, closeSync as closeSync6 } from "fs";
18163
+ import { existsSync as existsSync38, mkdirSync as mkdirSync7, openSync as openSync7, writeFileSync as writeFileSync6, readFileSync as readFileSync29, rmSync as rmSync4, statSync as statSync34, closeSync as closeSync7 } from "fs";
17883
18164
  import { dirname as dirname15, join as join38 } from "path";
17884
18165
  function acquireIndexLock(opts) {
17885
18166
  const lockPath = lockFilePath();
@@ -17891,7 +18172,7 @@ function acquireIndexLock(opts) {
17891
18172
  }
17892
18173
  let fd;
17893
18174
  try {
17894
- fd = openSync6(lockPath, "wx", 384);
18175
+ fd = openSync7(lockPath, "wx", 384);
17895
18176
  } catch (err) {
17896
18177
  if (err.code === "EEXIST")
17897
18178
  return null;
@@ -17901,7 +18182,7 @@ function acquireIndexLock(opts) {
17901
18182
  try {
17902
18183
  writeFileSync6(fd, JSON.stringify(holder));
17903
18184
  } finally {
17904
- closeSync6(fd);
18185
+ closeSync7(fd);
17905
18186
  }
17906
18187
  let released = false;
17907
18188
  return {
@@ -17911,7 +18192,7 @@ function acquireIndexLock(opts) {
17911
18192
  return;
17912
18193
  released = true;
17913
18194
  try {
17914
- const current = JSON.parse(readFileSync30(lockPath, "utf-8"));
18195
+ const current = JSON.parse(readFileSync29(lockPath, "utf-8"));
17915
18196
  if (current.pid !== holder.pid || current.startedAt !== holder.startedAt)
17916
18197
  return;
17917
18198
  } catch {
@@ -17933,7 +18214,7 @@ function stealStaleLock(lockPath, staleAfterMs) {
17933
18214
  const age = Date.now() - s.mtimeMs;
17934
18215
  let payload;
17935
18216
  try {
17936
- payload = JSON.parse(readFileSync30(lockPath, "utf-8"));
18217
+ payload = JSON.parse(readFileSync29(lockPath, "utf-8"));
17937
18218
  } catch {
17938
18219
  rmSync4(lockPath, { force: true });
17939
18220
  return true;
@@ -17977,7 +18258,7 @@ __export(http_exports, {
17977
18258
  clientHeaders: () => clientHeaders,
17978
18259
  fetchWithTimeout: () => fetchWithTimeout
17979
18260
  });
17980
- import { readFileSync as readFileSync31 } from "node:fs";
18261
+ import { readFileSync as readFileSync30 } from "node:fs";
17981
18262
  function cliVersion() {
17982
18263
  return CLI_VERSION;
17983
18264
  }
@@ -17998,10 +18279,10 @@ var init_http = __esm({
17998
18279
  "use strict";
17999
18280
  CLI_VERSION = "0.0.0";
18000
18281
  try {
18001
- CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
18282
+ CLI_VERSION = JSON.parse(readFileSync30(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
18002
18283
  } catch {
18003
18284
  try {
18004
- if (true) CLI_VERSION = "0.5.5";
18285
+ if (true) CLI_VERSION = "0.5.6";
18005
18286
  } catch {
18006
18287
  }
18007
18288
  }
@@ -18014,7 +18295,7 @@ __export(client_events_exports, {
18014
18295
  isTelemetryEnabled: () => isTelemetryEnabled,
18015
18296
  reportClientEvent: () => reportClientEvent
18016
18297
  });
18017
- import { readFileSync as readFileSync32 } from "node:fs";
18298
+ import { readFileSync as readFileSync31 } from "node:fs";
18018
18299
  function scrub(msg) {
18019
18300
  if (!msg) return "";
18020
18301
  const noHome = msg.replace(/\/(?:home|Users)\/[^/\s]+/g, "~");
@@ -18067,7 +18348,7 @@ var init_client_events = __esm({
18067
18348
  init_secret_redactor();
18068
18349
  CLI_VERSION2 = "0.0.0";
18069
18350
  try {
18070
- CLI_VERSION2 = JSON.parse(readFileSync32(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION2;
18351
+ CLI_VERSION2 = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION2;
18071
18352
  } catch {
18072
18353
  }
18073
18354
  }
@@ -18190,6 +18471,7 @@ var sync_client_exports = {};
18190
18471
  __export(sync_client_exports, {
18191
18472
  BATCH_SCAN_MAX_BYTES: () => BATCH_SCAN_MAX_BYTES,
18192
18473
  FULL_BUILD_MAX_BYTES: () => FULL_BUILD_MAX_BYTES,
18474
+ _resetCapsCacheForTests: () => _resetCapsCacheForTests,
18193
18475
  _resetTenantSecurityConfigCache: () => _resetTenantSecurityConfigCache,
18194
18476
  _resetTenantSyncConfigCache: () => _resetTenantSyncConfigCache,
18195
18477
  batchScanExternal: () => batchScanExternal,
@@ -18207,7 +18489,7 @@ __export(sync_client_exports, {
18207
18489
  syncSessions: () => syncSessions
18208
18490
  });
18209
18491
  import { join as join40, dirname as dirname16, basename as basename22 } from "node:path";
18210
- import { readFileSync as readFileSync33, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync4, rmSync as rmSync5, chmodSync as chmodSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync30, statSync as statSync35 } from "node:fs";
18492
+ import { readFileSync as readFileSync32, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync4, rmSync as rmSync5, chmodSync as chmodSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync30, statSync as statSync35 } from "node:fs";
18211
18493
  import { tmpdir as tmpdir3, hostname } from "node:os";
18212
18494
  import { createHash as createHash13 } from "node:crypto";
18213
18495
  import { fileURLToPath } from "node:url";
@@ -18229,8 +18511,10 @@ function saveCredentials(c) {
18229
18511
  }
18230
18512
  }
18231
18513
  function loadAllCredentials() {
18514
+ const fromEnv = envCredentials();
18515
+ if (fromEnv) return [{ serverUrl: fromEnv.serverUrl, token: fromEnv.token }];
18232
18516
  try {
18233
- const parsed = JSON.parse(readFileSync33(credPath(), "utf-8"));
18517
+ const parsed = JSON.parse(readFileSync32(credPath(), "utf-8"));
18234
18518
  if (Array.isArray(parsed?.targets)) return parsed.targets.filter((t) => t?.serverUrl).map((t) => ({ serverUrl: t.serverUrl, token: t.token || "" }));
18235
18519
  if (parsed?.serverUrl) return [{ serverUrl: parsed.serverUrl, token: parsed.token || "" }];
18236
18520
  } catch {
@@ -18389,6 +18673,10 @@ function isLocalHost(serverUrl) {
18389
18673
  async function syncSessions(opts = {}) {
18390
18674
  const targets = loadAllCredentials();
18391
18675
  if (targets.length === 0) throw new Error("Not logged in \u2014 run `chat-recall login <server-url> --token <token>`");
18676
+ try {
18677
+ pruneLedgerTargets(targets.map((t) => t.serverUrl));
18678
+ } catch {
18679
+ }
18392
18680
  let agg = null;
18393
18681
  const errors = [];
18394
18682
  for (const cred of targets) {
@@ -18411,6 +18699,7 @@ async function syncSessions(opts = {}) {
18411
18699
  errors.push(`${cred.serverUrl}: ${e instanceof Error ? e.message : e}`);
18412
18700
  }
18413
18701
  }
18702
+ flushLedger();
18414
18703
  if (!agg) throw new Error(`sync failed for all targets:
18415
18704
  ${errors.join("\n ")}`);
18416
18705
  if (errors.length > 0) console.error(`[sync] ${errors.length} target(s) failed (others succeeded):
@@ -18436,11 +18725,11 @@ async function reconcileFieldsForTarget(cred, fullRefs, opts = {}) {
18436
18725
  let pushed = 0, absent = 0, scanned = 0;
18437
18726
  for (const field of SYNC_FIELDS) {
18438
18727
  const needFull = !!opts.force || fieldNeedsFullPass(base2, field);
18439
- const candidates = needFull ? fullRefs() : opts.convRefs ?? fullRefs();
18728
+ const candidates2 = needFull ? fullRefs() : opts.convRefs ?? fullRefs();
18440
18729
  const send = [];
18441
18730
  const mtimeById = /* @__PURE__ */ new Map();
18442
18731
  const absentIds = [];
18443
- for (const ref of candidates) {
18732
+ for (const ref of candidates2) {
18444
18733
  if (!fieldNeedsScan(ledger.get(ref.prefixedId), field, ref.mtime, opts.force)) continue;
18445
18734
  scanned++;
18446
18735
  mtimeById.set(ref.prefixedId, ref.mtime);
@@ -18561,6 +18850,20 @@ function batchScanExternal(items, opts = {}) {
18561
18850
  }
18562
18851
  return { findings, scanned, slices, scanMs };
18563
18852
  }
18853
+ function _resetCapsCacheForTests() {
18854
+ capsOk.clear();
18855
+ }
18856
+ async function verifyServerApi(serverUrl, minApi) {
18857
+ const base2 = serverUrl.replace(/\/$/, "");
18858
+ const seen = capsOk.get(base2);
18859
+ if (seen !== void 0 && Date.now() - seen < CAPS_TTL_MS) return true;
18860
+ const caps = await fetchWithTimeout(`${base2}/api/capabilities`).then((r) => r.json());
18861
+ if ((caps.apiVersion ?? 0) < minApi) {
18862
+ throw new Error(`server too old: apiVersion ${caps.apiVersion ?? "none"} < required ${minApi} \u2014 update the server image before syncing`);
18863
+ }
18864
+ capsOk.set(base2, Date.now());
18865
+ return true;
18866
+ }
18564
18867
  async function syncToTarget(cred, opts = {}) {
18565
18868
  const sync = loadSettings().sync;
18566
18869
  const serverCfg = await fetchTenantSyncConfig(cred);
@@ -18581,14 +18884,9 @@ async function syncToTarget(cred, opts = {}) {
18581
18884
  return isProjectSyncable(r.source === "ignored" ? "" : r.id, projectPath, { syncMode: "only", syncOnly: syncOnly2 });
18582
18885
  };
18583
18886
  const MIN_SERVER_API = 2;
18584
- try {
18585
- const caps = await fetchWithTimeout(`${cred.serverUrl.replace(/\/$/, "")}/api/capabilities`).then((r) => r.json());
18586
- if ((caps.apiVersion ?? 0) < MIN_SERVER_API) {
18587
- throw new Error(`server too old: apiVersion ${caps.apiVersion ?? "none"} < required ${MIN_SERVER_API} \u2014 update the server image before syncing`);
18588
- }
18589
- } catch (e) {
18590
- if (e instanceof Error && e.message.startsWith("server too old")) throw e;
18591
- throw new Error(`cannot verify server version (${e instanceof Error ? e.message : e}) \u2014 refusing to sync blind`);
18887
+ {
18888
+ const ok = await verifyServerApi(cred.serverUrl, MIN_SERVER_API);
18889
+ if (!ok) throw new Error(`cannot verify server version \u2014 refusing to sync blind`);
18592
18890
  }
18593
18891
  const { tenantRules, verifySecrets } = await fetchTenantSecurityConfig(cred);
18594
18892
  const CHANGED_WALK_OVERLAP_MS = 15 * 6e4;
@@ -18645,8 +18943,11 @@ async function syncToTarget(cred, opts = {}) {
18645
18943
  try {
18646
18944
  const body2 = JSON.parse(text);
18647
18945
  detail = body2.error ?? text;
18946
+ if (body2.detail) detail += `
18947
+ ${body2.detail}`;
18648
18948
  if (body2.resetsAt) detail += ` (resets ${new Date(body2.resetsAt).toISOString().slice(0, 10)})`;
18649
- if (body2.upgradeUrl) detail += ` \u2014 ${body2.upgradeUrl}`;
18949
+ if (body2.upgradeUrl) detail += `
18950
+ ${body2.upgradeUrl}`;
18650
18951
  } catch {
18651
18952
  }
18652
18953
  throw new Error(`sync paused by the server: ${detail}`);
@@ -18793,16 +19094,21 @@ async function syncToTarget(cred, opts = {}) {
18793
19094
  const findingsBatch = makeBatcher("findings", 200, true);
18794
19095
  const scanSecrets = isSecretScannerAvailable() && upload.findings && process.env.CHAT_RECALL_SCAN_SECRETS !== "0";
18795
19096
  const verifySecretsScan = verifySecrets;
19097
+ let skipped = 0, redactions = 0, walked = 0;
18796
19098
  const kgSeen = /* @__PURE__ */ new Set();
18797
19099
  const kgEntityType = /* @__PURE__ */ new Map();
18798
- const kgRows = [];
18799
- const accumulateKg = (triples, sourceSession) => {
19100
+ const kgEntityNames = /* @__PURE__ */ new Set();
19101
+ const kgKey = (subject, predicate, object, validFrom) => createHash13("sha1").update(`${subject}|${predicate}|${object}|${validFrom}`).digest("hex").slice(0, 16);
19102
+ const accumulateKg = async (triples, sourceSession) => {
18800
19103
  for (const t of triples) {
18801
- const key = `${t.subject}|${t.predicate}|${t.object}|${t.validFrom ?? ""}`;
19104
+ const key = kgKey(t.subject, t.predicate, t.object, t.validFrom ?? "");
18802
19105
  if (kgSeen.has(key)) continue;
18803
19106
  kgSeen.add(key);
18804
19107
  if (t.predicate === "is_a" && !kgEntityType.has(t.subject)) kgEntityType.set(t.subject, t.object);
18805
- kgRows.push({
19108
+ kgEntityNames.add(t.subject);
19109
+ kgEntityNames.add(t.object);
19110
+ const count = { redactions: 0 };
19111
+ await kgTripleBatch.add(redactDeep({
18806
19112
  subject: t.subject,
18807
19113
  predicate: t.predicate,
18808
19114
  object: t.object,
@@ -18810,10 +19116,10 @@ async function syncToTarget(cred, opts = {}) {
18810
19116
  valid_to: null,
18811
19117
  confidence: t.confidence ?? 1,
18812
19118
  source_session: sourceSession
18813
- });
19119
+ }, count));
19120
+ redactions += count.redactions;
18814
19121
  }
18815
19122
  };
18816
- let skipped = 0, redactions = 0, walked = 0;
18817
19123
  let scanMs = 0, scanned = 0;
18818
19124
  const slice = refs.slice(0, opts.limit ?? refs.length);
18819
19125
  const trace = process.env.CHAT_RECALL_SYNC_TRACE === "1" ? (msg) => {
@@ -18946,7 +19252,7 @@ async function syncToTarget(cred, opts = {}) {
18946
19252
  await convBatch.add(built.conv);
18947
19253
  if (built.findings.length > 0) await findingsBatch.add(built.findings);
18948
19254
  if (upload.sessionMeta) {
18949
- accumulateKg(extractEntities(built.kgText, { projectPath: built.projectPath, sourceType: "session", sessionId: ref.prefixedId, validFrom: ref.mtime ? new Date(ref.mtime).toISOString().slice(0, 10) : void 0 }), ref.prefixedId);
19255
+ await accumulateKg(extractEntities(built.kgText, { projectPath: built.projectPath, sourceType: "session", sessionId: ref.prefixedId, validFrom: ref.mtime ? new Date(ref.mtime).toISOString().slice(0, 10) : void 0 }), ref.prefixedId);
18950
19256
  }
18951
19257
  if (upload.sessionMeta && sessionFileBytes(ref) <= FULL_BUILD_MAX_BYTES) {
18952
19258
  try {
@@ -19025,7 +19331,7 @@ async function syncToTarget(cred, opts = {}) {
19025
19331
  chunks: redactedChunks
19026
19332
  });
19027
19333
  if (upload.sessionMeta && redactedChunks.length > 0) {
19028
- accumulateKg(
19334
+ await accumulateKg(
19029
19335
  extractEntities(redactedChunks.map((c) => c.text).join("\n"), { projectPath: item.projectPath || "", sourceType: item.sourceType, validFrom: item.mtime ? new Date(Number(item.mtime)).toISOString().slice(0, 10) : void 0 }),
19030
19336
  null
19031
19337
  );
@@ -19050,16 +19356,8 @@ async function syncToTarget(cred, opts = {}) {
19050
19356
  }
19051
19357
  }
19052
19358
  }
19053
- if (upload.sessionMeta && kgRows.length > 0) {
19054
- const entityNames = /* @__PURE__ */ new Set();
19055
- for (const t of kgRows) {
19056
- const count = { redactions: 0 };
19057
- await kgTripleBatch.add(redactDeep(t, count));
19058
- redactions += count.redactions;
19059
- entityNames.add(t.subject);
19060
- entityNames.add(t.object);
19061
- }
19062
- for (const name of entityNames) {
19359
+ if (upload.sessionMeta && kgEntityNames.size > 0) {
19360
+ for (const name of kgEntityNames) {
19063
19361
  const count = { redactions: 0 };
19064
19362
  await kgEntityBatch.add(redactDeep({ name, type: kgEntityType.get(name) ?? "unknown", properties: {} }, count));
19065
19363
  redactions += count.redactions;
@@ -19419,56 +19717,58 @@ function collectDerived(ref, mtime, count) {
19419
19717
  const id = ref.prefixedId;
19420
19718
  const compute = [];
19421
19719
  let fullOutcome = null;
19422
- for (const kind of COMPUTE_KINDS) {
19423
- let data = null;
19424
- try {
19425
- if (kind === "diff") {
19426
- const replay = replaySessionAny(id);
19427
- if (replay.found) data = replay;
19428
- } else if (kind === "outcome") {
19429
- fullOutcome = computeOutcome(id);
19430
- if (fullOutcome.found) data = fullOutcome;
19431
- } else if (kind === "commits") {
19432
- if (!fullOutcome) fullOutcome = computeOutcome(id);
19433
- if (fullOutcome.found) data = fullOutcome.commits;
19434
- } else if (kind === "markers") {
19435
- const turns = extractTurnsAny(id, { maxTurns: 5e4 });
19436
- const prompts = turns.turns.filter((t) => t.kind === "user" && t.text).map((t) => ({ line: t.line, ts: t.ts, tsIso: t.tsIso, ...markPrompt(t.text) }));
19437
- data = { sessionId: id, prompts, summary: summarizeMarkers(prompts) };
19438
- }
19439
- } catch {
19440
- data = null;
19720
+ return withSessionReadCache(() => {
19721
+ for (const kind of COMPUTE_KINDS) {
19722
+ let data = null;
19723
+ try {
19724
+ if (kind === "diff") {
19725
+ const replay = replaySessionAny(id);
19726
+ if (replay.found) data = replay;
19727
+ } else if (kind === "outcome") {
19728
+ fullOutcome = computeOutcome(id);
19729
+ if (fullOutcome.found) data = fullOutcome;
19730
+ } else if (kind === "commits") {
19731
+ if (!fullOutcome) fullOutcome = computeOutcome(id);
19732
+ if (fullOutcome.found) data = fullOutcome.commits;
19733
+ } else if (kind === "markers") {
19734
+ const turns = extractTurnsAny(id, { maxTurns: 5e4 });
19735
+ const prompts = turns.turns.filter((t) => t.kind === "user" && t.text).map((t) => ({ line: t.line, ts: t.ts, tsIso: t.tsIso, ...markPrompt(t.text) }));
19736
+ data = { sessionId: id, prompts, summary: summarizeMarkers(prompts) };
19737
+ }
19738
+ } catch {
19739
+ data = null;
19740
+ }
19741
+ if (data === null) continue;
19742
+ const redacted = redactDeep(data, count);
19743
+ if (JSON.stringify(redacted).length > MAX_DERIVED_ROW_BYTES) continue;
19744
+ compute.push({ kind, mtime, data: redacted });
19745
+ }
19746
+ let outcomeRow = null;
19747
+ if (fullOutcome?.found) {
19748
+ outcomeRow = {
19749
+ sessionId: id,
19750
+ tool: ref.toolId,
19751
+ status: fullOutcome.status,
19752
+ reason: fullOutcome.reason,
19753
+ fileMtime: mtime,
19754
+ fileSize: 0,
19755
+ contentHash: "",
19756
+ fileCount: fullOutcome.fileCount,
19757
+ linesAdded: fullOutcome.totalLinesAdded,
19758
+ linesRemoved: fullOutcome.totalLinesRemoved,
19759
+ commits: fullOutcome.commits?.totalCommits ?? 0,
19760
+ isFull: true,
19761
+ classifiedAt: Date.now(),
19762
+ lastScannedOffset: 0
19763
+ };
19441
19764
  }
19442
- if (data === null) continue;
19443
- const redacted = redactDeep(data, count);
19444
- if (JSON.stringify(redacted).length > MAX_DERIVED_ROW_BYTES) continue;
19445
- compute.push({ kind, mtime, data: redacted });
19446
- }
19447
- let outcomeRow = null;
19448
- if (fullOutcome?.found) {
19449
- outcomeRow = {
19450
- sessionId: id,
19451
- tool: ref.toolId,
19452
- status: fullOutcome.status,
19453
- reason: fullOutcome.reason,
19454
- fileMtime: mtime,
19455
- fileSize: 0,
19456
- contentHash: "",
19457
- fileCount: fullOutcome.fileCount,
19458
- linesAdded: fullOutcome.totalLinesAdded,
19459
- linesRemoved: fullOutcome.totalLinesRemoved,
19460
- commits: fullOutcome.commits?.totalCommits ?? 0,
19461
- isFull: true,
19462
- classifiedAt: Date.now(),
19463
- lastScannedOffset: 0
19765
+ return {
19766
+ session_id: id,
19767
+ mtime,
19768
+ compute,
19769
+ outcome_row: outcomeRow ? redactDeep({ ...outcomeRow, sessionId: void 0 }, count) : null
19464
19770
  };
19465
- }
19466
- return {
19467
- session_id: id,
19468
- mtime,
19469
- compute,
19470
- outcome_row: outcomeRow ? redactDeep({ ...outcomeRow, sessionId: void 0 }, count) : null
19471
- };
19771
+ });
19472
19772
  }
19473
19773
  function isSyncSkip(r) {
19474
19774
  return typeof r.skipped === "string";
@@ -19497,10 +19797,11 @@ async function syncIncremental(opts = {}) {
19497
19797
  lock.release();
19498
19798
  }
19499
19799
  }
19500
- var ownVersion, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, hashPath, COMPUTE_KINDS, MAX_DERIVED_ROW_BYTES, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
19800
+ var ownVersion, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, hashPath, COMPUTE_KINDS, MAX_DERIVED_ROW_BYTES, CAPS_TTL_MS, capsOk, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
19501
19801
  var init_sync_client = __esm({
19502
19802
  "src/sync-client.ts"() {
19503
19803
  "use strict";
19804
+ init_credentials_env();
19504
19805
  init_secret_redactor();
19505
19806
  init_source_discovery();
19506
19807
  init_home_discovery();
@@ -19528,14 +19829,15 @@ var init_sync_client = __esm({
19528
19829
  init_sync_fields();
19529
19830
  init_index_lock();
19530
19831
  init_http();
19832
+ init_live_session_scan();
19531
19833
  init_backends();
19532
19834
  ownVersion = "0.0.0";
19533
19835
  try {
19534
19836
  const dir = dirname16(fileURLToPath(import.meta.url));
19535
- ownVersion = JSON.parse(readFileSync33(join40(dir, "../package.json"), "utf-8")).version || ownVersion;
19837
+ ownVersion = JSON.parse(readFileSync32(join40(dir, "../package.json"), "utf-8")).version || ownVersion;
19536
19838
  } catch {
19537
19839
  try {
19538
- if (true) ownVersion = "0.5.5";
19840
+ if (true) ownVersion = "0.5.6";
19539
19841
  } catch {
19540
19842
  }
19541
19843
  }
@@ -19546,6 +19848,8 @@ var init_sync_client = __esm({
19546
19848
  hashPath = (p) => p ? "p_" + createHash13("sha256").update(p).digest("hex").slice(0, 12) : "";
19547
19849
  COMPUTE_KINDS = ["diff", "outcome", "commits", "markers"];
19548
19850
  MAX_DERIVED_ROW_BYTES = 2 * 1024 * 1024;
19851
+ CAPS_TTL_MS = 60 * 6e4;
19852
+ capsOk = /* @__PURE__ */ new Map();
19549
19853
  FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "64", 10)) * 1024 * 1024;
19550
19854
  FULL_BUILD_TAIL_BYTES = 16 * 1024 * 1024;
19551
19855
  BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
@@ -19559,11 +19863,11 @@ __export(update_notice_exports, {
19559
19863
  refreshUpdateCheck: () => refreshUpdateCheck,
19560
19864
  updateNotice: () => updateNotice
19561
19865
  });
19562
- import { readFileSync as readFileSync34, writeFileSync as writeFileSync9 } from "node:fs";
19866
+ import { readFileSync as readFileSync33, writeFileSync as writeFileSync9 } from "node:fs";
19563
19867
  import { join as join41 } from "node:path";
19564
19868
  function readCheck() {
19565
19869
  try {
19566
- const c = JSON.parse(readFileSync34(checkPath(), "utf-8"));
19870
+ const c = JSON.parse(readFileSync33(checkPath(), "utf-8"));
19567
19871
  return c && typeof c.serverVersion === "string" ? c : null;
19568
19872
  } catch {
19569
19873
  return null;
@@ -19639,6 +19943,282 @@ var init_update_notice = __esm({
19639
19943
  }
19640
19944
  });
19641
19945
 
19946
+ // ../engine/dist/core/which.js
19947
+ import { existsSync as existsSync39, statSync as statSync36, accessSync, constants } from "node:fs";
19948
+ import { join as join42, isAbsolute as isAbsolute2, delimiter as delimiter2, sep as sep2 } from "node:path";
19949
+ function executable(p) {
19950
+ try {
19951
+ if (!statSync36(p).isFile())
19952
+ return false;
19953
+ } catch {
19954
+ return false;
19955
+ }
19956
+ if (isWindows)
19957
+ return true;
19958
+ try {
19959
+ accessSync(p, constants.X_OK);
19960
+ return true;
19961
+ } catch {
19962
+ return false;
19963
+ }
19964
+ }
19965
+ function candidates(name) {
19966
+ if (!isWindows)
19967
+ return [name];
19968
+ const exts = (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
19969
+ if (exts.some((e) => name.toLowerCase().endsWith(e.toLowerCase())))
19970
+ return [name];
19971
+ return exts.map((e) => name + e);
19972
+ }
19973
+ function resolveOnPath(name) {
19974
+ if (!name)
19975
+ return null;
19976
+ if (name.includes("/") || isWindows && name.includes(sep2) || isAbsolute2(name)) {
19977
+ for (const c of candidates(name))
19978
+ if (executable(c))
19979
+ return c;
19980
+ return null;
19981
+ }
19982
+ for (const raw of (process.env.PATH || "").split(delimiter2)) {
19983
+ const dir = raw.replace(/^"(.*)"$/, "$1");
19984
+ if (!dir)
19985
+ continue;
19986
+ for (const c of candidates(name)) {
19987
+ const full = join42(dir, c);
19988
+ if (existsSync39(full) && executable(full))
19989
+ return full;
19990
+ }
19991
+ }
19992
+ return null;
19993
+ }
19994
+ function isOnPath(name) {
19995
+ return resolveOnPath(name) !== null;
19996
+ }
19997
+ var isWindows;
19998
+ var init_which = __esm({
19999
+ "../engine/dist/core/which.js"() {
20000
+ "use strict";
20001
+ isWindows = process.platform === "win32";
20002
+ }
20003
+ });
20004
+
20005
+ // ../engine/dist/core/mcp-clients.js
20006
+ var mcp_clients_exports = {};
20007
+ __export(mcp_clients_exports, {
20008
+ MCP_CLIENTS: () => MCP_CLIENTS,
20009
+ MCP_SERVER_NAME: () => MCP_SERVER_NAME,
20010
+ getMcpClient: () => getMcpClient,
20011
+ inspectMcpClients: () => inspectMcpClients,
20012
+ registerMcpEverywhere: () => registerMcpEverywhere
20013
+ });
20014
+ import { existsSync as existsSync40, mkdirSync as mkdirSync10, readFileSync as readFileSync35, writeFileSync as writeFileSync11 } from "fs";
20015
+ import { homedir as homedir15 } from "os";
20016
+ import { dirname as dirname18, join as join44 } from "path";
20017
+ function envHome(varName, fallback) {
20018
+ const v = process.env[varName];
20019
+ return v && v.trim() ? v.trim() : fallback;
20020
+ }
20021
+ function writeFileMkdir(path, body) {
20022
+ mkdirSync10(dirname18(path), { recursive: true });
20023
+ writeFileSync11(path, body);
20024
+ }
20025
+ function registerMcpServersJson(target, path, spec) {
20026
+ let cfg = {};
20027
+ if (existsSync40(path)) {
20028
+ try {
20029
+ cfg = JSON.parse(readFileSync35(path, "utf-8"));
20030
+ } catch (err) {
20031
+ return { id: target.id, label: target.label, path, state: "unparseable", error: String(err) };
20032
+ }
20033
+ }
20034
+ const servers = cfg.mcpServers || {};
20035
+ const existing = servers[MCP_SERVER_NAME];
20036
+ const entry = {
20037
+ command: spec.command,
20038
+ ...spec.args ? { args: spec.args } : {}
20039
+ };
20040
+ if (spec.alwaysAllow)
20041
+ entry.alwaysAllow = existing?.alwaysAllow ?? spec.alwaysAllow;
20042
+ if (spec.env)
20043
+ entry.env = { ...existing?.env, ...spec.env };
20044
+ const isCurrent = !!existing && existing.command === spec.command && JSON.stringify(existing.args ?? null) === JSON.stringify(spec.args ?? null) && Object.entries(spec.env ?? {}).every(([k, v]) => existing.env?.[k] === v);
20045
+ if (isCurrent)
20046
+ return { id: target.id, label: target.label, path, state: "current" };
20047
+ servers[MCP_SERVER_NAME] = entry;
20048
+ cfg.mcpServers = servers;
20049
+ writeFileMkdir(path, JSON.stringify(cfg, null, 2) + "\n");
20050
+ return { id: target.id, label: target.label, path, state: existing ? "repaired" : "created" };
20051
+ }
20052
+ function registerOpencodeJson(path, spec) {
20053
+ const id = "opencode";
20054
+ const label = "OpenCode";
20055
+ let cfg = {};
20056
+ if (existsSync40(path)) {
20057
+ try {
20058
+ cfg = JSON.parse(readFileSync35(path, "utf-8"));
20059
+ } catch (err) {
20060
+ return { id, label, path, state: "unparseable", error: String(err) };
20061
+ }
20062
+ }
20063
+ if (!cfg.$schema)
20064
+ cfg.$schema = "https://opencode.ai/config.json";
20065
+ const mcp = cfg.mcp || {};
20066
+ const existing = mcp[MCP_SERVER_NAME];
20067
+ const argv = [spec.command, ...spec.args ?? []];
20068
+ const entry = { type: "local", command: argv, enabled: true };
20069
+ if (spec.env)
20070
+ entry.environment = { ...existing?.environment, ...spec.env };
20071
+ const isCurrent = !!existing && JSON.stringify(existing.command ?? null) === JSON.stringify(argv) && existing.enabled !== false && Object.entries(spec.env ?? {}).every(([k, v]) => existing.environment?.[k] === v);
20072
+ if (isCurrent)
20073
+ return { id, label, path, state: "current" };
20074
+ mcp[MCP_SERVER_NAME] = entry;
20075
+ cfg.mcp = mcp;
20076
+ writeFileMkdir(path, JSON.stringify(cfg, null, 2) + "\n");
20077
+ return { id, label, path, state: existing ? "repaired" : "created" };
20078
+ }
20079
+ function codexBlock(spec) {
20080
+ const lines = [CODEX_HEADER, `command = ${JSON.stringify(spec.command)}`];
20081
+ if (spec.args?.length)
20082
+ lines.push(`args = [${spec.args.map((a) => JSON.stringify(a)).join(", ")}]`);
20083
+ const env = Object.entries(spec.env ?? {});
20084
+ if (env.length) {
20085
+ lines.push(`[mcp_servers.${MCP_SERVER_NAME}.env]`);
20086
+ for (const [k, v] of env)
20087
+ lines.push(`${k} = ${JSON.stringify(v)}`);
20088
+ }
20089
+ return lines.join("\n") + "\n";
20090
+ }
20091
+ function registerCodexToml(path, spec) {
20092
+ const id = "codex";
20093
+ const label = "Codex";
20094
+ const block = codexBlock(spec);
20095
+ const prev = existsSync40(path) ? readFileSync35(path, "utf-8") : "";
20096
+ const lines = prev.length ? prev.split("\n") : [];
20097
+ const start = lines.findIndex((l) => l.trim() === CODEX_HEADER);
20098
+ if (start === -1) {
20099
+ const body = prev.length ? prev.replace(/\n*$/, "\n") + "\n" + block : block;
20100
+ writeFileMkdir(path, body);
20101
+ return { id, label, path, state: "created" };
20102
+ }
20103
+ let end = lines.length;
20104
+ for (let i = start + 1; i < lines.length; i++) {
20105
+ const t = lines[i].trim();
20106
+ if (!t.startsWith("["))
20107
+ continue;
20108
+ if (t.startsWith(`[mcp_servers.${MCP_SERVER_NAME}.`))
20109
+ continue;
20110
+ end = i;
20111
+ break;
20112
+ }
20113
+ const current = lines.slice(start, end).join("\n").replace(/\n*$/, "\n");
20114
+ if (current === block)
20115
+ return { id, label, path, state: "current" };
20116
+ const next = [...lines.slice(0, start), ...block.replace(/\n$/, "").split("\n"), ...lines.slice(end)];
20117
+ writeFileMkdir(path, next.join("\n").replace(/\n*$/, "\n"));
20118
+ return { id, label, path, state: "repaired" };
20119
+ }
20120
+ function getMcpClient(id) {
20121
+ const t = MCP_CLIENTS.find((c) => c.id === id);
20122
+ if (!t)
20123
+ throw new Error(`unknown MCP client: ${id}`);
20124
+ return t;
20125
+ }
20126
+ function registerMcpEverywhere(spec, opts = {}) {
20127
+ const home = opts.home ?? homedir15();
20128
+ const forced = new Set(opts.extraIds ?? []);
20129
+ return MCP_CLIENTS.filter((c) => c.isPresent(home) || forced.has(c.id)).map((c) => c.register(spec, home));
20130
+ }
20131
+ function inspectMcpClients(opts = {}) {
20132
+ const home = opts.home ?? homedir15();
20133
+ return MCP_CLIENTS.map((c) => {
20134
+ const path = c.configPath(home);
20135
+ let registered = false;
20136
+ if (existsSync40(path)) {
20137
+ try {
20138
+ const raw = readFileSync35(path, "utf-8");
20139
+ registered = c.id === "codex" ? raw.includes(CODEX_HEADER) : !!(JSON.parse(raw).mcpServers?.[MCP_SERVER_NAME] ?? JSON.parse(raw).mcp?.[MCP_SERVER_NAME]);
20140
+ } catch {
20141
+ registered = false;
20142
+ }
20143
+ }
20144
+ return { id: c.id, label: c.label, path, present: c.isPresent(home), registered };
20145
+ });
20146
+ }
20147
+ var MCP_SERVER_NAME, CODEX_HEADER, MCP_CLIENTS;
20148
+ var init_mcp_clients = __esm({
20149
+ "../engine/dist/core/mcp-clients.js"() {
20150
+ "use strict";
20151
+ MCP_SERVER_NAME = "chat-recall";
20152
+ CODEX_HEADER = `[mcp_servers.${MCP_SERVER_NAME}]`;
20153
+ MCP_CLIENTS = [
20154
+ {
20155
+ id: "claude",
20156
+ label: "Claude Code",
20157
+ bin: "claude",
20158
+ configPath: (home) => join44(home, ".mcp.json"),
20159
+ // Claude Code is the reference client and `init`'s historical target, so we
20160
+ // always write it — a fresh machine has no ~/.claude yet on first install.
20161
+ isPresent: () => true,
20162
+ register(spec, home) {
20163
+ return registerMcpServersJson(this, this.configPath(home), spec);
20164
+ }
20165
+ },
20166
+ {
20167
+ id: "codex",
20168
+ label: "Codex",
20169
+ bin: "codex",
20170
+ configPath: (home) => join44(envHome("CHAT_RECALL_CODEX_HOME", join44(home, ".codex")), "config.toml"),
20171
+ isPresent(home) {
20172
+ const p = this.configPath(home);
20173
+ return existsSync40(p) || existsSync40(dirname18(p));
20174
+ },
20175
+ register(spec, home) {
20176
+ return registerCodexToml(this.configPath(home), spec);
20177
+ }
20178
+ },
20179
+ {
20180
+ id: "gemini",
20181
+ label: "Gemini CLI",
20182
+ bin: "gemini",
20183
+ configPath: (home) => join44(envHome("CHAT_RECALL_GEMINI_HOME", join44(home, ".gemini")), "settings.json"),
20184
+ isPresent(home) {
20185
+ const p = this.configPath(home);
20186
+ return existsSync40(p) || existsSync40(dirname18(p));
20187
+ },
20188
+ register(spec, home) {
20189
+ return registerMcpServersJson(this, this.configPath(home), spec);
20190
+ }
20191
+ },
20192
+ {
20193
+ id: "opencode",
20194
+ label: "OpenCode",
20195
+ bin: "opencode",
20196
+ configPath: (home) => join44(envHome("XDG_CONFIG_HOME", join44(home, ".config")), "opencode", "opencode.json"),
20197
+ isPresent(home) {
20198
+ const p = this.configPath(home);
20199
+ return existsSync40(p) || existsSync40(dirname18(p));
20200
+ },
20201
+ register(spec, home) {
20202
+ return registerOpencodeJson(this.configPath(home), spec);
20203
+ }
20204
+ },
20205
+ {
20206
+ id: "cursor",
20207
+ label: "Cursor",
20208
+ bin: "cursor",
20209
+ configPath: (home) => join44(home, ".cursor", "mcp.json"),
20210
+ isPresent(home) {
20211
+ const p = this.configPath(home);
20212
+ return existsSync40(p) || existsSync40(dirname18(p));
20213
+ },
20214
+ register(spec, home) {
20215
+ return registerMcpServersJson(this, this.configPath(home), spec);
20216
+ }
20217
+ }
20218
+ ];
20219
+ }
20220
+ });
20221
+
19642
20222
  // ../engine/dist/core/companions.js
19643
20223
  var companions_exports = {};
19644
20224
  __export(companions_exports, {
@@ -19647,27 +20227,39 @@ __export(companions_exports, {
19647
20227
  CODEINDEX_INSTALL_DIR: () => CODEINDEX_INSTALL_DIR,
19648
20228
  checkCodeindexStatus: () => checkCodeindexStatus,
19649
20229
  detectPlatform: () => detectPlatform,
20230
+ expectedCodeindexSha256: () => expectedCodeindexSha256,
19650
20231
  installCodeindex: () => installCodeindex,
19651
20232
  registerCodeindexMcp: () => registerCodeindexMcp,
19652
20233
  uninstallCodeindex: () => uninstallCodeindex,
19653
20234
  unregisterCodeindexMcp: () => unregisterCodeindexMcp
19654
20235
  });
19655
- import { existsSync as existsSync39, mkdirSync as mkdirSync9, statSync as statSync36, chmodSync as chmodSync4, unlinkSync as unlinkSync3, renameSync as renameSync5, createWriteStream, readFileSync as readFileSync35, writeFileSync as writeFileSync10 } from "node:fs";
19656
- import { homedir as homedir15 } from "node:os";
19657
- import { join as join42 } from "node:path";
20236
+ import { existsSync as existsSync41, mkdirSync as mkdirSync11, statSync as statSync37, chmodSync as chmodSync4, unlinkSync as unlinkSync3, renameSync as renameSync5, createWriteStream, readFileSync as readFileSync36, writeFileSync as writeFileSync12 } from "node:fs";
20237
+ import { homedir as homedir16 } from "node:os";
20238
+ import { join as join45 } from "node:path";
19658
20239
  import { execSync as execSync3 } from "node:child_process";
20240
+ import { createHash as createHash14 } from "node:crypto";
20241
+ function expectedCodeindexSha256(artifact) {
20242
+ return CODEINDEX_SHA256[artifact.replace(/^codeindex-/, "").replace(/\.exe$/, "")] ?? null;
20243
+ }
19659
20244
  function detectPlatform() {
19660
20245
  const arch = process.arch;
19661
20246
  const platform = process.platform;
19662
20247
  const archMap = { x64: "x86_64", arm64: "aarch64" };
19663
- const platformMap = { linux: "linux", darwin: "darwin", win32: "windows" };
20248
+ const platformMap = { linux: "linux", darwin: "macos", win32: "windows" };
19664
20249
  const a = archMap[arch];
19665
20250
  const p = platformMap[platform];
19666
20251
  if (!a || !p) {
19667
20252
  return { reason: `Unsupported platform: ${arch}-${platform}` };
19668
20253
  }
19669
- const prebuiltCombos = /* @__PURE__ */ new Set(["x86_64-linux"]);
19670
- const artifact = `codeindex-${a}-${p}`;
20254
+ const prebuiltCombos = /* @__PURE__ */ new Set([
20255
+ "x86_64-linux",
20256
+ "aarch64-linux",
20257
+ "x86_64-macos",
20258
+ "aarch64-macos",
20259
+ "x86_64-windows",
20260
+ "aarch64-windows"
20261
+ ]);
20262
+ const artifact = `codeindex-${a}-${p}${p === "windows" ? ".exe" : ""}`;
19671
20263
  if (!prebuiltCombos.has(`${a}-${p}`)) {
19672
20264
  return {
19673
20265
  artifact,
@@ -19684,20 +20276,15 @@ function checkCodeindexStatus() {
19684
20276
  artifactName: platform.artifact,
19685
20277
  unsupportedReason: platform.reason
19686
20278
  };
19687
- let foundPath = null;
19688
- try {
19689
- const out = execSync3("command -v codeindex", { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
19690
- if (out)
19691
- foundPath = out;
19692
- } catch {
20279
+ let foundPath = resolveOnPath(CODEINDEX_BIN_NAME);
20280
+ if (!foundPath) {
20281
+ foundPath = resolveOnPath(CODEINDEX_BIN_PATH);
19693
20282
  }
19694
- if (!foundPath && existsSync39(CODEINDEX_BIN_PATH))
19695
- foundPath = CODEINDEX_BIN_PATH;
19696
20283
  if (foundPath) {
19697
20284
  status.installed = true;
19698
20285
  status.path = foundPath;
19699
20286
  try {
19700
- status.size = statSync36(foundPath).size;
20287
+ status.size = statSync37(foundPath).size;
19701
20288
  } catch {
19702
20289
  }
19703
20290
  try {
@@ -19709,16 +20296,9 @@ function checkCodeindexStatus() {
19709
20296
  return status;
19710
20297
  }
19711
20298
  async function downloadArtifact(artifact, dest) {
19712
- const hasGh = (() => {
19713
- try {
19714
- execSync3("command -v gh", { stdio: "ignore" });
19715
- return true;
19716
- } catch {
19717
- return false;
19718
- }
19719
- })();
20299
+ const hasGh = isOnPath("gh");
19720
20300
  if (hasGh) {
19721
- execSync3(`gh release download ${CODEINDEX_VERSION} --repo ${CODEINDEX_REPO} -p ${artifact} -O ${dest}`, {
20301
+ execSync3(`gh release download ${CODEINDEX_VERSION} --repo ${CODEINDEX_REPO} -p ${artifact} -O "${dest}"`, {
19722
20302
  stdio: ["ignore", "pipe", "pipe"],
19723
20303
  timeout: 6e4
19724
20304
  });
@@ -19757,15 +20337,26 @@ async function installCodeindex(opts = {}) {
19757
20337
  return status;
19758
20338
  if (!status.prebuiltAvailable)
19759
20339
  return status;
19760
- mkdirSync9(CODEINDEX_INSTALL_DIR, { recursive: true });
20340
+ mkdirSync11(CODEINDEX_INSTALL_DIR, { recursive: true });
19761
20341
  const tmpPath = `${CODEINDEX_BIN_PATH}.partial`;
19762
20342
  try {
19763
20343
  await downloadArtifact(status.artifactName, tmpPath);
20344
+ const expected = expectedCodeindexSha256(status.artifactName);
20345
+ if (!expected) {
20346
+ throw new Error(`No pinned checksum for ${status.artifactName} \u2014 refusing to install an unverified binary. Add its sha256 to CODEINDEX_SHA256 in companions.ts.`);
20347
+ }
20348
+ const actual = createHash14("sha256").update(readFileSync36(tmpPath)).digest("hex");
20349
+ if (actual !== expected) {
20350
+ throw new Error(`Checksum mismatch for ${status.artifactName}.
20351
+ expected ${expected}
20352
+ actual ${actual}
20353
+ Refusing to install. The release may have been re-uploaded, or something is serving different bytes.`);
20354
+ }
19764
20355
  chmodSync4(tmpPath, 493);
19765
20356
  renameSync5(tmpPath, CODEINDEX_BIN_PATH);
19766
20357
  } catch (err) {
19767
20358
  try {
19768
- if (existsSync39(tmpPath))
20359
+ if (existsSync41(tmpPath))
19769
20360
  unlinkSync3(tmpPath);
19770
20361
  } catch {
19771
20362
  }
@@ -19774,7 +20365,7 @@ async function installCodeindex(opts = {}) {
19774
20365
  return checkCodeindexStatus();
19775
20366
  }
19776
20367
  function uninstallCodeindex() {
19777
- if (existsSync39(CODEINDEX_BIN_PATH)) {
20368
+ if (existsSync41(CODEINDEX_BIN_PATH)) {
19778
20369
  unlinkSync3(CODEINDEX_BIN_PATH);
19779
20370
  return { removed: true, path: CODEINDEX_BIN_PATH };
19780
20371
  }
@@ -19782,9 +20373,9 @@ function uninstallCodeindex() {
19782
20373
  }
19783
20374
  function registerCodeindexMcp(mcpJsonPath, binaryPath) {
19784
20375
  let cfg = {};
19785
- if (existsSync39(mcpJsonPath)) {
20376
+ if (existsSync41(mcpJsonPath)) {
19786
20377
  try {
19787
- cfg = JSON.parse(readFileSync35(mcpJsonPath, "utf-8"));
20378
+ cfg = JSON.parse(readFileSync36(mcpJsonPath, "utf-8"));
19788
20379
  } catch {
19789
20380
  cfg = {};
19790
20381
  }
@@ -19793,33 +20384,42 @@ function registerCodeindexMcp(mcpJsonPath, binaryPath) {
19793
20384
  if (cfg.mcpServers["codeindex"])
19794
20385
  return { added: false };
19795
20386
  cfg.mcpServers["codeindex"] = { command: binaryPath, args: ["--mcp"] };
19796
- writeFileSync10(mcpJsonPath, JSON.stringify(cfg, null, 2));
20387
+ writeFileSync12(mcpJsonPath, JSON.stringify(cfg, null, 2));
19797
20388
  return { added: true };
19798
20389
  }
19799
20390
  function unregisterCodeindexMcp(mcpJsonPath) {
19800
- if (!existsSync39(mcpJsonPath))
20391
+ if (!existsSync41(mcpJsonPath))
19801
20392
  return { removed: false };
19802
20393
  let cfg;
19803
20394
  try {
19804
- cfg = JSON.parse(readFileSync35(mcpJsonPath, "utf-8"));
20395
+ cfg = JSON.parse(readFileSync36(mcpJsonPath, "utf-8"));
19805
20396
  } catch {
19806
20397
  return { removed: false };
19807
20398
  }
19808
20399
  if (!cfg.mcpServers?.["codeindex"])
19809
20400
  return { removed: false };
19810
20401
  delete cfg.mcpServers["codeindex"];
19811
- writeFileSync10(mcpJsonPath, JSON.stringify(cfg, null, 2));
20402
+ writeFileSync12(mcpJsonPath, JSON.stringify(cfg, null, 2));
19812
20403
  return { removed: true };
19813
20404
  }
19814
- var CODEINDEX_REPO, CODEINDEX_VERSION, CODEINDEX_INSTALL_DIR, CODEINDEX_BIN_NAME, CODEINDEX_BIN_PATH;
20405
+ var CODEINDEX_REPO, CODEINDEX_VERSION, CODEINDEX_SHA256, CODEINDEX_INSTALL_DIR, CODEINDEX_BIN_NAME, CODEINDEX_BIN_PATH;
19815
20406
  var init_companions = __esm({
19816
20407
  "../engine/dist/core/companions.js"() {
19817
20408
  "use strict";
20409
+ init_which();
19818
20410
  CODEINDEX_REPO = "munhq/codeindex";
19819
- CODEINDEX_VERSION = "v0.1.0";
19820
- CODEINDEX_INSTALL_DIR = join42(homedir15(), ".local", "bin");
19821
- CODEINDEX_BIN_NAME = "codeindex";
19822
- CODEINDEX_BIN_PATH = join42(CODEINDEX_INSTALL_DIR, CODEINDEX_BIN_NAME);
20411
+ CODEINDEX_VERSION = "v0.3.1";
20412
+ CODEINDEX_SHA256 = {
20413
+ "x86_64-linux": "4378f12e7c80fa8bb53e41c02db4b95955f2f878369abfb28b5edd94b4adbb85",
20414
+ "aarch64-linux": "5a1d6c9c2592a3721547c42cd9b6b11f460e903bfc46407ae27d5879e7259587",
20415
+ "x86_64-macos": "6ad2a2b40faa4ec72ac35fee84f35ea7c848cddc4f6382b4cafedb644f30389f",
20416
+ "aarch64-macos": "f366d00918f79a4a0acc09c6091d97c6c378c79edbdbe3cb22e4014ea57f08d2",
20417
+ "x86_64-windows": "a96fc8c8949bb399a44cc0615b12e458460a529c69f01e8829cc39a20f45ab29",
20418
+ "aarch64-windows": "42620d56272cd9ba9d785569d8d754d6b4a3e80f36f28db3e0c483fd600ed117"
20419
+ };
20420
+ CODEINDEX_INSTALL_DIR = join45(homedir16(), ".local", "bin");
20421
+ CODEINDEX_BIN_NAME = process.platform === "win32" ? "codeindex.exe" : "codeindex";
20422
+ CODEINDEX_BIN_PATH = join45(CODEINDEX_INSTALL_DIR, CODEINDEX_BIN_NAME);
19823
20423
  }
19824
20424
  });
19825
20425
 
@@ -19834,26 +20434,26 @@ __export(install_skills_exports, {
19834
20434
  skillsSourceDir: () => skillsSourceDir,
19835
20435
  uninstallSkills: () => uninstallSkills
19836
20436
  });
19837
- import { readdirSync as readdirSync31, existsSync as existsSync40, mkdirSync as mkdirSync10, rmSync as rmSync6, cpSync, writeFileSync as writeFileSync11, readFileSync as readFileSync36, statSync as statSync37, realpathSync as realpathSync4 } from "fs";
19838
- import { createHash as createHash14 } from "crypto";
19839
- import { join as join43, basename as basename23 } from "path";
20437
+ import { readdirSync as readdirSync31, existsSync as existsSync42, mkdirSync as mkdirSync12, rmSync as rmSync6, cpSync, writeFileSync as writeFileSync13, readFileSync as readFileSync37, statSync as statSync38, realpathSync as realpathSync4 } from "fs";
20438
+ import { createHash as createHash15 } from "crypto";
20439
+ import { join as join46, basename as basename23 } from "path";
19840
20440
  function skillsSourceDir() {
19841
- return join43(import.meta.dirname, "..", "skills");
20441
+ return join46(import.meta.dirname, "..", "skills");
19842
20442
  }
19843
20443
  function version() {
19844
20444
  try {
19845
- return JSON.parse(readFileSync36(new URL("../package.json", import.meta.url), "utf-8")).version || "0.0.0";
20445
+ return JSON.parse(readFileSync37(new URL("../package.json", import.meta.url), "utf-8")).version || "0.0.0";
19846
20446
  } catch {
19847
20447
  return "0.0.0";
19848
20448
  }
19849
20449
  }
19850
20450
  function skillsFingerprint() {
19851
20451
  const src = skillsSourceDir();
19852
- const h = createHash14("sha256");
20452
+ const h = createHash15("sha256");
19853
20453
  for (const name of bundledSkillNames()) {
19854
- for (const rel of filesUnder(join43(src, name)).sort()) {
20454
+ for (const rel of filesUnder(join46(src, name)).sort()) {
19855
20455
  h.update(name + "/" + rel);
19856
- h.update(readFileSync36(join43(src, name, rel)));
20456
+ h.update(readFileSync37(join46(src, name, rel)));
19857
20457
  }
19858
20458
  }
19859
20459
  return h.digest("hex").slice(0, 12);
@@ -19863,7 +20463,7 @@ function filesUnder(dir, prefix = "") {
19863
20463
  for (const e of readdirSync31(dir, { withFileTypes: true })) {
19864
20464
  if (e.name === MARKER) continue;
19865
20465
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
19866
- if (e.isDirectory()) out.push(...filesUnder(join43(dir, e.name), rel));
20466
+ if (e.isDirectory()) out.push(...filesUnder(join46(dir, e.name), rel));
19867
20467
  else out.push(rel);
19868
20468
  }
19869
20469
  return out;
@@ -19873,8 +20473,8 @@ function skillStamp() {
19873
20473
  }
19874
20474
  function bundledSkillNames() {
19875
20475
  const src = skillsSourceDir();
19876
- if (!existsSync40(src)) return [];
19877
- return readdirSync31(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync40(join43(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
20476
+ if (!existsSync42(src)) return [];
20477
+ return readdirSync31(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync42(join46(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
19878
20478
  }
19879
20479
  function realPathOrSelf(p) {
19880
20480
  try {
@@ -19887,7 +20487,7 @@ function claudeSkillTargets() {
19887
20487
  const targets = [];
19888
20488
  const seen = /* @__PURE__ */ new Set();
19889
20489
  for (const home of claudeHomeDirs()) {
19890
- const dir = join43(home, "skills");
20490
+ const dir = join46(home, "skills");
19891
20491
  const key = realPathOrSelf(dir);
19892
20492
  if (seen.has(key)) continue;
19893
20493
  seen.add(key);
@@ -19898,7 +20498,7 @@ function claudeSkillTargets() {
19898
20498
  dir,
19899
20499
  // Judge each profile on ITS OWN transcripts, not the primary's: a home
19900
20500
  // with no projects/ is a directory someone made, not a Claude install.
19901
- available: existsSync40(join43(home, "projects"))
20501
+ available: existsSync42(join46(home, "projects"))
19902
20502
  });
19903
20503
  }
19904
20504
  if (targets.length === 0) {
@@ -19921,7 +20521,7 @@ function skillTargets() {
19921
20521
  ];
19922
20522
  }
19923
20523
  function isManaged(dir) {
19924
- return existsSync40(join43(dir, MARKER));
20524
+ return existsSync42(join46(dir, MARKER));
19925
20525
  }
19926
20526
  function installSkills(opts = {}) {
19927
20527
  const onlyAvailable = opts.onlyAvailable ?? true;
@@ -19938,15 +20538,15 @@ function installSkills(opts = {}) {
19938
20538
  continue;
19939
20539
  }
19940
20540
  for (const name of names) {
19941
- const dest = join43(t.dir, name);
19942
- if (existsSync40(dest) && !isManaged(dest)) {
20541
+ const dest = join46(t.dir, name);
20542
+ if (existsSync42(dest) && !isManaged(dest)) {
19943
20543
  skippedUserOwned.push(name);
19944
20544
  continue;
19945
20545
  }
19946
- mkdirSync10(t.dir, { recursive: true });
20546
+ mkdirSync12(t.dir, { recursive: true });
19947
20547
  rmSync6(dest, { recursive: true, force: true });
19948
- cpSync(join43(src, name), dest, { recursive: true });
19949
- writeFileSync11(join43(dest, MARKER), `${stamp}
20548
+ cpSync(join46(src, name), dest, { recursive: true });
20549
+ writeFileSync13(join46(dest, MARKER), `${stamp}
19950
20550
  `);
19951
20551
  installed.push(name);
19952
20552
  }
@@ -19959,13 +20559,13 @@ function uninstallSkills() {
19959
20559
  const perTarget = [];
19960
20560
  for (const t of skillTargets()) {
19961
20561
  const removed = [];
19962
- if (!existsSync40(t.dir)) {
20562
+ if (!existsSync42(t.dir)) {
19963
20563
  perTarget.push({ id: t.id, label: t.label, removed });
19964
20564
  continue;
19965
20565
  }
19966
20566
  for (const entry of readdirSync31(t.dir, { withFileTypes: true })) {
19967
20567
  if (!entry.isDirectory()) continue;
19968
- const dir = join43(t.dir, entry.name);
20568
+ const dir = join46(t.dir, entry.name);
19969
20569
  if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
19970
20570
  rmSync6(dir, { recursive: true, force: true });
19971
20571
  removed.push(entry.name);
@@ -19982,10 +20582,10 @@ function skillsNeedRefresh() {
19982
20582
  for (const t of skillTargets()) {
19983
20583
  if (!t.available) continue;
19984
20584
  for (const name of names) {
19985
- const marker = join43(t.dir, name, MARKER);
19986
- if (!existsSync40(marker)) return true;
20585
+ const marker = join46(t.dir, name, MARKER);
20586
+ if (!existsSync42(marker)) return true;
19987
20587
  try {
19988
- if (readFileSync36(marker, "utf-8").trim() !== cur) return true;
20588
+ if (readFileSync37(marker, "utf-8").trim() !== cur) return true;
19989
20589
  } catch {
19990
20590
  return true;
19991
20591
  }
@@ -20000,11 +20600,11 @@ function skillStatus() {
20000
20600
  let installed = 0;
20001
20601
  let ver = null;
20002
20602
  for (const name of names) {
20003
- const marker = join43(t.dir, name, MARKER);
20004
- if (existsSync40(marker)) {
20603
+ const marker = join46(t.dir, name, MARKER);
20604
+ if (existsSync42(marker)) {
20005
20605
  installed++;
20006
20606
  try {
20007
- ver = readFileSync36(marker, "utf-8").trim();
20607
+ ver = readFileSync37(marker, "utf-8").trim();
20008
20608
  } catch {
20009
20609
  }
20010
20610
  }
@@ -20033,7 +20633,7 @@ __export(repair_exports, {
20033
20633
  repairSession: () => repairSession,
20034
20634
  repairSessions: () => repairSessions
20035
20635
  });
20036
- import { createHash as createHash15 } from "node:crypto";
20636
+ import { createHash as createHash16 } from "node:crypto";
20037
20637
  function resolveEndpoints(only) {
20038
20638
  const settings2 = loadSettings();
20039
20639
  const creds = loadAllCredentials();
@@ -20076,11 +20676,11 @@ async function repairSession(id, opts = {}) {
20076
20676
  const log6 = (m) => {
20077
20677
  if (opts.verbose) console.error(` [repair ${id}] ${m}`);
20078
20678
  };
20079
- const candidates = [];
20679
+ const candidates2 = [];
20080
20680
  const localShadow = readShadowContainer(tool, rawId);
20081
20681
  if (localShadow) {
20082
20682
  try {
20083
- candidates.push({ container: localShadow, messages: parseTranscriptFromContainer(localShadow).messages.length, source: "local-shadow" });
20683
+ candidates2.push({ container: localShadow, messages: parseTranscriptFromContainer(localShadow).messages.length, source: "local-shadow" });
20084
20684
  } catch {
20085
20685
  }
20086
20686
  }
@@ -20092,15 +20692,15 @@ async function repairSession(id, opts = {}) {
20092
20692
  if (arch?.gzB64) {
20093
20693
  try {
20094
20694
  const container = gunzipContainer(Buffer.from(arch.gzB64, "base64"));
20095
- if (container) candidates.push({ container, messages: parseTranscriptFromContainer(container).messages.length, source: ep.url });
20695
+ if (container) candidates2.push({ container, messages: parseTranscriptFromContainer(container).messages.length, source: ep.url });
20096
20696
  } catch {
20097
20697
  }
20098
20698
  }
20099
20699
  }
20100
- if (candidates.length === 0) {
20700
+ if (candidates2.length === 0) {
20101
20701
  return { sessionId: id, status: "no-archive", fullestMessages: 0, fullestSource: "", pushed: [], note: "no shadow or server archive found" };
20102
20702
  }
20103
- const fullest = candidates.reduce((a, b) => b.messages > a.messages ? b : a);
20703
+ const fullest = candidates2.reduce((a, b) => b.messages > a.messages ? b : a);
20104
20704
  log6(`fullest archive = ${fullest.messages} msg(s) from ${fullest.source}`);
20105
20705
  if (opts.dryRun) {
20106
20706
  const needy2 = endpoints.filter((ep) => (currentOnServer.get(ep.url) ?? 0) < fullest.messages || opts.force);
@@ -20202,7 +20802,7 @@ async function repairAll(opts = {}) {
20202
20802
  if (!page?.hasMore || sessions.length === 0) break;
20203
20803
  }
20204
20804
  log6(`window=${sinceHours}h \u2192 ${ids.length} session(s) to check on ${primary.url}`);
20205
- const candidates = [];
20805
+ const candidates2 = [];
20206
20806
  let scanned = 0;
20207
20807
  for (const id of ids) {
20208
20808
  scanned++;
@@ -20215,9 +20815,9 @@ async function repairAll(opts = {}) {
20215
20815
  if (archiveMsgs <= current) continue;
20216
20816
  const r = await repairSession(id, { dryRun: !opts.apply, verbose: opts.verbose, server: opts.server });
20217
20817
  if (r.status === "already-full") continue;
20218
- candidates.push(r);
20818
+ candidates2.push(r);
20219
20819
  }
20220
- return { scanned, candidates, discoveryServer: primary.url };
20820
+ return { scanned, candidates: candidates2, discoveryServer: primary.url };
20221
20821
  }
20222
20822
  var hashPath2;
20223
20823
  var init_repair = __esm({
@@ -20229,7 +20829,7 @@ var init_repair = __esm({
20229
20829
  init_http();
20230
20830
  init_sync_client();
20231
20831
  init_sync_client();
20232
- hashPath2 = (p) => p ? "p_" + createHash15("sha256").update(p).digest("hex").slice(0, 12) : "";
20832
+ hashPath2 = (p) => p ? "p_" + createHash16("sha256").update(p).digest("hex").slice(0, 12) : "";
20233
20833
  }
20234
20834
  });
20235
20835
 
@@ -20449,11 +21049,11 @@ var verify_repair_exports = {};
20449
21049
  __export(verify_repair_exports, {
20450
21050
  forceFullResync: () => forceFullResync
20451
21051
  });
20452
- import { copyFileSync as copyFileSync3, existsSync as existsSync41 } from "fs";
21052
+ import { copyFileSync as copyFileSync3, existsSync as existsSync43 } from "fs";
20453
21053
  function forceFullResync(server, sessionIds) {
20454
21054
  if (sessionIds.length === 0) return 0;
20455
21055
  const path = ledgerFilePath();
20456
- if (existsSync41(path)) {
21056
+ if (existsSync43(path)) {
20457
21057
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace(/-/g, "");
20458
21058
  try {
20459
21059
  copyFileSync3(path, `${path}.bak-${stamp}`);
@@ -20591,15 +21191,17 @@ var init_prompts = __esm({
20591
21191
  var collector_exports = {};
20592
21192
  __export(collector_exports, {
20593
21193
  COLLECTOR_VERSION: () => COLLECTOR_VERSION,
21194
+ GENERATED_PATTERNS: () => GENERATED_PATTERNS,
20594
21195
  collectCode: () => collectCode,
21196
+ isGenerated: () => isGenerated,
20595
21197
  isJunkWorkspacePath: () => isJunkWorkspacePath,
20596
21198
  isSecretRule: () => isSecretRule,
20597
21199
  resolveCodeindexBin: () => resolveCodeindexBin
20598
21200
  });
20599
21201
  import { spawn } from "node:child_process";
20600
21202
  import { execSync as execSync4 } from "node:child_process";
20601
- import { readFileSync as readFileSync37 } from "node:fs";
20602
- import { join as join44, basename as basename25 } from "node:path";
21203
+ import { readFileSync as readFileSync38 } from "node:fs";
21204
+ import { join as join47, basename as basename25 } from "node:path";
20603
21205
  async function resolveCodeindexBin(autoInstall = true) {
20604
21206
  let st = checkCodeindexStatus();
20605
21207
  if (!st.installed && autoInstall)
@@ -20686,10 +21288,11 @@ function isSecretRule(rule) {
20686
21288
  return /secret|password|credential|key|token|auth/i.test(rule);
20687
21289
  }
20688
21290
  function isJunkWorkspacePath(ws) {
20689
- return /(^|\/)(scratchpad|\.cache|node_modules|\.git|tmp)(\/|$)|claude-1000/i.test(ws);
21291
+ const norm = ws.replace(/\\/g, "/");
21292
+ return /(^|\/)(scratchpad|\.cache|node_modules|\.git|tmp)(\/|$)|claude-1000/i.test(norm);
20690
21293
  }
20691
21294
  async function collectCode(opts) {
20692
- const ws = opts.workspace.replace(/\/+$/, "");
21295
+ const ws = opts.workspace.replace(/[/\\]+$/, "");
20693
21296
  const log6 = opts.log ?? (() => {
20694
21297
  });
20695
21298
  if (isJunkWorkspacePath(ws)) {
@@ -20697,9 +21300,11 @@ async function collectCode(opts) {
20697
21300
  }
20698
21301
  const bin = opts.binPath ?? await resolveCodeindexBin(opts.autoInstall ?? true);
20699
21302
  const projectId = resolveProjectId(ws).id || `path:${ws}`;
20700
- const wsPrefix = ws + "/";
21303
+ const wsNorm = ws.replace(/\\/g, "/");
21304
+ const wsPrefix = wsNorm + "/";
20701
21305
  const rel = (p) => {
20702
- let r = p.startsWith(wsPrefix) ? p.slice(wsPrefix.length) : p;
21306
+ const n = p.replace(/\\/g, "/");
21307
+ let r = n.startsWith(wsPrefix) ? n.slice(wsPrefix.length) : n;
20703
21308
  if (r.startsWith("./"))
20704
21309
  r = r.slice(2);
20705
21310
  return r;
@@ -20760,7 +21365,7 @@ async function collectCode(opts) {
20760
21365
  }
20761
21366
  const complexity = (r) => {
20762
21367
  try {
20763
- const txt = readFileSync37(join44(ws, r), "utf8");
21368
+ const txt = readFileSync38(join47(ws, r), "utf8");
20764
21369
  if (txt.length > 2e6)
20765
21370
  return 1;
20766
21371
  const m = txt.match(CF_RE);
@@ -21344,7 +21949,7 @@ async function collectCode(opts) {
21344
21949
  void health;
21345
21950
  return { project, findings, hotspots, actions };
21346
21951
  }
21347
- var COLLECTOR_VERSION, ANALYSES, GENERATED, CF_RE, DEPTH, MCP_OUT_MAX_BYTES, call, COMMON_SYMBOL_NAMES;
21952
+ var COLLECTOR_VERSION, ANALYSES, GENERATED, CF_RE, DEPTH, MCP_OUT_MAX_BYTES, call, GENERATED_PATTERNS, COMMON_SYMBOL_NAMES;
21348
21953
  var init_collector = __esm({
21349
21954
  "../engine/dist/core/code/collector.js"() {
21350
21955
  "use strict";
@@ -21386,7 +21991,13 @@ var init_collector = __esm({
21386
21991
  "/vendor/",
21387
21992
  "/third_party/",
21388
21993
  "/site-packages/",
21994
+ // Lockfiles. '.lock' alone catches yarn.lock, Cargo.lock, poetry.lock and
21995
+ // composer.lock, and MISSES the two biggest ones in a JS repo —
21996
+ // package-lock.json and pnpm-lock.yaml are named '-lock.<ext>'. Found by
21997
+ // replacing this file's mirror test with one that calls the real predicate.
21389
21998
  ".lock",
21999
+ "-lock.json",
22000
+ "-lock.yaml",
21390
22001
  "/dist/",
21391
22002
  "/build/",
21392
22003
  "/out/",
@@ -21403,6 +22014,7 @@ var init_collector = __esm({
21403
22014
  DEPTH = 2;
21404
22015
  MCP_OUT_MAX_BYTES = 64 * 1024 * 1024;
21405
22016
  call = (id, name, args = {}) => ({ jsonrpc: "2.0", id, method: "tools/call", params: { name, arguments: args } });
22017
+ GENERATED_PATTERNS = GENERATED;
21406
22018
  COMMON_SYMBOL_NAMES = /* @__PURE__ */ new Set([
21407
22019
  "error",
21408
22020
  "err",
@@ -21468,11 +22080,11 @@ var init_collector = __esm({
21468
22080
  });
21469
22081
 
21470
22082
  // ../engine/dist/core/artifact-codec.js
21471
- import { readFileSync as readFileSync38 } from "fs";
21472
- import { join as join45 } from "path";
21473
- import { homedir as homedir16 } from "os";
22083
+ import { readFileSync as readFileSync39 } from "fs";
22084
+ import { join as join48 } from "path";
22085
+ import { homedir as homedir17 } from "os";
21474
22086
  function readCommand(filePath, format) {
21475
- const content = readFileSync38(filePath, "utf-8");
22087
+ const content = readFileSync39(filePath, "utf-8");
21476
22088
  if (format === "toml") {
21477
22089
  const t = parseScalarToml(content);
21478
22090
  return { name: t.name || base(filePath), description: t.description || "", body: t.prompt || "" };
@@ -21481,7 +22093,7 @@ function readCommand(filePath, format) {
21481
22093
  return { name: fm.name || base(filePath), description: fm.description || "", body: body.trim() };
21482
22094
  }
21483
22095
  function readAgent(filePath, format) {
21484
- const content = readFileSync38(filePath, "utf-8");
22096
+ const content = readFileSync39(filePath, "utf-8");
21485
22097
  if (format === "toml") {
21486
22098
  const t = parseScalarToml(content);
21487
22099
  return {
@@ -21494,35 +22106,35 @@ function readAgent(filePath, format) {
21494
22106
  return { name: fm.name || base(filePath), description: fm.description || "", tools: fm.tools, body: body.trim() };
21495
22107
  }
21496
22108
  function readInstructions(filePath, name) {
21497
- const content = readFileSync38(filePath, "utf-8");
22109
+ const content = readFileSync39(filePath, "utf-8");
21498
22110
  return { name, description: "", body: content };
21499
22111
  }
21500
22112
  function commandPath(tool, name) {
21501
22113
  switch (tool) {
21502
22114
  case "claude":
21503
- return join45(claudeBackend.commandsDir(), `${name}.md`);
22115
+ return join48(claudeBackend.commandsDir(), `${name}.md`);
21504
22116
  case "agy":
21505
- return join45(join45(agyBackend.homeDir(), "commands"), `${name}.toml`);
22117
+ return join48(join48(agyBackend.homeDir(), "commands"), `${name}.toml`);
21506
22118
  case "gemini":
21507
- return join45(geminiBackend.commandsDir(), `${name}.toml`);
22119
+ return join48(geminiBackend.commandsDir(), `${name}.toml`);
21508
22120
  case "opencode":
21509
- return join45(opencodeBackend.commandsDir(), `${name}.md`);
22121
+ return join48(opencodeBackend.commandsDir(), `${name}.md`);
21510
22122
  case "codex":
21511
- return join45(codexBackend.promptsDir(), `${name}.md`);
22123
+ return join48(codexBackend.promptsDir(), `${name}.md`);
21512
22124
  }
21513
22125
  }
21514
22126
  function agentPath(tool, name) {
21515
22127
  switch (tool) {
21516
22128
  case "claude":
21517
- return join45(claudeBackend.agentsDir(), `${name}.md`);
22129
+ return join48(claudeBackend.agentsDir(), `${name}.md`);
21518
22130
  case "agy":
21519
- return join45(join45(agyBackend.homeDir(), "agents"), `${name}.md`);
22131
+ return join48(join48(agyBackend.homeDir(), "agents"), `${name}.md`);
21520
22132
  case "gemini":
21521
- return join45(geminiBackend.agentsDir(), `${name}.md`);
22133
+ return join48(geminiBackend.agentsDir(), `${name}.md`);
21522
22134
  case "opencode":
21523
- return join45(opencodeBackend.agentsDir(), `${name}.md`);
22135
+ return join48(opencodeBackend.agentsDir(), `${name}.md`);
21524
22136
  case "codex":
21525
- return join45(codexBackend.agentsDir(), `${name}.toml`);
22137
+ return join48(codexBackend.agentsDir(), `${name}.toml`);
21526
22138
  }
21527
22139
  }
21528
22140
  function instructionsFilename(tool) {
@@ -21540,18 +22152,18 @@ function instructionsFilename(tool) {
21540
22152
  function instructionsPath(tool, projectPath) {
21541
22153
  const file = instructionsFilename(tool);
21542
22154
  if (projectPath)
21543
- return join45(projectPath, file);
22155
+ return join48(projectPath, file);
21544
22156
  switch (tool) {
21545
22157
  case "claude":
21546
- return join45(claudeBackend.homeDir(), file);
22158
+ return join48(claudeBackend.homeDir(), file);
21547
22159
  case "agy":
21548
- return join45(agyBackend.homeDir(), file);
22160
+ return join48(agyBackend.homeDir(), file);
21549
22161
  case "gemini":
21550
- return join45(geminiBackend.homeDir(), file);
22162
+ return join48(geminiBackend.homeDir(), file);
21551
22163
  case "codex":
21552
- return join45(codexBackend.homeDir(), file);
22164
+ return join48(codexBackend.homeDir(), file);
21553
22165
  case "opencode":
21554
- return join45(homedir16(), ".config", "opencode", file);
22166
+ return join48(homedir17(), ".config", "opencode", file);
21555
22167
  }
21556
22168
  }
21557
22169
  function emit(type, art, toTool, projectPath) {
@@ -21614,9 +22226,9 @@ __export(toolkit_sync_exports, {
21614
22226
  supportedTargetsFor: () => supportedTargetsFor,
21615
22227
  writeMcpEntry: () => writeMcpEntry
21616
22228
  });
21617
- import { existsSync as existsSync42, mkdirSync as mkdirSync11, readFileSync as readFileSync39, writeFileSync as writeFileSync12, cpSync as cpSync2 } from "fs";
21618
- import { dirname as dirname17, join as join46, basename as basename26 } from "path";
21619
- import { homedir as homedir17 } from "os";
22229
+ import { existsSync as existsSync44, mkdirSync as mkdirSync13, readFileSync as readFileSync40, writeFileSync as writeFileSync14, cpSync as cpSync2 } from "fs";
22230
+ import { dirname as dirname19, join as join49, basename as basename26 } from "path";
22231
+ import { homedir as homedir18 } from "os";
21620
22232
  function supportedTargetsFor(type) {
21621
22233
  return SUPPORTED_TARGETS[type];
21622
22234
  }
@@ -21657,7 +22269,7 @@ function skillsDirFor(tool) {
21657
22269
  case "claude":
21658
22270
  return claudeBackend.skillsDir();
21659
22271
  case "agy":
21660
- return join46(agyBackend.homeDir(), "skills");
22272
+ return join49(agyBackend.homeDir(), "skills");
21661
22273
  case "gemini":
21662
22274
  return geminiBackend.skillsDir();
21663
22275
  case "opencode":
@@ -21667,15 +22279,15 @@ function skillsDirFor(tool) {
21667
22279
  }
21668
22280
  }
21669
22281
  function copySkill(row, toTool) {
21670
- const skillDir = row.extra.skillDir || dirname17(row.filePath);
21671
- if (!existsSync42(skillDir))
22282
+ const skillDir = row.extra.skillDir || dirname19(row.filePath);
22283
+ if (!existsSync44(skillDir))
21672
22284
  return { ok: false, status: 404, error: `Source dir missing: ${skillDir}` };
21673
22285
  const name = row.extra.skillName || basename26(skillDir);
21674
- const targetDir = join46(skillsDirFor(toTool), name);
21675
- if (existsSync42(targetDir))
22286
+ const targetDir = join49(skillsDirFor(toTool), name);
22287
+ if (existsSync44(targetDir))
21676
22288
  return { ok: false, status: 409, error: `Already exists: ${targetDir}` };
21677
22289
  try {
21678
- mkdirSync11(dirname17(targetDir), { recursive: true });
22290
+ mkdirSync13(dirname19(targetDir), { recursive: true });
21679
22291
  cpSync2(skillDir, targetDir, { recursive: true });
21680
22292
  return { ok: true, targetPath: targetDir };
21681
22293
  } catch (e) {
@@ -21683,29 +22295,29 @@ function copySkill(row, toTool) {
21683
22295
  }
21684
22296
  }
21685
22297
  function readMcpEntry(tool, name) {
21686
- const home = homedir17();
22298
+ const home = homedir18();
21687
22299
  const tries = [];
21688
22300
  if (tool === "claude") {
21689
- tries.push({ path: join46(home, ".mcp.json"), key: "mcpServers" });
21690
- tries.push({ path: join46(home, ".claude.json"), key: "mcpServers" });
22301
+ tries.push({ path: join49(home, ".mcp.json"), key: "mcpServers" });
22302
+ tries.push({ path: join49(home, ".claude.json"), key: "mcpServers" });
21691
22303
  } else if (tool === "gemini") {
21692
- tries.push({ path: join46(home, ".gemini", "settings.json"), key: "mcpServers" });
22304
+ tries.push({ path: join49(home, ".gemini", "settings.json"), key: "mcpServers" });
21693
22305
  } else if (tool === "agy") {
21694
- tries.push({ path: join46(home, ".gemini", "config", "mcp_config.json"), key: "mcpServers" });
22306
+ tries.push({ path: join49(home, ".gemini", "config", "mcp_config.json"), key: "mcpServers" });
21695
22307
  } else if (tool === "opencode") {
21696
- tries.push({ path: join46(home, ".config", "opencode", "opencode.json"), key: "mcp" });
21697
- tries.push({ path: join46(home, ".config", "opencode", "config.json"), key: "mcp" });
21698
- tries.push({ path: join46(home, ".opencode", "config.json"), key: "mcp" });
22308
+ tries.push({ path: join49(home, ".config", "opencode", "opencode.json"), key: "mcp" });
22309
+ tries.push({ path: join49(home, ".config", "opencode", "config.json"), key: "mcp" });
22310
+ tries.push({ path: join49(home, ".opencode", "config.json"), key: "mcp" });
21699
22311
  } else if (tool === "codex") {
21700
- const entry = readCodexMcpEntry(join46(home, ".codex", "config.toml"), name);
22312
+ const entry = readCodexMcpEntry(join49(home, ".codex", "config.toml"), name);
21701
22313
  if (entry)
21702
22314
  return entry;
21703
22315
  }
21704
22316
  for (const t of tries) {
21705
- if (!existsSync42(t.path))
22317
+ if (!existsSync44(t.path))
21706
22318
  continue;
21707
22319
  try {
21708
- const cfg = JSON.parse(readFileSync39(t.path, "utf-8"));
22320
+ const cfg = JSON.parse(readFileSync40(t.path, "utf-8"));
21709
22321
  const block = cfg[t.key] || {};
21710
22322
  if (block[name])
21711
22323
  return block[name];
@@ -21715,11 +22327,11 @@ function readMcpEntry(tool, name) {
21715
22327
  return null;
21716
22328
  }
21717
22329
  function readCodexMcpEntry(path, target) {
21718
- if (!existsSync42(path))
22330
+ if (!existsSync44(path))
21719
22331
  return null;
21720
22332
  let content;
21721
22333
  try {
21722
- content = readFileSync39(path, "utf-8");
22334
+ content = readFileSync40(path, "utf-8");
21723
22335
  } catch {
21724
22336
  return null;
21725
22337
  }
@@ -21769,8 +22381,8 @@ function readCodexMcpEntry(path, target) {
21769
22381
  }
21770
22382
  function writeCodexMcpEntry(path, name, entry) {
21771
22383
  let content = "";
21772
- if (existsSync42(path))
21773
- content = readFileSync39(path, "utf-8");
22384
+ if (existsSync44(path))
22385
+ content = readFileSync40(path, "utf-8");
21774
22386
  const header = `[mcp_servers.${name}]`;
21775
22387
  if (content.includes(header))
21776
22388
  return false;
@@ -21799,14 +22411,14 @@ function writeCodexMcpEntry(path, name, entry) {
21799
22411
  for (const [k, v] of Object.entries(entry.env))
21800
22412
  lines.push(`${k} = ${escape(String(v))}`);
21801
22413
  }
21802
- mkdirSync11(dirname17(path), { recursive: true });
21803
- writeFileSync12(path, content + lines.join("\n") + "\n");
22414
+ mkdirSync13(dirname19(path), { recursive: true });
22415
+ writeFileSync14(path, content + lines.join("\n") + "\n");
21804
22416
  return true;
21805
22417
  }
21806
22418
  function writeMcpEntry(toTool, name, entry) {
21807
- const home = homedir17();
22419
+ const home = homedir18();
21808
22420
  if (toTool === "codex") {
21809
- const path2 = join46(home, ".codex", "config.toml");
22421
+ const path2 = join49(home, ".codex", "config.toml");
21810
22422
  if (!writeCodexMcpEntry(path2, name, entry))
21811
22423
  return { ok: false, status: 409, error: `MCP "${name}" already exists in ${path2}.` };
21812
22424
  return { ok: true, targetPath: path2 };
@@ -21814,27 +22426,27 @@ function writeMcpEntry(toTool, name, entry) {
21814
22426
  let path;
21815
22427
  let key;
21816
22428
  if (toTool === "claude") {
21817
- path = join46(home, ".mcp.json");
22429
+ path = join49(home, ".mcp.json");
21818
22430
  key = "mcpServers";
21819
22431
  } else if (toTool === "gemini") {
21820
- path = join46(home, ".gemini", "settings.json");
22432
+ path = join49(home, ".gemini", "settings.json");
21821
22433
  key = "mcpServers";
21822
22434
  } else if (toTool === "agy") {
21823
- path = join46(home, ".gemini", "config", "mcp_config.json");
22435
+ path = join49(home, ".gemini", "config", "mcp_config.json");
21824
22436
  key = "mcpServers";
21825
22437
  } else {
21826
- path = join46(home, ".config", "opencode", "opencode.json");
22438
+ path = join49(home, ".config", "opencode", "opencode.json");
21827
22439
  key = "mcp";
21828
22440
  }
21829
22441
  let cfg = {};
21830
- if (existsSync42(path)) {
22442
+ if (existsSync44(path)) {
21831
22443
  try {
21832
- cfg = JSON.parse(readFileSync39(path, "utf-8"));
22444
+ cfg = JSON.parse(readFileSync40(path, "utf-8"));
21833
22445
  } catch {
21834
22446
  return { ok: false, status: 500, error: `Target config is malformed: ${path}` };
21835
22447
  }
21836
22448
  } else {
21837
- mkdirSync11(dirname17(path), { recursive: true });
22449
+ mkdirSync13(dirname19(path), { recursive: true });
21838
22450
  }
21839
22451
  cfg[key] = cfg[key] || {};
21840
22452
  if (cfg[key][name])
@@ -21861,7 +22473,7 @@ function writeMcpEntry(toTool, name, entry) {
21861
22473
  }
21862
22474
  cfg[key][name] = normalized;
21863
22475
  try {
21864
- writeFileSync12(path, JSON.stringify(cfg, null, 2));
22476
+ writeFileSync14(path, JSON.stringify(cfg, null, 2));
21865
22477
  return { ok: true, targetPath: path };
21866
22478
  } catch (e) {
21867
22479
  return { ok: false, status: 500, error: e instanceof Error ? e.message : "write failed" };
@@ -21891,11 +22503,11 @@ function copyCommandOrAgent(type, row, toTool) {
21891
22503
  return { ok: false, status: 404, error: `Source unreadable: ${e instanceof Error ? e.message : "failed"}` };
21892
22504
  }
21893
22505
  const out = emit(type, art, toTool);
21894
- if (existsSync42(out.path))
22506
+ if (existsSync44(out.path))
21895
22507
  return { ok: false, status: 409, error: `Already exists: ${out.path}` };
21896
22508
  try {
21897
- mkdirSync11(dirname17(out.path), { recursive: true });
21898
- writeFileSync12(out.path, out.content);
22509
+ mkdirSync13(dirname19(out.path), { recursive: true });
22510
+ writeFileSync14(out.path, out.content);
21899
22511
  return { ok: true, targetPath: out.path };
21900
22512
  } catch (e) {
21901
22513
  return { ok: false, status: 500, error: e instanceof Error ? e.message : "write failed" };
@@ -21911,11 +22523,11 @@ function copyInstructions(row, toTool) {
21911
22523
  return { ok: false, status: 404, error: `Source unreadable: ${e instanceof Error ? e.message : "failed"}` };
21912
22524
  }
21913
22525
  const out = emit("instructions", art, toTool, row.projectPath || void 0);
21914
- if (existsSync42(out.path))
22526
+ if (existsSync44(out.path))
21915
22527
  return { ok: false, status: 409, error: `Already exists: ${out.path}` };
21916
22528
  try {
21917
- mkdirSync11(dirname17(out.path), { recursive: true });
21918
- writeFileSync12(out.path, out.content);
22529
+ mkdirSync13(dirname19(out.path), { recursive: true });
22530
+ writeFileSync14(out.path, out.content);
21919
22531
  return { ok: true, targetPath: out.path };
21920
22532
  } catch (e) {
21921
22533
  return { ok: false, status: 500, error: e instanceof Error ? e.message : "write failed" };
@@ -22087,9 +22699,9 @@ __export(project_tasks_exports, {
22087
22699
  pushProjectTaskStatuses: () => pushProjectTaskStatuses,
22088
22700
  renderTeamTasksMd: () => renderTeamTasksMd
22089
22701
  });
22090
- import { existsSync as existsSync43, readFileSync as readFileSync40, writeFileSync as writeFileSync13, mkdirSync as mkdirSync12, statSync as statSync38 } from "node:fs";
22091
- import { join as join47, dirname as dirname18 } from "node:path";
22092
- import { homedir as homedir18 } from "node:os";
22702
+ import { existsSync as existsSync45, readFileSync as readFileSync41, writeFileSync as writeFileSync15, mkdirSync as mkdirSync14, statSync as statSync39 } from "node:fs";
22703
+ import { join as join50, dirname as dirname20 } from "node:path";
22704
+ import { homedir as homedir19 } from "node:os";
22093
22705
  function isTeamTaskStatus(v) {
22094
22706
  return !!v && TEAM_STATUSES.has(v);
22095
22707
  }
@@ -22104,7 +22716,7 @@ function reconcile(kind, was, token, checked) {
22104
22716
  const done = kind === "secret" ? "rotated" : "done";
22105
22717
  if (token && token !== was) return token;
22106
22718
  if (kind === "team") {
22107
- if (checked && was !== "done") return "done";
22719
+ if (checked && was === "todo") return "in_progress";
22108
22720
  if (!checked && was === "done") return "todo";
22109
22721
  return token ?? was;
22110
22722
  }
@@ -22139,22 +22751,22 @@ function parseTaskFile(content) {
22139
22751
  return out;
22140
22752
  }
22141
22753
  function ledgerPath2() {
22142
- return join47(homedir18(), ".chat-recall", "project-tasks-ledger.json");
22754
+ return join50(homedir19(), ".chat-recall", "project-tasks-ledger.json");
22143
22755
  }
22144
22756
  function readLedger() {
22145
22757
  try {
22146
- return JSON.parse(readFileSync40(ledgerPath2(), "utf8"));
22758
+ return JSON.parse(readFileSync41(ledgerPath2(), "utf8"));
22147
22759
  } catch {
22148
22760
  return {};
22149
22761
  }
22150
22762
  }
22151
22763
  function writeLedger(l) {
22152
22764
  try {
22153
- mkdirSync12(dirname18(ledgerPath2()), { recursive: true });
22765
+ mkdirSync14(dirname20(ledgerPath2()), { recursive: true });
22154
22766
  } catch {
22155
22767
  }
22156
22768
  try {
22157
- writeFileSync13(ledgerPath2(), JSON.stringify(l));
22769
+ writeFileSync15(ledgerPath2(), JSON.stringify(l));
22158
22770
  } catch {
22159
22771
  }
22160
22772
  }
@@ -22172,18 +22784,18 @@ async function pushProjectTaskStatuses(base2, authHeaders, opts = {}) {
22172
22784
  continue;
22173
22785
  }
22174
22786
  for (const project of projects) {
22175
- const file = join47(project, fk.filename);
22176
- if (!existsSync43(file)) continue;
22787
+ const file = join50(project, fk.filename);
22788
+ if (!existsSync45(file)) continue;
22177
22789
  let mtime;
22178
22790
  try {
22179
- mtime = statSync38(file).mtimeMs;
22791
+ mtime = statSync39(file).mtimeMs;
22180
22792
  } catch {
22181
22793
  continue;
22182
22794
  }
22183
22795
  if (ledger[file] === mtime) continue;
22184
22796
  let changed;
22185
22797
  try {
22186
- changed = parseTaskFile(readFileSync40(file, "utf8")).filter((t) => t.kind === fk.kind);
22798
+ changed = parseTaskFile(readFileSync41(file, "utf8")).filter((t) => t.kind === fk.kind);
22187
22799
  } catch {
22188
22800
  continue;
22189
22801
  }
@@ -22216,7 +22828,8 @@ function renderTeamTasksMd(tasks2) {
22216
22828
  "# Team tasks",
22217
22829
  "",
22218
22830
  "<!-- Managed by chat-recall. Tick a box or edit the `status:` token, then run",
22219
- " `chat-recall tasks push`. Statuses: todo \xB7 in_progress \xB7 blocked \xB7 done. -->",
22831
+ " `chat-recall tasks push`. Statuses: todo \xB7 in_progress \xB7 done \xB7 rejected.",
22832
+ " A tick claims a task; only the session that does the work can close it. -->",
22220
22833
  ""
22221
22834
  ];
22222
22835
  if (tasks2.length === 0) lines.push("_No tasks assigned to you here._");
@@ -22236,7 +22849,7 @@ var init_project_tasks = __esm({
22236
22849
  init_http();
22237
22850
  init_secret_task_status();
22238
22851
  init_code_task_status();
22239
- TEAM_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress", "blocked", "done"]);
22852
+ TEAM_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress", "done", "rejected"]);
22240
22853
  ANCHOR_RE = /<!--\s*cr-(secret|task)\b(?:\s+kind=([a-z]+))?\s+id=([A-Za-z0-9_-]+)(?:\s+was=([a-z_-]+))?\s*-->/;
22241
22854
  CHECKBOX_RE = /^\s*-\s*\[([ xX])\]/;
22242
22855
  STATUS_TOKEN_RE = /status:\s*`?([a-z_-]+)`?/i;
@@ -22252,18 +22865,18 @@ var intent_drain_exports = {};
22252
22865
  __export(intent_drain_exports, {
22253
22866
  drainSyncIntents: () => drainSyncIntents
22254
22867
  });
22255
- import { existsSync as existsSync44, readFileSync as readFileSync41, writeFileSync as writeFileSync14, appendFileSync, mkdirSync as mkdirSync13 } from "node:fs";
22256
- import { join as join48 } from "node:path";
22257
- import { homedir as homedir19 } from "node:os";
22868
+ import { existsSync as existsSync46, readFileSync as readFileSync42, writeFileSync as writeFileSync16, appendFileSync, mkdirSync as mkdirSync15 } from "node:fs";
22869
+ import { join as join51 } from "node:path";
22870
+ import { homedir as homedir20 } from "node:os";
22258
22871
  import { fileURLToPath as fileURLToPath2 } from "node:url";
22259
- import { dirname as dirname19 } from "node:path";
22872
+ import { dirname as dirname21 } from "node:path";
22260
22873
  function applyCodeRecommendation(intent) {
22261
22874
  try {
22262
22875
  const meta = JSON.parse(intent.name || "{}");
22263
22876
  if (intent.artifact_type === "write_tasks_file") {
22264
22877
  if (!meta.rootPath || !meta.content) return { status: "error", result: JSON.stringify({ error: "missing rootPath or content" }) };
22265
- const file = join48(meta.rootPath, meta.filename || "CODE_TASKS.md");
22266
- writeFileSync14(file, meta.content);
22878
+ const file = join51(meta.rootPath, meta.filename || "CODE_TASKS.md");
22879
+ writeFileSync16(file, meta.content);
22267
22880
  return { status: "done", result: JSON.stringify({ wrote: file }) };
22268
22881
  }
22269
22882
  if (intent.artifact_type === "append_claude_md") {
@@ -22272,24 +22885,24 @@ function applyCodeRecommendation(intent) {
22272
22885
  if (!text) return { status: "error", result: JSON.stringify({ error: "missing text" }) };
22273
22886
  let file;
22274
22887
  if (isGlobal) {
22275
- const dir = join48(homedir19(), ".claude");
22888
+ const dir = join51(homedir20(), ".claude");
22276
22889
  try {
22277
- mkdirSync13(dir, { recursive: true });
22890
+ mkdirSync15(dir, { recursive: true });
22278
22891
  } catch {
22279
22892
  }
22280
- file = join48(dir, "CLAUDE.md");
22893
+ file = join51(dir, "CLAUDE.md");
22281
22894
  } else {
22282
22895
  if (!meta.rootPath) return { status: "error", result: JSON.stringify({ error: "missing rootPath" }) };
22283
- file = join48(meta.rootPath, "CLAUDE.md");
22896
+ file = join51(meta.rootPath, "CLAUDE.md");
22284
22897
  }
22285
- const existing = existsSync44(file) ? readFileSync41(file, "utf8") : "";
22898
+ const existing = existsSync46(file) ? readFileSync42(file, "utf8") : "";
22286
22899
  if (existing.includes(text)) return { status: "done", result: JSON.stringify({ skipped: "rule already present", file }) };
22287
22900
  const block = `${existing && !existing.endsWith("\n") ? "\n" : ""}
22288
22901
  ## Rule (added by chat-recall recommendation)
22289
22902
  ${text}
22290
22903
  `;
22291
22904
  if (existing) appendFileSync(file, block);
22292
- else writeFileSync14(file, `# ${meta.rootPath?.split("/").pop() || "Global"} instructions
22905
+ else writeFileSync16(file, `# ${meta.rootPath?.split("/").pop() || "Global"} instructions
22293
22906
  ${block}`);
22294
22907
  return { status: "done", result: JSON.stringify({ appended: file }) };
22295
22908
  }
@@ -22388,11 +23001,11 @@ var init_intent_drain = __esm({
22388
23001
  init_project_tasks();
22389
23002
  ownVersion2 = "0.0.0";
22390
23003
  try {
22391
- const dir = dirname19(fileURLToPath2(import.meta.url));
22392
- ownVersion2 = JSON.parse(readFileSync41(join48(dir, "../package.json"), "utf-8")).version || ownVersion2;
23004
+ const dir = dirname21(fileURLToPath2(import.meta.url));
23005
+ ownVersion2 = JSON.parse(readFileSync42(join51(dir, "../package.json"), "utf-8")).version || ownVersion2;
22393
23006
  } catch {
22394
23007
  try {
22395
- if (true) ownVersion2 = "0.5.5";
23008
+ if (true) ownVersion2 = "0.5.6";
22396
23009
  } catch {
22397
23010
  }
22398
23011
  }
@@ -22593,43 +23206,43 @@ var team_merge_exports = {};
22593
23206
  __export(team_merge_exports, {
22594
23207
  mergePullResult: () => mergePullResult
22595
23208
  });
22596
- import { existsSync as existsSync45, mkdirSync as mkdirSync14, readFileSync as readFileSync42, rmSync as rmSync7, writeFileSync as writeFileSync15, statSync as statSync39 } from "fs";
22597
- import { dirname as dirname20, join as join49 } from "path";
22598
- import { createHash as createHash16 } from "crypto";
23209
+ import { existsSync as existsSync47, mkdirSync as mkdirSync16, readFileSync as readFileSync43, rmSync as rmSync7, writeFileSync as writeFileSync17, statSync as statSync40 } from "fs";
23210
+ import { dirname as dirname22, join as join52 } from "path";
23211
+ import { createHash as createHash17 } from "crypto";
22599
23212
  function installPathFor(art, tool) {
22600
23213
  switch (art.type) {
22601
23214
  case "skill":
22602
23215
  switch (tool) {
22603
23216
  case "claude":
22604
- return join49(claudeBackend.skillsDir(), art.name, "SKILL.md");
23217
+ return join52(claudeBackend.skillsDir(), art.name, "SKILL.md");
22605
23218
  case "agy":
22606
23219
  case "gemini":
22607
- return join49(geminiBackend.skillsDir(), art.name, "SKILL.md");
23220
+ return join52(geminiBackend.skillsDir(), art.name, "SKILL.md");
22608
23221
  case "opencode":
22609
- return join49(opencodeBackend.skillsDir(), art.name, "SKILL.md");
23222
+ return join52(opencodeBackend.skillsDir(), art.name, "SKILL.md");
22610
23223
  // User skills go to ~/.codex/skills (NOT .system, which is OpenAI's bundle).
22611
23224
  case "codex":
22612
- return join49(codexBackend.skillsDir(), art.name, "SKILL.md");
23225
+ return join52(codexBackend.skillsDir(), art.name, "SKILL.md");
22613
23226
  }
22614
23227
  break;
22615
23228
  case "agent":
22616
23229
  if (tool === "claude")
22617
- return join49(claudeBackend.agentsDir(), `${art.name}.md`);
23230
+ return join52(claudeBackend.agentsDir(), `${art.name}.md`);
22618
23231
  return null;
22619
23232
  case "command":
22620
23233
  if (tool === "claude")
22621
- return join49(claudeBackend.commandsDir(), `${art.name}.md`);
23234
+ return join52(claudeBackend.commandsDir(), `${art.name}.md`);
22622
23235
  return null;
22623
23236
  case "hook":
22624
23237
  if (tool === "claude")
22625
- return join49(claudeBackend.homeDir(), "chat-recall-hooks", `${art.name}.json`);
23238
+ return join52(claudeBackend.homeDir(), "chat-recall-hooks", `${art.name}.json`);
22626
23239
  return null;
22627
23240
  case "plan":
22628
23241
  switch (tool) {
22629
23242
  case "claude":
22630
- return join49(claudeBackend.plansDir(), `${art.name}.md`);
23243
+ return join52(claudeBackend.plansDir(), `${art.name}.md`);
22631
23244
  case "opencode":
22632
- return join49(opencodeBackend.plansDir(), `${art.name}.md`);
23245
+ return join52(opencodeBackend.plansDir(), `${art.name}.md`);
22633
23246
  case "agy":
22634
23247
  case "gemini":
22635
23248
  return null;
@@ -22639,11 +23252,11 @@ function installPathFor(art, tool) {
22639
23252
  }
22640
23253
  break;
22641
23254
  case "instructions":
22642
- return join49(getDataDir(), "team-instructions", tool, `${art.name}.md`);
23255
+ return join52(getDataDir(), "team-instructions", tool, `${art.name}.md`);
22643
23256
  case "mcp":
22644
- return join49(getDataDir(), "team-mcps", `${art.name}.json`);
23257
+ return join52(getDataDir(), "team-mcps", `${art.name}.json`);
22645
23258
  case "plugin":
22646
- return join49(getDataDir(), "team-plugins", tool, art.name, "manifest.json");
23259
+ return join52(getDataDir(), "team-plugins", tool, art.name, "manifest.json");
22647
23260
  }
22648
23261
  return null;
22649
23262
  }
@@ -22669,20 +23282,20 @@ async function mergePullResult(opts) {
22669
23282
  continue;
22670
23283
  }
22671
23284
  const body = Buffer.from(a.bodyB64, "base64");
22672
- const sha = createHash16("sha256").update(body).digest("hex");
23285
+ const sha = createHash17("sha256").update(body).digest("hex");
22673
23286
  for (const tool of targets) {
22674
23287
  const path = installPathFor(a, tool);
22675
23288
  if (!path)
22676
23289
  continue;
22677
23290
  try {
22678
23291
  const existing = readFileIfExists(path);
22679
- if (existing && createHash16("sha256").update(existing).digest("hex") === sha) {
23292
+ if (existing && createHash17("sha256").update(existing).digest("hex") === sha) {
22680
23293
  report.skipped.push({ artifactId: a.id, path, reason: "unchanged" });
22681
23294
  pending.push({ artifactId: a.id, tool, artifactType: a.type, artifactName: a.name, sha256: sha, installedAt: Date.now() });
22682
23295
  continue;
22683
23296
  }
22684
- mkdirSync14(dirname20(path), { recursive: true });
22685
- writeFileSync15(path, body);
23297
+ mkdirSync16(dirname22(path), { recursive: true });
23298
+ writeFileSync17(path, body);
22686
23299
  pending.push({ artifactId: a.id, tool, artifactType: a.type, artifactName: a.name, sha256: sha, installedAt: Date.now() });
22687
23300
  report.written.push({ artifactId: a.id, path });
22688
23301
  } catch (err) {
@@ -22703,7 +23316,7 @@ async function mergePullResult(opts) {
22703
23316
  if (!path)
22704
23317
  continue;
22705
23318
  try {
22706
- if (existsSync45(path))
23319
+ if (existsSync47(path))
22707
23320
  rmSync7(path, { force: true });
22708
23321
  await teamInstallsForget(id, row.tool);
22709
23322
  report.removed.push({ artifactId: id, path });
@@ -22721,12 +23334,12 @@ async function mergePullResult(opts) {
22721
23334
  }
22722
23335
  function readFileIfExists(path) {
22723
23336
  try {
22724
- if (!existsSync45(path))
23337
+ if (!existsSync47(path))
22725
23338
  return null;
22726
- const s = statSync39(path);
23339
+ const s = statSync40(path);
22727
23340
  if (!s.isFile())
22728
23341
  return null;
22729
- return readFileSync42(path);
23342
+ return readFileSync43(path);
22730
23343
  } catch {
22731
23344
  return null;
22732
23345
  }
@@ -22805,8 +23418,8 @@ var init_vault_crypto = __esm({
22805
23418
  });
22806
23419
 
22807
23420
  // ../engine/dist/core/vault-source.js
22808
- import { existsSync as existsSync46, readdirSync as readdirSync32, statSync as statSync40 } from "fs";
22809
- import { extname, join as join50 } from "path";
23421
+ import { existsSync as existsSync48, readdirSync as readdirSync32, statSync as statSync41 } from "fs";
23422
+ import { extname, join as join53 } from "path";
22810
23423
  function* walkVaultSources() {
22811
23424
  const t = loadSettings().team.vault;
22812
23425
  const include2 = new Set(t.syncTools);
@@ -22821,7 +23434,7 @@ function* walkVaultSources() {
22821
23434
  }
22822
23435
  function safeStat(path) {
22823
23436
  try {
22824
- const s = statSync40(path);
23437
+ const s = statSync41(path);
22825
23438
  if (!s.isFile())
22826
23439
  return null;
22827
23440
  return { mtimeMs: s.mtimeMs, size: s.size };
@@ -22831,7 +23444,7 @@ function safeStat(path) {
22831
23444
  }
22832
23445
  function* walkClaude() {
22833
23446
  const root = claudeBackend.projectsDir();
22834
- if (!existsSync46(root))
23447
+ if (!existsSync48(root))
22835
23448
  return;
22836
23449
  let projectDirs = [];
22837
23450
  try {
@@ -22842,8 +23455,8 @@ function* walkClaude() {
22842
23455
  for (const proj of projectDirs) {
22843
23456
  if (proj.startsWith("."))
22844
23457
  continue;
22845
- const projectPath = "/" + proj.replace(/^-/, "").replace(/-/g, "/");
22846
- const projDir = join50(root, proj);
23458
+ const projectPath = decodeProjectDirName(proj);
23459
+ const projDir = join53(root, proj);
22847
23460
  let entries = [];
22848
23461
  try {
22849
23462
  entries = readdirSync32(projDir);
@@ -22853,7 +23466,7 @@ function* walkClaude() {
22853
23466
  for (const f of entries) {
22854
23467
  if (extname(f) !== ".jsonl")
22855
23468
  continue;
22856
- const path = join50(projDir, f);
23469
+ const path = join53(projDir, f);
22857
23470
  const s = safeStat(path);
22858
23471
  if (!s)
22859
23472
  continue;
@@ -22864,7 +23477,7 @@ function* walkClaude() {
22864
23477
  }
22865
23478
  function* walkGemini() {
22866
23479
  const tmp = geminiBackend.tmpDir();
22867
- if (!existsSync46(tmp))
23480
+ if (!existsSync48(tmp))
22868
23481
  return;
22869
23482
  let hashDirs = [];
22870
23483
  try {
@@ -22873,8 +23486,8 @@ function* walkGemini() {
22873
23486
  return;
22874
23487
  }
22875
23488
  for (const hash of hashDirs) {
22876
- const chatsDir = join50(tmp, hash, "chats");
22877
- if (!existsSync46(chatsDir))
23489
+ const chatsDir = join53(tmp, hash, "chats");
23490
+ if (!existsSync48(chatsDir))
22878
23491
  continue;
22879
23492
  let entries = [];
22880
23493
  try {
@@ -22886,7 +23499,7 @@ function* walkGemini() {
22886
23499
  const ext = extname(f);
22887
23500
  if (ext !== ".json" && ext !== ".jsonl")
22888
23501
  continue;
22889
- const path = join50(chatsDir, f);
23502
+ const path = join53(chatsDir, f);
22890
23503
  const s = safeStat(path);
22891
23504
  if (!s)
22892
23505
  continue;
@@ -22896,8 +23509,8 @@ function* walkGemini() {
22896
23509
  }
22897
23510
  }
22898
23511
  function* walkCodex() {
22899
- const sessionsRoot = join50(codexBackend.homeDir(), "sessions");
22900
- if (!existsSync46(sessionsRoot))
23512
+ const sessionsRoot = join53(codexBackend.homeDir(), "sessions");
23513
+ if (!existsSync48(sessionsRoot))
22901
23514
  return;
22902
23515
  const stack = [sessionsRoot];
22903
23516
  while (stack.length) {
@@ -22909,7 +23522,7 @@ function* walkCodex() {
22909
23522
  continue;
22910
23523
  }
22911
23524
  for (const e of entries) {
22912
- const path = join50(dir, e.name);
23525
+ const path = join53(dir, e.name);
22913
23526
  if (e.isDirectory()) {
22914
23527
  stack.push(path);
22915
23528
  continue;
@@ -22925,8 +23538,8 @@ function* walkCodex() {
22925
23538
  }
22926
23539
  }
22927
23540
  function* walkAgy() {
22928
- const brainDir = join50(agyBackend.homeDir(), "brain");
22929
- if (!existsSync46(brainDir))
23541
+ const brainDir = join53(agyBackend.homeDir(), "brain");
23542
+ if (!existsSync48(brainDir))
22930
23543
  return;
22931
23544
  let sessionDirs = [];
22932
23545
  try {
@@ -22935,18 +23548,18 @@ function* walkAgy() {
22935
23548
  return;
22936
23549
  }
22937
23550
  for (const rawId of sessionDirs) {
22938
- const sessionPath = join50(brainDir, rawId);
23551
+ const sessionPath = join53(brainDir, rawId);
22939
23552
  try {
22940
- if (!statSync40(sessionPath).isDirectory())
23553
+ if (!statSync41(sessionPath).isDirectory())
22941
23554
  continue;
22942
23555
  } catch {
22943
23556
  continue;
22944
23557
  }
22945
- let filePath = join50(sessionPath, ".system_generated", "logs", "transcript.jsonl");
22946
- if (!existsSync46(filePath)) {
22947
- filePath = join50(sessionPath, ".system_generated", "logs", "transcript_full.jsonl");
23558
+ let filePath = join53(sessionPath, ".system_generated", "logs", "transcript.jsonl");
23559
+ if (!existsSync48(filePath)) {
23560
+ filePath = join53(sessionPath, ".system_generated", "logs", "transcript_full.jsonl");
22948
23561
  }
22949
- if (!existsSync46(filePath))
23562
+ if (!existsSync48(filePath))
22950
23563
  continue;
22951
23564
  const s = safeStat(filePath);
22952
23565
  if (!s)
@@ -22963,6 +23576,7 @@ var init_vault_source = __esm({
22963
23576
  init_codex2();
22964
23577
  init_agy();
22965
23578
  init_settings();
23579
+ init_project_dir_name();
22966
23580
  }
22967
23581
  });
22968
23582
 
@@ -22979,8 +23593,8 @@ __export(vault_client_exports, {
22979
23593
  vaultStatus: () => vaultStatus,
22980
23594
  vaultSync: () => vaultSync
22981
23595
  });
22982
- import { mkdirSync as mkdirSync15, readFileSync as readFileSync43, writeFileSync as writeFileSync16, existsSync as existsSync47 } from "fs";
22983
- import { dirname as dirname21, join as join51 } from "path";
23596
+ import { mkdirSync as mkdirSync17, readFileSync as readFileSync44, writeFileSync as writeFileSync18, existsSync as existsSync49 } from "fs";
23597
+ import { dirname as dirname23, join as join54 } from "path";
22984
23598
  function context2() {
22985
23599
  const s = loadSettings().team;
22986
23600
  if (!s.enabled)
@@ -23119,7 +23733,7 @@ async function vaultSync(passphrase) {
23119
23733
  continue;
23120
23734
  }
23121
23735
  try {
23122
- const plaintext = readFileSync43(src.path);
23736
+ const plaintext = readFileSync44(src.path);
23123
23737
  const sourceSha = sha256Hex(plaintext);
23124
23738
  const priorSha = uploaded.get(uploadKey(src.sessionId, src.tool));
23125
23739
  if (priorSha === sourceSha) {
@@ -23171,7 +23785,7 @@ async function vaultSync(passphrase) {
23171
23785
  }
23172
23786
  function importedPathFor(tool, sessionId) {
23173
23787
  const base2 = sessionId.replace(/^gemini_/, "").replace(/^codex_/, "").replace(/^opencode_/, "");
23174
- return join51(IMPORTED_ROOT(), tool, `${base2}.jsonl`);
23788
+ return join54(IMPORTED_ROOT(), tool, `${base2}.jsonl`);
23175
23789
  }
23176
23790
  async function vaultRestore(passphrase, opts = {}) {
23177
23791
  const ctx = context2();
@@ -23192,7 +23806,7 @@ async function vaultRestore(passphrase, opts = {}) {
23192
23806
  }
23193
23807
  try {
23194
23808
  const path = importedPathFor(blob.tool, blob.sessionId);
23195
- if (existsSync47(path)) {
23809
+ if (existsSync49(path)) {
23196
23810
  const localStat = await import("fs").then((m) => m.statSync(path));
23197
23811
  if (Math.abs(localStat.mtimeMs - blob.mtimeSourceMs) < 1) {
23198
23812
  report.skipped.push({ sessionId: blob.sessionId, tool: blob.tool, reason: "unchanged" });
@@ -23208,8 +23822,8 @@ async function vaultRestore(passphrase, opts = {}) {
23208
23822
  throw new Error("cipher integrity check failed");
23209
23823
  }
23210
23824
  const plaintext = decryptBlob(key, cipher);
23211
- mkdirSync15(dirname21(path), { recursive: true });
23212
- writeFileSync16(path, Buffer.from(plaintext));
23825
+ mkdirSync17(dirname23(path), { recursive: true });
23826
+ writeFileSync18(path, Buffer.from(plaintext));
23213
23827
  const { utimesSync } = await import("fs");
23214
23828
  const t = blob.mtimeSourceMs / 1e3;
23215
23829
  utimesSync(path, t, t);
@@ -23260,7 +23874,7 @@ var init_vault_client = __esm({
23260
23874
  uploadKey = (sessionId, tool) => `${sessionId}\0${tool}`;
23261
23875
  VaultPassphraseMismatchError = class extends Error {
23262
23876
  };
23263
- IMPORTED_ROOT = () => join51(getDataDir(), "imported");
23877
+ IMPORTED_ROOT = () => join54(getDataDir(), "imported");
23264
23878
  }
23265
23879
  });
23266
23880
 
@@ -23414,15 +24028,15 @@ __export(service_installer_exports, {
23414
24028
  uninstallService: () => uninstallService
23415
24029
  });
23416
24030
  import { execSync as execSync5 } from "child_process";
23417
- import { existsSync as existsSync48, mkdirSync as mkdirSync16, rmSync as rmSync8, writeFileSync as writeFileSync17 } from "fs";
23418
- import { homedir as homedir20 } from "os";
23419
- import { join as join52 } from "path";
24031
+ import { existsSync as existsSync50, mkdirSync as mkdirSync18, rmSync as rmSync8, writeFileSync as writeFileSync19 } from "fs";
24032
+ import { homedir as homedir21 } from "os";
24033
+ import { join as join55 } from "path";
23420
24034
  import { fileURLToPath as fileURLToPath3 } from "url";
23421
24035
  function resolveDaemonPaths() {
23422
24036
  const watchJs = fileURLToPath3(new URL("./watch.js", import.meta.url));
23423
24037
  const node = process.execPath;
23424
- const dataDir = join52(homedir20(), ".chat-recall");
23425
- const logFile = join52(dataDir, "watch.log");
24038
+ const dataDir = join55(homedir21(), ".chat-recall");
24039
+ const logFile = join55(dataDir, "watch.log");
23426
24040
  return { watchJs, node, logFile, dataDir };
23427
24041
  }
23428
24042
  function tryCmd(cmd) {
@@ -23447,7 +24061,7 @@ function isServiceRunning() {
23447
24061
  }
23448
24062
  function installService() {
23449
24063
  const { watchJs, node, logFile, dataDir } = resolveDaemonPaths();
23450
- mkdirSync16(dataDir, { recursive: true });
24064
+ mkdirSync18(dataDir, { recursive: true });
23451
24065
  if (process.platform === "linux") {
23452
24066
  return installSystemd(watchJs, node, logFile);
23453
24067
  }
@@ -23463,11 +24077,11 @@ function installService() {
23463
24077
  );
23464
24078
  }
23465
24079
  function installSystemd(watchJs, node, logFile) {
23466
- const unitDir = join52(homedir20(), ".config", "systemd", "user");
23467
- const unitPath = join52(unitDir, "chat-recall-watch.service");
24080
+ const unitDir = join55(homedir21(), ".config", "systemd", "user");
24081
+ const unitPath = join55(unitDir, "chat-recall-watch.service");
23468
24082
  const unit = renderSystemdUnit(watchJs, node, logFile);
23469
- mkdirSync16(unitDir, { recursive: true });
23470
- writeFileSync17(unitPath, unit);
24083
+ mkdirSync18(unitDir, { recursive: true });
24084
+ writeFileSync19(unitPath, unit);
23471
24085
  try {
23472
24086
  execSync5("systemctl --user daemon-reload && systemctl --user enable chat-recall-watch.service && systemctl --user restart chat-recall-watch.service", { stdio: "inherit" });
23473
24087
  try {
@@ -23495,10 +24109,14 @@ function renderSystemdUnit(watchJs, node, logFile) {
23495
24109
  "Restart=on-failure",
23496
24110
  "RestartSec=10",
23497
24111
  "Nice=10",
23498
- // Keep the service's own stdout/stderr out of the journal noise but
23499
- // available for `chat-recall service status` without paging journald.
23500
- `StandardOutput=append:${logFile}`,
23501
- `StandardError=append:${logFile}`,
24112
+ // JOURNAL, not append:. The file version had no rotation and nothing to
24113
+ // give it any: systemd holds the fd open, so the daemon cannot rename or
24114
+ // truncate its own log without leaving systemd writing into a hole. It
24115
+ // reached 45 MB on this developer's machine and would have grown forever.
24116
+ // journald already rotates and applies disk limits. Read it with
24117
+ // journalctl --user -u chat-recall-watch -f
24118
+ "StandardOutput=journal",
24119
+ "StandardError=journal",
23502
24120
  "",
23503
24121
  "[Install]",
23504
24122
  "WantedBy=default.target",
@@ -23507,11 +24125,11 @@ function renderSystemdUnit(watchJs, node, logFile) {
23507
24125
  }
23508
24126
  function installLaunchd(watchJs, node, logFile) {
23509
24127
  const label = "com.chat-recall.watch";
23510
- const plistDir = join52(homedir20(), "Library", "LaunchAgents");
23511
- const plistPath = join52(plistDir, `${label}.plist`);
24128
+ const plistDir = join55(homedir21(), "Library", "LaunchAgents");
24129
+ const plistPath = join55(plistDir, `${label}.plist`);
23512
24130
  const plist = renderLaunchdPlist(watchJs, node, logFile, label);
23513
- mkdirSync16(plistDir, { recursive: true });
23514
- writeFileSync17(plistPath, plist);
24131
+ mkdirSync18(plistDir, { recursive: true });
24132
+ writeFileSync19(plistPath, plist);
23515
24133
  try {
23516
24134
  try {
23517
24135
  execSync5(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
@@ -23540,7 +24158,7 @@ function renderLaunchdPlist(watchJs, node, logFile, label = "com.chat-recall.wat
23540
24158
  }
23541
24159
  function installWindowsTask(watchJs, node, logFile) {
23542
24160
  const taskName = "chat-recall-watch";
23543
- const tr = `\\"${node}\\" --max-old-space-size=1536 \\"${watchJs}\\"`;
24161
+ const tr = `cmd /c \\"\\"${node}\\" --max-old-space-size=1536 \\"${watchJs}\\" >> \\"${logFile}\\" 2>&1\\"`;
23544
24162
  try {
23545
24163
  execSync5(`schtasks /create /tn "${taskName}" /tr "${tr}" /sc onlogon /rl LIMITED /f`, { stdio: "inherit" });
23546
24164
  try {
@@ -23552,8 +24170,8 @@ function installWindowsTask(watchJs, node, logFile) {
23552
24170
  }
23553
24171
  return { definitionPath: null, logFile };
23554
24172
  }
23555
- function renderWindowsTaskCommand(watchJs, node, taskName = "chat-recall-watch") {
23556
- const tr = `\\"${node}\\" --max-old-space-size=1536 \\"${watchJs}\\"`;
24173
+ function renderWindowsTaskCommand(watchJs, node, taskName = "chat-recall-watch", logFile) {
24174
+ const tr = logFile ? `cmd /c \\"\\"${node}\\" --max-old-space-size=1536 \\"${watchJs}\\" >> \\"${logFile}\\" 2>&1\\"` : `\\"${node}\\" --max-old-space-size=1536 \\"${watchJs}\\"`;
23557
24175
  return `schtasks /create /tn "${taskName}" /tr "${tr}" /sc onlogon /rl LIMITED /f`;
23558
24176
  }
23559
24177
  function uninstallService() {
@@ -23564,8 +24182,8 @@ function uninstallService() {
23564
24182
  removed = true;
23565
24183
  } catch {
23566
24184
  }
23567
- const unitPath = join52(homedir20(), ".config", "systemd", "user", "chat-recall-watch.service");
23568
- if (existsSync48(unitPath)) {
24185
+ const unitPath = join55(homedir21(), ".config", "systemd", "user", "chat-recall-watch.service");
24186
+ if (existsSync50(unitPath)) {
23569
24187
  rmSync8(unitPath, { force: true });
23570
24188
  removed = true;
23571
24189
  }
@@ -23576,13 +24194,13 @@ function uninstallService() {
23576
24194
  return removed;
23577
24195
  }
23578
24196
  if (process.platform === "darwin") {
23579
- const plistPath = join52(homedir20(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
24197
+ const plistPath = join55(homedir21(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
23580
24198
  try {
23581
24199
  execSync5(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
23582
24200
  } catch {
23583
24201
  }
23584
24202
  let removed = false;
23585
- if (existsSync48(plistPath)) {
24203
+ if (existsSync50(plistPath)) {
23586
24204
  rmSync8(plistPath, { force: true });
23587
24205
  removed = true;
23588
24206
  }
@@ -23627,7 +24245,7 @@ __export(device_auth_exports, {
23627
24245
  betterAuthDeviceLogin: () => betterAuthDeviceLogin,
23628
24246
  deviceLogin: () => deviceLogin
23629
24247
  });
23630
- import { createHash as createHash17, randomBytes } from "node:crypto";
24248
+ import { createHash as createHash18, randomBytes } from "node:crypto";
23631
24249
  async function deviceLogin(opts, onPrompt) {
23632
24250
  const issuer = (opts.issuer || DEFAULT_ISSUER).replace(/\/+$/, "");
23633
24251
  if (!issuer) {
@@ -23638,7 +24256,7 @@ async function deviceLogin(opts, onPrompt) {
23638
24256
  const clientId = opts.clientId || DEFAULT_CLIENT_ID;
23639
24257
  const scope = opts.scope || "openid email profile";
23640
24258
  const codeVerifier = b64url(randomBytes(32));
23641
- const codeChallenge = b64url(createHash17("sha256").update(codeVerifier).digest());
24259
+ const codeChallenge = b64url(createHash18("sha256").update(codeVerifier).digest());
23642
24260
  const startRes = await fetch(`${issuer}/protocol/openid-connect/auth/device`, {
23643
24261
  method: "POST",
23644
24262
  headers: { "content-type": "application/x-www-form-urlencoded" },
@@ -23760,10 +24378,9 @@ init_project_resolver();
23760
24378
  import { config } from "dotenv";
23761
24379
  import { Command } from "commander";
23762
24380
  import chalk from "chalk";
23763
- import { existsSync as existsSync49, readFileSync as readFileSync44, writeFileSync as writeFileSync18, realpathSync as realpathSync5 } from "fs";
23764
- import { homedir as homedir21 } from "os";
23765
- import { join as join53, resolve as resolve2, dirname as dirname22, basename as basename27 } from "path";
23766
- import { execSync as execSync6 } from "child_process";
24381
+ import { existsSync as existsSync51, readFileSync as readFileSync45, writeFileSync as writeFileSync20, realpathSync as realpathSync5 } from "fs";
24382
+ import { homedir as homedir22 } from "os";
24383
+ import { join as join56, resolve as resolve2, dirname as dirname24, basename as basename27 } from "path";
23767
24384
 
23768
24385
  // ../engine/dist/core/score-tier.js
23769
24386
  var STRONG_RATIO = 0.85;
@@ -23790,6 +24407,68 @@ function tierAll(results) {
23790
24407
  // src/cli.ts
23791
24408
  init_sync_client();
23792
24409
  init_update_notice();
24410
+ init_which();
24411
+
24412
+ // ../engine/dist/core/collector-health.js
24413
+ init_paths();
24414
+ import { readFileSync as readFileSync34, writeFileSync as writeFileSync10, mkdirSync as mkdirSync9 } from "node:fs";
24415
+ import { join as join43, dirname as dirname17 } from "node:path";
24416
+ var healthPath = () => join43(getDataDir(), "collector-health.json");
24417
+ function collectorHealthPath() {
24418
+ return healthPath();
24419
+ }
24420
+ function readCollectorHealth() {
24421
+ try {
24422
+ const h = JSON.parse(readFileSync34(healthPath(), "utf-8"));
24423
+ return h && h.v === 1 && typeof h.updatedAt === "number" ? h : null;
24424
+ } catch {
24425
+ return null;
24426
+ }
24427
+ }
24428
+ var STALE_AFTER_MS = 30 * 6e4;
24429
+ var CRASHLOOP_RESTARTS = 3;
24430
+ var ago = (ms) => {
24431
+ const m = Math.floor(ms / 6e4);
24432
+ if (m < 60)
24433
+ return `${m}m`;
24434
+ const h = Math.floor(m / 60);
24435
+ return h < 48 ? `${h}h` : `${Math.floor(h / 24)}d`;
24436
+ };
24437
+ function judgeHealth(h, now = Date.now()) {
24438
+ if (!h)
24439
+ return { ok: true, summary: null, reasons: [] };
24440
+ const reasons = [];
24441
+ const silent = now - h.updatedAt;
24442
+ if (silent > STALE_AFTER_MS) {
24443
+ reasons.push(`the collector has not reported for ${ago(silent)} \u2014 it may not be running`);
24444
+ }
24445
+ if (h.restartsLastHour >= CRASHLOOP_RESTARTS) {
24446
+ reasons.push(`it restarted ${h.restartsLastHour} times in the last hour`);
24447
+ }
24448
+ const targets = Object.entries(h.targets ?? {});
24449
+ const stale = targets.filter(([, t]) => t.lastOkAt === null || now - t.lastOkAt > STALE_AFTER_MS);
24450
+ if (targets.length > 0 && stale.length === targets.length) {
24451
+ const ranked = stale.map(([url, t]) => ({ url, t })).sort((a, b) => {
24452
+ const err = Number(!!b.t.lastError) - Number(!!a.t.lastError);
24453
+ if (err !== 0)
24454
+ return err;
24455
+ return (a.t.lastOkAt ?? 0) - (b.t.lastOkAt ?? 0);
24456
+ });
24457
+ const worst = ranked[0];
24458
+ const when = worst.t.lastOkAt ? `${ago(now - worst.t.lastOkAt)} ago` : "never";
24459
+ const why = worst.t.lastError ? ` (${worst.t.lastError})` : "";
24460
+ reasons.push(`nothing has synced since ${when}${why}`);
24461
+ }
24462
+ if (reasons.length === 0)
24463
+ return { ok: true, summary: null, reasons: [] };
24464
+ return {
24465
+ ok: false,
24466
+ summary: `chat-recall is not syncing: ${reasons.join("; ")}.`,
24467
+ reasons
24468
+ };
24469
+ }
24470
+
24471
+ // src/cli.ts
23793
24472
  function matchLabel(tier) {
23794
24473
  switch (tier) {
23795
24474
  case "strong":
@@ -23807,8 +24486,8 @@ function claudeHookConfigFiles() {
23807
24486
  const out = [];
23808
24487
  const seen = /* @__PURE__ */ new Set();
23809
24488
  for (const home of claudeHomeDirs()) {
23810
- if (!existsSync49(join53(home, "projects"))) continue;
23811
- const file = join53(home, "hooks.json");
24489
+ if (!existsSync51(join56(home, "projects"))) continue;
24490
+ const file = join56(home, "hooks.json");
23812
24491
  let key;
23813
24492
  try {
23814
24493
  key = realpathSync5(file);
@@ -23882,7 +24561,7 @@ async function serverDelete(path, body) {
23882
24561
  return res.json();
23883
24562
  }
23884
24563
  var pkgVersion = JSON.parse(
23885
- readFileSync44(new URL("../package.json", import.meta.url), "utf-8")
24564
+ readFileSync45(new URL("../package.json", import.meta.url), "utf-8")
23886
24565
  ).version;
23887
24566
  var DEFAULT_SERVER = "https://chatrecall.dev";
23888
24567
  var DEFAULT_ALLOW = [
@@ -23964,7 +24643,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
23964
24643
  ];
23965
24644
  for (const cli of clis) {
23966
24645
  try {
23967
- execSync6(`which ${cli.cmd}`, { stdio: "pipe" });
24646
+ if (!isOnPath(cli.cmd)) throw new Error("not on PATH");
23968
24647
  cli.available = true;
23969
24648
  } catch {
23970
24649
  }
@@ -23996,37 +24675,22 @@ program.command("init").description("Set up chat-recall: connect to your server,
23996
24675
  console.log();
23997
24676
  if (!options.skipMcp) {
23998
24677
  console.log(chalk.bold("4. Configuring MCP server..."));
23999
- const mcpJsonPath = join53(homedir21(), ".mcp.json");
24000
- let mcpConfig = {};
24001
- if (existsSync49(mcpJsonPath)) {
24002
- try {
24003
- mcpConfig = JSON.parse(readFileSync44(mcpJsonPath, "utf-8"));
24004
- } catch {
24005
- mcpConfig = {};
24006
- }
24007
- }
24008
- const mcpServers = mcpConfig.mcpServers || {};
24009
- const projectRoot = join53(import.meta.dirname, "..");
24678
+ const projectRoot = join56(import.meta.dirname, "..");
24010
24679
  let mcpBinOnPath = false;
24011
- try {
24012
- execSync6("command -v chat-recall-mcp", { stdio: "ignore" });
24013
- mcpBinOnPath = true;
24014
- } catch {
24015
- }
24016
- const launch = mcpBinOnPath ? { command: "chat-recall-mcp" } : { command: "node", args: [join53(projectRoot, "dist", "mcp.js")] };
24680
+ mcpBinOnPath = isOnPath("chat-recall-mcp");
24681
+ const launch = mcpBinOnPath ? { command: "chat-recall-mcp" } : { command: "node", args: [join56(projectRoot, "dist", "mcp.js")] };
24017
24682
  const MCP_ENV = { NODE_OPTIONS: "--max-old-space-size=1024" };
24018
- const existing = mcpServers["chat-recall"];
24019
- const isCurrent = !!existing && existing.command === launch.command && JSON.stringify(existing.args ?? null) === JSON.stringify(launch.args ?? null) && existing.env?.NODE_OPTIONS === MCP_ENV.NODE_OPTIONS;
24020
- if (isCurrent) {
24021
- console.log(` MCP server: ${chalk.green("already configured")} in ${mcpJsonPath}`);
24022
- } else {
24023
- const entry = { command: launch.command, alwaysAllow: existing?.alwaysAllow ?? DEFAULT_ALLOW, env: { ...existing?.env, ...MCP_ENV } };
24024
- if (launch.args) entry.args = launch.args;
24025
- mcpServers["chat-recall"] = entry;
24026
- mcpConfig.mcpServers = mcpServers;
24027
- writeFileSync18(mcpJsonPath, JSON.stringify(mcpConfig, null, 2));
24028
- console.log(` MCP server: ${chalk.green(existing ? "repaired" : "configured")} (\u2192 ${launch.command}) in ${mcpJsonPath}`);
24683
+ const { registerMcpEverywhere: registerMcpEverywhere2 } = await Promise.resolve().then(() => (init_mcp_clients(), mcp_clients_exports));
24684
+ const detectedClients = clis.filter((c) => c.available).map((c) => c.cmd).filter((c) => c === "gemini" || c === "opencode" || c === "codex");
24685
+ const results = registerMcpEverywhere2(
24686
+ { ...launch, env: MCP_ENV, alwaysAllow: DEFAULT_ALLOW },
24687
+ { extraIds: detectedClients }
24688
+ );
24689
+ for (const r of results) {
24690
+ const word = r.state === "current" ? chalk.green("already configured") : r.state === "created" ? chalk.green("configured") : r.state === "repaired" ? chalk.green("repaired") : chalk.yellow("left alone \u2014 file does not parse");
24691
+ console.log(` ${r.label}: ${word} \u2192 ${r.path}`);
24029
24692
  }
24693
+ console.log(chalk.dim(` Launch: ${launch.command}${launch.args ? " " + launch.args.join(" ") : ""}`));
24030
24694
  } else {
24031
24695
  console.log(chalk.bold("4. Skipping MCP configuration (--skip-mcp)"));
24032
24696
  }
@@ -24047,7 +24711,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
24047
24711
  console.log(chalk.bold("5. Detected codeindex companion"));
24048
24712
  console.log(` ${chalk.green("codeindex")}: ${detected.path}`);
24049
24713
  if (!options.skipMcp) {
24050
- const mcpJsonPath = join53(homedir21(), ".mcp.json");
24714
+ const mcpJsonPath = join56(homedir22(), ".mcp.json");
24051
24715
  const reg = registerCodeindexMcp2(mcpJsonPath, detected.path);
24052
24716
  if (reg.added) console.log(` ${chalk.green("codeindex MCP server registered")} in ${mcpJsonPath}`);
24053
24717
  else console.log(` codeindex MCP server: ${chalk.green("already registered")}`);
@@ -24060,7 +24724,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
24060
24724
  const sizeMb = result.size ? `${(result.size / 1024 / 1024).toFixed(1)} MB` : "?";
24061
24725
  console.log(` ${chalk.green("codeindex installed")} (${sizeMb}) \u2192 ${result.path}`);
24062
24726
  if (!options.skipMcp) {
24063
- const mcpJsonPath = join53(homedir21(), ".mcp.json");
24727
+ const mcpJsonPath = join56(homedir22(), ".mcp.json");
24064
24728
  const reg = registerCodeindexMcp2(mcpJsonPath, CODEINDEX_BIN_PATH2);
24065
24729
  if (reg.added) console.log(` ${chalk.green("codeindex MCP server registered")} in ${mcpJsonPath}`);
24066
24730
  }
@@ -24458,8 +25122,8 @@ memory.command("wake-up").description("Generate wake-up context (high-importance
24458
25122
  try {
24459
25123
  const identityFile = getIdentityFilePath();
24460
25124
  let identity = "AI coding assistant";
24461
- if (existsSync49(identityFile)) {
24462
- identity = readFileSync44(identityFile, "utf-8").trim();
25125
+ if (existsSync51(identityFile)) {
25126
+ identity = readFileSync45(identityFile, "utf-8").trim();
24463
25127
  }
24464
25128
  const lines = [
24465
25129
  chalk.bold("# Wake-Up Context"),
@@ -24516,8 +25180,8 @@ program.command("escalate [sessionId]").description("Escalate a finished session
24516
25180
  }
24517
25181
  });
24518
25182
  program.command("doctor").description("Quick health check across login, server, credentials, hooks, MCP server, and codeindex").option("--purge-local", "Remove the legacy local index (cache.db + vector/KG files) left by pre-thin-collector versions \u2014 storage is server-side now. Requires login; keeps credentials, sync ledger, identity, and the audit log.").action(async (opts) => {
24519
- const { existsSync: existsSync50, readFileSync: readFileSync45, statSync: statSync41 } = await import("fs");
24520
- const { execSync: execSync7 } = await import("child_process");
25183
+ const { existsSync: existsSync52, readFileSync: readFileSync46, statSync: statSync42 } = await import("fs");
25184
+ const { execSync: execSync6 } = await import("child_process");
24521
25185
  const rows = [];
24522
25186
  const note = (ok, label, detail) => rows.push({ ok, label, detail });
24523
25187
  const targets = loadAllCredentials();
@@ -24541,36 +25205,50 @@ program.command("doctor").description("Quick health check across login, server,
24541
25205
  } else {
24542
25206
  note(false, "Server reachable", "N/A (not logged in)");
24543
25207
  }
24544
- const credFile = join53(getDataDir(), "credentials.json");
24545
- if (!existsSync50(credFile)) {
25208
+ {
25209
+ const h = readCollectorHealth();
25210
+ if (!h) {
25211
+ note(false, "Collector reporting", `no health report at ${collectorHealthPath()} \u2014 the watch daemon may never have run (\`chat-recall watch --install-service\`)`);
25212
+ } else {
25213
+ const v = judgeHealth(h);
25214
+ note(v.ok, "Collector healthy", v.ok ? `last reported ${Math.max(0, Math.round((Date.now() - h.updatedAt) / 1e3))}s ago, ${Object.keys(h.targets).length} target(s)` : v.reasons.join("; "));
25215
+ for (const [url, t] of Object.entries(h.targets)) {
25216
+ const okRecently = t.lastOkAt !== null && Date.now() - t.lastOkAt < STALE_AFTER_MS;
25217
+ note(okRecently, `Synced to ${url}`, t.lastOkAt === null ? `never \u2014 ${t.failures} consecutive failure(s)${t.lastError ? `: ${t.lastError}` : ""}` : `${Math.round((Date.now() - t.lastOkAt) / 6e4)}m ago${t.failures ? ` (${t.failures} failure(s) since)` : ""}`);
25218
+ }
25219
+ }
25220
+ }
25221
+ const credFile = join56(getDataDir(), "credentials.json");
25222
+ if (!existsSync52(credFile)) {
24546
25223
  note(false, "Credentials file", `missing \u2014 run \`chat-recall login <server-url>\``);
24547
25224
  } else {
24548
25225
  try {
24549
- const mode = statSync41(credFile).mode & 511;
24550
- const safe = (mode & 63) === 0;
24551
- note(safe, "Credentials file", `${credFile} (mode ${mode.toString(8).padStart(3, "0")}${safe ? "" : " \u2014 should be 600"})`);
25226
+ const mode = statSync42(credFile).mode & 511;
25227
+ const isWin = process.platform === "win32";
25228
+ const safe = isWin || (mode & 63) === 0;
25229
+ note(safe, "Credentials file", isWin ? `${credFile} (Windows ACL \u2014 POSIX mode does not apply)` : `${credFile} (mode ${mode.toString(8).padStart(3, "0")}${safe ? "" : " \u2014 should be 600"})`);
24552
25230
  } catch (err) {
24553
25231
  note(false, "Credentials file", `error: ${err}`);
24554
25232
  }
24555
25233
  }
24556
25234
  for (const hooksJson of claudeHookConfigFiles()) {
24557
- const profile = basename27(dirname22(hooksJson));
25235
+ const profile = basename27(dirname24(hooksJson));
24558
25236
  const label = `Claude hooks (${profile})`;
24559
- if (!existsSync50(hooksJson)) {
25237
+ if (!existsSync52(hooksJson)) {
24560
25238
  note(false, label, `no ${hooksJson} \u2014 run \`chat-recall install-hooks\``);
24561
25239
  continue;
24562
25240
  }
24563
25241
  try {
24564
25242
  const readHooks = (file) => {
24565
- if (!existsSync50(file)) return {};
25243
+ if (!existsSync52(file)) return {};
24566
25244
  try {
24567
- return JSON.parse(readFileSync45(file, "utf-8")).hooks || {};
25245
+ return JSON.parse(readFileSync46(file, "utf-8")).hooks || {};
24568
25246
  } catch {
24569
25247
  return {};
24570
25248
  }
24571
25249
  };
24572
25250
  const cfg = readHooks(hooksJson);
24573
- const settings2 = readHooks(join53(dirname22(hooksJson), "settings.json"));
25251
+ const settings2 = readHooks(join56(dirname24(hooksJson), "settings.json"));
24574
25252
  const all = ["SessionStart", "UserPromptSubmit", "Stop", "PreCompact", "SessionEnd"];
24575
25253
  const hits = [];
24576
25254
  for (const ev of all) {
@@ -24594,7 +25272,7 @@ program.command("doctor").description("Quick health check across login, server,
24594
25272
  const want = bundledSkillNames2().length;
24595
25273
  for (const t of skillTargets2()) {
24596
25274
  if (!t.available) continue;
24597
- const have = want === 0 ? 0 : bundledSkillNames2().filter((n) => existsSync50(join53(t.dir, n, ".chat-recall-managed"))).length;
25275
+ const have = want === 0 ? 0 : bundledSkillNames2().filter((n) => existsSync52(join56(t.dir, n, ".chat-recall-managed"))).length;
24598
25276
  note(
24599
25277
  have === want,
24600
25278
  `Skills \u2014 ${t.label}`,
@@ -24604,17 +25282,18 @@ program.command("doctor").description("Quick health check across login, server,
24604
25282
  } catch (err) {
24605
25283
  note(false, "Skills", `check failed: ${err}`);
24606
25284
  }
24607
- const mcpJsonPath = join53(homedir21(), ".mcp.json");
24608
- if (!existsSync50(mcpJsonPath)) {
24609
- note(false, "MCP server registration", `no ${mcpJsonPath} \u2014 run \`chat-recall init\``);
24610
- } else {
24611
- try {
24612
- const cfg = JSON.parse(readFileSync45(mcpJsonPath, "utf-8"));
24613
- const has = !!cfg.mcpServers?.["chat-recall"];
24614
- note(has, "chat-recall MCP server", has ? `registered in ${mcpJsonPath}` : "not registered \u2014 run `chat-recall init`");
24615
- } catch {
24616
- note(false, "chat-recall MCP server", `${mcpJsonPath} unparseable`);
25285
+ try {
25286
+ const { inspectMcpClients: inspectMcpClients2 } = await Promise.resolve().then(() => (init_mcp_clients(), mcp_clients_exports));
25287
+ for (const c of inspectMcpClients2()) {
25288
+ if (!c.present && !c.registered) continue;
25289
+ note(
25290
+ c.registered,
25291
+ `MCP server \u2014 ${c.label}`,
25292
+ c.registered ? `registered in ${c.path}` : `missing from ${c.path} \u2014 run \`chat-recall init\``
25293
+ );
24617
25294
  }
25295
+ } catch (err) {
25296
+ note(false, "MCP server registration", `check failed: ${err}`);
24618
25297
  }
24619
25298
  try {
24620
25299
  const { checkCodeindexStatus: checkCodeindexStatus2 } = await Promise.resolve().then(() => (init_companions(), companions_exports));
@@ -24652,7 +25331,7 @@ program.command("doctor").description("Quick health check across login, server,
24652
25331
  let watchRunning = false;
24653
25332
  const tryCmd2 = (cmd) => {
24654
25333
  try {
24655
- return execSync7(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString();
25334
+ return execSync6(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString();
24656
25335
  } catch {
24657
25336
  return "";
24658
25337
  }
@@ -24668,14 +25347,14 @@ program.command("doctor").description("Quick health check across login, server,
24668
25347
  note(watchRunning, "Watch daemon", watchRunning ? "running" : "not running (run `chat-recall watch --install-service` to keep collection running)");
24669
25348
  const dataDir = getDataDir();
24670
25349
  const legacyPaths = [
24671
- join53(dataDir, "cache.db"),
24672
- join53(dataDir, "cache.db-wal"),
24673
- join53(dataDir, "cache.db-shm"),
24674
- join53(dataDir, "index", "lancedb"),
24675
- join53(dataDir, "index", "knowledge_graph.db"),
24676
- join53(dataDir, "index", "metadata.db")
25350
+ join56(dataDir, "cache.db"),
25351
+ join56(dataDir, "cache.db-wal"),
25352
+ join56(dataDir, "cache.db-shm"),
25353
+ join56(dataDir, "index", "lancedb"),
25354
+ join56(dataDir, "index", "knowledge_graph.db"),
25355
+ join56(dataDir, "index", "metadata.db")
24677
25356
  ];
24678
- const legacyPresent = legacyPaths.filter((p) => existsSync50(p));
25357
+ const legacyPresent = legacyPaths.filter((p) => existsSync52(p));
24679
25358
  if (opts.purgeLocal) {
24680
25359
  if (!target) {
24681
25360
  note(false, "Purge legacy index", "refused \u2014 log in and `chat-recall sync` first, so your history is on the server before removing local copies");
@@ -24746,7 +25425,7 @@ companions.command("install").description("Install codeindex (downloads from mun
24746
25425
  if (result.installed) {
24747
25426
  const sizeMb = result.size ? `${(result.size / 1024 / 1024).toFixed(1)} MB` : "?";
24748
25427
  console.log(chalk.green(`\u2713 codeindex installed (${sizeMb}) \u2192 ${result.path}`));
24749
- const mcpJsonPath = join53(homedir21(), ".mcp.json");
25428
+ const mcpJsonPath = join56(homedir22(), ".mcp.json");
24750
25429
  const reg = registerCodeindexMcp2(mcpJsonPath, CODEINDEX_BIN_PATH2);
24751
25430
  if (reg.added) console.log(chalk.dim(` Registered as MCP server in ${mcpJsonPath}`));
24752
25431
  else console.log(chalk.dim(` Already registered as MCP server`));
@@ -24768,7 +25447,7 @@ companions.command("uninstall").description("Remove the codeindex binary chat-re
24768
25447
  const r = uninstallCodeindex2();
24769
25448
  if (r.removed) console.log(chalk.green(`\u2713 Removed ${r.path}`));
24770
25449
  else console.log(chalk.dim(` Nothing to remove at ${r.path}`));
24771
- const mcpJsonPath = join53(homedir21(), ".mcp.json");
25450
+ const mcpJsonPath = join56(homedir22(), ".mcp.json");
24772
25451
  const u = unregisterCodeindexMcp2(mcpJsonPath);
24773
25452
  if (u.removed) console.log(chalk.dim(` Unregistered MCP server from ${mcpJsonPath}`));
24774
25453
  });
@@ -25263,10 +25942,10 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
25263
25942
  try {
25264
25943
  const { resolveProjectId: resolveProjectId2 } = await Promise.resolve().then(() => (init_project_resolver(), project_resolver_exports));
25265
25944
  const { renderTeamTasksMd: renderTeamTasksMd2, parseTeamTasksFile: parseTeamTasksFile2 } = await Promise.resolve().then(() => (init_project_tasks(), project_tasks_exports));
25266
- const { writeFileSync: writeFileSync19, readFileSync: readFileSync45, existsSync: existsSync50 } = await import("node:fs");
25267
- const { join: join54 } = await import("node:path");
25268
- const file = join54(cwd, "TEAM_TASKS.md");
25269
- if (!opts.force && existsSync50(file) && parseTeamTasksFile2(readFileSync45(file, "utf8")).length > 0) {
25945
+ const { writeFileSync: writeFileSync21, readFileSync: readFileSync46, existsSync: existsSync52 } = await import("node:fs");
25946
+ const { join: join57 } = await import("node:path");
25947
+ const file = join57(cwd, "TEAM_TASKS.md");
25948
+ if (!opts.force && existsSync52(file) && parseTeamTasksFile2(readFileSync46(file, "utf8")).length > 0) {
25270
25949
  console.error(chalk.red("TEAM_TASKS.md has unpushed edits."), "Run", chalk.bold("chat-recall tasks push"), "first, or", chalk.bold("tasks pull --force"), "to discard.");
25271
25950
  process.exit(1);
25272
25951
  }
@@ -25274,7 +25953,7 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
25274
25953
  const pid = resolved && resolved.source !== "ignored" ? resolved.id : "";
25275
25954
  const { tasks: tasks2 } = await serverGet("/api/tasks?assignee=@me");
25276
25955
  const mine = tasks2.filter((t) => !pid || !t.projectId || t.projectId === pid);
25277
- writeFileSync19(file, renderTeamTasksMd2(mine));
25956
+ writeFileSync21(file, renderTeamTasksMd2(mine));
25278
25957
  console.log(chalk.green("\u2713 wrote"), "TEAM_TASKS.md", chalk.gray(`(${mine.length} task(s))`), "\u2014 edit, then", chalk.bold("chat-recall tasks push"));
25279
25958
  } catch (e) {
25280
25959
  console.error(chalk.red(e instanceof Error ? e.message : String(e)));
@@ -25285,14 +25964,14 @@ tasks.command("push [dir]").description("Push TEAM_TASKS.md status edits back to
25285
25964
  const cwd = dir || process.cwd();
25286
25965
  try {
25287
25966
  const { parseTeamTasksFile: parseTeamTasksFile2 } = await Promise.resolve().then(() => (init_project_tasks(), project_tasks_exports));
25288
- const { readFileSync: readFileSync45, existsSync: existsSync50 } = await import("node:fs");
25289
- const { join: join54 } = await import("node:path");
25290
- const file = join54(cwd, "TEAM_TASKS.md");
25291
- if (!existsSync50(file)) {
25967
+ const { readFileSync: readFileSync46, existsSync: existsSync52 } = await import("node:fs");
25968
+ const { join: join57 } = await import("node:path");
25969
+ const file = join57(cwd, "TEAM_TASKS.md");
25970
+ if (!existsSync52(file)) {
25292
25971
  console.error(chalk.red("no TEAM_TASKS.md here \u2014"), "run", chalk.bold("chat-recall tasks pull"), "first");
25293
25972
  process.exit(1);
25294
25973
  }
25295
- const edits = parseTeamTasksFile2(readFileSync45(file, "utf8"));
25974
+ const edits = parseTeamTasksFile2(readFileSync46(file, "utf8"));
25296
25975
  let n = 0;
25297
25976
  for (const e of edits) {
25298
25977
  await serverPatch(`/api/tasks/${encodeURIComponent(e.id)}`, { status: e.intent });
@@ -25398,8 +26077,8 @@ team.command("publish <type> <name> <file>").description("Publish a local file a
25398
26077
  console.error(chalk.red(`Invalid tool: ${opts.tool}. Must be one of: ${validTools.join(", ")}`));
25399
26078
  process.exit(1);
25400
26079
  }
25401
- const { readFileSync: readFileSync45 } = await import("fs");
25402
- const body = readFileSync45(file);
26080
+ const { readFileSync: readFileSync46 } = await import("fs");
26081
+ const body = readFileSync46(file);
25403
26082
  const { teamPublish: teamPublish2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
25404
26083
  const a = await teamPublish2({
25405
26084
  type,
@@ -25586,15 +26265,15 @@ vault.command("forget").description("Remove the stored passphrase from this mach
25586
26265
  console.log(keyringDelete2() ? chalk.green("\u2713 Passphrase removed from the OS keyring.") : chalk.dim("Nothing stored in the OS keyring."));
25587
26266
  }));
25588
26267
  program.command("install-hooks").description("Install Claude Code hooks (Stop + PreCompact + UserPromptSubmit + SessionEnd + SessionStart) into every Claude profile").option("--uninstall", "Remove hooks instead of installing them").option("--no-resume-hint", "Don't install the UserPromptSubmit resume-hint hook").option("--no-escalate", "Don't install the SessionEnd learnings-escalation hook").option("--no-wakeup", "Don't install the SessionStart wake-up hook").action(async (opts) => {
25589
- const { mkdirSync: mkdirSync17, copyFileSync: copyFileSync4, chmodSync: chmodSync5, existsSync: existsSync50, readFileSync: readFileSync45, writeFileSync: writeFileSync19, statSync: statSync41 } = await import("fs");
26268
+ const { mkdirSync: mkdirSync19, copyFileSync: copyFileSync4, chmodSync: chmodSync5, existsSync: existsSync52, readFileSync: readFileSync46, writeFileSync: writeFileSync21, statSync: statSync42 } = await import("fs");
25590
26269
  const { fileURLToPath: fileURLToPath4 } = await import("url");
25591
26270
  const here = fileURLToPath4(new URL(".", import.meta.url));
25592
26271
  const findHook = (name) => {
25593
- const candidates = [
25594
- join53(here, "..", "hooks", name),
25595
- join53(here, "..", "..", "hooks", name)
26272
+ const candidates2 = [
26273
+ join56(here, "..", "hooks", name),
26274
+ join56(here, "..", "..", "hooks", name)
25596
26275
  ];
25597
- return candidates.find((p) => existsSync50(p));
26276
+ return candidates2.find((p) => existsSync52(p));
25598
26277
  };
25599
26278
  const sourceSaveHook = findHook("chat_recall_save_hook.sh");
25600
26279
  const sourceResumeHook = findHook("chat_recall_resume_hook.sh");
@@ -25605,15 +26284,15 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25605
26284
  process.exit(1);
25606
26285
  }
25607
26286
  const hooksDir = getHooksDir();
25608
- const installedSaveHook = join53(hooksDir, "chat_recall_save_hook.sh");
25609
- const installedResumeHook = join53(hooksDir, "chat_recall_resume_hook.sh");
25610
- const installedEscalateHook = join53(hooksDir, "chat_recall_escalate_hook.sh");
25611
- const installedWakeupHook = join53(hooksDir, "chat_recall_wakeup_hook.sh");
26287
+ const installedSaveHook = join56(hooksDir, "chat_recall_save_hook.sh");
26288
+ const installedResumeHook = join56(hooksDir, "chat_recall_resume_hook.sh");
26289
+ const installedEscalateHook = join56(hooksDir, "chat_recall_escalate_hook.sh");
26290
+ const installedWakeupHook = join56(hooksDir, "chat_recall_wakeup_hook.sh");
25612
26291
  const wakeupWiredInSettings = (home) => {
25613
- const settings2 = join53(home, "settings.json");
25614
- if (!existsSync50(settings2)) return false;
26292
+ const settings2 = join56(home, "settings.json");
26293
+ if (!existsSync52(settings2)) return false;
25615
26294
  try {
25616
- const cfg = JSON.parse(readFileSync45(settings2, "utf-8"));
26295
+ const cfg = JSON.parse(readFileSync46(settings2, "utf-8"));
25617
26296
  const arr = Array.isArray(cfg.hooks?.SessionStart) ? cfg.hooks.SessionStart : [];
25618
26297
  return arr.some((h) => (h?.hooks?.[0]?.command || "").includes("chat_recall_wakeup_hook"));
25619
26298
  } catch {
@@ -25622,9 +26301,9 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25622
26301
  };
25623
26302
  const hookConfigFiles = claudeHookConfigFiles();
25624
26303
  const readHookConfig = (file) => {
25625
- if (!existsSync50(file)) return { hooks: {} };
26304
+ if (!existsSync52(file)) return { hooks: {} };
25626
26305
  try {
25627
- const c = JSON.parse(readFileSync45(file, "utf-8"));
26306
+ const c = JSON.parse(readFileSync46(file, "utf-8"));
25628
26307
  if (!c.hooks || typeof c.hooks !== "object") c.hooks = {};
25629
26308
  return c;
25630
26309
  } catch (err) {
@@ -25650,8 +26329,8 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25650
26329
  if (filtered.length) config2.hooks[event] = filtered;
25651
26330
  else delete config2.hooks[event];
25652
26331
  }
25653
- if (removed === 0 && !existsSync50(hooksJson)) continue;
25654
- writeFileSync19(hooksJson, JSON.stringify(config2, null, 2) + "\n");
26332
+ if (removed === 0 && !existsSync52(hooksJson)) continue;
26333
+ writeFileSync21(hooksJson, JSON.stringify(config2, null, 2) + "\n");
25655
26334
  total += removed;
25656
26335
  console.log(chalk.green(`\u2713 Removed ${removed} chat-recall hook entr${removed === 1 ? "y" : "ies"} from ${hooksJson}`));
25657
26336
  }
@@ -25659,7 +26338,20 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25659
26338
  console.log(chalk.dim(` (Hook scripts left in ${hooksDir} \u2014 delete manually if you want.)`));
25660
26339
  return;
25661
26340
  }
25662
- mkdirSync17(hooksDir, { recursive: true });
26341
+ if (process.platform === "win32") {
26342
+ console.log(chalk.yellow("Hooks are not installed on Windows."));
26343
+ console.log(chalk.dim(" They are POSIX shell scripts, and Claude Code runs hook commands through"));
26344
+ console.log(chalk.dim(" cmd.exe, which cannot execute them. Registering them would put an error on"));
26345
+ console.log(chalk.dim(" every turn instead of saving anything."));
26346
+ console.log(chalk.dim(""));
26347
+ console.log(chalk.dim(" Everything else works: indexing, sync, search and the MCP server are"));
26348
+ console.log(chalk.dim(" unaffected. Hooks only add automatic fact-saving and the wake-up bundle,"));
26349
+ console.log(chalk.dim(" and you can get the same context by calling recall_wake_up from the agent."));
26350
+ console.log(chalk.dim(""));
26351
+ console.log(chalk.dim(" Under WSL, run this from inside the WSL shell and it installs normally."));
26352
+ return;
26353
+ }
26354
+ mkdirSync19(hooksDir, { recursive: true });
25663
26355
  copyFileSync4(sourceSaveHook, installedSaveHook);
25664
26356
  chmodSync5(installedSaveHook, 493);
25665
26357
  const installResume = opts.resumeHint !== false && !!sourceResumeHook;
@@ -25678,7 +26370,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25678
26370
  chmodSync5(installedWakeupHook, 493);
25679
26371
  }
25680
26372
  const stopEntry = { matcher: "", hooks: [{ type: "command", command: installedSaveHook }] };
25681
- const precompactEntry = { matcher: "", hooks: [{ type: "command", command: `${installedSaveHook} --precompact` }] };
26373
+ const precompactEntry = { matcher: "", hooks: [{ type: "command", command: `"${installedSaveHook}" --precompact` }] };
25682
26374
  const resumeEntry = { matcher: "", hooks: [{ type: "command", command: installedResumeHook }] };
25683
26375
  const escalateEntry = { matcher: "", hooks: [{ type: "command", command: installedEscalateHook }] };
25684
26376
  const wakeupEntry = { matcher: "startup|clear", hooks: [{ type: "command", command: installedWakeupHook }] };
@@ -25691,9 +26383,9 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25691
26383
  const wakeupSkipped = [];
25692
26384
  for (const hooksJson of hookConfigFiles) {
25693
26385
  const config2 = readHookConfig(hooksJson);
25694
- const home = dirname22(hooksJson);
26386
+ const home = dirname24(hooksJson);
25695
26387
  const alreadyWired = wakeupWiredInSettings(home);
25696
- if (alreadyWired) wakeupSkipped.push(join53(home, "settings.json"));
26388
+ if (alreadyWired) wakeupSkipped.push(join56(home, "settings.json"));
25697
26389
  const perProfile = installWakeup && !alreadyWired ? [...events, ["SessionStart", wakeupEntry]] : events;
25698
26390
  for (const event of HOOK_EVENTS) {
25699
26391
  const arr = Array.isArray(config2.hooks[event]) ? config2.hooks[event] : [];
@@ -25703,21 +26395,21 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25703
26395
  if (without.length) config2.hooks[event] = without;
25704
26396
  else delete config2.hooks[event];
25705
26397
  }
25706
- mkdirSync17(dirname22(hooksJson), { recursive: true });
25707
- writeFileSync19(hooksJson, JSON.stringify(config2, null, 2) + "\n");
26398
+ mkdirSync19(dirname24(hooksJson), { recursive: true });
26399
+ writeFileSync21(hooksJson, JSON.stringify(config2, null, 2) + "\n");
25708
26400
  }
25709
- const saveSz = statSync41(installedSaveHook).size;
26401
+ const saveSz = statSync42(installedSaveHook).size;
25710
26402
  console.log(chalk.green(`\u2713 Installed chat-recall save hook (${saveSz} bytes)`));
25711
26403
  if (installResume) {
25712
- const resumeSz = statSync41(installedResumeHook).size;
26404
+ const resumeSz = statSync42(installedResumeHook).size;
25713
26405
  console.log(chalk.green(`\u2713 Installed chat-recall resume-hint hook (${resumeSz} bytes)`));
25714
26406
  }
25715
26407
  if (installEscalate) {
25716
- const escalateSz = statSync41(installedEscalateHook).size;
26408
+ const escalateSz = statSync42(installedEscalateHook).size;
25717
26409
  console.log(chalk.green(`\u2713 Installed chat-recall learnings-escalation hook (${escalateSz} bytes)`));
25718
26410
  }
25719
26411
  if (installWakeup) {
25720
- const wakeupSz = statSync41(installedWakeupHook).size;
26412
+ const wakeupSz = statSync42(installedWakeupHook).size;
25721
26413
  console.log(chalk.green(`\u2713 Installed chat-recall wake-up hook (${wakeupSz} bytes)`));
25722
26414
  for (const f of wakeupSkipped) {
25723
26415
  console.log(chalk.dim(` (SessionStart already wired in ${f} \u2014 left as is, not duplicated)`));
@@ -25749,7 +26441,7 @@ program.command("dossier <project>").description("Generate a project dossier (ov
25749
26441
  );
25750
26442
  const md = dossier.markdown;
25751
26443
  if (options.out) {
25752
- writeFileSync18(options.out, md);
26444
+ writeFileSync20(options.out, md);
25753
26445
  console.log(chalk.green(`Wrote dossier to ${options.out} (${md.length} chars)`));
25754
26446
  } else {
25755
26447
  console.log(md);
@@ -25783,12 +26475,12 @@ program.command("watch").description("Run the live indexer daemon (watches all A
25783
26475
  }
25784
26476
  return;
25785
26477
  }
25786
- const candidates = [
26478
+ const candidates2 = [
25787
26479
  new URL("./watch.js", import.meta.url).href,
25788
26480
  new URL("../auto-indexer/indexer.js", import.meta.url).href
25789
26481
  ];
25790
26482
  let lastErr;
25791
- for (const spec of candidates) {
26483
+ for (const spec of candidates2) {
25792
26484
  try {
25793
26485
  await import(spec);
25794
26486
  return;
@@ -25965,8 +26657,8 @@ include.command("list", { isDefault: true }).description("Show paths opted back
25965
26657
  include.command("project <path>").description("Re-permit a path under a personal folder (substring match; e.g. ~/Documents/code)").action(async (path) => {
25966
26658
  const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
25967
26659
  const { resolve: resolve3 } = await import("node:path");
25968
- const { homedir: homedir22 } = await import("node:os");
25969
- const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir22()));
26660
+ const { homedir: homedir23 } = await import("node:os");
26661
+ const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir23()));
25970
26662
  const s = loadSettings2();
25971
26663
  s.sync.includeProjects = s.sync.includeProjects || [];
25972
26664
  if (s.sync.includeProjects.includes(abs)) {
@@ -25980,8 +26672,8 @@ include.command("project <path>").description("Re-permit a path under a personal
25980
26672
  include.command("remove <path>").description("Undo an opt-in (personal folder goes back to skipped)").action(async (path) => {
25981
26673
  const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
25982
26674
  const { resolve: resolve3 } = await import("node:path");
25983
- const { homedir: homedir22 } = await import("node:os");
25984
- const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir22()));
26675
+ const { homedir: homedir23 } = await import("node:os");
26676
+ const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir23()));
25985
26677
  const s = loadSettings2();
25986
26678
  const before = (s.sync.includeProjects || []).length;
25987
26679
  s.sync.includeProjects = (s.sync.includeProjects || []).filter((p) => p !== path && p !== abs);
@@ -26060,8 +26752,8 @@ program.command("projects").description("List distinct project ids discovered on
26060
26752
  exclude.command("project <path>").description("Exclude a project path from syncing (substring match on the project path; e.g. ~/.claude-pr-bot)").action(async (path) => {
26061
26753
  const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
26062
26754
  const { resolve: resolve3 } = await import("node:path");
26063
- const { homedir: homedir22 } = await import("node:os");
26064
- const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir22()));
26755
+ const { homedir: homedir23 } = await import("node:os");
26756
+ const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir23()));
26065
26757
  const s = loadSettings2();
26066
26758
  if (s.sync.excludeProjects.includes(abs)) {
26067
26759
  console.log(chalk.dim(`Already excluded: ${abs}`));
@@ -26090,8 +26782,8 @@ exclude.command("tool <tool>").description("Exclude an AI tool entirely (claude
26090
26782
  exclude.command("remove <value>").description("Remove an exclusion (tool name or project path)").action(async (value) => {
26091
26783
  const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
26092
26784
  const { resolve: resolve3 } = await import("node:path");
26093
- const { homedir: homedir22 } = await import("node:os");
26094
- const abs = resolve3(value.replace(/^~(?=\/|$)/, homedir22()));
26785
+ const { homedir: homedir23 } = await import("node:os");
26786
+ const abs = resolve3(value.replace(/^~(?=\/|$)/, homedir23()));
26095
26787
  const s = loadSettings2();
26096
26788
  const beforeTools = s.sync.excludeTools.length;
26097
26789
  const beforeProjects = s.sync.excludeProjects.length;