CLI + Skill + CI/CD:现代应用的新三件套

Why Every Application Needs CLI, Skill, and Automation in the AI Agent Era

上周五晚上,我在给 ai-notepad 项目加一个功能:让 AI Agent 能自动发现并安装这个项目的 Skill。写完 SKILL.md、落地页、版本同步脚本后,我突然意识到一件事——这套流程已经不是「锦上添花」,而是应用交付的新底线

如果你今天做一个应用,却没有提供 CLI 和 Skill,就像 2010 年做一个 Web 服务却没有 API 一样——用户(包括 AI Agent)根本用不了你。

CLI + Skill + CI/CD:从旧范式到新三件套

为什么是「新三件套」

API、MCP 和 Skills:三个概念的本质区别 中,我用餐厅的比喻解释了三者的区别。但知道区别是一回事,把它们做进产品里是另一回事。

传统应用的交付物是什么?一个 Web UI,一个移动端 App,也许再加一套 API 文档。但在 AI Agent 时代,这个清单需要更新:

传统三件套                    新三件套
┌─────────────┐              ┌─────────────┐
│  Web UI     │              │  CLI        │ ← 人 + Agent 都能用
│  Mobile App │              │  Skill      │ ← Agent 专用入口
│  API Docs   │              │  CI/CD 自动化│ ← 保持三者同步
└─────────────┘              └─────────────┘

区别在哪里?

CLI 是人和 Agent 的公共入口。 人类在终端敲命令,Agent 通过工具调用执行——同一个 CLI,两种用户。而 Web UI 只有人类能用,API 只有程序能调,CLI 是唯一同时服务两种用户的接口。

Skill 是 Agent 的「使用说明书」。 它不是给人看的 README,而是结构化的 Markdown(带 YAML frontmatter),让 Agent 能机器解析、自动加载、直接执行。

CI/CD 自动化是保持三者同步的粘合剂。 版本号在 package.jsonSKILL.md、落地页三处不一致?Agent 拿到的 Skill 是旧版?这些坑只有自动化能根治。

一个贯穿案例:ai-notepad

我不打算泛泛而谈。接下来用一个真实项目——ai-notepad(我的笔记应用)——来走完整条链路。

第一步:写 SKILL.md

SKILL.md 是 Skill 的本体。Agent 安装时读取它的全部内容,作为系统提示的一部分。结构很简单:

---
name: ai-notepad
description: "Manage Hugo's Notebook notes from terminal — list, read, create, update, delete, search, publish."
version: '1.2.7'
author: hugozhu
license: MIT
metadata:
  hermes:
    tags: [notes, notepad, writing, supabase, cli]
---

# AI Notepad CLI

Manage notes in Hugo's Notebook (hugozhu.site/notes) from the terminal.

## Auth
...

## Commands
...
# generated by hugo AI

这个文件看起来就是一个普通的 Markdown 文件,但它和普通 README 有三个本质区别:

  1. YAML frontmatter —— Agent 可以机器解析 name、version、tags,决定要不要加载
  2. 结构化的命令文档 —— Agent 读完就知道怎么调用 CLI
  3. 版本声明 —— Agent 可以检查自己持有的版本是否过期

第二步:一行 curl 安装

Skill 的价值不在于写出来,在于 装上去。各主流 AI Agent 的安装方式都是一行命令:

# Claude Code
curl -o .claude/skills/ai-notepad.md https://hugozhu.site/skills/ai-notepad/SKILL.md

# Cursor
curl -o .cursor/rules/ai-notepad.mdc https://hugozhu.site/skills/ai-notepad/SKILL.md

# OpenCode
curl -o .opencode/skills/ai-notepad.md https://hugozhu.site/skills/ai-notepad/SKILL.md
# generated by hugo AI

更进一步,Agent 还可以 自发现 可用 Skill:

curl -s https://hugozhu.site/.well-known/agent-skills.json
# generated by hugo AI

这就像 2010 年代的 API 发现机制(Swagger/OpenAPI),但面向的用户从开发者变成了 Agent。

第三步:版本同步自动化

这是最容易遗漏、也是最关键的一环。项目里有多处版本号:

文件用途
package.jsonnpm 版本号
index.html页脚展示
skills/ai-notepad/SKILL.mdSkill frontmatter
skills/ai-notepad/index.html落地页(CI 构建生成)

手动同步必然出错。我在实践中踩过三个坑:

坑一:husky 只 add 了部分文件。 version-bump.sh 改了 SKILL.md,但 .husky/pre-commitgit addpackage.json index.html js/i18n.js,导致 SKILL.md 的版本变更没进提交。提交后 package.json 是 v1.2.6,SKILL.md 还是 v1.2.5。

坑二:pre-commit 里调 build-skill-docs.js 导致冲突。 这个脚本会重新生成整个 skills/ai-notepad/index.html(内容变化大),和 lint-staged 的 stash/restore 机制冲突。修复方案:pre-commit 只负责 SKILL.md 的版本 bump,CI 构建时再生成落地页。

坑三:多次 bump 导致版本号跳跃。 反复尝试修复的过程中,版本号从 1.2.1 一路蹦到了 1.2.7。每次 commit 都会触发 pre-commit 再 bump 一次。教训:先把自动化链路跑通再发布。

最终的分工很清晰:

#!/bin/bash
# scripts/version-bump.sh
# pre-commit hook 调用,自动 bump patch 版本号

VERSION=$(node -p "require('./package.json').version")
NEW_VERSION=$(node -p "
  const [m,n,p] = '$VERSION'.split('.');
  \`\${m}.\${n}.\${parseInt(p)+1}\`
")

# 同步更新所有版本号
npm version patch --no-git-tag-version --silent
sed -i "s/version: '$VERSION'/version: '$NEW_VERSION'/" \
  skills/ai-notepad/SKILL.md

echo "Bumped $VERSION$NEW_VERSION"
# generated by hugo AI
pre-commit hook          CI 构建(deploy.yml)
┌──────────────────┐     ┌──────────────────────┐
│ version-bump.sh  │     │ build-skill-docs.js  │
│  bump 版本号      │     │  从 SKILL.md 生成    │
│  git add 所有文件  │────→│  落地页 index.html   │
│                  │     │  docker build + push │
└──────────────────┘     └──────────────────────┘

hook 只管 bump 和 stage,CI 只管构建和部署。职责不交叉,版本号永远一致。

「README 就够了」是个错觉

最常见的反对意见是:「Skill 不就是 Markdown 吗?我的 README 已经写得很好了,Agent 直接读不就行?」

这就像 2010 年有人说「我的 HTML 页面已经写得很好了,为什么还要做 API?」

README 和 Skill 的本质区别在于三点:

1. 结构化元数据。 YAML frontmatter 让 Agent 能机器解析——name、version、tags 都是可计算字段。README 是给人看的散文,Skill 是给 Agent 读的结构化数据。

2. 版本同步自动化。 README 的版本号?没人管。Skill 的版本号由 pre-commit hook + CI 自动同步,Agent 拿到的永远是最新版。

3. 自发现机制。 .well-known/agent-skills.json 让 Agent 主动找到并安装 Skill,不需要人类手动拷贝 URL。README 没有这个能力。

维度READMESkill
目标读者人类开发者AI Agent
元数据无结构化声明YAML frontmatter
版本管理手动更新自动化同步
发现机制GitHub search.well-known 自发现
安装方式拷贝粘贴一行 curl

AI coding 不是锦上添花,是效率杠杆

现在回到观点的另一半: 这套流程应该通过 AI coding 来完成。

为什么?因为三件套的工程量和维护成本,对个人开发者或小团队来说,纯手工做不起。

以 ai-notepad 为例,整个过程包括:

  • 写 SKILL.md(Markdown + YAML,本身不复杂)
  • build-skill-docs.js(从 SKILL.md 生成落地页,带深色/浅色模式自适应)
  • version-bump.sh(多文件版本号同步)
  • 配置 .husky/pre-commit(hook 链路)
  • 配置 .github/workflows/deploy.yml(CI 构建 + 部署)
  • 配置 .well-known/agent-skills.json(自发现)
  • 踩坑、修复、迭代

这些工作单独看都不难,但加起来是一个完整的工程链路。 用 AI coding(Hermes Agent)来做,从写第一个 SKILL.md 到跑通整条 CI/CD 流水线,一个晚上搞定。 如果纯手工,光调试 pre-commit 和 lint-staged 的冲突就可能耗掉一整天。

AI coding 的价值不仅是加速,更是 降低全链路工程的门槛。一个擅长写产品的开发者,不需要精通 husky、lint-staged、GitHub Actions 的每个细节——告诉 Agent 你要什么,让它来踩坑。正如我在 Claude Code 的自动纠错 Agent Loop 中记录的,AI coding 的核心价值不是「写代码更快」,而是「让不可能的工程变得可能」。

持续进化:Skill 不是一次性交付

最后一点,也是最容易被忽略的: Skill 不是写完就完了,它需要持续进化。

ai-notepad 的 Skill 从 v1.2.1 到 v1.2.7,经历了多次迭代:

  • 补全了认证流程的详细文档(auto-auth flow for DingTalk users)
  • 增加了 annotations 功能的命令说明
  • 修复了 shell glob expansion 的坑(密码里的特殊字符)
  • 增加了 JWT token truncation 的警告

每次迭代,开发者只需要修改 SKILL.md,commit 一下——pre-commit hook 自动 bump 版本号,CI 自动重新生成落地页,自动部署。 用户侧的 Agent 下次拉取时自动拿到最新版。

这就是「持续进化」的含义:不是一次性交付一个完美的 Skill,而是建立一个 让 Skill 随应用一起生长的机制

修改 SKILL.md → git commit → pre-commit bump → CI build → deploy
     ↑                                              ↓
     └──── 发现问题 / 新增功能 ←── Agent 使用反馈 ←──┘

总结

如果你今天在做一个新的应用或工具,我建议你把这三件事当作交付的底线:

  1. CLI —— 让人和 Agent 都能用你的工具
  2. Skill —— 让 Agent 能发现、安装、理解你的工具
  3. CI/CD 自动化 —— 让版本永远同步,让 Skill 持续进化

这三件事的工作量,用 AI coding 来做,一个晚上可以搞定。不用 AI coding,同样的工程链路需要更多的调试和踩坑时间。

2010 年,没有 API 的应用是半成品。2025 年,没有 CLI + Skill 的应用也是半成品。

区别只是,2010 年的用户是人类开发者,2025 年的用户是 AI Agent。


你在实际项目中给 Agent 做过 Skill 吗?踩过什么坑?欢迎留言讨论。


See also