skills/chat-history-skill/chat-history/SKILL.md
Search previous AI chat conversations from Cursor IDE and Claude Code by content, affected file, or project. Use when the user asks about previous conversations, wants to find how they solved something before, or needs to recall past AI interactions.
npx skillsauth add vltansky/skills chat-historyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Search through previous AI chat conversations stored locally by Cursor IDE and Claude Code.
IMPORTANT: Chat histories can be massive. Never load full conversations into context unnecessarily.
Two-Stage Approach:
Native Tools vs Bash:
For Claude Code JSONL files, prefer native tools when possible:
| Bash | Native Tool | Benefit |
|------|-------------|---------|
| head -10 | Read with limit: 10 | Cross-platform |
| tail -10 | Read with offset: -10 | No escaping |
| sed -n '20,30p' | Read with offset: 20, limit: 10 | Cleaner |
| grep -c | Grep with output_mode: count | Native |
| grep "pattern" | Grep with output_mode: content | Built-in |
Bash is still useful for: pipes, parallel ops, complex jq transforms.
Main Agent vs Subagent: | Scenario | Use | Why | |----------|-----|-----| | Simple/quick search | Main | Low overhead | | Results affect current task | Main | Need context continuity | | Interactive refinement needed | Main | "Not that, find another" | | Discuss findings with user | Main | Back-and-forth | | Heavy cross-project search | Subagent | Keep main context clean | | Background research | Subagent | Don't pollute main context | | One-off historical lookup | Subagent | Return summary only |
Subagent Prompt Template (when appropriate):
Search my chat history for conversations about {topic}.
Find relevant conversations, extract key solutions/code, and summarize.
Return: conversation IDs, brief summary, relevant code snippets.
Do not return raw data - only summarized findings.
| Tool | Storage | macOS | Linux | Windows |
|------|---------|-------|-------|---------|
| Cursor | SQLite | ~/Library/Application Support/Cursor/User/globalStorage/state.vscdb | ~/.config/Cursor/User/globalStorage/state.vscdb | %APPDATA%\Cursor\User\globalStorage\state.vscdb |
| Claude Code | JSONL | ~/.claude/projects/ | ~/.claude/projects/ | %USERPROFILE%\.claude\projects\ |
# Detect which exists (cross-platform)
# macOS
ls "$HOME/Library/Application Support/Cursor/User/globalStorage/state.vscdb" 2>/dev/null && echo "Cursor (macOS)"
# Linux
ls "$HOME/.config/Cursor/User/globalStorage/state.vscdb" 2>/dev/null && echo "Cursor (Linux)"
# Claude Code (same on macOS/Linux)
ls ~/.claude/projects/ 2>/dev/null && echo "Claude Code"
Returns IDs + metadata only. Never returns full conversation content.
SELECT
SUBSTR(key, 14) as id,
LENGTH(value) as size,
json_extract(value, '$.name') as title
FROM cursorDiskKV
WHERE key LIKE 'composerData:%'
ORDER BY ROWID DESC LIMIT 10;
SELECT
SUBSTR(key, 14) as id,
LENGTH(value) as size,
SUBSTR(value, MAX(1, INSTR(value, 'keyword') - 50), 150) as excerpt
FROM cursorDiskKV
WHERE key LIKE 'composerData:%'
AND value LIKE '%keyword%'
ORDER BY ROWID DESC LIMIT 10;
SELECT SUBSTR(key, 14) as id, LENGTH(value) as size
FROM cursorDiskKV
WHERE key LIKE 'composerData:%'
AND value LIKE '%keyword1%'
AND value LIKE '%keyword2%'
ORDER BY ROWID DESC LIMIT 10;
SELECT SUBSTR(key, 14) as id, LENGTH(value) as size, json_extract(value, '$.name') as title
FROM cursorDiskKV
WHERE key LIKE 'composerData:%'
AND value LIKE '%src/components/Button%'
ORDER BY ROWID DESC LIMIT 10;
SELECT SUBSTR(key, 14) as id, LENGTH(value) as size
FROM cursorDiskKV
WHERE key LIKE 'composerData:%'
AND value LIKE '%"suggestedCodeBlocks":%'
AND value LIKE '%"code":%'
ORDER BY ROWID DESC LIMIT 10;
After finding relevant IDs, extract only what's needed.
SELECT
json_extract(value, '$.name') as title,
json_extract(value, '$.latestConversationSummary.summary.summary') as ai_summary,
json_extract(value, '$.composerId') as id
FROM cursorDiskKV
WHERE key = 'composerData:{id}';
SELECT SUBSTR(value, MAX(1, INSTR(value, 'keyword') - 200), 500) as excerpt
FROM cursorDiskKV
WHERE key = 'composerData:{id}';
SELECT json_array_length(json_extract(value, '$.conversation')) as message_count
FROM cursorDiskKV
WHERE key = 'composerData:{id}';
SELECT json_extract(value, '$.conversation[0].text') as msg1,
json_extract(value, '$.conversation[1].text') as msg2,
json_extract(value, '$.conversation[2].text') as msg3
FROM cursorDiskKV
WHERE key = 'composerData:{id}';
-- WARNING: Loads entire JSON blob - can be 100KB-500KB per conversation
-- Prefer json_extract() for specific fields when possible
SELECT value FROM cursorDiskKV WHERE key = 'composerData:{id}';
Prefer partial extraction:
-- Instead of full load, extract only what you need:
SELECT
json_extract(value, '$.name') as title,
json_extract(value, '$.conversation[0].text') as first_msg,
json_extract(value, '$.conversation[1].text') as first_reply
FROM cursorDiskKV WHERE key = 'composerData:{id}';
For complex queries, use SQLite's full power:
-- Extract nested field
json_extract(value, '$.conversation[0].text')
-- Array length
json_array_length(json_extract(value, '$.conversation'))
-- Check if field exists
json_type(value, '$.name') IS NOT NULL
-- Extract excerpt around match (MAX prevents negative index)
SUBSTR(value, MAX(1, INSTR(value, 'keyword') - 100), 300)
-- Get position of match
INSTR(value, 'search_term')
-- Case-insensitive search
value LIKE '%keyword%' COLLATE NOCASE
-- By size (substantial conversations)
ORDER BY LENGTH(value) DESC
-- By recency (chronological)
ORDER BY ROWID DESC
-- Combined score
ORDER BY (LENGTH(value) / 1000) + (ROWID / 1000000) DESC
-- Count total
SELECT COUNT(*) FROM cursorDiskKV WHERE key LIKE 'composerData:%';
-- Count by criteria
SELECT COUNT(*) FROM cursorDiskKV
WHERE key LIKE 'composerData:%' AND value LIKE '%typescript%';
Returns file paths only, not content. Tip: Filter by project first for speed.
# All projects (slower ~2s)
grep -rl "keyword" ~/.claude/projects/ --include="*.jsonl" | head -10
# Specific project (faster)
grep -rl "keyword" ~/.claude/projects/-Users-me-myproject/ --include="*.jsonl"
grep -rl "keyword1" ~/.claude/projects/ --include="*.jsonl" | xargs grep -l "keyword2" | head -10
grep -rl "src/components/Button" ~/.claude/projects/ --include="*.jsonl" | head -10
# List all projects with conversations
ls ~/.claude/projects/
# List conversations for specific project
# Path encoding: /Users/me/my-project → -Users-me-my-project
ls ~/.claude/projects/-Users-me-my-project/
# All conversations for a project
find ~/.claude/projects/-Users-me-my-project/ -name "*.jsonl" -type f
# Last N conversations for a project (by modification time)
ls -lt ~/.claude/projects/-Users-me-my-project/*.jsonl | head -2
# Search within a project only (fast)
grep -rl "keyword" ~/.claude/projects/-Users-me-my-project/ --include="*.jsonl"
# Which tools were used on a specific file and how many times?
FILE=~/.claude/projects/{project}/{conversation}.jsonl
TARGET="src/components/Button.tsx"
grep "$TARGET" "$FILE" | jq -r '
.message.content[]? | select(.type=="tool_use") |
"\(.name): \(.input.file_path // "other")"
' 2>/dev/null | sort | uniq -c | sort -rn
Output example: 15 Edit: /path/to/file.tsx, 9 Read: /path/to/file.tsx
find ~/.claude/projects/ -name "*.jsonl" -mtime -7 -type f | head -20
find ~/.claude/projects/ -name "*.jsonl" -type f -exec ls -lh {} + | sort -k6,7 -r | head -20
wc -l < ~/.claude/projects/{project}/{file}.jsonl
grep "keyword" ~/.claude/projects/{project}/{file}.jsonl
grep -B1 -A1 "keyword" ~/.claude/projects/{project}/{file}.jsonl
# First 10 messages
head -10 ~/.claude/projects/{project}/{file}.jsonl
# Last 10 messages
tail -10 ~/.claude/projects/{project}/{file}.jsonl
# Messages 20-30
sed -n '20,30p' ~/.claude/projects/{project}/{file}.jsonl
# All user messages (streams line-by-line, doesn't load full file)
grep '"type":"user"' ~/.claude/projects/{project}/{file}.jsonl | jq -r '.message.content'
# First 5 user messages only
grep -m 5 '"type":"user"' ~/.claude/projects/{project}/{file}.jsonl | jq -r '.message.content'
# First 3 assistant text responses
grep -m 3 '"type":"assistant"' ~/.claude/projects/{project}/{file}.jsonl | jq -r '.message.content[] | select(.type=="text") | .text'
FILE=~/.claude/projects/{project}/{file}.jsonl
echo "total: $(wc -l < "$FILE")" &
echo "user: $(grep -c '"type":"user"' "$FILE")" &
echo "assistant: $(grep -c '"type":"assistant"' "$FILE")" &
wait
Note: Parallel greps (~300ms) beat jq -s (~235ms). For files >50MB, streaming is essential to avoid memory issues.
# "How did I fix this error before?"
grep -rl "Cannot find module" ~/.claude/projects/ --include="*.jsonl" | head -5
# "What commands did I run?"
grep '"Bash"' {file}.jsonl | jq -r '.message.content[]? | select(.name=="Bash") | .input.command' | head -10
# "What did I research?"
grep "WebSearch" {file}.jsonl | jq -r '.message.content[]? | select(.name=="WebSearch") | .input.query'
grep '"is_error":true' {file}.jsonl | jq -r '.content[0:200]'
# "What projects did I work on?"
find ~/.claude/projects/ -name "*.jsonl" -mtime -7 -type f | \
sed 's|.*/projects/||; s|/.*||' | sort | uniq -c | sort -rn | head -10
# "What complex tasks did I delegate?"
grep '"Task"' {file}.jsonl | jq -r '.message.content[]? | select(.name=="Task") | .input.prompt[0:100]'
For complex extraction, use jq's full power:
jq 'select(.type=="assistant")' file.jsonl
jq '{type, content: .message.content[0:200]}' file.jsonl
jq 'select(.message.content | contains("Button.tsx"))' file.jsonl
jq 'select(.type=="tool_use") | {tool: .name, input: .input}' file.jsonl
jq '.message.content = .message.content[0:300]' file.jsonl
# WARNING: jq -s loads entire file into memory - avoid on files >10MB
cat file.jsonl | jq -s 'group_by(.type) | map({type: .[0].type, count: length})'
| Operation | Speed | Notes |
|-----------|-------|-------|
| grep -m 5 | Instant | Stop after 5 matches |
| head -N / tail -N | Instant | First/last lines |
| wc -l | ~40ms/37MB | Line count |
| grep -c | ~70ms/37MB | Match count |
| Parallel greps | ~300ms/37MB | Multiple counts at once |
| jq -s | ~235ms/37MB | Fine for files <50MB |
| LIMIT N | Fast | SQLite result cap |
| Operation | Speed | Problem | Alternative |
|-----------|-------|---------|-------------|
| grep -rl all projects | ~2.3s | Scans everything | Filter by project first |
| jq -s on 100MB+ | Seconds | Memory pressure | Use streaming grep |
| Sequential greps | ~410ms/37MB | 3 file scans | Run in parallel with & |
Filter by project for 10x faster searches:
/Users/me/my-project → ~/.claude/projects/-Users-me-my-project/
LIMIT and head -N to cap resultsgrep -c for counts instead of jq -s | lengthjq -s on files >10MBgrep -rl across all projects without | head| Task | Cursor | Claude Code |
|------|--------|-------------|
| List IDs | SELECT SUBSTR(key,14) ... LIMIT 10 | find ... \| head |
| Search | value LIKE '%x%' LIMIT 10 | grep -rl "x" \| head -10 |
| Excerpt | SUBSTR(value, MAX(1,INSTR-100), 300) | grep -B1 -A1 |
| Count | json_array_length(...) | grep -c (fast) |
| Specific field | json_extract(value, '$.path') | grep + jq |
| First N | json_extract(value, '$[0]') | head -N |
cursor-schema.md - Cursor database schema and JSON formatclaude-code-schema.md - Claude Code JSONL format documentationtools
Prepare a Hetzner Cloud VPS for secure Codex remote SSH access. Use when the user wants to create or configure a Hetzner server for Codex remote control, fix "No codex found in PATH" on a remote machine, install agent development tooling on a VPS, harden SSH access to a Hetzner server, or connect the server through Codex Settings, Connections, Add SSH.
data-ai
Summarize your GitHub activity from the last 24 hours across all repos. Use when user says "what did I do", "my activity", "standup", "recap", "summarize my day", "what-i-did", "git activity", "daily summary".
development
Test-driven development loop. Write failing test first, then implement to make it pass. Use when the user says 'tdd', 'test first', 'write the test first', 'failing test', 'red green refactor', or for any bug fix where the fix should be proven by a test. Also use when autopilot or other skills need test-first execution.
development
Review changed code for reuse, quality, and efficiency, then fix any issues found. Use when the user says "simplify", "simplify this", "review changes", "clean up my code", "check for duplicates", "code reuse review", or wants a post-change quality sweep.