1. 项目概述:一个为AI Agent技能开发提速的脚手架
如果你正在为OpenClaw平台开发AI Agent技能,或者对构建能与Claude、GPTs等模型交互的“工具”感兴趣,那么你很可能经历过从零开始的繁琐:手动创建项目结构、配置TypeScript、编写SKILL.md规范文档、设置构建流程……这个过程不仅重复,还容易出错。openclaw-skill-boilerplate这个项目就是为了解决这个痛点而生的。它是一个生产就绪的脚手架工具,旨在让你在几分钟内就能完成一个OpenClaw技能的初始化、开发和发布全流程。
简单来说,它就是一个“技能生成器”。你只需要一条命令,它就能为你创建一个结构完整、配置妥当、开箱即用的技能项目模板。这个模板已经集成了TypeScript严格模式、标准的项目目录、CI/CD工作流以及最重要的——符合OpenClaw规范的SKILL.md文件骨架。无论你是想开发一个查询天气的技能、一个控制智能家居的工具,还是一个处理专业数据的助手,这个脚手架都能帮你跳过所有基础搭建工作,直接进入核心逻辑的开发。
这个项目特别适合两类开发者:一是已经了解OpenClaw/MCP(Model Context Protocol)协议,希望快速将想法落地为可分发技能的实践者;二是对AI Agent生态感兴趣,想学习如何为Claude等模型构建标准化扩展工具的新手。通过这个标准化的起点,你可以更专注于技能本身的创意和实现,而不是被工程细节绊住手脚。
2. 核心架构与设计思路拆解
2.1 为什么需要技能脚手架?
在深入代码之前,我们先聊聊为什么“脚手架”在AI技能开发中变得如此重要。OpenClaw及其背后的MCP协议,本质上是在定义AI模型与外部工具(技能)之间一种标准化的通信方式。一个技能,就像给AI模型安装了一个新的“插件”或“应用程序接口”。为了让AI能正确理解和使用这个插件,技能必须遵循特定的格式和约定,主要包括:一个描述技能元数据和工具的SKILL.md文件,以及实现这些工具功能的后端代码。
手动创建这一切的问题是显而易见的:一致性难以保证。每个开发者可能对目录结构、TypeScript配置、工具导出方式有不同的理解,这会导致技能质量参差不齐,也给ClaWHub这样的技能分发平台的管理带来困难。openclaw-skill-boilerplate通过提供一个官方推荐的“黄金标准”模板,统一了最佳实践。它确保了每个新技能都具备:
- 标准的项目结构:清晰的
src、dist、examples目录分离,便于代码组织和构建。 - 严格的类型安全:预配置了严格的
tsconfig.json,强制使用TypeScript,从开发阶段就减少运行时错误。 - 完整的开发工具链:内置了
build(构建)、dev(监听模式)、typecheck(类型检查)等脚本,开发体验流畅。 - 合规的SKILL.md骨架:自动生成包含正确YAML Frontmatter和章节结构的技能描述文件,这是技能能被AI正确识别的关键。
- 持续集成支持:预置了GitHub Actions工作流(
.github/workflows/ci.yml),在代码提交时自动进行构建和类型检查,保障代码库健康。
这种设计思路的核心是“约定优于配置”。它通过预设一套经过验证的良好约定,极大地降低了开发者的认知负担和启动成本。
2.2 项目结构深度解析
让我们仔细看看脚手架生成的项目结构,理解每个部分的作用:
my-awesome-skill/ ├── SKILL.md # 【核心】技能入口文件,AI Agent通过阅读此文件来了解技能 ├── README.md # 面向人类开发者的项目说明文档 ├── package.json # 项目依赖和脚本定义 ├── tsconfig.json # TypeScript编译配置(启用严格模式) ├── src/ │ ├── index.ts # 技能主逻辑,导出技能工具集合 │ ├── tools.ts # (可选)独立存放工具定义,保持模块清晰 │ └── types.ts # 共享的TypeScript类型定义,如SkillTool类型 ├── scripts/ │ └── scaffold.ts # 脚手架自身的逻辑,用于复制模板文件 ├── templates/ │ └── skill/ # 模板文件的源头 │ ├── SKILL.md.template │ ├── src/index.ts.template │ ├── package.json.template │ └── ... ├── examples/ │ └── hello-world/ # 一个极简的可运行示例,供开发者参考 │ ├── SKILL.md │ └── src/index.ts └── .github/ └── workflows/ └── ci.yml # CI流水线,确保每次提交的代码质量关键文件解读:
SKILL.md:这是技能的“说明书”和“接口文档”。它的YAML头部(Frontmatter)定义了技能的元数据(名称、描述、版本、作者、标签、触发词),而正文部分则用自然语言详细描述了每个工具的功能、参数和使用示例。AI模型(如Claude)在执行npx clawhub install后,会读取这个文件来学习如何调用你的技能。src/index.ts:这是技能的“大脑”。它导出了一个符合OpenClaw预期的技能对象,其中包含了所有工具的定义。每个工具都包含name、description、parameters和一个关键的execute异步函数。当AI决定使用某个工具时,最终执行的就是这里的代码。templates/目录:这是脚手架的“模具”。当你运行npx openclaw-skill-boilerplate my-skill时,scripts/scaffold.ts会读取这个目录下的模板文件,将其中类似<%= skillName %>的变量替换为你提供的技能名,然后复制到目标目录,从而生成一个全新的、个性化的项目。
注意:
templates/目录是脚手架工具内部使用的,而你通过脚手架生成的项目中并没有这个目录。理解它的存在有助于你明白脚手架的工作原理,甚至在未来需要自定义模板时知道如何修改。
2.3 与OpenClaw及ClaWHub的生态关系
理解这个脚手架,必须将其放在OpenClaw更大的生态系统中来看。OpenClaw是一个开源平台,而MCP是它采用的一种协议,用于让AI模型安全、结构化地调用外部工具。openclaw-skill-boilerplate是这个生态中的“基础设施工具”。
- 开发阶段:你使用此脚手架快速创建技能项目。
- 分发阶段:你使用
npx clawhub@latest publish将构建好的技能发布到ClaWHub。ClaWHub可以理解为一个开源的、去中心化的“技能商店”或注册中心。 - 使用阶段:其他用户(或AI Agent本身)可以通过
npx clawhub@latest install <skill-name>从ClaWHub安装你的技能。安装后,该技能的SKILL.md和编译后的代码会被集成到他们的OpenClaw环境中,供AI模型调用。
脚手架在其中扮演了“标准化助推器”的角色,它确保了从开发到发布整个流程中,技能包的结构和格式都是统一、可预测的,从而让整个生态运行得更顺畅。
3. 从零开始:使用脚手架创建你的第一个技能
理论说得再多,不如亲手实践。让我们一步步走完从创建到运行一个简单技能的完整流程。我们将创建一个名为“时间助手”的技能,它提供一个工具来获取当前时间。
3.1 环境准备与项目初始化
首先,确保你的本地开发环境已经就绪:
- Node.js:需要版本18或更高。你可以在终端运行
node --version来检查。 - npm:通常随Node.js一起安装。运行
npm --version确认。 - 代码编辑器:推荐使用VS Code,它对TypeScript有非常好的内置支持。
创建新技能有两种官方推荐的方式,我强烈推荐第一种,因为它最简洁,且不需要全局安装,避免污染环境。
方法一:使用npx(推荐)打开你的终端,执行以下命令:
npx openclaw-skill-boilerplate time-assistant cd time-assistant这条命令做了几件事:npx会临时下载并执行openclaw-skill-boilerplate这个npm包,并将time-assistant作为参数传递给它。脚手架工具随后会在当前目录下创建一个名为time-assistant的新文件夹,并将所有模板文件填充进去。完成后,我们进入这个项目目录。
方法二:全局安装后使用如果你计划频繁创建新技能,也可以选择全局安装:
npm install -g openclaw-skill-boilerplate openclaw-skill-boilerplate time-assistant cd time-assistant实操心得:始终使用
npx是更安全、更现代的做法。它能保证你每次使用的都是该包的最新版本,而全局安装的版本可能会过时。除非你每天都要创建好几个新项目,否则npx足矣。
进入项目后,首先安装依赖:
npm install这个过程会下载TypeScript编译器、相关的类型定义文件以及其他开发依赖。
3.2 解读生成的项目并修改SKILL.md
安装完成后,用你的编辑器打开time-assistant文件夹。你会看到之前提到的完整项目结构。我们首先关注最核心的SKILL.md文件。
用编辑器打开它,你会看到类似以下内容的模板:
--- name: my-skill description: What your skill does version: 0.1.0 author: your-name tags: - utility triggers: - natural language trigger phrase --- # My Skill Brief description of what this skill does. ## Tools ### tool-name Short description of the tool. **Parameters:** - `param1` (string, required): Description. **Example:**Use tool-name with param1 set to "value"
## Installation ```bash npx clawhub install my-skill我们需要将其修改为我们的“时间助手”技能。修改YAML头部和正文: ```markdown --- name: time-assistant description: 一个提供当前时间和日期信息的简单助手技能。 version: 0.1.0 author: [你的名字或GitHub用户名] tags: - utility - time triggers: - 现在几点 - 今天日期 - 当前时间 --- # 时间助手 (Time Assistant) 本技能提供一个工具,用于获取服务器当前的精确时间和日期信息。 ## Tools ### get-current-time 获取当前的日期和时间。 **参数:** - `format` (字符串,可选): 指定时间的输出格式。默认为 `"locale"`。 - `"locale"`: 返回本地化的日期时间字符串(例如:“2023年10月27日 星期五 下午3:30:25”)。 - `"iso"`: 返回ISO 8601格式的字符串(例如:“2023-10-27T15:30:25.123Z”)。 - `"timestamp"`: 返回自1970年1月1日以来的毫秒数(数字类型)。 **返回值:** 一个包含 `success` 布尔值和 `time` 字符串(或数字)的对象。 **示例:** 当用户询问时间时,AI可以这样调用:调用 get-current-time 工具。
或者指定格式:调用 get-current-time 工具,format参数设为 "iso"。
## 安装 ```bash npx clawhub install time-assistant**关键修改点说明:** 1. **`name`**:必须与你的项目目录名或计划发布的包名保持一致,这是技能的唯一标识符。 2. **`description`**:用清晰的语言描述技能功能,这有助于AI和用户理解其用途。 3. **`tags`**:添加了`time`标签,方便在ClaWHub上分类和搜索。 4. **`triggers`**:这是**极其重要**的一环。你需要思考用户可能会用哪些自然语言短语来触发这个技能。这里添加了“现在几点”、“今天日期”、“当前时间”等常见问法。AI模型会将用户的输入与这些触发词进行匹配,从而决定是否调用该技能。 5. **Tools部分**:我们定义了一个名为`get-current-time`的工具,并详细描述了它的参数、可选值、返回值和调用示例。这部分文档写得越清晰、越贴近自然场景,AI就越能正确地使用它。 ### 3.3 实现核心工具逻辑 接下来,我们打开`src/index.ts`文件,实现`get-current-time`工具。你会看到模板生成的初始代码,我们将其替换为我们的逻辑: ```typescript // src/index.ts import { Skill, SkillTool } from './types'; // 1. 定义我们的工具 const getCurrentTimeTool: SkillTool = { name: 'get-current-time', description: '获取服务器当前的日期和时间。', parameters: [ { name: 'format', type: 'string', description: '时间输出格式。可选值: "locale" (默认), "iso", "timestamp"。', required: false, // 这是一个可选参数 enum: ['locale', 'iso', 'timestamp'], // 明确指定可选值,有助于AI理解 }, ], execute: async (args) => { // 从参数中获取format,如果没有提供则使用默认值'locale' const format = (args.format as string) || 'locale'; const now = new Date(); let timeOutput: string | number; try { switch (format) { case 'iso': timeOutput = now.toISOString(); break; case 'timestamp': timeOutput = now.getTime(); // 返回数字类型的毫秒时间戳 break; case 'locale': default: // 返回本地化的完整日期时间字符串 timeOutput = now.toLocaleString('zh-CN', { dateStyle: 'full', timeStyle: 'long', hour12: false, // 使用24小时制 }); break; } // 返回标准化的成功结果 return { success: true, data: `当前时间:${timeOutput}`, rawData: { time: timeOutput, formatUsed: format }, // 额外返回原始数据供高级处理 }; } catch (error) { // 错误处理:确保技能即使出错也能返回结构化的信息 return { success: false, error: `获取时间时发生错误:${error instanceof Error ? error.message : '未知错误'}`, }; } }, }; // 2. 将工具组合成技能 const timeAssistantSkill: Skill = { name: 'time-assistant', description: '提供当前时间和日期信息的助手。', version: '0.1.0', tools: [getCurrentTimeTool], // 一个技能可以包含多个工具 }; // 3. 导出技能对象,这是OpenClaw的约定 export default timeAssistantSkill;代码逻辑详解:
- 工具定义 (
getCurrentTimeTool):我们创建了一个符合SkillTool类型的对象。parameters数组定义了AI调用时需要提供的参数。这里我们将format设为可选(required: false),并使用了enum来明确告知AI可选的值有哪些,这能极大提高调用的准确性。 - 执行函数 (
execute):这是工具的核心。它是一个异步函数,接收args参数(包含了AI调用时传入的参数)。我们根据format参数的值,使用JavaScript的Date对象生成不同格式的时间。'locale':生成中文环境下的友好日期时间字符串。'iso':生成标准的ISO字符串,常用于机器交换。'timestamp':返回毫秒时间戳,一个数字。
- 返回值:我们返回一个对象,其中
success: true表示执行成功,data字段包含给AI阅读的友好结果,rawData则包含了结构化的原始数据(可选,但推荐)。如果发生错误,我们捕获异常并返回success: false和一个error信息。统一的返回值结构对于AI后续处理至关重要。 - 技能组装与导出:最后,我们将工具放入一个
Skill对象中,并通过export default导出。OpenClaw的运行时预期会从这里加载技能。
3.4 构建与本地测试
代码写好后,我们需要将其从TypeScript编译成JavaScript(因为Node.js直接运行的是JS)。在项目根目录运行:
npm run build这个命令会调用tsc(TypeScript编译器),根据tsconfig.json中的配置,将src/目录下的.ts文件编译到dist/目录下。你可以查看dist/index.js来确认编译结果。
注意:在发布前,务必确保
npm run build能成功执行且没有类型错误。你可以先运行npm run typecheck来只进行类型检查而不输出文件,速度更快。
如何进行本地测试?目前,OpenClaw技能的完整测试通常需要在一个集成了OpenClaw运行时的AI Agent环境中进行(例如,在配置了ClaWHub的Claude Desktop中)。然而,在开发阶段,我们可以进行“单元测试”式的验证:
直接运行编译后的代码:创建一个简单的测试脚本
test.js(放在项目根目录,并加入.gitignore):// test.js - 用于简单验证工具逻辑 async function testTool() { // 动态导入编译后的技能模块 const skillModule = await import('./dist/index.js'); const skill = skillModule.default; const tool = skill.tools[0]; // 获取第一个工具 console.log('测试工具:', tool.name); console.log('---'); // 测试用例1: 默认格式 (locale) console.log('测试1 - 默认格式:'); const result1 = await tool.execute({}); console.log(JSON.stringify(result1, null, 2)); // 测试用例2: ISO格式 console.log('\n测试2 - ISO格式:'); const result2 = await tool.execute({ format: 'iso' }); console.log(JSON.stringify(result2, null, 2)); // 测试用例3: 时间戳格式 console.log('\n测试3 - 时间戳格式:'); const result3 = await tool.execute({ format: 'timestamp' }); console.log(JSON.stringify(result3, null, 2)); } testTool().catch(console.error);然后运行
node test.js,观察输出是否符合预期。这能验证工具的核心逻辑是否正确。检查SKILL.md格式:确保你的
SKILL.md文件语法正确,特别是YAML头部不能有格式错误。可以使用在线的YAML校验器进行检查。模拟AI调用思维:反复阅读你写在
SKILL.md中Tools部分的描述和示例,思考:如果我是AI,看到用户的输入“帮我看看现在几点了”,我能否根据这里的描述正确地调用get-current-time工具?描述是否足够清晰、无歧义?
4. 技能开发进阶:设计复杂工具与最佳实践
掌握了基础技能创建后,我们来探讨更复杂的场景和提升技能质量的最佳实践。一个真实的技能往往不止一个简单工具,它可能需要处理网络请求、访问文件系统、维护状态或进行复杂计算。
4.1 设计多工具与工具间协作
假设我们要升级“时间助手”,增加一个世界时钟和定时提醒功能。我们需要在src/index.ts中定义多个工具,并考虑它们如何协同工作。
// src/index.ts - 进阶版本 import { Skill, SkillTool } from './types'; import axios from 'axios'; // 假设我们需要调用世界时间API // --- 工具1: 获取当前时间 (原有工具,略作升级) --- const getCurrentTimeTool: SkillTool = { name: 'get-current-time', description: '获取服务器当前的日期和时间,或指定时区的时间。', parameters: [ { name: 'format', type: 'string', description: '输出格式。可选值: "locale", "iso", "timestamp"。默认为"locale"。', required: false, enum: ['locale', 'iso', 'timestamp'], }, { name: 'timezone', type: 'string', description: 'IANA时区名称,例如"Asia/Shanghai"或"America/New_York"。如不提供,使用服务器本地时区。', required: false, }, ], execute: async (args) => { const format = (args.format as string) || 'locale'; const timezone = args.timezone as string | undefined; let date: Date; try { if (timezone) { // 构造指定时区的时间字符串,这是一个简单模拟。生产环境应使用库如`luxon`或`date-fns-tz` const nowUtc = new Date().toISOString(); const formatter = new Intl.DateTimeFormat('zh-CN', { timeZone: timezone, dateStyle: 'full', timeStyle: 'long', hour12: false, }); // 注意:这里只是简单演示,Intl.DateTimeFormat用于格式化,要获取Date对象需更复杂处理 // 实际项目建议使用成熟的时区库 return { success: true, data: `时区 ${timezone} 的当前时间约为:${formatter.format(new Date())} (此功能为演示,如需精确计算请使用时区库)`, }; } else { date = new Date(); // ... 原有的格式化逻辑 } } catch (error) { return { success: false, error: `时区可能无效或处理出错: ${error}` }; } }, }; // --- 工具2: 查询世界主要城市时间 --- const getWorldTimeTool: SkillTool = { name: 'get-world-time', description: '查询全球主要城市的当前时间。', parameters: [ { name: 'city', type: 'string', description: '城市英文名,例如"New York", "London", "Tokyo", "Beijing"。', required: true, }, ], execute: async (args) => { const city = (args.city as string).toLowerCase(); // 一个简单的城市到时区的映射表(简化版,生产环境应用完整的时区数据库) const cityToTimezone: Record<string, string> = { 'new york': 'America/New_York', london: 'Europe/London', tokyo: 'Asia/Tokyo', beijing: 'Asia/Shanghai', shanghai: 'Asia/Shanghai', paris: 'Europe/Paris', sydney: 'Australia/Sydney', }; const timezone = cityToTimezone[city]; if (!timezone) { return { success: false, error: `暂不支持城市 "${city}"。目前支持: ${Object.keys(cityToTimezone).join(', ')}`, }; } // 模拟一个API调用(实际应调用世界时间API或使用时区库计算) try { // 这里仅作演示,实际应进行真正的时区转换 const now = new Date().toLocaleString('zh-CN', { timeZone: timezone, hour12: false }); return { success: true, data: `城市 ${city} 的当前时间大约是:${now} (时区: ${timezone})`, rawData: { city, estimatedTime: now, timezone }, }; } catch (error) { return { success: false, error: `获取时间失败: ${error}` }; } }, }; // --- 工具3: 设置一个一次性定时提醒(内存中,重启失效)--- // 注意:这是一个简化示例,生产环境需要持久化存储(如数据库) const reminders = new Map<string, { time: number; message: string }>(); const setReminderTool: SkillTool = { name: 'set-reminder', description: '设置一个在指定延迟后触发的提醒。', parameters: [ { name: 'delaySeconds', type: 'number', description: '延迟的秒数。', required: true, }, { name: 'message', type: 'string', description: '提醒的内容。', required: true, }, ], execute: async (args) => { const delaySeconds = args.delaySeconds as number; const message = args.message as string; if (delaySeconds <= 0) { return { success: false, error: '延迟时间必须大于0秒。' }; } const reminderId = `reminder_${Date.now()}`; const triggerTime = Date.now() + delaySeconds * 1000; reminders.set(reminderId, { time: triggerTime, message }); // 在实际的OpenClaw技能中,通常不会在工具内启动后台定时器。 // 更常见的模式是:工具将提醒任务存入数据库,由另一个后台服务或定时任务来检查并触发。 // 这里为了演示,我们使用setTimeout模拟,但请注意这在无服务器环境中可能不可靠。 setTimeout(() => { console.log(`[提醒] ${new Date().toISOString()}: ${message}`); reminders.delete(reminderId); // 触发后清理 }, delaySeconds * 1000); return { success: true, data: `已设置提醒。将在 ${delaySeconds} 秒后(大约 ${new Date(triggerTime).toLocaleTimeString()})提醒您:“${message}”。提醒ID: ${reminderId}`, rawData: { reminderId, triggerTime, message }, }; }, }; // --- 组装并导出技能 --- const advancedTimeAssistantSkill: Skill = { name: 'time-assistant-advanced', description: '高级时间助手,提供本地/世界时间查询和简单提醒功能。', version: '0.2.0', tools: [getCurrentTimeTool, getWorldTimeTool, setReminderTool], }; export default advancedTimeAssistantSkill;设计要点:
- 工具单一职责:每个工具应只做一件事,并把它做好。
get-world-time和get-current-time虽然都关于时间,但前者是查询特定城市,后者是获取本地或指定时区时间,职责清晰。 - 参数验证与友好错误:在
execute函数开头对参数进行有效性检查(如delaySeconds > 0),并返回明确、友好的错误信息,帮助AI理解哪里出了问题。 - 状态管理:
set-reminder工具演示了简单的内存状态管理(使用Map)。但务必注意:在真实的、可能运行在无服务器函数或短暂容器中的技能里,内存状态是不可靠的。生产级的提醒功能应该将任务持久化到数据库,并通过外部机制(如定时任务队列)触发。 - 异步操作:所有工具的
execute函数都是async的,这意味着你可以在其中安全地进行网络请求(axios)、文件读写等I/O操作。
4.2 技能配置与外部依赖管理
我们的技能可能需要访问外部API(如获取实时天气、汇率)或使用第三方Node.js库。这时,就需要管理依赖和配置。
添加依赖:使用npm安装所需库。
npm install axios npm install --save-dev @types/node # 如果用到Node.js特定类型环境变量与配置:技能不应将API密钥等敏感信息硬编码在代码中。应使用环境变量。
- 在代码中通过
process.env.API_KEY读取。 - 在
SKILL.md的文档中说明需要配置哪些环境变量。 - 创建一个
.env.example文件(并加入.gitignore)列出需要的变量,供其他开发者参考。 - 在
package.json的scripts中,可以使用dotenv等工具在开发时加载环境变量。
- 在代码中通过
更新
SKILL.md文档:当你添加了新工具或参数后,必须同步更新SKILL.md文件。AI完全依赖这个文件来了解你的技能。确保文档中的工具名称、描述、参数列表与代码中的定义完全一致。
4.3 调试与开发工作流优化
开发过程中,频繁地npm run build然后测试可能会很慢。利用脚手架提供的脚本可以提升效率:
- 监听模式开发:运行
npm run dev。这通常会启动tsc --watch,监视src/目录下的文件变化,一旦保存就自动重新编译。结合上文的test.js脚本,你可以实现一个快速的“修改-编译-测试”循环。 - 利用TypeScript类型检查:在编码时,你的IDE(如VS Code)会实时进行类型检查。此外,在提交代码前运行
npm run typecheck,可以确保没有类型错误,这比完整的build更快。 - 结构化日志:在工具的
execute函数中添加详细的日志输出(使用console.log或像winston这样的日志库),这对于调试复杂逻辑和了解AI是如何调用你的工具至关重要。记得在生产环境中调整日志级别。
5. 发布技能到ClaWHub与后续维护
当你的技能开发完成并通过测试后,就可以将其分享给其他人使用了。发布到ClaWHub是标准流程。
5.1 发布前的最终检查清单
在运行发布命令前,请逐一核对以下事项:
SKILL.md文件:- [ ] YAML头部信息完整且准确(
name,version,author,description,tags,triggers)。 - [ ]
Tools部分详细描述了每一个工具,包括所有参数、示例和可能的错误。 - [ ] 文档语言清晰、无歧义,站在AI模型的角度思考它是否能理解。
- [ ] YAML头部信息完整且准确(
- 代码质量:
- [ ]
npm run build成功执行,无编译错误。 - [ ]
npm run typecheck通过,无类型错误。 - [ ] 代码中已移除硬编码的敏感信息(如API密钥),改为使用环境变量。
- [ ] 进行了基本的错误处理(
try...catch)和参数验证。
- [ ]
package.json:- [ ]
name字段是否合适?(通常与技能名一致或相关) - [ ]
version字段是否已根据语义化版本规范更新?(例如,修复bug从0.1.0到0.1.1,新增功能从0.1.0到0.2.0) - [ ]
main字段是否指向正确的入口文件(通常是"dist/index.js")? - [ ] 所有依赖项是否都已正确列出?
- [ ]
- 项目根目录:
- [ ] 无关文件(如
node_modules/,dist/(如果不在.gitignore中),.env,test.js等)是否已添加到.gitignore? - [ ]
README.md是否提供了对开发者有用的信息?
- [ ] 无关文件(如
5.2 发布流程详解
发布过程通常很简单,但理解每一步背后的意义很重要。
# 1. 确保构建产物是最新的 npm run build # 2. 执行发布命令 npx clawhub@latest publish当你运行npx clawhub@latest publish时,clawhub这个CLI工具大致会做以下几件事:
- 读取你的项目:它会查找当前目录下的
SKILL.md和package.json等文件。 - 验证技能:检查技能格式是否基本合规。
- 打包:将必要的文件(主要是
SKILL.md和dist/目录下的编译产物,可能还包括package.json中定义的files字段指定的文件)打包。 - 发布到注册中心:将打包好的技能上传到ClaWHub的注册服务器。你可能需要登录或进行身份验证(具体取决于ClaWHub的配置)。
- 反馈结果:CLI会返回一个成功信息,并可能给出技能的安装命令。
发布成功后,其他人就可以通过以下命令安装你的技能了:
npx clawhub@latest install time-assistant重要提示:
clawhub工具和ClaWHub本身可能处于活跃开发阶段。具体的发布流程、身份验证方式(如是否需要GitHub Token)、以及发布的目标仓库(公共ClaWHub或私有实例)可能会发生变化。在发布前,请务必查阅OpenClaw和ClaWHub项目的最新官方文档。
5.3 版本管理与技能更新
软件迭代是常态,你的技能也需要更新。
语义化版本:遵循
主版本号.次版本号.修订号的规则。在package.json和SKILL.md的YAML头部更新version字段。- 修订号(0.1.0 -> 0.1.1):向后兼容的问题修复。
- 次版本号(0.1.0 -> 0.2.0):向后兼容的新功能(例如新增一个工具)。
- 主版本号(0.1.0 -> 1.0.0):不兼容的API变更(例如移除或重命名一个工具,或改变现有工具的参数结构)。不兼容的变更会导致已安装的用户技能中断,需谨慎处理。
更新并重新发布:
- 修改代码和文档。
- 更新版本号。
- 运行
npm run build。 - 再次运行
npx clawhub@latest publish。
用户如何更新:通常,用户可以通过重新运行
npx clawhub@latest install time-assistant来获取最新版本。有些CLI工具可能提供update或upgrade命令,请参考clawhub的文档。
5.4 常见问题与排查技巧实录
在开发和发布过程中,你可能会遇到一些典型问题。以下是我在实际操作中总结的一些排查思路:
问题1:技能安装成功,但AI无法识别或调用。
- 可能原因A:
SKILL.md格式错误。- 排查:检查YAML头部,确保缩进是空格而非Tab,冒号后有空格。可以使用在线YAML解析器验证。
- 排查:确保
triggers列表中的短语是自然语言,且覆盖了用户可能的问法。
- 可能原因B:工具描述不清晰。
- 排查:仔细阅读你为工具写的
description和参数描述。假设你是一个完全不懂代码的AI,这段描述能否让你明白在什么情况下、如何使用这个工具?补充更多示例往往很有效。
- 排查:仔细阅读你为工具写的
- 可能原因C:技能未正确加载到AI Agent环境中。
- 排查:确认用户是在正确的OpenClaw/Claude Desktop环境中运行的安装命令。不同的AI Agent平台集成MCP的方式可能不同。
问题2:工具被调用,但返回错误或意外结果。
- 可能原因A:参数类型或格式不匹配。
- 排查:在工具的
execute函数开头添加console.log(‘Received args:’, args);,查看AI实际传入的参数是什么。有时AI传递的字符串可能包含多余空格或格式与预期不符。 - 解决:在代码中增加参数清洗和类型转换的逻辑(例如,使用
String(args.input).trim())。
- 排查:在工具的
- 可能原因B:异步操作失败(如网络请求超时)。
- 排查:确保所有异步操作(
axios.get,fetch, 数据库查询)都被await等待,并且有完整的try...catch包裹。 - 解决:返回明确的错误信息,例如
{ success: false, error: “无法连接到天气服务,请稍后重试。” },而不是让异常抛出。
- 排查:确保所有异步操作(
问题3:本地构建成功,但发布到ClaWHub失败。
- 可能原因A:技能名称冲突。
- 排查:ClaWHub上可能已存在同名的技能。尝试修改
SKILL.md和package.json中的name字段。
- 排查:ClaWHub上可能已存在同名的技能。尝试修改
- 可能原因B:文件缺失或过大。
- 排查:确保
dist/index.js等关键构建产物存在。检查是否有不必要的巨型文件(如图片、视频)被意外包含在内。合理配置.gitignore和.npmignore(或package.json中的files字段)。
- 排查:确保
- 可能原因C:网络或认证问题。
- 排查:检查网络连接。确认你是否拥有发布权限(可能需要特定的Token或账户)。
问题4:TypeScript编译通过,但运行时出现“Cannot find module”错误。
- 可能原因:依赖项未正确打包或安装。
- 排查:确保
package.json中的dependencies(运行时依赖)和devDependencies(开发依赖)划分正确。只有dependencies中的包会被期望在运行技能的环境中可用。 - 解决:如果技能是作为库被其他项目安装,某些依赖可能需要打包进最终的bundle。这可能需要更复杂的构建工具(如
webpack,esbuild),而不仅仅是tsc。对于简单技能,尽量使用Node.js标准库或减少第三方依赖。
- 排查:确保
开发AI技能是一个与AI模型“协作”的过程。你的代码是能力的实现,而SKILL.md是与AI沟通的“语言”。写出清晰、准确、全面的文档,与编写健壮、可靠的代码同等重要。多从AI的视角去审视你的技能设计,思考它如何理解用户的意图并选择正确的工具,这将帮助你打造出更强大、更易用的OpenClaw技能。