Skill: moxunjinmu/siyuan-note (version 1.0.0)

--- name: siyuan-note description: 思源笔记（SiYuan Note）本地 API 操作助手。用于读写笔记本、文档、块、搜索、模板、SQL 查询等本地笔记操作。触发场景：用户提到"思源笔记"、"SiYuan"、"帮我创建文档"、"搜索笔记"、"查询数据库"等。 --- # 思源笔记（SiYuan Note）API Skill ## 基础规范 - **API 基础 URL**：`http://127.0.0.1:6806` - **认证**：请求头 `Authorization: Token <你的API Token>`（在 思源笔记 → 设置 → 关于 中查看） - **方法**：全部 POST，Content-Type: `application/json` - **返回格式**：`{ "code": 0, "msg": "", "data": ... }`，`code` 非 0 表示异常 > **注意**：代码执行必须包含 Token，调用示例会标注需要 Token 的端点。 ## 常用操作速查 ### 列出所有笔记本 ```bash curl -X POST http://127.0.0.1:6806/api/notebook/lsNotebooks \ -H "Authorization: Token <TOKEN>" ``` ### 创建文档（通过 Markdown） ```bash curl -X POST http://127.0.0.1:6806/api/filetree/createDocWithMd \ -H "Authorization: Token <TOKEN>" \ -H "Content-Type: application/json" \ -d '{"notebook": "<笔记本ID>", "path": "/我的文档", "markdown": "# 标题\n\n内容"}' ``` ### 执行 SQL 查询 ```bash curl -X POST http://127.0.0.1:6806/api/query/sql \ -H "Authorization: Token <TOKEN>" \ -H "Content-Type: application/json" \ -d '{"stmt": "SELECT * FROM blocks WHERE content LIKE '\''%关键词%'\'' LIMIT 10"}' ``` ### 插入块到文档 ```bash curl -X POST http://127.0.0.1:6806/api/block/appendBlock \ -H "Authorization: Token <TOKEN>" \ -H "Content-Type: application/json" \ -d '{"data": "新段落内容", "dataType": "markdown", "parentID": "<父块ID>"}' ``` ### 搜索文档 ```bash curl -X POST http://127.0.0.1:6806/api/query/sql \ -H "Authorization: Token <TOKEN>" \ -H "Content-Type: application/json" \ -d '{"stmt": "SELECT id, hpath, title FROM blocks WHERE type = '\''d'\'' AND title LIKE '\''%搜索词%'\'' LIMIT 20"}' ``` ## 核心工作流程 ### 工作流程 1：创建新文档 1. 调用 `lsNotebooks` 列出笔记本 → 拿到 `notebook ID` 2. 调用 `createDocWithMd` 创建文档 → 拿到 `文档ID` 3. （可选）调用 `setBlockAttrs` 设置文档属性 ### 工作流程 2：搜索并读取笔记 1. 调用 `getNotebookConf` 确认笔记本存在 2. 调用 SQL `SELECT * FROM blocks WHERE content LIKE '%关键词%' LIMIT N` 全文搜索 3. 对文档 ID 调用 `getBlockKramdown` 或 `exportMdContent` 获取内容 ### 工作流程 3：向文档追加内容 1. 获取目标文档的根块 ID（通常等于文档 ID 本身） 2. 调用 `appendBlock` 在文档末尾追加新块 3. 或调用 `prependBlock` 在文档开头插入 ### 工作流程 4：使用模板 1. 调用 `renderSprig` 渲染 Sprig 模板表达式（如 `{{now | date "2006-01-02"}}`） 2. 结合 `createDocWithMd` 创建带有日期标题的文档 ## SQL 查询要点 关键表： - `blocks` — 所有块（段落、标题、代码块等） - `id` / `hpath` / `title` / `content` / `type` / `subtype` / `markdown` / `created` / `updated` - `blocks` 表中 `type = 'd'` 为文档，标题块 `type = 'h'`，段落 `type = 'p'` 常用查询： ```sql -- 搜索包含关键词的块 SELECT id, hpath, content FROM blocks WHERE content LIKE '%关键词%' LIMIT 20; -- 列出某笔记本下所有文档 SELECT id, title, hpath FROM blocks WHERE notebook = '<笔记本ID>' AND type = 'd'; -- 搜索文档标题 SELECT id, hpath, title FROM blocks WHERE type = 'd' AND title LIKE '%标题%' LIMIT 10; ``` ## 重要限制 - SQL 接口在发布模式（Publication）下需要开启文档读写权限，否则会被禁止 - 资源文件上传使用 `multipart/form-data`，不是 JSON - API 全部为本地调用，思源笔记必须运行在本地 ## 完整 API 参考 见 [references/api.md](references/api.md)（包含所有端点的完整说明）

path: references/api.md
content: # 思源笔记 API 参考

> 来源：[官方 API 文档](https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md)

## 规范

- **Base URL**：`http://127.0.0.1:6806`
- **全部 POST**，Content-Type: `application/json`
- **认证**：`Authorization: Token <token>`（设置 → 关于 查看）
- **响应**：`{ "code": 0, "msg": "", "data": ... }`，code 非 0 = 异常

---

## 笔记本 `/api/notebook/`

### 列出笔记本
```
POST /api/notebook/lsNotebooks
无参数
```

### 打开笔记本
```json
{ "notebook": "<笔记本ID>" }
```

### 关闭笔记本
```json
{ "notebook": "<笔记本ID>" }
```

### 创建笔记本
```json
{ "name": "笔记本名称" }
```

### 重命名笔记本
```json
{ "notebook": "<笔记本ID>", "name": "新名称" }
```

### 删除笔记本
```json
{ "notebook": "<笔记本ID>" }
```

### 获取笔记本配置
```json
{ "notebook": "<笔记本ID>" }
```

### 保存笔记本配置
```json
{
  "notebook": "<笔记本ID>",
  "conf": { "name": "...", "closed": false, "refCreateSavePath": "", "createDocNameTemplate": "", "dailyNoteSavePath": "/daily note/{{now | date \"2006/01\"}}/{{now | date \"2006-01-02\"}}", "dailyNoteTemplatePath": "" }
}
```

---

## 文档 `/api/filetree/`

### 通过 Markdown 创建文档
```json
{
  "notebook": "<笔记本ID>",
  "path": "/foo/bar",
  "markdown": "# 标题\n\n内容"
}
```
- `path` 需要以 `/` 开头
- 重复调用不会覆盖已有文档

### 重命名文档（按路径）
```json
{ "notebook": "<笔记本ID>", "path": "/xxx.sy", "title": "新标题" }
```

### 重命名文档（按 ID）
```json
{ "id": "<文档ID>", "title": "新标题" }
```

### 删除文档（按路径）
```json
{ "notebook": "<笔记本ID>", "path": "/xxx.sy" }
```

### 删除文档（按 ID）
```json
{ "id": "<文档ID>" }
```

### 移动文档（按路径）
```json
{ "fromPaths": ["/xxx.sy"], "toNotebook": "<目标笔记本ID>", "toPath": "/" }
```

### 移动文档（按 ID）
```json
{ "fromIDs": ["<文档ID>"], "toID": "<目标父文档或笔记本ID>" }
```

### 根据路径获取人类可读路径
```json
{ "notebook": "<笔记本ID>", "path": "/xxx/xxx.sy" }
```

### 根据 ID 获取人类可读路径
```json
{ "id": "<块ID>" }
```

### 根据 ID 获取存储路径
```json
{ "id": "<块ID>" }
```

### 根据人类可读路径获取 IDs
```json
{ "path": "/foo/bar", "notebook": "<笔记本ID>" }
```

---

## 资源文件 `/api/asset/`

### 上传资源文件
```
POST /api/asset/upload
Content-Type: multipart/form-data
```
- `assetsDirPath`：资源目录，如 `"/assets/"` 或 `"/assets/sub/"`
- `file[]`：文件列表

---

## 块 `/api/block/`

### 插入块
```json
{
  "dataType": "markdown",
  "data": "内容",
  "nextID": "<后一个块ID>",
  "previousID": "<前一个块ID>",
  "parentID": "<父块ID>"
}
```
- `nextID` / `previousID` / `parentID` 至少有一个必须有值，优先级：nextID > previousID > parentID

### 插入前置子块
```json
{ "data": "内容", "dataType": "markdown", "parentID": "<父块ID>" }
```

### 插入后置子块
```json
{ "data": "内容", "dataType": "markdown", "parentID": "<父块ID>" }
```

### 更新块
```json
{ "dataType": "markdown", "data": "新内容", "id": "<块ID>" }
```

### 删除块
```json
{ "id": "<块ID>" }
```

### 移动块
```json
{ "id": "<块ID>", "previousID": "<前一个块ID>", "parentID": "<父块ID>" }
```

### 折叠块
```json
{ "id": "<块ID>" }
```

### 展开块
```json
{ "id": "<块ID>" }
```

### 获取块 kramdown 源码
```json
{ "id": "<块ID>" }
```

### 获取子块
```json
{ "id": "<父块ID>" }
```

### 转移块引用
```json
{ "fromID": "<源块ID>", "toID": "<目标块ID>", "refIDs": ["<引用块ID>"] }
```

---

## 属性 `/api/attr/`

### 设置块属性
```json
{ "id": "<块ID>", "attrs": { "custom-attr1": "值" } }
```
- 自定义属性必须以 `custom-` 为前缀

### 获取块属性
```json
{ "id": "<块ID>" }
```

---

## SQL `/api/query/`

### 执行 SQL 查询
```json
{ "stmt": "SELECT * FROM blocks WHERE content LIKE '%关键词%' LIMIT 10" }
```

### 提交事务
```
POST /api/sqlite/flushTransaction
无参数
```

### 常用 SQL 示例

```sql
-- 全文搜索
SELECT id, hpath, content FROM blocks WHERE content LIKE '%关键词%' LIMIT 20;

-- 列出所有文档
SELECT id, title, hpath FROM blocks WHERE type = 'd';

-- 按笔记本筛选
SELECT id, hpath, content FROM blocks WHERE notebook = '<笔记本ID>' LIMIT 20;

-- 搜索文档标题
SELECT id, hpath, title FROM blocks WHERE type = 'd' AND title LIKE '%标题%';

-- 获取块更新时间
SELECT id, updated FROM blocks ORDER BY updated DESC LIMIT 10;

-- 统计某笔记本块数
SELECT COUNT(*) FROM blocks WHERE notebook = '<笔记本ID>';
```

---

## 模板 `/api/template/`

### 渲染模板
```json
{ "id": "<文档ID>", "path": "模板文件绝对路径" }
```

### 渲染 Sprig 表达式
```json
{ "template": "/daily note/{{now | date \"2006/01\"}}/{{now | date \"2006-01-02\"}}" }
```

---

## 文件 `/api/file/`

### 获取文件
```json
{ "path": "/data/笔记本ID/文档ID.sy" }
```
- 返回 200 = 文件内容，202 = 错误信息

### 写入文件（multipart）
```
POST /api/file/putFile
Content-Type: multipart/form-data
```
- `path`：工作空间路径
- `isDir`：是否为文件夹
- `modTime`：Unix 时间戳
- `file`：文件内容

### 删除文件
```json
{ "path": "/data/xxx.sy" }
```

### 重命名文件
```json
{ "path": "/data/旧路径", "newPath": "/data/新路径" }
```

### 列出文件
```json
{ "path": "/data/笔记本ID" }
```

---

## 导出 `/api/export/`

### 导出 Markdown 文本
```json
{ "id": "<文档ID>" }
```

### 导出文件与目录
```json
{ "paths": ["/conf/appearance/boot", "/conf/appearance/langs"], "name": "导出文件名" }
```

---

## 转换 `/api/convert/`

### Pandoc 转换
```json
{ "dir": "test", "args": ["--to", "markdown_strict-raw_html", "foo.epub", "-o", "foo.md"] }
```

---

## 通知 `/api/notification/`

### 推送消息
```json
{ "msg": "消息内容", "timeout": 7000 }
```

### 推送报错消息
```json
{ "msg": "错误信息", "timeout": 7000 }
```

---

## 网络 `/api/network/`

### 正向代理
```json
{ "url": "https://example.com", "method": "GET", "timeout": 7000, "contentType": "text/html", "headers": [], "payload": {}, "payloadEncoding": "text", "responseEncoding": "text" }
```

---

## 系统 `/api/system/`

### 获取启动进度
```
POST /api/system/bootProgress
无参数
```

### 获取系统版本
```
POST /api/system/version
无参数
```

### 获取系统当前时间
```
POST /api/system/currentTime
无参数
```


path: scripts/siyuan.py
content: #!/usr/bin/env python3
"""
思源笔记 API 辅助脚本
用法: python siyuan.py <命令> [参数]

依赖: pip install requests

示例:
  python siyuan.py list-notebooks
  python siyuan.py create-doc <notebook> <path> <markdown>
  python siyuan.py search <keyword>
  python siyuan.py sql "SELECT * FROM blocks LIMIT 5"
"""

import json
import sys
import os

try:
    import requests
except ImportError:
    print("需要安装 requests: pip install requests")
    sys.exit(1)

BASE_URL = "http://127.0.0.1:6806"
[REDACTED_SECRET]"SIYUAN_TOKEN", "")


def headers():
    h = {"Content-Type": "application/json"}
    if TOKEN:
        h["Authorization"] = f"Token {TOKEN}"
    return h


def post(endpoint, data=None):
    r = requests.post(f"{BASE_URL}{endpoint}", json=data or {}, headers=headers(), timeout=10)
    r.raise_for_status()
    resp = r.json()
    if resp.get("code") != 0:
        raise Exception(f"API 错误 {resp.get('code')}: {resp.get('msg')}")
    return resp.get("data")


def list_notebooks():
    """列出所有笔记本"""
    data = post("/api/notebook/lsNotebooks")
    print(json.dumps(data, indent=2, ensure_ascii=False))


def create_doc(notebook, path, markdown):
    """通过 Markdown 创建文档"""
    doc_id = post("/api/filetree/createDocWithMd", {
        "notebook": notebook,
        "path": path,
        "markdown": markdown
    })
    print(f"文档创建成功，ID: {doc_id}")
    return doc_id


def search(keyword, limit=20):
    """全文搜索"""
    results = post("/api/query/sql", {
        "stmt": f"SELECT id, hpath, content FROM blocks WHERE content LIKE '%{keyword}%' LIMIT {limit}"
    })
    print(json.dumps(results, indent=2, ensure_ascii=False))
    return results


def search_titles(keyword, limit=20):
    """搜索文档标题"""
    results = post("/api/query/sql", {
        "stmt": f"SELECT id, hpath, title FROM blocks WHERE type = 'd' AND title LIKE '%{keyword}%' LIMIT {limit}"
    })
    print(json.dumps(results, indent=2, ensure_ascii=False))
    return results


def list_docs(notebook):
    """列出笔记本下所有文档"""
    results = post("/api/query/sql", {
        "stmt": f"SELECT id, title, hpath FROM blocks WHERE notebook = '{notebook}' AND type = 'd'"
    })
    print(json.dumps(results, indent=2, ensure_ascii=False))
    return results


def append_block(parent_id, data, data_type="markdown"):
    """向块追加子块"""
    result = post("/api/block/appendBlock", {
        "data": data,
        "dataType": data_type,
        "parentID": parent_id
    })
    block_id = result[0]["doOperations"][0]["id"]
    print(f"块插入成功，ID: {block_id}")
    return block_id


def update_block(block_id, data, data_type="markdown"):
    """更新块内容"""
    post("/api/block/updateBlock", {
        "dataType": data_type,
        "data": data,
        "id": block_id
    })
    print(f"块 {block_id} 更新成功")


def delete_block(block_id):
    """删除块"""
    post("/api/block/deleteBlock", {"id": block_id})
    print(f"块 {block_id} 删除成功")


def export_md(doc_id):
    """导出文档为 Markdown"""
    result = post("/api/export/exportMdContent", {"id": doc_id})
    print(f"路径: {result['hPath']}")
    print(f"---\n{result['content']}")
    return result


def run_sql(stmt):
    """执行 SQL 查询"""
    results = post("/api/query/sql", {"stmt": stmt})
    print(json.dumps(results, indent=2, ensure_ascii=False))
    return results


def get_version():
    """获取系统版本"""
    version = post("/api/system/version")
    print(f"思源笔记版本: {version}")


def render_sprig(template):
    """渲染 Sprig 模板"""
    result = post("/api/template/renderSprig", {"template": template})
    print(result)
    return result


def push_notification(msg, timeout=7000):
    """推送通知"""
    result = post("/api/notification/pushMsg", {"msg": msg, "timeout": timeout})
    print(f"通知已推送，ID: {result['id']}")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)

    cmd = sys.argv[1].replace("-", "_")

    if not [REDACTED_SECRET]"警告: 未设置 SIYUAN_TOKEN 环境变量，部分 API 可能无法调用")

    try:
        if cmd == "list_notebooks":
            list_notebooks()
        elif cmd == "create_doc" and len(sys.argv) >= 5:
            notebook, path, markdown = sys.argv[2], sys.argv[3], sys.argv[4]
            create_doc(notebook, path, markdown)
        elif cmd == "search" and len(sys.argv) >= 3:
            search(sys.argv[2])
        elif cmd == "search_titles" and len(sys.argv) >= 3:
            search_titles(sys.argv[2])
        elif cmd == "list_docs" and len(sys.argv) >= 3:
            list_docs(sys.argv[2])
        elif cmd == "append_block" and len(sys.argv) >= 4:
            append_block(sys.argv[2], sys.argv[3])
        elif cmd == "update_block" and len(sys.argv) >= 4:
            update_block(sys.argv[2], sys.argv[3])
        elif cmd == "delete_block" and len(sys.argv) >= 3:
            delete_block(sys.argv[2])
        elif cmd == "export_md" and len(sys.argv) >= 3:
            export_md(sys.argv[2])
        elif cmd == "sql" and len(sys.argv) >= 3:
            run_sql(sys.argv[2])
        elif cmd == "version":
            get_version()
        elif cmd == "render_sprig" and len(sys.argv) >= 3:
            render_sprig(sys.argv[2])
        elif cmd == "push_notification" and len(sys.argv) >= 3:
            push_notification(sys.argv[2])
        else:
            print("未知命令或参数不足")
            print(__doc__)
    except Exception as e:
        print(f"错误: {e}", file=sys.stderr)
        sys.exit(1)


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)