前15节课,你已经深入掌握了OpenClaw从部署、Workspace配置到企业级集成的方方面面。现在,你可能正面临这样一个渴望——让OpenClaw不再只是使用别人写好的技能,而是按照你自己独特的工作流程来行动。比如对接公司内部的CRM系统、从特定API拉取行业报表、自动同步个人理财数据、或者生成完全贴合你业务逻辑的周报。
官方Skills能满足文件整理、浏览器操控、邮件发送等通用需求,但面对这些个性化场景,就显得力不从心。而自定义Skills,正是让OpenClaw从“通用AI助手”变成“专属数字员工”的核心钥匙。
很多人觉得“开发Skill是高手的事”,但实际门槛远低于想象:只要你懂基础的JavaScript/TypeScript,遵循OpenClaw的标准化规范,就能在1小时内开发出第一个可运行的自定义Skill。OpenClaw v2026.4.20+版本以来,技能开发体系已经高度规范化——不仅有声明式Skill(仅需SKILL.md,零代码开发),还有原生代码Skill(TypeScript/JavaScript),你完全可以根据自己的需求和能力水平选择最适合的开发路径。
本节课,我们将完整走通自定义Skill的开发全链路:从环境准备和项目结构说起,到TypeScript编写技能的核心模板,再到ISkill接口的详解、参数校验、本地测试与调试方法,最后以一个货币汇率转换技能将全部概念串联。读完这节课,你将具备独立开发、测试、调试OpenClaw自定义技能的全套能力。
16.1 技能开发环境准备与项目结构
一句话概括:自定义Skill的开发环境极度轻量——Node.js 18+(推荐22+)加任意代码编辑器即可,采用“3文件核心结构”(plugin.json/SKILL.md + index.ts + 可选package.json),OpenClaw内核自动识别技能目录并完成加载与调度。
开始编写代码之前,先理解OpenClaw Skill的本质到底是什么。
理解Skill的本质
OpenClaw的Skill不是“黑盒插件”,而是遵循固定规范的TypeScript/JavaScript模块——核心作用是“接收OpenClaw内核的标准化指令,执行具体操作,返回标准化结果”。它的定位是:
- 不参与意图解析:用户说的话由OpenClaw内核转成结构化指令,Skill只负责“干活”;
- 不管理权限:所有文件访问、网络请求的权限,都由OpenClaw内核统一校验;
- 专注单一能力:一个Skill只做一件事(比如“查询实时汇率”“生成每周报告”),简单、可复用、易维护。
开发准备清单
只需要以下四个要素即可开始:
| 要素 | 最低要求 | 说明 |
|---|---|---|
| Node.js | v18+(推荐v22+) | OpenClaw运行核心依赖 |
| 包管理器 | npm/yarn/pnpm | 用于安装依赖和管理项目 |
| 代码编辑器 | VS Code(推荐) | TypeScript语法支持和调试体验 |
| OpenClaw环境 | 已部署运行的OpenClaw实例 | 用于技能加载和测试 |
避坑指南:建议使用与OpenClaw核心版本匹配的Node版本(官方推荐22+)。Node版本过低可能导致某些技能依赖的API不可用。
两种Skill开发模式
OpenClaw v2026.4.20+版本支持两种Skill开发模式,你可以根据功能复杂度和编码能力灵活选择:
| 类型 | 开发难度 | 功能上限 | 适用场景 |
|---|---|---|---|
| 声明式Skill | 极低(无需代码) | 中 | 工具组合、提示词封装、简单流程自动化 |
| 原生代码Skill | 中等(需TS/JS基础) | 高 | API集成、数据库操作、复杂逻辑编排 |
此外,还有一种MCP兼容Skill,可将第三方MCP Server暴露的工具直接包装成OpenClaw Skill,适用于飞书、企业微信对接、云服务操作等跨生态复用场景。
标准项目结构
一个完整的原生代码Skill项目遵循以下目录结构:
my-custom-skill/
├── SKILL.md # 技能元数据和AI调用说明(永远必需)
├── manifest.json # 权限声明和配置定义(v2026.4+推荐)
├── package.json # Node.js依赖配置
├── index.ts # 核心逻辑入口(TypeScript)
├── src/
│ ├── tools.ts # 工具定义
│ └── types.ts # 类型定义
├── tests/
│ ├── unit.test.ts # 单元测试
│ └── integration.test.ts # 集成测试
├── assets/ # 静态资源
└── README.md # 用户文档
对于最简单的声明式Skill,唯一必需文件只有SKILL.md。这极大降低了入门门槛——你可以从纯描述式技能开始,随着需求增长逐渐添加代码逻辑,形成渐进式的技能开发路径。
16.2 TypeScript编写技能的基本模板
一句话概括:使用官方推荐的脚手架工具生成TypeScript技能项目是最佳实践——一条命令便可快速构建一个完整的技能模板,包含SKILL.md、TypeScript严格模式配置、TDD工具链和ClawHub发布结构,一次性省去手动配置的繁琐工序。
推荐方式:使用脚手架工具
对于第一次开发自定义Skill,强烈建议使用openclaw-skill-boilerplate脚手架工具。这个由社区开发者构建的CLI工具填补了OpenClaw官方TypeScript模板的空白,让你告别手动复制SKILL.md结构、反复配置tsconfig的重复劳动——原本需要30+分钟的初始化准备工作,压缩到30秒内即可完成。
# 生成技能项目
npx openclaw-skill-boilerplate my-awesome-skill
# 进入目录
cd my-awesome-skill
# 安装依赖
npm install
# 编译TypeScript
npm run build
执行上述命令后,脚手架会自动生成一个包含以下元素的完整技能项目:
- ✅ 格式正确的SKILL.md(OpenClaw专用frontmatter)
- ✅ TypeScript严格模式配置(
tsconfig.json) - ✅
src/tools.ts + types.ts工具定义模式 - ✅
{{SKILL_NAME}}占位符在全部模板中的自动替换 - ✅ GitHub Actions CI流水线(build + typecheck)
- ✅ ClawHub发布就绪的项目结构
生成的项目目录结构如下:
my-awesome-skill/
├── SKILL.md # Skill入口文件(OpenClaw首先读取)
├── README.md # 用户文档
├── package.json # npm包配置
├── tsconfig.json # TypeScript严格模式配置
├── src/
│ ├── index.ts # 核心逻辑与导出
│ ├── tools.ts # 工具定义
│ └── types.ts # 共享类型定义
├── scripts/
│ └── scaffold.ts # CLI脚手架脚本
├── templates/
│ └── skill/ # 模板文件目录
├── examples/
│ └── hello-world/ # 最小可工作示例
└── .github/
└── workflows/
└── ci.yml # CI流水线
备选方式:手动创建技能
若你希望完全控制每个细节,或者仅需要一个最简结构的声明式技能(仅包含SKILL.md),可以使用以下手动方式:
Step 1:创建技能目录
Skills位于工作区根目录下的skills/文件夹中:
mkdir -p ~/.openclaw/workspace/skills/hello-world
cd ~/.openclaw/workspace/skills/hello-world
Step 2:编写SKILL.md(最简版)
---
name: hello-world
description: A simple skill that says hello.
---
# Hello World Skill
When the user asks for a greeting, use the `echo` tool to say "Hello from your custom skill!".
Step 3:加载并测试
# 重启Gateway或者开始新会话
openclaw gateway restart
# 或在聊天中发送 /new 命令
然后发送消息请求问候语,验证技能是否被调用。
Step 4:验证技能已加载
openclaw skills list
如果输出中包含hello-world且状态为enabled,说明技能已被正确识别。
16.3 ISkill接口详解与生命周期钩子
一句话概括:OpenClaw的Skills模块采用声明式注册机制,每个原生代码Skill需实现SkillDefinition接口——它定义了唯一标识(id)、输入参数schema、执行函数execute,以及可选的初始化init和清理cleanup等生命周期钩子。
SkillDefinition接口定义
一个符合OpenClaw规范的自定义Skill,其核心是导出一个符合SkillDefinition接口的对象。最新版本的Skill SDK提供了强类型的TypeScript接口,让你在编写过程中即可获得完整的类型检查和IDE智能提示。
import { SkillDefinition } from '@openclaw/skills-core';
const mySkill: SkillDefinition = {
id: "my-custom-skill",
name: "我的自定义技能",
description: "一句话描述技能功能",
version: "1.0.0",
// 输入参数schema定义
parameters: {
type: "object",
properties: {
query: { type: "string", description: "用户查询内容" },
limit: { type: "number", default: 10 }
},
required: ["query"]
},
// 核心执行函数
execute: async (args, context) => {
// 业务逻辑实现
return { success: true, data: result };
},
// 可选:技能初始化钩子(加载时执行一次)
init: async (context) => {
console.log("技能已加载");
},
// 可选:技能清理钩子(卸载时执行)
cleanup: async (context) => {
console.log("技能已卸载");
}
};
export default mySkill;
Skill插件的注册过程:需在Agent主入口文件中显式导入并注册该插件——Skills需通过Agent启动时显式加载,未注册的插件不会被网关路由识别。
// 在Agent配置对象的skills数组中插入该插件引用
import mySkill from '../skills/my-custom-skill';
const agent = createAgent({
skills: [mySkill, /* 其他技能 */]
});
核心组件详解
OpenClaw的Skills模块采用分层设计,各核心组件的职能划分非常清晰:
- Loader(加载器) :负责技能发现、加载和缓存,按照优先级搜索多个路径(workspace → user global → bundled)
- Registry(注册表) :持有活跃的技能实例及其注册的能力(providers、tools),管理技能元数据和运行状态
- Manifest Registry:读取静态元数据(来自plugin.json/manifest.json),无需加载技能代码即可获取信息
- Runtime:提供执行上下文的API——包括参数获取、日志记录和Memory读写接口
Skill插件运行在隔离沙箱中,仅能通过预设的context对象访问Gateway转发的请求与Memory读写接口。这种隔离设计保证了即使技能包含错误或恶意逻辑,也不会直接损坏主系统。
生命周期流程图
flowchart TD
A[Gateway启动] --> B[扫描技能路径]
B --> C[发现skill目录]
C --> D[加载SKILL.md元数据]
D --> E[调用init钩子]
E --> F[技能注册到Registry]
F --> G[等待用户触发]
G --> H{用户请求匹配?}
H -->|是| I[参数校验]
I --> J[调用execute]
J --> K[返回执行结果]
K --> G
H -->|否| G
F --> L[Gateway关闭]
L --> M[调用cleanup钩子]
M --> N[技能卸载]
无代码版本:更简单的Skill定义方式
对于不愿编写TypeScript代码的用户,OpenClaw也支持纯声明式Skill——只需一个SKILL.md即可让AI根据步骤指南完成工作,无需编写execute函数。AI会像阅读说明书一样读取SKILL.md中的指南,然后自主决定和组合Tool来完成你描述的任务。
16.4 技能的输入参数定义与类型校验
一句话概括:OpenClaw推荐使用ArkType进行参数schema定义和运行时校验——它比传统的JSON Schema更简洁、类型安全,且完全兼容TypeScript的类型推断。
参数Schema定义(ArkType推荐)
在OpenClaw v2026.3.31及以上版本中,官方推荐使用ArkType库进行输入参数的schema定义和运行时类型校验。与JSON Schema相比,ArkType的语法更接近TypeScript本身,让你在IDE中就能获得完整的类型检查和智能提示。
import { type } from '@arktype/arktype';
// 定义参数校验规则
const inputSchema = type({
from: 'string',
to: 'string',
amount: 'number'
});
这种方式比手写JSON Schema直观得多,且ArkType自动完成类型推断,无需额外编写TypeScript类型声明。
自定义插件中的参数处理
在自定义插件(plugin)的更高级模式中,代码结构略有不同。你可以在Skill Definition内部包含一个tools数组,其中每个工具都需要显式声明parameters.zip参数schema:
export const myTool: SkillTool = {
name: "currency-convert",
description: "Convert amount between currencies",
parameters: {
type: "object",
properties: {
from: { type: "string", description: "源货币代码,如USD" },
to: { type: "string", description: "目标货币代码,如CNY" },
amount: { type: "number", description: "转换金额" }
},
required: ["from", "to", "amount"]
},
execute: async (args) => {
const from = args.from as string;
const to = args.to as string;
const amount = args.amount as number;
// 业务逻辑
const result = await convertCurrency(from, to, amount);
return { success: true, data: result };
}
};
使用ArkType也可实现同样的schema定义,殊途同归。
参数校验最佳实践
在实际开发中,应始终遵循以下原则:
- 所有输入必须校验:即使你认为调用方只会传正确参数,也不能相信外部输入。
- 提供有意义的错误信息:当校验失败时,返回明确的错误提示而非通用的“参数错误”。
- 为参数设置合理默认值:例如limit默认10、timeout默认30秒,这样即使调用方漏传,技能仍能正常工作。
- 记录参数内容(脱敏后)到日志:便于排查问题,但注意避坑——切勿将API Key、密码等敏感信息记录到日志中。
- 使用TypeScript类型增强IDE体验:与ArkType配合,让所有参数在代码中有完整的类型提示。
16.5 技能的本地测试与调试方法
一句话概括:OpenClaw提供了从命令行基础调用到Gateway会话模拟的多级测试工具链,并支持DEV模式下的热重载,让你能够在真实环境中反复验证技能行为,而不必频繁重启服务。
完整的测试验证流程可以分为五个标准化步骤,我在其中加入了亲身实践得出的优化技巧。
第一层:命令行直接测试(最快反馈)
在技能源码仓库的根目录下直接运行测试命令,可在不依赖Gateway的情况下先验证核心逻辑:
# 假设技能包提供了测试脚本
pnpm --filter @my/skills run skill pipeline
对于使用脚手架生成的项目,通常在examples/hello-world目录下有最小可工作示例,你可以从中复制测试模式。
第二层:AI指令交互触发(端到端测试)
在集成环境中通过自然语言触发技能,验证从用户消息到技能执行的完整链路。先在聊天工具或WebUI中发送测试消息,观察Agent是否能够正确识别并调用你的技能:
帮我用currency-convert技能把100美元转成人民币
如果技能已被正确加载并注册到系统的user-invocable列表中,AI会自动将你自然语言的请求解析为技能调用。
第三层:开发者模式热重载(节省调试时间)
OpenClaw v2026.3.31及以上版本支持Skills模块的运行时热重载。启动时附加环境变量开启DEV模式:
OPENCLAW_DEV_MODE=true npm run start
在这种模式下,修改任何已注册技能文件并保存后,控制台会输出[HOTRELOAD] skill-name reloaded提示,技能逻辑立即生效。这是迭代调试时最节省时间的方式——无需反复重启Gateway。
第四层:调试日志输出策略
使用this.logger(在BaseSkill类中)或通过context.logger输出结构化日志,而不是随意的console.log:
context.logger.info("技能执行开始", { from, to, amount });
try {
const result = await doSomething();
context.logger.debug("API响应", { status: result.status });
return { success: true, data: result };
} catch (error) {
context.logger.error("技能执行失败", { error: error.message });
return { success: false, error: error.message };
}
登录OpenClaw日志查看方式:
openclaw logs --follow | grep "my-skill"
第五层:异常路径与边界条件测试
测试用例必须覆盖以下异常场景:
| 测试类型 | 示例 | 预期行为 |
|---|---|---|
| 参数缺失 | 调用时不传amount | 返回明确错误,明确提示缺少哪个参数 |
| 参数类型错误 | 传字符串而非数字 | 类型转换或报错 |
| 网络超时 | API响应缓慢 | 按指数退避重试,最终返回超时错误 |
| API凭证失效 | API Key过期 | 捕获401/403,提示用户更新Key |
| 限流触发 | 短时间内大量调用 | 队列等待或返回限流错误 |
| 数据格式异常 | API返回非预期JSON | 解析失败时优雅降级 |
常用CLI调试命令速查
# 列出所有已安装技能
openclaw skills list
# 查看指定技能的详细信息(含SKILL.md内容)
openclaw skills info my-skill
# 校验技能语法和格式
openclaw skill validate my-skill
# 强制重载技能(修改SKILL.md后)
openclaw skills reload
# 在命令行中直接调用技能测试(需Gateway运行)
openclaw skills run my-skill --params '{"from":"USD","to":"CNY","amount":100}'
16.6 实战:开发一个货币汇率转换技能
一句话概括:本实战将从头开发一个完整的货币汇率转换技能——它同时包含SKILL.md(声明式指导)和TypeScript代码(执行逻辑),让你在一个示例中体验声明式Skill和原生代码Skill两种模式的完整开发流程。
本实战将开发一个能够根据实时汇率完成货币转换的智能技能。它支持USD、EUR、CNY、JPY、GBP等主要货币的相互转换,当汇率API不可用时自动回退到缓存汇率,并在每次调用时记录转换日志。
第一步:脚手架生成项目
npx openclaw-skill-boilerplate currency-converter
cd currency-converter
npm install
第二步:编写SKILL.md(文档驱动)
编辑SKILL.md:
---
name: currency-converter
description: 当用户询问货币汇率、货币转换、某币种对另一币种的兑换时使用。调用实时汇率API进行精准转换。
author: your-name
version: 1.0.0
license: MIT
user-invocable: true
metadata:
openclaw:
emoji: "💱"
requires:
bins: ["curl"]
env: ["EXCHANGE_API_KEY"]
---
# 货币汇率转换技能
## 触发条件
当用户消息包含以下意图时调用此技能:
- “把X美元换成人民币”
- “100欧元等于多少日元”
- “查询USD/CNY汇率”
## 执行步骤
1. 从用户输入中识别源货币代码、目标货币代码和金额
2. 若用户只问汇率未提供金额,默认视为转换1单位
3. 调用汇率API获取实时数据
4. 执行金额转换计算
5. 格式化返回结果,保留两位小数
## 可用工具
本技能使用内置的 `fetch` 工具调用汇率API
## 输出格式
"{amount} {from} = {converted} {to} (汇率: {rate},数据时间: {timestamp})"
第三步:实现TypeScript核心逻辑(编辑src/tools.ts)
import { SkillTool } from '@openclaw/skills-core';
// 汇率API配置(示例免费API,生产环境请替换为正式服务)
const EXCHANGE_API_URL = 'https://api.exchangerate-api.com/v4/latest';
// 汇率缓存(简单实现,生产环境建议使用Redis或更完善的方案)
let rateCache: { rates: Record<string, number>; timestamp: number } | null = null;
const CACHE_TTL = 3600000; // 1小时缓存
async function fetchExchangeRates(base: string) {
// 检查缓存是否有效
if (rateCache && (Date.now() - rateCache.timestamp) < CACHE_TTL) {
return rateCache.rates;
}
try {
const response = await fetch(`${EXCHANGE_API_URL}/${base}`);
if (!response.ok) {
throw new Error(`API请求失败: ${response.status}`);
}
const data = await response.json();
// 更新缓存
rateCache = {
rates: data.rates,
timestamp: Date.now()
};
return data.rates;
} catch (error) {
// ⚠️【重要】网络错误时尝试返回上次缓存的汇率(即使已过期)
if (rateCache) {
console.warn(`API调用失败,使用缓存汇率: ${error.message}`);
return rateCache.rates;
}
throw new Error(`无法获取汇率数据: ${error.message}`);
}
}
export const convertCurrencyTool: SkillTool = {
name: "convert",
description: "Convert amount between currencies",
parameters: {
type: "object",
properties: {
from: {
type: "string",
description: "Source currency code (e.g., USD, EUR, CNY)",
enum: ["USD", "EUR", "CNY", "JPY", "GBP", "AUD", "CAD", "CHF"]
},
to: {
type: "string",
description: "Target currency code",
enum: ["USD", "EUR", "CNY", "JPY", "GBP", "AUD", "CAD", "CHF"]
},
amount: {
type: "number",
description: "Amount to convert",
minimum: 0,
default: 1
}
},
required: ["from", "to"]
},
execute: async (args) => {
const from = args.from as string;
const to = args.to as string;
const amount = (args.amount as number) || 1;
// 参数校验
if (amount <= 0) {
return {
success: false,
error: "金额必须大于0"
};
}
if (from === to) {
return {
success: true,
data: {
original: amount,
converted: amount,
rate: 1,
from,
to,
timestamp: new Date().toISOString()
},
summary: `${amount} ${from} = ${amount} ${to}`
};
}
try {
const rates = await fetchExchangeRates(from);
const rate = rates[to];
if (!rate) {
return {
success: false,
error: `不支持的货币类型: ${to}`
};
}
const converted = amount * rate;
return {
success: true,
data: {
original: amount,
converted: parseFloat(converted.toFixed(2)),
rate: parseFloat(rate.toFixed(4)),
from,
to,
timestamp: new Date().toISOString()
},
summary: `${amount} ${from} = ${converted.toFixed(2)} ${to} (汇率: ${rate.toFixed(4)})`
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
};
第四步:注册工具(编辑src/index.ts)
import { SkillDefinition } from '@openclaw/skills-core';
import { convertCurrencyTool } from './tools.js';
const currencyConverterSkill: SkillDefinition = {
id: "currency-converter",
name: "货币汇率转换",
description: "根据实时汇率完成货币金额转换",
version: "1.0.0",
// 注册此技能提供的工具
tools: [convertCurrencyTool],
execute: async (args, context) => {
// 直接透传给工具执行
return convertCurrencyTool.execute(args);
}
};
export default currencyConverterSkill;
第五步:编译与测试
# 编译TypeScript
npm run build
# 在OpenClaw命令行中测试
openclaw skills run currency-converter --params '{"from":"USD","to":"CNY","amount":100}'
# 在聊天环境中测试自然语言触发
# "帮我查询100美元等于多少人民币"
测试结果预期:
{
"success": true,
"data": {
"original": 100,
"converted": 720.50,
"rate": 7.2050,
"from": "USD",
"to": "CNY",
"timestamp": "2026-05-05T12:34:56.789Z"
},
"summary": "100 USD = 720.50 CNY (汇率: 7.2050)"
}
16.7 技能开发的常见错误与调试技巧
错误类型速查表
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 技能不加载 | SKILL.md YAML格式错误 | 检查frontmatter分隔符---是否首尾对齐,必需字段name和description是否存在 |
| openclaw skills list找不到 | 目录未放在正确位置 | Skill目录必须位于~/.openclaw/workspace/skills/或~/.openclaw/skills/中 |
| 技能无法触发 | 触发描述不匹配用户话术 | 检查SKILL.md的description字段是否足够明确地关联了用户意图 |
| 工具调用失败 | 缺少必需的API Key/环境变量 | 检查manifest.json中的required环境变量是否已在环境中配置好,或在技能配置中添加env字段 |
| 执行结果不符合预期 | 参数类型不匹配(string vs number) | 使用ArkType或显式类型转换确保参数类型正确;启用了ArkType的技能,校验失败会清晰报错提示“字段x应为number类型” |
| 权限被拒绝 | 技能未声明所需权限 | 在manifest.json的permissions数组中添加所需权限(如network:read、fs:read) |
| 返回数据格式错误 | 返回值不符合规范化结构 | 确保execute返回的是标准的{ success, data, summary }对象结构 |
| 编译错误 | TypeScript版本/配置问题 | 确认tsconfig.json的target设置为ES2022及以上,module为NodeNext |
| 热重载不生效 | DEV模式未开启 | 启动时添加OPENCLAW_DEV_MODE=true环境变量 |
调试专用工具(闪电排查)
如果技能无法加载或频繁崩溃,善用这条快速诊断命令链,能在一个会话里扫清90%的配置问题:
openclaw doctor # 诊断OpenClaw整体健康
openclaw skills check my-skill # 仅校验某个技能的SKILL.md格式
openclaw skills info my-skill # 显示技能的元数据和触发条件
发布到ClawHub
技能完成开发和测试后,可通过ClawHub分享给社区:
# 登录ClawHub
clawhub login
# 发布技能
clawhub publish
发布前确保:
- SKILL.md格式正确且通过
openclaw validate验证 - 版本号遵循语义化版本(如1.0.0)
- metadata.openclaw.security扫描显示为低风险或无恶意标记
- 已删除任何潜在的硬编码密钥和Token
16.8 本节小结
本节课我们完整走过了自定义Skill的开发全链路,核心知识点可总结为:
-
Skill的本质:不是传统意义的“插件”,而是遵循标准化规范的TypeScript/JavaScript模块——接收内核的标准化指令,执行业务逻辑,返回标准化结果,不参与意图解析,由内核统一进行权限管理。
-
两种开发模式:声明式Skill(仅需SKILL.md,零代码开发)适合简单的工具组合和提示词封装;原生代码Skill(TypeScript)适合API集成、数据库操作和复杂业务逻辑。
-
核心接口:SkillDefinition包含id、name、description、parameters(ArkType schema)和execute函数,以及可选的init和cleanup生命周期钩子。
-
开发脚手架:使用
openclaw-skill-boilerplate可30秒内生成完整项目结构,包含SKILL.md、TypeScript严格模式、工具定义模式和ClawHub发布就绪配置。 -
参数校验:推荐使用ArkType进行schema定义——语法简洁,完全兼容TypeScript的类型推断,比手写JSON Schema更高效。
-
测试调试:支持热重载DEV模式(
OPENCLAW_DEV_MODE=true),修改技能文件后立即生效无需重启Gateway;多级测试工具链从命令行基础调用延伸到Gateway会话模拟。 -
实战项目:完整实现了货币汇率转换技能——同时包含SKILL.md和TypeScript代码,涵盖API调用、缓存设计、错误处理和标准化的参数验证,展示了生产级技能的核心设计模式。
16.9 课后习题
1. Skill vs Plugin 概念辨析
根据DeepWiki中关于扩展类型的分类:Skills是通过Markdown文件定义Agent能力(位于ClawHub或workspace/skills),Plugins是通过TypeScript模块注册运行时能力(如模型提供商、工具、通道)。请用你自己的话解释Plugin和Skill的核心区别,并分别列举它们各自解决什么类型的问题。
2. 本地环境搭建与首个技能验证
根据16.2节的最小化Skill模板(仅需SKILL.md),在~/.openclaw/workspace/skills/下创建hello-world技能。测试发送“问候一下”或直接通过命令行工具观察系统如何加载该技能。记录openclaw skills list的输出状态。
3. Schema参数扩展
在16.6节的汇率转换技能基础上,扩展参数校验功能:增加date可选字段,允许用户查询历史汇率的某一天数据。若用户不提供date字段则默认获取今天汇率;若提供则从缓存API(如exchangerate.host)获取指定日期的汇率。实现时需处理日期格式校验和数据时效性判断。
4. 自定义日志记录调试
在开发中的技能内使用context.logger记录三阶段信息:技能执行开始(含入参)、汇率的API调用完成(含响应状态和耗时)、技能成功输出。运行时通过openclaw logs --follow | grep "your-skill-id"抓取这些日志,并用grep或awk筛选出执行耗时统计。
5. 异常路径与边界测试
为汇率转换技能设计一套单元测试用例,覆盖以下异常情形:API超时(使用mock延迟响应)、返回非200状态码、返回的JSON中不包含目标货币字段、amount金额为负数或零、用户传入不支持的货币代码。每个异常情形都要有对应的错误返回,且错误信息能清晰指示问题所在。
进阶思考题(选做)
如何将本节开发的汇率转换技能打包并通过ClawHub发布分享?发布前需要补充哪些元数据和安全清单?参考7.2节和7.5节的发布流程,设计一份检查清单,包含:SKILL.md元数据完整性、API Key的外部注入配置(而非硬编码!)、权限声明最小化(是否只需network:read?是否需要fs:write?申请过多则过重)、经过clawhub publish之前务必运行的security scan标记等。清单完成后再规划一个包括上述步骤在内的端到端验证脚本。
🔗《30节课精通 OpenClaw》系列课程导航
第一部分(第1-5课) :基础认知与入门部署——解决“这是什么、怎么搭建”的问题;
第二部分(第6-10课):核心原理深度剖析——解决“底层怎么工作”的问题;
第三部分(第11-15课) :应用场景与平台集成——解决“能用来做什么”的问题;
第四部分(第16-21课) :技能开发与定制扩展——解决“如何自己扩能力”的问题;
第五部分(第22-26课):高级特性与性能优化——解决“怎么用得更好”的问题;
第六部分(第27-30课) :安全、运维与生态进阶——解决“如何安全可靠地规模化”的问题;