- Add install_name, install_type, dir_category fields to all 62 plugin.json files to resolve name-mapping and skill-vs-command routing issues - Add install-skills.sh: idempotent cross-machine skill sync script - Routes skill→~/.claude/skills/<name>/, command→~/.claude/commands/<name>.md - rsync full skills/ directory (preserves multi-file skills like dev-test, req-deploy) - State file ~/.claude/.installed-skills.json tracks installed versions - Conflict detection: warns before overwriting locally modified files - --dry-run, --category, --force, --cleanup, --list flags - Add 9 new plugins migrated from local ~/.claude (agent-swarm, ai-chat, defect-analysis, executing-plans, finishing-branch, frontend-design, req-audit, req-lookback, req-retro) - Add update-plugin-meta.py helper used to bulk-update plugin.json - Fix siyuan SKILL.md: remove hardcoded server credentials, use env vars Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
973 lines
25 KiB
Markdown
973 lines
25 KiB
Markdown
---
|
||
name: siyuan
|
||
description: 思源笔记 API 集成。通过自然语言创建、编辑、搜索笔记,管理笔记本和文档树。当用户提到思源笔记、SiYuan、笔记管理、知识库相关任务时自动激活。
|
||
---
|
||
|
||
# 思源笔记 API 集成 Skill
|
||
|
||
## 环境变量配置(必须)
|
||
|
||
本 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`。
|
||
|
||
---
|
||
|
||
## 🔐 保存<E4BF9D><E5AD98><EFBFBD>禁:敏感<E6958F><E6849F>息脱敏
|
||
|
||
**保存任何内容到思源笔记前,必须先完成脱敏处理。**
|
||
|
||
### 脱敏规则
|
||
|
||
| 敏感类型 | 识别模式 | 脱敏方式 | 示例 |
|
||
|---------|---------|---------|------|
|
||
| 密码 | `密码`/`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'` 已脱敏
|
||
|
||
> 如用户明确要求保留明文(如自用私有笔记本),须先得到用户确认再保存。
|
||
|
||
---
|
||
|
||
## ⚠️ 强制规则
|
||
|
||
**创建文档前必须先查询是否存在!**
|
||
|
||
```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
|
||
|
||
## 会议记录
|
||
|
||
## 备注
|
||
"""
|
||
|
||
# 获取或创建日记笔记本
|
||
notebooks = api.list_notebooks()
|
||
diary_nb = next((nb for nb in notebooks if nb["name"] == "日记"), None)
|
||
if not diary_nb:
|
||
diary_nb = api.create_notebook("日记")["notebook"]
|
||
|
||
# 使用 upsert_doc 避免重复创建
|
||
doc_id = api.upsert_doc(diary_nb["id"], f"/{today}", markdown)
|
||
print(f"每日笔记: {doc_id}")
|
||
```
|
||
|
||
### 场景 2: 批量导入 Markdown 文件
|
||
|
||
```python
|
||
from pathlib import Path
|
||
|
||
api = SiYuanAPI()
|
||
notebook_id = "目标笔记本ID"
|
||
md_folder = "/path/to/markdown/files"
|
||
|
||
for md_file in Path(md_folder).glob("*.md"):
|
||
with open(md_file, "r", encoding="utf-8") as f:
|
||
content = f.read()
|
||
|
||
doc_name = md_file.stem
|
||
# 使用 upsert_doc 支持增量导入,避免重复
|
||
doc_id = api.upsert_doc(notebook_id, f"/{doc_name}", content)
|
||
print(f"导入: {doc_name} -> {doc_id}")
|
||
```
|
||
|
||
### 场景 3: 搜索并汇总
|
||
|
||
```python
|
||
api = SiYuanAPI()
|
||
|
||
# 搜索所有包含"会议"的内容
|
||
results = api.search("会议")
|
||
blocks = results.get("blocks", [])
|
||
|
||
print(f"找到 {len(blocks)} 条相关内容:")
|
||
for block in blocks[:10]:
|
||
print(f"- {block.get('content', '')[:50]}...")
|
||
```
|
||
|
||
### 场景 4: 发布合同到飞书云文档
|
||
|
||
```python
|
||
from siyuan import SiYuanAPI
|
||
|
||
api = SiYuanAPI()
|
||
|
||
# 1. 从思源笔记导出合同内容
|
||
contract_doc_id = "20260202080355-2c0vgac" # 合同文档 ID
|
||
markdown_content = api.export_md(contract_doc_id)
|
||
|
||
# 2. 使用飞书 MCP 创建云文档
|
||
# 调用 feishu-doc MCP 的 create_and_write_document 工具
|
||
# - title: "物流服务合同-妗晨与名风"
|
||
# - content: markdown_content
|
||
# - folder_token: (可选,指定目标文件夹)
|
||
|
||
# 3. 完整的发布函数
|
||
def publish_to_feishu(doc_id: str, doc_title: str = None) -> dict:
|
||
"""
|
||
将思源笔记文档发布到飞书云文档
|
||
|
||
Args:
|
||
doc_id: 思源笔记文档 ID
|
||
doc_title: 飞书文档标题(可选,默认使用原文档标题)
|
||
|
||
Returns:
|
||
dict: {"siyuan_doc_id": str, "content": str, "feishu_url": str}
|
||
"""
|
||
api = SiYuanAPI()
|
||
|
||
# 导出 Markdown 内容
|
||
content = api.export_md(doc_id)
|
||
|
||
# 获取原文档标题(如果未指定)
|
||
if not doc_title:
|
||
doc_info = api.get_block(doc_id)
|
||
doc_title = doc_info.get("content", "未命名文档")
|
||
|
||
# 返回准备好的内容,由调用者使用 feishu-doc MCP 创建
|
||
return {
|
||
"siyuan_doc_id": doc_id,
|
||
"doc_title": doc_title,
|
||
"content": content
|
||
}
|
||
|
||
# 使用示例
|
||
result = publish_to_feishu("20260202080355-2c0vgac", "物流服务合同-妗晨与名风-2026")
|
||
print(f"准备发布: {result['doc_title']}")
|
||
print(f"内容长度: {len(result['content'])} 字符")
|
||
|
||
# 然后调用 feishu-doc MCP:
|
||
# mcp__feishu-doc__create_and_write_document(
|
||
# title=result["doc_title"],
|
||
# content=result["content"]
|
||
# )
|
||
```
|
||
|
||
**工作流说明**:
|
||
|
||
1. **创建合同** (biz-contract 技能) → 生成合同 Markdown 文本
|
||
2. **存储到思源** (siyuan 技能) → 保存到"商务合同"笔记本,便于预览和修改
|
||
3. **发布到飞书** (feishu-doc MCP) → 导出并创建飞书云文档
|
||
|
||
**商务合同笔记本配置**:
|
||
|
||
| 配置项 | 值 |
|
||
|--------|-----|
|
||
| 笔记本名称 | 商务合同 |
|
||
| 笔记本 ID | `20260202080313-kjtgg1j` |
|
||
| 路径规范 | `/{合同类型}/{甲方简称}-{乙方简称}-{年份}` |
|
||
|
||
---
|
||
|
||
## 注意事项
|
||
|
||
- 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
|
||
|
||
## 配置变更
|
||
|
||
**变更时间**: 2026-01-15 01:12:00 CST
|
||
**生效时间**: 2026-01-15 01:15:00 CST
|
||
```
|
||
|
||
#### ❌ 错误示例
|
||
|
||
```markdown
|
||
- **发生时间**: 2026-01-15
|
||
- **创建时间**: 2026-01-15
|
||
- **最后更新**: 今天
|
||
- **验证时间**: 早上
|
||
```
|
||
|
||
#### 时间格式规范
|
||
|
||
| 场景 | 格式 | 示例 |
|
||
|------|------|------|
|
||
| 精确时间点 | `YYYY-MM-DD HH:MM:SS TZ` | `2026-01-15 02:00:01 CST` |
|
||
| 时间范围 | `YYYY-MM-DD HH:MM:SS - HH:MM:SS TZ` | `2026-01-15 00:00:00 - 00:46:00 CST` |
|
||
| 跨天时间范围 | `YYYY-MM-DD HH:MM:SS - YYYY-MM-DD HH:MM:SS TZ` | `2026-01-15 23:00:00 - 2026-01-16 01:00:00 CST` |
|
||
|
||
**时区标注**:
|
||
- CST: 中国标准时间 (服务器时间、生产环境)
|
||
- ACDT: 澳大利亚中部夏令时 (au-dev 本地开发时间)
|
||
- UTC: 协调世界时 (国际标准)
|
||
|
||
### 文档元信息要求
|
||
|
||
所有运维、技术类笔记文档必须在开头包含元信息:
|
||
|
||
```markdown
|
||
# 文档标题
|
||
|
||
**创建时间**: YYYY-MM-DD HH:MM:SS TZ
|
||
**创建人**: qiudl / Claude
|
||
**最后更新**: YYYY-MM-DD HH:MM:SS TZ
|
||
**文档状态**: ✅ 已完成 / 🔄 进行中 / ⚠️ 待验证 / ❌ 已废弃
|
||
**相关项目**: 项目名称
|
||
**标签**: #运维 #数据库 #备份
|
||
|
||
---
|
||
|
||
[正文内容...]
|
||
```
|
||
|
||
### 事件记录规范
|
||
|
||
记录重大事件或故障时必须包含:
|
||
|
||
1. **事件时间**: 完整的开始和结束时间(精确到秒)
|
||
2. **严重程度**: P0/P1/P2/P3
|
||
3. **影响范围**: 具体说明受影响的系统和用户
|
||
4. **事件描述**: 清晰简洁的事件说明
|
||
5. **恢复过程**: 详细的恢复步骤和时间点
|
||
6. **恢复完成时间**: 精确的恢复完成时间
|
||
7. **根本原因**: RCA (Root Cause Analysis)
|
||
8. **预防措施**: 包含完成时间的预防措施
|
||
9. **相关文档**: 链接到相关技术文档或代码仓库
|
||
|
||
**模板**:
|
||
|
||
```markdown
|
||
# YYYY-MM-DD 事件标题
|
||
|
||
## 事件概述
|
||
|
||
- **发生时间**: 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
|
||
## YYYY-MM-DD HH:MM 操作记录
|
||
|
||
**操作时间**: YYYY-MM-DD HH:MM:SS TZ
|
||
**操作人**: qiudl / Claude
|
||
**操作类型**: 数据库操作 / 服务重启 / 配置变更 / 部署发布
|
||
**操作内容**: 简短描述
|
||
|
||
### 详细步骤
|
||
|
||
1. **步骤 1** (HH:MM:SS)
|
||
```bash
|
||
具体命令
|
||
```
|
||
结果: [...]
|
||
|
||
2. **步骤 2** (HH:MM:SS)
|
||
```bash
|
||
具体命令
|
||
```
|
||
结果: [...]
|
||
|
||
### 验证结果
|
||
|
||
**验证时间**: YYYY-MM-DD HH:MM:SS TZ
|
||
**验证结果**: ✅ 成功 / ❌ 失败 / ⚠️ 部分成功
|
||
|
||
[验证详情...]
|
||
|
||
### 回滚方案
|
||
|
||
如需回滚,执行以下步骤:
|
||
```bash
|
||
回滚命令
|
||
```
|
||
|
||
---
|
||
|
||
**记录时间**: YYYY-MM-DD HH:MM:SS TZ
|
||
```
|
||
|
||
### 检查清单
|
||
|
||
在创建或更新思源笔记文档时,请检查:
|
||
|
||
- [ ] 所有时间记录都包含完整时间戳(精确到秒)
|
||
- [ ] 所有时间戳都标注了时区(CST/ACDT/UTC)
|
||
- [ ] 文档包含元信息(创建时间、创建人、状态)
|
||
- [ ] 事件记录包含完整的时间范围和时间线
|
||
- [ ] 技术方案包含各阶段的时间规划
|
||
- [ ] 会议记录包含会议时间和记录完成时间
|
||
- [ ] 操作日志包含操作时间、验证时间
|
||
- [ ] 所有时间使用 24 小时制
|
||
- [ ] 时间格式一致(YYYY-MM-DD HH:MM:SS TZ)
|
||
- [ ] 跨天操作明确标注日期变化
|
||
|
||
### 为什么需要完整时间戳
|
||
|
||
1. **故障排查**: 精确时间有助于关联服务器日志和追踪问题根源
|
||
2. **时间顺序**: 明确事件的先后关系,构建完整的时间线
|
||
3. **性能分析**: 精确计算操作耗时,识别性能瓶颈
|
||
4. **审计追踪**: 完整的操作记录,满足合规要求
|
||
5. **跨时区协作**: 明确时区避免混淆(澳洲团队 vs 中国服务器)
|
||
6. **自动化集成**: 便于后续工具提取时间信息进行分析
|
||
7. **知识沉淀**: 便于后续查阅和学习,理解决策的时间背景
|
||
|
||
### 常见错误和修正
|
||
|
||
| 错误写法 | 正确写法 | 说明 |
|
||
|---------|---------|------|
|
||
| 今天下午 | 2026-01-15 14:30:00 CST | 避免模糊表述 |
|
||
| 凌晨 | 2026-01-15 02:00:00 CST | 明确具体时间 |
|
||
| 2026-01-15 | 2026-01-15 14:30:00 CST | 补充时分秒 |
|
||
| 14:30 | 2026-01-15 14:30:00 CST | 补充日期和时区 |
|
||
| 昨天 | 2026-01-14 HH:MM:SS CST | 使用具体日期 |
|
||
| 刚才 | 2026-01-15 14:28:00 CST | 记录精确时间 |
|
||
|
||
---
|
||
|
||
**规范创建时间**: 2026-01-15 07:45:00 ACDT
|
||
**最后更新时间**: 2026-01-15 07:45:00 ACDT
|