我之前用Rust写过一个叫apifire的CLI。动机其实很朴素:把API测试这件事收一收,别每次都靠零散脚本和临时命令硬顶。从结果看,它并不算失败。至少到现在,这个仓库里围绕它能确认的命令面还是很清楚:apifireinit、apifirerun、apifireauth、
我之前用 Rust 写过一个叫
apifire的 CLI。动机其实很朴素:把 API 测试这件事收一收,别每次都靠零散脚本和临时命令硬顶。
从结果看,它并不算失败。至少到现在,这个仓库里围绕它能确认的命令面还是很清楚:apifire init、apifire run、apifire auth、apifire validate。这些命令都是真实存在、已经验证过的,职责也不散。
但问题是,一个工具“能用”,和它“用起来顺手”,中间往往隔着很长一段距离。
apifire 给我的真实感受就是这样:它不是不能完成工作,而是每次使用时,我都要先把脑子里的任务,翻译成命令行认识的那种句子。这件事做久了,会有一种很微妙的拧巴感——不是工具坏了,而是你总得先配合它说话。
后来我越来越确定,我真正想改的不是 apifire 的功能,而是它的入口。于是最后我没有继续在 CLI 这一层打补丁,而是把它包装成了一个 Claude Code Skill。
CLI 还是那个 CLI,能力边界也没扩张,但入口终于从“先想命令”变成了“先说人话”。

apifire 会“能用,但总有点别扭”我一直认 CLI 的价值。
命令行的好处很明确:表达直接、可组合、适合脚本化,也天然适合做工程工具。apifire 一开始就是按这个思路做的,所以仓库里现在能看到的也是一个刻意收敛过的命令面。README 和 skills/apifire/SKILL.md 都只覆盖已经验证过的动作,没有把它写成一个什么都想做的大而全工具。
这本来是优点。
但真正进入日常使用以后,另一个问题会慢慢浮出来:CLI 要求你按它的语法表达,而不是按你的任务表达。
比如我脑子里想的,经常是这种话:
但命令行不接受这个层次的表达。它要求我把这些话再压一遍,压成它能识别的形式:
apifire run
apifire auth --token-only
apifire validate
如果只是偶尔敲一次,这种转换成本几乎可以忽略。
可一旦它变成高频动作,感受就不一样了。你会发现自己在重复做一件没什么产出的事:先想需求,再回忆子命令,再想 flag,再确认语法,最后才执行。工具并没有帮你减少表达成本,它只是把这笔成本转嫁给了你。
所以后来我越来越觉得,apifire 的别扭,不在于命令设计失败,反而恰恰因为它的命令面够收敛、够明确,才把这个问题暴露得更彻底:我明明知道自己要做什么,但还是得先把自己翻译成 shell。
当时最自然的思路,其实还是继续在 CLI 外面补。
比如多写一点 README,多攒一点示例,多包几层 shell script,或者给几个高频动作起更顺手的别名。看起来这些都能改善体验,而且也不需要改太多结构。
但我后来发现,这些办法都只是在优化“怎么学会这套命令”,并没有解决“为什么每次都得先切到命令语法”这个问题。
说白了,问题不在文档够不够全,也不在命令够不够多,而在于输入方式本身不自然。
只要入口还是 CLI,用户就仍然要承担那层翻译工作。文档写得再清楚,也只是让这件事更容易掌握;它并没有把这件事从流程里删掉。
所以后来我换了个方向:不再替 apifire 发明另一套命令,而是把它接到 Claude Code 的 Skill 机制上。
这个仓库现在的定位其实写得很直接:它是一个 Claude Code plugin,用来安装 apifire skill;同时仓库里保留了 skills/apifire/ 这套 skill 源文件,以及 install.sh 这条更早期的文件复制安装路径。
这件事对我来说,重点从来不是“加了 AI”。
真正关键的是:入口终于可以按人的表达来组织,而不是按子命令和 flag 来组织。
比如现在我可以先说:
然后再由 Skill 把它们落回已经验证过的命令:
apifire run
apifire auth --token-only
apifire validate
这个变化看起来不大,但它实际拿掉的是最烦人的那一层:人肉翻译。
我很不想把这件事写成一篇“AI 让命令行焕发新生”的宣传稿,因为实际情况真没那么夸张。
这个 Skill 的核心能力非常克制,甚至可以说有点老实:把自然语言映射到已经确认存在、已经验证过的 apifire 命令和参数上。
skills/apifire/SKILL.md 里把边界写得很清楚,目前只认四个已验证命令:
apifire initapifire runapifire authapifire validate全局参数也只认已经确认过的那几个,比如 --verbose、--dir <DIR>、--help、--version。也就是说,这个 Skill 不是在“理解一个无限开放的工具宇宙”,而是在一个很小、很确定的命令面内做翻译。
这一点在 skills/apifire/examples.md 里看得更直观。仓库里没有塞一堆虚张声势的 demo,而是放了几条很具体的自然语言到命令映射,比如:
“运行当前项目里的所有 apifire 测试” -> apifire run
“只执行认证并输出 token” -> apifire auth --token-only
“校验 apifire 配置” -> apifire validate
再细一点也一样:
“只运行两个请求文件” -> apifire run requests/login.http requests/profile.http
“运行测试但跳过认证” -> apifire run --skip-auth
“并行运行测试” -> apifire run --parallel
这就是我后来越来越喜欢这个形态的原因。
它没有替 CLI 发明新世界,也没有偷偷扩展能力边界。它只是把原来那段必须靠人脑完成的翻译过程,挪到了一个更自然的入口上。
一旦把入口换成自然语言,风险也会跟着一起变大。
因为模型最容易做的一件事,就是顺着用户的话往下猜。用户说得含糊一点,它就可能补出一个“看起来合理”的命令;但对工程工具来说,“看起来合理”和“真的存在”中间,差的是可信度。
这正是我最不想要的事。
所以 SKILL.md 里真正重要的内容,其实不是它支持什么,而是它不允许自己做什么。里面写得很明确:
apifire --help 或子命令帮助我后来越来越觉得,一个工程向 Skill 真正的价值,不在于“更自由”,而在于“更有边界”。
因为用户一旦开始说人话,表达天然会更松。要是这时候系统没有边界,它就很容易从“翻译意图”滑到“编造能力”。这在聊天产品里也许只是小毛病,但在命令工具里,会直接变成错误预期,甚至错误操作。
所以这里的取舍我其实很明确:不用开放性换惊喜,用约束换可信度。
你可以说得很自然,但系统最后落下来的,仍然只能是仓库里已经证实过的那些命令。它不是一个“什么都能帮你编”的助手,而是一个“只在已知边界内替你降成本”的入口。
这次从 CLI 到 Skill,不只是多写了一个 SKILL.md 那么简单。仓库结构本身,其实已经把职责拆得很清楚了。
先看 skills/apifire/。
这里放的是 skill 本体:
SKILL.md:定义 Skill 做什么、不做什么、怎么工作examples.md:给出已经验证过的自然语言到命令映射这一层解决的是最核心的问题:怎么把用户的话,稳定地翻译成安全、可信的 apifire CLI 调用。
再看 .claude-plugin/。
这里放的是插件分发元数据:
plugin.json:说明这是一个 Claude Code plugin,用来安装 apifire skillmarketplace.json:补齐版本、作者、仓库地址、主页、关键词这些 marketplace 需要的信息这一层的意义是,apifire skill 不再只是我本地目录里的一组 prompt 文件,而是一个可以安装、发现、分发的标准插件产物。也就是说,它从“自己给自己缝的一个入口”往前走了一步,变成了别人也能用标准方式接入的东西。
最后是 install.sh。
它代表的是旧路径,也就是更早期的文件式安装方式:直接把 skills/apifire 复制到 Claude 的 skills 目录里。README 里也写得很清楚,现在推荐的是 plugin 安装,但这个 legacy installer 还保留着。
我很喜欢这个状态,因为它说明这次演进不是推倒重来,而是把原来的能力保留下来,再补上一条更正式的分发路径。
这次改完以后,最明显的变化,不是 apifire 突然会了什么新功能。
恰恰相反,真正让我觉得舒服的地方,是底层几乎没变:还是那些命令,还是那些边界,还是那个 CLI。变的只是入口。
但入口一变,整个日常使用的阻力就小了很多。
以前我的路径大概是这样:
apifire 里对应哪个子命令现在更像是:
表面看,好像只是少记了几个 flag。
但实际上减少的是两类更隐性的成本。
第一类是记忆成本。你不用一直记得 auth 后面那个输出 token 的参数到底叫 --token-only,你只需要说清楚“我现在只想拿 token”。
第二类是切换成本。你不用从“我要校验配置有没有问题”这层任务语义,切到“这个命令具体怎么拼”那层工具语义,再切回来。
而且很重要的一点是,这种顺手不是靠“模型模糊兜底”换来的。它之所以成立,恰恰是因为边界够死。底层 CLI 还是那个 CLI,命令面也还是那几个命令。变的是入口,不是能力幻想。
说到底,我后来不是觉得 Rust 写 CLI 这条路走错了。
apifire 作为命令行工具本身依然成立,甚至可以说,正因为它的命令面够收敛,我才更容易把它包进一个可控的 Skill 里。真正让我想重做的,从来不是它的功能,而是那个使用前总要先经历一下的“翻译动作”。
工具明明已经能做我要的事,但每次开口前,我都得先把自己翻译成命令行。
把它做成 Claude Code Skill 之后,这层别扭终于被拿掉了。CLI 还是原来的 CLI,只是入口终于更像人在说话。
对我来说,这就是这次演进最实际、也最值钱的收益。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!