安装策略与落点:cli-hub 会往你机器上写哪些目录
装任何一个包管理器之前,值得先问一句:它往我机器上写什么。cli-hub 这个工具尤其值得问,因为它管的不是纯 Python 库,而是一批要去驱动本机第三方桌面软件的 harness。本文只谈一件事:执行 cli-hub install 之后,哪些路径被写、哪些没有被写。截至 2026-08-10,我们读的是 CLI-Anything 仓库快照 39634a6,cli-hub 包自身版本写在 cli-hub/setup.py:53 与 cli-hub/cli_hub/__init__.py:3 两处,都是 0.4.1,setup.py 的分类器里带着 "Development Status :: 4 - Beta"(cli-hub/setup.py:88)——这个 Beta 标记是仓库自己打的,照实记在这里。
五种策略,写在同一个常量里
安装逻辑集中在 cli-hub/cli_hub/installer.py。支持的策略共 5 种,列在第 304-310 行:pip / npm / uv / command / bundled。
一条 registry 条目大多数时候不显式声明策略,走的是第 106-119 行的默认推断:
_source == "harness"(即来自仓库根registry.json的条目)→ 走 pip- 带
npm_package字段,或package_manager == npm→ 走 npm - 显式写了
uv/bundled→ 各自对应 - 其余全部落到
command
这条推断链解释了一个容易看错的现象:registry.json 里 79 条 harness(我们采集时实读的条数)几乎不写 install_strategy 字段——整个数组里只有 siyuan 一条带这个字段,值是 "pip"。其余 78 条不是”没配置”,而是靠 _source 兜到了 pip 分支。
反直觉的那一处:账本和落点不是同一样东西
cli-hub 会把安装记录写进 ~/.cli-hub/installed.json,矩阵安装状态写进 ~/.cli-hub/matrix_state.json(installer.py:15-16)。看到这两个文件,很容易以为 harness 被装进了 ~/.cli-hub/ 里的某个私有目录,卸载就是删这个目录。不是。
第 184-192 行写得很直白:harness 安装实际执行的是 sys.executable -m pip install …。也就是说,包被装进的是当前解释器所在的那个 Python 环境——你在哪个虚拟环境里跑 cli-hub,它就装进那个环境。installed.json 只是一本记在 home 目录下的账,它和真实落点是两套东西,可以各自独立地对不上。
这不是我们的推测,是源码里自己承认的:matrix doctor 的实现(installer.py:565-589)逐个成员检查两件事——是否已被记录为安装,以及 entry_point 是否在 PATH 里,任一不满足就给出 cli-hub install <name> 的修复命令。把这两个条件分开检查,本身就说明它们被当成两个可以分别失败的判据。
真实落点具体在哪,取决于策略:
| 策略 | 实际写到哪 | 源码位置 |
|---|---|---|
pip(harness 默认) | 当前 Python 环境(sys.executable -m pip install) | installer.py:184-192 |
npm | 全局 npm 目录(npm install -g <npm_package>) | installer.py:257-272 |
uv | 由 uv 决定;uv 缺失时只返回一段多行安装提示 | installer.py:57-64 |
command | 由 registry 条目自带的命令串决定 | installer.py:67-84 |
bundled | 不写任何文件 | installer.py:154-162 |
最后一行值得单独说。bundled 策略根本不执行安装:它只检测条目的 detect_cmd / entry_point 是否已经在 PATH 中,不在就提示你去上游 App 里启用。也就是说,同一句 cli-hub install,对某些条目意味着往磁盘写包,对另一些条目只意味着做一次探测。截至 2026-08-10 我们采集时,public_registry.json 里 package_manager 的取值分布是 npm 10、pip 4、brew 2、bundled 2、uv 1、script 1,另有 2 条缺该字段——bundled 那两条就属于”装了等于没装”的类型。
顺带一句:registry.json 有 79 条不等于这 79 个软件装上就能用。harness 是否齐件、能不能真正驱动宿主,是另一层的问题;README 也明确提醒过,包装真实桌面软件的 CLI 需要用户自行安装上游应用(README.md:236)。这一点在本站另有专门一篇讲 harness 的缺件情况。
卸载和更新是按名字拼出来的
既然装是走 pip,卸载与更新自然也是。installer.py:196 卸载 harness 时把包名按 cli-anything-<name> 拼接;第 206-215 行更新走的是 pip install --upgrade --force-reinstall。
拼接这件事有个直接后果:能不能卸干净,取决于 registry 里的 name 和 PyPI 上的实际包名是否严格对得上。harness 的 install_cmd 在 registry.json 里写成这种形态(以第 1 条 jumpserver 为例,registry.json:16):
pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=jumpserver/agent-harness
装是从 GitHub 子目录直接装的,而卸载走的是名字拼接这条独立路径——两边并不共用同一个字符串。
~/.cli-hub/ 下到底有什么
把散在几个模块里的落点合到一起,这个目录里会出现的东西是:
| 路径 | 写它的模块 | 说明 |
|---|---|---|
installed.json | installer.py:15-16 | 安装账本 |
matrix_state.json | installer.py:15-16 | 矩阵安装状态 |
registry_cache.json | registry.py:11-14 | 主 registry 缓存,TTL 3600 秒 |
public_registry_cache.json | registry.py:11-14 | 公共 registry 缓存,同 TTL |
matrix_registry_cache.json | matrix.py:13-15 | 矩阵 registry 缓存,同 TTL |
matrix/<name>/SKILL.md | matrix_skill.py:5-8、:31、:40 | 渲染出来的矩阵技能,同目录并排放 references/ 与 scripts/ |
matrix/<name>.SKILL.md | matrix_skill.py:68-79 | 旧的扁平布局,仅在新路径不存在时回落 |
.analytics_id | analytics.py:23、:194-210 | 一个 uuid4 的匿名标识 |
最后一行是很多人不会预期的:跑一次 cli-hub 就会在这里留下一个匿名 id。埋点默认 provider 是 posthog(analytics.py:16),退出方式是把 CLI_HUB_NO_ANALYTICS 设为 1 / true / yes(analytics.py:84-85)。事件通过 daemon 线程异步发送、超时 5 秒、异常一律吞掉,源码注释写的是 analytics 绝不能打断用户的工作流(analytics.py:247-264、:278-281)。另外它识别 agent 宿主的父进程探测走的是 /proc/<pid>/status 与 /proc/<pid>/cmdline(analytics.py:104-118),这是 Linux 路径。
矩阵那侧的渲染落点(~/.cli-hub/matrix/)另有一篇专讲,这里只强调它同样落在这个目录下,不要以为矩阵内容只存在于远端。
它会在你机器上执行外部命令
这一点必须说清楚,不能一笔带过。installer.py:67 与第 70-84 行的 _run_command(),在命令串里含 |、&&、||、;、$( 或反引号时会用 shell=True 执行,源码注释给出的理由是这些命令来自受信任的 registry、不是用户输入。这就是说:执行什么,由远端 registry 里的字符串决定。加上 harness 本身的用途就是驱动本机的第三方软件,整条链路上会有第三方程序与脚本在你的机器上跑起来。
这里不做任何”要不要放心用”的判断,只把机制摆出来:远端 JSON → 本地 shell。你自己的环境是否适合这样用,由你评估。
可复现的核查动作
不必安装就能自查的几步:
git clone仓库后打开cli-hub/cli_hub/installer.py,跳到第 304-310 行看策略枚举,再跳到 106-119 行看默认推断——两处对着读,就知道你关心的那条 registry 条目会走哪一支。- 想确认某条目是不是”装了等于没装”,看它有没有
package_manager: bundled,再对照installer.py:154-162。 - 已经装过的机器,直接列
~/.cli-hub/的文件,和上面那张表逐行对。表里没有的文件,说明来自我们这次没有逐行读完的模块(preview.py等),本文不做推测。 - 怀疑账本和环境脱节时,用矩阵的体检命令:
cli-hub matrix doctor <matrix-name>
它会逐个成员报”是否记录安装 / entry_point 是否在 PATH”。命令按仓库中的参数语义组合,未经实测,以官方文档与 --help 的实际输出为准。顺带记住退出码契约(cli-hub/cli_hub/cli.py:60-65):0 成功、1 失败或未找到、2 用法错误、3 部分失败或存在 gap——第 3 种最容易被脚本当成成功,因为它不是 1。
一处口径差
cli-hub-meta-skill/SKILL.md:85 把 cli-hub 描述为 “cli-hub is a lightweight wrapper around pip”,而 installer.py:304-310 实现的是 pip / npm / uv / command / bundled 五种策略;meta-skill 全文也没有提到 public registry 与 npm、uv 这两条安装路径。两处口径不一致,以我们实读的仓库状态为准。
说到这里就停——本篇只负责告诉你,按哪一份描述去预期落点会落空。
其它宿主的安装落点
如果你不是直接用 cli-hub,而是通过某个 agent 宿主的适配层接入,落点又是另一批路径:codex 侧是 ${CODEX_HOME:-$HOME/.codex}/skills/cli-anything,目录已存在会拒绝覆盖(codex-skill/scripts/install.sh:10-11、:28-32);hermes 侧是 ${HERMES_HOME:-$HOME/.hermes}/skills/cli-anything-hermes(hermes-skill/scripts/install.sh:7-18);Pi 扩展落在 $HOME/.pi/agent/extensions/cli-anything(.pi-extension/cli-anything/install.sh:17);qoder 那一路谁的目录都不建,只往 ${QODER_HOME:-$HOME}/.qoder.json 的 plugins.sources.local[] 里注册一个绝对路径,改写前先备份成 .qoder.json.bak(qoder-plugin/setup-qodercli.sh:41、:75-84、:103-109)。
这几家各自对接谁、机制有什么差别,本站另有一篇专门讲,这里只列落点,方便你在清理机器时知道该去哪几个地方看。
本文依据 CLI-Anything 官方仓库(github.com/HKUDS/CLI-Anything)的 README、registry.json、
docs/ 与各 agent-harness/ 下的源码整理,核对日 2026-08-10,对应仓库快照 39634a6。
本文内容为仓库源码与文档口径,我们没有安装或运行过其中任何一个 harness,
也没有在本机驱动过任何一款宿主软件,因此不涉及实际操控效果的任何描述。
这类 harness 会在本机执行外部程序与脚本,是否使用请结合自身环境评估。
注册表与 harness 内容随上游更新而变动,请以仓库最新内容为准。
安全相关做法请结合自身环境评估,本文不构成安全方案建议。