59 KiB
AGENTS.md — catbus(猫巴士)
本文件是本项目给所有 AI 编码代理(Claude Code / Codex / Cursor 等)的统一说明,也是 catbus 的规范本身。
CLAUDE.md通过@AGENTS.md引用本文件,只维护这一份。
1. 目标
做一个 CLI —— 操作各大平台接口的统一入口。
- 一个命令
catbus,按统一的命令规范调用 10 个平台:小红书、抖音、TikTok、B 站、快手、微博、闲鱼、淘宝、京东、X。 - 能力来源是作者 cv-cat 的 10 个开源仓库,已克隆到
references/,作为只读的原始来源。 - 所有平台都在本仓库里用 TypeScript 重写,不在运行时调用上游的 Python。
- 用户只需要安装 Node(版本见 7.1)。不需要 Python,不需要 C/C++ 编译器,也不需要其他系统依赖。
npm i -g catbus-cli装完就能离线使用。- 必须支持全部目标系统(见 7.2)。
- 以 npm 包分发,不发 PyPI。安装体积不是约束,优先保证开箱即用。
- 上游目前只有 web 端接口。规范为 app 端、pc 端预留位置,例如
catbus xianyu item get <id> -e app。 - 登录态统一保存在
~/.catbus/,按 平台 × 端 × 账号 隔离。web 端不登录几乎看不到内容,所以都需要登录;游客态留给 app / pc 端。 - 输出对人和对 Agent 都友好:stdout 只输出 JSON,字段跨平台统一。
- 同义同名:含义相同的能力在所有平台上用同一个命令、同一组参数、同一种输出结构。例如商品一律叫
item,搜索一律叫search。 - 将来的非 JS 上游(Go / Java……)通过 provider 协议接入(见 7.8)。
1.1 命名(已定)
| 项 | 取值 |
|---|---|
| 品牌 | catbus(猫巴士):一只能载你去任何地方的猫,呼应 cv-cat |
| slogan | 上车,开往任何平台 / all aboard, every platform |
| 命令 | catbus |
| npm 主包 | catbus-cli(npm 上的 catbus 已被别的库占用) |
| npm scope | @cv-cat,用于 @cv-cat/catbus-assets-jd、@cv-cat/catbus-assets-ocr 和将来的 provider 包 |
| 仓库 | https://github.com/cv-cat/catbus ,默认分支 master |
| 许可证 | MIT(主包、assets-jd、assets-ocr 相同) |
| 数据目录 | ~/.catbus/,可用 CATBUS_HOME 覆盖 |
| 环境变量前缀 | CATBUS_ |
| Logo | assets/logo.svg(图标)、assets/logo-wordmark.svg(横版)、assets/banner.txt(终端字符画)。猫巴士:车身就是一只橘猫,车头是猫脸和耳朵,车尾是尾巴,轮毂是肉垫;车顶路牌是终端提示符 >_;车窗里坐着小红书红、B 站粉、抖音青三只小猫乘客。会颠簸、转轮子、摇尾巴、眨眼、抖耳朵;字符画沿用 ( >ω_ )。配色取自 Catppuccin Mocha |
2. 范围
- 目前支持第 3 节的 10 个平台的 web 端;app / pc 端在注册表里为 planned(见 4.3)。
- 矩阵里的 ○(平台有这个概念、上游没有的能力)只占位:注册为 planned,执行时返回 NOT_IMPLEMENTED,有需要时再实现。移植中发现矩阵与上游不符(上游其实没有、或只是占位)的能力,按上游实际情况标为 ○ 或 ◐,以
docs/capabilities.md为准。 - 新平台开始移植时,先把平台 id、别名、
item叫法等登记到第 3 节、4.2 和 4.5,再进注册表;在那之前catbus <平台>是未知平台(USAGE)。 - 改规范先改本文件,再写代码。
3. 参考仓库 references/
| 平台 id | 仓库 | 主要入口 |
|---|---|---|
| xhs | Spider_XHS | apis/xhs_pc_apis.py、apis/xhs_creator_apis.py、xhs_utils/ |
| douyin | DouYin_Spider | dy_apis/douyin_api.py、dy_apis/douyin_creator_api.py、dy_apis/login_api.py |
| tiktok | TiktokApis | api/tiktok.py、api/tiktok_web.py、signing/ |
| bilibili | BilibiliApis | apis/bili_apis.py、apis/bili_*_apis.py、builder/auth.py |
| kuaishou | KuaiShou-Spider | ks_apis/kuaishou_api.py、ks_apis/publish_api.py、ks_apis/login_api.py |
| WeiboApis | apis/weibo_apis.py、apis/weibo_mobile_apis.py、apis/weibo_creator_apis.py |
|
| xianyu | XianYuApis | goofish_apis.py、goofish_live.py |
| taobao | TaoBaoApis | taobao_apis.py、taobao_live.py、utils/taobao_utils.py |
| jd | JdApis | jd_apis/jd_api.py、jd_apis/jd_login_api.py、builder/auth.py |
| x | XApis | x_apis/x_api.py、x_apis/x_write_api.py、x_apis/login_api.py |
参考资料:
- 每个仓库的鉴权、登录、API 方法、JS 资产、游客态生成位置,以及 Python → JS 的替换对照,见 docs/upstream-map.md。
- 各平台的能力矩阵见 docs/capabilities.md。
规则:
references/只读。- 可以阅读,也可以运行(用来生成对拍数据,见 7.5)。
- 可以在上游 gitignore 的位置装依赖,例如
node_modules/。 - 不能修改其中任何受版本控制的文件。
references/不进入本仓库的版本控制,它们各自是独立的 git 仓库。- 每个平台的移植基于哪个上游 commit,记录在
src/platforms/<p>/UPSTREAM。
4. 命令规范
4.1 语法
catbus <platform> <resource> <action> [参数...] [选项...]
- 选项可以出现在任意位置。
--之后的内容一律按参数处理,用于以-开头的文本。 - 端用选项
-e/--endpoint指定,默认web(见 4.3)。
示例:
catbus xhs item get "https://www.xiaohongshu.com/explore/<id>?xsec_token=..."
catbus xhs user items <user> --all -o jsonl
catbus bilibili item get BV1xx411c7mD
catbus douyin comment list <item> --limit 100
catbus x user get elonmusk
catbus jd item search 机械键盘 --sort sales
catbus taobao msg send "在吗" --item <商品 url>
catbus bilibili live listen <room> --duration 10m
catbus xhs auth login
catbus kuaishou auth status -a work
catbus xianyu item get <id> -e app # app 端尚未实现:NOT_IMPLEMENTED,退出码 4
4.2 平台 id 与别名
| 规范 id | 别名 |
|---|---|
xhs |
xiaohongshu, rednote |
douyin |
dy |
tiktok |
tt |
bilibili |
bili, b |
kuaishou |
ks |
weibo |
wb |
xianyu |
goofish, xy |
taobao |
tb |
jd |
jingdong |
x |
twitter |
目录名、配置键、输出里的 platform 字段一律使用规范 id。只有平台有别名;resource 和 action 没有别名。
4.3 端(endpoint)
-e/--endpoint web|app|pc,默认web,没有配置项可以改默认值。- 每个平台在注册表里声明各端的状态:目前 web 为 available,app / pc 为 planned。
命令无法执行时,按下面的顺序判断:
| 情况 | code | 退出码 |
|---|---|---|
| 端为 planned,命令在词表内(第 4.5、4.7 节) | NOT_IMPLEMENTED |
4 |
| 端可用,命令是矩阵里的 ○(规划中) | NOT_IMPLEMENTED |
4 |
| 命令是矩阵里的 —(平台没有这个概念),或者不在词表内 | UNSUPPORTED |
2 |
UNSUPPORTED时,如果别的端有这个命令,hint里提示加-e <端>。- 用了平台原生叫法(如
note、video),hint里提示对应的规范词(见 4.6)。 - 参数和选项的校验(
USAGE,以及筛选取值的UNSUPPORTED)先于 ○ 的判断:写错的命令先报用法错误。端为 planned 时不做校验。
4.4 动作语义
| 动作 | 语义 |
|---|---|
get |
取单个对象 |
list |
取一个集合,不需要关键词。默认是当前账号自己的,或者推荐流 |
search |
按关键词搜索 |
| 成对动作 | like/unlike、collect/uncollect、repost/unrepost、follow/unfollow |
| 写入 | add、send、publish、create、update、delete、upload |
| 长连接 | listen:持续输出 jsonl,直到 Ctrl-C 或 --duration 到期 |
4.5 通用词表
| resource | 含义 | action |
|---|---|---|
auth |
登录态 | login status logout list use |
user |
用户 | get search items likes collects reposts followers following follow unfollow |
item |
平台的主内容单元(见下表) | get search related list media download like unlike collect uncollect repost unrepost publish delete categories |
product |
内容平台里挂的商品(抖音 / TikTok 商城等) | get |
comment |
评论,也包括商品评价 | list replies add delete like unlike |
feed |
推荐 / 热门 / 关注流 | list categories |
live |
直播 | get list search categories listen history send like rank gifts products media replays start stop |
keyword |
搜索词 | suggest hot |
notice |
通知(评论 / @ / 赞 / 关注 / 系统) | list count |
msg |
私信 / IM / 客服 | list history send listen read revoke delete |
media |
媒体上传 | upload |
folder |
收藏夹 | list items create update delete |
series |
合集 / 播放列表 | list items |
history |
浏览历史 | list |
topic |
话题 | search |
poi |
地点 | search |
item 在各平台的含义:
| 平台 | item = |
|---|---|
| xhs | 笔记 |
| douyin | 作品 |
| tiktok | 视频 |
| bilibili | 稿件 |
| kuaishou | 作品 |
| 微博 | |
| x | 推文 |
| xianyu | 闲置商品 |
| taobao | 商品 |
| jd | 商品 SKU |
各动作的说明:
item list:当前账号自己发布的内容,带审核状态,数据来自创作者中心。item related:相关推荐。item media:可播放 / 可下载的媒体地址。item download:下载到本地。item categories:发布和搜索时--category的取值。feed list --kind recommend|hot|following:默认recommend。live listen:弹幕 / 礼物 / 进场等事件流。live history:最近的弹幕。live media:直播流地址。live replays <user>:回放列表。live start/stop:开播 / 下播。
4.6 平台原生叫法 → 规范词
| 平台原生叫法 | 规范词 |
|---|---|
| note / aweme / video / photo / work / post / status / tweet / goods / sku | item |
| digg、favorite(X 的点赞) | like |
| favour、bookmark、想要、关注商品 | collect |
| retweet | repost |
| board、collection(TikTok 收藏夹) | folder |
| mix、playlist、collection(快手合集) | series |
| reply、评价 | comment |
| DM、IM、咚咚 | msg |
| playurl | item media |
| location | poi |
| watch | listen |
原生叫法不做别名。用了原生叫法会报 UNSUPPORTED,hint 里给出规范词。
4.7 平台扩展
平台特有、没有跨平台对应物的能力,才用扩展:可以在通用 resource 上加 action,也可以新增 resource。新增扩展前先登记到本节,再写代码。
| 平台 | 命令 | 说明 | 输出 |
|---|---|---|---|
| tiktok | folder add <folder> <item> |
把视频加入收藏夹(一次一个视频)。TikTok 只能把已收藏的视频加进收藏夹 | {id}(视频 id) |
| tiktok | folder create <name> [--visibility public|private] |
收藏夹公开 / 私密:用 4.9 的标准选项 --visibility,只取 public、private;默认 private(与上游一致) |
Folder |
| tiktok | folder update <folder> [--name <name>] [--visibility public|private] |
同上。--name、--visibility 至少给一个,没给的保持原值 |
Folder |
| tiktok | item publish [--allow-comment on|off] |
私有选项:允许评论,默认 on |
Item |
| tiktok | item publish [--allow-duet on|off] |
私有选项:允许合拍(Duet),默认视频 off、图文 on(照上游) |
Item |
| tiktok | item publish [--allow-stitch on|off] |
私有选项:允许拼接(Stitch),默认视频 off、图文 on(照上游) |
Item |
| tiktok | item publish [--allow-content-reuse on|off] |
私有选项:允许他人复用内容,默认 on |
Item |
| tiktok | item publish [--allow-ai-remix on|off] |
私有选项:允许 AI 改编,默认 on |
Item |
| bilibili | item coin <item> [--count 1|2] [--like] |
投币;--like 同时点赞 |
{id} |
| bilibili | item triple <item> |
一键三连 | {id} |
| bilibili | item subtitles <item> |
字幕 | Subtitle[] |
| bilibili | danmaku list <item> |
视频弹幕 | Danmaku[] |
| bilibili | danmaku send <item> <text> --offset <秒> [--color] [--font-size] [--position] |
发视频弹幕。--color 为 #RRGGBB 或十进制,--font-size 默认 25,--position scroll|top|bottom 默认 scroll |
{id} |
| bilibili | dynamic publish --text [--image] |
发动态 | {id url} |
| bilibili | dynamic delete <id> |
删动态。<id> 为动态 ID 或动态链接(dynamic publish 输出的 id、url 都可以) |
{id} |
| bilibili | article publish --title --text [--category] [--tag] [--summary] [--draft] |
发专栏:先存草稿,再提交。--category 为专栏分区 ID(数字,默认 0),没有对应的 categories 命令;--summary 为摘要;--draft 只存草稿、不提交。上游没有专栏封面,所以没有 --cover |
{id url} |
| bilibili | draft get <id> |
专栏草稿 | Item(kind 为 article,status 为 draft) |
| bilibili | draft delete <id> |
删专栏草稿 | {id} |
| bilibili | user items <user> [--keyword <词>] |
私有选项:只看投稿里匹配关键词的 | Item[] |
| bilibili | item collect / uncollect <item> [--folder <收藏夹 id>] |
私有选项:收藏进 / 移出指定收藏夹,多个用逗号分隔。不给时收藏进第一个收藏夹,取消时从所有收藏夹移出 | {id} |
| bilibili | item publish [--source <来源>] [--dynamic <文案>] [--allow-reprint] |
私有选项:--source 表示转载并给出来源,不给为自制;--dynamic 为同步到动态的文案;默认禁止转载,--allow-reprint 允许 |
Item |
| bilibili | comment add <item> <text> --reply-to <comment> [--root <comment>] |
私有选项:回复楼中楼时 --root 给根评论、--reply-to 给被回复的那条;不给 --root 时 --reply-to 就是根评论 |
Comment |
| bilibili | live send <room> <text> [--color] [--font-size] [--position] [--reply-user <user>] |
私有选项:弹幕样式同 danmaku send;--reply-user 为回复的观众(UID 或空间链接) |
{id} |
| xianyu | item publish [--original-price <金额>] |
私有选项:原价(元),要和 --price 一起用 |
Item |
| xianyu | item publish [--shipping free|distance|fixed|none] |
私有选项:运费方式,默认 free 包邮;distance 按距离计费,fixed 一口价,none 无需邮寄 |
Item |
| xianyu | item publish [--postage <金额>] |
私有选项:一口价运费(元),配合 --shipping fixed |
Item |
| xianyu | item publish [--pickup] |
私有选项:支持自提 | Item |
| douyin | item search <kw> [--length all|short|medium|long] [--range all|seen|unseen|following] |
私有选项:视频时长(不限 / 1 分钟内 / 1–5 分钟 / 5 分钟以上)、搜索范围(不限 / 看过 / 没看过 / 关注的人)。--type video 走视频频道搜索,其余走综合频道(筛选值随 filter_selected 发出);--type image 只要图文 |
Item[] |
| douyin | user search <kw> [--fans 0_1k|1k_1w|1w_10w|10w_100w|100w_] [--user-type common|enterprise|personal] |
私有选项:粉丝数区间、用户类型(普通 / 企业 / 个人认证) | User[] |
| douyin | item publish [--poi-name <名称>] [--series <合集 id>] [--hotspot <热点词>] [--no-download] |
私有选项:地点名称(配合 --poi)、加入合集、关联热点、不允许下载 |
Item |
| douyin | item collect <item> --folder <folder>、item uncollect <item> --folder <folder> |
私有选项:收藏后移进某个收藏夹 / 从某个收藏夹移出(仍保留收藏)。<folder> 是收藏夹 ID 或名字 |
{id} |
| douyin | msg send [--file <path>] [--share <item|user|url>] |
私有选项:发文件(≤10MB)、分享作品 / 用户名片 / 网页卡片 | Message |
| douyin | live rank <room> [--ranking contribution|thousand] |
私有选项:榜单,默认贡献榜,thousand 为千票榜 |
Rank[] |
| douyin | live like <room> [--count N] |
私有选项:一次点赞的次数 | {id} |
| douyin | comment list <product> --product [--label <标签名或 id>] |
私有选项:商品评价按标签筛选(好评 / 差评 / 有图等) | Comment[] |
| douyin | notice list [--group all|fans|mention|comment|like|danmaku] |
私有选项:通知分组,不带时为默认分组 | Notice[] |
| xhs | item publish [--poi-name <名称>] [--poi <id>] |
私有选项:地点名称。小红书的地点只能按名称搜,搜不到 id,所以 --poi 要和 --poi-name 一起用(取 id 相同的那个);只给 --poi-name 时取名称完全相同的。都找不到时报 USAGE 并列出相近的地点 |
Item |
item publish [--poi-name <名称>] |
私有选项:地点名称,发成「#名称[地点]#」的地点标签。微博发布没有地点 id,不支持 --poi |
Item | |
| douyin | auth login --method sms --sso |
私有选项:短信登录改走 login.douyin.com 页的 SSO 链 |
Account |
| jd | order list |
订单 | Order[] |
| jd | order list [--range 3m|this_year|<年份>] |
私有选项:订单的时间范围。3m 近三个月(默认),this_year 今年内,2025 这样的四位年份表示那一年 |
Order[] |
| jd | cart count |
购物车数量 | {count} |
| jd | coupon list <item> |
商品可用优惠券 | Coupon[] |
| jd | item get / item search / item related / coupon list / cart count / history list / user collects [--area <地区编码>] |
私有选项:收货地区,影响价格和库存。取值是京东的地区编码 省_市_区_镇,用 _ 或 - 分隔,例如 1_2800_55812_0;默认取登录态里的 ipLoc-djd cookie,没有时为 1_2800_55812_0 |
Item / Item[] / Coupon[] / {count} |
| jd | msg send <text> --order <订单> [--item <item> | --conversation <venderId>] |
私有选项:按订单咨询客服,会话和消息都带上订单号。<订单> 是订单号(order list 输出的 id),也接受订单详情页 URL。可以和 --item、--conversation 之一一起用;单独用时联系京东自营客服 |
Message |
| x | item publish [--thread <text> ...] |
私有选项:发 thread。--text 是第一条,每个 --thread 是后面的一条,依次回复上一条;--image / --video / --quote 只作用于第一条。返回第一条 |
Item |
| x | article publish --text [--title] [--cover] |
发文章(Premium 长文):--text 是 Markdown,依次建草稿、写标题、正文、封面,再发布。不给 --title 时取正文第一行的 # 标题。独占一行的  上传成插图,相对路径按 --text @file 的文件所在目录解析(直接给正文时按当前目录) |
{id url} |
| x | article delete <article> |
删文章:草稿或已发布的都可以,已发布的连同文章推文一起删 | {id} |
扩展类型的字段见 6.2。
4.8 参数
目标参数同时接受以下几种形式,由平台实现归一化:
- ID;
- URL,包括分享短链;
- 平台上的自然标识,例如 B 站的 BV 号、X 的用户名;
me:只用于用户,表示当前账号。
所有输出对象都带 id 和 url,可以直接作为下一条命令的参数。xhs 的 url 带 xsec_token,因此 xhs 优先传 URL。
各 resource 的位置参数:
| resource | 形式 |
|---|---|
| user | get / items / follow / unfollow <user>;likes / collects / reposts / followers / following [user],省略时为 me;search <kw> |
| item | get / related / media / download / like / unlike / collect / uncollect / repost / unrepost / delete <item>;search <kw>;list、categories、publish 不带位置参数 |
| product | get <product> |
| comment | list <item>;replies / delete / like / unlike <item> <comment>;add <item> <text> [--reply-to <comment>] |
| feed | list、categories |
| live | get / listen / history / like / rank / gifts / products / media <room>;send <room> <text>;send <room> --gift <gift> [--count N];replays <user>;list、categories;search <kw>;start / stop |
| keyword | suggest <prefix>、hot |
| notice | list、count |
| msg | list;history / read / delete <conversation>;revoke <conversation> <message>;send <text> --to <user> | --conversation <id> | --item <item>,三者用一个,只有 --to 与 --item 可以同时用;可加 --image / --video |
| media | upload <file> |
| folder | list [user],省略时为 me;items <folder>;create <name>;update <folder> --name <name>;delete <folder> |
| series | list [user],省略时为 me;items <series> |
| history | list |
| topic / poi | search <kw> |
- 商品评价用
comment list <product>。- 传商品 URL 时,自动识别为商品。
- 传纯 ID 时要加
--product,否则按 item 处理。
msg send --item <item>:对商品卖家 / 客服发消息(闲鱼、淘宝、京东)。msg send --to <user> --item <item>:就这件商品给指定用户发消息(闲鱼:卖家主动联系买家)。不支持的平台报UNSUPPORTED。- taobao 没有按用户查询、按用户发起会话的接口:
user get <user>的<user>只接受商品链接,返回这件商品的卖家;msg send不支持--to(报UNSUPPORTED)。
4.9 选项
全局选项:
| 选项 | 说明 |
|---|---|
-a, --account <name> |
使用哪个账号(见 5.2) |
-e, --endpoint web|app|pc |
端,默认 web |
-o, --output json|jsonl |
输出格式,默认 json(见 6.1) |
--raw |
用平台原始对象代替归一化对象 |
--proxy <url> |
本次调用使用的代理 |
-v, --verbose |
stderr 输出调试日志(已脱敏) |
-q, --quiet |
不输出提示和进度,只保留错误 |
-y, --yes |
跳过危险操作的确认(见 4.11) |
-h, --help |
帮助 |
分页:
| 选项 | 说明 |
|---|---|
--limit N |
默认只取一页。指定 N 后自动翻页,直到取满 N 条。翻页间隔用平台默认值 |
--cursor <c> |
从上次返回的 page.cursor 继续翻。cursor 是不透明字符串。--limit 截在一页中间时,page.cursor 指回这一页,续翻时跳过已经输出的,不丢数据 |
--all |
一直翻到没有更多为止 |
筛选:各平台在注册表里声明支持哪些筛选、取哪些值。取值只能从下表里选;需要新取值时先改本表。
| 选项 | 标准取值 |
|---|---|
--sort |
general latest popular views comments collects sales price_asc price_desc |
--type |
all video image text article goods |
--time |
all day week month half_year year |
--kind |
recommend hot following(feed list,默认 recommend) |
--category <id> |
取值来自对应的 categories 命令 |
取值是标准值、但该平台不支持时报 UNSUPPORTED;不是标准值时报 USAGE。
发布(item publish、dynamic publish 等):
| 选项 | 说明 |
|---|---|
--title |
标题 |
--text <str|@file> |
正文。@file 表示从文件读取,正文里的相对路径(如 Markdown 插图)按该文件所在目录解析 |
--image <path|url> |
图片,可重复 |
--video <path|url> |
视频 |
--cover <path|url> |
封面 |
--tag、--topic、--mention |
均可重复 |
--poi <id> |
地点 |
--category <id> |
分类 |
--visibility public|private|friends|fans |
默认 public。friends 为好友(互相关注)可见,fans 为粉丝可见。平台在注册表里声明支持哪些取值,不声明时为 public private friends;平台不支持时报 UNSUPPORTED,不是标准值时报 USAGE |
--schedule <ISO 时间> |
定时发布 |
--price <金额> |
商品价格(闲鱼) |
--quote <item> |
引用一条内容(ID 或 URL),发成引用。目前只有 x 支持,其他平台没有这个选项 |
平台在注册表里用 supports 声明 item publish 支持上表中的哪些选项。没声明支持的选项不出现在帮助里,用了报 UNSUPPORTED(hint 列出可用的选项),不会被静默忽略。各平台的取舍见 capabilities.md。
本地文件会自动上传,不需要先调 media upload。
下载:
--dir <目录>:默认当前目录。文件名为<platform>_<id>_<序号>.<ext>。--overwrite:覆盖已有文件,默认跳过已有文件。
长连接:listen 总是输出 jsonl,断线自动重连。--duration <秒|10m|1h> 指定运行时长,Ctrl-C 正常退出,退出码 0。
平台私有选项在注册表里声明,不能与上面的标准选项重名。
4.10 全局命令与帮助
全局命令不带平台,名字与平台 id、别名互斥:
| 命令 | 输出 |
|---|---|
catbus platforms [platform] |
能力矩阵:平台 × 端 × resource.action 及其状态,数据来自注册表 |
catbus doctor |
逐项检查:Node 版本;原生依赖能否加载(HTTP 库、canvas、onnx);@cv-cat/catbus-assets-jd、@cv-cat/catbus-assets-ocr 是否就位;~/.catbus 的权限;签名 vm 能否运行 |
catbus auth list |
所有平台、所有端下的账号 |
catbus config get|set|unset|list |
读写 config.toml(见 5.5) |
catbus version(或 --version) |
版本、Node 版本、系统与架构 |
帮助:
- 不带参数的
catbus等同于catbus --help。 - 帮助分四层:
catbus --help、catbus xhs --help、catbus xhs item --help、catbus xhs item get --help。内容由注册表生成,写到 stdout,这是 stdout 上唯一不是 JSON 的输出。 - 命令级帮助列出参数、选项、示例,以及下面两行:
端: web ✓ · app ○ planned · pc ○ planned 登录: 需要 | 可选 - 面向人的文字(帮助、
message、hint、提示)一律用中文。
doctor 有检查项不通过时,ok 为 false、退出码 1,data 里仍然是完整的检查结果。
4.11 危险操作确认
以下操作执行前需要确认:
- 所有
delete; msg revoke;- bilibili 的
item coin/item triple; live send --gift。
确认方式:
- stdin 和 stderr 都是 TTY 时,在 stderr 上询问 y/N。
- 否则必须带
-y,不带时报CONFIRM_REQUIRED,退出码 2。
4.12 不提供的能力
以下上游能力不暴露为命令:
- 埋点 / 心跳 / 上报;
- 弹窗 / 合规 / 钱包类接口;
- 系统配置;
- 闲鱼的自动回复示例;
- B 站笔记;
- 关系查询;
- xhs 的 share_code;
- 抖音的 PK / 连麦 / 评论标签;
- TikTok 的 story 和收藏夹移动;
- xhs 的蒲公英(达人)和千帆(分销达人):只对开通了的品牌 / 机构账号有用,普通账号访问会被拒,被拒后几分钟内主站的评论等接口还会要求人机验证。
如果某条命令内部需要其中的能力(例如心跳),由实现内部调用。
5. 登录态
5.1 目录
~/.catbus/ # 可用 CATBUS_HOME 覆盖
├── config.toml # 见 5.5
├── auth/
│ └── <platform>/ # 规范 id
│ └── <endpoint>/ # web | app | pc
│ ├── <account>.json # 一个账号一个文件
│ ├── guest.json # 游客态:设备 cookie、guest token 等,自动生成
│ └── _current # 当前账号名
└── cache/<platform>/ # 可随时删除:二维码 PNG、短信登录的中间态
- 账号名匹配
[a-z0-9][a-z0-9_-]{0,31}。guest是保留名。 - 权限:
- 目录 0700,文件 0600;
- Windows 依赖用户目录本身的 ACL。
- 所有写入都是原子的:先写临时文件,再 rename。
5.2 身份选择与游客态
身份按以下优先级选择:-a <name> > _current > 游客。
-a guest强制使用游客身份(只在支持游客态的端上有意义)。auth login/logout/use不接受-a guest(保留名),报USAGE。-a指定的账号不存在时,报AUTH_REQUIRED,hint为catbus <p> auth login -a <name>。
游客态只在声明了 guest: true 的端上存在(见 7.6)。不支持游客态的端上没有 guest.json:auth status 等 auth 命令没有账号时,用一份只在内存里的空凭证,不落盘。
- web 端都不支持游客态:各平台的 web 端不登录几乎看不到内容,所以 web 端除
auth命令外全部需要登录,未登录时直接报AUTH_REQUIRED并提示登录命令。游客态主要留给 app 端。 - 支持游客态的端上,很多平台匿名访问也需要设备 cookie 或 token(例如 xhs 的
a1、抖音的ttwid、B 站的buvid、X 的 guest token)。catbus 自动生成这些数据,缓存到guest.json,过期后重新生成;各平台的生成方式见 upstream-map。 - 登录流程本身仍会先生成这些设备数据(很多平台的登录接口要求先有设备 cookie),这与游客态无关。
每个命令在注册表里标注 auth: required | optional;不支持游客态的端上一律是 required:
| 情况 | 行为 |
|---|---|
required,当前是游客 |
AUTH_REQUIRED,退出码 3,hint: catbus xhs auth login(非 web 端时带上 -e) |
optional,当前是游客 |
照常执行(信封里 account 为 "guest"),同时在 stderr 提示一行(见下) |
| 平台返回登录墙 | AUTH_REQUIRED,退出码 3 |
| 登录态失效 | AUTH_EXPIRED,退出码 3,hint 为重新登录的命令 |
optional 时在 stderr 输出的提示:
[catbus] 未登录 xhs (web),本次以游客身份访问,结果可能不完整;很多操作需要登录。登录:catbus xhs auth login
-q或-a guest时不输出这行提示。- 只提示,不在命令中途弹出交互式登录。
5.3 登录与登出
catbus <p> auth login [-a <name>] [--method qrcode|sms|password|cookie] [--scope <scope>]
账号名:
- 不带
-a时:有当前账号就登录到当前账号,否则登录到default。 - 登录成功后,如果还没有当前账号,就把它设为当前账号。
- 如果该名字下已经存了另一个用户,报
USAGE,hint为"用-a <新名字>,或者先 logout",新凭证不保存。
登录方式:每个平台在注册表里声明支持哪些方式、默认用哪种,见 capabilities.md。
| 方式 | 选项 | 流程 |
|---|---|---|
qrcode |
— | 二维码画到 stderr,同时把 PNG 存到 cache/<p>/,然后等待扫码 |
sms |
--phone、--code |
TTY 下先发验证码,再交互输入。非 TTY 下分两步:--phone 发验证码并保存中间态(10 分钟有效),再单独用 --code 完成登录 |
password |
--username、--password-stdin |
密码只从 stdin 读取,TTY 下隐藏输入 |
cookie |
--cookie <str|@file|-> |
导入后调一次 me 校验。也接受浏览器导出的 cookie JSON 数组;douyin、tiktok 另外接受一个 JSON 对象,把 ticket、证书、私钥等设备数据一并导入(这两个平台的写操作需要它们) |
- 验证码:上游能自动过的就自动过,过不了报
RISK_CONTROL(detail.kind = captcha),退出码 5。 - 子站点(创作者中心、直播、IM 等):
- 能自动换取的凭证,登录时或第一次用到时自动获取,不需要用户再登录一次(例如 xhs 的创作者中心:用主站登录态换取);
- 需要单独登录的,用
--scope <name>登录,各平台可用的 scope 在注册表里声明; - 命令缺少所需 scope 时,报
AUTH_REQUIRED,hint带上--scope。
auth status:在线校验,返回 AuthStatus。未登录或已失效时返回logged_in: false,不报错。auth logout:删除本地凭证文件(上游支持服务端登出的,同时调用服务端登出,目前有 B 站和快手),并清除指向它的_current。auth use <account>:设置_current。auth list:列出本平台、本端的账号。- 自动刷新:会自动刷新的 token(如 B 站的 refresh)和响应里的 Set-Cookie,都会合并回凭证文件并原子写入。
5.4 凭证文件
{
"schema": 1,
"platform": "xhs",
"endpoint": "web",
"account": "default", // 游客文件为 "guest"
"user": { "id": "5f...", "name": "...", "url": "..." }, // UserRef;游客为 null
"method": "qrcode", // qrcode | sms | password | cookie | guest
"created_at": "2026-09-27T12:00:00+08:00",
"updated_at": "2026-09-27T12:00:00+08:00",
"scopes": { // 子站点分区;单站点平台只有 main
"main": {
"cookies": [ { "name": "a1", "value": "...", "domain": ".xiaohongshu.com", "path": "/", "expires": null } ],
"tokens": {} // ticket / ct0 / access_token / refresh_token 等
},
"creator": { "cookies": [], "tokens": {} }
},
"device": {}, // 设备指纹、IM 私钥与证书、localStorage 等设备绑定数据
"extra": {} // 平台私有字段
}
- cookie 统一存成带 domain 的列表。只导入了一段 cookie 字符串时,domain 取平台默认域。
- 凭证文件只由 core 读写。平台实现从 core 拿凭证,更新后交回 core 落盘。
5.5 配置与代理
config.toml 只有这几项:
proxy = "http://127.0.0.1:7890" # 全局代理
timeout = 30 # 单次请求超时(秒)
[xhs]
proxy = "socks5://..." # 平台代理
- 代理的优先级:
--proxy><platform>.proxy>proxy。 - 不读取
HTTP(S)_PROXY环境变量,代理只能显式配置。 catbus config set xhs.proxy <url>,用点号表示平台段。
6. 输出
6.1 信封与格式
stdout 只输出结果。日志、提示、进度、二维码一律输出到 stderr。
信封:
{ "ok": true, "platform": "xhs", "endpoint": "web", "resource": "item", "action": "get",
"account": "default", "data": {}, "page": null, "error": null }
| 字段 | 说明 |
|---|---|
account |
使用的账号名,游客为 "guest" |
page |
分页命令为 { "cursor": "...", "has_more": true },其他命令为 null |
error |
出错时 ok 为 false,data 为 null,error 为 { "code", "message", "hint", "detail" }。hint 是建议执行的命令或做法,可以为 null |
全局命令的信封:
platform、endpoint、account都为 null;resource是命令名,action是子命令,没有子命令时为 null。- 例如
catbus auth list的 resource 是auth,action 是list。
-o 的两种格式:
json(默认):stdout 恰好输出一个信封,成功和失败都是。stdout 是 TTY 时缩进输出,否则输出紧凑的一行。jsonl:stdout 每行一个data条目。列表命令一行一条;非列表命令只有一行。- 结束时在 stderr 写一行摘要信封,
data为 null,带page和error,用来取 next cursor 或错误信息。
- 结束时在 stderr 写一行摘要信封,
--raw:用平台原始对象代替每个归一化对象,信封不变。
6.2 归一化类型
所有平台的同一种对象都输出同一结构。
基础类型:
| 类型 | 字段 |
|---|---|
| UserRef | id name url |
| Price | amount currency。amount 用货币主单位(元)的十进制数。虚拟货币也用它,currency 写平台货币代码 |
| Media | id type url width height duration。url 在刚上传、平台还没给出可访问地址时(例如视频转码前)为 null |
内容与互动:
| 类型 | 字段 |
|---|---|
| User | id name handle avatar url bio stats{followers following items likes} |
| Item | id kind url title text author:UserRef created_at cover media:Media[] stats{views likes comments collects shares} price:Price|null status|null |
| Comment | id item_id parent_id author:UserRef text created_at stats{likes replies} |
| Folder / Series | id name count url |
| Category | id name parent_id |
| Keyword | text heat |
| Topic | id name url stats{views items} |
| Poi | id name address |
直播:
| 类型 | 字段 |
|---|---|
| Live | id url title status host:UserRef cover stats{viewers} |
| Event | type time user:UserRef text gift{name count}|null |
| Gift | id name price:Price |
| Rank | rank user:UserRef score |
消息与通知:
| 类型 | 字段 |
|---|---|
| Conversation | id peer:UserRef unread last_message updated_at |
| Message | id conversation_id from:UserRef type text media:Media[] created_at |
| Notice | id type user:UserRef target{id url}|null text created_at |
| NoticeCount | total comment mention like follow system |
账号与本地:
| 类型 | 字段 |
|---|---|
| AuthStatus | logged_in user:UserRef|null method expires_at |
| Account | platform endpoint account current user:UserRef|null method updated_at |
| File | path type url size |
扩展类型(见 4.7):
| 类型 | 字段 |
|---|---|
| Subtitle | lang name url lines[{from to text}],lang 缺失时为 null |
| Danmaku | id item_id offset text created_at,offset 为视频内的秒数 |
| Order | id status total:Price items:Item[] created_at |
| Coupon | id title discount:Price threshold:Price|null start_at end_at |
枚举值:
| 字段 | 取值 |
|---|---|
| Item.kind | video image text article goods |
| Item.status | published reviewing rejected private draft;商品另有 on_sale sold off_shelf。只有自己的内容和商品才有,其余为 null |
| Media.type | image video audio |
| Live.status | live offline |
| Event.type | chat gift like enter follow other |
| Message.type | text image video card other |
| Notice.type | comment mention like follow system |
规则:
- ID 一律是字符串。
- 计数是整数或 null,例如 "1.2万" → 12000。
- 时间用带时区的 ISO 8601。
- 字段总是存在,缺失时为 null 或
[]。 - 不追加平台私有字段;需要原始字段时用
--raw。 - 新增类型或字段,先改本节。
6.3 各命令的输出
读取类命令按资源返回对应类型。列表类命令返回数组,并带 page。
| 命令 | data |
|---|---|
user get |
User |
user search / followers / following |
User[] |
user items / likes / collects / reposts,item search / related / list,feed list,folder items,series items,history list,live replays |
Item[] |
item get、product get |
Item |
live products |
Item[](kind 为 goods) |
item media、live media |
Media[] |
comment list / replies |
Comment[] |
live get |
Live |
live list / search |
Live[] |
live listen(流) |
Event |
live history |
Event[] |
live gifts |
Gift[] |
live rank |
Rank[] |
msg list |
Conversation[] |
msg history |
Message[] |
msg listen(流) |
Message |
notice list |
Notice[] |
notice count |
NoticeCount |
*categories |
Category[] |
keyword suggest / hot |
Keyword[] |
topic search |
Topic[] |
poi search |
Poi[] |
folder list |
Folder[] |
series list |
Series[] |
auth status |
AuthStatus |
auth list |
Account[] |
auth login / use / logout |
Account |
写操作的返回:
| 动作 | data |
|---|---|
成对动作、delete、msg read / revoke、live send / like / stop |
{id},即目标对象的 id |
item publish |
Item |
comment add |
Comment |
msg send |
Message |
media upload |
Media |
item download |
File[] |
folder create / update |
Folder |
live start |
{id push{url key}},推流地址 |
全局命令的返回:
| 命令 | data |
|---|---|
platforms |
Platform[] {id name aliases endpoints{web app pc} commands[{endpoint resource action auth status upstream note}]};带平台参数时返回单个对象 |
doctor |
{name ok message}[] |
config list |
对象:只含已设置的项,键用点号展开,例如 xhs.proxy |
config get |
值 |
config set / unset |
{key value} |
version |
{version node platform arch} |
6.4 退出码
| 退出码 | code | 含义 |
|---|---|---|
| 0 | — | 成功 |
| 1 | ERROR |
未分类错误 |
| 2 | USAGE / UNSUPPORTED / CONFIRM_REQUIRED |
参数错误 / 平台或端不支持该命令 / 危险操作未确认 |
| 3 | AUTH_REQUIRED / AUTH_EXPIRED |
未登录或登录态失效 |
| 4 | NOT_IMPLEMENTED |
planned 的端或能力 |
| 5 | RISK_CONTROL |
验证码或风控,detail.kind 为 captcha / rate_limit / blocked |
| 6 | NETWORK |
网络 / 代理错误 |
| 7 | UPSTREAM |
平台返回业务错误,原始错误码放在 detail 里 |
7. 架构
7.1 技术栈
- 语言:TypeScript,strict,ESM。用
tsc编译到dist/,发布的是编译后的 JS。Node 拒绝对node_modules里的文件做类型剥离,所以必须有编译这一步。 - Node 版本:
engines为^22.22.2 || ^24.15.0 || >=26.0.0,跟随 jsdom 30 的要求。 - 测试:vitest。
- 开发:用 npm scripts,不引入额外的构建工具。
7.2 打包与分发
| 包 | 内容 |
|---|---|
catbus-cli |
dist/(CLI、core、全部平台)和 static/(上游签名 JS 等)。bin 为 catbus → dist/cli/main.js |
@cv-cat/catbus-assets-jd |
JD 验证码(JCAP)的 onnx 模型,约 81 MB。作为 catbus-cli 的普通依赖,精确钉版本 |
@cv-cat/catbus-assets-ocr |
验证码 OCR 模型:ddddocr 1.6.1 的检测模型 common_det.onnx、识别模型 common_old.onnx 和字符集,约 34 MB(B 站极验点选在用)。同样是精确钉版本的普通依赖 |
- 拆出 assets-jd / assets-ocr 是因为模型很少变。放在主包里的话,每次发版都要上传 100 多 MB。
- 两个包的源码在
packages/assets-jd/、packages/assets-ocr/,是 npm workspace,开发时直接链接。 - 模型文件不进 git,由
npm run assets:jd(scripts/fetch-jd-models.mjs)按src/platforms/jd/UPSTREAM的 commit 取回并校验 sha256:有references/JdApis时从本地复制,否则从 GitHub 下载。打包 assets-jd 时会自动执行。 - OCR 模型同样不进 git,由
npm run assets:ocr(scripts/fetch-ocr-models.mjs)从 PyPI 上固定版本的 ddddocr wheel 里取出,wheel 和每个文件都校验 sha256:本机装过同版本 ddddocr(如.golden/<p>虚拟环境)时直接复制,否则下载 wheel(也可用CATBUS_DDDDOCR_WHEEL指定本地 wheel)。打包 assets-ocr 时会自动执行。ddddocr 是 MIT 许可证,包里附带它的原许可证LICENSE-ddddocr。 - catbus 自己不做按系统划分的运行时包。原生依赖(HTTP 库、
@napi-rs/canvas等)各自通过optionalDependencies带上本机的预编译包。 npm i之后完全离线可用,npmmirror 可以完整镜像。- 目标系统:
- win32-x64、win32-arm64;
- darwin-arm64、darwin-x64;
- linux-x64、linux-arm64,glibc 和 musl 都要支持。
- CI 在全部目标系统上跑冒烟测试,musl 用 Alpine。任何一个目标系统缺预编译包的依赖都不能用。
- 发布:
- 改
package.json的版本号。模型变了时同时改packages/assets-*/package.json的版本,并把主包里钉住的依赖改成同一个版本(测试会检查)。 - 在
CHANGELOG.md写好## [<版本>] - <日期>一节。 - 推送
v<版本>tag,触发.github/workflows/release.yml:先检查 tag 与版本号一致、CHANGELOG 里有这个版本;跑完整 CI;发布 CI 里测过的同一份 tarball;最后用scripts/release-notes.mjs从 CHANGELOG 生成 GitHub Release。
- 顺序:先发 assets-jd、assets-ocr,再发
catbus-cli。已经在 npm 上的版本跳过,失败后重跑不会重复发布。 - 认证:npm 上给三个包配了 trusted publishing(GitHub Actions,
cv-cat/catbus,release.yml,允许npm publish)时走 OIDC,否则用仓库 secretNPM_TOKEN。trusted publishing 配好后两天内要成功发布一次,否则失效。 - 仓库公开时发布带
--provenance,私有时不带(npm 只给公开仓库生成 provenance),由 release.yml 按仓库可见性自动判断。
- 改
- Docker 镜像(可选):
Dockerfile走与 npm 发布相同的路径,先打出三个包,再在node:<版本>-bookworm-slim里全局安装这三个 tarball;CATBUS_HOME=/data(卷),工作目录/work;默认以 root 运行(挂进来的目录都能写),Linux 上可以--user "$(id -u):$(id -g)"配合挂自己的数据目录。compose.yaml提供catbus(一次性命令)和live-listen(长时间监听直播)两个服务。CI 的.github/workflows/docker.yml在 x64 与 arm64 上构建并跑version/doctor,不推送到任何镜像仓库。 - 以后要提供 SDK 时,通过
package.json的exports暴露,现在不做。
7.3 依赖选型
原则:
- 优先纯 JS / WASM。
- 原生依赖必须在全部目标系统上都有预编译包,不能有 install 脚本,也不能需要 node-gyp。
- 一律用最新版,由
package-lock.json锁定。
| 用途 | 选择 |
|---|---|
| 带浏览器 TLS / HTTP2 指纹的 HTTP(替代 curl_cffi) | wreq-js:JA4 / H2 指纹与真 Chrome 一致,可关闭默认头以精确控制头顺序。impit 指纹不符、没有 WebSocket,不用 |
| WebSocket | wreq-js 自带的 websocket(),与 HTTP 同指纹、共享 cookie |
| protobuf | protobufjs。它的 Reader 也用来做无 schema 解码,替代 blackboxprotobuf |
| onnx 推理 | onnxruntime-web(WASM)。太慢再换 onnxruntime-node |
| 图像处理 | @techstark/opencv-js |
| 图片编解码 | @napi-rs/canvas,不用 sharp |
| 验证码 OCR | 移植 ddddocr:模型跑在 onnxruntime-web 上,可参考 ddddocr-node。模型来自 ddddocr 1.6.1 的 wheel,放在 @cv-cat/catbus-assets-ocr(见 7.2) |
| 签名 JS 的依赖 | jsdom(JD)、crypto-js(XHS)、@napi-rs/canvas(JD、KS) |
| 校验 | zod |
| 参数解析 | node:util 的 parseArgs,加上注册表。不用 CLI 框架 |
| 配置 | smol-toml |
| 终端二维码 | qrcode |
| 日志 | 自己写的 stderr logger,负责给凭证打码 |
wreq-js 默认会读 HTTP(S)_PROXY 环境变量和 Windows 系统代理。为遵守 5.5,core/http 在启动时设置 NO_PROXY=*,代理只通过 per-request 的 proxy 传入。
7.4 签名 JS
- 上游的签名 JS 原样复制到
static/<p>/,进版本控制,不手改。需要适配的地方在 TS 一侧包装。 - 执行方式:放到
node:vm的 context 里执行。- context 按脚本懒创建,创建后复用。
- 需要 DOM 的脚本(JD)注入 jsdom。
require用createRequire指向 catbus 自己的node_modules,这样crypto-js/jsdom/@napi-rs/canvas从主包依赖里解析。
- 原来按命令行脚本写的 JS(读 argv / stdin、往 stdout 打印),由 TS 包装层喂参数、收结果。
- vm 跑不了的脚本(依赖进程级状态等),退回到
worker_threads,或者用process.execPath起子进程。 - 上游 Python 里围绕 JS 的胶水代码,以及纯 Python 实现的签名(如 wbi、mtop sign),都移植成 TS。
7.5 移植与对拍测试
- 移植:上游 Python 的请求构造、签名、解析逻辑都重写成 TS。行为以上游为准:同样的输入,产出同样的请求(URL、query、header、body、签名)。
- 对拍数据:用
scripts/golden/<p>/gen.py生成,框架是scripts/golden/catbus_golden.py。- 在开发机上用 Python 3.13 + uv 建一个不进 git 的虚拟环境
.golden/<p>/(uv venv .golden/<p> --python 3.13,再装上游的依赖),运行references/里的上游代码。 - 框架替换了 Python 的
random/secrets/uuid/os.urandom/time,并截获 curl_cffi 与 requests 的请求(不联网,按用例给的响应回复);上游起的 node 子进程通过NODE_OPTIONS预加载node_determinism.cjs,固定Math.random与Date。 - 时区固定为北京时间(
Asia/Shanghai):框架会设置TZ,TS 侧的测试在vitest.config.ts里固定同一时区。 - 输出到
tests/golden/<p>/<case>.json。只能用假凭证。
- 在开发机上用 Python 3.13 + uv 建一个不进 git 的虚拟环境
- 对拍测试:
tests/<p>.test.ts用tests/golden.ts的replay()在同样的随机数与时钟下运行 TS 实现,expectRequests()逐字节比较请求(URL、header 顺序、cookie、body)。请求构造和签名都必须有对拍测试。 - 平台实现的约定(参考实现是
src/platforms/bilibili/web/):- 文件:
client.ts(会话:设备初始化、签名密钥、带重试的请求、业务码 → 错误)、api.ts(一个函数对应一个上游方法,字段与顺序照抄)、commands.ts(命令 handler)、normalize.ts(原始对象 → 6.2 的类型,挂上原始对象供--raw)、resolve.ts(参数归一化)。 - 随机数和当前时间只经
core/rand.ts,等待用rand.sleep;与 Python 对应的编码(urlencode、quote、json.dumps)用core/py.ts;需要精确键序的 JSON 用 Map。解析平台响应用core/py.ts的jsonLoads(与json.loads一样保留 64 位整数 ID),不直接用JSON.parse。 - HTTP 用
core/http.ts的HttpClient(toolkit.httpClient(ctx)),请求头按上游顺序显式给出;cookie 在凭证的 cookie 罐里,Set-Cookie 自动写回,命令结束时由 core 落盘。 - 签名 JS 复制到
static/<p>/,用core/vm.ts的loadScript/callScript执行。 - 业务错误映射到 6.4 的错误码:登录墙 →
toolkit.authError,风控 →RISK_CONTROL,其余 →UPSTREAM(原始错误码放detail)。
- 文件:
- 上游基线:
src/platforms/<p>/UPSTREAM记录上游仓库地址和 commit。 - 上游更新时:
- 用
git -C references/<repo> log <commit>..查看变更; - 同步移植;
- 重新生成对拍数据;
- 更新 UPSTREAM。
- 用
- 在线测试:
npm run test:e2e,用单独的配置tests/e2e/vitest.config.ts,不在npm test和 CI 里。使用开发者本机~/.catbus里的登录态,没登录的平台整组跳过;npm run test:e2e -- -t <platform>只跑一个平台(-t按名字前缀匹配,x 要写成-t '^x ',否则会连 xhs、xianyu 一起跑)。- 只跑只读命令:从注册表枚举每个平台已实现的读取类命令,前面命令返回的对象(item、用户、评论、直播间、会话、收藏夹……)的
url或id作为后面命令的参数,逐条校验信封和 6.2 的字段结构。写操作、下载、长连接不跑。 - 命令之间默认间隔 1.5 秒,小红书 4 秒,避免触发风控。以下记为跳过:子站点没登录(
AUTH_REQUIRED)、平台风控(RISK_CONTROL)、默认参数落在规划中的取值上(NOT_IMPLEMENTED)、注册表 note 写明的限制(有 note 的命令报UNSUPPORTED)。 - 每条命令的信封(列表只留前 3 条)写到
.e2e/<platform>.json,不进版本控制,用来检查归一化的字段是否取到了值。
- 只跑只读命令:从注册表枚举每个平台已实现的读取类命令,前面命令返回的对象(item、用户、评论、直播间、会话、收藏夹……)的
- CI(
.github/workflows/ci.yml):只跑离线测试,再加上全部目标系统的冒烟测试(scripts/smoke.mjs),冒烟测试包括:- 安装包;
- 运行
version、doctor、platforms; - 跑一次签名 vm;
- 加载原生依赖。
7.6 能力注册表
每个平台的 src/platforms/<p>/index.ts 导出平台声明:
export default definePlatform({
id: 'xhs', name: '小红书', aliases: ['xiaohongshu', 'rednote'], item: '笔记',
endpoints: {
web: {
login: { methods: ['qrcode', 'sms', 'cookie'], default: 'qrcode', scopes: ['creator'] },
commands: {
'user get': 'full',
'item get': { upstream: 'full', handler: () => import('./web/item.js').then((m) => m.get) },
'feed list': { upstream: 'partial', options: { kind: filter.kind('recommend', 'following') } },
'order list': { upstream: 'full', summary: '订单列表', args: [], output: 'Order[]',
auth: 'required', paged: true }, // 扩展命令(示意):字段写全
},
},
app: 'planned',
pc: 'planned',
},
})
- 第 4.5 节词表里的命令,默认的 summary、args、选项、auth、output 都来自
src/core/vocab.ts(summary 里的{item}替换成平台的 item 叫法)。平台只写与默认不同的部分。 - 命令的值可以是
'full' | 'partial' | 'none'的简写,也可以是对象。 - 矩阵里 — 的命令不写。
auth list/use/logout由 core 自动注册。- 端的字段:
login(登录方式与子站点)、logout?(服务端登出)、guest?(是否支持游客态,默认 false)、commands。guest为 false 时,除auth外的命令一律按auth: required处理,声明optional会在启动时报错。web 端都不设guest。
命令的字段:
| 字段 | 说明 |
|---|---|
summary |
一句话说明 |
args |
位置参数 |
options |
私有选项和筛选取值,用 zod 声明 |
supports? |
词表给这个命令的标准选项里,平台支持哪些(如 item publish 的发布选项)。不写时全部支持;没列出的不出现在帮助里,用了报 UNSUPPORTED |
auth |
required / optional;只在 guest: true 的端上可以是 optional |
confirm? |
是否需要危险操作确认 |
stream? |
是否长连接 |
paged? |
是否分页 |
output |
输出类型名 |
upstream |
上游支持程度:full / partial / none,即矩阵里的 ✓ / ◐ / ○ |
note? |
能力说明,例如"只支持图文" |
handler? |
动态 import,懒加载。有 handler 的命令 status 为 implemented,否则为 planned |
注册表是唯一的事实来源,以下都从它生成:
- argv 解析、
--help; catbus platforms;- NOT_IMPLEMENTED / UNSUPPORTED 的判定;
docs/capabilities.md:用npm run gen:capabilities生成,不手改,测试会检查它是否最新;- 将来的 MCP 工具,工具名为
<platform>_<resource>_<action>。
执行一条命令时只加载这一个平台的这一个 handler,不会连带加载别的平台的依赖(如 JD 的 onnx)。
7.7 目录结构
All-In-One/
├── AGENTS.md CLAUDE.md→AGENTS.md
├── README.md(另有 en、zh-TW、ja、ko 译本) CHANGELOG.md llms.txt
├── package.json tsconfig.json tsconfig.build.json # npm 主包 catbus-cli
├── Dockerfile compose.yaml Makefile .dockerignore
├── .github/ # 仓库治理:CONTRIBUTING、TESTING、SECURITY、PRIVACY、CODE_OF_CONDUCT
├── assets/ # logo、banner、README 用的图
├── docs/
│ ├── guide/ platforms/ # 用户指南、各平台说明
│ ├── examples/ # 组合 catbus 的示例脚本
│ ├── capabilities.md # 能力矩阵(由注册表生成,不手改)
│ ├── upstream-map.md # 上游移植对照
│ └── trouble.md roadmap.md # 内部文档(真机验证记录、路线图):只在本地,不进版本控制
├── .claude/skills/catbus/ # 给 AI Agent 的技能:SKILL.md 总纲,platforms/、workflows/ 按需读;与注册表的一致性由 tests/skill.test.ts 检查
├── references/ # 上游仓库:只读,不进版本控制
├── src/
│ ├── cli/ # 入口 main.ts、argv 解析、帮助、信封输出、确认
│ ├── core/ # registry、auth store、config、errors、http、schemas、signing(vm)、log
│ ├── platforms/_shared/ # 多个平台共用、但不属于 core 的实现,例如 impaas.ts(闲鱼、淘宝的钉钉 IMPaaS 私信长连)
│ └── platforms/<p>/
│ ├── index.ts # 平台声明与命令注册
│ ├── UPSTREAM # 移植所基于的上游仓库与 commit
│ └── web/ # web 端实现;app/、pc/ 等真正实现时再建
├── static/<p>/ # 上游签名 JS、proto 等,原样复制
├── packages/assets-jd/ # @cv-cat/catbus-assets-jd
├── packages/assets-ocr/ # @cv-cat/catbus-assets-ocr
├── scripts/golden/<p>/ # 调用上游 Python 生成对拍数据
└── tests/ # vitest;golden/<p>/ 放对拍数据
.gitignore:references/、node_modules/、dist/、packages/assets-jd/、packages/assets-ocr/ 下的模型文件,以及内部文档 docs/trouble.md、docs/roadmap.md。
7.8 provider 协议(规划中)
给将来的非 JS 上游(Go / Java……)用:任意语言的可执行文件,以子进程形式运行,通过 stdin / stdout 传 JSON。
- 清单:
provider.json声明id、支持的 platform / endpoint、能力列表(resource.action,同样遵守第 4 节词表)和启动命令。 - 安装位置:
~/.catbus/providers/<name>/,或者 npm 包@cv-cat/catbus-provider-<name>。 - 请求:core 往 stdin 写一个 JSON:
{v, platform, endpoint, resource, action, args, options, account, credential}。 - 响应:provider 往 stdout 逐行写 JSON,消息类型有:
result:最终结果;item:流式条目;credential_update:刷新后的凭证;error:错误,code 取 6.4。
- 日志:写到 stderr,core 原样转发。
- 凭证:provider 不直接读写
~/.catbus/。凭证由 core 放在请求里给它,更新后通过credential_update交回,由 core 落盘。 - 注册表:provider 的能力合并进注册表,
platforms、--help、NOT_IMPLEMENTED 对它一视同仁。
8. 开发约定
- 语言:与用户沟通用中文;面向用户的文字用中文;代码标识符用英文。
- 工具链:
- 用户侧只需要 Node(版本见 7.1)。
- 开发侧需要 Node、Python 3.13 和
uv。Python 只用来生成对拍数据。
- 依赖:不引入需要现场编译的依赖(node-gyp、install 脚本)。新增原生依赖前,先确认全部目标系统都有预编译包。
- 凭证:严禁把真实 cookie / token / 私钥写进代码、测试 fixture、日志或提交记录。凭证只在
~/.catbus/里。logger 对 cookie、token、Authorization 等字段打码。 - stdout 纯净:调试输出一律走 stderr / logger。
- 新增命令:
- 先查第 4 节词表,不够用就先改本文件;
- 在注册表里声明;
- 再实现。
- 移植:
- 移植以上游行为为准,请求构造和签名必须有对拍测试。
- 发现上游 bug 时,修到对应的上游仓库,再按新 commit 重新同步。
references/:只读。可以运行,可以在 gitignore 的位置装依赖,但不能修改受版本控制的文件。