crispy-recall 0.1.3 → 0.1.5
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 +31 -9
- package/dist/SKILL.md.template +34 -0
- package/dist/embed-pending.js +14 -4
- package/dist/recall.js +17 -6
- package/dist/stop-hook.js +9 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ recall install
|
|
|
19
19
|
llama embedding binary, the model, and the SQLite DB), wires a Stop hook into
|
|
20
20
|
Claude Code so your transcripts are indexed automatically as sessions end, and
|
|
21
21
|
installs the `recall` skill. If Codex is detected it also gets the `recall`
|
|
22
|
-
skill (so the agent can search), but not an automatic per-turn hook
|
|
22
|
+
skill (so the agent can search), but not an automatic per-turn hook (yet) —
|
|
23
23
|
index Codex history with `recall backfill --vendor codex`. After that, recall
|
|
24
24
|
runs passively — you only invoke `recall` directly for `status`, `doctor`,
|
|
25
25
|
`repair`, or `uninstall`.
|
|
@@ -37,8 +37,8 @@ Windows-native are separate installs.** If you use both, run the install in each
|
|
|
37
37
|
Prerequisites: Node ≥ 20 and Claude Code installed. If Codex is detected
|
|
38
38
|
(`~/.codex/` exists), recall installs the `recall` skill into Codex (so the
|
|
39
39
|
agent can search) and you can index your Codex history with
|
|
40
|
-
`recall backfill --vendor codex`. Real-time per-turn Codex indexing is **not
|
|
41
|
-
|
|
40
|
+
`recall backfill --vendor codex`. Real-time per-turn Codex indexing is **not
|
|
41
|
+
yet** supported.
|
|
42
42
|
|
|
43
43
|
## What it does
|
|
44
44
|
|
|
@@ -52,23 +52,38 @@ in v0.1.0.
|
|
|
52
52
|
Two steps. First, search for the relevant session:
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
-
|
|
55
|
+
recall "the thing you're trying to remember"
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
That returns a table of matching sessions. Then read one, centered on the match:
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
|
-
|
|
61
|
+
recall <session-id> <message-id>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Search and list default to the **current directory's project** — from inside one
|
|
65
|
+
repo you only see that repo's sessions. Add `--all` to search across every
|
|
66
|
+
indexed project (use it for cross-repo questions, or when a scoped search comes
|
|
67
|
+
back thin), or `--project <path>` to target a specific repo regardless of where
|
|
68
|
+
you are:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
recall --all "the thing you're trying to remember"
|
|
62
72
|
```
|
|
63
73
|
|
|
64
74
|
The skill's frontmatter teaches Claude *when* to reach for this (before
|
|
65
75
|
non-trivial tasks, architectural decisions, or work with obvious prior
|
|
66
|
-
history), so in practice the agent calls it for you.
|
|
76
|
+
history), so in practice the agent calls it for you. (Inside the installed skill
|
|
77
|
+
the command is written out as `node ~/.recall/bin/recall.js` so it works even
|
|
78
|
+
when `recall` isn't on the agent's `PATH` — `$RECALL_BIN` in the skill source is
|
|
79
|
+
a placeholder the installer substitutes, not an environment variable you set.)
|
|
67
80
|
|
|
68
81
|
## Commands
|
|
69
82
|
|
|
70
83
|
| Command | What it does |
|
|
71
84
|
|---|---|
|
|
85
|
+
| `recall "<query>" [--all] [--project <path>]` | Search past sessions (FTS5 + semantic). Defaults to the current project; `--all` searches every indexed project, `--project` targets one. |
|
|
86
|
+
| `recall <session-id> [<message-id>]` | Read a session, optionally centered on a matched message. |
|
|
72
87
|
| `recall install` | One-time setup: scaffold `~/.recall/`, wire the Stop hook, install the skill. |
|
|
73
88
|
| `recall uninstall` | Reverse the install (skill, hook, CLAUDE.md block). `--purge` also removes `~/.recall/`. |
|
|
74
89
|
| `recall status` | DB size, message count, last ingest, embedding gap, active backfill PID, GPU/CPU backend. |
|
|
@@ -76,6 +91,11 @@ history), so in practice the agent calls it for you.
|
|
|
76
91
|
| `recall repair --fts \| --vectors \| --full` | Rebuild the FTS index, re-embed vectors, or full reingest from JSONL. |
|
|
77
92
|
| `recall backfill [--auto-embed] [--vendor <v>] [--detach]` | Index historical transcripts. |
|
|
78
93
|
|
|
94
|
+
Add `--json` to `install`/`uninstall`/`status`/`doctor` for machine-readable
|
|
95
|
+
output. `recall install` also takes `--offline` (use a pre-staged binary +
|
|
96
|
+
model instead of downloading) and `--no-backfill` / `--auto-backfill` to control
|
|
97
|
+
the initial history index. Run `recall --help` for the full flag set.
|
|
98
|
+
|
|
79
99
|
## Where things live
|
|
80
100
|
|
|
81
101
|
- `~/.recall/` — the DB, model, binary, logs, and `config.json` (the resolved GPU/CPU embedder mode).
|
|
@@ -86,9 +106,11 @@ history), so in practice the agent calls it for you.
|
|
|
86
106
|
|
|
87
107
|
## Privacy
|
|
88
108
|
|
|
89
|
-
Everything is local. No telemetry
|
|
90
|
-
binary + model download at install
|
|
91
|
-
|
|
109
|
+
Everything is local. No telemetry. The only data transfer is the one-time
|
|
110
|
+
binary + model download at install (from llama.cpp's GitHub releases and
|
|
111
|
+
HuggingFace); `recall install` and `recall doctor` also send lightweight
|
|
112
|
+
reachability probes to those two hosts. Nothing else leaves your machine. The DB
|
|
113
|
+
is plain SQLite — open it with any SQLite browser if you want to poke around.
|
|
92
114
|
|
|
93
115
|
## Troubleshooting
|
|
94
116
|
|
package/dist/SKILL.md.template
CHANGED
|
@@ -71,6 +71,12 @@ DATE HANDLING: Any date the user mentions MUST become a flag — never search te
|
|
|
71
71
|
--recent Boost recent sessions (use when user says 'recently', 'latest')
|
|
72
72
|
Example: \"what happened April 10\" → $RECALL_BIN --list --since 2026-04-10 --until 2026-04-10
|
|
73
73
|
|
|
74
|
+
PROJECT SCOPING: search and list default to the CURRENT directory's project.
|
|
75
|
+
--all Search across ALL projects (use when the answer may live
|
|
76
|
+
in another repo, or when a scoped search returns little)
|
|
77
|
+
--project PATH Scope to a specific project path instead of the CWD
|
|
78
|
+
If results look thin or empty, retry with --all before concluding nothing exists.
|
|
79
|
+
|
|
74
80
|
CRITICAL READING RULE: After searching, ALWAYS read the matched message:
|
|
75
81
|
$RECALL_BIN <session-id> <message-id>
|
|
76
82
|
This auto-centers on the match and shows surrounding turns. The output
|
|
@@ -192,6 +198,33 @@ $RECALL_BIN "old bug" --until 2026-03-15
|
|
|
192
198
|
`$RECALL_BIN --list --since 2026-04-10 --until 2026-04-10` to find all sessions,
|
|
193
199
|
then search with topic keywords + date flags if needed.
|
|
194
200
|
|
|
201
|
+
## Project scope
|
|
202
|
+
|
|
203
|
+
**Search and list default to the current working directory's project.** Recall
|
|
204
|
+
filters results to the project whose path is your CWD — so from inside one repo
|
|
205
|
+
you only see that repo's sessions. This keeps everyday recall focused, but it
|
|
206
|
+
silently hides history when the answer lives elsewhere.
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# Default — scoped to the CWD's project
|
|
210
|
+
$RECALL_BIN "auth refactor"
|
|
211
|
+
|
|
212
|
+
# Search every project (cross-repo questions, or when a scoped search is thin)
|
|
213
|
+
$RECALL_BIN "auth refactor" --all
|
|
214
|
+
|
|
215
|
+
# Scope to a specific project regardless of CWD
|
|
216
|
+
$RECALL_BIN "auth refactor" --project /home/me/dev/other-repo
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
- `--all` — disable scoping; search across all indexed projects
|
|
220
|
+
- `--project PATH` — scope to an explicit project path instead of the CWD
|
|
221
|
+
- Both work in search and list modes
|
|
222
|
+
|
|
223
|
+
**When to reach for `--all`:** the user asks about work that may span repos, the
|
|
224
|
+
question isn't tied to the current repo, or a default (scoped) search returns
|
|
225
|
+
few/zero results. A thin scoped result is often just the scope — retry with
|
|
226
|
+
`--all` before concluding the history doesn't exist.
|
|
227
|
+
|
|
195
228
|
## Recency boost
|
|
196
229
|
|
|
197
230
|
When the user says "recently", "latest", "last few days", or otherwise indicates
|
|
@@ -209,4 +242,5 @@ pushing recent sessions to the top of results. Combine with `--since` for best r
|
|
|
209
242
|
- **Search is cheap, reading is expensive.** Run 3-5 varied queries before committing to reading sessions.
|
|
210
243
|
- **Message IDs are stable.** You can reference them across searches.
|
|
211
244
|
- **Use `--since` / `--until` to scope.** Both search and list modes accept date flags.
|
|
245
|
+
- **Search is scoped to the CWD's project by default.** Add `--all` for cross-repo questions or when a scoped search comes back thin.
|
|
212
246
|
- **Raw JSON output** (`--raw`) is available for programmatic processing.
|
package/dist/embed-pending.js
CHANGED
|
@@ -57,6 +57,9 @@ function runDir() {
|
|
|
57
57
|
function ensureDir() {
|
|
58
58
|
(0, import_node_fs.mkdirSync)(recallRoot(), { recursive: true });
|
|
59
59
|
}
|
|
60
|
+
function transcriptGlob(...segments) {
|
|
61
|
+
return segments.join("/").replace(/\\/g, "/");
|
|
62
|
+
}
|
|
60
63
|
var import_node_fs, import_node_os, import_node_path, rootOverride;
|
|
61
64
|
var init_paths = __esm({
|
|
62
65
|
"src/paths.ts"() {
|
|
@@ -6416,9 +6419,14 @@ function extractEntryText(entry) {
|
|
|
6416
6419
|
}
|
|
6417
6420
|
return "";
|
|
6418
6421
|
}
|
|
6422
|
+
function entriesCwd(entries) {
|
|
6423
|
+
for (const e of entries) {
|
|
6424
|
+
if (e.cwd)
|
|
6425
|
+
return e.cwd;
|
|
6426
|
+
}
|
|
6427
|
+
return void 0;
|
|
6428
|
+
}
|
|
6419
6429
|
async function ingestSessionMessages(sessionId, transcriptPath, vendor, options) {
|
|
6420
|
-
const rawProjectId = options?.projectId ?? null;
|
|
6421
|
-
const projectId = rawProjectId ? normalizePath(rawProjectId) : null;
|
|
6422
6430
|
let rawEntries;
|
|
6423
6431
|
try {
|
|
6424
6432
|
if (vendor === "claude") {
|
|
@@ -6439,6 +6447,8 @@ async function ingestSessionMessages(sessionId, transcriptPath, vendor, options)
|
|
|
6439
6447
|
if (rawEntries.length === 0) {
|
|
6440
6448
|
return { sessionId, chunksCreated: 0, skipped: true };
|
|
6441
6449
|
}
|
|
6450
|
+
const rawProjectId = options?.projectId ?? entriesCwd(rawEntries);
|
|
6451
|
+
const projectId = rawProjectId ? normalizePath(rawProjectId) : null;
|
|
6442
6452
|
const filtered = stripToolContent(rawEntries);
|
|
6443
6453
|
const topLevel = filtered.filter((e) => !e.parentToolUseID);
|
|
6444
6454
|
const records = [];
|
|
@@ -13203,9 +13213,9 @@ async function mtimeScan(opts) {
|
|
|
13203
13213
|
const codexRoot = process.env["CODEX_HOME"] ?? (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".codex");
|
|
13204
13214
|
const patterns = [];
|
|
13205
13215
|
if (vendors.includes("claude"))
|
|
13206
|
-
patterns.push([(
|
|
13216
|
+
patterns.push([transcriptGlob(claudeRoot, "projects", "**", "*.jsonl"), "claude"]);
|
|
13207
13217
|
if (vendors.includes("codex"))
|
|
13208
|
-
patterns.push([(
|
|
13218
|
+
patterns.push([transcriptGlob(codexRoot, "sessions", "**", "*.jsonl"), "codex"]);
|
|
13209
13219
|
const db3 = getDb(dbPath());
|
|
13210
13220
|
const watermarks = /* @__PURE__ */ new Map();
|
|
13211
13221
|
const rows = db3.all("SELECT * FROM ingest_watermark");
|
package/dist/recall.js
CHANGED
|
@@ -2570,6 +2570,9 @@ function logsDir() {
|
|
|
2570
2570
|
function ensureDir() {
|
|
2571
2571
|
(0, import_node_fs.mkdirSync)(recallRoot(), { recursive: true });
|
|
2572
2572
|
}
|
|
2573
|
+
function transcriptGlob(...segments) {
|
|
2574
|
+
return segments.join("/").replace(/\\/g, "/");
|
|
2575
|
+
}
|
|
2573
2576
|
var import_node_fs, import_node_os, import_node_path, rootOverride;
|
|
2574
2577
|
var init_paths = __esm({
|
|
2575
2578
|
"src/paths.ts"() {
|
|
@@ -13613,9 +13616,14 @@ function extractEntryText(entry) {
|
|
|
13613
13616
|
}
|
|
13614
13617
|
return "";
|
|
13615
13618
|
}
|
|
13619
|
+
function entriesCwd(entries) {
|
|
13620
|
+
for (const e2 of entries) {
|
|
13621
|
+
if (e2.cwd)
|
|
13622
|
+
return e2.cwd;
|
|
13623
|
+
}
|
|
13624
|
+
return void 0;
|
|
13625
|
+
}
|
|
13616
13626
|
async function ingestSessionMessages(sessionId, transcriptPath, vendor, options) {
|
|
13617
|
-
const rawProjectId = options?.projectId ?? null;
|
|
13618
|
-
const projectId = rawProjectId ? normalizePath(rawProjectId) : null;
|
|
13619
13627
|
let rawEntries;
|
|
13620
13628
|
try {
|
|
13621
13629
|
if (vendor === "claude") {
|
|
@@ -13636,6 +13644,8 @@ async function ingestSessionMessages(sessionId, transcriptPath, vendor, options)
|
|
|
13636
13644
|
if (rawEntries.length === 0) {
|
|
13637
13645
|
return { sessionId, chunksCreated: 0, skipped: true };
|
|
13638
13646
|
}
|
|
13647
|
+
const rawProjectId = options?.projectId ?? entriesCwd(rawEntries);
|
|
13648
|
+
const projectId = rawProjectId ? normalizePath(rawProjectId) : null;
|
|
13639
13649
|
const filtered = stripToolContent(rawEntries);
|
|
13640
13650
|
const topLevel = filtered.filter((e2) => !e2.parentToolUseID);
|
|
13641
13651
|
const records = [];
|
|
@@ -13757,9 +13767,9 @@ async function mtimeScan(opts) {
|
|
|
13757
13767
|
const codexRoot = process.env["CODEX_HOME"] ?? (0, import_node_path6.join)((0, import_node_os3.homedir)(), ".codex");
|
|
13758
13768
|
const patterns = [];
|
|
13759
13769
|
if (vendors.includes("claude"))
|
|
13760
|
-
patterns.push([(
|
|
13770
|
+
patterns.push([transcriptGlob(claudeRoot, "projects", "**", "*.jsonl"), "claude"]);
|
|
13761
13771
|
if (vendors.includes("codex"))
|
|
13762
|
-
patterns.push([(
|
|
13772
|
+
patterns.push([transcriptGlob(codexRoot, "sessions", "**", "*.jsonl"), "codex"]);
|
|
13763
13773
|
const db4 = getDb(dbPath());
|
|
13764
13774
|
const watermarks = /* @__PURE__ */ new Map();
|
|
13765
13775
|
const rows = db4.all("SELECT * FROM ingest_watermark");
|
|
@@ -14556,7 +14566,7 @@ function listAllSessions(opts) {
|
|
|
14556
14566
|
const codexRoot = process.env["CODEX_HOME"] ?? (0, import_node_path7.join)((0, import_node_os4.homedir)(), ".codex");
|
|
14557
14567
|
const out = [];
|
|
14558
14568
|
if (vendors.includes("claude")) {
|
|
14559
|
-
const files = globSync((
|
|
14569
|
+
const files = globSync(transcriptGlob(claudeRoot, "projects", "**", "*.jsonl"), { nodir: true });
|
|
14560
14570
|
for (const file of files) {
|
|
14561
14571
|
out.push({
|
|
14562
14572
|
sessionId: sessionIdFromPath2(file, "claude"),
|
|
@@ -14567,7 +14577,7 @@ function listAllSessions(opts) {
|
|
|
14567
14577
|
}
|
|
14568
14578
|
}
|
|
14569
14579
|
if (vendors.includes("codex")) {
|
|
14570
|
-
const files = globSync((
|
|
14580
|
+
const files = globSync(transcriptGlob(codexRoot, "sessions", "**", "*.jsonl"), { nodir: true });
|
|
14571
14581
|
for (const file of files) {
|
|
14572
14582
|
out.push({
|
|
14573
14583
|
sessionId: sessionIdFromPath2(file, "codex"),
|
|
@@ -14593,6 +14603,7 @@ var init_session_manager_shim = __esm({
|
|
|
14593
14603
|
init_esm5();
|
|
14594
14604
|
import_node_os4 = require("node:os");
|
|
14595
14605
|
import_node_path7 = require("node:path");
|
|
14606
|
+
init_paths();
|
|
14596
14607
|
}
|
|
14597
14608
|
});
|
|
14598
14609
|
|
package/dist/stop-hook.js
CHANGED
|
@@ -2663,9 +2663,14 @@ function extractEntryText(entry) {
|
|
|
2663
2663
|
}
|
|
2664
2664
|
return "";
|
|
2665
2665
|
}
|
|
2666
|
+
function entriesCwd(entries) {
|
|
2667
|
+
for (const e of entries) {
|
|
2668
|
+
if (e.cwd)
|
|
2669
|
+
return e.cwd;
|
|
2670
|
+
}
|
|
2671
|
+
return void 0;
|
|
2672
|
+
}
|
|
2666
2673
|
async function ingestSessionMessages(sessionId, transcriptPath, vendor, options) {
|
|
2667
|
-
const rawProjectId = options?.projectId ?? null;
|
|
2668
|
-
const projectId = rawProjectId ? normalizePath(rawProjectId) : null;
|
|
2669
2674
|
let rawEntries;
|
|
2670
2675
|
try {
|
|
2671
2676
|
if (vendor === "claude") {
|
|
@@ -2686,6 +2691,8 @@ async function ingestSessionMessages(sessionId, transcriptPath, vendor, options)
|
|
|
2686
2691
|
if (rawEntries.length === 0) {
|
|
2687
2692
|
return { sessionId, chunksCreated: 0, skipped: true };
|
|
2688
2693
|
}
|
|
2694
|
+
const rawProjectId = options?.projectId ?? entriesCwd(rawEntries);
|
|
2695
|
+
const projectId = rawProjectId ? normalizePath(rawProjectId) : null;
|
|
2689
2696
|
const filtered = stripToolContent(rawEntries);
|
|
2690
2697
|
const topLevel = filtered.filter((e) => !e.parentToolUseID);
|
|
2691
2698
|
const records = [];
|
package/package.json
CHANGED