@clawhub-giswilson-69164ed428
将飞书文档或知识库文档导出为 Markdown 文件,或导出为 PDF/Word 等格式(异步任务)。 当用户请求"导出文档"、"导出为 Markdown"、"转换为 Markdown"、"保存为 md"、 "导出 PDF"、"导出 Word"、"下载文档"时使用。 本技能专注于导出操作。从本地 DOCX 文件导...
---
name: feishu-cli-export
description: >-
将飞书文档或知识库文档导出为 Markdown 文件,或导出为 PDF/Word 等格式(异步任务)。
当用户请求"导出文档"、"导出为 Markdown"、"转换为 Markdown"、"保存为 md"、
"导出 PDF"、"导出 Word"、"下载文档"时使用。
本技能专注于导出操作。从本地 DOCX 文件导入请使用 feishu-cli-import。
argument-hint: <document_id|node_token|url> [output_path]
user-invocable: true
allowed-tools: Bash, Read
---
# 飞书文档导出技能
将飞书云文档或知识库文档导出为本地 Markdown 文件,或导出为 PDF/Word 等格式。
## 前置条件
- 需要已配置飞书应用凭证(`FEISHU_APP_ID` / `FEISHU_APP_SECRET`),通过环境变量或 `~/.feishu-cli/config.yaml` 设置
- App 权限:需要 `docx:document` 或 `docx:document:readonly`(文档导出)、`wiki:wiki:readonly`(知识库导出)
- User Token 权限:若 App 无权访问他人文档,需通过 `feishu-cli auth login --scopes "docx:document:readonly offline_access"` 授权,`doc export` 会自动读取保存的 User Token
- 使用 `--expand-mentions` 展开 @用户时,还需 `contact:user.base:readonly` 权限
## 核心概念
**Markdown 作为中间格式**:飞书云文档的内容通过 Markdown 格式导出到本地。选择 Markdown 作为中间格式,是因为它结构清晰、便于 Claude 理解和处理文档内容,同时也方便用户进行二次编辑或版本管理。中间文件默认存储在 `/tmp` 目录中。
## 使用方法
```bash
# 导出普通文档
/feishu-export <document_id>
/feishu-export <document_id> ./output.md
# 导出知识库文档
/feishu-export <wiki_url>
```
## 执行流程
1. **解析参数**
- 判断 URL 类型:
- `/docx/` → 普通文档
- `/wiki/` → 知识库文档
- document_id:必需
- output_path:可选,默认 `/tmp/<id>.md`
2. **执行导出**
**普通文档**:
```bash
feishu-cli doc export <document_id> --output <output_path>
```
**知识库文档**:
```bash
feishu-cli wiki export <node_token> --output <output_path>
```
3. **验证结果**
- 读取导出的 Markdown 文件
- 显示文件大小和内容预览
## 参数说明
| 参数 | 说明 | 默认值 |
|------|------|--------|
| document_id/node_token | 文档 ID 或知识库节点 Token | 必需 |
| output_path | 输出文件路径 | `/tmp/<id>.md` |
| --download-images | 下载文档中的图片和画板到本地(图片在飞书服务器以 token 形式存储,不下载则无法本地查看;画板自动导出为 PNG) | 否 |
| --assets-dir | 图片和画板的保存目录 | `./assets` |
| --front-matter | 添加 YAML front matter(标题和文档 ID) | 否 |
| --highlight | 保留文本颜色和背景色(输出为 HTML `<span>` 标签) | 否 |
| --expand-mentions | 展开 @用户为友好格式(需要 contact:user.base:readonly 权限) | 是(默认开启) |
| --user-access-token | User Access Token(用于访问无 App 权限的文档,未指定时自动从 `auth login` 读取) | 自动读取 |
## 支持的 URL 格式
| URL 格式 | 类型 | 命令 |
|---------|------|------|
| `https://xxx.feishu.cn/docx/<id>` | 普通文档 | `doc export` |
| `https://xxx.feishu.cn/wiki/<token>` | 知识库 | `wiki export` |
| `https://xxx.larkoffice.com/docx/<id>` | 普通文档 | `doc export` |
| `https://xxx.larkoffice.com/wiki/<token>` | 知识库 | `wiki export` |
## 输出格式
```
已导出文档!
文件路径: /path/to/output.md
文件大小: 2.5 KB
内容预览:
---
# 文档标题
...
```
## 示例
```bash
# 导出普通文档
/feishu-export <document_id>
/feishu-export <document_id> ~/Documents/doc.md
# 导出知识库文档
/feishu-export https://xxx.feishu.cn/wiki/<node_token>
/feishu-export <node_token> ./wiki_doc.md
# 导出并下载图片
/feishu-export <document_id> --download-images
# 导出并添加 YAML front matter
/feishu-export <document_id> -o doc.md --front-matter
# 导出并保留文本高亮颜色
/feishu-export <document_id> -o doc.md --highlight
```
### Front Matter 输出格式
使用 `--front-matter` 时,导出的 Markdown 文件顶部会添加:
```yaml
---
title: "文档标题"
document_id: ABC123def456
---
```
### 高亮颜色输出格式
使用 `--highlight` 时,带颜色的文本会输出为 HTML `<span>` 标签:
```html
<span style="color: #ef4444">红色文本</span>
<span style="background-color: #eff6ff">蓝色高亮背景</span>
```
支持的颜色:7 种字体颜色(红/橙/黄/绿/蓝/紫/灰)+ 14 种背景色(浅/深各 7 种)。
## 图片处理(重要)
导出文档时务必下载图片,以便后续理解图片内容:
### 导出并下载图片
```bash
# 普通文档
feishu-cli doc export <document_id> \
--output /tmp/doc.md \
--download-images \
--assets-dir /tmp/doc_assets
# 知识库文档
feishu-cli wiki export <node_token> \
--output /tmp/wiki.md \
--download-images \
--assets-dir /tmp/wiki_assets
```
### 查看和理解图片
```bash
# 查看下载的图片列表
ls -la /tmp/doc_assets/
# 使用 Read 工具读取图片(Claude 支持多模态)
# Read /tmp/doc_assets/image_1.png
# Read /tmp/doc_assets/image_2.png
```
### 完整流程
1. **导出时添加图片参数**:`--download-images --assets-dir <dir>`
2. **检查图片文件**:`ls <assets_dir>/`
3. **读取图片内容**:使用 Read 工具逐个读取图片
4. **整合分析**:将图片描述与文档文本结合
## 错误处理与边界情况
### 1. 常见错误
| 错误 | 原因 | 解决 |
|------|------|------|
| `code=1770032, msg=forBidden` | App Token 无权限访问该文档 | 通过 `auth login --scopes "docx:document:readonly offline_access"` 授权 User Token,`doc export` 会自动读取 |
| `code=99991679, msg=Unauthorized` | User Token 缺少 `docx:document:readonly` scope | 重新执行 `feishu-cli auth login --scopes "docx:document:readonly offline_access"` |
| `code=131002, param err` | 参数错误 | 检查 token 格式 |
| `code=131001, node not found` | 节点不存在 | 检查 token 是否正确 |
| `code=131003, no permission` | 无权限访问 | 确认应用有 docx:document 或 wiki:wiki:readonly 权限 |
| `code=99991672, open api request rate limit` | API 限流 | 等待几秒后重试 |
| `write /tmp/xxx.md: permission denied` | 文件权限问题 | 检查输出目录权限,更换输出路径 |
### 2. 边界情况处理
**情况 1:目录节点导出**
- 知识库目录节点导出内容可能显示为 `[Wiki 目录...]`
- 这是正常行为,表示该节点是目录而非实际文档
- 使用 `wiki nodes <space_id> --parent <token>` 获取子节点
**情况 2:文档内容为空**
- 检查文档是否真的为空
- 检查是否有权限查看内容
- 检查是否是目录节点
**情况 3:图片下载失败**
- 检查 `--assets-dir` 目录是否存在且可写
- 检查网络连接
- 图片可能已被删除或权限不足
**情况 4:导出中断**
- 大型文档导出可能耗时较长
- 如果中断,可以重新执行命令
- 使用 `--output` 指定固定路径以便续传
### 3. 重试机制
如果遇到网络错误或 API 限流:
```bash
# 添加 --debug 查看详细错误信息
feishu-cli doc export <doc_id> --debug
# 等待几秒后重试
sleep 5 && feishu-cli doc export <doc_id>
```
## 已知问题
| 问题 | 说明 |
|------|------|
| 表格导出 | 表格内单元格内容可能显示为 `<!-- Unknown block type: 32 -->`,这是块类型 32(表格单元格)的已知转换问题 |
| 目录节点 | 知识库目录节点导出内容为 `[Wiki 目录...]`,需单独获取子节点 |
## 已验证功能
以下导出功能已通过测试验证:
- 普通文档导出 ✅
- 知识库文档导出 ✅
- 标题、段落、列表(含嵌套列表)、代码块、引用、分割线 ✅
- 任务列表(Todo)✅
- **图片下载** ✅(使用 `--download-images`)
- **Callout 高亮块**(6 种类型)✅
- **公式**(块级 + 行内)✅
- **Front Matter** ✅(使用 `--front-matter`)
- **文本高亮颜色** ✅(使用 `--highlight`)
- **ISV 块**(Mermaid 绘图)✅
- **AddOns/SyncedBlock 展开** ✅
- **特殊字符转义** ✅
- **@用户展开** ✅(使用 `--expand-mentions`,默认开启)
- **新块类型**(Agenda/LinkPreview/SyncBlock/WikiCatalogV2/AITemplate)✅
- 表格结构 ⚠️(内容可能丢失)
- 飞书画板 → 画板链接/PNG 图片 ✅
## 双向转换说明
| 导入(Markdown → 飞书) | 导出(飞书 → Markdown) |
|------------------------|------------------------|
| Mermaid/PlantUML 代码块 → 飞书画板 | 飞书画板 → 画板链接/PNG 图片 |
| 大表格 → 自动拆分为多个表格 | 多个表格 → 分开的表格 |
| 缩进列表 → 嵌套父子块 | 嵌套列表 → 缩进 Markdown |
| `> [!NOTE]` → Callout 高亮块 | Callout 高亮块 → `> [!NOTE]` |
| `$formula$` → 行内公式 | 行内/块级公式 → `$formula$` |
| `<u>下划线</u>` → 下划线样式 | 下划线样式 → `<u>下划线</u>` |
**注意**:
- Mermaid/PlantUML 图表导入后会转换为飞书画板,导出时生成的是画板链接而非原始图表代码
- 飞书"文本绘图"小组件(TextDrawing/AddOns)导出时会自动还原为 Mermaid 或 PlantUML 代码块,保留图表源码
---
## 异步导出为 PDF/Word/Excel(doc export-file)
将飞书云文档导出为 PDF、Word 等格式(异步三步流程):
### 执行流程
```bash
# 一条命令完成全部流程(内部自动创建任务→轮询→下载)
feishu-cli doc export-file <doc_token> --type pdf -o output.pdf
```
### 支持的导出格式
| --type | 格式 | 说明 |
|--------|------|------|
| `pdf` | PDF | 保留排版 |
| `docx` | Word | 可编辑 |
### 参数
| 参数 | 说明 | 默认值 |
|------|------|--------|
| `<doc_token>` | 文档 Token | 必填 |
| `--type` | 导出格式 | 必填 |
| `-o, --output` | 输出文件路径 | 必填 |
| `--user-access-token` | User Access Token(用于导出无 App 权限的文档) | 自动读取 |
### 示例
```bash
# 导出为 PDF
feishu-cli doc export-file JKbxdRez1oNWEKxPz14cWMpBnKh --type pdf -o /tmp/report.pdf
# 导出为 Word
feishu-cli doc export-file JKbxdRez1oNWEKxPz14cWMpBnKh --type docx -o /tmp/report.docx
# 使用 User Token 导出(无 App 权限的文档)
feishu-cli doc export-file JKbxdRez1oNWEKxPz14cWMpBnKh --type pdf -o /tmp/report.pdf --user-access-token u-xxx
```
---
## 从本地文件导入为飞书云文档(doc import-file)
将本地 DOCX/XLSX 等文件导入为飞书云文档(异步流程):
### 执行流程
```bash
# 一条命令完成全部流程(内部自动上传→创建任务→轮询)
feishu-cli doc import-file local_file.docx --type docx --name "文档名称"
```
### 支持的导入格式
| --type | 格式 | 说明 |
|--------|------|------|
| `docx` | Word 文档 | 转换为飞书文档 |
### 参数
| 参数 | 说明 | 默认值 |
|------|------|--------|
| `<local_path>` | 本地文件路径 | 必填 |
| `--type` | 文件类型 | 必填 |
| `--name` | 飞书文档名称 | 文件名 |
| `--folder` | 目标文件夹 Token(**必须提供**,飞书 API 要求指定文档挂载点,不传会报 `field validation failed`) | — |
### 示例
```bash
# 导入 Word 文档(必须指定 --folder)
feishu-cli doc import-file ~/Documents/report.docx --type docx --name "季度报告" --folder fldcnXXX
```
---
## 常见问题
| 问题 | 原因 | 解决方法 |
|------|------|----------|
| `code=131003, no permission` | 应用未授权访问该文档 | 确认应用有 `docx:document` 或 `wiki:wiki:readonly` 权限,且文档对应用可见 |
| `code=99991672, rate limit` | API 请求频率超限 | 等待几秒后重试 |
| `field validation failed`(import-file) | 未传 `--folder` 参数 | 始终指定 `--folder fldcnXXX` |
| 导出的 Markdown 中图片显示为 token 而非内容 | 未使用 `--download-images` | 添加 `--download-images --assets-dir <dir>` 参数 |
| 表格单元格内容显示为 `<!-- Unknown block type: 32 -->` | 块类型 32(TableCell)的已知转换问题 | 暂无修复,手动补充内容 |
| `--expand-mentions` 报权限错误 | 缺少 `contact:user.base:readonly` 权限 | 在飞书开放平台为应用申请该权限,或使用 `--expand-mentions=false` 关闭 |
---
## 附录:Block 类型映射参考
以下是飞书文档块类型与导出 Markdown 格式的完整映射关系。
| 飞书块类型 | 导出结果 | 说明 |
|-----------|---------|------|
| 标题 (Heading 1-6) | `# ~ ######` | |
| 标题 (Heading 7-9) | `######` 或粗体段落 | 超出 H6 时降级 |
| 段落 (Text) | 普通文本 | |
| 无序列表 (Bullet) | `- item` | 支持无限深度嵌套 |
| 有序列表 (Ordered) | `1. item` | 保留原始编号序列 |
| 任务列表 (Todo) | `- [x]` / `- [ ]` | |
| 代码块 (Code) | ` ```lang ``` ` | 使用原始文本,无转义 |
| 引用 (Quote) | `> text` | |
| 引用容器 (QuoteContainer) | `> text` | 支持嵌套引用 |
| Callout 高亮块 | `> [!TYPE]` | 6 种类型 |
| 公式 (Equation) | `$formula$` | 块级公式 |
| 行内公式 | `$formula$` | 段落内嵌公式 |
| 分割线 (Divider) | `---` | |
| 表格 (Table) | Markdown 表格 | 管道符自动转义 |
| 图片 (Image) | `` 或本地路径 | 使用 `--download-images` 时下载到本地 |
| 链接 | `[text](url)` | URL 特殊字符自动编码 |
| 画板 (Board) | `[画板/Whiteboard](feishu://board/...)` 或 PNG 图片 | 使用 `--download-images` 时自动导出为 PNG |
| ISV 块 | 画板链接或 HTML 注释 | Mermaid 绘图/时间线 |
| Iframe | `<iframe>` HTML 标签 | 嵌入内容 |
| AddOns/TextDrawing | Mermaid/PlantUML 代码块 | 文本绘图小组件自动还原为图表源码 |
| AddOns/SyncedBlock | 展开子块内容 | 透明展开 |
| Wiki 目录 | `[Wiki 目录...]` | |
| Agenda/AgendaItem | 展开子块内容 | 议程块 |
| LinkPreview | 链接 | 链接预览 |
| SyncSource/SyncReference | 展开子块内容 | 同步块 |
| WikiCatalogV2 | `[知识库目录 V2]` | |
| AITemplate | HTML 注释 | AI 模板块 |
### Callout 高亮块导出
Callout 块(飞书高亮块)导出为 GitHub-style alert 语法:
```markdown
> [!NOTE]
> 这是一个提示信息。
> [!WARNING]
> 这是一个警告信息。
```
支持 6 种 Callout 类型(按背景色映射):
| 背景色 | 导出类型 | 说明 |
|--------|---------|------|
| 2 (红色) | `[!WARNING]` | 警告 |
| 3 (橙色) | `[!CAUTION]` | 警示 |
| 4 (黄色) | `[!TIP]` | 技巧 |
| 5 (绿色) | `[!SUCCESS]` | 成功 |
| 6 (蓝色) | `[!NOTE]` | 提示 |
| 7 (紫色) | `[!IMPORTANT]` | 重要 |
Callout 内部子块(段落、列表等)会在引用语法内逐行展示。
### 公式导出
- **块级公式**:独立行 `$formula$`
- **行内公式**:段落内嵌 `$E = mc^2$`
- 公式内容保持 LaTeX 原文,不做转义
### 特殊字符处理
导出时自动处理以下 Markdown 特殊字符:
- 普通文本中的 `* _ [ ] # ~ $ > |` 会自动添加 `\` 转义
- 代码块内的文本不做转义(使用原始文本)
- 表格单元格中的 `|` 会转义为 `\|`
- URL 中的括号 `(` `)` 会编码为 `%28` `%29`
飞书文档创建前的兼容性检查规范。覆盖 Mermaid/PlantUML 语法限制(8 种图表类型的飞书安全写法)、 表格自动拆分规则(9×9 限制)、Callout/公式/图片处理、API 限制与容错机制。 被 feishu-cli-write、feishu-cli-import 等技能引用,在生成将要导入飞书的...
---
name: feishu-cli-doc-guide
description: >-
飞书文档创建前的兼容性检查规范。覆盖 Mermaid/PlantUML 语法限制(8 种图表类型的飞书安全写法)、
表格自动拆分规则(9×9 限制)、Callout/公式/图片处理、API 限制与容错机制。
被 feishu-cli-write、feishu-cli-import 等技能引用,在生成将要导入飞书的 Markdown 之前必须参考。
确保内容兼容飞书,避免导入失败。
user-invocable: false
allowed-tools: Read
---
# 飞书文档创建规范指南
## 1. 概述
本技能是 **其他飞书文档技能的参考规范**,不可直接调用。整合了以下来源的验证经验:
- `feishu-cli` 项目代码实现(`converter/`、`client/board.go`、`cmd/import_markdown.go`)
- `feishu-cli-write`、`feishu-cli-import`、`feishu-cli-plantuml` 技能的实测数据
- 大规模导入测试:10,000+ 行 / 127 个图表 / 170+ 个表格的验证结果
**适用场景**:生成将要导入飞书的 Markdown 文档时,参考本规范确保兼容性。
---
## 2. TL;DR 速查清单
生成飞书 Markdown 前,快速过一遍这 10 条核心规则:
| # | 规则 | 严重度 |
|---|------|--------|
| 1 | ❌ Mermaid flowchart 标签禁止 `{}`(会被解析为菱形节点) | 必须遵守 |
| 2 | ❌ Mermaid 禁止 `par...and...end`(飞书完全不支持) | 必须遵守 |
| 3 | ❌ Mermaid 节点标签换行禁止 `\n`(会原样显示),用 `<br/>` | 必须遵守 |
| 4 | ⚠️ Mermaid sequenceDiagram:participant ≤ 8,alt 嵌套 ≤ 1 层 | 强烈建议 |
| 5 | ✅ 方括号标签含冒号时加双引号:`["类型: string"]` | 必须遵守 |
| 6 | ❌ PlantUML 禁止行首缩进、`skinparam`、可见性标记(`+ - # ~`) | 必须遵守 |
| 7 | ⚠️ 表格超过 9 行或 9 列会自动拆分,无需手动处理 | 了解即可 |
| 8 | ✅ Callout 仅 6 种:NOTE / WARNING / TIP / CAUTION / IMPORTANT / SUCCESS | 必须遵守 |
| 9 | ⚠️ 块级公式 `$$...$$` 会降级为行内 Equation(API 限制) | 了解即可 |
| 10 | ✅ 图片默认自动上传,失败时降级为占位块 | 了解即可 |
---
## 3. Mermaid 飞书语法规范
> 这是最重要的章节。Mermaid 图表导入飞书有严格的语法限制,不遵守会导致渲染失败。
### 支持的 8 种图表类型
| 类型 | 声明 | 飞书 diagram_type | 说明 |
|------|------|------------------|------|
| 流程图 | `flowchart TD` / `flowchart LR` | 6 (flowchart) | 支持 subgraph |
| 时序图 | `sequenceDiagram` | 2 (sequence) | 复杂度限制最严格 |
| 类图 | `classDiagram` | 4 (class) | |
| 状态图 | `stateDiagram-v2` | 7 (state) | 必须用 v2 |
| ER 图 | `erDiagram` | 5 (er) | |
| 甘特图 | `gantt` | 0 (auto) | |
| 饼图 | `pie` | 0 (auto) | |
| 思维导图 | `mindmap` | 1 (mindmap) | |
### 7 条强制性规则
#### 规则 1:❌ 禁止在标签中使用花括号 `{}`
花括号会被 Mermaid 解析器识别为菱形(decision)节点,导致语法错误。此规则针对 flowchart 节点标签,erDiagram/classDiagram 语法结构中的 `{}` 不受此限制。
```markdown
❌ 错误
flowchart TD
A["{name: value}"]
✅ 正确
flowchart TD
A["name: value"]
```
**替代方案**:移除花括号,或用圆括号/方括号替代。
#### 规则 2:❌ 禁止使用 `par...and...end` 并行语法
飞书画板完全不支持 `par` 语法(错误码 2891001)。
```markdown
❌ 错误
sequenceDiagram
par
A->>B: 请求1
and
A->>C: 请求2
end
✅ 正确:用 Note 替代
sequenceDiagram
Note over A,C: 并行处理
A->>B: 请求1
A->>C: 请求2
```
#### 规则 3:⚠️ 方括号中避免冒号
方括号 `[text:xxx]` 中的冒号可能导致解析歧义。
```markdown
❌ 可能出错
flowchart TD
A[类型:string]
✅ 正确
flowchart TD
A["类型: string"]
```
**修复方法**:给含冒号的标签加双引号。
#### 规则 4:⚠️ Note 作用域限制
`Note over` 最多跨 2 个相邻 participant。
```markdown
❌ 错误:跨太多参与者
sequenceDiagram
Note over A,D: 说明
✅ 正确
sequenceDiagram
Note over A,B: 说明
```
#### 规则 5:⚠️ sequenceDiagram 复杂度阈值
| 维度 | 安全阈值 | 超限风险 |
|------|---------|---------|
| participant 数量 | ≤ 8 | 超过 10 + 其他因素 → 失败 |
| alt/opt 嵌套 | ≤ 1 层 | 超过 2 层 → 失败风险增大 |
| 消息标签长度 | 简短(≤ 30 字符) | 长标签 + 多参与者 → 失败 |
| 总消息数 | ≤ 30 | 需结合其他因素评估 |
**超限组合**(实测必定失败):10+ participant + 2+ alt + 30+ 长消息标签
**建议**:超过安全阈值时,拆分为多个小图。
#### 规则 6:❌ 节点标签换行禁止 `\n`,必须用 `<br>` 或 `<br/>`
飞书画板不支持 Mermaid 节点标签中的 `\n` 转义符,会原样显示为 `\n` 文本。需要使用 `<br>` 或 `<br/>` 实现换行,也可以在源码中写真实换行(需用双引号包裹标签)。
```markdown
❌ 错误:\n 会原样显示为文本
flowchart TD
A["normalizePort\n(detect-port)"]
✅ 正确:使用 <br>
flowchart TD
A["normalizePort<br>(detect-port)"]
✅ 正确:使用 <br/>
flowchart TD
A["normalizePort<br/>(detect-port)"]
✅ 正确:源码中直接换行(标签必须用双引号)
flowchart TD
A["normalizePort
(detect-port)"]
```
#### 规则 7:⚠️ 避免过于复杂的嵌套结构
多层 subgraph 嵌套、大量条件分支等复杂结构会增加渲染失败概率。保持图表简洁。
### 生成前检查清单
在生成 Mermaid 代码块前,逐项检查:
- [ ] 图表类型是否在支持的 8 种之内?
- [ ] 标签中是否存在花括号 `{}`?→ 移除或替换
- [ ] 是否使用了 `par...and...end`?→ 改用 `Note over`
- [ ] 方括号标签中是否有冒号?→ 加双引号
- [ ] sequenceDiagram 参与者是否 ≤ 8?
- [ ] sequenceDiagram alt 嵌套是否 ≤ 1 层?
- [ ] 节点标签换行是否使用了 `\n`?→ 改用 `<br>` 或 `<br/>`
- [ ] 整体复杂度是否可控?→ 考虑拆分
> 详细的 8 种图表模板和更多正反示例见 `references/mermaid-spec.md`。
---
## 4. PlantUML 安全子集
### 全局规则
1. ✅ 使用 `@startuml` / `@enduml` 包裹(思维导图用 `@startmindmap` / `@endmindmap`)
2. ❌ **不要使用行首缩进**(飞书画板将缩进行视为独立行)
3. ❌ 避免 `skinparam`、`!define`、颜色、字体、对齐控制等样式指令
4. ⚠️ 避免方向控制指令(`left to right direction` 等在部分场景不可靠)
### 各图类型注意事项
| 图类型 | 安全语法 | 禁忌 |
|--------|---------|------|
| 活动图 | `start/stop`、`:动作;`、`if/then/else/endif`、`repeat`、`fork` | ❌ 避免过深嵌套 |
| 时序图 | `participant`、`->`/`-->`、`activate/deactivate`、`note`、`alt/opt/loop` | ❌ 避免样式指令 |
| 类图 | `class`、`interface`、`package`、关系箭头 | ❌ **避免可见性标记(+ - # ~)** |
| 用例图 | `actor`、`(用例)`、`<<include>>`/`<<extend>>` | ❌ 避免复杂布局 |
| 组件图 | `[Component]`、`package/node/cloud/database` | ❌ 避免 ArchiMate sprite |
| ER 图 | `entity`、关系箭头 | ⚠️ 与 Mermaid ER 语法不同 |
| 思维导图 | `@startmindmap`、`* / +` 层级标记 | ✅ 必须用专用包裹标记 |
### Mermaid vs PlantUML 选择策略
| 场景 | 推荐 | 原因 |
|------|------|------|
| 流程图 | **Mermaid** | 飞书原生支持更好,成功率高 |
| 时序图(简单) | **Mermaid** | 语法简洁 |
| 时序图(复杂) | PlantUML | Mermaid 复杂度限制严格 |
| 类图 | Mermaid | 两者都可,Mermaid 更简洁 |
| ER 图 | Mermaid | 语法更直观 |
| 状态图 | Mermaid | stateDiagram-v2 支持好 |
| 甘特图 | **Mermaid** | PlantUML 甘特图飞书支持差 |
| 饼图 | **Mermaid** | 简洁 |
| 思维导图 | 两者均可 | PlantUML 层级标记更灵活 |
| 用例图 | **PlantUML** | Mermaid 不支持 |
| 组件图 | **PlantUML** | Mermaid 不支持 |
| 活动图(复杂分支) | **PlantUML** | 支持更丰富的分支语法 |
**默认推荐 Mermaid**,仅在 Mermaid 不支持的图类型或复杂场景下使用 PlantUML。
---
## 5. Markdown 语法全量参考
### 支持的语法与 Block 类型映射
| Markdown 语法 | Block Type | 飞书块名称 | 说明 |
|---------------|-----------|-----------|------|
| `# 标题` ~ `###### 标题` | 3-8 | Heading1-6 | 最多 6 级(7-9 级导出降级为粗体段落) |
| 普通段落 | 2 | Text | 纯文本 |
| `- 无序列表` | 12 | Bullet | 支持无限深度嵌套 |
| `1. 有序列表` | 13 | Ordered | 支持无限深度嵌套 |
| `- [x]` / `- [ ]` | 17 | Todo | 任务列表 |
| `` ```lang `` | 14 | Code | 代码块(支持语言标识) |
| `> 引用` | 34 | QuoteContainer | 引用容器(导入使用 QuoteContainer) |
| `> [!NOTE]` | 19 | Callout | 高亮块(6 种类型,见第 6 节) |
| `---` | 22 | Divider | 分割线 |
| Markdown 表格 | 31 | Table | 超过 9 行或 9 列自动拆分(见第 7 节) |
| `` | 27 | Image | 默认自动上传(见第 8 节) |
| `` ```mermaid `` | 21→43 | Diagram→Board | 自动转飞书画板(见第 3 节) |
| `` ```plantuml `` / `` ```puml `` | 21→43 | Diagram→Board | 自动转飞书画板(见第 4 节) |
| `$$公式$$` | 16 | Equation | 块级公式(降级为行内 Equation) |
| `$公式$` | — | InlineEquation | 行内公式 |
### 新增块类型(导出支持)
以下块类型在导出时有对应的处理:
| Block Type | 名称 | 导出结果 | 说明 |
|------------|------|---------|------|
| 44 | Agenda | 展开子块 | 议程块 |
| 45 | AgendaItem | 展开子块 | 议程条目 |
| 46 | AgendaItemTitle | 粗体文本 | 议程标题 |
| 47 | AgendaItemContent | 展开子块 | 议程内容 |
| 48 | LinkPreview | 链接 | 链接预览块 |
| 49 | SyncSource | 展开子块 | 同步源块 |
| 50 | SyncReference | 展开子块 | 同步引用块 |
| 51 | WikiCatalogV2 | `[知识库目录 V2]` | 知识库目录 V2 |
| 52 | AITemplate | HTML 注释 | AI 模板块 |
### 行内样式
| Markdown | 效果 | 说明 |
|----------|------|------|
| `**粗体**` | **粗体** | Bold TextStyle |
| `*斜体*` | *斜体* | Italic TextStyle |
| `` `行内代码` `` | `代码` | InlineCode TextStyle |
| `~~删除线~~` | ~~删除线~~ | Strikethrough TextStyle |
| `<u>下划线</u>` | 下划线 | Underline TextStyle |
| `[文字](url)` | 链接 | Link TextElement |
| `==高亮==` | 高亮 | Highlight(需启用选项) |
### 嵌套列表示例
```markdown
- 一级无序
- 二级无序
- 三级无序
1. 四级有序
2. 四级有序
- 三级无序
- 二级无序
```
无序/有序列表支持 **无限深度嵌套** 和 **混合嵌套**,导入时自动保留缩进层级。
---
## 6. Callout 高亮块
### 6 种类型与背景色映射
| 类型 | bgColor | 颜色 | Markdown 语法 | 适用场景 |
|------|---------|------|--------------|---------|
| NOTE | 6 | 蓝色 | `> [!NOTE]` | 补充说明、提示信息 |
| WARNING | 2 | 红色 | `> [!WARNING]` | 警告、危险提醒 |
| TIP | 4 | 黄色 | `> [!TIP]` | 技巧、建议 |
| CAUTION | 3 | 橙色 | `> [!CAUTION]` | 注意事项 |
| IMPORTANT | 7 | 紫色 | `> [!IMPORTANT]` | 重要信息 |
| SUCCESS | 5 | 绿色 | `> [!SUCCESS]` | 成功、通过 |
> ⚠️ `INFO` 与 `NOTE` 等效(都映射为 bgColor=6 蓝色),统一使用 `NOTE`。
### 使用示例
```markdown
> [!NOTE]
> 这是一条补充说明信息。
> [!WARNING]
> 此操作不可逆,请谨慎执行。
> [!TIP]
> 使用 `--verbose` 参数可以查看详细进度。
> [!CAUTION]
> 注意:API 有频率限制。
> [!IMPORTANT]
> 必须在执行前配置环境变量。
> [!SUCCESS]
> 所有测试用例已通过。
```
### 注意事项
- ❌ Callout 块不能同时设置 `EmojiId`,仅通过 `BackgroundColor` 区分类型
- ✅ 支持 Callout 内包含子块(段落、列表等)
- ✅ 统一使用 `NOTE` 而非 `INFO`(两者等效,`NOTE` 是 Markdown 标准写法)
---
## 7. 表格规范
### 9 行 × 9 列限制与自动拆分
飞书 API 限制单个表格最多 **9 行**(包括表头)× **9 列**。超出时 feishu-cli 自动拆分为多个表格。
拆分逻辑(`converter/markdown_to_block.go`):
| 维度 | 处理方式 |
|------|---------|
| ≤ 9 行 且 ≤ 9 列 | ✅ 直接创建单个表格 |
| > 9 行 | 按行拆分为多个表格,每个最多 8 行数据 + 1 行表头 |
| > 9 列 | 按列拆分为多个表格,保留首列作为标识列 |
| > 9 行 且 > 9 列 | 先列拆分,再行拆分(复合拆分) |
**列拆分策略**:首列通常是标识/名称列,在所有列组中保留,避免拆分后数据行无法识别。每个列组最多 9 列(1 列标识 + 8 列数据)。
### 列宽自动计算
列宽根据单元格内容自动计算(`converter/markdown_to_block.go:25-103`):
| 参数 | 值 | 说明 |
|------|-----|------|
| 中文字符宽度 | 14px | 非 ASCII 字符 |
| 英文字符宽度 | 8px | ASCII 字符 |
| 列内边距 | 16px | 每列额外边距 |
| 最小列宽 | 80px | 不能更窄 |
| 最大列宽 | 400px | 不能更宽 |
| 文档默认宽度 | 700px | 总宽度不足时按比例扩展 |
### 单元格多块支持
表格单元格内可以包含多种块类型:
- Text(普通文本)
- Bullet(无序列表)
- Heading(标题)
⚠️ **注意**:飞书 API 创建表格时会自动在每个单元格内创建空的 Text 块。填充内容时应 **更新现有块** 而非创建新块。
### 表格编写建议
```markdown
| 列1 | 列2 | 列3 |
|-----|-----|-----|
| 数据 | 数据 | 数据 |
```
- ✅ 确保每行列数一致
- ✅ 大表格(超过 8 行数据或超过 9 列)会自动拆分,无需手动处理
- ✅ 列宽由内容自动决定,无需手动控制
---
## 8. 图片处理
### 图片上传(v1.8.0+)
`feishu-cli` 默认通过 `--upload-images` 自动上传图片:
1. 遇到 `` 时,自动下载网络图片或读取本地图片
2. 通过素材上传 API 上传到飞书,获取 file_token
3. 创建 Image 块并引用 file_token,实现图片插入
4. 上传失败时降级为占位块,导入报告显示失败数量
### 注意事项
- ✅ 默认开启图片上传,使用 `--no-upload-images` 可关闭(创建占位块)
- ⚠️ 图片并发上传数通过 `--image-workers` 控制(默认 2,API 限制 5 QPS)
- ✅ 支持本地图片路径和网络 URL(HTTP/HTTPS)
- ✅ 图片相关的 alt 文字会作为占位信息保留
---
## 9. 公式支持
### 行内公式
使用单美元符号包裹:`$E = mc^2$`
支持一个段落内包含多个行内公式:
```markdown
已知 $a^2 + b^2 = c^2$,当 $a = 3, b = 4$ 时,$c = 5$。
```
### 块级公式
使用双美元符号包裹:
```markdown
$$
\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
$$
```
### 注意事项
- ⚠️ 飞书 API 不支持直接创建块级 Equation(BlockType=16),实际导入时 **降级为行内 Equation**
- ✅ LaTeX 语法兼容飞书 KaTeX 渲染器
- ✅ 公式中的特殊字符无需额外转义
---
## 10. API 限制与容错
### 三阶段并发管道
`feishu-cli doc import` 采用三阶段管道架构(`cmd/import_markdown.go`):
| 阶段 | 方式 | 处理内容 |
|------|------|---------|
| 阶段一 | **顺序** | 按文档顺序创建所有块,为图表创建空画板占位块,收集表格任务 |
| 阶段二 | **并发** | 图表 worker 池(默认 5 并发)+ 表格 worker 池(默认 3 并发)同时处理 |
| 阶段三 | **逆序** | 处理失败的图表:删除空画板块,在原位置插入代码块(逆序避免索引偏移) |
### 批量操作限制
| 限制 | 值 | 处理方式 |
|------|-----|---------|
| 单次创建块数 | 最多 50 个 | 自动分批(`batchSize = 50`) |
| 单个表格行数 | 最多 9 行 | 自动拆分并复制表头 |
| 单个表格列数 | 最多 9 列 | 自动拆分并保留首列 |
| 文件夹子节点 | 不超过 1500 | 超出报错 1062507 |
| 文档块总数 | 有上限 | 超出报错 1770004 |
| 文件大小 | 最大 100MB | 超出直接报错 |
| API 频率 | 429 Too Many Requests | 自动重试 + 线性退避 |
### 图表重试与降级策略
| 错误类型 | 判断条件 | 处理方式 |
|---------|---------|---------|
| 语法错误 | `Parse error`、`Invalid request parameter` | ❌ **不重试**,直接降级为代码块 |
| 服务端错误 | 500/502/503、`internal error` | ✅ 重试(最多 10 次,1s 间隔) |
| 频率限制 | 429、`rate limit`、`frequency limit` | ✅ 重试(归为可重试错误) |
| 重试耗尽 | 超过最大重试次数 | ⚠️ 降级为代码块 |
降级处理流程:
1. 获取文档所有顶层子块
2. 按索引 **逆序** 处理失败图表(避免删除导致索引偏移)
3. 删除空画板块
4. 在原位置插入代码块(保留原始图表代码)
### CLI 并发控制参数
| 参数 | 默认值 | 说明 |
|------|-------|------|
| `--diagram-workers` | 5 | 图表(Mermaid/PlantUML)并发导入数 |
| `--table-workers` | 3 | 表格并发填充数 |
| `--diagram-retries` | 10 | 图表最大重试次数 |
| `--verbose` | false | 显示详细进度 |
### 画板 API 技术细节
- API 端点:`/open-apis/board/v1/whiteboards/{id}/nodes/plantuml`
- `syntax_type`:1 = PlantUML,2 = Mermaid
- `diagram_type` 映射:0=auto, 1=mindmap, 2=sequence, 3=activity, 4=class, 5=er, 6=flowchart, 7=state, 8=component
---
## 11. 完整预创建检查清单
创建将导入飞书的 Markdown 文档前,完成以下检查:
### 文档结构
- [ ] 标题层级不超过 6 级(H7-H9 会降级为粗体段落)
- [ ] 嵌套列表使用 2 或 4 空格缩进
- [ ] 表格数据行控制在 8 行以内(避免不必要拆分)
- [ ] 文件总大小不超过 100MB
### Mermaid 图表
- [ ] 图表类型在支持的 8 种之内
- [ ] ❌ 标签无花括号 `{}`
- [ ] ❌ 未使用 `par...and...end`
- [ ] ✅ 方括号标签内含冒号时已加双引号
- [ ] ⚠️ sequenceDiagram:participant ≤ 8,alt ≤ 1 层
- [ ] ❌ 节点标签换行未使用 `\n`,已改用 `<br>` 或 `<br/>`
- [ ] 复杂图表已拆分为多个小图
### PlantUML 图表
- [ ] ✅ 使用正确的包裹标记(`@startuml`/`@enduml`)
- [ ] ❌ 无行首缩进
- [ ] ❌ 无 `skinparam` 等样式指令
- [ ] ❌ 类图未使用可见性标记(`+ - # ~`)
### 特殊内容
- [ ] ✅ 图片路径正确(默认自动上传,失败降级为占位块)
- [ ] ✅ 公式语法正确(`$...$` 行内 / `$$...$$` 块级)
- [ ] ✅ Callout 类型在 6 种之内(NOTE/WARNING/TIP/CAUTION/IMPORTANT/SUCCESS)
### 性能考虑
- [ ] 大量图表时考虑增加 `--diagram-workers`
- [ ] 大量表格时考虑增加 `--table-workers`
- [ ] 首次导入建议加 `--verbose` 观察进度
FILE:references/mermaid-spec.md
# Mermaid 飞书画板语法规范
本文档是飞书画板 Mermaid 渲染的完整规范,包含 8 种图表类型的正确模板、飞书特有限制、常见错误修复方法和复杂度安全阈值。
---
## 1. 通用规则
### 强制性约束
| 规则 | 说明 | 违反后果 |
|------|------|---------|
| 禁止花括号 `{}` | flowchart 节点标签中的 `{}` 被识别为菱形节点(erDiagram/classDiagram 语法结构不受此限制) | Parse error,降级为代码块 |
| 禁止 `par...and...end` | 飞书画板不支持并行语法 | 错误码 2891001 |
| 方括号冒号加引号 | `[text:xxx]` 中冒号导致解析歧义 | 可能 Parse error |
| Note 跨度 ≤ 2 | `Note over` 最多跨 2 个相邻 participant | 渲染错误 |
| 避免过深嵌套 | 多层 subgraph/alt 嵌套增加失败率 | 服务端超时或错误 |
### 画板 API 映射
- API 端点:`/open-apis/board/v1/whiteboards/{id}/nodes/plantuml`
- `syntax_type = 2`(Mermaid)
- `diagram_type`:根据图表类型自动映射(`0` auto 适用于大多数场景)
---
## 2. 图表类型详解
### 2.1 flowchart(流程图)
**diagram_type**: 6 (flowchart)
#### 正确模板
```mermaid
flowchart TD
A[开始] --> B[步骤一]
B --> C{条件判断}
C -->|是| D[处理A]
C -->|否| E[处理B]
D --> F[结束]
E --> F
```
#### 带 subgraph 模板
```mermaid
flowchart LR
subgraph 前端
A[用户界面] --> B[API 调用]
end
subgraph 后端
C[接口层] --> D[业务逻辑]
D --> E[数据库]
end
B --> C
```
#### 支持的方向
| 声明 | 方向 |
|------|------|
| `flowchart TD` / `flowchart TB` | 上到下 |
| `flowchart BT` | 下到上 |
| `flowchart LR` | 左到右 |
| `flowchart RL` | 右到左 |
#### 支持的节点形状
| 语法 | 形状 |
|------|------|
| `A[文本]` | 矩形 |
| `A(文本)` | 圆角矩形 |
| `A([文本])` | 体育场形 |
| `A[[文本]]` | 子程序 |
| `A[(文本)]` | 数据库 |
| `A((文本))` | 圆形 |
| `A>文本]` | 旗帜形 |
| `A{文本}` | 菱形 ⚠️ 仅用于条件判断节点,不要在普通标签中使用 |
| `A{{文本}}` | 六边形 ⚠️ 同上 |
#### 飞书特有限制
- subgraph 嵌套不宜超过 2 层
- 节点标签中 **不要出现花括号**(除非是菱形条件节点)
- 标签含冒号时用双引号包裹:`A["类型: string"]`
#### 常见错误与修复
```mermaid
<!-- ❌ 错误:标签中有花括号 -->
flowchart TD
A["{name: value}"] --> B[处理]
<!-- ✅ 修复:移除花括号 -->
flowchart TD
A["name = value"] --> B[处理]
```
```mermaid
<!-- ❌ 错误:方括号中有冒号 -->
flowchart TD
A[类型:string] --> B[处理]
<!-- ✅ 修复:加双引号 -->
flowchart TD
A["类型: string"] --> B[处理]
```
---
### 2.2 sequenceDiagram(时序图)
**diagram_type**: 2 (sequence)
> ⚠️ 复杂度限制最严格的图表类型,必须严格控制规模。
#### 正确模板(简单)
```mermaid
sequenceDiagram
participant C as 客户端
participant S as 服务端
participant DB as 数据库
C->>S: 发送请求
S->>DB: 查询数据
DB-->>S: 返回结果
S-->>C: 响应数据
```
#### 正确模板(带条件)
```mermaid
sequenceDiagram
participant U as 用户
participant A as 认证服务
participant B as 业务服务
U->>A: 登录请求
alt 认证成功
A-->>U: 返回 Token
U->>B: 携带 Token 请求
B-->>U: 返回数据
else 认证失败
A-->>U: 返回错误
end
```
#### 支持的箭头类型
| 语法 | 含义 |
|------|------|
| `->>` | 实线带箭头(同步调用) |
| `-->>` | 虚线带箭头(返回/异步) |
| `->` | 实线无箭头 |
| `-->` | 虚线无箭头 |
| `-x` | 实线带 X(失败) |
| `--x` | 虚线带 X |
#### 支持的控制结构
| 结构 | 语法 | 飞书支持 |
|------|------|---------|
| 条件 | `alt...else...end` | ✅ 限 1 层 |
| 可选 | `opt...end` | ✅ |
| 循环 | `loop...end` | ✅ |
| 并行 | `par...and...end` | ❌ **完全不支持** |
| 临界区 | `critical...end` | ⚠️ 可能不支持 |
#### 复杂度安全阈值(实测数据)
通过二分法实测确定的飞书画板渲染限制:
| 维度 | 安全值 | 警告值 | 必定失败 |
|------|--------|--------|---------|
| participant 数量 | ≤ 6 | 7-9 | ≥ 10(与其他因素叠加) |
| alt 嵌套层数 | 0-1 | — | ≥ 2(与其他因素叠加) |
| 消息标签长度 | ≤ 20 字符 | 21-30 字符 | ≥ 30(与其他因素叠加) |
| 总消息数 | ≤ 20 | 21-30 | ≥ 30(与其他因素叠加) |
**关键发现**:单一维度超限不一定失败,**多维度同时超限** 时必定失败。
| 组合 | 结果 |
|------|------|
| 6 participant + 0 alt + 短标签 | ✅ 安全 |
| 8 participant + 1 alt + 短标签 | ✅ 通常安全 |
| 10 participant + 0 alt + 短标签 | ⚠️ 可能失败 |
| 10 participant + 2 alt + 长标签 | ❌ 必定失败 |
| 6 participant + 2 alt + 30+ 长标签 | ⚠️ 可能失败 |
#### 飞书特有限制
1. **禁止 `par` 语法**:改用 `Note over A,B: 并行处理` 标注
2. **Note 跨度**:`Note over X,Y` 中 X 和 Y 必须相邻,最多跨 2 个
3. **activate/deactivate**:支持,但大量激活可能增加复杂度
4. **participant 别名**:推荐使用(`participant A as 短名`)减少标签宽度
#### 常见错误与修复
```mermaid
<!-- ❌ 错误:使用 par 语法 -->
sequenceDiagram
par 并行处理
A->>B: 请求1
and
A->>C: 请求2
end
<!-- ✅ 修复:改用 Note -->
sequenceDiagram
Note over A,C: 并行处理
A->>B: 请求1
A->>C: 请求2
```
```mermaid
<!-- ❌ 错误:太多参与者 + 多层 alt -->
sequenceDiagram
participant A
participant B
participant C
participant D
participant E
participant F
participant G
participant H
participant I
participant J
A->>B: 这是一条很长很长的消息标签文字
alt 条件1
alt 条件2
B->>C: 嵌套条件
end
end
<!-- ✅ 修复:拆分为两个图 -->
```
#### 拆分建议
当时序图超过安全阈值时:
1. **按阶段拆分**:登录阶段、业务阶段、清理阶段
2. **按模块拆分**:前端交互、后端处理、数据存储
3. **提取公共流程**:认证流程单独一图,主流程引用
---
### 2.3 classDiagram(类图)
**diagram_type**: 4 (class)
#### 正确模板
```mermaid
classDiagram
class Animal {
+String name
+int age
+makeSound()
}
class Dog {
+String breed
+fetch()
}
class Cat {
+String color
+purr()
}
Animal <|-- Dog
Animal <|-- Cat
```
#### 支持的关系
| 语法 | 含义 |
|------|------|
| `<\|--` | 继承 |
| `*--` | 组合 |
| `o--` | 聚合 |
| `-->` | 依赖 |
| `--` | 关联 |
| `..\|>` | 实现 |
| `..>` | 虚线依赖 |
#### 飞书特有限制
- 类数量建议 ≤ 15,超过考虑拆分
- 方法和属性总数不宜过多
- 关系线条交叉过多可能渲染不清晰
---
### 2.4 stateDiagram-v2(状态图)
**diagram_type**: 0 (auto)
> 必须使用 `stateDiagram-v2`,不要使用旧版 `stateDiagram`。
#### 正确模板
```mermaid
stateDiagram-v2
[*] --> 待审核
待审核 --> 审核中: 提交审核
审核中 --> 已通过: 审核通过
审核中 --> 已拒绝: 审核拒绝
已拒绝 --> 待审核: 重新提交
已通过 --> [*]
```
#### 带嵌套状态
```mermaid
stateDiagram-v2
[*] --> Active
state Active {
[*] --> Idle
Idle --> Processing: 收到请求
Processing --> Idle: 处理完成
}
Active --> Inactive: 超时
Inactive --> Active: 唤醒
Inactive --> [*]
```
#### 飞书特有限制
- 嵌套状态不宜超过 2 层
- 中文状态名支持良好
- 并发状态(`--`)谨慎使用
---
### 2.5 erDiagram(ER 图)
**diagram_type**: 5 (er)
#### 正确模板
```mermaid
erDiagram
USER {
int id PK
string name
string email
}
ORDER {
int id PK
int user_id FK
date created_at
float total
}
PRODUCT {
int id PK
string name
float price
}
ORDER_ITEM {
int id PK
int order_id FK
int product_id FK
int quantity
}
USER ||--o{ ORDER : places
ORDER ||--|{ ORDER_ITEM : contains
PRODUCT ||--o{ ORDER_ITEM : "included in"
```
#### 支持的关系符号
| 语法 | 含义 |
|------|------|
| `\|\|--\|\|` | 一对一 |
| `\|\|--o{` | 一对多 |
| `}o--o{` | 多对多 |
| `\|\|--\|{` | 一对多(至少一个) |
#### 飞书特有限制
- 实体数量建议 ≤ 12
- 属性列表不宜过长(每实体 ≤ 10 个属性)
- 关系标签用双引号包裹多词标签
---
### 2.6 gantt(甘特图)
**diagram_type**: 0 (auto)
#### 正确模板
```mermaid
gantt
title 项目计划
dateFormat YYYY-MM-DD
section 设计阶段
需求分析 :a1, 2024-01-01, 7d
系统设计 :a2, after a1, 5d
section 开发阶段
前端开发 :b1, after a2, 14d
后端开发 :b2, after a2, 14d
section 测试阶段
集成测试 :c1, after b1, 7d
上线部署 :c2, after c1, 3d
```
#### 飞书特有限制
- 任务数量建议 ≤ 20
- `dateFormat` 推荐使用 `YYYY-MM-DD`
- 支持 `after` 依赖关系
- section 不宜过多
---
### 2.7 pie(饼图)
**diagram_type**: 0 (auto)
#### 正确模板
```mermaid
pie title 技术栈分布
"Go" : 45
"Python" : 25
"JavaScript" : 20
"其他" : 10
```
#### 飞书特有限制
- 分片数量建议 ≤ 8
- 标签用双引号包裹
- 数值为正数
- title 是可选的
---
### 2.8 mindmap(思维导图)
**diagram_type**: 1 (mindmap)
#### 正确模板
```mermaid
mindmap
root((项目架构))
前端
React
TypeScript
Webpack
后端
Go
gRPC
PostgreSQL
基础设施
Docker
Kubernetes
CI/CD
```
#### 飞书特有限制
- 根节点使用 `root((文字))` 或直接 `root(文字)`
- 缩进表示层级关系(使用空格)
- 层级深度建议 ≤ 4
- 每层节点数建议 ≤ 8
- 不支持节点间连线
---
## 3. 复杂场景处理
### 3.1 大型图表拆分策略
当图表规模超过安全阈值时,应拆分为多个小图:
| 图表类型 | 拆分维度 | 示例 |
|---------|---------|------|
| 流程图 | 按阶段/模块 | 总流程 + 各子流程 |
| 时序图 | 按阶段/参与者组 | 认证流程 + 业务流程 |
| 类图 | 按包/模块 | 模型层 + 服务层 |
| ER 图 | 按领域 | 用户域 + 订单域 |
### 3.2 图表标题与说明
每个图表建议在代码块前添加说明:
~~~~markdown
#### 用户认证流程
以下时序图展示了用户从登录到获取 Token 的完整流程:
```mermaid
sequenceDiagram
participant U as 用户
participant A as 认证服务
U->>A: 登录请求
A-->>U: 返回 Token
```
~~~~
### 3.3 失败降级预期
即使严格遵守规范,仍有约 **7% 的失败率**(基于 88 个图表的实测数据)。失败时 feishu-cli 会:
1. 自动重试(最多 10 次,每次间隔 1 秒)
2. 重试无效时降级为代码块(保留原始 Mermaid 代码)
3. 在导入报告中标注失败图表
**降级对文档影响**:代码块中的 Mermaid 代码仍然可读,用户可在飞书中手动处理。
---
## 4. 视觉样式规范(flowchart/graph 必须遵循)
> 生成 flowchart/graph 图表时,**必须**使用 `classDef` 为不同实体类型定义颜色,并使用不同节点形状区分实体类别。禁止所有节点使用相同的默认样式。
### classDef 颜色定义(复制到每个 flowchart 图表末尾)
```
classDef db fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
classDef es fill:#fff3e0,stroke:#e65100,color:#bf360c
classDef mq fill:#f3e5f5,stroke:#6a1b9a,color:#4a148c
classDef dw fill:#fce4ec,stroke:#c62828,color:#b71c1c
classDef svc fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
classDef faas fill:#e0f7fa,stroke:#00838f,color:#006064
classDef cfg fill:#fffde7,stroke:#f9a825,color:#f57f17
classDef fe fill:#f5f5f5,stroke:#616161,color:#212121
classDef legacy fill:#ffebee,stroke:#c62828,stroke-dasharray:5 5
classDef ext fill:#eceff1,stroke:#455a64,color:#263238
```
### 实体类型 → 颜色 + 形状 对照表
| 实体类型 | classDef | 节点形状 | 适用对象 |
|---------|----------|---------|---------|
| 数据库 MySQL/RDS | `db` | `[(文本)]` 圆柱体 | MySQL, RDS, PostgreSQL |
| 搜索引擎 ES | `es` | `[(文本)]` 圆柱体 | Elasticsearch, ES 索引 |
| 消息队列 MQ | `mq` | `([文本])` 体育场形 | EventBus, Kafka, RocketMQ |
| 数仓 | `dw` | `[(文本)]` 圆柱体 | OneService, Doris, Hive, Dorado |
| RPC 服务 | `svc` | `[文本]` 矩形 | 微服务、RPC 服务端 |
| FaaS 服务 | `faas` | `[文本]` 矩形 | ByteFaaS, DSync FaaS |
| 配置中心 | `cfg` | `[文本]` 矩形 | TCC, Kani, 配置平台 |
| 前端/用户 | `fe` | `(文本)` 圆角矩形 | 前端、用户界面、TLB |
| 旧系统 | `legacy` | `[文本]` 矩形虚线 | 待下线系统、遗留服务 |
| 外部服务 | `ext` | `[文本]` 矩形 | 第三方服务、非团队维护的服务 |
### 注意事项
- **sequenceDiagram 不支持 classDef**,仅 flowchart/graph 类型图表可用
- 每个图表都应通过颜色自解释实体类型
- 新旧系统并存时,旧系统使用 `legacy`(红色虚线边框)明确标识
- 完整规范和使用示例见 `~/.claude/rules/markdown-style-guide.md`
---
## 5. 快速参考卡片
### 视觉样式速查
```
DB=绿色圆柱体 | ES=橙色圆柱体 | MQ=紫色体育场形
RPC=蓝色矩形 | FaaS=青色矩形 | 配置=黄色矩形
前端=灰色圆角 | 旧系统=红色虚线 | 外部=灰蓝色矩形
```
### 图表类型选择
```
需要流程/步骤? → flowchart
需要交互/调用链? → sequenceDiagram(≤6 participant)
需要类/结构关系? → classDiagram
需要状态转换? → stateDiagram-v2
需要数据库表关系? → erDiagram
需要项目排期? → gantt
需要占比分布? → pie
需要层级梳理? → mindmap
```
### 安全检查速记
```
✅ 8 种图表类型
❌ 花括号 {} 在标签中
❌ par...and...end
❌ Note over 跨 3+ participant
❌ sequenceDiagram: 10+ participant + 2+ alt
⚠️ 方括号冒号 → 加双引号
⚠️ 复杂嵌套 → 考虑拆分
```
飞书会话浏览、消息互动与群聊管理。查看聊天记录、获取群聊历史消息、搜索群聊、 获取消息详情、Reaction 表情回应、Pin 置顶/取消置顶、删除消息、 群聊信息查询与管理(获取/更新/解散/成员管理)。 支持普通群和话题群两种模式,话题群自动获取线程回复。所有命令需要 User Token。 当用户请求"查看...
---
name: feishu-cli-chat
description: >-
飞书会话浏览、消息互动与群聊管理。查看聊天记录、获取群聊历史消息、搜索群聊、
获取消息详情、Reaction 表情回应、Pin 置顶/取消置顶、删除消息、
群聊信息查询与管理(获取/更新/解散/成员管理)。
支持普通群和话题群两种模式,话题群自动获取线程回复。所有命令需要 User Token。
当用户请求"查看聊天记录"、"看和某人的消息"、"群聊历史"、"群消息"、"搜索群聊"、
"查群信息"、"群成员"、"最近消息"、"聊天记录"、"Reaction"、"表情回应"、
"置顶消息"、"Pin"、"删除消息"、"获取消息"、"消息详情"、
"和谁聊了什么"、"群里说了什么"、"总结群消息"、"话题回复"、"线程回复"、
"thread replies"时使用。
也适用于:用户给出一个群聊名称或 chat_id 并希望浏览其消息的场景,
即使没有明确说"聊天记录"。当用户想了解某个群最近在讨论什么、
想找和某人的对话内容、或想对消息进行互动操作时,都应使用此技能。
argument-hint: <chat_id|群名|用户名>
user-invocable: true
allowed-tools: Bash, Read, Write
---
# 飞书会话浏览与管理
通过 feishu-cli 浏览飞书单聊/群聊消息历史、搜索会话、管理群聊信息和成员。
## 前置条件
本技能的核心命令**必须使用 User Token**,使用前需先登录。`chat create`、`chat link`、`msg read-users` 使用 App Token,属于 feishu-cli-toolkit 技能。
```bash
feishu-cli auth login
```
未登录时命令会直接报错并提示登录方式。登录后 token 自动加载,无需手动传参。
### 身份说明
| 身份 | 使用场景 |
|------|---------|
| **User Token**(必须) | 本技能所有读取/管理命令:chat get/update/delete、member list/add/remove、msg get/history/list/pins/reaction/search-chats、search messages |
| **App Token** | 仅 `chat create`、`chat link`、`msg read-users`(这三个命令不属于本技能核心流程) |
User Token 能力:
- 查看 bot 不在的群聊消息
- 查看私聊(p2p)消息
- 搜索用户有权限的所有会话
### 自动降级机制
当使用 User Token 调用 `msg history` / `msg list` 时,如果 bot 不在目标群中,API 会返回空结果。CLI 会自动检测这种情况并降级为 **search + get** 方式获取消息:
```
list API 返回空 + has_more=true → 自动切换到搜索模式 → 逐条获取消息内容
```
这个过程对用户透明,无需手动干预。
---
## 场景一:查看群聊历史消息
这是最常见的场景——用户想看某个群最近在聊什么。
### 步骤 1:找到群聊
如果用户给了群名而不是 chat_id,先搜索:
```bash
feishu-cli msg search-chats --query "群名关键词" -o json
```
输出中的 `chat_id`(形如 `oc_xxx`)就是后续命令需要的标识。
### 步骤 2:获取消息
```bash
# 获取最近 50 条消息(API 单次上限 50)
feishu-cli msg history \
--container-id oc_xxx \
--container-id-type chat \
--page-size 50 \
--sort-type ByCreateTimeDesc \
-o json
# 按时间范围获取(--start-time 为毫秒级时间戳,仅返回该时间之后的消息)
feishu-cli msg history \
--container-id oc_xxx \
--container-id-type chat \
--page-size 50 \
--start-time 1773778860000 \
--sort-type ByCreateTimeDesc \
-o json
# 获取更早的消息(使用上一次返回的 page_token 翻页)
feishu-cli msg history \
--container-id oc_xxx \
--container-id-type chat \
--page-size 50 \
--page-token "上一页返回的token" \
-o json
```
### 步骤 2.5:判断群类型并获取线程回复
飞书群聊分为**普通群**和**话题群**两种,消息结构和获取策略完全不同。
#### 如何判断群类型
观察 `msg history` 返回的消息字段:
| 群类型 | 特征 | 示例 |
|--------|------|------|
| **话题群** | **每条**消息都有 `thread_id`(形如 `omt_xxx`),主消息流仅包含每个话题的首条消息 | 泰国华商群 |
| **普通群** | 独立消息**无** `thread_id`,仅被回复的消息和回复消息才有 `thread_id` | Claude Code闲聊群 |
#### 消息中的线程相关字段
| 字段 | 说明 | 出现条件 |
|------|------|---------|
| `thread_id` | 线程/话题 ID(形如 `omt_xxx`) | 话题群所有消息 / 普通群中参与线程的消息 |
| `root_id` | 线程根消息 ID(即话题首条消息) | 线程回复消息 |
| `parent_id` | 直接上级消息 ID(被回复的那条消息) | 线程回复消息 |
#### 普通群:一次获取全部消息
普通群的 `msg history` 返回**所有消息**(独立消息 + 线程回复),平铺在同一列表中。通过 `root_id`/`parent_id` 可重建回复关系,**不需要**额外获取线程。
```
独立消息: 无 thread_id、无 root_id
被回复的消息: 有 thread_id(被回复后自动产生)
线程回复: 有 thread_id + root_id + parent_id
```
#### 话题群:需要逐个话题获取回复(重要)
话题群的 `msg history --container-id-type chat` **仅返回每个话题的首条消息**,线程回复不在主消息流中。必须按 thread_id 逐个获取:
```bash
# 获取单个话题的所有回复(按时间正序,方便阅读)
feishu-cli msg history \
--container-id omt_xxx \
--container-id-type thread \
--page-size 50 \
--sort-type ByCreateTimeAsc \
-o json
```
**完整的话题群获取流程**:
```bash
# 1. 获取主消息流(每个话题的首条消息)
feishu-cli msg history \
--container-id oc_xxx \
--container-id-type chat \
--page-size 50 \
--sort-type ByCreateTimeDesc \
-o json
# 2. 从返回结果中提取所有 thread_id
# 3. 对每个 thread_id 获取回复(可并发执行,提高效率)
feishu-cli msg history --container-id omt_xxx --container-id-type thread --page-size 50 --sort-type ByCreateTimeAsc -o json
feishu-cli msg history --container-id omt_yyy --container-id-type thread --page-size 50 --sort-type ByCreateTimeAsc -o json
# ... 多个话题可并行获取
```
> **性能提示**:话题群中活跃话题可能有 10-20 个,建议**并发获取**多个话题的回复。飞书 API 对 `msg history` 无严格 QPS 限制(不同于搜索 API),可以安全并发。
### 步骤 3:解析消息内容
API 返回的消息 body.content 是 JSON 字符串,常见格式:
| msg_type | content 格式 | 说明 |
|----------|-------------|------|
| text | `{"text":"消息内容"}` | 纯文本,`@_user_1` 是 at 占位符 |
| post | 富文本 JSON | 包含标题和段落结构 |
| image | `{"image_key":"xxx"}` | 图片 |
| interactive | 卡片 JSON | 交互式卡片 |
| share_calendar_event | `{"summary":"会议名","start_time":"ms","end_time":"ms",...}` | 日历事件分享 |
| sticker | `{"sticker_key":"xxx"}` | 表情包 |
| file | `{"file_key":"xxx","file_name":"..."}` | 文件 |
用 Python 提取文本内容的常用方式:
```python
import json
content = json.loads(msg['body']['content'])
text = content.get('text', '')
```
### 完整示例:获取并总结群聊最近 N 条消息
```bash
# 1. 搜索群聊
feishu-cli msg search-chats --query "Go讨论区" -o json
# 2. 获取消息(循环翻页直到够数)
feishu-cli msg history \
--container-id oc_xxx \
--container-id-type chat \
--page-size 50 \
--sort-type ByCreateTimeDesc \
-o json > /tmp/chat_page1.json
# 3. 提取 page_token 获取下一页
# ... 循环直到获取足够消息
```
> **注意**:Search API 的 page_size 与 List API 不同,降级模式下每页实际返回数量可能少于请求值。建议循环翻页直到 `has_more=false` 或达到目标数量。
---
## 场景二:查看和某人的私聊记录
飞书 Open API **不支持直接按用户查询 p2p 聊天记录**。需要通过搜索 API 间接实现。
### 方法:搜索 + 筛选
```bash
# 搜索私聊消息
feishu-cli search messages "关键词" \
--chat-type p2p_chat \
-o json
# 如果知道对方的 open_id,可以按发送者筛选
feishu-cli search messages "关键词" \
--chat-type p2p_chat \
--from-ids ou_xxx \
-o json
# 获取单条消息详情
feishu-cli msg get om_xxx -o json
```
### 查找用户 ID
如果用户只给了邮箱或手机号,可以查找对应的 open_id:
```bash
# 通过邮箱查找用户
feishu-cli user search --email [email protected] -o json
# 通过手机号查找用户
feishu-cli user search --mobile 13800138000 -o json
```
> **注意**:`user search` 仅支持 `--email` 和 `--mobile` 精确查找,不支持按姓名模糊搜索。
### 限制说明
- 搜索 API 的 `query` 参数**不能为空**,至少需要一个空格 `" "`
- p2p 聊天无法通过 `msg search-chats` 搜索(该 API 只搜索群聊)
- 搜索结果返回的是消息 ID 列表,需要逐条 `msg get` 获取完整内容
- **`msg get` 对私聊消息可能返回 230001 错误**(API 限制:部分私聊消息不支持通过 Get API 获取详情),此时只能依赖搜索结果中的摘要信息
---
## 场景三:搜索群聊
### 搜索群聊列表
```bash
# 按关键词搜索群聊
feishu-cli msg search-chats --query "关键词" -o json
# 分页获取所有群
feishu-cli msg search-chats --page-size 100 -o json
```
### 在群内搜索消息
```bash
# 在指定群中搜索消息
feishu-cli search messages "关键词" --chat-ids oc_xxx -o json
```
> **更多搜索功能**(按时间范围、消息类型、发送者、跨模块搜索文档/应用等)请使用 **feishu-cli-search** 技能,提供完整的筛选参数和 Token 排错指南。
---
## 场景四:群聊信息管理
### 查看群信息
```bash
feishu-cli chat get oc_xxx
```
默认输出 JSON 格式,包含群名、描述、群主、群类型、成员数量等。
### 查看群成员
```bash
# 获取成员列表
feishu-cli chat member list oc_xxx
# 指定 ID 类型
feishu-cli chat member list oc_xxx --member-id-type user_id
# 分页获取(大群)
feishu-cli chat member list oc_xxx --page-size 100 --page-token "xxx"
```
> **Scope 要求**:使用 User Token 时需要 `im:chat:readonly` 或 `im:chat.members:read` scope。若报 99991679 错误,需通过 `auth login --scopes "... im:chat:readonly"` 重新授权。
### 修改群信息
```bash
# 改群名
feishu-cli chat update oc_xxx --name "新群名"
# 改群描述
feishu-cli chat update oc_xxx --description "新的群描述"
# 转让群主
feishu-cli chat update oc_xxx --owner-id ou_xxx
```
### 群成员管理
```bash
# 添加成员
feishu-cli chat member add oc_xxx --id-list ou_xxx,ou_yyy
# 移除成员
feishu-cli chat member remove oc_xxx --id-list ou_xxx
# 使用 user_id 类型
feishu-cli chat member add oc_xxx --id-list user_xxx --member-id-type user_id
```
### 创建群聊
```bash
feishu-cli chat create --name "新群聊" --user-ids ou_xxx,ou_yyy
```
> **注意**:`chat create` 和 `chat link`(获取分享链接)仅支持 App Token(租户身份),不支持 User Token。
### 解散群聊
```bash
feishu-cli chat delete oc_xxx
# 会要求确认,不可逆操作
```
---
## 场景五:消息详情与互动
### 获取单条消息详情
```bash
feishu-cli msg get om_xxx -o json
```
### 查看消息已读情况
```bash
feishu-cli msg read-users om_xxx -o json
```
> **限制**:仅支持查询 bot 自己发送的、7 天内的消息,且 bot 必须在会话内。此命令仅使用 App Token。
### 查看群内置顶消息
```bash
feishu-cli msg pins --chat-id oc_xxx -o json
```
### 置顶/取消置顶消息
```bash
# 置顶消息
feishu-cli msg pin <message_id>
# 取消置顶
feishu-cli msg unpin <message_id>
```
### Reaction 表情回应
```bash
# 添加表情
feishu-cli msg reaction add <message_id> --emoji-type THUMBSUP
# 删除表情
feishu-cli msg reaction remove <message_id> --reaction-id <REACTION_ID>
# 查询表情列表
feishu-cli msg reaction list <message_id> [--emoji-type THUMBSUP] [--page-size 20]
```
常用 emoji-type:`THUMBSUP`(点赞)、`SMILE`(微笑)、`LAUGH`(大笑)、`HEART`(爱心)、`JIAYI`(加一)、`OK`、`FIRE`
### 删除消息
仅能删除机器人自己发送的消息,不可恢复。
```bash
feishu-cli msg delete <message_id>
```
---
## 常见操作速查表
| 用户意图 | 命令 | Token |
|---------|------|:---:|
| 看某群最近消息 | `msg history --container-id oc_xxx --container-id-type chat` | User |
| 看话题群的线程回复 | `msg history --container-id omt_xxx --container-id-type thread` | User |
| 看和某人的聊天 | `search messages " " --chat-type p2p_chat --from-ids ou_xxx` | User |
| 搜索群聊 | `msg search-chats --query "关键词"` | User |
| 在群内搜索消息 | `search messages "关键词" --chat-ids oc_xxx` | User |
| 查群信息 | `chat get oc_xxx` | User |
| 查群成员 | `chat member list oc_xxx` | User |
| 改群名/群主 | `chat update oc_xxx --name "新名"` | User |
| 加/删群成员 | `chat member add/remove oc_xxx --id-list xxx` | User |
| 查消息详情 | `msg get om_xxx` | User |
| 看置顶消息 | `msg pins --chat-id oc_xxx` | User |
| 置顶/取消置顶 | `msg pin/unpin <message_id>` | User |
| 添加 Reaction | `msg reaction add <message_id> --emoji-type THUMBSUP` | User |
| 删除消息 | `msg delete <message_id>` | User |
| 查消息已读 | `msg read-users om_xxx` | App(仅 bot 消息) |
| 创建群聊 | `chat create --name "群名"` | App |
| 获取群链接 | `chat link oc_xxx` | App |
> 标记 **User** 的命令必须先 `auth login`,未登录会报错。标记 **App** 的命令使用应用身份,无需登录。
---
## 处理大量消息的最佳实践
当需要获取并分析大量消息(如 100+ 条)时:
1. **保存到文件**:每页结果用 `-o json` 输出,重定向到文件
2. **循环翻页**:检查 `HasMore` 和 `PageToken`,循环获取直到满足条件
3. **用 Python 解析**:JSON 消息结构需要解析 `body.content` 提取文本
4. **注意限频**:搜索 API 有频率限制,大量请求间加 1s 延迟;`msg history` 限频较宽松,可安全并发
5. **时间戳**:`create_time` 是毫秒级时间戳,需除以 1000 转为秒
6. **话题群并发获取线程**:话题群需要对每个 `thread_id` 单独调用 `msg history --container-id-type thread`,建议**并行调用**多个话题以提高效率(实测 10-20 个并发无问题)
7. **已撤回消息**:`deleted: true` 的消息内容为 `"This message was recalled"`,汇总时应跳过
```python
import json
from datetime import datetime
# 解析消息时间
ts = int(msg['create_time']) / 1000
dt = datetime.fromtimestamp(ts)
time_str = dt.strftime('%Y-%m-%d %H:%M')
# 提取文本内容
content = json.loads(msg['body']['content'])
text = content.get('text', '')
```
---
## 权限要求
| scope | 说明 | 对应命令 |
|-------|------|---------|
| `im:message:readonly` | 消息读取 | msg get/history/list |
| `im:message.group_msg:get_as_user` | User 身份读取群消息 | msg history/list(读群消息必需) |
| `im:message.pins` | 消息置顶管理 | msg pin/unpin/pins |
| `im:message.reactions` | 消息 Reaction | msg reaction add/remove/list |
| `im:message` | 消息读写 | msg delete |
| `im:chat:read` | 群聊搜索 | msg search-chats |
| `im:chat:readonly` | 群聊信息只读 | chat get、chat member list |
| `im:chat.members:read` | 群成员读取 | chat member list |
| `im:chat` | 群聊管理 | chat update/delete |
| `im:chat.members` | 群成员管理 | chat member add/remove |
| `search:message` | 消息搜索 | search messages |
---
## 与其他技能的分工
| 场景 | 使用技能 |
|------|---------|
| 浏览聊天记录、搜索群聊、群信息/成员管理、Reaction/Pin/删除/获取消息 | **feishu-cli-chat**(本技能) |
| 发送消息、回复、转发/合并转发 | feishu-cli-msg |
| 搜索文档/应用、高级消息搜索(多条件筛选) | feishu-cli-search |
| 表格、日历、任务、文件、知识库等其他模块 | feishu-cli-toolkit |
| OAuth 登录、Token 管理 | feishu-cli-auth |
飞书画板全功能操作:创建画板、绘制架构图/流程图/看板(通过 create-notes API 精确控制节点位置和样式)、 导入 Mermaid/PlantUML 图表、下载画板图片、获取/复制画板节点。 当用户请求"画个图"、"画架构图"、"画流程图"、"画板"、"whiteboard"、"create-not...
---
name: feishu-cli-board
description: >-
飞书画板全功能操作:创建画板、绘制架构图/流程图/看板(通过 create-notes API 精确控制节点位置和样式)、
导入 Mermaid/PlantUML 图表、下载画板图片、获取/复制画板节点。
当用户请求"画个图"、"画架构图"、"画流程图"、"画板"、"whiteboard"、"create-notes"、
"在飞书里画图"、"画个看板"、"可视化"、"节点图"时使用。
也适用于:用户给出一组实体和关系,期望在飞书文档中生成可视化图表的场景。
与 Mermaid 导入的区别:Mermaid 由飞书服务端自动排版,create-notes 可精确控制坐标、颜色、连线,
适合需要精排的架构图和看板。
argument-hint: "[whiteboard_id]"
user-invocable: true
allowed-tools: Bash, Read, Write
---
# 飞书画板操作技能
## 前置条件
- **认证**:需要有效的 App Access Token(环境变量 `FEISHU_APP_ID` + `FEISHU_APP_SECRET`,或 `~/.feishu-cli/config.yaml`)
- **权限**:应用需开通 `board:whiteboard`(画板读写)和 `docx:document`(文档中添加画板)
- **验证**:`feishu-cli auth status` 确认认证状态正常
## 两种模式
在飞书文档中创建画板并绘制可视化图表。支持两种模式:
| 模式 | 方式 | 适用场景 |
|------|------|---------|
| **精排绘图** | `board create-notes` — JSON 描述节点坐标、颜色、连线 | 架构图、看板、自定义布局 |
| **图表导入** | `board import` — Mermaid/PlantUML 代码自动渲染 | 标准流程图、时序图等 8 种图表 |
### 何时使用哪种方式
| 需求 | 推荐方式 | 说明 |
|------|---------|------|
| 精确控制节点位置、颜色、坐标 | `board create-notes`(本技能) | 完全自定义布局,适合架构图、看板 |
| 从 Mermaid/PlantUML 代码快速生成图 | `board import` 或 `doc import` | 服务端自动排版,无需手动计算坐标 |
| 在文档中内嵌简单图表 | `feishu-cli-write` / `feishu-cli-import` 的 Mermaid 支持 | Markdown 中写 Mermaid 代码块,导入时自动转画板 |
**简单判断**:如果你只需要"画个流程图"且不关心精确坐标,优先用 Mermaid;如果需要"精排"或"自定义配色布局",用 `create-notes`。
## 精排绘图工作流(create-notes)
这是画板的核心能力,通过 JSON 精确控制每个节点的位置、大小、颜色和连线。
### 标准四步流程
```bash
# 1. 创建文档(或使用已有文档)
feishu-cli doc create --title "架构图" -o json
# → document_id
# 2. 在文档中添加画板
feishu-cli doc add-board <document_id> -o json
# → whiteboard_id
# 3. 创建节点(先形状,再连接线)
feishu-cli board create-notes <whiteboard_id> shapes.json -o json
# → node_ids(用于连接线引用)
feishu-cli board create-notes <whiteboard_id> connectors.json -o json
# 4. 截图验证
feishu-cli board image <whiteboard_id> output.png
```
**关键原则**:先创建所有形状节点获取 ID,再创建连接线引用这些 ID。
### 最小示例:2 个节点 + 1 条连线
在深入 JSON 格式细节之前,先看一个最小的完整示例——两个矩形节点通过一条箭头连接:
```bash
# shapes.json — 两个形状节点
cat > /tmp/minimal_shapes.json << 'EOF'
[
{"type":"composite_shape","x":100,"y":100,"width":160,"height":40,
"composite_shape":{"type":"round_rect"},
"text":{"text":"服务 A","font_size":14,"font_weight":"regular","horizontal_align":"center","vertical_align":"mid"},
"style":{"fill_color":"#3399ff","fill_opacity":100,"border_style":"none"},
"z_index":10},
{"type":"composite_shape","x":400,"y":100,"width":160,"height":40,
"composite_shape":{"type":"round_rect"},
"text":{"text":"服务 B","font_size":14,"font_weight":"regular","horizontal_align":"center","vertical_align":"mid"},
"style":{"fill_color":"#509863","fill_opacity":100,"border_style":"none"},
"z_index":10}
]
EOF
feishu-cli board create-notes $BOARD_ID /tmp/minimal_shapes.json -o json
# → 返回 node_ids: ["o1:1", "o1:2"]
# connector.json — 一条从服务 A 指向服务 B 的连线
cat > /tmp/minimal_connector.json << 'EOF'
[
{"type":"connector","width":1,"height":1,"z_index":50,
"connector":{"shape":"polyline",
"start":{"arrow_style":"none","attached_object":{"id":"o1:1","position":{"x":1,"y":0.5},"snap_to":"right"}},
"end":{"arrow_style":"triangle_arrow","attached_object":{"id":"o1:2","position":{"x":0,"y":0.5},"snap_to":"left"}}},
"style":{"border_color":"#646a73","border_opacity":100,"border_style":"solid","border_width":"narrow"}}
]
EOF
feishu-cli board create-notes $BOARD_ID /tmp/minimal_connector.json -o json
```
这就是 create-notes 的基本模式:**形状定义位置和样式 → 连接线通过 ID 引用形状**。下面是各字段的详细说明。
### 节点 JSON 格式
#### 形状节点(composite_shape)
最常用的节点类型,支持矩形、圆角矩形等:
```json
{
"type": "composite_shape",
"x": 100, "y": 100, "width": 200, "height": 50,
"composite_shape": {"type": "round_rect"},
"text": {
"text": "节点文本",
"font_size": 14,
"font_weight": "regular",
"horizontal_align": "center",
"vertical_align": "mid"
},
"style": {
"fill_color": "#8569cb",
"fill_opacity": 100,
"border_style": "none"
},
"z_index": 10
}
```
**注意**:多余字段会导致 `2890002 invalid arg` 错误,保持最小格式。
#### 连接线(connector)
连接两个已创建的形状节点:
```json
{
"type": "connector",
"width": 1, "height": 1, "z_index": 50,
"connector": {
"shape": "polyline",
"start": {
"arrow_style": "none",
"attached_object": {
"id": "<source_node_id>",
"position": {"x": 1, "y": 0.5},
"snap_to": "right"
}
},
"end": {
"arrow_style": "triangle_arrow",
"attached_object": {
"id": "<target_node_id>",
"position": {"x": 0, "y": 0.5},
"snap_to": "left"
}
}
},
"style": {
"border_color": "#646a73",
"border_opacity": 100,
"border_style": "solid",
"border_width": "narrow"
}
}
```
#### 连接方向速查
| 方向 | start position | start snap_to | end position | end snap_to |
|------|---------------|---------------|-------------|-------------|
| → 左到右 | `{x:1, y:0.5}` | `right` | `{x:0, y:0.5}` | `left` |
| ↓ 上到下 | `{x:0.5, y:1}` | `bottom` | `{x:0.5, y:0}` | `top` |
| ← 右到左 | `{x:0, y:0.5}` | `left` | `{x:1, y:0.5}` | `right` |
| ↑ 下到上 | `{x:0.5, y:0}` | `top` | `{x:0.5, y:1}` | `bottom` |
position 是归一化坐标(0-1),表示节点边缘上的连接点位置。一个节点连多条线时,调整 position 避免重叠(如扇出:`x:0.25`、`x:0.5`、`x:0.75`)。
### 配色方案
为不同实体类型使用不同颜色,让图表一目了然:
| 用途 | 填充色 | 边框色 | 适用对象 |
|------|--------|--------|---------|
| 强调/标题 | `#8569cb` | — | 核心服务、标题栏 |
| 紫色辅助 | `#eae2fe` | `#8569cb` | API、中间层 |
| 绿色正向 | `#509863` | — | 成功、输出、完成 |
| 绿色辅助 | `#d5e8d4` | `#509863` | 处理步骤 |
| 蓝色服务 | `#3399ff` | — | 主服务、入口 |
| 蓝色辅助 | `#cce5ff` | `#3399ff` | 子服务、组件 |
| 橙色并发 | `#ffc285` | — | 并发处理、Worker |
| 橙色辅助 | `#fff0e3` | `#ffc285` | 并发子任务 |
| 红色告警 | `#ef4444` | — | 错误、降级、告警 |
| 红色辅助 | `#ffe0e0` | `#ef4444` | 容错处理 |
| 灰色输入 | `#f5f5f5` | `#616161` | 用户、外部输入 |
| 连接线 | — | `#646a73` | 普通连线 |
### z_index 与透明度规则
| z_index | 用途 | fill_opacity |
|---------|------|-------------|
| 0-1 | 背景色带(分区底色) | ≤25% |
| 2-3 | 次级区域 | ≤60% |
| 10 | 常规形状节点 | 100% |
| 50 | 连接线 | border_opacity=100 |
背景色带 `fill_opacity` 必须 ≤60%,否则遮挡上层节点。
### 布局设计建议
绘制复杂图表时的布局规划方法:
1. **确定画布尺寸** — 宽 800px 左右,高度按行数估算(每行 ~80px 间距)
2. **按行分区** — 标题行、输入行、处理行、输出行,每行 y 坐标递增
3. **对齐网格** — 同层节点 y 坐标相同,节点间 x 间距 ≥30px
4. **背景色带** — 用低透明度矩形作为分区视觉标记(z_index=0)
5. **先画再连** — 所有形状一个批次创建,拿到 ID 后再创建连接线
### 传参方式
```bash
# 从 JSON 文件(推荐,复杂图表)
feishu-cli board create-notes <whiteboard_id> nodes.json -o json
# 内联 JSON(简单场景)
feishu-cli board create-notes <whiteboard_id> '<json_array>' --source-type content -o json
```
## Mermaid / PlantUML 图表导入
将 Mermaid 或 PlantUML 代码自动渲染为飞书画板(服务端排版):
```bash
# 从内容导入 Mermaid
feishu-cli board import <whiteboard_id> \
--source-type content \
-c "graph TD; A-->B-->C" \
--syntax mermaid
# 从文件导入 PlantUML
feishu-cli board import <whiteboard_id> diagram.puml --syntax plantuml
# 指定图表类型(通常 auto 即可)
feishu-cli board import <whiteboard_id> diagram.mmd --syntax mermaid --diagram-type 6
```
### 支持的 Mermaid 类型(8 种,全部验证通过)
| 类型 | 声明 | diagram-type |
|------|------|-------------|
| 流程图 | `flowchart TD` | 6 |
| 时序图 | `sequenceDiagram` | 2 |
| 类图 | `classDiagram` | 4 |
| 状态图 | `stateDiagram-v2` | 0 (auto) |
| ER 图 | `erDiagram` | 5 |
| 甘特图 | `gantt` | 0 (auto) |
| 饼图 | `pie` | 0 (auto) |
| 思维导图 | `mindmap` | 1 |
### Mermaid 限制
- 禁止花括号 `{text}`(被识别为菱形节点)
- 禁止 `par...and...end`(飞书不支持)
- 参与者建议 ≤8(过多会渲染失败)
- 复杂图表失败时会降级为代码块
## 其他画板命令
```bash
# 下载画板为 PNG 图片
feishu-cli board image <whiteboard_id> output.png
# 获取画板所有节点(JSON)
feishu-cli board nodes <whiteboard_id>
# 在文档中添加空画板
feishu-cli doc add-board <document_id> -o json
```
## 复制/修改画板(Redraw 模式)
画板 API 不支持修改或删除已有节点,修改需要重建:
1. `board nodes` 导出原始节点
2. 清洗数据 — 移除只读字段(`id`、`locked`、`children`、`text_color_type`、`fill_color_type`、`border_color_type`、`start_object`/`end_object`)
3. 分离形状和连接线
4. 新画板中先创建形状 → 映射旧 ID 到新 ID → 再创建连接线
详细的清洗字段列表和 Redraw 流程见 `references/node-api.md`。
## 权限要求
| 权限 | 说明 |
|------|------|
| `board:whiteboard` | 画板读写 |
| `docx:document` | 文档中添加画板 |
## 错误排障
### 错误码速查
| 错误码 | 含义 | 常见原因 |
|--------|------|---------|
| 2890001 | invalid format | JSON 格式错误 |
| 2890002 | invalid arg | 包含未公开字段(如 `sticky_note` 类型)或格式不对 |
| 2890003 | record missing | whiteboard_id 不存在 |
| 2890006 | rate limited | 超过 50 req/s |
### 排障指引
**2890002 invalid arg(最常见)**:
JSON 中包含了 API 不支持的字段。只使用以下安全字段:
- `composite_shape` 节点:`type`, `x`, `y`, `width`, `height`, `composite_shape`, `text`, `style`, `z_index`
- `connector` 节点:`type`, `width`, `height`, `z_index`, `connector`, `style`
- `text` 对象:`text`, `font_size`, `font_weight`, `horizontal_align`, `vertical_align`
- `style` 对象:`fill_color`, `fill_opacity`, `border_style`, `border_color`, `border_width`, `border_opacity`
排查步骤:逐步删减 JSON 字段,定位导致错误的多余字段。常见陷阱包括 `id`、`locked`、`children`、`text_color_type` 等只读字段。
**画板创建失败**:
- 检查应用是否已开通 `board:whiteboard` 权限
- 确认 `whiteboard_id` 来自 `doc add-board` 的返回值,而非 `document_id`
- 运行 `feishu-cli auth status` 确认 Token 有效
**Mermaid 导入降级为代码块**:
- 飞书服务端解析失败时会自动降级,属于预期行为
- 使用 `--verbose` 查看具体的服务端错误信息
- 常见原因:花括号 `{text}`、`par...and...end` 语法、参与者过多(>8)
- 解决方案:简化图表语法,或拆分为多个小图表
## 完整示例:绘制简单架构图
```bash
# 1. 创建文档和画板
DOC_ID=$(feishu-cli doc create --title "架构图" -o json | python3 -c "import sys,json;print(json.load(sys.stdin)['document_id'])")
BOARD_ID=$(feishu-cli doc add-board $DOC_ID -o json | python3 -c "import sys,json;print(json.load(sys.stdin)['whiteboard_id'])")
# 2. 创建形状节点
cat > /tmp/shapes.json << 'EOF'
[
{"type":"composite_shape","x":50,"y":50,"width":150,"height":40,
"composite_shape":{"type":"round_rect"},
"text":{"text":"客户端","font_size":14,"font_weight":"regular","horizontal_align":"center","vertical_align":"mid"},
"style":{"fill_color":"#f5f5f5","fill_opacity":100,"border_style":"solid","border_color":"#616161","border_width":"narrow"},
"z_index":10},
{"type":"composite_shape","x":300,"y":50,"width":150,"height":40,
"composite_shape":{"type":"round_rect"},
"text":{"text":"API 网关","font_size":14,"font_weight":"bold","horizontal_align":"center","vertical_align":"mid"},
"style":{"fill_color":"#3399ff","fill_opacity":100,"border_style":"none"},
"z_index":10},
{"type":"composite_shape","x":550,"y":50,"width":150,"height":40,
"composite_shape":{"type":"round_rect"},
"text":{"text":"数据库","font_size":14,"font_weight":"regular","horizontal_align":"center","vertical_align":"mid"},
"style":{"fill_color":"#509863","fill_opacity":100,"border_style":"none"},
"z_index":10}
]
EOF
feishu-cli board create-notes $BOARD_ID /tmp/shapes.json -o json
# → 返回 node_ids: ["o1:1", "o1:2", "o1:3"]
# 3. 创建连接线(引用上面返回的 ID)
cat > /tmp/connectors.json << 'EOF'
[
{"type":"connector","width":1,"height":1,"z_index":50,
"connector":{"shape":"polyline",
"start":{"arrow_style":"none","attached_object":{"id":"o1:1","position":{"x":1,"y":0.5},"snap_to":"right"}},
"end":{"arrow_style":"triangle_arrow","attached_object":{"id":"o1:2","position":{"x":0,"y":0.5},"snap_to":"left"}}},
"style":{"border_color":"#646a73","border_opacity":100,"border_style":"solid","border_width":"narrow"}},
{"type":"connector","width":1,"height":1,"z_index":50,
"connector":{"shape":"polyline",
"start":{"arrow_style":"none","attached_object":{"id":"o1:2","position":{"x":1,"y":0.5},"snap_to":"right"}},
"end":{"arrow_style":"triangle_arrow","attached_object":{"id":"o1:3","position":{"x":0,"y":0.5},"snap_to":"left"}}},
"style":{"border_color":"#646a73","border_opacity":100,"border_style":"solid","border_width":"narrow"}}
]
EOF
feishu-cli board create-notes $BOARD_ID /tmp/connectors.json -o json
# 4. 截图验证
feishu-cli board image $BOARD_ID /tmp/architecture.png
```
## 详细 API 参考
节点类型完整字段、connector 高级参数、Redraw 清洗字段等详细信息见 `references/node-api.md`。
FILE:references/node-api.md
# 画板节点 API 详细参考
通过 `board create-notes` 命令或直接调用节点 API,在飞书画板上批量创建形状、文本、连接线等元素。
## API 概览
| 操作 | 端点 | 说明 |
|------|------|------|
| 创建节点 | POST `/open-apis/board/v1/whiteboards/{id}/nodes` | 批量创建,上限 3000 |
| 获取节点 | GET `/open-apis/board/v1/whiteboards/{id}/nodes` | 获取全部节点 |
| 修改/删除 | — | **不支持**,需 redraw(重建画板) |
- 频率限制:50 req/s
- 请求体格式:`{"nodes": [...]}`
## 节点类型
### composite_shape(形状)
最常用的节点类型,支持矩形、圆角矩形等。
**最小格式**(推荐,多余字段可能导致 2890002 错误):
```json
{
"type": "composite_shape",
"x": 100, "y": 100, "width": 200, "height": 50,
"composite_shape": {"type": "round_rect"},
"text": {
"text": "节点文本",
"font_size": 14,
"font_weight": "regular",
"horizontal_align": "center",
"vertical_align": "mid"
},
"style": {
"fill_color": "#8569cb",
"fill_opacity": 100,
"border_style": "none"
},
"z_index": 10
}
```
**text 字段说明**:
| 字段 | 值 | 说明 |
|------|------|------|
| `font_size` | 12, 14, 16... | 字号 |
| `font_weight` | `regular`, `bold` | 字重 |
| `horizontal_align` | `left`, `center`, `right` | 水平对齐 |
| `vertical_align` | `top`, `mid`, `bottom` | 垂直对齐 |
**style 字段说明**:
| 字段 | 值 | 说明 |
|------|------|------|
| `fill_color` | `#rrggbb` | 填充颜色 |
| `fill_opacity` | 0-100 | 填充透明度 |
| `border_style` | `none`, `solid`, `dash`, `dot` | 边框样式 |
| `border_color` | `#rrggbb` | 边框颜色 |
| `border_width` | `narrow`, `medium`, `bold` | 边框宽度 |
| `border_opacity` | 0-100 | 边框透明度 |
### connector(连接线)
连接两个已创建的节点。**必须在形状节点创建后再创建连接线**(需要引用节点 ID)。
```json
{
"type": "connector",
"width": 1, "height": 1,
"z_index": 50,
"connector": {
"shape": "polyline",
"start": {
"arrow_style": "none",
"attached_object": {
"id": "<source_node_id>",
"position": {"x": 1, "y": 0.5},
"snap_to": "right"
}
},
"end": {
"arrow_style": "triangle_arrow",
"attached_object": {
"id": "<target_node_id>",
"position": {"x": 0, "y": 0.5},
"snap_to": "left"
}
}
},
"style": {
"border_color": "#646a73",
"border_opacity": 100,
"border_style": "solid",
"border_width": "narrow"
}
}
```
**connector 参数**:
| 字段 | 值 | 说明 |
|------|------|------|
| `shape` | `straight`, `polyline`, `curve`, `right_angled_polyline` | 连线形状 |
| `arrow_style` | `none`, `triangle_arrow` | 箭头样式 |
| `position` | `{"x": 0-1, "y": 0-1}` | 连接点位置(归一化坐标) |
| `snap_to` | `left`, `right`, `top`, `bottom` | 吸附方向 |
**注意**:GET 返回的 `start_object`/`end_object` 是只读字段,POST 时**不要**发送,使用 `start`/`end` 代替。
## z_index 与 fill_opacity(渲染层级)
正确的层级和透明度设置对画板渲染至关重要:
**关键规则**:背景色块 `fill_opacity` 必须 ≤60%,否则会完全遮挡上层元素。
| z_index 范围 | 用途 | fill_opacity |
|-------------|------|-------------|
| 0-1 | 外层容器、背景色带 | ≤50% |
| 2-3 | 次级色带、表头区域 | ≤60% |
| 4-8 | 列容器(dash border) | — |
| 9-16 | 文本标签(表头、图例) | — |
| 17+ | 内容卡片/条目 | 100%(实心) |
| 50 | 连接线 | border_opacity=100 |
## 典型工作流
### 1. 创建文档 + 画板 + 节点
```bash
# 步骤 1: 创建文档
feishu-cli doc create --title "架构图" -o json
# 返回 document_id
# 步骤 2: 添加画板
feishu-cli doc add-board <document_id> -o json
# 返回 whiteboard_id
# 步骤 3: 创建节点
feishu-cli board create-notes <whiteboard_id> nodes.json -o json
# 返回 node_ids
# 步骤 4: 截图验证
feishu-cli board image <whiteboard_id> output.png
```
### 2. 复制/修改画板(Redraw 模式)
画板 API 不支持 PATCH/DELETE,修改已有画板需要 redraw:
```bash
# 步骤 1: 导出原始节点
feishu-cli board nodes <original_whiteboard_id> > original.json
# 步骤 2: 清洗节点数据(移除只读字段)
# 需移除: id, locked, children, text.text_color_type,
# style.fill_color_type, style.border_color_type
# 步骤 3: 分离形状和连接线
# 先创建形状 → 获取新 ID → 映射旧 ID → 再创建连接线
# 步骤 4: 创建新画板并写入
feishu-cli doc create --title "修改版" -o json
feishu-cli doc add-board <new_doc_id> -o json
feishu-cli board create-notes <new_whiteboard_id> cleaned_shapes.json -o json
feishu-cli board create-notes <new_whiteboard_id> remapped_connectors.json -o json
```
### 3. 需要清洗的字段(GET → POST)
从 `board nodes` 获取的数据不能直接用于 `create-notes`,需要移除以下字段:
| 层级 | 需移除的字段 | 原因 |
|------|------------|------|
| 顶层 | `id`, `locked`, `children` | 只读/系统生成 |
| `text.*` | `text_color_type` | 未公开的内部字段 |
| `style.*` | `fill_color_type`, `border_color_type` | 未公开的内部字段 |
| `connector.*` | `start_object`, `end_object` | 只读,改用 `start`/`end` |
## 错误码
| 错误码 | 含义 | 常见原因 |
|--------|------|---------|
| 2890001 | invalid format | JSON 格式错误 |
| 2890002 | invalid arg | 包含未公开字段、连接线格式错误 |
| 2890003 | record missing | whiteboard_id 不存在 |
| 2890006 | rate limited | 超过 50 req/s 频率限制 |
## 常用配色参考
| 用途 | 颜色 | 示例 |
|------|------|------|
| 紫色强调 | `#8569cb` | 分类标题 |
| 浅紫背景 | `#eae2fe` | 次级区域 |
| 绿色标注 | `#509863` | 流程节点 |
| 绿色域名 | `#d5e8d4` | 领域标签 |
| 浅蓝容器 | `#f0f4fc` | 嵌套分组 |
| 灰色背景 | `#f5f5f5` | 子条目 |
| 灰色边框 | `#bbbfc4` | 虚线容器 |
| 连接线灰 | `#646a73` | 连接线 |
| 蓝色成熟 | `#3399ff` | 状态-成熟 |
| 浅蓝进展 | `#cce5ff` | 状态-进展中 |
| 橙色成熟 | `#ffc285` | 二级状态-成熟 |
| 浅橙进展 | `#fff0e3` | 二级状态-进展中 |
飞书 OAuth 认证和 User Access Token 管理。两步式非交互登录(AI Agent 专用)、 Token 状态检查、scope 配置、自动刷新机制、搜索功能的 Token 依赖关系。 当用户请求"登录飞书"、"获取 Token"、"OAuth 授权"、"auth login"、"认证"、 "搜...
---
name: feishu-cli-auth
description: >-
飞书 OAuth 认证和 User Access Token 管理。两步式非交互登录(AI Agent 专用)、
Token 状态检查、scope 配置、自动刷新机制、搜索功能的 Token 依赖关系。
当用户请求"登录飞书"、"获取 Token"、"OAuth 授权"、"auth login"、"认证"、
"搜索需要什么权限"、"Token 过期了"、"刷新 Token"时使用。
当遇到权限错误(如 99991679 Unauthorized)、Token 过期、state 不匹配等问题时也应使用此技能。
也适用于:搜索命令报权限错误、Token 相关的排错、需要判断当前授权状态的场景。
当其他飞书技能(toolkit/msg/read 等)遇到 User Access Token 相关问题时,也应参考此技能。
user-invocable: true
allowed-tools: Bash, Read
---
# 飞书 OAuth 认证与 Token 管理
feishu-cli 通过 OAuth 2.0 Authorization Code Flow 获取 User Access Token,用于搜索等需要用户身份的 API。
## 核心概念
**Token 存储位置**:所有 OAuth Token 保存在 `~/.feishu-cli/token.json`,包括 Access Token、Refresh Token、过期时间和授权 scope。登录、刷新、退出等操作都围绕此文件进行。
**两种身份**:
- **App Access Token**(应用身份):通过 app_id/app_secret 自动获取,大多数文档操作使用此身份
- **User Access Token**(用户身份):需 OAuth 授权,搜索 API **必须**使用此身份
**Token 生命周期**:
- Access Token:**2 小时**有效
- Refresh Token:**30 天**有效(需 `offline_access` scope)
- 过期后自动用 Refresh Token 刷新,用户无感
---
## 两步式非交互登录(AI Agent 推荐)
AI Agent 的 Bash tool 无法进行交互式 stdin 输入,因此 `--manual` 模式不可用。使用 `--print-url` + `auth callback` 两步式流程:
### 步骤 1:生成授权 URL
**始终使用最大 scope 范围授权**,一次性覆盖 feishu-cli 所有用户身份功能,避免后续因 scope 不足导致 99991679 错误:
```bash
feishu-cli auth login --print-url --scopes "offline_access search:docs:read search:message drive:drive.search:readonly wiki:wiki:readonly calendar:calendar:read calendar:calendar.event:read calendar:calendar.event:create calendar:calendar.event:update calendar:calendar.event:reply calendar:calendar.free_busy:read task:task:read task:task:write task:tasklist:read task:tasklist:write im:message:readonly im:message.group_msg:get_as_user im:chat:read im:chat:readonly im:chat.members:read contact:user.base:readonly drive:drive.metadata:readonly"
```
输出 JSON(stdout):
```json
{
"auth_url": "https://accounts.feishu.cn/open-apis/authen/v1/authorize?...",
"state": "随机64字符十六进制字符串",
"redirect_uri": "http://127.0.0.1:9768/callback"
}
```
**立即返回,不阻塞,不启动 HTTP 服务器。**
将 `auth_url` 展示给用户,请用户在浏览器中打开并完成授权。授权后浏览器会跳转到一个无法访问的页面(`127.0.0.1:9768/callback?code=xxx&state=yyy`),这是正常的——让用户复制地址栏中的完整 URL。
### 步骤 2:用回调 URL 换 Token
```bash
feishu-cli auth callback "<回调URL>" --state "<步骤1输出的state>"
```
输出 JSON(stdout)+ 人类可读信息(stderr):
```json
{
"status": "success",
"expires_at": "2026-03-09T04:31:11+08:00",
"scope": "auth:user.id:read search:docs:read search:message offline_access"
}
```
Token 自动保存到 `~/.feishu-cli/token.json`。
### auth callback 常见错误
| 错误 | 原因 | 解决 |
|------|------|------|
| `code has expired` | 授权 code 有效期约 5 分钟,用户复制回调 URL 太慢 | 重新执行步骤 1 获取新的授权 URL,提醒用户尽快完成 |
| `state 不匹配` | `--state` 参数与回调 URL 中的 state 不一致,或混用了不同次 `--print-url` 的结果 | 确保 `--state` 使用的是同一次 `--print-url` 输出的 state 值 |
| 网络超时 / 连接失败 | 无法访问飞书 OAuth 服务器(网络不通或代理问题) | 检查网络连通性,确认能访问 `open.feishu.cn`;如有代理需配置 `HTTPS_PROXY` |
### 完整示例
```bash
# 步骤 1(使用最大 scope 范围)
feishu-cli auth login --print-url --scopes "offline_access search:docs:read search:message drive:drive.search:readonly wiki:wiki:readonly calendar:calendar:read calendar:calendar.event:read calendar:calendar.event:create calendar:calendar.event:update calendar:calendar.event:reply calendar:calendar.free_busy:read task:task:read task:task:write task:tasklist:read task:tasklist:write im:message:readonly im:message.group_msg:get_as_user im:chat:read im:chat:readonly im:chat.members:read contact:user.base:readonly drive:drive.metadata:readonly"
# → 展示 auth_url 给用户,用户浏览器授权后复制回调 URL
# 步骤 2(用步骤 1 的 state 和用户提供的回调 URL)
feishu-cli auth callback "http://127.0.0.1:9768/callback?code=xxx&state=yyy" --state "yyy"
```
---
## Scope 配置
scope 决定了 Token 能访问哪些 API。登录时通过 `--scopes` 指定(空格分隔)。scope 名称 = 飞书开放平台开发者后台的权限名称,最多 50 个,多次授权**累加**生效。
### 默认策略:始终使用最大 scope
**每次登录都使用以下完整 scope 列表**,一次性覆盖 feishu-cli 全部用户身份功能。避免因 scope 不足导致部分命令报 99991679 错误:
```
offline_access search:docs:read search:message drive:drive.search:readonly wiki:wiki:readonly calendar:calendar:read calendar:calendar.event:read calendar:calendar.event:create calendar:calendar.event:update calendar:calendar.event:reply calendar:calendar.free_busy:read task:task:read task:task:write task:tasklist:read task:tasklist:write im:message:readonly im:message.group_msg:get_as_user im:chat:read im:chat:readonly im:chat.members:read contact:user.base:readonly drive:drive.metadata:readonly
```
### Token 使用策略
feishu-cli 命令按 Token 要求分为三类:
**必须 User Token**:搜索命令(`search docs/messages/apps`)和消息/群聊互动命令(`msg get/list/history/pins/pin/unpin/reaction/delete`、`msg search-chats`、`chat get/update/delete`、`chat member list/add/remove`)通过 `resolveRequiredUserToken` 强制使用 User Token,自动从 token.json 加载。
**可选 User Token**:wiki、calendar、task、doc export 等命令通过 `resolveOptionalUserToken` **默认使用 App Token(租户身份)**,不会自动从 token.json 加载。仅在通过 `--user-access-token` 参数或 `FEISHU_USER_ACCESS_TOKEN` 环境变量显式指定时才使用 User Token。
**仅 App Token**:文档创建/导入、消息发送/回复/转发、权限管理等命令仅使用 App Token,不支持 User Token。
### Scope 完整说明
| scope | 作用 | 对应命令 |
|-------|------|---------|
| `offline_access` | 获取 Refresh Token(30 天有效) | **必须包含**,否则 2 小时后需重新登录 |
| `search:docs:read` | 搜索云文档 | `search docs` |
| `search:message` | 搜索消息 | `search messages` |
| `drive:drive.search:readonly` | 搜索云空间文件 | `search docs`(补充权限) |
| `wiki:wiki:readonly` | 知识库读取(用户身份) | `wiki get/export/nodes` |
| `calendar:calendar:read` | 日历读取 | `calendar list/get/primary` |
| `calendar:calendar.event:read` | 日历事件读取 | `calendar event-search` |
| `calendar:calendar.event:create` | 创建日历事件 | `calendar create-event` |
| `calendar:calendar.event:update` | 更新日历事件 | `calendar event-reply` |
| `calendar:calendar.event:reply` | 回复日历事件 | `calendar event-reply` |
| `calendar:calendar.free_busy:read` | 忙闲查询 | `calendar freebusy` |
| `task:task:read` | 任务读取 | `task list/get` |
| `task:task:write` | 任务写入 | `task create/complete` |
| `task:tasklist:read` | 任务列表读取 | `tasklist list/get` |
| `task:tasklist:write` | 任务列表写入 | `tasklist create/delete` |
| `im:message:readonly` | 消息历史读取 | `msg history/get` |
| `im:message.group_msg:get_as_user` | 用户身份读取群消息 | `msg list/history`(User Token 读群消息必需) |
| `im:chat:read` | 群聊搜索(User Token) | `msg search-chats` |
| `im:chat:readonly` | 群聊信息只读(User Token) | `chat get`、`chat member list` |
| `im:chat.members:read` | 群成员列表读取(User Token) | `chat member list` |
| `im:message.pins` | 消息置顶管理(User Token) | `msg pin/unpin/pins` |
| `im:message.reactions` | 消息 Reaction 管理(User Token) | `msg reaction add/remove/list` |
| `contact:user.base:readonly` | 用户信息读取 | `user info/search` |
| `drive:drive.metadata:readonly` | 文件元数据读取 | `file list/meta` |
| `auth:user.id:read` | 用户身份信息 | 通常自动包含 |
### 前提条件
scope 中的权限必须先在飞书开放平台 → 应用详情 → 权限管理中启用。未启用的权限在授权时会被忽略或报错 20027。
### 常见错误
| 错误 | 原因 | 解决 |
|------|------|------|
| `error=99991679, Unauthorized` | Token 的 scope 不包含目标 API 权限 | 重新登录,使用最大 scope |
| Refresh Token 为空 | 缺少 `offline_access` scope | 重新登录,使用最大 scope |
| `error=20027` | 开发者后台未启用该权限 | 在飞书开放平台启用对应权限后重新授权 |
---
## Token 状态检查
```bash
# 人类可读格式
feishu-cli auth status
# JSON 格式(AI Agent 推荐)
feishu-cli auth status -o json
```
**当已登录且 Token 有效时:**
```json
{
"logged_in": true,
"access_token_valid": true,
"access_token_expires_at": "2026-03-09T04:32:19+08:00",
"refresh_token_valid": true,
"refresh_token_expires_at": "2026-03-16T02:32:19+08:00",
"scope": "auth:user.id:read search:docs:read search:message offline_access"
}
```
**当未登录时:**
```json
{"logged_in": false}
```
### 状态判断逻辑
```
logged_in=false → 从未登录,需要 auth login
access_token_valid=true → 正常可用
access_token_valid=false + refresh_token_valid=true → 下次调用时自动刷新,无需操作
access_token_valid=false + refresh_token_valid=false → 需要重新 auth login
scope 中无目标权限 → 需要重新登录并补充 scope
```
---
## Token 自动刷新机制
搜索、消息互动、群聊管理等**必须** User Access Token 的命令(`resolveRequiredUserToken`)通过 `ResolveUserAccessToken()` 按以下优先级链查找。其他可选命令(`resolveOptionalUserToken`)仅检查第 1、2 项,默认使用 App Token:
1. `--user-access-token` 命令行参数
2. `FEISHU_USER_ACCESS_TOKEN` 环境变量
3. `~/.feishu-cli/token.json`:
- access_token 有效 → 直接使用
- access_token 过期 + refresh_token 有效 → **自动刷新并保存新 Token**
- 都过期 → 报错"已过期,请重新登录"
4. `config.yaml` 中的 `user_access_token` 静态配置
5. 全部为空 → 报错"缺少 User Access Token",列出 4 种获取方式
**刷新过程对用户透明**:stderr 输出 `[自动刷新] 刷新成功...`,命令正常执行。
---
## User Access Token 的使用场景
### 必需 User Access Token 的命令
| 命令类别 | 需要的 scope |
|---------|-------------|
| `search docs "关键词"` | `search:docs:read` |
| `search messages "关键词"` | `search:message` |
| `search apps "关键词"` | (需确认应用是否已开通搜索应用权限) |
| `msg get` | `im:message:readonly` |
| `msg list/history` | `im:message:readonly`、`im:message.group_msg:get_as_user` |
| `msg pin/unpin/pins` | `im:message.pins` |
| `msg reaction add/remove/list` | `im:message.reactions` |
| `msg delete` | `im:message` |
| `msg search-chats` | `im:chat:read` |
| `chat get` | `im:chat:readonly` |
| `chat update/delete` | `im:chat` |
| `chat member list/add/remove` | `im:chat:readonly`、`im:chat.members:read`、`im:chat.members` |
### 可选 User Access Token 的命令
以下命令默认使用 App Token(租户身份),仅在通过 `--user-access-token` 参数或 `FEISHU_USER_ACCESS_TOKEN` 环境变量显式指定时才使用 User Token:
| 命令类别 | 需要的 scope |
|---------|-------------|
| `wiki get/export/nodes` | `wiki:wiki:readonly` |
| `calendar list/get/event-search/freebusy` | `calendar:calendar:read` 等 |
| `task create/complete/list` | `task:task:read task:task:write` |
| `tasklist create/list/delete` | `task:tasklist:read task:tasklist:write` |
| `user info/search` | `contact:user.base:readonly` |
### 登录前的检查流程
```bash
# 1. 检查是否已登录且 Token 有效
feishu-cli auth status -o json
# 2. 如果未登录或已过期,执行两步式登录(使用最大 scope)
feishu-cli auth login --print-url --scopes "offline_access search:docs:read search:message drive:drive.search:readonly wiki:wiki:readonly calendar:calendar:read calendar:calendar.event:read calendar:calendar.event:create calendar:calendar.event:update calendar:calendar.event:reply calendar:calendar.free_busy:read task:task:read task:task:write task:tasklist:read task:tasklist:write im:message:readonly im:message.group_msg:get_as_user im:chat:read im:chat:readonly im:chat.members:read contact:user.base:readonly drive:drive.metadata:readonly"
# ... 用户授权 ...
feishu-cli auth callback "<回调URL>" --state "<state>"
# 3. 登录后搜索命令自动从 token.json 读取 Token
feishu-cli search docs "产品需求"
# 其他命令默认使用 App Token,需要时可显式传 --user-access-token
feishu-cli wiki export <node_token> -o doc.md
feishu-cli task create --summary "待办事项"
```
---
## 其他登录模式
除 AI Agent 的两步式外,还有三种人类用户直接使用的模式:
```bash
# 本地桌面环境(默认):自动打开浏览器 + 本地 HTTP 回调
feishu-cli auth login --scopes "offline_access search:docs:read search:message drive:drive.search:readonly wiki:wiki:readonly calendar:calendar:read calendar:calendar.event:read calendar:calendar.event:create calendar:calendar.event:update calendar:calendar.event:reply calendar:calendar.free_busy:read task:task:read task:task:write task:tasklist:read task:tasklist:write im:message:readonly im:message.group_msg:get_as_user im:chat:read im:chat:readonly im:chat.members:read contact:user.base:readonly drive:drive.metadata:readonly"
# 远程 SSH 环境:打印 URL,用户手动粘贴回调 URL(交互式 stdin)
feishu-cli auth login --manual --scopes "offline_access search:docs:read search:message drive:drive.search:readonly wiki:wiki:readonly calendar:calendar:read calendar:calendar.event:read calendar:calendar.event:create calendar:calendar.event:update calendar:calendar.event:reply calendar:calendar.free_busy:read task:task:read task:task:write task:tasklist:read task:tasklist:write im:message:readonly im:message.group_msg:get_as_user im:chat:read im:chat:readonly im:chat.members:read contact:user.base:readonly drive:drive.metadata:readonly"
# Device Flow:无需在飞书开放平台配置重定向 URL 白名单
feishu-cli auth login --method device
# Device Flow + 指定 scope
feishu-cli auth login --method device --scopes "offline_access search:docs:read"
```
### Authorization Code Flow 前置条件
在飞书开放平台 → 应用详情 → 安全设置 → 重定向 URL 中添加:
```
http://127.0.0.1:9768/callback
```
如果使用自定义端口(`--port 8080`),需添加对应的重定向 URL。
Device Flow(`--method device`)无需此配置。
### Device Flow 说明
`--method device` 是 Authorization Code Flow 的平替方案,区别仅在于无需配置重定向 URL 白名单:
1. 执行 `feishu-cli auth login --method device`
2. 终端显示用户码和验证链接,在浏览器中打开链接并输入用户码完成授权
3. 命令自动轮询等待授权完成,成功后保存 Token
Device Flow 支持 `--scopes` 参数指定 OAuth scope(会自动追加 `offline_access`)。未指定时默认请求 `offline_access`。
---
## 退出登录
```bash
feishu-cli auth logout
```
删除 `~/.feishu-cli/token.json`,不影响 App Access Token(app_id/app_secret)。
---
## 排错指南
| 问题 | 诊断 | 解决 |
|------|------|------|
| "缺少 User Access Token" | 从未登录 | 执行 `auth login` |
| "User Access Token 已过期" | token.json 中 access + refresh 都过期 | 重新 `auth login` |
| 搜索报 99991679 权限错误 | scope 不足 | 重新登录,加上缺失的 scope |
| Refresh Token 为空 | 未包含 `offline_access` scope | 重新登录,加上 `offline_access` |
| "state 不匹配" | `auth callback` 的 `--state` 与 URL 中的 state 不一致 | 确保使用同一次 `--print-url` 的 state |
| "端口被占用" | 9768 端口已被其他进程使用 | 使用 `--port 其他端口`,并在飞书平台添加对应回调 URL |
| `auth login --manual` 在 AI Agent 中卡住 | stdin 阻塞 | 改用 `--print-url` + `auth callback` 两步式 |