547 lines
13 KiB
Markdown
547 lines
13 KiB
Markdown
---
|
||
name: openakita/skills@yuque-skills
|
||
description: Manage Yuque (语雀) knowledge bases, documents, and team collaboration through API integration. Supports personal search, weekly reports, knowledge base management, document CRUD, and group collaboration workflows. Based on yuque/yuque-skills.
|
||
license: MIT
|
||
metadata:
|
||
author: openakita
|
||
version: "1.0.0"
|
||
---
|
||
|
||
# Yuque Skills — 语雀知识管理
|
||
|
||
## When to Use
|
||
|
||
- 用户需要搜索语雀中的文档和知识
|
||
- 需要创建、编辑、发布语雀文档
|
||
- 需要生成周报/月报并发布到语雀
|
||
- 需要管理知识库结构(目录、分类)
|
||
- 需要在团队知识库中进行协作
|
||
- 需要从语雀导出文档或同步内容
|
||
- 需要统计知识库的使用数据
|
||
|
||
---
|
||
|
||
## Prerequisites
|
||
|
||
### 必需配置
|
||
|
||
| 配置项 | 说明 |
|
||
|--------|------|
|
||
| `YUQUE_TOKEN` | 语雀 API Token |
|
||
| `YUQUE_HOST` | 语雀 API 地址(默认 `https://www.yuque.com/api/v2`) |
|
||
|
||
**获取 Token:**
|
||
|
||
1. 登录语雀 → 点击头像 → 设置 → Token
|
||
2. 或访问:https://www.yuque.com/settings/tokens
|
||
3. 创建新 Token,勾选所需权限
|
||
|
||
在 `.env` 中配置:
|
||
|
||
```
|
||
YUQUE_TOKEN=your_yuque_token_here
|
||
YUQUE_HOST=https://www.yuque.com/api/v2
|
||
```
|
||
|
||
> 企业版语雀的 Host 格式为 `https://your-company.yuque.com/api/v2`
|
||
|
||
### 必需依赖
|
||
|
||
| 依赖 | 用途 | 安装方式 |
|
||
|------|------|---------|
|
||
| `httpx` | HTTP API 调用 | `pip install httpx` |
|
||
|
||
### 可选依赖
|
||
|
||
| 依赖 | 用途 | 安装方式 |
|
||
|------|------|---------|
|
||
| `markdownify` | HTML → Markdown 转换 | `pip install markdownify` |
|
||
| `beautifulsoup4` | HTML 解析 | `pip install beautifulsoup4` |
|
||
|
||
### 验证配置
|
||
|
||
```bash
|
||
curl -s -H "X-Auth-Token: $YUQUE_TOKEN" "https://www.yuque.com/api/v2/user" | python -m json.tool
|
||
```
|
||
|
||
---
|
||
|
||
## Instructions
|
||
|
||
### 语雀核心概念
|
||
|
||
| 概念 | 英文 | 说明 |
|
||
|------|------|------|
|
||
| 用户 | User | 个人账号 |
|
||
| 团队 | Group | 组织/团队空间 |
|
||
| 知识库 | Book/Repo | 文档集合,类似文件夹 |
|
||
| 文档 | Doc | 具体的内容页面 |
|
||
| 目录 | TOC | 知识库内的文档组织结构 |
|
||
| 协作者 | Collaborator | 知识库的共同维护者 |
|
||
|
||
### API 基础调用
|
||
|
||
所有 API 请求需携带 Token:
|
||
|
||
```python
|
||
import httpx
|
||
|
||
YUQUE_HOST = os.environ.get("YUQUE_HOST", "https://www.yuque.com/api/v2")
|
||
YUQUE_TOKEN = os.environ["YUQUE_TOKEN"]
|
||
|
||
headers = {
|
||
"X-Auth-Token": YUQUE_TOKEN,
|
||
"Content-Type": "application/json",
|
||
"User-Agent": "OpenAkita-Agent/1.0"
|
||
}
|
||
|
||
async def yuque_api(method, path, data=None):
|
||
async with httpx.AsyncClient() as client:
|
||
url = f"{YUQUE_HOST}{path}"
|
||
response = await client.request(method, url, headers=headers, json=data)
|
||
response.raise_for_status()
|
||
return response.json()["data"]
|
||
```
|
||
|
||
### 权限说明
|
||
|
||
| 权限 | 可执行操作 |
|
||
|------|-----------|
|
||
| 只读 | 搜索、查看文档、导出 |
|
||
| 读写 | 创建、编辑、删除文档 |
|
||
| 管理 | 知识库设置、成员管理 |
|
||
|
||
---
|
||
|
||
## Workflows
|
||
|
||
### Workflow 1: 个人文档搜索
|
||
|
||
**步骤 1 — 获取用户信息**
|
||
|
||
```python
|
||
user = await yuque_api("GET", "/user")
|
||
user_login = user["login"]
|
||
print(f"当前用户: {user['name']} ({user_login})")
|
||
```
|
||
|
||
**步骤 2 — 搜索文档**
|
||
|
||
```python
|
||
async def search_docs(query, scope="user"):
|
||
"""搜索语雀文档"""
|
||
params = {
|
||
"q": query,
|
||
"type": "doc",
|
||
"scope": scope,
|
||
}
|
||
result = await yuque_api("GET", f"/search?q={query}&type=doc")
|
||
return result
|
||
```
|
||
|
||
**步骤 3 — 获取文档内容**
|
||
|
||
```python
|
||
async def get_doc(repo_slug, doc_slug):
|
||
"""获取文档详细内容"""
|
||
doc = await yuque_api("GET", f"/repos/{repo_slug}/docs/{doc_slug}")
|
||
return {
|
||
"title": doc["title"],
|
||
"body": doc["body"], # Markdown 格式
|
||
"body_html": doc["body_html"], # HTML 格式
|
||
"word_count": doc["word_count"],
|
||
"updated_at": doc["updated_at"],
|
||
}
|
||
```
|
||
|
||
**步骤 4 — 返回搜索结果摘要**
|
||
|
||
---
|
||
|
||
### Workflow 2: 周报/月报生成
|
||
|
||
**步骤 1 — 收集周报内容**
|
||
|
||
向用户询问或从其他来源汇总:
|
||
|
||
| 模块 | 内容 |
|
||
|------|------|
|
||
| 本周完成 | 已完成的工作项列表 |
|
||
| 进行中 | 正在推进的工作 |
|
||
| 下周计划 | 下周的工作安排 |
|
||
| 风险与阻塞 | 需要协助的问题 |
|
||
| 数据指标 | 关键 KPI 变化 |
|
||
|
||
**步骤 2 — 生成 Markdown 内容**
|
||
|
||
```python
|
||
def generate_weekly_report(data):
|
||
"""生成周报 Markdown"""
|
||
report = f"""# 周报 | {data['date_range']}
|
||
|
||
## ✅ 本周完成
|
||
|
||
{format_task_list(data['completed'])}
|
||
|
||
## 🔄 进行中
|
||
|
||
{format_task_list(data['in_progress'])}
|
||
|
||
## 📋 下周计划
|
||
|
||
{format_task_list(data['next_week'])}
|
||
|
||
## ⚠️ 风险与阻塞
|
||
|
||
{format_risk_list(data['risks'])}
|
||
|
||
## 📊 关键指标
|
||
|
||
{format_metrics_table(data['metrics'])}
|
||
"""
|
||
return report
|
||
```
|
||
|
||
**步骤 3 — 发布到语雀**
|
||
|
||
```python
|
||
async def publish_report(repo_slug, title, content):
|
||
"""发布周报到指定知识库"""
|
||
doc_data = {
|
||
"title": title,
|
||
"slug": generate_slug(title),
|
||
"body": content,
|
||
"format": "markdown",
|
||
"status": 1, # 0=草稿, 1=发布
|
||
}
|
||
result = await yuque_api("POST", f"/repos/{repo_slug}/docs", data=doc_data)
|
||
return result
|
||
```
|
||
|
||
**步骤 4 — 返回文档链接**
|
||
|
||
---
|
||
|
||
### Workflow 3: 知识库管理
|
||
|
||
**列出所有知识库**
|
||
|
||
```python
|
||
async def list_repos(user_login=None, group_login=None):
|
||
"""列出知识库"""
|
||
if group_login:
|
||
repos = await yuque_api("GET", f"/groups/{group_login}/repos")
|
||
else:
|
||
repos = await yuque_api("GET", f"/users/{user_login}/repos")
|
||
|
||
return [{
|
||
"id": r["id"],
|
||
"name": r["name"],
|
||
"slug": r["slug"],
|
||
"description": r["description"],
|
||
"docs_count": r["items_count"],
|
||
"namespace": r["namespace"],
|
||
"public": r["public"],
|
||
"updated_at": r["updated_at"],
|
||
} for r in repos]
|
||
```
|
||
|
||
**获取知识库目录**
|
||
|
||
```python
|
||
async def get_toc(repo_namespace):
|
||
"""获取知识库的目录结构"""
|
||
toc = await yuque_api("GET", f"/repos/{repo_namespace}/toc")
|
||
return toc
|
||
```
|
||
|
||
**创建知识库**
|
||
|
||
```python
|
||
async def create_repo(user_or_group_login, name, description="", public=0):
|
||
"""创建新知识库"""
|
||
data = {
|
||
"name": name,
|
||
"slug": slugify(name),
|
||
"description": description,
|
||
"public": public, # 0=私有, 1=公开
|
||
"type": "Book",
|
||
}
|
||
result = await yuque_api("POST", f"/users/{user_or_group_login}/repos", data=data)
|
||
return result
|
||
```
|
||
|
||
---
|
||
|
||
### Workflow 4: 文档 CRUD 操作
|
||
|
||
**创建文档**
|
||
|
||
```python
|
||
async def create_doc(repo_namespace, title, body, format="markdown"):
|
||
data = {
|
||
"title": title,
|
||
"slug": generate_slug(title),
|
||
"body": body,
|
||
"format": format,
|
||
}
|
||
return await yuque_api("POST", f"/repos/{repo_namespace}/docs", data=data)
|
||
```
|
||
|
||
**更新文档**
|
||
|
||
```python
|
||
async def update_doc(repo_namespace, doc_id, title=None, body=None):
|
||
data = {}
|
||
if title:
|
||
data["title"] = title
|
||
if body:
|
||
data["body"] = body
|
||
return await yuque_api("PUT", f"/repos/{repo_namespace}/docs/{doc_id}", data=data)
|
||
```
|
||
|
||
**删除文档**
|
||
|
||
```python
|
||
async def delete_doc(repo_namespace, doc_id):
|
||
return await yuque_api("DELETE", f"/repos/{repo_namespace}/docs/{doc_id}")
|
||
```
|
||
|
||
**导出文档**
|
||
|
||
```python
|
||
async def export_doc(repo_namespace, doc_slug, format="markdown"):
|
||
"""导出文档为指定格式"""
|
||
doc = await get_doc(repo_namespace, doc_slug)
|
||
if format == "markdown":
|
||
return doc["body"]
|
||
elif format == "html":
|
||
return doc["body_html"]
|
||
elif format == "text":
|
||
from bs4 import BeautifulSoup
|
||
return BeautifulSoup(doc["body_html"], "html.parser").get_text()
|
||
```
|
||
|
||
---
|
||
|
||
### Workflow 5: 团队协作
|
||
|
||
**列出团队**
|
||
|
||
```python
|
||
async def list_groups():
|
||
"""列出用户加入的所有团队"""
|
||
groups = await yuque_api("GET", "/users/groups")
|
||
return [{
|
||
"id": g["id"],
|
||
"name": g["name"],
|
||
"login": g["login"],
|
||
"description": g["description"],
|
||
"members_count": g["members_count"],
|
||
} for g in groups]
|
||
```
|
||
|
||
**团队知识库报告**
|
||
|
||
```python
|
||
async def generate_team_report(group_login):
|
||
"""生成团队知识库使用报告"""
|
||
repos = await list_repos(group_login=group_login)
|
||
|
||
report = {
|
||
"total_repos": len(repos),
|
||
"total_docs": sum(r["docs_count"] for r in repos),
|
||
"repos_detail": [],
|
||
}
|
||
|
||
for repo in repos:
|
||
docs = await yuque_api("GET", f"/repos/{repo['namespace']}/docs")
|
||
recent_docs = sorted(docs, key=lambda d: d["updated_at"], reverse=True)[:5]
|
||
report["repos_detail"].append({
|
||
"name": repo["name"],
|
||
"doc_count": repo["docs_count"],
|
||
"recent_updates": [d["title"] for d in recent_docs],
|
||
})
|
||
|
||
return report
|
||
```
|
||
|
||
**知识库协作者管理**
|
||
|
||
```python
|
||
async def add_collaborator(repo_namespace, user_login, role="writer"):
|
||
"""添加知识库协作者"""
|
||
data = {
|
||
"login": user_login,
|
||
"role": role, # reader, writer, admin
|
||
}
|
||
return await yuque_api("POST", f"/repos/{repo_namespace}/collaborators", data=data)
|
||
```
|
||
|
||
---
|
||
|
||
### Workflow 6: 知识库内容同步
|
||
|
||
将外部内容同步到语雀,或从语雀同步到本地:
|
||
|
||
**从 Markdown 文件同步到语雀**
|
||
|
||
```python
|
||
async def sync_markdown_to_yuque(md_dir, repo_namespace):
|
||
"""将本地 Markdown 文件目录同步到语雀知识库"""
|
||
import glob
|
||
|
||
md_files = glob.glob(f"{md_dir}/**/*.md", recursive=True)
|
||
|
||
for md_file in md_files:
|
||
with open(md_file, "r", encoding="utf-8") as f:
|
||
content = f.read()
|
||
|
||
title = os.path.splitext(os.path.basename(md_file))[0]
|
||
|
||
existing = await search_doc_by_title(repo_namespace, title)
|
||
if existing:
|
||
await update_doc(repo_namespace, existing["id"], body=content)
|
||
print(f"更新: {title}")
|
||
else:
|
||
await create_doc(repo_namespace, title, content)
|
||
print(f"创建: {title}")
|
||
```
|
||
|
||
**从语雀导出到本地**
|
||
|
||
```python
|
||
async def export_repo_to_local(repo_namespace, output_dir):
|
||
"""将语雀知识库导出为本地 Markdown 文件"""
|
||
docs = await yuque_api("GET", f"/repos/{repo_namespace}/docs")
|
||
|
||
os.makedirs(output_dir, exist_ok=True)
|
||
|
||
for doc_info in docs:
|
||
doc = await get_doc(repo_namespace, doc_info["slug"])
|
||
file_path = os.path.join(output_dir, f"{doc_info['slug']}.md")
|
||
with open(file_path, "w", encoding="utf-8") as f:
|
||
f.write(f"# {doc['title']}\n\n{doc['body']}")
|
||
print(f"导出: {doc['title']} -> {file_path}")
|
||
```
|
||
|
||
---
|
||
|
||
## Output Format
|
||
|
||
### 搜索结果
|
||
|
||
```
|
||
🔍 搜索 "项目规范" 共找到 5 条结果:
|
||
|
||
1. 📄 代码规范 v2.0
|
||
- 知识库: 工程团队/开发规范
|
||
- 更新时间: 2025-02-28
|
||
- 链接: https://www.yuque.com/team/repo/doc-slug
|
||
|
||
2. 📄 项目管理规范
|
||
- 知识库: PMO/流程文档
|
||
- 更新时间: 2025-02-25
|
||
- 链接: https://www.yuque.com/team/repo/doc-slug2
|
||
|
||
...
|
||
```
|
||
|
||
### 周报输出
|
||
|
||
生成后返回发布链接和摘要。
|
||
|
||
### 知识库统计
|
||
|
||
```
|
||
📊 团队知识库统计
|
||
- 总知识库数: 12
|
||
- 总文档数: 456
|
||
- 本月新增: 28 篇
|
||
- 最活跃知识库: "产品设计" (本月 12 篇更新)
|
||
- 最近更新:
|
||
1. API 接口文档 v3 (2 小时前)
|
||
2. Q1 OKR 复盘 (昨天)
|
||
3. 新员工入职手册 (3 天前)
|
||
```
|
||
|
||
---
|
||
|
||
## Common Pitfalls
|
||
|
||
### 1. Token 权限不足
|
||
|
||
**症状**:API 返回 401 或 403
|
||
**解决**:
|
||
- 确认 Token 有读写权限
|
||
- 企业版语雀可能需要管理员授权
|
||
- 检查 Token 是否过期
|
||
|
||
### 2. namespace 格式错误
|
||
|
||
语雀的 namespace 格式为 `{user_or_group_login}/{repo_slug}`,例如 `myteam/dev-docs`。
|
||
|
||
**错误**:使用 repo 名称代替 slug
|
||
**正确**:使用 URL 中的 slug
|
||
|
||
### 3. Markdown 与 HTML 格式混淆
|
||
|
||
语雀文档有两种内容格式:
|
||
- `body`:Markdown 格式(创建时使用 `format: "markdown"`)
|
||
- `body_html`:HTML 格式
|
||
|
||
创建文档时需明确指定 `format` 参数。
|
||
|
||
### 4. API 频率限制
|
||
|
||
语雀 API 有请求频率限制:
|
||
- 个人版:约 100 次/分钟
|
||
- 企业版:通常更高
|
||
|
||
**解决**:批量操作时添加延迟:
|
||
|
||
```python
|
||
import asyncio
|
||
for doc in docs:
|
||
await process_doc(doc)
|
||
await asyncio.sleep(0.5)
|
||
```
|
||
|
||
### 5. 文档 slug 冲突
|
||
|
||
同一知识库中 slug 必须唯一。创建文档前先检查是否已存在:
|
||
|
||
```python
|
||
async def safe_create_doc(repo_namespace, title, body):
|
||
slug = generate_slug(title)
|
||
existing = await find_doc_by_slug(repo_namespace, slug)
|
||
if existing:
|
||
slug = f"{slug}-{int(time.time())}"
|
||
return await create_doc(repo_namespace, title, body, slug=slug)
|
||
```
|
||
|
||
### 6. 企业版与个人版 API 差异
|
||
|
||
企业版语雀的部分 API 路径和参数可能有差异:
|
||
- Host 不同(`your-company.yuque.com`)
|
||
- 部分接口需要额外权限
|
||
- 团队管理接口更丰富
|
||
|
||
### 7. 大文档性能问题
|
||
|
||
超过 5 万字的文档在创建/更新时可能超时。建议:
|
||
- 拆分为多篇文档
|
||
- 使用分页上传
|
||
- 图片先上传到 CDN 再引用
|
||
|
||
---
|
||
|
||
## EXTEND.md 扩展
|
||
|
||
用户可在技能同目录下创建 `EXTEND.md` 添加:
|
||
- 默认知识库 namespace
|
||
- 团队的语雀 Host 地址
|
||
- 周报/月报模板定制
|
||
- 常用知识库列表和别名
|
||
- 自定义文档模板
|