here is a script. put it in a fresh cursor file with your storage.vcsdb file. ~/Library/Application Support/Cursor/User/globalStorage/ <on Mac
the script will pull out all your chats with code changes and chat names into a folder. just have chat run it for you.
#!/usr/bin/env python3
import sqlite3
import json
import os
import re
from datetime import datetime
# Database path
db_path = "/Users/passes/Library/Application Support/Cursor/User/globalStorage/state.vscdb"
output_dir = "/Users/passes/Desktop/Passes CRM/cursor-chats-complete"
# Ensure output directory exists
os.makedirs(output_dir, exist_ok=True)
# Connect to database
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
def sanitize_filename(name):
name = re.sub(r'[<>:"/\\|?*]', '', name)
name = name.replace(' ', '_')
return name[:100]
print("=" * 70)
print("EXTRACTING COMPLETE CURSOR CONVERSATIONS")
print("=" * 70)
# Step 1: Get all composer data with names and conversation info
print("\nStep 1: Loading composer metadata...")
cursor.execute("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'")
composer_data_rows = cursor.fetchall()
composers = {}
for key, value in composer_data_rows:
try:
data = json.loads(value)
composer_id = data.get('composerId')
name = data.get('name', 'Untitled Chat')
created_at = data.get('createdAt')
conversation_headers = data.get('fullConversationHeadersOnly', [])
composers[composer_id] = {
'name': name,
'created_at': created_at,
'bubble_ids': [h.get('bubbleId') for h in conversation_headers if 'bubbleId' in h],
'data': data
}
except Exception as e:
pass
print(f" Found {len(composers)} composer conversations")
# Step 2: Get all bubbles (messages)
print("\nStep 2: Loading all message bubbles...")
cursor.execute("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'bubbleId:%'")
bubble_rows = cursor.fetchall()
# Organize bubbles by composer
bubbles_by_composer = {}
for key, value in bubble_rows:
try:
# Key format: bubbleId:<composer-id>:<bubble-id>
parts = key.split(':')
if len(parts) >= 3:
composer_id = parts[1]
bubble_id = parts[2]
if composer_id not in bubbles_by_composer:
bubbles_by_composer[composer_id] = {}
bubble_data = json.loads(value)
bubbles_by_composer[composer_id][bubble_id] = bubble_data
except Exception as e:
pass
print(f" Found {len(bubble_rows)} message bubbles across {len(bubbles_by_composer)} composers")
# Step 3: Export each conversation
print("\nStep 3: Exporting complete conversations...\n")
exported_count = 0
for idx, (composer_id, composer_info) in enumerate(sorted(composers.items(), key=lambda x: x[1].get('created_at', 0), reverse=True), 1):
name = composer_info['name']
created_at = composer_info['created_at']
bubble_ids = composer_info['bubble_ids']
# Skip if no bubbles
if not bubble_ids or composer_id not in bubbles_by_composer:
continue
# Get all bubbles for this conversation
composer_bubbles = bubbles_by_composer.get(composer_id, {})
# Create filename
safe_name = sanitize_filename(name) if name and name not in ['New Chat', 'Chat'] else f"chat_{composer_id[:16]}"
filename = f"{idx:03d}_{safe_name}.md"
filepath = os.path.join(output_dir, filename)
# Build the conversation
with open(filepath, 'w', encoding='utf-8') as f:
f.write(f"# {name}\n\n")
f.write(f"**Composer ID:** `{composer_id}`\n")
if created_at:
dt = datetime.fromtimestamp(created_at / 1000)
f.write(f"**Created:** {dt.strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"**Messages:** {len(bubble_ids)}\n\n")
f.write("---\n\n")
# Write each message
message_count = 0
for bubble_id in bubble_ids:
if bubble_id not in composer_bubbles:
continue
bubble = composer_bubbles[bubble_id]
msg_type = bubble.get('type')
text = bubble.get('text', '')
rich_text = bubble.get('richText', '')
# Type 1 = User, Type 2 = Assistant
if msg_type == 1:
f.write(f"## 👤 User\n\n")
elif msg_type == 2:
f.write(f"## 🤖 Assistant\n\n")
else:
f.write(f"## Message (type {msg_type})\n\n")
# Write the message content
if text:
f.write(f"{text}\n\n")
message_count += 1
elif rich_text:
# Rich text might be JSON, try to extract readable content
try:
if isinstance(rich_text, str):
rich_data = json.loads(rich_text)
else:
rich_data = rich_text
f.write(f"```json\n{json.dumps(rich_data, indent=2)[:1000]}...\n```\n\n")
except:
f.write(f"{str(rich_text)[:500]}...\n\n")
f.write("---\n\n")
if message_count > 0:
exported_count += 1
print(f" ✓ {idx:03d}. {name[:60]:<60} ({message_count} messages)")
print(f"\n✅ Exported {exported_count} complete conversations")
# Step 4: Create an index
print("\nStep 4: Creating index...")
index_path = os.path.join(output_dir, "00_INDEX.md")
with open(index_path, 'w', encoding='utf-8') as f:
f.write("# Cursor Conversations Index\n\n")
f.write(f"**Exported:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"**Total Conversations:** {exported_count}\n\n")
f.write("---\n\n")
for idx, (composer_id, composer_info) in enumerate(sorted(composers.items(), key=lambda x: x[1].get('created_at', 0), reverse=True), 1):
name = composer_info['name']
created_at = composer_info['created_at']
bubble_ids = composer_info['bubble_ids']
if not bubble_ids or composer_id not in bubbles_by_composer:
continue
composer_bubbles = bubbles_by_composer.get(composer_id, {})
message_count = sum(1 for bid in bubble_ids if bid in composer_bubbles and composer_bubbles[bid].get('text'))
if message_count > 0:
safe_name = sanitize_filename(name) if name and name not in ['New Chat', 'Chat'] else f"chat_{composer_id[:16]}"
filename = f"{idx:03d}_{safe_name}.md"
f.write(f"## {idx}. {name}\n\n")
f.write(f"- **File:** `{filename}`\n")
f.write(f"- **Messages:** {message_count}\n")
if created_at:
dt = datetime.fromtimestamp(created_at / 1000)
f.write(f"- **Created:** {dt.strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write("\n")
print(f"✅ Created index: 00_INDEX.md")
conn.close()
print("\n" + "=" * 70)
print("EXPORT COMPLETE!")
print("=" * 70)
print(f"\n📁 Location: {output_dir}")
print(f"📝 {exported_count} conversations exported with full message history")
print(f"📋 Index file: 00_INDEX.md")