cli-hub 做什么:从注册表到本机的那一段
CLI-Anything 这个仓库里,真正会被装到你本机的第一个东西不是某个 harness,而是 cli-hub。它是那层分发工具:从远端注册表把条目列给你看,再按条目里写的方式把东西装下来。我们采集时(2026-08-10,对应仓库快照 39634a6)读的是仓库里的 cli-hub/ 目录,本文只讲这一段——从注册表到本机的那一段——以及这一段里最容易被误解的地方。
先把它的身份摆清楚。cli-hub/setup.py 第 52 行与第 84 行分别定下 PyPI 包名 cli-anything-hub 与命令名 cli-hub;版本 0.4.1 写在两处(setup.py:53 与 cli_hub/__init__.py:3);作者标为 HKUDS,setup.py 元数据里标的许可是 MIT(注意这是这个子包的包元数据,仓库整体的 LICENSE 是 Apache-2.0,两处并存),python_requires=">=3.10"(setup.py:57、:66、:77)。运行期依赖只有两个:click>=8.0 与 requests>=2.28(setup.py:78-81),entry_points 里只注册了一个 console_script(:82-86)。还有一行值得先看:分类器里写着 "Development Status :: 4 - Beta"(setup.py:88),这是项目自己给的成熟度标注,原样记在这里。
整个 cli-hub/ 目录我们采集时共 14 个文件、7447 行。模块分布是这样的:preview.py 1839 行(最大的一个)、cli.py 1030 行、installer.py 604 行、matrix.py 537 行、analytics.py 405 行、matrix_skill.py 397 行、registry.py 117 行。测试在 tests/ 下两个文件:test_cli_hub.py 2021 行、test_matrix_skill_dist.py 265 行,测试函数数按 grep -c "^def test_\| def test_" 分别是 143 个与 12 个。也就是说,这个分发工具本身的代码量,比它的 README(121 行)能讲清的多得多。
那一段链路,拆成四步
第一步是拉表。 cli_hub/registry.py 第 9 到 10 行写死两个远端地址:registry.json 与 public_registry.json,都托管在 https://hkuds.github.io/CLI-Anything/。本地缓存落在 ~/.cli-hub/,缓存文件名 registry_cache.json / public_registry_cache.json,TTL 3600 秒(registry.py:11-14)。抓取超时 15 秒;抓失败时会回落到过期缓存,连缓存都没有才抛异常(:44-52)。
第二步是合并。 fetch_all_clis() 把两个 registry 拼在一起,并给每条打上 _source 标记,值是 harness 或 public(registry.py:73-90)。这个标记后面决定装法。search_clis() 匹配 name / description / category / display_name 四个字段(:102-112)。我们实读仓库根的两个文件:registry.json 79 条,public_registry.json 22 条,合计 101 条,两个文件的 meta.updated 都是 2026-06-19。registry 本身的字段结构与拉取合并的细节另有篇目在讲,这里只交代它在链路上的位置。
第三步是选策略。 这是本篇的重点,下一节展开。
第四步是记账。 安装记录写进 ~/.cli-hub/installed.json,矩阵安装状态写进 ~/.cli-hub/matrix_state.json(installer.py:15-16)。加上上面的缓存文件,~/.cli-hub/ 就是这条链路在你本机留下的全部落点目录。
反直觉的那一处:它不是「pip 的一层壳」
cli-hub-meta-skill/SKILL.md 第 85 行——那是给 agent 读的元技能文件——写的是 “cli-hub is a lightweight wrapper around pip”。而 cli_hub/installer.py 第 304 到 310 行的分派表里有五种策略:pip / npm / uv / command / bundled。同一件事,一处写成 pip 的轻量包装,一处实现了五条路径;meta-skill 全文也没有提 public registry 与 npm / uv 这两条安装路径。两处不一致,以我们实读的仓库状态为准,原因我们不推断。
五条路径怎么选,规则在 installer.py:106-119:_source == "harness" 走 pip;带 npm_package 或 package_manager == npm 的走 npm;uv、bundled 各自对应;其余落到 command。public_registry 那 22 条里,package_manager 的分布是 npm 10、pip 4、brew 2、bundled 2、uv 1、script 1,另有 2 条没这个字段。
分开来看,每条路径落在你本机的位置都不一样:
| 策略 | 实际动作 | 依据 |
|---|---|---|
pip | 执行 sys.executable -m pip install …,装进当前 Python 环境 | installer.py:184-192 |
npm | 走 npm install -g <npm_package>;找不到 npm 时返回 Node.js 安装提示 | installer.py:257-272 |
uv | uv 缺失时返回一段多行提示,列了四种装 uv 的方式 | installer.py:57-64 |
command | 执行条目里写的命令串 | installer.py:304-310 |
bundled | 不装任何东西 | installer.py:154-162 |
最后一行是最容易踩空的:bundled 策略只检测 detect_cmd 或 entry_point 是否已经在 PATH 里,在就报「已可用」,不在就返回一段提示,让你去上游 App 里装或启用它(installer.py:154-162)。这条路径跑完之后,本机并不会因此多出一个可执行文件——它只是替你查了一遍 PATH。
pip 那条同样有个方向要认准:它用的是 sys.executable,装进的是你此刻这个解释器所在的环境,不是某个固定位置。卸载 harness 时包名按 cli-anything-<name> 拼接(installer.py:196),更新走 pip install --upgrade --force-reinstall(:206-215)。至于该不该给它单独开一个隔离环境,取决于你的用法,项目没给通用值,本文不替你定。
「注册表有 79 条」不等于「有 79 个能用」
这是这一段链路上最需要说清的一句。三个可核查的地方叠在一起:
一是装的是壳,不是宿主软件。仓库 README 第 236 行明确提醒:包装真实桌面软件的那些 CLI,需要用户自行安装上游应用。也就是说 cli-hub install 成功只意味着那层 harness 的 Python 包进了你的环境,被它驱动的那个软件仍然要你自己装。
二是有些条目按设计就不由它管。上面 bundled 那条已经是一例。到了矩阵这一侧更明确:cli_hub/matrix.py 第 20 行的 INSTALLABLE_KINDS 只含 harness-cli 与 public-cli 两类,而 provider 的 kind 枚举一共有 8 种(matrix.py:26-35);其中 agent-skill 被单列进 AGENT_INSTALLABLE_KINDS,preflight 时直接标 available=False、状态写成 agent-installable(:17、:173-188)。
三是工具自己就有一个「没齐」的出口。preflight 检查三类依赖:环境变量 env、可执行文件 binary(shutil.which)、Python 包 package(importlib 系)(matrix.py:150-198)。一个 capability 算被覆盖,条件是至少有一个可用 provider,或存在 agent-installable 兜底;其余算硬 gap,并驱动退出码 3(:269-275)。仓库根的 matrix_registry.json 里 5 个矩阵各自带着 3 到 4 条 known_gaps——「有缺口」是这套设计里被正面记录的状态,不是异常。
所以从注册表条数直接推「能用多少」这一步是断的。要判断某个条目在你机器上到底是什么状态,可用的动作是 preflight 与 matrix doctor:后者逐个检查矩阵成员是否已记录安装、entry_point 是否在 PATH,并给出对应的 cli-hub install <name> 修复命令(installer.py:565-589)。
命令面与那个退出码 3
顶层 main 是 click.group(invoke_without_command=True),裸跑就打印 help(cli.py:82-93)。按 grep -c "@main.command\|@main.group" 数出来是 10 个:8 个顶层命令 install / uninstall / update / list / search / info / launch / can,加两个命令组 previews 与 matrix。matrix 下 7 个子命令(list / search / info / preflight / install / doctor / recipes),previews 下 4 个(inspect / html / watch / open)。矩阵那套机制另有一篇专门讲。
退出码契约写在 cli.py:60-65:0 成功 / 1 失败或未找到 / 2 用法错误 / 3 部分失败或存在 gap。这四个值得单独记,因为 3 不是「出错」——它是「这次装了一部分、还有缺口」。你在脚本里按「非 0 即失败」处理,会把这种状态误判成失败;cli-hub-meta-skill/SKILL.md 第 55 到 56 行复述的也是同一套约定,两处一致。
previews 这一组的分工在 README 里写得很硬:cli-anything-<software> preview … 负责产生预览状态,cli-hub previews … 只负责查看,原文是它 “never renders or publishes previews by itself”(cli-hub/README.md:44-46、:74)。翻不出预览时,先认清是哪一侧没产出。
两处对不上的数字
写在这里,是因为你按 README 找东西时会直接撞上。
一是条数。cli-hub/README.md:5 与 setup.py:54 的 description 都写 “40+ CLI harnesses”,而我们实读 registry.json 是 79 条、加 public_registry 共 101 条。二是分类。README 的 “Available categories” 一节(:107)列了 23 个分类名,而两个 registry 合并去重后有 35 个 category 值,按 Python set() 差集,README 那份没列到的包括 automation、data-science、debugging、devtools、finance、knowledge、mobile、productivity、science、storage 等 12 个。两处差异各自可核,我们只陈述到这里。
装它之前该知道的两件事
一件是埋点。 cli_hub/analytics.py 第 16 行把默认 provider 定为 posthog,legacy 的 umami 通过环境变量 CLI_HUB_ANALYTICS_PROVIDER=umami 切(:88-90);退出方式是把 CLI_HUB_NO_ANALYTICS 设为 1 / true / yes(:84-85)。匿名 distinct_id 是一个 uuid4,存在 ~/.cli-hub/.analytics_id(:23、:194-210)。事件由 daemon 线程异步发,超时 5 秒,异常一律吞掉,注释原文是 “analytics must never break the user’s workflow”(:247-264、:278-281);进程退出时 atexit 等在飞请求,每个线程 join 超时 3 秒(:73-81)。埋点函数共 9 个 track_*:install / uninstall / launch / matrix_install / matrix_preflight / matrix_discover / matrix_info / visit / first_run(:284-387)。这是默认开、可用环境变量关的设计,装之前值得知道。
另一件是它会在本机执行外部程序。 五种策略里,pip(sys.executable -m pip install …)、npm(npm install -g …)、command(执行条目里写的命令串)这三条在源码里有明确的外部命令执行动作,bundled 按设计不装任何东西。installer.py 第 67 行与第 70 到 84 行还有一处需要留意:当命令串里含 |、&&、||、;、$( 或反引号时,_run_command() 使用 shell=True,注释写明理由是「命令来自受信任的 registry,不是用户输入」。换句话说,这条链路的信任边界落在 registry 的内容上。要不要在受限环境或隔离账户下跑这类工具,是通用运维层面的判断,不是该项目文档里的内容,请结合自身环境评估。
你可以自己复现的核查动作
不必安装,clone 下来读文件就能核:
# 两个 registry 各有多少条
python -c "import io,json;print(len(json.load(io.open('registry.json',encoding='utf-8'))['clis']))"
python -c "import io,json;print(len(json.load(io.open('public_registry.json',encoding='utf-8'))['clis']))"
# 命令面与子命令组
grep -c "@main.command\|@main.group" cli-hub/cli_hub/cli.py
grep -c "@matrix.command" cli-hub/cli_hub/cli.py
两条 grep 是我们采集时用的原样口径;两条 python 是按同一口径(读 JSON 后取 clis 数组长度)复原的写法,未经实测,以你本机 Python 与仓库实际内容为准。
再比对三处文本:cli-hub/README.md:5 的 “40+“、cli-hub-meta-skill/SKILL.md:85 的 “wrapper around pip”、cli-hub/cli_hub/installer.py:304-310 的五策略表。这三处摆在一起看,前面说的那件事就不用记结论了——你会自己看出来「一条 install 命令背后到底会发生什么」不止一种可能。数字会随上游更新变动,重要的是记住这条链路的四步,和 bundled 那个不装东西的方向别记反。
本文依据 CLI-Anything 官方仓库(github.com/HKUDS/CLI-Anything)的 README、registry.json、
docs/ 与各 agent-harness/ 下的源码整理,核对日 2026-08-10,对应仓库快照 39634a6。
本文内容为仓库源码与文档口径,我们没有安装或运行过其中任何一个 harness,
也没有在本机驱动过任何一款宿主软件,因此不涉及实际操控效果的任何描述。
这类 harness 会在本机执行外部程序与脚本,是否使用请结合自身环境评估。
注册表与 harness 内容随上游更新而变动,请以仓库最新内容为准。
许可条款请以官方 LICENSE 原文为准,本文不构成法律意见。
安全相关做法请结合自身环境评估,本文不构成安全方案建议。