跳到正文
曾亮懿 ZengLiangYi Blog

从 ChatCrystal 导出到 Obsidian 的工作流

分页导出 ChatCrystal 笔记,用稳定 ID 建立 Obsidian 双向链接,并明确快照、人工编辑和增量同步之间的边界。

曾亮懿

曾亮懿

阅读约 6 分钟
目录 展开 收起

ChatCrystal 用于从 AI 编程对话中整理知识,Obsidian 则可以把这些知识与项目文档、读书笔记和自己的思考放在一起。连接两者的方式不必复杂:读取结构化笔记,生成 Markdown 文件,放进一个 Vault。

本文介绍如何通过 CLI 读取笔记,并设计供 Obsidian 阅读的独立快照。下面的命令与片段说明转换方法,Markdown 文件的生成需要自行实现。命令与返回结构按 ChatCrystal 提交 7642768 修订于 2026 年 9 月 23 日。

先明确数据方向

ChatCrystal 笔记与关联
  → crystal --json notes list / get / relations
  → Markdown 快照目录
  → Obsidian 阅读、搜索和链接

这个流程不把 Obsidian 的修改写回 ChatCrystal,也不复制 embedding 索引。两边继续使用各自的检索能力。Markdown 是可阅读的导出结果,不是完整数据库备份。

要导出的核心内容包括标题、摘要、结论、代码片段、标签、项目和创建时间。关联关系另取,用于生成指向其他已导出笔记的 wikilink。

列表只是一页,不是整个知识库

先在已配置好连接的终端验证:

crystal --version
crystal --json notes list --page 1 --limit 100

当前列表的结构是:

{
  "items": [{"id": 1, "title": "示例笔记"}],
  "total": 1,
  "offset": 0,
  "limit": 100
}

这里的 items 才是当前页的数据,不能按 { notes } 解构。默认列表也不是全量导出,导出时需要继续请求后续页,直到收集到 total 指示的笔记。

随后逐条读取详情和关系:

crystal --json notes get 1
crystal --json notes relations 1

CLI 已经处理服务端响应封装,因此详情输出可直接作为笔记对象读取,关系输出是数组。连接地址与凭据沿用 CLI 的现有配置,远端连接方式参考项目 CLI 文档。

组织一次完整导出

本文参照的 ChatCrystal 版本要求 Node.js 24。先确认列表、详情和关系命令均能正常返回,再按以下顺序实现转换:

  1. 分页收集全部笔记 ID,核对总数与分页进度。
  2. 读取每条笔记的完整详情和关联关系。
  3. 将字段转换为 Markdown,检查代码块和关联链接。
  4. 写入 Vault 内一个新的目录,例如 ChatCrystal-2026-09-24,核对成功后再用于阅读。

导出期间应暂停导入、摘要和人工修改。多次 CLI 请求不是数据库事务快照,总数相同也不能证明内容未变。发生请求或磁盘写入错误时,应将本次输出视为未完成,保留旧快照并在新目录中重试。

用 ID 做文件名,用标题做展示

标题不是可靠的文件标识:两条笔记可能同名,标题可能变化,还可能包含路径分隔符。可以使用 note-<id>.md,并把标题写入 frontmatter 和正文:

---
title: "SQLite 事务与恢复"
chatcrystal_id: 42
tags: ["sqlite", "recovery"]
project: "ChatCrystal"
created: "2026-06-11T01:00:00Z"
---

上面是示意。可以使用 JSON 字符串序列化生成这些 YAML 值,处理引号和换行,而不是只替换双引号。created 复制源笔记的时间值;没有时间就保留为空,不替它推断时区。

同一个 ChatCrystal 实例中,标题变动不会改变输出文件名。来自不同实例的相同数字 ID 则不能直接混用,应分别输出到不同目录。

关联关系要处理两个方向

一条关系可能是当前笔记指向其他笔记,也可能是其他笔记指向当前笔记。目标不能永远取 target_note_id:

const other = relation.source_note_id === note.id
  ? relation.target_note_id
  : relation.target_note_id === note.id
    ? relation.source_note_id
    : null;

只应为本次导出集合内的目标生成链接,使用与文件名一致的 [[note-42]],并保留源 ID、目标 ID、关系类型和置信度。例如 [[note-2]] → [[note-1]] — DEPENDS_ON (80%) 表示笔记 2 依赖笔记 1;在任一端查看时,箭头方向都保持一致。置信度为 0 也应显示,而不是被真假判断误删。

Obsidian 支持 wikilink 和反向链接;关系是否有业务意义仍取决于源数据,不能把一条连线等同于经过验证的因果关系。内部链接说明

每个快照目录都可能包含 note-42.md。不要把很多完整快照同时长期放在同一个 Vault 里,否则短文件名链接可能产生歧义。可把历史快照移到 Vault 外,或在长期方案中改为明确的 Vault 相对路径链接。

代码围栏不能固定为三个反引号

导出的代码可能本身就是 Markdown,其中包含三个反引号。如果外层也用三个,后半段代码会意外变成正文。

生成代码块时,先寻找代码中最长的连续反引号,再生成更长的外层围栏;语言标记只保留安全的标识字符。这样能保留代码原文,而不是通过删除反引号“修好”格式。

转换时应保留完整摘要和标签原值;若希望规范大小写或适配特殊标签字符,应另外定义规则,而不是在导出时悄悄改变语义。

快照与增量同步是两个承诺

“只导出 updated_at 晚于上次运行的笔记”还不够实现可靠同步:正文没变但关系变了怎么办?删除如何传播?标题变动是否留下旧文件?用户已在 Obsidian 中修改的内容该保留哪一份?

当前 CLI 列表并没有在本文使用的返回契约中提供足够的同步版本信息。这里采用全量快照的设计,每次写入新的目录。这样可以先确认导出的准确性,再决定是否需要更复杂的同步系统。

如果之后实现增量同步,应至少维护稳定 ID 到文件的映射、内容与关系的哈希、删除策略和人工编辑冲突规则。一次失败不能把同步水位提前到当前时间,否则可能永久跳过未完成的笔记。

定时任务也应遵守这个边界:每次使用唯一目录、记录退出码、仅在成功后切换到新快照。不要直接每小时重写你正在编辑的 Vault 文件。

在 Obsidian 中怎样使用

打开新目录中的笔记,确认正文、代码块和相关笔记链接正常。你可以通过标题和正文搜索知识,通过标签浏览主题,再沿反向链接探索关系。建议把自己的补充写进独立笔记,通过链接引用导出内容,避免下一次导出与人工编辑争夺同一文件。

若需要 ChatCrystal 的语义搜索,可在终端执行:

crystal search "SQLite 事务恢复" --limit 5

也可以进一步集成到自己的工作流,但不要把未转义的选中文本直接插进 shell 命令。搜索与导出是两种入口,不需要把向量文件搬入 Obsidian。

如何检查导出是否可信

实现自动导出时,建议用构造的 CLI 响应验证分页、同名标题、关系方向、零置信度、嵌套围栏、请求失败和禁止覆盖,再对实际知识库做抽样检查。

首次运行后,至少核对笔记总数、一条长代码笔记、两条相互关联的笔记,以及包含引号的标题。确认满意后再把输出用于日常阅读。

这条流程的目标是让知识能够离开工具继续使用。导入端的结构差异见五种 AI 对话格式,检索层的取舍见本地向量数据库选型。

分享:

相关文章

ChatCrystal 入门:从安装到第一条可搜索笔记

按 ChatCrystal 0.6.1 的实际 CLI,依次验证安装、服务、两类模型、导入、摘要与搜索,区分 npm 包和桌面应用的使用入口。

曾亮懿 曾亮懿
阅读约 5 分钟
#ChatCrystal #Node.js #知识管理

AI 对话格式差异:事件、快照与跨库消息定位

对照五个固定适配器,区分 JSONL 容器、事件语义与 SQLite 键布局,用两个内存数据库观察消息索引、孤立数据和未知版本。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #Node.js #SQLite

AI 对话管理方案:四种存储路径与迁移验收

比较 Claude Code、Cursor、Copilot、Trae 的固定适配器输入,区分原始副本、可读导出与知识库,并用合成清单验证身份和信息保留。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #Node.js #SQLite

从文章到代码

到我的 GitHub 看看 ChatCrystal、开发者工具和日常学习记录,欢迎交流实现细节。