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 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 in v0.1.0
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
- in v0.1.0.
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
- $RECALL_BIN "the thing you're trying to remember"
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
- $RECALL_BIN <session-id> <message-id>
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, no network calls except the one-time
90
- binary + model download at install. The DB is plain SQLite — open it with any
91
- SQLite browser if you want to poke around.
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
 
@@ -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.
@@ -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([(0, import_node_path7.join)(claudeRoot, "projects", "**", "*.jsonl"), "claude"]);
13216
+ patterns.push([transcriptGlob(claudeRoot, "projects", "**", "*.jsonl"), "claude"]);
13207
13217
  if (vendors.includes("codex"))
13208
- patterns.push([(0, import_node_path7.join)(codexRoot, "sessions", "**", "*.jsonl"), "codex"]);
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([(0, import_node_path6.join)(claudeRoot, "projects", "**", "*.jsonl"), "claude"]);
13770
+ patterns.push([transcriptGlob(claudeRoot, "projects", "**", "*.jsonl"), "claude"]);
13761
13771
  if (vendors.includes("codex"))
13762
- patterns.push([(0, import_node_path6.join)(codexRoot, "sessions", "**", "*.jsonl"), "codex"]);
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((0, import_node_path7.join)(claudeRoot, "projects", "**", "*.jsonl"), { nodir: true });
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((0, import_node_path7.join)(codexRoot, "sessions", "**", "*.jsonl"), { nodir: true });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crispy-recall",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Local session transcript memory for Claude Code and Codex — search past sessions with FTS5 + semantic vectors.",
5
5
  "license": "MIT",
6
6
  "author": "Sylvester Wong",