registry 的拉取与合并规则:117 行代码决定你看到多少条

2026-08-10

cli-hub 这个分发工具的清单层出奇地小。我们采集时(2026-08-10,对应仓库快照 39634a6),cli-hub/cli_hub/registry.py 是 117 行,在整个 cli_hub 包里是最短的模块之一——同目录的 preview.py 是 1839 行,cli.py 是 1030 行。但你敲 cli-hub list 看到多少条、cli-hub search 能搜到什么、以及后面装的时候走哪条路,源头都在这 117 行里。

这一层做的事可以拆成三段:从哪里拉、拉不到怎么办、拉到之后怎么拼。三段各有一处容易记反的地方。

从哪里拉:是两个清单,不是一个

registry.py 第 9 到 10 行写死了两个远端地址,都在 https://hkuds.github.io/CLI-Anything/ 下:一个是 registry.json,一个是 public_registry.json

这两个清单装的不是同一类东西。我们采集时实读仓库根的两份文件,registry.jsonclis 数组是 79 条,public_registry.json 是 22 条,合起来 101 条。两份文件的 meta.updated 都写着 2026-06-19

顺带说清一件容易混的事:矩阵那套东西走的是第三个地址(matrix_registry.json),由 cli_hub/matrix.py 单独拉、单独缓存,不在本文这两个清单的合并链路里。矩阵机制另有一篇专门讲。

拉不到怎么办:缓存的三个参数

同一个文件第 11 到 14 行定下了缓存的落点与时限:缓存目录是 ~/.cli-hub/,两份清单各存一个文件,registry_cache.jsonpublic_registry_cache.json,缓存有效期常量定为 3600 秒。

抓取本身的超时是 15 秒(registry.py:44-52)。这一段里真正值得记住的是失败路径的顺序:抓取失败时回落到过期缓存,仍然没有缓存才抛异常

这就是第一处反直觉。TTL 3600 秒容易被理解成「一小时后这份缓存作废」,但在这段代码的语义里,3600 秒只决定「要不要去联网重拉」,不决定「过期的还能不能用」。网络那一步一旦失败,过期缓存仍然是有效的兜底数据源。也就是说,你在离线或者上游不可达的时候看到的那份清单,可能是很早以前落到 ~/.cli-hub/ 里的快照,而不是当天的内容。

这跟前面那个 meta.updated 是两个层面的时间:2026-06-19 是上游把清单更新到哪一天,你本机缓存文件的写入时间是你上一次成功拉到的时刻。排查「为什么我搜不到某个条目」的时候,这两个时间要分开看。

拉到之后怎么拼:_source 不是显示字段

合并发生在 fetch_all_clis()registry.py:73-90)。它先取 harness 清单、逐条复制一份并打上 _source = "harness",再取 public 清单、逐条打上 _source = "public",最后拼成一个扁平数组返回。按名字查单条与 search_clis() 都建在这个合并结果上。

_source 这个下划线开头的键,看名字很像是给显示用的来源标签。但它的真正去处在安装器里。

cli-hub/cli_hub/installer.py 第 304 到 310 行列出了五种安装策略:pip / npm / uv / command / bundled。第 106 到 119 行是默认推断规则:_source == "harness" 的条目走 pip;带 npm_packagepackage_manager == npm 的走 npm;标了 uvbundled 的各自对应;剩下的一律落到 command

所以合并这一步不只是把两个列表接起来,它同时给每条条目定下了安装时走哪条默认分支。harness 那 79 条走 pip 的这条路还能再往下看一层:installer.py:184-192 执行的是 sys.executable -m pip install …,装到的是当前这个 Python 环境——也就是你运行 cli-hub 时所在的那个解释器环境,不是某个独立沙箱。你如果在系统 Python 下装 cli-hub,装 harness 就是往系统 Python 里装。至于卸载与更新各自走什么命令、装完之后本机哪些目录会多出文件,属于安装侧的落点与策略细节,另有一篇专门讲,本文只到「_source 决定走哪条分支」为止。

public_registry.json 这 22 条则分散在多种策略上。我们采集时按 package_manager 字段统计的分布是:npm 10、pip 4、brew 2、bundled 2、uv 1、script 1,还有 2 条没写这个字段。

101 条不等于 101 个能用

这是必须说清的一条:合并出来的条目数是清单长度,不是可用能力数。

最直白的例子是 bundled 这个策略本身。installer.py:154-162 写明它不真装任何东西,只检测 detect_cmd / entry_point 是否已经在 PATH 里,不在就提示你去上游的那个 App 里启用。按上面的分布,public 清单里有 2 条是这个策略。也就是说,同样是清单里的一行,bundled 那两条根本不经过任何安装动作,它们能不能用完全取决于你本机早先装没装那个上游 App。清单长度把这种差别抹平了,数字看不出来。

harness 那 79 条同样不能按数字理解成 79 个可用能力:一个 harness 是否齐件、能不能真的驱动起对应的宿主软件,取决于它自己那棵目录树的完整度,仓库里确实存在缺件的 harness,那是另一篇的题目。

还有一件必须摆在明面上的事:这一整条链路的终点是在你本机执行外部程序installer.py:67 与第 70 到 84 行的 _run_command() 在命令串里出现 |&&||;$( 或反引号时会走 shell=True,源码注释给出的理由是命令来自受信任的 registry、不是用户输入。这句注释描述的是这段代码的信任前提,值得你在决定要不要用之前自己看一眼——它执行的命令来自一个从网络拉取(或从本机过期缓存回落)的 JSON 清单。而 harness 装好之后要真正干活,还需要你自己在本机装上对应的宿主软件。

检索面:搜索比你以为的宽

search_clis()registry.py:102-112)比对的是四个字段:namedescriptioncategorydisplay_name。按这个写法,用一个分类名当关键词去搜,命中的不会只是名字里含这个词的条目,整类都会被 category 字段捞进来。

命令行侧对应的过滤在 cli-hub/cli_hub/cli.py:158-163list 支持 --category/-c--source/-s(可选值 harness / public / npm / all)和 --json--source 这个选项能存在,正是因为合并时打了 _source

cli-hub list --source harness --json
cli-hub list --category knowledge-management --json
cli-hub search <关键>

以上为按仓库中的参数语义组合的示例,未经实测,以官方文档与 --help 的实际输出为准。另外 cli.py:60-65 定的退出码契约是:0 成功 / 1 失败或未找到 / 2 用法错误 / 3 部分失败或存在 gap,写脚本判断结果时按这个来,别只看有没有输出。

三处文档与代码对不上的地方

在数这几处之前先顺带一提:cli-hub/setup.py:88 的分类器里写的是 Development Status :: 4 - Beta,这是项目自己标的状态。下面这三处口径差异照实列出来,只说差在哪,不推断原因。

第一处,条数。 cli-hub/README.md:5cli-hub/setup.py:54 的描述都写 “40+ CLI harnesses”,而我们采集时实读 registry.json 是 79 条 harness 条目,加上 public 清单共 101 条。README 自称的是 40+,实读是 79/101,两者不一致;以我们实读的仓库状态为准。

第二处,分类清单。 cli-hub/README.md:107 的 “Available categories” 列了 23 个分类名,而两个清单合并去重后的 category 值我们数到 35 个,README 那份里没有出现的 12 个是:automation、data-science、debugging、devtools、finance、knowledge、knowledge-management、mobile、productivity、science、scientific、storage。两处不一致,说到这里为止。你要按分类筛条目时,README 上那一行不是完整清单。

第三处,对 cli-hub 自身的描述。 cli-hub-meta-skill/SKILL.md:85 写的是 “cli-hub is a lightweight wrapper around pip”,而 installer.py:304-310 实现的是五种策略;meta-skill 全文也没有提到 public registry 与 npm / uv 这两条安装路径。两处口径不一致,以源码为准。

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

上面的条数、分类数都是从仓库文件里数出来的,clone 下来就能复现:

python -c "import json;print(len(json.load(open('registry.json'))['clis']))"
python -c "import json;print(len(json.load(open('public_registry.json'))['clis']))"
python -c "import json;print(json.load(open('registry.json'))['meta']['updated'])"

要核对合并与缓存这一层的行为,直接读源码更快,重点是这几处:registry.py 第 9 到 14 行(两个 URL、缓存目录与文件名、TTL 3600)、第 44 到 52 行(超时 15 秒与失败回落顺序)、第 73 到 90 行(合并与 _source),再跳到 installer.py 第 106 到 119 行看 _source 怎么变成安装策略。这四段连起来看一遍,比记住任何一个数字都管用。

本机侧还有一个位置值得知道:清单缓存落在 ~/.cli-hub/ 下的 registry_cache.jsonpublic_registry_cache.json。这两份存的是清单快照,和「你本机到底装了什么」不是同一份记录,排查时别把两件事混成一件。

最后提醒一句时效:这些数字对应的是我们采集时(2026-08-10)的仓库快照 39634a6,清单是随上游更新的,79、22、101、35 这些值随时会变。真正稳定的是规则本身——两个远端、一份可过期回落的本地缓存、一个决定安装分支的 _source


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

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