Skip to content

技能包开发

这篇文档说明如何为 OpenClaw/Claude code等Agent 开发一个可复用的技能包。重点不是技能加载原理,而是技能包本身应该如何组织,包括目录结构、主 SKILL.md 的写法、模块化文档拆分、CLI 封装,以及最终的 zip 打包规范。

推荐目录结构

一个技能包本质上是一个目录,目录里至少要有根级 SKILL.md,其余文件按需要补充。

text
my-skill/
├── SKILL.md
├── README.md
├── .clawignore
├── references/
│   ├── getting-started.md
│   ├── commands.md
│   └── troubleshooting.md
├── examples/
│   └── demo-input.json
└── cli/
    ├── package.json
    └── index.js

各文件和目录建议分工如下:

  • SKILL.md:必需。技能入口文件,负责 frontmatter 和主说明。
  • README.md:可选。给人看的介绍文档,适合市场展示、仓库说明或维护者阅读。
  • .clawignore:可选。用于排除缓存、构建产物、截图、日志、密钥文件等不应随技能发布的内容。
  • references/:可选但强烈推荐。用于放详细模块文档、参数说明、工作流说明、错误处理和排障文档。
  • examples/:可选。放少量示例输入输出,帮助模型和维护者理解接口。
  • cli/:如果技能涉及自编程逻辑,建议作为标准位置存放 CLI 实现。

SKILL.md 文件头格式

SKILL.md 建议采用与成熟技能包一致的写法:前面是简短 frontmatter,后面是正文说明。

最小示例:

markdown
---
name: my-awesome-skill
description: 一个通过本地 CLI 执行任务的示例技能包。
version: 1.0.0
icon: 🚀
---

# My Awesome Skill

当用户请求执行这个封装后的工作流时,使用这个技能。

下面这个示例更接近即梦 AI 这类模块化技能包的风格:

markdown
---
name: sample-ops-toolkit
description: 通过打包 CLI 和模块化参考文档执行本地多步骤工作流。
version: 1.0.0
icon: 🛠
metadata:
  clawdbot:
    emoji: 🛠
    requires:
      bins:
        - node
    commands:
      job run: node {baseDir}/cli/index.js job run
      job status: node {baseDir}/cli/index.js job status
---

# Sample Ops Toolkit

执行某个命令前,先读取 `references/` 下对应模块文档。

字段建议:

  • name:技能包的稳定标识,建议使用小写和短横线命名。
  • description:技能被发现和命中的关键字段,必须明确说明“这个技能在什么场景下使用”。
  • version:建议提供,方便后续分发和维护。
  • icon:可选,但对技能展示页和市场页面有帮助。
  • metadata.clawdbot.requires:声明运行依赖,例如 nodeuv 或环境变量。
  • metadata.clawdbot.commands:如果技能暴露 CLI 命令,建议在这里显式列出来。

SKILL.md 应该做索引,而不是塞满所有细节

对于大型项目或多模块技能,不建议把所有说明都堆进根级 SKILL.md。更合理的做法是让主 SKILL.md 只承担索引和分发职责。

推荐做法:

  • 在主 SKILL.md 里写清楚技能用途、约束、依赖、模块入口和总命令索引。
  • 把每个子模块的详细说明放进 references/ 目录。
  • 在主 SKILL.md 中明确要求代理在执行某个模块前先读取对应模块文档。

这样做的好处:

  • 主提示更短,减少无关上下文
  • 维护时不会把不同模块逻辑混在一起
  • 新增模块时只需要补充对应文档,不必频繁膨胀根文件

大型技能建议模块化

如果一个技能涉及多个业务能力,应当把它当成一个小型产品来组织,而不是一个单文件 prompt。

示例:

text
travel-assistant/
├── SKILL.md
├── references/
│   ├── flights.md
│   ├── hotels.md
│   ├── visas.md
│   └── support.md
└── cli/
    └── index.js

SKILL.md 可以写成这样的索引风格:

markdown
# Travel Assistant

执行前先读取对应模块文档:

- 航班查询和预订流程 -> `references/flights.md`
- 酒店查询和预订流程 -> `references/hotels.md`
- 签证材料检查流程 -> `references/visas.md`
- 异常处理和人工兜底 -> `references/support.md`

以下场景尤其适合模块化:

  • 一个技能有多个独立业务能力
  • 一个技能接了多个外部服务
  • 参数很多、执行模式很多
  • 技能由多人维护

如果涉及自编程,必须封装成 CLI

如果技能里需要写自定义代码,不要把逻辑散落成临时脚本然后在说明里东拼西凑。应当把所有自编程操作统一封装成 CLI。

强制要求:

  • 只要涉及自编程,所有操作都要通过 CLI 暴露

推荐技术选型:

  • Node 项目使用 npmpnpm 或等价的 Node 包管理方式
  • Python 项目使用 uv
  • 命令入口保持稳定,例如 node {baseDir}/cli/index.js ...uv run my-skill ...

为什么要这样做:

  • 接口清晰,代理调用方式稳定
  • 命令可以脱离代理独立测试
  • 参数、退出码和输出格式更容易规范化
  • 后续打包、安装和排障都更简单

Node CLI 示例

text
weather-tool/
├── SKILL.md
└── cli/
    ├── package.json
    └── index.js

cli/package.json

json
{
  "name": "weather-tool-cli",
  "private": true,
  "type": "module",
  "scripts": {
    "start": "node index.js"
  }
}

cli/index.js

js
#!/usr/bin/env node

const [, , moduleName, action, ...args] = process.argv;

if (moduleName === 'forecast' && action === 'city') {
  const city = args.join(' ').trim();
  if (!city) {
    console.error('Usage: node index.js forecast city <city-name>');
    process.exit(1);
  }

  console.log(JSON.stringify({ city, status: 'ok' }, null, 2));
  process.exit(0);
}

console.error('Unknown command');
process.exit(1);

对应的 SKILL.md 命令映射:

yaml
metadata:
  clawdbot:
    commands:
      forecast city: node {baseDir}/cli/index.js forecast city

Python CLI 示例(使用 uv

text
csv-tool/
├── SKILL.md
└── cli/
    ├── pyproject.toml
    └── src/csv_tool/main.py

pyproject.toml

toml
[project]
name = "csv-tool"
version = "0.1.0"
requires-python = ">=3.11"

[project.scripts]
csv-tool = "csv_tool.main:main"

在技能里建议这样调用:

yaml
metadata:
  clawdbot:
    commands:
      csv validate: uv run csv-tool validate

一个简单技能包示例

目录:

text
hello-ops/
├── SKILL.md
├── references/
│   └── usage.md
└── cli/
    ├── package.json
    └── index.js

示例 SKILL.md

markdown
---
name: hello-ops
description: 通过打包的 Node CLI 执行一个简单示例工作流。
version: 1.0.0
icon: 👋
metadata:
  clawdbot:
    requires:
      bins:
        - node
    commands:
      hello run: node {baseDir}/cli/index.js hello run
---

# Hello Ops

当用户请求 hello 工作流时使用这个技能。

执行规则:

- 先读取 `references/usage.md`
- 默认动作使用 `hello run`

示例 references/usage.md

markdown
# Usage

Command: `hello run`

Behavior:

- 输出结构化成功结果
- 成功时退出码为 `0`
- 输入非法时退出非零退出码

示例 cli/index.js

js
#!/usr/bin/env node

const [, , group, command] = process.argv;

if (group === 'hello' && command === 'run') {
  console.log(JSON.stringify({ message: 'hello from the packaged CLI' }, null, 2));
  process.exit(0);
}

console.error('Unknown command');
process.exit(1);

zip 打包规范

技能包最终如果要作为 zip 分发,不要把文件直接打在压缩包根目录。

强制要求:

  • zip 文件中必须先有一个目录
  • 技能包所有文件都必须放在这个目录里面

正确示例:

text
hello-ops.zip
└── hello-ops/
    ├── SKILL.md
    ├── README.md
    ├── references/
    └── cli/

错误示例:

text
hello-ops.zip
├── SKILL.md
├── README.md
├── references/
└── cli/

这样要求的原因很直接:

  • 解压路径稳定,不会把多个技能包文件混到一起
  • 安装器更容易定位技能根目录
  • 用户手动解压时不容易污染目标目录

发布前检查清单

  • 根目录存在 SKILL.md
  • frontmatter 字段完整且描述清晰
  • 自编程逻辑全部通过 CLI 暴露
  • 大型技能已经把详细说明拆到 references/
  • .clawignore 已排除缓存、日志、密钥和临时文件
  • 产出的 zip 只有一个顶层技能目录

发布前测试建议

在发布技能包之前,至少做这些检查:

  • 直接运行 CLI,确认退出码正确
  • 检查所有 {baseDir} 路径引用都真实存在
  • 至少验证一个正常路径和一个失败路径
  • 确认 nodeuv 等运行时依赖已经声明在元数据中
  • 在干净目录里解压 zip,确认结构仍然符合要求

基于 MIT 许可发布

🌐加入社区