chat-recall 0.5.4 → 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
  }
@@ -6278,6 +6349,24 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
6278
6349
  }
6279
6350
  const filesToParse = hasSubagentsDir(sessionPath) ? [sessionPath, ...getSubagentPaths(sessionPath)] : [sessionPath];
6280
6351
  let userCount = 0;
6352
+ const seenPrompts = /* @__PURE__ */ new Set();
6353
+ const recordPrompt = (raw, line, dedupe = false) => {
6354
+ if (userCount >= maxMessages)
6355
+ return;
6356
+ const withoutReminders = raw.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "");
6357
+ if (!withoutReminders.trim() || withoutReminders.trim().length <= 10)
6358
+ return;
6359
+ const cleanedText = stripInjectedBanners(withoutReminders).trim();
6360
+ if (cleanedText.length < 10)
6361
+ return;
6362
+ if (dedupe && seenPrompts.has(cleanedText))
6363
+ return;
6364
+ seenPrompts.add(cleanedText);
6365
+ content.userMessages.push({ text: cleanedText, lineNumber: line, contentType: "user" });
6366
+ userCount++;
6367
+ if (!content.firstPrompt)
6368
+ content.firstPrompt = cleanedText.slice(0, 1e3);
6369
+ };
6281
6370
  let assistantCount = 0;
6282
6371
  let toolCount = 0;
6283
6372
  let lineNum = 0;
@@ -6356,19 +6445,11 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
6356
6445
  text = textParts.join("\n");
6357
6446
  }
6358
6447
  }
6359
- if (text && !text.includes("<system-reminder>") && text.trim().length > 10) {
6360
- const cleanedText = stripInjectedBanners(text).trim();
6361
- if (cleanedText.length < 10)
6362
- continue;
6363
- content.userMessages.push({
6364
- text: cleanedText,
6365
- lineNumber: lineNum,
6366
- contentType: "user"
6367
- });
6368
- userCount++;
6369
- if (!content.firstPrompt) {
6370
- content.firstPrompt = cleanedText.slice(0, 1e3);
6371
- }
6448
+ if (text)
6449
+ recordPrompt(text, lineNum);
6450
+ } else if (msgType === "queue-operation") {
6451
+ if (obj.operation === "enqueue" && typeof obj.content === "string") {
6452
+ recordPrompt(obj.content, lineNum, true);
6372
6453
  }
6373
6454
  } else if (msgType === "assistant") {
6374
6455
  const msg = obj.message;
@@ -6484,8 +6565,8 @@ function decodeDirName(dirName) {
6484
6565
  }
6485
6566
  for (let len = 2; len <= segments.length - i; len++) {
6486
6567
  const joinedSegments = segments.slice(i, i + len);
6487
- for (const sep2 of ["-", "_", "."]) {
6488
- const candidate = join5(currentPath, joinedSegments.join(sep2));
6568
+ for (const sep3 of ["-", "_", "."]) {
6569
+ const candidate = join5(currentPath, joinedSegments.join(sep3));
6489
6570
  if (existsSync5(candidate)) {
6490
6571
  currentPath = candidate;
6491
6572
  i += len;
@@ -6855,8 +6936,8 @@ var init_claude = __esm({
6855
6936
 
6856
6937
  // ../engine/dist/transcript/gemini.js
6857
6938
  async function parseGeminiTranscript(sessionPath) {
6858
- const { readFileSync: readFileSync45 } = await import("fs");
6859
- return parseGeminiTranscriptText(readFileSync45(sessionPath, "utf-8"));
6939
+ const { readFileSync: readFileSync46 } = await import("fs");
6940
+ return parseGeminiTranscriptText(readFileSync46(sessionPath, "utf-8"));
6860
6941
  }
6861
6942
  function parseGeminiTranscriptText(text) {
6862
6943
  let content;
@@ -7312,11 +7393,11 @@ async function archiveRawSession(store, sessionId) {
7312
7393
  return await store.putRawSession(sessionId, exp.tool, Math.floor(exp.mtime), gz, size);
7313
7394
  }
7314
7395
  function detectForkPredecessor(sessionPath) {
7315
- const { readFileSync: readFileSync45, readdirSync: readdirSync33 } = requireFs();
7316
- const { dirname: dirname23, join: join54, basename: basename28 } = requirePath();
7396
+ const { readFileSync: readFileSync46, readdirSync: readdirSync33 } = requireFs();
7397
+ const { dirname: dirname25, join: join57, basename: basename28 } = requirePath();
7317
7398
  let text;
7318
7399
  try {
7319
- text = readFileSync45(sessionPath, "utf-8");
7400
+ text = readFileSync46(sessionPath, "utf-8");
7320
7401
  } catch {
7321
7402
  return null;
7322
7403
  }
@@ -7338,7 +7419,7 @@ function detectForkPredecessor(sessionPath) {
7338
7419
  return null;
7339
7420
  if (text.includes(`"uuid":"${headParent}"`))
7340
7421
  return null;
7341
- const dir = dirname23(sessionPath);
7422
+ const dir = dirname25(sessionPath);
7342
7423
  const self = basename28(sessionPath);
7343
7424
  let entries;
7344
7425
  try {
@@ -7350,7 +7431,7 @@ function detectForkPredecessor(sessionPath) {
7350
7431
  if (!f.endsWith(".jsonl") || f === self || f === "sessions-index.json")
7351
7432
  continue;
7352
7433
  try {
7353
- if (readFileSync45(join54(dir, f), "utf-8").includes(`"uuid":"${headParent}"`)) {
7434
+ if (readFileSync46(join57(dir, f), "utf-8").includes(`"uuid":"${headParent}"`)) {
7354
7435
  return basename28(f, ".jsonl");
7355
7436
  }
7356
7437
  } catch {
@@ -8151,6 +8232,20 @@ var init_tail_read = __esm({
8151
8232
  }
8152
8233
  });
8153
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
+
8154
8249
  // ../engine/dist/core/backends/gemini.js
8155
8250
  import { createHash as createHash3 } from "crypto";
8156
8251
  import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync3, statSync as statSync6 } from "fs";
@@ -8192,6 +8287,7 @@ var init_gemini2 = __esm({
8192
8287
  init_session_git();
8193
8288
  init_generic_engine();
8194
8289
  init_tail_read();
8290
+ init_flat_string();
8195
8291
  PREFIX = "gemini_";
8196
8292
  FULL_PARSE_MAX = 4 * 1024 * 1024;
8197
8293
  GeminiBackend = class {
@@ -8301,7 +8397,7 @@ var init_gemini2 = __esm({
8301
8397
  sessionId = readSessionId(fullPath, format);
8302
8398
  const messages = readGeminiMessages(fullPath, format);
8303
8399
  const firstUser = messages.find((m) => m.type === "user");
8304
- 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));
8305
8401
  messageCount = messages.length;
8306
8402
  } else if (format === "jsonl") {
8307
8403
  const lines = readHeadSync(fullPath, 1048576).split("\n");
@@ -8323,7 +8419,7 @@ var init_gemini2 = __esm({
8323
8419
  }
8324
8420
  if (m?.type !== "user")
8325
8421
  continue;
8326
- 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));
8327
8423
  break;
8328
8424
  }
8329
8425
  }
@@ -8499,7 +8595,7 @@ var init_gemini2 = __esm({
8499
8595
  if (!existsSync7(tmpRoot))
8500
8596
  return [];
8501
8597
  const projMap = this.loadProjectMap();
8502
- const candidates = [];
8598
+ const candidates2 = [];
8503
8599
  for (const proj of readdirSync5(tmpRoot, { withFileTypes: true })) {
8504
8600
  if (!proj.isDirectory())
8505
8601
  continue;
@@ -8530,13 +8626,13 @@ var init_gemini2 = __esm({
8530
8626
  if (st.mtimeMs < opts.sinceMs)
8531
8627
  continue;
8532
8628
  const trimLen = isJsonl ? 6 : 5;
8533
- candidates.push({ rawIdHint: f.slice(0, -trimLen), mtime: st.mtimeMs });
8629
+ candidates2.push({ rawIdHint: f.slice(0, -trimLen), mtime: st.mtimeMs });
8534
8630
  } catch {
8535
8631
  }
8536
8632
  }
8537
8633
  }
8538
- candidates.sort((a, b) => b.mtime - a.mtime);
8539
- 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;
8540
8636
  const edits = [];
8541
8637
  for (const c of limited) {
8542
8638
  const scan = this.liveScanEdits(c.rawIdHint);
@@ -8668,6 +8764,7 @@ var init_opencode = __esm({
8668
8764
  init_session_outcome();
8669
8765
  init_session_git();
8670
8766
  init_generic_engine();
8767
+ init_flat_string();
8671
8768
  PREFIX2 = "opencode_";
8672
8769
  OpencodeBackend = class {
8673
8770
  id = "opencode";
@@ -8835,7 +8932,7 @@ var init_opencode = __esm({
8835
8932
  if (firstUser) {
8836
8933
  try {
8837
8934
  const parsed = JSON.parse(firstUser.data);
8838
- firstPrompt = String(parsed?.text || "").slice(0, 200);
8935
+ firstPrompt = flatString(String(parsed?.text || "").slice(0, 200));
8839
8936
  } catch {
8840
8937
  }
8841
8938
  }
@@ -9326,6 +9423,7 @@ var init_codex2 = __esm({
9326
9423
  init_session_git();
9327
9424
  init_generic_engine();
9328
9425
  init_tail_read();
9426
+ init_flat_string();
9329
9427
  PREFIX3 = "codex_";
9330
9428
  CodexBackend = class {
9331
9429
  id = "codex";
@@ -9438,7 +9536,7 @@ var init_codex2 = __esm({
9438
9536
  if (ev.type === "event_msg" && ev.payload?.type === "user_message") {
9439
9537
  messageCount++;
9440
9538
  if (!firstPrompt && ev.payload.message) {
9441
- firstPrompt = String(ev.payload.message).slice(0, 200);
9539
+ firstPrompt = flatString(String(ev.payload.message).slice(0, 200));
9442
9540
  }
9443
9541
  } else if (ev.type === "response_item" && ev.payload?.type === "message") {
9444
9542
  messageCount++;
@@ -9539,23 +9637,24 @@ var init_codex2 = __esm({
9539
9637
  return null;
9540
9638
  }
9541
9639
  /**
9542
- * Stream a Codex JSONL rollout into canonical events. Codex separates:
9543
- * - event_msg + payload.type='user_message' → user
9544
- * - response_item + payload.type='message' → assistant_text
9545
- * - response_item + payload.type='function_call' → tool_use
9546
- * - response_item + payload.type='function_call_output' → tool_result
9547
- *
9548
- * Filters Codex's wrapper-bearing user messages (environment_context etc.).
9549
- *
9550
- * **Subagent fan-out:** Codex orchestrates work via `spawn_agent` calls
9551
- * that produce sibling rollout files in the same YYYY/MM/DD directory.
9552
- * Sub-agent rollouts carry `payload.source.subagent.thread_spawn` linking
9553
- * back to the parent. The actual file edits often happen inside those
9554
- * subagent rollouts — without folding their events into the parent's
9555
- * stream, replay/scan would see the parent as edit-free. We append every
9556
- * subagent rollout's events here so the generic engine sees one unified
9557
- * stream per user-facing session.
9558
- */
9640
+ * Stream a Codex JSONL rollout into canonical events. Codex separates:
9641
+ * - event_msg + payload.type='user_message' → user
9642
+ * - response_item + payload.type='message' → user OR assistant_text,
9643
+ * by payload.role
9644
+ * - response_item + payload.type='function_call' → tool_use
9645
+ * - response_item + payload.type='function_call_output' → tool_result
9646
+ *
9647
+ * Filters Codex's wrapper-bearing user messages (environment_context etc.).
9648
+ *
9649
+ * **Subagent fan-out:** Codex orchestrates work via `spawn_agent` calls
9650
+ * that produce sibling rollout files in the same YYYY/MM/DD directory.
9651
+ * Sub-agent rollouts carry `payload.source.subagent.thread_spawn` linking
9652
+ * back to the parent. The actual file edits often happen inside those
9653
+ * subagent rollouts — without folding their events into the parent's
9654
+ * stream, replay/scan would see the parent as edit-free. We append every
9655
+ * subagent rollout's events here so the generic engine sees one unified
9656
+ * stream per user-facing session.
9657
+ */
9559
9658
  readEvents(rawId) {
9560
9659
  const located = findCodexSessionFile(this.toRawId(rawId));
9561
9660
  if (!located)
@@ -9625,6 +9724,11 @@ var init_codex2 = __esm({
9625
9724
  if (text && !isCodexInjectedWrapper2(text)) {
9626
9725
  events.push({ kind: "user", ts, tsIso, line: lineNum, text });
9627
9726
  }
9727
+ } else if (obj.type === "response_item" && payload.type === "message" && payload.role === "user") {
9728
+ const text = flattenContent(payload.content).trim();
9729
+ if (text && !isCodexInjectedWrapper2(text)) {
9730
+ events.push({ kind: "user", ts, tsIso, line: lineNum, text });
9731
+ }
9628
9732
  } else if (obj.type === "response_item" && payload.type === "message" && payload.role === "assistant") {
9629
9733
  const text = flattenContent(payload.content).trim();
9630
9734
  if (text)
@@ -10005,6 +10109,7 @@ var init_agy = __esm({
10005
10109
  init_session_outcome();
10006
10110
  init_session_git();
10007
10111
  init_generic_engine();
10112
+ init_flat_string();
10008
10113
  PREFIX4 = "agy_";
10009
10114
  AgyBackend = class {
10010
10115
  id = "agy";
@@ -10105,7 +10210,7 @@ var init_agy = __esm({
10105
10210
  const content = obj.content;
10106
10211
  const match = content.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
10107
10212
  const rawText = match ? match[1].trim() : content.trim();
10108
- firstPrompt = rawText.slice(0, 200);
10213
+ firstPrompt = flatString(rawText.slice(0, 200));
10109
10214
  break;
10110
10215
  }
10111
10216
  }
@@ -10339,7 +10444,7 @@ var init_agy = __esm({
10339
10444
  if (brainRoots.length === 0)
10340
10445
  return [];
10341
10446
  const brainDir = brainRoots[0];
10342
- const candidates = [];
10447
+ const candidates2 = [];
10343
10448
  let sessionDirs;
10344
10449
  try {
10345
10450
  sessionDirs = readdirSync7(brainDir);
@@ -10361,12 +10466,12 @@ var init_agy = __esm({
10361
10466
  const st = statSync8(filePath);
10362
10467
  if (st.mtimeMs < opts.sinceMs)
10363
10468
  continue;
10364
- candidates.push({ rawId, mtime: st.mtimeMs });
10469
+ candidates2.push({ rawId, mtime: st.mtimeMs });
10365
10470
  } catch {
10366
10471
  }
10367
10472
  }
10368
- candidates.sort((a, b) => b.mtime - a.mtime);
10369
- 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;
10370
10475
  const edits = [];
10371
10476
  for (const c of limited) {
10372
10477
  const scan = this.liveScanEdits(c.rawId);
@@ -10638,18 +10743,43 @@ function updateShadow(sessionId, exp) {
10638
10743
  path
10639
10744
  };
10640
10745
  }
10641
- async function snapshotShadow(sessionId) {
10746
+ async function snapshotShadow(sessionId, opts = {}) {
10642
10747
  await Promise.resolve().then(() => (init_backends(), backends_exports));
10643
10748
  const { getBackendForId: getBackendForId2, getBackend: getBackend2 } = await Promise.resolve().then(() => (init_tool_backend(), tool_backend_exports));
10644
10749
  const backend = getBackendForId2(sessionId) ?? getBackend2("claude");
10645
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
+ }
10646
10762
  let exp = null;
10647
10763
  try {
10648
10764
  exp = backend.exportRawSession(rawId);
10649
10765
  } catch {
10650
10766
  exp = null;
10651
10767
  }
10652
- 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
+ }
10653
10783
  }
10654
10784
  function seedShadow(sessionId, container) {
10655
10785
  const tool = container.tool;
@@ -10668,7 +10798,7 @@ function shadowSizeOnDisk(tool, rawId) {
10668
10798
  return 0;
10669
10799
  }
10670
10800
  }
10671
- var WHOLE_FILE_TOOLS, SINGLETON_META;
10801
+ var WHOLE_FILE_TOOLS, SINGLETON_META, lastLiveFingerprint;
10672
10802
  var init_shadow = __esm({
10673
10803
  "../engine/dist/transcript/shadow.js"() {
10674
10804
  "use strict";
@@ -10676,6 +10806,27 @@ var init_shadow = __esm({
10676
10806
  init_raw();
10677
10807
  WHOLE_FILE_TOOLS = /* @__PURE__ */ new Set(["gemini"]);
10678
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";
10679
10830
  }
10680
10831
  });
10681
10832
 
@@ -10702,7 +10853,7 @@ function findSessionFile(sessionId) {
10702
10853
  path: candidate,
10703
10854
  projectDir: entry.name,
10704
10855
  // Project path is encoded by replacing slashes with dashes.
10705
- projectPath: entry.name.replace(/-/g, "/").replace(/^\//, "/")
10856
+ projectPath: decodeProjectDirName(entry.name)
10706
10857
  };
10707
10858
  }
10708
10859
  }
@@ -10731,7 +10882,7 @@ function findSessionFiles(sessionId) {
10731
10882
  out.push({
10732
10883
  path: candidate,
10733
10884
  projectDir: entry.name,
10734
- projectPath: entry.name.replace(/-/g, "/").replace(/^\//, "/")
10885
+ projectPath: decodeProjectDirName(entry.name)
10735
10886
  });
10736
10887
  }
10737
10888
  }
@@ -10755,7 +10906,31 @@ function resolveSessionContentGroups(sessionId) {
10755
10906
  const names = [...groups.keys()].sort((a, b) => a === "main" ? -1 : b === "main" ? 1 : a.localeCompare(b));
10756
10907
  return names.map((name) => ({ name, paths: groups.get(name) }));
10757
10908
  }
10909
+ function withSessionReadCache(fn) {
10910
+ readScopeDepth++;
10911
+ try {
10912
+ return fn();
10913
+ } finally {
10914
+ if (--readScopeDepth === 0)
10915
+ readScope = null;
10916
+ }
10917
+ }
10758
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
+ }
10759
10934
  let text = "";
10760
10935
  let mtime = 0;
10761
10936
  for (const p of group.paths) {
@@ -10771,7 +10946,10 @@ function readSessionGroupText(group) {
10771
10946
  }
10772
10947
  text = text ? mergeLineText(text, raw).text : raw;
10773
10948
  }
10774
- return { text, mtime };
10949
+ const out = { text, mtime };
10950
+ if (readScopeDepth > 0 && key)
10951
+ readScope = { key, value: out };
10952
+ return out;
10775
10953
  }
10776
10954
  function resolveSessionContentPaths(sessionFile) {
10777
10955
  if (!hasSubagentsDir(sessionFile))
@@ -10994,7 +11172,7 @@ function findCodexSessionFileIn(root, sessionId) {
10994
11172
  }
10995
11173
  return scanDir(root);
10996
11174
  }
10997
- var geminiProjectMapCache, MAX_EVENT_LINE_BYTES;
11175
+ var readScope, readScopeDepth, geminiProjectMapCache, MAX_EVENT_LINE_BYTES;
10998
11176
  var init_live_session_scan = __esm({
10999
11177
  "../engine/dist/core/live-session-scan.js"() {
11000
11178
  "use strict";
@@ -11003,79 +11181,117 @@ var init_live_session_scan = __esm({
11003
11181
  init_tool_backend();
11004
11182
  init_tool_paths();
11005
11183
  init_backends();
11184
+ init_project_dir_name();
11185
+ readScope = null;
11186
+ readScopeDepth = 0;
11006
11187
  geminiProjectMapCache = null;
11007
11188
  MAX_EVENT_LINE_BYTES = 8 * 1024 * 1024;
11008
11189
  }
11009
11190
  });
11010
11191
 
11011
11192
  // ../engine/dist/core/first-prompt.js
11012
- 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";
11013
11195
  function stripBanners2(s) {
11014
11196
  let out = s;
11015
11197
  for (const re of BANNERS)
11016
11198
  out = out.replace(re, " ");
11017
11199
  return out.replace(/[ \t]{2,}/g, " ").trim();
11018
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
+ }
11019
11241
  function extractFirstUserPromptSync(filePath, options = {}) {
11020
11242
  const maxLines = options.maxLines ?? 500;
11021
11243
  const maxLength = options.maxLength ?? 1e3;
11022
- if (!existsSync12(filePath))
11023
- return "";
11024
- let raw;
11244
+ const maxBytes = options.maxBytes ?? 4 * 1024 * 1024;
11245
+ let fd;
11025
11246
  try {
11026
- raw = readFileSync8(filePath, "utf-8");
11247
+ fd = openSync5(filePath, "r");
11027
11248
  } catch {
11028
11249
  return "";
11029
11250
  }
11030
- const lines = raw.split("\n");
11031
- const upTo = Math.min(lines.length, maxLines);
11032
- for (let i = 0; i < upTo; i++) {
11033
- const line = lines[i];
11034
- if (!line.trim())
11035
- continue;
11036
- 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 {
11037
11284
  try {
11038
- obj = JSON.parse(line);
11285
+ closeSync5(fd);
11039
11286
  } catch {
11040
- continue;
11041
11287
  }
11042
- if (obj.type !== "user")
11043
- continue;
11044
- if (obj.isSidechain === true)
11045
- continue;
11046
- const message = obj.message;
11047
- if (!message)
11048
- continue;
11049
- const content = message.content;
11050
- let text = "";
11051
- if (typeof content === "string") {
11052
- text = content;
11053
- } else if (Array.isArray(content)) {
11054
- const parts = [];
11055
- for (const block of content) {
11056
- if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
11057
- parts.push(block.text);
11058
- }
11059
- }
11060
- text = parts.join("\n");
11061
- }
11062
- if (!text)
11063
- continue;
11064
- if (text.includes("<system-reminder>"))
11065
- continue;
11066
- if (text.trim() === "[Request interrupted by user]")
11067
- continue;
11068
- const cleaned = stripBanners2(text);
11069
- if (cleaned.length < 10)
11070
- continue;
11071
- return cleaned.slice(0, maxLength);
11072
11288
  }
11073
- return "";
11074
11289
  }
11075
11290
  var BANNERS;
11076
11291
  var init_first_prompt = __esm({
11077
11292
  "../engine/dist/core/first-prompt.js"() {
11078
11293
  "use strict";
11294
+ init_flat_string();
11079
11295
  BANNERS = [
11080
11296
  /MCP issues detected\. ?Run \/mcp list for status\.?/g,
11081
11297
  /Context low[^\n]*Run \/compact[^\n]*/g,
@@ -11085,8 +11301,11 @@ var init_first_prompt = __esm({
11085
11301
  });
11086
11302
 
11087
11303
  // ../engine/dist/core/backends/claude.js
11088
- 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";
11089
11305
  import { join as join14, basename as basename11 } from "path";
11306
+ function stripReminders(text) {
11307
+ return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
11308
+ }
11090
11309
  function parseExitCode(text) {
11091
11310
  const m = text.match(/(?:^|\n)\s*(?:exit\s+code|returncode|exit\s+status|exit)\s*[:=]\s*(\d+)\b/i);
11092
11311
  return m ? Number(m[1]) : void 0;
@@ -11101,6 +11320,7 @@ var init_claude2 = __esm({
11101
11320
  init_session_git();
11102
11321
  init_first_prompt();
11103
11322
  init_tail_read();
11323
+ init_project_dir_name();
11104
11324
  init_generic_engine();
11105
11325
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11106
11326
  ClaudeBackend = class {
@@ -11177,13 +11397,13 @@ var init_claude2 = __esm({
11177
11397
  return null;
11178
11398
  let head;
11179
11399
  try {
11180
- const fd = openSync5(located.path, "r");
11400
+ const fd = openSync6(located.path, "r");
11181
11401
  try {
11182
11402
  const buf = Buffer.alloc(65536);
11183
- const n = readSync5(fd, buf, 0, buf.length, 0);
11403
+ const n = readSync6(fd, buf, 0, buf.length, 0);
11184
11404
  head = buf.toString("utf-8", 0, n);
11185
11405
  } finally {
11186
- closeSync5(fd);
11406
+ closeSync6(fd);
11187
11407
  }
11188
11408
  } catch {
11189
11409
  return null;
@@ -11242,11 +11462,11 @@ var init_claude2 = __esm({
11242
11462
  if (filter && !proj.name.toLowerCase().includes(filter))
11243
11463
  continue;
11244
11464
  const projPath = join14(root, proj.name);
11245
- const projectPath = proj.name.replace(/-/g, "/").replace(/^\//, "/");
11465
+ const projectPath = decodeProjectDirName(proj.name);
11246
11466
  const indexPath = join14(projPath, "sessions-index.json");
11247
11467
  if (existsSync13(indexPath)) {
11248
11468
  try {
11249
- const indexData = JSON.parse(readFileSync9(indexPath, "utf-8"));
11469
+ const indexData = JSON.parse(readFileSync8(indexPath, "utf-8"));
11250
11470
  const entries = indexData.entries || [];
11251
11471
  for (const entry of entries) {
11252
11472
  if (!entry.fullPath || !existsSync13(entry.fullPath))
@@ -11395,6 +11615,15 @@ var init_claude2 = __esm({
11395
11615
  }
11396
11616
  const tsIso = typeof obj.timestamp === "string" ? obj.timestamp : void 0;
11397
11617
  const ts = tsIso ? Date.parse(tsIso) || mtime : mtime;
11618
+ if (obj.type === "queue-operation") {
11619
+ if (obj.operation === "enqueue" && typeof obj.content === "string") {
11620
+ const queued = stripReminders(obj.content);
11621
+ if (queued && !queued.startsWith("<task-notification")) {
11622
+ events.push({ kind: "user", ts, tsIso, line: lineNum, text: queued });
11623
+ }
11624
+ }
11625
+ continue;
11626
+ }
11398
11627
  if (obj.type === "user") {
11399
11628
  const msg = obj.message;
11400
11629
  const content = msg?.content;
@@ -11428,9 +11657,9 @@ var init_claude2 = __esm({
11428
11657
  }
11429
11658
  }
11430
11659
  }
11431
- if (text && !text.includes("<system-reminder>")) {
11432
- events.push({ kind: "user", ts, tsIso, line: lineNum, text });
11433
- }
11660
+ const spoken = stripReminders(text);
11661
+ if (spoken)
11662
+ events.push({ kind: "user", ts, tsIso, line: lineNum, text: spoken });
11434
11663
  for (const tr of toolResults) {
11435
11664
  events.push({
11436
11665
  kind: "tool_result",
@@ -11529,7 +11758,7 @@ var init_claude2 = __esm({
11529
11758
  * with quiet main + active subagents still surfaces.
11530
11759
  */
11531
11760
  collectRecentEdits(opts) {
11532
- const candidates = [];
11761
+ const candidates2 = [];
11533
11762
  for (const root of claudeProjectDirs()) {
11534
11763
  if (!existsSync13(root))
11535
11764
  continue;
@@ -11565,14 +11794,14 @@ var init_claude2 = __esm({
11565
11794
  }
11566
11795
  if (mtime < opts.sinceMs)
11567
11796
  continue;
11568
- candidates.push({ rawId: basename11(f, ".jsonl"), mtime });
11797
+ candidates2.push({ rawId: basename11(f, ".jsonl"), mtime });
11569
11798
  } catch {
11570
11799
  }
11571
11800
  }
11572
11801
  }
11573
11802
  }
11574
- candidates.sort((a, b) => b.mtime - a.mtime);
11575
- 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;
11576
11805
  const edits = [];
11577
11806
  for (const c of limited) {
11578
11807
  const scan = this.liveScanEdits(c.rawId);
@@ -11671,7 +11900,7 @@ __export(project_resolver_exports, {
11671
11900
  });
11672
11901
  import { execSync } from "child_process";
11673
11902
  import { createHash as createHash6 } from "crypto";
11674
- 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";
11675
11904
  import { basename as basename12, dirname as dirname12, isAbsolute, join as join15, resolve, sep } from "path";
11676
11905
  function getProjectsConfigPath() {
11677
11906
  return join15(getDataDir(), "projects.json");
@@ -11689,7 +11918,7 @@ function loadProjectsConfig() {
11689
11918
  if (cachedConfig && cachedConfig.mtime === mtime)
11690
11919
  return cachedConfig.cfg;
11691
11920
  try {
11692
- const raw = readFileSync10(path, "utf-8");
11921
+ const raw = readFileSync9(path, "utf-8");
11693
11922
  const cfg = JSON.parse(raw);
11694
11923
  cachedConfig = { mtime, cfg };
11695
11924
  return cfg;
@@ -11790,8 +12019,8 @@ function resolveBotWorktree(realPath) {
11790
12019
  };
11791
12020
  }
11792
12021
  function matchDeclaredProject(realPath, projects) {
11793
- const candidates = [...projects].sort((a, b) => b.root.length - a.root.length);
11794
- for (const proj of candidates) {
12022
+ const candidates2 = [...projects].sort((a, b) => b.root.length - a.root.length);
12023
+ for (const proj of candidates2) {
11795
12024
  const root = safeRealpath(proj.root);
11796
12025
  if (!isInside(realPath, root))
11797
12026
  continue;
@@ -12016,6 +12245,24 @@ var init_project_resolver = __esm({
12016
12245
  }
12017
12246
  });
12018
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
+
12019
12266
  // ../engine/dist/core/source-discovery.js
12020
12267
  import { createHash as createHash7 } from "crypto";
12021
12268
  import { existsSync as existsSync15, readdirSync as readdirSync11, statSync as statSync13 } from "fs";
@@ -12131,7 +12378,7 @@ __export(home_discovery_exports, {
12131
12378
  sessionIdsInHome: () => sessionIdsInHome
12132
12379
  });
12133
12380
  import { execFileSync as execFileSync2 } from "child_process";
12134
- 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";
12135
12382
  import { homedir as homedir5, platform as osPlatform } from "os";
12136
12383
  import { join as join17 } from "path";
12137
12384
  function candidateRoots(plat = osPlatform(), env = process.env) {
@@ -12319,7 +12566,7 @@ function declaredHomes(env = process.env, plat = osPlatform()) {
12319
12566
  for (const rc of rcs) {
12320
12567
  let text;
12321
12568
  try {
12322
- text = readFileSync11(rc, "utf-8");
12569
+ text = readFileSync10(rc, "utf-8");
12323
12570
  } catch {
12324
12571
  continue;
12325
12572
  }
@@ -12344,7 +12591,7 @@ function runningToolHomes(plat = osPlatform()) {
12344
12591
  for (const pid of pids) {
12345
12592
  let env;
12346
12593
  try {
12347
- env = readFileSync11(`/proc/${pid}/environ`, "utf-8");
12594
+ env = readFileSync10(`/proc/${pid}/environ`, "utf-8");
12348
12595
  } catch {
12349
12596
  continue;
12350
12597
  }
@@ -12478,7 +12725,7 @@ __export(detector_install_exports, {
12478
12725
  });
12479
12726
  import { spawnSync } from "child_process";
12480
12727
  import { createHash as createHash8 } from "crypto";
12481
- 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";
12482
12729
  import { join as join18 } from "path";
12483
12730
  function managedDetectorPath(name) {
12484
12731
  return join18(getDetectorBinDir(), name + exeSuffix());
@@ -12487,11 +12734,11 @@ function receiptPath(name) {
12487
12734
  return join18(getDetectorBinDir(), `${name}.receipt.json`);
12488
12735
  }
12489
12736
  function sha256File(path) {
12490
- return createHash8("sha256").update(readFileSync12(path)).digest("hex");
12737
+ return createHash8("sha256").update(readFileSync11(path)).digest("hex");
12491
12738
  }
12492
12739
  function readReceipt(name) {
12493
12740
  try {
12494
- return JSON.parse(readFileSync12(receiptPath(name), "utf-8"));
12741
+ return JSON.parse(readFileSync11(receiptPath(name), "utf-8"));
12495
12742
  } catch {
12496
12743
  return null;
12497
12744
  }
@@ -12702,7 +12949,7 @@ __export(secret_scanner_exports, {
12702
12949
  scanTenantRules: () => scanTenantRules
12703
12950
  });
12704
12951
  import { spawnSync as spawnSync2 } from "child_process";
12705
- 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";
12706
12953
  import { tmpdir } from "os";
12707
12954
  import { join as join19 } from "path";
12708
12955
  function mask(s) {
@@ -12766,7 +13013,7 @@ function runGitleaks(binary, filePath) {
12766
13013
  return [];
12767
13014
  let parsed = [];
12768
13015
  try {
12769
- parsed = JSON.parse(readFileSync13(reportPath, "utf-8")) || [];
13016
+ parsed = JSON.parse(readFileSync12(reportPath, "utf-8")) || [];
12770
13017
  } catch {
12771
13018
  }
12772
13019
  try {
@@ -12830,7 +13077,7 @@ function runGitleaksDir(binary, dir) {
12830
13077
  }
12831
13078
  let parsed = [];
12832
13079
  try {
12833
- parsed = JSON.parse(readFileSync13(reportPath, "utf-8")) || [];
13080
+ parsed = JSON.parse(readFileSync12(reportPath, "utf-8")) || [];
12834
13081
  } catch {
12835
13082
  }
12836
13083
  try {
@@ -12921,7 +13168,7 @@ function scanFileForSecrets(filePath, opts = {}) {
12921
13168
  out.push({ detector: "trufflehog", ...f });
12922
13169
  if (opts.tenantRules && opts.tenantRules.length > 0) {
12923
13170
  try {
12924
- const text = readFileSync13(filePath, "utf-8");
13171
+ const text = readFileSync12(filePath, "utf-8");
12925
13172
  out.push(...scanTenantRules(text, opts.tenantRules));
12926
13173
  } catch {
12927
13174
  }
@@ -12949,7 +13196,7 @@ async function scanSessionForSecrets(opts) {
12949
13196
  try {
12950
13197
  const tenantRules = (await opts.store.listSecretRules()).filter((r) => r.enabled).map((r) => ({ name: r.name, regex: r.regex, severity: r.severity }));
12951
13198
  if (tenantRules.length > 0) {
12952
- const text = readFileSync13(opts.filePath, "utf-8");
13199
+ const text = readFileSync12(opts.filePath, "utf-8");
12953
13200
  const lines = text.split("\n");
12954
13201
  for (const r of tenantRules) {
12955
13202
  let re;
@@ -13107,10 +13354,10 @@ var init_types = __esm({
13107
13354
 
13108
13355
  // ../engine/dist/transcript/opencode.js
13109
13356
  async function parseOpenCodeSubagents(parentSessionId) {
13110
- const { existsSync: existsSync50 } = await import("fs");
13357
+ const { existsSync: existsSync52 } = await import("fs");
13111
13358
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
13112
13359
  const dbPath = opencodeBackend2.dbPath();
13113
- if (!existsSync50(dbPath))
13360
+ if (!existsSync52(dbPath))
13114
13361
  return [];
13115
13362
  const realParent = parentSessionId.replace("opencode_", "");
13116
13363
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -13151,10 +13398,10 @@ async function parseOpenCodeSubagents(parentSessionId) {
13151
13398
  }
13152
13399
  }
13153
13400
  async function parseOpenCodeTranscript(sessionId) {
13154
- const { existsSync: existsSync50 } = await import("fs");
13401
+ const { existsSync: existsSync52 } = await import("fs");
13155
13402
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
13156
13403
  const dbPath = opencodeBackend2.dbPath();
13157
- if (!existsSync50(dbPath))
13404
+ if (!existsSync52(dbPath))
13158
13405
  return [];
13159
13406
  const realId = sessionId.replace("opencode_", "");
13160
13407
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -14195,7 +14442,7 @@ var init_utils = __esm({
14195
14442
  });
14196
14443
 
14197
14444
  // ../engine/dist/parsers/plan-source.js
14198
- 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";
14199
14446
  import { homedir as homedir7 } from "os";
14200
14447
  import { join as join21, basename as basename13 } from "path";
14201
14448
  import { createHash as createHash9 } from "crypto";
@@ -14237,6 +14484,7 @@ var init_plan_source = __esm({
14237
14484
  init_claude2();
14238
14485
  init_agy();
14239
14486
  init_settings();
14487
+ init_project_dir_name();
14240
14488
  MAX_CHUNK_CHARS = 2e3;
14241
14489
  UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14242
14490
  PlanSource = class {
@@ -14261,7 +14509,7 @@ var init_plan_source = __esm({
14261
14509
  for (const entry of entries) {
14262
14510
  if (!entry.isDirectory() || entry.name.startsWith("."))
14263
14511
  continue;
14264
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
14512
+ const projectPath = decodeProjectDirName(entry.name);
14265
14513
  if (existsSync20(projectPath)) {
14266
14514
  this._knownProjectDirs.push(projectPath);
14267
14515
  }
@@ -14299,7 +14547,7 @@ var init_plan_source = __esm({
14299
14547
  const stat3 = statSync18(filePath);
14300
14548
  const planName = basename13(file, ".md");
14301
14549
  const isAgentPlan = planName.includes("-agent-");
14302
- const content = readFileSync14(filePath, "utf-8");
14550
+ const content = readFileSync13(filePath, "utf-8");
14303
14551
  const fm = parseFrontmatter(content);
14304
14552
  const body = fm.body;
14305
14553
  const firstLine = body.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || planName;
@@ -14341,7 +14589,7 @@ var init_plan_source = __esm({
14341
14589
  const filePath = join21(plansDir, file);
14342
14590
  try {
14343
14591
  const stat3 = statSync18(filePath);
14344
- const content = readFileSync14(filePath, "utf-8");
14592
+ const content = readFileSync13(filePath, "utf-8");
14345
14593
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
14346
14594
  const planName = `${opencodeBackend.idPrefix}plan_${basename13(file, ".md")}`;
14347
14595
  const projectPath = this.extractProjectPath(content);
@@ -14371,7 +14619,7 @@ var init_plan_source = __esm({
14371
14619
  const projectsPath = geminiBackend.projectsJson();
14372
14620
  if (existsSync20(projectsPath)) {
14373
14621
  try {
14374
- const data = JSON.parse(readFileSync14(projectsPath, "utf-8"));
14622
+ const data = JSON.parse(readFileSync13(projectsPath, "utf-8"));
14375
14623
  for (const path of Object.keys(data.projects || {})) {
14376
14624
  projMap.set(createHash9("sha256").update(path).digest("hex"), path);
14377
14625
  }
@@ -14407,7 +14655,7 @@ var init_plan_source = __esm({
14407
14655
  const filePath = join21(plansDir, file);
14408
14656
  try {
14409
14657
  const stat3 = statSync18(filePath);
14410
- const content = readFileSync14(filePath, "utf-8");
14658
+ const content = readFileSync13(filePath, "utf-8");
14411
14659
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
14412
14660
  const planName = `${geminiBackend.idPrefix}plan_${sess}_${basename13(file, ".md")}`;
14413
14661
  yield {
@@ -14486,7 +14734,7 @@ var init_plan_source = __esm({
14486
14734
  async parse(item) {
14487
14735
  if (!existsSync20(item.filePath))
14488
14736
  return [];
14489
- const content = readFileSync14(item.filePath, "utf-8");
14737
+ const content = readFileSync13(item.filePath, "utf-8");
14490
14738
  const chunks = [];
14491
14739
  const { body } = parseFrontmatter(content);
14492
14740
  const sections = splitByHeaders(body);
@@ -14630,7 +14878,7 @@ var init_plan_source = __esm({
14630
14878
  const filePath = join21(sessionPath, file);
14631
14879
  try {
14632
14880
  const stat3 = statSync18(filePath);
14633
- const content = readFileSync14(filePath, "utf-8");
14881
+ const content = readFileSync13(filePath, "utf-8");
14634
14882
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
14635
14883
  const sessionLoc = agyBackend.findSession(rawId);
14636
14884
  const projectPath = sessionLoc?.projectPath || this.extractProjectPath(content);
@@ -14659,7 +14907,7 @@ var init_plan_source = __esm({
14659
14907
  });
14660
14908
 
14661
14909
  // ../engine/dist/parsers/task-source.js
14662
- 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";
14663
14911
  import { join as join22 } from "path";
14664
14912
  var MAX_CHUNK_CHARS2, TaskSource;
14665
14913
  var init_task_source = __esm({
@@ -14709,7 +14957,7 @@ var init_task_source = __esm({
14709
14957
  continue;
14710
14958
  let parsed;
14711
14959
  try {
14712
- const raw = readFileSync15(filePath, "utf-8");
14960
+ const raw = readFileSync14(filePath, "utf-8");
14713
14961
  parsed = JSON.parse(raw);
14714
14962
  if (!Array.isArray(parsed) || parsed.length === 0)
14715
14963
  continue;
@@ -14847,7 +15095,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14847
15095
  return [];
14848
15096
  let parsed;
14849
15097
  try {
14850
- parsed = JSON.parse(readFileSync15(item.filePath, "utf-8"));
15098
+ parsed = JSON.parse(readFileSync14(item.filePath, "utf-8"));
14851
15099
  } catch {
14852
15100
  return [];
14853
15101
  }
@@ -14879,7 +15127,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14879
15127
  });
14880
15128
  for (const file of sorted) {
14881
15129
  try {
14882
- const content = readFileSync15(join22(taskDir, file), "utf-8");
15130
+ const content = readFileSync14(join22(taskDir, file), "utf-8");
14883
15131
  const task = JSON.parse(content);
14884
15132
  if (task.subject) {
14885
15133
  tasks2.push(task);
@@ -14895,7 +15143,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14895
15143
  });
14896
15144
 
14897
15145
  // ../engine/dist/parsers/claude-md-source.js
14898
- 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";
14899
15147
  import { homedir as homedir8 } from "os";
14900
15148
  import { join as join23, basename as basename14, dirname as dirname13 } from "path";
14901
15149
  import { createHash as createHash10 } from "crypto";
@@ -14916,7 +15164,7 @@ function discoverProjectDirs(claudeDir) {
14916
15164
  for (const entry of entries) {
14917
15165
  if (!entry.isDirectory() || entry.name.startsWith("."))
14918
15166
  continue;
14919
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
15167
+ const projectPath = decodeProjectDirName(entry.name);
14920
15168
  addProject(projectPath);
14921
15169
  }
14922
15170
  } catch {
@@ -14939,7 +15187,7 @@ function discoverProjectDirs(claudeDir) {
14939
15187
  const gemProjects = geminiBackend.projectsJson();
14940
15188
  if (existsSync22(gemProjects)) {
14941
15189
  try {
14942
- const data = JSON.parse(readFileSync16(gemProjects, "utf-8"));
15190
+ const data = JSON.parse(readFileSync15(gemProjects, "utf-8"));
14943
15191
  for (const path of Object.keys(data.projects || {}))
14944
15192
  addProject(path);
14945
15193
  } catch {
@@ -14973,6 +15221,7 @@ var init_claude_md_source = __esm({
14973
15221
  init_gemini2();
14974
15222
  init_settings();
14975
15223
  init_opencode();
15224
+ init_project_dir_name();
14976
15225
  MAX_CHUNK_CHARS3 = 2e3;
14977
15226
  NOTE_FILES = [
14978
15227
  { filename: "CLAUDE.md", tool: "claude" },
@@ -15053,7 +15302,7 @@ var init_claude_md_source = __esm({
15053
15302
  async parse(item) {
15054
15303
  if (!existsSync22(item.filePath))
15055
15304
  return [];
15056
- const content = readFileSync16(item.filePath, "utf-8");
15305
+ const content = readFileSync15(item.filePath, "utf-8");
15057
15306
  const chunks = [];
15058
15307
  const sections = splitByHeaders(content);
15059
15308
  for (let i = 0; i < sections.length; i++) {
@@ -15095,7 +15344,7 @@ var init_claude_md_source = __esm({
15095
15344
  const projectDir = dirname13(filePath);
15096
15345
  const projectName = basename14(projectDir);
15097
15346
  const projectPath = projectDir;
15098
- const content = readFileSync16(filePath, "utf-8");
15347
+ const content = readFileSync15(filePath, "utf-8");
15099
15348
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim()?.slice(0, 200) || `${filename} for ${projectName}`;
15100
15349
  const id = tool === "claude" ? projectName : `${projectName}_${tool}`;
15101
15350
  return {
@@ -15120,7 +15369,7 @@ var init_claude_md_source = __esm({
15120
15369
  });
15121
15370
 
15122
15371
  // ../engine/dist/parsers/agent-memory-source.js
15123
- 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";
15124
15373
  import { homedir as homedir9 } from "os";
15125
15374
  import { join as join24, basename as basename15 } from "path";
15126
15375
  function parseFrontmatter2(content) {
@@ -15148,7 +15397,7 @@ function parseFrontmatter2(content) {
15148
15397
  return { fm, body };
15149
15398
  }
15150
15399
  function projectHashToPath(hashDir) {
15151
- return hashDir.replace(/^-/, "/").replace(/-/g, "/");
15400
+ return decodeProjectDirName(hashDir);
15152
15401
  }
15153
15402
  function discoverMemoryDirs() {
15154
15403
  const out = [];
@@ -15190,6 +15439,7 @@ var init_agent_memory_source = __esm({
15190
15439
  init_utils();
15191
15440
  init_claude2();
15192
15441
  init_settings();
15442
+ init_project_dir_name();
15193
15443
  MAX_CHUNK_CHARS4 = 2e3;
15194
15444
  MAX_CHUNKS = 12;
15195
15445
  AgentMemorySource = class {
@@ -15220,7 +15470,7 @@ var init_agent_memory_source = __esm({
15220
15470
  async parse(item) {
15221
15471
  if (!existsSync23(item.filePath))
15222
15472
  return [];
15223
- const content = readFileSync17(item.filePath, "utf-8");
15473
+ const content = readFileSync16(item.filePath, "utf-8");
15224
15474
  const { body } = parseFrontmatter2(content);
15225
15475
  const chunks = [];
15226
15476
  const sections = splitByHeaders(body);
@@ -15273,7 +15523,7 @@ var init_agent_memory_source = __esm({
15273
15523
  fileToItem(filePath, filename, projectPath, _projectHash) {
15274
15524
  try {
15275
15525
  const stat3 = statSync21(filePath);
15276
- const content = readFileSync17(filePath, "utf-8");
15526
+ const content = readFileSync16(filePath, "utf-8");
15277
15527
  const { fm } = parseFrontmatter2(content);
15278
15528
  const stem = filename.replace(/\.md$/, "");
15279
15529
  const idBase = fm.name || stem;
@@ -15488,7 +15738,7 @@ var init_history_source = __esm({
15488
15738
  });
15489
15739
 
15490
15740
  // ../engine/dist/parsers/paste-source.js
15491
- 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";
15492
15742
  import { homedir as homedir11 } from "os";
15493
15743
  import { join as join26, basename as basename16 } from "path";
15494
15744
  var MAX_CHUNK_CHARS6, MAX_FILE_SIZE, PasteSource;
@@ -15544,7 +15794,7 @@ var init_paste_source = __esm({
15544
15794
  if (stat3.size < 10)
15545
15795
  continue;
15546
15796
  const hash = basename16(file, ".txt");
15547
- const content = readFileSync18(filePath, "utf-8");
15797
+ const content = readFileSync17(filePath, "utf-8");
15548
15798
  const firstLine = content.split("\n").find((l) => l.trim().length > 5)?.trim()?.slice(0, 100) || `Paste ${hash.slice(0, 8)}`;
15549
15799
  yield {
15550
15800
  id: hash,
@@ -15565,7 +15815,7 @@ var init_paste_source = __esm({
15565
15815
  async parse(item) {
15566
15816
  if (!existsSync25(item.filePath))
15567
15817
  return [];
15568
- const content = readFileSync18(item.filePath, "utf-8");
15818
+ const content = readFileSync17(item.filePath, "utf-8");
15569
15819
  if (content.trim().length < 20)
15570
15820
  return [];
15571
15821
  return [{
@@ -15588,7 +15838,7 @@ var init_paste_source = __esm({
15588
15838
  });
15589
15839
 
15590
15840
  // ../engine/dist/parsers/gemini-source.js
15591
- 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";
15592
15842
  import { join as join27, basename as basename17 } from "path";
15593
15843
  import { createHash as createHash11 } from "crypto";
15594
15844
  function loadGeminiProjectMap2() {
@@ -15597,7 +15847,7 @@ function loadGeminiProjectMap2() {
15597
15847
  if (!existsSync26(projectsPath))
15598
15848
  return map;
15599
15849
  try {
15600
- const data = JSON.parse(readFileSync19(projectsPath, "utf-8"));
15850
+ const data = JSON.parse(readFileSync18(projectsPath, "utf-8"));
15601
15851
  const projects = data.projects || {};
15602
15852
  for (const [path] of Object.entries(projects)) {
15603
15853
  const hash = createHash11("sha256").update(path).digest("hex");
@@ -15642,7 +15892,7 @@ var init_gemini_source = __esm({
15642
15892
  const filePath = join27(chatsDir, file);
15643
15893
  try {
15644
15894
  const stat3 = statSync24(filePath);
15645
- const content = JSON.parse(readFileSync19(filePath, "utf-8"));
15895
+ const content = JSON.parse(readFileSync18(filePath, "utf-8"));
15646
15896
  const sessionId = content.sessionId || basename17(file, ".json");
15647
15897
  if (seen.has(sessionId))
15648
15898
  continue;
@@ -15723,7 +15973,7 @@ var init_gemini_source = __esm({
15723
15973
  async parse(item) {
15724
15974
  if (!existsSync26(item.filePath))
15725
15975
  return [];
15726
- const content = JSON.parse(readFileSync19(item.filePath, "utf-8"));
15976
+ const content = JSON.parse(readFileSync18(item.filePath, "utf-8"));
15727
15977
  const messages = content.messages || [];
15728
15978
  const chunks = [];
15729
15979
  const getText = (m) => {
@@ -15771,7 +16021,7 @@ var init_gemini_source = __esm({
15771
16021
  });
15772
16022
 
15773
16023
  // ../engine/dist/parsers/gemini-brain-source.js
15774
- 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";
15775
16025
  import { join as join28 } from "path";
15776
16026
  var MAX_CHUNK_CHARS8, GeminiBrainSource;
15777
16027
  var init_gemini_brain_source = __esm({
@@ -15804,14 +16054,14 @@ var init_gemini_brain_source = __esm({
15804
16054
  const filePath = join28(sessionDir, file);
15805
16055
  try {
15806
16056
  const stat3 = statSync25(filePath);
15807
- const content = readFileSync20(filePath, "utf-8");
16057
+ const content = readFileSync19(filePath, "utf-8");
15808
16058
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim() || "";
15809
16059
  const heading = content.split("\n").find((l) => l.startsWith("#"))?.replace(/^#+\s*/, "").trim() || file;
15810
16060
  const metaPath = `${filePath}.metadata.json`;
15811
16061
  let metaSummary = "";
15812
16062
  if (existsSync27(metaPath)) {
15813
16063
  try {
15814
- const meta = JSON.parse(readFileSync20(metaPath, "utf-8"));
16064
+ const meta = JSON.parse(readFileSync19(metaPath, "utf-8"));
15815
16065
  metaSummary = meta.summary || "";
15816
16066
  } catch {
15817
16067
  }
@@ -15842,7 +16092,7 @@ var init_gemini_brain_source = __esm({
15842
16092
  async parse(item) {
15843
16093
  if (!existsSync27(item.filePath))
15844
16094
  return [];
15845
- const content = readFileSync20(item.filePath, "utf-8");
16095
+ const content = readFileSync19(item.filePath, "utf-8");
15846
16096
  const chunks = [];
15847
16097
  const sections = splitByHeaders(content);
15848
16098
  for (let i = 0; i < sections.length; i++) {
@@ -16185,7 +16435,7 @@ var init_opencode_todo_source = __esm({
16185
16435
  });
16186
16436
 
16187
16437
  // ../engine/dist/parsers/codex-session-source.js
16188
- 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";
16189
16439
  import { join as join29, basename as basename18 } from "path";
16190
16440
  function getCodexSessionsDir() {
16191
16441
  return codexBackend.sessionsDir();
@@ -16213,7 +16463,7 @@ function extractReasoningText(event) {
16213
16463
  return "";
16214
16464
  }
16215
16465
  function readJsonlLines(filePath) {
16216
- const text = readFileSync21(filePath, "utf-8");
16466
+ const text = readFileSync20(filePath, "utf-8");
16217
16467
  const lines = text.split("\n");
16218
16468
  const events = [];
16219
16469
  for (const line of lines) {
@@ -16248,7 +16498,7 @@ var init_codex_session_source = __esm({
16248
16498
  for (const filePath of jsonlFiles) {
16249
16499
  try {
16250
16500
  const stat3 = statSync26(filePath);
16251
- const lines = readFileSync21(filePath, "utf-8").split("\n");
16501
+ const lines = readFileSync20(filePath, "utf-8").split("\n");
16252
16502
  const firstLine = lines.find((l) => l.trim());
16253
16503
  if (!firstLine)
16254
16504
  continue;
@@ -16421,7 +16671,7 @@ var init_codex_session_source = __esm({
16421
16671
  });
16422
16672
 
16423
16673
  // ../engine/dist/parsers/diary-source.js
16424
- 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";
16425
16675
  import { join as join30, basename as basename19 } from "path";
16426
16676
  var MAX_CHUNK_CHARS11, DiarySource;
16427
16677
  var init_diary_source = __esm({
@@ -16447,7 +16697,7 @@ var init_diary_source = __esm({
16447
16697
  const filePath = join30(agentPath2, file);
16448
16698
  try {
16449
16699
  const stat3 = statSync27(filePath);
16450
- const raw = readFileSync22(filePath, "utf-8");
16700
+ const raw = readFileSync21(filePath, "utf-8");
16451
16701
  const entry = JSON.parse(raw);
16452
16702
  const entryId = basename19(file, ".json");
16453
16703
  const title = `[${entry.agent}] ${entry.topic}: ${entry.content.slice(0, 80)}`;
@@ -16474,7 +16724,7 @@ var init_diary_source = __esm({
16474
16724
  async parse(item) {
16475
16725
  if (!existsSync31(item.filePath))
16476
16726
  return [];
16477
- const raw = readFileSync22(item.filePath, "utf-8");
16727
+ const raw = readFileSync21(item.filePath, "utf-8");
16478
16728
  const entry = JSON.parse(raw);
16479
16729
  if (!entry.content || entry.content.trim().length < 10)
16480
16730
  return [];
@@ -16537,7 +16787,7 @@ var init_diary_source = __esm({
16537
16787
  const entries = [];
16538
16788
  for (const file of files) {
16539
16789
  try {
16540
- const raw = readFileSync22(join30(agentDir, file), "utf-8");
16790
+ const raw = readFileSync21(join30(agentDir, file), "utf-8");
16541
16791
  entries.push(JSON.parse(raw));
16542
16792
  } catch {
16543
16793
  continue;
@@ -16550,7 +16800,7 @@ var init_diary_source = __esm({
16550
16800
  });
16551
16801
 
16552
16802
  // ../engine/dist/parsers/skills-source.js
16553
- 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";
16554
16804
  import { homedir as homedir12 } from "os";
16555
16805
  import { join as join31 } from "path";
16556
16806
  function resolveRoots() {
@@ -16643,7 +16893,7 @@ var init_skills_source = __esm({
16643
16893
  continue;
16644
16894
  let content;
16645
16895
  try {
16646
- content = readFileSync23(skillMd, "utf-8");
16896
+ content = readFileSync22(skillMd, "utf-8");
16647
16897
  } catch {
16648
16898
  continue;
16649
16899
  }
@@ -16703,7 +16953,7 @@ var init_skills_source = __esm({
16703
16953
  async parse(item) {
16704
16954
  if (!existsSync32(item.filePath))
16705
16955
  return [];
16706
- const content = readFileSync23(item.filePath, "utf-8");
16956
+ const content = readFileSync22(item.filePath, "utf-8");
16707
16957
  if (!content.trim())
16708
16958
  return [];
16709
16959
  return [{
@@ -16726,14 +16976,14 @@ var init_skills_source = __esm({
16726
16976
  });
16727
16977
 
16728
16978
  // ../engine/dist/parsers/mcps-source.js
16729
- 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";
16730
16980
  import { homedir as homedir13 } from "os";
16731
16981
  import { join as join32 } from "path";
16732
16982
  function readJson(path) {
16733
16983
  if (!existsSync33(path))
16734
16984
  return null;
16735
16985
  try {
16736
- const raw = readFileSync24(path, "utf-8");
16986
+ const raw = readFileSync23(path, "utf-8");
16737
16987
  const stripped = path.endsWith(".jsonc") ? raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/,(\s*[}\]])/g, "$1") : raw;
16738
16988
  return JSON.parse(stripped);
16739
16989
  } catch {
@@ -16846,7 +17096,7 @@ var init_mcps_source = __esm({
16846
17096
  return;
16847
17097
  let content;
16848
17098
  try {
16849
- content = readFileSync24(path, "utf-8");
17099
+ content = readFileSync23(path, "utf-8");
16850
17100
  } catch {
16851
17101
  return;
16852
17102
  }
@@ -17015,7 +17265,7 @@ var init_toolkit_format = __esm({
17015
17265
  });
17016
17266
 
17017
17267
  // ../engine/dist/parsers/slash-commands-source.js
17018
- 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";
17019
17269
  import { join as join33, basename as basename20 } from "path";
17020
17270
  function parseCommand(content, fallbackName, format) {
17021
17271
  if (format === "toml") {
@@ -17043,6 +17293,7 @@ var init_slash_commands_source = __esm({
17043
17293
  init_codex2();
17044
17294
  init_settings();
17045
17295
  init_toolkit_format();
17296
+ init_project_dir_name();
17046
17297
  MAX_CHUNK_CHARS13 = 2e3;
17047
17298
  SlashCommandsSource = class {
17048
17299
  sourceType = "command";
@@ -17056,7 +17307,7 @@ var init_slash_commands_source = __esm({
17056
17307
  for (const entry of readdirSync26(projectsRoot, { withFileTypes: true })) {
17057
17308
  if (!entry.isDirectory() || entry.name.startsWith("."))
17058
17309
  continue;
17059
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
17310
+ const projectPath = decodeProjectDirName(entry.name);
17060
17311
  const cmdDir = join33(projectPath, ".claude", "commands");
17061
17312
  if (existsSync34(cmdDir))
17062
17313
  roots.push({ path: cmdDir, tool: "claude", scope: "project", projectPath, format: "md" });
@@ -17089,7 +17340,7 @@ var init_slash_commands_source = __esm({
17089
17340
  const filePath = join33(root.path, file);
17090
17341
  try {
17091
17342
  const stat3 = statSync30(filePath);
17092
- const content = readFileSync25(filePath, "utf-8");
17343
+ const content = readFileSync24(filePath, "utf-8");
17093
17344
  const parsed = parseCommand(content, basename20(file, ext), root.format);
17094
17345
  yield {
17095
17346
  id: `${root.tool}_cmd_${root.scope}_${parsed.name}`,
@@ -17115,7 +17366,7 @@ var init_slash_commands_source = __esm({
17115
17366
  async parse(item) {
17116
17367
  if (!existsSync34(item.filePath))
17117
17368
  return [];
17118
- const content = readFileSync25(item.filePath, "utf-8");
17369
+ const content = readFileSync24(item.filePath, "utf-8");
17119
17370
  if (!content.trim())
17120
17371
  return [];
17121
17372
  return [{
@@ -17138,7 +17389,7 @@ var init_slash_commands_source = __esm({
17138
17389
  });
17139
17390
 
17140
17391
  // ../engine/dist/parsers/subagents-source.js
17141
- 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";
17142
17393
  import { join as join34, basename as basename21 } from "path";
17143
17394
  function parseAgent(content, fallbackName, format) {
17144
17395
  if (format === "toml") {
@@ -17168,6 +17419,7 @@ var init_subagents_source = __esm({
17168
17419
  init_codex2();
17169
17420
  init_settings();
17170
17421
  init_toolkit_format();
17422
+ init_project_dir_name();
17171
17423
  MAX_CHUNK_CHARS14 = 2e3;
17172
17424
  SubagentsSource = class {
17173
17425
  sourceType = "agent";
@@ -17181,7 +17433,7 @@ var init_subagents_source = __esm({
17181
17433
  for (const entry of readdirSync27(projectsRoot, { withFileTypes: true })) {
17182
17434
  if (!entry.isDirectory() || entry.name.startsWith("."))
17183
17435
  continue;
17184
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
17436
+ const projectPath = decodeProjectDirName(entry.name);
17185
17437
  const agentDir = join34(projectPath, ".claude", "agents");
17186
17438
  if (existsSync35(agentDir))
17187
17439
  roots.push({ path: agentDir, tool: "claude", scope: "project", projectPath, format: "md" });
@@ -17214,7 +17466,7 @@ var init_subagents_source = __esm({
17214
17466
  const filePath = join34(root.path, file);
17215
17467
  try {
17216
17468
  const stat3 = statSync31(filePath);
17217
- const content = readFileSync26(filePath, "utf-8");
17469
+ const content = readFileSync25(filePath, "utf-8");
17218
17470
  const parsed = parseAgent(content, basename21(file, ext), root.format);
17219
17471
  yield {
17220
17472
  id: `${root.tool}_agent_${root.scope}_${parsed.name}`,
@@ -17241,7 +17493,7 @@ var init_subagents_source = __esm({
17241
17493
  async parse(item) {
17242
17494
  if (!existsSync35(item.filePath))
17243
17495
  return [];
17244
- const content = readFileSync26(item.filePath, "utf-8");
17496
+ const content = readFileSync25(item.filePath, "utf-8");
17245
17497
  if (!content.trim())
17246
17498
  return [];
17247
17499
  return [{
@@ -17264,13 +17516,13 @@ var init_subagents_source = __esm({
17264
17516
  });
17265
17517
 
17266
17518
  // ../engine/dist/parsers/hooks-source.js
17267
- 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";
17268
17520
  import { join as join35 } from "path";
17269
17521
  function readJson2(path) {
17270
17522
  if (!existsSync36(path))
17271
17523
  return null;
17272
17524
  try {
17273
- return JSON.parse(readFileSync27(path, "utf-8"));
17525
+ return JSON.parse(readFileSync26(path, "utf-8"));
17274
17526
  } catch {
17275
17527
  return null;
17276
17528
  }
@@ -17281,6 +17533,7 @@ var init_hooks_source = __esm({
17281
17533
  "use strict";
17282
17534
  init_claude2();
17283
17535
  init_settings();
17536
+ init_project_dir_name();
17284
17537
  HooksSource = class {
17285
17538
  sourceType = "hook";
17286
17539
  async *discover() {
@@ -17293,7 +17546,7 @@ var init_hooks_source = __esm({
17293
17546
  for (const entry of readdirSync28(projectsRoot, { withFileTypes: true })) {
17294
17547
  if (!entry.isDirectory() || entry.name.startsWith("."))
17295
17548
  continue;
17296
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
17549
+ const projectPath = decodeProjectDirName(entry.name);
17297
17550
  const settingsPath2 = join35(projectPath, ".claude", "settings.json");
17298
17551
  if (existsSync36(settingsPath2)) {
17299
17552
  yield* this.fromSettings(settingsPath2, "project", projectPath);
@@ -17386,14 +17639,14 @@ var init_hooks_source = __esm({
17386
17639
  });
17387
17640
 
17388
17641
  // ../engine/dist/parsers/plugins-source.js
17389
- 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";
17390
17643
  import { homedir as homedir14 } from "os";
17391
17644
  import { join as join36 } from "path";
17392
17645
  function readJson3(path) {
17393
17646
  if (!existsSync37(path))
17394
17647
  return null;
17395
17648
  try {
17396
- return JSON.parse(readFileSync28(path, "utf-8"));
17649
+ return JSON.parse(readFileSync27(path, "utf-8"));
17397
17650
  } catch {
17398
17651
  return null;
17399
17652
  }
@@ -17641,14 +17894,16 @@ var init_extractor_version = __esm({
17641
17894
  "use strict";
17642
17895
  BASE_EXTRACTOR_VERSION = 2;
17643
17896
  TOOL_EXTRACTOR_BUMP = {
17644
- agy: 2
17897
+ agy: 2,
17898
+ claude: 1,
17899
+ codex: 1
17645
17900
  };
17646
17901
  EXTRACTOR_VERSION = BASE_EXTRACTOR_VERSION;
17647
17902
  }
17648
17903
  });
17649
17904
 
17650
17905
  // src/sync-ledger.ts
17651
- 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";
17652
17907
  import { join as join37, dirname as dirname14 } from "node:path";
17653
17908
  function toRow(entry) {
17654
17909
  if (entry === void 0) return void 0;
@@ -17660,24 +17915,77 @@ function ledgerFilePath() {
17660
17915
  function load() {
17661
17916
  if (cache3) return cache3;
17662
17917
  try {
17663
- const parsed = JSON.parse(readFileSync29(ledgerPath(), "utf-8"));
17918
+ const parsed = JSON.parse(readFileSync28(ledgerPath(), "utf-8"));
17664
17919
  cache3 = parsed && typeof parsed === "object" ? parsed : {};
17665
17920
  } catch {
17666
17921
  cache3 = {};
17667
17922
  }
17668
17923
  return cache3;
17669
17924
  }
17670
- function persist(data) {
17925
+ function persistNow(data) {
17671
17926
  const path = ledgerPath();
17672
17927
  mkdirSync6(dirname14(path), { recursive: true });
17673
17928
  const tmp = `${path}.tmp`;
17674
17929
  writeFileSync5(tmp, JSON.stringify(data));
17675
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);
17676
17984
  }
17677
17985
  function loadItemVersionsAll() {
17678
17986
  if (itemVersionsCache) return itemVersionsCache;
17679
17987
  try {
17680
- const parsed = JSON.parse(readFileSync29(itemVersionsPath(), "utf-8"));
17988
+ const parsed = JSON.parse(readFileSync28(itemVersionsPath(), "utf-8"));
17681
17989
  itemVersionsCache = parsed && typeof parsed === "object" ? parsed : {};
17682
17990
  } catch {
17683
17991
  itemVersionsCache = {};
@@ -17759,7 +18067,7 @@ function markFieldCoverage(server, field, rows) {
17759
18067
  function loadReconcile() {
17760
18068
  if (reconcileCache) return reconcileCache;
17761
18069
  try {
17762
- const parsed = JSON.parse(readFileSync29(reconcilePath(), "utf-8"));
18070
+ const parsed = JSON.parse(readFileSync28(reconcilePath(), "utf-8"));
17763
18071
  reconcileCache = parsed && typeof parsed === "object" ? parsed : {};
17764
18072
  } catch {
17765
18073
  reconcileCache = {};
@@ -17807,7 +18115,7 @@ function syncMode(row, mtime, fileSize, extractorVersion, isAppendOnly) {
17807
18115
  if (row.m >= Math.floor(mtime)) return "skip";
17808
18116
  return "full";
17809
18117
  }
17810
- var ledgerPath, cache3, itemVersionsPath, itemVersionsCache, unackedCursorAttempts, reconcilePath, reconcileCache;
18118
+ var ledgerPath, cache3, LEDGER_FLUSH_MS, pendingWrite, flushTimer, itemVersionsPath, itemVersionsCache, unackedCursorAttempts, reconcilePath, reconcileCache;
17811
18119
  var init_sync_ledger = __esm({
17812
18120
  "src/sync-ledger.ts"() {
17813
18121
  "use strict";
@@ -17815,6 +18123,9 @@ var init_sync_ledger = __esm({
17815
18123
  init_extractor_version();
17816
18124
  ledgerPath = () => join37(getDataDir(), "sync-ledger.json");
17817
18125
  cache3 = null;
18126
+ LEDGER_FLUSH_MS = 2e3;
18127
+ pendingWrite = false;
18128
+ flushTimer = null;
17818
18129
  itemVersionsPath = () => join37(getDataDir(), "item-versions.json");
17819
18130
  itemVersionsCache = null;
17820
18131
  unackedCursorAttempts = 0;
@@ -17849,7 +18160,7 @@ var init_sync_fields = __esm({
17849
18160
  });
17850
18161
 
17851
18162
  // ../engine/dist/core/index-lock.js
17852
- 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";
17853
18164
  import { dirname as dirname15, join as join38 } from "path";
17854
18165
  function acquireIndexLock(opts) {
17855
18166
  const lockPath = lockFilePath();
@@ -17861,7 +18172,7 @@ function acquireIndexLock(opts) {
17861
18172
  }
17862
18173
  let fd;
17863
18174
  try {
17864
- fd = openSync6(lockPath, "wx", 384);
18175
+ fd = openSync7(lockPath, "wx", 384);
17865
18176
  } catch (err) {
17866
18177
  if (err.code === "EEXIST")
17867
18178
  return null;
@@ -17871,7 +18182,7 @@ function acquireIndexLock(opts) {
17871
18182
  try {
17872
18183
  writeFileSync6(fd, JSON.stringify(holder));
17873
18184
  } finally {
17874
- closeSync6(fd);
18185
+ closeSync7(fd);
17875
18186
  }
17876
18187
  let released = false;
17877
18188
  return {
@@ -17881,7 +18192,7 @@ function acquireIndexLock(opts) {
17881
18192
  return;
17882
18193
  released = true;
17883
18194
  try {
17884
- const current = JSON.parse(readFileSync30(lockPath, "utf-8"));
18195
+ const current = JSON.parse(readFileSync29(lockPath, "utf-8"));
17885
18196
  if (current.pid !== holder.pid || current.startedAt !== holder.startedAt)
17886
18197
  return;
17887
18198
  } catch {
@@ -17903,7 +18214,7 @@ function stealStaleLock(lockPath, staleAfterMs) {
17903
18214
  const age = Date.now() - s.mtimeMs;
17904
18215
  let payload;
17905
18216
  try {
17906
- payload = JSON.parse(readFileSync30(lockPath, "utf-8"));
18217
+ payload = JSON.parse(readFileSync29(lockPath, "utf-8"));
17907
18218
  } catch {
17908
18219
  rmSync4(lockPath, { force: true });
17909
18220
  return true;
@@ -17947,7 +18258,7 @@ __export(http_exports, {
17947
18258
  clientHeaders: () => clientHeaders,
17948
18259
  fetchWithTimeout: () => fetchWithTimeout
17949
18260
  });
17950
- import { readFileSync as readFileSync31 } from "node:fs";
18261
+ import { readFileSync as readFileSync30 } from "node:fs";
17951
18262
  function cliVersion() {
17952
18263
  return CLI_VERSION;
17953
18264
  }
@@ -17968,10 +18279,10 @@ var init_http = __esm({
17968
18279
  "use strict";
17969
18280
  CLI_VERSION = "0.0.0";
17970
18281
  try {
17971
- 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;
17972
18283
  } catch {
17973
18284
  try {
17974
- if (true) CLI_VERSION = "0.5.4";
18285
+ if (true) CLI_VERSION = "0.5.6";
17975
18286
  } catch {
17976
18287
  }
17977
18288
  }
@@ -17984,7 +18295,7 @@ __export(client_events_exports, {
17984
18295
  isTelemetryEnabled: () => isTelemetryEnabled,
17985
18296
  reportClientEvent: () => reportClientEvent
17986
18297
  });
17987
- import { readFileSync as readFileSync32 } from "node:fs";
18298
+ import { readFileSync as readFileSync31 } from "node:fs";
17988
18299
  function scrub(msg) {
17989
18300
  if (!msg) return "";
17990
18301
  const noHome = msg.replace(/\/(?:home|Users)\/[^/\s]+/g, "~");
@@ -18037,7 +18348,7 @@ var init_client_events = __esm({
18037
18348
  init_secret_redactor();
18038
18349
  CLI_VERSION2 = "0.0.0";
18039
18350
  try {
18040
- 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;
18041
18352
  } catch {
18042
18353
  }
18043
18354
  }
@@ -18160,6 +18471,7 @@ var sync_client_exports = {};
18160
18471
  __export(sync_client_exports, {
18161
18472
  BATCH_SCAN_MAX_BYTES: () => BATCH_SCAN_MAX_BYTES,
18162
18473
  FULL_BUILD_MAX_BYTES: () => FULL_BUILD_MAX_BYTES,
18474
+ _resetCapsCacheForTests: () => _resetCapsCacheForTests,
18163
18475
  _resetTenantSecurityConfigCache: () => _resetTenantSecurityConfigCache,
18164
18476
  _resetTenantSyncConfigCache: () => _resetTenantSyncConfigCache,
18165
18477
  batchScanExternal: () => batchScanExternal,
@@ -18177,7 +18489,7 @@ __export(sync_client_exports, {
18177
18489
  syncSessions: () => syncSessions
18178
18490
  });
18179
18491
  import { join as join40, dirname as dirname16, basename as basename22 } from "node:path";
18180
- 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";
18181
18493
  import { tmpdir as tmpdir3, hostname } from "node:os";
18182
18494
  import { createHash as createHash13 } from "node:crypto";
18183
18495
  import { fileURLToPath } from "node:url";
@@ -18199,8 +18511,10 @@ function saveCredentials(c) {
18199
18511
  }
18200
18512
  }
18201
18513
  function loadAllCredentials() {
18514
+ const fromEnv = envCredentials();
18515
+ if (fromEnv) return [{ serverUrl: fromEnv.serverUrl, token: fromEnv.token }];
18202
18516
  try {
18203
- const parsed = JSON.parse(readFileSync33(credPath(), "utf-8"));
18517
+ const parsed = JSON.parse(readFileSync32(credPath(), "utf-8"));
18204
18518
  if (Array.isArray(parsed?.targets)) return parsed.targets.filter((t) => t?.serverUrl).map((t) => ({ serverUrl: t.serverUrl, token: t.token || "" }));
18205
18519
  if (parsed?.serverUrl) return [{ serverUrl: parsed.serverUrl, token: parsed.token || "" }];
18206
18520
  } catch {
@@ -18359,6 +18673,10 @@ function isLocalHost(serverUrl) {
18359
18673
  async function syncSessions(opts = {}) {
18360
18674
  const targets = loadAllCredentials();
18361
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
+ }
18362
18680
  let agg = null;
18363
18681
  const errors = [];
18364
18682
  for (const cred of targets) {
@@ -18381,6 +18699,7 @@ async function syncSessions(opts = {}) {
18381
18699
  errors.push(`${cred.serverUrl}: ${e instanceof Error ? e.message : e}`);
18382
18700
  }
18383
18701
  }
18702
+ flushLedger();
18384
18703
  if (!agg) throw new Error(`sync failed for all targets:
18385
18704
  ${errors.join("\n ")}`);
18386
18705
  if (errors.length > 0) console.error(`[sync] ${errors.length} target(s) failed (others succeeded):
@@ -18406,11 +18725,11 @@ async function reconcileFieldsForTarget(cred, fullRefs, opts = {}) {
18406
18725
  let pushed = 0, absent = 0, scanned = 0;
18407
18726
  for (const field of SYNC_FIELDS) {
18408
18727
  const needFull = !!opts.force || fieldNeedsFullPass(base2, field);
18409
- const candidates = needFull ? fullRefs() : opts.convRefs ?? fullRefs();
18728
+ const candidates2 = needFull ? fullRefs() : opts.convRefs ?? fullRefs();
18410
18729
  const send = [];
18411
18730
  const mtimeById = /* @__PURE__ */ new Map();
18412
18731
  const absentIds = [];
18413
- for (const ref of candidates) {
18732
+ for (const ref of candidates2) {
18414
18733
  if (!fieldNeedsScan(ledger.get(ref.prefixedId), field, ref.mtime, opts.force)) continue;
18415
18734
  scanned++;
18416
18735
  mtimeById.set(ref.prefixedId, ref.mtime);
@@ -18531,6 +18850,20 @@ function batchScanExternal(items, opts = {}) {
18531
18850
  }
18532
18851
  return { findings, scanned, slices, scanMs };
18533
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
+ }
18534
18867
  async function syncToTarget(cred, opts = {}) {
18535
18868
  const sync = loadSettings().sync;
18536
18869
  const serverCfg = await fetchTenantSyncConfig(cred);
@@ -18551,14 +18884,9 @@ async function syncToTarget(cred, opts = {}) {
18551
18884
  return isProjectSyncable(r.source === "ignored" ? "" : r.id, projectPath, { syncMode: "only", syncOnly: syncOnly2 });
18552
18885
  };
18553
18886
  const MIN_SERVER_API = 2;
18554
- try {
18555
- const caps = await fetchWithTimeout(`${cred.serverUrl.replace(/\/$/, "")}/api/capabilities`).then((r) => r.json());
18556
- if ((caps.apiVersion ?? 0) < MIN_SERVER_API) {
18557
- throw new Error(`server too old: apiVersion ${caps.apiVersion ?? "none"} < required ${MIN_SERVER_API} \u2014 update the server image before syncing`);
18558
- }
18559
- } catch (e) {
18560
- if (e instanceof Error && e.message.startsWith("server too old")) throw e;
18561
- 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`);
18562
18890
  }
18563
18891
  const { tenantRules, verifySecrets } = await fetchTenantSecurityConfig(cred);
18564
18892
  const CHANGED_WALK_OVERLAP_MS = 15 * 6e4;
@@ -18610,6 +18938,20 @@ async function syncToTarget(cred, opts = {}) {
18610
18938
  if (res.status === 401 || res.status === 403) {
18611
18939
  throw new Error(`server rejected this machine's device token (HTTP ${res.status} \u2014 revoked?). Reconnect with: chat-recall login ${base2}`);
18612
18940
  }
18941
+ if (res.status === 402) {
18942
+ let detail = text;
18943
+ try {
18944
+ const body2 = JSON.parse(text);
18945
+ detail = body2.error ?? text;
18946
+ if (body2.detail) detail += `
18947
+ ${body2.detail}`;
18948
+ if (body2.resetsAt) detail += ` (resets ${new Date(body2.resetsAt).toISOString().slice(0, 10)})`;
18949
+ if (body2.upgradeUrl) detail += `
18950
+ ${body2.upgradeUrl}`;
18951
+ } catch {
18952
+ }
18953
+ throw new Error(`sync paused by the server: ${detail}`);
18954
+ }
18613
18955
  if (!retryable) throw new Error(`sync failed: HTTP ${res.status} ${text}`);
18614
18956
  if (attempt >= RETRY_DELAYS_MS.length) throw new Error(`sync failed after ${attempt + 1} attempts: HTTP ${res.status} ${text}`);
18615
18957
  const retryAfterS = Number(res.headers.get("retry-after"));
@@ -18752,16 +19094,21 @@ async function syncToTarget(cred, opts = {}) {
18752
19094
  const findingsBatch = makeBatcher("findings", 200, true);
18753
19095
  const scanSecrets = isSecretScannerAvailable() && upload.findings && process.env.CHAT_RECALL_SCAN_SECRETS !== "0";
18754
19096
  const verifySecretsScan = verifySecrets;
19097
+ let skipped = 0, redactions = 0, walked = 0;
18755
19098
  const kgSeen = /* @__PURE__ */ new Set();
18756
19099
  const kgEntityType = /* @__PURE__ */ new Map();
18757
- const kgRows = [];
18758
- 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) => {
18759
19103
  for (const t of triples) {
18760
- const key = `${t.subject}|${t.predicate}|${t.object}|${t.validFrom ?? ""}`;
19104
+ const key = kgKey(t.subject, t.predicate, t.object, t.validFrom ?? "");
18761
19105
  if (kgSeen.has(key)) continue;
18762
19106
  kgSeen.add(key);
18763
19107
  if (t.predicate === "is_a" && !kgEntityType.has(t.subject)) kgEntityType.set(t.subject, t.object);
18764
- kgRows.push({
19108
+ kgEntityNames.add(t.subject);
19109
+ kgEntityNames.add(t.object);
19110
+ const count = { redactions: 0 };
19111
+ await kgTripleBatch.add(redactDeep({
18765
19112
  subject: t.subject,
18766
19113
  predicate: t.predicate,
18767
19114
  object: t.object,
@@ -18769,10 +19116,10 @@ async function syncToTarget(cred, opts = {}) {
18769
19116
  valid_to: null,
18770
19117
  confidence: t.confidence ?? 1,
18771
19118
  source_session: sourceSession
18772
- });
19119
+ }, count));
19120
+ redactions += count.redactions;
18773
19121
  }
18774
19122
  };
18775
- let skipped = 0, redactions = 0, walked = 0;
18776
19123
  let scanMs = 0, scanned = 0;
18777
19124
  const slice = refs.slice(0, opts.limit ?? refs.length);
18778
19125
  const trace = process.env.CHAT_RECALL_SYNC_TRACE === "1" ? (msg) => {
@@ -18905,7 +19252,7 @@ async function syncToTarget(cred, opts = {}) {
18905
19252
  await convBatch.add(built.conv);
18906
19253
  if (built.findings.length > 0) await findingsBatch.add(built.findings);
18907
19254
  if (upload.sessionMeta) {
18908
- 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);
18909
19256
  }
18910
19257
  if (upload.sessionMeta && sessionFileBytes(ref) <= FULL_BUILD_MAX_BYTES) {
18911
19258
  try {
@@ -18984,7 +19331,7 @@ async function syncToTarget(cred, opts = {}) {
18984
19331
  chunks: redactedChunks
18985
19332
  });
18986
19333
  if (upload.sessionMeta && redactedChunks.length > 0) {
18987
- accumulateKg(
19334
+ await accumulateKg(
18988
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 }),
18989
19336
  null
18990
19337
  );
@@ -19009,16 +19356,8 @@ async function syncToTarget(cred, opts = {}) {
19009
19356
  }
19010
19357
  }
19011
19358
  }
19012
- if (upload.sessionMeta && kgRows.length > 0) {
19013
- const entityNames = /* @__PURE__ */ new Set();
19014
- for (const t of kgRows) {
19015
- const count = { redactions: 0 };
19016
- await kgTripleBatch.add(redactDeep(t, count));
19017
- redactions += count.redactions;
19018
- entityNames.add(t.subject);
19019
- entityNames.add(t.object);
19020
- }
19021
- for (const name of entityNames) {
19359
+ if (upload.sessionMeta && kgEntityNames.size > 0) {
19360
+ for (const name of kgEntityNames) {
19022
19361
  const count = { redactions: 0 };
19023
19362
  await kgEntityBatch.add(redactDeep({ name, type: kgEntityType.get(name) ?? "unknown", properties: {} }, count));
19024
19363
  redactions += count.redactions;
@@ -19378,56 +19717,58 @@ function collectDerived(ref, mtime, count) {
19378
19717
  const id = ref.prefixedId;
19379
19718
  const compute = [];
19380
19719
  let fullOutcome = null;
19381
- for (const kind of COMPUTE_KINDS) {
19382
- let data = null;
19383
- try {
19384
- if (kind === "diff") {
19385
- const replay = replaySessionAny(id);
19386
- if (replay.found) data = replay;
19387
- } else if (kind === "outcome") {
19388
- fullOutcome = computeOutcome(id);
19389
- if (fullOutcome.found) data = fullOutcome;
19390
- } else if (kind === "commits") {
19391
- if (!fullOutcome) fullOutcome = computeOutcome(id);
19392
- if (fullOutcome.found) data = fullOutcome.commits;
19393
- } else if (kind === "markers") {
19394
- const turns = extractTurnsAny(id, { maxTurns: 5e4 });
19395
- 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) }));
19396
- data = { sessionId: id, prompts, summary: summarizeMarkers(prompts) };
19397
- }
19398
- } catch {
19399
- 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
+ };
19400
19764
  }
19401
- if (data === null) continue;
19402
- const redacted = redactDeep(data, count);
19403
- if (JSON.stringify(redacted).length > MAX_DERIVED_ROW_BYTES) continue;
19404
- compute.push({ kind, mtime, data: redacted });
19405
- }
19406
- let outcomeRow = null;
19407
- if (fullOutcome?.found) {
19408
- outcomeRow = {
19409
- sessionId: id,
19410
- tool: ref.toolId,
19411
- status: fullOutcome.status,
19412
- reason: fullOutcome.reason,
19413
- fileMtime: mtime,
19414
- fileSize: 0,
19415
- contentHash: "",
19416
- fileCount: fullOutcome.fileCount,
19417
- linesAdded: fullOutcome.totalLinesAdded,
19418
- linesRemoved: fullOutcome.totalLinesRemoved,
19419
- commits: fullOutcome.commits?.totalCommits ?? 0,
19420
- isFull: true,
19421
- classifiedAt: Date.now(),
19422
- lastScannedOffset: 0
19765
+ return {
19766
+ session_id: id,
19767
+ mtime,
19768
+ compute,
19769
+ outcome_row: outcomeRow ? redactDeep({ ...outcomeRow, sessionId: void 0 }, count) : null
19423
19770
  };
19424
- }
19425
- return {
19426
- session_id: id,
19427
- mtime,
19428
- compute,
19429
- outcome_row: outcomeRow ? redactDeep({ ...outcomeRow, sessionId: void 0 }, count) : null
19430
- };
19771
+ });
19431
19772
  }
19432
19773
  function isSyncSkip(r) {
19433
19774
  return typeof r.skipped === "string";
@@ -19456,10 +19797,11 @@ async function syncIncremental(opts = {}) {
19456
19797
  lock.release();
19457
19798
  }
19458
19799
  }
19459
- 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;
19460
19801
  var init_sync_client = __esm({
19461
19802
  "src/sync-client.ts"() {
19462
19803
  "use strict";
19804
+ init_credentials_env();
19463
19805
  init_secret_redactor();
19464
19806
  init_source_discovery();
19465
19807
  init_home_discovery();
@@ -19487,14 +19829,15 @@ var init_sync_client = __esm({
19487
19829
  init_sync_fields();
19488
19830
  init_index_lock();
19489
19831
  init_http();
19832
+ init_live_session_scan();
19490
19833
  init_backends();
19491
19834
  ownVersion = "0.0.0";
19492
19835
  try {
19493
19836
  const dir = dirname16(fileURLToPath(import.meta.url));
19494
- 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;
19495
19838
  } catch {
19496
19839
  try {
19497
- if (true) ownVersion = "0.5.4";
19840
+ if (true) ownVersion = "0.5.6";
19498
19841
  } catch {
19499
19842
  }
19500
19843
  }
@@ -19505,6 +19848,8 @@ var init_sync_client = __esm({
19505
19848
  hashPath = (p) => p ? "p_" + createHash13("sha256").update(p).digest("hex").slice(0, 12) : "";
19506
19849
  COMPUTE_KINDS = ["diff", "outcome", "commits", "markers"];
19507
19850
  MAX_DERIVED_ROW_BYTES = 2 * 1024 * 1024;
19851
+ CAPS_TTL_MS = 60 * 6e4;
19852
+ capsOk = /* @__PURE__ */ new Map();
19508
19853
  FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "64", 10)) * 1024 * 1024;
19509
19854
  FULL_BUILD_TAIL_BYTES = 16 * 1024 * 1024;
19510
19855
  BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
@@ -19518,11 +19863,11 @@ __export(update_notice_exports, {
19518
19863
  refreshUpdateCheck: () => refreshUpdateCheck,
19519
19864
  updateNotice: () => updateNotice
19520
19865
  });
19521
- import { readFileSync as readFileSync34, writeFileSync as writeFileSync9 } from "node:fs";
19866
+ import { readFileSync as readFileSync33, writeFileSync as writeFileSync9 } from "node:fs";
19522
19867
  import { join as join41 } from "node:path";
19523
19868
  function readCheck() {
19524
19869
  try {
19525
- const c = JSON.parse(readFileSync34(checkPath(), "utf-8"));
19870
+ const c = JSON.parse(readFileSync33(checkPath(), "utf-8"));
19526
19871
  return c && typeof c.serverVersion === "string" ? c : null;
19527
19872
  } catch {
19528
19873
  return null;
@@ -19598,6 +19943,282 @@ var init_update_notice = __esm({
19598
19943
  }
19599
19944
  });
19600
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
+
19601
20222
  // ../engine/dist/core/companions.js
19602
20223
  var companions_exports = {};
19603
20224
  __export(companions_exports, {
@@ -19606,27 +20227,39 @@ __export(companions_exports, {
19606
20227
  CODEINDEX_INSTALL_DIR: () => CODEINDEX_INSTALL_DIR,
19607
20228
  checkCodeindexStatus: () => checkCodeindexStatus,
19608
20229
  detectPlatform: () => detectPlatform,
20230
+ expectedCodeindexSha256: () => expectedCodeindexSha256,
19609
20231
  installCodeindex: () => installCodeindex,
19610
20232
  registerCodeindexMcp: () => registerCodeindexMcp,
19611
20233
  uninstallCodeindex: () => uninstallCodeindex,
19612
20234
  unregisterCodeindexMcp: () => unregisterCodeindexMcp
19613
20235
  });
19614
- 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";
19615
- import { homedir as homedir15 } from "node:os";
19616
- 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";
19617
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
+ }
19618
20244
  function detectPlatform() {
19619
20245
  const arch = process.arch;
19620
20246
  const platform = process.platform;
19621
20247
  const archMap = { x64: "x86_64", arm64: "aarch64" };
19622
- const platformMap = { linux: "linux", darwin: "darwin", win32: "windows" };
20248
+ const platformMap = { linux: "linux", darwin: "macos", win32: "windows" };
19623
20249
  const a = archMap[arch];
19624
20250
  const p = platformMap[platform];
19625
20251
  if (!a || !p) {
19626
20252
  return { reason: `Unsupported platform: ${arch}-${platform}` };
19627
20253
  }
19628
- const prebuiltCombos = /* @__PURE__ */ new Set(["x86_64-linux"]);
19629
- 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" : ""}`;
19630
20263
  if (!prebuiltCombos.has(`${a}-${p}`)) {
19631
20264
  return {
19632
20265
  artifact,
@@ -19643,20 +20276,15 @@ function checkCodeindexStatus() {
19643
20276
  artifactName: platform.artifact,
19644
20277
  unsupportedReason: platform.reason
19645
20278
  };
19646
- let foundPath = null;
19647
- try {
19648
- const out = execSync3("command -v codeindex", { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
19649
- if (out)
19650
- foundPath = out;
19651
- } catch {
20279
+ let foundPath = resolveOnPath(CODEINDEX_BIN_NAME);
20280
+ if (!foundPath) {
20281
+ foundPath = resolveOnPath(CODEINDEX_BIN_PATH);
19652
20282
  }
19653
- if (!foundPath && existsSync39(CODEINDEX_BIN_PATH))
19654
- foundPath = CODEINDEX_BIN_PATH;
19655
20283
  if (foundPath) {
19656
20284
  status.installed = true;
19657
20285
  status.path = foundPath;
19658
20286
  try {
19659
- status.size = statSync36(foundPath).size;
20287
+ status.size = statSync37(foundPath).size;
19660
20288
  } catch {
19661
20289
  }
19662
20290
  try {
@@ -19668,16 +20296,9 @@ function checkCodeindexStatus() {
19668
20296
  return status;
19669
20297
  }
19670
20298
  async function downloadArtifact(artifact, dest) {
19671
- const hasGh = (() => {
19672
- try {
19673
- execSync3("command -v gh", { stdio: "ignore" });
19674
- return true;
19675
- } catch {
19676
- return false;
19677
- }
19678
- })();
20299
+ const hasGh = isOnPath("gh");
19679
20300
  if (hasGh) {
19680
- 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}"`, {
19681
20302
  stdio: ["ignore", "pipe", "pipe"],
19682
20303
  timeout: 6e4
19683
20304
  });
@@ -19716,15 +20337,26 @@ async function installCodeindex(opts = {}) {
19716
20337
  return status;
19717
20338
  if (!status.prebuiltAvailable)
19718
20339
  return status;
19719
- mkdirSync9(CODEINDEX_INSTALL_DIR, { recursive: true });
20340
+ mkdirSync11(CODEINDEX_INSTALL_DIR, { recursive: true });
19720
20341
  const tmpPath = `${CODEINDEX_BIN_PATH}.partial`;
19721
20342
  try {
19722
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
+ }
19723
20355
  chmodSync4(tmpPath, 493);
19724
20356
  renameSync5(tmpPath, CODEINDEX_BIN_PATH);
19725
20357
  } catch (err) {
19726
20358
  try {
19727
- if (existsSync39(tmpPath))
20359
+ if (existsSync41(tmpPath))
19728
20360
  unlinkSync3(tmpPath);
19729
20361
  } catch {
19730
20362
  }
@@ -19733,7 +20365,7 @@ async function installCodeindex(opts = {}) {
19733
20365
  return checkCodeindexStatus();
19734
20366
  }
19735
20367
  function uninstallCodeindex() {
19736
- if (existsSync39(CODEINDEX_BIN_PATH)) {
20368
+ if (existsSync41(CODEINDEX_BIN_PATH)) {
19737
20369
  unlinkSync3(CODEINDEX_BIN_PATH);
19738
20370
  return { removed: true, path: CODEINDEX_BIN_PATH };
19739
20371
  }
@@ -19741,9 +20373,9 @@ function uninstallCodeindex() {
19741
20373
  }
19742
20374
  function registerCodeindexMcp(mcpJsonPath, binaryPath) {
19743
20375
  let cfg = {};
19744
- if (existsSync39(mcpJsonPath)) {
20376
+ if (existsSync41(mcpJsonPath)) {
19745
20377
  try {
19746
- cfg = JSON.parse(readFileSync35(mcpJsonPath, "utf-8"));
20378
+ cfg = JSON.parse(readFileSync36(mcpJsonPath, "utf-8"));
19747
20379
  } catch {
19748
20380
  cfg = {};
19749
20381
  }
@@ -19752,33 +20384,42 @@ function registerCodeindexMcp(mcpJsonPath, binaryPath) {
19752
20384
  if (cfg.mcpServers["codeindex"])
19753
20385
  return { added: false };
19754
20386
  cfg.mcpServers["codeindex"] = { command: binaryPath, args: ["--mcp"] };
19755
- writeFileSync10(mcpJsonPath, JSON.stringify(cfg, null, 2));
20387
+ writeFileSync12(mcpJsonPath, JSON.stringify(cfg, null, 2));
19756
20388
  return { added: true };
19757
20389
  }
19758
20390
  function unregisterCodeindexMcp(mcpJsonPath) {
19759
- if (!existsSync39(mcpJsonPath))
20391
+ if (!existsSync41(mcpJsonPath))
19760
20392
  return { removed: false };
19761
20393
  let cfg;
19762
20394
  try {
19763
- cfg = JSON.parse(readFileSync35(mcpJsonPath, "utf-8"));
20395
+ cfg = JSON.parse(readFileSync36(mcpJsonPath, "utf-8"));
19764
20396
  } catch {
19765
20397
  return { removed: false };
19766
20398
  }
19767
20399
  if (!cfg.mcpServers?.["codeindex"])
19768
20400
  return { removed: false };
19769
20401
  delete cfg.mcpServers["codeindex"];
19770
- writeFileSync10(mcpJsonPath, JSON.stringify(cfg, null, 2));
20402
+ writeFileSync12(mcpJsonPath, JSON.stringify(cfg, null, 2));
19771
20403
  return { removed: true };
19772
20404
  }
19773
- 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;
19774
20406
  var init_companions = __esm({
19775
20407
  "../engine/dist/core/companions.js"() {
19776
20408
  "use strict";
20409
+ init_which();
19777
20410
  CODEINDEX_REPO = "munhq/codeindex";
19778
- CODEINDEX_VERSION = "v0.1.0";
19779
- CODEINDEX_INSTALL_DIR = join42(homedir15(), ".local", "bin");
19780
- CODEINDEX_BIN_NAME = "codeindex";
19781
- 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);
19782
20423
  }
19783
20424
  });
19784
20425
 
@@ -19793,26 +20434,26 @@ __export(install_skills_exports, {
19793
20434
  skillsSourceDir: () => skillsSourceDir,
19794
20435
  uninstallSkills: () => uninstallSkills
19795
20436
  });
19796
- 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";
19797
- import { createHash as createHash14 } from "crypto";
19798
- 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";
19799
20440
  function skillsSourceDir() {
19800
- return join43(import.meta.dirname, "..", "skills");
20441
+ return join46(import.meta.dirname, "..", "skills");
19801
20442
  }
19802
20443
  function version() {
19803
20444
  try {
19804
- 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";
19805
20446
  } catch {
19806
20447
  return "0.0.0";
19807
20448
  }
19808
20449
  }
19809
20450
  function skillsFingerprint() {
19810
20451
  const src = skillsSourceDir();
19811
- const h = createHash14("sha256");
20452
+ const h = createHash15("sha256");
19812
20453
  for (const name of bundledSkillNames()) {
19813
- for (const rel of filesUnder(join43(src, name)).sort()) {
20454
+ for (const rel of filesUnder(join46(src, name)).sort()) {
19814
20455
  h.update(name + "/" + rel);
19815
- h.update(readFileSync36(join43(src, name, rel)));
20456
+ h.update(readFileSync37(join46(src, name, rel)));
19816
20457
  }
19817
20458
  }
19818
20459
  return h.digest("hex").slice(0, 12);
@@ -19822,7 +20463,7 @@ function filesUnder(dir, prefix = "") {
19822
20463
  for (const e of readdirSync31(dir, { withFileTypes: true })) {
19823
20464
  if (e.name === MARKER) continue;
19824
20465
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
19825
- if (e.isDirectory()) out.push(...filesUnder(join43(dir, e.name), rel));
20466
+ if (e.isDirectory()) out.push(...filesUnder(join46(dir, e.name), rel));
19826
20467
  else out.push(rel);
19827
20468
  }
19828
20469
  return out;
@@ -19832,8 +20473,8 @@ function skillStamp() {
19832
20473
  }
19833
20474
  function bundledSkillNames() {
19834
20475
  const src = skillsSourceDir();
19835
- if (!existsSync40(src)) return [];
19836
- 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();
19837
20478
  }
19838
20479
  function realPathOrSelf(p) {
19839
20480
  try {
@@ -19846,7 +20487,7 @@ function claudeSkillTargets() {
19846
20487
  const targets = [];
19847
20488
  const seen = /* @__PURE__ */ new Set();
19848
20489
  for (const home of claudeHomeDirs()) {
19849
- const dir = join43(home, "skills");
20490
+ const dir = join46(home, "skills");
19850
20491
  const key = realPathOrSelf(dir);
19851
20492
  if (seen.has(key)) continue;
19852
20493
  seen.add(key);
@@ -19857,7 +20498,7 @@ function claudeSkillTargets() {
19857
20498
  dir,
19858
20499
  // Judge each profile on ITS OWN transcripts, not the primary's: a home
19859
20500
  // with no projects/ is a directory someone made, not a Claude install.
19860
- available: existsSync40(join43(home, "projects"))
20501
+ available: existsSync42(join46(home, "projects"))
19861
20502
  });
19862
20503
  }
19863
20504
  if (targets.length === 0) {
@@ -19880,7 +20521,7 @@ function skillTargets() {
19880
20521
  ];
19881
20522
  }
19882
20523
  function isManaged(dir) {
19883
- return existsSync40(join43(dir, MARKER));
20524
+ return existsSync42(join46(dir, MARKER));
19884
20525
  }
19885
20526
  function installSkills(opts = {}) {
19886
20527
  const onlyAvailable = opts.onlyAvailable ?? true;
@@ -19897,15 +20538,15 @@ function installSkills(opts = {}) {
19897
20538
  continue;
19898
20539
  }
19899
20540
  for (const name of names) {
19900
- const dest = join43(t.dir, name);
19901
- if (existsSync40(dest) && !isManaged(dest)) {
20541
+ const dest = join46(t.dir, name);
20542
+ if (existsSync42(dest) && !isManaged(dest)) {
19902
20543
  skippedUserOwned.push(name);
19903
20544
  continue;
19904
20545
  }
19905
- mkdirSync10(t.dir, { recursive: true });
20546
+ mkdirSync12(t.dir, { recursive: true });
19906
20547
  rmSync6(dest, { recursive: true, force: true });
19907
- cpSync(join43(src, name), dest, { recursive: true });
19908
- writeFileSync11(join43(dest, MARKER), `${stamp}
20548
+ cpSync(join46(src, name), dest, { recursive: true });
20549
+ writeFileSync13(join46(dest, MARKER), `${stamp}
19909
20550
  `);
19910
20551
  installed.push(name);
19911
20552
  }
@@ -19918,13 +20559,13 @@ function uninstallSkills() {
19918
20559
  const perTarget = [];
19919
20560
  for (const t of skillTargets()) {
19920
20561
  const removed = [];
19921
- if (!existsSync40(t.dir)) {
20562
+ if (!existsSync42(t.dir)) {
19922
20563
  perTarget.push({ id: t.id, label: t.label, removed });
19923
20564
  continue;
19924
20565
  }
19925
20566
  for (const entry of readdirSync31(t.dir, { withFileTypes: true })) {
19926
20567
  if (!entry.isDirectory()) continue;
19927
- const dir = join43(t.dir, entry.name);
20568
+ const dir = join46(t.dir, entry.name);
19928
20569
  if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
19929
20570
  rmSync6(dir, { recursive: true, force: true });
19930
20571
  removed.push(entry.name);
@@ -19941,10 +20582,10 @@ function skillsNeedRefresh() {
19941
20582
  for (const t of skillTargets()) {
19942
20583
  if (!t.available) continue;
19943
20584
  for (const name of names) {
19944
- const marker = join43(t.dir, name, MARKER);
19945
- if (!existsSync40(marker)) return true;
20585
+ const marker = join46(t.dir, name, MARKER);
20586
+ if (!existsSync42(marker)) return true;
19946
20587
  try {
19947
- if (readFileSync36(marker, "utf-8").trim() !== cur) return true;
20588
+ if (readFileSync37(marker, "utf-8").trim() !== cur) return true;
19948
20589
  } catch {
19949
20590
  return true;
19950
20591
  }
@@ -19959,11 +20600,11 @@ function skillStatus() {
19959
20600
  let installed = 0;
19960
20601
  let ver = null;
19961
20602
  for (const name of names) {
19962
- const marker = join43(t.dir, name, MARKER);
19963
- if (existsSync40(marker)) {
20603
+ const marker = join46(t.dir, name, MARKER);
20604
+ if (existsSync42(marker)) {
19964
20605
  installed++;
19965
20606
  try {
19966
- ver = readFileSync36(marker, "utf-8").trim();
20607
+ ver = readFileSync37(marker, "utf-8").trim();
19967
20608
  } catch {
19968
20609
  }
19969
20610
  }
@@ -19992,7 +20633,7 @@ __export(repair_exports, {
19992
20633
  repairSession: () => repairSession,
19993
20634
  repairSessions: () => repairSessions
19994
20635
  });
19995
- import { createHash as createHash15 } from "node:crypto";
20636
+ import { createHash as createHash16 } from "node:crypto";
19996
20637
  function resolveEndpoints(only) {
19997
20638
  const settings2 = loadSettings();
19998
20639
  const creds = loadAllCredentials();
@@ -20035,11 +20676,11 @@ async function repairSession(id, opts = {}) {
20035
20676
  const log6 = (m) => {
20036
20677
  if (opts.verbose) console.error(` [repair ${id}] ${m}`);
20037
20678
  };
20038
- const candidates = [];
20679
+ const candidates2 = [];
20039
20680
  const localShadow = readShadowContainer(tool, rawId);
20040
20681
  if (localShadow) {
20041
20682
  try {
20042
- 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" });
20043
20684
  } catch {
20044
20685
  }
20045
20686
  }
@@ -20051,15 +20692,15 @@ async function repairSession(id, opts = {}) {
20051
20692
  if (arch?.gzB64) {
20052
20693
  try {
20053
20694
  const container = gunzipContainer(Buffer.from(arch.gzB64, "base64"));
20054
- 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 });
20055
20696
  } catch {
20056
20697
  }
20057
20698
  }
20058
20699
  }
20059
- if (candidates.length === 0) {
20700
+ if (candidates2.length === 0) {
20060
20701
  return { sessionId: id, status: "no-archive", fullestMessages: 0, fullestSource: "", pushed: [], note: "no shadow or server archive found" };
20061
20702
  }
20062
- 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);
20063
20704
  log6(`fullest archive = ${fullest.messages} msg(s) from ${fullest.source}`);
20064
20705
  if (opts.dryRun) {
20065
20706
  const needy2 = endpoints.filter((ep) => (currentOnServer.get(ep.url) ?? 0) < fullest.messages || opts.force);
@@ -20161,7 +20802,7 @@ async function repairAll(opts = {}) {
20161
20802
  if (!page?.hasMore || sessions.length === 0) break;
20162
20803
  }
20163
20804
  log6(`window=${sinceHours}h \u2192 ${ids.length} session(s) to check on ${primary.url}`);
20164
- const candidates = [];
20805
+ const candidates2 = [];
20165
20806
  let scanned = 0;
20166
20807
  for (const id of ids) {
20167
20808
  scanned++;
@@ -20174,9 +20815,9 @@ async function repairAll(opts = {}) {
20174
20815
  if (archiveMsgs <= current) continue;
20175
20816
  const r = await repairSession(id, { dryRun: !opts.apply, verbose: opts.verbose, server: opts.server });
20176
20817
  if (r.status === "already-full") continue;
20177
- candidates.push(r);
20818
+ candidates2.push(r);
20178
20819
  }
20179
- return { scanned, candidates, discoveryServer: primary.url };
20820
+ return { scanned, candidates: candidates2, discoveryServer: primary.url };
20180
20821
  }
20181
20822
  var hashPath2;
20182
20823
  var init_repair = __esm({
@@ -20188,7 +20829,7 @@ var init_repair = __esm({
20188
20829
  init_http();
20189
20830
  init_sync_client();
20190
20831
  init_sync_client();
20191
- 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) : "";
20192
20833
  }
20193
20834
  });
20194
20835
 
@@ -20408,11 +21049,11 @@ var verify_repair_exports = {};
20408
21049
  __export(verify_repair_exports, {
20409
21050
  forceFullResync: () => forceFullResync
20410
21051
  });
20411
- import { copyFileSync as copyFileSync3, existsSync as existsSync41 } from "fs";
21052
+ import { copyFileSync as copyFileSync3, existsSync as existsSync43 } from "fs";
20412
21053
  function forceFullResync(server, sessionIds) {
20413
21054
  if (sessionIds.length === 0) return 0;
20414
21055
  const path = ledgerFilePath();
20415
- if (existsSync41(path)) {
21056
+ if (existsSync43(path)) {
20416
21057
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace(/-/g, "");
20417
21058
  try {
20418
21059
  copyFileSync3(path, `${path}.bak-${stamp}`);
@@ -20550,15 +21191,17 @@ var init_prompts = __esm({
20550
21191
  var collector_exports = {};
20551
21192
  __export(collector_exports, {
20552
21193
  COLLECTOR_VERSION: () => COLLECTOR_VERSION,
21194
+ GENERATED_PATTERNS: () => GENERATED_PATTERNS,
20553
21195
  collectCode: () => collectCode,
21196
+ isGenerated: () => isGenerated,
20554
21197
  isJunkWorkspacePath: () => isJunkWorkspacePath,
20555
21198
  isSecretRule: () => isSecretRule,
20556
21199
  resolveCodeindexBin: () => resolveCodeindexBin
20557
21200
  });
20558
21201
  import { spawn } from "node:child_process";
20559
21202
  import { execSync as execSync4 } from "node:child_process";
20560
- import { readFileSync as readFileSync37 } from "node:fs";
20561
- 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";
20562
21205
  async function resolveCodeindexBin(autoInstall = true) {
20563
21206
  let st = checkCodeindexStatus();
20564
21207
  if (!st.installed && autoInstall)
@@ -20645,10 +21288,11 @@ function isSecretRule(rule) {
20645
21288
  return /secret|password|credential|key|token|auth/i.test(rule);
20646
21289
  }
20647
21290
  function isJunkWorkspacePath(ws) {
20648
- 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);
20649
21293
  }
20650
21294
  async function collectCode(opts) {
20651
- const ws = opts.workspace.replace(/\/+$/, "");
21295
+ const ws = opts.workspace.replace(/[/\\]+$/, "");
20652
21296
  const log6 = opts.log ?? (() => {
20653
21297
  });
20654
21298
  if (isJunkWorkspacePath(ws)) {
@@ -20656,9 +21300,11 @@ async function collectCode(opts) {
20656
21300
  }
20657
21301
  const bin = opts.binPath ?? await resolveCodeindexBin(opts.autoInstall ?? true);
20658
21302
  const projectId = resolveProjectId(ws).id || `path:${ws}`;
20659
- const wsPrefix = ws + "/";
21303
+ const wsNorm = ws.replace(/\\/g, "/");
21304
+ const wsPrefix = wsNorm + "/";
20660
21305
  const rel = (p) => {
20661
- 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;
20662
21308
  if (r.startsWith("./"))
20663
21309
  r = r.slice(2);
20664
21310
  return r;
@@ -20719,7 +21365,7 @@ async function collectCode(opts) {
20719
21365
  }
20720
21366
  const complexity = (r) => {
20721
21367
  try {
20722
- const txt = readFileSync37(join44(ws, r), "utf8");
21368
+ const txt = readFileSync38(join47(ws, r), "utf8");
20723
21369
  if (txt.length > 2e6)
20724
21370
  return 1;
20725
21371
  const m = txt.match(CF_RE);
@@ -21073,6 +21719,14 @@ async function collectCode(opts) {
21073
21719
  agentPrompt: manifestPrompt(v.violation_type ?? "", file, v.line ?? null, v.description ?? "")
21074
21720
  });
21075
21721
  }
21722
+ {
21723
+ const before = findings.length;
21724
+ const own = findings.filter((f) => !f.file || !isGenerated(f.file));
21725
+ if (own.length !== before) {
21726
+ findings.splice(0, before, ...own);
21727
+ log6(`dropped ${before - own.length} finding(s) in vendored/generated files`);
21728
+ }
21729
+ }
21076
21730
  const godSet = new Set((coup?.god_modules ?? []).map((m) => rel(m.file)));
21077
21731
  const cloneSet = /* @__PURE__ */ new Set();
21078
21732
  for (const g of clones?.groups ?? [])
@@ -21158,7 +21812,9 @@ async function collectCode(opts) {
21158
21812
  }
21159
21813
  for (const g of clones?.groups ?? []) {
21160
21814
  if ((g.count ?? 0) >= 3 || (g.lines ?? 0) >= 12) {
21161
- const fns = (g.functions ?? []).filter((x) => x && typeof x === "object");
21815
+ const fns = (g.functions ?? []).filter((x) => x && typeof x === "object").filter((x) => !isGenerated(rel(x.file)));
21816
+ if (fns.length < 2)
21817
+ continue;
21162
21818
  const names = [...new Set(fns.map((x) => x.name))];
21163
21819
  const locs = fns.map((x) => rel(x.file)).slice(0, 5);
21164
21820
  pushAction((g.lines ?? 0) * (g.count ?? 0) >= 150 ? 1 : 2, "duplication", `${names.join(", ")} copy-pasted ${g.count}\xD7 (${g.lines} lines each)`, "Extract one shared implementation and replace the copies.", locs.map((f) => ({ file: f })), clonePrompt(names, g.lines ?? 0, g.count ?? 0, locs));
@@ -21177,10 +21833,14 @@ async function collectCode(opts) {
21177
21833
  }
21178
21834
  for (const m of (coup?.god_modules ?? []).slice(0, 5)) {
21179
21835
  const file = rel(m.file);
21836
+ if (isGenerated(file))
21837
+ continue;
21180
21838
  pushAction(2, "modularity", `God module ${file} (fan-in ${m.fan_in}, fan-out ${m.fan_out})`, "Split into cohesive sub-modules; introduce interfaces to cut fan-out.", [{ file }], couplingPrompt(file, m.fan_in ?? 0, m.fan_out ?? 0, Math.round((m.instability ?? 0) * 100) / 100));
21181
21839
  }
21182
21840
  for (const c of (cyc?.cycles ?? []).slice(0, 5)) {
21183
- const chain = (c.files ?? []).map(rel);
21841
+ const chain = (c.files ?? []).map(rel).filter((f) => !isGenerated(f));
21842
+ if (chain.length < 2)
21843
+ continue;
21184
21844
  pushAction(2, "architecture", `Circular dependency (${chain.length} files)`, "Break the cycle: extract the shared type/interface into a separate module.", chain.slice(0, 4).map((f) => ({ file: f })), cyclePrompt(chain.slice(0, 6)));
21185
21845
  }
21186
21846
  for (const h of hotspotsAll.slice(0, 5)) {
@@ -21289,7 +21949,7 @@ async function collectCode(opts) {
21289
21949
  void health;
21290
21950
  return { project, findings, hotspots, actions };
21291
21951
  }
21292
- 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;
21293
21953
  var init_collector = __esm({
21294
21954
  "../engine/dist/core/code/collector.js"() {
21295
21955
  "use strict";
@@ -21319,11 +21979,42 @@ var init_collector = __esm({
21319
21979
  "migration_parity",
21320
21980
  "manifest_compliance"
21321
21981
  ];
21322
- GENERATED = ["generated/", ".pb.go", ".gen.", "_pb2.py", "/gen/", ".g.dart", "/node_modules/", "/vendor/", ".lock"];
21982
+ GENERATED = [
21983
+ "generated/",
21984
+ "__generated__/",
21985
+ ".pb.go",
21986
+ ".gen.",
21987
+ "_pb2.py",
21988
+ "/gen/",
21989
+ ".g.dart",
21990
+ "/node_modules/",
21991
+ "/vendor/",
21992
+ "/third_party/",
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.
21998
+ ".lock",
21999
+ "-lock.json",
22000
+ "-lock.yaml",
22001
+ "/dist/",
22002
+ "/build/",
22003
+ "/out/",
22004
+ "/target/",
22005
+ "/.next/",
22006
+ "/coverage/",
22007
+ ".min.js",
22008
+ ".min.css",
22009
+ ".bundle.js",
22010
+ "-bundle.js",
22011
+ ".map"
22012
+ ];
21323
22013
  CF_RE = /\b(if|for|while|switch|case|catch|elif|when|loop|&&|\|\||\?)\b|\?/g;
21324
22014
  DEPTH = 2;
21325
22015
  MCP_OUT_MAX_BYTES = 64 * 1024 * 1024;
21326
22016
  call = (id, name, args = {}) => ({ jsonrpc: "2.0", id, method: "tools/call", params: { name, arguments: args } });
22017
+ GENERATED_PATTERNS = GENERATED;
21327
22018
  COMMON_SYMBOL_NAMES = /* @__PURE__ */ new Set([
21328
22019
  "error",
21329
22020
  "err",
@@ -21389,11 +22080,11 @@ var init_collector = __esm({
21389
22080
  });
21390
22081
 
21391
22082
  // ../engine/dist/core/artifact-codec.js
21392
- import { readFileSync as readFileSync38 } from "fs";
21393
- import { join as join45 } from "path";
21394
- 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";
21395
22086
  function readCommand(filePath, format) {
21396
- const content = readFileSync38(filePath, "utf-8");
22087
+ const content = readFileSync39(filePath, "utf-8");
21397
22088
  if (format === "toml") {
21398
22089
  const t = parseScalarToml(content);
21399
22090
  return { name: t.name || base(filePath), description: t.description || "", body: t.prompt || "" };
@@ -21402,7 +22093,7 @@ function readCommand(filePath, format) {
21402
22093
  return { name: fm.name || base(filePath), description: fm.description || "", body: body.trim() };
21403
22094
  }
21404
22095
  function readAgent(filePath, format) {
21405
- const content = readFileSync38(filePath, "utf-8");
22096
+ const content = readFileSync39(filePath, "utf-8");
21406
22097
  if (format === "toml") {
21407
22098
  const t = parseScalarToml(content);
21408
22099
  return {
@@ -21415,35 +22106,35 @@ function readAgent(filePath, format) {
21415
22106
  return { name: fm.name || base(filePath), description: fm.description || "", tools: fm.tools, body: body.trim() };
21416
22107
  }
21417
22108
  function readInstructions(filePath, name) {
21418
- const content = readFileSync38(filePath, "utf-8");
22109
+ const content = readFileSync39(filePath, "utf-8");
21419
22110
  return { name, description: "", body: content };
21420
22111
  }
21421
22112
  function commandPath(tool, name) {
21422
22113
  switch (tool) {
21423
22114
  case "claude":
21424
- return join45(claudeBackend.commandsDir(), `${name}.md`);
22115
+ return join48(claudeBackend.commandsDir(), `${name}.md`);
21425
22116
  case "agy":
21426
- return join45(join45(agyBackend.homeDir(), "commands"), `${name}.toml`);
22117
+ return join48(join48(agyBackend.homeDir(), "commands"), `${name}.toml`);
21427
22118
  case "gemini":
21428
- return join45(geminiBackend.commandsDir(), `${name}.toml`);
22119
+ return join48(geminiBackend.commandsDir(), `${name}.toml`);
21429
22120
  case "opencode":
21430
- return join45(opencodeBackend.commandsDir(), `${name}.md`);
22121
+ return join48(opencodeBackend.commandsDir(), `${name}.md`);
21431
22122
  case "codex":
21432
- return join45(codexBackend.promptsDir(), `${name}.md`);
22123
+ return join48(codexBackend.promptsDir(), `${name}.md`);
21433
22124
  }
21434
22125
  }
21435
22126
  function agentPath(tool, name) {
21436
22127
  switch (tool) {
21437
22128
  case "claude":
21438
- return join45(claudeBackend.agentsDir(), `${name}.md`);
22129
+ return join48(claudeBackend.agentsDir(), `${name}.md`);
21439
22130
  case "agy":
21440
- return join45(join45(agyBackend.homeDir(), "agents"), `${name}.md`);
22131
+ return join48(join48(agyBackend.homeDir(), "agents"), `${name}.md`);
21441
22132
  case "gemini":
21442
- return join45(geminiBackend.agentsDir(), `${name}.md`);
22133
+ return join48(geminiBackend.agentsDir(), `${name}.md`);
21443
22134
  case "opencode":
21444
- return join45(opencodeBackend.agentsDir(), `${name}.md`);
22135
+ return join48(opencodeBackend.agentsDir(), `${name}.md`);
21445
22136
  case "codex":
21446
- return join45(codexBackend.agentsDir(), `${name}.toml`);
22137
+ return join48(codexBackend.agentsDir(), `${name}.toml`);
21447
22138
  }
21448
22139
  }
21449
22140
  function instructionsFilename(tool) {
@@ -21461,18 +22152,18 @@ function instructionsFilename(tool) {
21461
22152
  function instructionsPath(tool, projectPath) {
21462
22153
  const file = instructionsFilename(tool);
21463
22154
  if (projectPath)
21464
- return join45(projectPath, file);
22155
+ return join48(projectPath, file);
21465
22156
  switch (tool) {
21466
22157
  case "claude":
21467
- return join45(claudeBackend.homeDir(), file);
22158
+ return join48(claudeBackend.homeDir(), file);
21468
22159
  case "agy":
21469
- return join45(agyBackend.homeDir(), file);
22160
+ return join48(agyBackend.homeDir(), file);
21470
22161
  case "gemini":
21471
- return join45(geminiBackend.homeDir(), file);
22162
+ return join48(geminiBackend.homeDir(), file);
21472
22163
  case "codex":
21473
- return join45(codexBackend.homeDir(), file);
22164
+ return join48(codexBackend.homeDir(), file);
21474
22165
  case "opencode":
21475
- return join45(homedir16(), ".config", "opencode", file);
22166
+ return join48(homedir17(), ".config", "opencode", file);
21476
22167
  }
21477
22168
  }
21478
22169
  function emit(type, art, toTool, projectPath) {
@@ -21535,9 +22226,9 @@ __export(toolkit_sync_exports, {
21535
22226
  supportedTargetsFor: () => supportedTargetsFor,
21536
22227
  writeMcpEntry: () => writeMcpEntry
21537
22228
  });
21538
- import { existsSync as existsSync42, mkdirSync as mkdirSync11, readFileSync as readFileSync39, writeFileSync as writeFileSync12, cpSync as cpSync2 } from "fs";
21539
- import { dirname as dirname17, join as join46, basename as basename26 } from "path";
21540
- 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";
21541
22232
  function supportedTargetsFor(type) {
21542
22233
  return SUPPORTED_TARGETS[type];
21543
22234
  }
@@ -21578,7 +22269,7 @@ function skillsDirFor(tool) {
21578
22269
  case "claude":
21579
22270
  return claudeBackend.skillsDir();
21580
22271
  case "agy":
21581
- return join46(agyBackend.homeDir(), "skills");
22272
+ return join49(agyBackend.homeDir(), "skills");
21582
22273
  case "gemini":
21583
22274
  return geminiBackend.skillsDir();
21584
22275
  case "opencode":
@@ -21588,15 +22279,15 @@ function skillsDirFor(tool) {
21588
22279
  }
21589
22280
  }
21590
22281
  function copySkill(row, toTool) {
21591
- const skillDir = row.extra.skillDir || dirname17(row.filePath);
21592
- if (!existsSync42(skillDir))
22282
+ const skillDir = row.extra.skillDir || dirname19(row.filePath);
22283
+ if (!existsSync44(skillDir))
21593
22284
  return { ok: false, status: 404, error: `Source dir missing: ${skillDir}` };
21594
22285
  const name = row.extra.skillName || basename26(skillDir);
21595
- const targetDir = join46(skillsDirFor(toTool), name);
21596
- if (existsSync42(targetDir))
22286
+ const targetDir = join49(skillsDirFor(toTool), name);
22287
+ if (existsSync44(targetDir))
21597
22288
  return { ok: false, status: 409, error: `Already exists: ${targetDir}` };
21598
22289
  try {
21599
- mkdirSync11(dirname17(targetDir), { recursive: true });
22290
+ mkdirSync13(dirname19(targetDir), { recursive: true });
21600
22291
  cpSync2(skillDir, targetDir, { recursive: true });
21601
22292
  return { ok: true, targetPath: targetDir };
21602
22293
  } catch (e) {
@@ -21604,29 +22295,29 @@ function copySkill(row, toTool) {
21604
22295
  }
21605
22296
  }
21606
22297
  function readMcpEntry(tool, name) {
21607
- const home = homedir17();
22298
+ const home = homedir18();
21608
22299
  const tries = [];
21609
22300
  if (tool === "claude") {
21610
- tries.push({ path: join46(home, ".mcp.json"), key: "mcpServers" });
21611
- 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" });
21612
22303
  } else if (tool === "gemini") {
21613
- tries.push({ path: join46(home, ".gemini", "settings.json"), key: "mcpServers" });
22304
+ tries.push({ path: join49(home, ".gemini", "settings.json"), key: "mcpServers" });
21614
22305
  } else if (tool === "agy") {
21615
- 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" });
21616
22307
  } else if (tool === "opencode") {
21617
- tries.push({ path: join46(home, ".config", "opencode", "opencode.json"), key: "mcp" });
21618
- tries.push({ path: join46(home, ".config", "opencode", "config.json"), key: "mcp" });
21619
- 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" });
21620
22311
  } else if (tool === "codex") {
21621
- const entry = readCodexMcpEntry(join46(home, ".codex", "config.toml"), name);
22312
+ const entry = readCodexMcpEntry(join49(home, ".codex", "config.toml"), name);
21622
22313
  if (entry)
21623
22314
  return entry;
21624
22315
  }
21625
22316
  for (const t of tries) {
21626
- if (!existsSync42(t.path))
22317
+ if (!existsSync44(t.path))
21627
22318
  continue;
21628
22319
  try {
21629
- const cfg = JSON.parse(readFileSync39(t.path, "utf-8"));
22320
+ const cfg = JSON.parse(readFileSync40(t.path, "utf-8"));
21630
22321
  const block = cfg[t.key] || {};
21631
22322
  if (block[name])
21632
22323
  return block[name];
@@ -21636,11 +22327,11 @@ function readMcpEntry(tool, name) {
21636
22327
  return null;
21637
22328
  }
21638
22329
  function readCodexMcpEntry(path, target) {
21639
- if (!existsSync42(path))
22330
+ if (!existsSync44(path))
21640
22331
  return null;
21641
22332
  let content;
21642
22333
  try {
21643
- content = readFileSync39(path, "utf-8");
22334
+ content = readFileSync40(path, "utf-8");
21644
22335
  } catch {
21645
22336
  return null;
21646
22337
  }
@@ -21690,8 +22381,8 @@ function readCodexMcpEntry(path, target) {
21690
22381
  }
21691
22382
  function writeCodexMcpEntry(path, name, entry) {
21692
22383
  let content = "";
21693
- if (existsSync42(path))
21694
- content = readFileSync39(path, "utf-8");
22384
+ if (existsSync44(path))
22385
+ content = readFileSync40(path, "utf-8");
21695
22386
  const header = `[mcp_servers.${name}]`;
21696
22387
  if (content.includes(header))
21697
22388
  return false;
@@ -21720,14 +22411,14 @@ function writeCodexMcpEntry(path, name, entry) {
21720
22411
  for (const [k, v] of Object.entries(entry.env))
21721
22412
  lines.push(`${k} = ${escape(String(v))}`);
21722
22413
  }
21723
- mkdirSync11(dirname17(path), { recursive: true });
21724
- writeFileSync12(path, content + lines.join("\n") + "\n");
22414
+ mkdirSync13(dirname19(path), { recursive: true });
22415
+ writeFileSync14(path, content + lines.join("\n") + "\n");
21725
22416
  return true;
21726
22417
  }
21727
22418
  function writeMcpEntry(toTool, name, entry) {
21728
- const home = homedir17();
22419
+ const home = homedir18();
21729
22420
  if (toTool === "codex") {
21730
- const path2 = join46(home, ".codex", "config.toml");
22421
+ const path2 = join49(home, ".codex", "config.toml");
21731
22422
  if (!writeCodexMcpEntry(path2, name, entry))
21732
22423
  return { ok: false, status: 409, error: `MCP "${name}" already exists in ${path2}.` };
21733
22424
  return { ok: true, targetPath: path2 };
@@ -21735,27 +22426,27 @@ function writeMcpEntry(toTool, name, entry) {
21735
22426
  let path;
21736
22427
  let key;
21737
22428
  if (toTool === "claude") {
21738
- path = join46(home, ".mcp.json");
22429
+ path = join49(home, ".mcp.json");
21739
22430
  key = "mcpServers";
21740
22431
  } else if (toTool === "gemini") {
21741
- path = join46(home, ".gemini", "settings.json");
22432
+ path = join49(home, ".gemini", "settings.json");
21742
22433
  key = "mcpServers";
21743
22434
  } else if (toTool === "agy") {
21744
- path = join46(home, ".gemini", "config", "mcp_config.json");
22435
+ path = join49(home, ".gemini", "config", "mcp_config.json");
21745
22436
  key = "mcpServers";
21746
22437
  } else {
21747
- path = join46(home, ".config", "opencode", "opencode.json");
22438
+ path = join49(home, ".config", "opencode", "opencode.json");
21748
22439
  key = "mcp";
21749
22440
  }
21750
22441
  let cfg = {};
21751
- if (existsSync42(path)) {
22442
+ if (existsSync44(path)) {
21752
22443
  try {
21753
- cfg = JSON.parse(readFileSync39(path, "utf-8"));
22444
+ cfg = JSON.parse(readFileSync40(path, "utf-8"));
21754
22445
  } catch {
21755
22446
  return { ok: false, status: 500, error: `Target config is malformed: ${path}` };
21756
22447
  }
21757
22448
  } else {
21758
- mkdirSync11(dirname17(path), { recursive: true });
22449
+ mkdirSync13(dirname19(path), { recursive: true });
21759
22450
  }
21760
22451
  cfg[key] = cfg[key] || {};
21761
22452
  if (cfg[key][name])
@@ -21782,7 +22473,7 @@ function writeMcpEntry(toTool, name, entry) {
21782
22473
  }
21783
22474
  cfg[key][name] = normalized;
21784
22475
  try {
21785
- writeFileSync12(path, JSON.stringify(cfg, null, 2));
22476
+ writeFileSync14(path, JSON.stringify(cfg, null, 2));
21786
22477
  return { ok: true, targetPath: path };
21787
22478
  } catch (e) {
21788
22479
  return { ok: false, status: 500, error: e instanceof Error ? e.message : "write failed" };
@@ -21812,11 +22503,11 @@ function copyCommandOrAgent(type, row, toTool) {
21812
22503
  return { ok: false, status: 404, error: `Source unreadable: ${e instanceof Error ? e.message : "failed"}` };
21813
22504
  }
21814
22505
  const out = emit(type, art, toTool);
21815
- if (existsSync42(out.path))
22506
+ if (existsSync44(out.path))
21816
22507
  return { ok: false, status: 409, error: `Already exists: ${out.path}` };
21817
22508
  try {
21818
- mkdirSync11(dirname17(out.path), { recursive: true });
21819
- writeFileSync12(out.path, out.content);
22509
+ mkdirSync13(dirname19(out.path), { recursive: true });
22510
+ writeFileSync14(out.path, out.content);
21820
22511
  return { ok: true, targetPath: out.path };
21821
22512
  } catch (e) {
21822
22513
  return { ok: false, status: 500, error: e instanceof Error ? e.message : "write failed" };
@@ -21832,11 +22523,11 @@ function copyInstructions(row, toTool) {
21832
22523
  return { ok: false, status: 404, error: `Source unreadable: ${e instanceof Error ? e.message : "failed"}` };
21833
22524
  }
21834
22525
  const out = emit("instructions", art, toTool, row.projectPath || void 0);
21835
- if (existsSync42(out.path))
22526
+ if (existsSync44(out.path))
21836
22527
  return { ok: false, status: 409, error: `Already exists: ${out.path}` };
21837
22528
  try {
21838
- mkdirSync11(dirname17(out.path), { recursive: true });
21839
- writeFileSync12(out.path, out.content);
22529
+ mkdirSync13(dirname19(out.path), { recursive: true });
22530
+ writeFileSync14(out.path, out.content);
21840
22531
  return { ok: true, targetPath: out.path };
21841
22532
  } catch (e) {
21842
22533
  return { ok: false, status: 500, error: e instanceof Error ? e.message : "write failed" };
@@ -22008,9 +22699,9 @@ __export(project_tasks_exports, {
22008
22699
  pushProjectTaskStatuses: () => pushProjectTaskStatuses,
22009
22700
  renderTeamTasksMd: () => renderTeamTasksMd
22010
22701
  });
22011
- import { existsSync as existsSync43, readFileSync as readFileSync40, writeFileSync as writeFileSync13, mkdirSync as mkdirSync12, statSync as statSync38 } from "node:fs";
22012
- import { join as join47, dirname as dirname18 } from "node:path";
22013
- 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";
22014
22705
  function isTeamTaskStatus(v) {
22015
22706
  return !!v && TEAM_STATUSES.has(v);
22016
22707
  }
@@ -22025,7 +22716,7 @@ function reconcile(kind, was, token, checked) {
22025
22716
  const done = kind === "secret" ? "rotated" : "done";
22026
22717
  if (token && token !== was) return token;
22027
22718
  if (kind === "team") {
22028
- if (checked && was !== "done") return "done";
22719
+ if (checked && was === "todo") return "in_progress";
22029
22720
  if (!checked && was === "done") return "todo";
22030
22721
  return token ?? was;
22031
22722
  }
@@ -22060,22 +22751,22 @@ function parseTaskFile(content) {
22060
22751
  return out;
22061
22752
  }
22062
22753
  function ledgerPath2() {
22063
- return join47(homedir18(), ".chat-recall", "project-tasks-ledger.json");
22754
+ return join50(homedir19(), ".chat-recall", "project-tasks-ledger.json");
22064
22755
  }
22065
22756
  function readLedger() {
22066
22757
  try {
22067
- return JSON.parse(readFileSync40(ledgerPath2(), "utf8"));
22758
+ return JSON.parse(readFileSync41(ledgerPath2(), "utf8"));
22068
22759
  } catch {
22069
22760
  return {};
22070
22761
  }
22071
22762
  }
22072
22763
  function writeLedger(l) {
22073
22764
  try {
22074
- mkdirSync12(dirname18(ledgerPath2()), { recursive: true });
22765
+ mkdirSync14(dirname20(ledgerPath2()), { recursive: true });
22075
22766
  } catch {
22076
22767
  }
22077
22768
  try {
22078
- writeFileSync13(ledgerPath2(), JSON.stringify(l));
22769
+ writeFileSync15(ledgerPath2(), JSON.stringify(l));
22079
22770
  } catch {
22080
22771
  }
22081
22772
  }
@@ -22093,18 +22784,18 @@ async function pushProjectTaskStatuses(base2, authHeaders, opts = {}) {
22093
22784
  continue;
22094
22785
  }
22095
22786
  for (const project of projects) {
22096
- const file = join47(project, fk.filename);
22097
- if (!existsSync43(file)) continue;
22787
+ const file = join50(project, fk.filename);
22788
+ if (!existsSync45(file)) continue;
22098
22789
  let mtime;
22099
22790
  try {
22100
- mtime = statSync38(file).mtimeMs;
22791
+ mtime = statSync39(file).mtimeMs;
22101
22792
  } catch {
22102
22793
  continue;
22103
22794
  }
22104
22795
  if (ledger[file] === mtime) continue;
22105
22796
  let changed;
22106
22797
  try {
22107
- changed = parseTaskFile(readFileSync40(file, "utf8")).filter((t) => t.kind === fk.kind);
22798
+ changed = parseTaskFile(readFileSync41(file, "utf8")).filter((t) => t.kind === fk.kind);
22108
22799
  } catch {
22109
22800
  continue;
22110
22801
  }
@@ -22137,7 +22828,8 @@ function renderTeamTasksMd(tasks2) {
22137
22828
  "# Team tasks",
22138
22829
  "",
22139
22830
  "<!-- Managed by chat-recall. Tick a box or edit the `status:` token, then run",
22140
- " `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. -->",
22141
22833
  ""
22142
22834
  ];
22143
22835
  if (tasks2.length === 0) lines.push("_No tasks assigned to you here._");
@@ -22157,7 +22849,7 @@ var init_project_tasks = __esm({
22157
22849
  init_http();
22158
22850
  init_secret_task_status();
22159
22851
  init_code_task_status();
22160
- TEAM_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress", "blocked", "done"]);
22852
+ TEAM_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress", "done", "rejected"]);
22161
22853
  ANCHOR_RE = /<!--\s*cr-(secret|task)\b(?:\s+kind=([a-z]+))?\s+id=([A-Za-z0-9_-]+)(?:\s+was=([a-z_-]+))?\s*-->/;
22162
22854
  CHECKBOX_RE = /^\s*-\s*\[([ xX])\]/;
22163
22855
  STATUS_TOKEN_RE = /status:\s*`?([a-z_-]+)`?/i;
@@ -22173,18 +22865,18 @@ var intent_drain_exports = {};
22173
22865
  __export(intent_drain_exports, {
22174
22866
  drainSyncIntents: () => drainSyncIntents
22175
22867
  });
22176
- import { existsSync as existsSync44, readFileSync as readFileSync41, writeFileSync as writeFileSync14, appendFileSync, mkdirSync as mkdirSync13 } from "node:fs";
22177
- import { join as join48 } from "node:path";
22178
- 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";
22179
22871
  import { fileURLToPath as fileURLToPath2 } from "node:url";
22180
- import { dirname as dirname19 } from "node:path";
22872
+ import { dirname as dirname21 } from "node:path";
22181
22873
  function applyCodeRecommendation(intent) {
22182
22874
  try {
22183
22875
  const meta = JSON.parse(intent.name || "{}");
22184
22876
  if (intent.artifact_type === "write_tasks_file") {
22185
22877
  if (!meta.rootPath || !meta.content) return { status: "error", result: JSON.stringify({ error: "missing rootPath or content" }) };
22186
- const file = join48(meta.rootPath, meta.filename || "CODE_TASKS.md");
22187
- writeFileSync14(file, meta.content);
22878
+ const file = join51(meta.rootPath, meta.filename || "CODE_TASKS.md");
22879
+ writeFileSync16(file, meta.content);
22188
22880
  return { status: "done", result: JSON.stringify({ wrote: file }) };
22189
22881
  }
22190
22882
  if (intent.artifact_type === "append_claude_md") {
@@ -22193,24 +22885,24 @@ function applyCodeRecommendation(intent) {
22193
22885
  if (!text) return { status: "error", result: JSON.stringify({ error: "missing text" }) };
22194
22886
  let file;
22195
22887
  if (isGlobal) {
22196
- const dir = join48(homedir19(), ".claude");
22888
+ const dir = join51(homedir20(), ".claude");
22197
22889
  try {
22198
- mkdirSync13(dir, { recursive: true });
22890
+ mkdirSync15(dir, { recursive: true });
22199
22891
  } catch {
22200
22892
  }
22201
- file = join48(dir, "CLAUDE.md");
22893
+ file = join51(dir, "CLAUDE.md");
22202
22894
  } else {
22203
22895
  if (!meta.rootPath) return { status: "error", result: JSON.stringify({ error: "missing rootPath" }) };
22204
- file = join48(meta.rootPath, "CLAUDE.md");
22896
+ file = join51(meta.rootPath, "CLAUDE.md");
22205
22897
  }
22206
- const existing = existsSync44(file) ? readFileSync41(file, "utf8") : "";
22898
+ const existing = existsSync46(file) ? readFileSync42(file, "utf8") : "";
22207
22899
  if (existing.includes(text)) return { status: "done", result: JSON.stringify({ skipped: "rule already present", file }) };
22208
22900
  const block = `${existing && !existing.endsWith("\n") ? "\n" : ""}
22209
22901
  ## Rule (added by chat-recall recommendation)
22210
22902
  ${text}
22211
22903
  `;
22212
22904
  if (existing) appendFileSync(file, block);
22213
- else writeFileSync14(file, `# ${meta.rootPath?.split("/").pop() || "Global"} instructions
22905
+ else writeFileSync16(file, `# ${meta.rootPath?.split("/").pop() || "Global"} instructions
22214
22906
  ${block}`);
22215
22907
  return { status: "done", result: JSON.stringify({ appended: file }) };
22216
22908
  }
@@ -22309,11 +23001,11 @@ var init_intent_drain = __esm({
22309
23001
  init_project_tasks();
22310
23002
  ownVersion2 = "0.0.0";
22311
23003
  try {
22312
- const dir = dirname19(fileURLToPath2(import.meta.url));
22313
- 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;
22314
23006
  } catch {
22315
23007
  try {
22316
- if (true) ownVersion2 = "0.5.4";
23008
+ if (true) ownVersion2 = "0.5.6";
22317
23009
  } catch {
22318
23010
  }
22319
23011
  }
@@ -22514,43 +23206,43 @@ var team_merge_exports = {};
22514
23206
  __export(team_merge_exports, {
22515
23207
  mergePullResult: () => mergePullResult
22516
23208
  });
22517
- import { existsSync as existsSync45, mkdirSync as mkdirSync14, readFileSync as readFileSync42, rmSync as rmSync7, writeFileSync as writeFileSync15, statSync as statSync39 } from "fs";
22518
- import { dirname as dirname20, join as join49 } from "path";
22519
- 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";
22520
23212
  function installPathFor(art, tool) {
22521
23213
  switch (art.type) {
22522
23214
  case "skill":
22523
23215
  switch (tool) {
22524
23216
  case "claude":
22525
- return join49(claudeBackend.skillsDir(), art.name, "SKILL.md");
23217
+ return join52(claudeBackend.skillsDir(), art.name, "SKILL.md");
22526
23218
  case "agy":
22527
23219
  case "gemini":
22528
- return join49(geminiBackend.skillsDir(), art.name, "SKILL.md");
23220
+ return join52(geminiBackend.skillsDir(), art.name, "SKILL.md");
22529
23221
  case "opencode":
22530
- return join49(opencodeBackend.skillsDir(), art.name, "SKILL.md");
23222
+ return join52(opencodeBackend.skillsDir(), art.name, "SKILL.md");
22531
23223
  // User skills go to ~/.codex/skills (NOT .system, which is OpenAI's bundle).
22532
23224
  case "codex":
22533
- return join49(codexBackend.skillsDir(), art.name, "SKILL.md");
23225
+ return join52(codexBackend.skillsDir(), art.name, "SKILL.md");
22534
23226
  }
22535
23227
  break;
22536
23228
  case "agent":
22537
23229
  if (tool === "claude")
22538
- return join49(claudeBackend.agentsDir(), `${art.name}.md`);
23230
+ return join52(claudeBackend.agentsDir(), `${art.name}.md`);
22539
23231
  return null;
22540
23232
  case "command":
22541
23233
  if (tool === "claude")
22542
- return join49(claudeBackend.commandsDir(), `${art.name}.md`);
23234
+ return join52(claudeBackend.commandsDir(), `${art.name}.md`);
22543
23235
  return null;
22544
23236
  case "hook":
22545
23237
  if (tool === "claude")
22546
- return join49(claudeBackend.homeDir(), "chat-recall-hooks", `${art.name}.json`);
23238
+ return join52(claudeBackend.homeDir(), "chat-recall-hooks", `${art.name}.json`);
22547
23239
  return null;
22548
23240
  case "plan":
22549
23241
  switch (tool) {
22550
23242
  case "claude":
22551
- return join49(claudeBackend.plansDir(), `${art.name}.md`);
23243
+ return join52(claudeBackend.plansDir(), `${art.name}.md`);
22552
23244
  case "opencode":
22553
- return join49(opencodeBackend.plansDir(), `${art.name}.md`);
23245
+ return join52(opencodeBackend.plansDir(), `${art.name}.md`);
22554
23246
  case "agy":
22555
23247
  case "gemini":
22556
23248
  return null;
@@ -22560,11 +23252,11 @@ function installPathFor(art, tool) {
22560
23252
  }
22561
23253
  break;
22562
23254
  case "instructions":
22563
- return join49(getDataDir(), "team-instructions", tool, `${art.name}.md`);
23255
+ return join52(getDataDir(), "team-instructions", tool, `${art.name}.md`);
22564
23256
  case "mcp":
22565
- return join49(getDataDir(), "team-mcps", `${art.name}.json`);
23257
+ return join52(getDataDir(), "team-mcps", `${art.name}.json`);
22566
23258
  case "plugin":
22567
- return join49(getDataDir(), "team-plugins", tool, art.name, "manifest.json");
23259
+ return join52(getDataDir(), "team-plugins", tool, art.name, "manifest.json");
22568
23260
  }
22569
23261
  return null;
22570
23262
  }
@@ -22590,20 +23282,20 @@ async function mergePullResult(opts) {
22590
23282
  continue;
22591
23283
  }
22592
23284
  const body = Buffer.from(a.bodyB64, "base64");
22593
- const sha = createHash16("sha256").update(body).digest("hex");
23285
+ const sha = createHash17("sha256").update(body).digest("hex");
22594
23286
  for (const tool of targets) {
22595
23287
  const path = installPathFor(a, tool);
22596
23288
  if (!path)
22597
23289
  continue;
22598
23290
  try {
22599
23291
  const existing = readFileIfExists(path);
22600
- if (existing && createHash16("sha256").update(existing).digest("hex") === sha) {
23292
+ if (existing && createHash17("sha256").update(existing).digest("hex") === sha) {
22601
23293
  report.skipped.push({ artifactId: a.id, path, reason: "unchanged" });
22602
23294
  pending.push({ artifactId: a.id, tool, artifactType: a.type, artifactName: a.name, sha256: sha, installedAt: Date.now() });
22603
23295
  continue;
22604
23296
  }
22605
- mkdirSync14(dirname20(path), { recursive: true });
22606
- writeFileSync15(path, body);
23297
+ mkdirSync16(dirname22(path), { recursive: true });
23298
+ writeFileSync17(path, body);
22607
23299
  pending.push({ artifactId: a.id, tool, artifactType: a.type, artifactName: a.name, sha256: sha, installedAt: Date.now() });
22608
23300
  report.written.push({ artifactId: a.id, path });
22609
23301
  } catch (err) {
@@ -22624,7 +23316,7 @@ async function mergePullResult(opts) {
22624
23316
  if (!path)
22625
23317
  continue;
22626
23318
  try {
22627
- if (existsSync45(path))
23319
+ if (existsSync47(path))
22628
23320
  rmSync7(path, { force: true });
22629
23321
  await teamInstallsForget(id, row.tool);
22630
23322
  report.removed.push({ artifactId: id, path });
@@ -22642,12 +23334,12 @@ async function mergePullResult(opts) {
22642
23334
  }
22643
23335
  function readFileIfExists(path) {
22644
23336
  try {
22645
- if (!existsSync45(path))
23337
+ if (!existsSync47(path))
22646
23338
  return null;
22647
- const s = statSync39(path);
23339
+ const s = statSync40(path);
22648
23340
  if (!s.isFile())
22649
23341
  return null;
22650
- return readFileSync42(path);
23342
+ return readFileSync43(path);
22651
23343
  } catch {
22652
23344
  return null;
22653
23345
  }
@@ -22726,8 +23418,8 @@ var init_vault_crypto = __esm({
22726
23418
  });
22727
23419
 
22728
23420
  // ../engine/dist/core/vault-source.js
22729
- import { existsSync as existsSync46, readdirSync as readdirSync32, statSync as statSync40 } from "fs";
22730
- 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";
22731
23423
  function* walkVaultSources() {
22732
23424
  const t = loadSettings().team.vault;
22733
23425
  const include2 = new Set(t.syncTools);
@@ -22742,7 +23434,7 @@ function* walkVaultSources() {
22742
23434
  }
22743
23435
  function safeStat(path) {
22744
23436
  try {
22745
- const s = statSync40(path);
23437
+ const s = statSync41(path);
22746
23438
  if (!s.isFile())
22747
23439
  return null;
22748
23440
  return { mtimeMs: s.mtimeMs, size: s.size };
@@ -22752,7 +23444,7 @@ function safeStat(path) {
22752
23444
  }
22753
23445
  function* walkClaude() {
22754
23446
  const root = claudeBackend.projectsDir();
22755
- if (!existsSync46(root))
23447
+ if (!existsSync48(root))
22756
23448
  return;
22757
23449
  let projectDirs = [];
22758
23450
  try {
@@ -22763,8 +23455,8 @@ function* walkClaude() {
22763
23455
  for (const proj of projectDirs) {
22764
23456
  if (proj.startsWith("."))
22765
23457
  continue;
22766
- const projectPath = "/" + proj.replace(/^-/, "").replace(/-/g, "/");
22767
- const projDir = join50(root, proj);
23458
+ const projectPath = decodeProjectDirName(proj);
23459
+ const projDir = join53(root, proj);
22768
23460
  let entries = [];
22769
23461
  try {
22770
23462
  entries = readdirSync32(projDir);
@@ -22774,7 +23466,7 @@ function* walkClaude() {
22774
23466
  for (const f of entries) {
22775
23467
  if (extname(f) !== ".jsonl")
22776
23468
  continue;
22777
- const path = join50(projDir, f);
23469
+ const path = join53(projDir, f);
22778
23470
  const s = safeStat(path);
22779
23471
  if (!s)
22780
23472
  continue;
@@ -22785,7 +23477,7 @@ function* walkClaude() {
22785
23477
  }
22786
23478
  function* walkGemini() {
22787
23479
  const tmp = geminiBackend.tmpDir();
22788
- if (!existsSync46(tmp))
23480
+ if (!existsSync48(tmp))
22789
23481
  return;
22790
23482
  let hashDirs = [];
22791
23483
  try {
@@ -22794,8 +23486,8 @@ function* walkGemini() {
22794
23486
  return;
22795
23487
  }
22796
23488
  for (const hash of hashDirs) {
22797
- const chatsDir = join50(tmp, hash, "chats");
22798
- if (!existsSync46(chatsDir))
23489
+ const chatsDir = join53(tmp, hash, "chats");
23490
+ if (!existsSync48(chatsDir))
22799
23491
  continue;
22800
23492
  let entries = [];
22801
23493
  try {
@@ -22807,7 +23499,7 @@ function* walkGemini() {
22807
23499
  const ext = extname(f);
22808
23500
  if (ext !== ".json" && ext !== ".jsonl")
22809
23501
  continue;
22810
- const path = join50(chatsDir, f);
23502
+ const path = join53(chatsDir, f);
22811
23503
  const s = safeStat(path);
22812
23504
  if (!s)
22813
23505
  continue;
@@ -22817,8 +23509,8 @@ function* walkGemini() {
22817
23509
  }
22818
23510
  }
22819
23511
  function* walkCodex() {
22820
- const sessionsRoot = join50(codexBackend.homeDir(), "sessions");
22821
- if (!existsSync46(sessionsRoot))
23512
+ const sessionsRoot = join53(codexBackend.homeDir(), "sessions");
23513
+ if (!existsSync48(sessionsRoot))
22822
23514
  return;
22823
23515
  const stack = [sessionsRoot];
22824
23516
  while (stack.length) {
@@ -22830,7 +23522,7 @@ function* walkCodex() {
22830
23522
  continue;
22831
23523
  }
22832
23524
  for (const e of entries) {
22833
- const path = join50(dir, e.name);
23525
+ const path = join53(dir, e.name);
22834
23526
  if (e.isDirectory()) {
22835
23527
  stack.push(path);
22836
23528
  continue;
@@ -22846,8 +23538,8 @@ function* walkCodex() {
22846
23538
  }
22847
23539
  }
22848
23540
  function* walkAgy() {
22849
- const brainDir = join50(agyBackend.homeDir(), "brain");
22850
- if (!existsSync46(brainDir))
23541
+ const brainDir = join53(agyBackend.homeDir(), "brain");
23542
+ if (!existsSync48(brainDir))
22851
23543
  return;
22852
23544
  let sessionDirs = [];
22853
23545
  try {
@@ -22856,18 +23548,18 @@ function* walkAgy() {
22856
23548
  return;
22857
23549
  }
22858
23550
  for (const rawId of sessionDirs) {
22859
- const sessionPath = join50(brainDir, rawId);
23551
+ const sessionPath = join53(brainDir, rawId);
22860
23552
  try {
22861
- if (!statSync40(sessionPath).isDirectory())
23553
+ if (!statSync41(sessionPath).isDirectory())
22862
23554
  continue;
22863
23555
  } catch {
22864
23556
  continue;
22865
23557
  }
22866
- let filePath = join50(sessionPath, ".system_generated", "logs", "transcript.jsonl");
22867
- if (!existsSync46(filePath)) {
22868
- 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");
22869
23561
  }
22870
- if (!existsSync46(filePath))
23562
+ if (!existsSync48(filePath))
22871
23563
  continue;
22872
23564
  const s = safeStat(filePath);
22873
23565
  if (!s)
@@ -22884,6 +23576,7 @@ var init_vault_source = __esm({
22884
23576
  init_codex2();
22885
23577
  init_agy();
22886
23578
  init_settings();
23579
+ init_project_dir_name();
22887
23580
  }
22888
23581
  });
22889
23582
 
@@ -22900,8 +23593,8 @@ __export(vault_client_exports, {
22900
23593
  vaultStatus: () => vaultStatus,
22901
23594
  vaultSync: () => vaultSync
22902
23595
  });
22903
- import { mkdirSync as mkdirSync15, readFileSync as readFileSync43, writeFileSync as writeFileSync16, existsSync as existsSync47 } from "fs";
22904
- 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";
22905
23598
  function context2() {
22906
23599
  const s = loadSettings().team;
22907
23600
  if (!s.enabled)
@@ -23040,7 +23733,7 @@ async function vaultSync(passphrase) {
23040
23733
  continue;
23041
23734
  }
23042
23735
  try {
23043
- const plaintext = readFileSync43(src.path);
23736
+ const plaintext = readFileSync44(src.path);
23044
23737
  const sourceSha = sha256Hex(plaintext);
23045
23738
  const priorSha = uploaded.get(uploadKey(src.sessionId, src.tool));
23046
23739
  if (priorSha === sourceSha) {
@@ -23092,7 +23785,7 @@ async function vaultSync(passphrase) {
23092
23785
  }
23093
23786
  function importedPathFor(tool, sessionId) {
23094
23787
  const base2 = sessionId.replace(/^gemini_/, "").replace(/^codex_/, "").replace(/^opencode_/, "");
23095
- return join51(IMPORTED_ROOT(), tool, `${base2}.jsonl`);
23788
+ return join54(IMPORTED_ROOT(), tool, `${base2}.jsonl`);
23096
23789
  }
23097
23790
  async function vaultRestore(passphrase, opts = {}) {
23098
23791
  const ctx = context2();
@@ -23113,7 +23806,7 @@ async function vaultRestore(passphrase, opts = {}) {
23113
23806
  }
23114
23807
  try {
23115
23808
  const path = importedPathFor(blob.tool, blob.sessionId);
23116
- if (existsSync47(path)) {
23809
+ if (existsSync49(path)) {
23117
23810
  const localStat = await import("fs").then((m) => m.statSync(path));
23118
23811
  if (Math.abs(localStat.mtimeMs - blob.mtimeSourceMs) < 1) {
23119
23812
  report.skipped.push({ sessionId: blob.sessionId, tool: blob.tool, reason: "unchanged" });
@@ -23129,8 +23822,8 @@ async function vaultRestore(passphrase, opts = {}) {
23129
23822
  throw new Error("cipher integrity check failed");
23130
23823
  }
23131
23824
  const plaintext = decryptBlob(key, cipher);
23132
- mkdirSync15(dirname21(path), { recursive: true });
23133
- writeFileSync16(path, Buffer.from(plaintext));
23825
+ mkdirSync17(dirname23(path), { recursive: true });
23826
+ writeFileSync18(path, Buffer.from(plaintext));
23134
23827
  const { utimesSync } = await import("fs");
23135
23828
  const t = blob.mtimeSourceMs / 1e3;
23136
23829
  utimesSync(path, t, t);
@@ -23181,7 +23874,7 @@ var init_vault_client = __esm({
23181
23874
  uploadKey = (sessionId, tool) => `${sessionId}\0${tool}`;
23182
23875
  VaultPassphraseMismatchError = class extends Error {
23183
23876
  };
23184
- IMPORTED_ROOT = () => join51(getDataDir(), "imported");
23877
+ IMPORTED_ROOT = () => join54(getDataDir(), "imported");
23185
23878
  }
23186
23879
  });
23187
23880
 
@@ -23335,15 +24028,15 @@ __export(service_installer_exports, {
23335
24028
  uninstallService: () => uninstallService
23336
24029
  });
23337
24030
  import { execSync as execSync5 } from "child_process";
23338
- import { existsSync as existsSync48, mkdirSync as mkdirSync16, rmSync as rmSync8, writeFileSync as writeFileSync17 } from "fs";
23339
- import { homedir as homedir20 } from "os";
23340
- 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";
23341
24034
  import { fileURLToPath as fileURLToPath3 } from "url";
23342
24035
  function resolveDaemonPaths() {
23343
24036
  const watchJs = fileURLToPath3(new URL("./watch.js", import.meta.url));
23344
24037
  const node = process.execPath;
23345
- const dataDir = join52(homedir20(), ".chat-recall");
23346
- const logFile = join52(dataDir, "watch.log");
24038
+ const dataDir = join55(homedir21(), ".chat-recall");
24039
+ const logFile = join55(dataDir, "watch.log");
23347
24040
  return { watchJs, node, logFile, dataDir };
23348
24041
  }
23349
24042
  function tryCmd(cmd) {
@@ -23368,7 +24061,7 @@ function isServiceRunning() {
23368
24061
  }
23369
24062
  function installService() {
23370
24063
  const { watchJs, node, logFile, dataDir } = resolveDaemonPaths();
23371
- mkdirSync16(dataDir, { recursive: true });
24064
+ mkdirSync18(dataDir, { recursive: true });
23372
24065
  if (process.platform === "linux") {
23373
24066
  return installSystemd(watchJs, node, logFile);
23374
24067
  }
@@ -23384,11 +24077,11 @@ function installService() {
23384
24077
  );
23385
24078
  }
23386
24079
  function installSystemd(watchJs, node, logFile) {
23387
- const unitDir = join52(homedir20(), ".config", "systemd", "user");
23388
- 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");
23389
24082
  const unit = renderSystemdUnit(watchJs, node, logFile);
23390
- mkdirSync16(unitDir, { recursive: true });
23391
- writeFileSync17(unitPath, unit);
24083
+ mkdirSync18(unitDir, { recursive: true });
24084
+ writeFileSync19(unitPath, unit);
23392
24085
  try {
23393
24086
  execSync5("systemctl --user daemon-reload && systemctl --user enable chat-recall-watch.service && systemctl --user restart chat-recall-watch.service", { stdio: "inherit" });
23394
24087
  try {
@@ -23416,10 +24109,14 @@ function renderSystemdUnit(watchJs, node, logFile) {
23416
24109
  "Restart=on-failure",
23417
24110
  "RestartSec=10",
23418
24111
  "Nice=10",
23419
- // Keep the service's own stdout/stderr out of the journal noise but
23420
- // available for `chat-recall service status` without paging journald.
23421
- `StandardOutput=append:${logFile}`,
23422
- `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",
23423
24120
  "",
23424
24121
  "[Install]",
23425
24122
  "WantedBy=default.target",
@@ -23428,11 +24125,11 @@ function renderSystemdUnit(watchJs, node, logFile) {
23428
24125
  }
23429
24126
  function installLaunchd(watchJs, node, logFile) {
23430
24127
  const label = "com.chat-recall.watch";
23431
- const plistDir = join52(homedir20(), "Library", "LaunchAgents");
23432
- const plistPath = join52(plistDir, `${label}.plist`);
24128
+ const plistDir = join55(homedir21(), "Library", "LaunchAgents");
24129
+ const plistPath = join55(plistDir, `${label}.plist`);
23433
24130
  const plist = renderLaunchdPlist(watchJs, node, logFile, label);
23434
- mkdirSync16(plistDir, { recursive: true });
23435
- writeFileSync17(plistPath, plist);
24131
+ mkdirSync18(plistDir, { recursive: true });
24132
+ writeFileSync19(plistPath, plist);
23436
24133
  try {
23437
24134
  try {
23438
24135
  execSync5(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
@@ -23461,7 +24158,7 @@ function renderLaunchdPlist(watchJs, node, logFile, label = "com.chat-recall.wat
23461
24158
  }
23462
24159
  function installWindowsTask(watchJs, node, logFile) {
23463
24160
  const taskName = "chat-recall-watch";
23464
- const tr = `\\"${node}\\" --max-old-space-size=1536 \\"${watchJs}\\"`;
24161
+ const tr = `cmd /c \\"\\"${node}\\" --max-old-space-size=1536 \\"${watchJs}\\" >> \\"${logFile}\\" 2>&1\\"`;
23465
24162
  try {
23466
24163
  execSync5(`schtasks /create /tn "${taskName}" /tr "${tr}" /sc onlogon /rl LIMITED /f`, { stdio: "inherit" });
23467
24164
  try {
@@ -23473,8 +24170,8 @@ function installWindowsTask(watchJs, node, logFile) {
23473
24170
  }
23474
24171
  return { definitionPath: null, logFile };
23475
24172
  }
23476
- function renderWindowsTaskCommand(watchJs, node, taskName = "chat-recall-watch") {
23477
- 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}\\"`;
23478
24175
  return `schtasks /create /tn "${taskName}" /tr "${tr}" /sc onlogon /rl LIMITED /f`;
23479
24176
  }
23480
24177
  function uninstallService() {
@@ -23485,8 +24182,8 @@ function uninstallService() {
23485
24182
  removed = true;
23486
24183
  } catch {
23487
24184
  }
23488
- const unitPath = join52(homedir20(), ".config", "systemd", "user", "chat-recall-watch.service");
23489
- if (existsSync48(unitPath)) {
24185
+ const unitPath = join55(homedir21(), ".config", "systemd", "user", "chat-recall-watch.service");
24186
+ if (existsSync50(unitPath)) {
23490
24187
  rmSync8(unitPath, { force: true });
23491
24188
  removed = true;
23492
24189
  }
@@ -23497,13 +24194,13 @@ function uninstallService() {
23497
24194
  return removed;
23498
24195
  }
23499
24196
  if (process.platform === "darwin") {
23500
- const plistPath = join52(homedir20(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
24197
+ const plistPath = join55(homedir21(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
23501
24198
  try {
23502
24199
  execSync5(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
23503
24200
  } catch {
23504
24201
  }
23505
24202
  let removed = false;
23506
- if (existsSync48(plistPath)) {
24203
+ if (existsSync50(plistPath)) {
23507
24204
  rmSync8(plistPath, { force: true });
23508
24205
  removed = true;
23509
24206
  }
@@ -23548,7 +24245,7 @@ __export(device_auth_exports, {
23548
24245
  betterAuthDeviceLogin: () => betterAuthDeviceLogin,
23549
24246
  deviceLogin: () => deviceLogin
23550
24247
  });
23551
- import { createHash as createHash17, randomBytes } from "node:crypto";
24248
+ import { createHash as createHash18, randomBytes } from "node:crypto";
23552
24249
  async function deviceLogin(opts, onPrompt) {
23553
24250
  const issuer = (opts.issuer || DEFAULT_ISSUER).replace(/\/+$/, "");
23554
24251
  if (!issuer) {
@@ -23559,7 +24256,7 @@ async function deviceLogin(opts, onPrompt) {
23559
24256
  const clientId = opts.clientId || DEFAULT_CLIENT_ID;
23560
24257
  const scope = opts.scope || "openid email profile";
23561
24258
  const codeVerifier = b64url(randomBytes(32));
23562
- const codeChallenge = b64url(createHash17("sha256").update(codeVerifier).digest());
24259
+ const codeChallenge = b64url(createHash18("sha256").update(codeVerifier).digest());
23563
24260
  const startRes = await fetch(`${issuer}/protocol/openid-connect/auth/device`, {
23564
24261
  method: "POST",
23565
24262
  headers: { "content-type": "application/x-www-form-urlencoded" },
@@ -23681,10 +24378,9 @@ init_project_resolver();
23681
24378
  import { config } from "dotenv";
23682
24379
  import { Command } from "commander";
23683
24380
  import chalk from "chalk";
23684
- import { existsSync as existsSync49, readFileSync as readFileSync44, writeFileSync as writeFileSync18, realpathSync as realpathSync5 } from "fs";
23685
- import { homedir as homedir21 } from "os";
23686
- import { join as join53, resolve as resolve2, dirname as dirname22, basename as basename27 } from "path";
23687
- 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";
23688
24384
 
23689
24385
  // ../engine/dist/core/score-tier.js
23690
24386
  var STRONG_RATIO = 0.85;
@@ -23711,6 +24407,68 @@ function tierAll(results) {
23711
24407
  // src/cli.ts
23712
24408
  init_sync_client();
23713
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
23714
24472
  function matchLabel(tier) {
23715
24473
  switch (tier) {
23716
24474
  case "strong":
@@ -23728,8 +24486,8 @@ function claudeHookConfigFiles() {
23728
24486
  const out = [];
23729
24487
  const seen = /* @__PURE__ */ new Set();
23730
24488
  for (const home of claudeHomeDirs()) {
23731
- if (!existsSync49(join53(home, "projects"))) continue;
23732
- const file = join53(home, "hooks.json");
24489
+ if (!existsSync51(join56(home, "projects"))) continue;
24490
+ const file = join56(home, "hooks.json");
23733
24491
  let key;
23734
24492
  try {
23735
24493
  key = realpathSync5(file);
@@ -23803,7 +24561,7 @@ async function serverDelete(path, body) {
23803
24561
  return res.json();
23804
24562
  }
23805
24563
  var pkgVersion = JSON.parse(
23806
- readFileSync44(new URL("../package.json", import.meta.url), "utf-8")
24564
+ readFileSync45(new URL("../package.json", import.meta.url), "utf-8")
23807
24565
  ).version;
23808
24566
  var DEFAULT_SERVER = "https://chatrecall.dev";
23809
24567
  var DEFAULT_ALLOW = [
@@ -23826,6 +24584,8 @@ var DEFAULT_ALLOW = [
23826
24584
  "recall_tasks",
23827
24585
  "recall_task_create",
23828
24586
  "recall_task_update",
24587
+ "recall_task_comment",
24588
+ "recall_help",
23829
24589
  "recall_smart_resume",
23830
24590
  "recall_project_context",
23831
24591
  "recall_weekly_digest",
@@ -23883,7 +24643,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
23883
24643
  ];
23884
24644
  for (const cli of clis) {
23885
24645
  try {
23886
- execSync6(`which ${cli.cmd}`, { stdio: "pipe" });
24646
+ if (!isOnPath(cli.cmd)) throw new Error("not on PATH");
23887
24647
  cli.available = true;
23888
24648
  } catch {
23889
24649
  }
@@ -23915,37 +24675,22 @@ program.command("init").description("Set up chat-recall: connect to your server,
23915
24675
  console.log();
23916
24676
  if (!options.skipMcp) {
23917
24677
  console.log(chalk.bold("4. Configuring MCP server..."));
23918
- const mcpJsonPath = join53(homedir21(), ".mcp.json");
23919
- let mcpConfig = {};
23920
- if (existsSync49(mcpJsonPath)) {
23921
- try {
23922
- mcpConfig = JSON.parse(readFileSync44(mcpJsonPath, "utf-8"));
23923
- } catch {
23924
- mcpConfig = {};
23925
- }
23926
- }
23927
- const mcpServers = mcpConfig.mcpServers || {};
23928
- const projectRoot = join53(import.meta.dirname, "..");
24678
+ const projectRoot = join56(import.meta.dirname, "..");
23929
24679
  let mcpBinOnPath = false;
23930
- try {
23931
- execSync6("command -v chat-recall-mcp", { stdio: "ignore" });
23932
- mcpBinOnPath = true;
23933
- } catch {
23934
- }
23935
- 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")] };
23936
24682
  const MCP_ENV = { NODE_OPTIONS: "--max-old-space-size=1024" };
23937
- const existing = mcpServers["chat-recall"];
23938
- 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;
23939
- if (isCurrent) {
23940
- console.log(` MCP server: ${chalk.green("already configured")} in ${mcpJsonPath}`);
23941
- } else {
23942
- const entry = { command: launch.command, alwaysAllow: existing?.alwaysAllow ?? DEFAULT_ALLOW, env: { ...existing?.env, ...MCP_ENV } };
23943
- if (launch.args) entry.args = launch.args;
23944
- mcpServers["chat-recall"] = entry;
23945
- mcpConfig.mcpServers = mcpServers;
23946
- writeFileSync18(mcpJsonPath, JSON.stringify(mcpConfig, null, 2));
23947
- 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}`);
23948
24692
  }
24693
+ console.log(chalk.dim(` Launch: ${launch.command}${launch.args ? " " + launch.args.join(" ") : ""}`));
23949
24694
  } else {
23950
24695
  console.log(chalk.bold("4. Skipping MCP configuration (--skip-mcp)"));
23951
24696
  }
@@ -23966,7 +24711,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
23966
24711
  console.log(chalk.bold("5. Detected codeindex companion"));
23967
24712
  console.log(` ${chalk.green("codeindex")}: ${detected.path}`);
23968
24713
  if (!options.skipMcp) {
23969
- const mcpJsonPath = join53(homedir21(), ".mcp.json");
24714
+ const mcpJsonPath = join56(homedir22(), ".mcp.json");
23970
24715
  const reg = registerCodeindexMcp2(mcpJsonPath, detected.path);
23971
24716
  if (reg.added) console.log(` ${chalk.green("codeindex MCP server registered")} in ${mcpJsonPath}`);
23972
24717
  else console.log(` codeindex MCP server: ${chalk.green("already registered")}`);
@@ -23979,7 +24724,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
23979
24724
  const sizeMb = result.size ? `${(result.size / 1024 / 1024).toFixed(1)} MB` : "?";
23980
24725
  console.log(` ${chalk.green("codeindex installed")} (${sizeMb}) \u2192 ${result.path}`);
23981
24726
  if (!options.skipMcp) {
23982
- const mcpJsonPath = join53(homedir21(), ".mcp.json");
24727
+ const mcpJsonPath = join56(homedir22(), ".mcp.json");
23983
24728
  const reg = registerCodeindexMcp2(mcpJsonPath, CODEINDEX_BIN_PATH2);
23984
24729
  if (reg.added) console.log(` ${chalk.green("codeindex MCP server registered")} in ${mcpJsonPath}`);
23985
24730
  }
@@ -24377,8 +25122,8 @@ memory.command("wake-up").description("Generate wake-up context (high-importance
24377
25122
  try {
24378
25123
  const identityFile = getIdentityFilePath();
24379
25124
  let identity = "AI coding assistant";
24380
- if (existsSync49(identityFile)) {
24381
- identity = readFileSync44(identityFile, "utf-8").trim();
25125
+ if (existsSync51(identityFile)) {
25126
+ identity = readFileSync45(identityFile, "utf-8").trim();
24382
25127
  }
24383
25128
  const lines = [
24384
25129
  chalk.bold("# Wake-Up Context"),
@@ -24435,8 +25180,8 @@ program.command("escalate [sessionId]").description("Escalate a finished session
24435
25180
  }
24436
25181
  });
24437
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) => {
24438
- const { existsSync: existsSync50, readFileSync: readFileSync45, statSync: statSync41 } = await import("fs");
24439
- 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");
24440
25185
  const rows = [];
24441
25186
  const note = (ok, label, detail) => rows.push({ ok, label, detail });
24442
25187
  const targets = loadAllCredentials();
@@ -24460,36 +25205,50 @@ program.command("doctor").description("Quick health check across login, server,
24460
25205
  } else {
24461
25206
  note(false, "Server reachable", "N/A (not logged in)");
24462
25207
  }
24463
- const credFile = join53(getDataDir(), "credentials.json");
24464
- 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)) {
24465
25223
  note(false, "Credentials file", `missing \u2014 run \`chat-recall login <server-url>\``);
24466
25224
  } else {
24467
25225
  try {
24468
- const mode = statSync41(credFile).mode & 511;
24469
- const safe = (mode & 63) === 0;
24470
- 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"})`);
24471
25230
  } catch (err) {
24472
25231
  note(false, "Credentials file", `error: ${err}`);
24473
25232
  }
24474
25233
  }
24475
25234
  for (const hooksJson of claudeHookConfigFiles()) {
24476
- const profile = basename27(dirname22(hooksJson));
25235
+ const profile = basename27(dirname24(hooksJson));
24477
25236
  const label = `Claude hooks (${profile})`;
24478
- if (!existsSync50(hooksJson)) {
25237
+ if (!existsSync52(hooksJson)) {
24479
25238
  note(false, label, `no ${hooksJson} \u2014 run \`chat-recall install-hooks\``);
24480
25239
  continue;
24481
25240
  }
24482
25241
  try {
24483
25242
  const readHooks = (file) => {
24484
- if (!existsSync50(file)) return {};
25243
+ if (!existsSync52(file)) return {};
24485
25244
  try {
24486
- return JSON.parse(readFileSync45(file, "utf-8")).hooks || {};
25245
+ return JSON.parse(readFileSync46(file, "utf-8")).hooks || {};
24487
25246
  } catch {
24488
25247
  return {};
24489
25248
  }
24490
25249
  };
24491
25250
  const cfg = readHooks(hooksJson);
24492
- const settings2 = readHooks(join53(dirname22(hooksJson), "settings.json"));
25251
+ const settings2 = readHooks(join56(dirname24(hooksJson), "settings.json"));
24493
25252
  const all = ["SessionStart", "UserPromptSubmit", "Stop", "PreCompact", "SessionEnd"];
24494
25253
  const hits = [];
24495
25254
  for (const ev of all) {
@@ -24513,7 +25272,7 @@ program.command("doctor").description("Quick health check across login, server,
24513
25272
  const want = bundledSkillNames2().length;
24514
25273
  for (const t of skillTargets2()) {
24515
25274
  if (!t.available) continue;
24516
- 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;
24517
25276
  note(
24518
25277
  have === want,
24519
25278
  `Skills \u2014 ${t.label}`,
@@ -24523,17 +25282,18 @@ program.command("doctor").description("Quick health check across login, server,
24523
25282
  } catch (err) {
24524
25283
  note(false, "Skills", `check failed: ${err}`);
24525
25284
  }
24526
- const mcpJsonPath = join53(homedir21(), ".mcp.json");
24527
- if (!existsSync50(mcpJsonPath)) {
24528
- note(false, "MCP server registration", `no ${mcpJsonPath} \u2014 run \`chat-recall init\``);
24529
- } else {
24530
- try {
24531
- const cfg = JSON.parse(readFileSync45(mcpJsonPath, "utf-8"));
24532
- const has = !!cfg.mcpServers?.["chat-recall"];
24533
- note(has, "chat-recall MCP server", has ? `registered in ${mcpJsonPath}` : "not registered \u2014 run `chat-recall init`");
24534
- } catch {
24535
- 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
+ );
24536
25294
  }
25295
+ } catch (err) {
25296
+ note(false, "MCP server registration", `check failed: ${err}`);
24537
25297
  }
24538
25298
  try {
24539
25299
  const { checkCodeindexStatus: checkCodeindexStatus2 } = await Promise.resolve().then(() => (init_companions(), companions_exports));
@@ -24571,7 +25331,7 @@ program.command("doctor").description("Quick health check across login, server,
24571
25331
  let watchRunning = false;
24572
25332
  const tryCmd2 = (cmd) => {
24573
25333
  try {
24574
- return execSync7(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString();
25334
+ return execSync6(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString();
24575
25335
  } catch {
24576
25336
  return "";
24577
25337
  }
@@ -24587,14 +25347,14 @@ program.command("doctor").description("Quick health check across login, server,
24587
25347
  note(watchRunning, "Watch daemon", watchRunning ? "running" : "not running (run `chat-recall watch --install-service` to keep collection running)");
24588
25348
  const dataDir = getDataDir();
24589
25349
  const legacyPaths = [
24590
- join53(dataDir, "cache.db"),
24591
- join53(dataDir, "cache.db-wal"),
24592
- join53(dataDir, "cache.db-shm"),
24593
- join53(dataDir, "index", "lancedb"),
24594
- join53(dataDir, "index", "knowledge_graph.db"),
24595
- 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")
24596
25356
  ];
24597
- const legacyPresent = legacyPaths.filter((p) => existsSync50(p));
25357
+ const legacyPresent = legacyPaths.filter((p) => existsSync52(p));
24598
25358
  if (opts.purgeLocal) {
24599
25359
  if (!target) {
24600
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");
@@ -24665,7 +25425,7 @@ companions.command("install").description("Install codeindex (downloads from mun
24665
25425
  if (result.installed) {
24666
25426
  const sizeMb = result.size ? `${(result.size / 1024 / 1024).toFixed(1)} MB` : "?";
24667
25427
  console.log(chalk.green(`\u2713 codeindex installed (${sizeMb}) \u2192 ${result.path}`));
24668
- const mcpJsonPath = join53(homedir21(), ".mcp.json");
25428
+ const mcpJsonPath = join56(homedir22(), ".mcp.json");
24669
25429
  const reg = registerCodeindexMcp2(mcpJsonPath, CODEINDEX_BIN_PATH2);
24670
25430
  if (reg.added) console.log(chalk.dim(` Registered as MCP server in ${mcpJsonPath}`));
24671
25431
  else console.log(chalk.dim(` Already registered as MCP server`));
@@ -24687,7 +25447,7 @@ companions.command("uninstall").description("Remove the codeindex binary chat-re
24687
25447
  const r = uninstallCodeindex2();
24688
25448
  if (r.removed) console.log(chalk.green(`\u2713 Removed ${r.path}`));
24689
25449
  else console.log(chalk.dim(` Nothing to remove at ${r.path}`));
24690
- const mcpJsonPath = join53(homedir21(), ".mcp.json");
25450
+ const mcpJsonPath = join56(homedir22(), ".mcp.json");
24691
25451
  const u = unregisterCodeindexMcp2(mcpJsonPath);
24692
25452
  if (u.removed) console.log(chalk.dim(` Unregistered MCP server from ${mcpJsonPath}`));
24693
25453
  });
@@ -25182,10 +25942,10 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
25182
25942
  try {
25183
25943
  const { resolveProjectId: resolveProjectId2 } = await Promise.resolve().then(() => (init_project_resolver(), project_resolver_exports));
25184
25944
  const { renderTeamTasksMd: renderTeamTasksMd2, parseTeamTasksFile: parseTeamTasksFile2 } = await Promise.resolve().then(() => (init_project_tasks(), project_tasks_exports));
25185
- const { writeFileSync: writeFileSync19, readFileSync: readFileSync45, existsSync: existsSync50 } = await import("node:fs");
25186
- const { join: join54 } = await import("node:path");
25187
- const file = join54(cwd, "TEAM_TASKS.md");
25188
- 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) {
25189
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.");
25190
25950
  process.exit(1);
25191
25951
  }
@@ -25193,7 +25953,7 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
25193
25953
  const pid = resolved && resolved.source !== "ignored" ? resolved.id : "";
25194
25954
  const { tasks: tasks2 } = await serverGet("/api/tasks?assignee=@me");
25195
25955
  const mine = tasks2.filter((t) => !pid || !t.projectId || t.projectId === pid);
25196
- writeFileSync19(file, renderTeamTasksMd2(mine));
25956
+ writeFileSync21(file, renderTeamTasksMd2(mine));
25197
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"));
25198
25958
  } catch (e) {
25199
25959
  console.error(chalk.red(e instanceof Error ? e.message : String(e)));
@@ -25204,14 +25964,14 @@ tasks.command("push [dir]").description("Push TEAM_TASKS.md status edits back to
25204
25964
  const cwd = dir || process.cwd();
25205
25965
  try {
25206
25966
  const { parseTeamTasksFile: parseTeamTasksFile2 } = await Promise.resolve().then(() => (init_project_tasks(), project_tasks_exports));
25207
- const { readFileSync: readFileSync45, existsSync: existsSync50 } = await import("node:fs");
25208
- const { join: join54 } = await import("node:path");
25209
- const file = join54(cwd, "TEAM_TASKS.md");
25210
- 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)) {
25211
25971
  console.error(chalk.red("no TEAM_TASKS.md here \u2014"), "run", chalk.bold("chat-recall tasks pull"), "first");
25212
25972
  process.exit(1);
25213
25973
  }
25214
- const edits = parseTeamTasksFile2(readFileSync45(file, "utf8"));
25974
+ const edits = parseTeamTasksFile2(readFileSync46(file, "utf8"));
25215
25975
  let n = 0;
25216
25976
  for (const e of edits) {
25217
25977
  await serverPatch(`/api/tasks/${encodeURIComponent(e.id)}`, { status: e.intent });
@@ -25317,8 +26077,8 @@ team.command("publish <type> <name> <file>").description("Publish a local file a
25317
26077
  console.error(chalk.red(`Invalid tool: ${opts.tool}. Must be one of: ${validTools.join(", ")}`));
25318
26078
  process.exit(1);
25319
26079
  }
25320
- const { readFileSync: readFileSync45 } = await import("fs");
25321
- const body = readFileSync45(file);
26080
+ const { readFileSync: readFileSync46 } = await import("fs");
26081
+ const body = readFileSync46(file);
25322
26082
  const { teamPublish: teamPublish2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
25323
26083
  const a = await teamPublish2({
25324
26084
  type,
@@ -25505,15 +26265,15 @@ vault.command("forget").description("Remove the stored passphrase from this mach
25505
26265
  console.log(keyringDelete2() ? chalk.green("\u2713 Passphrase removed from the OS keyring.") : chalk.dim("Nothing stored in the OS keyring."));
25506
26266
  }));
25507
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) => {
25508
- 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");
25509
26269
  const { fileURLToPath: fileURLToPath4 } = await import("url");
25510
26270
  const here = fileURLToPath4(new URL(".", import.meta.url));
25511
26271
  const findHook = (name) => {
25512
- const candidates = [
25513
- join53(here, "..", "hooks", name),
25514
- join53(here, "..", "..", "hooks", name)
26272
+ const candidates2 = [
26273
+ join56(here, "..", "hooks", name),
26274
+ join56(here, "..", "..", "hooks", name)
25515
26275
  ];
25516
- return candidates.find((p) => existsSync50(p));
26276
+ return candidates2.find((p) => existsSync52(p));
25517
26277
  };
25518
26278
  const sourceSaveHook = findHook("chat_recall_save_hook.sh");
25519
26279
  const sourceResumeHook = findHook("chat_recall_resume_hook.sh");
@@ -25524,15 +26284,15 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25524
26284
  process.exit(1);
25525
26285
  }
25526
26286
  const hooksDir = getHooksDir();
25527
- const installedSaveHook = join53(hooksDir, "chat_recall_save_hook.sh");
25528
- const installedResumeHook = join53(hooksDir, "chat_recall_resume_hook.sh");
25529
- const installedEscalateHook = join53(hooksDir, "chat_recall_escalate_hook.sh");
25530
- 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");
25531
26291
  const wakeupWiredInSettings = (home) => {
25532
- const settings2 = join53(home, "settings.json");
25533
- if (!existsSync50(settings2)) return false;
26292
+ const settings2 = join56(home, "settings.json");
26293
+ if (!existsSync52(settings2)) return false;
25534
26294
  try {
25535
- const cfg = JSON.parse(readFileSync45(settings2, "utf-8"));
26295
+ const cfg = JSON.parse(readFileSync46(settings2, "utf-8"));
25536
26296
  const arr = Array.isArray(cfg.hooks?.SessionStart) ? cfg.hooks.SessionStart : [];
25537
26297
  return arr.some((h) => (h?.hooks?.[0]?.command || "").includes("chat_recall_wakeup_hook"));
25538
26298
  } catch {
@@ -25541,9 +26301,9 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25541
26301
  };
25542
26302
  const hookConfigFiles = claudeHookConfigFiles();
25543
26303
  const readHookConfig = (file) => {
25544
- if (!existsSync50(file)) return { hooks: {} };
26304
+ if (!existsSync52(file)) return { hooks: {} };
25545
26305
  try {
25546
- const c = JSON.parse(readFileSync45(file, "utf-8"));
26306
+ const c = JSON.parse(readFileSync46(file, "utf-8"));
25547
26307
  if (!c.hooks || typeof c.hooks !== "object") c.hooks = {};
25548
26308
  return c;
25549
26309
  } catch (err) {
@@ -25569,8 +26329,8 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25569
26329
  if (filtered.length) config2.hooks[event] = filtered;
25570
26330
  else delete config2.hooks[event];
25571
26331
  }
25572
- if (removed === 0 && !existsSync50(hooksJson)) continue;
25573
- writeFileSync19(hooksJson, JSON.stringify(config2, null, 2) + "\n");
26332
+ if (removed === 0 && !existsSync52(hooksJson)) continue;
26333
+ writeFileSync21(hooksJson, JSON.stringify(config2, null, 2) + "\n");
25574
26334
  total += removed;
25575
26335
  console.log(chalk.green(`\u2713 Removed ${removed} chat-recall hook entr${removed === 1 ? "y" : "ies"} from ${hooksJson}`));
25576
26336
  }
@@ -25578,7 +26338,20 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25578
26338
  console.log(chalk.dim(` (Hook scripts left in ${hooksDir} \u2014 delete manually if you want.)`));
25579
26339
  return;
25580
26340
  }
25581
- 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 });
25582
26355
  copyFileSync4(sourceSaveHook, installedSaveHook);
25583
26356
  chmodSync5(installedSaveHook, 493);
25584
26357
  const installResume = opts.resumeHint !== false && !!sourceResumeHook;
@@ -25597,7 +26370,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25597
26370
  chmodSync5(installedWakeupHook, 493);
25598
26371
  }
25599
26372
  const stopEntry = { matcher: "", hooks: [{ type: "command", command: installedSaveHook }] };
25600
- const precompactEntry = { matcher: "", hooks: [{ type: "command", command: `${installedSaveHook} --precompact` }] };
26373
+ const precompactEntry = { matcher: "", hooks: [{ type: "command", command: `"${installedSaveHook}" --precompact` }] };
25601
26374
  const resumeEntry = { matcher: "", hooks: [{ type: "command", command: installedResumeHook }] };
25602
26375
  const escalateEntry = { matcher: "", hooks: [{ type: "command", command: installedEscalateHook }] };
25603
26376
  const wakeupEntry = { matcher: "startup|clear", hooks: [{ type: "command", command: installedWakeupHook }] };
@@ -25610,9 +26383,9 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25610
26383
  const wakeupSkipped = [];
25611
26384
  for (const hooksJson of hookConfigFiles) {
25612
26385
  const config2 = readHookConfig(hooksJson);
25613
- const home = dirname22(hooksJson);
26386
+ const home = dirname24(hooksJson);
25614
26387
  const alreadyWired = wakeupWiredInSettings(home);
25615
- if (alreadyWired) wakeupSkipped.push(join53(home, "settings.json"));
26388
+ if (alreadyWired) wakeupSkipped.push(join56(home, "settings.json"));
25616
26389
  const perProfile = installWakeup && !alreadyWired ? [...events, ["SessionStart", wakeupEntry]] : events;
25617
26390
  for (const event of HOOK_EVENTS) {
25618
26391
  const arr = Array.isArray(config2.hooks[event]) ? config2.hooks[event] : [];
@@ -25622,21 +26395,21 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
25622
26395
  if (without.length) config2.hooks[event] = without;
25623
26396
  else delete config2.hooks[event];
25624
26397
  }
25625
- mkdirSync17(dirname22(hooksJson), { recursive: true });
25626
- writeFileSync19(hooksJson, JSON.stringify(config2, null, 2) + "\n");
26398
+ mkdirSync19(dirname24(hooksJson), { recursive: true });
26399
+ writeFileSync21(hooksJson, JSON.stringify(config2, null, 2) + "\n");
25627
26400
  }
25628
- const saveSz = statSync41(installedSaveHook).size;
26401
+ const saveSz = statSync42(installedSaveHook).size;
25629
26402
  console.log(chalk.green(`\u2713 Installed chat-recall save hook (${saveSz} bytes)`));
25630
26403
  if (installResume) {
25631
- const resumeSz = statSync41(installedResumeHook).size;
26404
+ const resumeSz = statSync42(installedResumeHook).size;
25632
26405
  console.log(chalk.green(`\u2713 Installed chat-recall resume-hint hook (${resumeSz} bytes)`));
25633
26406
  }
25634
26407
  if (installEscalate) {
25635
- const escalateSz = statSync41(installedEscalateHook).size;
26408
+ const escalateSz = statSync42(installedEscalateHook).size;
25636
26409
  console.log(chalk.green(`\u2713 Installed chat-recall learnings-escalation hook (${escalateSz} bytes)`));
25637
26410
  }
25638
26411
  if (installWakeup) {
25639
- const wakeupSz = statSync41(installedWakeupHook).size;
26412
+ const wakeupSz = statSync42(installedWakeupHook).size;
25640
26413
  console.log(chalk.green(`\u2713 Installed chat-recall wake-up hook (${wakeupSz} bytes)`));
25641
26414
  for (const f of wakeupSkipped) {
25642
26415
  console.log(chalk.dim(` (SessionStart already wired in ${f} \u2014 left as is, not duplicated)`));
@@ -25668,7 +26441,7 @@ program.command("dossier <project>").description("Generate a project dossier (ov
25668
26441
  );
25669
26442
  const md = dossier.markdown;
25670
26443
  if (options.out) {
25671
- writeFileSync18(options.out, md);
26444
+ writeFileSync20(options.out, md);
25672
26445
  console.log(chalk.green(`Wrote dossier to ${options.out} (${md.length} chars)`));
25673
26446
  } else {
25674
26447
  console.log(md);
@@ -25702,12 +26475,12 @@ program.command("watch").description("Run the live indexer daemon (watches all A
25702
26475
  }
25703
26476
  return;
25704
26477
  }
25705
- const candidates = [
26478
+ const candidates2 = [
25706
26479
  new URL("./watch.js", import.meta.url).href,
25707
26480
  new URL("../auto-indexer/indexer.js", import.meta.url).href
25708
26481
  ];
25709
26482
  let lastErr;
25710
- for (const spec of candidates) {
26483
+ for (const spec of candidates2) {
25711
26484
  try {
25712
26485
  await import(spec);
25713
26486
  return;
@@ -25884,8 +26657,8 @@ include.command("list", { isDefault: true }).description("Show paths opted back
25884
26657
  include.command("project <path>").description("Re-permit a path under a personal folder (substring match; e.g. ~/Documents/code)").action(async (path) => {
25885
26658
  const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
25886
26659
  const { resolve: resolve3 } = await import("node:path");
25887
- const { homedir: homedir22 } = await import("node:os");
25888
- const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir22()));
26660
+ const { homedir: homedir23 } = await import("node:os");
26661
+ const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir23()));
25889
26662
  const s = loadSettings2();
25890
26663
  s.sync.includeProjects = s.sync.includeProjects || [];
25891
26664
  if (s.sync.includeProjects.includes(abs)) {
@@ -25899,8 +26672,8 @@ include.command("project <path>").description("Re-permit a path under a personal
25899
26672
  include.command("remove <path>").description("Undo an opt-in (personal folder goes back to skipped)").action(async (path) => {
25900
26673
  const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
25901
26674
  const { resolve: resolve3 } = await import("node:path");
25902
- const { homedir: homedir22 } = await import("node:os");
25903
- const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir22()));
26675
+ const { homedir: homedir23 } = await import("node:os");
26676
+ const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir23()));
25904
26677
  const s = loadSettings2();
25905
26678
  const before = (s.sync.includeProjects || []).length;
25906
26679
  s.sync.includeProjects = (s.sync.includeProjects || []).filter((p) => p !== path && p !== abs);
@@ -25979,8 +26752,8 @@ program.command("projects").description("List distinct project ids discovered on
25979
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) => {
25980
26753
  const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
25981
26754
  const { resolve: resolve3 } = await import("node:path");
25982
- const { homedir: homedir22 } = await import("node:os");
25983
- const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir22()));
26755
+ const { homedir: homedir23 } = await import("node:os");
26756
+ const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir23()));
25984
26757
  const s = loadSettings2();
25985
26758
  if (s.sync.excludeProjects.includes(abs)) {
25986
26759
  console.log(chalk.dim(`Already excluded: ${abs}`));
@@ -26009,8 +26782,8 @@ exclude.command("tool <tool>").description("Exclude an AI tool entirely (claude
26009
26782
  exclude.command("remove <value>").description("Remove an exclusion (tool name or project path)").action(async (value) => {
26010
26783
  const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
26011
26784
  const { resolve: resolve3 } = await import("node:path");
26012
- const { homedir: homedir22 } = await import("node:os");
26013
- const abs = resolve3(value.replace(/^~(?=\/|$)/, homedir22()));
26785
+ const { homedir: homedir23 } = await import("node:os");
26786
+ const abs = resolve3(value.replace(/^~(?=\/|$)/, homedir23()));
26014
26787
  const s = loadSettings2();
26015
26788
  const beforeTools = s.sync.excludeTools.length;
26016
26789
  const beforeProjects = s.sync.excludeProjects.length;