☰
CLI-Anything评测:用YAML配置将复杂参数变成交互式问答
2026/9/28 5:23:02 网站建设 项目流程

1. 从“命令记不住”到“让终端来问我”:我为什么开始折腾CLI-Anything

这些年我手上攒了一堆乱七八糟的脚本:部署要敲环境参数,备份要输日期,批量处理文件要传路径。每个脚本的用法我都写在注释里,可真的要用的时候,还是得去翻历史记录,一边翻一边骂自己“当初怎么不写个帮助文档”。

后来我注意到社区里有人聊 CLI-Anything,说是能让你“用配置而不是代码”快速生成一套交互式命令行工具。消息刚放出来的时候,质疑声挺多:YAML 写命令参数和交互提示,听起来很方便,可一旦遇到复杂校验、嵌套参数、动态选项,会不会比写代码还痛苦?

我实际把它下载下来,配了两个真实场景(一个部署脚本、一个日志采集脚本),跑了大概一周,最后把项目里三个原来靠命令行参数硬撑的模块全部换成了它。这篇文章就是这次完整评测的记录,从安装到配置,再到扩展机制和踩坑过程,全部基于我自己在 0.4.x 版本上的实测。

先说结论:CLI-Anything 的定位不是一个“万能命令行框架”,它解决的是一个很具体的问题——把那些你不想背、又必须传的复杂参数,变成终端里的一个个提问,让你从“回忆参数格式”变成“回答几个问题”。如果你跟我一样被长参数折磨过,它大概率对你有用;但如果你希望它像 Click 或 Commander 那样提供完整的编程接口,那可能会失望。下面我把整个体验过程拆开讲。

2. 安装和初始化:Node环境、全局安装与第一份配置

2.1 为什么选这个工具的安装方式:一条命令背后的依赖逻辑

CLI-Anything 是一个基于 Node.js 的 CLI 工具,默认通过 npm 全局安装。官方文档给了一条命令:

npm install -g cli-anything

我建议装之前先确认 Node 版本。我在测试机上最开始用的是 Node 16,结果安装过程虽然成功,但第一条命令跑起来就直接报错,提示缺少globalThis相关特性。查了下 issue 区,官方要求 Node 18 及以上,因为内部使用了较新的node:util接口和structuredClone。换到 Node 20 之后一切正常。

如果你平时用 pnpm 或者 yarn,也可以装,但注意全局安装的 bin 链接路径可能不同。我自己在 macOS 上是直接用 npm 装的,没额外配环境变量;Windows 上可能遇到 npm 全局目录没加到 PATH 的问题,装完执行anything --version没反应的话,先检查npm prefix -g指向的目录是否在 PATH 里。

这里说一个我比较欣赏的设计:CLI-Anything 并不要求你先创建一个“项目”才能使用。它默认在工作目录下寻找配置文件,同时支持通过环境变量指定配置路径。这意味着你可以把它当成一个全局工具箱,在任意目录下执行,只要给一条--config参数指定配置文件位置即可。

2.2 初始化命令:自动生成配置骨架

安装完成后,我建议不要急着写配置,而是先在某个空目录里执行:

anything init

这个命令会生成一个anything.yaml初始文件,内容大致是:

version: "1.0" settings: locale: zh-CN show_hint: true commands: example: description: "示例命令" script: "echo hello" prompts: []

它帮你把最基础的结构搭好了。这里有个很多人初接触时容易误会的地方:CLI-Anything 里的script字段不是要你写一段完整的 shell 脚本,而是指定“最终要执行的那条命令或脚本文件”。交互式提示(prompts)负责收集参数,收集完后工具会把参数注入到这条 script 里,再交给系统的 shell 执行。

我实际使用时,anything init生成的样例配置本身没有实际执行价值,但它有两个好处:一是帮你验证安装是否成功,二是一个可直接修改的模板。我看社区里有人把 init 生成的文件直接丢进 Git 仓库当模板,然后团队内部所有人都从这份模板复制,省去不少沟通成本。

2.3 三个最容易被新手踩中的安装坑

我装完第一天就踩了三个坑,这里按严重程度排个序:

  1. PATH 不生效:npm install -g成功,但终端跑anything提示 command not found。处理方式是先执行npm root -g找到全局目录,再把$(npm prefix -g)/bin加入 PATH。
  2. Node 版本过低:症状是执行任何命令都报Error: module is not defined in ES module scope,因为 0.4.x 内部用的esm打包模式对 Node 版本有硬性要求。升级到 Node 18+ 立刻解决。
  3. YAML 解析失败但没明显语法错误:CLI-Anything 对缩进和生产中常见的tab字符非常敏感。我从别处复制配置时,编辑器把空格转成了 tab,直接导致解析失败。所以配置文件的缩进统一用两个空格,别用 tab。

判断安装是否成功,不是因为能跑anything --version就够了,最好再执行一次anything doctor(如果有这个命令的话)。实测在 0.4.x 中,这个命令会检查 Node 版本、全局配置路径权限、默认 shell 是否可用,一次性帮你排除掉环境层面的隐患。

3. 配置文件的字段拆解:每个定义如何影响运行时行为

3.1 顶层结构:commands、settings 与全局变量

CLI-Anything 的配置文件核心只有三个顶层区块:settings、commands、globals。

  • settings:全局开关,比如locale控制提示语言,show_hint控制是否显示底部快捷键,shell指定执行脚本用的 shell 解释器。
  • commands:你定义的所有子命令,每个命令名对应一张完整配置。
  • globals:定义全局可用的模板变量,在多个命令中可以通过{{ varName }}引用。

我一开始忽略了globals的作用,以为把所有变量写进每个命令里最简单。后来发现几十个命令里重复出现同一个枚举值(比如环境名列表),改一处要改所有地方。把公共变量放进globals后,命令配置里的select选项可以直接引用,大大减少了维护成本。

下面是我在测试项目里用的一份简化配置,你可以拿它当参考:

globals: environments: - dev - staging - prod git_tag_pattern: "^v[0-9]+\\.[0-9]+\\.[0-9]+$" commands: deploy: description: "构建并部署到指定环境" script: "./scripts/deploy.sh" prompts: - name: env label: "选择目标环境" type: "select" options: "{{ environments }}" - name: version label: "输入发布版本号" type: "input" validate: "{{ git_tag_pattern }}" required: true

运行anything run deploy时,CLI-Anything 会依次弹出两个问题:先让你选环境,再让你输版本号。收集完这两个值,最终拼接并执行命令:

./scripts/deploy.sh --env staging --version v1.2.3

这里的关键在于:它不是简单地把参数拼接进字符串,而是按照script字段指定的模板进行变量替换。具体替换规则我后面有一节专门演示。

3.2 prompts 的类型:input、select、confirm、multiselect

交互式提示是整个工具的灵魂,而提示类型直接决定了你输入参数的方式。0.4.x 版本里我实测比较常用的有四种:

type用途交互形态我实测的感受
input自由输入文本直接出现输入框适合输版本号、路径、用户名
select单选方向键上下选择适合选环境、分支、操作类型
confirm是/否y/N 问答适合确认是否执行危险操作
multiselect多选空格键多选,回车确认适合选模块、功能特性

如果你用过 Inquirer.js 或者交互式表单库,对这套交互不会陌生。CLI-Anything 的做法是把这个交互层下沉到配置文件中,而不是写在代码里。代价是动态逻辑的表达能力变弱,但也正因为弱,配置看起来非常直观,非开发人员也能改明白。

我自己试过给一个命令配置了 6 个连续的 prompt,交互过程没有卡顿感,每个 prompt 之间响应速度很快。唯一要注意的是multiselect的输出格式:默认是一个逗号分隔的字符串,如果你要把它传入脚本,可能需要配合后面的“模板变量”功能来做格式化。

3.3 validate、required、default:参数校验的三板斧

参数校验是 CLI 工具体验的分水岭。很多命令行工具的校验都是“你传错了我就报错”,而 CLI-Anything 是“你传错了我不让你提交”。它在 prompt 配置里提供了三个字段:

  • required: 是否必填,设为 true 后,空值无法跳过。
  • validate: 一个正则表达式字符串,输入必须匹配,否则提示重新输入。
  • default: 预填值,用户直接回车可以跳过输入。

我们看一个实际场景:

prompts: - name: date label: "输入日期(YYYY-MM-DD)" type: "input" validate: "^\\d{4}-\\d{2}-\\d{2}$" default: "2025-01-01"

如果我输入2025/01/01,正则匹配失败,工具会显示红色错误提示并让重新输入。如果直接回车,默认值是2025-01-01。

我特别注意了一下正则的转义规则:因为在 YAML 里,^\\d{4}这种写法最终传给正则引擎的是^\d{4}。如果你写成\d,反而可能解析失败或者匹配到字面字符“d”。这是新手最容易翻车的地方。

再补充一个心得:相比于把校验规则写得过于严格,我建议在生产配置里尽量放宽。原因是 CLI-Anything 的交互式流程对“错误重试”的提示还算友好,但错误信息默认就是“输入不合法”五个字,没法自定义。如果你希望给用户更具体的提示(比如“日期格式应为YYYY-MM-DD”),目前只能靠label字段里加说明,或者把校验切到自定义 JavaScript 钩子(后面会讲)。所以,要么写一个比较宽松但安全的校验,要么把格式要求直接写进 label。

4. 交互式参数生成器:CLI-Anything 真正提升效率的地方

4.1 运行一个命令:从“记忆参数”变成“回答问题”

配置好之后,执行一个命令的方式非常统一:

anything run deploy

运行后,终端会展示一个带标题的交互面板。以我那个部署命令为例,顺序是:

  1. 显示选择目标环境,列出 dev、staging、prod,默认光标停在 dev 上。
  2. 显示输入发布版本号,输入框里预填上一个版本号,如果直接回车就用默认值。
  3. 收集完所有参数后,底部出现一行确认提示:确认执行? [Y/n],按回车执行。

这个“最后确认”的机制我很喜欢。它相当于给危险的部署操作加了一道保险,而且不需要额外配置。如果你执行的是诸如rm -rf这类危险命令,建议一定保留确认步骤。

完成所有交互后,CLI-Anything 会先打印一条拼接好的完整命令(比如./scripts/deploy.sh --env staging --version v1.2.3),再执行。这样做的好处是:一旦出问题,你能立刻看到真正执行的命令是什么,方便排查。

4.2 模板变量的拼接逻辑:我为什么说它不简单拼接

刚才讲到,脚本字段并不是简单的字符串模板套用。实际上它有两种变量替换方式。

第一种是直接替换:

script: "./scripts/deploy.sh --env {{ env }} --version {{ version }}"

运行时会原样替换为:

./scripts/deploy.sh --env staging --version v1.2.3

第二种是基于 shell 环境变量的传递。你可以在settings里开启export_env,这样所有 prompt 收集到的值会以环境变量的形式传给子进程:

settings: export_env: true

这样 script 脚本里可以直接用$PARAM_ENV、$PARAM_VERSION来读取,脚本本身就不需要重复解析参数了。我用这种模式改造了一个原本要写 20 行参数解析逻辑的 Python 脚本,改用环境变量后,脚本开头几行全是os.getenv(),干净很多。

实际执行时,CLI-Anything 会把这两种模式合在一起:如果 script 字符串里有{{ varName }},就做模板替换;同时还把参数放进子进程环境变量。这样脚本既可以接收位置参数,也可以读取环境变量,扩展性很强。

4.3 快捷参数与子命令嵌套:复杂场景下的组织方式

如果你的命令很多,全部塞进commands下会让配置文件变得很长。CLI-Anything 支持在命令配置里加subcommands子命令结构。实际表达类似这样:

commands: deploy: description: "部署相关命令" subcommands: web: script: "./scripts/deploy-web.sh" api: script: "./scripts/deploy-api.sh"

运行时就可以执行:

anything run deploy web

这个设计我能给高分。它不像很多框架那样把子命令当成一个独立的“命令前缀”,而更像一棵命令树。你可以先定义共同逻辑(比如父命令添加一些公共 prompt),再在子命令里补充更具体的部分。

我还测试了参数的“快捷传入”能力。CLI-Anything 完全在交互提示里获取参数,并不像传统 CLI 那样支持--env staging这种直接传参方式。但可以用另一个命令anything ask deploy env=staging,跳过该 prompt,继续问剩下的问题。实测这样确实能跳过指定 prompt,很适合在自动化脚本里半交互式地调用。

5. 扩展机制:自定义校验、钩子和模板渲染

5.1 用 JavaScript 文件做自定义校验器

正则校验只适合简单格式。一旦遇到“版本号必须大于上一个版本号”“环境名必须存在于生产服务器列表”这类逻辑,就需要自定义校验。

CLI-Anything 允许在 prompt 配置里写validator字段,指向一个 js 文件路径。比如:

prompts: - name: version label: "输入版本号" type: "input" validator: "./validators/version-check.js"

这个文件需要导出这样一个函数:

module.exports = function(input, context) { const minVersion = context.globals.minVersion || "v1.0.0"; if (input < minVersion) { return `版本号必须大于 ${minVersion}`; } return true; };

返回true就表示校验通过,返回字符串就会被当作错误提示信息。这里的context对象里装着之前所有 prompt 的回答以及globals配置,所以你可以做跨字段校验,比如“如果环境是 prod,则版本号必须以 release 开头”。

我一开始遇到的主要问题是路径解析:validator字段如果写相对路径,是相对于当前工作目录的,而不是配置文件所在目录。在多目录结构下很容易踩坑。建议统一用绝对路径,或者在运行时先cd到配置文件所在目录,再执行命令。

5.2 Hooks:在命令执行前后插一脚

CLI-Anything 的另一个实用扩展是 hooks。它可以在命令正式执行前后运行指定的脚本,常见用途是:

  • 执行前自动拉取最新代码、检查端口占用。
  • 执行后推送通知、上传日志。

配置如下:

commands: deploy: script: "./scripts/deploy.sh" hooks: pre: "./hooks/pre-deploy.sh" post: "./hooks/post-deploy.sh"

实测发现,pre 钩子如果执行失败(非零退出码),后续的主命令会直接中止,不会继续执行。这一点非常有用,等于自带了一个“前置条件检查闸门”。比如我在 pre 钩子放了一个网络连通性检查,ping 不通环境时直接阻断部署,避免脚本跑到一半才报错。

post 钩子的行为则不同:即使主命令失败,post 钩子默认也会执行。如果你不希望失败时执行通知脚本,可以在钩子配置里加on: success字段:

hooks: post: script: "./hooks/upload-log.sh" on: success

5.3 通过模板生成脚本内容:不只是变量替换

比script字段更进一步的是template字段。它允许你直接在配置里写一小段模板文本,运行时渲染成临时脚本再执行。这个设计解决一个痛点:某些脚本内容很短,单独建一个 .sh 文件很啰嗦,但又需要用到复杂变量。

commands: report: template: | #!/bin/bash echo "生成 {{ appName }} 的日报" echo "日期:{{ date }}" ./scripts/gen-report.sh --app "{{ appName }}" --date "{{ date }}"

运行anything run report时,CLI-Anything 会先把这段模板渲染成真实 bash 脚本,再放进临时目录执行。

如果你用过 Ansible 的 playbook 模板,会发现这个思路异曲同工。好处是脚本内容和配置封装在一个文件里;坏处是 YAML 里写 shell 脚本同样存在缩进和引号转义的经典问题,写完建议先执行一次anything lint(如果有这个命令)检查语法。

模板引擎支持简单的循环和条件。比如在globals里定义了应用列表,模板里可以这样:

commands: scan: template: | {% for app in apps %} ./scripts/scan.sh "{{ app }}" {% endfor %}

这种能力已经让它不止是一个“交互式命令包装器”,而是一个相对完整的命令生成器。但请注意:模板引擎的执行环境是 Node.js,所以写{% for %}时变量作用域规则跟 JS 类似。如果报变量未定义,先检查 globals 是否真的传进去了。

6. 性能与兼容性实测:参数不大但血泪不少

6.1 冷启动与交互延迟数据

命令行工具最怕“启动慢”。我专门测了几组耗时数据,环境是 macOS 14、Node 20.11,机器是 M1 芯片。

测试项耗时
anything --version冷启动约 320ms
anything run deploy进入第一个 prompt约 480ms
配置为 50 个命令时的解析耗时约 90ms
一个 6 prompt 命令的完整交互+执行耗时约 2.8s(含人工输入时间)

冷启动 300 多毫秒,对于日常使用属于可接受范围,但比直接用原生 shell 写脚本要慢一个量级。如果你有人每天执行 50 次命令,这 300ms 累积下来也有十几秒。不过 CLI-Anything 的主要价值是减少“想参数”的时间,那部分时间通常远超 300ms,所以整体还是划算的。

配置解析性能我测了 50 个命令的情况,完全没有压力。实际上 0.4.x 解析 YAML 是一次性载入内存的,只要不把配置文件写成几 MB,性能问题基本不用考虑。更大容量的时候我建议把命令拆成多个配置文件,通过imports字段引入。

6.2 Windows 兼容性:路径分隔符和 cmd 的坑

我知道不少用户是在 Windows 上用这类工具的。实测在 Windows 11 + PowerShell 环境下,首要问题是路径分隔符。配置脚本里写./scripts/xxx.sh在 cmd 环境下不会直接执行,需要指定settings.shell为powershell.exe,或者把脚本写成.ps1。

我在 Windows 测试机上跑了两个命令,结论是:

  • 如果 script 字段指向的是 .exe 或 .bat 文件,问题不大。
  • 如果指向的是 .sh 文件,必须确保有 shell 环境,比如 Git Bash,并在配置里指定shell: "C:\\Program Files\\Git\\bin\\bash.exe"。

另外一个坑是环境变量的格式。在 PowerShell 下,$PARAM_ENV这种 bash 变量写法不会被识别,需要改成$env:PARAM_ENV。由于 CLI-Anything 默认把参数导出为环境变量,你的脚本如果同时兼容两种 shell,就要避免直接引用环境变量,尽量用{{ var }}做模板替换,这样就不依赖 shell 语法。

6.3 与项目里已有 CLI 框架的冲突排查

如果你已经有了一套基于 Commander 或 Click 的命令行体系,再引入 CLI-Anything 可能会遇到命令名冲突。比如你的项目里已经有一个deploy子命令,配置文件里又定义了一个deploy,CLI-Anything 会优先执行自己定义的,原命令就无法触达。

解决方式有两种:一是通过配置文件里的aliases把命令名改成不冲突的名字;二是干脆只把 CLI-Anything 用于“交互式封装”,底层脚本全走原来的入口,相当于在旧体系之上加了一个可视化外壳。

我实际更推荐后者。因为 CLI-Anything 的价值核心是交互层,而不是执行引擎。保留原有脚本作为底层接口,上层用 CLI-Anything 做交互封装,这样既不用重写业务逻辑,又能获得提示、校验和钩子功能。

7. 我的最终评价和落地建议

7.1 什么场景真正适合它

评测完一周,我自己的使用结论是:CLI-Anything 适合“命令参数复杂、使用频率中等、使用者可能记不住参数”的场景。典型如:

  • 运维同学需要给非技术同事提供“安全的部署/回滚入口”。
  • 团队内部有几十个脚本,参数全靠口口相传,新人上手慢。
  • 你有大量一次性命令,不常执行,每次执行都要翻历史。

在这些场景下,交互式提示大幅降低了命令的使用门槛。你不再需要把--env staging --version v1.2.3 --force --retry 3这样的参数背下来,只需要在终端里回答几个问题。

7.2 什么场景不建议引入

反过来,如果出现以下情况,我劝你别用:

  • 命令执行频率极高、参数完全固定,比如 CI/CD 流水线里的构建命令,直接写死即可,不需要交互。
  • 你需要非常复杂的命令解析逻辑,比如多个子命令相互嵌套、参数之间联动、动态从远端拉配置。这类需求还是老老实实用完整的命令行框架。
  • 你的团队不熟悉 YAML,而是熟悉 TypeScript。CLI-Anything 的配置化思维可以帮助新人不写代码,但对代码基础较好的团队也是一种约束。

7.3 落地时的配置组织建议

根据我这周的折腾,如果你准备在项目中正式引入 CLI-Anything,下面这套组织方式比较稳:

infra/ cli/ anything.yaml # 主配置 commands/ deploy.yaml # deploy 命令的独立配置 backup.yaml scripts/ deploy.sh backup.js hooks/ pre-check.sh validators/ version-check.js

把命令拆分成多个文件,主配置通过imports引入这些子文件,可以避免所有命令挤在单文件里变得不可维护。CLI-Anything 支持这种拆分,建议从超过 10 个命令时就动手拆分。

配置文件最好提交到 Git 仓库,保证团队成员使用的是同一份命令定义。命令里的脚本路径建议用相对配置文件位置的路径,这样任何人 clone 后直接能跑,不用改本地的绝对路径。

7.4 最后的个人体会

我最初觉得 CLI-Anything 是个“把简单问题复杂化”的工具,命令行参数本来就是给人用的,多一层交互反而啰嗦。但真正把十几个高频命令配完后,我发现它的价值在于把“人”和“脚本”之间的知识传递变成了“工具”和“人”之间的对话。脚本的参数格式不再只存在于注释里,而是被工具用提问的方式主动唤起。对我这种同时管好几个项目、脚本一多就忘的人来说,这种体验提升是实打实的。

如果你是那种习惯把所有命令参数背得滚瓜烂熟的老手,CLI-Anything 可能帮不上太多忙。但如果你身边有协作的同事、或者希望把日常操作沉淀成团队可共享的工具,它值得花半小时试试看。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询