开始前(可选):版本检查
如需确认是否为最新版本,请运行:
CODEBLOCK0
- - 若远程版本获取失败:不会阻塞任务
- 若检测到版本落后:会提示但不阻塞
编辑策略选择(最重要)
根据用户意图选择正确的编辑方式,选错会导致数据丢失:
| 用户想要 | 正确做法 | 错误做法 |
|---|
| 修改单个块内容 | INLINECODE0 (最高效) | ~~apply-patch 整个文档~~ |
| 删除单个块 |
delete-block(最高效) | ~~apply-patch 整个文档~~ |
| 批量修改已有内容 |
apply-patch(update/delete/reorder/insert) | |
| 批量删除/重排块 |
apply-patch(delete/reorder) | |
| 添加新内容 |
append-block(简单稳妥)或
apply-patch insert(批量场景) | |
| 在指定位置插入新块 |
insert-block --before/--after(首选)或
apply-patch insert | |
| 替换章节内容 |
replace-section | ~~apply-patch 删除+插入~~ |
| 重构文档(如拆表格) |
replace-section --clear +
append-block 逐步重建 | ~~apply-patch 删除旧块+插入新块~~ |
| 跨章节分散修改多个块 | 编写单个 JS 脚本,循环
openDocument →
updateBlock(同一进程内版本自动刷新) | ~~并行 Bash 调用 update-block(会版本冲突)~~ |
Intent Decision Tree
CODEBLOCK1
Write Safety Protocol
写入前通常需要完成这 2 步(create-doc / rename-doc 例外):
CODEBLOCK2
- -
open-doc 和 open-section 计为"已读";headings/blocks/doc-tree 等不算 - 核心保护:版本检查(乐观锁)——写入前对比文档
updated 时间戳,若文档在读取后被其他端修改过则拒绝写入 - 连续写入安全:每次写入成功后自动刷新版本号,
open-doc → write → write → write 不会误报冲突 - ⚠️ 版本刷新仅在同一 node 进程内生效。若用多个独立 Bash 命令(如 Claude Code 并行调用),每次写入会改变文档版本,后续的独立命令会版本冲突。解决方案:对同一文档的多次写入必须串行执行,不能并行
- 读标记持久化存储在磁盘缓存中,跨 CLI 调用有效(同一台机器上的不同终端共享读标记)
- 读标记超过 3600 秒自动过期(仅作为缓存清理,版本检查才是真正的安全机制)
- 例外:
create-doc 与 rename-doc 不要求先 INLINECODE24
Core Commands Quick Reference
所有命令: INLINECODE25
读取
| Command | Signature | Description |
|---|
| INLINECODE26 | INLINECODE27 | 搜索笔记(type: p/h/l/c/d…) |
| INLINECODE28 |
{keyword} [limit] [type] | 搜索并输出 Markdown 结果页 |
|
open-doc |
{docID} [readable\|patchable] [--full] [--cursor {块ID}] [--limit-chars {N}] [--limit-blocks {N}] | 打开文档(默认 readable)。超长文档自动截断/分页,
--full 跳过截断输出全量。
副作用:标记已读 |
|
open-section |
{标题块ID} [readable\|patchable] | 读取标题下的章节内容。
副作用:标记文档已读 |
|
search-in-doc |
{docID} {关键词} [数量] | 在指定文档内搜索匹配的块 |
|
notebooks | | 列出笔记本 |
|
docs |
[notebookID] [limit] | 列出文档(含文档 ID,默认 200) |
|
headings |
{docID} [level] | 文档标题(level 格式:
h1/
h2/…/
h6,不是数字) |
|
blocks |
{docID} [type] | 文档子块(含块 ID,可用于写入) |
|
doc-children |
{notebookID} [path] | 子文档列表 |
|
doc-tree |
{notebookID} [path] [depth] | 子文档树(默认深度 4) |
|
doc-tree-id |
{docID} [depth] | 以文档 ID 展示子文档树 |
|
tag |
{tagName} | 按标签搜索 |
|
backlinks |
{blockID} | 反向链接 |
|
tasks |
[status] [days] | 任务(
[ ]/
[x]/
[-],默认 7 天) |
|
daily |
{start} {end} | Daily Note(YYYYMMDD) |
|
attr |
{name} [value] | 按属性查询(自定义属性加
custom- 前缀) |
|
bookmarks |
[name] | 书签 |
|
random |
{docID} | 随机标题 |
|
recent |
[days] [type] | 最近修改(默认 7 天) |
|
unreferenced |
{notebookID} | 未被引用的文档 |
|
check | | 连接检查 |
|
version | | 内核版本 |
写入
| Command | Signature | 适用场景 |
|---|
| INLINECODE77 | INLINECODE78 | 创建新文档(标题即文档名,初始内容仅支持 stdin) |
| INLINECODE79 |
{docID} {新标题} | 重命名文档 |
|
update-block |
{块ID} | 更新块内容(Markdown 仅支持 stdin;多块输入自动拆块安全写入) |
|
delete-block |
{块ID} | 删除单个块 |
|
append-block |
{parentID} | 添加新内容(parentID 可以是文档 ID 或标题块 ID;Markdown 仅支持 stdin) |
|
insert-block |
{--before 块ID\|--after 块ID\|--parent 块ID} | 在指定锚点插入内容(前/后/父块下;Markdown 仅支持 stdin) |
|
replace-section |
{headingID} 或
{headingID} --clear | 替换/清空章节(保留标题块本身;Markdown 仅支持 stdin) |
|
apply-patch |
{docID} (PMF 通过 stdin 传入) |
仅限批量修改/删除/重排已有块(拒绝 partial PMF) |
|
move-docs-by-id |
{targetID} {sourceIDs} | 移动文档(需先 open-doc 目标文档
和所有来源文档) |
|
subdoc-analyze-move |
{targetID} {sourceIDs} [depth] | 分析移动计划(只读) |
Common Patterns
1. 搜索并阅读
CODEBLOCK3
2. 修改已有块内容(apply-patch 安全用法)
CODEBLOCK4
3. 添加新内容到文档
CODEBLOCK5
3.5 在指定位置插入内容
CODEBLOCK6
4. 重构文档(如拆分表格为多个)
CODEBLOCK7
5. 创建新文档
CODEBLOCK8
6. 修改/删除单个块
CODEBLOCK9
注意: 若传入内容可解析为多个块(例如"段落 + $$公式$$"),update-block 会自动执行"首块 update + 后续 insert",并做写后校验,避免刷新后内容丢失。
7. 超长文档处理
CODEBLOCK10
8. 跨章节分散修改多个块(单进程批量脚本)
当需要修改同一文档中分散在不同章节的多个块时,不能用多个并行 Bash 命令(会版本冲突)。正确做法是编写单个 JS 脚本:
CODEBLOCK11
CODEBLOCK12
关键点:
- - 所有操作在同一个 node 进程内串行执行
- 每次写入前
openDocument 刷新版本号(进程内版本缓存自动更新) - JS API 签名:
updateBlock(id, markdown)、insertBlock(markdown, { previousID?, nextID?, parentID? })、deleteBlock(id)、 INLINECODE103
错误恢复
| 错误 | 原因 | 恢复方法 |
|---|
| INLINECODE104 | 块 ID 不存在或格式错误 | 重新导出 open-doc ... patchable --full,校验 block ID 后再提交 |
| 文档被清空 |
提交了不完整 PMF,缺失块被当作删除 | 用
open-doc ... patchable --full 重新导出完整 PMF 后恢复 |
| 写入围栏报错 | 未先 open-doc/open-section 或读标记过期 |
open-doc "docID" readable(或
open-section)然后重试 |
| 版本冲突报错 | 文档在读取后被其他端修改 |
open-doc "docID" readable 重新读取最新版本然后重试 |
| PMF 版本冲突 | PMF 导出后文档被修改 |
open-doc "docID" patchable --full 重新导出后再编辑,输出用
tee 保存 |
| partial PMF 被拒绝 | 分页/章节导出的 PMF 不完整 | 改用
update-block 编辑单块,或
open-section +
replace-section 编辑章节 |
| 只读模式报错 | 未设置
SIYUAN_ENABLE_WRITE=true | 在命令前加
SIYUAN_ENABLE_WRITE=true |
| 文档标题为"未命名文档" |
createDocWithMd 的 path 参数决定标题 | 用
create-doc CLI 命令(自动设置标题)或
rename-doc 修正 |
| 连接失败 | 思源未运行/端口/Token 错误 |
node index.js check 验证 |
| search 返回空 | 关键词过短/确实无匹配 | 改用
search-in-doc 限定文档范围,或扩大关键词上下文 |
| 并行写入版本冲突 | 多个独立 Bash 命令并行修改同一文档 | 改用单个 JS 脚本串行执行(见模式8),或每次写入前重新
open-doc |
Output Guidance
- - 读取命令返回格式化文本 → 直接展示给用户
- INLINECODE123 返回干净 Markdown → 适合阅读和总结(超长文档自动截断到 ~15K 字符,附带标题大纲)
- INLINECODE124 返回 PMF 格式 → 仅用于编辑后 apply-patch(超长文档自动分页,分页 PMF 含
partial=true,不可用于 apply-patch) - INLINECODE126 返回单章节内容 → 适合精确阅读/编辑特定章节
- 写入命令返回 JSON(通常很冗长) → 只提取关键信息(如新文档 ID、成功/失败)展示给用户,不要原样输出全部 JSON
- 公式渲染:行内公式用
$...$,独立公式块用 $$...$$(思源会渲染为数学块)
KaTeX Formula Rules (No Silent Rewrite)
核心原则:写入内容必须与模型输出一致。禁止在写入前/写入中对公式做隐式重写。
必须遵守
- - 数学定界符只用两种:行内
$...$,独立 INLINECODE130 - 数学模式内禁止再次出现
$(例如 $$ ... $x$ ... $$ 是错误) - 需要乘幂星号时写
e^* 或 e^{\ast},不要写 INLINECODE135 - 计数项不要写
#web_search、#tokens,改写为 N_{web\_search}、 INLINECODE139 - 不要把"给普通文本用的转义"直接搬进数学模式(如
\_、\=、\*)
常见错误与正确写法
- - 错误:
$$ ... $s_0,a_0^j,o_1^j$ ... $$(数学模式内嵌 $)
正确:
$$ ... s_0, a_0^j, o_1^j ... $$
正确:
$-\lambda \cdot N_{web\_search}$
正确: INLINECODE149
写后验证(必须)
- - 写入后立刻
open-doc {docID} readable 回读,确保模型看到的是"最终落地文本" - 至少搜索以下高风险串:
KaTeX parse error、Undefined control sequence、e^\\*、#web_search、 INLINECODE155 - 若命中,优先修正文档内容本身,不要在工具层做自动改写
Supporting Files
SQL Quick Reference
- - SQLite 语法;默认最多返回 64 行
- 时间格式 INLINECODE156
- 主要表:
blocks(块)、refs(引用)、attributes(属性) - 块类型:
d文档 h标题 p段落 l列表 c代码 t表格 m公式 b引述 s超级块 - ⚠️
tb 是分割线(thematic break / ---),不是"表格体"! 表格的块类型是 INLINECODE171 - 层级:
root_id → 文档,parent_id → 父容器 - JS API 查询:
s.executeSiyuanQuery('SQL') 执行查询,s.formatResults(r) 格式化输出
Block Type Reference(思源内核源码 treenode/node.go)
| 类型代码 | 节点类型 | 说明 | 是否容器块 |
|---|
| INLINECODE177 | NodeDocument | 文档 | 是 |
| INLINECODE178 |
NodeHeading | 标题 | 否 |
|
p | NodeParagraph | 段落 | 否 |
|
l | NodeList | 列表 | 是 |
|
i | NodeListItem | 列表项 | 是 |
|
c | NodeCodeBlock | 代码块 | 否 |
|
m | NodeMathBlock | 公式块 | 否 |
|
t | NodeTable |
表格(整体为一个块,内部 head/body/row/cell 不是独立块) | 否 |
|
b | NodeBlockquote | 引述 | 是 |
|
s | NodeSuperBlock | 超级块 | 是 |
|
tb | NodeThematicBreak |
分割线(
---),⚠️ 不是表格体 | 否 |
|
html | NodeHTMLBlock | HTML 块 | 否 |
|
av | NodeAttributeView | 属性视图(数据库) | 否 |
表格结构说明:思源中表格(t)是原子块,内部的 TableHead/TableBody/TableRow/TableCell 是 AST 节点,没有独立 block ID。编辑表格只能整体替换,不能操作单个单元格。
Notes
- - cwd 必须是 skill 目录(
index.js 所在目录) - INLINECODE193 自动从
index.js 目录加载 - 路径含空格需加引号
- Markdown 内容含换行时必须用
$'...\n...' 语法(Bash ANSI-C quoting),普通双引号 "...\n..." 中的 \n 是字面文本不是换行 - 连接检查: INLINECODE198
开始前(可选):版本检查
如需确认是否为最新版本,请运行:
bash
node index.js version-check
- - 若远程版本获取失败:不会阻塞任务
- 若检测到版本落后:会提示但不阻塞
编辑策略选择(最重要)
根据用户意图选择正确的编辑方式,选错会导致数据丢失:
| 用户想要 | 正确做法 | 错误做法 |
|---|
| 修改单个块内容 | update-block(最高效) | ~~apply-patch 整个文档~~ |
| 删除单个块 |
delete-block(最高效) | ~~apply-patch 整个文档~~ |
| 批量修改已有内容 | apply-patch(update/delete/reorder/insert) | |
| 批量删除/重排块 | apply-patch(delete/reorder) | |
| 添加新内容 | append-block(简单稳妥)或 apply-patch insert(批量场景) | |
| 在指定位置插入新块 | insert-block --before/--after(首选)或 apply-patch insert | |
| 替换章节内容 | replace-section | ~~apply-patch 删除+插入~~ |
| 重构文档(如拆表格) | replace-section --clear + append-block 逐步重建 | ~~apply-patch 删除旧块+插入新块~~ |
| 跨章节分散修改多个块 | 编写单个 JS 脚本,循环 openDocument → updateBlock(同一进程内版本自动刷新) | ~~并行 Bash 调用 update-block(会版本冲突)~~ |
Intent Decision Tree
用户想要…
├─ 查找/搜索内容 ──────→ search / search-md / tag / attr / bookmarks
├─ 在文档内搜索 ──────→ search-in-doc {docID} {关键词}
├─ 阅读文档 ──────────→ open-doc {ID} readable(超长文档自动截断,输出大纲导航)
├─ 阅读章节 ──────────→ open-section {标题块ID} readable(精确读取一个标题的内容)
├─ 浏览最近动态 ──────→ recent / tasks / daily
├─ 了解文档结构 ──────→ docs / notebooks / headings / blocks / doc-tree / doc-tree-id / doc-children
├─ 查看引用关系 ──────→ backlinks / unreferenced
├─ 修改单个块 ────────→ open-doc readable → update-block {块ID} {内容}(最高效)
├─ 删除单个块 ────────→ open-doc readable → delete-block {块ID}(最高效)
├─ 批量修改已有内容 ──→ open-doc patchable → 编辑内容 → apply-patch(支持 update/delete/reorder/insert)
├─ 创建新文档 ────────→ create-doc(指定笔记本、标题、可选初始内容)
├─ 重命名文档 ────────→ rename-doc(只需文档 ID 和新标题)
├─ 添加新内容 ────────→ open-doc readable → append-block(逐个追加)
├─ 指定位置插入 ──────→ open-doc readable → insert-block --before/--after(按锚点插入)
├─ 替换章节 ──────────→ open-doc patchable → replace-section
├─ 重构文档结构 ──────→ open-doc readable → replace-section --clear → append-block 逐步重建
├─ 组织文档层级 ──────→ subdoc-analyze-move → move-docs-by-id
├─ 跨章节分散修改 ────→ 编写单个 JS 脚本,循环 openDocument → updateBlock(见模式8)
├─ SQL 高级查询 ──────→ node -e + executeSiyuanQuery()
└─ 检查连接 ──────────→ check / version
Write Safety Protocol
写入前通常需要完成这 2 步(create-doc / rename-doc 例外):
bash
步骤 1:读取文档或章节(标记为已读,同时记录文档版本快照)
node index.js open-doc docID readable
或:node index.js open-section headingBlockID readable
步骤 2:环境变量启用写入
SIYUAN
ENABLEWRITE=true node index.js append-block docID 内容
- - open-doc 和 open-section 计为已读;headings/blocks/doc-tree 等不算
- 核心保护:版本检查(乐观锁)——写入前对比文档 updated 时间戳,若文档在读取后被其他端修改过则拒绝写入
- 连续写入安全:每次写入成功后自动刷新版本号,open-doc → write → write → write 不会误报冲突
- ⚠️ 版本刷新仅在同一 node 进程内生效。若用多个独立 Bash 命令(如 Claude Code 并行调用),每次写入会改变文档版本,后续的独立命令会版本冲突。解决方案:对同一文档的多次写入必须串行执行,不能并行
- 读标记持久化存储在磁盘缓存中,跨 CLI 调用有效(同一台机器上的不同终端共享读标记)
- 读标记超过 3600 秒自动过期(仅作为缓存清理,版本检查才是真正的安全机制)
- 例外:create-doc 与 rename-doc 不要求先 open-doc
Core Commands Quick Reference
所有命令:node index.js {command} [args]
读取
| Command | Signature | Description |
|---|
| search | {keyword} [limit] [type] | 搜索笔记(type: p/h/l/c/d…) |
| search-md |
{keyword} [limit] [type] | 搜索并输出 Markdown 结果页 |
| open-doc | {docID} [readable\|patchable] [--full] [--cursor {块ID}] [--limit-chars {N}] [--limit-blocks {N}] | 打开文档(默认 readable)。超长文档自动截断/分页,--full 跳过截断输出全量。
副作用:标记已读 |
| open-section | {标题块ID} [readable\|patchable] | 读取标题下的章节内容。
副作用:标记文档已读 |
| search-in-doc | {docID} {关键词} [数量] | 在指定文档内搜索匹配的块 |
| notebooks | | 列出笔记本 |
| docs | [notebookID] [limit] | 列出文档(含文档 ID,默认 200) |
| headings | {docID} [level] | 文档标题(level 格式:h1/h2/…/h6,不是数字) |
| blocks | {docID} [type] | 文档子块(含块 ID,可用于写入) |
| doc-children | {notebookID} [path] | 子文档列表 |
| doc-tree | {notebookID} [path] [depth] | 子文档树(默认深度 4) |
| doc-tree-id | {docID} [depth] | 以文档 ID 展示子文档树 |
| tag | {tagName} | 按标签搜索 |
| backlinks | {blockID} | 反向链接 |
| tasks | [status] [days] | 任务([ ]/[x]/[-],默认 7 天) |
| daily | {start} {end} | Daily Note(YYYYMMDD) |
| attr | {name} [value] | 按属性查询(自定义属性加 custom- 前缀) |
| bookmarks | [name] | 书签 |
| random | {docID} | 随机标题 |
| recent | [days] [type] | 最近修改(默认 7 天) |
| unreferenced | {notebookID} | 未被引用的文档 |
| check | | 连接检查 |
| version | | 内核版本 |
写入
| Command | Signature | 适用场景 |
|---|
| create-doc | {notebookID} {标题} | 创建新文档(标题即文档名,初始内容仅支持 stdin) |
| rename-doc |
{docID} {新标题} | 重命名文档 |
| update-block | {块ID} | 更新块内容(Markdown 仅支持 stdin;多块输入自动拆块安全