@clawhub-keson1521-35d0c0c51b
知识管理全流程:文章链接/对话内容/文稿 → 结构化总结 → 飞书知识库归档。触发词:"整理到飞书"、"帮我处理文章"、"/feishu-knowledge-flow"。
--- name: feishu-knowledge-flow description: 知识管理全流程:文章链接/对话内容/文稿 → 结构化总结 → 飞书知识库归档。触发词:"整理到飞书"、"帮我处理文章"、"/feishu-knowledge-flow"。 argument-hint: <url> | --chat [主题] | --summarize-only <url> | --screen-only <url> | --setup allowed-tools: Bash(node *), Bash(lark-cli *), Bash(curl *), Bash(python3 *), Bash(rm *), Bash(mkdir *), Bash(cd *), WebFetch, Skill --- # 知识管理工作流 输入(文章链接/对话/文稿)→ 抓取 → 总结 → 分类 → 飞书归档。 > **执行原则:全程自动,不中断不确认。** 用户说"整理到飞书"后,从抓取到总结到分类到写入到图片上传到索引更新,一口气跑完,中间不问用户任何问题。所有 bash 命令(curl/lark-cli/mkdir/rm 等)直接执行。 --- ## 0. 首次使用引导(--setup 或自动检测) 每次执行工作流前,先做环境检查。**如果全部通过则静默继续,不打断用户**;只有检查失败时才进入引导流程。 ### 0.1 检查清单 ```bash # 1) 检查 lark-cli 是否安装 which lark-cli 2>/dev/null || echo "NOT_INSTALLED" # 2) 检查飞书认证状态 lark-cli auth status 2>/dev/null || echo "NOT_AUTHED" # 3) 检查本 skill 目录下是否有 .wiki-config 配置文件 cat "CLAUDE_SKILL_DIR/.wiki-config" 2>/dev/null || echo "NOT_CONFIGURED" ``` ### 0.2 引导流程(仅在检查失败时执行) **Step 1:安装 lark-cli**(若未安装) ``` ⚠️ 未检测到 lark-cli,这是连接飞书的必备工具。 安装方式(任选一种): npm install -g @anthropic/lark-cli # 或参考 https://github.com/nicepkg/lark-cli 安装完成后重新运行 /knowledge-workflow --setup ``` **Step 2:飞书认证**(若未认证) ``` ⚠️ 飞书尚未登录,需要先完成认证才能归档到知识库。 请运行: lark-cli config init # 首次配置飞书应用 lark-cli auth login # 登录认证 ``` 提示用户运行命令后,等待用户确认再继续。 **Step 3:知识库配置**(若无 .wiki-config) 当认证通过后,引导用户完成知识库配置: ``` ✅ 飞书已连接!现在配置你的知识库归档位置。 请提供以下信息: 1. 知识空间 ID(在飞书知识库 URL 中可以找到) 2. 索引文档 doc_id(可选,用于记录归档索引。没有的话我帮你新建一个) ``` 收到信息后,自动: 1. 验证知识空间可访问:`lark-cli wiki spaces get --params '{"space_id":"<SPACE_ID>"}'` 2. 若用户没有索引文档,自动创建一个: ```bash lark-cli wiki nodes create --params '{"space_id":"<SPACE_ID>"}' \ --data '{"node_type":"origin","obj_type":"docx","title":"【总】阅读文章索引"}' --as user ``` 3. 将配置写入 `CLAUDE_SKILL_DIR/.wiki-config`: ```bash cat > "CLAUDE_SKILL_DIR/.wiki-config" << 'WIKIEOF' # 知识库配置(首次 setup 时自动生成) WIKI_SPACE_ID=<用户提供的知识空间ID> INDEX_DOC_ID=<索引文档的doc_id> INDEX_NODE_TOKEN=<索引文档的node_token> WIKIEOF ``` 4. 初始化框架树配置 `CLAUDE_SKILL_DIR/.wiki-tree`(空模板): ```bash cat > "CLAUDE_SKILL_DIR/.wiki-tree" << 'TREEEOF' # 知识库框架树 # 格式:分类路径 | node_token | doc_id # 已有 doc 的节点直接用,新建节点后必须回填到此处。 # # 示例: # 大模型/Claude | L1n9xxxx | YoJHxxxx # 大模型/GPT | AZubxxxx | HoBixxxx # AI认知/AI方法论 | Mv8jxxxx | Hqfkxxxx TREEEOF ``` **Step 4:确认完成** ``` 🎉 配置完成!你的知识库已就绪: - 知识空间:<SPACE_ID> - 索引文档:<INDEX_DOC_ID> 现在你可以: - 发送文章链接,我会自动抓取、总结、归档到飞书 - 说"整理到飞书"归档当前对话 - /knowledge-workflow --setup 重新配置 ``` ### 0.3 加载配置 每次执行时从配置文件加载: ```bash # 加载知识库配置 source "CLAUDE_SKILL_DIR/.wiki-config" # 加载框架树(解析为查找表) WIKI_TREE="CLAUDE_SKILL_DIR/.wiki-tree" ``` --- ## 1. 模式路由 | 输入 | 模式 | 流程 | |---|---|---| | URL(无 flag) | 全流程 | 抓取 → 总结 → 归档 | | 用户粘贴的文稿/口播 | 全流程 | 跳过抓取 → 总结 → 归档 | | `--chat [主题]` | 对话归档 | 提取对话洞察 → 归档 | | `--summarize-only <url>` | 仅总结 | 抓取 → 总结(不归档) | | `--screen-only <url>` | 仅筛选 | 抓取 → 三维评分 | | `--setup` | 配置 | 重新运行首次引导 | | "整理到飞书" | 智能判断 | 根据上下文走全流程或对话归档 | --- ## 2. 内容抓取(两级回退,全自动) 依次尝试,成功即停。**禁止要求用户手动粘贴**,两级都失败则在最终报告中标记该条失败并跳过,不中断流程: 1. **Playwright**:`node "CLAUDE_SKILL_DIR/fetch-article.js" "<url>"`,返回 JSON,content > 200 字有效 2. **WebFetch**:prompt = "Extract full article: title, author, date, complete body text. Original language. Do not summarize." --- ## 3. 生成总结 ### 格式模板 ```markdown --- # {序号}. {标题} > 🔗 [原文链接]({url}) > ✍️ {作者} 📅 {日期} ## 速览 {2-3句核心主张,10秒判断是否深读} ## 详细解读 ### {块标题} {150-300字,按原文逻辑复述} ``` > 序号规则:同一文档内的文章按写入顺序递增编号(1、2、3...),新文章追加时查看文档已有最大序号,+1 继续。 对话归档时,元数据行改为 `> 💬 对话归档 📅 {日期}`,"详细解读"改为"核心洞察"。 ### 图片处理 抓取文章时,同时提取文章中的图片 URL(`data-src` 属性,域名 `mmbiz.qpic.cn`)。筛选有价值的图片上传飞书: **上传标准**:包含信息量大的图片才上传,包括: - 数据图表、信息图、流程图、框架图 - 关键截图(产品界面、对比图、证据截图) - 核心论点的可视化表达 **不上传**:纯装饰图、头像、二维码、广告图、表情包 **执行流程**: 1. 抓取时用正则提取所有 `data-src="https://mmbiz.qpic.cn/..."` 图片 URL 2. 判断哪些图片有信息价值(根据上下文位置和图片描述) 3. 下载到 `CLAUDE_SKILL_DIR/_imgs/` 临时目录 4. 文字内容写入飞书后,用 `+media-insert` 逐张上传并添加 caption 5. 上传完成后删除临时目录 ```bash # 下载图片 mkdir -p "CLAUDE_SKILL_DIR/_imgs" curl -s -o "CLAUDE_SKILL_DIR/_imgs/name.png" "<img_url>" -H "Referer: https://mp.weixin.qq.com/" # 上传到飞书文档(必须 cd 到图片目录用相对路径) cd "CLAUDE_SKILL_DIR/_imgs" && lark-cli docs +media-insert \ --doc "<doc_id>" --file "./name.png" --caption "图片说明" --align center --as user # 清理 rm -rf "CLAUDE_SKILL_DIR/_imgs" ``` ### 原则 - **用原作者的口吻和第一人称写**,不要用"作者认为"、"文章指出"等第三人称旁观视角。读起来像作者本人在讲给你听 - 忠于原文,不加外部知识 - 保留作者的语言风格、比喻、金句、口语化表达 - 作者/日期无法识别时省略该行 - 全流程模式不在对话中展示总结,直接归档 --- ## 4. 飞书归档 ### 4.1 加载配置 ```bash source "CLAUDE_SKILL_DIR/.wiki-config" # WIKI_SPACE_ID, INDEX_DOC_ID, INDEX_NODE_TOKEN 均从配置文件读取 ``` ### 4.2 分类 → 查 token → 写入 **第一步:判断分类** 根据内容核心主张,匹配框架树(`.wiki-tree`)中已有的分类路径。无法匹配时新建节点。 **第二步:获取 doc_id** - 框架树中已有 `doc_id` 的节点 → **直接用,不查 API** - 框架树中无记录的分类 → 用 `lark-cli wiki nodes create` 创建,创建后立即更新 `.wiki-tree` ```bash # 创建一级节点 lark-cli wiki nodes create --params "{\"space_id\":\"WIKI_SPACE_ID\"}" \ --data '{"node_type":"origin","obj_type":"docx","title":"<名称>"}' --as user # 创建二级节点(需指定 parent_node_token) lark-cli wiki nodes create --params "{\"space_id\":\"WIKI_SPACE_ID\"}" \ --data '{"node_type":"origin","obj_type":"docx","title":"<名称>","parent_node_token":"<父节点>"}' --as user ``` **第三步:写入内容** ```bash cd "CLAUDE_SKILL_DIR" && lark-cli docs +update \ --doc "<doc_id>" --mode append --as user --markdown @_temp.md rm -f "CLAUDE_SKILL_DIR/_temp.md" ``` **第四步:更新索引** 新日期/新文章插入到索引文档**最顶部**(新的在上),使用 `insert_before` 定位到第一个日期标题前。**日期和星期必须用 `date` 命令动态获取,禁止写死或凭记忆**: ```bash # 生成当日日期标题(中文星期) TODAY=$(date +"%-m月%-d日 周$(date +%u | tr '1234567' '一二三四五六日')") echo "## $TODAY" ``` ```markdown ## {date 动态生成,如 4月23日 周四} - [ ] [{标题}](https://www.feishu.cn/wiki/{node_token}) → {一级} / {二级} ``` ```bash # 插入到文档最前面(在第一个 ## 标题之前) cd "CLAUDE_SKILL_DIR" && lark-cli docs +update \ --doc "INDEX_DOC_ID" --mode insert_before \ --selection-by-title "## " --as user --markdown @_index.md rm -f "CLAUDE_SKILL_DIR/_index.md" ``` > 如果文档为空或找不到 `## ` 标题,回退用 `append` 模式。 > 同日追加文章时,用 `insert_after --selection-by-title "## {当日日期}"` 追加到该日期块下。 ### 4.3 输出确认 ``` ✅ 已归档 → {一级} / {二级} 🔗 https://www.feishu.cn/wiki/{node_token} 📋 索引已更新 ``` --- ## 5. 对话归档(--chat 或"整理到飞书") ### 触发条件 对话涉及以下话题时,可主动提示归档: - AI 技术原理、大模型、Agent、提示工程 - 认知升级、思维框架、方法论 - 行业趋势、产品洞察、商业认知 - 社会现象、文化分析 日常闲聊、代码调试、文件操作不触发。 ### 执行 1. 从对话中提取核心洞察,剥离噪声 2. 按第 3 节格式生成总结 3. 按第 4 节归档到飞书 --- ## 知识库框架树 框架树存储在 `CLAUDE_SKILL_DIR/.wiki-tree`,格式为: ``` # 分类路径 | node_token | doc_id 大模型/Claude | <node_token> | <doc_id> 大模型/GPT | <node_token> | <doc_id> AI认知/AI方法论 | <node_token> | <doc_id> 商业认知/AI产业 | <node_token> | <doc_id> 社会认知/人生哲学 | <node_token> | <doc_id> ``` > **已有 doc 的节点直接用,不查 API。新建节点后必须回填到 `.wiki-tree` 文件。** ### 分类速查(默认推荐分类,可自定义) | 内容关键词 | 分类路径 | |---|---| | Claude/GPT/Gemini/具体模型 | 大模型 / {模型名} | | Agent/多智能体/外化架构 | 大模型 / Agent架构 | | MoE/Transformer/模型结构 | 大模型 / 模型架构 | | AI使用心得/思维框架/方法论 | AI认知 / AI方法论 | | 训练数据/算力/投融资/产业链 | 商业认知 / AI产业 | | 产品/创业/商业模式 | 商业认知 / 产品/创业 | | 社会现象/两性/文化心理 | 社会认知 / 两性与社会心理 | | 人生哲学/认知框架/心态 | 社会认知 / 人生哲学 | | 工作方法/效率/职业发展 | 职场效能 / {子类} | | 以上都不匹配 | 新建最合适的一级+二级 | --- ## 错误处理 - 单篇失败不阻塞,跳过继续,最后汇总 - 权限不足 → 提示 `lark-cli auth login --domain all` - 超过 5 篇 → 建议分批 --- ## 附录:三维评分(--screen-only) | 维度 | 5分 | 1分 | |---|---|---| | 信息密度 | 大量一手数据/案例 | 几乎无实质内容 | | 原创性 | 全新框架/视角 | 纯搬运 | | 实操性 | 读完立刻可照做 | 纯抽象讨论 | 总分 ≥ 10 通过。 FILE:fetch-article.js const { chromium } = require('playwright'); const url = process.argv[2]; if (!url) { console.error('Usage: node fetch-article.js <url>'); process.exit(1); } (async () => { const browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/3.9.10.19 XWEB/11275', viewport: { width: 1280, height: 900 }, }); const page = await context.newPage(); try { await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 }); await page.waitForTimeout(2000); const data = await page.evaluate(() => { const title = document.querySelector('#activity-name')?.innerText?.trim() || document.querySelector('.rich_media_title')?.innerText?.trim() || document.title || ''; const author = document.querySelector('#js_name')?.innerText?.trim() || document.querySelector('.rich_media_meta_nickname')?.innerText?.trim() || ''; const date = document.querySelector('#publish_time')?.innerText?.trim() || document.querySelector('.rich_media_meta_date')?.innerText?.trim() || ''; const content = document.querySelector('#js_content')?.innerText?.trim() || document.querySelector('.rich_media_content')?.innerText?.trim() || document.body.innerText?.trim() || ''; return { title, author, date, content }; }); const output = JSON.stringify(data, null, 2); process.stdout.write(output); } catch (e) { console.error('Error:', e.message); process.exit(1); } finally { await browser.close(); } })(); FILE:package.json { "name": "feishu-knowledge-flow", "version": "1.0.0", "description": "Playwright-based article fetcher for knowledge workflow skill", "main": "fetch-article.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "type": "commonjs", "dependencies": { "playwright": "^1.59.1" } } FILE:README.md # Knowledge Workflow — 知识管理全流程 **一条链接丢进去,结构化总结自动归档到飞书知识库。** 读文章 → 忘了。收藏文章 → 吃灰。这个 Skill 解决的就是"看过 ≠ 学到"的问题。 --- ## 它能做什么 把你看到的好文章、有价值的对话,**一键变成结构化知识**,自动归档到飞书知识库。 ``` 你:https://mp.weixin.qq.com/s/xxxxx 整理到飞书 Claude:(全自动)抓取全文 → AI 总结 → 分类 → 写入飞书 → 更新索引 你:✅ 已归档 → 大模型 / Claude ``` **全程零操作**。不问你确认,不让你选分类,不需要你粘贴正文。丢个链接,去倒杯水,回来知识库里就多了一篇。 --- ## 核心能力 ### 📖 智能抓取 - **Playwright 浏览器引擎**抓取,连微信公众号都能搞定 - 两级回退机制(Playwright → WebFetch),成功率极高 - 自动提取标题、作者、日期、正文,零手动干预 ### 🧠 高质量总结 - **保留原作者口吻**,不是那种"作者认为…文章指出…"的八股文 - 速览(10 秒判断要不要深读)+ 详细解读(按原文逻辑复述) - 保留金句、比喻、数据,忠于原文不注水 ### 🖼️ 图片智能筛选 - 自动提取文章中的数据图表、流程图、框架图 - 过滤装饰图、头像、二维码、广告 - 上传到飞书文档,带图片说明 ### 📂 自动分类归档 - AI 判断内容主题,自动匹配知识库分类 - 分类不存在?自动新建节点 - 框架树自增长,用得越多分类越精准 ### 📋 索引自动更新 - 每篇文章自动登记到索引文档 - 按日期倒序排列,新的永远在最上面 - 带分类标签和直达链接,方便回顾 --- ## 五种使用模式 | 用法 | 说明 | |---|---| | `https://xxx.com/article` | 全流程:抓取 → 总结 → 归档 | | 粘贴文稿/口播内容 | 跳过抓取,直接总结归档 | | `--chat` 或 "整理到飞书" | 把当前对话的精华提炼归档 | | `--summarize-only <url>` | 只要总结,不归档 | | `--screen-only <url>` | 三维评分(信息密度/原创性/实操性),帮你判断值不值得读 | --- ## 快速开始 ### 前置条件 - [Claude Code](https://claude.ai/code) 已安装 - [lark-cli](https://github.com/nicepkg/lark-cli) 已安装(连接飞书的桥梁) - 一个飞书知识库(免费版就行) ### 安装 Skill 将本 Skill 文件夹放入项目的 `.claude/skills/` 目录下(建议命名为 `knowledge-workflow`),然后安装依赖: ```bash cd .claude/skills/feishu-knowledge-flow npm install ``` ### 首次配置 运行 `/feishu-knowledge-flow --setup` 或直接丢一个链接,Skill 会自动检测环境: 1. **飞书认证** — 引导你完成 `lark-cli config init` 和 `lark-cli auth login` 2. **知识库连接** — 输入你的知识空间 ID,自动验证并创建索引文档 3. **框架树初始化** — 生成空模板,后续使用中自动填充 配置只需一次,之后每次使用都是全自动。 ### 开始使用 ``` /feishu-knowledge-flow https://mp.weixin.qq.com/s/你的文章链接 # 或者更自然地: 整理到飞书 https://mp.weixin.qq.com/s/你的文章链接 ``` --- ## 效果展示 归档后的飞书文档长这样: ```markdown # 3. 为什么 Claude 的上下文理解比 GPT 更好 > 🔗 原文链接 > ✍️ 某作者 📅 2025-04-20 ## 速览 我一直在想为什么 Claude 处理长文档的能力明显优于竞品, 深入研究后发现关键在于三个架构决策... ## 详细解读 ### 注意力机制的差异 Anthropic 在注意力窗口的处理上采用了...(150-300字) ### 训练数据的策略 和 OpenAI 不同的是...(150-300字) ``` --- ## 自定义分类 框架树存储在 `.wiki-tree` 文件中,格式简单: ``` # 分类路径 | node_token | doc_id 大模型/Claude | xxx | xxx AI认知/AI方法论 | xxx | xxx ``` 你可以: - 手动编辑添加自己的分类 - 让 Skill 在使用中自动创建并回填 - 完全重新设计分类体系 默认推荐的分类覆盖:大模型、AI 认知、商业认知、社会认知、职场效能等方向,开箱即用。 --- ## 常见问题 **Q:支持哪些网站?** A:理论上支持所有公开网页。微信公众号、知乎、少数派等中文平台实测效果好。Playwright 引擎会模拟真实浏览器访问。 **Q:抓取失败怎么办?** A:两级回退(Playwright → WebFetch),大部分情况至少有一级能成功。实在不行你也可以直接粘贴文章内容,跳过抓取环节。 **Q:总结质量如何?** A:不是那种干巴巴的摘要。保留原作者口吻,像作者本人在跟你讲,保留金句和数据。你可以用 `--summarize-only` 先看效果。 **Q:能批量处理吗?** A:单次建议不超过 5 篇。多了建议分批,避免超时。 **Q:配置丢了怎么办?** A:重新运行 `--setup` 即可,30 秒搞定。 --- ## 技术栈 - **Playwright** — 无头浏览器抓取,搞定各种反爬 - **lark-cli** — 飞书 API 的 CLI 封装,文档读写、节点管理 - **Claude Code Skill** — 全流程编排,AI 总结 + 分类 --- ## 许可 MIT
完整复制飞书云文档至同一账号云空间,分块读取与写入,保持文字、表格、代码格式100%一致(不含图片)。
# feishu-doc-copy - 飞书文档完整复制技能
## 技能描述
**功能**:完整复制飞书云文档到同一账号的云空间,保持文字、表格、代码、格式 100% 一致(图片除外)。
**适用场景**:
- 有文档查看权限但无复制/下载权限
- 需要备份飞书文档
- 需要创建文档的完整副本
- 长文档(>10,000 字符)的完整复制
**前提条件**:
- 对源文档有读取权限(能打开查看)
- 有自己的云空间写入权限
- 已安装 OpenClaw 并配置飞书渠道
**限制**:
- 图片无法复制(飞书 API token 权限限制)
- 单次复制最大支持 100,000 字符
---
## 核心方法
**分块读取 + 分块写入 + 严格验证**
这是唯一可行的路径,因为:
1. 飞书文档 API 单次读取有长度限制(limit 参数)
2. 飞书文档编辑 API 单次写入有长度限制
3. 文档无复制权限时,无法使用客户端复制粘贴
4. 必须通过 API 分块搬运
---
## 完整实现流程
### 步骤 1:获取源文档信息
```bash
# 读取源文档前 50 字符,获取 total_length
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 50
```
**返回值示例**:
```json
{
"doc_id": "ABC123xyz",
"title": "文档标题",
"total_length": 43548,
"has_more": true,
"length": 50,
"markdown": "..."
}
```
**关键信息**:
- `total_length`:原文档总字符数(用于计算分块数量)
- `doc_id`:源文档 ID
---
### 步骤 2:计算分块策略
```bash
# 每块大小:9000 字符(推荐值,避免切断表格/代码块)
BLOCK_SIZE=9000
# 计算分块数量
BLOCK_COUNT = ceil(TOTAL_LENGTH / BLOCK_SIZE)
```
**示例**:
- 原文档 43,548 字符
- 每块 9,000 字符
- 分块数量:ceil(43548/9000) = 5 块
**分块偏移量**:
```
块 0: offset 0
块 1: offset 9000
块 2: offset 18000
块 3: offset 27000
块 4: offset 36000
```
---
### 步骤 3:创建临时目录
```bash
# 创建临时目录存储分块文件
TEMP_DIR="/tmp/feishu_copy_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$TEMP_DIR"
```
---
### 步骤 4:分块读取源文档
**循环读取每一块**:
```bash
OFFSET=0
BLOCK_NUM=0
while [ $OFFSET -lt $TOTAL_LENGTH ]; do
echo "读取块 $BLOCK_NUM(offset: $OFFSET)..."
# 读取当前块
openclaw feishu fetch-doc \
--doc_id "$SOURCE_DOC_ID" \
--limit $BLOCK_SIZE \
--offset $OFFSET \
> "$TEMP_DIR/chunk_BLOCK_NUM.json"
# 提取 markdown 内容
cat "$TEMP_DIR/chunk_BLOCK_NUM.json" | jq -r '.markdown' > "$TEMP_DIR/chunk_BLOCK_NUM.md"
# 检查是否还有更多
HAS_MORE=$(cat "$TEMP_DIR/chunk_BLOCK_NUM.json" | jq -r '.has_more')
if [ "$HAS_MORE" = "false" ]; then
echo "最后一块读取完成"
break
fi
OFFSET=$(( $OFFSET + $BLOCK_SIZE ))
BLOCK_NUM=$(( $BLOCK_NUM + 1 ))
# 避免 API 限流
sleep 0.5
done
```
**关键验证点**:
1. 每次读取后检查 `has_more` 标志
2. 最后一块 `has_more` 必须为 `false`
3. 每块文件必须成功保存
---
### 步骤 5:创建新文档
```bash
# 创建新文档(初始内容可以是任意占位符)
openclaw feishu create-doc \
--title "<新文档标题>" \
--markdown "# <新文档标题>\n\n正在搬运中..."
```
**返回值示例**:
```json
{
"doc_id": "XYZ789abc",
"doc_url": "https://www.feishu.cn/docx/XYZ789abc",
"message": "文档创建成功"
}
```
**记录新文档 ID**:`NEW_DOC_ID`
---
### 步骤 6:分块写入新文档
**写入规则**:
- 第 1 块(块 0):使用 `overwrite` 模式
- 后续块(块 1-N):使用 `append` 模式
```bash
TOTAL_BLOCKS=$BLOCK_NUM # 块编号从 0 开始,所以总块数 = 最大块编号 + 1
for i in $(seq 0 $BLOCK_NUM); do
CHUNK_FILE="$TEMP_DIR/chunk_i.md"
if [ $i -eq 0 ]; then
MODE="overwrite"
echo "写入块 $i($MODE 模式)..."
else
MODE="append"
echo "追加块 $i($MODE 模式)..."
fi
# 写入当前块
openclaw feishu update-doc \
--doc_id "$NEW_DOC_ID" \
--mode $MODE \
--markdown @"$CHUNK_FILE"
# 避免 API 限流
sleep 1
done
```
**关键验证点**:
1. 第 1 块必须用 `overwrite`
2. 后续块必须用 `append`
3. 每次写入后检查返回状态
4. 写入间隔至少 1 秒(避免限流)
---
### 步骤 7:验证结果
```bash
# 获取新文档信息
openclaw feishu fetch-doc \
--doc_id "$NEW_DOC_ID" \
--limit 50 \
> "$TEMP_DIR/new_doc_info.json"
# 提取新文档总长度
NEW_LENGTH=$(cat "$TEMP_DIR/new_doc_info.json" | jq -r '.total_length')
# 计算差异
DIFF=$(( $TOTAL_LENGTH - $NEW_LENGTH ))
PERCENT=$(echo "scale=2; $NEW_LENGTH * 100 / $TOTAL_LENGTH" | bc)
echo "原文档:$TOTAL_LENGTH 字符"
echo "新文档:$NEW_LENGTH 字符"
echo "差异:$DIFF 字符($PERCENT% 完整)"
# 验证通过条件:差异 < 2%
if [ $(echo "$PERCENT < 98" | bc) -eq 1 ]; then
echo "⚠️ 差异较大,请手动检查"
else
echo "✅ 验证通过"
fi
```
**预期结果**:
- 差异 < 2%:正常(主要是图片 token 占位符差异)
- 差异 > 5%:异常,需要检查哪块写入失败
---
### 步骤 8:清理临时文件
```bash
rm -rf "$TEMP_DIR"
```
---
## 完整自动化脚本
将以上流程整合为可执行脚本:
```bash
#!/bin/bash
# 飞书文档完整复制脚本
# 用法:./feishu-doc-copy.sh <源文档 ID> <新文档标题>
set -e # 遇到错误立即退出
SOURCE_DOC_ID="$1"
NEW_DOC_TITLE="$2"
BLOCK_SIZE=9000 # 每块字符数
if [ -z "$SOURCE_DOC_ID" ] || [ -z "$NEW_DOC_TITLE" ]; then
echo "用法:$0 <源文档 ID> <新文档标题>"
echo "示例:$0 ABC123xyz 文档标题 - 副本"
exit 1
fi
# 创建临时目录
TEMP_DIR="/tmp/feishu_copy_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$TEMP_DIR"
echo "📋 开始复制飞书文档..."
echo " 源文档 ID: $SOURCE_DOC_ID"
echo " 新文档标题:$NEW_DOC_TITLE"
echo " 临时目录:$TEMP_DIR"
echo ""
# 步骤 1: 获取源文档信息
echo "📊 步骤 1: 获取源文档信息..."
DOC_INFO=$(openclaw feishu fetch-doc --doc_id "$SOURCE_DOC_ID" --limit 50)
TOTAL_LENGTH=$(echo "$DOC_INFO" | jq -r '.total_length')
SOURCE_TITLE=$(echo "$DOC_INFO" | jq -r '.title')
echo " 源文档标题:$SOURCE_TITLE"
echo " 原文档总长度:$TOTAL_LENGTH 字符"
# 步骤 2: 计算分块数量
BLOCK_COUNT=$(( ($TOTAL_LENGTH + $BLOCK_SIZE - 1) / $BLOCK_SIZE ))
echo " 分块大小:$BLOCK_SIZE 字符/块"
echo " 分块数量:$BLOCK_COUNT 块"
echo ""
# 步骤 3: 分块读取
echo "📥 步骤 3: 分块读取源文档..."
OFFSET=0
BLOCK_NUM=0
while [ $OFFSET -lt $TOTAL_LENGTH ]; do
echo " 读取块 $BLOCK_NUM(offset: $OFFSET)..."
openclaw feishu fetch-doc \
--doc_id "$SOURCE_DOC_ID" \
--limit $BLOCK_SIZE \
--offset $OFFSET \
> "$TEMP_DIR/chunk_BLOCK_NUM.json"
# 提取 markdown 内容
cat "$TEMP_DIR/chunk_BLOCK_NUM.json" | jq -r '.markdown' > "$TEMP_DIR/chunk_BLOCK_NUM.md"
# 检查是否还有更多
HAS_MORE=$(cat "$TEMP_DIR/chunk_BLOCK_NUM.json" | jq -r '.has_more')
if [ "$HAS_MORE" = "false" ]; then
echo " ✅ 最后一块读取完成"
break
fi
OFFSET=$(( $OFFSET + $BLOCK_SIZE ))
BLOCK_NUM=$(( $BLOCK_NUM + 1 ))
sleep 0.5
done
TOTAL_BLOCKS=$BLOCK_NUM
echo " ✅ 共读取 $(( $TOTAL_BLOCKS + 1 )) 块"
echo ""
# 步骤 4: 创建新文档
echo "📝 步骤 4: 创建新文档..."
NEW_DOC=$(openclaw feishu create-doc \
--title "$NEW_DOC_TITLE" \
--markdown "# $NEW_DOC_TITLE\n\n正在搬运中...")
NEW_DOC_ID=$(echo "$NEW_DOC" | jq -r '.doc_id')
NEW_DOC_URL=$(echo "$NEW_DOC" | jq -r '.doc_url')
echo " 新文档 ID: $NEW_DOC_ID"
echo " 新文档链接:$NEW_DOC_URL"
echo ""
# 步骤 5: 分块写入
echo "📤 步骤 5: 分块写入新文档..."
for i in $(seq 0 $TOTAL_BLOCKS); do
CHUNK_FILE="$TEMP_DIR/chunk_i.md"
if [ $i -eq 0 ]; then
MODE="overwrite"
else
MODE="append"
fi
echo " 写入块 $i($MODE 模式)..."
openclaw feishu update-doc \
--doc_id "$NEW_DOC_ID" \
--mode $MODE \
--markdown @"$CHUNK_FILE"
sleep 1
done
echo " ✅ 所有块写入完成"
echo ""
# 步骤 6: 验证
echo "🔍 步骤 6: 验证结果..."
NEW_DOC_INFO=$(openclaw feishu fetch-doc --doc_id "$NEW_DOC_ID" --limit 50)
NEW_LENGTH=$(echo "$NEW_DOC_INFO" | jq -r '.total_length')
DIFF=$(( $TOTAL_LENGTH - $NEW_LENGTH ))
PERCENT=$(echo "scale=2; $NEW_LENGTH * 100 / $TOTAL_LENGTH" | bc)
echo " 原文档:$TOTAL_LENGTH 字符"
echo " 新文档:$NEW_LENGTH 字符"
echo " 差异:$DIFF 字符($PERCENT% 完整)"
if [ $(echo "$PERCENT >= 98" | bc) -eq 1 ]; then
echo " ✅ 验证通过(差异 < 2%,主要是图片 token)"
VERIFICATION_STATUS="✅ 通过"
else
echo " ⚠️ 差异较大,请手动检查"
VERIFICATION_STATUS="⚠️ 需检查"
fi
echo ""
# 步骤 7: 清理
echo "🧹 步骤 7: 清理临时文件..."
rm -rf "$TEMP_DIR"
echo " ✅ 临时文件已清理"
echo ""
# 输出结果
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🎉 复制完成!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 源文档:$SOURCE_TITLE"
echo " 新文档:$NEW_DOC_TITLE"
echo " 新文档链接:$NEW_DOC_URL"
echo " 完整性:$VERIFICATION_STATUS"
echo " 原文档:$TOTAL_LENGTH 字符"
echo " 新文档:$NEW_LENGTH 字符"
echo " 差异:$DIFF 字符"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
```
---
## 使用方法
### 方法 A:使用自动化脚本
```bash
# 1. 保存脚本
# 将上面的完整脚本保存为 ~/.openclaw/scripts/feishu-doc-copy.sh
# 2. 添加执行权限
chmod +x ~/.openclaw/scripts/feishu-doc-copy.sh
# 3. 执行
~/.openclaw/scripts/feishu-doc-copy.sh <源文档 ID> <新文档标题>
# 示例
~/.openclaw/scripts/feishu-doc-copy.sh ABC123xyz 文档标题 - 副本
```
### 方法 B:手动执行(适合调试)
```bash
# 1. 获取源文档信息
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 50
# 2. 创建临时目录
mkdir -p /tmp/feishu_copy_$$
# 3. 分块读取(假设原文档 43548 字符)
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 9000 --offset 0 > /tmp/feishu_copy_$$/chunk_0.json
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 9000 --offset 9000 > /tmp/feishu_copy_$$/chunk_1.json
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 9000 --offset 18000 > /tmp/feishu_copy_$$/chunk_2.json
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 9000 --offset 27000 > /tmp/feishu_copy_$$/chunk_3.json
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 9000 --offset 36000 > /tmp/feishu_copy_$$/chunk_4.json
# 4. 提取 markdown
for f in /tmp/feishu_copy_$$/chunk_*.json; do
cat "$f" | jq -r '.markdown' > "f%.json.md"
done
# 5. 创建新文档
openclaw feishu create-doc --title "<新文档标题>" --markdown "# <新文档标题>\n\n正在搬运中..."
# 6. 分块写入
openclaw feishu update-doc --doc_id <NEW_DOC_ID> --mode overwrite --markdown @/tmp/feishu_copy_$$/chunk_0.md
openclaw feishu update-doc --doc_id <NEW_DOC_ID> --mode append --markdown @/tmp/feishu_copy_$$/chunk_1.md
openclaw feishu update-doc --doc_id <NEW_DOC_ID> --mode append --markdown @/tmp/feishu_copy_$$/chunk_2.md
openclaw feishu update-doc --doc_id <NEW_DOC_ID> --mode append --markdown @/tmp/feishu_copy_$$/chunk_3.md
openclaw feishu update-doc --doc_id <NEW_DOC_ID> --mode append --markdown @/tmp/feishu_copy_$$/chunk_4.md
# 7. 验证
openclaw feishu fetch-doc --doc_id <NEW_DOC_ID> --limit 50
```
---
## 验证清单
复制完成后,请检查以下项目:
### 必检项目
- [ ] **字数验证**:新文档字数 / 原文档字数 ≥ 98%
- [ ] **目录完整**:所有 ## 章节标题都存在
- [ ] **表格完整**:所有 `<lark-table>` 标签完整
- [ ] **代码块完整**:所有 ``` 代码块完整
- [ ] **结语完整**:文档结尾没有截断
### 选检项目
- [ ] callout 格式正确
- [ ] 列表格式正确
- [ ] 粗体/斜体完整
- [ ] 链接完整
---
## 常见问题
### Q1: 为什么新文档字数比原文档少?
**A**: 正常现象,主要原因:
1. 图片 token 占位符差异(每张约 100-150 字符)
2. callout 内部换行被压缩(不影响渲染)
3. 多余空白字符被清理
只要差异 < 2%,文档内容就是完整的。
### Q2: 表格显示不正常怎么办?
**A**: 检查 `<lark-table>` 标签是否完整:
- 必须有 `rows`、`cols` 属性
- 所有 `<lark-tr>` 和 `<lark-td>` 标签必须配对
- 分块时没有在表格中间切断
### Q3: 如何获取文档 ID?
**A**: 从飞书文档 URL 中提取:
```
URL: https://xxx.feishu.cn/wiki/DOC_ID_HERE
↑
文档 ID
```
文档 ID 是 URL 最后一段字符串。
### Q4: 脚本执行失败怎么办?
**A**: 排查步骤:
1. 检查是否有源文档读取权限(能打开查看)
2. 检查 `openclaw feishu` 命令是否可用
3. 检查临时目录是否有写入权限
4. 查看错误输出,定位失败的步骤
### Q5: 如何断点续传?
**A**: 如果写入中途失败:
1. 不要删除临时目录
2. 记录最后成功写入的块编号
3. 从下一个块继续执行写入循环
4. 完成后正常验证和清理
---
## 技术细节
### 分块大小选择
| 分块大小 | 优点 | 缺点 | 推荐场景 |
|---------|------|------|---------|
| 5,000 | 不易切断表格/代码 | API 调用次数多 | 复杂文档 |
| 9,000 | 平衡点 | - | **推荐** |
| 15,000 | API 调用次数少 | 可能超过限制 | 简单文档 |
### API 限流处理
- 读取间隔:0.5 秒
- 写入间隔:1 秒
- 如遇 429 错误:增加间隔时间
### 错误处理
脚本使用 `set -e`,遇到错误立即退出。临时文件保留,便于调试。
---
## 版本历史
- **v1.0** (2026-03-14): 初始版本
- 分块读取 + 分块写入核心方法
- 完整自动化脚本
- 验证流程
---
## 许可证
MIT License
---
*本技能基于 OpenClaw 飞书 API 实现,适用于所有有查看权限但无复制权限的飞书文档。*
FILE:feishu-doc-copy.sh
#!/bin/bash
# 飞书文档完整复制脚本
# 用法:./feishu-doc-copy.sh <源文档 ID> <新文档标题>
set -e # 遇到错误立即退出
SOURCE_DOC_ID="$1"
NEW_DOC_TITLE="$2"
BLOCK_SIZE=9000 # 每块字符数
if [ -z "$SOURCE_DOC_ID" ] || [ -z "$NEW_DOC_TITLE" ]; then
echo "用法:$0 <源文档 ID> <新文档标题>"
echo "示例:$0 ABC123xyz 文档标题 - 副本"
exit 1
fi
# 创建临时目录
TEMP_DIR="/tmp/feishu_copy_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$TEMP_DIR"
echo "📋 开始复制飞书文档..."
echo " 源文档 ID: $SOURCE_DOC_ID"
echo " 新文档标题:$NEW_DOC_TITLE"
echo " 临时目录:$TEMP_DIR"
echo ""
# 步骤 1: 获取源文档信息
echo "📊 步骤 1: 获取源文档信息..."
DOC_INFO=$(openclaw feishu fetch-doc --doc_id "$SOURCE_DOC_ID" --limit 50)
TOTAL_LENGTH=$(echo "$DOC_INFO" | jq -r '.total_length')
SOURCE_TITLE=$(echo "$DOC_INFO" | jq -r '.title')
echo " 源文档标题:$SOURCE_TITLE"
echo " 原文档总长度:$TOTAL_LENGTH 字符"
# 步骤 2: 计算分块数量
BLOCK_COUNT=$(( ($TOTAL_LENGTH + $BLOCK_SIZE - 1) / $BLOCK_SIZE ))
echo " 分块大小:$BLOCK_SIZE 字符/块"
echo " 分块数量:$BLOCK_COUNT 块"
echo ""
# 步骤 3: 分块读取
echo "📥 步骤 3: 分块读取源文档..."
OFFSET=0
BLOCK_NUM=0
while [ $OFFSET -lt $TOTAL_LENGTH ]; do
echo " 读取块 $BLOCK_NUM(offset: $OFFSET)..."
openclaw feishu fetch-doc \
--doc_id "$SOURCE_DOC_ID" \
--limit $BLOCK_SIZE \
--offset $OFFSET \
> "$TEMP_DIR/chunk_BLOCK_NUM.json"
# 提取 markdown 内容
cat "$TEMP_DIR/chunk_BLOCK_NUM.json" | jq -r '.markdown' > "$TEMP_DIR/chunk_BLOCK_NUM.md"
# 检查是否还有更多
HAS_MORE=$(cat "$TEMP_DIR/chunk_BLOCK_NUM.json" | jq -r '.has_more')
if [ "$HAS_MORE" = "false" ]; then
echo " ✅ 最后一块读取完成"
break
fi
OFFSET=$(( $OFFSET + $BLOCK_SIZE ))
BLOCK_NUM=$(( $BLOCK_NUM + 1 ))
sleep 0.5
done
TOTAL_BLOCKS=$BLOCK_NUM
echo " ✅ 共读取 $(( $TOTAL_BLOCKS + 1 )) 块"
echo ""
# 步骤 4: 创建新文档
echo "📝 步骤 4: 创建新文档..."
NEW_DOC=$(openclaw feishu create-doc \
--title "$NEW_DOC_TITLE" \
--markdown "# $NEW_DOC_TITLE\n\n正在搬运中...")
NEW_DOC_ID=$(echo "$NEW_DOC" | jq -r '.doc_id')
NEW_DOC_URL=$(echo "$NEW_DOC" | jq -r '.doc_url')
echo " 新文档 ID: $NEW_DOC_ID"
echo " 新文档链接:$NEW_DOC_URL"
echo ""
# 步骤 5: 分块写入
echo "📤 步骤 5: 分块写入新文档..."
for i in $(seq 0 $TOTAL_BLOCKS); do
CHUNK_FILE="$TEMP_DIR/chunk_i.md"
if [ $i -eq 0 ]; then
MODE="overwrite"
else
MODE="append"
fi
echo " 写入块 $i($MODE 模式)..."
openclaw feishu update-doc \
--doc_id "$NEW_DOC_ID" \
--mode $MODE \
--markdown @"$CHUNK_FILE"
sleep 1
done
echo " ✅ 所有块写入完成"
echo ""
# 步骤 6: 验证
echo "🔍 步骤 6: 验证结果..."
NEW_DOC_INFO=$(openclaw feishu fetch-doc --doc_id "$NEW_DOC_ID" --limit 50)
NEW_LENGTH=$(echo "$NEW_DOC_INFO" | jq -r '.total_length')
DIFF=$(( $TOTAL_LENGTH - $NEW_LENGTH ))
PERCENT=$(echo "scale=2; $NEW_LENGTH * 100 / $TOTAL_LENGTH" | bc)
echo " 原文档:$TOTAL_LENGTH 字符"
echo " 新文档:$NEW_LENGTH 字符"
echo " 差异:$DIFF 字符($PERCENT% 完整)"
if [ $(echo "$PERCENT >= 98" | bc) -eq 1 ]; then
echo " ✅ 验证通过(差异 < 2%,主要是图片 token)"
VERIFICATION_STATUS="✅ 通过"
else
echo " ⚠️ 差异较大,请手动检查"
VERIFICATION_STATUS="⚠️ 需检查"
fi
echo ""
# 步骤 7: 清理
echo "🧹 步骤 7: 清理临时文件..."
rm -rf "$TEMP_DIR"
echo " ✅ 临时文件已清理"
echo ""
# 输出结果
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🎉 复制完成!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 源文档:$SOURCE_TITLE"
echo " 新文档:$NEW_DOC_TITLE"
echo " 新文档链接:$NEW_DOC_URL"
echo " 完整性:$VERIFICATION_STATUS"
echo " 原文档:$TOTAL_LENGTH 字符"
echo " 新文档:$NEW_LENGTH 字符"
echo " 差异:$DIFF 字符"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"