--- name: siyuan description: 思源笔记 API 集成。通过自然语言创建、编辑、搜索笔记,管理笔记本和文档树。当用户提到思源笔记、SiYuan、笔记管理、知识库相关任务时自动激活。 metadata: invocation: user-invoked disable-model-invocation: true --- ## 环境变量配置(必须) 本 skill 通过环境变量读取思源笔记连接信息,不同用户/组织配置不同的值。 **在 `~/.claude/settings.json` 中配置:** ```json { "env": { "SIYUAN_URL": "${SIYUAN_URL}", "SIYUAN_TOKEN": "your-api-token-here" } } ``` 或在项目级 `.claude/settings.json` 中配置(优先级更高)。 **检查配置是否就绪:** 执行任何思源操作前,先确认环境变量已设置: ```bash 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 私钥内容 - [ ] 代码示例中的凭据已替换为占位符(如 ``) - [ ] 命令行示例中的 `-p 'xxx'` 已脱敏 > 如用户明确要求保留明文(如自用私有笔记本),须先得到用户确认再保存。 --- ## ⚠️ 强制规则 **创建文档前必须先查询是否存在!** ```python # ❌ 错误:直接创建,会产生重复文档 api.create_doc(notebook_id, "/文档路径", content) # ✅ 正确:使用 upsert 模式 api.upsert_doc(notebook_id, "/文档路径", content) ``` `createDocWithMd` API 每次调用都会创建新文档,即使路径相同也会产生重复。必须使用 upsert 模式(先查询后创建/更新)。 --- ## 功能概述 - **笔记本管理**: 创建、列出、删除笔记本 - **文档操作**: 创建、编辑、删除、移动文档 - **内容编辑**: 插入/更新/删除块内容 - **搜索功能**: 全文搜索、SQL 查询 - **导入导出**: Markdown 导入导出 - **资源管理**: 上传附件、图片 --- ## API 基础 ### 请求格式 所有 API 请求使用 POST 方法,需携带 Token: ```bash curl -X POST ${SIYUAN_URL}/api/xxx \ -H "Authorization: Token ${SIYUAN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"param": "value"}' ``` ### 响应格式 ```json { "code": 0, // 0 表示成功 "msg": "", // 错误信息 "data": {} // 返回数据 } ``` --- ## 自然语言操作示例 | 用户说 | 执行操作 | |--------|----------| | "创建一个笔记本叫工作日志" | 创建笔记本 | | "在工作日志里新建一篇笔记" | 创建文档 | | "搜索包含'会议'的笔记" | 全文搜索 | | "列出所有笔记本" | 获取笔记本列表 | | "把这段内容添加到今日笔记" | 追加内容 | | "删除这篇笔记" | 删除文档 | --- ## 常用 API ### 笔记本操作 #### 列出所有笔记本 ```bash curl -X POST ${SIYUAN_URL}/api/notebook/lsNotebooks \ -H "Authorization: Token ${SIYUAN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{}' ``` #### 创建笔记本 ```bash curl -X POST ${SIYUAN_URL}/api/notebook/createNotebook \ -H "Authorization: Token ${SIYUAN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"name": "笔记本名称"}' ``` #### 删除笔记本 ```bash curl -X POST ${SIYUAN_URL}/api/notebook/removeNotebook \ -H "Authorization: Token ${SIYUAN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"notebook": "笔记本ID"}' ``` --- ### 文档操作 > **重要**: 创建文档前必须先检查是否已存在同名文档!`createDocWithMd` 每次调用都会创建新文档,即使路径相同也会产生重复。请优先使用下方的"创建或更新文档"流程。 #### 创建或更新文档(推荐) 先查询文档是否存在,再决定创建还是更新: ```bash # 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 ``` #### 创建文档 (仅新建时使用) ```bash 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正文内容..." }' ``` #### 获取文档内容 ```bash curl -X POST ${SIYUAN_URL}/api/filetree/getDoc \ -H "Authorization: Token ${SIYUAN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"id": "文档ID"}' ``` #### 删除文档 ```bash curl -X POST ${SIYUAN_URL}/api/filetree/removeDoc \ -H "Authorization: Token ${SIYUAN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"notebook": "笔记本ID", "path": "/文档路径"}' ``` #### 重命名文档 ```bash curl -X POST ${SIYUAN_URL}/api/filetree/renameDoc \ -H "Authorization: Token ${SIYUAN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"notebook": "笔记本ID", "path": "/旧路径", "title": "新标题"}' ``` --- ### 块操作 #### 插入块 ```bash curl -X POST ${SIYUAN_URL}/api/block/insertBlock \ -H "Authorization: Token ${SIYUAN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "dataType": "markdown", "data": "要插入的内容", "previousID": "前一个块ID" }' ``` #### 更新块 ```bash curl -X POST ${SIYUAN_URL}/api/block/updateBlock \ -H "Authorization: Token ${SIYUAN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "dataType": "markdown", "data": "更新后的内容", "id": "块ID" }' ``` #### 删除块 ```bash curl -X POST ${SIYUAN_URL}/api/block/deleteBlock \ -H "Authorization: Token ${SIYUAN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"id": "块ID"}' ``` --- ### 搜索 #### 全文搜索 ```bash curl -X POST ${SIYUAN_URL}/api/search/fullTextSearchBlock \ -H "Authorization: Token ${SIYUAN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "query": "搜索关键词", "page": 1 }' ``` #### SQL 查询 ```bash 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 ```bash curl -X POST ${SIYUAN_URL}/api/export/exportMdContent \ -H "Authorization: Token ${SIYUAN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"id": "文档ID"}' ``` --- ## Python 封装 ```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: 创建每日笔记 ```python 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 **文档状态**: ✅ 已完成 ``` ### 技术方案文档规范 记录技术方案、架构设计时应包含: ```markdown # 方案标题 **提出时间**: 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 ``` ### 会议记录规范 记录技术会议、评审会议时应包含: ```markdown # 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 ``` ### 操作日志规范 记录手动运维操作时应包含: ```markdown ## 详细内容 以下章节已移到 [references/details.md](references/details.md): - 备注 - 配置变更 - YYYY-MM-DD HH:MM 操作记录