Skip to content

Commit 0351a8f

Browse files
committed
export only specific tabs
1 parent b73ee82 commit 0351a8f

File tree

3 files changed

+29
-16
lines changed

3 files changed

+29
-16
lines changed

README.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
This project provides a command-line interface (CLI) tool to discover and export AI chat data from [Cursor](https://cursor.sh). The tool is implemented in `chat.py` and leverages utility classes for querying the database, formatting the chat data, and saving it to files.
44

5-
Cursor's chat history is stored in `sqlite3` databases using `state.vscdb` files. One such file is for one workspace. All the chats for a workspace are saved here in so called *tabs*, one tab means a single chat.
5+
Cursor's chat history is stored in `sqlite3` databases using `state.vscdb` files. One such file is for one workspace. All the chats for a workspace are saved here in so-called *tabs*, one tab means a single chat.
66

77
Also see [this](https://forum.cursor.com/t/guide-5-steps-exporting-chats-prompts-from-cursor/2825) forum post on this topic.
88

99
## Features
1010

1111
- **Discover Chats**: Discover all chats from all workspaces and print a few lines of dialogue so one can identify which is the workspace (or chat) one is searching for. It's also possible to filter by text.
12-
- **Export Chats**: Export chats for the most recent (or a specific) workspace to Markdown files or print it to the command line.
12+
- **Export Chats**: Export chats for the most recent (or a specific) workspace to Markdown files or print them to the command line.
1313

1414
## Installation
1515

@@ -26,7 +26,7 @@ Also see [this](https://forum.cursor.com/t/guide-5-steps-exporting-chats-prompts
2626

2727
## Usage
2828

29-
First, find, where the `state.vscdb` files are located on your computer. Confirm that corresponding to your system, the right path is set in the [config.yml](./config.yml) file. Update it if not set correctly.
29+
First, find where the `state.vscdb` files are located on your computer. Confirm that corresponding to your system, the right path is set in the [config.yml](./config.yml) file. Update it if not set correctly.
3030

3131
Both the `discover` and `export` commands will work with this path by default, but you can also provide a custom path any time.
3232

@@ -64,6 +64,9 @@ See `./chat.py export --help` for general help. Examples:
6464
# Export only the latest chat of the most recent workspace
6565
./chat.py export --latest-tab --output-dir "/path/to/output"
6666
67+
# Export only chat No. 2 and 3 of the most recent workspace
68+
./chat.py export --tab-ids 2,3 --output-dir "/path/to/output"
69+
6770
# Export all chats of a specifc workspace
6871
./chat.py export --output-dir "/path/to/output" "/path/to/workspaces/workspace-dir/state.vscdb"
6972
```

chat.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
def export(
2121
db_path: str = typer.Argument(None, help="The path to the SQLite database file. If not provided, the latest workspace will be used."),
2222
output_dir: str = typer.Option(None, help="The directory where the output markdown files will be saved. If not provided, prints to command line."),
23-
latest_tab: bool = typer.Option(False, "--latest-tab", help="Export only the latest tab. If not set, all tabs will be exported.")
23+
latest_tab: bool = typer.Option(False, "--latest-tab", help="Export only the latest tab. If not set, all tabs will be exported."),
24+
tab_ids: str = typer.Option(None, help="Comma-separated list of tab IDs to export. For example, '1,2,3'. If not set, all tabs will be exported.")
2425
):
2526
"""
2627
Export chat data from the database to markdown files or print it to the command line.
@@ -43,10 +44,14 @@ def export(
4344
# Convert the chat data from JSON string to dictionary
4445
chat_data_dict = json.loads(chat_data[0])
4546

47+
tab_id_list = None
4648
if latest_tab:
4749
# Get the latest tab by timestamp
4850
latest_tab = max(chat_data_dict['tabs'], key=lambda tab: tab.get('timestamp', 0))
4951
chat_data_dict['tabs'] = [latest_tab]
52+
elif tab_ids:
53+
# Filter tabs by provided tab IDs
54+
tab_id_list = [int(ti) - 1 for ti in tab_ids.split(',')]
5055

5156
# Check if there are any images in the chat data
5257
has_images = any('image' in bubble for tab in chat_data_dict['tabs'] for bubble in tab.get('bubbles', []))
@@ -60,11 +65,11 @@ def export(
6065
# Save the chat data
6166
saver = MarkdownFileSaver()
6267
exporter = ChatExporter(formatter, saver)
63-
exporter.export(chat_data_dict, output_dir, image_dir)
68+
exporter.export(chat_data_dict, output_dir, image_dir, tab_ids=tab_id_list)
6469
success_message = f"Chat data has been successfully exported to {output_dir}"
6570
logger.info(success_message)
6671
else:
67-
formatted_chats = formatter.format(chat_data_dict, image_dir)
72+
formatted_chats = formatter.format(chat_data_dict, image_dir, tab_ids=tab_id_list)
6873
# Print the chat data to the command line using markdown
6974
for formatted_data in formatted_chats:
7075
console.print(Markdown(formatted_data))

src/export.py

+15-10
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99

1010
class ChatFormatter(ABC):
1111
@abstractmethod
12-
def format(self, chat_data: dict[str, Any], image_dir: str = 'images') -> str:
12+
def format(self, chat_data: dict[str, Any], image_dir: str = 'images') -> dict[int, str] | None:
1313
"""Format the chat data into Markdown format.
1414
1515
Args:
1616
chat_data (dict[str, Any]): The chat data to format.
1717
image_dir (str): The directory where images will be saved. Defaults to 'images'.
1818
1919
Returns:
20-
str: The formatted chat data in Markdown.
20+
dict[int, str]: The formatted chat for each tab.
2121
"""
2222
pass
2323

@@ -61,19 +61,24 @@ def _extract_text_from_user_bubble(self, bubble: dict) -> str:
6161

6262
return user_text_text
6363

64-
def format(self, chat_data: dict[str, Any], image_dir: str | None = 'images') -> str | None:
64+
def format(self, chat_data: dict[str, Any], image_dir: str | None = 'images', tab_ids: list[int] | None = None) -> dict[int, str] | None:
6565
"""Format the chat data into Markdown format.
6666
6767
Args:
6868
chat_data (dict[str, Any]): The chat data to format.
6969
image_dir (str): The directory where images will be saved. Defaults to 'images'.
70+
tab_ids (list[int]): List of tab indices to include exclusively.
7071
7172
Returns:
72-
str: The formatted chat data in Markdown.
73+
dict[int, str]: The formatted chat in Markdown for each tab.
7374
"""
7475
try:
75-
formatted_chats = []
76+
formatted_chats = {}
7677
for tab_index, tab in enumerate(chat_data['tabs']):
78+
if tab_ids is not None:
79+
if tab_index not in tab_ids:
80+
continue
81+
7782
bubbles = tab['bubbles']
7883
formatted_chat = [f"# Chat Transcript - Tab {tab_index + 1}\n"]
7984

@@ -116,7 +121,7 @@ def format(self, chat_data: dict[str, Any], image_dir: str | None = 'images') ->
116121
raw_text = re.sub(r'```python:[^\n]+', '```python', bubble['rawText'])
117122
formatted_chat.append(f"## AI ({model_type}):\n\n{raw_text}\n")
118123

119-
formatted_chats.append("\n".join(formatted_chat))
124+
formatted_chats[f"tab_{tab_index + 1}"] = "\n".join(formatted_chat)
120125

121126
logger.success("Chats formatted.")
122127
return formatted_chats
@@ -163,7 +168,7 @@ def __init__(self, formatter: ChatFormatter, saver: FileSaver) -> None:
163168
self.formatter = formatter
164169
self.saver = saver
165170

166-
def export(self, chat_data: dict[str, Any], output_dir: str, image_dir: str) -> None:
171+
def export(self, chat_data: dict[str, Any], output_dir: str, image_dir: str, tab_ids: list[int] | None = None) -> None:
167172
"""Export the chat data by formatting and saving it.
168173
169174
Args:
@@ -173,10 +178,10 @@ def export(self, chat_data: dict[str, Any], output_dir: str, image_dir: str) ->
173178
"""
174179
try:
175180
os.makedirs(output_dir, exist_ok=True)
176-
formatted_chats = self.formatter.format(chat_data, image_dir)
181+
formatted_chats = self.formatter.format(chat_data, image_dir, tab_ids=tab_ids)
177182
if formatted_chats is not None:
178-
for tab_index, formatted_data in enumerate(formatted_chats):
179-
tab_file_path = os.path.join(output_dir, f"tab_{tab_index + 1}.md")
183+
for tab_name, formatted_data in formatted_chats.items():
184+
tab_file_path = os.path.join(output_dir, f"{tab_name}.md")
180185
self.saver.save(formatted_data, tab_file_path)
181186
except Exception as e:
182187
logger.error(f"Failed to export chat data: {e}")

0 commit comments

Comments
 (0)