extract all your personal data history from cursor, codex, claude-code, windsurf, and trae
Python
1,352
22 commits
updated Oct 7, 2026
Complete toolkit to extract ALL chat, agent, and code context data from AI coding assistants for machine learning training.
Automatically discovers and extracts complete conversation history including:
Pi and OMP session exports use one shared parser. The default follows the active
branch; python3 extract_pi.py --all-branches and python3 extract_omp.py --all-branches
export separate leaf conversations with their ancestry and branch IDs. String
answers, custom context, reasoning, tool-call IDs, model changes, advisor/subagent
sessions and branch summaries are preserved. OMP also supports --prompt-history
for a separate read-only SQLite snapshot, including committed WAL rows.
extract_droid.py reads Factory/Droid sessions and settings under ~/.factory/sessions.
The aggregate script includes all three. The parser consolidation carries the
Pi/OMP work from PR #26, Joshua Warren's OMP coverage from #17 and Umang Bhalla's
Droid contribution from #7, with regression fixes and attribution.
extract_claude_code.pyExtracts from Claude Code / Claude Desktop
~/.claude, ~/.claude-code, ~/.claude-local, ~/.claude-m2, ~/.claude-zaiextract_codex.pyExtracts from Codex (if installed)
~/.codex, ~/.codex-localextract_cursor.pyExtracts from Cursor (Chat + Composer + Agent) - ALL VERSIONS
~/Library/Application Support/Cursor (macOS) or equivalentstate.vscdb, cursorDiskKV)extract_trae.pyExtracts from Trae
~/.trae, ~/Library/Application Support/Traeextract_windsurf.pyExtracts from Windsurf
~/Library/Application Support/Windsurf or equivalentextract_continue.pyExtracts from Continue AI Assistant
~/.continue/sessions/extract_gemini.pyExtracts from Google Gemini CLI
~/.gemini/tmp/[hash]/chats/extract_opencode.pyExtracts from OpenCode (CLI + Desktop)
~/.local/share/opencode/ (Linux), ~/Library/Application Support/opencode (macOS)~/.local/share/ai.opencode.app (Linux), ~/Library/Application Support/ai.opencode.app (macOS)opencode.db), JSON files (sessions/messages/parts), and Tauri .dat files (desktop)opencode.dbstorage/message, storage/part, and storage/sessionstorage/session_diff, storage/directory-readme, storage/agent-usage-reminder, and storage/rules-injectorextract_cursor_cli.pyExtracts from the cursor-agent terminal CLI (the Composer/Agent history that the GUI
extractor in extract_cursor.py does not cover)
~/.cursor/chats/<chatId>/<agentId>/store.db (all platforms)blobs table: id (sha256 hex) → data; message blobs are JSON ({role, content, id, ...}),
tree nodes are protobuf-framed lists of 32-byte child blob-id refsmeta table: hex-encoded JSON with agentId, latestRootBlobId, name, mode, createdAtlatestRootBlobId# No dependencies required - uses Python 3 standard library
python3 --version # Ensure Python 3.6+ is installed
# Extract from Claude Code
python3 extract_claude_code.py
# Extract from Cursor
python3 extract_cursor.py
# Extract from Codex
python3 extract_codex.py
# Extract from Trae
python3 extract_trae.py
# Extract from Windsurf
python3 extract_windsurf.py
# Extract from Continue
python3 extract_continue.py
# Extract from Gemini CLI
python3 extract_gemini.py
# Extract from OpenCode
python3 extract_opencode.py
# Extract from Cursor CLI (cursor-agent)
python3 extract_cursor_cli.py
# Extract from ALL tools at once
./extract_all.sh
All scripts create an extracted_data/ directory with timestamped JSONL files:
extracted_data/
├── claude_code_conversations_20250116_143022.jsonl
├── cursor_complete_20250116_143045.jsonl
├── gemini_conversations_20250116_143145.jsonl
├── codex_conversations_20250116_143102.jsonl
├── trae_conversations_20250116_143115.jsonl
├── windsurf_conversations_20250116_143130.jsonl
├── continue_conversations_20250116_143145.jsonl
└── opencode_conversations_20250116_143200.jsonl
Each conversation is a single JSON line in JSONL format:
{
"messages": [
{
"role": "user",
"content": "How do I fix this TypeScript error?",
"code_context": [
{
"file": "/Users/user/project/src/index.ts",
"code": "const x: string = 123;",
"range": {
"selectionStartLineNumber": 10,
"positionLineNumber": 10
}
}
],
"timestamp": "2025-01-16T14:30:22.123Z"
},
{
"role": "assistant",
"content": "The error occurs because you're assigning a number to a string type...",
"suggested_diffs": [...],
"model": "claude-sonnet-4-5",
"timestamp": "2025-01-16T14:30:25.456Z"
}
],
"source": "cursor-composer",
"name": "TypeScript Type Error Fix",
"created_at": 1705414222000
}
Each script follows this pattern:
~/Library/Application Support, ~/.config, ~/~/.config, ~/.local/share, ~/%APPDATA%, %LOCALAPPDATA%, ~/.vscdb, .db)~/.claude/projects/[project]/[session].jsonl~/Library/Application Support/Cursor/User/workspaceStorage/[hash]/state.vscdb~/Library/Application Support/Cursor/User/globalStorage/state.vscdbItemTable (Chat), cursorDiskKV (Composer/Agent)ItemTablecomposerData.conversation[])bubbleId:{composer}:{bubble} keys)workbench.panel.aichat.view.aichat.chatdata (Chat mode)composerData:{uuid} (Composer metadata + conversation)bubbleId:{composer}:{bubble} (Individual messages - transitional format)codeBlockDiff:{id} (Code block diffs)user: Human developer messagesassistant: AI assistant responsescode_context: File selections and code snippetssuggested_diffs: AI-proposed code changestool_use: Code execution, file operationstool_results: Execution outputs, diffs applieddiff_histories: Full edit historysource: Which tool (e.g., "cursor-composer", "claude-code")session_id/composer_id: Unique conversation IDproject_path: Working directorytimestamp: Message timemodel: AI model used (if available)# Combine all JSONL files
cat extracted_data/*.jsonl > all_conversations.jsonl
# Count total conversations
wc -l all_conversations.jsonl
# Count by source
grep -o '"source":"[^"]*"' all_conversations.jsonl | sort | uniq -c
corpus_to_skills.py samples extracted conversations and asks an
OpenAI-compatible chat-completions model to synthesize reusable
Agent Skills. Each result is written as
<skill-name>/SKILL.md with validated name and description metadata.
The command is local-first: unless OPENAI_BASE_URL is set, it connects to
http://127.0.0.1:8000/v1. Point it at a locally served model and a filtered
corpus:
python3 corpus_to_skills.py filtered_data \
--model your-local-model \
--skills 5 \
--output-dir generated_skills
You can also set SKILLS_MODEL instead of passing --model. The script uses
only Python's standard library, samples across the corpus with a configurable
character budget, treats corpus content as untrusted data, validates the model's
JSON response, and refuses to overwrite existing skills unless --overwrite
is passed.
Review every generated skill before installing it. Filter the extracted corpus first, especially before explicitly configuring a remote API endpoint; model output can still repeat sensitive source material despite prompt-level guards.
import json
from datetime import datetime
with open('extracted_data/cursor_complete_20250116.jsonl') as f:
for line in f:
conv = json.loads(line)
created = conv.get('created_at', 0)
if created > 1704067200000: # After Jan 1, 2024
print(json.dumps(conv))
import json
with open('extracted_data/cursor_complete.jsonl') as f:
for line in f:
conv = json.loads(line)
if any('suggested_diffs' in m or 'diff_histories' in m
for m in conv['messages']):
print(json.dumps(conv))
✅ Complete Conversations:
✅ Code Context:
✅ Diffs and Edits:
✅ Metadata:
⚠️ Partial Data:
⚠️ Privacy Considerations:
The optional filter_privacy.py command runs the official
openai/privacy-filter model
locally and replaces detected spans with typed placeholders such as
<PRIVATE_EMAIL> and <SECRET>. It recursively filters every string value, so
messages, code, tool output, diffs, and path metadata are all covered while the
JSON structure stays intact.
The privacy filter requires Python 3.10+ and downloads the model weights on its first run (about 2.8 GB). Install the pinned official OpenAI runtime separately:
python3 -m pip install -r requirements-privacy-filter.txt
Filter individual JSONL files or a whole extraction directory:
python3 filter_privacy.py extracted_data --output-dir filtered_data
# Use CUDA when available
python3 filter_privacy.py extracted_data --output-dir filtered_data --device cuda
Inputs are never modified. Existing outputs are refused unless --overwrite
is supplied. The unfiltered text stays local: the runtime downloads the model
from Hugging Face and performs inference on the selected CPU or CUDA device.
Privacy Filter is a data-minimization aid, not an anonymization or compliance guarantee. Review filtered output and combine it with secret scanning and your organization's privacy controls before sharing or training.
pip install detect-secrets
detect-secrets scan extracted_data/*.jsonl
from datasets import load_dataset
dataset = load_dataset(
'json',
data_files='extracted_data/*.jsonl',
split='train'
)
# Filter complete conversations
dataset = dataset.filter(
lambda x: any(m['role'] == 'assistant' for m in x['messages'])
)
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
"unsloth/qwen2.5-coder-7b-instruct",
max_seq_length=4096,
load_in_4bit=True,
)
def format_chat(example):
return {
'text': tokenizer.apply_chat_template(
example['messages'],
tokenize=False
)
}
dataset = dataset.map(format_chat)
Problem: Script reports "No installations found"
Solutions:
# Add to find_XXX_installations() function
locations.append(Path("/custom/path/to/tool"))
Problem: Extraction completes but no data found
Solutions:
find ~ -name "*.vscdb" -o -name "*.db" 2>/dev/null
Cursor database paths containing #, ?, or literal percent escapes are supported; filesystem paths are URI-encoded before opening them read-only.
Problem: SQLite database is locked
Solutions:
conn = sqlite3.connect(Path(db_path).resolve().as_uri() + '?mode=ro', uri=True)
Problem: Cannot read certain files
Solutions:
~/Library/Application Support for most tools~/Library/Application Support/[Tool]/User/~/.config and ~/.local/share~/.local/state for some tools$XDG_CONFIG_HOME if set%APPDATA% and %LOCALAPPDATA%C:\Users\[User]\AppData\Roaming\[Tool]cursorDiskKVItemTable# Process in chunks
split -l 1000 all_conversations.jsonl chunk_
# Compress for storage
gzip extracted_data/*.jsonl
# Use multiprocessing for large scans
from multiprocessing import Pool
with Pool() as pool:
results = pool.map(extract_from_db, db_files)
Found a new storage format or tool? Contributions welcome!
MIT License - Use freely for training ML models
This toolkit extracts YOUR OWN data from locally installed AI tools. Users are responsible for:
Generated: January 16, 2025 Status: Production Ready Compatibility: Python 3.6+, macOS/Linux/Windows
581 followers · starred Mar 2026
122 followers · starred Mar 2026
234 followers · starred Nov 2025
70 followers · starred Mar 2026
extract all your personal data history from cursor, codex, claude-code, windsurf, and trae
Python
1,352
22 commits
updated Oct 7, 2026
Complete toolkit to extract ALL chat, agent, and code context data from AI coding assistants for machine learning training.
Automatically discovers and extracts complete conversation history including:
Pi and OMP session exports use one shared parser. The default follows the active
branch; python3 extract_pi.py --all-branches and python3 extract_omp.py --all-branches
export separate leaf conversations with their ancestry and branch IDs. String
answers, custom context, reasoning, tool-call IDs, model changes, advisor/subagent
sessions and branch summaries are preserved. OMP also supports --prompt-history
for a separate read-only SQLite snapshot, including committed WAL rows.
extract_droid.py reads Factory/Droid sessions and settings under ~/.factory/sessions.
The aggregate script includes all three. The parser consolidation carries the
Pi/OMP work from PR #26, Joshua Warren's OMP coverage from #17 and Umang Bhalla's
Droid contribution from #7, with regression fixes and attribution.
extract_claude_code.pyExtracts from Claude Code / Claude Desktop
~/.claude, ~/.claude-code, ~/.claude-local, ~/.claude-m2, ~/.claude-zaiextract_codex.pyExtracts from Codex (if installed)
~/.codex, ~/.codex-localextract_cursor.pyExtracts from Cursor (Chat + Composer + Agent) - ALL VERSIONS
~/Library/Application Support/Cursor (macOS) or equivalentstate.vscdb, cursorDiskKV)extract_trae.pyExtracts from Trae
~/.trae, ~/Library/Application Support/Traeextract_windsurf.pyExtracts from Windsurf
~/Library/Application Support/Windsurf or equivalentextract_continue.pyExtracts from Continue AI Assistant
~/.continue/sessions/extract_gemini.pyExtracts from Google Gemini CLI
~/.gemini/tmp/[hash]/chats/extract_opencode.pyExtracts from OpenCode (CLI + Desktop)
~/.local/share/opencode/ (Linux), ~/Library/Application Support/opencode (macOS)~/.local/share/ai.opencode.app (Linux), ~/Library/Application Support/ai.opencode.app (macOS)opencode.db), JSON files (sessions/messages/parts), and Tauri .dat files (desktop)opencode.dbstorage/message, storage/part, and storage/sessionstorage/session_diff, storage/directory-readme, storage/agent-usage-reminder, and storage/rules-injectorextract_cursor_cli.pyExtracts from the cursor-agent terminal CLI (the Composer/Agent history that the GUI
extractor in extract_cursor.py does not cover)
~/.cursor/chats/<chatId>/<agentId>/store.db (all platforms)blobs table: id (sha256 hex) → data; message blobs are JSON ({role, content, id, ...}),
tree nodes are protobuf-framed lists of 32-byte child blob-id refsmeta table: hex-encoded JSON with agentId, latestRootBlobId, name, mode, createdAtlatestRootBlobId# No dependencies required - uses Python 3 standard library
python3 --version # Ensure Python 3.6+ is installed
# Extract from Claude Code
python3 extract_claude_code.py
# Extract from Cursor
python3 extract_cursor.py
# Extract from Codex
python3 extract_codex.py
# Extract from Trae
python3 extract_trae.py
# Extract from Windsurf
python3 extract_windsurf.py
# Extract from Continue
python3 extract_continue.py
# Extract from Gemini CLI
python3 extract_gemini.py
# Extract from OpenCode
python3 extract_opencode.py
# Extract from Cursor CLI (cursor-agent)
python3 extract_cursor_cli.py
# Extract from ALL tools at once
./extract_all.sh
All scripts create an extracted_data/ directory with timestamped JSONL files:
extracted_data/
├── claude_code_conversations_20250116_143022.jsonl
├── cursor_complete_20250116_143045.jsonl
├── gemini_conversations_20250116_143145.jsonl
├── codex_conversations_20250116_143102.jsonl
├── trae_conversations_20250116_143115.jsonl
├── windsurf_conversations_20250116_143130.jsonl
├── continue_conversations_20250116_143145.jsonl
└── opencode_conversations_20250116_143200.jsonl
Each conversation is a single JSON line in JSONL format:
{
"messages": [
{
"role": "user",
"content": "How do I fix this TypeScript error?",
"code_context": [
{
"file": "/Users/user/project/src/index.ts",
"code": "const x: string = 123;",
"range": {
"selectionStartLineNumber": 10,
"positionLineNumber": 10
}
}
],
"timestamp": "2025-01-16T14:30:22.123Z"
},
{
"role": "assistant",
"content": "The error occurs because you're assigning a number to a string type...",
"suggested_diffs": [...],
"model": "claude-sonnet-4-5",
"timestamp": "2025-01-16T14:30:25.456Z"
}
],
"source": "cursor-composer",
"name": "TypeScript Type Error Fix",
"created_at": 1705414222000
}
Each script follows this pattern:
~/Library/Application Support, ~/.config, ~/~/.config, ~/.local/share, ~/%APPDATA%, %LOCALAPPDATA%, ~/.vscdb, .db)~/.claude/projects/[project]/[session].jsonl~/Library/Application Support/Cursor/User/workspaceStorage/[hash]/state.vscdb~/Library/Application Support/Cursor/User/globalStorage/state.vscdbItemTable (Chat), cursorDiskKV (Composer/Agent)ItemTablecomposerData.conversation[])bubbleId:{composer}:{bubble} keys)workbench.panel.aichat.view.aichat.chatdata (Chat mode)composerData:{uuid} (Composer metadata + conversation)bubbleId:{composer}:{bubble} (Individual messages - transitional format)codeBlockDiff:{id} (Code block diffs)user: Human developer messagesassistant: AI assistant responsescode_context: File selections and code snippetssuggested_diffs: AI-proposed code changestool_use: Code execution, file operationstool_results: Execution outputs, diffs applieddiff_histories: Full edit historysource: Which tool (e.g., "cursor-composer", "claude-code")session_id/composer_id: Unique conversation IDproject_path: Working directorytimestamp: Message timemodel: AI model used (if available)# Combine all JSONL files
cat extracted_data/*.jsonl > all_conversations.jsonl
# Count total conversations
wc -l all_conversations.jsonl
# Count by source
grep -o '"source":"[^"]*"' all_conversations.jsonl | sort | uniq -c
corpus_to_skills.py samples extracted conversations and asks an
OpenAI-compatible chat-completions model to synthesize reusable
Agent Skills. Each result is written as
<skill-name>/SKILL.md with validated name and description metadata.
The command is local-first: unless OPENAI_BASE_URL is set, it connects to
http://127.0.0.1:8000/v1. Point it at a locally served model and a filtered
corpus:
python3 corpus_to_skills.py filtered_data \
--model your-local-model \
--skills 5 \
--output-dir generated_skills
You can also set SKILLS_MODEL instead of passing --model. The script uses
only Python's standard library, samples across the corpus with a configurable
character budget, treats corpus content as untrusted data, validates the model's
JSON response, and refuses to overwrite existing skills unless --overwrite
is passed.
Review every generated skill before installing it. Filter the extracted corpus first, especially before explicitly configuring a remote API endpoint; model output can still repeat sensitive source material despite prompt-level guards.
import json
from datetime import datetime
with open('extracted_data/cursor_complete_20250116.jsonl') as f:
for line in f:
conv = json.loads(line)
created = conv.get('created_at', 0)
if created > 1704067200000: # After Jan 1, 2024
print(json.dumps(conv))
import json
with open('extracted_data/cursor_complete.jsonl') as f:
for line in f:
conv = json.loads(line)
if any('suggested_diffs' in m or 'diff_histories' in m
for m in conv['messages']):
print(json.dumps(conv))
✅ Complete Conversations:
✅ Code Context:
✅ Diffs and Edits:
✅ Metadata:
⚠️ Partial Data:
⚠️ Privacy Considerations:
The optional filter_privacy.py command runs the official
openai/privacy-filter model
locally and replaces detected spans with typed placeholders such as
<PRIVATE_EMAIL> and <SECRET>. It recursively filters every string value, so
messages, code, tool output, diffs, and path metadata are all covered while the
JSON structure stays intact.
The privacy filter requires Python 3.10+ and downloads the model weights on its first run (about 2.8 GB). Install the pinned official OpenAI runtime separately:
python3 -m pip install -r requirements-privacy-filter.txt
Filter individual JSONL files or a whole extraction directory:
python3 filter_privacy.py extracted_data --output-dir filtered_data
# Use CUDA when available
python3 filter_privacy.py extracted_data --output-dir filtered_data --device cuda
Inputs are never modified. Existing outputs are refused unless --overwrite
is supplied. The unfiltered text stays local: the runtime downloads the model
from Hugging Face and performs inference on the selected CPU or CUDA device.
Privacy Filter is a data-minimization aid, not an anonymization or compliance guarantee. Review filtered output and combine it with secret scanning and your organization's privacy controls before sharing or training.
pip install detect-secrets
detect-secrets scan extracted_data/*.jsonl
from datasets import load_dataset
dataset = load_dataset(
'json',
data_files='extracted_data/*.jsonl',
split='train'
)
# Filter complete conversations
dataset = dataset.filter(
lambda x: any(m['role'] == 'assistant' for m in x['messages'])
)
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
"unsloth/qwen2.5-coder-7b-instruct",
max_seq_length=4096,
load_in_4bit=True,
)
def format_chat(example):
return {
'text': tokenizer.apply_chat_template(
example['messages'],
tokenize=False
)
}
dataset = dataset.map(format_chat)
Problem: Script reports "No installations found"
Solutions:
# Add to find_XXX_installations() function
locations.append(Path("/custom/path/to/tool"))
Problem: Extraction completes but no data found
Solutions:
find ~ -name "*.vscdb" -o -name "*.db" 2>/dev/null
Cursor database paths containing #, ?, or literal percent escapes are supported; filesystem paths are URI-encoded before opening them read-only.
Problem: SQLite database is locked
Solutions:
conn = sqlite3.connect(Path(db_path).resolve().as_uri() + '?mode=ro', uri=True)
Problem: Cannot read certain files
Solutions:
~/Library/Application Support for most tools~/Library/Application Support/[Tool]/User/~/.config and ~/.local/share~/.local/state for some tools$XDG_CONFIG_HOME if set%APPDATA% and %LOCALAPPDATA%C:\Users\[User]\AppData\Roaming\[Tool]cursorDiskKVItemTable# Process in chunks
split -l 1000 all_conversations.jsonl chunk_
# Compress for storage
gzip extracted_data/*.jsonl
# Use multiprocessing for large scans
from multiprocessing import Pool
with Pool() as pool:
results = pool.map(extract_from_db, db_files)
Found a new storage format or tool? Contributions welcome!
MIT License - Use freely for training ML models
This toolkit extracts YOUR OWN data from locally installed AI tools. Users are responsible for:
Generated: January 16, 2025 Status: Production Ready Compatibility: Python 3.6+, macOS/Linux/Windows
581 followers · starred Mar 2026
122 followers · starred Mar 2026
234 followers · starred Nov 2025
70 followers · starred Mar 2026