crispy-recall 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/recall.js CHANGED
@@ -2378,11 +2378,11 @@ var require_extract_zip = __commonJS({
2378
2378
  var { createWriteStream: createWriteStream3, promises: fs5 } = require("fs");
2379
2379
  var getStream = require_get_stream();
2380
2380
  var path4 = require("path");
2381
- var { promisify: promisify3 } = require("util");
2381
+ var { promisify: promisify4 } = require("util");
2382
2382
  var stream2 = require("stream");
2383
2383
  var yauzl = require_yauzl();
2384
- var openZip = promisify3(yauzl.open);
2385
- var pipeline = promisify3(stream2.pipeline);
2384
+ var openZip = promisify4(yauzl.open);
2385
+ var pipeline = promisify4(stream2.pipeline);
2386
2386
  var Extractor = class {
2387
2387
  constructor(zipPath, opts) {
2388
2388
  this.zipPath = zipPath;
@@ -2466,7 +2466,7 @@ var require_extract_zip = __commonJS({
2466
2466
  if (isDir)
2467
2467
  return;
2468
2468
  debug("opening read stream", dest);
2469
- const readStream = await promisify3(this.zipfile.openReadStream.bind(this.zipfile))(entry);
2469
+ const readStream = await promisify4(this.zipfile.openReadStream.bind(this.zipfile))(entry);
2470
2470
  if (symlink) {
2471
2471
  const link = await getStream(readStream);
2472
2472
  debug("creating symlink", link, dest);
@@ -2584,16 +2584,55 @@ var init_paths = __esm({
2584
2584
  }
2585
2585
  });
2586
2586
 
2587
+ // src/installer/stable-node.ts
2588
+ function stableNodePath(opts = {}) {
2589
+ const execPath = opts.execPath ?? process.execPath;
2590
+ const platform2 = opts.platform ?? process.platform;
2591
+ if (platform2 !== "darwin")
2592
+ return execPath;
2593
+ const candidates = opts.candidates ?? DEFAULT_CANDIDATES;
2594
+ let realExec;
2595
+ try {
2596
+ realExec = (0, import_node_fs2.realpathSync)(execPath);
2597
+ } catch {
2598
+ return execPath;
2599
+ }
2600
+ for (const candidate of candidates) {
2601
+ try {
2602
+ if ((0, import_node_fs2.existsSync)(candidate) && (0, import_node_fs2.realpathSync)(candidate) === realExec) {
2603
+ return candidate;
2604
+ }
2605
+ } catch {
2606
+ }
2607
+ }
2608
+ return execPath;
2609
+ }
2610
+ var import_node_fs2, DEFAULT_CANDIDATES;
2611
+ var init_stable_node = __esm({
2612
+ "src/installer/stable-node.ts"() {
2613
+ "use strict";
2614
+ import_node_fs2 = require("node:fs");
2615
+ DEFAULT_CANDIDATES = [
2616
+ "/opt/homebrew/bin/node",
2617
+ // Apple Silicon Homebrew
2618
+ "/opt/homebrew/opt/node/bin/node",
2619
+ "/usr/local/bin/node",
2620
+ // Intel Homebrew
2621
+ "/usr/local/opt/node/bin/node"
2622
+ ];
2623
+ }
2624
+ });
2625
+
2587
2626
  // src/installer/settings-merge.ts
2588
2627
  function writeFileAtomic(path4, contents) {
2589
2628
  const tmp = `${path4}.tmp.${process.pid}`;
2590
2629
  try {
2591
- (0, import_node_fs2.writeFileSync)(tmp, contents);
2592
- (0, import_node_fs2.renameSync)(tmp, path4);
2630
+ (0, import_node_fs3.writeFileSync)(tmp, contents);
2631
+ (0, import_node_fs3.renameSync)(tmp, path4);
2593
2632
  } catch (err) {
2594
2633
  try {
2595
- if ((0, import_node_fs2.existsSync)(tmp))
2596
- (0, import_node_fs2.unlinkSync)(tmp);
2634
+ if ((0, import_node_fs3.existsSync)(tmp))
2635
+ (0, import_node_fs3.unlinkSync)(tmp);
2597
2636
  } catch {
2598
2637
  }
2599
2638
  throw err;
@@ -2604,7 +2643,7 @@ function backupStamp() {
2604
2643
  }
2605
2644
  function backupFile(path4) {
2606
2645
  const dest = `${path4}.bak.${backupStamp()}`;
2607
- (0, import_node_fs2.copyFileSync)(path4, dest);
2646
+ (0, import_node_fs3.copyFileSync)(path4, dest);
2608
2647
  return dest;
2609
2648
  }
2610
2649
  function detectLineEnding(raw2) {
@@ -2653,9 +2692,9 @@ function makeEntry(command) {
2653
2692
  return { matcher: "", hooks: [{ type: "command", command }] };
2654
2693
  }
2655
2694
  function mergeStopHook(filePath, hookScriptPath) {
2656
- const desiredCommand = `"${process.execPath}" "${hookScriptPath}"`;
2657
- const exists = (0, import_node_fs2.existsSync)(filePath);
2658
- const raw2 = exists ? (0, import_node_fs2.readFileSync)(filePath, "utf-8") : null;
2695
+ const desiredCommand = `"${stableNodePath()}" "${hookScriptPath}"`;
2696
+ const exists = (0, import_node_fs3.existsSync)(filePath);
2697
+ const raw2 = exists ? (0, import_node_fs3.readFileSync)(filePath, "utf-8") : null;
2659
2698
  const obj = raw2 && raw2.trim().length > 0 ? parseTolerant(raw2) : {};
2660
2699
  if (!obj.hooks || typeof obj.hooks !== "object")
2661
2700
  obj.hooks = {};
@@ -2681,9 +2720,9 @@ function mergeStopHook(filePath, hookScriptPath) {
2681
2720
  return backup ? { changed: true, backup } : { changed: true };
2682
2721
  }
2683
2722
  function removeStopHook(filePath) {
2684
- if (!(0, import_node_fs2.existsSync)(filePath))
2723
+ if (!(0, import_node_fs3.existsSync)(filePath))
2685
2724
  return { changed: false };
2686
- const raw2 = (0, import_node_fs2.readFileSync)(filePath, "utf-8");
2725
+ const raw2 = (0, import_node_fs3.readFileSync)(filePath, "utf-8");
2687
2726
  if (raw2.trim().length === 0)
2688
2727
  return { changed: false };
2689
2728
  const obj = parseTolerant(raw2);
@@ -2713,11 +2752,12 @@ function removeStopHook(filePath) {
2713
2752
  writeFileAtomic(filePath, serialize(obj, raw2));
2714
2753
  return { changed: true, backup };
2715
2754
  }
2716
- var import_node_fs2, HOOK_ARRAYS;
2755
+ var import_node_fs3, HOOK_ARRAYS;
2717
2756
  var init_settings_merge = __esm({
2718
2757
  "src/installer/settings-merge.ts"() {
2719
2758
  "use strict";
2720
- import_node_fs2 = require("node:fs");
2759
+ import_node_fs3 = require("node:fs");
2760
+ init_stable_node();
2721
2761
  HOOK_ARRAYS = ["Stop", "SubagentStop"];
2722
2762
  }
2723
2763
  });
@@ -2728,10 +2768,10 @@ function configPath() {
2728
2768
  }
2729
2769
  function readConfig() {
2730
2770
  const p = configPath();
2731
- if (!(0, import_node_fs3.existsSync)(p))
2771
+ if (!(0, import_node_fs4.existsSync)(p))
2732
2772
  return null;
2733
2773
  try {
2734
- return JSON.parse((0, import_node_fs3.readFileSync)(p, "utf-8"));
2774
+ return JSON.parse((0, import_node_fs4.readFileSync)(p, "utf-8"));
2735
2775
  } catch (err) {
2736
2776
  log({
2737
2777
  source: "installer/config",
@@ -2743,7 +2783,7 @@ function readConfig() {
2743
2783
  }
2744
2784
  function writeEmbedderConfig(embedder) {
2745
2785
  const p = configPath();
2746
- (0, import_node_fs3.mkdirSync)((0, import_node_path2.dirname)(p), { recursive: true });
2786
+ (0, import_node_fs4.mkdirSync)((0, import_node_path2.dirname)(p), { recursive: true });
2747
2787
  const existing = readConfig() ?? {};
2748
2788
  const merged = { ...existing, embedder };
2749
2789
  writeFileAtomic(p, JSON.stringify(merged, null, 2) + "\n");
@@ -2760,11 +2800,11 @@ function readEmbedderConfig() {
2760
2800
  return cfg.embedder;
2761
2801
  return { mode: "cpu", ngl: 0, libDir: null, detectedAt: "" };
2762
2802
  }
2763
- var import_node_fs3, import_node_path2;
2803
+ var import_node_fs4, import_node_path2;
2764
2804
  var init_config = __esm({
2765
2805
  "src/installer/config.ts"() {
2766
2806
  "use strict";
2767
- import_node_fs3 = require("node:fs");
2807
+ import_node_fs4 = require("node:fs");
2768
2808
  import_node_path2 = require("node:path");
2769
2809
  init_paths();
2770
2810
  init_log();
@@ -2775,15 +2815,21 @@ var init_config = __esm({
2775
2815
  // src/recall/embedder.ts
2776
2816
  var embedder_exports = {};
2777
2817
  __export(embedder_exports, {
2818
+ LLAMA_RELEASE_TAG: () => LLAMA_RELEASE_TAG,
2819
+ MACOS_MIN_VERSION: () => MACOS_MIN_VERSION,
2820
+ buildUnzipInvocations: () => buildUnzipInvocations,
2778
2821
  disposeEmbedder: () => disposeEmbedder,
2779
2822
  embed: () => embed,
2780
2823
  embedBatch: () => embedBatch,
2781
2824
  ensureBinary: () => ensureBinary,
2782
2825
  ensureModel: () => ensureModel,
2826
+ extractArchive: () => extractArchive,
2783
2827
  getBinaryPath: () => getBinaryPath,
2784
2828
  getModelPath: () => getModelPath,
2785
2829
  hasNvidiaGpu: () => hasNvidiaGpu,
2786
- initEmbedder: () => initEmbedder
2830
+ initEmbedder: () => initEmbedder,
2831
+ installExtractedBinaries: () => installExtractedBinaries,
2832
+ withTimeout: () => withTimeout
2787
2833
  });
2788
2834
  function gpuRuntime() {
2789
2835
  if (_gpuRuntime === void 0) {
@@ -2860,8 +2906,8 @@ async function getBinaryAssetCandidates() {
2860
2906
  async function ensureBinary() {
2861
2907
  const binPath = getBinaryPath();
2862
2908
  const serverBinPath = getServerBinaryPath();
2863
- const embeddingExists = (0, import_node_fs4.existsSync)(binPath);
2864
- const serverNeeded = SERVER_SUPPORTED && !(0, import_node_fs4.existsSync)(serverBinPath);
2909
+ const embeddingExists = (0, import_node_fs5.existsSync)(binPath);
2910
+ const serverNeeded = SERVER_SUPPORTED && !(0, import_node_fs5.existsSync)(serverBinPath);
2865
2911
  if (embeddingExists && !serverNeeded) {
2866
2912
  binaryPath = binPath;
2867
2913
  return binPath;
@@ -2904,11 +2950,11 @@ async function validateBinary(binPath) {
2904
2950
  }
2905
2951
  }
2906
2952
  async function clearBinDir() {
2907
- if (!(0, import_node_fs4.existsSync)(binDir()))
2953
+ if (!(0, import_node_fs5.existsSync)(binDir()))
2908
2954
  return;
2909
2955
  for (const file of await (0, import_promises.readdir)(binDir())) {
2910
2956
  try {
2911
- (0, import_node_fs4.unlinkSync)((0, import_node_path3.join)(binDir(), file));
2957
+ (0, import_node_fs5.unlinkSync)((0, import_node_path3.join)(binDir(), file));
2912
2958
  } catch {
2913
2959
  }
2914
2960
  }
@@ -2957,10 +3003,86 @@ async function performBinaryDownload(binPath) {
2957
3003
  }
2958
3004
  throw new Error("No suitable llama-embedding binary found");
2959
3005
  }
3006
+ function withTimeout(promise, ms, label) {
3007
+ let timer;
3008
+ const timeout = new Promise((_3, reject) => {
3009
+ timer = setTimeout(
3010
+ () => reject(new Error(`${label} timed out after ${Math.round(ms / 1e3)}s`)),
3011
+ ms
3012
+ );
3013
+ });
3014
+ promise.catch(() => {
3015
+ });
3016
+ return Promise.race([promise, timeout]).finally(() => {
3017
+ if (timer)
3018
+ clearTimeout(timer);
3019
+ });
3020
+ }
3021
+ function psQuote(p) {
3022
+ return p.replace(/'/g, "''");
3023
+ }
3024
+ function buildUnzipInvocations(plat, archivePath, dir) {
3025
+ if (plat === "win32") {
3026
+ return [{
3027
+ cmd: "powershell",
3028
+ args: [
3029
+ "-NoProfile",
3030
+ "-NonInteractive",
3031
+ "-Command",
3032
+ `Expand-Archive -LiteralPath '${psQuote(archivePath)}' -DestinationPath '${psQuote(dir)}' -Force`
3033
+ ]
3034
+ }];
3035
+ }
3036
+ const unzip = { cmd: "unzip", args: ["-o", "-q", archivePath, "-d", dir] };
3037
+ return plat === "darwin" ? [unzip, { cmd: "ditto", args: ["-x", "-k", archivePath, dir] }] : [unzip];
3038
+ }
3039
+ async function osUnzip(archivePath, dir) {
3040
+ (0, import_node_fs5.mkdirSync)(dir, { recursive: true });
3041
+ const invocations = buildUnzipInvocations((0, import_node_os2.platform)(), archivePath, dir);
3042
+ let lastErr;
3043
+ for (const { cmd, args } of invocations) {
3044
+ try {
3045
+ await execFileAsync(cmd, args, { timeout: EXTRACT_TIMEOUT_MS, windowsHide: true });
3046
+ return;
3047
+ } catch (err) {
3048
+ lastErr = err;
3049
+ }
3050
+ }
3051
+ throw lastErr instanceof Error ? lastErr : new Error(`Native unzip failed for ${archivePath}`);
3052
+ }
3053
+ async function extractArchive(archivePath, baseDir, assetName, timeoutMs = EXTRACT_TIMEOUT_MS) {
3054
+ try {
3055
+ await withTimeout((0, import_extract_zip.default)(archivePath, { dir: baseDir }), timeoutMs, `Extracting ${assetName}`);
3056
+ return baseDir;
3057
+ } catch (err) {
3058
+ const msg = err instanceof Error ? err.message : String(err);
3059
+ log({
3060
+ source: "recall-catchup",
3061
+ level: "warn",
3062
+ summary: `Bundled unzip failed for ${assetName} (${msg}); falling back to system unzip`
3063
+ });
3064
+ const fallbackDir = (0, import_node_path3.join)(baseDir, "__os_unzip__");
3065
+ await osUnzip(archivePath, fallbackDir);
3066
+ return fallbackDir;
3067
+ }
3068
+ }
3069
+ async function installExtractedBinaries(extractedDir, destDir = binDir()) {
3070
+ const buildBinDir = (0, import_node_path3.join)(extractedDir, "build", "bin");
3071
+ const sourceDir = (0, import_node_fs5.existsSync)(buildBinDir) ? buildBinDir : extractedDir;
3072
+ (0, import_node_fs5.mkdirSync)(destDir, { recursive: true });
3073
+ let copiedCount = 0;
3074
+ for (const file of await (0, import_promises.readdir)(sourceDir)) {
3075
+ if (!isWantedFile(file))
3076
+ continue;
3077
+ await (0, import_promises.cp)((0, import_node_path3.join)(sourceDir, file), (0, import_node_path3.join)(destDir, file), { force: true });
3078
+ copiedCount++;
3079
+ }
3080
+ return copiedCount;
3081
+ }
2960
3082
  async function downloadAndExtract(assetName, binPath) {
2961
3083
  const url = `https://github.com/ggml-org/llama.cpp/releases/download/${LLAMA_RELEASE_TAG}/${assetName}`;
2962
3084
  const isGpu = assetName.includes("cuda") || assetName.includes("vulkan");
2963
- (0, import_node_fs4.mkdirSync)(binDir(), { recursive: true });
3085
+ (0, import_node_fs5.mkdirSync)(binDir(), { recursive: true });
2964
3086
  const archivePath = (0, import_node_path3.join)(binDir(), assetName);
2965
3087
  const tmpPath = archivePath + ".tmp";
2966
3088
  log({
@@ -2970,56 +3092,46 @@ async function downloadAndExtract(assetName, binPath) {
2970
3092
  data: { url, dest: binPath }
2971
3093
  });
2972
3094
  try {
2973
- if ((0, import_node_fs4.existsSync)(tmpPath))
2974
- (0, import_node_fs4.unlinkSync)(tmpPath);
3095
+ if ((0, import_node_fs5.existsSync)(tmpPath))
3096
+ (0, import_node_fs5.unlinkSync)(tmpPath);
2975
3097
  await downloadFile(url, tmpPath);
2976
- (0, import_node_fs4.renameSync)(tmpPath, archivePath);
3098
+ (0, import_node_fs5.renameSync)(tmpPath, archivePath);
2977
3099
  const tmpExtractDir = (0, import_node_path3.join)((0, import_node_os2.tmpdir)(), `llama-extract-${Date.now()}`);
2978
- (0, import_node_fs4.mkdirSync)(tmpExtractDir, { recursive: true });
3100
+ (0, import_node_fs5.mkdirSync)(tmpExtractDir, { recursive: true });
2979
3101
  try {
2980
- await (0, import_extract_zip.default)(archivePath, { dir: tmpExtractDir });
2981
- const buildBinDir = (0, import_node_path3.join)(tmpExtractDir, "build", "bin");
2982
- const sourceDir = (0, import_node_fs4.existsSync)(buildBinDir) ? buildBinDir : tmpExtractDir;
2983
- let copiedCount = 0;
2984
- for (const file of await (0, import_promises.readdir)(sourceDir)) {
2985
- if (!isWantedFile(file))
2986
- continue;
2987
- const src = (0, import_node_path3.join)(sourceDir, file);
2988
- const dest = (0, import_node_path3.join)(binDir(), file);
2989
- await (0, import_promises.cp)(src, dest, { force: true });
2990
- copiedCount++;
2991
- }
3102
+ const extractedDir = await extractArchive(archivePath, tmpExtractDir, assetName);
3103
+ const copiedCount = await installExtractedBinaries(extractedDir);
2992
3104
  if (copiedCount === 0) {
2993
3105
  throw new Error(`No binaries found in archive ${assetName}`);
2994
3106
  }
2995
3107
  } finally {
2996
3108
  try {
2997
- (0, import_node_fs4.rmSync)(tmpExtractDir, { recursive: true, force: true });
3109
+ (0, import_node_fs5.rmSync)(tmpExtractDir, { recursive: true, force: true });
2998
3110
  } catch {
2999
3111
  }
3000
3112
  }
3001
- if ((0, import_node_fs4.existsSync)(archivePath)) {
3113
+ if ((0, import_node_fs5.existsSync)(archivePath)) {
3002
3114
  try {
3003
- (0, import_node_fs4.unlinkSync)(archivePath);
3115
+ (0, import_node_fs5.unlinkSync)(archivePath);
3004
3116
  } catch {
3005
3117
  }
3006
3118
  }
3007
3119
  if ((0, import_node_os2.platform)() !== "win32") {
3008
- if ((0, import_node_fs4.existsSync)(binPath))
3009
- (0, import_node_fs4.chmodSync)(binPath, 493);
3120
+ if ((0, import_node_fs5.existsSync)(binPath))
3121
+ (0, import_node_fs5.chmodSync)(binPath, 493);
3010
3122
  const serverBin = getServerBinaryPath();
3011
- if ((0, import_node_fs4.existsSync)(serverBin)) {
3012
- (0, import_node_fs4.chmodSync)(serverBin, 493);
3123
+ if ((0, import_node_fs5.existsSync)(serverBin)) {
3124
+ (0, import_node_fs5.chmodSync)(serverBin, 493);
3013
3125
  }
3014
3126
  }
3015
- if (!(0, import_node_fs4.existsSync)(binPath)) {
3127
+ if (!(0, import_node_fs5.existsSync)(binPath)) {
3016
3128
  throw new Error(`${BIN_NAME} not found after extracting ${assetName}`);
3017
3129
  }
3018
3130
  } catch (err) {
3019
3131
  for (const p of [tmpPath, archivePath]) {
3020
- if ((0, import_node_fs4.existsSync)(p)) {
3132
+ if ((0, import_node_fs5.existsSync)(p)) {
3021
3133
  try {
3022
- (0, import_node_fs4.unlinkSync)(p);
3134
+ (0, import_node_fs5.unlinkSync)(p);
3023
3135
  } catch {
3024
3136
  }
3025
3137
  }
@@ -3032,8 +3144,8 @@ function getModelPath() {
3032
3144
  }
3033
3145
  async function ensureModel() {
3034
3146
  const modelPath = getModelPath();
3035
- if ((0, import_node_fs4.existsSync)(modelPath)) {
3036
- const stat = (0, import_node_fs4.statSync)(modelPath);
3147
+ if ((0, import_node_fs5.existsSync)(modelPath)) {
3148
+ const stat = (0, import_node_fs5.statSync)(modelPath);
3037
3149
  if (stat.size > 1e8)
3038
3150
  return modelPath;
3039
3151
  }
@@ -3049,9 +3161,9 @@ async function ensureModel() {
3049
3161
  async function performModelDownload(modelPath) {
3050
3162
  const tmpPath = modelPath + ".tmp";
3051
3163
  try {
3052
- (0, import_node_fs4.mkdirSync)(modelsDir(), { recursive: true });
3053
- if ((0, import_node_fs4.existsSync)(tmpPath)) {
3054
- (0, import_node_fs4.unlinkSync)(tmpPath);
3164
+ (0, import_node_fs5.mkdirSync)(modelsDir(), { recursive: true });
3165
+ if ((0, import_node_fs5.existsSync)(tmpPath)) {
3166
+ (0, import_node_fs5.unlinkSync)(tmpPath);
3055
3167
  }
3056
3168
  log({
3057
3169
  source: "recall-catchup",
@@ -3060,7 +3172,7 @@ async function performModelDownload(modelPath) {
3060
3172
  data: { url: MODEL_URL, dest: modelPath }
3061
3173
  });
3062
3174
  await downloadFile(MODEL_URL, tmpPath);
3063
- (0, import_node_fs4.renameSync)(tmpPath, modelPath);
3175
+ (0, import_node_fs5.renameSync)(tmpPath, modelPath);
3064
3176
  log({
3065
3177
  source: "recall-catchup",
3066
3178
  level: "info",
@@ -3068,9 +3180,9 @@ async function performModelDownload(modelPath) {
3068
3180
  });
3069
3181
  return modelPath;
3070
3182
  } catch (err) {
3071
- if ((0, import_node_fs4.existsSync)(tmpPath)) {
3183
+ if ((0, import_node_fs5.existsSync)(tmpPath)) {
3072
3184
  try {
3073
- (0, import_node_fs4.unlinkSync)(tmpPath);
3185
+ (0, import_node_fs5.unlinkSync)(tmpPath);
3074
3186
  } catch {
3075
3187
  }
3076
3188
  }
@@ -3088,7 +3200,7 @@ async function downloadFile(url, destPath) {
3088
3200
  }
3089
3201
  };
3090
3202
  import("node:https").then(({ default: https }) => {
3091
- const file = (0, import_node_fs4.createWriteStream)(destPath);
3203
+ const file = (0, import_node_fs5.createWriteStream)(destPath);
3092
3204
  const pipeResponse = (response) => {
3093
3205
  if (response.statusCode !== 200) {
3094
3206
  cleanup();
@@ -3158,7 +3270,7 @@ function getPidFilePath() {
3158
3270
  return (0, import_node_path3.join)(runDir(), `llama-embed-${process.pid}.pid`);
3159
3271
  }
3160
3272
  function writePidFile(pid, socketPath) {
3161
- (0, import_node_fs4.writeFileSync)(getPidFilePath(), JSON.stringify({
3273
+ (0, import_node_fs5.writeFileSync)(getPidFilePath(), JSON.stringify({
3162
3274
  pid,
3163
3275
  socketPath,
3164
3276
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3168,20 +3280,20 @@ function writePidFile(pid, socketPath) {
3168
3280
  function cleanupPidAndSocket() {
3169
3281
  for (const f3 of [getPidFilePath(), getSocketPath()]) {
3170
3282
  try {
3171
- (0, import_node_fs4.unlinkSync)(f3);
3283
+ (0, import_node_fs5.unlinkSync)(f3);
3172
3284
  } catch {
3173
3285
  }
3174
3286
  }
3175
3287
  }
3176
3288
  function cleanupStalePidFiles() {
3177
- if (!(0, import_node_fs4.existsSync)(runDir()))
3289
+ if (!(0, import_node_fs5.existsSync)(runDir()))
3178
3290
  return;
3179
3291
  try {
3180
- const files = (0, import_node_fs4.readdirSync)(runDir()).filter((f3) => f3.startsWith("llama-embed-") && f3.endsWith(".pid"));
3292
+ const files = (0, import_node_fs5.readdirSync)(runDir()).filter((f3) => f3.startsWith("llama-embed-") && f3.endsWith(".pid"));
3181
3293
  for (const f3 of files) {
3182
3294
  const pidFile = (0, import_node_path3.join)(runDir(), f3);
3183
3295
  try {
3184
- const data = JSON.parse((0, import_node_fs4.readFileSync)(pidFile, "utf-8"));
3296
+ const data = JSON.parse((0, import_node_fs5.readFileSync)(pidFile, "utf-8"));
3185
3297
  const ownerPid = data.ownerPid ?? data.pid;
3186
3298
  if (!isProcessAlive(ownerPid)) {
3187
3299
  if (data.pid && isProcessAlive(data.pid)) {
@@ -3191,19 +3303,19 @@ function cleanupStalePidFiles() {
3191
3303
  }
3192
3304
  }
3193
3305
  try {
3194
- (0, import_node_fs4.unlinkSync)(pidFile);
3306
+ (0, import_node_fs5.unlinkSync)(pidFile);
3195
3307
  } catch {
3196
3308
  }
3197
3309
  if (data.socketPath) {
3198
3310
  try {
3199
- (0, import_node_fs4.unlinkSync)(data.socketPath);
3311
+ (0, import_node_fs5.unlinkSync)(data.socketPath);
3200
3312
  } catch {
3201
3313
  }
3202
3314
  }
3203
3315
  }
3204
3316
  } catch {
3205
3317
  try {
3206
- (0, import_node_fs4.unlinkSync)(pidFile);
3318
+ (0, import_node_fs5.unlinkSync)(pidFile);
3207
3319
  } catch {
3208
3320
  }
3209
3321
  }
@@ -3242,14 +3354,14 @@ async function waitForHealth(socketPath, child) {
3242
3354
  async function startServer() {
3243
3355
  const modelPath = await ensureModel();
3244
3356
  const serverBin = getServerBinaryPath();
3245
- if (!(0, import_node_fs4.existsSync)(serverBin)) {
3357
+ if (!(0, import_node_fs5.existsSync)(serverBin)) {
3246
3358
  throw new Error("llama-server binary not available");
3247
3359
  }
3248
- (0, import_node_fs4.mkdirSync)(runDir(), { recursive: true });
3360
+ (0, import_node_fs5.mkdirSync)(runDir(), { recursive: true });
3249
3361
  const socket = getSocketPath();
3250
- if ((0, import_node_fs4.existsSync)(socket)) {
3362
+ if ((0, import_node_fs5.existsSync)(socket)) {
3251
3363
  try {
3252
- (0, import_node_fs4.unlinkSync)(socket);
3364
+ (0, import_node_fs5.unlinkSync)(socket);
3253
3365
  } catch {
3254
3366
  }
3255
3367
  }
@@ -3612,12 +3724,12 @@ async function embedBatchInner(texts) {
3612
3724
  async function disposeEmbedder() {
3613
3725
  await killServer();
3614
3726
  }
3615
- var import_node_child_process, import_node_fs4, import_promises, import_node_http, import_node_path3, import_node_os2, import_node_util, import_extract_zip, execFileAsync, _gpuRuntime, MODEL_FILENAME, MODEL_URL, EXPECTED_DIMS, BATCH_SEPARATOR, MAX_ARG_BYTES, LLAMA_RELEASE_TAG, BIN_NAME, SERVER_BIN_NAME, SERVER_SUPPORTED, SERVER_THRESHOLD, IDLE_TIMEOUT_MS, HEALTH_POLL_INTERVAL_MS, HEALTH_POLL_TIMEOUT_MS, HTTP_REQUEST_TIMEOUT_MS, SERVER_KILL_TIMEOUT_MS, SERVER_COOLDOWN_MS, embedQueue, embedRunning, binaryPath, downloadPromise, binaryDownloadPromise, serverProcess, activeSocketPath, idleTimer, serverStartPromise, serverCooldownUntil, serverRetryPromise, activeServerRequests, WANTED_FILES, WANTED_LIB_PATTERNS;
3727
+ var import_node_child_process, import_node_fs5, import_promises, import_node_http, import_node_path3, import_node_os2, import_node_util, import_extract_zip, execFileAsync, _gpuRuntime, MODEL_FILENAME, MODEL_URL, EXPECTED_DIMS, BATCH_SEPARATOR, MAX_ARG_BYTES, LLAMA_RELEASE_TAG, MACOS_MIN_VERSION, BIN_NAME, SERVER_BIN_NAME, SERVER_SUPPORTED, SERVER_THRESHOLD, IDLE_TIMEOUT_MS, HEALTH_POLL_INTERVAL_MS, HEALTH_POLL_TIMEOUT_MS, HTTP_REQUEST_TIMEOUT_MS, SERVER_KILL_TIMEOUT_MS, SERVER_COOLDOWN_MS, EXTRACT_TIMEOUT_MS, embedQueue, embedRunning, binaryPath, downloadPromise, binaryDownloadPromise, serverProcess, activeSocketPath, idleTimer, serverStartPromise, serverCooldownUntil, serverRetryPromise, activeServerRequests, WANTED_FILES, WANTED_LIB_PATTERNS;
3616
3728
  var init_embedder = __esm({
3617
3729
  "src/recall/embedder.ts"() {
3618
3730
  "use strict";
3619
3731
  import_node_child_process = require("node:child_process");
3620
- import_node_fs4 = require("node:fs");
3732
+ import_node_fs5 = require("node:fs");
3621
3733
  import_promises = require("node:fs/promises");
3622
3734
  import_node_http = require("node:http");
3623
3735
  import_node_path3 = require("node:path");
@@ -3634,6 +3746,7 @@ var init_embedder = __esm({
3634
3746
  BATCH_SEPARATOR = "<#sep#>";
3635
3747
  MAX_ARG_BYTES = (0, import_node_os2.platform)() === "win32" ? 0 : 1e5;
3636
3748
  LLAMA_RELEASE_TAG = "b5300";
3749
+ MACOS_MIN_VERSION = { arm64: "14.0", x64: "13.7" };
3637
3750
  BIN_NAME = (0, import_node_os2.platform)() === "win32" ? "llama-embedding.exe" : "llama-embedding";
3638
3751
  SERVER_BIN_NAME = (0, import_node_os2.platform)() === "win32" ? "llama-server.exe" : "llama-server";
3639
3752
  SERVER_SUPPORTED = (0, import_node_os2.platform)() !== "win32";
@@ -3644,6 +3757,7 @@ var init_embedder = __esm({
3644
3757
  HTTP_REQUEST_TIMEOUT_MS = 12e4;
3645
3758
  SERVER_KILL_TIMEOUT_MS = 5e3;
3646
3759
  SERVER_COOLDOWN_MS = 6e4;
3760
+ EXTRACT_TIMEOUT_MS = 12e4;
3647
3761
  embedQueue = [];
3648
3762
  embedRunning = false;
3649
3763
  binaryPath = null;
@@ -4127,9 +4241,9 @@ var require_backup = __commonJS({
4127
4241
  "use strict";
4128
4242
  var fs5 = require("fs");
4129
4243
  var path4 = require("path");
4130
- var { promisify: promisify3 } = require("util");
4244
+ var { promisify: promisify4 } = require("util");
4131
4245
  var { cppdb } = require_util();
4132
- var fsAccess = promisify3(fs5.access);
4246
+ var fsAccess = promisify4(fs5.access);
4133
4247
  module2.exports = async function backup(filename, options) {
4134
4248
  if (options == null)
4135
4249
  options = {};
@@ -4591,7 +4705,7 @@ function getDb(dbPath2) {
4591
4705
  if (db) {
4592
4706
  closeDb();
4593
4707
  }
4594
- (0, import_node_fs5.mkdirSync)((0, import_node_path4.dirname)(dbPath2), { recursive: true });
4708
+ (0, import_node_fs6.mkdirSync)((0, import_node_path4.dirname)(dbPath2), { recursive: true });
4595
4709
  cleanupWasmArtifacts(dbPath2);
4596
4710
  const raw2 = openDatabase(dbPath2);
4597
4711
  try {
@@ -4628,13 +4742,13 @@ function openDatabase(dbPath2) {
4628
4742
  }
4629
4743
  function resolveNativeBinding() {
4630
4744
  const sibling = (0, import_node_path4.join)(__dirname, "better_sqlite3.node");
4631
- if ((0, import_node_fs5.existsSync)(sibling))
4745
+ if ((0, import_node_fs6.existsSync)(sibling))
4632
4746
  return sibling;
4633
4747
  const resolved = resolveInstalledBinding();
4634
4748
  if (resolved)
4635
4749
  return resolved;
4636
4750
  const staged = (0, import_node_path4.join)(binDir(), "better_sqlite3.node");
4637
- if ((0, import_node_fs5.existsSync)(staged))
4751
+ if ((0, import_node_fs6.existsSync)(staged))
4638
4752
  return staged;
4639
4753
  return null;
4640
4754
  }
@@ -4652,7 +4766,7 @@ function findNativeBinding(baseDir) {
4652
4766
  (0, import_node_path4.join)(baseDir, "build", "Debug", "better_sqlite3.node")
4653
4767
  ]) {
4654
4768
  try {
4655
- if ((0, import_node_fs5.statSync)(c).isFile())
4769
+ if ((0, import_node_fs6.statSync)(c).isFile())
4656
4770
  return c;
4657
4771
  } catch {
4658
4772
  }
@@ -4662,7 +4776,7 @@ function findNativeBinding(baseDir) {
4662
4776
  const dir = stack.pop();
4663
4777
  let entries;
4664
4778
  try {
4665
- entries = (0, import_node_fs5.readdirSync)(dir, { withFileTypes: true });
4779
+ entries = (0, import_node_fs6.readdirSync)(dir, { withFileTypes: true });
4666
4780
  } catch {
4667
4781
  continue;
4668
4782
  }
@@ -4710,12 +4824,12 @@ function configurePragmas(raw2, dbPath2) {
4710
4824
  }
4711
4825
  function cleanupWasmArtifacts(dbPath2) {
4712
4826
  try {
4713
- (0, import_node_fs5.rmSync)(`${dbPath2}.lock`, { recursive: true, force: true });
4827
+ (0, import_node_fs6.rmSync)(`${dbPath2}.lock`, { recursive: true, force: true });
4714
4828
  } catch {
4715
4829
  }
4716
4830
  try {
4717
- if ((0, import_node_fs5.existsSync)(`${dbPath2}.owner`))
4718
- (0, import_node_fs5.unlinkSync)(`${dbPath2}.owner`);
4831
+ if ((0, import_node_fs6.existsSync)(`${dbPath2}.owner`))
4832
+ (0, import_node_fs6.unlinkSync)(`${dbPath2}.owner`);
4719
4833
  } catch {
4720
4834
  }
4721
4835
  }
@@ -4803,11 +4917,11 @@ function ensureSchema(db4) {
4803
4917
  CREATE INDEX IF NOT EXISTS idx_watermark_vendor ON ingest_watermark(vendor);
4804
4918
  `);
4805
4919
  }
4806
- var import_node_fs5, import_node_path4, import_node_module, import_better_sqlite3, BindingLoadError, db, currentDbPath;
4920
+ var import_node_fs6, import_node_path4, import_node_module, import_better_sqlite3, BindingLoadError, db, currentDbPath;
4807
4921
  var init_db = __esm({
4808
4922
  "src/db.ts"() {
4809
4923
  "use strict";
4810
- import_node_fs5 = require("node:fs");
4924
+ import_node_fs6 = require("node:fs");
4811
4925
  import_node_path4 = require("node:path");
4812
4926
  import_node_module = require("node:module");
4813
4927
  init_log();
@@ -4817,7 +4931,10 @@ var init_db = __esm({
4817
4931
  constructor(dbPath2, cause) {
4818
4932
  super(
4819
4933
  `recall: failed to load the better-sqlite3 native binding while opening ${dbPath2}: ${cause.message}
4820
- The binding is ABI-locked to the Node it was built for \u2014 run \`recall doctor\` (usually fixed by re-running \`recall install\`).`
4934
+ The native SQLite binding is ABI-locked to the Node it was built for. This is usually one of:
4935
+ \u2022 recall is running under a different Node than the one npm installed it with (e.g. Homebrew node vs nvm) \u2014 reinstall with \`npm install -g crispy-recall\` using the node on your PATH, then \`recall install\`.
4936
+ \u2022 no prebuilt binary matched your Node version and it could not compile \u2014 use Node 22 LTS or 24+ (Node 23 has no prebuilt SQLite binding), and on macOS make sure Xcode Command Line Tools are installed (\`xcode-select --install\`).
4937
+ Run \`recall doctor\` for details.`
4821
4938
  );
4822
4939
  this.dbPath = dbPath2;
4823
4940
  this.cause = cause;
@@ -9465,7 +9582,7 @@ var init_esm3 = __esm({
9465
9582
  });
9466
9583
 
9467
9584
  // node_modules/path-scurry/dist/esm/index.js
9468
- var import_node_path5, import_node_url, import_fs, actualFS, import_promises2, realpathSync, defaultFS, fsFromOption, uncDriveRegexp, uncToDrive, eitherSep, UNKNOWN, IFIFO, IFCHR, IFDIR, IFBLK, IFREG, IFLNK, IFSOCK, IFMT, IFMT_UNKNOWN, READDIR_CALLED, LSTAT_CALLED, ENOTDIR, ENOENT, ENOREADLINK, ENOREALPATH, ENOCHILD, TYPEMASK, entToType, normalizeCache, normalize, normalizeNocaseCache, normalizeNocase, ResolveCache, ChildrenCache, setAsCwd, PathBase, PathWin32, PathPosix, PathScurryBase, PathScurryWin32, PathScurryPosix, PathScurryDarwin, Path, PathScurry;
9585
+ var import_node_path5, import_node_url, import_fs, actualFS, import_promises2, realpathSync2, defaultFS, fsFromOption, uncDriveRegexp, uncToDrive, eitherSep, UNKNOWN, IFIFO, IFCHR, IFDIR, IFBLK, IFREG, IFLNK, IFSOCK, IFMT, IFMT_UNKNOWN, READDIR_CALLED, LSTAT_CALLED, ENOTDIR, ENOENT, ENOREADLINK, ENOREALPATH, ENOCHILD, TYPEMASK, entToType, normalizeCache, normalize, normalizeNocaseCache, normalizeNocase, ResolveCache, ChildrenCache, setAsCwd, PathBase, PathWin32, PathPosix, PathScurryBase, PathScurryWin32, PathScurryPosix, PathScurryDarwin, Path, PathScurry;
9469
9586
  var init_esm4 = __esm({
9470
9587
  "node_modules/path-scurry/dist/esm/index.js"() {
9471
9588
  init_esm2();
@@ -9475,13 +9592,13 @@ var init_esm4 = __esm({
9475
9592
  actualFS = __toESM(require("node:fs"), 1);
9476
9593
  import_promises2 = require("node:fs/promises");
9477
9594
  init_esm3();
9478
- realpathSync = import_fs.realpathSync.native;
9595
+ realpathSync2 = import_fs.realpathSync.native;
9479
9596
  defaultFS = {
9480
9597
  lstatSync: import_fs.lstatSync,
9481
9598
  readdir: import_fs.readdir,
9482
9599
  readdirSync: import_fs.readdirSync,
9483
9600
  readlinkSync: import_fs.readlinkSync,
9484
- realpathSync,
9601
+ realpathSync: realpathSync2,
9485
9602
  promises: {
9486
9603
  lstat: import_promises2.lstat,
9487
9604
  readdir: import_promises2.readdir,
@@ -13319,7 +13436,7 @@ async function mtimeScan(opts) {
13319
13436
  result.scanned++;
13320
13437
  let stat;
13321
13438
  try {
13322
- stat = (0, import_node_fs6.statSync)(file);
13439
+ stat = (0, import_node_fs7.statSync)(file);
13323
13440
  } catch {
13324
13441
  continue;
13325
13442
  }
@@ -13363,12 +13480,12 @@ function sessionIdFromPath(file, vendor) {
13363
13480
  const m2 = base.match(/^rollout-.+-([0-9a-f-]{36})$/i);
13364
13481
  return m2 ? m2[1] : base;
13365
13482
  }
13366
- var import_node_fs6, import_node_os3, import_node_path6;
13483
+ var import_node_fs7, import_node_os3, import_node_path6;
13367
13484
  var init_mtime_scan = __esm({
13368
13485
  "src/recall/mtime-scan.ts"() {
13369
13486
  "use strict";
13370
13487
  init_esm5();
13371
- import_node_fs6 = require("node:fs");
13488
+ import_node_fs7 = require("node:fs");
13372
13489
  import_node_os3 = require("node:os");
13373
13490
  import_node_path6 = require("node:path");
13374
13491
  init_db();
@@ -14155,21 +14272,21 @@ function isAlive(pid) {
14155
14272
  function tryAcquireEmbedLock() {
14156
14273
  for (let attempt = 0; attempt < 2; attempt++) {
14157
14274
  try {
14158
- (0, import_node_fs7.writeFileSync)(LOCK_PATH, String(process.pid), { flag: "wx" });
14275
+ (0, import_node_fs8.writeFileSync)(LOCK_PATH, String(process.pid), { flag: "wx" });
14159
14276
  return true;
14160
14277
  } catch {
14161
14278
  let heldPid;
14162
14279
  let ageMs;
14163
14280
  try {
14164
- heldPid = parseInt((0, import_node_fs7.readFileSync)(LOCK_PATH, "utf8"), 10);
14165
- ageMs = Date.now() - (0, import_node_fs7.statSync)(LOCK_PATH).mtimeMs;
14281
+ heldPid = parseInt((0, import_node_fs8.readFileSync)(LOCK_PATH, "utf8"), 10);
14282
+ ageMs = Date.now() - (0, import_node_fs8.statSync)(LOCK_PATH).mtimeMs;
14166
14283
  } catch {
14167
14284
  continue;
14168
14285
  }
14169
14286
  if (isAlive(heldPid) && ageMs < STALE_MS)
14170
14287
  return false;
14171
14288
  try {
14172
- (0, import_node_fs7.unlinkSync)(LOCK_PATH);
14289
+ (0, import_node_fs8.unlinkSync)(LOCK_PATH);
14173
14290
  } catch {
14174
14291
  }
14175
14292
  }
@@ -14178,17 +14295,17 @@ function tryAcquireEmbedLock() {
14178
14295
  }
14179
14296
  function releaseEmbedLock() {
14180
14297
  try {
14181
- const held = parseInt((0, import_node_fs7.readFileSync)(LOCK_PATH, "utf8"), 10);
14298
+ const held = parseInt((0, import_node_fs8.readFileSync)(LOCK_PATH, "utf8"), 10);
14182
14299
  if (held === process.pid)
14183
- (0, import_node_fs7.unlinkSync)(LOCK_PATH);
14300
+ (0, import_node_fs8.unlinkSync)(LOCK_PATH);
14184
14301
  } catch {
14185
14302
  }
14186
14303
  }
14187
- var import_node_fs7, import_node_path8, LOCK_PATH, STALE_MS;
14304
+ var import_node_fs8, import_node_path8, LOCK_PATH, STALE_MS;
14188
14305
  var init_embed_lock = __esm({
14189
14306
  "src/recall/embed-lock.ts"() {
14190
14307
  "use strict";
14191
- import_node_fs7 = require("node:fs");
14308
+ import_node_fs8 = require("node:fs");
14192
14309
  import_node_path8 = require("node:path");
14193
14310
  init_paths();
14194
14311
  LOCK_PATH = (0, import_node_path8.join)(runDir(), "embed.lock");
@@ -14244,7 +14361,7 @@ function clearCancelFlag() {
14244
14361
  function startLockHeartbeat(lockPath) {
14245
14362
  const t = setInterval(() => {
14246
14363
  try {
14247
- (0, import_node_fs8.utimesSync)(lockPath, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
14364
+ (0, import_node_fs9.utimesSync)(lockPath, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
14248
14365
  } catch {
14249
14366
  }
14250
14367
  }, 5 * 60 * 1e3);
@@ -14264,7 +14381,7 @@ async function runFts5Catchup(opts) {
14264
14381
  continue;
14265
14382
  if (alreadyIndexed.has(s.sessionId))
14266
14383
  continue;
14267
- if (!(0, import_node_fs8.existsSync)(s.path))
14384
+ if (!(0, import_node_fs9.existsSync)(s.path))
14268
14385
  continue;
14269
14386
  try {
14270
14387
  const result = await ingestSessionMessages(s.sessionId, s.path, s.vendor);
@@ -14457,11 +14574,11 @@ async function startRecallCatchup(opts = {}) {
14457
14574
  running = false;
14458
14575
  }
14459
14576
  }
14460
- var import_node_fs8, import_node_os5, SILENT_EMBED_THRESHOLD, FREE_MEM_FLOOR_MB, SECONDS_PER_MESSAGE, CATCHUP_BATCH_SIZE, MAX_CONSECUTIVE_FAILURES, status, cancelRequested, running;
14577
+ var import_node_fs9, import_node_os5, SILENT_EMBED_THRESHOLD, FREE_MEM_FLOOR_MB, SECONDS_PER_MESSAGE, CATCHUP_BATCH_SIZE, MAX_CONSECUTIVE_FAILURES, status, cancelRequested, running;
14461
14578
  var init_catchup = __esm({
14462
14579
  "src/recall/catchup.ts"() {
14463
14580
  "use strict";
14464
- import_node_fs8 = require("node:fs");
14581
+ import_node_fs9 = require("node:fs");
14465
14582
  import_node_os5 = require("node:os");
14466
14583
  init_dist2();
14467
14584
  init_session_manager_shim();
@@ -14495,7 +14612,7 @@ function cudaAssetUrl() {
14495
14612
  let version = "";
14496
14613
  try {
14497
14614
  const pkgPath = (0, import_node_path9.join)(__dirname, "..", "package.json");
14498
- const pkg = JSON.parse((0, import_node_fs9.readFileSync)(pkgPath, "utf8"));
14615
+ const pkg = JSON.parse((0, import_node_fs10.readFileSync)(pkgPath, "utf8"));
14499
14616
  version = typeof pkg.version === "string" ? pkg.version : "";
14500
14617
  const repoUrl = typeof pkg.repository === "string" ? pkg.repository : pkg.repository?.url;
14501
14618
  const m2 = repoUrl?.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
@@ -14541,7 +14658,7 @@ async function detectGpu(opts = {}) {
14541
14658
  if (p === "win32" && a3 === "x64") {
14542
14659
  return { ...base, cudaAvailable: "prebuilt", plannedMode: "gpu" };
14543
14660
  }
14544
- if (p === "linux" && a3 === "x64" && (0, import_node_fs9.existsSync)(cudaBackendLib())) {
14661
+ if (p === "linux" && a3 === "x64" && (0, import_node_fs10.existsSync)(cudaBackendLib())) {
14545
14662
  return { ...base, cudaAvailable: "reuse-existing", plannedMode: "gpu" };
14546
14663
  }
14547
14664
  if (p === "linux" && a3 === "x64") {
@@ -14676,16 +14793,16 @@ async function runGpuPhase(opts = {}) {
14676
14793
  async function defaultStage(args) {
14677
14794
  const target = binDir();
14678
14795
  const libPath = cudaBackendLib();
14679
- if ((0, import_node_fs9.existsSync)(libPath))
14796
+ if ((0, import_node_fs10.existsSync)(libPath))
14680
14797
  return target;
14681
14798
  if (args.offline)
14682
14799
  return null;
14683
14800
  try {
14684
- (0, import_node_fs9.mkdirSync)(target, { recursive: true });
14801
+ (0, import_node_fs10.mkdirSync)(target, { recursive: true });
14685
14802
  const zipPath = (0, import_node_path9.join)((0, import_node_os6.tmpdir)(), CUDA_ASSET_NAME);
14686
14803
  await downloadFile2(CUDA_ASSET_URL, zipPath);
14687
14804
  await (0, import_extract_zip2.default)(zipPath, { dir: target });
14688
- return (0, import_node_fs9.existsSync)(libPath) ? target : null;
14805
+ return (0, import_node_fs10.existsSync)(libPath) ? target : null;
14689
14806
  } catch (err) {
14690
14807
  log({ source: "installer/gpu", level: "info", summary: `prebuilt CUDA staging failed: ${err.message}` });
14691
14808
  return null;
@@ -14708,7 +14825,7 @@ function downloadFile2(url, destPath) {
14708
14825
  done(new Error(`HTTP ${status2}`));
14709
14826
  return;
14710
14827
  }
14711
- const file = (0, import_node_fs9.createWriteStream)(destPath);
14828
+ const file = (0, import_node_fs10.createWriteStream)(destPath);
14712
14829
  res.pipe(file);
14713
14830
  file.on("finish", () => file.close(() => done()));
14714
14831
  file.on("error", done);
@@ -14716,12 +14833,12 @@ function downloadFile2(url, destPath) {
14716
14833
  (0, import_node_https.get)(url, { headers: { "User-Agent": "recall" } }, pipeTo).on("error", done);
14717
14834
  });
14718
14835
  }
14719
- var import_node_child_process3, import_node_fs9, import_node_path9, import_node_util2, import_node_os6, import_node_https, import_extract_zip2, execFileAsync2, EXPECTED_DIMS2, OFFLOAD_TIMEOUT_MS, DEFAULT_NGL, CUDA_ASSET_NAME, CUDA_ASSET_URL, STAGE_DOWNLOAD_TIMEOUT_MS;
14836
+ var import_node_child_process3, import_node_fs10, import_node_path9, import_node_util2, import_node_os6, import_node_https, import_extract_zip2, execFileAsync2, EXPECTED_DIMS2, OFFLOAD_TIMEOUT_MS, DEFAULT_NGL, CUDA_ASSET_NAME, CUDA_ASSET_URL, STAGE_DOWNLOAD_TIMEOUT_MS;
14720
14837
  var init_gpu = __esm({
14721
14838
  "src/installer/gpu.ts"() {
14722
14839
  "use strict";
14723
14840
  import_node_child_process3 = require("node:child_process");
14724
- import_node_fs9 = require("node:fs");
14841
+ import_node_fs10 = require("node:fs");
14725
14842
  import_node_path9 = require("node:path");
14726
14843
  import_node_util2 = require("node:util");
14727
14844
  import_node_os6 = require("node:os");
@@ -14776,7 +14893,7 @@ function installLockPath() {
14776
14893
  }
14777
14894
  function readLockBody() {
14778
14895
  try {
14779
- return JSON.parse((0, import_node_fs10.readFileSync)(installLockPath(), "utf-8"));
14896
+ return JSON.parse((0, import_node_fs11.readFileSync)(installLockPath(), "utf-8"));
14780
14897
  } catch {
14781
14898
  return null;
14782
14899
  }
@@ -14790,17 +14907,17 @@ function pidAlive(pid) {
14790
14907
  }
14791
14908
  }
14792
14909
  function acquireInstallLock() {
14793
- (0, import_node_fs10.mkdirSync)(runDir(), { recursive: true });
14910
+ (0, import_node_fs11.mkdirSync)(runDir(), { recursive: true });
14794
14911
  const body = JSON.stringify({ pid: process.pid, ts: Date.now() });
14795
14912
  try {
14796
- (0, import_node_fs10.writeFileSync)(installLockPath(), body, { flag: "wx" });
14913
+ (0, import_node_fs11.writeFileSync)(installLockPath(), body, { flag: "wx" });
14797
14914
  return { ok: true, tookOver: false };
14798
14915
  } catch {
14799
14916
  const existing = readLockBody();
14800
14917
  if (existing && pidAlive(existing.pid) && Date.now() - existing.ts < STALE_LOCK_MS) {
14801
14918
  return { ok: false, tookOver: false, existingPid: existing.pid };
14802
14919
  }
14803
- (0, import_node_fs10.writeFileSync)(installLockPath(), body);
14920
+ (0, import_node_fs11.writeFileSync)(installLockPath(), body);
14804
14921
  return { ok: true, tookOver: true, ...existing ? { existingPid: existing.pid } : {} };
14805
14922
  }
14806
14923
  }
@@ -14808,7 +14925,7 @@ function releaseInstallLock() {
14808
14925
  try {
14809
14926
  const existing = readLockBody();
14810
14927
  if (existing && existing.pid === process.pid) {
14811
- (0, import_node_fs10.unlinkSync)(installLockPath());
14928
+ (0, import_node_fs11.unlinkSync)(installLockPath());
14812
14929
  }
14813
14930
  } catch {
14814
14931
  }
@@ -14817,14 +14934,14 @@ function detectWsl() {
14817
14934
  if (process.env["WSL_DISTRO_NAME"])
14818
14935
  return true;
14819
14936
  try {
14820
- return /microsoft/i.test((0, import_node_fs10.readFileSync)("/proc/version", "utf-8"));
14937
+ return /microsoft/i.test((0, import_node_fs11.readFileSync)("/proc/version", "utf-8"));
14821
14938
  } catch {
14822
14939
  return false;
14823
14940
  }
14824
14941
  }
14825
14942
  function canWrite(path4) {
14826
14943
  try {
14827
- (0, import_node_fs10.accessSync)(path4, import_node_fs10.constants.W_OK);
14944
+ (0, import_node_fs11.accessSync)(path4, import_node_fs11.constants.W_OK);
14828
14945
  return true;
14829
14946
  } catch {
14830
14947
  return false;
@@ -14844,7 +14961,7 @@ function checkPlatform(report, opts) {
14844
14961
  remediation: "recall supports Linux x64/arm64, macOS x64/arm64, Windows x64."
14845
14962
  });
14846
14963
  }
14847
- if (isWsl && (0, import_node_fs10.existsSync)((0, import_node_path10.join)((0, import_node_os7.homedir)(), ".claude")) && process.env["CLAUDE_CONFIG_DIR"] === void 0) {
14964
+ if (isWsl && (0, import_node_fs11.existsSync)((0, import_node_path10.join)((0, import_node_os7.homedir)(), ".claude")) && process.env["CLAUDE_CONFIG_DIR"] === void 0) {
14848
14965
  report.warnings.push({
14849
14966
  check: "platform.wsl",
14850
14967
  severity: "WARN",
@@ -14852,6 +14969,51 @@ function checkPlatform(report, opts) {
14852
14969
  });
14853
14970
  }
14854
14971
  }
14972
+ async function readMacosProductVersion() {
14973
+ try {
14974
+ const { stdout } = await execFileAsync3("sw_vers", ["-productVersion"], { timeout: 5e3, windowsHide: true });
14975
+ const v2 = stdout.trim();
14976
+ return v2.length > 0 ? v2 : null;
14977
+ } catch {
14978
+ return null;
14979
+ }
14980
+ }
14981
+ function compareMajorMinor(a3, b3) {
14982
+ const pa = a3.split(".").map((n) => parseInt(n, 10) || 0);
14983
+ const pb = b3.split(".").map((n) => parseInt(n, 10) || 0);
14984
+ for (let i = 0; i < 2; i++) {
14985
+ const da = pa[i] ?? 0;
14986
+ const db4 = pb[i] ?? 0;
14987
+ if (da !== db4)
14988
+ return da - db4;
14989
+ }
14990
+ return 0;
14991
+ }
14992
+ async function checkMacosFloor(report, opts) {
14993
+ const os3 = opts.platform ?? (0, import_node_os7.platform)();
14994
+ if (os3 !== "darwin")
14995
+ return;
14996
+ const a3 = opts.arch ?? (0, import_node_os7.arch)();
14997
+ const { MACOS_MIN_VERSION: MACOS_MIN_VERSION2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
14998
+ const floor = a3 === "arm64" ? MACOS_MIN_VERSION2.arm64 : MACOS_MIN_VERSION2.x64;
14999
+ const version = opts.macosProductVersion ? await opts.macosProductVersion() : await readMacosProductVersion();
15000
+ if (!version) {
15001
+ report.warnings.push({
15002
+ check: "platform.macos-version",
15003
+ severity: "WARN",
15004
+ message: "Could not determine the macOS version (sw_vers unavailable) \u2014 skipping the minimum-OS check for the bundled llama.cpp binaries."
15005
+ });
15006
+ return;
15007
+ }
15008
+ if (compareMajorMinor(version, floor) < 0) {
15009
+ report.failures.push({
15010
+ check: "platform.macos-version",
15011
+ severity: "FAIL",
15012
+ message: `macOS ${version} is below the minimum for recall's bundled llama.cpp binaries (needs ${MACOS_MIN_VERSION2.arm64}+ on Apple Silicon, ${MACOS_MIN_VERSION2.x64}+ on Intel). Semantic embedding cannot run.`,
15013
+ remediation: `Upgrade macOS to ${floor} or newer to install recall.`
15014
+ });
15015
+ }
15016
+ }
14855
15017
  function checkClaude(report) {
14856
15018
  const dir = claudeDir();
14857
15019
  const settings = claudeSettingsPath();
@@ -14861,7 +15023,7 @@ function checkClaude(report) {
14861
15023
  let ok = true;
14862
15024
  let status2 = "ready";
14863
15025
  let existingHooks = 0;
14864
- if (!(0, import_node_fs10.existsSync)(dir)) {
15026
+ if (!(0, import_node_fs11.existsSync)(dir)) {
14865
15027
  ok = false;
14866
15028
  status2 = "missing";
14867
15029
  report.failures.push({
@@ -14870,12 +15032,12 @@ function checkClaude(report) {
14870
15032
  message: `~/.claude not found at ${dir}`,
14871
15033
  remediation: "Install Claude Code first, or set CLAUDE_CONFIG_DIR."
14872
15034
  });
14873
- report.claude = { ok, status: status2, paths, existingHooks, existingInstall: (0, import_node_fs10.existsSync)(recallRoot()) };
15035
+ report.claude = { ok, status: status2, paths, existingHooks, existingInstall: (0, import_node_fs11.existsSync)(recallRoot()) };
14874
15036
  return;
14875
15037
  }
14876
- if ((0, import_node_fs10.existsSync)(settings)) {
15038
+ if ((0, import_node_fs11.existsSync)(settings)) {
14877
15039
  try {
14878
- const parsed = JSON.parse((0, import_node_fs10.readFileSync)(settings, "utf-8"));
15040
+ const parsed = JSON.parse((0, import_node_fs11.readFileSync)(settings, "utf-8"));
14879
15041
  const stop = parsed?.hooks?.Stop;
14880
15042
  const subStop = parsed?.hooks?.SubagentStop;
14881
15043
  existingHooks = (Array.isArray(stop) ? stop.length : 0) + (Array.isArray(subStop) ? subStop.length : 0);
@@ -14889,17 +15051,17 @@ function checkClaude(report) {
14889
15051
  status2 = "fresh";
14890
15052
  report.warnings.push({ check: "claude.settings", severity: "WARN", message: "No settings.json \u2014 will create one." });
14891
15053
  }
14892
- if (!(0, import_node_fs10.existsSync)(skills)) {
15054
+ if (!(0, import_node_fs11.existsSync)(skills)) {
14893
15055
  report.warnings.push({ check: "claude.skills", severity: "WARN", message: "skills/ absent \u2014 will create it." });
14894
15056
  }
14895
- if ((0, import_node_fs10.existsSync)(skillFile)) {
15057
+ if ((0, import_node_fs11.existsSync)(skillFile)) {
14896
15058
  report.warnings.push({ check: "claude.recall-skill", severity: "WARN", message: "recall skill present \u2014 will back up + overwrite." });
14897
15059
  }
14898
- report.claude = { ok, status: status2, paths, existingHooks, existingInstall: (0, import_node_fs10.existsSync)(recallRoot()) };
15060
+ report.claude = { ok, status: status2, paths, existingHooks, existingInstall: (0, import_node_fs11.existsSync)(recallRoot()) };
14899
15061
  }
14900
15062
  function checkCodex(report) {
14901
15063
  const dir = codexDir();
14902
- if (!(0, import_node_fs10.existsSync)(dir)) {
15064
+ if (!(0, import_node_fs11.existsSync)(dir)) {
14903
15065
  report.codex = null;
14904
15066
  return;
14905
15067
  }
@@ -14910,8 +15072,8 @@ function checkCodex(report) {
14910
15072
  }
14911
15073
  function checkDisk() {
14912
15074
  try {
14913
- const base = (0, import_node_fs10.existsSync)(recallRoot()) ? recallRoot() : (0, import_node_os7.homedir)();
14914
- const st = (0, import_node_fs10.statfsSync)(base);
15075
+ const base = (0, import_node_fs11.existsSync)(recallRoot()) ? recallRoot() : (0, import_node_os7.homedir)();
15076
+ const st = (0, import_node_fs11.statfsSync)(base);
14915
15077
  const freeBytes = Number(st.bavail) * Number(st.bsize);
14916
15078
  const freeMb = Math.floor(freeBytes / (1024 * 1024));
14917
15079
  const disk = `${freeMb} MB free`;
@@ -14955,8 +15117,8 @@ function httpReachable(url, timeoutMs) {
14955
15117
  async function checkNetwork(offline) {
14956
15118
  if (offline) {
14957
15119
  const { getBinaryPath: getBinaryPath2, getModelPath: getModelPath2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
14958
- const haveBin = (0, import_node_fs10.existsSync)(getBinaryPath2());
14959
- const haveModel = (0, import_node_fs10.existsSync)(getModelPath2());
15120
+ const haveBin = (0, import_node_fs11.existsSync)(getBinaryPath2());
15121
+ const haveModel = (0, import_node_fs11.existsSync)(getModelPath2());
14960
15122
  if (haveBin && haveModel)
14961
15123
  return { network: "offline (pre-staged binary + model present)" };
14962
15124
  if (haveBin || haveModel)
@@ -14981,6 +15143,7 @@ async function runPreflight(opts = {}) {
14981
15143
  failures: []
14982
15144
  };
14983
15145
  checkPlatform(report, opts);
15146
+ await checkMacosFloor(report, opts);
14984
15147
  checkClaude(report);
14985
15148
  checkCodex(report);
14986
15149
  const node = checkNode();
@@ -15012,16 +15175,19 @@ async function runPreflight(opts = {}) {
15012
15175
  function preflightPassed(report) {
15013
15176
  return report.failures.length === 0;
15014
15177
  }
15015
- var import_node_fs10, import_node_os7, import_node_path10, import_node_https2, STALE_LOCK_MS;
15178
+ var import_node_fs11, import_node_os7, import_node_path10, import_node_https2, import_node_child_process4, import_node_util3, execFileAsync3, STALE_LOCK_MS;
15016
15179
  var init_preflight = __esm({
15017
15180
  "src/installer/preflight.ts"() {
15018
15181
  "use strict";
15019
- import_node_fs10 = require("node:fs");
15182
+ import_node_fs11 = require("node:fs");
15020
15183
  import_node_os7 = require("node:os");
15021
15184
  import_node_path10 = require("node:path");
15022
15185
  import_node_https2 = require("node:https");
15186
+ import_node_child_process4 = require("node:child_process");
15187
+ import_node_util3 = require("node:util");
15023
15188
  init_paths();
15024
15189
  init_gpu();
15190
+ execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process4.execFile);
15025
15191
  STALE_LOCK_MS = 60 * 60 * 1e3;
15026
15192
  }
15027
15193
  });
@@ -15130,7 +15296,7 @@ var init_manifest = __esm({
15130
15296
  function openReadonly(dbFile) {
15131
15297
  const staged = (0, import_node_path11.join)(binDir(), "better_sqlite3.node");
15132
15298
  try {
15133
- return (0, import_node_fs11.existsSync)(staged) ? new import_better_sqlite32.default(dbFile, { readonly: true, fileMustExist: true, nativeBinding: staged }) : new import_better_sqlite32.default(dbFile, { readonly: true, fileMustExist: true });
15299
+ return (0, import_node_fs12.existsSync)(staged) ? new import_better_sqlite32.default(dbFile, { readonly: true, fileMustExist: true, nativeBinding: staged }) : new import_better_sqlite32.default(dbFile, { readonly: true, fileMustExist: true });
15134
15300
  } catch {
15135
15301
  try {
15136
15302
  return new import_better_sqlite32.default(dbFile, { readonly: true, fileMustExist: true });
@@ -15141,10 +15307,10 @@ function openReadonly(dbFile) {
15141
15307
  }
15142
15308
  function classifyUpgrade() {
15143
15309
  const dbFile = dbPath();
15144
- if (!(0, import_node_fs11.existsSync)(dbFile)) {
15310
+ if (!(0, import_node_fs12.existsSync)(dbFile)) {
15145
15311
  return { state: "fresh", journalMode: null, markerPresent: false, coverage: 1 };
15146
15312
  }
15147
- const markerPresent = (0, import_node_fs11.existsSync)((0, import_node_path11.join)(binDir(), ".binding-info.json"));
15313
+ const markerPresent = (0, import_node_fs12.existsSync)((0, import_node_path11.join)(binDir(), ".binding-info.json"));
15148
15314
  let journalMode = null;
15149
15315
  let coverage = 1;
15150
15316
  const raw2 = openReadonly(dbFile);
@@ -15176,14 +15342,14 @@ function classifyUpgrade() {
15176
15342
  }
15177
15343
  function snapshotDb() {
15178
15344
  const dbFile = dbPath();
15179
- if (!(0, import_node_fs11.existsSync)(dbFile))
15345
+ if (!(0, import_node_fs12.existsSync)(dbFile))
15180
15346
  return null;
15181
15347
  const dest = `${dbFile}.pre-upgrade-${backupStamp()}`;
15182
15348
  try {
15183
- (0, import_node_fs11.copyFileSync)(dbFile, dest);
15349
+ (0, import_node_fs12.copyFileSync)(dbFile, dest);
15184
15350
  const journal = `${dbFile}-journal`;
15185
- if ((0, import_node_fs11.existsSync)(journal))
15186
- (0, import_node_fs11.copyFileSync)(journal, `${dest}-journal`);
15351
+ if ((0, import_node_fs12.existsSync)(journal))
15352
+ (0, import_node_fs12.copyFileSync)(journal, `${dest}-journal`);
15187
15353
  return dest;
15188
15354
  } catch (e2) {
15189
15355
  log({ source: "installer/upgrade", level: "warn", summary: `pre-upgrade snapshot failed: ${e2.message}` });
@@ -15223,10 +15389,10 @@ function handleIntegrity() {
15223
15389
  }
15224
15390
  function backfillAlreadyRunning() {
15225
15391
  const pidFile = (0, import_node_path11.join)(runDir(), "backfill.pid");
15226
- if (!(0, import_node_fs11.existsSync)(pidFile))
15392
+ if (!(0, import_node_fs12.existsSync)(pidFile))
15227
15393
  return false;
15228
15394
  try {
15229
- const pid = parseInt((0, import_node_fs11.readFileSync)(pidFile, "utf-8").trim(), 10);
15395
+ const pid = parseInt((0, import_node_fs12.readFileSync)(pidFile, "utf-8").trim(), 10);
15230
15396
  if (Number.isNaN(pid))
15231
15397
  return false;
15232
15398
  process.kill(pid, 0);
@@ -15241,11 +15407,11 @@ function migrationReportLine(coverage) {
15241
15407
  const pct = Math.round(coverage * 100);
15242
15408
  return `Embed migration: re-embedding in background (${pct}% at v${EMBED_VERSION}) \u2014 semantic search stays available`;
15243
15409
  }
15244
- var import_node_fs11, import_node_path11, import_better_sqlite32;
15410
+ var import_node_fs12, import_node_path11, import_better_sqlite32;
15245
15411
  var init_upgrade_migrate = __esm({
15246
15412
  "src/installer/upgrade-migrate.ts"() {
15247
15413
  "use strict";
15248
- import_node_fs11 = require("node:fs");
15414
+ import_node_fs12 = require("node:fs");
15249
15415
  import_node_path11 = require("node:path");
15250
15416
  import_better_sqlite32 = __toESM(require_lib());
15251
15417
  init_db();
@@ -15258,8 +15424,8 @@ var init_upgrade_migrate = __esm({
15258
15424
 
15259
15425
  // src/installer/claudemd-nudge.ts
15260
15426
  function applyNudge(filePath) {
15261
- const exists = (0, import_node_fs12.existsSync)(filePath);
15262
- const raw2 = exists ? (0, import_node_fs12.readFileSync)(filePath, "utf-8") : "";
15427
+ const exists = (0, import_node_fs13.existsSync)(filePath);
15428
+ const raw2 = exists ? (0, import_node_fs13.readFileSync)(filePath, "utf-8") : "";
15263
15429
  if (RECALL_HEADING.test(raw2))
15264
15430
  return { changed: false };
15265
15431
  const ending = raw2.includes("\r\n") ? "\r\n" : "\n";
@@ -15273,9 +15439,9 @@ function applyNudge(filePath) {
15273
15439
  return backup ? { changed: true, backup } : { changed: true };
15274
15440
  }
15275
15441
  function removeNudge(filePath) {
15276
- if (!(0, import_node_fs12.existsSync)(filePath))
15442
+ if (!(0, import_node_fs13.existsSync)(filePath))
15277
15443
  return { changed: false };
15278
- const raw2 = (0, import_node_fs12.readFileSync)(filePath, "utf-8");
15444
+ const raw2 = (0, import_node_fs13.readFileSync)(filePath, "utf-8");
15279
15445
  if (!RECALL_HEADING.test(raw2))
15280
15446
  return { changed: false };
15281
15447
  const ending = raw2.includes("\r\n") ? "\r\n" : "\n";
@@ -15303,11 +15469,11 @@ function removeNudge(filePath) {
15303
15469
  writeFileAtomic(filePath, next);
15304
15470
  return { changed: true, backup };
15305
15471
  }
15306
- var import_node_fs12, RECALL_BLOCK, RECALL_HEADING;
15472
+ var import_node_fs13, RECALL_BLOCK, RECALL_HEADING;
15307
15473
  var init_claudemd_nudge = __esm({
15308
15474
  "src/installer/claudemd-nudge.ts"() {
15309
15475
  "use strict";
15310
- import_node_fs12 = require("node:fs");
15476
+ import_node_fs13 = require("node:fs");
15311
15477
  init_settings_merge();
15312
15478
  RECALL_BLOCK = [
15313
15479
  "",
@@ -15325,13 +15491,14 @@ var init_claudemd_nudge = __esm({
15325
15491
  var install_exports = {};
15326
15492
  __export(install_exports, {
15327
15493
  defaultDistDir: () => defaultDistDir,
15494
+ probeStagedBinding: () => probeStagedBinding,
15328
15495
  runInstall: () => runInstall
15329
15496
  });
15330
15497
  function defaultDistDir() {
15331
15498
  const argv1 = process.argv[1];
15332
15499
  if (argv1) {
15333
15500
  try {
15334
- return (0, import_node_path12.dirname)((0, import_node_fs13.realpathSync)(argv1));
15501
+ return (0, import_node_path12.dirname)((0, import_node_fs14.realpathSync)(argv1));
15335
15502
  } catch {
15336
15503
  return (0, import_node_path12.dirname)(argv1);
15337
15504
  }
@@ -15339,7 +15506,7 @@ function defaultDistDir() {
15339
15506
  return __dirname;
15340
15507
  }
15341
15508
  function resolveTemplatePath(explicit) {
15342
- if (explicit && (0, import_node_fs13.existsSync)(explicit))
15509
+ if (explicit && (0, import_node_fs14.existsSync)(explicit))
15343
15510
  return explicit;
15344
15511
  const here = __dirname;
15345
15512
  const candidates = [
@@ -15351,44 +15518,44 @@ function resolveTemplatePath(explicit) {
15351
15518
  // source: src/installer/
15352
15519
  ];
15353
15520
  for (const c of candidates)
15354
- if ((0, import_node_fs13.existsSync)(c))
15521
+ if ((0, import_node_fs14.existsSync)(c))
15355
15522
  return c;
15356
15523
  return candidates[candidates.length - 1];
15357
15524
  }
15358
15525
  function recallBinCommand() {
15359
- return `"${process.execPath}" "${(0, import_node_path12.join)(binDir(), "recall.js")}"`;
15526
+ return `"${stableNodePath()}" "${(0, import_node_path12.join)(binDir(), "recall.js")}"`;
15360
15527
  }
15361
15528
  function writeSkill(targetPath, templatePath, runnable) {
15362
- const template = (0, import_node_fs13.readFileSync)(templatePath, "utf-8");
15529
+ const template = (0, import_node_fs14.readFileSync)(templatePath, "utf-8");
15363
15530
  const body = template.replaceAll("$RECALL_BIN", runnable);
15364
- (0, import_node_fs13.mkdirSync)((0, import_node_path12.dirname)(targetPath), { recursive: true });
15365
- if ((0, import_node_fs13.existsSync)(targetPath)) {
15366
- if ((0, import_node_fs13.readFileSync)(targetPath, "utf-8") === body)
15531
+ (0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(targetPath), { recursive: true });
15532
+ if ((0, import_node_fs14.existsSync)(targetPath)) {
15533
+ if ((0, import_node_fs14.readFileSync)(targetPath, "utf-8") === body)
15367
15534
  return false;
15368
15535
  backupFile(targetPath);
15369
15536
  }
15370
- (0, import_node_fs13.writeFileSync)(targetPath, body);
15537
+ (0, import_node_fs14.writeFileSync)(targetPath, body);
15371
15538
  return true;
15372
15539
  }
15373
15540
  function stageBundles(distDir, written) {
15374
- (0, import_node_fs13.mkdirSync)(binDir(), { recursive: true });
15541
+ (0, import_node_fs14.mkdirSync)(binDir(), { recursive: true });
15375
15542
  for (const name of STAGED_BUNDLES) {
15376
15543
  const src = (0, import_node_path12.join)(distDir, name);
15377
- if (!(0, import_node_fs13.existsSync)(src)) {
15544
+ if (!(0, import_node_fs14.existsSync)(src)) {
15378
15545
  log({ source: "installer/install", level: "warn", summary: `bundle ${name} not found in ${distDir} \u2014 skipping stage` });
15379
15546
  continue;
15380
15547
  }
15381
15548
  const dest = (0, import_node_path12.join)(binDir(), name);
15382
- (0, import_node_fs13.copyFileSync)(src, dest);
15549
+ (0, import_node_fs14.copyFileSync)(src, dest);
15383
15550
  written.push(dest);
15384
15551
  }
15385
- stageNativeBinding(written);
15552
+ return stageNativeBinding(written);
15386
15553
  }
15387
15554
  function stageNativeBinding(written) {
15388
15555
  const staleWasm = (0, import_node_path12.join)(binDir(), "node-sqlite3-wasm.wasm");
15389
- if ((0, import_node_fs13.existsSync)(staleWasm)) {
15556
+ if ((0, import_node_fs14.existsSync)(staleWasm)) {
15390
15557
  try {
15391
- (0, import_node_fs13.unlinkSync)(staleWasm);
15558
+ (0, import_node_fs14.unlinkSync)(staleWasm);
15392
15559
  } catch {
15393
15560
  }
15394
15561
  }
@@ -15399,26 +15566,45 @@ function stageNativeBinding(written) {
15399
15566
  level: "warn",
15400
15567
  summary: "better_sqlite3.node not found in node_modules \u2014 the CLI/hooks will fail to load SQLite until reinstalled"
15401
15568
  });
15402
- return;
15569
+ return { ok: true };
15403
15570
  }
15404
15571
  const dest = (0, import_node_path12.join)(binDir(), "better_sqlite3.node");
15405
- (0, import_node_fs13.copyFileSync)(binding, dest);
15572
+ (0, import_node_fs14.copyFileSync)(binding, dest);
15406
15573
  written.push(dest);
15574
+ const probe = probeStagedBinding(dest);
15575
+ if (!probe.ok) {
15576
+ const remediation = isBindingLoadError({ message: probe.message }) ? `The SQLite binding npm installed was built for a different Node than the one running recall (${process.execPath}, ${process.version}). This usually means \`npm install -g\` ran under a different Node (e.g. Homebrew vs nvm). Re-run \`npm install -g crispy-recall\` using the same node that is on your PATH, then \`recall install\`. (${probe.message})` : `The staged SQLite binding could not be loaded (${probe.message}). Re-run \`npm install -g crispy-recall\` with the node that is on your PATH, then \`recall install\`.`;
15577
+ return { ok: false, remediation };
15578
+ }
15407
15579
  const markerPath = (0, import_node_path12.join)(binDir(), ".binding-info.json");
15408
- (0, import_node_fs13.writeFileSync)(
15580
+ (0, import_node_fs14.writeFileSync)(
15409
15581
  markerPath,
15410
15582
  JSON.stringify(
15411
15583
  {
15412
15584
  platform: process.platform,
15413
15585
  arch: process.arch,
15414
15586
  nodeModuleVersion: process.versions.modules,
15415
- nodePath: process.execPath
15587
+ // Upgrade-stable public path, not the Cellar path brew upgrade deletes;
15588
+ // doctor's pinnedNodeOk checks existsSync(nodePath), which this satisfies.
15589
+ nodePath: stableNodePath()
15416
15590
  },
15417
15591
  null,
15418
15592
  2
15419
15593
  )
15420
15594
  );
15421
15595
  written.push(markerPath);
15596
+ return { ok: true };
15597
+ }
15598
+ function probeStagedBinding(dest) {
15599
+ const res = (0, import_node_child_process5.spawnSync)(process.execPath, ["-e", "require(process.argv[1])", dest], {
15600
+ encoding: "utf-8",
15601
+ timeout: 2e4,
15602
+ windowsHide: true
15603
+ });
15604
+ if (res.status === 0)
15605
+ return { ok: true };
15606
+ const message = (res.stderr || res.stdout || "").trim() || (res.error ? res.error.message : `binding probe exited with code ${res.status}`);
15607
+ return { ok: false, message };
15422
15608
  }
15423
15609
  function resolveInstalledBinding2() {
15424
15610
  try {
@@ -15434,7 +15620,7 @@ function findDotNode(baseDir) {
15434
15620
  (0, import_node_path12.join)(baseDir, "build", "Debug", "better_sqlite3.node")
15435
15621
  ]) {
15436
15622
  try {
15437
- if ((0, import_node_fs13.statSync)(c).isFile())
15623
+ if ((0, import_node_fs14.statSync)(c).isFile())
15438
15624
  return c;
15439
15625
  } catch {
15440
15626
  }
@@ -15444,7 +15630,7 @@ function findDotNode(baseDir) {
15444
15630
  const dir = stack.pop();
15445
15631
  let entries;
15446
15632
  try {
15447
- entries = (0, import_node_fs13.readdirSync)(dir, { withFileTypes: true });
15633
+ entries = (0, import_node_fs14.readdirSync)(dir, { withFileTypes: true });
15448
15634
  } catch {
15449
15635
  continue;
15450
15636
  }
@@ -15530,12 +15716,27 @@ ${wmsg}` });
15530
15716
  say("upgrade: quiesced stale Stop hook before migration");
15531
15717
  }
15532
15718
  for (const d3 of [binDir(), modelsDir(), runDir(), logsDir()])
15533
- (0, import_node_fs13.mkdirSync)(d3, { recursive: true });
15534
- stageBundles(opts.distDir ?? defaultDistDir(), filesWritten);
15719
+ (0, import_node_fs14.mkdirSync)(d3, { recursive: true });
15720
+ const staged = stageBundles(opts.distDir ?? defaultDistDir(), filesWritten);
15721
+ if (!staged.ok) {
15722
+ if (interactive)
15723
+ le(staged.remediation, "Install aborted");
15724
+ else
15725
+ log({ source: "installer/install", level: "error", summary: `install aborted: ${staged.remediation}` });
15726
+ return {
15727
+ aborted: true,
15728
+ abortReason: staged.remediation,
15729
+ report,
15730
+ selected: [...selected],
15731
+ gpu,
15732
+ filesWritten,
15733
+ migration: { state: migration.state, coverage: migration.coverage, drainLaunched: false }
15734
+ };
15735
+ }
15535
15736
  say(`scaffolded ${recallRoot()}`);
15536
15737
  if (opts.offline) {
15537
- const haveBin = (0, import_node_fs13.existsSync)(getBinaryPath());
15538
- const haveModel = (0, import_node_fs13.existsSync)(getModelPath());
15738
+ const haveBin = (0, import_node_fs14.existsSync)(getBinaryPath());
15739
+ const haveModel = (0, import_node_fs14.existsSync)(getModelPath());
15539
15740
  if (!haveBin || !haveModel) {
15540
15741
  const msg = `--offline but runtime not pre-staged (binary=${haveBin}, model=${haveModel}). Pre-stage ~/.recall/bin + model (or drop --offline), then re-run \`recall install\`` + (migration.state === "needs-migration" ? " (which restores the recall Stop hook)." : ".");
15541
15742
  log({ source: "installer/install", level: "error", summary: msg });
@@ -15716,29 +15917,29 @@ ${wmsg}` });
15716
15917
  };
15717
15918
  }
15718
15919
  function spawnDetachedBackfill() {
15719
- (0, import_node_fs13.mkdirSync)(runDir(), { recursive: true });
15720
- (0, import_node_fs13.mkdirSync)(logsDir(), { recursive: true });
15920
+ (0, import_node_fs14.mkdirSync)(runDir(), { recursive: true });
15921
+ (0, import_node_fs14.mkdirSync)(logsDir(), { recursive: true });
15721
15922
  const recallBundle = (0, import_node_path12.join)(binDir(), "recall.js");
15722
- const logFd = (0, import_node_fs13.openSync)((0, import_node_path12.join)(logsDir(), "backfill.log"), "a");
15723
- const child = (0, import_node_child_process4.spawn)(process.execPath, [recallBundle, "backfill", "--auto-embed"], {
15923
+ const logFd = (0, import_node_fs14.openSync)((0, import_node_path12.join)(logsDir(), "backfill.log"), "a");
15924
+ const child = (0, import_node_child_process5.spawn)(process.execPath, [recallBundle, "backfill", "--auto-embed"], {
15724
15925
  detached: true,
15725
15926
  stdio: ["ignore", logFd, logFd],
15726
15927
  windowsHide: true,
15727
15928
  env: { ...process.env }
15728
15929
  });
15729
15930
  if (child.pid !== void 0) {
15730
- (0, import_node_fs13.writeFileSync)((0, import_node_path12.join)(runDir(), "backfill.pid"), String(child.pid));
15931
+ (0, import_node_fs14.writeFileSync)((0, import_node_path12.join)(runDir(), "backfill.pid"), String(child.pid));
15731
15932
  }
15732
15933
  child.unref();
15733
15934
  return child.pid;
15734
15935
  }
15735
- var import_node_fs13, import_node_child_process4, import_node_module2, import_node_path12, STAGED_BUNDLES;
15936
+ var import_node_fs14, import_node_child_process5, import_node_module2, import_node_path12, STAGED_BUNDLES;
15736
15937
  var init_install = __esm({
15737
15938
  "src/installer/install.ts"() {
15738
15939
  "use strict";
15739
15940
  init_dist2();
15740
- import_node_fs13 = require("node:fs");
15741
- import_node_child_process4 = require("node:child_process");
15941
+ import_node_fs14 = require("node:fs");
15942
+ import_node_child_process5 = require("node:child_process");
15742
15943
  import_node_module2 = require("node:module");
15743
15944
  import_node_path12 = require("node:path");
15744
15945
  init_paths();
@@ -15748,6 +15949,7 @@ var init_install = __esm({
15748
15949
  init_manifest();
15749
15950
  init_gpu();
15750
15951
  init_settings_merge();
15952
+ init_stable_node();
15751
15953
  init_upgrade_migrate();
15752
15954
  init_claudemd_nudge();
15753
15955
  init_embedder();
@@ -15765,8 +15967,8 @@ function runUninstall(opts = {}) {
15765
15967
  const removed = [];
15766
15968
  for (const skillFile of [claudeRecallSkillPath(), codexRecallSkillPath()]) {
15767
15969
  const dir = (0, import_node_path13.dirname)(skillFile);
15768
- if ((0, import_node_fs14.existsSync)(dir)) {
15769
- (0, import_node_fs14.rmSync)(dir, { recursive: true, force: true });
15970
+ if ((0, import_node_fs15.existsSync)(dir)) {
15971
+ (0, import_node_fs15.rmSync)(dir, { recursive: true, force: true });
15770
15972
  removed.push(dir);
15771
15973
  }
15772
15974
  }
@@ -15781,8 +15983,8 @@ function runUninstall(opts = {}) {
15781
15983
  let purged = false;
15782
15984
  if (opts.purge) {
15783
15985
  closeDb();
15784
- if ((0, import_node_fs14.existsSync)(recallRoot())) {
15785
- (0, import_node_fs14.rmSync)(recallRoot(), { recursive: true, force: true });
15986
+ if ((0, import_node_fs15.existsSync)(recallRoot())) {
15987
+ (0, import_node_fs15.rmSync)(recallRoot(), { recursive: true, force: true });
15786
15988
  removed.push(recallRoot());
15787
15989
  }
15788
15990
  purged = true;
@@ -15790,11 +15992,11 @@ function runUninstall(opts = {}) {
15790
15992
  log({ source: "installer/uninstall", level: "info", summary: `uninstall removed ${removed.length} targets (purge=${purged})` });
15791
15993
  return { removed, purged };
15792
15994
  }
15793
- var import_node_fs14, import_node_path13;
15995
+ var import_node_fs15, import_node_path13;
15794
15996
  var init_uninstall = __esm({
15795
15997
  "src/installer/uninstall.ts"() {
15796
15998
  "use strict";
15797
- import_node_fs14 = require("node:fs");
15999
+ import_node_fs15 = require("node:fs");
15798
16000
  import_node_path13 = require("node:path");
15799
16001
  init_settings_merge();
15800
16002
  init_claudemd_nudge();
@@ -15816,14 +16018,14 @@ function getStatus() {
15816
16018
  const messageCount = d3.get("SELECT COUNT(*) AS c FROM messages").c;
15817
16019
  const lastRow = d3.get("SELECT MAX(created_at) AS m FROM messages");
15818
16020
  const lastIngest = lastRow.m ? new Date(lastRow.m).toISOString() : null;
15819
- const dbSizeBytes = (0, import_node_fs15.existsSync)(dbPath()) ? (0, import_node_fs15.statSync)(dbPath()).size : 0;
16021
+ const dbSizeBytes = (0, import_node_fs16.existsSync)(dbPath()) ? (0, import_node_fs16.statSync)(dbPath()).size : 0;
15820
16022
  const embeddingGap = getEmbeddingGapStats();
15821
16023
  const embedVersions = getEmbedVersionStats();
15822
16024
  let backfillPid = null;
15823
16025
  let backfillRunning = false;
15824
16026
  const pidFile = (0, import_node_path14.join)(runDir(), "backfill.pid");
15825
- if ((0, import_node_fs15.existsSync)(pidFile)) {
15826
- const pid = parseInt((0, import_node_fs15.readFileSync)(pidFile, "utf-8").trim(), 10);
16027
+ if ((0, import_node_fs16.existsSync)(pidFile)) {
16028
+ const pid = parseInt((0, import_node_fs16.readFileSync)(pidFile, "utf-8").trim(), 10);
15827
16029
  if (!Number.isNaN(pid)) {
15828
16030
  backfillPid = pid;
15829
16031
  try {
@@ -15866,11 +16068,11 @@ function printStatus(json) {
15866
16068
  console.log(`Embedder: ${s.embedder.toUpperCase()}`);
15867
16069
  console.log(`Backfill: ${s.backfillPid === null ? "none recorded" : `PID ${s.backfillPid} (${s.backfillRunning ? "running" : "finished"})`}`);
15868
16070
  }
15869
- var import_node_fs15, import_node_path14;
16071
+ var import_node_fs16, import_node_path14;
15870
16072
  var init_status = __esm({
15871
16073
  "src/installer/status.ts"() {
15872
16074
  "use strict";
15873
- import_node_fs15 = require("node:fs");
16075
+ import_node_fs16 = require("node:fs");
15874
16076
  import_node_path14 = require("node:path");
15875
16077
  init_db();
15876
16078
  init_paths();
@@ -15983,7 +16185,7 @@ function stagedBindingPath() {
15983
16185
  }
15984
16186
  function checkBindingHealth() {
15985
16187
  const problems = [];
15986
- const installed = (0, import_node_fs16.existsSync)((0, import_node_path15.join)(binDir(), "recall.js"));
16188
+ const installed = (0, import_node_fs17.existsSync)((0, import_node_path15.join)(binDir(), "recall.js"));
15987
16189
  if (!installed) {
15988
16190
  return {
15989
16191
  installed: false,
@@ -15997,21 +16199,21 @@ function checkBindingHealth() {
15997
16199
  };
15998
16200
  }
15999
16201
  const markerPath = (0, import_node_path15.join)(binDir(), ".binding-info.json");
16000
- const markerPresent = (0, import_node_fs16.existsSync)(markerPath);
16202
+ const markerPresent = (0, import_node_fs17.existsSync)(markerPath);
16001
16203
  let abiOk = null;
16002
16204
  let pinnedNodeOk = null;
16003
16205
  if (!markerPresent) {
16004
16206
  problems.push("no .binding-info.json marker \u2014 pre-migration (wasm-era) install; run `recall install`");
16005
16207
  } else {
16006
16208
  try {
16007
- const marker = JSON.parse((0, import_node_fs16.readFileSync)(markerPath, "utf-8"));
16209
+ const marker = JSON.parse((0, import_node_fs17.readFileSync)(markerPath, "utf-8"));
16008
16210
  abiOk = String(marker.nodeModuleVersion) === String(process.versions.modules);
16009
16211
  if (!abiOk) {
16010
16212
  problems.push(
16011
16213
  `binding ABI mismatch (staged for NODE_MODULE_VERSION ${marker.nodeModuleVersion}, current ${process.versions.modules}) \u2014 run \`recall install\``
16012
16214
  );
16013
16215
  }
16014
- pinnedNodeOk = !!marker.nodePath && (0, import_node_fs16.existsSync)(marker.nodePath);
16216
+ pinnedNodeOk = !!marker.nodePath && (0, import_node_fs17.existsSync)(marker.nodePath);
16015
16217
  if (!pinnedNodeOk) {
16016
16218
  problems.push(`pinned Node path missing (${marker.nodePath ?? "unset"}) \u2014 run \`recall install\``);
16017
16219
  }
@@ -16024,9 +16226,9 @@ function checkBindingHealth() {
16024
16226
  let embedCoverage = null;
16025
16227
  const localBinding = stagedBindingPath();
16026
16228
  const dbFile = dbPath();
16027
- if ((0, import_node_fs16.existsSync)(dbFile)) {
16229
+ if ((0, import_node_fs17.existsSync)(dbFile)) {
16028
16230
  try {
16029
- const raw2 = (0, import_node_fs16.existsSync)(localBinding) ? new import_better_sqlite33.default(dbFile, { readonly: true, fileMustExist: true, nativeBinding: localBinding }) : new import_better_sqlite33.default(dbFile, { readonly: true, fileMustExist: true });
16231
+ const raw2 = (0, import_node_fs17.existsSync)(localBinding) ? new import_better_sqlite33.default(dbFile, { readonly: true, fileMustExist: true, nativeBinding: localBinding }) : new import_better_sqlite33.default(dbFile, { readonly: true, fileMustExist: true });
16030
16232
  bindingLoads = true;
16031
16233
  journalMode = String(raw2.pragma("journal_mode", { simple: true }));
16032
16234
  try {
@@ -16056,7 +16258,7 @@ function checkBindingHealth() {
16056
16258
  }
16057
16259
  } else {
16058
16260
  try {
16059
- const raw2 = (0, import_node_fs16.existsSync)(localBinding) ? new import_better_sqlite33.default(":memory:", { nativeBinding: localBinding }) : new import_better_sqlite33.default(":memory:");
16261
+ const raw2 = (0, import_node_fs17.existsSync)(localBinding) ? new import_better_sqlite33.default(":memory:", { nativeBinding: localBinding }) : new import_better_sqlite33.default(":memory:");
16060
16262
  bindingLoads = true;
16061
16263
  raw2.close();
16062
16264
  } catch (e2) {
@@ -16130,11 +16332,11 @@ function printTable(report, embedderMode, fallbackReason) {
16130
16332
  if (!report.warnings.length && !report.failures.length)
16131
16333
  console.log("\nAll checks passed.");
16132
16334
  }
16133
- var import_node_fs16, import_node_path15, import_better_sqlite33;
16335
+ var import_node_fs17, import_node_path15, import_better_sqlite33;
16134
16336
  var init_doctor = __esm({
16135
16337
  "src/installer/doctor.ts"() {
16136
16338
  "use strict";
16137
- import_node_fs16 = require("node:fs");
16339
+ import_node_fs17 = require("node:fs");
16138
16340
  import_node_path15 = require("node:path");
16139
16341
  import_better_sqlite33 = __toESM(require_lib());
16140
16342
  init_preflight();
@@ -16738,8 +16940,8 @@ function gitSafe(repoRoot, args) {
16738
16940
  }
16739
16941
 
16740
16942
  // src/cli/recall.ts
16741
- var import_node_fs17 = require("node:fs");
16742
- var import_node_child_process5 = require("node:child_process");
16943
+ var import_node_fs18 = require("node:fs");
16944
+ var import_node_child_process6 = require("node:child_process");
16743
16945
  var import_node_path16 = require("node:path");
16744
16946
  var UUID_PREFIX = /^[0-9a-f]{8}/i;
16745
16947
  var argv = process.argv.slice(2);
@@ -16830,7 +17032,7 @@ for (let i = 0; i < argv.length; i++) {
16830
17032
  function getVersion() {
16831
17033
  try {
16832
17034
  const pkg = JSON.parse(
16833
- (0, import_node_fs17.readFileSync)((0, import_node_path16.join)(__dirname, "..", "package.json"), "utf8")
17035
+ (0, import_node_fs18.readFileSync)((0, import_node_path16.join)(__dirname, "..", "package.json"), "utf8")
16834
17036
  );
16835
17037
  return pkg.version ?? "unknown";
16836
17038
  } catch {
@@ -17406,22 +17608,22 @@ async function runBackfill() {
17406
17608
  clearCancelFlag();
17407
17609
  const detach = hasFlag("--detach");
17408
17610
  if (detach) {
17409
- (0, import_node_fs17.mkdirSync)(logsDir(), { recursive: true });
17410
- (0, import_node_fs17.mkdirSync)(runDir(), { recursive: true });
17611
+ (0, import_node_fs18.mkdirSync)(logsDir(), { recursive: true });
17612
+ (0, import_node_fs18.mkdirSync)(runDir(), { recursive: true });
17411
17613
  const logPath = (0, import_node_path16.join)(logsDir(), "backfill.log");
17412
- const logFd = (0, import_node_fs17.openSync)(logPath, "a");
17614
+ const logFd = (0, import_node_fs18.openSync)(logPath, "a");
17413
17615
  const args = [process.argv[1], "backfill", "--auto-embed"];
17414
17616
  const vendors2 = parseVendors();
17415
17617
  if (vendors2) {
17416
17618
  args.push("--vendor", vendors2[0]);
17417
17619
  }
17418
- const child = (0, import_node_child_process5.spawn)(process.execPath, args, {
17620
+ const child = (0, import_node_child_process6.spawn)(process.execPath, args, {
17419
17621
  detached: true,
17420
17622
  stdio: ["ignore", logFd, logFd],
17421
17623
  windowsHide: true
17422
17624
  });
17423
17625
  if (child.pid !== void 0) {
17424
- (0, import_node_fs17.writeFileSync)((0, import_node_path16.join)(runDir(), "backfill.pid"), String(child.pid));
17626
+ (0, import_node_fs18.writeFileSync)((0, import_node_path16.join)(runDir(), "backfill.pid"), String(child.pid));
17425
17627
  }
17426
17628
  child.unref();
17427
17629
  process.stderr.write(`[recall] backfill detached (pid=${child.pid}, log=${logPath})