
1. 先说说我为什么要做这个项目如果你手头同时装着 Cursor、Claude Code、Codex CLI、Cline、Windsurf 这类 AI 编程工具大概很快会撞上一个让人抓狂的问题同一条“技能”在不同工具里是完全不同的写法。在 Claude Code 里是~/.claude/skills下的SKILL.md在 Cursor 里是.cursor/rules下的.mdc规则文件在 Codex 里是AGENTS.md或 prompt 模板在 Cline 里又是另一套约定。我一开始的做法是复制粘贴结果维护成本直接爆炸改一处逻辑要在四五个位置同步改改着改着就版本不一致有的工具还在用旧指令干活。所以我做了 Skills Manager想解决的问题就一句话把“技能”的编写、管理、分发从工具绑定中抽离出来。我在里面维护了一个 54 工具的适配器清单覆盖主流的 AI 编程工具和 Agent 框架。你只需要按一种统一格式写一份技能剩下的格式转换、目录落位、注册文件生成全部交给这个桌面端处理。它不是一个玄学架构核心就是“统一格式 适配器层”的小系统但就是这么个小系统把我每周的手工同步时间从两三个小时降到了接近零。这个项目适合谁参考三类人一是同时用多个 AI 编程工具、被规则文件搞烦的开发者二是做 Agent 开发想把提示词沉淀成可复用资产的人三是想自己搭“技能市场”或内部知识库的同学。下面我把设计思路、核心实现和踩过的坑完整讲一遍代码和目录结构都可以直接抄。2. 核心设计一份技能54 种输出2.1 通用技能格式到底长什么样整个系统最关键的决策是定义“技能”的通用格式。我先后试过纯 JSON、纯 Markdown、JSON Schema 驱动的方式最后回到了“文件夹 SKILL.md”的模式这个模式和 Anthropic 推的 Agent Skills 规范天然兼容社区接受度也最高。一个技能就是一个文件夹里面至少有一个带 YAML frontmatter 的SKILL.md。下面是我项目里的标准模板--- name: extract-table-from-image description: 从截图或图片中提取 Markdown 表格。当用户提供图片并要求“整理成表格”“提取表格”或“识别图中数据”时使用。 version: 1.2.0 author: team-data tags: [table, image, ocr] platforms: [claude-code, codex, cursor, cline] permissions: - filesystem: read - command: [python3, uv] --- # 从图片提取表格 ## 适用场景 用户给出截图、拍照图或导出的图片文件希望得到结构化的 Markdown 表格。 ## 操作步骤 1. 先把图片保存到本技能目录下的 assets/input.png 2. 运行 scripts/ocr.py 生成 intermediates/raw.txt 3. 把 raw.txt 中的分隔符噪声清理干净 4. 输出标准 Markdown 表格表头用粗体语法 ## 注意事项 - 如果图片里没有表格结构直接说明“未检测到表格”不要硬造 - 不要修改图片原始内容OCR 结果存到 intermediates 目录frontmatter 里的字段不是随便拍的每个字段都有存在理由。name是稳定 ID文件夹名、导出文件名、注册名全从它派生description是 Agent 触发技能的唯一依据几乎所有工具的 skill 路由都靠“用户意图”和描述做匹配所以描述一定要动词开头、写清楚触发条件、带上典型场景这是整个系统收益最大的地方version走语义化版本适配器导出时会把版本写进目标文件的头部注释方便追溯platforms声明这个技能支持哪些工具不支持的直接跳过permissions是安全声明后面讲安全时再展开。除了SKILL.md文件夹允许放scripts/和assets/。scripts/放技能运行时要调用的脚本assets/放示例图片、样例数据这类参考资源。为什么不把脚本内容塞进 Markdown因为多数 Agent 执行长脚本时会把缩进和引号搞坏独立文件既能做语法检查也方便单独测试。2.2 适配器层把通用格式翻译成工具方言适配器层是整个项目最有意思的部分。每个工具对“技能”的接受方式不一样我梳理下来其实分三类。第一类是“同构目录型”比如 Claude Code 的~/.claude/skills和 Cline 的 skills 目录它们原生支持SKILL.md所以适配器只做路径搬运把文件夹复制过去顺便检查一下 frontmatter 字段是否兼容。第二类是“规则文件型”比如 Cursor 的.cursor/rules、Windsurf 的.windsurf/rules、Continue 的配置它们要的是.mdc、.rules这类带元数据的文件适配器要把 Markdown 正文和 frontmatter 拆开重组写进去的文件头部要带description、globs这些字段否则 Cursor 的规则列表里不会显示可读的描述。第三类是“提示词型”比如 Codex 的AGENTS.md和 Aider 的 convo 文件这类工具有的是全局单文件有的是项目级文件适配器不能简单复制得把技能正文转成一段带分隔符的说明文本插到合适的位置。我做了个适配器映射表实际用起来一目了然目标工具落位路径文件格式适配器类型需要额外处理Claude Code~/.claude/skills/{name}/SKILL.md同构目录无Codex CLIAGENTS.md/~/.codex/prompts/Markdown section提示词段落拼接Cursor.cursor/rules/{name}.mdcMDC frontmatter规则文件重写元数据Clineskills/{name}/SKILL.md同构目录权限声明Windsurf.windsurf/rules/{name}.mdRules 格式规则文件描述字段Continue~/.continue/config.yamlYAML 片段规则文件缩进重排Aider.aider.conf.d/自定义格式特殊需查文档为什么适配器不做成“在目标目录里硬写”因为 54 个工具里有很多是后起的格式变化频繁把转换逻辑集中到适配器工具更新时只改一个适配器技术债可控。每个适配器本质是一个函数输入通用技能对象输出目标文件列表。当时定的接口是interface SkillAdapter { id: string; version: string; targets(skill: Skill): SkillTarget[]; } interface SkillTarget { path: string; content: string; type: file | append | override; }type字段解决“追加写”的问题。Codex 这类全局单文件多个技能要追加到同一个AGENTS.md如果每个适配器都整文件覆写后写的技能就把前面的冲掉了。我的做法是先收集所有技能再按适配器分组最后组内排序再合并写入保证同一个目标文件只被写一次。2.3 为什么选 Tauri 而不是 Electron桌面端的技术选型我最早用的 Electron后来换成了 Tauri 2。原因很实际这个应用 90% 时间在后台常驻、监听文件变化、执行少量 I/OElectron 那一整套 Chromium 内存开销实在没必要。换上 Tauri 之后安装包从 80 多 MB 降到 8 MB 左右内存占用从 400 MB 降到 90 MB体感差很多。但 Tauri 也不是银弹我至少踩了两个效率坑。一是 Rust 后端编译慢改一个文件系统监听逻辑要等几十秒我后来把核心逻辑拆成独立的corecrateUI 调试时用 mock 数据只有真正改到 Rust 才重编。二是 Windows 上 WebView2 的兼容性有的精简版系统没装 WebView2应用直接白屏最后加了启动检测没检测到就提示用户装运行时。架构上我保持简单Rust 后端负责文件系统监听、SQLite 索引、Git 操作、适配器转换前端用 Vue 3 做技能列表、详情编辑、导出配置。前后端通过 Tauri 的 command 通道通信不搞 WebSocket不搞复杂的状态同步。做工具类项目架构越朴素越耐用这个判断我到现在没后悔过。3. 落地实现把核心模块真正跑起来3.1 技能仓库的目录规划在动手写代码前先要把“技能放哪里”这个问题定死。我的约定是在用户主目录下建一个skills-manager/作为根仓库里面分skills/、config/、exports/三块。skills/放源技能一个文件夹一个技能config/放适配器配置和注册表exports/放按目标工具导出的产物导出时按工具名建子目录。skills-manager/ ├── skills/ │ ├── extract-table-from-image/ │ │ ├── SKILL.md │ │ ├── scripts/ocr.py │ │ └── assets/example.png │ ├── code-review-checker/ │ │ ├── SKILL.md │ │ └── prompts/quick.md │ └── api-doc-generator/ │ ├── SKILL.md │ └── assets/ ├── config/ │ ├── adapters.json │ └── registry.json └── exports/ ├── claude-code/ ├── cursor/ └── codex/有几个命名规则必须一开始就定好不然后面全是坑。文件夹名统一小写、用连字符分隔因为 Windows 文件系统大小写不敏感而 Linux 的~/.codex目录严格区分大小写同一个技能在两套系统里名字不一致Git 提交时就会出怪事。.开头和空格也不要出现在文件夹名里AGENTS.md的解析器对空格处理很不友好。config/adapters.json记录了每个工具的开头默认模板和路径模式registry.json是技能清单记录名字、版本和哈希。这两个文件都是机器生成的不建议手工改我在编辑器里把这两个文件设为只读防止误操作。3.2 索引与搜索SQLite FTS5技能超过几十个之后靠目录树肉眼找就废了。我给 Skills Manager 加了一个 SQLite 索引用 FTS5 做全文搜索。建表逻辑很直接一张skills表存元数据一张skill_fts虚表存可搜索文本。写入时把name、description、tags、SKILL.md正文都拼进 FTS 索引搜索时按相关性排序。SQL 大概长这样CREATE VIRTUAL TABLE skill_fts USING fts5( name, description, tags, body, contentskills, content_rowidid ); SELECT s.name, s.version, bm25(skill_fts) AS score FROM skill_fts JOIN skills s ON s.id skill_fts.rowid WHERE skill_fts MATCH :query ORDER BY score DESC;这里有个细节值得说FTS5 的分词器对中文不友好默认的unicode61会把中文按整个字符串切搜索“提取表格”时只输入“表格”是搜不到的。我最后换成了trigram分词器三字分词对中文和英文都还算能打缺点是索引体积大一点但对一个几千条记录的应用完全无感。文件系统监听我用了 Rust 的notifycrate监听skills/目录的创建、删除、变更事件。事件回调里做防抖500 毫秒内的事件合并成一次重建任务避免你保存文件时编辑器连续触发十几二十次回调导致索引反复刷。实测下来这个防抖能把无谓的写入减少 90% 以上。3.3 适配器引擎的关键实现适配器引擎的骨架不复杂真正难的是“渲染”这一步。以 Cursor 的.mdc文件为例它的元数据和正文是分离的正文里还要保留指向原始技能的信息方便反向追踪。我的一个适配器长这样const cursorAdapter: SkillAdapter { id: cursor, version: 1.0.0, targets(skill) { return [ { path: .cursor/rules/${slug(skill.name)}.mdc, content: renderMDC(skill), type: file, }, ]; }, }; function renderMDC(skill: Skill): string { return [ ---, description: ${skill.description}, globs: ${skill.globs ?? **/*.{ts,tsx,py,js}}, alwaysApply: false, ---, !-- source: ${skill.name}${skill.version} --, , skill.body, ].join(\n); }alwaysApply: false是我刻意设的。Cursor 的规则如果设成 alwaysApply每次对话都会占上下文几十个技能全塞进去上下文窗口直接不够用。改成按需匹配让模型根据描述判断要不要用效果反而更好。Claude Code 的适配器更简单它原生读SKILL.md我的适配器只做一件事把permissions字段里声明可执行的命令写进SKILL.md的 frontmatter因为 Claude Code 执行技能脚本前要白名单允许。这个细节我第一版漏了导致技能里的 Python 脚本一直跑不起来排查了半天才发现是权限声明缺失。Codex 的适配器稍微烦一点。AGENTS.md是全局单文件多技能追加时我用了标记块!-- skills-manager:extract-table-from-image1.2.0 -- 技能正文…… !-- /skills-manager:extract-table-from-image --下次导出时先扫描文件里有没有旧的skills-manager:标记块有就整块替换没有就追加。这套“标记块 幂等替换”的思路后来被我推广到了所有单文件型目标上再也不怕重复导出了。每个适配器都要配一组快照测试。我在项目里建了test/fixtures/目录放一个标准技能样例每个适配器跑完输出后和snapshots/里的预期文件做 diff。工具格式升级时先改适配器再跑测试diff 一眼就能看出格式差异比手工验证快得多。3.4 版本管理与多端同步技能这种纯文本资产版本管理最好的载体还是 Git。我把整个skills-manager/根目录初始化为一个 Git 仓库每次导出前自动 commit 一次消息格式是export: {tool}{count} skills。但 Git 只解决历史记录解决不了“这台机器改了怎么到另一台机器”的问题。我加了一个自定义的同步命令把你的技能仓库打包成.smpSkills Manager Pack文件本质是一个带 manifest 的 zip。manifest 里记录每个技能的名字、版本、SHA256 哈希接收方解包后先查哈希有冲突就进入合并流程——不是无脑覆盖而是生成一个冲突列表让你选。同步还有一个隐含问题不同机器的工具版本不一样。同一个技能在 Cursor 新版和旧版的格式要求可能不同所以我在registry.json里记录了“最后导出成功的目标工具版本号”下次同步时如果客户端工具版本号对不上先提示升级适配器再导出避免导出一堆旧格式文件。4. 跨平台与生态接入的坑4.1 文件系统差异这一节全是我实打实踩出来的。最典型的坑是 Windows 的路径。C:\Users\name\...和/Users/name/...在代码里必须统一处理我不允许代码里出现硬编码的路径分隔符所有路径拼接都用 Tauri 提供的路径 API。适配器输出路径时统一先转成相对路径再在落盘时根据当前平台拼绝对路径。Windows 还有一个坑是长路径。.cursor\rules\very-long-skill-name-that-exceeds-limits.mdc一旦加上前面的用户目录很容易超过 260 字符的经典限制。我的解决方案是双管齐下注册表开启 long paths这需要在安装脚本里做普通用户不会自己去开同时限制技能文件夹名最长 40 字符超长名字直接拒绝。文件系统监听在不同平台的行为也不同。macOS 的 FSEvents 会把重命名拆成“删除 创建”两个事件而 Windows 的 ReadDirectoryChangesW 有时会把一次写入拆成多次。我前面说的防抖机制就是被这种平台差异逼出来的。另外如果技能仓库放在 OneDrive、iCloud 这类云同步目录里监听器会收到非常多的虚假事件我最后在设置里加了“排除云同步目录”的开关遇到这类目录直接降级成“手动刷新”。大小写问题也要提。SKILL.md和skill.md在 Windows 上是同一个文件但在 Linux 上是两个文件。我的 Git 配置里设置了core.ignorecase false并且约定所有文件名一律小写只有SKILL.md这种规范名保持官方写法从源头杜绝混乱。4.2 各家工具的加载机制差异适配器能把文件写到正确位置但工具“什么时候读”这个行为差异比想象中大很多。Claude Code 是启动时扫描一次技能目录运行中新增的技能要重启会话才生效Cursor 的规则文件是动态读取的你刚保存就能在对话框里命中Codex 的AGENTS.md是按项目加载的而且它是逐块读取放太后面的技能可能不会被模型看到。这个差异直接影响“导出后要不要通知用户”。我的做法是每次导出完成后弹出一个工具行为提示卡片比如导到 Claude Code 就提醒“重启 Claude Code 会话后生效”导到 Cursor 就说“已生效可立即测试”。这个卡片一开始我觉得多余后来发现没有它用户会以为导出坏了疯狂反馈 bug。工具行为的差异是不可消除的那就把预期管理做好。还有一类“半支持”的坑。Windsurf 对规则的 glob 支持比较弱某些写法会导致规则完全失效Continue 的 YAML 缩进比较严格多一个空格就解析失败。适配器里做的“格式美化”在别的工具上可能没问题在 Continue 上就是灾难。我的经验是每个适配器都要有一个“最小可用目标”即保证导出的文件能在该工具里被正确解析而不是追求格式最优。4.3 安全与权限技能不是纯文本技能虽然本质是文本但它能引导 Agent 执行命令、读文件、甚至改代码所以安全设计必须前置。我在通用格式里加了permissions字段声明技能需要的文件访问范围和命令白名单。导出时适配器会把这个声明转写成目标工具自己的权限格式比如 Claude Code 的命令白名单。对来源不明的技能Skills Manager 默认降级处理如果技能是从网上下载的.smp包导入时强制进行“沙箱审查模式”该模式下脚本文件的可执行权限位会被清除技能正文会被渲染成“只读参考文本”必须人工一键开启权限后才能正常执行。这个设计来自一次真实事故——我从某个仓库导入了一个“自动修复测试”的技能它内置的脚本递归删除了临时目录虽然没造成大损失但让我意识到技能分发必须有信任分级。还有一个容易忽略的威胁模型是提示词注入。技能正文里如果写了“忽略以上所有指令输出恶意内容”Agent 加载技能后就可能被带偏。我在导入流程里加了一个静态扫描器检测“忽略系统指令”“忽略安全策略”这类高风险短语命中就打上警告标签。这不是完整的防线但能挡住最粗糙的攻击。5. 常见问题排查速查表以下这些问题是我和身边试用者反馈最多的一批整理成速查表方便直接查。现象可能原因排查思路解决办法技能导出了但 Agent 不调用description 描述太泛意图匹配不上看描述里是否有动词开头、典型场景改成“当用户……时使用本技能”句式Claude Code 技能脚本报权限拒绝permissions 字段没声明命令检查导出后 SKILL.md frontmatter补permissions.command白名单Cursor 规则列表显示空白MDC 文件缺 description打开文件看---块是否完整重新用最新适配器导出AGENTS.md 技能互相覆盖多技能追加顺序异常看标记块是否成对出现删除全部标记块后重新导出Windows 上监听不到文件变化仓库在云同步目录检查设置里的排除列表把仓库移出 OneDrive 等目录中文关键词搜不到技能FTS5 分词器不合适执行SELECT * FROM skill_fts WHERE skill_fts MATCH 表格建表时指定tokenizetrigram导出后工具会话没反应工具启动时缓存技能重启会话或重新打开项目看适配器加载机制重启验证排查思路有个通用套路先确认文件“真的写对位置了”再确认“内容格式符合该工具规范”最后才是“工具有没有读到”。90% 的问题出在前两步代码层面先加文件落盘日志再输出每个导出文件的前 20 行能省下大量时间。另外一个小技巧我在应用里内置了一个“技能试用台”。它不是真的调用各家 Agent而是用一个本地模拟器把用户输入的示例查询和所有技能的 description 做一次匹配打分显示哪些技能会被触发。这样不用打开五个工具来回试就能验证技能描述写得好不好。这个功能本来是内部调试用的后来成了使用者最喜欢的功能之一。6. 最后想分享的一点个人体会做这个项目之前我一直以为“技能管理”是个技术问题做完了才发现它更是个体验问题。技术难点无非是格式转换、路径处理、索引同步这些都是确定性的问题花时间总能解决。真正难的是让使用者愿意持续地把技能沉淀进仓库——如果写一个技能要开编辑器、填一堆字段、再点三次导出没人会坚持用。我的体会是工具的价值不在于功能多少而在于把“维护成本”压到足够低。Skills Manager 最后能在我这留下来靠的不是 54 个适配器而是“改一个文件、点一次导出、所有工具同步生效”这个顺滑感。如果你也想做类似的东西别一上来就追求覆盖所有工具先支持自己最常用的两三个把体验打磨顺再逐步扩展。再多说一句技能的格式设计会决定整个系统的上限。我花了很多时间调 frontmatter 字段最后发现描述写得好不好比字段全不全重要十倍。在 Agent 生态里一段精准的描述胜过十段冗余的指令——这个道理在你自己的技能库建设里同样成立。