在AI编程工具遍地开花的这两年一个很尴尬的现实是每个工具都在推自己的Agent能力但它们的技能体系互不兼容。Cursor有RulesClaude Code有SkillsCodex有AGENTS.md国内很多基于开源模型的IDE也有各自的指令模板。我在本机同时装了六七个这样的工具每次想迁移一套工作流都得把同样的技能用不同的格式重复写一遍维护成本高得离谱。后来我动手做了一个叫Skills Manager的跨平台桌面小工具专门把54 AI编程工具里的Agent技能统一收拢到一个本地中枢里管理。这篇文章就聊聊我是怎么设计它的、核心模块怎么落地以及踩过的那些坑。如果你也重度依赖多个AI编程工具被它们各自的技能格式折腾过那这篇应该对你有用。1. 为什么要把54 AI编程工具的Agent技能统一管起来1.1 工具碎片化带来的技能重复建设先说个真实场景。我平时写代码的主力是Cursor辅助偶尔切到Codex CLI和Claude Code。三周前我在Cursor里花两个小时调好了一套代码评审的规则包含文件变更范围判断、安全扫描要点、提交信息规范效果还不错。结果切到Claude Code想用同一套逻辑傻眼了人家根本不认识Cursor的.cursor/rules格式我得把同样的规则用SKILL.md重写一遍。这还不是最痛苦的。更痛苦的是技能里通常不止描述文字还挂着脚本、模板、参考文档。Cursor里我引用了一个review.shClaude Code那边也得复制一份。改一处逻辑所有工具里的副本都要同步改。到第四个工具的时候我已经分不清哪个副本是最新的了。市面上叫得上名字的AI编程工具我梳理了一下技能承载形式至少能分成六类基于规则文件的比如Cursor的Rules、Windsurf的Rules基于独立技能目录的比如Claude Code的Skills、Continue的Commands基于项目说明文档的比如Codex的AGENTS.md、OpenAI的提示约定基于插件市场的比如各种IDE商店里的Agent插件基于命令行配置的比如各类CLI工具的config里嵌入的指令模板基于云端定义的一些Saas化IDE把技能存在服务端也就是说一个统一技能中枢要面对的不是两三种格式差异而是一整个格式光谱。这背后反映的痛点是Agent技能本质上是一份资产但当前生态里这份资产被困在了各自工具的孤岛里。1.2 合理演绎统一管理要解决的四个核心问题从实用角度出发我把统一技能管理拆成了四个问题来解第一描述统一。所有技能无论服务于哪个工具都用同一套结构来描述包括名称、用途、触发条件、参数、依赖、脚本入口。这是整个系统的地基。第二格式适配。统一描述不直接喂给工具而是经过适配器翻译成每种工具认得的格式。翻译规则可配置、可插拔工具升级了改适配器就行不用动技能本身。第三版本与复用。技能存成文件纳入Git管理每个技能有版本号。团队里其他成员clone一套技能库激活之后就能在各自的工具里使用不用再问你的规则文件发我一份。第四可见性与冲突检测。桌面端随时能看到当前启用了哪些技能、每个技能被映射到了哪些工具、是否有两个技能争抢同一个触发词。这些东西在纯文件管理时代是看不见的。这四个问题对应的其实就是我的开发路线图。先把描述统一做好再做适配器再补版本管理和界面。2. 方案选型与实践思路为什么是跨平台桌面2.1 跨平台桌面框架的取舍核心诉求是跨平台因为我的技能库里既有macOS的环境也有Windows的机器团队里还有用Linux的。桌面端的候选方案其实就那么几条路Electron、Tauri、Qt、Flutter。我最终选了Tauri理由有三个。第一个是资源占用。Electron打包出来的应用随便就150MB起步内存常驻300MB很常见。我这工具本质上是个管理面板加文件同步器没必要背一个Chromium。Tauri用系统WebView渲染打包体积能压到10MB以内内存占用低一个量级。第二个是系统集成能力。Tauri的Rust后端做文件监听、子进程调用、SQLite操作都很顺手尤其调用外部命令比如触发技能脚本时Rust的std::process::Command比Node.js的child_process更稳不会有奇怪的event loop阻塞。第三个是社区生态。Tauri 2.0以后插件体系成熟了不少权限模型清晰做本地应用够用。Qt虽然性能更好但界面开发效率低做这种表单密集型工具不划算。当然Electron也有它的优势生态最成熟、招人容易、调试工具链完整。如果你团队里全是前端选Electron完全说得通。但我一个人维护追求轻量和省心Tauri是对的。打包产物在Windows、macOS、Ubuntu三端我都实测过没有出现WebView兼容性翻车的情况。2.2 本地优先的存储设计YAML文件做事实源SQLite做索引技能管理这种事我不建议一上来就设计云同步。原因很现实技能直接关联到本地的工具配置和密钥体系上云等于把安全边界搞复杂了。我的方案是本地优先YAML文件做事实源。每个技能在技能库目录里占一个文件夹~/.skills-manager/skills/ ├── conventional-commit/ │ ├── skill.yaml │ ├── scripts/ │ │ └── make_commit.py │ └── assets/ │ └── template.md ├── code-reviewer/ │ ├── skill.yaml │ └── scripts/ │ └── review.py └── api-debugger/ ├── skill.yaml └── scripts/ └── check_api.shskill.yaml是技能的源数据脚本放同目录子文件夹里这样整个技能可以作为一个整体被Git追踪、被压缩归档、被分享。那SQLite用来干什么做索引和状态缓存。桌面端的技能列表、搜索、启用停用状态、映射关系这些高频操作用SQLite响应更快不用每次全量扫描YAML。启动时扫描一次文件目录把变更同步进SQLite之后界面读取都走数据库。2.3 桌面端与Agent运行时的通信方式这一块是很多人会忽略但在实际使用中极其关键的桌面端不是直接去驱动AI编程工具而是通过文件系统和命令行来协作。比如用户激活了一个技能桌面端做的事情是把统一技能翻译成目标工具认得的格式写到对应工具读取的位置比如.cursor/rules/、~/.claude/skills/触发一个变更事件工具下次启动或重新加载时自动识别技能执行时也一样。桌面端不抢Agent的控制权只负责在Agent调用技能前把参数准备好、把环境变量设置好然后用Command::new去跑技能里的脚本。跑完把stdout和stderr收回来显示在桌面的执行日志里。这种写文件调子进程的组合方式最大好处是兼容性极强。不管工具是Electron写的还是Rust写的是IDE插件还是CLI只要它能读文件、能跑命令就能接入这套体系。我在设计时特意没有引入任何Agent通信协议的概念因为现阶段各家工具根本不可能统一协议统一到文件层反而是最稳的。3. 核心能力拆解统一技能模型与多工具适配器3.1 技能描述文件的Schema设计统一技能能否成立完全取决于描述模型设计得好不好。我前后迭代了三版最终定下来的skill.yaml核心字段长这样name: conventional-commit description: 根据暂存区diff生成符合Conventional Commits规范的提交信息 version: 1.2.0 author: octoexample.com license: MIT triggers: - commit - 生成提交信息 - write commit message parameters: - name: style type: enum default: conventional options: [conventional, gitmoji] - name: scope type: string required: false dependencies: - command: git min_version: 2.30.0 - command: python3 providers: cursor: format: rules entry: .cursor/rules/commit.skill.md claude-code: format: skills entry: skills/commit/SKILL.md codex: format: agents-md entry: AGENTS.md#commit continue: format: commands entry: commands/commit.yaml lifecycle: on_activate: scripts/on_activate.sh before_run: scripts/prepare_env.sh after_run: scripts/cleanup.sh这里有几个设计上的讲究。triggers是技能被Agent唤起的关键词集合不同工具对触发词的处理逻辑不同Cursor偏向规则匹配Claude Code支持自然语言描述Codex更多靠AGENTS.md里的段落锚点。统一模型里把它们归一化成一个数组适配时再各自转换。parameters声明技能可以接受哪些输入参数这直接决定了工具调用技能时的交互形式。比如style参数支持conventional和gitmoji两种风格适配器可以据此生成不同的命令模板。dependencies是我后来补上的字段。之前经常遇到技能脚本依赖的git版本太低跑出来格式不对排查半天才发现是环境问题。把依赖写进描述文件桌面端在激活技能时可以做一次环境预检不满足就直接警告省去很多隐性故障。providers是适配层的映射声明。一个技能可以映射到多台工具每台工具用不同的格式和入口。新增工具时只需要往这个列表里加一项再实现对应的适配器逻辑即可。lifecycle里挂的是技能生命周期钩子。其实大部分技能用不到on_activate但有些需要准备虚拟环境、初始化临时目录放到钩子里能让技能更干净。3.2 适配器层一份技能翻译成六种格式统一模型只是第一步真正干活的是适配器层。我目前实现了6个主流适配器Cursor Rules、Claude Code Skills、Codex AGENTS.md、Continue Commands、Windsurf Rules以及一个通用Markdown模板。每个适配器做的事情类似读取skill.yaml按目标工具的偏好渲染出对应文件。以Cursor为例它认的是.cursor/rules下的Markdown文件里面用front matter声明规则名称和描述正文是规则内容。适配器就把skill.yaml转成--- description: 根据暂存区diff生成符合Conventional Commits规范的提交信息 globs: [*.py, *.js, *.ts, *.go] --- 当用户要求生成提交信息时先执行 git diff --cached 获取暂存区变更 再调用 scripts/make_commit.py 生成提交信息严格遵守Conventional Commits格式。Claude Code的Skills格式则是目录结构skills/commit/ ├── SKILL.md └── scripts/make_commit.pySKILL.md头部的YAML front matter里声明name和description正文描述使用方式。适配器直接按这个结构把技能目录复制过去。Codex的AGENTS.md又是另一种玩法它是Markdown加锚点适配器就把技能内容转成一个段落并确定一个锚点链接比如AGENTS.md#commit。这中间的翻译逻辑我抽象成了一个接口function transform(skill, target) { const adapter adapters[target]; if (!adapter) throw new Error(Unsupported target: ${target}); return adapter.render(skill); }新增适配器时不用改动任何已有代码只要注册一个实现了render函数的适配器对象。这是我整项目里最得意的设计因为工具链还在快速演进几乎每个月都会有新工具冒出来可插拔的架构才能跟上变化。3.3 路径一份配置多端同步的执行链路统一模型和适配器解决的是描述问题但技能能不能真正跑起来取决于执行链路。我举个例子你激活了conventional-commit技能接着在Claude Code里让Agent生成提交信息链路是这样的Claude Code读取skills/commit/SKILL.md理解该技能的存在和调用方式Agent在执行过程中需要添加参数时桌面端根据parameters定义生成提示模板引导用户补全style参数Agent调用scripts/make_commit.py时桌面端的子进程管理模块介入设置好HOME、PATH等环境变量让脚本能正确找到git和python3脚本输出结果被Agent读取形成最终回复这个链路里桌面端完全不干预Agent的决策逻辑只是在工具调用技能的前后做了两件事保证环境正确和把技能描述放到工具能读到的位置。这种边界清晰的设计让我不用去逆向各家工具的API也不需要等待任何官方协议实现成本大大降低。说实话Skills Manager这套东西本质上不是技术多高深而是把如何把一份抽象定义翻译成N份具体配置这件事做扎实了。只要描述模型够稳定适配器够全整个体系就能滚动起来。4. 实操全程从安装部署到创建第一个统一技能4.1 环境准备与工程目录初始化先交代一下环境。我的主力开发机是macOS另外在Windows 11和Ubuntu 22.04上各有验证环境。桌面端用Tauri 2.0 React TypeScript后端Rust负责文件监听、子进程、SQLite。技能库本身是纯文件结构跟桌面端解耦——就算哪天桌面端挂了技能文件依然能手动拷到目标工具里用。安装过程我就简短说Tauri 2.0的脚手架可以用npm create tauri-applatest初始化选React模板。Rust后端需要的crate有serde、serde_yaml、rusqlite、notify文件监听、dirs跨平台目录处理。这些都比较常规照着文档配置tauri.conf.json里的beforeDevCommand和beforeBuildCommand就能跑起来。目录结构我遵循了Tauri的惯例但技能库单独放在用户主目录下的隐藏文件夹里不放在应用安装目录这样才能保证跨平台路径稳定。4.2 五步创建一个统一技能并同步到三个工具我建议新手不要一上来就建十个技能先用一个最简单的技能跑通全链路感受一下一份定义、多处生效的流程。下面以刚才的conventional-commit技能为例完整走一遍。第一步创建技能目录和描述文件在~/.skills-manager/skills/下建文件夹写入skill.yaml内容就是上面那个示例。注意name字段要用中划线命名法别用空格否则生成路径时很容易踩坑。第二步写执行脚本技能要真正干活得有脚本兜底。我写了一个极简的Python脚本#!/usr/bin/env python3 import subprocess import sys def get_staged_diff(): result subprocess.run( [git, diff, --cached, --stat], capture_outputTrue, textTrue, ) return result.stdout.strip() def main(): style conventional for arg in sys.argv[1:]: if arg.startswith(--style): style arg.split()[1] diff get_staged_diff() if not diff: print(暂存区没有变更请先 git add) sys.exit(1) # 生产环境中这里会调用大模型接口生成提交信息 print(f[{style}] {diff.splitlines()[0]} 等 N 个文件变更) if __name__ __main__: main()脚本写得简单但有两个关键点一是从git diff --cached取暂存区数据确保只统计准备提交的内容二是支持--style参数跟描述文件里的parameters对得上。实际生产版本的调用大模型部分我封装成了独立模块这样脚本本身便于测试。第三步声明providers映射在skill.yaml的providers里加上C Claude Code和Cursor就像示例里写的那样。这一步的意义是告诉桌面端这个技能要分别以什么形式、写到什么位置。多写一段映射就少一份手工搬运的活。第四步在桌面端激活并按需同步打开Skills Manager界面左侧技能列表里会看到conventional-commit右侧有目标工具映射面板。勾选Claude Code和Cursor点同步按钮。此时桌面端做的事情是读取YAML调用两个适配器分别生成SKILL.md和.cursor/rules/commit.skill.md然后复制到对应位置。这个复制动作是有讲究的。如果目标工具正在运行直接写文件有时不会被热加载。我在后端加了文件监听写完配置后再touch一下触发事件。这个细节很多自己手动往工具里塞配置的人都遇到过——明明文件写进去了工具就是没反应十有八九是没触发重载。第五步验证到Claude Code里输入生成提交信息如果技能识别正常它会调用make_commit.py并返回结果。再到Cursor里同样试一次两边表现一致就说明统一模型和适配器都工作正常。我把这个验证动作固化成了一条命令skm verify conventional-commit它会检查映射文件是否存在、依赖是否满足、脚本能否执行。发布一个技能之前先跑一次验证能省掉大量线上返工。4.3 技能包导出与团队共享个人用的技能可以只管本地但一旦要共享给团队版本管理就很重要了。我有两种方式一种是把技能目录放进Git仓库团队内共用一份技能库。谁新增技能提交PR合并其他人git pull后在桌面端点刷新技能就同步了。这种方式适合小团队简单直接。另一种是打包成.skmp技能包把技能目录压成tar.gz带一个manifest。放在内网共享目录或者打包到桌面端的技能市场里其他人下载后双击就能安装。我在Rust后端写了一个SkillsPack模块负责打包和解包校验文件完整性防止装到一半缺文件。比较关键的是版本控制我直接复用Git语义。version字段写的是1.2.0桌面端对比远程仓库的tag以后提示有更新可用用户点更新适配器重新渲染就完成了技能升级。整个过程不需要人肉管那些散落的规则文件。5. 常见问题与排查技巧整理5.1 最常见的问题和对应解法这个项目从v0.1到v1.2我排了不少bug下面是几个有代表性的按出现频率排序现象根因解决办法技能在Cursor里识别但在Claude Code里不识别适配器没有把triggers正确写入SKILL.md的front matter检查生成的SKILL.md是否包含description字段且无特殊符号脚本执行报错command not found桌面端子进程没继承用户PATH环境变量在Rust端调用前手动读取/etc/paths和shell配置文件合并PATH同步到一半界面卡死文件监听事件风暴短时间内大量写入触发递归监听用notify的延时合并机制比如300ms内的连续事件只处理一次Windows上技能路径带反斜杠工具不认路径分隔符在渲染Markdown时被转义统一用/生成映射文件只在文件系统操作层还原为系统路径工具升级后旧的映射文件不生效新版本改名或改变了读取目录适配器加版本检测锁定已知兼容版本不满足时在UI上提示技能在目标工具中不生效这是频率最高的问题。大多数时候不是技能写错了而是写到了工具不读取的位置。比如Cursor某些版本读取规则目录是.cursor/rules但更早版本是.cursorrules单文件。适配器版本不对就会写错。排查办法是先看桌面端生成的映射文件路径对不对再看工具文档里的读取顺序。我自己的原则是先相信文件系统不要相信工具的报错提示很多IDE对规则加载失败是不报错的。触发词互相覆盖多个技能如果写了相同或相似的triggersAgent可能会困惑。比如commit这个词被两个技能同时声明有的工具会按文件顺序优先有的会报冲突。我在桌面端加了一个冲突检测同步时扫描所有技能,发现重叠就警告并提示用户修改某个技能的触发词。这个功能虽然简单但能避免很多莫名其妙的Agent行为漂移。5.2 独家避坑经验从实际使用中总结的教训下面这几条属于那种文档里永远查不到但实战里特别要命的东西单独列出来给各位参考。第一不要把执行脚本放在系统临时目录。早期版本我让技能脚本运行在/tmp/skm/下图省事。后来发现macOS和Linux的临时目录清理策略不同有些脚本跑到一半文件被清了出了好几次诡异故障。现在所有技能脚本都放在技能目录内部执行时再拷贝到临时位置跑完清理。多一层拷贝看起来冗余但胜在稳定。第二环境变量隔离一定要做。很多技能脚本依赖GITHUB_TOKEN、OPENAI_API_KEY之类的密钥如果脚本运行环境直接继承整个桌面端进程的环境变量潜在风险很大。我在子进程管理模块里做了一个白名单机制脚本需要在skill.yaml里声明需要哪些环境变量比如env_allow: [GITHUB_TOKEN, OPENAI_API_KEY]其他变量一律过滤。缺了变量就提示用户在桌面端配置而不是静默继承。第三设置超时和熔断。Agent生成的命令有时候是死循环比如某个技能脚本在等一个永远不会来的网络响应。我在子进程执行的时候强制设置超时比如脚本默认60秒超时直接kill。同时做成可配置重活可以调大但必须有上限。没有超时机制之前我曾经被一个卡死的脚本拖垮了整个桌面端后来索性在Rust后端把进程组都管理起来连shell子进程都能一起干掉。第四适配器要及时跟进工具更新。我这半年迭代下来Cursor、Claude Code、Codex都有过大版本更新规则格式或多或少变了。所以适配器模块我特意做成独立版本控制每个工具一个子模块工具的breaking change只影响对应适配器的版本号不影响技能本体。升级流程是工具更新 - 适配器发新版 - 桌面端检测到依赖不匹配 - 提示用户升级适配器。写在最后的一点体会Skills Manager这个项目做下来我最大的体会是统一技能管理这件事难的不是技术实现而是边界划分。最开始我也想过做一个万能Agent来接管所有工具后来发现方向完全错了。真正的价值不是替代工具而是当好翻译官和调度员让技能资产在多个工具之间自由流动。最后再分享一个小技巧如果你不打算自己写一整套桌面端也可以只做适配器层把skill.yaml到各工具格式的转换逻辑抽成一个命令行工具配合Git hook自动同步效果也不会太差。但如果你跟我一样工具多、技能多、还要带团队共用一个带界面的桌面中枢还是值得投入的——毕竟技能库长到五六十个技能以后靠肉眼管文件真的会疯。