技能发布(Skill Release)
> 只管上架。技能内容怎么写、写什么,不归这里(那是 skill-creator)。
0. 先分清:能力在哪,流程在哪
只盯技能文件会误判。三层要分开看:
| 东西 | 位置 | 作用 |
|---|---|---|
| 发布能力 | ~/.skillhub/skills_store_cli.py(官方 CLI)+ ~/.local/bin/skillhub(shim) |
真正干活的那个 |
| 本技能 | ~/.workbuddy/skills/skill-release/SKILL.md |
只是「怎么用 + 有哪些坑」的规程 |
| 技能本体 | ~/.workbuddy/skills/<name>/SKILL.md |
供模型按 description 触发 |
两条推论(都很反直觉,但实测如此):
- 删掉任何「发布技能」,发布能力一点不减 —— CLI 完全独立。所以那类技能的价值全在规程质量,不在"有没有它"。
- CLI 的
list是空的(实测返回No installed skills.)——~/.workbuddy/skills/下的技能由 WorkBuddy 自己管理(扫目录),不在 CLI 的账上。所以「卸载技能」= 删目录,CLI 里没有uninstall这个命令。
1. 前置:CLI 与身份
# 安装(原则:先下脚本逐行审阅、核对哈希,再执行;不要 curl | bash)
# 只要 CLI 用 --cli-only(完整安装会把默认技能写进 ~/.openclaw/,没有 OpenClaw 的机器上就是死文件)
# 位置:~/.skillhub/{skills_store_cli.py, config.json, credentials.json, metadata.json, version.json}
# shim:~/.local/bin/skillhub
skillhub auth whoami # 核验身份:期望输出 handle + userId
凭据结构(不必再问用户):~/.skillhub/credentials.json 里
{"version":…, "orgs":…, "user": {"host","token","handle","loggedInAt"}} —— Token 在 user.token(skh_ 前缀)。
脚本里读出来直接拼 Authorization: Bearer <token>;不要打印到屏幕、不要写进任何文件、不要入库。
2. 六步流程
2.1 先判断该不该发(省掉一半无用功)
| 情形 | 怎么办 |
|---|---|
| 本地自用、写着玩 | 不发。发布要过三线审核、要长期维护版本 |
| 有实测积累、别人会用 | 发。这类才是真资产 |
| 只是改了错字 | 攒一批再发(每次发布都要过审,别碎发) |
补丁打在别人的技能上要警觉:改本地一份社区技能,你以为在攒资产,其实发不出去(没有发布权),只是给自己打补丁。攒到三五处就该另立自有技能。
2.2 发布前自检(本地能干的全干完)
skillhub publish <skill-dir> --dry-run # 本地预检:只校验 metadata + 打包,不发 HTTP
但预检通过 ≠ 能发布(重要):本地预检对服务端规则完全无感。实测:含 LICENSE 的包 dry-run 报 ✓ Dry-run passed,正式发布返 HTTP 400。
所以下面三条必须人工过一遍:
① frontmatter 必填五项:name / description / slug / displayName / version。
> 最容易踩的一条:本地自用技能通常只写 name + description + agent_created(自用不需要 slug 和版本号)。
> 从「自用」转「发布」,头一件事就是把后三项补齐,否则第一次发布必然卡住;版本从 1.0.0 起算。
② 平台解析器不是完整 YAML:只支持 key: value 与 key: [a, b]。值里不能出现「ASCII 冒号 + 空格」(会被读错)。
嵌套映射 → 读成空串 → 报「缺少 displayName」(硬失败);缩进块列表 → 静默丢弃(不报错但内容没了,最难发现)。
③ 文件类型是白名单制,且平台没有排除机制:
- 白名单:
.md .txt .json .yaml .yml .js .cjs .mjs .ts .py .sh .html .css .csv .toml .png .jpg .svg - 名单外即
HTTP 400 不允许的文件类型: <文件名>;无扩展名文件一律被拒(LICENSE要改名LICENSE.md) - 只排除
.git/.idea/.vscode/node_modules/__pycache__—— 连.bak、.log、.env都不排除 - 所以:包里有隐私文件 ≠ 平台帮你兜底,而是它真的会发出去
打包前逐文件列一遍(find <dir> -type f),核三件事:数量对得上、无隐藏文件、无无扩展名文件。
2.3 脱敏(发出去就收不回来)
核心难点:平台没有排除文件机制,所以敏感内容只能靠"发之前把它换掉"。
要保住真实案例/真实数据,只能拆两版:
| 版本 | 位置 | 内容 |
|---|---|---|
| 私有版 | 留在项目里(如 scripts/<name>.config.json) |
真实路径、真实字段、真实案例 |
| 公开版 | 发到平台 | 全部换成虚构体系(统一用一个假项目名,如 acme) |
词表必须从真相源派生,不能凭记忆手写:
记忆手写的词表一定不够长 → 自检报「0 命中、全绿」→ 假绿(比压根没查更危险,会让你放心地发出去)。
派生来源取并集:① 源码门禁规则里的字面量 ② 出站闸门/敏感词过滤的词表 ③ 项目红线清单。
私有词表刻意放在技能目录之外(如 ~/.workbuddy/skills/.<name>-private.json),否则会被自己打包发出去。
逐项 grep 三样:内部命名/编号码、真实主机名与路径、用户目录名。
2.4 description 是唯一的触发器
模型只依据 frontmatter 的 description 决定要不要加载技能——每次会话它随技能列表整体注入。所以:
> 「能不能用自然语言唤起」≡「description 写得好不好」
四件事:
- 抄真实口语:把用户真会说的话原样写进去(「我这网站安全吗」「记忆涨得太快了」),不要写成产品文档腔
- 消歧:同类技能会抢触发。点名写出边界("功能崩溃走 X,机器稳不稳走 Y")——写在正文里没用,触发时只看 description
- 发布前拿真人话自测:新开一个会话,用大白话问一句,看会不会自动加载。当前会话自测无效(旧 description 已注入,改完不会回灌)
- 改完必升版本(description 属内容变更)
2.5 发布
skillhub publish <skill-dir> --changelog "本次改了什么" --json
- 路径用 Windows 盘符写法(
C:/...);Git Bash 下写/c/...会被解析成D:\c\...→ MODULE_NOT_FOUND(见 §5) --version可覆盖 SKILL.md 里的 version;没有--tags参数(分类与标签只能去网页后台加)网络错误: Tunnel connection failed: 502 Bad Gateway是瞬时抖动,不是内容问题:实测同一刻curl公开端点返 200 / 0.32s、一切正常,重试第 1 次即成功。
处置:包 3 次重试循环(间隔 4s,命中"ok": true才 break)。别因为 502 就去改配置或怀疑包内容。
成功返回的 JSON 里必须拿 fileCount 对账:应等于本地数出来的文件数。
少了 = 有文件被拒或被排除;多了 = 夹带了不该带的。
{"ok": true, "skillId": 245769, "versionId": 369478, "slug": "api-exposure-audit", "version": "1.1.0",
"fileCount": 12, "reviewStatus": "pending", "contentAuditStatus": "pending", "securityScanStatus": "pending",
"category": "", "subCategories": [], "tags": {"latest": "1.1.0"}}
2.6 核验状态(不要只看命令退出码)
# ① 账号维度(要 Token):看 status / reviewStatus / latestApprovedVersion
curl -s -H "Authorization: Bearer $SKH_KEY" https://api.skillhub.cn/api/v1/dashboard/skills
# ② 公开维度(不用 Token,更客观)
curl -s https://api.skillhub.cn/api/v1/skills/<slug>
公开页地址:https://www.skillhub.cn/skills/<handle>/<slug>。
状态语义(实测,三条都遇过):
reviewStatus |
latestApprovedVersion |
含义 |
|---|---|---|
pending |
空串 | 首发、尚无过审版本:记录已建、占位已生成,公开接口已返 200 但 latestVersion.version 为空、页面暂无内容可展示。属正常,不是发布失败 |
pending |
有值 | 已发新版、新版在审:reviewStatus 会从 approved 回落成 pending,而 latestApprovedVersion 仍保留上一个过审版本 → 公开页照旧可装,用户不会因为提交了新版就装不到 |
approved |
有值 | 已上架。三线审核(正文 / 内容 / 安全扫描)全通过即自动上架,无需手动点「上架」 |
2.7 上架后:读统计与 TRACE 评分(公开端点,不需要 Token)
四个只读端点(https://api.skillhub.cn):
| 端点 | 给什么 |
|---|---|
GET /api/v1/skills/{slug} |
详情:skill.stats.{downloads,installs,stars,versions}、securityReports、owner.displayName |
GET /api/v1/skills/{slug}/evaluation |
TRACE 评分明细:五维 → 条目 → score / reason + summary |
GET /api/v1/search?q= |
关键词结果,带 downloads / installs |
GET /api/v1/showcase/{hot,featured,newest,recommended,trending,paid} |
各榜单前 100 |
TRACE 五维:adaptability / convention / effectiveness / reliability / trust(A/C/E/R/T)。
页面显示分 = 各条目分的算术平均(实测 15 条均分 4.60 → 页面显示 4.6/5.0)。
它是平台 AI 读文档给出的文档质量评价,与用户口碑、真实使用效果无关——不要对外当"用户好评"用。
三个陷阱:
installs不可信:实测showcase/newest100 个技能 installs 全为 0(其中含downloads700+ 的),而search接口又确实返非 0。不能用installs=0推断"没人装"。downloads计「取包完成」,且发布/扫描动作本身就会让它涨:实测在自己从未调用下载接口的前提下,发布前后downloads由1 → 2—— 这一次增量只可能来自发布/审核流水线取包。单次计数不可归因,更不能当"外部真实用户"。- 不要用
updatedAt反推动作时间:实测同一技能发布12:31→ 云鼎报告签名13:16→ AI 评分15:24→updatedAt15:49,四者不同步,触发条件未确认。
页面「XX K tokens 预估消耗」= 整包全部文本的 token 估算(实测 94.1 KB 的包显示 28.2K,约 3.3 字节/token),不等于"加载即消耗这么多"——渐进披露下实际只读 SKILL.md。可当"信息密度"参考,别当体量判据。
3. 版本规则
- 首发从
1.0.0起 - 内容变更(含 description、正文、脚本)都要升版本,否则平台看是同版本重复提交
- 改之前先确认这技能是不是自己的(见 §4),别人的 slug 我们发不上去,改它的版本号只会制造错位
4. 先确认归属,再动手(一条血训)
社区技能装到本地后,看着跟自写的一模一样(都只有一个 SKILL.md)。判断归属只看平台,不看本地文件:
curl -s -o /dev/null -w "%{http_code}\n" https://api.skillhub.cn/api/v1/skills/<slug> # 404 = 平台无此名
curl -s -H "Authorization: Bearer $SKH_KEY" https://api.skillhub.cn/api/v1/dashboard/skills # 名下有哪些
实测踩过:以为自己在维护"自己的发布技能",查了才发现 owner 是别人(社区作者),本地那份只是装的旧版快照 → 该 slug 我们没有发布权,
给它升版本号毫无意义。归属判据只信平台返回,不信本地文件长相。
5. Windows / Git Bash 环境(官方口径是 WSL,但 Git Bash 可跑通)
三处必补:
# ① PATH 里补 Git 的 coreutils(否则连 dirname/tail 都没有,shim 会打一堆 command not found)
export PATH="/c/Program Files/Git/usr/bin:/c/Program Files/Git/mingw64/bin:$PATH"
# ② CLI 的 shim 会剥掉 HOME → 报 Could not determine home directory;补上四个变量
export HOME=/c/Users/<user> USERPROFILE='C:\Users\<user>' HOMEDRIVE=C: HOMEPATH='\Users\<user>'
# ③ 传路径用盘符写法
node "C:/Users/<user>/.workbuddy/skills/<name>/scripts/x.cjs" # ✅
node "~/.../x.cjs" 或 node "/c/Users/.../x.cjs" # ✗ 都会被解析成 D:\c\...
另外 tar 处理盘符路径要加 --force-local(否则把 D: 当成远程主机名)。
6. 失败对照表
| 现象 | 原因 | 处置 |
|---|---|---|
dry-run 通过,发布 HTTP 400 不允许的文件类型 |
白名单外文件(常是无扩展名的 LICENSE) |
改名加扩展名,或删掉 |
| 报「缺少 displayName / slug」 | frontmatter 缺必填五项,或值里有「冒号+空格」被解析器读坏 | 补齐 / 去掉冒号+空格 |
| 发布成功但页面上"没东西" | latestApprovedVersion 为空 → 还在审核 |
正常,等三线审核 |
502 Bad Gateway |
瞬时网络抖动 | 重试循环(§2.5) |
skillhub: command not found |
~/.local/bin 不在 PATH |
加入用户级 PATH(勿动系统级;原值先备份) |
| 登录成功但命令仍报未登录 | 走了另一个同名 CLI | 认准 skillhub.cn + skh_ 前缀 Token |
| 改完 description 但当前会话没反应 | 旧 description 在会话开头就注入了 | 新开会话自测 |
7. 沉淀
发布这件事的坑只在真发的时候才暴露。每踩一条,就补一条到 §6 与 §5 —— 这个技能的价值等于它积累的坑的数量。
发布完成后的收尾:① 记清 skillId / versionId / 版本号(下次更新要用);② 顺带确认"公开页现在能装到哪个版本";③ 若这次是首发,检查 description 的触发词是否够口语(这是新技能唯一能被发现的机会)。