从零编写 Skills:5种模式完整示例与最佳实践
第18章:从零编写 Skills——5种模式完整示例与最佳实践
本章概览
理论已经足够,本章进入纯实战模式。我们将完整展示 Skills 系统的 5 种典型写法模式,每种模式都附有可直接使用的完整 SKILL.md 代码、适用场景分析和关键设计决策说明。最后,我们总结 Degree of Freedom 匹配原则和"Concise is Key"哲学,以及最容易踩的常见错误。
18.1 模式一:最简工具型(Hello World 级别)
适用场景
- 封装一个简单的单步操作
- 让模型记住一个特定的工作方式
- 快速验证 Skill 概念(MVP 阶段)
设计原则
最简工具型 Skill 的核心是:最少字段,最清晰指导。不要为了"看起来专业"而添加不必要的复杂度。
完整 SKILL.md 示例
---
name: word-count
description: |
当用户需要统计文件或文本的字数、行数、字符数时使用。
提供快速的 wc 命令用法指导。
metadata:
openclaw:
emoji: "🔢"
requires:
bins:
- wc
---
# Word Count Helper
统计文件内容的快速工具。
## 基本用法
```bash
# 统计字数、行数、字符数
wc -lwc filename.txt
# 仅统计行数
wc -l filename.txt
# 统计多个文件
wc -l *.md
输出格式说明
wc 输出三列:行数 / 字数 / 字节数
如果用户只说"统计一下",默认使用 wc -lwc。
### 为什么这样写
- `description` 精确描述了触发场景(字数/行数/字符数统计)
- `requires.bins: [wc]` 确保在没有 wc 的环境不会加载(Windows 需要 WSL)
- 正文简洁:只有必要信息,没有废话
- 包含了边界情况处理("用户只说统计一下")
---
## 18.2 模式二:信息型(多步骤 GitHub PR 工作流)
### 适用场景
- 需要引导模型完成多个有序步骤
- 工作流中有判断分支
- 需要强制执行团队规范
### 设计原则
信息型 Skill 的价值在于**将团队的隐性知识显性化**。它把"老员工知道但新人不知道"的流程固化为可重复的步骤。
### 完整 SKILL.md 示例
```yaml
---
name: github-pr
description: |
当用户需要创建 Pull Request、进行 PR 审查、管理 PR 标签和审查者,
或者询问团队 PR 规范时使用。包含完整的从分支到合并的 PR 工作流。
user-invocable: true
metadata:
openclaw:
emoji: "🔀"
requires:
bins:
- git
- gh
env:
- GITHUB_TOKEN
install:
- type: brew
pkg: gh
---
# GitHub PR 完整工作流
## 第一步:准备分支
```bash
# 确认当前分支状态
git status
git log --oneline -5
# 如果有未提交的变更,先提交
git add -p # 交互式暂存,避免提交多余文件
git commit -m "feat: <简短描述>"
提交信息规范(强制执行):
| 前缀 | 场景 |
|---|---|
feat: |
新功能 |
fix: |
Bug 修复 |
refactor: |
代码重构(无新功能) |
docs: |
文档变更 |
test: |
测试相关 |
chore: |
构建/工具链变更 |
第二步:推送并创建 PR
# 推送当前分支
git push -u origin HEAD
# 使用 GitHub CLI 创建 PR
gh pr create \
--title "feat: 添加用户认证模块" \
--body "$(cat <<'EOF'
## 变更说明
- 实现 JWT 认证
- 添加刷新 token 机制
## 测试方案
- [ ] 单元测试通过
- [ ] 集成测试通过
- [ ] 手动测试认证流程
EOF
)"
第三步:分配审查者和标签
# 添加审查者
gh pr edit --add-reviewer username1,username2
# 添加标签
gh pr edit --add-label "needs-review,feature"
# 设置里程碑
gh pr edit --milestone "v2.0"
第四步:处理审查反馈
当审查者留下评论时:
- 在本地 checkout 到对应分支
- 修改代码并提交(使用
fix:或refactor:前缀) git push(会自动更新 PR)- 在 PR 评论中回复"已处理,见 commit xyz"
第五步:合并
# 检查是否可以合并(CI 通过?审查批准?)
gh pr checks
gh pr view --json reviewDecision
# 合并(使用 squash 保持主分支历史整洁)
gh pr merge --squash --delete-branch
常见问题处理
合并冲突:
git fetch origin main
git rebase origin/main
# 解决冲突后
git add .
git rebase --continue
git push --force-with-lease
PR 卡在 CI:
gh pr checks --watch # 实时监控 CI 状态
gh run view <run-id> # 查看具体失败原因
---
## 18.3 模式三:工具集成型(带 requires.bins + install 的 image-lab)
### 适用场景
- 依赖特定外部工具(ffmpeg、imagemagick 等)
- 需要自动安装依赖
- 涉及多种工具的协调使用
### 完整 SKILL.md 示例
```yaml
---
name: image-lab
description: |
当用户需要处理图片或视频时使用:格式转换、裁剪、调整尺寸、
批量处理、添加水印、视频截图、GIF 制作、图片压缩优化。
支持 PNG/JPG/WebP/AVIF/GIF/MP4 等格式。
metadata:
openclaw:
emoji: "🖼️"
os: darwin
requires:
bins:
- ffmpeg
- convert # ImageMagick
anyBins:
- cwebp
- avifenc
primaryEnv: shell
homepage: https://imagemagick.org
install:
- type: brew
pkg: ffmpeg
- type: brew
pkg: imagemagick
- type: brew
pkg: webp
---
# Image Lab — 图像与视频处理工具集
## 图片格式转换
```bash
# JPG → WebP(质量 85)
cwebp -q 85 input.jpg -o output.webp
# PNG → AVIF(高压缩率)
avifenc --min 0 --max 63 --speed 6 input.png output.avif
# 批量转换目录下所有 JPG 为 WebP
for f in *.jpg; do cwebp -q 85 "$f" -o "${f%.jpg}.webp"; done
图片尺寸调整
# 调整为指定宽度(高度自动等比缩放)
convert input.jpg -resize 800x output.jpg
# 调整为指定尺寸(可能变形)
convert input.jpg -resize 800x600! output.jpg
# 调整为指定尺寸(不超出边界,填充白色)
convert input.jpg -resize 800x600 -background white -gravity center -extent 800x600 output.jpg
图片裁剪
# 从坐标 (x=100, y=50) 裁剪 400x300 区域
convert input.jpg -crop 400x300+100+50 output.jpg
# 中心裁剪
convert input.jpg -gravity center -crop 400x400+0+0 output.jpg
视频操作(FFmpeg)
# 提取视频第 10 秒的帧为 PNG
ffmpeg -i video.mp4 -ss 00:00:10 -vframes 1 screenshot.png
# 视频转 GIF(优化质量)
ffmpeg -i video.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" output.gif
# 压缩视频(H.264,CRF 23)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium output.mp4
批量水印
# 在所有图片右下角添加文字水印
for f in *.jpg; do
convert "$f" \
-gravity southeast \
-fill "rgba(255,255,255,0.7)" \
-pointsize 24 \
-annotate +10+10 "© MyBrand" \
"watermarked_$f"
done
选择工具的优先级
- WebP 输出 → 优先
cwebp(质量更好) - AVIF 输出 → 使用
avifenc - 复杂合成/滤镜 → 使用
convert(ImageMagick) - 视频相关 → 始终使用
ffmpeg
---
## 18.4 模式四:command-dispatch 型(零推理成本的 browser Skill)
### 适用场景
- 斜杠命令应该立即触发特定工具,不经过模型推理
- 需要极低延迟(毫秒级响应)
- 命令行为完全确定,不需要模型判断
### 关键设计
`command-dispatch: tool` 是这种模式的核心。它告诉 OpenClaw:当用户输入 `/browser` 时,直接将参数传递给 `browser_action` 工具,**跳过模型的 token 消耗**。
### 完整 SKILL.md 示例
```yaml
---
name: browser
description: |
当用户需要控制浏览器、访问网页、点击元素、填写表单、
截图,或者执行任何网页自动化任务时使用。
支持 /browser <url> 直接打开网页。
user-invocable: true
command-dispatch: tool
command-tool: "browser_action"
command-arg-mode: raw
metadata:
openclaw:
emoji: "🌐"
requires:
anyBins:
- chromium
- google-chrome
- firefox
install:
- type: brew
pkg: chromium
---
# Browser Automation Skill
## 快速启动
/browser https://example.com
等价于直接调用 `browser_action` 工具,无需模型推理。
## 常用操作
### 打开网页并截图
导航到指定 URL,等待加载完成后截图: browser_action("navigate", url="https://target.com") browser_action("screenshot")
### 填写表单
找到表单元素,填入内容: browser_action("find", selector="#email-input") browser_action("type", text="[email protected]") browser_action("find", selector="#submit-btn") browser_action("click")
### 提取页面内容
获取页面文本内容: browser_action("extract_text", selector=".article-body")
获取页面 HTML: browser_action("get_html")
## command-dispatch 工作机制说明
当用户输入 `/browser https://news.ycombinator.com` 时:
用户输入 → OpenClaw 路由 → browser_action(url="https://news.ycombinator.com") (不经过模型,0 token 消耗)
对比普通 Skill 调用:
用户输入 → 模型读取 description → 模型决策 → 模型调用工具 (需要消耗 ~500-2000 tokens)
## 注意事项
- 动态内容丰富的页面请等待 `networkIdle` 状态
- 登录态页面需要先处理 cookies
- 如需持久化浏览器状态,使用 `--profile` 参数
18.5 模式五:TDD 专业型(完整的测试驱动开发 Skill)
适用场景
- 规范团队的 TDD 实践流程
- 复杂的多阶段工作流,有明确的循环结构
- 需要发现和处理反模式
完整 SKILL.md 示例
---
name: tdd-pro
description: |
当用户需要按照测试驱动开发(TDD)方法编写代码时使用:
先写测试、再写实现、然后重构。适用于新功能开发、Bug 修复、
遗留代码改造。包含 Red-Green-Refactor 循环和反模式检测。
user-invocable: true
metadata:
openclaw:
emoji: "🧪"
requires:
anyBins:
- jest
- vitest
- pytest
- go
primaryEnv: node
homepage: https://martinfowler.com/bliki/TestDrivenDevelopment.html
---
# TDD Pro — 专业测试驱动开发指南
## 核心循环:Red → Green → Refactor
┌─────────────────────────────────────┐ │ 1. RED: 写一个失败的测试 │ │ → 运行测试,确认它确实失败 │ │ → 确认失败原因是"功能未实现" │ ├─────────────────────────────────────┤ │ 2. GREEN: 写最少量的代码使测试通过 │ │ → 不要过度设计 │ │ → 先让测试绿,再考虑优雅 │ ├─────────────────────────────────────┤ │ 3. REFACTOR: 重构,保持测试绿 │ │ → 消除重复 │ │ → 改善命名和结构 │ │ → 每次改动后运行测试 │ └─────────────────────────────────────┘
## 第一步:模式发现(开始前必做)
在开始写任何代码之前,先分析需求:
```markdown
## 需求分析模板
- **输入是什么?** (类型、格式、边界值)
- **输出是什么?** (类型、格式、异常情况)
- **核心行为是什么?** (业务规则、转换逻辑)
- **边界情况有哪些?** (空值、溢出、并发)
- **与外部系统的集成点?** (需要 Mock 的依赖)
第二步:测试用例设计
按照以下顺序设计测试(从简单到复杂):
// 示例:为 calculateTax(amount, rate) 设计测试
describe('calculateTax', () => {
// 1. 基础正常情况
it('should calculate tax correctly for positive amount', () => {
expect(calculateTax(100, 0.1)).toBe(10)
})
// 2. 边界值:零值
it('should return 0 for zero amount', () => {
expect(calculateTax(0, 0.1)).toBe(0)
})
// 3. 边界值:零税率
it('should return 0 for zero rate', () => {
expect(calculateTax(100, 0)).toBe(0)
})
// 4. 异常情况:负数
it('should throw for negative amount', () => {
expect(() => calculateTax(-100, 0.1)).toThrow('Amount cannot be negative')
})
// 5. 精度问题
it('should handle floating point correctly', () => {
expect(calculateTax(0.1, 0.1)).toBeCloseTo(0.01, 10)
})
})
第三步:RED 阶段
# 运行测试,确认失败
npm test -- --testNamePattern="calculateTax"
# 预期输出:FAIL(因为函数还不存在)
# 如果意外通过,说明测试写错了
第四步:GREEN 阶段
关键原则:只写让当前失败测试通过的最少量代码。
// 第一次让测试通过的"丑陋但有效"实现
function calculateTax(amount, rate) {
if (amount < 0) throw new Error('Amount cannot be negative')
return amount * rate
}
这就够了。不要在 GREEN 阶段引入复杂性。
第五步:REFACTOR 阶段
测试全绿后,开始改善代码质量:
// 重构:更清晰的错误处理、更好的命名
/**
* 计算税额
* @param {number} amount - 税前金额(必须 >= 0)
* @param {number} rate - 税率(0-1 之间的小数)
* @returns {number} 税额
*/
function calculateTax(amount, rate) {
if (amount < 0) {
throw new RangeError(`Amount must be non-negative, received: ${amount}`)
}
if (rate < 0 || rate > 1) {
throw new RangeError(`Rate must be between 0 and 1, received: ${rate}`)
}
return Number((amount * rate).toFixed(10))
}
重构后再次运行测试,确认仍然全绿。
反模式识别与避免
| 反模式 | 症状 | 修复方式 |
|---|---|---|
| 先写实现后写测试 | 测试刚好覆盖了实现的逻辑 | 删除实现重来 |
| 测试太大 | 单个 it() 包含多个断言 | 拆分为独立测试 |
| 过度 Mock | Mock 了 80%+ 的依赖 | 考虑重新设计接口 |
| 测试实现细节 | 测试内部变量而非行为 | 改为测试公共 API |
| 跳过 RED 阶段 | "我知道它会失败" | 强制运行一次,看到 FAIL |
不同语言的测试运行命令
# JavaScript/TypeScript
npm test -- --watch
# Python
pytest -xvs tests/
# Go
go test ./... -v -run TestCalculateTax
# Rust
cargo test calculate_tax -- --nocapture
参考资料
详细的 Mock 策略见:{baseDir}/references/mocking-strategies.md 常用断言模式见:{baseDir}/references/assertion-patterns.md
---
## 18.6 Degree of Freedom 匹配原则
选择哪种模式,应该与任务的"自由度"(Degree of Freedom)匹配:
| 任务自由度 | 描述 | 推荐模式 |
|-----------|------|----------|
| 极低 | 完全确定的单步命令 | command-dispatch 型 |
| 低 | 固定流程,无分支 | 工具型或信息型 |
| 中 | 有分支,需要判断 | 信息型或工具集成型 |
| 高 | 复杂工作流,有循环和反模式 | TDD 专业型 |
| 极高 | 任务高度定制化 | 信息型 + references/ 扩展 |
**错误案例:** 用 TDD 专业型来包装一个简单的 `wc` 命令——过度设计,浪费 context。
**正确做法:** 根据任务复杂度选择最轻量的足够模式。
---
## 18.7 "Concise is Key"——context 预算哲学
在 Skill 写作中,**简洁是最重要的美德**,原因如下:
### 过长 Skill 的代价
一个 8000 token 的 Skill,即使只用到其中 20% 的内容,也会全量消耗 context 预算(懒加载只保护了"是否加载"这一决策,不保护加载后的大小)。
### 实用写作规则
1. **每条指令一行**:不要用段落写可以用列表表达的内容
2. **代码示例代替文字解释**:`wc -l file.txt` 比"使用 wc 命令的 -l 参数统计指定文件的行数"少 80% 的 tokens
3. **把详细参考移到 references/**:SKILL.md 正文只留操作核心,细节放 references/
4. **避免重复**:不要在正文里重复 description 里已经说过的话
### 目标长度参考
| 模式 | 推荐 token 范围 |
|------|----------------|
| 工具型 | 100-300 tokens |
| 信息型 | 300-800 tokens |
| 工具集成型 | 400-1000 tokens |
| command-dispatch 型 | 50-200 tokens |
| TDD 专业型 | 800-2000 tokens |
---
## 18.8 常见错误模式
### 错误一:把 shell 命令写进 skill body 当作"执行代码"
```yaml
# 错误写法:好像 Skill 会"执行"这段脚本
---
name: setup-env
description: 设置开发环境
---
#!/bin/bash
npm install
cp .env.example .env
docker-compose up -d
问题: Skill 正文是给模型读的指导,不是可执行脚本。模型读到这里会把它当成"应该告诉用户执行的命令",而不是自动执行。
正确写法:
---
name: setup-env
description: 当用户需要初始化本项目开发环境时使用
---
## 初始化步骤
让用户按顺序执行以下命令:
```bash
npm install
cp .env.example .env
docker-compose up -d
完成后验证:docker ps 查看容器是否正常运行。
### 错误二:description 过于宽泛导致误触发
```yaml
# 危险:会与几乎所有任务匹配
description: 帮助处理各种代码和开发相关的任务
错误三:在 command-dispatch Skill 中添加复杂正文
command-dispatch Skill 的正文永远不会被模型读取(因为绕过了模型),所以在正文里写详细说明是无效的。
错误四:忘记 {baseDir} 导致路径引用失效
# 错误:硬编码路径
参考文档见 ~/.openclaw/skills/my-skill/references/guide.md
# 正确:使用占位符
参考文档见 {baseDir}/references/guide.md
18.9 本章小结
- 5 种模式覆盖了从最简单到最复杂的所有 Skill 需求
- 工具型(最简洁)→ 信息型(多步骤)→ 工具集成型(有依赖)→ command-dispatch 型(零推理)→ TDD 专业型(高复杂度)
- Degree of Freedom 匹配原则:任务复杂度决定模式选择
- "Concise is Key":Skill 过长比过短危害更大
- 最常见错误:把 shell 命令误当作可执行代码写入 Skill body
下一章探索 ClawHub 生态:13,000+ Skills 的平台架构、Top 10 精选,以及如何用向量搜索找到真正有价值的 Skill。