Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 0.5.0

Closes #9.

- Add **Cursor Agent** session support (Cursor IDE + `agent` CLI)
- Indexes `~/.cursor/projects/*/agent-transcripts/<uuid>/<uuid>.jsonl`
- New `--source cursor` filter; results tagged `[cursor]`
- Decodes workspace slug to filesystem path for `--project` matching
- Session timestamps use file mtime (Cursor JSONL has no message timestamps)
- `read_session.py` detects Cursor transcripts by path under `agent-transcripts/`
- Resume: `agent --resume <session-id>` (see SKILL.md)

### Upgrading to 0.5.0

Run `--reindex` once to pull Cursor sessions into the index:

```bash
python3 ~/.claude/skills/recall/scripts/recall.py --reindex "test"
```

## 0.4.1

- Make the positional `query` argument optional. When omitted, list every
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# recall

Ever lost a conversation session with Claude Code, Codex, or pi and wish you could resume it? This skill lets your agents search across all your past conversations with full-text search. Builds a SQLite FTS5 index over `~/.claude/projects/`, `~/.codex/sessions/`, and `~/.pi/agent/sessions/` with BM25 ranking, Porter stemming, CJK support, and incremental updates.
Ever lost a conversation session with Claude Code, Codex, pi, or Cursor Agent and wish you could resume it? This skill lets your agents search across all your past conversations with full-text search. Builds a SQLite FTS5 index over `~/.claude/projects/`, `~/.codex/sessions/`, `~/.pi/agent/sessions/`, and `~/.cursor/projects/*/agent-transcripts/` with BM25 ranking, Porter stemming, CJK support, and incremental updates.

## Install

Expand All @@ -14,11 +14,13 @@ Then use `/recall` in Claude Code (or Codex, or pi) or ask "find a past session
### Index

```
~/.claude/projects/**/*.jsonl ──┐
~/.codex/sessions/**/*.jsonl ───┼─▶ Index ──▶ ~/.recall.db (SQLite FTS5)
│ [incremental - mtime-based]
~/.pi/agent/sessions/**/*.jsonl ┘
~/.claude/projects/**/*.jsonl ──────────────┐
~/.codex/sessions/**/*.jsonl ───────────────┼─▶ Index ──▶ ~/.recall.db (SQLite FTS5)
│ [incremental - mtime-based]
~/.pi/agent/sessions/**/*.jsonl ────────────┤
~/.cursor/projects/**/agent-transcripts/ ───┘
```
### Query
```
Expand All @@ -43,7 +45,7 @@ Then use `/recall` in Claude Code (or Codex, or pi) or ask "find a past session
- CJK messages are selectively indexed into the trigram table; query routing is automatic
- Skips tool_use, tool_result, thinking, and image blocks
- Results ranked by BM25 with a slight recency bias (recent sessions get up to a 20% boost, decaying with a 30-day half-life)
- Results tagged `[claude]`, `[codex]`, or `[pi]` with highlighted excerpts
- Results tagged `[claude]`, `[codex]`, `[pi]`, or `[cursor]` with highlighted excerpts
- No dependencies — Python 3.9+ stdlib only (sqlite3, json, argparse)

## Tests
Expand All @@ -53,9 +55,9 @@ python3 -m unittest discover tests -v
```

Stdlib `unittest` only — no test deps. Synthetic JSONL fixtures generated
in `tmpdir` from the suite itself (no fixture files committed). An
integration test runs against any real pi sessions in `~/.pi/agent/sessions/`
on the host and is skipped if none are present.
in `tmpdir` from the suite itself (no fixture files committed). Integration
tests run against real sessions on the host when present (`~/.pi/agent/sessions/`
for pi, `~/.cursor/projects/` for Cursor) and are skipped otherwise.

## Contributing

Expand Down
44 changes: 32 additions & 12 deletions SKILL.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
---
name: recall
description: >
Search past Claude Code, Codex, and pi sessions. Triggers: /recall, "search old conversations",
"find a past session", "recall a previous conversation", "search session history",
"what did we discuss", "remember when we"
Search past Claude Code, Codex, pi, and Cursor Agent sessions. Triggers: /recall,
"search old conversations", "find a past session", "recall a previous conversation",
"search session history", "what did we discuss", "remember when we"
metadata:
author: arjunkmrm
version: "0.4.1"
version: "0.5.0"
license: MIT
---

# /recall — Search Past Claude, Codex & pi Sessions
# /recall — Search Past Claude, Codex, pi & Cursor Sessions

Search all past Claude Code, Codex, and pi sessions using full-text search with BM25 ranking.
Search all past Claude Code, Codex, pi, and **Cursor Agent** (IDE + CLI) sessions using full-text search with BM25 ranking.

## Usage

```bash
python3 ~/.claude/skills/recall/scripts/recall.py [QUERY] [--project PATH] [--days N] [--source claude|codex|pi] [--limit N] [--reindex]
python3 ~/.claude/skills/recall/scripts/recall.py [QUERY] [--project PATH] [--days N] [--source claude|codex|pi|cursor] [--limit N] [--reindex]
```

## Examples
Expand All @@ -26,8 +26,8 @@ python3 ~/.claude/skills/recall/scripts/recall.py [QUERY] [--project PATH] [--da
# List every session in the last day (no text search)
python3 ~/.claude/skills/recall/scripts/recall.py --days 1

# List every pi session in the last week
python3 ~/.claude/skills/recall/scripts/recall.py --days 7 --source pi
# List every Cursor session in the last week
python3 ~/.claude/skills/recall/scripts/recall.py --days 7 --source cursor

# Simple keyword search
python3 ~/.claude/skills/recall/scripts/recall.py "bufferStore"
Expand All @@ -53,7 +53,10 @@ python3 ~/.claude/skills/recall/scripts/recall.py "buffer" --source codex
# Search only pi sessions
python3 ~/.claude/skills/recall/scripts/recall.py "buffer" --source pi

# Force reindex
# Search only Cursor Agent sessions (IDE + CLI)
python3 ~/.claude/skills/recall/scripts/recall.py "sorting" --source cursor --project quick-gtasks

# Force reindex (required once after upgrading to 0.5.0 for Cursor)
python3 ~/.claude/skills/recall/scripts/recall.py --reindex "test"
```

Expand Down Expand Up @@ -82,6 +85,12 @@ codex resume SESSION_ID
# Pi sessions [pi]
cd /path/to/project
pi --session SESSION_ID # full or partial id; pi resolves prefix matches

# Cursor Agent sessions [cursor] — IDE or CLI (agent)
cd /path/to/project
agent --resume SESSION_ID # UUID from recall output
# or: agent resume # latest session
# or: agent ls # pick interactively
```

Each result includes a `File:` path. Use it to read the raw transcript (auto-detects format):
Expand All @@ -95,12 +104,23 @@ If results are missing `File:` paths, run `--reindex` to backfill.
## Notes

- Index is stored at `~/.recall.db` (SQLite FTS5, auto-migrated from `~/.claude/recall.db`)
- Indexes three sources: `~/.claude/projects/` (Claude Code), `~/.codex/sessions/` (Codex), and `~/.pi/agent/sessions/` (pi)
- Indexes four sources:
- `~/.claude/projects/` — Claude Code
- `~/.codex/sessions/` — Codex
- `~/.pi/agent/sessions/` — pi
- `~/.cursor/projects/*/agent-transcripts/` — **Cursor Agent** (IDE + `agent` CLI)
- First run indexes all sessions (a few seconds); subsequent runs are incremental
- Only user and assistant messages are indexed (tool calls, thinking blocks, state snapshots skipped)
- Results show `[claude]`, `[codex]`, or `[pi]` tags to indicate the source
- Results show `[claude]`, `[codex]`, `[pi]`, or `[cursor]` tags to indicate the source
- Dual-table FTS: English queries use Porter stemming, CJK queries use trigram matching
- Omit the query argument for **list mode** — every session in the window, sorted by recency, no FTS
- Provide a query for full-text search; both modes accept `--project`, `--days`, `--source`, `--limit`
- **Upgrading to 0.5.0**: run `--reindex` once to pull in Cursor Agent sessions
- **Upgrading from 0.3.x**: run `--reindex` once to pull in pi sessions
- **Upgrading from 0.2.x**: run `--reindex` once to build the CJK index

## Cursor-specific behavior

- **Project path**: prefer `working_directory` from Shell tool calls in the transcript; fall back to decoding the encoded folder name under `~/.cursor/projects/` (hyphens in folder names like `quick-gtasks` are not always recoverable from the slug alone).
- Session timestamps use file modification time (Cursor JSONL has no per-message timestamps).
- `--project` matches path prefix **or** substring (e.g. `quick-gtasks` matches `C:\Users\you\src\quick-gtasks`).
32 changes: 24 additions & 8 deletions scripts/read_session.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Pretty-print a Claude Code, Codex, or pi session transcript."""
"""Pretty-print a Claude Code, Codex, pi, or Cursor Agent session transcript."""

import json
import sys
Expand Down Expand Up @@ -44,7 +44,16 @@ def iter_messages(path):
if entry.get("record_type") == "state":
continue

if fmt == "pi":
if fmt == "cursor":
role = entry.get("role", "")
if role not in ("user", "assistant"):
continue
msg = entry.get("message", {})
if not isinstance(msg, dict):
continue
content = msg.get("content", "")

elif fmt == "pi":
# Pi: {type, id, parentId, timestamp, message: {role, content, ...}}
# Header is {type: "session", id, cwd, version, ...} — skip.
etype = entry.get("type", "")
Expand Down Expand Up @@ -105,14 +114,19 @@ def iter_messages(path):


def detect_format(path):
"""Detect whether a session file is Claude Code, Codex, or pi format.
"""Detect whether a session file is Claude Code, Codex, pi, or Cursor format.

Detection runs on the first non-empty parseable line and returns one of
"pi", "claude", or "codex". Order matters: pi headers carry both `type:
"session"` and `cwd`, which is the most distinctive signature; Claude
files have `parentUuid` or a top-level `message`; Codex files have
`record_type`, `instructions`, or `type: "session_meta"`.
"cursor", "pi", "claude", or "codex". Cursor transcripts live under
~/.cursor/projects/.../agent-transcripts/; pi headers carry `type:
"session"` and `cwd`; Claude files have `parentUuid` or a top-level
`message`; Codex files have `record_type`, `instructions`, or
`type: "session_meta"`.
"""
norm = str(path).replace("\\", "/")
if "/.cursor/projects/" in norm and "/agent-transcripts/" in norm:
return "cursor"

with open(path, "r", encoding="utf-8", errors="replace") as f:
for line in f:
line = line.strip()
Expand Down Expand Up @@ -145,7 +159,9 @@ def detect_format(path):

def main():
import argparse
parser = argparse.ArgumentParser(description="Pretty-print a Claude Code, Codex, or pi session transcript")
parser = argparse.ArgumentParser(
description="Pretty-print a Claude Code, Codex, pi, or Cursor Agent session transcript"
)
parser.add_argument("path", help="Path to a session .jsonl file")
parser.add_argument("--pretty", action="store_true", help="Human-readable output instead of JSON")
args = parser.parse_args()
Expand Down
Loading