☰
OpenClaw技能开发:用plugin.json与index.ts搭一个可复用的自定义技能骨架
2026/9/26 11:27:07 网站建设 项目流程

1. 为什么我要把重复操作做成 OpenClaw 自定义技能

如果你已经在用 OpenClaw 处理日常任务,大概率会遇到一个瓶颈:每次都要用自然语言把同一套操作重新描述一遍。比如“帮我统计一下这个目录下各类文件的数量,生成一份 Markdown 报表”,第一次说还挺新鲜,说到第五次就开始烦了。OpenClaw 自定义技能就是解决这个问题的——它让你把一套固定的输入、处理、输出逻辑封装成一个可复用的模块,之后只需要一句话触发,甚至可以让 Agent 在编排流程里自动调用。

OpenClaw 技能开发的核心其实就两个文件:plugin.json负责声明“这个技能叫什么、能执行什么动作、需要什么参数和权限”,index.ts负责实现“具体怎么干”。前者是身份证加说明书,后者是手脚加操作手册。把这两个文件写对,再放到正确的目录下,OpenClaw 内核就能加载并调用它。

这篇文章面向的是需要把重复操作沉淀为可复用能力的开发者。我会给出plugin.json与index.ts的可复制骨架,说明 TaoToken 统一 Key/API 通道在技能里的配置位置,然后完整走一遍本地加载技能、验证触发与返回的流程。目标是一次跑通从技能注册到调用链路,让你拿到骨架就能改出自己的技能。

2. TaoToken 前置:统一 Key 与 API 通道的配置位置

在写技能逻辑之前,先把模型调用的通道配好。OpenClaw 技能本身不绑定某个模型供应商,但如果你希望技能内部调用大模型能力(比如让技能对统计结果做一段自然语言总结),就需要一个统一的 API 入口。我用的是 TaoToken 的统一 Key 通道,好处是技能代码里只认一个 base URL 和一个 Key,换模型不用改业务逻辑。

TaoToken 的 API 地址是https://taotoken.net/api,官网是https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。你需要在控制台创建一个 API Key,然后把它放到环境变量里,而不是硬编码在index.ts中。技能代码通过process.env.TAOTOKEN_API_KEY读取,这样本地调试和部署到其他环境时只需要改环境变量。

具体操作路径:先打开控制台创建 Key,地址是https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite。创建完成后,在 API Keys 页面复制 Key,地址是https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite。如果你对模型对话能力还不熟悉,可以先在模型对话页面试一下通道是否通,地址是https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite。

配置环境变量的方式,在 macOS/Linux 下可以写进~/.zshrc或~/.bashrc:

export TAOTOKEN_API_KEY="你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"

Windows PowerShell 下用:

$env:TAOTOKEN_API_KEY="你的Key" $env:TAOTOKEN_BASE_URL="https://taotoken.net/api"

这样技能代码里就可以统一读取这两个变量。注意 API 地址不要加 UTM 参数,保持https://taotoken.net/api干净即可。

3. 可复制骨架:plugin.json 与 index.ts 怎么写

3.1 目录结构先定好

一个标准的 OpenClaw 自定义技能文件夹,我建议这样组织:

file-report-skill/ ├── plugin.json ├── index.ts ├── package.json └── tsconfig.json

plugin.json是必须的,index.ts是核心逻辑,package.json用来声明依赖,tsconfig.json保证 TypeScript 编译配置正确。如果你只用 Node.js 原生模块,package.json里可以不装第三方依赖,但保留它方便后续扩展。

3.2 plugin.json 骨架

这个文件告诉 OpenClaw 内核:技能叫什么、能执行哪个 action、需要哪些参数、申请什么权限。下面是一个可直接复制的骨架,我以“文件统计报表”为例:

{ "name": "file-report-skill", "version": "1.0.0", "description": "统计目录文件类型并生成 Markdown 报表", "author": "your-name", "skills": [ { "action": "generate-file-report", "description": "统计指定目录下各类文件数量并输出 Markdown 报表", "parameters": [ { "name": "dirPath", "type": "string", "required": true, "description": "要统计的目录绝对路径" }, { "name": "outputPath", "type": "string", "required": false, "default": "./file-report.md", "description": "报表保存路径" } ], "permissions": ["file.read", "file.write"] } ] }

几个关键点:action是内核调用时传入的动作名,必须和index.ts里判断的字符串一致;parameters里required为 true 的参数如果缺失,内核会在调用前拦截;permissions遵循最小权限原则,只读就不写file.write。

3.3 index.ts 骨架

index.ts必须导出一个默认的异步函数,接收action和params,返回标准化结果。下面这个骨架包含了参数解析、核心处理、结果输出和异常处理:

import fs from 'fs'; import path from 'path'; interface SkillResult { success: boolean; message: string; data?: any; } function countFilesByType(dirPath: string): Record<string, number> { const stats: Record<string, number> = {}; const entries = fs.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { if (entry.isFile()) { const ext = path.extname(entry.name).toLowerCase() || 'no-ext'; stats[ext] = (stats[ext] || 0) + 1; } } return stats; } function generateMarkdownReport(stats: Record<string, number>, dirPath: string): string { const lines: string[] = []; lines.push(`# 文件统计报表`); lines.push(''); lines.push(`统计目录:\`${dirPath}\``); lines.push(''); lines.push('| 文件类型 | 数量 |'); lines.push('| --- | --- |'); for (const [ext, count] of Object.entries(stats)) { lines.push(`| ${ext} | ${count} |`); } lines.push(''); lines.push(`总计:${Object.values(stats).reduce((a, b) => a + b, 0)} 个文件`); return lines.join('\n'); } export default async function run(action: string, params: any): Promise<SkillResult> { try { if (action !== 'generate-file-report') { return { success: false, message: `不支持的动作:${action}` }; } const { dirPath, outputPath = './file-report.md' } = params; if (!dirPath || typeof dirPath !== 'string') { return { success: false, message: '参数 dirPath 必须是非空字符串' }; } if (!fs.existsSync(dirPath)) { return { success: false, message: `目录不存在:${dirPath}` }; } const fileStats = countFilesByType(dirPath); const markdown = generateMarkdownReport(fileStats, dirPath); fs.writeFileSync(outputPath, markdown, 'utf8'); return { success: true, message: `报表已生成至 ${outputPath}`, data: fileStats }; } catch (error: any) { return { success: false, message: error.message || '未知错误' }; } }

这个骨架里,run函数先校验 action,再校验参数,然后执行统计和报表生成,最后返回统一结构。任何异常都被try...catch捕获,不会让内核崩溃。

3.4 如果技能内部要调用模型

假设你想在报表生成后,让模型对统计结果做一段自然语言总结,可以在index.ts里加一个调用函数。这里用 TaoToken 的统一通道:

async function summarizeWithModel(stats: Record<string, number>): Promise<string> { const apiKey = process.env.TAOTOKEN_API_KEY; const baseUrl = process.env.TAOTOKEN_BASE_URL || 'https://taotoken.net/api'; if (!apiKey) { return '未配置 TAOTOKEN_API_KEY,跳过模型总结'; } const prompt = `请用一段话总结以下文件统计结果:${JSON.stringify(stats)}`; const response = await fetch(`${baseUrl}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: prompt }] }) }); const data = await response.json(); return data.choices?.[0]?.message?.content || '模型未返回内容'; }

注意 base URL 用https://taotoken.net/api,不要加 UTM 参数。模型名称根据你实际使用的通道支持的模型来填。如果你需要长期做编码类技能,可以考虑 Coding Plan,地址是https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite。

4. 本地加载技能并验证触发与返回

4.1 安装依赖与编译

进入技能目录,先初始化package.json和tsconfig.json:

cd file-report-skill npm init -y npm install typescript ts-node @types/node --save-dev npx tsc --init

tsconfig.json里确保target和module设置合理,比如:

{ "compilerOptions": { "target": "ES2020", "module": "CommonJS", "outDir": "./dist", "rootDir": "./", "strict": true, "esModuleInterop": true, "skipLibCheck": true }, "include": ["index.ts"] }

然后编译:

npx tsc

编译成功后会在dist/下生成index.js。OpenClaw 加载技能时,如果配置指向index.ts,需要确保运行环境支持 TypeScript;更稳妥的方式是编译后指向dist/index.js。

4.2 把技能放到 OpenClaw 技能目录

OpenClaw 加载自定义技能通常有两种方式:一种是放到全局技能目录,另一种是在项目配置里指定技能路径。我建议先在项目级配置里指定,方便调试。假设你的 OpenClaw 项目根目录下有一个skills/文件夹,把整个file-report-skill复制进去:

cp -r file-report-skill /path/to/your-openclaw-project/skills/

然后在 OpenClaw 的配置文件里注册这个技能。不同版本的配置字段可能略有差异,核心是告诉内核技能目录和入口文件。一个常见的配置片段如下:

{ "skills": [ { "name": "file-report-skill", "path": "./skills/file-report-skill", "entry": "dist/index.js" } ] }

保存后重启 OpenClaw 服务,让内核重新扫描技能目录。

4.3 验证触发

重启后,在 OpenClaw 的对话界面里输入触发语句,比如:

请调用 file-report-skill 的 generate-file-report 动作,统计 /Users/me/projects 目录,输出到 ./report.md

如果内核正确加载了技能,它会解析出 action 和参数,然后调用index.ts里的run函数。你可以在技能代码里加一行console.log来确认调用是否发生:

console.log('[file-report-skill] 收到调用', action, params);

4.4 验证返回

调用成功后,检查./report.md是否生成,内容应该是一个 Markdown 表格,列出各类文件的数量。同时,OpenClaw 对话界面应该返回类似:

{ "success": true, "message": "报表已生成至 ./report.md", "data": { ".ts": 12, ".json": 3, ".md": 5 } }

如果返回success: false,先看message里的错误信息,再对照下一节的排查清单。

5. 本篇常见错排查

5.1 技能加载后不触发

最常见的原因是plugin.json里的action和index.ts里判断的字符串不一致。比如plugin.json写的是generate-file-report,但index.ts里判断的是generateFileReport,内核传过来的 action 匹配不上,直接返回“不支持的动作”。排查方法:在run函数开头打印action,对比两个文件。

另一个原因是入口文件路径不对。如果配置里写entry: "index.ts"但运行环境不支持直接执行 TypeScript,就会加载失败。建议编译后指向dist/index.js,或者用ts-node注册。

5.2 参数传递为空

OpenClaw 内核在调用技能前会根据plugin.json的parameters做校验。如果required: true的参数没传,内核可能直接拦截,也可能传空值进来。你需要在index.ts里做二次校验,比如if (!dirPath)就返回明确错误。不要假设内核一定帮你校验完整。

5.3 权限不足导致文件读写失败

plugin.json里声明了file.read和file.write,但实际运行环境的文件系统权限可能不够。比如技能试图写入/root/下的文件,但进程没有写权限,就会抛异常。排查方法:先用一个你有权限的目录测试,比如./output/,确认逻辑通了再换目标路径。

5.4 模型调用返回 401 或 404

如果技能内部调用了 TaoToken 通道,返回 401 通常是 Key 没配或配错。检查process.env.TAOTOKEN_API_KEY是否在当前 shell 会话里生效,可以用echo $TAOTOKEN_API_KEY确认。返回 404 通常是 base URL 写错了,确保是https://taotoken.net/api,不要多加/v1之外的路径,也不要在 API 地址后面加 UTM 参数。

5.5 编译报错找不到模块

index.ts里import fs from 'fs'如果报错,检查tsconfig.json里是否设置了"esModuleInterop": true。如果用了第三方库比如axios,确保npm install已经执行,并且package.json的dependencies里有记录。OpenClaw 加载技能时,如果依赖没装,运行时会报Cannot find module。

5.6 技能返回了但界面没显示

有些 OpenClaw 版本要求技能返回结构里必须包含success字段,否则界面可能不渲染结果。确保你的run函数在所有分支都返回{ success: boolean, message: string }。如果返回了data,确认它是可序列化的对象,不要返回undefined或循环引用。

6. 把技能接入 TaoToken 通道的完整动作

如果你希望技能不仅能做本地文件处理,还能调用模型能力,接入 TaoToken 统一通道的完整动作是这样的:先在控制台创建 Key,地址是https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite;然后在 API Keys 页面复制 Key,地址是https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite;接着把 Key 写入环境变量TAOTOKEN_API_KEY,base URL 设为https://taotoken.net/api;最后在index.ts里用fetch或 SDK 调用/v1/chat/completions。

如果你在接入过程中遇到报错,可以先对照接入文档排查,地址是https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite。如果你更习惯用 Claude Code 这类编码工具来辅助开发技能,可以参考 ClaudeCodeAnthropic 的配置说明,地址是https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude-code-anthropic&utm_campaign=rewrite。长期做编码类技能或 Agent 编排的话,Coding Plan 会更省心,地址是https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite。

技能骨架跑通之后,你可以把countFilesByType换成任何自己的业务逻辑,比如对接内部 CRM、生成周报、批量重命名文件。核心模式不变:plugin.json声明能力,index.ts实现逻辑,TaoToken 提供统一的模型通道。先把一个最小技能跑通,再逐步加参数、加权限、加模型调用,这样每一步都有可验证的结果。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询