尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从使用者到创造者:手把手教你开发AI编程助手自定义技能

从使用者到创造者:手把手教你开发AI编程助手自定义技能 1. 从“使用”到“创造”为什么你需要掌握自定义技能开发如果你已经用了一段时间的Claude Code或者类似的AI编程助手你可能已经习惯了在编辑器里输入“/”来调用各种现成的技能Skills。比如快速生成一段代码注释、格式化一个JSON文件或者帮你重构某个函数。这些预置的技能确实很方便像是给编辑器装上了一套瑞士军刀。但不知道你有没有遇到过这样的时刻你有一个非常具体、重复性的任务现有的技能要么没有要么用起来不够顺手需要你反复调整指令。比如你团队内部有一套独特的代码提交信息规范或者你需要定期将项目中的特定日志格式转换成报告。这时候一个念头就会冒出来要是能有一个完全按我心意工作的“专属技能”就好了。这就是自定义技能Custom Skill的价值所在。它意味着你将从一个工具的“使用者”转变为一个“创造者”。你不再受限于别人提供的功能列表而是可以亲手打造一个能精准解决你个人或团队特定痛点的自动化工具。这个过程本质上是在将你的工作流和专业知识“固化”成一段可重复执行的智能指令。对于开发者、技术写作者、数据分析师乃至任何需要与结构化文本打交道的专业人士来说这都是一项极具杠杆效应的能力。网络上关于“skills推荐”、“skills下载”的讨论很多但“skills开发”相关的深度内容却相对稀缺。大家更关注“用什么”而不是“怎么造”。今天我们就跳过那些琳琅满目的技能商店直接深入核心手把手带你编写你的第一个自定义技能。我们将从一个最实用、最常见的场景出发创建一个能自动为代码文件生成标准化Markdown格式文档的技能。这个技能会读取你的代码提取关键信息如函数名、参数、简要描述并输出整洁的API文档草稿。通过这个完整的例子你将彻底理解一个Skill从构思、编写、调试到集成的全流程。注意本文假设你已在VSCode中安装并配置好了Claude Code或类似插件具备基本的Markdown和JavaScript或你选择技能脚本语言知识。我们的目标是理解原理和流程因此会尽量使用简明清晰的代码示例。2. 技能的本质拆解一个Skill的构成要素在动手写代码之前我们必须先搞清楚一个技能到底是什么它由哪些部分组成。如果把一个Skill比作一个智能小工具那么它至少包含以下几个核心要素2.1 触发器Trigger技能何时被唤醒触发器定义了技能启动的时机和方式。最常见的是命令Command触发也就是在编辑器里输入特定的命令例如/doc。此外还可以是快捷键Keybinding触发、右键菜单Context Menu触发或者基于文件内容、语言类型的自动触发。对于我们的第一个技能我们将采用最直观的命令触发方式。2.2 输入Input技能需要什么信息当技能被触发后它需要从用户或当前编辑环境中获取信息。这可能包括选中的文本用户高亮选择的代码块。当前文件编辑器正在活动的整个文件内容。光标位置光标所在的行、列信息。用户输入通过一个弹出框Input Box让用户临时输入一些参数比如文档的标题、作者等。工作区信息当前打开的项目路径、文件列表等。我们的文档生成技能主要输入将是用户选中的代码片段。如果用户没有选择任何文本我们可以设计一个备选逻辑比如处理整个当前文件。2.3 处理逻辑Core Logic技能的核心大脑这是技能的“魔法”发生的地方是一段实实在在的代码通常是JavaScript/TypeScript、Python等。它负责解析输入理解选中的代码是什么是函数类还是一段配置。这里可能会用到简单的正则表达式或者更复杂的语法分析库如对于JavaScript可以使用Babel解析器。提取信息从解析后的结构中抽取出我们关心的元素比如函数名、参数列表、返回值类型、函数内的注释等。应用规则按照我们预设的文档模板将提取的信息填充进去。例如我们规定函数文档必须包含“描述”、“参数表”、“返回值”和“示例”四个部分。生成输出将填充好的模板组合成最终的Markdown字符串。2.4 输出Output技能如何呈现结果处理完成后技能需要将结果交付给用户。常见的方式有替换选中文本用生成的文档直接替换掉原先选中的代码通常不这么做因为会覆盖源码。插入到光标位置在光标处插入生成的文档。这是我们最常用的方式可以将文档插入到代码的上方或下方。在新编辑器中打开将生成的文档在一个新的临时标签页中打开供用户预览和进一步编辑。复制到剪贴板静默地将结果复制到系统剪贴板让用户自行粘贴。显示信息提示对于简单的操作可能只是一个“Done”的提示。对于文档生成技能最友好的方式是在当前代码的上方插入生成的Markdown文档这样用户能立刻看到上下文并且不会破坏原有代码。2.5 配置Configuration可选让技能更灵活一个健壮的技能应该允许用户进行一些自定义配置。例如文档模板的样式用哪个级别的标题是否包含作者和时间戳。默认的行为当未选中文本时是处理整个文件还是弹出提示。支持的语言这个技能只处理Python函数还是也处理JavaScript函数。配置可以通过插件的设置Settings页面或者一个独立的配置文件如.skillrc.json来管理。对于入门技能我们可以先实现固定逻辑后续再考虑增加配置项。理解了这五个要素我们就能像搭积木一样构思我们的第一个技能了。我们的技能蓝图是通过/gendoc命令触发获取当前选中的代码解析出函数签名和注释按照一个预设的Markdown模板生成文档并插入到该函数的上方。3. 实战一步步构建你的第一个文档生成技能现在让我们进入实战环节。我们将为Claude Code或类似支持自定义技能的AI编码助手插件创建一个技能。不同插件的具体实现方式可能略有不同但核心思想和流程是相通的。这里我们以一种抽象的、通用的技能开发模式来讲解你可以根据自己使用的插件文档进行微调。3.1 环境与项目结构准备首先你需要在你的插件管理界面找到“开发自定义技能”或“Skill Development”的相关入口。通常这会引导你创建一个技能项目文件夹。一个典型的技能项目结构可能如下所示my-first-skill/ ├── package.json # 技能项目的元数据如名称、版本、依赖 ├── skill.js (或 index.js) # 技能的主逻辑文件 ├── manifest.json (或 skill.json) # 技能的“说明书”定义触发器、命令、配置等 └── README.md # 技能的说明文档package.json和manifest.json是技能的核心配置文件。package.json类似于Node.js项目声明依赖manifest.json则专门描述这个技能如何与编辑器交互。3.2 定义技能清单Manifestmanifest.json文件是技能的“身份证”和“使用说明书”。它告诉编辑器“我有一个技能名叫‘代码文档生成器’当你输入/gendoc时请执行skill.js文件里的generateDoc函数。”{ name: code-doc-generator, version: 1.0.0, description: 自动为选中的代码函数生成Markdown格式的API文档。, author: Your Name, commands: [ { command: gendoc, title: 生成代码文档, category: Documentation, handler: generateDoc // 指向主逻辑文件中的函数名 } ], activationEvents: [onCommand:gendoc], // 定义技能激活的事件 main: ./skill.js // 技能的主入口文件 }关键字段解析commands: 定义了用户可调用的命令。command是实际输入的指令如/gendoctitle可能会显示在命令面板中handler是关联的处理函数。activationEvents: 为了性能技能通常不会一直加载。这个字段告诉编辑器只有当用户执行gendoc命令时才激活并加载这个技能。main: 技能代码的入口点。3.3 编写核心处理逻辑skill.js这是最具技术含量的部分。我们将编写一个generateDoc函数。为了清晰我们分步骤实现// skill.js /** * 主处理函数生成代码文档 * param {Object} context - 插件提供的上下文对象包含编辑器状态、选中等信息 */ async function generateDoc(context) { // 1. 获取编辑器当前状态 const editor context.editor; if (!editor) { context.showErrorMessage(没有活动的文本编辑器); return; } const document editor.document; const selection editor.selection; // 2. 获取输入选中的文本如果没有选中则使用当前行的内容 let selectedText document.getText(selection); if (!selectedText.trim()) { // 如果没选中可以尝试获取光标所在行的函数块这是一个简化逻辑 // 更复杂的实现可以分析语言语法找到整个函数体。 const line document.lineAt(selection.active.line); selectedText line.text; // 这里简单提示用户更优做法是自动扩展选择到函数边界 context.showInformationMessage(未选中文本将处理当前行。); } // 3. 解析代码并提取信息这里以JavaScript函数为例 const functionInfo parseJavaScriptFunction(selectedText); if (!functionInfo) { context.showErrorMessage(未能识别出有效的函数定义。请确保选中了一个函数。); return; } // 4. 根据提取的信息填充Markdown模板 const markdownDoc generateMarkdownTemplate(functionInfo); // 5. 输出在函数上方插入生成的文档 // 计算插入位置函数定义行的起始位置 const insertPosition document.positionAt(document.offsetAt(selection.start)); await editor.edit((editBuilder) { editBuilder.insert(insertPosition, markdownDoc \n\n); // 插入并添加空行 }); context.showInformationMessage(文档已生成并插入); } // 导出让manifest.json能调用 module.exports { generateDoc }; /** * 解析JavaScript函数字符串提取基本信息。 * 这是一个简化版解析器使用正则表达式。生产环境建议使用babel/parser等工具。 * param {string} code - 函数代码字符串 * returns {Object|null} 函数信息对象解析失败返回null */ function parseJavaScriptFunction(code) { // 匹配 function 关键字定义的函数和箭头函数简化版 const funcRegex /(?:function\s(\w)\s*\(([^)]*)\)|const\s(\w)\s*\s*(?:\(([^)]*)\)|(\w))\s*)/; const match code.match(funcRegex); if (!match) { return null; } // 从正则匹配结果中提取函数名和参数 let funcName match[1] || match[3]; // 普通函数名或箭头函数变量名 let paramsStr match[2] || match[4] || match[5] || ; // 清理参数字符串分割成参数数组 const parameters paramsStr.split(,).map(p p.trim()).filter(p p); // 尝试从代码中提取第一行注释作为描述 const commentMatch code.match(/\/\/\s*(.)$/m) || code.match(/\/\*\*\s*\n\s*\*\s*(.?)\n/) ; const description commentMatch ? commentMatch[1] : 请补充函数描述; return { name: funcName || anonymous, parameters: parameters, description: description }; } /** * 根据函数信息生成Markdown文档字符串 * param {Object} funcInfo - 包含name, parameters, description的对象 * returns {string} 生成的Markdown字符串 */ function generateMarkdownTemplate(funcInfo) { const paramList funcInfo.parameters.length 0 ? funcInfo.parameters.map(p * \${p}\: 参数描述).join(\n) : 无; return ## ${funcInfo.name}()\n\n**描述**\n\n${funcInfo.description}\n\n**参数**\n\n${paramList}\n\n**返回值**\n\n\any\ - 返回值描述\n\n**示例**\n\n\\\javascript\n// 示例代码\n${funcInfo.name}(${funcInfo.parameters.join(, )});\n\\\; }代码逻辑逐步解析获取上下文函数接收一个context对象这是插件注入的“环境包”包含了当前编辑器、文档、选区等所有必要信息。输入处理首先尝试获取用户选中的文本。如果选区为空我们做了一个简单的降级处理使用当前行文本。在实际更完善的技能中你可能会调用编辑器的API来智能扩展选区到整个函数体。核心解析parseJavaScriptFunction这里我们写了一个简化的解析器使用正则表达式匹配常见的函数定义格式。它提取三样东西函数名、参数列表、以及函数上方或右侧的单行注释作为描述。这是一个关键取舍点正则表达式简单快速但对代码格式要求严格复杂情况嵌套括号、默认参数等容易出错。对于严肃的技能你应该考虑集成一个真正的JavaScript解析器如Babel但这会增加复杂度。作为第一个技能我们用正则演示原理。模板生成generateMarkdownTemplate将提取的信息填充到一个预设的Markdown模板字符串中。模板是固定的但你可以设计得非常美观包含表格、代码块等。输出结果使用编辑器的editAPI在计算好的位置函数开始处插入生成的Markdown文本。最后给用户一个完成提示。3.4 调试与安装你的技能编写完成后你通常可以通过插件提供的“加载本地技能”或“开发模式”来调试。流程一般是在插件的技能管理界面选择“添加本地技能”或“开发新技能”。指向你创建的my-first-skill文件夹。插件会读取manifest.json并注册你的命令。打开一个JavaScript文件选中一个函数在命令面板CtrlShiftP中输入“生成代码文档”或直接键入/gendoc。观察技能是否被触发生成的文档是否正确插入。如果出错查看编辑器的“输出”面板或开发者控制台F12中的错误信息。调试是技能开发中最耗时但也最重要的环节。你需要测试各种边界情况没有注释的函数、箭头函数、异步函数、未选中文本等等确保你的技能行为稳健。4. 从“能用”到“好用”技能开发的进阶思考与优化恭喜你现在你已经拥有一个可以运行的自定义技能了但这只是一个起点。要让这个技能从“玩具”变成真正提升效率的“利器”还需要考虑以下几个方面4.1 增强代码解析的鲁棒性我们之前用的正则表达式解析器非常脆弱。一个健壮的文档生成技能必须能准确理解代码结构。以下是升级方案使用语言服务器对于支持Language Server Protocol (LSP)的编辑器你可以直接查询语言服务器来获取准确的语法树AST。这是最准确的方式但集成复杂度高。集成专用解析库这是最实用的折中方案。例如对于JavaScript/TypeScript可以在你的技能项目中安装babel/parser。npm install babel/parser --save然后在skill.js中引入并使用const parser require(babel/parser); function parseCodeWithBabel(code) { try { const ast parser.parse(code, { sourceType: module, plugins: [jsx, typescript] // 根据需要添加插件 }); // 遍历AST精准定位函数声明、箭头函数、方法定义等 // 提取函数名、参数包括类型、默认值、返回值类型、关联的JSDoc注释等。 // ... 复杂的AST遍历逻辑 ... } catch (error) { console.error(解析失败:, error); return null; } }使用AST解析你可以轻松处理嵌套函数、解构参数、泛型等复杂语法提取的信息也全面得多。4.2 设计可配置的文档模板硬编码的模板缺乏灵活性。我们可以引入配置系统。一种简单的方法是在技能根目录创建一个config.json或template.md文件。config.json示例{ template: { includeAuthor: true, author: {{默认作者}}, includeTimestamp: false, sections: [描述, 参数, 返回值, 示例, 注意事项] } }template.md示例作为模板文件## {{functionName}} **作者**: {{author}} **创建时间**: {{timestamp}} ### 描述 {{description}} ### 参数 | 参数名 | 类型 | 描述 | |--------|------|------| {{#each parameters}} | {{name}} | {{type}} | {{description}} | {{/each}} ### 返回值 {{returnType}} - {{returnDescription}}然后在主逻辑中你需要一个简单的模板引擎如handlebars或自己写一个字符串替换函数来将提取的functionInfo对象和配置数据填充到模板中。4.3 处理多语言与上下文感知一个更高级的技能应该能识别不同编程语言并应用不同的解析规则和模板。你可以在manifest.json中声明技能支持的语言或者在代码中根据当前文件的扩展名document.languageId来动态切换逻辑。const language document.languageId; // 例如 javascript, python, java switch(language) { case javascript: case typescript: funcInfo parseJavaScript(code); template getTemplate(js); break; case python: funcInfo parsePython(code); // 需要实现Python解析器 template getTemplate(py); break; default: context.showWarningMessage(暂不支持 ${language} 语言的文档生成。); return; }4.4 错误处理与用户反馈良好的用户体验离不开清晰的反馈。我们的技能已经包含了一些基本的showErrorMessage和showInformationMessage。还可以做得更好提供撤销操作在插入文档后可以提供一个“撤销”的快速选项QuickPick让用户能一键回退。进度指示如果解析或生成过程较慢例如处理大型文件应该显示一个进度条或旋转图标。更详细的错误诊断当解析失败时不仅告诉用户失败还可以提示可能的原因比如“未检测到标准函数定义请检查代码格式”或“检测到可能是箭头函数但缺少参数括号”。4.5 技能的打包与分享当你打磨好一个技能后可能会想分享给团队成员。这时你需要了解插件的技能分发机制。通常有两种方式本地文件夹共享直接将整个技能文件夹打包发给同事让他们通过“加载本地技能”安装。这种方式简单但不易管理版本。发布到技能市场如果插件支持像Claude Code这类插件未来可能会有官方的技能商店。你需要按照其发布规范准备图标、更详细的README、版本号然后提交审核。这能让你的技能被更多人使用和反馈。5. 避坑指南技能开发中常见的“雷区”与解决方案在开发自定义技能的过程中我踩过不少坑。这里总结几个最常见的问题和解决方案希望能帮你节省时间。5.1 异步操作与编辑器API的时序问题编辑器的API如editor.edit()很多是异步的。在技能逻辑中如果你在异步操作如网络请求、文件读取完成之前就尝试操作编辑器可能会导致错误或状态不一致。// 错误示例在异步操作内直接调用同步编辑器API async function badExample(context) { const data await fetchSomeData(); // 异步请求 // 此时editor的状态可能已经改变用户切换了文件 context.editor.edit(builder { ... }); // 可能操作了错误的文档 } // 正确做法在异步操作开始前捕获当前需要的状态 async function goodExample(context) { const editor context.editor; const document editor.document; const selection editor.selection; const selectedText document.getText(selection); // 先获取并保存状态 const data await fetchSomeData(selectedText); // 使用保存的状态 // 再次确认编辑器状态是否依然有效可选但推荐 if (editor.document.uri.toString() ! document.uri.toString()) { context.showErrorMessage(文档已切换操作已取消。); return; } await editor.edit(builder { // 使用之前保存的 document 和 selection 信息进行计算 const insertPos document.positionAt(...); builder.insert(insertPos, data); }); }5.2 正则表达式的复杂性与维护噩梦正如之前提到的用正则表达式解析代码是条“捷径”但很容易变成“绝路”。当你想支持更多语法变体时正则会变得极其复杂且难以调试。核心建议对于任何超出简单文本匹配的代码分析任务尽早放弃正则转向AST解析。初期学习AST的成本远低于后期维护一个满是漏洞的正则表达式“补丁堆”。babel/parser、pyhton的ast模块、java的JavaParser等都是成熟的选择。5.3 技能性能与响应速度技能的执行不应该阻塞编辑器的主线程。如果你的技能需要处理非常大的文件或进行复杂的计算如全文语法分析考虑以下优化增量处理只处理用户选中的部分或可见区域而不是整个文件。Web Worker将繁重的计算任务丢给Web Worker避免界面卡顿。不过在技能开发环境中使用Worker可能需要处理额外的模块化和通信问题。缓存机制对于重复性的分析结果如同一个文件在短时间内被多次请求可以进行缓存。5.4 技能配置的持久化与默认值用户配置了技能后下次启动编辑器时应该还能生效。你需要使用插件提供的存储API如context.globalState或workspace.getConfiguration来持久化配置。同时一定要为所有配置项提供合理的默认值确保用户在不进行任何配置的情况下技能也能以基本模式运行。5.5 测试策略单元测试与集成测试技能也是软件需要测试。可以为其编写单元测试特别是核心的解析函数和模板生成函数。使用像Jest、Mocha这样的测试框架。对于与编辑器交互的部分命令注册、文本插入可以编写集成测试或者至少进行详尽的手动测试用例覆盖不同语言、不同代码结构、边界情况。开发第一个技能的过程就像学骑自行车。一开始可能会摇摇晃晃但一旦你掌握了平衡理解了技能的生命周期、编辑器API的调用方式、异步处理你就能自由地驶向任何你想自动化的方向。这个“代码文档生成器”只是一个起点你可以基于这个框架创造出代码格式化、数据转换、文本分析、甚至与外部API联动的各种强大技能真正让你的编辑器和AI助手成为你工作流的延伸。
返回列表