# Pull Feishu Minutes

> 把飞书妙记（会议/讲座/Coffee Chat 的录音转写）全量拉到本地，存成带 AI 总结的 Markdown 笔记。首次全量、之后自动增量。用户不需要自建飞书应用、不需要申请 API 权限、也不需要管理员审批——只要在弹出的浏览器里登录一次飞书即可，完全没授权过飞书的人也能用。触发词：拉飞书妙记、同步妙记、下载妙记、把妙记存到本地、导出飞书妙记、飞书录音转写、feishu minutes、把我的妙记同步到 Obsidian。

- Skill: `yijiaduan/pull-feishu-minutes` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add yijiaduan/pull-feishu-minutes`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yijiaduan/pull-feishu-minutes/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: YijiaDuan (https://skillmd.com/u/yijiaduan)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/yijiaduan/pull-feishu-minutes

---


# 拉取飞书妙记到本地

把用户飞书账号下「我的妙记」里的全部录音转写拉到本地，每条存成一篇 Markdown：
**顶部是为「一个月后已经忘光的自己」写的总结，底部是完整的原始逐字稿。**

## 为什么不用飞书开放 API

先说清楚，避免走弯路：

- **开放 API 没有「列出我全部妙记」的接口。** 只有一个关键词搜索接口（`minutes/v1/minutes/search`），按相关度返回少量命中，**枚举不全**。
- **开放 API 的逐字稿导出权限 `minutes:minutes.transcript:export` 是敏感权限**，需要自建应用 + 企业管理员在管理后台单独审批。普通用户往往不是管理员，这一步就卡死了。

所以本 skill 走**网页登录态 + 妙记内部接口**：零建应用、零权限申请、零管理员审批。代价是依赖未公开接口，飞书改版有失效可能（失效时报错会明确提示）。

## 流程

### 第 1 步：准备环境（幂等，可重复跑）

```bash
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 下的某个文件夹）。然后：

```bash
~/.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：

```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` 里的**每一个**文件：读取它 → **通读全文**（长录音要分段读完，不能只看开头）→ **改写文件头部**，把 `## 原始逐字稿` 及其之后的内容**一字不动地保留在文末**。

#### 心法：你不是在压缩，是在为一个失忆的人重建现场

读者是**一个月后的用户本人**。那时他对这场活动的记忆基本归零，甚至想不起来自己去过。所以这篇总结要同时干两件事：

1. **让他"想起来"** —— 靠具体的、感官性的锚点，而不是概括。
2. **让他"用得上"** —— 靠可迁移的认知，而不是流水账。

**判断标准**：写完之后自问——如果我一个月后只读这段、不看逐字稿，我能不能跟别人复述这场活动？能不能拿其中一条去改进我手上的事？两个都能，才算合格。

#### 心法：分清「谁的 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 小时、多人圆桌 | 按活动自身的环节分大节，每节内再分块 |

**宁可长而具体，不要短而空洞。** 但如果一场活动确实没什么干货（纯产品宣讲、大部分时间在闲聊），就如实写短，不要注水凑篇幅。

#### 改写后的结构：

```markdown
---
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 段每条都能落到动作。

**写总结的红线：**

1. **必须过滤掉**：寒暄、设备/网络故障吐槽、点餐闲聊、他人八卦，以及**涉及个人隐私与现状的内容**（离职、健康、作息、薪资、房价、感情、家庭）。Coffee Chat 里这类内容往往占比很高，删起来不要手软——只留行业认知、方法论、具体案例、新想法。
2. **标题要重写**。飞书默认标题常常是「新录音」「新录音 2」这种无意义的名字。标题要能让人一眼看出这场讲了什么，可以用冒号带副标题。
3. **绝不写正确的废话**。「讨论了增长策略」「分享了很多经验」这类句子出现即失败。每一句都应该带信息量。
4. **加粗只用来承载论点句**（每段开头把结论说完的那一句），不要给随机名词加粗。加粗轰炸和排比堆砌是 AI 味的主要来源。书面但不端着。
5. **不确定的信息不要编**。语音转写对英文产品名和人名错得很厉害（"Sintra"可能被转成"星上/新上/Singra"）。要么略过，要么注明「转写里作 XXX」。宁可模糊，不可写错。
6. **第三方姓名一律用角色代替**（「一位做高端猎头的参与者」而不是具体人名）。
7. **保留分歧和立场归属**。谁提出的、谁反驳的、谁不认同，都要标清楚。把多方讨论写成统一结论，是信息损失最大的一种失败。

改完后**把文件重命名**成 `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。

