1. 项目概述:一个本地优先的AI编程助手聊天记录聚合器

如果你和我一样,日常重度依赖像 Claude Code 和 Cursor 这样的 AI 编程助手,那你肯定也遇到过这个痛点:想找上周和 Claude 讨论的那个数据库优化方案,或者昨天在 Cursor 里调试的某个 React 组件逻辑,结果发现聊天记录散落在两个不同的应用里,翻找起来极其麻烦。更别提想全局搜索某个关键词,看看自己之前是怎么解决类似问题的了。这就是我动手开发 Chat Recall 的初衷——一个完全运行在你本地机器上的工具,帮你把 Claude Code 和 Cursor 的聊天历史聚合到一个统一的界面里,支持搜索、按项目分组,甚至还能生成每周的工作总结。

这个项目的核心设计哲学是 “离线优先” “隐私至上” 。你的所有聊天数据都只存在于你的电脑上,Chat Recall 只是作为一个本地的“阅读器”,直接读取这些应用存储在磁盘上的历史文件。这意味着没有网络请求,没有数据上传到任何云端服务器,也没有任何形式的遥测或分析。对于处理代码、设计思路甚至可能包含敏感信息的对话来说,这种本地化处理带来的安全感是无价的。整个技术栈基于 Next.js 16 的 App Router 和 React Server Components,配合 TypeScript 确保类型安全,UI 则选用了 Material-UI 来快速搭建一个清晰可用的界面。

2. 核心需求解析与设计思路

2.1 为什么需要这样一个工具?

在深入代码之前,我们先聊聊需求。现代 AI 编程助手已经成为开发者工作流中不可或缺的一部分,但它们各自为政的数据存储方式带来了几个显著问题:

信息孤岛与检索困难 :Claude Code 和 Cursor 将聊天记录分别存储在自己的私有目录下,格式也互不兼容。当你试图回忆一个模糊的解决方案时,不得不在两个应用间来回切换、手动翻找,效率极低。

缺乏上下文关联与知识沉淀 :我们与 AI 的对话往往围绕特定项目展开。但这些对话被淹没在按时间排序的线性列表中,无法与项目文件、代码仓库形成有效关联。宝贵的讨论过程和决策依据难以沉淀为可复用的团队知识。

隐私与数据主权的考量 :作为开发者,我们与 AI 讨论的代码、业务逻辑、甚至未公开的产品设计,都可能包含敏感信息。将这些对话历史无条件信任给第三方云服务或未经审视的本地工具,存在潜在的数据泄露风险。

Chat Recall 的目标,就是成为你本地工作环境中的一个“中枢神经系统”,在不牺牲隐私和安全的前提下,解决上述痛点。它不生产数据,只是数据的搬运工和整理者。

2.2 架构选型背后的“为什么”

面对这个需求,技术选型上我做了几个关键决策,每一个都经过了仔细的权衡。

为什么选择 Next.js 16 和 App Router? 首先,这是一个典型的富交互前端应用,需要良好的路由、状态管理和构建体验。Next.js 提供了开箱即用的解决方案。更重要的是,我选择了 App Router 而非旧的 Pages Router,并大量使用了 React Server Components 。这是本项目架构的核心。因为所有数据读取(访问本地 SQLite 数据库、解析 JSON 文件)都是 I/O 密集型的同步操作,且完全在本地进行,没有网络延迟。将这些逻辑放在 Server Components 中执行,可以:

  1. 避免将敏感的本地文件路径或原始数据发送到客户端,提升了安全性。
  2. 直接生成静态的 HTML,减少了客户端 JavaScript 的包体积,提升了初始加载速度。
  3. 简化了数据获取逻辑,在组件内直接使用 fs better-sqlite3 等 Node.js API,代码更直观。

为什么前端用 MUI (Material-UI)? 对于一个工具类应用,UI 的清晰、一致和快速搭建比独特的视觉设计更重要。MUI 提供了丰富、成熟且可访问性良好的组件库,让我能快速构建出包含列表、搜索框、折叠面板、日期选择器等复杂交互的界面,而无需从零开始编写样式和交互逻辑。这能将开发精力集中在核心的数据处理和业务逻辑上。

为什么用 better-sqlite3 读取 Cursor 数据? 经过逆向工程(后面会详细讲),我发现 Cursor 将聊天记录存储在一个本地的 SQLite 数据库中。在 Node.js 环境中操作 SQLite, better-sqlite3 是性能最高、最稳定的选择之一。它提供了同步 API,这对于在 Server Component 中直接读取数据非常合适,避免了不必要的异步复杂度。

为什么构建工具是 pnpm? 纯粹是个人偏好和项目一致性。pnpm 的磁盘空间效率和速度优势在 monorepo 或依赖较多的项目中很明显。本项目依赖不算特别多,但用 pnpm 能确保依赖树清晰,安装速度快。

3. 数据源解析与本地文件读取实战

这是整个项目最基础也是最关键的一环:如何找到并正确解析 Claude Code 和 Cursor 存储在本地磁盘上的聊天数据。如果这一步出错,后面所有功能都是空中楼阁。

3.1 定位数据存储路径

不同的操作系统和应用程序有不同的数据存储规范。Chat Recall 需要兼容 macOS、Windows 和 Linux。

Claude Code 的数据路径 Claude Code(基于 VS Code)遵循常规的配置存储模式。在类 Unix 系统(macOS, Linux)上,数据通常位于用户主目录下的隐藏文件夹中。

# macOS / Linux
~/.claude/projects/

在 Windows 系统上,路径则位于 %APPDATA% 目录下,对应为:

# Windows
C:\Users\<YourUsername>\AppData\Roaming\claude\projects\

注意 :路径中的 ~ 代表当前用户的主目录。在代码中,我们可以使用 Node.js 的 os.homedir() 方法来动态获取这个路径,从而实现跨平台兼容。

在这个 projects 目录下,每个你打开过的项目(或文件夹)都会有一个以项目路径哈希值命名的子文件夹。里面就存放着该项目的聊天记录 JSON 文件。

Cursor 的数据路径 Cursor 的数据存储位置更依赖于操作系统的“应用程序支持”目录。

  • macOS : ~/Library/Application Support/Cursor/User/globalStorage/state.vscdb
  • Windows : %APPDATA%\Cursor\User\globalStorage\state.vscdb
  • Linux : ~/.config/Cursor/User/globalStorage/state.vscdb

关键点在于,Cursor 将大量状态信息(包括聊天记录)存储在一个名为 state.vscdb SQLite 数据库文件 中,而不是明文的 JSON 文件。这增加了读取的复杂度。

3.2 解析数据格式与结构

找到文件只是第一步,理解其内部结构才能提取出有用的对话信息。

解析 Claude Code 的 JSON 文件 Claude Code 的聊天记录以相对友好的 JSON 格式存储。每个项目文件夹下可能有多个以时间戳命名的 .json 文件。我们需要遍历这些文件,解析其结构。一个典型的文件内容骨架如下:

{
  "chatSessions": [
    {
      "id": "session_123",
      "createdAt": "2024-01-01T10:00:00Z",
      "title": "讨论用户登录逻辑",
      "messages": [
        {
          "role": "user",
          "content": "如何实现一个安全的 JWT 认证流程?",
          "timestamp": "..."
        },
        {
          "role": "assistant",
          "content": "一个典型的 JWT 流程包括...",
          "timestamp": "..."
        }
      ],
      "projectPath": "/Users/me/code/my-app"
    }
  ]
}

我们的解析逻辑需要:

  1. 递归遍历 ~/.claude/projects/ 下的所有子目录。
  2. 找到所有 .json 文件。
  3. 读取并解析 JSON 内容。
  4. chatSessions 中提取出每一条对话的元数据(ID、时间、标题)和消息内容。
  5. 将项目路径从哈希值映射回可读的路径(如果可能)。

逆向工程 Cursor 的 SQLite 数据库 这是更有挑战性的一部分。 state.vscdb 是一个二进制数据库文件,我们需要用 better-sqlite3 打开它并查询正确的表。

首先,通过 SQLite 命令行工具或 DB Browser for SQLite 这样的 GUI 工具打开数据库文件,探索其结构。经过分析,我发现聊天记录主要存储在 ItemTable 这个表中,其中 key 字段标识数据类型, value 字段存储了经过压缩或编码的实际内容。

一个关键的 key 值可能是 chat.sessions 。其对应的 value 是一个经过 JSON 编码的字符串,里面包含了所有聊天会话的列表。解析流程如下:

  1. 使用 better-sqlite3 建立数据库连接。
  2. 执行 SQL 查询: SELECT value FROM ItemTable WHERE key = 'chat.sessions'
  3. 将查询结果的 value 字段(一个 Buffer)转换为字符串。
  4. 解析这个 JSON 字符串,其结构可能是一个数组,包含了每个会话的 ID、时间戳、消息列表等信息。
  5. 消息内容本身可能被进一步编码或压缩,需要额外的解码步骤。

实操心得 :Cursor 的数据格式可能随着版本更新而变化。在代码中,必须对 JSON 解析、字段访问做好异常处理(try-catch),并考虑在无法识别新格式时提供友好的错误提示,而不是让整个应用崩溃。可以增加一个“数据版本检测”的逻辑,在启动时检查并告知用户当前是否支持其 Cursor 版本。

3.3 实现统一的数据模型与读取服务

为了给前端提供一致的数据,我们需要将来自两个不同源的数据,转换成一个统一的内部模型。我定义了一个 ChatSession 接口:

interface ChatSession {
  id: string; // 全局唯一ID,可用源ID+会话ID组合生成
  source: 'claude' | 'cursor'; // 数据来源
  title: string; // 会话标题,可从前几条消息自动生成
  preview: string; // 内容预览(如第一条消息)
  projectName: string; // 项目名称,从路径提取
  projectPath: string; // 项目完整路径
  timestamp: Date; // 会话最后活动时间
  messageCount: number; // 消息总数
  // 如果需要完整渲染,可以包含完整的消息数组
  // messages: ChatMessage[];
}

然后,创建两个数据服务类: ClaudeDataService CursorDataService 。它们分别实现一个共同的接口 IChatDataService ,提供 getAllSessions(): Promise<ChatSession[]> 等方法。

在 Next.js 的 Server Component 中,我们可以同时调用这两个服务,将结果合并、排序(例如按时间倒序),然后传递给 UI 组件进行渲染。由于这是服务端逻辑,所有文件读取和数据库操作都安全地运行在服务器环境(即你的本地机器)上,不会暴露给浏览器。

4. 核心功能实现与界面构建

有了统一的数据模型,我们就可以在此基础上构建用户界面和功能了。前端采用 Next.js App Router,页面结构清晰。

4.1 构建统一会话列表视图

主页面 ( app/page.tsx ) 是一个 Server Component,它负责在服务端获取所有聊天会话数据。

// app/page.tsx
import { getCombinedChatSessions } from '@/services/data-aggregator';

export default async function HomePage() {
  // 在服务端获取数据
  const sessions = await getCombinedChatSessions();
  
  // 将数据传递给客户端组件进行渲染
  return <SessionListView initialSessions={sessions} />;
}

SessionListView 是一个客户端组件,它接收会话数组,并使用 MUI 的 List ListItem 等组件来渲染一个可交互的列表。每个列表项显示会话标题、项目名称、时间戳和内容预览。

关键实现细节:虚拟滚动 如果用户积累了大量的聊天记录(比如上千条),一次性渲染所有列表项会导致页面卡顿。为了解决这个问题,我引入了 虚拟滚动 。我选择了 react-virtuoso 这个库,它只渲染当前视窗内可见的列表项,无论数据量多大,都能保持流畅滚动。

import { Virtuoso } from 'react-virtuoso';
// ... 在组件内
<Virtuoso
  data={sessions}
  itemContent={(index, session) => <SessionListItem session={session} />}
/>

4.2 实现全局全文搜索功能

搜索是提升信息检索效率的核心。我们需要支持跨所有会话、所有消息内容进行关键词搜索。

前端搜索交互 在页面顶部放置一个搜索输入框(MUI 的 TextField )。使用 React 的 useState 来管理搜索关键词,并用 useMemo 来执行实际的过滤逻辑,避免在每次渲染时都进行昂贵的计算。

const [searchTerm, setSearchTerm] = useState('');
const filteredSessions = useMemo(() => {
  if (!searchTerm.trim()) return sessions;
  const term = searchTerm.toLowerCase();
  return sessions.filter(session => 
    session.title.toLowerCase().includes(term) ||
    session.preview.toLowerCase().includes(term) ||
    // 如果存储了完整消息,这里需要遍历 messages.content
    session.messages?.some(msg => msg.content.toLowerCase().includes(term))
  );
}, [sessions, searchTerm]);

后端增强搜索与性能考量 上述前端搜索简单快捷,但有两个局限:1) 如果只存储了预览,则无法搜索历史消息的完整内容;2) 数据量极大时,前端过滤仍有压力。

更强大的方案是实现 服务端搜索 。我们可以:

  1. 在 Server Component 或 API Route 中,读取原始数据时,不仅构建 ChatSession 模型,同时为每条消息的内容建立 倒排索引 (一个简单的版本就是 关键词 -> [会话ID, 消息ID] 的映射)。
  2. 当用户输入搜索词时,前端向一个 API 端点 ( /api/search ) 发送请求。
  3. 后端在这个索引中进行查找,快速返回匹配的会话列表。
  4. 为了提升体验,可以引入防抖(debounce),避免用户每输入一个字符就发起请求。

注意事项 :建立全文索引会占用更多内存。对于纯本地工具,我们需要权衡索引的详细程度和内存消耗。一个折中方案是只索引消息的前 N 个字符(例如500字),或者采用分段加载索引的策略。

4.3 按项目分组与筛选

聊天会话天然属于某个项目。按项目分组能极大提升浏览效率。

项目信息提取 ChatSession.projectPath 中,我们可以提取出项目名称。通常,项目名就是路径的最后一部分(例如 /Users/me/code/my-awesome-app 的项目名是 my-awesome-app )。我们可以提供一个侧边栏或下拉筛选器,列出所有唯一的项目名。

实现项目筛选器 使用 MUI 的 Chip Autocomplete FormGroup 组件来创建一组项目筛选标签。状态管理上,可以使用一个数组 selectedProjects 来记录被选中的项目。

const [selectedProjects, setSelectedProjects] = useState<string[]>([]);
const filteredSessions = useMemo(() => {
  let result = sessions;
  if (selectedProjects.length > 0) {
    result = result.filter(session => selectedProjects.includes(session.projectName));
  }
  // ... 再叠加搜索过滤
  return result;
}, [sessions, selectedProjects, searchTerm]);

4.4 开发“每周总结”视图

“每周总结”是我个人非常喜欢的一个功能。它能以周为单位,将你的聊天活动可视化,帮你回顾每周在哪些项目上投入了最多时间与 AI 进行讨论。

数据聚合逻辑

  1. 按周分组 :遍历所有会话,根据 session.timestamp 计算出它所属的年份和周数(ISO 周数)。可以使用 date-fns 库的 getISOWeek getISOWeekYear 函数。
  2. 按项目聚合 :在每一周内,再按 session.projectName 进行分组。
  3. 计算指标 :对于每一周下的每一个项目,可以计算:
    • 会话总数
    • 消息总数
    • 总对话时长(如果数据中有时间信息)
    • 一个代表性的会话标题或预览

UI 呈现 可以设计一个专门的 /weekly-summary 页面。使用 MUI 的 Grid Card 组件来布局。每一周是一个大卡片,里面包含多个代表不同项目的小卡片或列表。用 Typography Chip 组件来展示数据和标签。

交互设计 :点击某一周下的某个项目,可以跳转回主列表视图,并自动应用该项目和该周的时间范围筛选,直接看到对应的所有会话。这提供了从宏观总结到微观细节的无缝导航。

5. 开发、构建与部署实操指南

5.1 本地开发环境搭建

按照项目 README 的步骤操作基本无误,但有几个细节需要注意:

  1. 克隆与安装

    git clone https://github.com/icyJoseph/chat-recall.git
    cd chat-recall
    pnpm install # 确保使用 pnpm,如果未安装请先 `npm install -g pnpm`
    

    常见问题 :如果 pnpm install 失败,尤其是 better-sqlite3 编译失败,很可能是因为你的系统缺少编译原生模块所需的工具链。

    • macOS : 安装 Xcode Command Line Tools: xcode-select --install
    • Windows : 需要安装 Visual Studio Build Tools 或 Windows Build Tools。
    • Linux : 安装 build-essential 等基础编译工具和 python3
  2. 环境变量与配置 :本项目理论上无需环境变量,因为它直接读取固定路径。但如果你的 Claude Code 或 Cursor 安装在非标准位置,可以考虑创建一个 .env.local 文件来覆盖默认路径,例如:

    # .env.local
    CLAUDE_DATA_PATH=/custom/path/to/.claude/projects
    CURSOR_DB_PATH=/custom/path/to/state.vscdb
    

    然后在代码中通过 process.env.CLAUDE_DATA_PATH 读取。

  3. 启动开发服务器

    pnpm dev
    

    访问 http://localhost:3000 。Next.js 的热重载功能会让你在修改代码后几乎立刻看到变化。

5.2 生产构建与优化

开发完成后,需要构建一个用于生产环境(即你自己长期使用)的版本。

pnpm build

这个命令会启动 Next.js 的构建过程:

  • 检查 TypeScript 类型错误。
  • 打包并优化所有 JavaScript 和 CSS 资源。
  • 对页面进行静态生成或服务端渲染优化。

构建成功后,你可以使用以下命令启动生产服务器:

pnpm start

生产服务器更高效、稳定,适合长期在后台运行。

关于静态导出 :由于本项目严重依赖服务端读取本地文件系统的能力, 无法 使用 next export 进行完全静态导出。因为静态导出后的网站是纯前端的,无法运行 Node.js 代码。所以必须运行 Node.js 服务器 ( pnpm start )。

5.3 打包为桌面应用(可选进阶)

虽然通过浏览器访问 localhost:3000 已经可用,但作为一个本地工具,打包成独立的桌面应用体验会更佳。这里推荐使用 Tauri

Tauri 相比 Electron 的优势是打包体积极小(仅几MB),因为它使用系统自带的 WebView,而不是捆绑一个完整的 Chromium。

简要步骤

  1. 在项目根目录初始化 Tauri: pnpm add -D @tauri-apps/cli ,然后运行 pnpm tauri init
  2. 按照向导回答配置问题。前端构建命令填 pnpm build ,输出目录填 .next (Next.js 默认输出目录)。
  3. 修改 Tauri 配置 ( src-tauri/tauri.conf.json ),允许应用访问本地文件系统(需要配置 fs 作用域)。
  4. 因为 Next.js 生产服务器在 start 时运行,而 Tauri 需要的是构建好的静态文件,这里需要调整。一个可行的方法是配置 Next.js 以 静态站点生成 模式输出,但如前所述,这要求剥离服务端数据读取逻辑,将其移至 Tauri 的 Rust 后端,通过前端与后端通信来获取数据。这涉及较大的架构改动,是另一个项目的起点。

对于第一版,建议先以本地服务器形式使用,体验核心功能。桌面化可以作为未来的优化方向。

6. 隐私安全设计与常见问题排查

6.1 隐私架构深度解析

“隐私第一”不是一句空话,它贯穿于 Chat Recall 的整个架构设计。

  1. 无网络请求 :应用启动后,不会向 claude.ai cursor.sh 或任何第三方服务器发送任何 HTTP 请求。所有数据流动都发生在你的计算机内存和磁盘之间。
  2. 数据不离境 :聊天数据的读取、解析、索引、展示全流程均在本地完成。即便是搜索功能,也是在本地内存中进行的字符串匹配,没有查询词上传。
  3. 无遥测与日志 :应用不包含任何 analytics(如 Google Analytics, Mixpanel)、错误收集(如 Sentry)或使用情况统计代码。控制台日志仅在开发模式下输出,用于调试。
  4. Server Components 的屏障 :使用 Next.js Server Components 是关键。敏感的文件路径和原始聊天数据仅在 Node.js 服务端环境中被处理。传递给客户端组件的只是经过清洗和格式化后的安全数据(如标题、预览文本、脱敏后的消息内容)。客户端永远无法接触到 ~/.claude/projects/ 这样的原始路径字符串或数据库文件句柄。

6.2 常见问题与解决方案实录

在实际使用和开发中,你可能会遇到以下问题:

问题一:启动应用后看不到任何聊天记录。

  • 可能原因 1:路径不正确

    • 排查 :检查控制台(浏览器开发者工具或终端)是否有错误日志。应用在启动时会尝试读取默认路径。可以在代码中添加调试日志,打印出它正在尝试读取的完整路径。
    • 解决 :确认 Claude Code 和 Cursor 已在你当前使用的电脑上生成过聊天记录。如果它们安装在自定义位置,需要通过环境变量配置正确路径。
  • 可能原因 2:数据格式不兼容

    • 排查 :Claude Code 或 Cursor 可能更新了数据存储格式。查看项目 issues 页面是否有类似报告。
    • 解决 :尝试用文本编辑器打开 Claude 的 JSON 文件,或用 SQLite 工具打开 Cursor 的数据库,检查其结构是否与代码中的解析逻辑匹配。可能需要更新解析器。

问题二:搜索功能慢或卡顿。

  • 可能原因 :会话数量过多(例如超过 5000 条),且前端在进行全文搜索时未做优化。
  • 解决
    1. 实施虚拟滚动 :确保列表使用了 react-virtuoso ,避免渲染所有 DOM 节点。
    2. 实现服务端搜索 :如前所述,在后端建立索引。
    3. 添加搜索防抖 :在前端搜索输入框上添加防抖函数,避免频繁触发过滤计算。
    import { useDebounce } from 'use-debounce';
    const [searchTerm, setSearchTerm] = useState('');
    const [debouncedSearchTerm] = useDebounce(searchTerm, 300); // 延迟300毫秒
    // 使用 debouncedSearchTerm 进行过滤计算
    

问题三:在特定操作系统上构建失败(特别是 better-sqlite3 )。

  • 可能原因 :缺少编译原生 Node.js 模块所需的系统依赖。
  • 解决
    • macOS : 确保已安装 Xcode Command Line Tools。
    • Windows : 安装 windows-build-tools (可能需要以管理员身份运行 PowerShell: npm install --global windows-build-tools ) 或 Visual Studio 2019/2022 Build Tools。
    • Linux : 安装 build-essential , python3 , pkg-config 等包。例如在 Ubuntu/Debian 上: sudo apt-get install build-essential python3 pkg-config

问题四:生产构建后,页面样式错乱或功能异常。

  • 可能原因 :Next.js 构建过程中的静态优化与客户端动态行为不匹配,或环境变量在构建时和运行时不同。
  • 解决
    1. 运行 pnpm build 后,仔细阅读构建输出日志,看是否有警告或错误。
    2. 检查是否错误地将一个使用了浏览器 API(如 window , document )的组件标记为了 Server Component。这会导致构建错误或运行时错误。
    3. 确保所有环境变量在 .env.local 中正确设置,并且运行 pnpm start 时能读取到。

问题五:如何备份我的 Chat Recall 配置或数据?

  • 解答 :Chat Recall 本身不存储你的原始聊天数据,它只是实时读取。因此,你无需备份 Chat Recall 的数据。你需要备份的是源数据:
    • Claude Code 数据 :备份 ~/.claude/projects/ 目录(或 Windows 对应路径)。
    • Cursor 数据 :备份 state.vscdb 文件所在目录。 整个 Chat Recall 应用就是一个可执行的程序(或源代码),重新安装即可。你的所有聊天记录仍然安全地存放在原应用中。

更多推荐