+1 this would be great. or perhaps another way to back them up in the cloud
+1 We need this quality-of-life feature.
In some projects in my case the chat is the actual source code from which the code is reconstructed. It contains versions, history, justification of choices. I want to add it in git. @sheikheddy gives a good work around, but this really needs to be a central feature. I want to be able to browse all the snippets that modified a given file, I want to be able to do the equivalent of git blame
on my code to get back to where a regression happened, etc.
I made a small script just for my specific use, hopefully will get it to be more featured in the future. It extracts the latest AI chat and prints it as a markdown:
this is awesome, worked great, thank you for postingā¦
we have the same needs
I have created a Cursor (VSCode) extension version that extracts the chats and lets you save the chats as markdown to a file or a new text using the command palette.
Let me know if youāre interested Iām finalizing it tomorrow and making it downloadable.
Iām in. Please share download. You could sell this one dude.
Can you make a Cursor Marketplace extension and put it in there?
Iāve made a cursor chat browser for both chat and composer messages (using cursor).
Feel free to check it out here. It has already helped me a lot. You can download the logs as md, pdf or html.
Feel free to contribute to improvements.
Aināt it crazy that rather than fix my npm install, I found it faster to just generate the same thing with flask?
Here you go, this one finds all your conversations, orders them by time and allows you to search their content.
Done in 20 minutes with our favorite tool before a meeting.
I just asked cursor to change this script to let us specify the CHAT TITLE as an argument to find the conversation we want to print, because I thought that would be more flexible. Also, on Mac the path seems to be different for stored conversations, so I changed that too.
"""
This script extracts chat data from the most recent Cursor SQLite database and reconstructs conversations.
Functionality:
- Connects to the most recent Cursor workspace SQLite database
- Extracts 'workbench.panel.aichat.view.aichat.chatdata' from ItemTable
- Reconstructs conversations from the 'bubbles' in each tab
- Outputs conversations in Markdown format
Requirements:
- Python 3.x
- sqlite3 module (usually comes pre-installed with Python)
- os and glob modules for file operations
"""
import sqlite3
import os
import glob
import json
from datetime import datetime
import sys
def get_latest_vscdb_file():
print(f"Debug - OS name: {os.name}, Platform: {sys.platform}") # Add this debug line
# Get the appropriate base path depending on the OS
if os.name == 'nt': # Windows
base_path = os.path.join(os.environ['APPDATA'], 'Cursor', 'User', 'workspaceStorage')
elif os.name == 'posix': # macOS and Linux
if sys.platform == 'darwin': # macOS
base_path = os.path.expanduser('~/Library/Application Support/Cursor/User/workspaceStorage')
else: # Linux
base_path = os.path.expanduser('~/.config/Cursor/User/workspaceStorage')
else:
raise OSError("Unsupported operating system")
print(f"Looking for Cursor data in: {base_path}")
if not os.path.exists(base_path):
raise FileNotFoundError(f"Cursor workspace storage not found at: {base_path}")
folders = glob.glob(os.path.join(base_path, "*"))
if not folders:
raise FileNotFoundError("No folders found in Cursor workspace storage.")
latest_folder = max(folders, key=os.path.getctime)
vscdb_file = os.path.join(latest_folder, "state.vscdb")
if not os.path.exists(vscdb_file):
raise FileNotFoundError(f"state.vscdb not found in the latest folder: {latest_folder}")
print(f"Latest folder: {latest_folder}")
return vscdb_file
def extract_data_from_vscdb(db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute(
"SELECT value FROM ItemTable WHERE key = 'workbench.panel.aichat.view.aichat.chatdata'"
)
result = cursor.fetchone()
conn.close()
if result:
return json.loads(result[0])
else:
return None
def get_search_title():
while True:
search_title = input("Enter the title to search for in conversations: ").strip()
if not search_title:
print("Search title cannot be empty. Please enter a title to search for.")
continue
confirm = input(f"Search for conversations containing '{search_title}'? (y/n): ").lower()
if confirm == 'y':
return search_title
elif confirm == 'n':
continue
else:
print("Please answer 'y' or 'n'")
def reconstruct_conversations(chat_data, search_title):
conversations = []
for tab in chat_data.get("tabs", []):
# Only include conversations that match the search title
if search_title.lower() not in tab.get("chatTitle", "").lower():
continue
conversation = {
"title": tab.get("chatTitle", "Untitled Chat"),
"messages": [],
"timestamp": tab.get("lastSendTime", 0),
}
for bubble in tab.get("bubbles", []):
speaker = "Human" if bubble.get("type") == "user" else "AI"
message = bubble.get("text", "")
conversation["messages"].append((speaker, message))
conversations.append(conversation)
# Sort conversations by timestamp
conversations.sort(key=lambda x: x["timestamp"], reverse=True)
if not conversations:
print(f"No conversations found with '{search_title}' in the title.")
return conversations
def format_markdown(conversations):
markdown = "# Cursor Chat Conversations\n\n"
for idx, conv in enumerate(conversations, 1):
markdown += f"## Conversation {idx}: {conv['title']}\n\n"
markdown += f"*Timestamp: {datetime.fromtimestamp(conv['timestamp']/1000).strftime('%Y-%m-%d %H:%M:%S')}*\n\n"
for speaker, message in conv["messages"]:
markdown += f"### {speaker}:\n\n{message}\n\n"
markdown += "---\n\n"
return markdown
def main():
try:
db_path = get_latest_vscdb_file()
print(f"Using database: {db_path}")
chat_data = extract_data_from_vscdb(db_path)
if chat_data is None:
print("No chat data found in the database.")
return
# Get search title from user (now required)
search_title = get_search_title()
# Get filtered conversations
conversations = reconstruct_conversations(chat_data, search_title)
if not conversations:
return
markdown_output = format_markdown(conversations)
# Print to console
print(markdown_output)
# Create filename using the search term
output_file = f"{search_title.lower().replace(' ', '_')}_conversations.md"
with open(output_file, "w", encoding="utf-8") as f:
f.write(markdown_output)
print(f"Markdown output saved to {output_file}")
except Exception as e:
print(f"An error occurred: {str(e)}")
if __name__ == "__main__":
main()
you should post this in Showcase if you havenāt already. great work!
who else has tested this?
Over a year ago. Any updates?
great work thomas-pedersen!!!
Just tried it, love being able to search, and simple display.
Getting a runtime error trying to display some workspaces:
anyone know why?
We built an extension that natively integrates with Cursorās fork of VS Code to do this from the command palette. You can try it out here: SpecStory (Cursor Extension) - Visual Studio Marketplace
Read a bit more about it here in showcase: Built a Cursor Extension to save and share chat and composer history
looks like Cursor changed location for saved chats after update.
I write a small script to export all chats to markdown files:
Coding alongside #ai can be a real #productivity booster. My current tool is @anon-10470271. But when you canāt properly copy that useful conversation output, itās hard to save stuff for reference etc.
Thatās why I created the Cursor Convo Export extension:
Use it with the command palette or via status shortcut