supsub-sub Skill
List, add, remove subscription sources, and browse the articles inside each source. Sources are typed MP(微信公众号)、WEBSITE(网站)或 X(推特 / Twitter 平台账号)。
⚠️ 关于字母「X」的歧义(务必先读)
本文档里 X 有两种完全不同的含义,不要混淆:
--type X—— 这是一个真实的 sourceType 枚举值,专指 推特 / Twitter / X 平台账号。- 占位符 X —— 用户口语里说「订阅 X」「取消订阅 X」「X 公众号最近的文章」时,这里的 X 通常只是一个占位代号,代指某个具体账号(可能是公众号、网站,也可能就是名字里带 X),与推特无关。
判定规则:
- 只有当用户**明确提到「推特 / Twitter / X 平台」**时,才使用
--type X。 - 用户说「订阅 X」「X 公众号」「看 X 的文章」而没有提到推特平台时,不要据此推断
--type X;这里的 X 是占位符,应按其真实类型(多为MP或WEBSITE)处理。
Prerequisites
- 安装:
curl -fsSL https://raw.githubusercontent.com/SupSub-AI/supsub-cli/master/scripts/install.sh | bash(native 安装,装到~/.local、支持后台自动更新);或包管理器npm i -g @supsub/cli/pnpm add -g @supsub/cli - 已登录:
supsub auth status显示 Authenticated(首次使用先supsub auth login) - 未授权(exit 2 / UNAUTHORIZED)时不要止步于「你未登录」:直接运行
supsub auth login为用户打开浏览器授权(命令会自动打开浏览器并阻塞等待授权,请用足够长的超时,如 10 分钟;用户只需在浏览器点确认,无需在终端输入任何内容),授权成功后重试原命令。无浏览器 / 无头环境再回退为提示用户SUPSUB_NO_BROWSER=1 supsub auth login。
Commands
List subscriptions
supsub sub list [--type <MP|WEBSITE|X>]
| Flag | Default | Description |
|---|---|---|
--type |
— (all) | 过滤来源类型:MP(公众号)/ WEBSITE(网站)/ X(推特) |
Each row includes: sourceId, sourceType, name, img, description, unreadCount。表格模式下 类型 列会把 sourceType 显示为中文:MP→公众号、WEBSITE→网站、X→推特。
# 列出全部订阅
supsub sub list
# 仅看公众号
supsub sub list --type MP
# 仅看网站
supsub sub list --type WEBSITE
# 仅看推特(X 平台账号)
supsub sub list --type X
# 导出 JSON
supsub sub list -o json
JSON shape: {"success":true,"data":[{"sourceType":"MP","sourceId":12345,"name":"...","img":"...","description":"...","unreadCount":3}, ...]}
Add a subscription
sub add 有两条互斥入口,对应两种"拿到的 ID 形态":
# A) 全局搜索 / 已收录源 → 用内部正整数 sourceId
supsub sub add --source-id <id> --type <MP|WEBSITE|X> [--group <gid>]...
# B) mp search 发现的微信原生公众号 → 用 base64 字符串 mpId
supsub sub add --mp-id <mpId> [--type MP] [--group <gid>]...
| Flag | Required | Description |
|---|---|---|
--source-id |
二选一 | 信息源 ID(正整数)。来自 supsub search / supsub sub list 的 sourceId |
--mp-id |
二选一 | 公众号 mpId(base64 字符串)。来自 supsub mp search 返回结果 |
--type |
见说明 | --source-id 模式必填,取 MP / WEBSITE / X(推特);--mp-id 模式可省,传了必须是 MP |
--group |
no | 分组 ID(数字,可重复指定多个) |
互斥:
--source-id与--mp-id必须恰好二选一。同时给 / 都不给都会抛INVALID_ARGS。两条路径走的是不同 endpoint:
--source-id→POST /api/subscriptions(已收录源订阅);--mp-id→POST /api/mps(按微信原生 ID 把新公众号纳入并订阅)。
# 路径 A:sub list / search 看到的内部 sourceId(公众号)
supsub sub add --source-id 12345 --type MP
# 路径 A:网站 + 多分组
supsub sub add --source-id 67890 --type WEBSITE --group 1 --group 2
# 路径 A:推特账号(用户明确说要订阅某个 Twitter / X 平台账号时)
supsub sub add --source-id 24680 --type X
# 路径 B:mp search 拿到的 mpId(base64)
supsub sub add --mp-id "MzkyNTYzODk0NQ=="
# 路径 B + 分组
supsub sub add --mp-id "MzkyNTYzODk0NQ==" --group 3
# JSON 输出
supsub sub add --source-id 12345 --type MP -o json
JSON shape: {"success":true,"data":{"message":"..."}}
--source-id必须是正整数(非数字 →INVALID_ARGS);--mp-id是字符串,不做格式校验,由后端裁决;--group接受多个数字 ID,非数字会报INVALID_ARGS(exit 64)。
Remove a subscription
supsub sub remove --source-id <id> --type <MP|WEBSITE|X>
| Flag | Required | Description |
|---|---|---|
--source-id |
yes | 信息源 ID(正整数) |
--type |
yes | MP(公众号)/ WEBSITE(网站)/ X(推特) |
⚠️ 退订是破坏性操作,执行前必须向用户二次确认:复述源名称(从
sub list取),获得明确同意后才执行。 与 mark-read 一样,CLI 不会再问一次——把命令交给用户自己在终端跑时,不要说「执行后会提示确认」。⚠️ 退订会连带把该源移出它所在的全部分组(实测:订阅并入组后退订,
group subs返回空)。 分组本身不受影响。重新订阅不会自动恢复原来的分组归属,需要再跑一次group add-sub。 因此用户说「先退订,回头再订回来」时,要提醒他分组归属会丢。
# 取消订阅某个公众号(需用户确认后执行)
supsub sub remove --source-id 12345 --type MP
# 取消订阅某个网站
supsub sub remove --source-id 67890 --type WEBSITE -o json
# 取消订阅某个推特账号
supsub sub remove --source-id 24680 --type X
JSON shape: {"success":true,"data":{"message":"..."}}
Browse articles in a subscription
supsub sub contents --source-id <id> --type <MP|WEBSITE|X> [--unread | --all] [--brief] [--page <n>] [--page-size <n>]
| Flag | Default | Description |
|---|---|---|
--source-id |
required | 信息源 ID(正整数)。别名 --id(与 focus contents --id 对齐,两个写法等价) |
--type |
required | MP(公众号)/ WEBSITE(网站)/ X(推特) |
--unread |
(default) | 仅返回未读文章 |
--all |
— | 返回全部文章(已读 + 未读) |
--brief |
— | 精简输出:只留 contentId / title / publishedAt(+Text) / isRead |
--page |
1 | 页码(正整数) |
--page-size |
20 | 每页条数,1-100 |
⚠️
--unread与--all互斥:同时指定会抛INVALID_ARGS。不传任何一个时,默认行为等价于--unread。
⚠️ 「最近」不等于「未读」—— 按用户意图选 flag,别让默认值替用户做决定:
| 用户说法 | 意图 | 用哪个 |
|---|---|---|
| 「看一下最近 X 的内容」「X 最近有什么」「X 讲了什么」「X 更新了吗」 | 按时间看这个源在讲什么 | --all,isRead 只用来标注(「其中 3 篇你还没看过」),不要拿来过滤 |
| 「X 还有什么没看的」「X 的未读」「X 剩几篇没读」 | 明确要未读 | 默认(--unread) |
| 说不清 | —— | --all + 标注哪些未读 |
用默认值应付「看一下最近 X 的内容」会踩这个坑:该源已经读完时返回空数组,于是回复「没有内容」—— 可用户问的是「最近」,不是「没读的」。只有用户话里出现「未读 / 没看的 / 没读的 / 清未读」才该过滤。
批量枚举一律加 --brief:默认输出每条都带 summary / coverImage / tags,100 条约 90KB,
足以撑爆工具输出上限被迫落盘;--brief 同样 100 条只有约 14KB。只在需要给用户讲内容时才用完整输出。
每行字段:contentId, url, title, coverImage, tags[], summary, publishedAt, publishedAtText, isRead。
--brief 时只有 contentId, title, publishedAt, publishedAtText, isRead。
publishedAt是 Unix 秒级时间戳(少数后端版本为字符串),publishedAtText是 CLI 已格式化好的"YYYY-MM-DD HH:mm",直接用即可,不要自己写date -r/ 时间戳转换。排序仍用publishedAt。
# 默认(未读)—— 看某个公众号里有哪些文章
supsub sub contents --source-id 12345 --type MP
# 全部文章(含已读)
supsub sub contents --source-id 12345 --type MP --all
# 看更早的文章:翻到第 2 页
supsub sub contents --source-id 12345 --type MP --page 2
# 一次多拿一些(上限 100)
supsub sub contents --source-id 12345 --type MP --page-size 100 -o json
# 全部文章 + JSON
supsub sub contents --source-id 12345 --type MP --all -o json
JSON shape: {"success":true,"data":[{"contentId":"...","url":"...","title":"...","coverImage":"...","tags":[...],"summary":"...","publishedAt":<timestamp 或字符串>,"isRead":false}, ...]}
publishedAt的原始形态随后端版本而异(Unix 秒数字 /"YYYY-MM-DD HH:mm:ss"字符串), 不用自己兼容——直接读 CLI 补好的publishedAtText(恒为"YYYY-MM-DD HH:mm")。翻页策略:浏览类请求只拉第 1 页并告知总量;「全部 / 统计 / 导出」类请求用
--page-size 100自动循环翻页(返回条数 < page-size 即末页),不要逐页询问用户。详见supsub-unread。
Mark a source as read(整源标记已读)
supsub sub mark-read --source-id <id> --type <MP|WEBSITE|X> --all
| Flag | Required | Description |
|---|---|---|
--source-id |
yes | 信息源 ID(正整数) |
--type |
yes | MP / WEBSITE / X(推特) |
--all |
yes | 确认整源全部标为已读(不可逆) |
⚠️ 只支持整源已读,没有单篇已读。 文章没有「详情 / 阅读」这一步,产生不了自然的单篇已读事件,所以 CLI 只保留用户明确表达的「这个号我读完了」。传
--content-id会抛INVALID_ARGS。用户说「把这篇标为已读」时如实说明不支持,并问是否要整源已读。⚠️
--all必填且不可逆(CLI 无 mark-as-unread)。执行前必须向用户二次确认:复述源名称 + 当前未读数(从sub list/supsub unread取),获得明确同意后才执行。CLI 不会再问一次:这条命令没有交互式确认提示,一执行就生效。若把命令交给用户自己在终端跑, 不要说「执行后 CLI 会提示确认」——那是不存在的安全网。
# 整源已读(需用户确认后执行)
supsub sub mark-read --source-id 12345 --type MP --all -o json
JSON shape: {"success":true,"data":{"message":"已标记该订阅源全部内容为已读(不可逆)"}}
Agent Usage Notes
- 解析数据时统一用
-o json;表格输出有截断、列宽限制,不适合做下游处理。 - 所有 JSON 响应都是
{"success":true,"data":<payload>}结构(来自src/ui/output.ts)。 --type取值是MP/WEBSITE/X(CLI 内部会toUpperCase,大小写不敏感);常见错误是写成mp_account/wechat/rss/TWITTER会报INVALID_ARGS——推特只接受X,不接受TWITTER。- 再次强调字母「X」的歧义:
--type X= 推特平台账号。用户口语里「订阅 X」「X 公众号」「看 X 的文章」中的 X 多半是占位符代指某个具体账号,不要因为出现字母 X 就传--type X;只有用户明确提到「推特 / Twitter / X 平台」时才用--type X。 - 添加订阅前先想清楚 ID 来源:
- 来自
supsub search/sub list的 内部正整数sourceId→sub add --source-id <id> --type ... - 来自
supsub mp search的 base64 字符串mpId→sub add --mp-id <mpId>(type 默认 MP,可省) - 不要把
mp search的mpId强转成数字塞进--source-id—— 那是不同 ID 空间,会被后端拒。
- 来自
sub contents默认只看未读 —— 「看一下最近 X 的内容」「这个号有哪些文章」「X 讲了什么」这类按时间浏览的请求一律加--all,把isRead当标注而不是过滤条件;只有用户明说「未读 / 没看的」才用默认。sub contents默认每页 20 条,用--page/--page-size(上限 100)翻页取更早的历史。- 标记已读只有整源一档(
mark-read --all):没有单篇已读,因为文章没有「详情 / 阅读」这一步。整源已读不可逆,执行前必须向用户二次确认。跨源的未读概览与清未读工作流见supsub-unread。 - 分组管理(把这个源加进某个分组、分组里有哪些订阅)见
supsub-group;订阅时可直接用sub add --group <gid>一步入组。 - 重复订阅不是错误:实测对同一个源再跑一次
sub add返回{"success":true,"data":{"message":"订阅成功"}},exit0(幂等)。不要据此判断"是否已订阅"——要判断请看sub list或搜索结果里的isSubscribed。重复退订才会报错(订阅不存在,400 → exit1),两者不对称。 - Exit codes:
0OK,2UNAUTHORIZED,64INVALID_ARGS(--type/--source-id/--group/--page/--page-size校验失败、--all与--unread互斥、mark-read 未给--all或误传--content-id),1业务错误(后端 4xx,如源不存在 / 未订阅 / 重复退订),10网络错误,11服务端错误(5xx)。