baoyu-wechat-summary
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWeChat Group Summary
微信群聊总结
群聊精华提取专家。把零散的微信群聊记录提炼成结构化、可读性强的简报,并维护跨次运行的群聊历史与群友画像。底层依赖外部 wx-cli 二进制( 命令),不打包脚本。
wx⚠️ Sandbox restrictionwx-cli reads from(config, cache, daemon socket) and from WeChat's data directory (~/.wx-cli/on macOS). Both paths are outside Claude Code's default sandbox. Every~/Library/Containers/com.tencent.xinWeChat/command in this skill needs to run withwxfrom the start — don't waste a sandbox attempt first. The user can usedangerouslyDisableSandbox: trueto view/edit restrictions./sandbox
群聊精华提取专家。把零散的微信群聊记录提炼成结构化、可读性强的简报,并维护跨次运行的群聊历史与群友画像。底层依赖外部 wx-cli 二进制( 命令),不打包脚本。
wx⚠️ 沙箱限制wx-cli 读取(配置、缓存、守护进程套接字)和微信数据目录(macOS 上为~/.wx-cli/)。这两个路径均在 Claude Code 默认沙箱之外。本技能中的所有~/Library/Containers/com.tencent.xinWeChat/命令从一开始就需要以wx参数运行——不要先尝试默认沙箱,以免浪费机会。用户可使用dangerouslyDisableSandbox: true命令查看/编辑沙箱限制。/sandbox
User Input Tools
用户输入工具
When this skill prompts the user, follow this tool-selection rule (priority order):
- Prefer built-in user-input tools exposed by the current agent runtime — e.g., ,
AskUserQuestion,request_user_input,clarify, or any equivalent.ask_user - Fallback: if no such tool exists, emit a numbered plain-text message and ask the user to reply with the chosen number/answer for each question.
- Batching: if the tool supports multiple questions per call, combine all applicable questions into a single call; if only single-question, ask them one at a time in priority order.
Concrete references below are examples — substitute the local equivalent in other runtimes.
AskUserQuestion当本技能需要提示用户时,请遵循以下工具选择规则(优先级从高到低):
- 优先使用当前Agent运行时提供的内置用户输入工具——例如 、
AskUserQuestion、request_user_input、clarify或任何等效工具。ask_user - 备选方案:如果没有此类工具,则输出带编号的纯文本消息,要求用户回复所选编号/答案以回答每个问题。
- 批量处理:如果工具支持单次调用包含多个问题,则将所有适用问题合并为单次调用;如果仅支持单个问题,则按优先级顺序逐个询问。
以下具体的 引用为示例——在其他运行时中请替换为本地等效工具。
AskUserQuestionPrerequisites
前置条件
Before invoking the workflow, verify the environment. Run these checks in order; stop at the first failure and surface the exact next command the user needs.
- wx-cli installed — run . If missing, tell the user to install it themselves (
wx --versionor use one of the alternatives at https://github.com/jackwener/wx-cli). Do NOT auto-install — this repo forbids piped/silent installs.npm install -g @jackwener/wx-cli - directory owned by the current user —
~/.wx-clihistorically chowned this directory to root, which breaks every subsequent non-sudosudo wx initcall. Check:wxIf the directory exists but the owner isbashls -la ~/.wx-cli/ 2>/dev/null | head -5(or anything other thanroot), tell the user to repair it themselves:$(whoami)The skill should NOT runbashsudo chown -R $(whoami) ~/.wx-cli sudo rm -f ~/.wx-cli/daemon.pid ~/.wx-cli/daemon.sock wx daemon starton the user's behalf.sudo - wx-cli initialized — should return data. If it fails with "no keys" / "init required", instruct the user to run
wx sessionswhile WeChat is running (on macOS,wx initfirst). Prefer non-sudo init; only fall back tocodesign --force --deep --sign - /Applications/WeChat.appif the user's wx-cli version requires it — and warn them that they'll need step 2's chown after.sudo wx init - WeChat 4.x running and logged in — required for the daemon to find data files.
在调用工作流之前,请验证环境。按顺序执行以下检查;若首次检查失败,则停止并告知用户需要执行的具体下一步命令。
- 已安装wx-cli——运行 。若未安装,请告知用户自行安装(
wx --version或使用 https://github.com/jackwener/wx-cli 提供的其他安装方式)。请勿自动安装——本仓库禁止管道式/静默安装。npm install -g @jackwener/wx-cli - 目录归当前用户所有——历史上使用
~/.wx-cli会将该目录的所有者设置为root,这会导致后续所有非sudo的sudo wx init调用失败。检查命令:wx如果目录存在但所有者为bashls -la ~/.wx-cli/ 2>/dev/null | head -5(或非当前用户root),请告知用户自行修复:$(whoami)本技能不得代表用户运行bashsudo chown -R $(whoami) ~/.wx-cli sudo rm -f ~/.wx-cli/daemon.pid ~/.wx-cli/daemon.sock wx daemon start命令。sudo - wx-cli已初始化——应返回数据。若失败并提示“no keys”/“init required”,请指导用户在微信运行时执行
wx sessions(在macOS上,需先执行wx init)。优先使用非sudo初始化;仅当用户的wx-cli版本要求时,才退而使用codesign --force --deep --sign - /Applications/WeChat.app——并警告用户之后需要执行步骤2的chown命令。sudo wx init - 微信4.x已运行并登录——这是守护进程找到数据文件的必要条件。
Preferences (EXTEND.md)
偏好设置(EXTEND.md)
Check EXTEND.md in priority order — the first one found wins:
| Priority | Path | Scope |
|---|---|---|
| 1 | | Project |
| 2 | | XDG |
| 3 | | User home |
| Result | Action |
|---|---|
| Found | Read, parse, apply. On first use in session, briefly remind: "Using preferences from [path]. Edit it to change defaults." |
| Not found | MUST run first-time setup (BLOCKING) before generating any digest — do NOT silently use defaults. |
按优先级顺序检查EXTEND.md——找到的第一个文件生效:
| 优先级 | 路径 | 作用域 |
|---|---|---|
| 1 | | 项目级 |
| 2 | | XDG标准 |
| 3 | | 用户主目录 |
| 结果 | 操作 |
|---|---|
| 找到文件 | 读取、解析并应用配置。在会话中首次使用时,简要提示:“正在使用[路径]中的偏好设置。编辑该文件可修改默认配置。” |
| 未找到文件 | 必须先完成首次设置(阻塞式),再生成任何摘要——不得静默使用默认值。 |
Supported keys
支持的配置项
EXTEND.md is plain text with or lines, for comments, case-insensitive keys.
key: valuekey=value#| Key | Type | Default | Purpose |
|---|---|---|---|
| string | (required) | The owning account's wxid. Messages whose |
| string | (required) | Display name to substitute for the user's own messages in digest text. |
| | | Which version(s) to generate when the user doesn't say otherwise. |
| string (e.g. | (none) | Default range when the user omits time and there's no incremental anchor. |
| path | | Override where digest folders live. |
A starter template lives at EXTEND.md.example.
EXTEND.md为纯文本文件,采用 或 格式, 用于注释,配置项名称不区分大小写。
key: valuekey=value#| 配置项 | 类型 | 默认值 | 用途 |
|---|---|---|---|
| 字符串 | 必填 | 当前账号的wxid。 |
| 字符串 | 必填 | 在摘要文本中替换用户本人消息的显示名称。 |
| | | 用户未指定时,默认生成的版本类型。 |
| 字符串(例如 | 无 | 用户未指定时间范围且无增量锚点时的默认时间范围。 |
| 路径 | | 覆盖摘要文件的存储目录。 |
入门模板可参考 EXTEND.md.example。
First-Time Setup (BLOCKING)
首次设置(阻塞式)
If no EXTEND.md is found, do NOT silently proceed.
Step A — Try to auto-discover and first. Run (in order, stop at the first that succeeds):
self_wxidself_displaybash
undefined若未找到EXTEND.md,不得静默继续执行。
步骤A — 先尝试自动发现 和 。按顺序执行命令,首次成功即停止:
self_wxidself_displaybash
undefined1. If wx-cli exposes a whoami, use it
1. 如果wx-cli提供whoami命令,则使用它
wx whoami --json 2>/dev/null
wx whoami --json 2>/dev/null
2. Otherwise, find self-sent messages in recent sessions
2. 否则,在最近的会话中查找用户发送的消息
wx sessions --json --limit 20 2>/dev/null
For option 2, scan the sessions for any private/group thread the user has sent into and read one of their own `from_wxid` / `from_nickname` pairs. If you can confidently pre-fill both values, use them as defaults in the question below; otherwise leave the fields blank for the user to fill in.
**Step B — Confirm with one `AskUserQuestion` call (batched), pre-filling whatever auto-discovery found:**
- `self_wxid` (e.g., `wxid_abc123`) — fall-back hint: the user can find it with `wx contacts --query "<own nickname>"`, or by inspecting any of their own sent messages in `wx sessions --json`
- `self_display` (e.g., `宝玉`) — how they want their messages attributed
- `default_version` — pick one of `normal` / `roast` / `both`
- `data_root` — where digest folders live. Default: `{project_root}/wechat`. Enter a custom absolute path (e.g. `~/Documents/wechat-digests`) or leave blank for default.
- Save location — pick one of project / XDG / home
Write EXTEND.md to the chosen path. If the user provided a non-default `data_root`, include it as an uncommented line; otherwise omit it (the default applies automatically). Confirm "Preferences saved to [path]. Edit it any time to change defaults.", then continue with the digest workflow.wx sessions --json --limit 20 2>/dev/null
对于选项2,扫描会话中用户参与的私聊/群聊线程,读取其中一条用户本人消息的 `from_wxid` / `from_nickname` 配对。如果能可靠地预填充这两个值,则在后续问题中使用它们作为默认值;否则留空让用户填写。
**步骤B — 通过一次 `AskUserQuestion` 调用(批量)确认信息,预填充自动发现的内容:**
- `self_wxid`(例如 `wxid_abc123`)——备选提示:用户可通过 `wx contacts --query "<自己的昵称>"` 或查看 `wx sessions --json` 中自己发送的消息找到该值
- `self_display`(例如 `宝玉`)——用户希望自己的消息以何种名称显示
- `default_version`——从 `normal` / `roast` / `both` 中选择一个
- `data_root`——摘要文件的存储目录。默认值:`{project_root}/wechat`。可输入自定义绝对路径(例如 `~/Documents/wechat-digests`)或留空使用默认值。
- 保存位置——从项目级/XDG标准/用户主目录中选择一个
将EXTEND.md写入所选路径。如果用户提供了非默认的 `data_root`,将其作为未注释的行添加到文件中;否则省略(自动使用默认值)。确认“偏好设置已保存至[路径]。随时编辑该文件可修改默认配置。”,然后继续执行摘要工作流。Workflow
工作流
Step 1: Parse the user's request
步骤1:解析用户请求
Extract:
- Group name (or partial name for fuzzy matching)
- Time range — interpret flexibly:
- "最近 1 天" / "今天" / "last 24 hours" → 1 day
- "最近 3 天" → 3 days
- "最近 7 天" / "这周" → 7 days
- "最近 30 天" / "最近一个月" → 30 days
- "某天" (e.g. "3 月 5 号") → that specific date
- "某天到某天" (e.g. "3 月 1 号到 3 月 5 号") → date range
- "从上次开始" / "继续" / "接着上次" / "since last" → incremental mode: read for this group, use
history.jsonas the startlast_digest.last_message_time - No time specified → incremental mode. If no exists yet, fall back to
history.jsonfrom EXTEND.md if set, else last 24 hours.default_time_range
- Version(s) to generate:
- Start from in EXTEND.md.
default_version - User request overrides: keywords "毒舌"/"roast"/"挑衅"/"再来个毒的"/"sass" → force . Keywords "只要正经的"/"normal only"/"不要毒舌" → force
include_roast=true. "都来一份"/"两个版本都要"/"both" → both.include_normal=true, include_roast=false - At least one of /
include_normalmust end up true.include_roast
- Start from
Convert relative ranges into absolute pairs using today's local date.
--since YYYY-MM-DD --until YYYY-MM-DD提取以下信息:
- 群组名称(或用于模糊匹配的部分名称)
- 时间范围——灵活解读:
- "最近1天" / "今天" / "last 24 hours" → 1天
- "最近3天" → 3天
- "最近7天" / "这周" → 7天
- "最近30天" / "最近一个月" → 30天
- "某天"(例如“3月5号”) → 特定日期
- "某天到某天"(例如“3月1号到3月5号”) → 日期范围
- "从上次开始" / "继续" / "接着上次" / "since last" → 增量模式:读取该群组的 ,使用
history.json作为起始时间last_digest.last_message_time - 未指定时间范围 → 增量模式。若 尚未存在,则使用EXTEND.md中的
history.json(若已设置),否则默认最近24小时。default_time_range
- 要生成的版本:
- 从EXTEND.md中的 开始。
default_version - 用户请求将覆盖默认值:关键词“毒舌”/“roast”/“挑衅”/“再来个毒的”/“sass” → 强制设置 。关键词“只要正经的”/“normal only”/“不要毒舌” → 强制设置
include_roast=true。“都来一份”/“两个版本都要”/“both” → 生成两个版本。include_normal=true, include_roast=false - 最终必须至少启用 /
include_normal中的一个。include_roast
- 从EXTEND.md中的
使用本地日期将相对时间范围转换为绝对的 参数对。
--since YYYY-MM-DD --until YYYY-MM-DDStep 2: Find the group + resolve folder path
步骤2:查找群组并解析目录路径
bash
wx contacts --query "<group_name>" --jsonFilter for entries whose ends in . If multiple groups match, use to disambiguate. If none match, fall back to and search there before asking the user.
username@chatroomAskUserQuestionwx sessions --jsonOnce resolved, compute the folder path:
{data_root}/{group_id}-{sanitized_group_name}/where is from EXTEND.md (default ).
data_root{project_root}/wechatSanitize the group name — replace any of and control characters with . Trim trailing dots and whitespace. Don't strip emoji or Chinese characters.
/ \ : * ? " < > | NUL_Group-rename detection: list existing folders under and find any folder whose name starts with . If one exists but the suffix differs (group was renamed), rename the existing folder to the new form. If a target with the new name already exists (rare), keep both and prefer the existing one for this run.
{data_root}/{group_id}-{group_id}-{sanitized_new_name}bash
wx contacts --query "<group_name>" --json筛选出 以 结尾的条目。如果有多个群组匹配,使用 让用户明确选择。如果没有匹配结果,先尝试在 中搜索,再询问用户。
username@chatroomAskUserQuestionwx sessions --json确定群组后,计算目录路径:
{data_root}/{group_id}-{sanitized_group_name}/其中 来自EXTEND.md(默认值为 )。
data_root{project_root}/wechat群组名称清理——将 及控制字符替换为 。删除末尾的点号和空白字符。保留表情符号和中文字符。
/ \ : * ? " < > | NUL_群组重命名检测:列出 下的现有目录,查找任何以 开头的目录。如果目录存在但后缀不同(群组已重命名),将现有目录重命名为新的 格式。如果新名称的目录已存在(罕见情况),则保留两个目录,并在本次运行中优先使用现有目录。
{data_root}/{group_id}-{group_id}-{sanitized_new_name}Step 3: Fetch messages
步骤3:获取消息
For small batches (single-day digest, typically < 200 messages), pipe JSON into the agent directly:
bash
wx history "<group_name_or_id>" --since YYYY-MM-DD --until YYYY-MM-DD -n 5000 --jsonFor large batches (weekly / monthly digests, > 200 messages), redirect to first so the raw payload never sits in conversation context:
$TMPDIRbash
wx history "<group_name_or_id>" --since YYYY-MM-DD --until YYYY-MM-DD -n 5000 --json > "$TMPDIR/wx-messages.json"
wc -c "$TMPDIR/wx-messages.json"
jq 'length' "$TMPDIR/wx-messages.json"Then read the file in slices via with + , or process with queries (e.g. , for a lightweight skeleton pass). Reading all 500+ messages at once will burn token budget unnecessarily.
Readoffsetlimitjqjq '.[0:200]'jq '[.[] | {id, from_nickname, timestamp, content: (.content | .[0:50])}]'Notes:
- is inclusive;
--sinceis interpreted as a date (the whole day). If the user asked for "today only", set both to today.--until - is a defensive cap; for very active groups, raise it and re-fetch.
-n 5000 - Filter the returned messages by their to be safe (some daemons may return adjacent days).
timestamp - Range splitting: for ranges > 7 days OR > 500 messages, prefer generating per-3-day digests and then a meta-summary over forcing one giant digest — the categorization quality degrades sharply past a week's worth of unrelated topics.
Incremental mode: after the fetch, drop any message whose is the from . If zero messages remain, tell the user "上次摘要后没有新消息,已跳过生成" and exit.
timestamp<=last_message_timehistory.json对于小批量消息(单日摘要,通常少于200条),直接将JSON输出传递给Agent:
bash
wx history "<group_name_or_id>" --since YYYY-MM-DD --until YYYY-MM-DD -n 5000 --json对于大批量消息(每周/每月摘要,超过200条),先将输出重定向到 ,避免原始数据占用对话上下文:
$TMPDIRbash
wx history "<group_name_or_id>" --since YYYY-MM-DD --until YYYY-MM-DD -n 5000 --json > "$TMPDIR/wx-messages.json"
wc -c "$TMPDIR/wx-messages.json"
jq 'length' "$TMPDIR/wx-messages.json"然后通过 工具结合 + 分片读取文件,或使用 查询处理(例如 、 生成轻量骨架)。一次性读取500条以上消息会不必要地消耗token配额。
Readoffsetlimitjqjq '.[0:200]'jq '[.[] | {id, from_nickname, timestamp, content: (.content | .[0:50])}]'注意事项:
- 为包含性;
--since被解析为日期(全天)。如果用户要求“仅限今天”,则将两者都设置为今天。--until - 是防御性上限;对于非常活跃的群组,可提高该值并重试获取。
-n 5000 - 为确保安全,根据消息的 过滤返回的消息(部分守护进程可能返回相邻日期的消息)。
timestamp - 范围拆分:对于超过7天或超过500条消息的时间范围,优先生成每3天的摘要,再生成汇总摘要——超过一周的无关话题会导致分类质量急剧下降。
增量模式:获取消息后,删除所有 小于等于 中 的消息。如果没有剩余消息,告知用户“上次摘要后没有新消息,已跳过生成”并退出。
timestamphistory.jsonlast_message_timeStep 3.5: Parse the message schema
步骤3.5:解析消息结构
wx history --json- /
id/msg_id— message identifier (use whichever wx-cli emits). Reference IDs in working notes as anchors when building the skeleton.local_id - — stable sender identifier
from_wxid - — display name (may be the group remark or original nickname)
from_nickname - — text payload. Examples:
content- Plain text → use as-is
- → opaque placeholder; see image handling below
[图片] - → emoji/sticker; skip in body unless surrounded by discussion
[表情] - /
[视频]→ media reference; skip unless discussed[文件] - or
[链接] <title>→ shared article; the title IS the information — quote it and credit the sharer[链接/文件] <title> - → revoked; exclude from digest and from leaderboard
[系统] ... revokemsg
- — convert to
timestampfor display (and use full ISO forMM-DD HH:MM)generated_at - — sanity-check
chat_typegroup - Quote/reply — try ,
quote_id,reply_to, or any nestedquoted_msg_idobject. If present, use it as strong attribution. If absent, fall back to context but flag the inferred link as uncertain.quote
wx history --json- /
id/msg_id——消息标识符(使用wx-cli输出的任意一个)。构建骨架时,在工作笔记中引用这些ID作为锚点。local_id - ——稳定的发送者标识符
from_wxid - ——显示名称(可能是群备注或原始昵称)
from_nickname - ——文本内容。示例:
content- 纯文本 → 直接使用
- → 不透明占位符;见下文图片处理
[图片] - → 表情/贴纸;除非有相关讨论,否则在正文中跳过
[表情] - /
[视频]→ 媒体引用;除非有相关讨论,否则跳过[文件] - 或
[链接] <title>→ 分享的文章;标题即为信息——引用标题并注明分享者[链接/文件] <title> - → 已撤回消息;从摘要和排行榜中排除
[系统] ... revokemsg
- ——转换为
timestamp格式显示(MM-DD HH:MM使用完整ISO格式)generated_at - ——检查是否为群组聊天
chat_type - 引用/回复——尝试查找 、
quote_id、reply_to或任何嵌套的quoted_msg_id对象。如果存在,将其作为强归属依据。如果不存在,则依赖上下文,但在工作笔记中标记推断的关联为不确定。quote
Step 3.6: Resolve self + ambiguous nicknames
步骤3.6:解析本人及模糊昵称
- Substitute for every message whose
self_displaymatchesfrom_wxid(from EXTEND.md). Apply this in the leaderboard, portraits, and body text. The user MUST appear under their real display name and count toward stats — never skip them.self_wxid - Scan all unique senders for ambiguous handles: ≤2 characters, common programming words (,
nil,null,test,admin,user), single emoji, or otherwise low-information. For each, runundefinedand pick a meaningful name in this priority: remark > nickname > wxid. Apply the substitution everywhere in the digest.wx contacts --query "<nick>" --json --limit 5
- 对于所有 匹配EXTEND.md中
from_wxid的消息,替换为self_wxid。该替换应用于排行榜、画像和正文中。用户必须以其真实显示名称出现并计入统计——不得跳过。self_display - 扫描所有唯一发送者的模糊昵称:≤2个字符、常见编程词汇(、
nil、null、test、admin、user)、单个表情符号或其他信息量低的昵称。对于每个此类昵称,运行undefined,并按优先级选择有意义的名称:备注 > 昵称 > wxid。将该替换应用于摘要的所有部分。wx contacts --query "<nick>" --json --limit 5
Step 3.7: Load user profiles
步骤3.7:加载用户画像
For each unique sender appearing in this batch:
- Look in by
{folder}/profiles/{wxid}-*.mdprefix match. Read the matched file if found.wxid - If , also look in
include_roastfor the roast pass.{folder}/profiles-roast/{wxid}-*.md
Compile a condensed profile context block as internal working memory — do NOT write it into the final digest. Example shape:
== 群友历史画像(来自 profiles/)==
K. H:空中直播员 / 生活百科全书。常见话题:旅行、金融、美食。经典金句:"要不要买moderna"。
可可苏玛:...Rules:
- Only load profiles for users active in this batch — never preload everyone.
- Profile is background, not template. Current messages are still the primary source.
- Use historical labels for continuity ("又双叒叕化身空中直播员") or contrast ("一向省钱的 XX 今天居然...").
- Strict separation: normal pass reads only , roast pass reads only
profiles/. Never cross-load.profiles-roast/
See references/profiles.md for the full file format.
对于本次批次中出现的每个唯一发送者:
- 通过wxid前缀匹配查找 文件。如果找到,读取该文件。
{folder}/profiles/{wxid}-*.md - 如果启用 ,同时在
include_roast中查找毒舌版画像。{folder}/profiles-roast/{wxid}-*.md
编译一个浓缩的画像上下文块作为内部工作内存——不要写入最终摘要。示例格式:
== 群友历史画像(来自profiles/)==
K. H:空中直播员 / 生活百科全书。常见话题:旅行、金融、美食。经典金句:"要不要买moderna"。
可可苏玛:...规则:
- 仅加载本次批次中活跃用户的画像——不要预加载所有用户。
- 画像为背景信息,而非模板。当前消息仍是主要信息来源。
- 使用历史标签体现连续性(“又双叒叕化身空中直播员”)或反差(“一向省钱的XX今天居然...”)。
- 严格分离:普通版仅读取 ,毒舌版仅读取
profiles/。不得交叉加载。profiles-roast/
完整文件格式请参考 references/profiles.md。
Step 3.8: Detect existing in-chat digests (optional)
步骤3.8:检测现有群内摘要(可选)
Some users (e.g., the original 宝玉 workflow) post digests directly into the group as messages. If we don't notice these, the new digest will re-cover the same ground.
Scan the fetched messages for signals of a prior in-chat digest:
- AND
from_wxid == self_wxid - contains
contentOR群聊精华OR消息统计:OR a leaderboard pattern (e.g.📊 消息统计), AND^\d+\. .+: \d+ 条 - length > 1500 chars.
content
If a match is found:
- Extract the digest's covered date or range from the title line (e.g., or
xxx 群聊精华 · 2026-05-12).... · 2026-05-10 ~ 2026-05-12 - Surface the finding to the user via :
AskUserQuestion- "Detected an in-chat digest by you covering {范围}. Use {范围 end + 1} as the start instead of ?"
history.json - Options: /
Yes, skip up to {end of detected range}/No, use history.json.No, cover everything in the requested range
- "Detected an in-chat digest by you covering {范围}. Use {范围 end + 1} as the start instead of
- Apply the chosen anchor.
This is a heuristic — when uncertain (multiple matches, malformed title), default to and tell the user what was skipped.
history.jsonGenerate the digest in three rounds so nothing slips through. The methodology stays here in SKILL.md; the content/style rules live in references/output-formats.md — read that file in Round 2 before drafting.
部分用户(例如原始宝玉工作流)会直接将摘要发布到群聊中作为消息。如果未注意到这些摘要,新生成的摘要会重复覆盖相同内容。
扫描获取的消息,查找群内已有摘要的信号:
- 且
from_wxid == self_wxid - 包含「群聊精华」或「消息统计:」或「📊 消息统计」或排行榜模式(例如
content),且^\d+\. .+: \d+ 条 - 长度超过1500字符。
content
如果找到匹配项:
- 从标题行提取摘要覆盖的日期或范围(例如「xxx群聊精华 · 2026-05-12」或「... · 2026-05-10 ~ 2026-05-12」)。
- 通过 将发现告知用户:
AskUserQuestion- “检测到您发布的群内摘要,覆盖范围为{范围}。是否使用{范围结束日期+1}作为起始时间,而非?”
history.json - 选项:「是,跳过至{检测范围结束日期}」/「否,使用history.json」/「否,覆盖请求范围内的所有内容」。
- “检测到您发布的群内摘要,覆盖范围为{范围}。是否使用{范围结束日期+1}作为起始时间,而非
- 应用用户选择的锚点。
这是一种启发式方法——当不确定时(多个匹配项、标题格式错误),默认使用 并告知用户跳过的内容。
history.json分三轮生成摘要,确保无遗漏。方法论记录在SKILL.md中;内容/风格规则记录在 references/output-formats.md 中——在第二轮起草前请阅读该文件。
Round 1 — Build the skeleton
第一轮 — 构建骨架
Read every message in order. Skip image fetching/decoding in this round. List every distinct discussion topic. Bias toward over-listing — trim in Round 3.
Internal working format (not written to the final file):
== 话题清单(共 N 条消息)==
1. [HH:MM-HH:MM] 话题名称(参与者:A, B, C)— 一句话概括(锚点 id:54052, 54055, 54063)
2. [HH:MM-HH:MM] 话题名称(参与者:D, E)— 一句话概括(锚点 id:54100-54112)
...
== 可能需要图片上下文的话题 ==
- 话题 3:锚点 id=49661(图片是讨论主体)
== 发言统计 ==
1. XXX — N 条 2. YYY — N 条 ...Topic principles:
- Topic-switch signals: time gap > 30 min, participant change, content jump.
- 2+ participants OR substantive content qualifies as a topic; pure emoji-banter does not.
- Strict attribution: each topic must record "who said what". Don't fuse adjacent messages from different senders just because they're close in time — when minutes apart or interleaved with others, split into separate topics. Prefer two topics over one wrongly-merged topic.
- Carry anchor IDs: list the key message IDs for each topic. In Round 2, jump back to these IDs in the raw messages and verify content, don't guess from context. If /
quote_idis present, use the ID chain — that's the most reliable attribution.reply_to
Flag-for-images criteria (any one triggers): an explicit comment on an image (, , ), multiple people piling onto the same image without saying what it is, an image as the core information (晒单/截图/资料), an explanatory line right after an image (, ), or cross-sender ambiguity (B says "这个看着像 X" but the previous image is from A).
看发型是X?这是谁?笑死gpt-image-2太可怕了按顺序读取每条消息。本轮跳过图片获取/解码。列出所有不同的讨论话题。倾向于多列话题——在第三轮中再进行删减。
内部工作格式(不写入最终文件):
== 话题清单(共N条消息)==
1. [HH:MM-HH:MM] 话题名称(参与者:A, B, C)— 一句话概括(锚点ID:54052, 54055, 54063)
2. [HH:MM-HH:MM] 话题名称(参与者:D, E)— 一句话概括(锚点ID:54100-54112)
...
== 可能需要图片上下文的话题 ==
- 话题3:锚点ID=49661(图片是讨论主体)
== 发言统计 ==
1. XXX — N条 2. YYY — N条 ...话题原则:
- 话题切换信号:时间间隔超过30分钟、参与者变化、内容跳转。
- 至少2名参与者或实质性内容才算作话题;纯表情互动不算。
- 严格归属:每个话题必须记录“谁说了什么”。不要仅仅因为时间接近就合并不同发送者的相邻消息——如果间隔数分钟或与其他消息交错,应拆分为不同话题。宁愿分两个话题,也不要合并错误的话题。
- 携带锚点ID:列出每个话题的关键消息ID。在第二轮中,跳转到原始消息中的这些ID并验证内容,不要仅凭上下文猜测。如果存在 /
quote_id,使用ID链——这是最可靠的归属依据。reply_to
图片标记标准(满足任意一项即触发):对图片的明确评论(「看发型是X?」「这是谁?」「笑死」)、多人针对同一张图片讨论但未说明内容、图片为核心信息(晒单/截图/资料)、图片后紧跟解释性文字(「gpt-image-2」「太可怕了」)或跨发送者歧义(B说「这个看着像X」但上一张图片来自A)。
Round 2 — Flesh out + write the digest
第二轮 — 填充内容并写入摘要
For each topic in the skeleton, jump back to its anchor IDs and expand into full content with quotes and clear attribution. Then write the digest file.
Image handling (limited — wx-cli does not decode chat images):
For each flagged topic, check whether a description file already exists at . If yes, read it (one-line plain text) and weave its content into the topic. If no, treat the image as opaque () and write around it — describe what the surrounding messages tell us, but don't invent visual content.
{folder}/imgs/{message_id}.txt[图片]The directory exists as an extension point: a user (or a future wx-cli capability) can drop files with one-line descriptions, and the skill will pick them up. The skill itself does NOT generate these files in this version.
imgs/{message_id}.txtUse the profile context block (from Step 3.7):
- Echo continuity for matching behavior ("又双叒叕直播飞行体验")
- Highlight contrast for departures ("一向话少的 XX 今天突然爆发")
- Callback past quotes ("继上次'要不要买 moderna'之后,这次又...")
- Don't sacrifice current material to force a callback.
Writing order: write the body categories first, then the opening overview based on the finished body (so the hook is accurate).
Detailed structure, voice, formatting rules, and content guidelines are in references/output-formats.md. Load that file now if not already loaded.
针对骨架中的每个话题,跳转到其锚点ID,扩展为包含引用和明确归属的完整内容。然后写入摘要文件。
图片处理(有限支持——wx-cli不解码聊天图片):
对于每个标记的话题,检查 是否存在描述文件。如果存在,读取文件内容(单行纯文本)并将其融入话题内容。如果不存在,则将图片视为不透明的 ,围绕其进行描述——根据周围消息说明讨论的内容,但不要编造视觉信息。
{folder}/imgs/{message_id}.txt[图片]imgs/{message_id}.txt使用画像上下文块(来自步骤3.7):
- 呼应匹配行为的连续性(「又双叒叕直播飞行体验」)
- 突出与以往行为的反差(「一向话少的XX今天突然爆发」)
- 回调过往金句(「继上次'要不要买moderna'之后,这次又...」)
- 不要为了强行回调而牺牲当前内容。
写作顺序:先写正文分类内容,再根据完成的正文写开头概述(确保引言准确)。
详细的结构、语气、格式规则和内容指南请参考 references/output-formats.md。如果尚未加载,请立即加载该文件。
Round 3 — Audit
第三轮 — 审核
Walk the Round 1 skeleton against the finished digest. Check:
- Any listed topic missing from the digest?
- Quotes, names, product/tool names preserved verbatim?
- Categorization makes sense — is anything in the wrong bucket?
Fix in place. When clean, confirm and proceed.
将第一轮的骨架与完成的摘要进行比对。检查:
- 是否有列出的话题未包含在摘要中?
- 引用、名称、产品/工具名称是否保留原文?
- 分类是否合理——是否有内容放错分类?
就地修复。确认无误后继续执行。
Step 7: Save the digest file(s)
步骤7:保存摘要文件
If :
include_normal- Single date →
{folder}/YYYY-MM-DD.md - Date range →
{folder}/YYYY-MM-DD_YYYY-MM-DD.md - Overwrite if the same date/range already exists.
If :
include_roast- Same naming, but with suffix:
-roastorYYYY-MM-DD-roast.md.YYYY-MM-DD_YYYY-MM-DD-roast.md
Both versions share the same statistics (message count, leaderboard) and the same underlying skeleton.
如果启用 :
include_normal- 单日 →
{folder}/YYYY-MM-DD.md - 日期范围 →
{folder}/YYYY-MM-DD_YYYY-MM-DD.md - 如果相同日期/范围的文件已存在,则覆盖。
如果启用 :
include_roast- 命名规则相同,但添加 后缀:
-roast或YYYY-MM-DD-roast.md。YYYY-MM-DD_YYYY-MM-DD-roast.md
两个版本共享相同的统计信息(消息数量、排行榜)和底层骨架。
Step 8: Save history (two files)
步骤8:保存历史记录(两个文件)
Maintain two files in the group folder:
在群组目录中维护两个文件:
history.json
— single record, fast read
history.jsonhistory.json
— 单条记录,快速读取
history.jsonAlways reflects only the most recent normal digest. Overwrite on each run when .
include_normal=truejson
{
"group_id": "12345678901@chatroom",
"group_name": "相亲相爱一家人",
"folder": "12345678901@chatroom-相亲相爱一家人",
"last_digest": {
"file": "2026-03-12.md",
"date_range": "2026-03-12",
"generated_at": "2026-03-12T10:30:00+08:00",
"message_count": 150,
"last_message_time": "03-12 18:45"
}
}- updates on every run (handles renames).
group_name - records the current folder basename for cross-reference.
folder - is the timestamp of the most recent message included, in
last_message_time— used by incremental mode.MM-DD HH:MM - Roast-only runs do NOT touch this file.
始终仅反映最新的普通版摘要。当 时,每次运行都会覆盖该文件。
include_normal=truejson
{
"group_id": "12345678901@chatroom",
"group_name": "相亲相爱一家人",
"folder": "12345678901@chatroom-相亲相爱一家人",
"last_digest": {
"file": "2026-03-12.md",
"date_range": "2026-03-12",
"generated_at": "2026-03-12T10:30:00+08:00",
"message_count": 150,
"last_message_time": "03-12 18:45"
}
}- 在每次运行时更新(处理群组重命名)。
group_name - 记录当前目录的基名,用于交叉引用。
folder - 是包含的最新消息的时间戳,格式为
last_message_time——供增量模式使用。MM-DD HH:MM - 仅生成毒舌版的运行不会修改此文件。
history-digests.jsonl
— append-only archive
history-digests.jsonlhistory-digests.jsonl
— 追加式归档
history-digests.jsonlOne JSON object per line, same shape as . Every normal-version run appends one line (in chronological order). Used by backfill and historical lookups. Never read for incremental mode (which only needs the latest).
last_digestjsonl
{"file":"2026-03-10.md","date_range":"2026-03-10","generated_at":"2026-03-10T09:00:00+08:00","message_count":420,"last_message_time":"03-10 22:30"}
{"file":"2026-03-11.md","date_range":"2026-03-11","generated_at":"2026-03-11T09:05:00+08:00","message_count":312,"last_message_time":"03-11 23:10"}
{"file":"2026-03-12.md","date_range":"2026-03-12","generated_at":"2026-03-12T10:30:00+08:00","message_count":150,"last_message_time":"03-12 18:45"}If a normal digest with the same name is regenerated, append a new line anyway (the JSONL is a strict log; readers can dedupe by if they need to).
filefile每行一个JSON对象,格式与 相同。每次普通版运行都会追加一行(按时间顺序)。供回溯和历史查询使用。增量模式不会读取此文件(仅需最新记录)。
last_digestjsonl
{"file":"2026-03-10.md","date_range":"2026-03-10","generated_at":"2026-03-10T09:00:00+08:00","message_count":420,"last_message_time":"03-10 22:30"}
{"file":"2026-03-11.md","date_range":"2026-03-11","generated_at":"2026-03-11T09:05:00+08:00","message_count":312,"last_message_time":"03-11 23:10"}
{"file":"2026-03-12.md","date_range":"2026-03-12","generated_at":"2026-03-12T10:30:00+08:00","message_count":150,"last_message_time":"03-12 18:45"}如果重新生成了相同文件名的普通版摘要,仍需追加新行(JSONL是严格的日志;读取者可根据 去重)。
fileStep 8.5: Update user profiles
步骤8.5:更新用户画像
For each user with 3+ messages in this batch who appeared in the 群友画像 section:
- If , update
include_normal.{folder}/profiles/{wxid}-{nickname}.md - If , update
include_roast.{folder}/profiles-roast/{wxid}-{nickname}.md
Counts, frontmatter updates, append-only rules for quotes and events, and privacy guardrails are detailed in references/profiles.md. Load that file when running this step.
对于本次批次中发言3条以上且出现在群友画像部分的每个用户:
- 如果启用 ,更新
include_normal。{folder}/profiles/{wxid}-{nickname}.md - 如果启用 ,更新
include_roast。{folder}/profiles-roast/{wxid}-{nickname}.md
统计计数、前置更新、金句和事件的追加规则以及隐私防护机制详细记录在 references/profiles.md 中。执行此步骤时请加载该文件。
Completion checklist
完成检查清单
Profile updates are easy to forget once the digest is on disk. Before reporting the run as "done", verify every applicable file:
- written (if
{folder}/YYYY-MM-DD.md)include_normal - written (if
{folder}/YYYY-MM-DD-roast.md)include_roast - overwritten with the new
{folder}/history.json(iflast_digest)include_normal - appended one line (if
{folder}/history-digests.jsonl)include_normal - updated for every user with 3+ messages (if
{folder}/profiles/{wxid}-*.md)include_normal - updated for every user with 3+ messages (if
{folder}/profiles-roast/{wxid}-*.md)include_roast
If any item is unchecked, finish it before declaring success. Don't ship a digest with a stale — incremental mode depends on it.
history.json摘要写入磁盘后,很容易忘记更新画像。在报告运行“完成”之前,请验证所有适用文件:
- 已写入(如果启用
{folder}/YYYY-MM-DD.md)include_normal - 已写入(如果启用
{folder}/YYYY-MM-DD-roast.md)include_roast - 已用新的
{folder}/history.json覆盖(如果启用last_digest)include_normal - 已追加一行(如果启用
{folder}/history-digests.jsonl)include_normal - 已为所有发言3条以上的用户更新(如果启用
{folder}/profiles/{wxid}-*.md)include_normal - 已为所有发言3条以上的用户更新(如果启用
{folder}/profiles-roast/{wxid}-*.md)include_roast
如果有任何未勾选的项,请完成后再宣告成功。不要交付带有过期 的摘要——增量模式依赖于此文件。
history.jsonStep 9: Backfill (user-triggered)
步骤9:回溯画像(用户触发)
When the user says "回溯画像" / "初始化画像" / "backfill profiles":
- Confirm the target group (if not specified, ask which one).
- List all digest files in and
{folder}/.history-digests.jsonl - Read existing digests in batches of 10–15 to avoid context blowup.
- For users appearing in 3+ digests, seed profile files using their leaderboard counts, portrait paragraphs, and quoted lines from the historical digests.
- Write to (and
profiles/if anyprofiles-roast/files exist).-roast.md - Report back: how many profiles were created, how many users covered.
Full procedure in references/profiles.md.
当用户请求「回溯画像」「初始化画像」「backfill profiles」时:
- 确认目标群组(如果未指定,询问用户具体是哪个群组)。
- 列出 中的所有摘要文件和
{folder}/。history-digests.jsonl - 批量读取现有摘要,每次10-15个,避免上下文过载。
- 对于出现在3个以上摘要中的用户,使用其排行榜计数、画像段落和历史摘要中的引用语句生成初始画像文件。
- 写入 (如果存在
profiles/文件,同时写入-roast.md)。profiles-roast/ - 反馈结果:创建了多少个画像,覆盖了多少用户。
完整流程请参考 references/profiles.md。
Storage layout
存储结构
{data_root}/ # default: {project_root}/wechat/
└── {group_id}-{group_name}/ # e.g. 12345678901@chatroom-相亲相爱一家人/
├── history.json # last digest pointer (fast)
├── history-digests.jsonl # append-only archive
├── 2026-03-12.md # normal digest, single date
├── 2026-03-12-roast.md # roast digest (only if generated)
├── 2026-03-10_2026-03-12.md # normal digest, date range
├── profiles/ # normal user profiles
│ ├── onlytiancai-胡浩🐸.md
│ └── ...
├── profiles-roast/ # roast user profiles (only if any roast generated)
│ ├── onlytiancai-胡浩🐸.md
│ └── ...
└── imgs/ # optional image-description files
├── 49661.txt # one-line plain text description
└── ...{data_root}/ # 默认值:{project_root}/wechat/
└── {group_id}-{group_name}/ # 示例:12345678901@chatroom-相亲相爱一家人/
├── history.json # 最新摘要指针(快速读取)
├── history-digests.jsonl # 追加式归档
├── 2026-03-12.md # 普通版摘要,单日
├── 2026-03-12-roast.md # 毒舌版摘要(仅当生成时存在)
├── 2026-03-10_2026-03-12.md # 普通版摘要,日期范围
├── profiles/ # 普通版用户画像
│ ├── onlytiancai-胡浩🐸.md
│ └── ...
├── profiles-roast/ # 毒舌版用户画像(仅当生成过毒舌版时存在)
│ ├── onlytiancai-胡浩🐸.md
│ └── ...
└── imgs/ # 可选图片描述文件
├── 49661.txt # 单行纯文本描述
└── ...wx-cli quick reference
wx-cli快速参考
| Command | Purpose |
|---|---|
| Sanity-check that wx-cli is installed |
| List recent sessions; useful for verifying init and finding the user's own wxid |
| Fuzzy-match contacts/groups by display name, remark, or wxid |
| Pull a group's messages within a date range as JSON |
| List a group's members (rarely needed; mostly for completeness) |
| wx-cli's built-in stats; we compute our own from |
| Daemon lifecycle (troubleshooting) |
All commands accept for machine-readable output. Default output is YAML — only use it for human eyeballing during debugging.
wx--json| 命令 | 用途 |
|---|---|
| 检查wx-cli是否已安装 |
| 列出最近的会话;用于验证初始化状态和查找用户本人的wxid |
| 根据显示名称、备注或wxid模糊匹配联系人/群组 |
| 获取指定日期范围内的群组消息,以JSON格式输出 |
| 列出群组成员(很少需要;主要用于完整性) |
| wx-cli内置的统计功能;我们通过 |
| 守护进程生命周期管理(故障排查) |
所有 命令均接受 参数以输出机器可读格式。默认输出为YAML——仅在调试时供人工查看使用。
wx--jsonTroubleshooting
故障排查
When a command fails, diagnose by the symptom, not by retrying blindly. Common patterns:
wx| Symptom | Cause | Fix (tell the user to run these — do NOT run |
|---|---|---|
| Sandbox is on | Re-run the command with |
| | |
| Daemon is stuck | |
| Keys went stale (WeChat restart, version upgrade) | Make sure WeChat is running, then |
| Group is folded into 折叠群 or the daemon hasn't indexed it yet | |
Messages returned but | Date string not in | Confirm the dates are local-time |
| Empty result for a chat that should have activity | | Raise |
Recovery order when nothing makes sense:
- Is WeChat running?
- Is owned by
~/.wx-cli?$(whoami) - Is the daemon healthy? ()
wx daemon status - Restart the daemon ()
wx daemon stop && wx daemon start - Last resort: (while WeChat is running)
wx init --force
Never auto-retry inside the skill — every failure should produce a clear diagnostic plus the exact command the user needs to run.
当 命令失败时,请根据症状诊断,不要盲目重试。常见模式:
wx| 症状 | 原因 | 修复方法(告知用户执行以下命令——不得代表用户运行 |
|---|---|---|
| 沙箱已启用 | 使用 |
| | |
| 守护进程卡住 | |
守护进程正常工作后提示 | 密钥失效(微信重启、版本升级) | 确保微信正在运行,然后执行 |
| 群组被折叠到「折叠群」中,或守护进程尚未索引 | 在 |
返回消息但 | 日期字符串格式不是 | 确认日期为本地时间的 |
| 本应有消息的聊天返回空结果 | | 提高 |
当所有方法都无效时的恢复顺序:
- 微信是否正在运行?
- 是否归当前用户
~/.wx-cli所有?$(whoami) - 守护进程是否健康?()
wx daemon status - 重启守护进程()
wx daemon stop && wx daemon start - 最后手段:(微信运行时执行)
wx init --force
不得在技能内部自动重试——每次失败都应给出明确的诊断信息和用户需要执行的具体命令。
Notes and limitations
注意事项与限制
- Image content is opaque. wx-cli does not decode chat images. The skill respects an extension point but does not auto-populate it. When a topic depends heavily on an image with no description file, the digest should say so honestly rather than invent visual content.
imgs/{message_id}.txt - Reply attribution is best-effort. If wx-cli's output exposes a quote/reply field, use it. Otherwise fall back to context and flag uncertain inferences in working notes.
- Local time only. Date parsing uses the agent's local time zone. Cross-time-zone group members may show timestamps that don't match their wall clock. Per the format rules, never use timestamps to infer sleep or location.
- wx-cli reinit. If suddenly returns nothing after a WeChat restart, the keys may be stale. Tell the user to run
wx history(while WeChat is running) and retry.sudo wx init --force
- 图片内容不透明。wx-cli不解码聊天图片。本技能支持 扩展点,但不会自动填充该文件。当某个话题严重依赖无描述文件的图片时,摘要应如实说明,而非编造视觉内容。
imgs/{message_id}.txt - 回复归属为尽力而为。如果wx-cli的输出提供了引用/回复字段,则使用该字段。否则依赖上下文,并在工作笔记中标记不确定的推断。
- 仅支持本地时间。日期解析使用Agent的本地时区。跨时区的群成员可能显示与他们本地时间不符的时间戳。根据格式规则,不得使用时间戳推断睡眠状态或位置。
- wx-cli重新初始化。如果微信重启后 突然无返回结果,可能是密钥失效。告知用户在微信运行时执行
wx history并重试。sudo wx init --force