- 11 个超 20KB 的 SKILL.md 拆分,大章节移入 references/details.md - 7 处非标准字段(arguments/user_invocable)移入 metadata - 修 req-dev 4 条相对链接;validate 跳过行内代码避免  误报 - validate-skills.py: 23 warns -> 0 warns,0 errors
18 KiB
18 KiB
name, description, metadata, disable-model-invocation
| name | description | metadata | disable-model-invocation | ||
|---|---|---|---|---|---|
| siyuan | 思源笔记 API 集成。通过自然语言创建、编辑、搜索笔记,管理笔记本和文档树。当用户提到思源笔记、SiYuan、笔记管理、知识库相关任务时自动激活。 |
|
true |
环境变量配置(必须)
本 skill 通过环境变量读取思源笔记连接信息,不同用户/组织配置不同的值。
在 ~/.claude/settings.json 中配置:
{
"env": {
"SIYUAN_URL": "${SIYUAN_URL}",
"SIYUAN_TOKEN": "your-api-token-here"
}
}
或在项目级 .claude/settings.json 中配置(优先级更高)。
检查配置是否就绪: 执行任何思源操作前,先确认环境变量已设置:
echo "URL: ${SIYUAN_URL:-未配置}"
echo "TOKEN: ${SIYUAN_TOKEN:+已配置}"
如果未配置,提示用户在 ~/.claude/settings.json 的 env 中添加 SIYUAN_URL 和 SIYUAN_TOKEN。
🔐 保存���禁:敏感��息脱敏
保存任何内容到思源笔记前,必须先完成脱敏处理。
脱敏规则
| 敏感类型 | 识别模式 | 脱敏方式 | 示例 |
|---|---|---|---|
| 密码 | 密码/password/passwd 附近的值 |
保留首尾各2字符,中间替换为 **** |
zh****26 |
| API Token | 长随机字符串(>16位) | 保留前4后4,中间 ... |
mkea...0jxqy |
| SSH 私钥内容 | -----BEGIN 开头 |
整体替换为 [SSH私钥-已脱敏] |
|
| IP + 端口组合 | x.x.x.x:port |
保留,但标注"内网" | 47.93.23.182(阿里云内网) |
| 访问码/PIN | 访问码/accessAuthCode 附近 |
同密码规则 | Si****26 |
脱敏示例
原文: 密码: zhiyun2026
脱敏: 密码: zh****26
原文: API Token: mkea1080c0x0jxqy
脱敏: API Token: mkea...0jxqy
原文: sshpass -p 'zhiyun2026' ssh root@...
脱敏: sshpass -p '***' ssh root@...
执行检查清单
保存笔记前逐项确认:
- 文档中无明文密码
- 文档中无完整 API Token / Bearer Token
- 文档中无 SSH 私钥内容
- 代码示例中的凭据已替换为占位符(如
<YOUR_PASSWORD>) - 命令行示例中的
-p 'xxx'已脱敏
如用户明确要求保留明文(如自用私有笔记本),须先得到用户确认再保存。
⚠️ 强制规则
创建文档前必须先查询是否存在!
# ❌ 错误:直接创建,会产生重复文档
api.create_doc(notebook_id, "/文档路径", content)
# ✅ 正确:使用 upsert 模式
api.upsert_doc(notebook_id, "/文档路径", content)
createDocWithMd API 每次调用都会创建新文档,即使路径相同也会产生重复。必须使用 upsert 模式(先查询后创建/更新)。
功能概述
- 笔记本管理: 创建、列出、删除笔记本
- 文档操作: 创建、编辑、删除、移动文档
- 内容编辑: 插入/更新/删除块内容
- 搜索功能: 全文搜索、SQL 查询
- 导入导出: Markdown 导入导出
- 资源管理: 上传附件、图片
API 基础
请求格式
所有 API 请求使用 POST 方法,需携带 Token:
curl -X POST ${SIYUAN_URL}/api/xxx \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"param": "value"}'
响应格式
{
"code": 0, // 0 表示成功
"msg": "", // 错误信息
"data": {} // 返回数据
}
自然语言操作示例
| 用户说 | 执行操作 |
|---|---|
| "创建一个笔记本叫工作日志" | 创建笔记本 |
| "在工作日志里新建一篇笔记" | 创建文档 |
| "搜索包含'会议'的笔记" | 全文搜索 |
| "列出所有笔记本" | 获取笔记本列表 |
| "把这段内容添加到今日笔记" | 追加内容 |
| "删除这篇笔记" | 删除文档 |
常用 API
笔记本操作
列出所有笔记本
curl -X POST ${SIYUAN_URL}/api/notebook/lsNotebooks \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{}'
创建笔记本
curl -X POST ${SIYUAN_URL}/api/notebook/createNotebook \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"name": "笔记本名称"}'
删除笔记本
curl -X POST ${SIYUAN_URL}/api/notebook/removeNotebook \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"notebook": "笔记本ID"}'
文档操作
重要: 创建文档前必须先检查是否已存在同名文档!
createDocWithMd每次调用都会创建新文档,即使路径相同也会产生重复。请优先使用下方的"创建或更新文档"流程。
创建或更新文档(推荐)
先查询文档是否存在,再决定创建还是更新:
# 1. 先查询是否存在(按路径精确匹配)
DOC_PATH="/网络管理/家庭Tailscale网络"
EXISTING=$(curl -s -X POST ${SIYUAN_URL}/api/query/sql \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"stmt\": \"SELECT id FROM blocks WHERE type='d' AND hpath='${DOC_PATH}' LIMIT 1\"}" \
| jq -r '.data[0].id // empty')
if [ -n "$EXISTING" ]; then
# 2a. 存在则更新
curl -s -X POST ${SIYUAN_URL}/api/block/updateBlock \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"id\": \"${EXISTING}\", \"dataType\": \"markdown\", \"data\": \"# 更新的内容\"}"
else
# 2b. 不存在则创建
curl -s -X POST ${SIYUAN_URL}/api/filetree/createDocWithMd \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"notebook": "笔记本ID", "path": "/网络管理/家庭Tailscale网络", "markdown": "# 新内容"}'
fi
创建文档 (仅新建时使用)
curl -X POST ${SIYUAN_URL}/api/filetree/createDocWithMd \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"notebook": "笔记本ID",
"path": "/文档路径/文档名",
"markdown": "# 标题\n\n正文内容..."
}'
获取文档内容
curl -X POST ${SIYUAN_URL}/api/filetree/getDoc \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"id": "文档ID"}'
删除文档
curl -X POST ${SIYUAN_URL}/api/filetree/removeDoc \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"notebook": "笔记本ID", "path": "/文档路径"}'
重命名文档
curl -X POST ${SIYUAN_URL}/api/filetree/renameDoc \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"notebook": "笔记本ID", "path": "/旧路径", "title": "新标题"}'
块操作
插入块
curl -X POST ${SIYUAN_URL}/api/block/insertBlock \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"dataType": "markdown",
"data": "要插入的内容",
"previousID": "前一个块ID"
}'
更新块
curl -X POST ${SIYUAN_URL}/api/block/updateBlock \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"dataType": "markdown",
"data": "更新后的内容",
"id": "块ID"
}'
删除块
curl -X POST ${SIYUAN_URL}/api/block/deleteBlock \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"id": "块ID"}'
搜索
全文搜索
curl -X POST ${SIYUAN_URL}/api/search/fullTextSearchBlock \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"query": "搜索关键词",
"page": 1
}'
SQL 查询
curl -X POST ${SIYUAN_URL}/api/query/sql \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"stmt": "SELECT * FROM blocks WHERE content LIKE '\''%关键词%'\'' LIMIT 10"
}'
导入导出
导出 Markdown
curl -X POST ${SIYUAN_URL}/api/export/exportMdContent \
-H "Authorization: Token ${SIYUAN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"id": "文档ID"}'
Python 封装
import os
import requests
from typing import Optional, Dict, Any
class SiYuanAPI:
"""思源笔记 API 封装"""
def __init__(self, base_url: str = None, token: str = None):
base_url = base_url or os.environ.get("SIYUAN_URL", "")
token = token or os.environ.get("SIYUAN_TOKEN", "")
self.base_url = base_url
self.headers = {
"Authorization": f"Token {token}",
"Content-Type": "application/json"
}
def _post(self, endpoint: str, data: Dict = None) -> Dict[str, Any]:
"""发送 POST 请求"""
url = f"{self.base_url}/api/{endpoint}"
response = requests.post(url, headers=self.headers, json=data or {})
result = response.json()
if result.get("code") != 0:
raise Exception(f"API Error: {result.get('msg')}")
return result.get("data")
# === 笔记本操作 ===
def list_notebooks(self) -> list:
"""列出所有笔记本"""
data = self._post("notebook/lsNotebooks")
return data.get("notebooks", [])
def create_notebook(self, name: str) -> Dict:
"""创建笔记本"""
return self._post("notebook/createNotebook", {"name": name})
def remove_notebook(self, notebook_id: str) -> None:
"""删除笔记本"""
self._post("notebook/removeNotebook", {"notebook": notebook_id})
# === 文档操作 ===
def find_doc_by_path(self, path: str) -> Optional[str]:
"""根据路径查找文档,返回文档 ID(不存在返回 None)"""
sql = f"SELECT id FROM blocks WHERE type='d' AND hpath='{path}' LIMIT 1"
result = self.sql_query(sql)
return result[0]["id"] if result else None
def create_doc(self, notebook_id: str, path: str, markdown: str) -> str:
"""创建文档,返回文档 ID(注意:会创建新文档,即使同名文档已存在)"""
return self._post("filetree/createDocWithMd", {
"notebook": notebook_id,
"path": path,
"markdown": markdown
})
def upsert_doc(self, notebook_id: str, path: str, markdown: str) -> str:
"""创建或更新文档(推荐使用)
- 如果文档已存在,更新内容
- 如果文档不存在,创建新文档
返回文档 ID
"""
existing_id = self.find_doc_by_path(path)
if existing_id:
self.update_block(existing_id, markdown)
return existing_id
else:
return self.create_doc(notebook_id, path, markdown)
def get_doc(self, doc_id: str) -> Dict:
"""获取文档内容"""
return self._post("filetree/getDoc", {"id": doc_id})
def remove_doc(self, notebook_id: str, path: str) -> None:
"""删除文档"""
self._post("filetree/removeDoc", {"notebook": notebook_id, "path": path})
# === 块操作 ===
def insert_block(self, data: str, previous_id: str = None, parent_id: str = None) -> Dict:
"""插入块"""
payload = {"dataType": "markdown", "data": data}
if previous_id:
payload["previousID"] = previous_id
if parent_id:
payload["parentID"] = parent_id
return self._post("block/insertBlock", payload)
def update_block(self, block_id: str, data: str) -> Dict:
"""更新块"""
return self._post("block/updateBlock", {
"dataType": "markdown",
"data": data,
"id": block_id
})
def delete_block(self, block_id: str) -> None:
"""删除块"""
self._post("block/deleteBlock", {"id": block_id})
# === 搜索 ===
def search(self, query: str, page: int = 1) -> Dict:
"""全文搜索"""
return self._post("search/fullTextSearchBlock", {"query": query, "page": page})
def sql_query(self, sql: str) -> list:
"""SQL 查询"""
return self._post("query/sql", {"stmt": sql})
# === 导出 ===
def export_md(self, doc_id: str) -> str:
"""导出文档为 Markdown"""
data = self._post("export/exportMdContent", {"id": doc_id})
return data.get("content", "")
# === 发布到云文档 ===
def publish_to_feishu(self, doc_id: str, doc_name: str, folder_token: str = None) -> dict:
"""
将思源文档发布到飞书云文档
Args:
doc_id: 思源文档ID
doc_name: 飞书文档名称
folder_token: 飞书文件夹token(可选)
Returns:
dict: {"url": "飞书文档链接", "document_id": "飞书文档ID"}
"""
# 1. 导出Markdown
content = self.export_md(doc_id)
if not content:
raise Exception("导出Markdown失败")
# 2. 调用飞书API创建文档
# 需要配合 feishu-doc MCP 或直接调用飞书API
# 这里返回内容,由调用方决定如何发布
return {
"content": content,
"doc_name": doc_name,
"source_doc_id": doc_id
}
# 使用示例
if __name__ == "__main__":
api = SiYuanAPI()
# 列出笔记本
notebooks = api.list_notebooks()
for nb in notebooks:
print(f"- {nb['name']} ({nb['id']})")
# 创建或更新笔记(推荐)
notebook_id = notebooks[0]["id"]
doc_id = api.upsert_doc(notebook_id, "/测试笔记", "# 标题\n\n这是测试内容")
print(f"文档 ID: {doc_id}")
# 再次调用不会创建重复文档
doc_id = api.upsert_doc(notebook_id, "/测试笔记", "# 标题\n\n更新后的内容")
print(f"更新后文档 ID: {doc_id}") # 同一个 ID
# 搜索
results = api.search("测试")
print(f"搜索结果: {len(results.get('blocks', []))} 条")
常见场景
场景 1: 创建每日笔记
from datetime import datetime
api = SiYuanAPI()
today = datetime.now().strftime("%Y-%m-%d")
markdown = f"""# {today} 工作日志
## 今日任务
- [ ] 任务1
- [ ] 任务2
## 会议记录
## 注意事项
- API Token 需要保密,不要提交到代码仓库
- 删除操作不可恢复,谨慎使用
- 大批量操作建议添加延时,避免服务器压力
- 思源笔记数据存储在 `/vol1/1000/Docker/siyuan/` (飞牛OS)
---
## 文档编写规范
> **重要**: 本章节规范所有通过 API 创建的思源笔记文档的编写标准。确保文档的可追溯性和完整性。
### 时间戳格式要求
**强制规则**: 所有时间相关的记录必须包含完整的时间戳,而不仅仅是日期。
#### ✅ 正确示例
```markdown
## 事件概述
- **发生时间**: 2026-01-15 00:00:00 - 00:46:00 CST
- **创建时间**: 2026-01-15 07:30:00 ACDT
- **最后更新**: 2026-01-15 07:35:00 ACDT
- **验证时间**: 2026-01-15 08:00:00 CST
## 事件概述
- **发生时间**: YYYY-MM-DD HH:MM:SS - HH:MM:SS TZ
- **严重程度**: P0 - 数据丢失 / P1 - 服务中断 / P2 - 性能下降 / P3 - 功能异常
- **影响范围**: 具体描述
- **根本原因**: 简短说明
- **恢复状态**: ✅ 已完全恢复 / 🔄 部分恢复 / ❌ 未恢复
---
## 时间线
YYYY-MM-DD HH:MM:SS - 事件发生 YYYY-MM-DD HH:MM:SS - 发现问题 YYYY-MM-DD HH:MM:SS - 开始处理 YYYY-MM-DD HH:MM:SS - 恢复完成 YYYY-MM-DD HH:MM:SS - 验证通过
## 详细过程
[详细描述...]
## 恢复步骤
1. **步骤 1** (执行时间: HH:MM:SS)
- 具体操作
- 结果验证
2. **步骤 2** (执行时间: HH:MM:SS)
- 具体操作
- 结果验证
## 预防措施
1. ✅ 措施 1 (完成时间: YYYY-MM-DD HH:MM:SS TZ)
2. ✅ 措施 2 (完成时间: YYYY-MM-DD HH:MM:SS TZ)
3. ⏳ 措施 3 (计划完成: YYYY-MM-DD)
## 相关资源
- 相关代码: [链接]
- 监控面板: [链接]
- 技术文档: [链接]
---
**创建时间**: YYYY-MM-DD HH:MM:SS TZ
**创建人**: qiudl
**最后验证**: YYYY-MM-DD HH:MM:SS TZ
**文档状态**: ✅ 已完成
技术方案文档规范
记录技术方案、架构设计时应包含:
# 方案标题
**提出时间**: YYYY-MM-DD HH:MM:SS TZ
**提出人**: 姓名
**评审时间**: YYYY-MM-DD HH:MM:SS TZ
**批准时间**: YYYY-MM-DD HH:MM:SS TZ
**实施时间**: YYYY-MM-DD HH:MM:SS - HH:MM:SS TZ
**状态**: 📝 草稿 / 🔍 评审中 / ✅ 已批准 / 🚀 实施中 / ✅ 已完成
## 背景
[为什么需要这个方案...]
## 目标
[期望达到的目标...]
## 技术方案
[具体的技术实现...]
## 时间规划
| 阶段 | 开始时间 | 结束时间 | 负责人 | 状态 |
|------|---------|---------|--------|------|
| 需求分析 | YYYY-MM-DD HH:MM | YYYY-MM-DD HH:MM | 姓名 | ✅ |
| 方案设计 | YYYY-MM-DD HH:MM | YYYY-MM-DD HH:MM | 姓名 | 🔄 |
| 开发实施 | YYYY-MM-DD HH:MM | YYYY-MM-DD HH:MM | 姓名 | ⏳ |
## 风险评估
[可能的风险和应对措施...]
## 验收标准
- [ ] 标准 1 (验证时间: ________)
- [ ] 标准 2 (验证时间: ________)
---
**最后更新**: YYYY-MM-DD HH:MM:SS TZ
会议记录规范
记录技术会议、评审会议时应包含:
# YYYY-MM-DD 会议标题
**会议时间**: YYYY-MM-DD HH:MM:SS - HH:MM:SS TZ
**会议地点**: 线上/线下
**参会人员**: 姓名1, 姓名2, 姓名3
**记录人**: 姓名
**会议类型**: 技术评审 / 方案讨论 / 故障复盘 / 周会
## 议题
1. 议题 1
2. 议题 2
3. 议题 3
## 讨论记录
### 议题 1: [标题]
**讨论时间**: HH:MM - HH:MM
[讨论内容...]
**决议**:
- 决定 1
- 决定 2
### 议题 2: [标题]
[...]
## 待办事项
| 任务 | 负责人 | 截止时间 | 状态 |
|------|--------|---------|------|
| 任务 1 | 姓名 | YYYY-MM-DD HH:MM | ⏳ |
| 任务 2 | 姓名 | YYYY-MM-DD HH:MM | ✅ |
## 下次会议
**计划时间**: YYYY-MM-DD HH:MM:SS TZ
**议题预告**: [...]
---
**记录完成时间**: YYYY-MM-DD HH:MM:SS TZ
操作日志规范
记录手动运维操作时应包含:
## 详细内容
以下章节已移到 [references/details.md](references/details.md):
- 备注
- 配置变更
- YYYY-MM-DD HH:MM 操作记录