From learning-skills
Summarizes incremental audio transcripts from WPS notes every 60s: scans for new sentences, detects scenes/speakers, learns style from past notes, generates structured XML summaries, inserts into notes. Useful for live meetings, lectures, podcasts, interviews.
npx claudepluginhub wpsnote/wpsnote-skills --plugin learning-skillsThis skill uses the workspace's default tool permissions.
实时监听当前 WPS 笔记中的音频录音转写,每 60 秒轮询一次,**只处理新增句子(增量模式)**,根据场景自动选择模板整理内容并写回笔记。
Processes audio recordings, transcripts, podcasts, lectures into structured Obsidian notes with action items, decisions, glossary. Runs intake interview; suggests agent chaining.
Summarizes meeting transcripts into structured Markdown notes with date, participants, topic, decisions, points, action items, and open questions.
Transcribes audio sessions from aside recorder or files, aligns transcripts with real-time memos, and distills into structured Obsidian vault notes using Enzyme. For processing calls, lectures, or interviews.
Share bugs, ideas, or general feedback.
实时监听当前 WPS 笔记中的音频录音转写,每 60 秒轮询一次,只处理新增句子(增量模式),根据场景自动选择模板整理内容并写回笔记。
优先使用 wpsnote-cli(CLI),不支持时才退回 MCP 工具调用。
| 操作 | CLI 命令 | MCP 工具 |
|---|---|---|
| 获取当前笔记 | wpsnote-cli current --json | get_current_note() |
| 获取大纲 | wpsnote-cli outline --note_id ID --json | get_note_outline() |
| 获取转写 | wpsnote-cli audio --shorthand_id ID --json | get_audio_transcript() |
| 搜索笔记 | wpsnote-cli find --keyword KW --json | search_notes() |
| 搜索笔记内内容 | wpsnote-cli search --note_id ID --query Q --json | search_note_content() |
| 插入内容 | wpsnote-cli edit --json-args '...' | edit_block(op="insert") |
| 替换内容 | wpsnote-cli edit --json-args '...' | edit_block(op="replace") |
| 批量编辑 | wpsnote-cli batch-edit --json-args '...' | batch_edit() |
CLI 中 XML 含尖括号时,统一用
--json-args传 JSON 对象,避免 shell 转义问题。
content 参数必须是 XML 字符串,绝不能是数组或对象。
// ✅ 正确
{ "content": "<h2>标题</h2><p>内容</p>" }
// ❌ 禁止——数组
{ "content": [{"type": "text", "text": "<h2>标题</h2>"}] }
// ❌ 禁止——对象
{ "content": {"type": "text", "text": "<h2>标题</h2>"} }
遇到 BLOCK_NOT_FOUND 或写入反复失败时,第一步检查 content 是否为字符串,再用 outline 刷新 block_id 后重试。
启动(仅一次):
1. 检查 CLI 可用性
2. 获取当前笔记 note_id
3. 风格学习(搜索 3-5 篇历史同类笔记)
4. 初始化状态文件
5. 输出启动告知
loop(每 60 秒):
1. 扫描笔记大纲,找 NoteAudioCard
2. 全量拉取转写,过滤新增句子(start_time > last_end_time)
3. 若无新增 → 检测临时区用户输入 → 更新状态 → sleep 60 → continue
4. 首轮:场景识别 + 发言人推断
5. 人名联动:提取人名 → 搜索笔记库 → 注入背景上下文
6. 首轮:先输出内容地图,再开始写入
7. 用新增句子生成摘要 XML
8. 写回笔记(首轮在末尾插入;后续追加到 summary_anchor 之后,始终在临时区之前)
9. 回扫大纲:核查新内容是否落在正确章节,发现位置错误立即修正
10. 首轮:在笔记末尾创建临时区(hr + 提示块)
11. 检测临时区用户输入 → 合并到正文 → 清空用户 block
12. 检测 *** 补全请求
13. 更新状态文件(last_end_time、summary_anchor_id、temp_zone_anchor_id 等)
14. 输出本轮简报 → sleep 60 → loop
wpsnote-cli status
# 输出包含"连接中... 成功" → CLI 可用,否则退回 MCP 模式
NOTE=$(wpsnote-cli current --json)
NOTE_ID=$(echo $NOTE | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data']['note_id'])")
# MCP 备选
get_current_note() → { note_id, title }
若返回 NO_ACTIVE_EDITOR_WINDOW,提示用户打开笔记后重试。
搜索 3-5 篇历史同类笔记(会议纪要/课堂笔记/总结),每篇最多读 3000 字,综合归纳风格特征:
NOTES=$(wpsnote-cli find --keyword "会议纪要 纪要 会议总结 周会 月会" --limit 5 --json)
归纳维度(多篇投票取多数):语言风格(口语 vs 书面)、结构偏好(bullet list vs 分章节)、详略程度、是否中英混写、是否常用色块/分栏。
样本不足 3 篇时继续,标注「风格样本不足,参考有限」。
state = {
"note_id": note_id,
"shorthand_id": None, # 首轮扫描到 AudioCard 后填入
"scene": None, # 首轮场景识别后填入
"last_end_time": -1.0, # -1 代表从头开始
"summary_anchor_id": None, # 上轮最后写入的 block_id(临时区 hr 之前)
"temp_zone_anchor_id": None, # 临时区 highlightBlock 的 block_id
"round": 0,
"speaker_map": {}, # {"发言人 1": "真实姓名"}
"person_cache": {}, # {"张总": [{"note_title": ..., "snippet": ...}]}
"started_at": time.time(),
}
# 保存到 /tmp/lts_state_{note_id}.json
已开始边听边总结 ✓
当前笔记:[笔记标题]
识别场景:等待录音开始后自动识别
参考风格:[找到 N 篇 / 未找到历史总结]
模式:增量(只处理新增转写)+ 人名联动
轮询间隔:60 秒
---
援助功能:
① 补全:在笔记任意位置输入 ***,AI 结合录音自动补全
② 临时区:笔记末尾灰色区域,写下想法,AI 下轮自动合并到正文
③ 停止:告诉我「停止总结」即可
---
outline_data = json.loads(wpsnote_cli_outline(note_id))
audio_blocks = [b for b in outline_data["data"]["blocks"] if b["type"] == "note_audio_card"]
if not audio_blocks:
# sleep 60, continue
pass
audio_block = audio_blocks[0] # 取第一个,或 status=recording 的那个
shorthand_id = audio_block["attrs"]["shorthand_id"]
status = audio_block["attrs"].get("status", "unknown")
# recording / paused / stopped 均继续处理
all_sentences = get_transcript(shorthand_id) # 每个 sentence: {text, speaker, start_time, end_time}
# 只取新增句子
new_sentences = [s for s in all_sentences if s["start_time"] > state["last_end_time"]]
if not new_sentences:
# → 跳到 Step 8(检测临时区),然后 sleep 60
pass
wpsnote-cli audio只支持全量拉取,过滤在本地完成,每轮实际处理量恒定。
if state["scene"] is None and new_sentences:
sample = " ".join(s["text"] for s in new_sentences[:10])
state["scene"] = detect_scene(note_title, sample) # 见「场景识别表」
for s in new_sentences:
if "我是" in s["text"] or "我叫" in s["text"]:
state["speaker_map"][s["speaker"]] = extracted_name
# 若发现纠正,批量替换笔记中旧名称
从新增句子和 speaker_map 中提取人名,搜索笔记库,将背景上下文注入本轮摘要 prompt。
搜索策略(依次降级):全名 → 后两字 → 名字单字(慎用)
每人最多取 2 篇 × 3 段,结果缓存在 state["person_cache"],同一 session 不重复搜索。
详细代码见 person-linking.md
仅首轮(state["round"] == 0)触发:在写入笔记之前,根据全量转写梳理整体逻辑结构,以树形图展示给用户,然后直接开始写入。
📋 内容地图
[笔记标题]
├── 基本信息(时间、参与人等)
├── 第一部分:[主题名称]
├── 第二部分:[主题名称]
│ ├── [子主题]
│ └── [子主题]
├── 第 N 部分:[主题名称]
└── 行动项
展示后立即开始写入,大标题从 H2 开始,无需等待用户确认。
输入给 AI 的 prompt:
[场景: {scene}] [发言人映射: {speaker_map}]
新增转写({N} 句,{时长}秒):
{speaker}: {text}
...
[人物背景(来自笔记库,供参考)]:
- 张总(来自《Q4 复盘》):...
请按 {模板名} 模板提取要点,生成 WPS XML 格式的增量摘要。
只写本段新增内容对应的要点,不要重复已有内容。
约束:严格基于转写、跳过空章节、模糊信息标注「待确认」、参考用户风格、默认富文本排版。
核心原则:新摘要内容始终插入到临时区 hr 之前,临时区永远压底。
首轮写入(临时区尚不存在):
# 1. 插入摘要内容到笔记末尾
wpsnote-cli edit --json-args "{
\"note_id\": \"$NOTE_ID\",
\"op\": \"insert\",
\"anchor_id\": \"$LAST_BLOCK\",
\"position\": \"after\",
\"content\": \"<h2>转写摘要</h2><p>...</p>\"
}"
# 记录 summary_anchor_id = 本次最后插入的 block_id
# 2. 在末尾创建临时区
wpsnote-cli edit --json-args "{
\"note_id\": \"$NOTE_ID\",
\"op\": \"insert\",
\"anchor_id\": \"$SUMMARY_ANCHOR_ID\",
\"position\": \"after\",
\"content\": \"<hr/><highlightBlock emoji=\\\"💬\\\" highlightBlockBackgroundColor=\\\"#EBEBEB\\\" highlightBlockBorderColor=\\\"#C5C5C5\\\"><p><span fontColor=\\\"#757575\\\">把你的想法、补充、纠正贴到这里,AI 下轮自动合并到正文对应位置。也可以直接改上面的正文。</span></p></highlightBlock>\"
}"
# 记录 temp_zone_anchor_id = highlightBlock 的 block_id
后续轮次(临时区已存在):
# 新内容追加到 summary_anchor_id 之后(临时区 hr 之前)
wpsnote-cli edit --json-args "{
\"note_id\": \"$NOTE_ID\",
\"op\": \"insert\",
\"anchor_id\": \"$SUMMARY_ANCHOR_ID\",
\"position\": \"after\",
\"content\": \"<p>新增摘要内容...</p>\"
}"
# 更新 summary_anchor_id = 本次最后插入的 block_id
每轮写入完成后,立即重新拉取笔记大纲,逐章核查内容是否在正确位置:
OUTLINE=$(wpsnote-cli outline --note_id "$NOTE_ID" --json)
检查项:
发现异常时:
batch_edit 将其移动到正确的 h2 节下summary_anchor_id 为当前章节末尾的正确 block_id检查用户是否在临时区写了内容(提示块之后的任意 block 即为用户输入):
temp_anchor_id = state.get("temp_zone_anchor_id")
if not temp_anchor_id:
pass # 临时区尚未创建,跳过
else:
# 读取 temp_zone_anchor_id 后方 10 个 block
# 过滤掉 hr 块和含「把你的想法」的提示块
user_blocks = [b for b in after_blocks
if b["type"] != "hr"
and "把你的想法" not in b.get("content_text", "")]
若有用户输入,执行合并:
batch_edit 插入/替换到正文对应位置<hr/> 和提示块合并 prompt:
用户写在临时区的内容:
{user_temp_content}
当前笔记摘要大纲(含 block_id):
{summary_outline}
请判断每条内容应插入到哪个 block_id 之后,生成对应 WPS XML。
规则:补充 → 插入对应观点之后;新话题 → 插入摘要末尾(临时区 hr 之前);纠正 → 替换对应 block。
保持原有排版风格。
STARS=$(wpsnote-cli search --note_id "$NOTE_ID" --query "***" --json)
若找到 ***,读取其前后 3 个 block,生成补全内容后替换:
wpsnote-cli edit --json-args "{
\"note_id\": \"$NOTE_ID\",
\"op\": \"replace\",
\"block_id\": \"$STAR_BLOCK\",
\"content\": \"<p>补全后的内容</p>\"
}"
if new_sentences:
state["last_end_time"] = new_sentences[-1]["end_time"]
state["summary_anchor_id"] = last_inserted_block_id
if state["temp_zone_anchor_id"] is None and temp_zone_block_id:
state["temp_zone_anchor_id"] = temp_zone_block_id
state["round"] += 1
json.dump(state, open(state_path, "w"), ensure_ascii=False)
✓ 第 3 轮完成(新增 18 句 / 142 秒 → 摘要 +85 字)
场景:会议记录 发言人:张总、李工❓
人名联动:张总→《Q4 复盘》、李工→未找到
行动项:+2 条 ***补全:1 处 临时区合并:1 条
下一轮:60 秒后
| 场景关键词 | 场景类型 | 使用模板 |
|---|---|---|
| 会议、讨论、周会、评审 | 会议记录 | 会议纪要模板 |
| 课堂、授课、教学、老师 | 课堂笔记 | 课堂笔记模板 |
| 培训、讲师、学员 | 培训课程 | 培训课程模板 |
| 知识分享、分享会、经验分享 | 知识分享 | 知识分享模板 |
| 直播、主播、观众 | 直播内容 | 直播记录模板 |
| 播客、嘉宾、节目 | 播客/视频 | 播客纪要模板 |
| 采访、记者、被采访 | 采访记录 | 采访记录模板 |
| 谈判、甲方、乙方、合同 | 商务谈判 | 商务谈判模板 |
| 复盘、总结、里程碑 | 项目复盘 | 项目复盘模板 |
| 庭审、原告、被告、法庭 | 庭审记录 | 庭审纪要模板 |
| 患者、诊断、医嘱、症状 | 病例记录 | 病例记录模板 |
| 口述、文章、博客 | 口述转文章 | 文章模板 |
| 电话、通话、客服 | 电话录音 | 电话记录模板 |
| 无明显特征 | 通用场景 | 通用转写模板 |
默认使用富文本排版。用户明确说「纯文本」「不要排版」时才退回普通段落。
columnBackgroundColor:
#EBF2FF:中性信息、发言人观点#E8FCEF:结论、共识#FFF5EB:注意事项、待确认#FFECEB:风险、问题、争议点#FAF0FF:补充信息、延伸内容<columns>
<column columnBackgroundColor="#EBF2FF">
<h4>发言人 · 身份信息</h4>
<p listType="bullet" listLevel="0">要点一</p>
<blockquote>「原话片段」</blockquote>
</column>
</columns>
先按主题/议题分 h2/h3 章节,再在章节内用色块区分发言人:
h2 主题一
├── columns(蓝) 发言人 A
├── columns(蓝) 发言人 B
└── columns(绿) 结论/共识
h2 行动项
└── todo list(不套色块)
各模板均遵循「严格基于转写、跳过空章节、默认富文本排版」原则。
| 场景 | 主结构 | 特殊要求 |
|---|---|---|
| 通用 | h2摘要 → h3主题 → columns(蓝) → todo | — |
| 会议纪要 | h2纪要 → h3议题 → columns(蓝)发言人 → columns(绿)结论 → todo | 含参会人、决策标注 |
| 课堂笔记 | h2笔记 → h3知识点 → h4细节 → h3重点 → todo作业 | 无色块,以层级代替 |
| 知识分享 | h2记录 → h3核心知识 → h4知识点 → h3要点 → todo学习 | — |
| 直播/播客 | h2纪要 → h3主题 → columns(黄)嘉宾A → columns(蓝)嘉宾B | 注明嘉宾身份 |
| 商务谈判 | h2记录 → h3议题 → 双栏(蓝/红)甲乙方 → columns(绿)协议 → todo | 双栏对立布局 |
| 项目复盘 | h2复盘 → h3成果 → h3问题 → h3经验 → todo改进 | 量化成果 |
| 庭审/病例/采访/电话/培训 | 按各专业流程顺序组织,见下方说明 | — |
专业场景结构:
完整 XML 模板见 templates.md
从新增转写中识别任务:
<p listType="todo" listLevel="0" checked="0">[任务](负责人:XX,截止:XX)</p>
status=stopped 且已完成最终总结停止时清理状态文件:rm /tmp/lts_state_{note_id}.json
| 错误 | 处理方式 |
|---|---|
| CLI 不可用 | 退回 MCP 模式 |
NO_ACTIVE_EDITOR_WINDOW | 提示用户打开笔记,等待 30 秒后重试 |
WEBSOCKET_NOT_CONNECTED | 等待 10 秒后重试转写获取 |
EDITOR_NOT_READY | 等待 2 秒后重试写入 |
BLOCK_NOT_FOUND | 用 outline 刷新 ID 后重试;检查 content 是否为字符串 |
DOCUMENT_READ_ONLY | 告知用户笔记为只读,停止写入但继续读取 |
| 转写返回空 | 等待 30 秒(可能正在转写中)后重试 |
| 写入反复失败 | 检查 content 是否为 XML 字符串,不能是数组/对象;刷新 block_id 后重试 |