| name | pull-feishu-minutes |
| description | 把飞书妙记(会议/讲座/Coffee Chat 的录音转写)全量拉到本地,存成带 AI 总结的 Markdown 笔记。首次全量、之后自动增量。用户不需要自建飞书应用、不需要申请 API 权限、也不需要管理员审批——只要在弹出的浏览器里登录一次飞书即可,完全没授权过飞书的人也能用。触发词:拉飞书妙记、同步妙记、下载妙记、把妙记存到本地、导出飞书妙记、飞书录音转写、feishu minutes、把我的妙记同步到 Obsidian。 |
拉取飞书妙记到本地
把用户飞书账号下「我的妙记」里的全部录音转写拉到本地,每条存成一篇 Markdown:
顶部是为「一个月后已经忘光的自己」写的总结,底部是完整的原始逐字稿。
为什么不用飞书开放 API
先说清楚,避免走弯路:
- 开放 API 没有「列出我全部妙记」的接口。 只有一个关键词搜索接口(
minutes/v1/minutes/search),按相关度返回少量命中,枚举不全。
- 开放 API 的逐字稿导出权限
minutes:minutes.transcript:export 是敏感权限,需要自建应用 + 企业管理员在管理后台单独审批。普通用户往往不是管理员,这一步就卡死了。
所以本 skill 走网页登录态 + 妙记内部接口:零建应用、零权限申请、零管理员审批。代价是依赖未公开接口,飞书改版有失效可能(失效时报错会明确提示)。
流程
第 1 步:准备环境(幂等,可重复跑)
bash ~/.claude/skills/pull-feishu-minutes/scripts/setup.sh
建 venv、装 playwright、下 chromium 内核。已就绪时会很快跳过。
路径说明:本文以全局安装(~/.claude/skills/pull-feishu-minutes/)为准。
若装在项目级(.claude/skills/pull-feishu-minutes/),把下文所有
~/.claude/skills/pull-feishu-minutes 换成该项目里的实际路径即可。
venv 始终建在 skill 目录内,跟着 skill 走。
第 2 步:拉取
先问用户存到哪个目录(若用户已说明则直接用;Obsidian 用户通常是 vault 下的某个文件夹)。然后:
~/.claude/skills/pull-feishu-minutes/.venv/bin/python \
~/.claude/skills/pull-feishu-minutes/scripts/sync_minutes.py \
--out "<输出目录>"
- 首次运行绝对不要加
--headless:会弹出一个浏览器窗口(标题是 Google Chrome for Testing,不是用户平时的 Chrome),用户要在里面登录飞书。加了 headless 就没界面,用户根本无从登录,只会干等到超时。
- 提醒用户点页面右上角的 「注册/登录」(英文环境是 Sign Up/Log In),扫码最快。脚本会轮询等待,登录成功自动继续,用户不需要复制 cookie 或做任何技术操作。
- 登录态保存在
~/.config/feishu-minutes/browser-profile(用户级共享,换输出目录也不必重登)。之后再跑就该加 --headless 静默运行,不要再打扰用户。
- 已拉过的会自动跳过(依据
.feishu_minutes_state.json 与已有 md 的 minute_token)。
- 首次若妙记很多,可加
--limit 5 先试跑几条。
这一步耗时可能超过 1 分钟,放后台跑并定期查看进度,不要干等。 等待登录时更要如此——用户可能正在忙,别把前台卡死。
脚本 stdout 最后一行是 JSON:
{"ok":true,"total":12,"new":[{"token":"...","title":"...","path":"/abs/path.md","paragraphs":73}],"skipped":11}
用它拿到本次新增文件的路径列表,进入第 3 步。
先检查这三种情况,不要闷头往下走:
| 情况 | 含义 | 该怎么做 |
|---|
ok 为 false | 没登录成功(多半是等待超时) | 让用户重跑并完成登录,不要报告成功 |
有 warning 字段 | 判定为已登录、却一条妙记都没拉到 | 如实转达这条警告。若用户确信账号里有妙记,删掉 ~/.config/feishu-minutes/browser-profile 后重跑重新登录 |
new 为空、skipped > 0 | 正常,没有新妙记 | 直接告诉用户「没有新的」,结束 |
第 3 步:逐篇写总结(这一步由你,AI,亲自完成)
对 new 里的每一个文件:读取它 → 通读全文(长录音要分段读完,不能只看开头)→ 改写文件头部,把 ## 原始逐字稿 及其之后的内容一字不动地保留在文末。
心法:你不是在压缩,是在为一个失忆的人重建现场
读者是一个月后的用户本人。那时他对这场活动的记忆基本归零,甚至想不起来自己去过。所以这篇总结要同时干两件事:
- 让他"想起来" —— 靠具体的、感官性的锚点,而不是概括。
- 让他"用得上" —— 靠可迁移的认知,而不是流水账。
判断标准:写完之后自问——如果我一个月后只读这段、不看逐字稿,我能不能跟别人复述这场活动?能不能拿其中一条去改进我手上的事?两个都能,才算合格。
心法:分清「谁的 idea」,别人的 idea 才是重点
妙记的主人(运行这个 skill 的人)通常本人就在场、是其中一个说话人。对他最有价值的往往是「其他人」的 idea 和判断,而不是他自己已经知道的话。 所以:
- 先认人。语音模型给的"说话人 1/2/3"只是编号、跨录音不固定,要从内容认出哪个说话人是主人本人(靠他的职业、在做的项目、经历等线索——这些通常在对话里会露出来;调用方也可能在运行时告诉你主人的身份特征)。在正文顶部用一行"🧑🤝🧑 说话人对应"写清:说话人 X = 我(主人)、说话人 Y = 对方(一句话点明对方是谁/什么背景)。
- 主体写「对方」的经验、判断、方法,并明确归属("对方认为…""他的判断是…")——这是这篇笔记价值所在。
- 主人自己的看法单独放在末尾一节(如
## 我当时的看法(在迭代中)),并注明这些还在迭代、只作回看、不一定是结论——本人的观点常在快速变化,别当定论记。
- 多人对话里保留分歧和归属:谁提出、谁反驳、谁不认同,标清楚,别磨平成一个统一结论。
- 被提到但不在场的第三方,用角色代替姓名。
「这是哪一场」怎么写
目标是唤起记忆,所以:
- 开头一句话交代主办方、形式、时长、地点性质(线下/线上、workshop/圆桌/一对一)。
- 抓"无用但难忘"的细节。这是最关键的技巧:讲者穿统一黑衣服、现场发城市限定冰箱贴、建群暗号是
0721、边吃披萨边聊、被拆的团队就坐在台下——这些信息本身没价值,但它们是记忆的钩子,比"讨论了产品增长"有用一百倍。
- 如果活动有自己的结构,就用它的结构。分了三个环节就写成三条,三位讲者就按讲者列。用编号列表,每条点明这个人具体演示/讲了什么("演示用 AI 整理 Gmail 发票并写进 Notion",而不是"介绍了产品功能")。
- 人物用角色指代,不写姓名("一位做高端猎头的参与者")。
- 结尾用引用块写一句「一句话记忆点」,提炼这场最锋利的那个判断。
「沉淀下来的认知」怎么写
这是全文最重的部分,目标是可复用。
- 每一块用一句加粗的"论点句"开头,把结论一次说完,后面再展开论证。不要"关于定价,大家讨论了很多"这种引子。
- ✅
**多 Agent 互相打转的根因是"定位不清",不是模型不行。** 然后展开。
- ❌
**定价**:他们聊了定价策略。
- 数字、金额、比例、周期一个都不要丢。"onboarding 做得差流失 50%,做得好只流失 10%"、"12000~14000 元/月"、"苹果税 30%,巴西还要再抽 50%"、"999 → 1319"——细节才是能被回忆和引用的东西,抽象概括等于没写。
- 原话里锋利的表述,用引用块原样保留。比如"所有供应商信息都不要信,某个工具火一定是品牌方的原因,不是你的原因"。这类句子重写就毁了。
- 保留分歧,不要磨平成共识。现场有人反驳、有人不认同,恰恰是最有信息量的地方。明确标出立场归属:「主持人的判断是…」「现场反方认为…」「我的反驳观点是…」。把讨论写成一言堂是最常见的失败。
- 内容超过三四块时,用
### 小标题分主题;短录音直接用加粗论点句分段即可,不必强行加标题层级。
- 对比性内容用表格(两种方案的差异、行情价格、适用边界)。
「可借鉴的 idea」怎么写
3~6 条,每条都要能落到动作上。"要重视用户心理"是废话;"做落地页前先问三个问题:痛点够不够痛、有没有相似的人已经成功、行动位置有没有降低风险的承诺"才是能用的。可以包含"值得去要一份那个 checklist"这类具体待办。
长度标定
跟着信息密度走,不要一刀切:
| 录音 | 大致篇幅 |
|---|
| 30 分钟、单一主题的分享 | 「认知」3~5 块,不用小标题 |
| 1 小时、多环节的讲座 | 「认知」6~9 块,按主题加 ### 小标题 |
| 2 小时、多人圆桌 | 按活动自身的环节分大节,每节内再分块 |
宁可长而具体,不要短而空洞。 但如果一场活动确实没什么干货(纯产品宣讲、大部分时间在闲聊),就如实写短,不要注水凑篇幅。
改写后的结构:
---
title: "<重写的、有信息量的标题>"
type: 飞书妙记
场景: <Coffee Chat | 讲座 | 会议 | 内部分享 | 播客 …>
tags:
- 飞书妙记
- <场景>
- <2~4 个主题标签>
date: <保留原值>
duration: <保留原值>
source: <保留原值>
minute_token: <保留原值>
speakers: <保留原值>
imported: <保留原值>
enriched: true
---
# <重写的标题>
> 📅 <日期> ⏱ <时长> 🎙 <N> 位说话人 🏷 <场景> · [在飞书打开](<source>)
>
> 🧑🤝🧑 说话人对应:说话人 X = 我(主人);说话人 Y = 对方(<一句话背景>)。本篇重点是「对方」的观点;我的看法见文末、且在迭代中。
## 这是哪一场
某某组织的 XX Workshop,线下,约半小时。三个人轮流上台演示自家产品,
统一穿黑衣服;现场发折扣卡和城市限定冰箱贴,最后是自由动手环节。
三位讲者各讲一个视角:
1. **产品同学**——演示用 AI 整理 Gmail 里过去 7 天的发票,导出 Excel
并写进 Notion,再把这套流程一键转成每周自动化任务。
2. **研发同学**——演示多个 AI 在同一频道里协作修一个真实 bug
("命令行只能展示 10 条、但空间里有 75 人"),最后由 AI 提 PR。
3. **运营同学**——演示官媒运营全流程,从选题推荐一路到发推。
> 一句话记忆点:**他们不是把 AI 做成更强的工具,而是做成组织里的"人"**
> ——有名字、有自我介绍、有自己的账号,入职还走一遍 onboarding。
## 沉淀下来的认知
**多 Agent 互相打转的根因是"定位不清",不是模型不行。** 市面上很多产品会
出现 AI 之间反复兜圈子、产出重复内容,根子在于每个 agent 不知道自己在这个
任务里是谁、负责哪一段。他们的解法是让 AI 进群时先自我介绍、确立责任边界。
**真正的壁垒是"集成的团队复用"。** 他们自己也承认,多开几个 session 同样能
跑通那些流程。差异在于一个人配好的整套外部工具连接,全团队直接可用——
原话的意思是:**你等于享受了团队里最会用 AI 的那个成员的成果。**
## 我当时的看法(在迭代中,仅作回看)
<主人本人在场时,把他自己的观点/判断单独收在这里,并注明还在迭代、不一定是结论。
若主人全程只是听/问、没有明显自己的主张,这一节可省略。>
## 可借鉴的 idea
- **"享受团队里最会用 AI 的那个人的成果"** 这个表述值得偷:把一个偏技术的
功能翻译成了一句有画面感的收益。
- 多 Agent 产品设计要点:先解决"你是谁、你负责什么",再谈协作机制。
---
## 原始逐字稿
(原样保留,不要改动)
上面是写法示范,不是填空模板。注意几个细节:场景段里出现了"黑衣服""冰箱贴"这种无用但难忘的锚点;认知段每块用加粗论点句开头、并保留了原话;idea 段每条都能落到动作。
写总结的红线:
- 必须过滤掉:寒暄、设备/网络故障吐槽、点餐闲聊、他人八卦,以及涉及个人隐私与现状的内容(离职、健康、作息、薪资、房价、感情、家庭)。Coffee Chat 里这类内容往往占比很高,删起来不要手软——只留行业认知、方法论、具体案例、新想法。
- 标题要重写。飞书默认标题常常是「新录音」「新录音 2」这种无意义的名字。标题要能让人一眼看出这场讲了什么,可以用冒号带副标题。
- 绝不写正确的废话。「讨论了增长策略」「分享了很多经验」这类句子出现即失败。每一句都应该带信息量。
- 加粗只用来承载论点句(每段开头把结论说完的那一句),不要给随机名词加粗。加粗轰炸和排比堆砌是 AI 味的主要来源。书面但不端着。
- 不确定的信息不要编。语音转写对英文产品名和人名错得很厉害("Sintra"可能被转成"星上/新上/Singra")。要么略过,要么注明「转写里作 XXX」。宁可模糊,不可写错。
- 第三方姓名一律用角色代替(「一位做高端猎头的参与者」而不是具体人名)。
- 保留分歧和立场归属。谁提出的、谁反驳的、谁不认同,都要标清楚。把多方讨论写成统一结论,是信息损失最大的一种失败。
改完后把文件重命名成 YYYY-MM-DD <新标题>.md(重命名安全:去重依据是 frontmatter 里的 minute_token,不是文件名)。
第 4 步:汇报
告诉用户:本次新增几篇、分别是什么、存在哪;以及跳过了多少条已有的。若结果 JSON 里 untranscribed 非空,说明有妙记没有飞书转写、且未启用 ASR 兜底——把这些条目告诉用户,并提示可以配置 ASR(见下)来补转。
可选:ASR 兜底(转写飞书没转的妙记)
飞书免费版只有 300 分钟 转写额度,超额的妙记会没有逐字稿,或只转了开头一小段就停。本 skill 能把这类妙记的音频抠出来,交给一个语音大模型补转。
默认关闭。 只有当环境变量齐备时才启用(脚本自动探测;缺变量就跳过这些妙记并在结果里列进 untranscribed)。
支持两个后端,FEISHU_ASR_BACKEND 选(默认 auto):
| 后端 | 说明 |
|---|
| volcano(推荐) | 火山引擎 豆包语音大模型 Seed-ASR。中英混杂、专有名词、标点明显更准(实测 PMF/agent/跨境电商/脉脉 这类词 Paraformer 会错、火山基本全对)。需 ffmpeg(把飞书的 m4a 转 mp3)。 |
| paraformer | 阿里云百炼 Paraformer。免转码(直接吃 m4a),但中英混杂词错得多。 |
auto | 有 VOLC_ASR_KEY 走 volcano,否则有 DASHSCOPE_API_KEY 走 paraformer。 |
| 环境变量 | 用途 |
|---|
ALIYUN_ACCESS_KEY_ID / ALIYUN_ACCESS_KEY_SECRET | 上传音频到 OSS 中转(两个后端都要) |
FEISHU_ASR_OSS_BUCKET | 用作中转的 OSS bucket 名 |
FEISHU_ASR_OSS_ENDPOINT | OSS endpoint,默认 oss-cn-hangzhou.aliyuncs.com |
VOLC_ASR_KEY | 火山后端:控制台开通「录音文件识别大模型版(极速版)」后拿的 X-Api-Key(单 key 鉴权) |
DASHSCOPE_API_KEY | 百炼后端:一个 key 同时管 Paraformer 语音 + Qwen 文本 |
为什么要 OSS 中转:飞书音频地址是登录态保护的,语音服务够不着;两个后端的录音文件识别都只收「它自己能访问的公网 URL」。所以音频先进用户自己的私有 bucket,只给一个 2 小时过期的签名 URL(不公开),转写完立即删除中转文件。
判断哪条需要补转:不是看有没有逐字稿,而是看转写覆盖了录音时长的多少。免费额度耗尽时飞书常常只转了开头两三句,光看"有没有段落"会误判成"已转写"。脚本按覆盖率 < 50% 判定为残缺 → 触发 ASR,用全量音频重转(transcribed_by: dashscope-paraformer 会写进 frontmatter)。
火山后端还需要 ffmpeg(setup.sh 不装它——请用户自行 brew install ffmpeg / apt install ffmpeg;缺了会在 missing_env 里提示)。
运行:把 env 准备好(通常放在 secrets 文件里,运行前 source 一下),再照第 2 步正常跑。带 --no-asr 可临时禁用。ASR 那条会在 new 里标 "source":"volcano-seed-asr" 或 "dashscope-paraformer",你照样为它写总结(第 3 步),并在总结顶部注明逐字稿由哪个模型补转、可能有识别错。
成本:都极低,转 1 小时音频约几毛钱、1~2 分钟出结果(火山极速版实测 46 分钟音频 29 秒)。
增量与重跑
- 再次执行本 skill 时,只会拉取上次之后新增的妙记。
- 想强制重拉某一条:删掉对应的 md,并从
.feishu_minutes_state.json 里删掉那个 token。
- 登录态过期时脚本会报「等待登录超时」,重跑一次让用户重新登录即可。
常见问题
- 浏览器没弹出来:首次登录时不能加
--headless。反过来,登录态已存在时就该加 --headless,不必再开窗口。
- 一直停在"请登录":确认用户点的是脚本弹出的那个窗口(Google Chrome for Testing)。它和用户日常的 Chrome 是完全隔离的两套配置,在日常 Chrome 里已登录飞书对这里不起作用——这是设计如此,也正是"没授权过飞书的人也能用"的原因。
- 拉到 0 条但用户说有妙记:登录态没真正生效。删掉
~/.config/feishu-minutes/browser-profile 后重跑重新登录。
- 列表接口异常 / 导出报错:多半是登录态过期,重跑并重新登录。若重新登录后仍失败,可能是飞书改版导致内部接口变动——如实告诉用户,不要绕开报错假装成功。
- 国内网络走了代理导致连不上:脚本已对 feishu.cn 设置代理绕行;若仍失败,让用户临时关掉系统代理再试。
- 只想先试几条:加
--limit 3。
- 想换个目录重新存一份:登录态是用户级共享的,换
--out 不用重新登录;但新目录会被当成空目录,从头全量拉一遍。
给维护者:绝对不要改坏的几处
这些都是实测踩出来的,改动 sync_minutes.py 前务必先读代码注释:
- 不能靠接口返回值判断登录态。未登录时
space/list 同样返回 {"code":0,"list":[]},与"已登录但没有妙记"无法区分,会造成静默的假成功。只能靠页面上有没有登录入口来判断。
- 轮询等待登录时绝不能
page.reload()。用户正在扫码或输密码时刷新,会把整个登录流程冲掉。
- 不能用
wait_for_load_state("networkidle")。妙记页面有长连接,永远到不了 networkidle,超时抛异常被吞掉后表现为"死等登录"。
- 登录后飞书会重定向到企业专属域名(如
xxx.feishu.cn),API 必须跟随页面当前源,跨域 fetch 会被 CORS 直接拦死。
- 导出接口是 POST,必须带
bv-csrf-token 头,且每个域名各有一份不同的值,取错域一律 HTTP 400。
- 判断"需要 ASR"要按覆盖率,不能按有没有段落。免费额度耗尽的妙记,飞书常常已经转了开头两三句,
parse_transcript 会返回非空段落——只看"有没有段落"必然漏判。用「最后一段的时间戳 / 录音时长」判断。
- 音频地址要先打开播放页才拿得到(读
<audio>.currentSrc),它在 internal-api-drive-stream.feishu.cn 上,是带过期的临时地址。下载用 ctx.request(带 cookie),不能在页面里跨域 fetch。
- 百炼录音文件识别只收公网 URL,收不了本地文件、也够不到飞书的登录态地址——所以必须 OSS(或任意公网可访问处)中转,给签名 URL 即可,不必公开 bucket。