1. 从“能聊天”到“能干活”:OpenClaw 插件开发到底解决什么问题
很多人第一次用 OpenClaw 的时候,都会经历一个心理落差:对话很流畅,但真让它去统计一个目录、生成一份报表,它就开始“编”——要么给你一段看起来像那么回事的伪代码,要么把文件数量猜个大概。原因不复杂,模型负责“思考”,但“执行”这件事得靠 Skill 来兜底。OpenClaw 插件开发的核心,就是把你脑子里的那套固定流程,写成一段可被内核稳定调度的代码,让 AI 从“嘴上说说”变成“手上真干”。
这篇要做的,是一个“文件统计与报表生成”Skill。它能干的事很具体:你给它一个目录路径,它遍历该目录下的文件,按扩展名归类计数,最后输出一份 Markdown 报表,包含统计目录、统计时间、各类型数量以及总文件数。适合谁?适合已经装好 OpenClaw、会一点 TypeScript 或 JavaScript、想让 AI 接管重复性文件整理工作的开发者。哪怕你之前没写过插件,只要跟着把三文件结构搭起来,一小时内跑通第一个 Skill 并不夸张。
我试过把这个 Skill 接到日常的项目目录巡检里,每周一早上让它扫一遍src和docs,报表直接落到工作区,比手动ls | wc -l省事得多。下面从目录结构、入口函数、参数定义,到把模型请求 endpoint 改到 TaoToken,完整走一遍。
2. TaoToken 前置准备:为什么 Skill 的模型请求要单独配 endpoint
在写 Skill 之前,先把“模型请求走哪里”这件事定下来。OpenClaw 的 Skill 本身是执行单元,但很多场景下 Skill 内部或 Agent 层仍需要调用模型来做意图解析、结果润色或报表摘要。默认情况下,这些请求会走 OpenClaw 内置的模型通道。如果你希望统一管理模型调用、把请求收敛到一个可控的入口,就需要把 endpoint 指向 TaoToken。
TaoToken 在这里扮演的角色很明确:它是一个模型请求的统一接入点。你不需要在 Skill 代码里硬编码某个厂商的地址,而是把 Base URL 配成https://taotoken.net/api,再用 API Key 做鉴权,Model ID 指定你要用的模型。这样 Skill 里发起的模型请求、Agent 的对话请求,都能走同一条链路,排查问题时也只需要看一个地方。
前置准备分三步。第一步,拿到 API Key。访问https://taotoken.net/api-keys,登录后创建一个 Key,复制出来先存到安全的地方。第二步,确认你要用的 Model ID。不同模型在报表摘要、意图识别上的表现不一样,建议先用一个你熟悉的模型跑通链路,再按需替换。第三步,把 Base URL、Key、Model ID 这三件套记下来,后面配置里会反复用到。
这里要强调一点:Skill 的“执行逻辑”和“模型请求”是两回事。文件统计、报表生成这些纯代码逻辑,不需要模型参与,走本地 Node.js 就行。但如果你想让 Skill 在生成报表后,再让模型写一段“本周文件变化摘要”,那这段摘要请求就应该走 TaoToken。把这两层分清楚,配置的时候就不会乱。
3. 可复制配置:Skill 目录结构、plugin.json 与模型 endpoint 三件套
现在进入动手环节。先建目录,再写元数据,最后把模型请求的配置片段准备好。整个 Skill 的目录结构如下:
file-stat-skill/ ├── plugin.json # Skill 元信息与参数定义 ├── index.ts # 核心执行逻辑 ├── package.json # 依赖配置 └── tsconfig.json # TypeScript 编译配置plugin.json是 OpenClaw 识别 Skill 的“身份证”,它告诉内核:这个 Skill 叫什么、有哪些 action、每个 action 需要什么参数、申请什么权限。下面这份可以直接复制,注意action名和参数名要和后面index.ts里的处理逻辑保持一致。
{ "name": "file-stat-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": "要统计的目录绝对路径,如 D:/Documents 或 /home/user/docs" }, { "name": "outputPath", "type": "string", "required": false, "default": "./file-report.md", "description": "报表保存路径(含文件名),默认生成在当前目录" } ], "permissions": ["file.read", "file.write"] } ] }接下来是模型请求的 endpoint 配置。如果你希望 Skill 在生成报表后调用模型写摘要,或者 Agent 层需要走 TaoToken,就把下面这段配置放到 OpenClaw 的模型配置里。Base URL 用https://taotoken.net/api,Key 换成你在 API Keys 页面创建的那串,Model ID 按你实际使用的模型填写。
{ "model": { "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的TaoToken密钥", "modelId": "你的模型ID", "timeout": 60000 } }如果你用的是 TOML 风格的配置文件,等价写法如下:
[model] baseUrl = "https://taotoken.net/api" apiKey = "sk-你的TaoToken密钥" modelId = "你的模型ID" timeout = 60000三件套里,Base URL 决定请求发往哪里,API Key 决定能不能通过鉴权,Model ID 决定用哪个模型。这三个缺一不可,而且要和 Skill 里实际发请求的代码对应上。很多“请求发不出去”的问题,最后查下来都是这三者里有一个写错了,或者 Key 复制时带了空格。
index.ts的核心逻辑分三块:统计函数、报表生成函数、导出的默认执行函数。统计函数用fs.readdirSync配合withFileTypes区分文件和目录,只统计文件,按扩展名归类。报表生成函数把统计结果按数量降序排列,拼成 Markdown 表格。默认执行函数负责参数校验、调用前两个函数、写文件、返回标准化结果。
import fs from 'fs'; import path from 'path'; function countFilesByType(dirPath: string): Record<string, number> { const stats: Record<string, number> = {}; if (!fs.existsSync(dirPath)) { throw new Error(`目录不存在:${dirPath}`); } const files = fs.readdirSync(dirPath, { withFileTypes: true }); for (const file of files) { if (file.isDirectory()) continue; const ext = path.extname(file.name).toLowerCase() || '无扩展名'; stats[ext] = (stats[ext] || 0) + 1; } return stats; } function generateMarkdownReport(stats: Record<string, number>, dirPath: string): string { const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }); let md = `# 文件统计报表\n\n`; md += `**统计目录**:\`${dirPath}\`\n`; md += `**统计时间**:${now}\n\n`; md += `| 文件类型 | 数量 |\n|----------|------|\n`; const sorted = Object.entries(stats).sort((a, b) => b[1] - a[1]); for (const [ext, count] of sorted) { md += `| \`${ext}\` | ${count} |\n`; } const total = Object.values(stats).reduce((sum, v) => sum + v, 0); md += `\n**总文件数**:${total}\n`; return md; } export default async function run(action: string, params: any) { try { if (action !== 'generate-file-report') { return { success: false, message: `不支持的动作:${action}`, data: null }; } const { dirPath, outputPath = './file-report.md' } = params; const fileStats = countFilesByType(dirPath); const markdown = generateMarkdownReport(fileStats, dirPath); const fullOutputPath = path.isAbsolute(outputPath) ? outputPath : path.join(process.cwd(), outputPath); const outputDir = path.dirname(fullOutputPath); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } fs.writeFileSync(fullOutputPath, markdown, 'utf8'); return { success: true, message: '文件统计报表已生成', data: { stats: fileStats, reportPath: fullOutputPath, totalFiles: Object.values(fileStats).reduce((sum, v) => sum + v, 0) } }; } catch (error) { return { success: false, message: `执行失败:${(error as Error).message}`, data: null }; } }编译用npx tsc,生成的dist/index.js就是 OpenClaw 实际加载的文件。把plugin.json和dist/index.js复制到~/.openclaw/workspace/skills/file-stat-skill/,再执行openclaw skill register file-stat-skill完成注册。
4. 验证请求:本地调用与真实运行输出
配置写完,必须验证。验证分两层:先验证 Skill 本身能跑通,再验证模型请求走 TaoToken 能通。
第一层,用 OpenClaw 命令行直接调用 Skill。假设你要统计当前项目目录:
openclaw run --skill file-stat-skill --action generate-file-report --params '{"dirPath": "./"}'预期返回一个 JSON,success为true,data.stats里是各扩展名的数量,data.reportPath是报表落盘路径。如果返回success: false,先看message里的错误信息,通常是目录不存在或权限不足。
第二层,验证模型请求。如果你在 Skill 里加了模型摘要逻辑,或者想单独测 TaoToken 的连通性,可以用 curl 发一个最小请求:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的TaoToken密钥" \ -H "Content-Type: application/json" \ -d '{ "model": "你的模型ID", "messages": [{"role": "user", "content": "用一句话总结:文件统计完成"}] }'如果返回里有choices字段且内容正常,说明 Base URL、Key、Model ID 三件套配置正确。如果返回 401,检查 Key 是否复制完整;如果返回local proxy failed,检查 Base URL 是否写成了https://taotoken.net/api而不是别的路径。
真实运行输出长这样。我在一个含 11 个文件的测试目录下执行统计,返回:
{ "success": true, "message": "文件统计报表已生成", "data": { "stats": { ".js": 5, ".ts": 3, ".json": 2, "无扩展名": 1 }, "reportPath": "/Users/yourname/file-report.md", "totalFiles": 11 } }打开file-report.md,内容是一张 Markdown 表格,按数量降序排列,.js5 个排第一,.ts3 个第二,.json2 个第三,无扩展名 1 个垫底,最后一行是总文件数 11。这个输出就是验证动作的终点:Skill 执行成功、报表落盘、数据可读。
5. 本篇常见错排查:401、local proxy failed、reading choices 与 OAuth
排障这块,我把实际踩过的坑按报错现象列出来,对照着查能省不少时间。
401 Unauthorized。最常见的原因是 API Key 写错或过期。先确认 Key 是从https://taotoken.net/api-keys页面新创建的,复制时没有多余空格。如果 Key 没问题,检查请求头格式是不是Authorization: Bearer sk-xxx,Bearer 和 Key 之间有一个空格,少了这个空格也会 401。
local proxy failed。这个报错通常出现在 Base URL 配置不对的时候。确认你填的是https://taotoken.net/api,不要多加/v1或别的后缀,除非文档明确要求。另外检查本地网络是否能正常访问该地址,有些公司网络会拦截外部请求,这种情况需要换网络环境再试。
reading choices 报错。这个一般出现在模型返回体解析阶段。如果你在 Skill 里手动解析模型响应,要确认返回结构里有choices数组,且choices[0].message.content存在。如果模型返回的是流式响应,而你的代码按非流式解析,就会读不到choices。解决办法是统一请求参数里的stream字段,要么都开,要么都关。
OAuth 相关报错。如果你用的是需要 OAuth 的模型通道,但配置里只填了 API Key,就会报鉴权方式不匹配。检查你的 Model ID 对应的鉴权方式,该用 Key 的用 Key,该走 OAuth 的走 OAuth,不要混用。TaoToken 的 API Key 方式适用于大多数模型,如果某个模型要求 OAuth,按对应文档单独配置。
还有一个容易忽略的点:Skill 注册后没生效。执行openclaw skill list看不到你的 Skill,通常是文件没放对目录。工作区 Skill 要放在~/.openclaw/workspace/skills/下,托管 Skill 放在~/.openclaw/skills/下,放错位置内核扫不到。Windows 用户注意路径是C:\Users\你的用户名\.openclaw\workspace\skills\。
6. 语义一致 CTA:把 Skill 接入 TaoToken 后的下一步
Skill 跑通之后,模型请求的 endpoint 已经指向 TaoToken,接下来就是把它用起来。如果你主要做排障和接入,先去 API Keys 页面把 Key 管理好,再对照接入文档把配置固化下来。如果你还在选模型、想先验证不同模型在报表摘要上的效果,可以直接在模型对话里试。如果你打算长期做编码类 Agent、把 Skill 组合成工作流,Coding Plan 会更合适。
三个入口按需取用:
- 排障与接入:API Keys 管理
https://taotoken.net/api-keys,接入文档https://taotoken.net/doc - 验证模型效果:模型对话
https://taotoken.net/chat - 长期编码与 Agent 工作流:Coding Plan
https://taotoken.net/coding-plan
最后留一个实用技巧。Skill 开发过程中,建议把console.log打在关键分支上,用tail -f ~/.openclaw/logs/gateway.log | grep "\[file-stat\]"实时看输出。上线前把这些日志删掉或加条件编译,避免日志刷屏。报表输出路径尽量用绝对路径,相对路径在不同工作目录下执行时容易落到意想不到的地方。