世界上有一种能力,叫Skills。它就像AI的手,让它从只会聊天的"话痨"变成能干活儿的"打工人"。4分17秒前,我决定写一个Skill...
Agent Skills是OpenClaw的核心扩展机制。本文将手把手教你如何从零开发一个Skill,并发布到ClawHub供全球开发者使用。
Skills是OpenClaw的能力模块,每个Skill教AI如何做一件特定的事:
你可以把Skills理解为"超能力卡片",装上它,AI就多了一项技能。
一个标准的Skill包含以下文件:
my-skill/
├── SKILL.md # 技能描述和使用说明
├── index.js # 主逻辑(可选)
├── utils.js # 工具函数(可选)
├── package.json # Node.js依赖(可选)
└── examples/ # 示例代码(可选)
SKILL.md是Skill的核心文件,采用YAML Frontmatter格式:
---
name: weather-check
version: 1.0.0
description: 查询全球城市天气信息
author: miaoquai
tags:
- weather
- api
- utility
requires:
- node >= 14
permissions:
- network
- env:WEATHER_API_KEY
---
# Weather Check Skill
查询指定城市的实时天气和未来7天预报。
## 使用方式
```
查询北京天气
北京今天天气怎么样
明天上海会下雨吗
```
## 环境变量
- `WEATHER_API_KEY`: 天气API密钥(从 weatherapi.com 获取)
## 返回格式
```json
{
"city": "北京",
"temperature": 25,
"condition": "晴天",
"humidity": "45%"
}
```
--- 包围的YAML frontmatter是元数据如果需要自定义逻辑,创建 index.js:
// index.js
const axios = require('axios');
module.exports = {
// Skill名称
name: 'weather-check',
// Skill描述
description: '查询全球城市天气',
// 工具函数列表
tools: [
{
name: 'get_weather',
description: '获取指定城市的天气信息',
parameters: {
type: 'object',
properties: {
city: {
type: 'string',
description: '城市名称(如:北京、London)'
},
days: {
type: 'integer',
description: '预报天数(1-7)',
default: 1
}
},
required: ['city']
},
handler: async ({ city, days = 1 }) => {
const apiKey = process.env.WEATHER_API_KEY;
const url = `https://api.weatherapi.com/v1/forecast.json?key=${apiKey}&q=${city}&days=${days}`;
try {
const response = await axios.get(url);
return {
success: true,
data: response.data
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
}
]
};
开发完成后,在本地测试:
# 1. 将Skill复制到OpenClaw技能目录
cp -r my-skill ~/.openclaw/skills/
# 2. 重启OpenClaw加载新Skill
openclaw restart
# 3. 测试Skill功能
openclaw test my-skill "查询北京天气"
# 4. 查看调试日志
openclaw logs --skill my-skill
npm install -g @openclaw/clawhub
clawhub login
# 1. 初始化Skill项目
clawhub init my-skill
# 2. 编写和测试Skill(见上文)
# 3. 打包Skill
clawhub pack
# 4. 发布到ClawHub
clawhub publish
# 5. 查看已发布的Skill
clawhub list
const Joi = require('joi');
const schema = Joi.object({
city: Joi.string().min(2).max(50).required(),
days: Joi.number().integer().min(1).max(7).default(1)
});
// 在handler中验证
const { error, value } = schema.validate(params);
if (error) {
throw new Error(`参数错误: ${error.message}`);
}
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 300 }); // 5分钟缓存
async function getWeather(city) {
const cacheKey = `weather:${city}`;
let result = cache.get(cacheKey);
if (!result) {
result = await fetchWeatherFromAPI(city);
cache.set(cacheKey, result);
}
return result;
}
async function handler(params) {
try {
const result = await doSomething(params);
return { success: true, data: result };
} catch (error) {
console.error('Skill执行错误:', error);
return {
success: false,
error: '操作失败,请稍后重试',
details: error.message
};
}
}
weather-check ✅,WeatherCheck ❌get_weather、send_emailAPI_KEY、BASE_URL下面是一个完整的实战案例,展示如何创建一个有用的Skill:
// github-pr-analyzer/index.js
const { Octokit } = require('@octokit/rest');
module.exports = {
name: 'github-pr-analyzer',
description: '分析GitHub Pull Request的代码变更和质量',
tools: [
{
name: 'analyze_pr',
description: '分析指定PR的代码变更',
parameters: {
type: 'object',
properties: {
owner: { type: 'string', description: '仓库所有者' },
repo: { type: 'string', description: '仓库名' },
pr_number: { type: 'integer', description: 'PR编号' }
},
required: ['owner', 'repo', 'pr_number']
},
handler: async ({ owner, repo, pr_number }) => {
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
try {
// 获取PR详情
const { data: pr } = await octokit.pulls.get({
owner, repo, pull_number: pr_number
});
// 获取文件变更
const { data: files } = await octokit.pulls.listFiles({
owner, repo, pull_number: pr_number
});
// 分析结果
const analysis = {
title: pr.title,
author: pr.user.login,
additions: files.reduce((sum, f) => sum + f.additions, 0),
deletions: files.reduce((sum, f) => sum + f.deletions, 0),
files_changed: files.length,
summary: pr.body?.substring(0, 200) || '无描述'
};
return { success: true, data: analysis };
} catch (error) {
return { success: false, error: error.message };
}
}
}
]
};