从 apifire CLI 到 Claude Code Skill:我为什么把一个能用的工具重新包了一层

  • King
  • 发布于 2天前
  • 阅读 88

我之前用Rust写过一个叫apifire的CLI。动机其实很朴素:把API测试这件事收一收,别每次都靠零散脚本和临时命令硬顶。从结果看,它并不算失败。至少到现在,这个仓库里围绕它能确认的命令面还是很清楚:apifireinit、apifirerun、apifireauth、

我之前用 Rust 写过一个叫 apifire 的 CLI。动机其实很朴素:把 API 测试这件事收一收,别每次都靠零散脚本和临时命令硬顶。

从结果看,它并不算失败。至少到现在,这个仓库里围绕它能确认的命令面还是很清楚:apifire initapifire runapifire authapifire validate。这些命令都是真实存在、已经验证过的,职责也不散。

但问题是,一个工具“能用”,和它“用起来顺手”,中间往往隔着很长一段距离。

apifire 给我的真实感受就是这样:它不是不能完成工作,而是每次使用时,我都要先把脑子里的任务,翻译成命令行认识的那种句子。这件事做久了,会有一种很微妙的拧巴感——不是工具坏了,而是你总得先配合它说话。

后来我越来越确定,我真正想改的不是 apifire 的功能,而是它的入口。于是最后我没有继续在 CLI 这一层打补丁,而是把它包装成了一个 Claude Code Skill。

CLI 还是那个 CLI,能力边界也没扩张,但入口终于从“先想命令”变成了“先说人话”。

官网:https://apifire.vercel.app/

image.png

为什么 apifire 会“能用,但总有点别扭”

我一直认 CLI 的价值。

命令行的好处很明确:表达直接、可组合、适合脚本化,也天然适合做工程工具。apifire 一开始就是按这个思路做的,所以仓库里现在能看到的也是一个刻意收敛过的命令面。README 和 skills/apifire/SKILL.md 都只覆盖已经验证过的动作,没有把它写成一个什么都想做的大而全工具。

这本来是优点。

但真正进入日常使用以后,另一个问题会慢慢浮出来:CLI 要求你按它的语法表达,而不是按你的任务表达。

比如我脑子里想的,经常是这种话:

  • 运行当前项目里的所有 apifire 测试
  • 只执行认证并输出 token
  • 校验 apifire 配置

但命令行不接受这个层次的表达。它要求我把这些话再压一遍,压成它能识别的形式:

apifire run
apifire auth --token-only
apifire validate

如果只是偶尔敲一次,这种转换成本几乎可以忽略。

可一旦它变成高频动作,感受就不一样了。你会发现自己在重复做一件没什么产出的事:先想需求,再回忆子命令,再想 flag,再确认语法,最后才执行。工具并没有帮你减少表达成本,它只是把这笔成本转嫁给了你。

所以后来我越来越觉得,apifire 的别扭,不在于命令设计失败,反而恰恰因为它的命令面够收敛、够明确,才把这个问题暴露得更彻底:我明明知道自己要做什么,但还是得先把自己翻译成 shell。

为什么我没有继续折腾 CLI,而是做了 Claude Code Skill

当时最自然的思路,其实还是继续在 CLI 外面补。

比如多写一点 README,多攒一点示例,多包几层 shell script,或者给几个高频动作起更顺手的别名。看起来这些都能改善体验,而且也不需要改太多结构。

但我后来发现,这些办法都只是在优化“怎么学会这套命令”,并没有解决“为什么每次都得先切到命令语法”这个问题。

说白了,问题不在文档够不够全,也不在命令够不够多,而在于输入方式本身不自然

只要入口还是 CLI,用户就仍然要承担那层翻译工作。文档写得再清楚,也只是让这件事更容易掌握;它并没有把这件事从流程里删掉。

所以后来我换了个方向:不再替 apifire 发明另一套命令,而是把它接到 Claude Code 的 Skill 机制上。

https://github.com/lispking/apifire-skills

这个仓库现在的定位其实写得很直接:它是一个 Claude Code plugin,用来安装 apifire skill;同时仓库里保留了 skills/apifire/ 这套 skill 源文件,以及 install.sh 这条更早期的文件复制安装路径。

这件事对我来说,重点从来不是“加了 AI”。

真正关键的是:入口终于可以按人的表达来组织,而不是按子命令和 flag 来组织。

比如现在我可以先说:

  • “运行当前项目里的所有 apifire 测试”
  • “只执行认证并输出 token”
  • “校验 apifire 配置”

然后再由 Skill 把它们落回已经验证过的命令:

apifire run
apifire auth --token-only
apifire validate

这个变化看起来不大,但它实际拿掉的是最烦人的那一层:人肉翻译。

这个 Skill 实际上只做一件事:把自然语言翻译成已验证命令

我很不想把这件事写成一篇“AI 让命令行焕发新生”的宣传稿,因为实际情况真没那么夸张。

这个 Skill 的核心能力非常克制,甚至可以说有点老实:把自然语言映射到已经确认存在、已经验证过的 apifire 命令和参数上。

skills/apifire/SKILL.md 里把边界写得很清楚,目前只认四个已验证命令:

  • apifire init
  • apifire run
  • apifire auth
  • apifire 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 设边界,避免它变成“很会猜,但经常猜错”

一旦把入口换成自然语言,风险也会跟着一起变大。

因为模型最容易做的一件事,就是顺着用户的话往下猜。用户说得含糊一点,它就可能补出一个“看起来合理”的命令;但对工程工具来说,“看起来合理”和“真的存在”中间,差的是可信度。

这正是我最不想要的事。

所以 SKILL.md 里真正重要的内容,其实不是它支持什么,而是它不允许自己做什么。里面写得很明确:

  • 只使用已经验证过的命令和参数
  • 不发明不存在的子命令或 flag
  • 语法精确度重要时,优先去看 apifire --help 或子命令帮助
  • 如果命令可能覆盖文件、重新初始化项目,先确认再执行
  • 不宣称支持代码生成、OpenAPI 导入等未验证能力

我后来越来越觉得,一个工程向 Skill 真正的价值,不在于“更自由”,而在于“更有边界”。

因为用户一旦开始说人话,表达天然会更松。要是这时候系统没有边界,它就很容易从“翻译意图”滑到“编造能力”。这在聊天产品里也许只是小毛病,但在命令工具里,会直接变成错误预期,甚至错误操作。

所以这里的取舍我其实很明确:不用开放性换惊喜,用约束换可信度。

你可以说得很自然,但系统最后落下来的,仍然只能是仓库里已经证实过的那些命令。它不是一个“什么都能帮你编”的助手,而是一个“只在已知边界内替你降成本”的入口。

仓库里这几层结构,分别在解决什么问题

这次从 CLI 到 Skill,不只是多写了一个 SKILL.md 那么简单。仓库结构本身,其实已经把职责拆得很清楚了。

先看 skills/apifire/

这里放的是 skill 本体:

  • SKILL.md:定义 Skill 做什么、不做什么、怎么工作
  • examples.md:给出已经验证过的自然语言到命令映射

这一层解决的是最核心的问题:怎么把用户的话,稳定地翻译成安全、可信的 apifire CLI 调用。

再看 .claude-plugin/

这里放的是插件分发元数据:

  • plugin.json:说明这是一个 Claude Code plugin,用来安装 apifire skill
  • marketplace.json:补齐版本、作者、仓库地址、主页、关键词这些 marketplace 需要的信息

这一层的意义是,apifire skill 不再只是我本地目录里的一组 prompt 文件,而是一个可以安装、发现、分发的标准插件产物。也就是说,它从“自己给自己缝的一个入口”往前走了一步,变成了别人也能用标准方式接入的东西。

最后是 install.sh

它代表的是旧路径,也就是更早期的文件式安装方式:直接把 skills/apifire 复制到 Claude 的 skills 目录里。README 里也写得很清楚,现在推荐的是 plugin 安装,但这个 legacy installer 还保留着。

我很喜欢这个状态,因为它说明这次演进不是推倒重来,而是把原来的能力保留下来,再补上一条更正式的分发路径。

做完之后,日常 API 测试为什么一下顺了很多

这次改完以后,最明显的变化,不是 apifire 突然会了什么新功能。

恰恰相反,真正让我觉得舒服的地方,是底层几乎没变:还是那些命令,还是那些边界,还是那个 CLI。变的只是入口。

但入口一变,整个日常使用的阻力就小了很多。

以前我的路径大概是这样:

  1. 先想清楚我要做什么
  2. 再回忆 apifire 里对应哪个子命令
  3. 再确认 flag 应该怎么写
  4. 最后才执行

现在更像是:

  1. 直接把事情说出来
  2. Skill 把它翻译成已验证命令
  3. 缺什么再补什么,不缺就继续

表面看,好像只是少记了几个 flag。

但实际上减少的是两类更隐性的成本。

第一类是记忆成本。你不用一直记得 auth 后面那个输出 token 的参数到底叫 --token-only,你只需要说清楚“我现在只想拿 token”。

第二类是切换成本。你不用从“我要校验配置有没有问题”这层任务语义,切到“这个命令具体怎么拼”那层工具语义,再切回来。

而且很重要的一点是,这种顺手不是靠“模型模糊兜底”换来的。它之所以成立,恰恰是因为边界够死。底层 CLI 还是那个 CLI,命令面也还是那几个命令。变的是入口,不是能力幻想。

说到底,我后来不是觉得 Rust 写 CLI 这条路走错了。

apifire 作为命令行工具本身依然成立,甚至可以说,正因为它的命令面够收敛,我才更容易把它包进一个可控的 Skill 里。真正让我想重做的,从来不是它的功能,而是那个使用前总要先经历一下的“翻译动作”。

工具明明已经能做我要的事,但每次开口前,我都得先把自己翻译成命令行。

把它做成 Claude Code Skill 之后,这层别扭终于被拿掉了。CLI 还是原来的 CLI,只是入口终于更像人在说话。

对我来说,这就是这次演进最实际、也最值钱的收益。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
King
King
0x56af...a0dd
擅长Rust/Solidity/FunC/Move开发