here is a script. put it in a fresh cursor file with your state.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::
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")