本文面向:使用 Cursor 的开发者,想了解 ChatCrystal 如何从 Cursor 中提取对话数据。 预计阅读时间:8 分钟
Cursor 的对话存在哪
Cursor 基于 VS Code,对话数据存在 SQLite 数据库里,不是像 Claude Code 那样的独立 JSONL 文件。
数据分布在两个位置:
1. 工作区数据库(Composer 元数据)
bash
~/.config/Cursor/User/workspaceStorage/<hash>/state.vscdb
每个工作区一个数据库,存储 Composer(对话)的元数据:ID、创建时间、项目路径等。
<hash> 是工作区路径的哈希值,workspace.json 里记录了实际的项目路径。
2. 全局数据库(对话内容)
bash
~/.config/Cursor/User/globalStorage/state.vscdb
所有对话的实际内容(Bubble 数据)存在这个全局数据库里,通过 cursorDiskKV 表按 bubbleId:<composerId>:<bubbleId> 的 key 格式存储。
各平台路径
| 系统 | 路径 |
|---|---|
| Windows | %APPDATA%\Cursor\User\ |
| macOS | ~/Library/Application Support/Cursor/User/ |
| Linux | ~/.config/Cursor/User/ |
导入时发生了什么
bash
扫描 workspaceStorage/*/state.vscdb
→ 读取 composer.composerData 获取对话列表
→ 读取 workspace.json 获取项目路径
→ 从 globalStorage/state.vscdb 读取 bubble 数据
→ 解析每条 bubble(type=1 用户,type=2 助手)
→ 提取文本、思考过程、工具调用信息
→ 按时间排序
→ 写入 SQLite
Bubble 数据结构
Cursor 的每条消息是一个 Bubble,存储格式:
json
{
"_v": 3,
"type": 1,
"text": "帮我看看这个组件的性能问题",
"createdAt": "2026-05-10T10:30:00Z",
"isAgentic": true,
"toolResults": [...],
"codeBlocks": [...],
"allThinkingBlocks": [...]
}
| 字段 | 说明 |
|---|---|
type |
1 = 用户消息,2 = 助手消息 |
text |
消息文本 |
isAgentic |
是否使用了 Agent 模式 |
toolResults |
工具调用结果 |
codeBlocks |
代码块 |
allThinkingBlocks |
思考过程 |
ChatCrystal 只提取有文本内容的消息,跳过空的助手消息(流式传输的中间状态)。
孤立对话的发现
有些对话的工作区已经被删除了,但全局数据库里的 Bubble 数据还在。ChatCrystal 会扫描全局数据库中所有 bubbleId:* 的 key,找出不在任何工作区 Composer 列表中的对话。
这些「孤立对话」也会被导入,不会丢失。
自定义数据目录
如果 Cursor 数据不在默认位置:
bash
CURSOR_DATA_DIR=/path/to/cursor/User crystal import
常见问题
导入后看不到 Cursor 对话
- 确认 Cursor 有历史对话:打开 Cursor,查看 Composer 面板
- 检查路径是否正确:
ls ~/.config/Cursor/User/workspaceStorage/(Linux)或对应平台路径 - 如果用了自定义
CURSOR_DATA_DIR,确认路径指向User目录
导入速度慢
Cursor 的 SQLite 数据库可能比较大(几百 MB),首次扫描需要遍历所有工作区。后续增量导入会快很多。
对话内容不完整
Cursor 的 Bubble 数据依赖 _v(schema 版本)字段。如果 Cursor 更新了数据格式,ChatCrystal 可能需要适配新版本。遇到格式警告时可以忽略,不影响已支持的格式。
下一步
- Claude Code 对话自动导入完全指南 --- 了解 Claude Code 数据源的导入方式
- LLM 和 Embedding 不能混用 --- 配置 LLM 时最常见的坑