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/README.md +25 -2
- package/dist/embed-pending.js +100 -15
- package/dist/recall.js +417 -215
- package/dist/stop-hook.js +4 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,6 +6,22 @@ Searchable memory for your Claude Code and Codex sessions. Local, fast, no daemo
|
|
|
6
6
|
|
|
7
7
|
A standalone spin-off of the recall feature from [Crispy](https://github.com/TheSylvester/crispy). See the parent project for the broader multi-agent orchestration GUI.
|
|
8
8
|
|
|
9
|
+
## What's new in 0.2.1
|
|
10
|
+
|
|
11
|
+
- **macOS install support.** 0.2.0's native SQLite engine shipped without ever
|
|
12
|
+
running on Apple hardware; 0.2.1 fixes the macOS install path and validates it
|
|
13
|
+
end-to-end on Apple Silicon. Fixes: a pre-flight macOS-version check (the
|
|
14
|
+
bundled llama.cpp embedding binaries need macOS 14+ on arm64, 13.7+ on Intel),
|
|
15
|
+
a Node path that stays valid across `brew upgrade node` during in-place
|
|
16
|
+
upgrades (it previously pinned a Homebrew Cellar path that a later upgrade
|
|
17
|
+
removes), an up-front rejection of Node 23 (no prebuilt SQLite binding) with an
|
|
18
|
+
explanation instead of a late failure, and a load-check of the staged SQLite
|
|
19
|
+
binding before the installer trusts it. Non-macOS installs are unaffected.
|
|
20
|
+
- **`recall install` no longer hangs during setup.** Extraction of the embedding
|
|
21
|
+
binaries is now time-boxed with a fallback to the OS unzip tool
|
|
22
|
+
(`unzip`/`ditto`/`Expand-Archive`), so a stalled extract can no longer wedge
|
|
23
|
+
the installer.
|
|
24
|
+
|
|
9
25
|
## What's new in 0.2.0
|
|
10
26
|
|
|
11
27
|
- **Native SQLite engine with real WAL.** The database now runs on
|
|
@@ -13,7 +29,8 @@ A standalone spin-off of the recall feature from [Crispy](https://github.com/The
|
|
|
13
29
|
binding — which never actually engaged WAL and could silently corrupt the
|
|
14
30
|
index when multiple processes wrote at once. That failure mode is eliminated
|
|
15
31
|
at the root. `recall doctor` gains a SQLite-binding health section.
|
|
16
|
-
**Node.js ≥ 22.16 is now required
|
|
32
|
+
**Node.js 22 LTS (≥ 22.16) or 24+ is now required** (Node 23 has no prebuilt
|
|
33
|
+
SQLite binding).
|
|
17
34
|
- **Git provenance: `--commit` and `--blame`.** `recall --commit <hash>` lists
|
|
18
35
|
the session(s) that produced a commit; `recall --blame <path>[:line[-line]]`
|
|
19
36
|
traces a file or line range back to the conversations responsible. Matching
|
|
@@ -65,13 +82,19 @@ Run it in the environment where you actually use Claude Code: **WSL and
|
|
|
65
82
|
Windows-native are separate installs.** If you use both, run the install in each
|
|
66
83
|
— the installer only configures the environment it is invoked in.
|
|
67
84
|
|
|
85
|
+
> **macOS: use Node 22 LTS or 24+.** Node 23 has no prebuilt SQLite binding, so
|
|
86
|
+
> `npm install -g` would fall back to compiling it and fail without Xcode
|
|
87
|
+
> Command Line Tools. macOS 14+ (Apple Silicon) / 13.7+ (Intel) is required for
|
|
88
|
+
> the bundled semantic-embedding binaries.
|
|
89
|
+
|
|
68
90
|
> **Don't install via `npx`.** recall must stay resident. `npx` runs from an
|
|
69
91
|
> ephemeral cache and leaves no `recall` command on your PATH, so follow-up
|
|
70
92
|
> commands (`recall status` / `doctor` / `repair` / `uninstall`) and the
|
|
71
93
|
> installed skill's command contract have nothing to call. Use the global
|
|
72
94
|
> install above.
|
|
73
95
|
|
|
74
|
-
Prerequisites: Node ≥ 22.16
|
|
96
|
+
Prerequisites: Node 22 LTS (≥ 22.16) or 24+ — Node 23 is unsupported (no
|
|
97
|
+
prebuilt SQLite binding) — and Claude Code installed. If Codex is detected
|
|
75
98
|
(`~/.codex/` exists), recall installs the `recall` skill into Codex (so the
|
|
76
99
|
agent can search) and you can index your Codex history with
|
|
77
100
|
`recall backfill --vendor codex`. Real-time per-turn Codex indexing is **not
|
package/dist/embed-pending.js
CHANGED
|
@@ -3466,10 +3466,18 @@ var require_extract_zip = __commonJS({
|
|
|
3466
3466
|
}
|
|
3467
3467
|
});
|
|
3468
3468
|
|
|
3469
|
+
// src/installer/stable-node.ts
|
|
3470
|
+
var init_stable_node = __esm({
|
|
3471
|
+
"src/installer/stable-node.ts"() {
|
|
3472
|
+
"use strict";
|
|
3473
|
+
}
|
|
3474
|
+
});
|
|
3475
|
+
|
|
3469
3476
|
// src/installer/settings-merge.ts
|
|
3470
3477
|
var init_settings_merge = __esm({
|
|
3471
3478
|
"src/installer/settings-merge.ts"() {
|
|
3472
3479
|
"use strict";
|
|
3480
|
+
init_stable_node();
|
|
3473
3481
|
}
|
|
3474
3482
|
});
|
|
3475
3483
|
|
|
@@ -3513,15 +3521,21 @@ var init_config = __esm({
|
|
|
3513
3521
|
// src/recall/embedder.ts
|
|
3514
3522
|
var embedder_exports = {};
|
|
3515
3523
|
__export(embedder_exports, {
|
|
3524
|
+
LLAMA_RELEASE_TAG: () => LLAMA_RELEASE_TAG,
|
|
3525
|
+
MACOS_MIN_VERSION: () => MACOS_MIN_VERSION,
|
|
3526
|
+
buildUnzipInvocations: () => buildUnzipInvocations,
|
|
3516
3527
|
disposeEmbedder: () => disposeEmbedder,
|
|
3517
3528
|
embed: () => embed,
|
|
3518
3529
|
embedBatch: () => embedBatch,
|
|
3519
3530
|
ensureBinary: () => ensureBinary,
|
|
3520
3531
|
ensureModel: () => ensureModel,
|
|
3532
|
+
extractArchive: () => extractArchive,
|
|
3521
3533
|
getBinaryPath: () => getBinaryPath,
|
|
3522
3534
|
getModelPath: () => getModelPath,
|
|
3523
3535
|
hasNvidiaGpu: () => hasNvidiaGpu,
|
|
3524
|
-
initEmbedder: () => initEmbedder
|
|
3536
|
+
initEmbedder: () => initEmbedder,
|
|
3537
|
+
installExtractedBinaries: () => installExtractedBinaries,
|
|
3538
|
+
withTimeout: () => withTimeout
|
|
3525
3539
|
});
|
|
3526
3540
|
function gpuRuntime() {
|
|
3527
3541
|
if (_gpuRuntime === void 0) {
|
|
@@ -3695,6 +3709,82 @@ async function performBinaryDownload(binPath) {
|
|
|
3695
3709
|
}
|
|
3696
3710
|
throw new Error("No suitable llama-embedding binary found");
|
|
3697
3711
|
}
|
|
3712
|
+
function withTimeout(promise, ms, label) {
|
|
3713
|
+
let timer;
|
|
3714
|
+
const timeout = new Promise((_, reject) => {
|
|
3715
|
+
timer = setTimeout(
|
|
3716
|
+
() => reject(new Error(`${label} timed out after ${Math.round(ms / 1e3)}s`)),
|
|
3717
|
+
ms
|
|
3718
|
+
);
|
|
3719
|
+
});
|
|
3720
|
+
promise.catch(() => {
|
|
3721
|
+
});
|
|
3722
|
+
return Promise.race([promise, timeout]).finally(() => {
|
|
3723
|
+
if (timer)
|
|
3724
|
+
clearTimeout(timer);
|
|
3725
|
+
});
|
|
3726
|
+
}
|
|
3727
|
+
function psQuote(p) {
|
|
3728
|
+
return p.replace(/'/g, "''");
|
|
3729
|
+
}
|
|
3730
|
+
function buildUnzipInvocations(plat, archivePath, dir) {
|
|
3731
|
+
if (plat === "win32") {
|
|
3732
|
+
return [{
|
|
3733
|
+
cmd: "powershell",
|
|
3734
|
+
args: [
|
|
3735
|
+
"-NoProfile",
|
|
3736
|
+
"-NonInteractive",
|
|
3737
|
+
"-Command",
|
|
3738
|
+
`Expand-Archive -LiteralPath '${psQuote(archivePath)}' -DestinationPath '${psQuote(dir)}' -Force`
|
|
3739
|
+
]
|
|
3740
|
+
}];
|
|
3741
|
+
}
|
|
3742
|
+
const unzip = { cmd: "unzip", args: ["-o", "-q", archivePath, "-d", dir] };
|
|
3743
|
+
return plat === "darwin" ? [unzip, { cmd: "ditto", args: ["-x", "-k", archivePath, dir] }] : [unzip];
|
|
3744
|
+
}
|
|
3745
|
+
async function osUnzip(archivePath, dir) {
|
|
3746
|
+
(0, import_node_fs4.mkdirSync)(dir, { recursive: true });
|
|
3747
|
+
const invocations = buildUnzipInvocations((0, import_node_os2.platform)(), archivePath, dir);
|
|
3748
|
+
let lastErr;
|
|
3749
|
+
for (const { cmd, args } of invocations) {
|
|
3750
|
+
try {
|
|
3751
|
+
await execFileAsync(cmd, args, { timeout: EXTRACT_TIMEOUT_MS, windowsHide: true });
|
|
3752
|
+
return;
|
|
3753
|
+
} catch (err) {
|
|
3754
|
+
lastErr = err;
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
throw lastErr instanceof Error ? lastErr : new Error(`Native unzip failed for ${archivePath}`);
|
|
3758
|
+
}
|
|
3759
|
+
async function extractArchive(archivePath, baseDir, assetName, timeoutMs = EXTRACT_TIMEOUT_MS) {
|
|
3760
|
+
try {
|
|
3761
|
+
await withTimeout((0, import_extract_zip.default)(archivePath, { dir: baseDir }), timeoutMs, `Extracting ${assetName}`);
|
|
3762
|
+
return baseDir;
|
|
3763
|
+
} catch (err) {
|
|
3764
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3765
|
+
log({
|
|
3766
|
+
source: "recall-catchup",
|
|
3767
|
+
level: "warn",
|
|
3768
|
+
summary: `Bundled unzip failed for ${assetName} (${msg}); falling back to system unzip`
|
|
3769
|
+
});
|
|
3770
|
+
const fallbackDir = (0, import_node_path4.join)(baseDir, "__os_unzip__");
|
|
3771
|
+
await osUnzip(archivePath, fallbackDir);
|
|
3772
|
+
return fallbackDir;
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
async function installExtractedBinaries(extractedDir, destDir = binDir()) {
|
|
3776
|
+
const buildBinDir = (0, import_node_path4.join)(extractedDir, "build", "bin");
|
|
3777
|
+
const sourceDir = (0, import_node_fs4.existsSync)(buildBinDir) ? buildBinDir : extractedDir;
|
|
3778
|
+
(0, import_node_fs4.mkdirSync)(destDir, { recursive: true });
|
|
3779
|
+
let copiedCount = 0;
|
|
3780
|
+
for (const file of await (0, import_promises.readdir)(sourceDir)) {
|
|
3781
|
+
if (!isWantedFile(file))
|
|
3782
|
+
continue;
|
|
3783
|
+
await (0, import_promises.cp)((0, import_node_path4.join)(sourceDir, file), (0, import_node_path4.join)(destDir, file), { force: true });
|
|
3784
|
+
copiedCount++;
|
|
3785
|
+
}
|
|
3786
|
+
return copiedCount;
|
|
3787
|
+
}
|
|
3698
3788
|
async function downloadAndExtract(assetName, binPath) {
|
|
3699
3789
|
const url = `https://github.com/ggml-org/llama.cpp/releases/download/${LLAMA_RELEASE_TAG}/${assetName}`;
|
|
3700
3790
|
const isGpu = assetName.includes("cuda") || assetName.includes("vulkan");
|
|
@@ -3715,18 +3805,8 @@ async function downloadAndExtract(assetName, binPath) {
|
|
|
3715
3805
|
const tmpExtractDir = (0, import_node_path4.join)((0, import_node_os2.tmpdir)(), `llama-extract-${Date.now()}`);
|
|
3716
3806
|
(0, import_node_fs4.mkdirSync)(tmpExtractDir, { recursive: true });
|
|
3717
3807
|
try {
|
|
3718
|
-
await (
|
|
3719
|
-
const
|
|
3720
|
-
const sourceDir = (0, import_node_fs4.existsSync)(buildBinDir) ? buildBinDir : tmpExtractDir;
|
|
3721
|
-
let copiedCount = 0;
|
|
3722
|
-
for (const file of await (0, import_promises.readdir)(sourceDir)) {
|
|
3723
|
-
if (!isWantedFile(file))
|
|
3724
|
-
continue;
|
|
3725
|
-
const src = (0, import_node_path4.join)(sourceDir, file);
|
|
3726
|
-
const dest = (0, import_node_path4.join)(binDir(), file);
|
|
3727
|
-
await (0, import_promises.cp)(src, dest, { force: true });
|
|
3728
|
-
copiedCount++;
|
|
3729
|
-
}
|
|
3808
|
+
const extractedDir = await extractArchive(archivePath, tmpExtractDir, assetName);
|
|
3809
|
+
const copiedCount = await installExtractedBinaries(extractedDir);
|
|
3730
3810
|
if (copiedCount === 0) {
|
|
3731
3811
|
throw new Error(`No binaries found in archive ${assetName}`);
|
|
3732
3812
|
}
|
|
@@ -4350,7 +4430,7 @@ async function embedBatchInner(texts) {
|
|
|
4350
4430
|
async function disposeEmbedder() {
|
|
4351
4431
|
await killServer();
|
|
4352
4432
|
}
|
|
4353
|
-
var import_node_child_process, import_node_fs4, import_promises, import_node_http, import_node_path4, 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;
|
|
4433
|
+
var import_node_child_process, import_node_fs4, import_promises, import_node_http, import_node_path4, 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;
|
|
4354
4434
|
var init_embedder = __esm({
|
|
4355
4435
|
"src/recall/embedder.ts"() {
|
|
4356
4436
|
"use strict";
|
|
@@ -4372,6 +4452,7 @@ var init_embedder = __esm({
|
|
|
4372
4452
|
BATCH_SEPARATOR = "<#sep#>";
|
|
4373
4453
|
MAX_ARG_BYTES = (0, import_node_os2.platform)() === "win32" ? 0 : 1e5;
|
|
4374
4454
|
LLAMA_RELEASE_TAG = "b5300";
|
|
4455
|
+
MACOS_MIN_VERSION = { arm64: "14.0", x64: "13.7" };
|
|
4375
4456
|
BIN_NAME = (0, import_node_os2.platform)() === "win32" ? "llama-embedding.exe" : "llama-embedding";
|
|
4376
4457
|
SERVER_BIN_NAME = (0, import_node_os2.platform)() === "win32" ? "llama-server.exe" : "llama-server";
|
|
4377
4458
|
SERVER_SUPPORTED = (0, import_node_os2.platform)() !== "win32";
|
|
@@ -4382,6 +4463,7 @@ var init_embedder = __esm({
|
|
|
4382
4463
|
HTTP_REQUEST_TIMEOUT_MS = 12e4;
|
|
4383
4464
|
SERVER_KILL_TIMEOUT_MS = 5e3;
|
|
4384
4465
|
SERVER_COOLDOWN_MS = 6e4;
|
|
4466
|
+
EXTRACT_TIMEOUT_MS = 12e4;
|
|
4385
4467
|
embedQueue = [];
|
|
4386
4468
|
embedRunning = false;
|
|
4387
4469
|
binaryPath = null;
|
|
@@ -4681,7 +4763,10 @@ var BindingLoadError = class extends Error {
|
|
|
4681
4763
|
constructor(dbPath2, cause) {
|
|
4682
4764
|
super(
|
|
4683
4765
|
`recall: failed to load the better-sqlite3 native binding while opening ${dbPath2}: ${cause.message}
|
|
4684
|
-
The binding is ABI-locked to the Node it was built for
|
|
4766
|
+
The native SQLite binding is ABI-locked to the Node it was built for. This is usually one of:
|
|
4767
|
+
\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\`.
|
|
4768
|
+
\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\`).
|
|
4769
|
+
Run \`recall doctor\` for details.`
|
|
4685
4770
|
);
|
|
4686
4771
|
this.dbPath = dbPath2;
|
|
4687
4772
|
this.cause = cause;
|