Skip to content

自定义技能

创建你自己的 OpenClaw 技能,扩展 AI 助手的能力。

技能结构

一个完整的技能包含以下文件:

my-skill/
├── skill.json      # 技能配置文件
├── index.ts        # 主入口文件
├── prompts/        # 提示词模板
│   └── main.md
├── functions/      # 辅助函数
│   └── helper.ts
└── tests/          # 测试文件
    └── index.test.ts

创建第一个技能

1. 创建技能目录

bash
mkdir -p ~/.openclaw/skills/my-skill
cd ~/.openclaw/skills/my-skill

2. 创建配置文件

skill.json:

json
{
  "name": "my-skill",
  "version": "1.0.0",
  "description": "我的第一个自定义技能",
  "author": "your-name",
  "main": "index.ts",
  "permissions": [
    "filesystem",
    "network"
  ],
  "config": {
    "apiKey": {
      "type": "string",
      "required": true,
      "description": "API 密钥"
    }
  },
  "triggers": [
    {
      "type": "command",
      "pattern": "myskill"
    }
  ]
}

3. 创建入口文件

index.ts:

typescript
import { Skill, Context, Response } from 'openclaw'

interface MySkillConfig {
  apiKey: string
}

export default class MySkill implements Skill {
  name = 'my-skill'
  description = '我的第一个技能'
  
  private config: MySkillConfig

  async initialize(config: MySkillConfig) {
    this.config = config
    console.log('技能初始化完成')
  }

  async execute(context: Context): Promise<Response> {
    const { message, user } = context
    
    // 解析用户意图
    const intent = await this.parseIntent(message.content)
    
    // 执行相应操作
    const result = await this.performAction(intent)
    
    return {
      success: true,
      message: result,
      metadata: {
        skill: this.name,
        executedAt: new Date().toISOString()
      }
    }
  }

  private async parseIntent(content: string) {
    // 解析用户意图
    if (content.includes('查询')) {
      return { action: 'query', params: {} }
    }
    return { action: 'default', params: {} }
  }

  private async performAction(intent: any) {
    // 执行具体操作
    switch (intent.action) {
      case 'query':
        return '查询结果:...'
      default:
        return '默认响应'
    }
  }
}

4. 创建提示词模板

prompts/main.md:

markdown
# 技能说明

你是一个专业的助手,帮助用户完成以下任务:

1. 任务一描述
2. 任务二描述

## 用户请求

{{user_message}}

## 上下文

{{context}}

## 请根据以上信息执行相应操作

5. 安装依赖

bash
npm init -y
npm install typescript @types/node

6. 注册技能

bash
openclaw skills register ./my-skill

技能 API

Context 对象

typescript
interface Context {
  // 用户消息
  message: {
    id: string
    content: string
    attachments?: Attachment[]
  }
  
  // 用户信息
  user: {
    id: string
    name: string
    preferences?: Record<string, any>
  }
  
  // 会话信息
  session: {
    id: string
    history: Message[]
    metadata: Record<string, any>
  }
  
  // 配置
  config: Record<string, any>
  
  // 工具函数
  tools: {
    memory: MemoryTools
    filesystem: FilesystemTools
    network: NetworkTools
    browser: BrowserTools
  }
}

Response 对象

typescript
interface Response {
  success: boolean
  message: string | ResponseMessage
  actions?: Action[]
  metadata?: Record<string, any>
}

interface ResponseMessage {
  text: string
  rich?: {
    type: 'markdown' | 'html' | 'json'
    content: string
  }
  attachments?: Attachment[]
}

使用工具

typescript
// 文件系统操作
const content = await context.tools.filesystem.readFile('/path/to/file')
await context.tools.filesystem.writeFile('/path/to/file', 'content')

// 网络请求
const response = await context.tools.network.fetch('https://api.example.com')

// 浏览器控制
await context.tools.browser.open('https://example.com')
const screenshot = await context.tools.browser.screenshot()

// 记忆操作
await context.tools.memory.store('key', { data: 'value' })
const memory = await context.tools.memory.retrieve('key')

技能配置

配置 Schema

json
{
  "config": {
    "apiKey": {
      "type": "string",
      "required": true,
      "description": "API 密钥",
      "default": "",
      "validation": {
        "pattern": "^sk-",
        "message": "API Key 必须以 sk- 开头"
      }
    },
    "maxRetries": {
      "type": "number",
      "required": false,
      "description": "最大重试次数",
      "default": 3
    },
    "features": {
      "type": "object",
      "properties": {
        "enableCache": {
          "type": "boolean",
          "default": true
        }
      }
    }
  }
}

用户配置

用户可以在 ~/.openclaw/config.json 中配置技能:

json
{
  "skills": {
    "my-skill": {
      "apiKey": "your-api-key",
      "maxRetries": 5,
      "features": {
        "enableCache": true
      }
    }
  }
}

触发器

技能可以通过多种方式触发:

命令触发

json
{
  "triggers": [
    {
      "type": "command",
      "pattern": "myskill"
    }
  ]
}

用户发送 /myskill 即可触发。

关键词触发

json
{
  "triggers": [
    {
      "type": "keyword",
      "patterns": ["查询天气", "天气预报"]
    }
  ]
}

定时触发

json
{
  "triggers": [
    {
      "type": "schedule",
      "cron": "0 9 * * *"
    }
  ]
}

事件触发

json
{
  "triggers": [
    {
      "type": "event",
      "event": "file.created",
      "filter": {
        "path": "/Documents/*.md"
      }
    }
  ]
}

技能权限

声明技能所需的权限:

json
{
  "permissions": [
    {
      "type": "filesystem",
      "paths": ["~/Documents"],
      "operations": ["read", "write"]
    },
    {
      "type": "network",
      "domains": ["api.example.com"],
      "methods": ["GET", "POST"]
    },
    {
      "type": "command",
      "allow": ["git", "npm"],
      "deny": ["rm -rf"]
    }
  ]
}

测试技能

单元测试

tests/index.test.ts:

typescript
import MySkill from '../index'
import { createMockContext } from 'openclaw-testing'

describe('MySkill', () => {
  let skill: MySkill

  beforeEach(() => {
    skill = new MySkill()
  })

  test('should execute successfully', async () => {
    const context = createMockContext({
      message: { content: '测试消息' }
    })
    
    const response = await skill.execute(context)
    
    expect(response.success).toBe(true)
    expect(response.message).toBeDefined()
  })
})

运行测试:

bash
openclaw skills test my-skill

调试模式

bash
openclaw skills debug my-skill

发布技能

发布到 ClawHub

bash
# 登录
openclaw login

# 发布
openclaw skills publish my-skill

版本管理

bash
# 更新版本
npm version patch  # 1.0.0 -> 1.0.1

# 发布新版本
openclaw skills publish my-skill --update

最佳实践

  1. 单一职责:每个技能专注一个任务
  2. 错误处理:优雅处理各种异常
  3. 用户反馈:提供清晰的操作反馈
  4. 日志记录:记录关键操作日志
  5. 性能优化:避免阻塞操作
  6. 安全考虑:不暴露敏感信息

示例:天气查询技能

typescript
import { Skill, Context, Response } from 'openclaw'

export default class WeatherSkill implements Skill {
  name = 'weather'
  description = '查询天气信息'

  async execute(context: Context): Promise<Response> {
    const city = this.extractCity(context.message.content)
    
    if (!city) {
      return {
        success: false,
        message: '请告诉我你想查询哪个城市的天气?'
      }
    }
    
    const weather = await this.fetchWeather(city)
    
    return {
      success: true,
      message: `${city}今天${weather.condition},气温${weather.temp}°C`
    }
  }

  private extractCity(content: string): string | null {
    const match = content.match(/(.+)天气/)
    return match ? match[1] : null
  }

  private async fetchWeather(city: string) {
    const response = await fetch(`https://api.weather.com/${city}`)
    return response.json()
  }
}

下一步

基于 MIT 许可发布