skill 与 harness 是不是一一对应

2026-08-10

如果你想给 agent 装上 CLI-Anything 里某个软件的能力,最直觉的做法是:仓库根目录下有一个跟这个软件同名的目录,那 skills/ 下大概就有一个 cli-anything-<同名> 的包。我们采集时(2026-08-10,对应仓库快照 39634a6)这个直觉在大多数情况下成立,但至少有四处会让你扑空——而且扑空的方式各不相同。

这篇只讲一件事:skill 与 harness 的对应关系到底是怎么算出来的,以及它在哪些地方不成立。skills/ 目录里每个包放了哪些文件、SKILL.md 的字段和正文结构怎么写,我们另有专门的篇目在讲,这里不重复。

先把四个数摆出来

我们采集时用 find 数出来的四个数:

口径命令结果
skills/ 下的子目录find skills -maxdepth 1 -mindepth 1 -type d | wc -l70
其中以 cli-anything- 开头的同上加 -name 'cli-anything-*'69
harness 目录find . -maxdepth 2 -mindepth 2 -type d -name 'agent-harness' | wc -l69
全仓 SKILL.md 总数find . -name 'SKILL.md' -not -path './.git/*' | wc -l150

70 减 69 等于 1,多出来的那一个是 skills/cli-hub-meta-skill/——它不对应任何一款软件,是一个用来发现目录的 meta skill,命名上也没跟着 cli-anything- 前缀走。这一处好解释。

难解释的是中间那两个 69。它们数值相等,但不是同一批东西

两个 69 是两处偏差抵消出来的

第一处偏差:sketch 这个 harness 没有任何 SKILL.md。我们在仓库里 find sketch -iname '*skill*',无输出;sketch/agent-harness/ 下只有 src、tests、tokens、examples 这些目录(例如 sketch/agent-harness/src/cli.js)。registry.json 里 sketch 那条的 skill_md 字段也是 null。也就是说 69 个 harness 里,实际只有 68 个产出了 skill。

第二处偏差:iTerm2 一个 harness 对应了两个 skill 包skills/cli-anything-iterm2/SKILL.mdskills/cli-anything-iterm2-ctl/SKILL.md 都是 100 行,diff 出来只有第 2 行的 name 不同,正文完全一样。

68 加 1,正好又是 69。这就是两个 69 相等的全部原因——一个 harness 交白卷,另一个 harness 交了两份,一减一加抵消了。你如果只看总数,会得出「一一对应」的结论;一旦你按软件名去找具体的那一个,就会在 sketch 上找不到、在 iterm2 上找到两个。

反直觉的那一处:skill id 是从 Python 包名推出来的,不是从目录名

上面 iTerm2 那份重复,来自仓库里同时存在两套命名来源

同步脚本 .github/scripts/sync_root_skills.py 第 13 到 23 行规定了规范 skill id 的算法:优先取 cli_anything 后面那一级的包名,把下划线换成连字符,再加 cli-anything- 前缀;取不到才退回顶层目录名。而 cli-anything-plugin/repl_skin.py 第 130 到 132 行有一张运行时别名表 software_aliases = {"iterm2_ctl": "iterm2"},banner 里展示给用户的 skill id 是 cli-anything-iterm2。同一个 harness,按包名同步出来的是 cli-anything-iterm2-ctl,按别名展示出来的是 cli-anything-iterm2,两个 id 各自在 skills/ 下有一个目录。

一旦你记住「skill id 跟的是 cli_anything/ 下那一级包名」,另外两处对不上的地方就都能解释了:

harness 目录名cli_anything/ 下的包名skill 包目录
cc-switchccswitchskills/cli-anything-ccswitch/
3MFthreemfskills/cli-anything-threemf/
iterm2iterm2_ctlskills/cli-anything-iterm2-ctl/(另有 cli-anything-iterm2

这三处的 packaged 副本路径分别是 cc-switch/agent-harness/cli_anything/ccswitch/skills/SKILL.md3MF/agent-harness/cli_anything/threemf/skills/SKILL.md。你按目录名 cc-switch3MFskills/ 下搜,一个都搜不到;按包名搜就都在。

反直觉在于:仓库根目录那一层的目录名是你最先看见的东西,也是 README 和 registry 里出现频率最高的写法,但它在 skill 这条链路上不是权威名字。真正决定 skill id 的是藏在两层之下的那个 Python 包目录。

第三套名字:registry.json 里的 skill_md

到这里已经有两套名字了(目录名、包名),registry.json 里还有第三套——它用 skill_md 字段直接写路径,于是每条条目指向哪份 SKILL.md 是逐条声明的,不是算出来的。我们用 Python 读 JSON 分了个类,79 条的分布是:skills/ 下的相对路径 56 条、远程 http URL 11 条、指向 harness 内部 deep packaged 路径的 7 条(calibre、lldb、qgis、unrealinsights、macrocli、3mf、minimax)、null 的 5 条(adguardhome、comfyui、mermaid、sketch、clibrowser)。

CONTRIBUTING.md 第 70 行规定,仓库内 harness 的 skill_md 要写成根 skills/ 树下的相对路径。规则写的是一种,79 条里有 7 条写的是另一种;这两处都可以自己打开文件核对,我们只陈述到这里。

null 的那 5 条里也有两种情况:sketch 与 clibrowser 确实没有对应的 skills/ 目录,而 adguardhome、comfyui、mermaid 三个在 skills/ 下是有目录的——文件已经在了,registry 字段没填。

反过来数也有缺口:registry 里走 skills/ 相对路径的 56 条覆盖了 56 个子目录,skills/ 下另有 14 个目录不被任何一条 registry 的相对路径引用,它们是 cli-anything-adguardhomecli-anything-calibrecli-anything-comfyuicli-anything-iterm2-ctlcli-anything-lldbcli-anything-macroclicli-anything-mermaidcli-anything-minimaxcli-anything-qgiscli-anything-rekordboxcli-anything-threemfcli-anything-unrealinsightscli-anything-zoterocli-hub-meta-skill。这 14 个里,有 7 个是上面那批被写成 deep 路径的,剩下的属于没被引用到。

rekordbox 是这里面比较特别的一个:它有目录、skills/cli-anything-rekordbox 也在,但 registry.json 的 79 个 name 里查不到它。

packaged 副本自己的名字也有对不上的

同一份 SKILL.md 在仓库里通常存两份:一份在根 skills/<skill-id>/,一份在 harness 包内 cli_anything/<software>/skills/。我们把 68 个 packaged 源逐个和根目录下对应文件比了一遍,其中有 5 个 packaged 文件的 name 值与根 skills/ 里的 skill id 对不上

  • 3MF/agent-harness/cli_anything/threemf/skills/SKILL.md:2cli-anything-3mf,根目录是 skills/cli-anything-threemf/
  • obs-studio/agent-harness/cli_anything/obs_studio/skills/SKILL.md:2cli-anything-obs_studio(下划线),根目录是 skills/cli-anything-obs-studio/
  • slay_the_spire_ii/agent-harness/cli_anything/slay_the_spire_ii/skills/SKILL.md:2cli-anything-sts2,根目录是 skills/cli-anything-slay-the-spire-ii/
  • musescore/agent-harness/cli_anything/musescore/skills/SKILL.md:2musescore,没有 cli-anything- 前缀
  • quietshrink/agent-harness/cli_anything/quietshrink/skills/SKILL.md:2quietshrink,同样没有前缀

同步脚本会在写入根 skills/ 时把 frontmatter 里的第一个 name: 整体改写成 name: "<skill_id>"sync_root_skills.py:26-55),源文件不动。所以「你看到的 skill id」和「harness 包里自称的 name」可以是两个值。两份副本之间还有别的差异,属于副本一致性那一篇的话题,这里不展开。

另外有两份 SKILL.md 落在 */agent-harness/skills/cli-anything-*/SKILL.md 这个位置(firefly-iii 与 openrefine),不在同步脚本的两条 glob(sync_root_skills.py:60-61,只扫 cli_anything/ 路径)范围内,因此也不进 CI 校验。其中 firefly-iii/agent-harness/skills/cli-anything-firefly-iii/SKILL.md 是 281 行、version: "1.0.0",而根目录那份是 709 行、version: "2.0.0"

150 份 SKILL.md 分别在哪

前面提到全仓 150 份,这个数拆开看就是上述几套关系的总和:

位置份数
skills/*/SKILL.md70
*/agent-harness/cli_anything/*/skills/SKILL.md67
unimol_tools/agent-harness/cli_anything/unimol_tools/SKILL.md1
*/agent-harness/skills/cli-anything-*/SKILL.md2
其余(cli-hub-matrix 5 个、cli-hub-meta-skill、codex-skill、hermes-skill、reasonix-skill、macrocli)10

unimol_tools 那一份路径少了一层 skills/,与 cli-anything-plugin/guides/skill-generation.md:70-76 画的目录树不一致;同步脚本靠第二条 glob 兜住了它。这也是我们把同步脚本的 _discover_sources() 拿来在本地文件树上求值时,得到 68 个源而不是 67 的原因(我们没有跑 CI,只是用 Python 读取脚本函数后在本地求值)。

注册表里有一条,不等于这个软件你装上就能用

对应关系数清楚之后,还有一层更要紧的:有 skill 包不代表有能用的能力

registry.json 里 79 条这个数字本身也不是「79 个可用软件」。79 条里有 11 条的 source_url 非 null,指向的是独立仓库,仓库根目录下并没有同名目录;sketch 有 harness 目录却连 SKILL.md 都没有;harness 之间的齐件程度差异另有一篇在讲。README 也明确提醒过一句:包装真实桌面软件的 CLI,需要用户自行安装上游应用(README.md:236)。

还有仓库自己标注了状态的:skills/cli-anything-notebooklm/SKILL.md 第 69 行原文写 Treat this harness as experimental and unofficial.,第 3 行和第 8 行的描述里也都带 Experimentalskills/cli-anything-zotero/SKILL.md:29 写 SQLite 写入命令是 Experimental、仅本地、仅用户库,应当按非稳定的高级操作对待。

另外要如实说明的是:这类 harness 的作用就是在你本机去驱动第三方软件,运行时会执行外部程序与脚本,装不装、给它什么权限,需要你结合自己的环境判断。我们没有安装或运行过其中任何一个 harness。

你可以自己跑一遍的核对动作

上面每个数都是从文件系统数出来的,clone 下来就能复现:

find skills -maxdepth 1 -mindepth 1 -type d | wc -l
find skills -maxdepth 1 -mindepth 1 -type d -name 'cli-anything-*' | wc -l
find . -maxdepth 2 -mindepth 2 -type d -name 'agent-harness' | wc -l
find . -name 'SKILL.md' -not -path './.git/*' | wc -l
find sketch -iname '*skill*'
diff skills/cli-anything-iterm2/SKILL.md skills/cli-anything-iterm2-ctl/SKILL.md

我们采集时前四条依次得到 70、69、69、150,第五条无输出,第六条只输出第 2 行的差异。以上命令为按仓库内的文件布局组合的核对动作,未经实测运行环境差异,以你本地 find 实现的实际输出为准。

要判断某个软件到底有没有 skill,比较稳的顺序是:先看 <软件目录>/agent-harness/cli_anything/ 下那一级包名叫什么,再去 skills/cli-anything-<包名转连字符>/ 找;找不到再回 registry.json 里查这一条的 skill_md 字段写的是什么路径。数字会随上游更新变动,重要的不是记住 69 和 70,而是记住这条链路上有三套名字,以及权威的那一套不是你最先看见的目录名。


本文依据 CLI-Anything 官方仓库(github.com/HKUDS/CLI-Anything)的 README、registry.jsondocs/ 与各 agent-harness/ 下的源码整理,核对日 2026-08-10,对应仓库快照 39634a6。 本文内容为仓库源码与文档口径,我们没有安装或运行过其中任何一个 harness, 也没有在本机驱动过任何一款宿主软件,因此不涉及实际操控效果的任何描述。 这类 harness 会在本机执行外部程序与脚本,是否使用请结合自身环境评估。 注册表与 harness 内容随上游更新而变动,请以仓库最新内容为准。 安全相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。