Claude Code 会话攒太多,我用 Go 写了个本地管理台
Claude Code 的会话都堆在 ~/.claude/projects 下面,一个会话一个 uuid.jsonl,父目录还是 cwd 编码过的名字。会话攒多之后,想找回某次对话只能翻目录,或者先 grep 一遍再打开文件。自带的 resume 列表解决不了跨项目查找。
所以我写了个本地工具:Go + SQLite + 原生前端,单二进制,只监听 127.0.0.1:7788。它能按项目浏览会话、全文搜索、做多层归类、看 fork 谱系,还能调本机 Claude Code 给会话写摘要。
项目叫 session-deck,代码在 github.com/zhangweiwei…,Apache-2.0。
图 1:三栏布局。左边项目/归类,中间会话列表,右边详情。
先看它做了什么
图 2:搜索、切换项目、展开消息的操作过程。
| 能力 | 实现方式 |
|---|---|
| 项目归并 | 读 jsonl 里的 cwd,把会话归到对应项目,不移动文件 |
| 全文搜索 | SQLite FTS5,Ctrl+K 唤起 |
| 逻辑归类 | 多层文件夹,只写 SQLite |
| 会话谱系 | 解析 forkedFrom,画家族树 |
| 恢复会话 | Windows Terminal + PowerShell,降级到 cmd |
| AI 摘要 | 调本机 claude CLI,不另配 API key |
| 实时刷新 | hooks 触发增量扫描,SSE 推给前端 |
一条原则:jsonl 是唯一真值
Claude Code 自己写 ~/.claude/projects/<cwd>/<uuid>.jsonl。这个目录我不重构,SQLite 只存索引、归类、AI 摘要这些本地元数据。删掉 data/sessions.db 不会影响任何会话,下次启动重新扫一遍就回来了。
图 3:数据流。SQLite 是可丢的索引层。
归类是纯逻辑的。你在界面里建多级文件夹、把会话拖进去,改的是 SQLite 里的 folders 表,~/.claude/projects 的目录结构一个字节都不动。这样做的好处是,Claude Code 升级、/cd 切目录、手动整理文件夹,都不会让工具里的归类错乱。
写操作只有四类:重命名(追加一行 custom-title)、分支/副本(逐行复制)、新建会话(写新的 jsonl)、删除(移到回收站)。forkedFrom 只读不写。
增量扫描:改了解析逻辑得主动失效
每 30 秒扫一次,加上 hooks 和手动同步。如果每轮都重读几百个 jsonl,磁盘和 CPU 都受不了,所以扫描器会先 stat 文件:size 和 mtime 都和索引里那行一致、状态还是 ok,直接跳过。
图 4:判定顺序。绝大多数会话停在上面的分支。
这里有个容易踩的坑。改了解析方式之后,重启服务可能发现数据还是旧的。原因很直白:文件没变,size 和 mtime 都没变,扫描器按契约跳过了。
解决办法是强制失效。凡是改动了解析逻辑的迁移,都在迁移里加一句:
UPDATE sessions SET file_mtime = -1;
-1 跟任何真实 mtime 都对不上,下一轮必然重读。新增解析字段的迁移也走这个路子。同样的机制还处理了另一种情况:首次启动时 FTS 表是空的,但 sessions 已经有数据,这时也会把 file_mtime 重置,让所有行重新进索引。
同一个会话 ID 可能出现在多个项目目录里(比如会话中途 /cd)。扫描器取 mtime 最新的那份,旧的索引行会被覆盖。
中文搜索:trigram 和「至少三个字」
SQLite 的 FTS5 默认分词器对中文不友好,一整句会被当成一个 token。我选了 trigram 分词器,它按三字符滑窗切分,中文子串能直接命中,比如搜「线程池配置」不用先分词。
trigram 的代价是查询长度至少 3 个字符。少于 3 个字的查询,FTS 直接返回空,所以这部分走 LIKE:
// trigram 检索要求至少 3 个字符,短查询继续走 LIKE。
if utf8.RuneCountInString(q) >= 3 {
return s.searchSessions(ctx, f, where, args)
}
where = append(where, "(s.title LIKE ? OR s.last_prompt LIKE ? OR s.id LIKE ?)")
另一个限制是索引体积。jsonl 里经常有超大的工具输出,一条消息几 MB 不奇怪。扫描器给正文设了 200KB 上限,只索引前 200KB,超出的部分不入库。搜索命中后的高亮用 FTS5 的 snippet() 生成。
AI 摘要生成之后,会单独回填到 FTS 的 summary 列。这样搜「鉴权」时,即使会话正文里没出现这个词,摘要里写到了也能命中,列表里还会高亮摘要片段。
两个 project 维度和 forkedFrom
一个会话有两个「项目」属性:
project_id:从 jsonl 的cwd得出,是事实来源。display_project_id:会话实际展示在哪个项目下。
平时两者相同。如果文件里最新的 cwd 变了,扫描器会自动把展示位置改到新项目。一旦你手动移动过会话,display_project_source 记成 manual,自动归属就停了,手动结果优先。
分支和副本会写 forkedFrom,扫描器把它解析成 parent_id 和 fork_point_uuid。详情页能顺着血缘链往上翻,也能看到从第几条消息分出去的。父会话被删掉后关系还在,界面标成「缺失」,不会强行清理。
AI 摘要:把会话重新喂给本机 Claude Code
会话列表里有一行摘要,比「最后一条 prompt」有用得多。摘要走的是本机的 claude 可执行文件,没有另外配 API key,登录态直接复用现成的。
钱还是要花的,所以输入要省。一个几千行的会话不会整个塞进去,采样规则是:前 8 条用户提问 + 后 4 条用户提问 + 最后一条回答的结论段(最多 400 字符),默认 1500 字符预算。
图 5:采样、过滤、脱敏、调用的顺序。
读取时顺手做了几件事:跳过 isSidechain 的子代理消息;工具结果也记成 user 消息,得剔掉,否则输入全是命令输出;「继续」「好的」「可以」这类确认短句不计入。最后过一遍脱敏规则,sk-、ghp_、AKIA、Bearer、password= 这些串在进模型之前就被替换掉。
调用命令长这样:
claude -p --output-format json --no-session-persistence --tools "" \
--json-schema '<压成单行的 JSON Schema>'
--tools "" 关掉工具,--no-session-persistence 不留会话文件,避免摘要任务自己也被索引进去。输出由 JSON Schema 约束成固定字段:目标、关键动作、结论、话题标签,解析时不用正则在自由文本里捞。
不同版本的 CLI 对参数支持不一样,所以启动时会依次试 bare、lean、plain 三档,用最小的请求探一下,取第一个能跑通的。批量摘要默认 10 条一起发,某条返回的 JSON 不合法就拆成单条重试。token 用量和累计成本在设置页能看到。
派生会话有单独处理:分叉之后新增不到 3 条消息,直接继承父摘要;否则只采样分叉点之后的内容。不然每个分支都要为同一段历史付一次钱。
Windows 上绕不开的几处
工具目前只支持 Windows,原因主要在恢复会话。
恢复会话要复用最近的 Windows Terminal 窗口,并在项目目录里执行 claude --resume <id>。命令是这样拼的:
wt.exe -w 0 nt -d <项目路径> pwsh.exe -NoExit -EncodedCommand <base64>
-w 0 表示复用当前窗口,nt 是新标签页。PowerShell 脚本先清掉 Claude Code 相关的环境变量,再进目录执行 resume。降级顺序是:pwsh + Terminal → powershell + Terminal → cmd /c start 新开窗口 → 都不行就返回错误,同时把可直接复制的命令给出来。
安全上有三道限制:会话 ID 必须是 UUID;项目路径必须已经出现在索引里;命令用参数数组传递,不拼 shell 字符串。另外,重命名和删除之前会检查 ~/.claude/sessions/*.json,会话正在运行就返回 409,不让改。
删除不是真删。文件先移到 ~/.claude-session/trash/<session-id>/,索引行标成 deleted,从回收站可以还原或彻底清掉。
还有个 Windows 特有的小问题:发布版用 -H windowsgui 编译,程序自己没控制台,但它调用的 claude 子进程是控制台程序,Windows 会给它新开一个黑窗口,每跑一次摘要闪一下。解决办法是给子进程加 CREATE_NO_WINDOW:
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
跑起来
从源码跑:
go run ./cmd/server
构建发布版:
go build -trimpath -ldflags "-H windowsgui -s -w" -o bin/session-deck.exe ./cmd/server
双击 exe 或 start.bat,浏览器打开 http://127.0.0.1:7788。默认读 ~/.claude/projects,数据库在 data/sessions.db,日志在 data/session-deck.log。
想让它更实时,可以把 hooks 片段加到 ~/.claude/settings.json,GET /api/hooks/config 能拿到现成的 JSON。配置之后,每次 Claude Code 回复结束都会触发一次增量扫描,页面通过 SSE 自动刷新;不配也有 30 秒定时扫描兜底。
限制和后续
- 只支持 Windows。macOS / Linux 上的终端恢复要重写,暂时没做。
- 本机得装好并登录 Claude Code,AI 摘要才有用。
- AI 摘要会消耗 token,批量跑之前先看设置页的成本估算。
- 不是云端服务,换台机器数据不跟着走。
路线图上是会话内容编辑、标签与批量操作、多实例访问。代码总共约 9000 行 Go 加 5000 行原生前端,直接依赖只有一个 modernc.org/sqlite,用来免掉 CGO。
仓库在 github.com/zhangweiwei…,README 里有完整的 API 路由表。如果你也在 ~/.claude/projects 里翻过会话,可以拉下来试试。