技能自动发布
把「改完技能 → 自检 → 打包 → 升版 → 提交 → 确认落库」压成一条命令。
存在的理由不是省几次 API 调用,而是把踩过的坑变成机器门禁。最典型的一个坑:description 写成触发词清单,会被内容审核在 15 秒内退回,而平台只推拒绝通知、通过时一声不响——于是很容易连续发七八版都在同一个地方栽倒,还以为是正文有问题。
什么时候用
- 技能改完了,要发新版本
- 第一次发布一个新技能
- 发布被退回,要查是哪一项没过
- 不确定线上最新是哪个版本、到底有没有发上去
命令
三条,够用:
# 1. 只自检,不联网提交(改稿阶段反复跑这个)
python scripts/autopub.py check --dir "<技能目录>"
# 2. 完整发布:自检 → 自动升版 → 打包 → 提交 → 轮询确认上线
python scripts/autopub.py publish --dir "<技能目录>"
# 3. 查线上版本列表(确认到底上没上)
python scripts/autopub.py status --dir "<技能目录>"
publish 的常用开关:
| 开关 | 作用 | 什么时候用 |
|---|---|---|
--dry-run |
自检 + 打包,不提交 | 想先看看包长什么样 |
--wait 0 |
提交后不轮询 | 不想占着终端等 3~5 分钟 |
--wait 600 |
加长等待 | 平台排队慢的时候 |
--changelog "…" |
自定义版本说明 | 默认留空更好,平台自动填中性文本 |
--slug xxx |
覆盖 frontmatter 的 slug | 改名发布 |
--token skh_xxx |
临时指定密钥 | 不想写进环境变量 |
密钥三种给法,优先级从高到低:--token → 环境变量 SKILLHUB_KEY → ~/.skillhub/credentials.json 里的 user.token。
退出码不是摆设
别只看有没有报错,要看退出码——「已提交」和「已上线」是两件事:
| 码 | 含义 | 该怎么办 |
|---|---|---|
| 0 | 成功(publish 时含已确认上线) | 收工 |
| 2 | 参数错误 | 看 --help |
| 3 | 自检未通过,没有提交 | 按报错改,改完重跑 |
| 4 | 密钥无效或过期 | 去 dashboard 重新取 |
| 5 | 版本冲突没解决 | 手动改 version 后再跑 |
| 6 | 平台拒绝(含内容审核退回) | 看下文「被退回了怎么办」 |
| 7 | 已提交,但超时没等到落库 | 不是失败,过几分钟跑 status |
退出码 7 单独存在,就是为了防止把「没消息」当成「成功了」。审核被拒不会发通知,唯一的硬证据是新版本号出现在版本列表里。
四种状态,别混成两种
实际发布一遍才会知道,中间还有一层:
| 状态 | 看什么 | 含义 |
|---|---|---|
| 提交成功 | HTTP 201 | 服务器收下了,仅此而已 |
| 已收下待审 | 技能详情里 version 数 ≥1,但列表仍为空 |
正在走扫描与审核,不是失败 |
| ├ 扫描阶段 | 扫描状态 = 排队中 |
还在排队,等着就行 |
| └ 审核阶段 | 扫描状态 = 安全,无风险 而列表仍为空 |
安全扫描过了,卡在内容审核 |
| 已上线 | 版本列表出现目标版本号 | 唯一硬证据,可以收工 |
| 被拒 | 版本数不再增长、列表始终为空、也没有站内通知 | 走「被退回了怎么办」 |
第二行是最容易误判的:这个阶段技能记录已经建好了,tags 里也记上了目标版本,但版本列表还是空的。只看版本列表就会以为发布失败,然后换版本号重发——结果撞上「版本已存在」,越搞越乱。
脚本的 status 会把这两层分开说,publish --wait 的轮询也会报「已收下待审」而不是笼统的「还没出现」。
自检到底查什么
七类,任何一类出 ERR 就当场拦下,不浪费一次提交:
- frontmatter 字段 —— 必需 8 个(
name/slug/displayName/version/summary/description/tags/license)是否齐全;有没有混进平台不认的旧字段 description—— 头号门禁,见下节- 打包范围 —— 有没有非文本文件(位图、Office、压缩包必拒)、有没有无扩展名文件(
LICENSE就这么被退的)、体积是否偏大 - 绝对路径泄漏 —— 包里不该出现开发机的
C:\Users\...之类路径 - 相对路径插图 —— 平台不托管包内文件,相对路径在详情页必破图
- 引用的本地文件是否存在 —— 文档里用反引号点了
scripts/下的某个文件,但文件不在,会扣可靠性分 - 线上版本冲突 —— 联网比对,提前升版而不是等服务器踢回来
description 的四条硬线
这是整件事的核心,代码里写成了断言,不是建议:
- 不许出现「核心触发词 / 辅助触发词 / 关键词:/ Keywords」这类小标题
- 不许写成顿号、逗号堆起来的词条清单(合计超过 10 个就拦)
- 长度落在 60~300 字符,两百字以内最好
- 单段陈述句,不换行
实测对照:同一份正文,description 是 385 字符的堆词版 → 15 秒被拒;压成 103 字符的一段话 → 直接上线。正文一个字没改。
写不好怎么办?照这个句式套:「做什么 + 解决什么问题 + 怎么用」一句话说完,别去想召回率,平台不靠这个词表检索。
详细的判定特征与失败样本见 references/review-rules.md。
被退回了怎么办
先跑 check,八成能直接定位。没定位到就按这个顺序排:
description是不是堆词了(占绝大多数)- frontmatter 有没有多余字段
- 正文里有没有平台不认的表述
不要把上一次过审的包原样重发试试。规则会随时间收紧,复刻旧过审版是死路——有过实例:历史过审包只改了版本号回发,28 秒被拒。
真查不出来,用二分法:拿一个已知能过的极简包(只有 SKILL.md,description 一段话)发一次,确认能过;然后逐轮只加回一个变量,每轮几十秒就能拿到结论。比在完整包上猜改措辞快一个数量级。
不要做的事
- 别把
--changelog写成改稿说明。写「调整了审核措辞」这类元描述,等于主动提醒审核去看措辞。留空,平台会填「发布 x.y.z」。 - 别手工打包。手工打容易夹进
.DS_Store、__pycache__、位图,这些都会让整包被退。用脚本打。 - 别把图标塞进包。图标要在发布页单独上传,位图进包必拒。
- 别在
description里写英文否定句(never do X之类)。中文陈述句更稳。 - 别跳过
--wait。等不到落库就不知道成没成,而失败是不通知的。
边界
做:本地自检、打包、升版、提交、查状态。
不做:改你的正文内容、生成图标、替你判断业务上该不该发布。自检只能拦住形式问题,内容对不对得你自己看。