chat-recall 0.5.30 → 0.5.32

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
@@ -4259,7 +4259,7 @@ var require_pino = __commonJS({
4259
4259
  messageKey,
4260
4260
  errorKey,
4261
4261
  nestedKey,
4262
- base: base2,
4262
+ base: base3,
4263
4263
  name,
4264
4264
  level: level2,
4265
4265
  customLevels,
@@ -4298,11 +4298,11 @@ var require_pino = __commonJS({
4298
4298
  [formattersSym]: allFormatters
4299
4299
  });
4300
4300
  let chindings = "";
4301
- if (base2 !== null) {
4301
+ if (base3 !== null) {
4302
4302
  if (name === void 0) {
4303
- chindings = coreChindings(base2);
4303
+ chindings = coreChindings(base3);
4304
4304
  } else {
4305
- chindings = coreChindings(Object.assign({}, base2, { name }));
4305
+ chindings = coreChindings(Object.assign({}, base3, { name }));
4306
4306
  }
4307
4307
  }
4308
4308
  const time2 = timestamp instanceof Function ? timestamp : timestamp ? epochTime : nullTime;
@@ -4743,56 +4743,56 @@ function freshDefaults() {
4743
4743
  team: defaultTeam()
4744
4744
  };
4745
4745
  }
4746
- function mergeSources(base2, partial) {
4746
+ function mergeSources(base3, partial) {
4747
4747
  if (!partial)
4748
- return base2;
4748
+ return base3;
4749
4749
  const enabled = {
4750
- claude: { ...base2.enabled.claude, ...partial.enabled?.claude ?? {} },
4751
- gemini: { ...base2.enabled.gemini, ...partial.enabled?.gemini ?? {} },
4752
- opencode: { ...base2.enabled.opencode, ...partial.enabled?.opencode ?? {} },
4753
- codex: { ...base2.enabled.codex, ...partial.enabled?.codex ?? {} },
4754
- agy: { ...base2.enabled.agy, ...partial.enabled?.agy ?? {} },
4755
- cursor: { ...base2.enabled.cursor, ...partial.enabled?.cursor ?? {} },
4756
- shared: { ...base2.enabled.shared, ...partial.enabled?.shared ?? {} },
4757
- common: { ...base2.enabled.common, ...partial.enabled?.common ?? {} }
4750
+ claude: { ...base3.enabled.claude, ...partial.enabled?.claude ?? {} },
4751
+ gemini: { ...base3.enabled.gemini, ...partial.enabled?.gemini ?? {} },
4752
+ opencode: { ...base3.enabled.opencode, ...partial.enabled?.opencode ?? {} },
4753
+ codex: { ...base3.enabled.codex, ...partial.enabled?.codex ?? {} },
4754
+ agy: { ...base3.enabled.agy, ...partial.enabled?.agy ?? {} },
4755
+ cursor: { ...base3.enabled.cursor, ...partial.enabled?.cursor ?? {} },
4756
+ shared: { ...base3.enabled.shared, ...partial.enabled?.shared ?? {} },
4757
+ common: { ...base3.enabled.common, ...partial.enabled?.common ?? {} }
4758
4758
  };
4759
4759
  return {
4760
- claudeHome: partial.claudeHome ?? base2.claudeHome,
4761
- geminiHome: partial.geminiHome ?? base2.geminiHome,
4762
- codexHome: partial.codexHome ?? base2.codexHome,
4763
- opencodeDbPath: partial.opencodeDbPath ?? base2.opencodeDbPath,
4764
- agyHome: partial.agyHome ?? base2.agyHome,
4765
- cursorHome: partial.cursorHome ?? base2.cursorHome,
4766
- cursorIdeHome: partial.cursorIdeHome ?? base2.cursorIdeHome,
4767
- extraClaudeHomes: partial.extraClaudeHomes ?? base2.extraClaudeHomes,
4768
- approvedHomes: partial.approvedHomes ?? base2.approvedHomes,
4769
- declinedHomes: partial.declinedHomes ?? base2.declinedHomes,
4770
- homesGrandfathered: partial.homesGrandfathered ?? base2.homesGrandfathered,
4760
+ claudeHome: partial.claudeHome ?? base3.claudeHome,
4761
+ geminiHome: partial.geminiHome ?? base3.geminiHome,
4762
+ codexHome: partial.codexHome ?? base3.codexHome,
4763
+ opencodeDbPath: partial.opencodeDbPath ?? base3.opencodeDbPath,
4764
+ agyHome: partial.agyHome ?? base3.agyHome,
4765
+ cursorHome: partial.cursorHome ?? base3.cursorHome,
4766
+ cursorIdeHome: partial.cursorIdeHome ?? base3.cursorIdeHome,
4767
+ extraClaudeHomes: partial.extraClaudeHomes ?? base3.extraClaudeHomes,
4768
+ approvedHomes: partial.approvedHomes ?? base3.approvedHomes,
4769
+ declinedHomes: partial.declinedHomes ?? base3.declinedHomes,
4770
+ homesGrandfathered: partial.homesGrandfathered ?? base3.homesGrandfathered,
4771
4771
  enabled
4772
4772
  };
4773
4773
  }
4774
- function mergePrivacy(base2, partial) {
4774
+ function mergePrivacy(base3, partial) {
4775
4775
  if (!partial)
4776
- return base2;
4777
- return { ...base2, ...partial };
4776
+ return base3;
4777
+ return { ...base3, ...partial };
4778
4778
  }
4779
- function mergeSync(base2, partial) {
4779
+ function mergeSync(base3, partial) {
4780
4780
  if (!partial)
4781
- return base2;
4781
+ return base3;
4782
4782
  return {
4783
- ...base2,
4783
+ ...base3,
4784
4784
  ...partial,
4785
- upload: { ...base2.upload, ...partial.upload ?? {} }
4785
+ upload: { ...base3.upload, ...partial.upload ?? {} }
4786
4786
  };
4787
4787
  }
4788
- function mergeTeam(base2, partial) {
4788
+ function mergeTeam(base3, partial) {
4789
4789
  if (!partial)
4790
- return base2;
4790
+ return base3;
4791
4791
  return {
4792
- ...base2,
4792
+ ...base3,
4793
4793
  ...partial,
4794
- publishAllowed: { ...base2.publishAllowed, ...partial.publishAllowed ?? {} },
4795
- vault: { ...base2.vault, ...partial.vault ?? {} }
4794
+ publishAllowed: { ...base3.publishAllowed, ...partial.publishAllowed ?? {} },
4795
+ vault: { ...base3.vault, ...partial.vault ?? {} }
4796
4796
  };
4797
4797
  }
4798
4798
  function settingsFilePath() {
@@ -4910,15 +4910,15 @@ function loadSettings() {
4910
4910
  }
4911
4911
  try {
4912
4912
  const parsed = JSON.parse(readFileSync(path, "utf-8"));
4913
- const base2 = freshDefaults();
4913
+ const base3 = freshDefaults();
4914
4914
  const result = {
4915
4915
  v: SCHEMA_VERSION,
4916
- embedding: { ...base2.embedding, ...parsed.embedding ?? {} },
4917
- summary: { ...base2.summary, ...parsed.summary ?? {} },
4918
- sources: mergeSources(base2.sources, parsed.sources),
4919
- privacy: mergePrivacy(base2.privacy, parsed.privacy),
4920
- sync: mergeSync(base2.sync, parsed.sync),
4921
- team: mergeTeam(base2.team, parsed.team)
4916
+ embedding: { ...base3.embedding, ...parsed.embedding ?? {} },
4917
+ summary: { ...base3.summary, ...parsed.summary ?? {} },
4918
+ sources: mergeSources(base3.sources, parsed.sources),
4919
+ privacy: mergePrivacy(base3.privacy, parsed.privacy),
4920
+ sync: mergeSync(base3.sync, parsed.sync),
4921
+ team: mergeTeam(base3.team, parsed.team)
4922
4922
  };
4923
4923
  if (mtimeMs >= 0)
4924
4924
  _settingsCache = { mtimeMs, size, value: structuredClone(result) };
@@ -5125,10 +5125,10 @@ function legacyAutoSyncedHomes() {
5125
5125
  { base: join3(home, ".local", "share", "opencode"), marker: "opencode.db" },
5126
5126
  { base: join3(home, ".cursor"), marker: "chats" }
5127
5127
  ];
5128
- for (const { base: base2, marker } of bases) {
5129
- add(base2, marker);
5130
- const parent = dirname3(base2);
5131
- const leaf = base2.slice(parent.length + 1);
5128
+ for (const { base: base3, marker } of bases) {
5129
+ add(base3, marker);
5130
+ const parent = dirname3(base3);
5131
+ const leaf = base3.slice(parent.length + 1);
5132
5132
  try {
5133
5133
  for (const e of readdirSync2(parent, { withFileTypes: true })) {
5134
5134
  if (!e.isDirectory())
@@ -5326,17 +5326,17 @@ function cursorHomeDir() {
5326
5326
  function cursorIdeHomeDir() {
5327
5327
  return process.env.CHAT_RECALL_CURSOR_IDE_HOME || sources().cursorIdeHome || (process.platform === "darwin" ? join4(homedir3(), "Library", "Application Support", "Cursor") : join4(homedir3(), ".config", "Cursor"));
5328
5328
  }
5329
- function siblingHomes(base2, overridden, suffix) {
5329
+ function siblingHomes(base3, overridden, suffix) {
5330
5330
  const roots = [];
5331
5331
  const add = (home) => {
5332
5332
  const root = suffix ? join4(home, suffix) : home;
5333
5333
  if (existsSync4(root) && !roots.includes(root))
5334
5334
  roots.push(root);
5335
5335
  };
5336
- add(base2);
5336
+ add(base3);
5337
5337
  if (!overridden) {
5338
- const parent = dirname4(base2);
5339
- const leaf = basename(base2);
5338
+ const parent = dirname4(base3);
5339
+ const leaf = basename(base3);
5340
5340
  try {
5341
5341
  for (const entry of readdirSync3(parent, { withFileTypes: true })) {
5342
5342
  if (!entry.isDirectory())
@@ -5594,8 +5594,8 @@ function _clearServerRulePack() {
5594
5594
  serverPack = null;
5595
5595
  }
5596
5596
  function activeRules() {
5597
- const base2 = settingsView().rules;
5598
- return serverPack && serverPack.rules.length > 0 ? [...base2, ...serverPack.rules] : base2;
5597
+ const base3 = settingsView().rules;
5598
+ return serverPack && serverPack.rules.length > 0 ? [...base3, ...serverPack.rules] : base3;
5599
5599
  }
5600
5600
  function isRedactionEnabled() {
5601
5601
  const env = isEnvRedactionRequested();
@@ -6063,6 +6063,64 @@ var init_chunker = __esm({
6063
6063
  }
6064
6064
  });
6065
6065
 
6066
+ // ../engine/dist/core/project-dir-name.js
6067
+ import { readdirSync as readdirSync4 } from "node:fs";
6068
+ function looksWindowsEncoded(dirName) {
6069
+ return /^[A-Za-z]--/.test(dirName);
6070
+ }
6071
+ function decodeProjectDirName(dirName) {
6072
+ if (!dirName)
6073
+ return "";
6074
+ if (looksWindowsEncoded(dirName)) {
6075
+ const drive = dirName[0].toUpperCase();
6076
+ const rest = dirName.slice(3).replace(/-/g, "\\");
6077
+ return `${drive}:\\${rest}`;
6078
+ }
6079
+ return "/" + dirName.replace(/^-/, "").replace(/-/g, "/");
6080
+ }
6081
+ function resolveProjectDirName(dirName, deps = {}) {
6082
+ const structural = decodeProjectDirName(dirName);
6083
+ if (!dirName)
6084
+ return structural;
6085
+ if (looksWindowsEncoded(dirName))
6086
+ return structural;
6087
+ const readdir3 = deps.readdir ?? ((p) => readdirSync4(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name));
6088
+ const encodeName = (name) => name.replace(/[-_.]/g, "-");
6089
+ let remaining = dirName.replace(/^-/, "");
6090
+ let current = "/";
6091
+ while (remaining) {
6092
+ let children;
6093
+ try {
6094
+ children = readdir3(current);
6095
+ } catch {
6096
+ break;
6097
+ }
6098
+ let best = "";
6099
+ for (const child of children) {
6100
+ const encoded = encodeName(child);
6101
+ if (!encoded || encoded.length < best.length)
6102
+ continue;
6103
+ if (!remaining.startsWith(encoded))
6104
+ continue;
6105
+ const next = remaining[encoded.length];
6106
+ if (next !== void 0 && next !== "-")
6107
+ continue;
6108
+ if (encoded.length > best.length)
6109
+ best = child;
6110
+ }
6111
+ if (!best)
6112
+ break;
6113
+ current = current === "/" ? `/${best}` : `${current}/${best}`;
6114
+ remaining = remaining.slice(encodeName(best).length).replace(/^-/, "");
6115
+ }
6116
+ return remaining ? structural : current;
6117
+ }
6118
+ var init_project_dir_name = __esm({
6119
+ "../engine/dist/core/project-dir-name.js"() {
6120
+ "use strict";
6121
+ }
6122
+ });
6123
+
6066
6124
  // ../engine/dist/core/model-pricing.js
6067
6125
  function pricingFor(modelId) {
6068
6126
  if (!modelId)
@@ -6234,7 +6292,7 @@ var init_model_pricing = __esm({
6234
6292
  });
6235
6293
 
6236
6294
  // ../engine/dist/parsers/session.js
6237
- import { createReadStream, existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync2, statSync as statSync4, openSync, readSync, closeSync } from "fs";
6295
+ import { createReadStream, existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync2, statSync as statSync4, openSync, readSync, closeSync } from "fs";
6238
6296
  import { createInterface } from "readline";
6239
6297
  import { join as join5, basename as basename2 } from "path";
6240
6298
  function removeCodeBlocks(text) {
@@ -6305,7 +6363,7 @@ function* iterAllSessionIndices(claudeDir) {
6305
6363
  if (!existsSync5(dir)) {
6306
6364
  return;
6307
6365
  }
6308
- const entries = readdirSync4(dir, { withFileTypes: true });
6366
+ const entries = readdirSync5(dir, { withFileTypes: true });
6309
6367
  for (const entry of entries) {
6310
6368
  if (!entry.isDirectory())
6311
6369
  continue;
@@ -6327,7 +6385,7 @@ function getSubagentPaths(sessionPath) {
6327
6385
  const subagentsDir = join5(sessionPath.slice(0, -6), "subagents");
6328
6386
  if (!existsSync5(subagentsDir))
6329
6387
  return [];
6330
- return readdirSync4(subagentsDir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => join5(subagentsDir, f));
6388
+ return readdirSync5(subagentsDir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => join5(subagentsDir, f));
6331
6389
  }
6332
6390
  function getSubagentProjectPath(sessionPath, folderDerivedPath) {
6333
6391
  const subagentPaths = getSubagentPaths(sessionPath);
@@ -6644,40 +6702,6 @@ ${resultText}` : resultText;
6644
6702
  });
6645
6703
  return content;
6646
6704
  }
6647
- function decodeDirName(dirName) {
6648
- const segments = dirName.replace(/^-/, "").split("-");
6649
- let currentPath = "/";
6650
- let i = 0;
6651
- while (i < segments.length) {
6652
- let matched = false;
6653
- const singleCandidate = join5(currentPath, segments[i]);
6654
- if (existsSync5(singleCandidate)) {
6655
- currentPath = singleCandidate;
6656
- i++;
6657
- matched = true;
6658
- continue;
6659
- }
6660
- for (let len = 2; len <= segments.length - i; len++) {
6661
- const joinedSegments = segments.slice(i, i + len);
6662
- for (const sep4 of ["-", "_", "."]) {
6663
- const candidate = join5(currentPath, joinedSegments.join(sep4));
6664
- if (existsSync5(candidate)) {
6665
- currentPath = candidate;
6666
- i += len;
6667
- matched = true;
6668
- break;
6669
- }
6670
- }
6671
- if (matched)
6672
- break;
6673
- }
6674
- if (!matched) {
6675
- currentPath = join5(currentPath, segments[i]);
6676
- i++;
6677
- }
6678
- }
6679
- return currentPath;
6680
- }
6681
6705
  function* getAllSessions(claudeDir) {
6682
6706
  const seenSessionIds = /* @__PURE__ */ new Set();
6683
6707
  const projectDirs = claudeDir ? [claudeDir] : claudeProjectDirs();
@@ -6692,12 +6716,12 @@ function* getAllSessions(claudeDir) {
6692
6716
  }
6693
6717
  if (!existsSync5(dir))
6694
6718
  continue;
6695
- const dirEntries = readdirSync4(dir, { withFileTypes: true });
6719
+ const dirEntries = readdirSync5(dir, { withFileTypes: true });
6696
6720
  for (const projDirEntry of dirEntries) {
6697
6721
  if (!projDirEntry.isDirectory())
6698
6722
  continue;
6699
6723
  const projectPath = join5(dir, projDirEntry.name);
6700
- const files = readdirSync4(projectPath);
6724
+ const files = readdirSync5(projectPath);
6701
6725
  for (const file of files) {
6702
6726
  if (!file.endsWith(".jsonl"))
6703
6727
  continue;
@@ -6711,7 +6735,7 @@ function* getAllSessions(claudeDir) {
6711
6735
  const fullPath = join5(projectPath, file);
6712
6736
  try {
6713
6737
  const stat3 = statSync4(fullPath);
6714
- const derivedProjectPath = decodeDirName(projDirEntry.name);
6738
+ const derivedProjectPath = resolveProjectDirName(projDirEntry.name);
6715
6739
  const effectiveMtime = getSessionMtime(fullPath);
6716
6740
  let projectPath2 = "";
6717
6741
  if (hasSubagentsDir(fullPath)) {
@@ -6750,6 +6774,7 @@ var init_session = __esm({
6750
6774
  init_chunker();
6751
6775
  init_claude2();
6752
6776
  init_tool_paths();
6777
+ init_project_dir_name();
6753
6778
  init_model_pricing();
6754
6779
  }
6755
6780
  });
@@ -7487,7 +7512,7 @@ async function archiveRawSession(store, sessionId) {
7487
7512
  return await store.putRawSession(sessionId, exp.tool, Math.floor(exp.mtime), gz, size);
7488
7513
  }
7489
7514
  function detectForkPredecessor(sessionPath) {
7490
- const { readFileSync: readFileSync50, readdirSync: readdirSync36 } = requireFs();
7515
+ const { readFileSync: readFileSync50, readdirSync: readdirSync37 } = requireFs();
7491
7516
  const { dirname: dirname28, join: join61, basename: basename30 } = requirePath();
7492
7517
  let text;
7493
7518
  try {
@@ -7517,7 +7542,7 @@ function detectForkPredecessor(sessionPath) {
7517
7542
  const self = basename30(sessionPath);
7518
7543
  let entries;
7519
7544
  try {
7520
- entries = readdirSync36(dir);
7545
+ entries = readdirSync37(dir);
7521
7546
  } catch {
7522
7547
  return null;
7523
7548
  }
@@ -8342,7 +8367,7 @@ var init_flat_string = __esm({
8342
8367
 
8343
8368
  // ../engine/dist/core/backends/gemini.js
8344
8369
  import { createHash as createHash3 } from "crypto";
8345
- import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync3, statSync as statSync6 } from "fs";
8370
+ import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync3, statSync as statSync6 } from "fs";
8346
8371
  import { basename as basename7, join as join8 } from "path";
8347
8372
  function readSessionId(path, format) {
8348
8373
  try {
@@ -8456,7 +8481,7 @@ var init_gemini2 = __esm({
8456
8481
  const out = [];
8457
8482
  const seen = /* @__PURE__ */ new Map();
8458
8483
  for (const root of geminiTmpDirs()) {
8459
- for (const projDir of readdirSync5(root, { withFileTypes: true })) {
8484
+ for (const projDir of readdirSync6(root, { withFileTypes: true })) {
8460
8485
  if (!projDir.isDirectory())
8461
8486
  continue;
8462
8487
  const projectPath = projMap.get(projDir.name) || "";
@@ -8467,7 +8492,7 @@ var init_gemini2 = __esm({
8467
8492
  continue;
8468
8493
  let files;
8469
8494
  try {
8470
- files = readdirSync5(chatsDir);
8495
+ files = readdirSync6(chatsDir);
8471
8496
  } catch {
8472
8497
  continue;
8473
8498
  }
@@ -8690,7 +8715,7 @@ var init_gemini2 = __esm({
8690
8715
  return [];
8691
8716
  const projMap = this.loadProjectMap();
8692
8717
  const candidates2 = [];
8693
- for (const proj of readdirSync5(tmpRoot, { withFileTypes: true })) {
8718
+ for (const proj of readdirSync6(tmpRoot, { withFileTypes: true })) {
8694
8719
  if (!proj.isDirectory())
8695
8720
  continue;
8696
8721
  const projectPath = projMap.get(proj.name) || "";
@@ -8704,7 +8729,7 @@ var init_gemini2 = __esm({
8704
8729
  continue;
8705
8730
  let files;
8706
8731
  try {
8707
- files = readdirSync5(chats);
8732
+ files = readdirSync6(chats);
8708
8733
  } catch {
8709
8734
  continue;
8710
8735
  }
@@ -9370,7 +9395,7 @@ var init_opencode = __esm({
9370
9395
  });
9371
9396
 
9372
9397
  // ../engine/dist/core/backends/codex.js
9373
- import { readdirSync as readdirSync6, readFileSync as readFileSync4, statSync as statSync7, openSync as openSync3, readSync as readSync3, closeSync as closeSync3 } from "fs";
9398
+ import { readdirSync as readdirSync7, readFileSync as readFileSync4, statSync as statSync7, openSync as openSync3, readSync as readSync3, closeSync as closeSync3 } from "fs";
9374
9399
  import { basename as basename8, dirname as dirname9, join as join10 } from "path";
9375
9400
  function parseShellWrites(cmd) {
9376
9401
  const out = [];
@@ -9466,7 +9491,7 @@ function parseCodexPatch(input) {
9466
9491
  function* walkRollouts(root) {
9467
9492
  let entries;
9468
9493
  try {
9469
- entries = readdirSync6(root, { withFileTypes: true });
9494
+ entries = readdirSync7(root, { withFileTypes: true });
9470
9495
  } catch {
9471
9496
  return;
9472
9497
  }
@@ -9758,7 +9783,7 @@ var init_codex2 = __esm({
9758
9783
  const dayDir = located.path.substring(0, located.path.lastIndexOf("/"));
9759
9784
  let entries = [];
9760
9785
  try {
9761
- entries = readdirSync6(dayDir);
9786
+ entries = readdirSync7(dayDir);
9762
9787
  } catch {
9763
9788
  }
9764
9789
  for (const entry of entries) {
@@ -9944,7 +9969,7 @@ var init_codex2 = __esm({
9944
9969
  const scanDir = (dir) => {
9945
9970
  let entries;
9946
9971
  try {
9947
- entries = readdirSync6(dir, { withFileTypes: true });
9972
+ entries = readdirSync7(dir, { withFileTypes: true });
9948
9973
  } catch {
9949
9974
  return;
9950
9975
  }
@@ -10037,7 +10062,7 @@ var init_codex2 = __esm({
10037
10062
  if (parentId) {
10038
10063
  const dayDir = dirname9(loc.path);
10039
10064
  try {
10040
- for (const f of readdirSync6(dayDir)) {
10065
+ for (const f of readdirSync7(dayDir)) {
10041
10066
  if (!f.endsWith(".jsonl") || !f.startsWith("rollout-"))
10042
10067
  continue;
10043
10068
  const p = join10(dayDir, f);
@@ -10084,21 +10109,8 @@ var init_codex2 = __esm({
10084
10109
  });
10085
10110
 
10086
10111
  // ../engine/dist/core/backends/agy.js
10087
- import { existsSync as existsSync9, readdirSync as readdirSync7, readFileSync as readFileSync5, statSync as statSync8 } from "fs";
10112
+ import { existsSync as existsSync9, readdirSync as readdirSync8, readFileSync as readFileSync5, statSync as statSync8 } from "fs";
10088
10113
  import { basename as basename9, dirname as dirname10, join as join11 } from "path";
10089
- function getFallbackProjectPath(homeDir) {
10090
- try {
10091
- const settingsPath2 = join11(homeDir, "settings.json");
10092
- if (existsSync9(settingsPath2)) {
10093
- const json = JSON.parse(readFileSync5(settingsPath2, "utf-8"));
10094
- if (Array.isArray(json?.trustedWorkspaces) && json.trustedWorkspaces[0]) {
10095
- return json.trustedWorkspaces[0];
10096
- }
10097
- }
10098
- } catch {
10099
- }
10100
- return "";
10101
- }
10102
10114
  function agyTranscriptPath(logsDir) {
10103
10115
  const full = join11(logsDir, "transcript_full.jsonl");
10104
10116
  if (existsSync9(full))
@@ -10111,8 +10123,8 @@ function agyTranscriptPath(logsDir) {
10111
10123
  function resolveAgyProject(logsDir, homeDir) {
10112
10124
  const deriveFrom = agyTranscriptPath(logsDir);
10113
10125
  if (!deriveFrom)
10114
- return getFallbackProjectPath(homeDir);
10115
- return extractProjectPathFromTranscript(deriveFrom, homeDir) || getFallbackProjectPath(homeDir);
10126
+ return "";
10127
+ return extractProjectPathFromTranscript(deriveFrom, homeDir);
10116
10128
  }
10117
10129
  function extractProjectPathFromTranscript(path, homeDir) {
10118
10130
  let content;
@@ -10266,7 +10278,7 @@ var init_agy = __esm({
10266
10278
  for (const brainDir of agyBrainDirs()) {
10267
10279
  let sessionDirs;
10268
10280
  try {
10269
- sessionDirs = readdirSync7(brainDir);
10281
+ sessionDirs = readdirSync8(brainDir);
10270
10282
  } catch {
10271
10283
  continue;
10272
10284
  }
@@ -10541,7 +10553,7 @@ var init_agy = __esm({
10541
10553
  const candidates2 = [];
10542
10554
  let sessionDirs;
10543
10555
  try {
10544
- sessionDirs = readdirSync7(brainDir);
10556
+ sessionDirs = readdirSync8(brainDir);
10545
10557
  } catch {
10546
10558
  return [];
10547
10559
  }
@@ -10617,7 +10629,7 @@ var init_agy = __esm({
10617
10629
  });
10618
10630
 
10619
10631
  // ../engine/dist/core/backends/cursor-store.js
10620
- import { existsSync as existsSync10, readdirSync as readdirSync8, readFileSync as readFileSync6, statSync as statSync9 } from "fs";
10632
+ import { existsSync as existsSync10, readdirSync as readdirSync9, readFileSync as readFileSync6, statSync as statSync9 } from "fs";
10621
10633
  import { join as join12 } from "path";
10622
10634
  function cursorProjectSlug(absPath) {
10623
10635
  return absPath.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
@@ -10626,7 +10638,7 @@ function listChatsIn(chatsRoot) {
10626
10638
  const out = [];
10627
10639
  let hashDirs;
10628
10640
  try {
10629
- hashDirs = readdirSync8(chatsRoot);
10641
+ hashDirs = readdirSync9(chatsRoot);
10630
10642
  } catch {
10631
10643
  return out;
10632
10644
  }
@@ -10636,7 +10648,7 @@ function listChatsIn(chatsRoot) {
10636
10648
  try {
10637
10649
  if (!statSync9(hashPath3).isDirectory())
10638
10650
  continue;
10639
- chatIds = readdirSync8(hashPath3);
10651
+ chatIds = readdirSync9(hashPath3);
10640
10652
  } catch {
10641
10653
  continue;
10642
10654
  }
@@ -10681,7 +10693,7 @@ function findChat(chatsRoots, chatId) {
10681
10693
  for (const root of chatsRoots) {
10682
10694
  let hashDirs;
10683
10695
  try {
10684
- hashDirs = readdirSync8(root);
10696
+ hashDirs = readdirSync9(root);
10685
10697
  } catch {
10686
10698
  continue;
10687
10699
  }
@@ -11002,7 +11014,7 @@ var init_cursor_store = __esm({
11002
11014
  });
11003
11015
 
11004
11016
  // ../engine/dist/core/backends/cursor-ide.js
11005
- import { existsSync as existsSync11, readFileSync as readFileSync7, readdirSync as readdirSync9, statSync as statSync10 } from "fs";
11017
+ import { existsSync as existsSync11, readFileSync as readFileSync7, readdirSync as readdirSync10, statSync as statSync10 } from "fs";
11006
11018
  import { dirname as dirname11, join as join13 } from "path";
11007
11019
  function jsonOf(value) {
11008
11020
  if (value == null)
@@ -11065,7 +11077,7 @@ function workspaceIndex(workspaceRoots) {
11065
11077
  for (const root of workspaceRoots) {
11066
11078
  let entries;
11067
11079
  try {
11068
- entries = readdirSync9(root);
11080
+ entries = readdirSync10(root);
11069
11081
  } catch {
11070
11082
  continue;
11071
11083
  }
@@ -11152,7 +11164,7 @@ function listComposers(globalDbPath, workspaceRoots) {
11152
11164
  for (const root of workspaceRoots) {
11153
11165
  let entries;
11154
11166
  try {
11155
- entries = readdirSync9(root);
11167
+ entries = readdirSync10(root);
11156
11168
  } catch {
11157
11169
  continue;
11158
11170
  }
@@ -12052,28 +12064,8 @@ var init_shadow = __esm({
12052
12064
  }
12053
12065
  });
12054
12066
 
12055
- // ../engine/dist/core/project-dir-name.js
12056
- function looksWindowsEncoded(dirName) {
12057
- return /^[A-Za-z]--/.test(dirName);
12058
- }
12059
- function decodeProjectDirName(dirName) {
12060
- if (!dirName)
12061
- return "";
12062
- if (looksWindowsEncoded(dirName)) {
12063
- const drive = dirName[0].toUpperCase();
12064
- const rest = dirName.slice(3).replace(/-/g, "\\");
12065
- return `${drive}:\\${rest}`;
12066
- }
12067
- return "/" + dirName.replace(/^-/, "").replace(/-/g, "/");
12068
- }
12069
- var init_project_dir_name = __esm({
12070
- "../engine/dist/core/project-dir-name.js"() {
12071
- "use strict";
12072
- }
12073
- });
12074
-
12075
12067
  // ../engine/dist/core/live-session-scan.js
12076
- import { existsSync as existsSync14, readdirSync as readdirSync10, readFileSync as readFileSync10, statSync as statSync13, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
12068
+ import { existsSync as existsSync14, readdirSync as readdirSync11, readFileSync as readFileSync10, statSync as statSync13, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
12077
12069
  import { join as join16, basename as basename10 } from "path";
12078
12070
  import { createHash as createHash5 } from "crypto";
12079
12071
  function lazyGeminiProjectsJson() {
@@ -12110,7 +12102,7 @@ function sessionFileIndex() {
12110
12102
  seenRoots.add(root);
12111
12103
  let entries;
12112
12104
  try {
12113
- entries = readdirSync10(root, { withFileTypes: true });
12105
+ entries = readdirSync11(root, { withFileTypes: true });
12114
12106
  } catch {
12115
12107
  continue;
12116
12108
  }
@@ -12120,7 +12112,7 @@ function sessionFileIndex() {
12120
12112
  const dir = join16(root, entry.name);
12121
12113
  let names;
12122
12114
  try {
12123
- names = readdirSync10(dir);
12115
+ names = readdirSync11(dir);
12124
12116
  } catch {
12125
12117
  continue;
12126
12118
  }
@@ -12131,7 +12123,7 @@ function sessionFileIndex() {
12131
12123
  const located = {
12132
12124
  path: join16(dir, name),
12133
12125
  projectDir: entry.name,
12134
- projectPath: decodeProjectDirName(entry.name)
12126
+ projectPath: resolveProjectDirName(entry.name)
12135
12127
  };
12136
12128
  const prior = byId.get(id);
12137
12129
  if (prior)
@@ -12218,7 +12210,7 @@ function resolveSessionContentPaths(sessionFile) {
12218
12210
  const subDir = join16(sessionFile.slice(0, -6), "subagents");
12219
12211
  if (!existsSync14(subDir))
12220
12212
  return [sessionFile];
12221
- const subPaths = readdirSync10(subDir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => join16(subDir, f));
12213
+ const subPaths = readdirSync11(subDir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => join16(subDir, f));
12222
12214
  return [sessionFile, ...subPaths];
12223
12215
  }
12224
12216
  function loadGeminiProjectMap() {
@@ -12266,7 +12258,7 @@ function geminiIndex() {
12266
12258
  continue;
12267
12259
  let projs;
12268
12260
  try {
12269
- projs = readdirSync10(tmpRoot, { withFileTypes: true });
12261
+ projs = readdirSync11(tmpRoot, { withFileTypes: true });
12270
12262
  } catch {
12271
12263
  continue;
12272
12264
  }
@@ -12278,14 +12270,14 @@ function geminiIndex() {
12278
12270
  continue;
12279
12271
  let files;
12280
12272
  try {
12281
- files = readdirSync10(chats);
12273
+ files = readdirSync11(chats);
12282
12274
  } catch {
12283
12275
  continue;
12284
12276
  }
12285
12277
  for (const f of files) {
12286
12278
  if (!isGeminiChat(f))
12287
12279
  continue;
12288
- const base2 = f.replace(/\.jsonl?$/, "");
12280
+ const base3 = f.replace(/\.jsonl?$/, "");
12289
12281
  const located = {
12290
12282
  path: join16(chats, f),
12291
12283
  projectDir: proj.name,
@@ -12293,9 +12285,9 @@ function geminiIndex() {
12293
12285
  format: f.endsWith(".jsonl") ? "jsonl" : "json"
12294
12286
  };
12295
12287
  all.push(located);
12296
- if (!byBase.has(base2))
12297
- byBase.set(base2, located);
12298
- const tail = base2.split("-").pop() || "";
12288
+ if (!byBase.has(base3))
12289
+ byBase.set(base3, located);
12290
+ const tail = base3.split("-").pop() || "";
12299
12291
  if (tail.length >= 4 && !byTail.has(tail))
12300
12292
  byTail.set(tail, located);
12301
12293
  }
@@ -12397,21 +12389,21 @@ function findCodexSessionFile(sessionId) {
12397
12389
  function findCodexSessionFileIn(root, sessionId) {
12398
12390
  if (!existsSync14(root))
12399
12391
  return null;
12400
- for (const year of readdirSync10(root, { withFileTypes: true })) {
12392
+ for (const year of readdirSync11(root, { withFileTypes: true })) {
12401
12393
  if (!year.isDirectory())
12402
12394
  continue;
12403
12395
  const yearPath = join16(root, year.name);
12404
- for (const month of readdirSync10(yearPath, { withFileTypes: true })) {
12396
+ for (const month of readdirSync11(yearPath, { withFileTypes: true })) {
12405
12397
  if (!month.isDirectory())
12406
12398
  continue;
12407
12399
  const monthPath = join16(yearPath, month.name);
12408
- for (const day of readdirSync10(monthPath, { withFileTypes: true })) {
12400
+ for (const day of readdirSync11(monthPath, { withFileTypes: true })) {
12409
12401
  if (!day.isDirectory())
12410
12402
  continue;
12411
12403
  const dayPath = join16(monthPath, day.name);
12412
12404
  let files;
12413
12405
  try {
12414
- files = readdirSync10(dayPath);
12406
+ files = readdirSync11(dayPath);
12415
12407
  } catch {
12416
12408
  continue;
12417
12409
  }
@@ -12426,7 +12418,7 @@ function findCodexSessionFileIn(root, sessionId) {
12426
12418
  }
12427
12419
  }
12428
12420
  function scanDir(dir) {
12429
- for (const entry of readdirSync10(dir, { withFileTypes: true })) {
12421
+ for (const entry of readdirSync11(dir, { withFileTypes: true })) {
12430
12422
  if (entry.isDirectory()) {
12431
12423
  const found = scanDir(join16(dir, entry.name));
12432
12424
  if (found)
@@ -12581,7 +12573,7 @@ var init_first_prompt = __esm({
12581
12573
  });
12582
12574
 
12583
12575
  // ../engine/dist/core/backends/claude.js
12584
- import { existsSync as existsSync16, readdirSync as readdirSync11, readFileSync as readFileSync11, statSync as statSync14, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
12576
+ import { existsSync as existsSync16, readdirSync as readdirSync12, readFileSync as readFileSync11, statSync as statSync14, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
12585
12577
  import { join as join17, basename as basename11 } from "path";
12586
12578
  function stripReminders(text) {
12587
12579
  return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
@@ -12736,13 +12728,13 @@ var init_claude2 = __esm({
12736
12728
  collectSessionsFromRoot(root, cutoff, filter, seen, out) {
12737
12729
  if (!existsSync16(root))
12738
12730
  return;
12739
- for (const proj of readdirSync11(root, { withFileTypes: true })) {
12731
+ for (const proj of readdirSync12(root, { withFileTypes: true })) {
12740
12732
  if (!proj.isDirectory())
12741
12733
  continue;
12742
12734
  if (filter && !proj.name.toLowerCase().includes(filter))
12743
12735
  continue;
12744
12736
  const projPath = join17(root, proj.name);
12745
- const projectPath = decodeProjectDirName(proj.name);
12737
+ const projectPath = resolveProjectDirName(proj.name);
12746
12738
  const indexPath = join17(projPath, "sessions-index.json");
12747
12739
  if (existsSync16(indexPath)) {
12748
12740
  try {
@@ -12773,7 +12765,7 @@ var init_claude2 = __esm({
12773
12765
  }
12774
12766
  let files;
12775
12767
  try {
12776
- files = readdirSync11(projPath);
12768
+ files = readdirSync12(projPath);
12777
12769
  } catch {
12778
12770
  continue;
12779
12771
  }
@@ -13042,7 +13034,7 @@ var init_claude2 = __esm({
13042
13034
  for (const root of claudeProjectDirs()) {
13043
13035
  if (!existsSync16(root))
13044
13036
  continue;
13045
- for (const projEntry of readdirSync11(root, { withFileTypes: true })) {
13037
+ for (const projEntry of readdirSync12(root, { withFileTypes: true })) {
13046
13038
  if (!projEntry.isDirectory())
13047
13039
  continue;
13048
13040
  if (opts.projectFilter && !projEntry.name.toLowerCase().includes(opts.projectFilter.toLowerCase()))
@@ -13050,7 +13042,7 @@ var init_claude2 = __esm({
13050
13042
  const projPath = join17(root, projEntry.name);
13051
13043
  let files;
13052
13044
  try {
13053
- files = readdirSync11(projPath);
13045
+ files = readdirSync12(projPath);
13054
13046
  } catch {
13055
13047
  continue;
13056
13048
  }
@@ -13063,7 +13055,7 @@ var init_claude2 = __esm({
13063
13055
  let mtime = st.mtimeMs;
13064
13056
  const subDir = join17(projPath, f.slice(0, -6), "subagents");
13065
13057
  if (existsSync16(subDir)) {
13066
- for (const sub of readdirSync11(subDir)) {
13058
+ for (const sub of readdirSync12(subDir)) {
13067
13059
  try {
13068
13060
  const subSt = statSync14(join17(subDir, sub));
13069
13061
  if (subSt.mtimeMs > mtime)
@@ -13180,7 +13172,7 @@ __export(project_resolver_exports, {
13180
13172
  });
13181
13173
  import { execSync } from "child_process";
13182
13174
  import { createHash as createHash6 } from "crypto";
13183
- import { existsSync as existsSync17, readFileSync as readFileSync12, realpathSync as realpathSync2, readdirSync as readdirSync12, statSync as statSync15 } from "fs";
13175
+ import { existsSync as existsSync17, readFileSync as readFileSync12, realpathSync as realpathSync2, readdirSync as readdirSync13, statSync as statSync15 } from "fs";
13184
13176
  import { basename as basename12, dirname as dirname13, isAbsolute, join as join18, resolve, sep } from "path";
13185
13177
  function getProjectsConfigPath() {
13186
13178
  return join18(getDataDir(), "projects.json");
@@ -13453,7 +13445,7 @@ function countGitChildren(dir) {
13453
13445
  let count = 0;
13454
13446
  let entries;
13455
13447
  try {
13456
- entries = readdirSync12(dir);
13448
+ entries = readdirSync13(dir);
13457
13449
  } catch {
13458
13450
  return 0;
13459
13451
  }
@@ -13568,7 +13560,7 @@ var init_credentials_env = __esm({
13568
13560
 
13569
13561
  // ../engine/dist/core/source-discovery.js
13570
13562
  import { createHash as createHash7 } from "crypto";
13571
- import { existsSync as existsSync18, readdirSync as readdirSync13, statSync as statSync16 } from "fs";
13563
+ import { existsSync as existsSync18, readdirSync as readdirSync14, statSync as statSync16 } from "fs";
13572
13564
  import { join as join19 } from "path";
13573
13565
  function sourceId(path) {
13574
13566
  return "src_" + createHash7("sha256").update(path).digest("hex").slice(0, 12);
@@ -13605,7 +13597,7 @@ function countTranscripts(root, tool) {
13605
13597
  return;
13606
13598
  let entries;
13607
13599
  try {
13608
- entries = readdirSync13(dir, { withFileTypes: true });
13600
+ entries = readdirSync14(dir, { withFileTypes: true });
13609
13601
  } catch {
13610
13602
  return;
13611
13603
  }
@@ -13684,7 +13676,7 @@ __export(home_discovery_exports, {
13684
13676
  sessionIdsInHome: () => sessionIdsInHome
13685
13677
  });
13686
13678
  import { execFileSync as execFileSync2 } from "child_process";
13687
- import { existsSync as existsSync19, readdirSync as readdirSync14, readFileSync as readFileSync13, realpathSync as realpathSync3, statSync as statSync17 } from "fs";
13679
+ import { existsSync as existsSync19, readdirSync as readdirSync15, readFileSync as readFileSync13, realpathSync as realpathSync3, statSync as statSync17 } from "fs";
13688
13680
  import { homedir as homedir5, platform as osPlatform } from "os";
13689
13681
  import { join as join20 } from "path";
13690
13682
  function candidateRoots(plat = osPlatform(), env = process.env) {
@@ -13728,7 +13720,7 @@ function dirHasTranscript(root, match, depth) {
13728
13720
  return false;
13729
13721
  let entries;
13730
13722
  try {
13731
- entries = readdirSync14(root, { withFileTypes: true });
13723
+ entries = readdirSync15(root, { withFileTypes: true });
13732
13724
  } catch {
13733
13725
  return false;
13734
13726
  }
@@ -13760,7 +13752,7 @@ function countSessions(dir, tool) {
13760
13752
  return;
13761
13753
  let entries;
13762
13754
  try {
13763
- entries = readdirSync14(d, { withFileTypes: true });
13755
+ entries = readdirSync15(d, { withFileTypes: true });
13764
13756
  } catch {
13765
13757
  return;
13766
13758
  }
@@ -13812,7 +13804,7 @@ function sessionIdsInHome(dir, tool) {
13812
13804
  return [];
13813
13805
  if (tool === "agy") {
13814
13806
  try {
13815
- for (const e of readdirSync14(join20(dir, "brain"), { withFileTypes: true })) {
13807
+ for (const e of readdirSync15(join20(dir, "brain"), { withFileTypes: true })) {
13816
13808
  if (e.isDirectory())
13817
13809
  out.add(`agy_${e.name}`);
13818
13810
  }
@@ -13822,11 +13814,11 @@ function sessionIdsInHome(dir, tool) {
13822
13814
  }
13823
13815
  if (tool === "cursor") {
13824
13816
  try {
13825
- for (const hash of readdirSync14(join20(dir, "chats"), { withFileTypes: true })) {
13817
+ for (const hash of readdirSync15(join20(dir, "chats"), { withFileTypes: true })) {
13826
13818
  if (!hash.isDirectory())
13827
13819
  continue;
13828
13820
  try {
13829
- for (const chat of readdirSync14(join20(dir, "chats", hash.name), { withFileTypes: true })) {
13821
+ for (const chat of readdirSync15(join20(dir, "chats", hash.name), { withFileTypes: true })) {
13830
13822
  if (chat.isDirectory())
13831
13823
  out.add(`cursor_${chat.name}`);
13832
13824
  }
@@ -13842,7 +13834,7 @@ function sessionIdsInHome(dir, tool) {
13842
13834
  return;
13843
13835
  let entries;
13844
13836
  try {
13845
- entries = readdirSync14(d, { withFileTypes: true });
13837
+ entries = readdirSync15(d, { withFileTypes: true });
13846
13838
  } catch {
13847
13839
  return;
13848
13840
  }
@@ -13914,7 +13906,7 @@ function runningToolHomes(plat = osPlatform()) {
13914
13906
  if (plat === "linux") {
13915
13907
  let pids;
13916
13908
  try {
13917
- pids = readdirSync14("/proc").filter((p) => /^\d+$/.test(p));
13909
+ pids = readdirSync15("/proc").filter((p) => /^\d+$/.test(p));
13918
13910
  } catch {
13919
13911
  return out;
13920
13912
  }
@@ -13994,7 +13986,7 @@ function walkForHomes(dir, depth, visit) {
13994
13986
  return;
13995
13987
  let entries;
13996
13988
  try {
13997
- entries = readdirSync14(dir, { withFileTypes: true });
13989
+ entries = readdirSync15(dir, { withFileTypes: true });
13998
13990
  } catch {
13999
13991
  return;
14000
13992
  }
@@ -15670,7 +15662,7 @@ var init_session_source = __esm({
15670
15662
  });
15671
15663
 
15672
15664
  // ../engine/dist/core/utils.js
15673
- import { existsSync as existsSync22, readdirSync as readdirSync15 } from "fs";
15665
+ import { existsSync as existsSync22, readdirSync as readdirSync16 } from "fs";
15674
15666
  import { homedir as homedir6 } from "os";
15675
15667
  import { join as join23 } from "path";
15676
15668
  function splitByHeaders(markdown) {
@@ -15721,7 +15713,7 @@ function getClaudeDirs() {
15721
15713
  }
15722
15714
  add(join23(home, ".claude"));
15723
15715
  try {
15724
- const entries = readdirSync15(home, { withFileTypes: true });
15716
+ const entries = readdirSync16(home, { withFileTypes: true });
15725
15717
  for (const entry of entries) {
15726
15718
  if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
15727
15719
  const candidate = join23(home, entry.name);
@@ -15751,7 +15743,7 @@ var init_utils = __esm({
15751
15743
  });
15752
15744
 
15753
15745
  // ../engine/dist/parsers/plan-source.js
15754
- import { existsSync as existsSync23, readdirSync as readdirSync16, readFileSync as readFileSync16, statSync as statSync21 } from "fs";
15746
+ import { existsSync as existsSync23, readdirSync as readdirSync17, readFileSync as readFileSync16, statSync as statSync21 } from "fs";
15755
15747
  import { homedir as homedir7 } from "os";
15756
15748
  import { join as join24, basename as basename13 } from "path";
15757
15749
  import { createHash as createHash9 } from "crypto";
@@ -15778,7 +15770,7 @@ function parseFrontmatter(content) {
15778
15770
  }
15779
15771
  function safeReaddir(dir) {
15780
15772
  try {
15781
- return readdirSync16(dir);
15773
+ return readdirSync17(dir);
15782
15774
  } catch {
15783
15775
  return [];
15784
15776
  }
@@ -15814,11 +15806,11 @@ var init_plan_source = __esm({
15814
15806
  if (!existsSync23(projectsDir))
15815
15807
  return this._knownProjectDirs;
15816
15808
  try {
15817
- const entries = readdirSync16(projectsDir, { withFileTypes: true });
15809
+ const entries = readdirSync17(projectsDir, { withFileTypes: true });
15818
15810
  for (const entry of entries) {
15819
15811
  if (!entry.isDirectory() || entry.name.startsWith("."))
15820
15812
  continue;
15821
- const projectPath = decodeProjectDirName(entry.name);
15813
+ const projectPath = resolveProjectDirName(entry.name);
15822
15814
  if (existsSync23(projectPath)) {
15823
15815
  this._knownProjectDirs.push(projectPath);
15824
15816
  }
@@ -15846,7 +15838,7 @@ var init_plan_source = __esm({
15846
15838
  for (const plansDir of this.plansDirs) {
15847
15839
  if (!existsSync23(plansDir))
15848
15840
  continue;
15849
- const files = readdirSync16(plansDir).filter((f) => f.endsWith(".md"));
15841
+ const files = readdirSync17(plansDir).filter((f) => f.endsWith(".md"));
15850
15842
  for (const file of files) {
15851
15843
  if (seenClaude.has(file))
15852
15844
  continue;
@@ -15890,7 +15882,7 @@ var init_plan_source = __esm({
15890
15882
  return;
15891
15883
  let files;
15892
15884
  try {
15893
- files = readdirSync16(plansDir).filter((f) => f.endsWith(".md"));
15885
+ files = readdirSync17(plansDir).filter((f) => f.endsWith(".md"));
15894
15886
  } catch {
15895
15887
  return;
15896
15888
  }
@@ -15937,7 +15929,7 @@ var init_plan_source = __esm({
15937
15929
  }
15938
15930
  let projDirs;
15939
15931
  try {
15940
- projDirs = readdirSync16(tmpRoot);
15932
+ projDirs = readdirSync17(tmpRoot);
15941
15933
  } catch {
15942
15934
  return;
15943
15935
  }
@@ -15946,7 +15938,7 @@ var init_plan_source = __esm({
15946
15938
  const projRoot = join24(tmpRoot, projDir);
15947
15939
  let sessions;
15948
15940
  try {
15949
- sessions = readdirSync16(projRoot);
15941
+ sessions = readdirSync17(projRoot);
15950
15942
  } catch {
15951
15943
  continue;
15952
15944
  }
@@ -15956,7 +15948,7 @@ var init_plan_source = __esm({
15956
15948
  continue;
15957
15949
  let files;
15958
15950
  try {
15959
- files = readdirSync16(plansDir).filter((f) => f.endsWith(".md"));
15951
+ files = readdirSync17(plansDir).filter((f) => f.endsWith(".md"));
15960
15952
  } catch {
15961
15953
  continue;
15962
15954
  }
@@ -16165,7 +16157,7 @@ var init_plan_source = __esm({
16165
16157
  return;
16166
16158
  let sessionDirs;
16167
16159
  try {
16168
- sessionDirs = readdirSync16(brainDir);
16160
+ sessionDirs = readdirSync17(brainDir);
16169
16161
  } catch {
16170
16162
  return;
16171
16163
  }
@@ -16179,7 +16171,7 @@ var init_plan_source = __esm({
16179
16171
  }
16180
16172
  let files;
16181
16173
  try {
16182
- files = readdirSync16(sessionPath).filter((f) => f.endsWith(".md"));
16174
+ files = readdirSync17(sessionPath).filter((f) => f.endsWith(".md"));
16183
16175
  } catch {
16184
16176
  continue;
16185
16177
  }
@@ -16216,7 +16208,7 @@ var init_plan_source = __esm({
16216
16208
  });
16217
16209
 
16218
16210
  // ../engine/dist/parsers/task-source.js
16219
- import { existsSync as existsSync24, readdirSync as readdirSync17, readFileSync as readFileSync17, statSync as statSync22 } from "fs";
16211
+ import { existsSync as existsSync24, readdirSync as readdirSync18, readFileSync as readFileSync17, statSync as statSync22 } from "fs";
16220
16212
  import { join as join25 } from "path";
16221
16213
  var MAX_CHUNK_CHARS2, TaskSource;
16222
16214
  var init_task_source = __esm({
@@ -16250,7 +16242,7 @@ var init_task_source = __esm({
16250
16242
  return;
16251
16243
  let files;
16252
16244
  try {
16253
- files = readdirSync17(todosDir).filter((f) => f.endsWith(".json"));
16245
+ files = readdirSync18(todosDir).filter((f) => f.endsWith(".json"));
16254
16246
  } catch {
16255
16247
  return;
16256
16248
  }
@@ -16299,7 +16291,7 @@ var init_task_source = __esm({
16299
16291
  async *discoverInDir(tasksDir) {
16300
16292
  if (!existsSync24(tasksDir))
16301
16293
  return;
16302
- const entries = readdirSync17(tasksDir, { withFileTypes: true });
16294
+ const entries = readdirSync18(tasksDir, { withFileTypes: true });
16303
16295
  for (const entry of entries) {
16304
16296
  if (!entry.isDirectory())
16305
16297
  continue;
@@ -16307,7 +16299,7 @@ var init_task_source = __esm({
16307
16299
  const taskDir = join25(tasksDir, sessionUuid);
16308
16300
  try {
16309
16301
  const stat3 = statSync22(taskDir);
16310
- const taskFiles = readdirSync17(taskDir).filter((f) => f.endsWith(".json"));
16302
+ const taskFiles = readdirSync18(taskDir).filter((f) => f.endsWith(".json"));
16311
16303
  if (taskFiles.length === 0)
16312
16304
  continue;
16313
16305
  const tasks2 = this.readTasks(taskDir, taskFiles);
@@ -16341,7 +16333,7 @@ var init_task_source = __esm({
16341
16333
  const taskDir = item.filePath;
16342
16334
  if (!existsSync24(taskDir))
16343
16335
  return [];
16344
- const taskFiles = readdirSync17(taskDir).filter((f) => f.endsWith(".json"));
16336
+ const taskFiles = readdirSync18(taskDir).filter((f) => f.endsWith(".json"));
16345
16337
  const tasks2 = this.readTasks(taskDir, taskFiles);
16346
16338
  if (tasks2.length === 0)
16347
16339
  return [];
@@ -16452,7 +16444,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
16452
16444
  });
16453
16445
 
16454
16446
  // ../engine/dist/parsers/claude-md-source.js
16455
- import { existsSync as existsSync25, readFileSync as readFileSync18, readdirSync as readdirSync18, statSync as statSync23 } from "fs";
16447
+ import { existsSync as existsSync25, readFileSync as readFileSync18, readdirSync as readdirSync19, statSync as statSync23 } from "fs";
16456
16448
  import { homedir as homedir8 } from "os";
16457
16449
  import { join as join26, basename as basename14, dirname as dirname14 } from "path";
16458
16450
  import { createHash as createHash10 } from "crypto";
@@ -16469,11 +16461,11 @@ function discoverProjectDirs(claudeDir) {
16469
16461
  if (!existsSync25(projectsDir))
16470
16462
  return;
16471
16463
  try {
16472
- const entries = readdirSync18(projectsDir, { withFileTypes: true });
16464
+ const entries = readdirSync19(projectsDir, { withFileTypes: true });
16473
16465
  for (const entry of entries) {
16474
16466
  if (!entry.isDirectory() || entry.name.startsWith("."))
16475
16467
  continue;
16476
- const projectPath = decodeProjectDirName(entry.name);
16468
+ const projectPath = resolveProjectDirName(entry.name);
16477
16469
  addProject(projectPath);
16478
16470
  }
16479
16471
  } catch {
@@ -16486,7 +16478,7 @@ function discoverProjectDirs(claudeDir) {
16486
16478
  const home = homedir8();
16487
16479
  scanProjectsDir(claudeBackend.projectsDir());
16488
16480
  try {
16489
- for (const entry of readdirSync18(home, { withFileTypes: true })) {
16481
+ for (const entry of readdirSync19(home, { withFileTypes: true })) {
16490
16482
  if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
16491
16483
  scanProjectsDir(join26(home, entry.name, "projects"));
16492
16484
  }
@@ -16588,7 +16580,7 @@ var init_claude_md_source = __esm({
16588
16580
  }
16589
16581
  }
16590
16582
  try {
16591
- const entries = readdirSync18(searchDir, { withFileTypes: true });
16583
+ const entries = readdirSync19(searchDir, { withFileTypes: true });
16592
16584
  for (const entry of entries) {
16593
16585
  if (!entry.isDirectory())
16594
16586
  continue;
@@ -16678,7 +16670,7 @@ var init_claude_md_source = __esm({
16678
16670
  });
16679
16671
 
16680
16672
  // ../engine/dist/parsers/agent-memory-source.js
16681
- import { existsSync as existsSync26, readFileSync as readFileSync19, readdirSync as readdirSync19, statSync as statSync24 } from "fs";
16673
+ import { existsSync as existsSync26, readFileSync as readFileSync19, readdirSync as readdirSync20, statSync as statSync24 } from "fs";
16682
16674
  import { homedir as homedir9 } from "os";
16683
16675
  import { join as join27, basename as basename15 } from "path";
16684
16676
  function parseFrontmatter2(content) {
@@ -16706,7 +16698,7 @@ function parseFrontmatter2(content) {
16706
16698
  return { fm, body };
16707
16699
  }
16708
16700
  function projectHashToPath(hashDir) {
16709
- return decodeProjectDirName(hashDir);
16701
+ return resolveProjectDirName(hashDir);
16710
16702
  }
16711
16703
  function discoverMemoryDirs() {
16712
16704
  const out = [];
@@ -16720,7 +16712,7 @@ function discoverMemoryDirs() {
16720
16712
  const home = homedir9();
16721
16713
  const claudeRoots = [claudeBackend.projectsDir()];
16722
16714
  try {
16723
- for (const entry of readdirSync19(home, { withFileTypes: true })) {
16715
+ for (const entry of readdirSync20(home, { withFileTypes: true })) {
16724
16716
  if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
16725
16717
  claudeRoots.push(join27(home, entry.name, "projects"));
16726
16718
  }
@@ -16731,7 +16723,7 @@ function discoverMemoryDirs() {
16731
16723
  if (!existsSync26(projectsDir))
16732
16724
  continue;
16733
16725
  try {
16734
- for (const entry of readdirSync19(projectsDir, { withFileTypes: true })) {
16726
+ for (const entry of readdirSync20(projectsDir, { withFileTypes: true })) {
16735
16727
  if (!entry.isDirectory() || entry.name.startsWith("."))
16736
16728
  continue;
16737
16729
  add(join27(projectsDir, entry.name, "memory"), entry.name);
@@ -16760,7 +16752,7 @@ var init_agent_memory_source = __esm({
16760
16752
  for (const { memoryDir, projectHash } of discoverMemoryDirs()) {
16761
16753
  const projectPath = projectHashToPath(projectHash);
16762
16754
  try {
16763
- for (const entry of readdirSync19(memoryDir, { withFileTypes: true })) {
16755
+ for (const entry of readdirSync20(memoryDir, { withFileTypes: true })) {
16764
16756
  if (!entry.isFile() || !entry.name.endsWith(".md"))
16765
16757
  continue;
16766
16758
  const filePath = join27(memoryDir, entry.name);
@@ -16863,7 +16855,7 @@ var init_agent_memory_source = __esm({
16863
16855
  });
16864
16856
 
16865
16857
  // ../engine/dist/parsers/history-source.js
16866
- import { existsSync as existsSync27, readdirSync as readdirSync20, statSync as statSync25 } from "fs";
16858
+ import { existsSync as existsSync27, readdirSync as readdirSync21, statSync as statSync25 } from "fs";
16867
16859
  import { createReadStream as createReadStream3 } from "fs";
16868
16860
  import { createInterface as createInterface2 } from "readline";
16869
16861
  import { homedir as homedir10 } from "os";
@@ -16890,7 +16882,7 @@ var init_history_source = __esm({
16890
16882
  };
16891
16883
  tryAdd(claudeBackend.historyFile());
16892
16884
  try {
16893
- for (const entry of readdirSync20(home, { withFileTypes: true })) {
16885
+ for (const entry of readdirSync21(home, { withFileTypes: true })) {
16894
16886
  if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
16895
16887
  tryAdd(join28(home, entry.name, "history.jsonl"));
16896
16888
  }
@@ -17047,7 +17039,7 @@ var init_history_source = __esm({
17047
17039
  });
17048
17040
 
17049
17041
  // ../engine/dist/parsers/paste-source.js
17050
- import { existsSync as existsSync28, readdirSync as readdirSync21, readFileSync as readFileSync20, statSync as statSync26 } from "fs";
17042
+ import { existsSync as existsSync28, readdirSync as readdirSync22, readFileSync as readFileSync20, statSync as statSync26 } from "fs";
17051
17043
  import { homedir as homedir11 } from "os";
17052
17044
  import { join as join29, basename as basename16 } from "path";
17053
17045
  var MAX_CHUNK_CHARS6, MAX_FILE_SIZE, PasteSource;
@@ -17073,7 +17065,7 @@ var init_paste_source = __esm({
17073
17065
  };
17074
17066
  tryAdd(claudeBackend.pasteCacheDir());
17075
17067
  try {
17076
- for (const entry of readdirSync21(home, { withFileTypes: true })) {
17068
+ for (const entry of readdirSync22(home, { withFileTypes: true })) {
17077
17069
  if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
17078
17070
  tryAdd(join29(home, entry.name, "paste-cache"));
17079
17071
  }
@@ -17090,7 +17082,7 @@ var init_paste_source = __esm({
17090
17082
  for (const cacheDir of this.cacheDirs) {
17091
17083
  if (!existsSync28(cacheDir))
17092
17084
  continue;
17093
- const files = readdirSync21(cacheDir).filter((f) => f.endsWith(".txt"));
17085
+ const files = readdirSync22(cacheDir).filter((f) => f.endsWith(".txt"));
17094
17086
  for (const file of files) {
17095
17087
  if (seen.has(file))
17096
17088
  continue;
@@ -17147,7 +17139,7 @@ var init_paste_source = __esm({
17147
17139
  });
17148
17140
 
17149
17141
  // ../engine/dist/parsers/gemini-source.js
17150
- import { existsSync as existsSync29, readdirSync as readdirSync22, readFileSync as readFileSync21, statSync as statSync27 } from "fs";
17142
+ import { existsSync as existsSync29, readdirSync as readdirSync23, readFileSync as readFileSync21, statSync as statSync27 } from "fs";
17151
17143
  import { join as join30, basename as basename17 } from "path";
17152
17144
  import { createHash as createHash11 } from "crypto";
17153
17145
  function loadGeminiProjectMap2() {
@@ -17189,13 +17181,13 @@ var init_gemini_source = __esm({
17189
17181
  return;
17190
17182
  const seen = /* @__PURE__ */ new Set();
17191
17183
  try {
17192
- for (const projectDirEntry of readdirSync22(tmpDir, { withFileTypes: true })) {
17184
+ for (const projectDirEntry of readdirSync23(tmpDir, { withFileTypes: true })) {
17193
17185
  if (!projectDirEntry.isDirectory())
17194
17186
  continue;
17195
17187
  const chatsDir = join30(tmpDir, projectDirEntry.name, "chats");
17196
17188
  if (!existsSync29(chatsDir))
17197
17189
  continue;
17198
- for (const file of readdirSync22(chatsDir)) {
17190
+ for (const file of readdirSync23(chatsDir)) {
17199
17191
  if (!file.endsWith(".json") || !file.startsWith("session-"))
17200
17192
  continue;
17201
17193
  const filePath = join30(chatsDir, file);
@@ -17330,7 +17322,7 @@ var init_gemini_source = __esm({
17330
17322
  });
17331
17323
 
17332
17324
  // ../engine/dist/parsers/gemini-brain-source.js
17333
- import { existsSync as existsSync30, readdirSync as readdirSync23, readFileSync as readFileSync22, statSync as statSync28 } from "fs";
17325
+ import { existsSync as existsSync30, readdirSync as readdirSync24, readFileSync as readFileSync22, statSync as statSync28 } from "fs";
17334
17326
  import { join as join31 } from "path";
17335
17327
  var MAX_CHUNK_CHARS8, GeminiBrainSource;
17336
17328
  var init_gemini_brain_source = __esm({
@@ -17351,12 +17343,12 @@ var init_gemini_brain_source = __esm({
17351
17343
  return;
17352
17344
  if (!existsSync30(this.brainDir))
17353
17345
  return;
17354
- const sessions = readdirSync23(this.brainDir, { withFileTypes: true });
17346
+ const sessions = readdirSync24(this.brainDir, { withFileTypes: true });
17355
17347
  for (const entry of sessions) {
17356
17348
  if (!entry.isDirectory())
17357
17349
  continue;
17358
17350
  const sessionDir = join31(this.brainDir, entry.name);
17359
- const files = readdirSync23(sessionDir).filter((f) => f.endsWith(".md") && !f.includes(".resolved") && !f.includes(".metadata"));
17351
+ const files = readdirSync24(sessionDir).filter((f) => f.endsWith(".md") && !f.includes(".resolved") && !f.includes(".metadata"));
17360
17352
  if (files.length === 0)
17361
17353
  continue;
17362
17354
  for (const file of files) {
@@ -17744,7 +17736,7 @@ var init_opencode_todo_source = __esm({
17744
17736
  });
17745
17737
 
17746
17738
  // ../engine/dist/parsers/codex-session-source.js
17747
- import { existsSync as existsSync33, readdirSync as readdirSync24, readFileSync as readFileSync23, statSync as statSync29 } from "fs";
17739
+ import { existsSync as existsSync33, readdirSync as readdirSync25, readFileSync as readFileSync23, statSync as statSync29 } from "fs";
17748
17740
  import { join as join32, basename as basename18 } from "path";
17749
17741
  function getCodexSessionsDir() {
17750
17742
  return codexBackend.sessionsDir();
@@ -17965,7 +17957,7 @@ var init_codex_session_source = __esm({
17965
17957
  const results = [];
17966
17958
  if (!existsSync33(dir))
17967
17959
  return results;
17968
- for (const entry of readdirSync24(dir, { withFileTypes: true })) {
17960
+ for (const entry of readdirSync25(dir, { withFileTypes: true })) {
17969
17961
  const fullPath = join32(dir, entry.name);
17970
17962
  if (entry.isDirectory()) {
17971
17963
  results.push(...this.collectJsonlFiles(fullPath));
@@ -17980,7 +17972,7 @@ var init_codex_session_source = __esm({
17980
17972
  });
17981
17973
 
17982
17974
  // ../engine/dist/parsers/diary-source.js
17983
- import { existsSync as existsSync34, mkdirSync as mkdirSync5, readdirSync as readdirSync25, readFileSync as readFileSync24, writeFileSync as writeFileSync4, statSync as statSync30 } from "fs";
17975
+ import { existsSync as existsSync34, mkdirSync as mkdirSync5, readdirSync as readdirSync26, readFileSync as readFileSync24, writeFileSync as writeFileSync4, statSync as statSync30 } from "fs";
17984
17976
  import { join as join33, basename as basename19 } from "path";
17985
17977
  var MAX_CHUNK_CHARS11, DiarySource;
17986
17978
  var init_diary_source = __esm({
@@ -17998,10 +17990,10 @@ var init_diary_source = __esm({
17998
17990
  async *discover() {
17999
17991
  if (!existsSync34(this.diaryDir))
18000
17992
  return;
18001
- const agents = readdirSync25(this.diaryDir, { withFileTypes: true }).filter((d) => d.isDirectory());
17993
+ const agents = readdirSync26(this.diaryDir, { withFileTypes: true }).filter((d) => d.isDirectory());
18002
17994
  for (const agentDir of agents) {
18003
17995
  const agentPath2 = join33(this.diaryDir, agentDir.name);
18004
- const files = readdirSync25(agentPath2).filter((f) => f.endsWith(".json"));
17996
+ const files = readdirSync26(agentPath2).filter((f) => f.endsWith(".json"));
18005
17997
  for (const file of files) {
18006
17998
  const filePath = join33(agentPath2, file);
18007
17999
  try {
@@ -18092,7 +18084,7 @@ var init_diary_source = __esm({
18092
18084
  const agentDir = join33(dir, agentName.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
18093
18085
  if (!existsSync34(agentDir))
18094
18086
  return [];
18095
- const files = readdirSync25(agentDir).filter((f) => f.endsWith(".json")).sort().reverse().slice(0, lastN);
18087
+ const files = readdirSync26(agentDir).filter((f) => f.endsWith(".json")).sort().reverse().slice(0, lastN);
18096
18088
  const entries = [];
18097
18089
  for (const file of files) {
18098
18090
  try {
@@ -18141,7 +18133,7 @@ var init_redact_inline = __esm({
18141
18133
  });
18142
18134
 
18143
18135
  // ../engine/dist/parsers/skills-source.js
18144
- import { existsSync as existsSync35, readdirSync as readdirSync26, readFileSync as readFileSync25, statSync as statSync31 } from "fs";
18136
+ import { existsSync as existsSync35, readdirSync as readdirSync27, readFileSync as readFileSync25, statSync as statSync31 } from "fs";
18145
18137
  import { homedir as homedir12 } from "os";
18146
18138
  import { join as join34 } from "path";
18147
18139
  function rebuildBody(content) {
@@ -18222,7 +18214,7 @@ var init_skills_source = __esm({
18222
18214
  return;
18223
18215
  const codexPluginsDir = codexBackend.pluginsDir();
18224
18216
  if (existsSync35(codexPluginsDir)) {
18225
- for (const pluginName of readdirSync26(codexPluginsDir)) {
18217
+ for (const pluginName of readdirSync27(codexPluginsDir)) {
18226
18218
  const skillsDir = join34(codexPluginsDir, pluginName, "skills");
18227
18219
  if (!existsSync35(skillsDir))
18228
18220
  continue;
@@ -18233,7 +18225,7 @@ var init_skills_source = __esm({
18233
18225
  async *fromSkillRoot(root, pluginName) {
18234
18226
  let entries;
18235
18227
  try {
18236
- entries = readdirSync26(root.path);
18228
+ entries = readdirSync27(root.path);
18237
18229
  } catch {
18238
18230
  return;
18239
18231
  }
@@ -18267,14 +18259,14 @@ var init_skills_source = __esm({
18267
18259
  }
18268
18260
  const subdirs = {};
18269
18261
  try {
18270
- for (const sub of readdirSync26(skillDir)) {
18262
+ for (const sub of readdirSync27(skillDir)) {
18271
18263
  const subPath = join34(skillDir, sub);
18272
18264
  try {
18273
18265
  const ss = statSync31(subPath);
18274
18266
  if (ss.isDirectory()) {
18275
18267
  let count = 0;
18276
18268
  try {
18277
- count = readdirSync26(subPath).length;
18269
+ count = readdirSync27(subPath).length;
18278
18270
  } catch {
18279
18271
  }
18280
18272
  subdirs[sub] = count;
@@ -18340,7 +18332,7 @@ var init_skills_source = __esm({
18340
18332
  });
18341
18333
 
18342
18334
  // ../engine/dist/parsers/mcps-source.js
18343
- import { existsSync as existsSync36, readdirSync as readdirSync27, readFileSync as readFileSync26, statSync as statSync32 } from "fs";
18335
+ import { existsSync as existsSync36, readdirSync as readdirSync28, readFileSync as readFileSync26, statSync as statSync32 } from "fs";
18344
18336
  import { homedir as homedir13 } from "os";
18345
18337
  import { join as join35 } from "path";
18346
18338
  function readJson(path) {
@@ -18454,7 +18446,7 @@ var init_mcps_source = __esm({
18454
18446
  yield* this.fromCodexToml(codexBackend.configToml());
18455
18447
  const pluginsDir = codexBackend.pluginsDir();
18456
18448
  if (existsSync36(pluginsDir)) {
18457
- for (const plugin of readdirSync27(pluginsDir)) {
18449
+ for (const plugin of readdirSync28(pluginsDir)) {
18458
18450
  const mcpPath = join35(pluginsDir, plugin, ".mcp.json");
18459
18451
  yield* this.fromObject(readJson(mcpPath)?.mcpServers, {
18460
18452
  tool: "codex",
@@ -18678,7 +18670,7 @@ var init_toolkit_format = __esm({
18678
18670
  });
18679
18671
 
18680
18672
  // ../engine/dist/parsers/slash-commands-source.js
18681
- import { existsSync as existsSync37, readdirSync as readdirSync28, readFileSync as readFileSync27, statSync as statSync33 } from "fs";
18673
+ import { existsSync as existsSync37, readdirSync as readdirSync29, readFileSync as readFileSync27, statSync as statSync33 } from "fs";
18682
18674
  import { join as join36, basename as basename20 } from "path";
18683
18675
  function rebuildBody2(content) {
18684
18676
  const { text, redacted } = redactInlineSecrets(content);
@@ -18733,10 +18725,10 @@ var init_slash_commands_source = __esm({
18733
18725
  const projectsRoot = claudeBackend.projectsDir();
18734
18726
  if (existsSync37(projectsRoot)) {
18735
18727
  try {
18736
- for (const entry of readdirSync28(projectsRoot, { withFileTypes: true })) {
18728
+ for (const entry of readdirSync29(projectsRoot, { withFileTypes: true })) {
18737
18729
  if (!entry.isDirectory() || entry.name.startsWith("."))
18738
18730
  continue;
18739
- const projectPath = decodeProjectDirName(entry.name);
18731
+ const projectPath = resolveProjectDirName(entry.name);
18740
18732
  const cmdDir = join36(projectPath, ".claude", "commands");
18741
18733
  if (existsSync37(cmdDir))
18742
18734
  roots.push({ path: cmdDir, tool: "claude", scope: "project", projectPath, format: "md" });
@@ -18764,7 +18756,7 @@ var init_slash_commands_source = __esm({
18764
18756
  const ext = root.format === "toml" ? ".toml" : ".md";
18765
18757
  let files;
18766
18758
  try {
18767
- files = readdirSync28(root.path).filter((f) => f.endsWith(ext));
18759
+ files = readdirSync29(root.path).filter((f) => f.endsWith(ext));
18768
18760
  } catch {
18769
18761
  continue;
18770
18762
  }
@@ -18824,7 +18816,7 @@ var init_slash_commands_source = __esm({
18824
18816
  });
18825
18817
 
18826
18818
  // ../engine/dist/parsers/subagents-source.js
18827
- import { existsSync as existsSync38, readdirSync as readdirSync29, readFileSync as readFileSync28, statSync as statSync34 } from "fs";
18819
+ import { existsSync as existsSync38, readdirSync as readdirSync30, readFileSync as readFileSync28, statSync as statSync34 } from "fs";
18828
18820
  import { join as join37, basename as basename21 } from "path";
18829
18821
  function rebuildBody3(content) {
18830
18822
  const { text, redacted } = redactInlineSecrets(content);
@@ -18881,10 +18873,10 @@ var init_subagents_source = __esm({
18881
18873
  const projectsRoot = claudeBackend.projectsDir();
18882
18874
  if (existsSync38(projectsRoot)) {
18883
18875
  try {
18884
- for (const entry of readdirSync29(projectsRoot, { withFileTypes: true })) {
18876
+ for (const entry of readdirSync30(projectsRoot, { withFileTypes: true })) {
18885
18877
  if (!entry.isDirectory() || entry.name.startsWith("."))
18886
18878
  continue;
18887
- const projectPath = decodeProjectDirName(entry.name);
18879
+ const projectPath = resolveProjectDirName(entry.name);
18888
18880
  const agentDir = join37(projectPath, ".claude", "agents");
18889
18881
  if (existsSync38(agentDir))
18890
18882
  roots.push({ path: agentDir, tool: "claude", scope: "project", projectPath, format: "md" });
@@ -18912,7 +18904,7 @@ var init_subagents_source = __esm({
18912
18904
  const ext = root.format === "toml" ? ".toml" : ".md";
18913
18905
  let files;
18914
18906
  try {
18915
- files = readdirSync29(root.path).filter((f) => f.endsWith(ext));
18907
+ files = readdirSync30(root.path).filter((f) => f.endsWith(ext));
18916
18908
  } catch {
18917
18909
  continue;
18918
18910
  }
@@ -18973,7 +18965,7 @@ var init_subagents_source = __esm({
18973
18965
  });
18974
18966
 
18975
18967
  // ../engine/dist/parsers/hooks-source.js
18976
- import { existsSync as existsSync39, readFileSync as readFileSync29, readdirSync as readdirSync30, statSync as statSync35 } from "fs";
18968
+ import { existsSync as existsSync39, readFileSync as readFileSync29, readdirSync as readdirSync31, statSync as statSync35 } from "fs";
18977
18969
  import { join as join38 } from "path";
18978
18970
  function readJson2(path) {
18979
18971
  if (!existsSync39(path))
@@ -19000,10 +18992,10 @@ var init_hooks_source = __esm({
19000
18992
  const projectsRoot = claudeBackend.projectsDir();
19001
18993
  if (existsSync39(projectsRoot)) {
19002
18994
  try {
19003
- for (const entry of readdirSync30(projectsRoot, { withFileTypes: true })) {
18995
+ for (const entry of readdirSync31(projectsRoot, { withFileTypes: true })) {
19004
18996
  if (!entry.isDirectory() || entry.name.startsWith("."))
19005
18997
  continue;
19006
- const projectPath = decodeProjectDirName(entry.name);
18998
+ const projectPath = resolveProjectDirName(entry.name);
19007
18999
  const settingsPath2 = join38(projectPath, ".claude", "settings.json");
19008
19000
  if (existsSync39(settingsPath2)) {
19009
19001
  yield* this.fromSettings(settingsPath2, "project", projectPath);
@@ -19096,7 +19088,7 @@ var init_hooks_source = __esm({
19096
19088
  });
19097
19089
 
19098
19090
  // ../engine/dist/parsers/plugins-source.js
19099
- import { existsSync as existsSync40, readFileSync as readFileSync30, readdirSync as readdirSync31, statSync as statSync36 } from "fs";
19091
+ import { existsSync as existsSync40, readFileSync as readFileSync30, readdirSync as readdirSync32, statSync as statSync36 } from "fs";
19100
19092
  import { homedir as homedir14 } from "os";
19101
19093
  import { join as join39 } from "path";
19102
19094
  function readJson3(path) {
@@ -19158,7 +19150,7 @@ var init_plugins_source = __esm({
19158
19150
  if (isSourceEnabled("gemini", "extensions") && existsSync40(gemRoot)) {
19159
19151
  let entries;
19160
19152
  try {
19161
- entries = readdirSync31(gemRoot);
19153
+ entries = readdirSync32(gemRoot);
19162
19154
  } catch {
19163
19155
  entries = [];
19164
19156
  }
@@ -19206,7 +19198,7 @@ var init_plugins_source = __esm({
19206
19198
  if (isSourceEnabled("codex", "plugins") && existsSync40(codexPluginsRoot)) {
19207
19199
  let entries;
19208
19200
  try {
19209
- entries = readdirSync31(codexPluginsRoot);
19201
+ entries = readdirSync32(codexPluginsRoot);
19210
19202
  } catch {
19211
19203
  entries = [];
19212
19204
  }
@@ -19362,7 +19354,7 @@ var init_extractor_version = __esm({
19362
19354
  command: 1
19363
19355
  };
19364
19356
  TOOL_EXTRACTOR_BUMP = {
19365
- agy: 2,
19357
+ agy: 3,
19366
19358
  claude: 1,
19367
19359
  codex: 1
19368
19360
  };
@@ -19502,13 +19494,13 @@ function markSynced(server, rows) {
19502
19494
  const o = (cursorFromCaller ? r.offset : void 0) ?? prevOS.o ?? 0;
19503
19495
  const s = (cursorFromCaller ? r.size : void 0) ?? prevOS.s ?? 0;
19504
19496
  const ev = extractorVersionForId(r.id);
19505
- const base2 = prevF ? { m: Math.floor(r.mtime), v: ev, f: prevF } : { m: Math.floor(r.mtime), v: ev };
19497
+ const base3 = prevF ? { m: Math.floor(r.mtime), v: ev, f: prevF } : { m: Math.floor(r.mtime), v: ev };
19506
19498
  if (o > 0 || s > 0) {
19507
- base2.o = o;
19508
- base2.s = s;
19499
+ base3.o = o;
19500
+ base3.s = s;
19509
19501
  }
19510
- if (r.hash !== void 0) base2.h = r.hash;
19511
- forServer[r.id] = base2;
19502
+ if (r.hash !== void 0) base3.h = r.hash;
19503
+ forServer[r.id] = base3;
19512
19504
  }
19513
19505
  persist(data);
19514
19506
  }
@@ -19750,7 +19742,7 @@ var init_http = __esm({
19750
19742
  CLI_VERSION = JSON.parse(readFileSync33(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
19751
19743
  } catch {
19752
19744
  try {
19753
- if (true) CLI_VERSION = "0.5.30";
19745
+ if (true) CLI_VERSION = "0.5.32";
19754
19746
  } catch {
19755
19747
  }
19756
19748
  }
@@ -20100,9 +20092,9 @@ function cooldownFor(failures) {
20100
20092
  function breakerState(serverUrl, now = Date.now()) {
20101
20093
  const s = state.get(key(serverUrl));
20102
20094
  if (!s || s.openUntil <= now) {
20103
- return { open: false, retryInMs: 0, failures: s?.failures ?? 0, lastError: s?.lastError ?? "" };
20095
+ return { open: false, retryInMs: 0, failures: s?.failures ?? 0, lastError: s?.lastError ?? "", tripped: false };
20104
20096
  }
20105
- return { open: true, retryInMs: s.openUntil - now, failures: s.failures, lastError: s.lastError };
20097
+ return { open: true, retryInMs: s.openUntil - now, failures: s.failures, lastError: s.lastError, tripped: false };
20106
20098
  }
20107
20099
  function noteTargetSuccess(serverUrl) {
20108
20100
  state.delete(key(serverUrl));
@@ -20114,11 +20106,11 @@ function noteTargetFailure(serverUrl, error, now = Date.now()) {
20114
20106
  const lastError = error.slice(0, 200);
20115
20107
  if (failures < TRIP_AFTER_FAILURES) {
20116
20108
  state.set(k, { failures, openUntil: 0, lastError });
20117
- return { open: false, retryInMs: 0, failures, lastError };
20109
+ return { open: false, retryInMs: 0, failures, lastError, tripped: false };
20118
20110
  }
20119
20111
  const cooldown = cooldownFor(failures);
20120
20112
  state.set(k, { failures, openUntil: now + cooldown, lastError });
20121
- return { open: true, retryInMs: cooldown, failures, lastError };
20113
+ return { open: true, retryInMs: cooldown, failures, lastError, tripped: failures === TRIP_AFTER_FAILURES };
20122
20114
  }
20123
20115
  function breakerNotice(serverUrl, v) {
20124
20116
  if (!v.open) return null;
@@ -20245,6 +20237,14 @@ async function flush() {
20245
20237
  }
20246
20238
  }));
20247
20239
  }
20240
+ function stableMark(value) {
20241
+ try {
20242
+ const { createHash: createHash20 } = __require("node:crypto");
20243
+ return createHash20("sha256").update(value).digest("hex").slice(0, 12);
20244
+ } catch {
20245
+ return "";
20246
+ }
20247
+ }
20248
20248
  function hashHost() {
20249
20249
  try {
20250
20250
  const { createHash: createHash20 } = __require("node:crypto");
@@ -20262,7 +20262,7 @@ var init_telemetry = __esm({
20262
20262
  init_sync_client();
20263
20263
  cliVersion2 = () => {
20264
20264
  try {
20265
- return true ? "0.5.30" : "dev";
20265
+ return true ? "0.5.32" : "dev";
20266
20266
  } catch {
20267
20267
  return "dev";
20268
20268
  }
@@ -20399,11 +20399,11 @@ __export(server_probe_exports, {
20399
20399
  function probeOk(r) {
20400
20400
  return r.kind === "ok" && r.apiVersion >= 2;
20401
20401
  }
20402
- async function probeServer(base2, opts = {}) {
20402
+ async function probeServer(base3, opts = {}) {
20403
20403
  const f = opts.fetchImpl || fetch;
20404
20404
  let res;
20405
20405
  try {
20406
- res = await f(`${base2.replace(/\/+$/, "")}/api/capabilities`, {
20406
+ res = await f(`${base3.replace(/\/+$/, "")}/api/capabilities`, {
20407
20407
  signal: AbortSignal.timeout(opts.timeoutMs ?? 1e4)
20408
20408
  });
20409
20409
  } catch (err) {
@@ -20440,34 +20440,34 @@ async function probeServer(base2, opts = {}) {
20440
20440
  raw: parsed
20441
20441
  };
20442
20442
  }
20443
- function probeAdvice(r, base2) {
20443
+ function probeAdvice(r, base3) {
20444
20444
  switch (r.kind) {
20445
20445
  case "ok":
20446
20446
  return r.apiVersion >= 2 ? `apiVersion ${r.apiVersion}${r.edition ? `, edition ${r.edition}` : ""}` : `apiVersion ${r.apiVersion} is too old for this CLI \u2014 upgrade the server`;
20447
20447
  case "moved":
20448
- return `HTTP ${r.status} \u2014 nothing is served at ${base2}. If the host moved, re-point it: \`chat-recall login <new-server-url>\``;
20448
+ return `HTTP ${r.status} \u2014 nothing is served at ${base3}. If the host moved, re-point it: \`chat-recall login <new-server-url>\``;
20449
20449
  case "unauthorized":
20450
- return `HTTP ${r.status} \u2014 ${base2} rejected the credential. Re-login: \`chat-recall login ` + base2 + "`";
20450
+ return `HTTP ${r.status} \u2014 ${base3} rejected the credential. Re-login: \`chat-recall login ` + base3 + "`";
20451
20451
  case "server_error":
20452
- return `HTTP ${r.status} \u2014 ${base2} is up but failing. Nothing to fix locally; check the server.`;
20452
+ return `HTTP ${r.status} \u2014 ${base3} is up but failing. Nothing to fix locally; check the server.`;
20453
20453
  case "not_api":
20454
- return `${base2} answered ${r.status} with something that is not the chat-recall API (${JSON.stringify(r.snippet)}) \u2014 check the URL, a proxy or a captive portal`;
20454
+ return `${base3} answered ${r.status} with something that is not the chat-recall API (${JSON.stringify(r.snippet)}) \u2014 check the URL, a proxy or a captive portal`;
20455
20455
  case "unreachable":
20456
20456
  switch (r.reason) {
20457
20457
  case "dns":
20458
- return `cannot resolve the host in ${base2} (${r.error}) \u2014 check the URL for a typo, and your DNS or VPN`;
20458
+ return `cannot resolve the host in ${base3} (${r.error}) \u2014 check the URL for a typo, and your DNS or VPN`;
20459
20459
  case "refused":
20460
- return `${base2} resolves but refused the connection (${r.error}) \u2014 the server is not running, or the port is wrong`;
20460
+ return `${base3} resolves but refused the connection (${r.error}) \u2014 the server is not running, or the port is wrong`;
20461
20461
  case "tls":
20462
- return `the TLS certificate at ${base2} was rejected (${r.error}) \u2014 expired, self-signed, or issued for another host. A corporate proxy that re-signs traffic does this.`;
20462
+ return `the TLS certificate at ${base3} was rejected (${r.error}) \u2014 expired, self-signed, or issued for another host. A corporate proxy that re-signs traffic does this.`;
20463
20463
  case "timeout":
20464
- return `${base2} did not answer in time (${r.error}) \u2014 a firewall or proxy that drops traffic silently looks exactly like this`;
20464
+ return `${base3} did not answer in time (${r.error}) \u2014 a firewall or proxy that drops traffic silently looks exactly like this`;
20465
20465
  case "reset":
20466
- return `the connection to ${base2} was cut (${r.error}) \u2014 often a proxy or a captive portal`;
20466
+ return `the connection to ${base3} was cut (${r.error}) \u2014 often a proxy or a captive portal`;
20467
20467
  case "bad_port":
20468
- return `${base2} names a port that Node and every browser refuse to open (the WHATWG bad-port list) \u2014 use the server's real port`;
20468
+ return `${base3} names a port that Node and every browser refuse to open (the WHATWG bad-port list) \u2014 use the server's real port`;
20469
20469
  default:
20470
- return `no HTTP response from ${base2} (${r.error}) \u2014 check the URL, DNS, or that the server is running`;
20470
+ return `no HTTP response from ${base3} (${r.error}) \u2014 check the URL, DNS, or that the server is running`;
20471
20471
  }
20472
20472
  }
20473
20473
  }
@@ -20500,8 +20500,8 @@ function describeCause(err) {
20500
20500
  const code2 = e.code;
20501
20501
  if (typeof code2 === "string" && code2 && !parts.includes(code2)) parts.push(code2);
20502
20502
  }
20503
- const base2 = err instanceof Error ? err.message : String(err);
20504
- return parts.length ? `${base2}: ${parts.join(" \u2192 ")}` : base2;
20503
+ const base3 = err instanceof Error ? err.message : String(err);
20504
+ return parts.length ? `${base3}: ${parts.join(" \u2192 ")}` : base3;
20505
20505
  }
20506
20506
  function describeUnreachable(err) {
20507
20507
  const reason = classifyUnreachable(err);
@@ -20605,7 +20605,7 @@ __export(auto_update_exports, {
20605
20605
  import { execSync as execSync2 } from "node:child_process";
20606
20606
  import { readFileSync as readFileSync36 } from "node:fs";
20607
20607
  import { fileURLToPath as fileURLToPath2 } from "node:url";
20608
- import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync32, statSync as statSync38 } from "node:fs";
20608
+ import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync33, statSync as statSync38 } from "node:fs";
20609
20609
  import { join as join43, dirname as dirname18, sep as sep2 } from "node:path";
20610
20610
  import { tmpdir as tmpdir2 } from "node:os";
20611
20611
  import { createHash as createHash12 } from "node:crypto";
@@ -20637,12 +20637,12 @@ function installedVersion() {
20637
20637
  return null;
20638
20638
  }
20639
20639
  }
20640
- function planAutoUpdate(base2, caps, ownVersion3, flag) {
20640
+ function planAutoUpdate(base3, caps, ownVersion3, flag) {
20641
20641
  const rel = caps.cli;
20642
20642
  if (!rel || !rel.version || !rel.sha256) return { update: false, reason: "server advertises no CLI release" };
20643
20643
  if (!isAutoUpdateEnabled(caps.edition, flag)) return { update: false, reason: `auto-update disabled (edition ${caps.edition ?? "?"})` };
20644
20644
  if (compareVersions(rel.version, ownVersion3) <= 0) return { update: false, reason: `already current (own ${ownVersion3} >= server ${rel.version})` };
20645
- return { update: true, reason: `server ${rel.version} > own ${ownVersion3}`, url: tarballUrl(base2), sha256: rel.sha256, from: ownVersion3, to: rel.version };
20645
+ return { update: true, reason: `server ${rel.version} > own ${ownVersion3}`, url: tarballUrl(base3), sha256: rel.sha256, from: ownVersion3, to: rel.version };
20646
20646
  }
20647
20647
  function sweepStaleStaging(root = tmpdir2(), now = Date.now()) {
20648
20648
  const MKDTEMP_SUFFIX = /^cr-update-.{6}$/s;
@@ -20650,7 +20650,7 @@ function sweepStaleStaging(root = tmpdir2(), now = Date.now()) {
20650
20650
  let removed = 0;
20651
20651
  let entries;
20652
20652
  try {
20653
- entries = readdirSync32(root);
20653
+ entries = readdirSync33(root);
20654
20654
  } catch {
20655
20655
  return 0;
20656
20656
  }
@@ -20660,7 +20660,7 @@ function sweepStaleStaging(root = tmpdir2(), now = Date.now()) {
20660
20660
  try {
20661
20661
  const st = statSync38(dir);
20662
20662
  if (!st.isDirectory() || now - st.mtimeMs < COLD_MS) continue;
20663
- const inside = readdirSync32(dir);
20663
+ const inside = readdirSync33(dir);
20664
20664
  if (inside.length > 1 || inside.length === 1 && inside[0] !== "chat-recall.tgz") continue;
20665
20665
  rmSync5(dir, { recursive: true, force: true });
20666
20666
  removed++;
@@ -20738,18 +20738,28 @@ function realRestart(platform2) {
20738
20738
  else if (platform2 === "darwin") execSync2("launchctl kickstart -k gui/$(id -u)/com.chat-recall.watch", { stdio: "ignore", shell: "/bin/bash" });
20739
20739
  else if (platform2 === "win32") execSync2("schtasks /End /TN chat-recall-watch & schtasks /Run /TN chat-recall-watch", { stdio: "ignore" });
20740
20740
  }
20741
- async function runAutoUpdate(base2, authHeaders, ownVersion3, deps) {
20741
+ async function runAutoUpdate(base3, authHeaders, ownVersion3, deps) {
20742
20742
  let caps;
20743
20743
  try {
20744
- const res = await fetchWithTimeout(`${base2.replace(/\/+$/, "")}/api/capabilities`, { headers: authHeaders });
20744
+ const res = await fetchWithTimeout(`${base3.replace(/\/+$/, "")}/api/capabilities`, { headers: authHeaders });
20745
20745
  if (!res.ok) return { updated: false, reason: `capabilities HTTP ${res.status}` };
20746
20746
  caps = await res.json();
20747
20747
  } catch (e) {
20748
20748
  return { updated: false, reason: `capabilities unreachable: ${e instanceof Error ? e.message : e}` };
20749
20749
  }
20750
20750
  const effectiveOwn = newerOf(ownVersion3, installedVersion());
20751
- const plan = planAutoUpdate(base2, caps, effectiveOwn, process.env.CHAT_RECALL_AUTO_UPDATE);
20752
- if (!plan.update) return { updated: false, reason: plan.reason };
20751
+ const plan = planAutoUpdate(base3, caps, effectiveOwn, process.env.CHAT_RECALL_AUTO_UPDATE);
20752
+ if (!plan.update) {
20753
+ if (/disabled|no CLI release/.test(plan.reason) && !reportedStandingSkip.has(base3)) {
20754
+ reportedStandingSkip.add(base3);
20755
+ try {
20756
+ const { reportClientEvent: reportClientEvent2 } = await Promise.resolve().then(() => (init_client_events(), client_events_exports));
20757
+ reportClientEvent2("auto_update_skipped", { message: `${plan.reason} (server offers ${caps.cli?.version ?? "?"}, this device runs ${effectiveOwn})` });
20758
+ } catch {
20759
+ }
20760
+ }
20761
+ return { updated: false, reason: plan.reason };
20762
+ }
20753
20763
  const result = await executeAutoUpdate(plan, {
20754
20764
  download: deps?.download ?? realDownload,
20755
20765
  install: deps?.install ?? realInstall,
@@ -20765,12 +20775,13 @@ async function runAutoUpdate(base2, authHeaders, ownVersion3, deps) {
20765
20775
  }
20766
20776
  return result;
20767
20777
  }
20768
- var tarballUrl;
20778
+ var tarballUrl, reportedStandingSkip;
20769
20779
  var init_auto_update = __esm({
20770
20780
  "src/auto-update.ts"() {
20771
20781
  "use strict";
20772
20782
  init_http();
20773
- tarballUrl = (base2) => `${base2.replace(/\/+$/, "")}/install/chat-recall.tgz`;
20783
+ tarballUrl = (base3) => `${base3.replace(/\/+$/, "")}/install/chat-recall.tgz`;
20784
+ reportedStandingSkip = /* @__PURE__ */ new Set();
20774
20785
  }
20775
20786
  });
20776
20787
 
@@ -20786,6 +20797,9 @@ __export(sync_client_exports, {
20786
20797
  batchScanExternal: () => batchScanExternal,
20787
20798
  buildConversationSync: () => buildConversationSync,
20788
20799
  buildConversationTail: () => buildConversationTail,
20800
+ classedError: () => classedError,
20801
+ classifyError: () => classifyError,
20802
+ isStandingFailure: () => isStandingFailure,
20789
20803
  isSyncSkip: () => isSyncSkip,
20790
20804
  loadAllCredentials: () => loadAllCredentials,
20791
20805
  loadCredentials: () => loadCredentials,
@@ -20801,22 +20815,37 @@ __export(sync_client_exports, {
20801
20815
  syncSessions: () => syncSessions
20802
20816
  });
20803
20817
  import { join as join44, dirname as dirname19, basename as basename22 } from "node:path";
20804
- import { readFileSync as readFileSync37, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync4, rmSync as rmSync6, chmodSync as chmodSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync33, statSync as statSync39 } from "node:fs";
20818
+ import { readFileSync as readFileSync37, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync4, rmSync as rmSync6, chmodSync as chmodSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync34, statSync as statSync39 } from "node:fs";
20805
20819
  import { tmpdir as tmpdir3, hostname as hostname2 } from "node:os";
20806
20820
  import { createHash as createHash13 } from "node:crypto";
20807
20821
  import { fileURLToPath as fileURLToPath3 } from "node:url";
20808
20822
  import { gzipSync as gzipSync3 } from "zlib";
20809
- function classifyError(msg) {
20823
+ function classedError(errorClass, message) {
20824
+ const e = new Error(message);
20825
+ Object.defineProperty(e, ERROR_CLASS, { value: errorClass, enumerable: false });
20826
+ return e;
20827
+ }
20828
+ function taggedClass(err) {
20829
+ const v = err?.[ERROR_CLASS];
20830
+ return typeof v === "string" && v ? v : null;
20831
+ }
20832
+ function isStandingFailure(err) {
20833
+ const c = taggedClass(err);
20834
+ return c !== null && STANDING_FAILURE.has(c);
20835
+ }
20836
+ function classifyError(msg, err) {
20837
+ const tagged = taggedClass(err);
20838
+ if (tagged) return tagged;
20810
20839
  const m = msg.toLowerCase();
20811
20840
  if (m.includes("429")) return "rate_limited";
20812
20841
  if (m.includes("402")) return "payment_required";
20813
20842
  if (m.includes("401") || m.includes("403") || m.includes("token")) return "auth";
20814
- if (m.includes("timed out") || m.includes("etimedout")) return "timeout";
20843
+ if (m.includes("timed out") || m.includes("etimedout") || m.includes("aborted due to timeout")) return "timeout";
20815
20844
  if (m.includes("econnrefused")) return "refused";
20816
20845
  if (m.includes("enotfound") || m.includes("dns")) return "dns";
20817
20846
  if (m.includes("fetch failed") || m.includes("econnreset")) return "network";
20818
- if (m.includes("plain http") || m.includes("https")) return "insecure_transport";
20819
- if (/5\d\d/.test(m)) return "server_error";
20847
+ if (m.includes("over plain http") || m.includes("unsupported scheme")) return "insecure_transport";
20848
+ if (/\b5\d\d\b/.test(m)) return "server_error";
20820
20849
  return "other";
20821
20850
  }
20822
20851
  function saveCredentials(c) {
@@ -20849,15 +20878,15 @@ function loadAllCredentials() {
20849
20878
  return [];
20850
20879
  }
20851
20880
  async function fetchTenantSecurityConfig(cred) {
20852
- const base2 = cred.serverUrl.replace(/\/+$/, "");
20853
- const cached = _securityConfigCache.get(base2);
20881
+ const base3 = cred.serverUrl.replace(/\/+$/, "");
20882
+ const cached = _securityConfigCache.get(base3);
20854
20883
  if (cached && Date.now() - cached.fetchedAt < SECURITY_CONFIG_TTL_MS) return cached.config;
20855
20884
  const headers = { "content-type": "application/json" };
20856
20885
  if (cred.token) headers.authorization = `Bearer ${cred.token}`;
20857
20886
  try {
20858
20887
  const [rulesRes, settingsRes] = await Promise.all([
20859
- fetchWithTimeout(`${base2}/api/secrets/rules`, { headers }),
20860
- fetchWithTimeout(`${base2}/api/teams/security-config`, { headers }).catch(() => null)
20888
+ fetchWithTimeout(`${base3}/api/secrets/rules`, { headers }),
20889
+ fetchWithTimeout(`${base3}/api/teams/security-config`, { headers }).catch(() => null)
20861
20890
  ]);
20862
20891
  let tenantRules = [];
20863
20892
  let rulePackVersion = null;
@@ -20882,18 +20911,18 @@ async function fetchTenantSecurityConfig(cred) {
20882
20911
  const body = await settingsRes.json().catch(() => ({}));
20883
20912
  if (typeof body.verifySecrets === "boolean") verifySecrets = body.verifySecrets;
20884
20913
  if (typeof body.telemetry === "boolean") {
20885
- setTelemetryEligible(base2, body.telemetry);
20914
+ setTelemetryEligible(base3, body.telemetry);
20886
20915
  try {
20887
20916
  const prior = readCollectorHealth()?.telemetryEligible ?? {};
20888
20917
  updateCollectorHealth({
20889
- telemetryEligible: { ...prior, [base2]: { allowed: body.telemetry, at: Date.now() } }
20918
+ telemetryEligible: { ...prior, [base3]: { allowed: body.telemetry, at: Date.now() } }
20890
20919
  });
20891
20920
  } catch {
20892
20921
  }
20893
20922
  }
20894
20923
  }
20895
20924
  const config2 = { tenantRules, verifySecrets, rulePackVersion };
20896
- _securityConfigCache.set(base2, { fetchedAt: Date.now(), config: config2 });
20925
+ _securityConfigCache.set(base3, { fetchedAt: Date.now(), config: config2 });
20897
20926
  return config2;
20898
20927
  } catch {
20899
20928
  return { tenantRules: [], verifySecrets: true, rulePackVersion: null };
@@ -20912,18 +20941,18 @@ function readPersistedSyncConfigs() {
20912
20941
  return {};
20913
20942
  }
20914
20943
  }
20915
- function persistSyncConfig(base2, config2) {
20944
+ function persistSyncConfig(base3, config2) {
20916
20945
  try {
20917
20946
  const all = readPersistedSyncConfigs();
20918
- all[base2] = { fetchedAt: Date.now(), config: config2 };
20947
+ all[base3] = { fetchedAt: Date.now(), config: config2 };
20919
20948
  mkdirSync9(dirname19(syncConfigCachePath()), { recursive: true });
20920
20949
  writeFileSync9(syncConfigCachePath(), JSON.stringify(all, null, 2));
20921
20950
  } catch {
20922
20951
  }
20923
20952
  }
20924
20953
  async function fetchTenantSyncConfig(cred) {
20925
- const base2 = cred.serverUrl.replace(/\/+$/, "");
20926
- const cached = _syncConfigCache.get(base2);
20954
+ const base3 = cred.serverUrl.replace(/\/+$/, "");
20955
+ const cached = _syncConfigCache.get(base3);
20927
20956
  if (cached && Date.now() - cached.fetchedAt < SECURITY_CONFIG_TTL_MS) return cached.config;
20928
20957
  const headers = { "content-type": "application/json" };
20929
20958
  if (cred.token) headers.authorization = `Bearer ${cred.token}`;
@@ -20931,7 +20960,7 @@ async function fetchTenantSyncConfig(cred) {
20931
20960
  try {
20932
20961
  const sources3 = discoverSessionSources();
20933
20962
  if (sources3.length > 0) {
20934
- await fetchWithTimeout(`${base2}/api/sync-config/sources`, {
20963
+ await fetchWithTimeout(`${base3}/api/sync-config/sources`, {
20935
20964
  method: "POST",
20936
20965
  headers,
20937
20966
  body: JSON.stringify({ device: deviceLabel(), sources: sources3 })
@@ -20939,12 +20968,12 @@ async function fetchTenantSyncConfig(cred) {
20939
20968
  }
20940
20969
  } catch {
20941
20970
  }
20942
- const res = await fetchWithTimeout(`${base2}/api/sync-config`, { headers });
20971
+ const res = await fetchWithTimeout(`${base3}/api/sync-config`, { headers });
20943
20972
  if (res.status === 404) {
20944
- _syncConfigCache.set(base2, { fetchedAt: Date.now(), config: EMPTY_SYNC_CONFIG });
20973
+ _syncConfigCache.set(base3, { fetchedAt: Date.now(), config: EMPTY_SYNC_CONFIG });
20945
20974
  return EMPTY_SYNC_CONFIG;
20946
20975
  }
20947
- if (!res.ok) return lastKnownSyncConfig(base2, `HTTP ${res.status}`);
20976
+ if (!res.ok) return lastKnownSyncConfig(base3, `HTTP ${res.status}`);
20948
20977
  const body = await res.json().catch(() => ({}));
20949
20978
  const config2 = {
20950
20979
  excludeTools: Array.isArray(body.excludeTools) ? body.excludeTools.filter((t) => typeof t === "string") : [],
@@ -20981,22 +21010,22 @@ async function fetchTenantSyncConfig(cred) {
20981
21010
  if (decided > 0) console.error(`[sync] applied ${decided} folder decision(s) from the dashboard`);
20982
21011
  } catch {
20983
21012
  }
20984
- _syncConfigCache.set(base2, { fetchedAt: Date.now(), config: config2 });
20985
- persistSyncConfig(base2, config2);
21013
+ _syncConfigCache.set(base3, { fetchedAt: Date.now(), config: config2 });
21014
+ persistSyncConfig(base3, config2);
20986
21015
  return config2;
20987
21016
  } catch (err) {
20988
21017
  if (isSyncRulesRefusal(err)) throw err;
20989
- return lastKnownSyncConfig(base2, err instanceof Error ? err.message : "request failed");
21018
+ return lastKnownSyncConfig(base3, err instanceof Error ? err.message : "request failed");
20990
21019
  }
20991
21020
  }
20992
- function lastKnownSyncConfig(base2, reason) {
20993
- const remembered = _syncConfigCache.get(base2) ?? readPersistedSyncConfigs()[base2];
21021
+ function lastKnownSyncConfig(base3, reason) {
21022
+ const remembered = _syncConfigCache.get(base3) ?? readPersistedSyncConfigs()[base3];
20994
21023
  if (remembered) {
20995
21024
  console.error(`[sync] could not read server-side sync rules (${reason}) \u2014 applying the last known set from ${new Date(remembered.fetchedAt).toISOString()}`);
20996
21025
  return remembered.config;
20997
21026
  }
20998
21027
  const refusal = new Error(
20999
- `Cannot read the server-side sync rules from ${base2} (${reason}), and this machine has never successfully read them. Refusing to sync: uploading without knowing your exclusions could ship a project you told the dashboard to hold back. Fix the server or retry.`
21028
+ `Cannot read the server-side sync rules from ${base3} (${reason}), and this machine has never successfully read them. Refusing to sync: uploading without knowing your exclusions could ship a project you told the dashboard to hold back. Fix the server or retry.`
21000
21029
  );
21001
21030
  refusal[SYNC_RULES_REFUSAL] = true;
21002
21031
  throw refusal;
@@ -21034,17 +21063,23 @@ async function syncSessions(opts = {}) {
21034
21063
  let agg = null;
21035
21064
  const errors = [];
21036
21065
  const skippedTargets = [];
21066
+ const perTarget = {};
21037
21067
  for (const cred of targets) {
21038
21068
  const verdict = breakerState(cred.serverUrl);
21039
21069
  if (verdict.open) {
21040
21070
  const notice = breakerNotice(cred.serverUrl, verdict);
21041
21071
  if (notice) console.error(`[sync] ${notice}`);
21042
21072
  skippedTargets.push(cred.serverUrl);
21073
+ perTarget[cred.serverUrl] = {
21074
+ ok: false,
21075
+ error: notice ?? `in failure cooldown for ~${Math.max(1, Math.round(verdict.retryInMs / 6e4))}m`
21076
+ };
21043
21077
  continue;
21044
21078
  }
21045
21079
  try {
21046
21080
  const r = await syncToTarget(cred, opts);
21047
21081
  noteTargetSuccess(cred.serverUrl);
21082
+ perTarget[cred.serverUrl] = { ok: true };
21048
21083
  agg = agg ? {
21049
21084
  uploaded: agg.uploaded + r.uploaded,
21050
21085
  skipped: agg.skipped + r.skipped,
@@ -21061,14 +21096,20 @@ async function syncSessions(opts = {}) {
21061
21096
  } catch (e) {
21062
21097
  const msg = e instanceof Error ? e.message : String(e);
21063
21098
  errors.push(`${cred.serverUrl}: ${msg}`);
21099
+ perTarget[cred.serverUrl] = { ok: false, error: msg };
21064
21100
  const next = noteTargetFailure(cred.serverUrl, msg);
21065
21101
  const notice = breakerNotice(cred.serverUrl, next);
21066
21102
  if (notice) console.error(`[sync] ${notice}`);
21067
21103
  record({
21068
- kind: next.open ? "breaker_trip" : "target_failure",
21104
+ // `tripped`, not `open`: an open breaker re-opens on every attempt until
21105
+ // a success clears it, so `open` counted elapsed time. Every failure is
21106
+ // still reported — as `target_failure`, carrying the streak in
21107
+ // `failures` — so nothing becomes invisible, it just stops being counted
21108
+ // as a fresh incident.
21109
+ kind: next.tripped ? "breaker_trip" : "target_failure",
21069
21110
  failures: next.failures,
21070
21111
  retryInMs: next.retryInMs,
21071
- errorClass: classifyError(msg),
21112
+ errorClass: classifyError(msg, e),
21072
21113
  local: isLocalHost(cred.serverUrl) ? 1 : 0
21073
21114
  });
21074
21115
  }
@@ -21099,27 +21140,27 @@ async function syncSessions(opts = {}) {
21099
21140
  ${errors.join("\n ")}`);
21100
21141
  if (errors.length > 0) console.error(`[sync] ${errors.length} target(s) failed (others succeeded):
21101
21142
  ${errors.join("\n ")}`);
21102
- return agg;
21143
+ return { ...agg, perTarget };
21103
21144
  }
21104
- async function postFieldBatch(base2, token, rows) {
21145
+ async function postFieldBatch(base3, token, rows) {
21105
21146
  const headers = { "content-type": "application/json" };
21106
21147
  if (token) headers.authorization = `Bearer ${token}`;
21107
21148
  const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
21108
21149
  for (let attempt = 0; ; attempt++) {
21109
- const res = await fetchWithTimeout(`${base2}/api/sync`, { method: "POST", headers, body: JSON.stringify({ fields: rows }) }, 6e4);
21150
+ const res = await fetchWithTimeout(`${base3}/api/sync`, { method: "POST", headers, body: JSON.stringify({ fields: rows }) }, 6e4);
21110
21151
  if (res.ok) return;
21111
21152
  const retryable = res.status === 429 || res.status >= 500;
21112
- if (!retryable || attempt >= 5) throw new Error(`${base2}: fields batch HTTP ${res.status} ${await res.text().catch(() => "")}`);
21153
+ if (!retryable || attempt >= 5) throw new Error(`${base3}: fields batch HTTP ${res.status} ${await res.text().catch(() => "")}`);
21113
21154
  const ra = Number(res.headers.get("retry-after")) || 0;
21114
21155
  await sleep2(ra > 0 ? ra * 1e3 : [500, 1500, 4e3, 8e3, 15e3][Math.min(attempt, 4)]);
21115
21156
  }
21116
21157
  }
21117
21158
  async function reconcileFieldsForTarget(cred, fullRefs, opts = {}) {
21118
- const base2 = cred.serverUrl.replace(/\/+$/, "");
21119
- const ledger = getSyncedRows(base2);
21159
+ const base3 = cred.serverUrl.replace(/\/+$/, "");
21160
+ const ledger = getSyncedRows(base3);
21120
21161
  let pushed = 0, absent = 0, scanned = 0;
21121
21162
  for (const field of SYNC_FIELDS) {
21122
- const needFull = !!opts.force || fieldNeedsFullPass(base2, field);
21163
+ const needFull = !!opts.force || fieldNeedsFullPass(base3, field);
21123
21164
  const candidates2 = needFull ? fullRefs() : opts.convRefs ?? fullRefs();
21124
21165
  const send = [];
21125
21166
  const mtimeById = /* @__PURE__ */ new Map();
@@ -21135,15 +21176,15 @@ async function reconcileFieldsForTarget(cred, fullRefs, opts = {}) {
21135
21176
  }
21136
21177
  for (let i = 0; i < send.length; i += 200) {
21137
21178
  const chunk = send.slice(i, i + 200);
21138
- await postFieldBatch(base2, cred.token, chunk);
21139
- markFieldCoverage(base2, field, chunk.map((r) => ({ id: r.session_id, present: true, mtime: mtimeById.get(r.session_id) ?? 0 })));
21179
+ await postFieldBatch(base3, cred.token, chunk);
21180
+ markFieldCoverage(base3, field, chunk.map((r) => ({ id: r.session_id, present: true, mtime: mtimeById.get(r.session_id) ?? 0 })));
21140
21181
  pushed += chunk.length;
21141
21182
  }
21142
21183
  if (absentIds.length) {
21143
- markFieldCoverage(base2, field, absentIds.map((id) => ({ id, present: false, mtime: mtimeById.get(id) ?? 0 })));
21184
+ markFieldCoverage(base3, field, absentIds.map((id) => ({ id, present: false, mtime: mtimeById.get(id) ?? 0 })));
21144
21185
  absent += absentIds.length;
21145
21186
  }
21146
- if (needFull) markFieldFullPassDone(base2, field);
21187
+ if (needFull) markFieldFullPassDone(base3, field);
21147
21188
  }
21148
21189
  return { pushed, absent, scanned };
21149
21190
  }
@@ -21173,7 +21214,7 @@ function reapStaleScanDirs(dir = tmpdir3(), maxAgeMs = 6 * 36e5, now = Date.now(
21173
21214
  let reaped = 0;
21174
21215
  let names;
21175
21216
  try {
21176
- names = readdirSync33(dir);
21217
+ names = readdirSync34(dir);
21177
21218
  } catch {
21178
21219
  return 0;
21179
21220
  }
@@ -21260,25 +21301,25 @@ function serverIngestConcurrency(serverUrl) {
21260
21301
  return advertisedIngestConc.get(serverUrl.replace(/\/$/, "")) ?? null;
21261
21302
  }
21262
21303
  async function verifyServerApi(serverUrl, minApi) {
21263
- const base2 = serverUrl.replace(/\/$/, "");
21264
- const seen = capsOk.get(base2);
21304
+ const base3 = serverUrl.replace(/\/$/, "");
21305
+ const seen = capsOk.get(base3);
21265
21306
  if (seen !== void 0 && Date.now() - seen < CAPS_TTL_MS) return true;
21266
21307
  const { probeServer: probeServer2, probeAdvice: probeAdvice2 } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
21267
- const probe = await probeServer2(base2, {
21308
+ const probe = await probeServer2(base3, {
21268
21309
  fetchImpl: ((url, init) => fetchWithTimeout(String(url), init ?? {}))
21269
21310
  });
21270
- if (probe.kind !== "ok") throw new Error(probeAdvice2(probe, base2));
21311
+ if (probe.kind !== "ok") throw new Error(probeAdvice2(probe, base3));
21271
21312
  const caps = probe.raw;
21272
21313
  if ((caps.apiVersion ?? 0) < minApi) {
21273
21314
  throw new Error(`server too old: apiVersion ${caps.apiVersion ?? "none"} < required ${minApi} \u2014 update the server image before syncing`);
21274
21315
  }
21275
21316
  const adv = caps.limits?.ingestConcurrencyPerTenant;
21276
21317
  if (typeof adv === "number" && Number.isFinite(adv) && adv >= 1) {
21277
- advertisedIngestConc.set(base2, Math.floor(adv));
21318
+ advertisedIngestConc.set(base3, Math.floor(adv));
21278
21319
  }
21279
- if (caps.limits?.acceptsGzipBody === true) advertisedGzipBody.add(base2);
21280
- else advertisedGzipBody.delete(base2);
21281
- capsOk.set(base2, Date.now());
21320
+ if (caps.limits?.acceptsGzipBody === true) advertisedGzipBody.add(base3);
21321
+ else advertisedGzipBody.delete(base3);
21322
+ capsOk.set(base3, Date.now());
21282
21323
  return true;
21283
21324
  }
21284
21325
  async function syncToTarget(cred, opts = {}) {
@@ -21286,7 +21327,7 @@ async function syncToTarget(cred, opts = {}) {
21286
21327
  }
21287
21328
  async function syncToTargetInScope(cred, opts = {}) {
21288
21329
  const risk = transportRisk(cred.serverUrl);
21289
- if (risk) throw new Error(risk);
21330
+ if (risk) throw classedError("insecure_transport", risk);
21290
21331
  const sync = loadSettings().sync;
21291
21332
  const serverCfg = await fetchTenantSyncConfig(cred);
21292
21333
  const excludeTools = /* @__PURE__ */ new Set([...sync?.excludeTools ?? [], ...serverCfg.excludeTools]);
@@ -21343,7 +21384,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21343
21384
  const ac = new AbortController();
21344
21385
  const timer = setTimeout(() => ac.abort(new Error(`upload timed out after ${UPLOAD_TIMEOUT_MS}ms`)), UPLOAD_TIMEOUT_MS);
21345
21386
  try {
21346
- const res = await fetch(`${base2}/api/sync`, {
21387
+ const res = await fetch(`${base3}/api/sync`, {
21347
21388
  method: "POST",
21348
21389
  // No token ⇒ no auth header: a no-auth local server (AUTH_PROVIDER=none)
21349
21390
  // accepts it as the single 'default' tenant.
@@ -21358,26 +21399,26 @@ async function syncToTargetInScope(cred, opts = {}) {
21358
21399
  if (res.ok) {
21359
21400
  const body2 = await res.json().catch(() => ({}));
21360
21401
  if (typeof body2.telemetry === "boolean") {
21361
- setTelemetryEligible(base2, body2.telemetry);
21402
+ setTelemetryEligible(base3, body2.telemetry);
21362
21403
  try {
21363
21404
  const prior = readCollectorHealth()?.telemetryEligible ?? {};
21364
21405
  updateCollectorHealth({
21365
- telemetryEligible: { ...prior, [base2]: { allowed: body2.telemetry, at: Date.now() } }
21406
+ telemetryEligible: { ...prior, [base3]: { allowed: body2.telemetry, at: Date.now() } }
21366
21407
  });
21367
21408
  } catch {
21368
21409
  }
21369
21410
  }
21370
21411
  const adv = body2.limits?.ingestConcurrencyPerTenant;
21371
21412
  if (typeof adv === "number" && Number.isFinite(adv) && adv >= 1) {
21372
- setServerIngestConcurrency(base2, Math.floor(adv));
21413
+ setServerIngestConcurrency(base3, Math.floor(adv));
21373
21414
  }
21374
21415
  if (body2.cli && body2.cli.version) {
21375
21416
  const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2, sweepStaleStaging: sweepStaleStaging2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
21376
21417
  const authHeaders = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
21377
- const plan = planAutoUpdate2(base2, { cli: body2.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
21418
+ const plan = planAutoUpdate2(base3, { cli: body2.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
21378
21419
  sweepStaleStaging2();
21379
21420
  if (plan.update) {
21380
- void runAutoUpdate2(base2, authHeaders, ownVersion).catch(() => {
21421
+ void runAutoUpdate2(base3, authHeaders, ownVersion).catch(() => {
21381
21422
  });
21382
21423
  }
21383
21424
  }
@@ -21386,7 +21427,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21386
21427
  const retryable = res.status === 429 || res.status >= 500;
21387
21428
  const text = await res.text().catch(() => "");
21388
21429
  if (res.status === 401 || res.status === 403) {
21389
- throw new Error(`server rejected this machine's device token (HTTP ${res.status} \u2014 revoked?). Reconnect with: chat-recall login ${base2}`);
21430
+ throw classedError("auth", `server rejected this machine's device token (HTTP ${res.status} \u2014 revoked?). Reconnect with: chat-recall login ${base3}`);
21390
21431
  }
21391
21432
  if (res.status === 402) {
21392
21433
  let detail = text;
@@ -21400,17 +21441,17 @@ async function syncToTargetInScope(cred, opts = {}) {
21400
21441
  ${body2.upgradeUrl}`;
21401
21442
  } catch {
21402
21443
  }
21403
- throw new Error(`sync paused by the server: ${detail}`);
21444
+ throw classedError("payment_required", `sync paused by the server: ${detail}`);
21404
21445
  }
21405
21446
  if (!retryable) throw new Error(`sync failed: HTTP ${res.status} ${text}`);
21406
21447
  if (attempt >= RETRY_DELAYS_MS.length) throw new Error(`sync failed after ${attempt + 1} attempts: HTTP ${res.status} ${text}`);
21407
21448
  const retryAfterS = Number(res.headers.get("retry-after"));
21408
21449
  const waitMs = res.status === 429 && retryAfterS > 0 ? retryAfterS * 1e3 : RETRY_DELAYS_MS[attempt];
21409
- console.error(`[sync] HTTP ${res.status} from ${base2} \u2014 backing off ${Math.round(waitMs / 1e3)}s (attempt ${attempt + 1})`);
21450
+ console.error(`[sync] HTTP ${res.status} from ${base3} \u2014 backing off ${Math.round(waitMs / 1e3)}s (attempt ${attempt + 1})`);
21410
21451
  await sleep2(waitMs);
21411
21452
  continue;
21412
21453
  } catch (err) {
21413
- const fatal = err instanceof Error && /HTTP 4\d\d|sync failed after/.test(err.message);
21454
+ const fatal = isStandingFailure(err) || err instanceof Error && /HTTP 4\d\d|sync failed after/.test(err.message);
21414
21455
  if (fatal || attempt >= RETRY_DELAYS_MS.length) throw err;
21415
21456
  console.error(`[sync] upload attempt ${attempt + 1} failed (${err instanceof Error ? err.message : err}) \u2014 retrying in ${RETRY_DELAYS_MS[attempt] / 1e3}s`);
21416
21457
  await sleep2(RETRY_DELAYS_MS[attempt]);
@@ -21473,11 +21514,11 @@ async function syncToTargetInScope(cred, opts = {}) {
21473
21514
  }
21474
21515
  };
21475
21516
  };
21476
- const base2 = cred.serverUrl.replace(/\/$/, "");
21477
- const ledger = opts.useLedger ? getSyncedRows(base2) : null;
21517
+ const base3 = cred.serverUrl.replace(/\/$/, "");
21518
+ const ledger = opts.useLedger ? getSyncedRows(base3) : null;
21478
21519
  const convBatch = makeBatcher("conversations", 25, false, (rows) => {
21479
21520
  try {
21480
- markSynced(base2, rows.map((r) => {
21521
+ markSynced(base3, rows.map((r) => {
21481
21522
  const off = typeof r.from_offset === "number" ? r.from_offset : 0;
21482
21523
  const hash = shippedHash.get(r.session_id);
21483
21524
  return off > 0 ? { id: r.session_id, mtime: r.mtime, offset: off, size: off, hash, acked: true } : { id: r.session_id, mtime: r.mtime, hash };
@@ -21492,7 +21533,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21492
21533
  const ac = new AbortController();
21493
21534
  const timer = setTimeout(() => ac.abort(new Error(`upload timed out after ${UPLOAD_TIMEOUT_MS}ms`)), UPLOAD_TIMEOUT_MS);
21494
21535
  try {
21495
- const res = await fetch(`${base2}/api/sync`, {
21536
+ const res = await fetch(`${base3}/api/sync`, {
21496
21537
  method: "POST",
21497
21538
  headers,
21498
21539
  body: JSON.stringify({ conversations: convs }),
@@ -21503,10 +21544,10 @@ async function syncToTargetInScope(cred, opts = {}) {
21503
21544
  if (body.cli && body.cli.version) {
21504
21545
  const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2, sweepStaleStaging: sweepStaleStaging2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
21505
21546
  const authHeaders = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
21506
- const plan = planAutoUpdate2(base2, { cli: body.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
21547
+ const plan = planAutoUpdate2(base3, { cli: body.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
21507
21548
  sweepStaleStaging2();
21508
21549
  if (plan.update) {
21509
- void runAutoUpdate2(base2, authHeaders, ownVersion).catch(() => {
21550
+ void runAutoUpdate2(base3, authHeaders, ownVersion).catch(() => {
21510
21551
  });
21511
21552
  }
21512
21553
  }
@@ -21514,7 +21555,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21514
21555
  const acked = convs.filter((c) => !fullNeeded.has(c.session_id));
21515
21556
  if (acked.length > 0) {
21516
21557
  try {
21517
- markSynced(base2, acked.map((c) => ({
21558
+ markSynced(base3, acked.map((c) => ({
21518
21559
  id: c.session_id,
21519
21560
  mtime: c.mtime,
21520
21561
  offset: c.from_offset,
@@ -21584,7 +21625,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21584
21625
  const m = process.memoryUsage();
21585
21626
  console.error(`[sync-trace] ${msg} \xB7 heap=${(m.heapUsed / 1048576).toFixed(0)}MB rss=${(m.rss / 1048576).toFixed(0)}MB`);
21586
21627
  } : null;
21587
- trace?.(`walk start: ${slice.length} ref(s) \u2192 ${base2}`);
21628
+ trace?.(`walk start: ${slice.length} ref(s) \u2192 ${base3}`);
21588
21629
  if (upload.sessionMeta) {
21589
21630
  try {
21590
21631
  await reconcileFieldsForTarget(
@@ -21599,7 +21640,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21599
21640
  { convRefs: slice }
21600
21641
  );
21601
21642
  } catch (e) {
21602
- console.error(`[sync] field reconcile (${base2}): ${e instanceof Error ? e.message : e}`);
21643
+ console.error(`[sync] field reconcile (${base3}): ${e instanceof Error ? e.message : e}`);
21603
21644
  }
21604
21645
  }
21605
21646
  trace?.("field reconcile done");
@@ -21674,7 +21715,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21674
21715
  continue;
21675
21716
  } else {
21676
21717
  try {
21677
- markSynced(base2, [{ id: ref.prefixedId, mtime: ref.mtime }]);
21718
+ markSynced(base3, [{ id: ref.prefixedId, mtime: ref.mtime }]);
21678
21719
  } catch {
21679
21720
  }
21680
21721
  skipped++;
@@ -21697,7 +21738,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21697
21738
  const off = getBackendForId(ref.prefixedId)?.fileSize?.(ref.rawId) ?? 0;
21698
21739
  const priorCursor = ackRow?.s ?? 0;
21699
21740
  const safeToAck = off > 0 && off <= priorCursor;
21700
- markSynced(base2, [safeToAck ? { id: ref.prefixedId, mtime: ref.mtime, offset: off, size: off, hash: built.srcHash, acked: true } : { id: ref.prefixedId, mtime: ref.mtime, hash: built.srcHash }]);
21741
+ markSynced(base3, [safeToAck ? { id: ref.prefixedId, mtime: ref.mtime, offset: off, size: off, hash: built.srcHash, acked: true } : { id: ref.prefixedId, mtime: ref.mtime, hash: built.srcHash }]);
21701
21742
  } catch {
21702
21743
  }
21703
21744
  }
@@ -21707,7 +21748,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21707
21748
  if (!built) {
21708
21749
  if (ledger) {
21709
21750
  try {
21710
- markSynced(base2, [{ id: ref.prefixedId, mtime: ref.mtime }]);
21751
+ markSynced(base3, [{ id: ref.prefixedId, mtime: ref.mtime }]);
21711
21752
  } catch {
21712
21753
  }
21713
21754
  }
@@ -21763,7 +21804,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21763
21804
  walked++;
21764
21805
  if (walked % 250 === 0) console.error(`[sync] ${walked}/${slice.length} sessions\u2026`);
21765
21806
  }
21766
- const itemVersions = loadItemVersions(base2);
21807
+ const itemVersions = loadItemVersions(base3);
21767
21808
  const seenTools = /* @__PURE__ */ new Set();
21768
21809
  const seenItemKeys = /* @__PURE__ */ new Map();
21769
21810
  const registry = buildSourceRegistry();
@@ -21849,7 +21890,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21849
21890
  await appendFlush();
21850
21891
  for (const id of appendResults.fullResyncNeeded) {
21851
21892
  try {
21852
- markFullResync(base2, id);
21893
+ markFullResync(base3, id);
21853
21894
  } catch {
21854
21895
  }
21855
21896
  }
@@ -21866,7 +21907,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21866
21907
  const rec = {};
21867
21908
  for (const t of seenTools) rec[t] = extractorVersionForTool(t);
21868
21909
  for (const [k, v] of seenItemKeys) rec[k] = v;
21869
- saveItemVersions(base2, rec);
21910
+ saveItemVersions(base3, rec);
21870
21911
  } catch {
21871
21912
  }
21872
21913
  }
@@ -21879,9 +21920,9 @@ async function syncToTargetInScope(cred, opts = {}) {
21879
21920
  }
21880
21921
  if (tombstones.length > 0) {
21881
21922
  await post({ tombstones });
21882
- const data = getLedgerData(base2);
21923
+ const data = getLedgerData(base3);
21883
21924
  for (const t of tombstones) delete data[t.session_id];
21884
- persistLedgerData(base2, data);
21925
+ persistLedgerData(base3, data);
21885
21926
  }
21886
21927
  }
21887
21928
  if (opts.prune) await post({ prune_empty_sessions: true });
@@ -21949,7 +21990,12 @@ async function buildConversationSync(ref, mtime, opts = {}) {
21949
21990
  const containerBytes = built ? built.files.reduce((n, f) => n + Buffer.byteLength(f.text), 0) : 0;
21950
21991
  if (containerBytes > FULL_BUILD_MAX_BYTES) {
21951
21992
  console.error(`[sync] ${ref.prefixedId} is ${(containerBytes / 1048576).toFixed(0)}MB (over the ${(FULL_BUILD_MAX_BYTES / 1048576).toFixed(0)}MB ceiling) \u2014 shipping without the raw archive and derived data to keep the walk in memory`);
21952
- record({ kind: "oversized_session", mb: Math.round(containerBytes / 1048576), tool: ref.toolId });
21993
+ record({
21994
+ kind: "oversized_session",
21995
+ mb: Math.round(containerBytes / 1048576),
21996
+ tool: ref.toolId,
21997
+ dedupeKey: stableMark(ref.prefixedId)
21998
+ });
21953
21999
  includeRaw = false;
21954
22000
  includeMeta = false;
21955
22001
  }
@@ -22260,7 +22306,7 @@ async function syncIncremental(opts = {}) {
22260
22306
  lock.release();
22261
22307
  }
22262
22308
  }
22263
- var ownVersion, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
22309
+ var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
22264
22310
  var init_sync_client = __esm({
22265
22311
  "src/sync-client.ts"() {
22266
22312
  "use strict";
@@ -22306,10 +22352,16 @@ var init_sync_client = __esm({
22306
22352
  ownVersion = JSON.parse(readFileSync37(join44(dir, "../package.json"), "utf-8")).version || ownVersion;
22307
22353
  } catch {
22308
22354
  try {
22309
- if (true) ownVersion = "0.5.30";
22355
+ if (true) ownVersion = "0.5.32";
22310
22356
  } catch {
22311
22357
  }
22312
22358
  }
22359
+ ERROR_CLASS = "__chatRecallErrorClass";
22360
+ STANDING_FAILURE = /* @__PURE__ */ new Set([
22361
+ "auth",
22362
+ "payment_required",
22363
+ "insecure_transport"
22364
+ ]);
22313
22365
  walkProgressForUpload = null;
22314
22366
  lastServerPackSpec = null;
22315
22367
  credPath = () => join44(getDataDir(), "credentials.json");
@@ -22370,9 +22422,9 @@ async function refreshUpdateCheck(force = false) {
22370
22422
  const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
22371
22423
  for (const cred of loadAllCredentials2()) {
22372
22424
  if (!cred.serverUrl) continue;
22373
- const base2 = cred.serverUrl.replace(/\/+$/, "");
22425
+ const base3 = cred.serverUrl.replace(/\/+$/, "");
22374
22426
  try {
22375
- const res = await fetchWithTimeout(`${base2}/api/capabilities`, {
22427
+ const res = await fetchWithTimeout(`${base3}/api/capabilities`, {
22376
22428
  headers: cred.token ? { authorization: `Bearer ${cred.token}` } : {}
22377
22429
  }, 5e3);
22378
22430
  if (!res.ok) continue;
@@ -22967,7 +23019,7 @@ __export(install_skills_exports, {
22967
23019
  skillsSourceDir: () => skillsSourceDir,
22968
23020
  uninstallSkills: () => uninstallSkills
22969
23021
  });
22970
- import { readdirSync as readdirSync34, existsSync as existsSync46, mkdirSync as mkdirSync12, rmSync as rmSync7, cpSync, writeFileSync as writeFileSync13, readFileSync as readFileSync41, statSync as statSync42, realpathSync as realpathSync4 } from "fs";
23022
+ import { readdirSync as readdirSync35, existsSync as existsSync46, mkdirSync as mkdirSync12, rmSync as rmSync7, cpSync, writeFileSync as writeFileSync13, readFileSync as readFileSync41, statSync as statSync42, realpathSync as realpathSync4 } from "fs";
22971
23023
  import { createHash as createHash15 } from "crypto";
22972
23024
  import { join as join49, basename as basename23 } from "path";
22973
23025
  function skillsSourceDir() {
@@ -22993,7 +23045,7 @@ function skillsFingerprint() {
22993
23045
  }
22994
23046
  function filesUnder(dir, prefix = "") {
22995
23047
  const out = [];
22996
- for (const e of readdirSync34(dir, { withFileTypes: true })) {
23048
+ for (const e of readdirSync35(dir, { withFileTypes: true })) {
22997
23049
  if (e.name === MARKER) continue;
22998
23050
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
22999
23051
  if (e.isDirectory()) out.push(...filesUnder(join49(dir, e.name), rel));
@@ -23007,7 +23059,7 @@ function skillStamp() {
23007
23059
  function bundledSkillNames() {
23008
23060
  const src = skillsSourceDir();
23009
23061
  if (!existsSync46(src)) return [];
23010
- return readdirSync34(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync46(join49(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
23062
+ return readdirSync35(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync46(join49(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
23011
23063
  }
23012
23064
  function realPathOrSelf(p) {
23013
23065
  try {
@@ -23099,7 +23151,7 @@ function uninstallSkills() {
23099
23151
  perTarget.push({ id: t.id, label: t.label, removed });
23100
23152
  continue;
23101
23153
  }
23102
- for (const entry of readdirSync34(t.dir, { withFileTypes: true })) {
23154
+ for (const entry of readdirSync35(t.dir, { withFileTypes: true })) {
23103
23155
  if (!entry.isDirectory()) continue;
23104
23156
  const dir = join49(t.dir, entry.name);
23105
23157
  if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
@@ -23802,9 +23854,9 @@ function portableCommand(cmd) {
23802
23854
  }
23803
23855
  if (isExecutableFile(cmd))
23804
23856
  return { command: cmd, rewritten: false };
23805
- const base2 = basename26(cmd);
23806
- if (onPath(base2))
23807
- return { command: base2, rewritten: true };
23857
+ const base3 = basename26(cmd);
23858
+ if (onPath(base3))
23859
+ return { command: base3, rewritten: true };
23808
23860
  return { missing: cmd };
23809
23861
  }
23810
23862
  function entryFromSpec(spec) {
@@ -24870,17 +24922,17 @@ async function collectCode(opts) {
24870
24922
  return r;
24871
24923
  };
24872
24924
  log6(`indexing ${ws} + analyses \u2026`);
24873
- const base2 = await runMcp(bin, ws, [
24925
+ const base3 = await runMcp(bin, ws, [
24874
24926
  call(1, "index_workspace", { path: ws }),
24875
24927
  call(2, "status"),
24876
24928
  call(3, "get_tree"),
24877
24929
  ...ANALYSES.map((a, i) => call(10 + i, "analyze", { analysis: a }))
24878
24930
  ]);
24879
- const st = parseJson(base2, 2, {});
24880
- const tree = parseJson(base2, 3, []);
24931
+ const st = parseJson(base3, 2, {});
24932
+ const tree = parseJson(base3, 3, []);
24881
24933
  const A = {};
24882
24934
  ANALYSES.forEach((a, i) => {
24883
- A[a] = parseJson(base2, 10 + i, {});
24935
+ A[a] = parseJson(base3, 10 + i, {});
24884
24936
  });
24885
24937
  const { health, security: sec, duplication: dup, clones, literal_scan: lit, coupling: coup, cycles: cyc, dead_code: dead, unwrap_audit: unwrap, test_coverage: coverage, architecture: arch, crossref, type_drift: typeDrift, db_schema: dbSchema, migration_parity: migParity, manifest_compliance: manifest } = A;
24886
24938
  if (tree.length === 0) {
@@ -25780,14 +25832,14 @@ function writeLedger(l) {
25780
25832
  } catch {
25781
25833
  }
25782
25834
  }
25783
- async function pushProjectTaskStatuses(base2, authHeaders, opts = {}) {
25835
+ async function pushProjectTaskStatuses(base3, authHeaders, opts = {}) {
25784
25836
  const out = { files: 0, pushedTasks: 0 };
25785
25837
  const ledger = readLedger();
25786
25838
  let ledgerDirty = false;
25787
25839
  for (const fk of FILE_KINDS) {
25788
25840
  let projects;
25789
25841
  try {
25790
- const res = await fetchWithTimeout(`${base2}${fk.trackedPath}`, { headers: authHeaders });
25842
+ const res = await fetchWithTimeout(`${base3}${fk.trackedPath}`, { headers: authHeaders });
25791
25843
  if (!res.ok) continue;
25792
25844
  projects = (await res.json()).projects || [];
25793
25845
  } catch {
@@ -25815,7 +25867,7 @@ async function pushProjectTaskStatuses(base2, authHeaders, opts = {}) {
25815
25867
  continue;
25816
25868
  }
25817
25869
  try {
25818
- const res = await fetchWithTimeout(`${base2}${fk.statusPath}`, {
25870
+ const res = await fetchWithTimeout(`${base3}${fk.statusPath}`, {
25819
25871
  method: "POST",
25820
25872
  headers: { "content-type": "application/json", ...authHeaders },
25821
25873
  body: JSON.stringify({ project, items: changed.map((t) => ({ id: t.id, status: t.intent })) })
@@ -25921,16 +25973,16 @@ ${block}`);
25921
25973
  return { status: "error", result: JSON.stringify({ error: e instanceof Error ? e.message : "failed" }) };
25922
25974
  }
25923
25975
  }
25924
- async function runPull(base2, opts) {
25976
+ async function runPull(base3, opts) {
25925
25977
  const { executePull: executePull2 } = await Promise.resolve().then(() => (init_toolkit_pull(), toolkit_pull_exports));
25926
25978
  const { hostname: hostname3 } = await import("node:os");
25927
- const creds = loadAllCredentials().find((c) => c.serverUrl.replace(/\/+$/, "") === base2.replace(/\/+$/, ""));
25979
+ const creds = loadAllCredentials().find((c) => c.serverUrl.replace(/\/+$/, "") === base3.replace(/\/+$/, ""));
25928
25980
  const headers = creds?.token ? { authorization: `Bearer ${creds.token}` } : {};
25929
25981
  const wanted = opts.types?.length ? opts.types : ["mcp", "skill", "command", "agent", "instructions"];
25930
25982
  const rows = [];
25931
25983
  for (const type of wanted) {
25932
25984
  try {
25933
- const res = await fetchWithTimeout(`${base2}/api/toolkit/browse/${type}?limit=1000`, { headers });
25985
+ const res = await fetchWithTimeout(`${base3}/api/toolkit/browse/${type}?limit=1000`, { headers });
25934
25986
  if (!res.ok) continue;
25935
25987
  const body = await res.json();
25936
25988
  for (const it of body.items || []) rows.push(it);
@@ -25999,20 +26051,20 @@ async function runIntent(intent, ctx) {
25999
26051
  async function drainSyncIntents(opts = {}) {
26000
26052
  const out = { processed: 0, done: 0, errored: 0 };
26001
26053
  for (const cred of loadAllCredentials()) {
26002
- const base2 = cred.serverUrl.replace(/\/+$/, "");
26054
+ const base3 = cred.serverUrl.replace(/\/+$/, "");
26003
26055
  const authHeaders = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
26004
26056
  let pending;
26005
26057
  try {
26006
- const res = await fetchWithTimeout(`${base2}/api/sync-intents/pending`, { headers: authHeaders });
26058
+ const res = await fetchWithTimeout(`${base3}/api/sync-intents/pending`, { headers: authHeaders });
26007
26059
  if (!res.ok) continue;
26008
26060
  const data = await res.json();
26009
26061
  pending = data.intents || [];
26010
26062
  if (data.cli && data.cli.version) {
26011
26063
  const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2, sweepStaleStaging: sweepStaleStaging2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
26012
- const plan = planAutoUpdate2(base2, { cli: data.cli }, ownVersion2, process.env.CHAT_RECALL_AUTO_UPDATE);
26064
+ const plan = planAutoUpdate2(base3, { cli: data.cli }, ownVersion2, process.env.CHAT_RECALL_AUTO_UPDATE);
26013
26065
  sweepStaleStaging2();
26014
26066
  if (plan.update) {
26015
- void runAutoUpdate2(base2, authHeaders, ownVersion2).catch(() => {
26067
+ void runAutoUpdate2(base3, authHeaders, ownVersion2).catch(() => {
26016
26068
  });
26017
26069
  }
26018
26070
  }
@@ -26020,7 +26072,7 @@ async function drainSyncIntents(opts = {}) {
26020
26072
  continue;
26021
26073
  }
26022
26074
  for (const intent of pending) {
26023
- const { status, result } = await runIntent(intent, { base: base2 });
26075
+ const { status, result } = await runIntent(intent, { base: base3 });
26024
26076
  out.processed++;
26025
26077
  if (status === "done") out.done++;
26026
26078
  else out.errored++;
@@ -26029,7 +26081,7 @@ async function drainSyncIntents(opts = {}) {
26029
26081
  console.error(`[sync-intent] ${label}: ${status}`);
26030
26082
  }
26031
26083
  try {
26032
- await fetchWithTimeout(`${base2}/api/sync-intents/${intent.id}/ack`, {
26084
+ await fetchWithTimeout(`${base3}/api/sync-intents/${intent.id}/ack`, {
26033
26085
  method: "POST",
26034
26086
  headers: { "content-type": "application/json", ...authHeaders },
26035
26087
  body: JSON.stringify({ status, result })
@@ -26038,7 +26090,7 @@ async function drainSyncIntents(opts = {}) {
26038
26090
  }
26039
26091
  }
26040
26092
  try {
26041
- await pushProjectTaskStatuses(base2, authHeaders, { verbose: opts.verbose });
26093
+ await pushProjectTaskStatuses(base3, authHeaders, { verbose: opts.verbose });
26042
26094
  } catch {
26043
26095
  }
26044
26096
  }
@@ -26064,7 +26116,7 @@ var init_intent_drain = __esm({
26064
26116
  ownVersion2 = JSON.parse(readFileSync46(join55(dir, "../package.json"), "utf-8")).version || ownVersion2;
26065
26117
  } catch {
26066
26118
  try {
26067
- if (true) ownVersion2 = "0.5.30";
26119
+ if (true) ownVersion2 = "0.5.32";
26068
26120
  } catch {
26069
26121
  }
26070
26122
  }
@@ -26486,7 +26538,7 @@ var init_vault_crypto = __esm({
26486
26538
  });
26487
26539
 
26488
26540
  // ../engine/dist/core/vault-source.js
26489
- import { existsSync as existsSync53, readdirSync as readdirSync35, statSync as statSync46 } from "fs";
26541
+ import { existsSync as existsSync53, readdirSync as readdirSync36, statSync as statSync46 } from "fs";
26490
26542
  import { extname, join as join57 } from "path";
26491
26543
  function* walkVaultSources() {
26492
26544
  const t = loadSettings().team.vault;
@@ -26518,18 +26570,18 @@ function* walkClaude() {
26518
26570
  return;
26519
26571
  let projectDirs = [];
26520
26572
  try {
26521
- projectDirs = readdirSync35(root);
26573
+ projectDirs = readdirSync36(root);
26522
26574
  } catch {
26523
26575
  return;
26524
26576
  }
26525
26577
  for (const proj of projectDirs) {
26526
26578
  if (proj.startsWith("."))
26527
26579
  continue;
26528
- const projectPath = decodeProjectDirName(proj);
26580
+ const projectPath = resolveProjectDirName(proj);
26529
26581
  const projDir = join57(root, proj);
26530
26582
  let entries = [];
26531
26583
  try {
26532
- entries = readdirSync35(projDir);
26584
+ entries = readdirSync36(projDir);
26533
26585
  } catch {
26534
26586
  continue;
26535
26587
  }
@@ -26551,7 +26603,7 @@ function* walkGemini() {
26551
26603
  return;
26552
26604
  let hashDirs = [];
26553
26605
  try {
26554
- hashDirs = readdirSync35(tmp);
26606
+ hashDirs = readdirSync36(tmp);
26555
26607
  } catch {
26556
26608
  return;
26557
26609
  }
@@ -26561,7 +26613,7 @@ function* walkGemini() {
26561
26613
  continue;
26562
26614
  let entries = [];
26563
26615
  try {
26564
- entries = readdirSync35(chatsDir);
26616
+ entries = readdirSync36(chatsDir);
26565
26617
  } catch {
26566
26618
  continue;
26567
26619
  }
@@ -26573,8 +26625,8 @@ function* walkGemini() {
26573
26625
  const s = safeStat(path);
26574
26626
  if (!s)
26575
26627
  continue;
26576
- const base2 = f.replace(/^session-/, "").replace(/\.(jsonl?)$/, "");
26577
- yield { sessionId: `gemini_${base2}`, tool: "gemini", path, mtimeMs: s.mtimeMs, sizeBytes: s.size };
26628
+ const base3 = f.replace(/^session-/, "").replace(/\.(jsonl?)$/, "");
26629
+ yield { sessionId: `gemini_${base3}`, tool: "gemini", path, mtimeMs: s.mtimeMs, sizeBytes: s.size };
26578
26630
  }
26579
26631
  }
26580
26632
  }
@@ -26607,7 +26659,7 @@ function* walkCodex() {
26607
26659
  const dir = stack.pop();
26608
26660
  let entries = [];
26609
26661
  try {
26610
- entries = readdirSync35(dir, { withFileTypes: true });
26662
+ entries = readdirSync36(dir, { withFileTypes: true });
26611
26663
  } catch {
26612
26664
  continue;
26613
26665
  }
@@ -26622,8 +26674,8 @@ function* walkCodex() {
26622
26674
  const s = safeStat(path);
26623
26675
  if (!s)
26624
26676
  continue;
26625
- const base2 = e.name.replace(/\.jsonl$/, "");
26626
- yield { sessionId: `codex_${base2}`, tool: "codex", path, mtimeMs: s.mtimeMs, sizeBytes: s.size };
26677
+ const base3 = e.name.replace(/\.jsonl$/, "");
26678
+ yield { sessionId: `codex_${base3}`, tool: "codex", path, mtimeMs: s.mtimeMs, sizeBytes: s.size };
26627
26679
  }
26628
26680
  }
26629
26681
  }
@@ -26633,7 +26685,7 @@ function* walkAgy() {
26633
26685
  return;
26634
26686
  let sessionDirs = [];
26635
26687
  try {
26636
- sessionDirs = readdirSync35(brainDir);
26688
+ sessionDirs = readdirSync36(brainDir);
26637
26689
  } catch {
26638
26690
  return;
26639
26691
  }
@@ -26766,11 +26818,11 @@ function vaultEnable(passphrase, opts = {}) {
26766
26818
  }
26767
26819
  async function vaultEnableWithRemote(passphrase, remote, opts = {}) {
26768
26820
  const doFetch = opts.fetchImpl ?? fetch;
26769
- const base2 = remote.baseUrl.replace(/\/+$/, "");
26821
+ const base3 = remote.baseUrl.replace(/\/+$/, "");
26770
26822
  const headers = { authorization: `Bearer ${remote.token}`, "content-type": "application/json" };
26771
26823
  let published = { saltHex: null, keyId: null };
26772
26824
  try {
26773
- const r = await doFetch(`${base2}/api/vault/salt`, { headers });
26825
+ const r = await doFetch(`${base3}/api/vault/salt`, { headers });
26774
26826
  if (r.ok)
26775
26827
  published = await r.json();
26776
26828
  } catch {
@@ -26784,7 +26836,7 @@ async function vaultEnableWithRemote(passphrase, remote, opts = {}) {
26784
26836
  }
26785
26837
  const result = vaultEnable(passphrase);
26786
26838
  try {
26787
- const r = await doFetch(`${base2}/api/vault/salt`, {
26839
+ const r = await doFetch(`${base3}/api/vault/salt`, {
26788
26840
  method: "POST",
26789
26841
  headers,
26790
26842
  body: JSON.stringify({ saltHex: result.saltHex, keyId: result.keyId })
@@ -26877,8 +26929,8 @@ async function vaultSync(passphrase) {
26877
26929
  return report;
26878
26930
  }
26879
26931
  function importedPathFor(tool, sessionId) {
26880
- const base2 = sessionId.replace(/^gemini_/, "").replace(/^codex_/, "").replace(/^agy_/, "").replace(/^cursor_/, "").replace(/^opencode_/, "");
26881
- return join58(IMPORTED_ROOT(), tool, `${base2}.jsonl`);
26932
+ const base3 = sessionId.replace(/^gemini_/, "").replace(/^codex_/, "").replace(/^agy_/, "").replace(/^cursor_/, "").replace(/^opencode_/, "");
26933
+ return join58(IMPORTED_ROOT(), tool, `${base3}.jsonl`);
26882
26934
  }
26883
26935
  async function vaultRestore(passphrase, opts = {}) {
26884
26936
  const ctx = context2();
@@ -27421,8 +27473,8 @@ async function deviceLogin(opts, onPrompt) {
27421
27473
  throw new Error("device-auth timed out \u2014 the code expired before approval.");
27422
27474
  }
27423
27475
  async function betterAuthDeviceLogin(serverUrl, onPrompt) {
27424
- const base2 = serverUrl.replace(/\/+$/, "");
27425
- const startRes = await fetch(`${base2}/api/auth/device/code`, {
27476
+ const base3 = serverUrl.replace(/\/+$/, "");
27477
+ const startRes = await fetch(`${base3}/api/auth/device/code`, {
27426
27478
  method: "POST",
27427
27479
  headers: { "content-type": "application/json" },
27428
27480
  body: JSON.stringify({ client_id: BETTER_AUTH_CLI_CLIENT_ID })
@@ -27430,7 +27482,7 @@ async function betterAuthDeviceLogin(serverUrl, onPrompt) {
27430
27482
  if (!startRes.ok) {
27431
27483
  throw new Error(`device-auth init failed: HTTP ${startRes.status} ${await startRes.text().catch(() => "")}`);
27432
27484
  }
27433
- const start = await readDeviceCode(startRes, base2);
27485
+ const start = await readDeviceCode(startRes, base3);
27434
27486
  onPrompt({
27435
27487
  url: start.verification_uri_complete || start.verification_uri,
27436
27488
  userCode: start.user_code,
@@ -27440,7 +27492,7 @@ async function betterAuthDeviceLogin(serverUrl, onPrompt) {
27440
27492
  let interval = (start.interval ?? 5) * 1e3;
27441
27493
  while (Date.now() < deadline) {
27442
27494
  await sleep(interval);
27443
- const res = await fetch(`${base2}/api/auth/device/token`, {
27495
+ const res = await fetch(`${base3}/api/auth/device/token`, {
27444
27496
  method: "POST",
27445
27497
  headers: { "content-type": "application/json" },
27446
27498
  body: JSON.stringify({
@@ -27525,6 +27577,140 @@ var init_install_report = __esm({
27525
27577
  }
27526
27578
  });
27527
27579
 
27580
+ // src/bulk-delete.ts
27581
+ var bulk_delete_exports = {};
27582
+ __export(bulk_delete_exports, {
27583
+ BATCH: () => BATCH,
27584
+ bulkDelete: () => bulkDelete,
27585
+ listTombstones: () => listTombstones,
27586
+ needsConfirmation: () => needsConfirmation,
27587
+ restoreSessions: () => restoreSessions,
27588
+ selectSessions: () => selectSessions
27589
+ });
27590
+ async function selectSessions(target, opts, fetchImpl = fetch) {
27591
+ const found = [];
27592
+ const want = opts.match?.trim();
27593
+ for (let offset = 0; ; offset += PAGE) {
27594
+ const qs = new URLSearchParams({ limit: String(PAGE), offset: String(offset) });
27595
+ if (opts.project) qs.set("project", opts.project);
27596
+ if (opts.tool) qs.set("tool", opts.tool);
27597
+ const res = await fetchImpl(`${base2(target.serverUrl)}/api/conversations/recent?${qs}`, {
27598
+ headers: { authorization: `Bearer ${target.token}` }
27599
+ });
27600
+ if (!res.ok) throw new Error(`list failed: HTTP ${res.status}`);
27601
+ const body = await res.json();
27602
+ const page = body.sessions ?? [];
27603
+ if (page.length === 0) break;
27604
+ for (const row of page) {
27605
+ if (!row.sessionId) continue;
27606
+ if (want !== void 0 && (row.firstPrompt ?? "").trim() !== want) continue;
27607
+ found.push(row);
27608
+ if (opts.limit && found.length >= opts.limit) return found;
27609
+ }
27610
+ if (page.length < PAGE) break;
27611
+ }
27612
+ return found;
27613
+ }
27614
+ async function bulkDelete(ids, opts = {}) {
27615
+ const unique = [...new Set(ids.filter(Boolean))];
27616
+ const targets = opts.targets ?? loadAllCredentials();
27617
+ const fetchImpl = opts.fetchImpl ?? fetch;
27618
+ const out = { requested: unique.length, perTarget: {}, deleted: 0 };
27619
+ if (unique.length === 0 || targets.length === 0) return out;
27620
+ const succeeded = /* @__PURE__ */ new Set();
27621
+ for (const t of targets) {
27622
+ let deleted = 0;
27623
+ try {
27624
+ for (let i = 0; i < unique.length; i += BATCH) {
27625
+ const slice = unique.slice(i, i + BATCH);
27626
+ const res = await fetchImpl(`${base2(t.serverUrl)}/api/sync`, {
27627
+ method: "POST",
27628
+ headers: { "content-type": "application/json", authorization: `Bearer ${t.token}` },
27629
+ body: JSON.stringify({
27630
+ tombstones: slice.map((session_id) => ({ session_id, deleted_at: Date.now() }))
27631
+ })
27632
+ });
27633
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
27634
+ deleted += slice.length;
27635
+ for (const id of slice) succeeded.add(id);
27636
+ opts.onProgress?.(deleted, unique.length);
27637
+ }
27638
+ out.perTarget[t.serverUrl] = { deleted };
27639
+ } catch (e) {
27640
+ out.perTarget[t.serverUrl] = { deleted, error: e instanceof Error ? e.message : "failed" };
27641
+ }
27642
+ }
27643
+ out.deleted = succeeded.size;
27644
+ for (const t of targets) {
27645
+ if (!out.perTarget[t.serverUrl] || out.perTarget[t.serverUrl].error) continue;
27646
+ try {
27647
+ const data = getLedgerData(t.serverUrl);
27648
+ let touched = false;
27649
+ for (const id of succeeded) {
27650
+ if (id in data) {
27651
+ delete data[id];
27652
+ touched = true;
27653
+ }
27654
+ }
27655
+ if (touched) persistLedgerData(t.serverUrl, data);
27656
+ } catch {
27657
+ }
27658
+ }
27659
+ return out;
27660
+ }
27661
+ async function listTombstones(target, limit = 500, fetchImpl = fetch) {
27662
+ const res = await fetchImpl(`${base2(target.serverUrl)}/api/data/tombstones?limit=${limit}`, {
27663
+ headers: { authorization: `Bearer ${target.token}` }
27664
+ });
27665
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
27666
+ return await res.json();
27667
+ }
27668
+ async function restoreSessions(ids, opts = {}) {
27669
+ const unique = [...new Set(ids.filter(Boolean))];
27670
+ const targets = opts.targets ?? loadAllCredentials();
27671
+ const fetchImpl = opts.fetchImpl ?? fetch;
27672
+ const out = { requested: unique.length, restored: 0, perTarget: {} };
27673
+ if (unique.length === 0 || targets.length === 0) return out;
27674
+ let best = 0;
27675
+ for (const t of targets) {
27676
+ try {
27677
+ let restored = 0, notDeleted = 0;
27678
+ for (let i = 0; i < unique.length; i += BATCH) {
27679
+ const res = await fetchImpl(`${base2(t.serverUrl)}/api/data/restore`, {
27680
+ method: "POST",
27681
+ headers: { "content-type": "application/json", authorization: `Bearer ${t.token}` },
27682
+ body: JSON.stringify({ session_ids: unique.slice(i, i + BATCH) })
27683
+ });
27684
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
27685
+ const body = await res.json();
27686
+ restored += body.restored ?? 0;
27687
+ notDeleted += (body.notDeleted ?? []).length;
27688
+ }
27689
+ out.perTarget[t.serverUrl] = { restored, notDeleted };
27690
+ best = Math.max(best, restored);
27691
+ } catch (e) {
27692
+ out.perTarget[t.serverUrl] = { restored: 0, notDeleted: 0, error: e instanceof Error ? e.message : "failed" };
27693
+ }
27694
+ }
27695
+ out.restored = best;
27696
+ return out;
27697
+ }
27698
+ function needsConfirmation(opts) {
27699
+ const enumeratedByCaller = opts.argvIds > 0 && !opts.project && !opts.stdin;
27700
+ return !enumeratedByCaller;
27701
+ }
27702
+ var PAGE, BATCH, base2;
27703
+ var init_bulk_delete = __esm({
27704
+ "src/bulk-delete.ts"() {
27705
+ "use strict";
27706
+ init_sync_client();
27707
+ init_sync_ledger();
27708
+ PAGE = 200;
27709
+ BATCH = 250;
27710
+ base2 = (url) => url.replace(/\/+$/, "");
27711
+ }
27712
+ });
27713
+
27528
27714
  // ../engine/dist/core/resume-command.js
27529
27715
  function toolOfSessionId(sessionId) {
27530
27716
  if (sessionId.startsWith("codex_"))
@@ -27735,13 +27921,13 @@ function requireTarget() {
27735
27921
  }
27736
27922
  return t;
27737
27923
  }
27738
- async function readJsonBody(res, base2, what) {
27924
+ async function readJsonBody(res, base3, what) {
27739
27925
  const text = await res.text().catch(() => "");
27740
27926
  try {
27741
27927
  return JSON.parse(text);
27742
27928
  } catch {
27743
27929
  throw new Error(
27744
- `${base2} answered ${res.status} with something that is not JSON (${JSON.stringify(text.trim().slice(0, 60))}) for ${what} \u2014 a proxy, a captive portal or a host that no longer serves the chat-recall API`
27930
+ `${base3} answered ${res.status} with something that is not JSON (${JSON.stringify(text.trim().slice(0, 60))}) for ${what} \u2014 a proxy, a captive portal or a host that no longer serves the chat-recall API`
27745
27931
  );
27746
27932
  }
27747
27933
  }
@@ -28526,9 +28712,9 @@ program.command("feedback").argument("<message...>", "what you want to say \u201
28526
28712
  return;
28527
28713
  }
28528
28714
  const target = firstTarget();
28529
- const base2 = target?.base || (process.env.CHAT_RECALL_SERVER || "https://chatrecall.dev").replace(/\/+$/, "");
28715
+ const base3 = target?.base || (process.env.CHAT_RECALL_SERVER || "https://chatrecall.dev").replace(/\/+$/, "");
28530
28716
  try {
28531
- const res = await fetch(`${base2}/api/contact/feedback`, {
28717
+ const res = await fetch(`${base3}/api/contact/feedback`, {
28532
28718
  method: "POST",
28533
28719
  headers: {
28534
28720
  "content-type": "application/json",
@@ -28555,7 +28741,7 @@ program.command("feedback").argument("<message...>", "what you want to say \u201
28555
28741
  console.log(chalk2.dim(" Add --email <address> next time if you want a reply."));
28556
28742
  }
28557
28743
  } catch (err) {
28558
- console.error(chalk2.red(`Could not reach ${base2}.`));
28744
+ console.error(chalk2.red(`Could not reach ${base3}.`));
28559
28745
  console.error(chalk2.dim(" Your message, so it is not lost:"));
28560
28746
  console.error(chalk2.dim(` ${message}`));
28561
28747
  process.exitCode = 1;
@@ -29123,13 +29309,13 @@ sources2.command("decline <path>").description("Keep a folder out of sync (e.g.
29123
29309
  }
29124
29310
  const { fetchWithTimeout: fetchWithTimeout2 } = await Promise.resolve().then(() => (init_http(), http_exports));
29125
29311
  for (const t of targets) {
29126
- const base2 = t.serverUrl.replace(/\/+$/, "");
29312
+ const base3 = t.serverUrl.replace(/\/+$/, "");
29127
29313
  const headers = {};
29128
29314
  if (t.token) headers.authorization = `Bearer ${t.token}`;
29129
29315
  let deleted = 0, missing = 0, failed = 0;
29130
29316
  for (const id of ids) {
29131
29317
  try {
29132
- const res = await fetchWithTimeout2(`${base2}/api/conversations/${encodeURIComponent(id)}`, { method: "DELETE", headers }, 3e4);
29318
+ const res = await fetchWithTimeout2(`${base3}/api/conversations/${encodeURIComponent(id)}`, { method: "DELETE", headers }, 3e4);
29133
29319
  if (res.ok) deleted++;
29134
29320
  else if (res.status === 404) missing++;
29135
29321
  else failed++;
@@ -29137,7 +29323,7 @@ sources2.command("decline <path>").description("Keep a folder out of sync (e.g.
29137
29323
  failed++;
29138
29324
  }
29139
29325
  }
29140
- const line = ` ${base2}: deleted ${deleted}` + (missing ? `, ${missing} were not there` : "") + (failed ? chalk2.red(`, ${failed} FAILED`) : "");
29326
+ const line = ` ${base3}: deleted ${deleted}` + (missing ? `, ${missing} were not there` : "") + (failed ? chalk2.red(`, ${failed} FAILED`) : "");
29141
29327
  console.log(failed ? chalk2.yellow(line) : chalk2.green(line));
29142
29328
  if (failed) process.exitCode = 1;
29143
29329
  }
@@ -29985,10 +30171,10 @@ program.command("service <action>").description("Manage the per-user background
29985
30171
  });
29986
30172
  async function runLogin(serverUrl, opts) {
29987
30173
  const { saveCredentials: saveCredentials2, loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
29988
- const base2 = serverUrl.replace(/\/+$/, "");
30174
+ const base3 = serverUrl.replace(/\/+$/, "");
29989
30175
  const verifyToken = async (token) => {
29990
30176
  try {
29991
- const res = await fetch(`${base2}/api/status`, {
30177
+ const res = await fetch(`${base3}/api/status`, {
29992
30178
  headers: token ? { authorization: `Bearer ${token}` } : {},
29993
30179
  signal: AbortSignal.timeout(1e4)
29994
30180
  });
@@ -30004,36 +30190,36 @@ async function runLogin(serverUrl, opts) {
30004
30190
  return failLogin(opts, 'the token contains non-ASCII characters \u2014 this looks like a masked/truncated copy (e.g. "ct_12345678\u2026")');
30005
30191
  }
30006
30192
  const { probeServer: identifyServer, probeOk: identified, probeAdvice: identifyAdvice } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
30007
- const who = await identifyServer(base2, { timeoutMs: 1e4 });
30008
- if (!identified(who)) return failLogin(opts, identifyAdvice(who, base2));
30193
+ const who = await identifyServer(base3, { timeoutMs: 1e4 });
30194
+ if (!identified(who)) return failLogin(opts, identifyAdvice(who, base3));
30009
30195
  const check = await verifyToken(token);
30010
30196
  if (!check.ok) {
30011
30197
  if (check.status === 401 || check.status === 403) {
30012
30198
  console.error(chalk2.dim("Tokens are shown once \u2014 mint a fresh one (Account \u2192 Connect your machine) and copy the whole line."));
30013
- return failLogin(opts, `${base2} rejected the token (HTTP ${check.status})`);
30199
+ return failLogin(opts, `${base3} rejected the token (HTTP ${check.status})`);
30014
30200
  }
30015
30201
  if (check.status !== void 0 && check.status >= 500) {
30016
- return failLogin(opts, `${base2} answered HTTP ${check.status} \u2014 the server is up but failing. The token was never checked. Nothing to fix locally.`);
30202
+ return failLogin(opts, `${base3} answered HTTP ${check.status} \u2014 the server is up but failing. The token was never checked. Nothing to fix locally.`);
30017
30203
  }
30018
30204
  if (check.status === 404 || check.status === 410) {
30019
- return failLogin(opts, `${base2} does not serve the chat-recall API (HTTP ${check.status}). If the server moved, log in to its new URL.`);
30205
+ return failLogin(opts, `${base3} does not serve the chat-recall API (HTTP ${check.status}). If the server moved, log in to its new URL.`);
30020
30206
  }
30021
- return failLogin(opts, `could not verify the token against ${base2}: ${check.error || `HTTP ${check.status}`}`);
30207
+ return failLogin(opts, `could not verify the token against ${base3}: ${check.error || `HTTP ${check.status}`}`);
30022
30208
  }
30023
30209
  saveCredentials2({ serverUrl, token });
30024
30210
  console.log(chalk2.green("\u2713 Logged in.") + chalk2.dim(` server: ${serverUrl} (token verified)`));
30025
30211
  return;
30026
30212
  }
30027
- const existing = loadAllCredentials2().find((t) => t.serverUrl.replace(/\/+$/, "") === base2);
30213
+ const existing = loadAllCredentials2().find((t) => t.serverUrl.replace(/\/+$/, "") === base3);
30028
30214
  if (existing && (await verifyToken(existing.token)).ok) {
30029
30215
  console.log(chalk2.green("\u2713 Already connected.") + chalk2.dim(` server: ${serverUrl} (existing token verified)`));
30030
30216
  return;
30031
30217
  }
30032
30218
  try {
30033
30219
  const { probeServer: probeServer2, probeOk: probeOk2 } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
30034
- const identified = await probeServer2(base2, { timeoutMs: 8e3 });
30220
+ const identified = await probeServer2(base3, { timeoutMs: 8e3 });
30035
30221
  if (probeOk2(identified)) {
30036
- const probe = await fetch(`${base2}/api/status`, { signal: AbortSignal.timeout(8e3) });
30222
+ const probe = await fetch(`${base3}/api/status`, { signal: AbortSignal.timeout(8e3) });
30037
30223
  if (probe.status === 200) {
30038
30224
  saveCredentials2({ serverUrl, token: "" });
30039
30225
  console.log(chalk2.green("\u2713 Logged in.") + chalk2.dim(` ${serverUrl} (local server \u2014 no auth, no token needed)`));
@@ -30054,16 +30240,16 @@ async function runLogin(serverUrl, opts) {
30054
30240
  if (!issuer) {
30055
30241
  const { probeServer: probeServer2, probeOk: probeOk2, probeAdvice: probeAdvice2 } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
30056
30242
  for (let attempt = 1; attempt <= 3 && !authProvider; attempt++) {
30057
- const probe = await probeServer2(base2, { timeoutMs: 15e3 });
30243
+ const probe = await probeServer2(base3, { timeoutMs: 15e3 });
30058
30244
  if (probeOk2(probe)) {
30059
30245
  const caps = probe.raw;
30060
30246
  authProvider = caps?.authProvider;
30061
30247
  if (caps?.oidcIssuer) issuer = caps.oidcIssuer;
30062
30248
  capsError = null;
30063
30249
  } else if (probe.kind === "moved" || probe.kind === "not_api") {
30064
- return failLogin(opts, probeAdvice2(probe, base2));
30250
+ return failLogin(opts, probeAdvice2(probe, base3));
30065
30251
  } else {
30066
- capsError = probeAdvice2(probe, base2);
30252
+ capsError = probeAdvice2(probe, base3);
30067
30253
  }
30068
30254
  if (!authProvider && attempt < 3) await new Promise((r) => setTimeout(r, attempt * 1e3));
30069
30255
  }
@@ -30072,7 +30258,7 @@ async function runLogin(serverUrl, opts) {
30072
30258
  authProvider = "better-auth";
30073
30259
  if (capsError) {
30074
30260
  console.log(` ${chalk2.yellow("Could not read the server's capabilities")} (${capsError}) \u2014 assuming the standard sign-in flow.`);
30075
- void Promise.resolve().then(() => (init_install_report(), install_report_exports)).then(({ reportInstallFailure: reportInstallFailure2 }) => reportInstallFailure2(base2, {
30261
+ void Promise.resolve().then(() => (init_install_report(), install_report_exports)).then(({ reportInstallFailure: reportInstallFailure2 }) => reportInstallFailure2(base3, {
30076
30262
  step: "cli_login_probe",
30077
30263
  reason: capsError?.slice(0, 40) ?? null,
30078
30264
  message: capsError
@@ -30094,20 +30280,20 @@ async function runLogin(serverUrl, opts) {
30094
30280
  console.log(chalk2.dim(` If the page asks for a code: ${chalk2.bold(p.userCode)}`));
30095
30281
  console.log(chalk2.dim("Then come back here \u2014 this finishes on its own. Waiting\u2026"));
30096
30282
  };
30097
- const tokens = authProvider === "better-auth" ? await betterAuthDeviceLogin2(base2, onPrompt) : await deviceLogin2({ issuer, clientId: opts.clientId }, onPrompt);
30283
+ const tokens = authProvider === "better-auth" ? await betterAuthDeviceLogin2(base3, onPrompt) : await deviceLogin2({ issuer, clientId: opts.clientId }, onPrompt);
30098
30284
  const authHdr = { authorization: `Bearer ${tokens.accessToken}`, "content-type": "application/json" };
30099
- const meRes = await fetch(`${base2}/api/me`, { headers: authHdr });
30100
- if (!meRes.ok) throw new Error(`${base2}/api/me: HTTP ${meRes.status} ${await meRes.text().catch(() => "")}`);
30101
- const me = await readJsonBody(meRes, base2, "GET /api/me");
30285
+ const meRes = await fetch(`${base3}/api/me`, { headers: authHdr });
30286
+ if (!meRes.ok) throw new Error(`${base3}/api/me: HTTP ${meRes.status} ${await meRes.text().catch(() => "")}`);
30287
+ const me = await readJsonBody(meRes, base3, "GET /api/me");
30102
30288
  const teams = me.teams || [];
30103
30289
  let slug = opts.team;
30104
30290
  if (!slug) {
30105
30291
  if (teams.length === 1) slug = teams[0].team_slug;
30106
30292
  else if (teams.length === 0) {
30107
30293
  const name = me.user?.email?.split("@")[0]?.replace(/[^a-z0-9]/gi, "") || "workspace";
30108
- const created = await fetch(`${base2}/api/teams`, { method: "POST", headers: authHdr, body: JSON.stringify({ name }) });
30294
+ const created = await fetch(`${base3}/api/teams`, { method: "POST", headers: authHdr, body: JSON.stringify({ name }) });
30109
30295
  if (!created.ok) throw new Error(`workspace create failed: HTTP ${created.status} ${await created.text().catch(() => "")}`);
30110
- slug = (await readJsonBody(created, base2, "POST /api/teams")).slug;
30296
+ slug = (await readJsonBody(created, base3, "POST /api/teams")).slug;
30111
30297
  console.log(chalk2.dim(`Created workspace "${name}" (${slug}) \u2014 you're its owner.`));
30112
30298
  } else {
30113
30299
  teams.forEach((t) => console.error(` ${t.team_slug} ${chalk2.dim(t.name)}`));
@@ -30115,13 +30301,13 @@ async function runLogin(serverUrl, opts) {
30115
30301
  }
30116
30302
  }
30117
30303
  const deviceId = opts.deviceId || (await import("node:os")).hostname();
30118
- const mint = await fetch(`${base2}/api/teams/${encodeURIComponent(slug)}/tokens`, {
30304
+ const mint = await fetch(`${base3}/api/teams/${encodeURIComponent(slug)}/tokens`, {
30119
30305
  method: "POST",
30120
30306
  headers: authHdr,
30121
30307
  body: JSON.stringify({ device_id: deviceId })
30122
30308
  });
30123
30309
  if (!mint.ok) throw new Error(`token mint failed: HTTP ${mint.status} ${await mint.text().catch(() => "")}`);
30124
- const { token } = await readJsonBody(mint, base2, "POST /api/teams/:slug/tokens");
30310
+ const { token } = await readJsonBody(mint, base3, "POST /api/teams/:slug/tokens");
30125
30311
  saveCredentials2({ serverUrl, token });
30126
30312
  console.log(chalk2.green(`\u2713 Logged in to ${chalk2.bold(slug)}`) + chalk2.dim(` (device: ${deviceId}) server: ${serverUrl}`));
30127
30313
  if (!opts.fromInit) {
@@ -30373,30 +30559,142 @@ program.command("logout <server-url>").description("Remove one sync target (othe
30373
30559
  process.exit(1);
30374
30560
  }
30375
30561
  });
30376
- program.command("delete <session-id>").description("Delete a session from chat-recall everywhere: purges it on every logged-in server and tombstones it so it can't resurrect on the next sync. Does NOT touch the AI tool's own transcript file.").action(async (sessionId) => {
30562
+ program.command("delete [session-ids...]").description("Delete sessions from chat-recall everywhere: purges them on every logged-in server and tombstones them so they can't resurrect on the next sync. Does NOT touch the AI tool's own transcript files.").option("--project <id>", "Select every session in a project (e.g. git:github.com/me/repo)").option("--match <text>", "With --project: only sessions whose first prompt is EXACTLY this").option("--tool <name>", "With --project: only sessions from this tool (claude, codex, \u2026)").option("--stdin", "Read session ids from stdin, one per line").option("-n, --dry-run", "List what would be deleted and exit").option("-y, --yes", "Skip the confirmation prompt").action(async (sessionIds, opts) => {
30377
30563
  const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
30564
+ const { selectSessions: selectSessions2, bulkDelete: bulkDelete2, needsConfirmation: needsConfirmation2 } = await Promise.resolve().then(() => (init_bulk_delete(), bulk_delete_exports));
30378
30565
  const targets = loadAllCredentials2();
30379
30566
  if (targets.length === 0) {
30380
30567
  console.error(chalk2.red("Not logged in \u2014 run `chat-recall login <server-url>` first."));
30381
30568
  process.exit(1);
30382
30569
  }
30383
- let ok = 0;
30384
- for (const t of targets) {
30570
+ if ((opts.match || opts.tool) && !opts.project) {
30571
+ console.error(chalk2.red("--match and --tool select WITHIN a project; pass --project too."));
30572
+ process.exit(1);
30573
+ }
30574
+ let ids = [...sessionIds];
30575
+ let preview = [];
30576
+ if (opts.stdin) {
30577
+ const stdin = await new Promise((resolve3) => {
30578
+ let buf = "";
30579
+ process.stdin.setEncoding("utf-8");
30580
+ process.stdin.on("data", (d) => {
30581
+ buf += d;
30582
+ });
30583
+ process.stdin.on("end", () => resolve3(buf));
30584
+ });
30585
+ ids.push(...stdin.split("\n").map((l) => l.trim()).filter(Boolean));
30586
+ }
30587
+ if (opts.project) {
30385
30588
  try {
30386
- const res = await fetch(`${t.serverUrl.replace(/\/+$/, "")}/api/conversations/${encodeURIComponent(sessionId)}`, {
30387
- method: "DELETE",
30388
- headers: { authorization: `Bearer ${t.token}` }
30589
+ preview = await selectSessions2(targets[0], { project: opts.project, match: opts.match, tool: opts.tool });
30590
+ } catch (e) {
30591
+ console.error(chalk2.red(`Could not list sessions: ${e instanceof Error ? e.message : "failed"}`));
30592
+ process.exit(1);
30593
+ }
30594
+ ids.push(...preview.map((r) => r.sessionId));
30595
+ }
30596
+ ids = [...new Set(ids.filter(Boolean))];
30597
+ if (ids.length === 0) {
30598
+ console.log(chalk2.yellow("Nothing selected \u2014 no session ids given and no selector matched."));
30599
+ return;
30600
+ }
30601
+ const shown = preview.length > 0 ? preview.slice(0, 10) : ids.slice(0, 10).map((id) => ({ sessionId: id, firstPrompt: void 0 }));
30602
+ for (const r of shown) {
30603
+ const label = r.firstPrompt ? chalk2.dim(` \u2014 ${r.firstPrompt.replace(/\s+/g, " ").slice(0, 60)}`) : "";
30604
+ console.log(` ${r.sessionId}${label}`);
30605
+ }
30606
+ if (ids.length > shown.length) console.log(chalk2.dim(` \u2026 and ${ids.length - shown.length} more`));
30607
+ if (opts.dryRun) {
30608
+ console.log(chalk2.cyan(`
30609
+ Dry run \u2014 ${ids.length} session(s) would be deleted on ${targets.length} server(s). Nothing was changed.`));
30610
+ return;
30611
+ }
30612
+ if (!opts.yes && needsConfirmation2({ argvIds: sessionIds.length, project: opts.project, stdin: opts.stdin })) {
30613
+ if (!process.stdin.isTTY) {
30614
+ console.error(chalk2.red(`
30615
+ Refusing to delete ${ids.length} session(s) without confirmation \u2014 pass --yes (or -n to preview).`));
30616
+ process.exit(1);
30617
+ }
30618
+ const readline = await import("node:readline/promises");
30619
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
30620
+ const answer = await rl.question(chalk2.yellow(`
30621
+ Permanently delete ${ids.length} session(s) on ${targets.length} server(s)? This cannot be undone. [y/N] `));
30622
+ rl.close();
30623
+ if (answer.trim().toLowerCase() !== "y") {
30624
+ console.log("Aborted \u2014 nothing was deleted.");
30625
+ return;
30626
+ }
30627
+ }
30628
+ const res = await bulkDelete2(ids, { targets });
30629
+ for (const [server, r] of Object.entries(res.perTarget)) {
30630
+ if (r.error) console.error(chalk2.red(`\u2717 ${server}: ${r.deleted}/${ids.length} deleted, then failed \u2014 ${r.error}`));
30631
+ else console.log(chalk2.green(`\u2713 ${server}: ${r.deleted} deleted`));
30632
+ }
30633
+ if (res.deleted === 0) {
30634
+ console.error(chalk2.red("Nothing was deleted."));
30635
+ process.exit(1);
30636
+ }
30637
+ console.log(chalk2.dim(`Tombstoned \u2014 re-sync cannot resurrect them. Local transcript files were not touched.`));
30638
+ if (res.deleted < ids.length) {
30639
+ console.error(chalk2.yellow(`Note: ${ids.length - res.deleted} session(s) did not delete on any server.`));
30640
+ }
30641
+ });
30642
+ program.command("restore [session-ids...]").description("Undo a delete: lifts the tombstone so those sessions can be synced again. Restores no content by itself \u2014 re-run `chat-recall index --force` on a machine that still has the transcript. With no ids, lists what has been deleted.").option("--stdin", "Read session ids from stdin, one per line").option("--list", "Just show what has been deleted, newest first").option("--limit <n>", "How many deletions to list (default 50)").action(async (sessionIds, opts) => {
30643
+ const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
30644
+ const { listTombstones: listTombstones2, restoreSessions: restoreSessions2 } = await Promise.resolve().then(() => (init_bulk_delete(), bulk_delete_exports));
30645
+ const targets = loadAllCredentials2();
30646
+ if (targets.length === 0) {
30647
+ console.error(chalk2.red("Not logged in \u2014 run `chat-recall login <server-url>` first."));
30648
+ process.exit(1);
30649
+ }
30650
+ const ids = [...sessionIds];
30651
+ if (opts.stdin) {
30652
+ const buf = await new Promise((resolve3) => {
30653
+ let b = "";
30654
+ process.stdin.setEncoding("utf-8");
30655
+ process.stdin.on("data", (d) => {
30656
+ b += d;
30389
30657
  });
30390
- if (res.ok) {
30391
- ok++;
30392
- console.log(chalk2.green(`\u2713 Deleted on ${t.serverUrl}`));
30393
- } else console.error(chalk2.red(`\u2717 ${t.serverUrl}: HTTP ${res.status}`));
30658
+ process.stdin.on("end", () => resolve3(b));
30659
+ });
30660
+ ids.push(...buf.split("\n").map((l) => l.trim()).filter(Boolean));
30661
+ }
30662
+ if (opts.list || ids.length === 0) {
30663
+ const limit = Math.max(1, parseInt(opts.limit ?? "") || 50);
30664
+ try {
30665
+ const { total, tombstones } = await listTombstones2(targets[0], limit);
30666
+ if (total === 0) {
30667
+ console.log(chalk2.dim("Nothing has been deleted."));
30668
+ return;
30669
+ }
30670
+ console.log(chalk2.bold(`${total} deleted session(s) \u2014 newest first:
30671
+ `));
30672
+ for (const t of tombstones) {
30673
+ console.log(` ${t.session_id} ${chalk2.dim(new Date(t.deleted_at).toISOString().replace("T", " ").slice(0, 19))}`);
30674
+ }
30675
+ if (total > tombstones.length) console.log(chalk2.dim(`
30676
+ \u2026 and ${total - tombstones.length} more (--limit to see them)`));
30677
+ console.log(chalk2.dim("\nRestore with: chat-recall restore <id...>"));
30394
30678
  } catch (e) {
30395
- console.error(chalk2.red(`\u2717 ${t.serverUrl}: ${e instanceof Error ? e.message : "failed"}`));
30679
+ console.error(chalk2.red(`Could not list deletions: ${e instanceof Error ? e.message : "failed"}`));
30680
+ process.exit(1);
30396
30681
  }
30682
+ return;
30397
30683
  }
30398
- if (ok === 0) process.exit(1);
30399
- console.log(chalk2.dim(`Tombstoned on ${ok}/${targets.length} server(s) \u2014 re-sync cannot resurrect it.`));
30684
+ const res = await restoreSessions2(ids, { targets });
30685
+ for (const [server, r] of Object.entries(res.perTarget)) {
30686
+ if (r.error) console.error(chalk2.red(`\u2717 ${server}: ${r.error}`));
30687
+ else {
30688
+ console.log(chalk2.green(`\u2713 ${server}: ${r.restored} tombstone(s) lifted`));
30689
+ if (r.notDeleted > 0) console.log(chalk2.yellow(` ${r.notDeleted} id(s) were not deleted here \u2014 nothing to lift`));
30690
+ }
30691
+ }
30692
+ if (res.restored === 0) {
30693
+ console.error(chalk2.red("No tombstones were lifted."));
30694
+ process.exit(1);
30695
+ }
30696
+ console.log(chalk2.dim("\nThe content is still gone from the server. Re-upload it from a machine that"));
30697
+ console.log(chalk2.dim("still holds the transcript: chat-recall index --force"));
30400
30698
  });
30401
30699
  program.command("rename <session-id> <name>").description('Give a session a memorable name on every logged-in server (mirrors Claude Code\'s /rename \u2014 shows in `recall recent` and the web UI in place of the auto summary). Pass an empty name "" to clear it and revert to the auto title.').action(async (sessionId, name) => {
30402
30700
  const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
@@ -30486,16 +30784,16 @@ program.command("update").description("Update this CLI now from the server you s
30486
30784
  }
30487
30785
  let installFailed = false;
30488
30786
  for (const cred of targets) {
30489
- const base2 = cred.serverUrl.replace(/\/+$/, "");
30787
+ const base3 = cred.serverUrl.replace(/\/+$/, "");
30490
30788
  const headers = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
30491
- const r = await runAutoUpdate2(base2, headers, cliVersion3());
30789
+ const r = await runAutoUpdate2(base3, headers, cliVersion3());
30492
30790
  if (r.updated) {
30493
- console.log(chalk2.green(`\u2713 ${r.reason}`) + chalk2.dim(` (${base2})`));
30791
+ console.log(chalk2.green(`\u2713 ${r.reason}`) + chalk2.dim(` (${base3})`));
30494
30792
  continue;
30495
30793
  }
30496
30794
  const benign = /already current|no CLI release|disabled/.test(r.reason);
30497
30795
  if (/install failed|download failed|checksum/.test(r.reason)) installFailed = true;
30498
- console.log((benign ? chalk2.dim("\u2022 ") : chalk2.red("\u2717 ")) + r.reason + chalk2.dim(` (${base2})`));
30796
+ console.log((benign ? chalk2.dim("\u2022 ") : chalk2.red("\u2717 ")) + r.reason + chalk2.dim(` (${base3})`));
30499
30797
  }
30500
30798
  await refreshUpdateCheck2(true);
30501
30799
  if (installFailed) {