手写一个 AI 技能分发工具:Electron 自动检测 8 类客户端的实现

发布时间:2026/8/2 12:14:01
手写一个 AI 技能分发工具:Electron 自动检测 8 类客户端的实现 最近在给自己的 AI 编程助手做技能管理时踩进了一个工程问题我攒了65 个技能要分发到本机8 类 AI 客户端上。手动维护了两周后我放弃了写了个 Electron 工具来自动化这件事。这篇文章记录完整实现客户端自动检测、两类部署模型、幂等卸载、Ed25519 激活码体系以及两个让我熬到半夜的打包坑360 安全卫士的内核驱动拦截、Windows 上手工给 macOS 打包。代码均已脱敏思路可直接复用。技术栈Electron 33 React TypeScript打包用 electron-builder便携版免安装。一、需求65 个技能 × 8 类客户端的分发难题先说清楚问题。现在主流的 AI 编程客户端加载「自定义技能/规则」的方式分两派目录派——直接把技能放进指定目录客户端启动时自己扫描客户端技能目录Claude Code~/.claude/skillsCodex~/.codex/skillsKimi Code~/.agents/skillsReasonix自有技能目录规则派——没有技能目录概念只有一份全局规则/记忆文件要让模型「知道有哪些技能可用」得往规则文件里注入一张路由表routing matrix模型读到后自己去挑技能客户端规则文件Cursor~/.cursor/rules/Cline~/Documents/Cline/Rules/Windsurf~/.codeium/windsurf/memories/global_rules.mdKiro~/.kiro/steering/65 个技能手动部署意味着给目录派各拷一遍技能目录、给规则派各注入一遍路由表8 个客户端配完半天没了。而且换个机器又要重来——这才是真正忍不了的点。需求收敛成一句话自动检测本机装了哪几类客户端检测到几个部署几个卸载要干净。二、检测逻辑特征目录/文件扫描检测没什么黑科技就是穷举特征路径。每个客户端都有固定的安装/配置痕迹启动时扫一遍interfaceClientDescriptor{id:string;// claude-code | codex | ...kind:dir|rules;// 目录派还是规则派markers:string[];// 特征路径命中任一即视为已安装deployTarget:string;// 部署目标目录或规则文件}constCLIENTS:ClientDescriptor[][{id:claude-code,kind:dir,markers:[~/.claude],deployTarget:~/.claude/skills,},{id:windsurf,kind:rules,markers:[~/.codeium/windsurf],deployTarget:~/.codeium/windsurf/memories/global_rules.md,},// ... 共 8 条];asyncfunctiondetectClients():PromiseClientDescriptor[]{constfound:ClientDescriptor[][];for(constcofCLIENTS){for(constmofc.markers){if(awaitpathExists(expandHome(m))){found.push(c);break;}}}returnfound;}实际运行时的体感打开软件界面上 8 个客户端的图标装了哪个亮哪个检测到几个就部署几个没装的直接置灰跳过。不存在「为了凑数给没装的客户端建目录」这种蠢事。一个细节Windsurf 的global_rules.md是单文件检测条件要加上「目录存在但文件不存在也算装了客户端」否则空规则文件的用户会被漏检。三、两类部署 幂等卸载设计目录派技能目录整体放置目录派最简单——把 65 个技能目录拷到目标位置。注意点只有覆盖策略已存在同名技能就整体替换保证版本一致用户自己写的其他技能目录一概不碰。规则派标记块注入规则派是设计重点。规则文件是用户自己的东西里面可能有他手写的规则我们的路由表必须以「标记块」形式注入!-- AGENT-POJIA:BEGIN v1.0 -- ## Skill Routing Matrix | 任务类型 | 调用技能 | | --- | --- | | 二进制逆向分析 | ida-reverse / ghidra-reverse | | 协议分析 | protocol-reverse | ... !-- AGENT-POJIA:END --注入逻辑文件不存在 → 创建写入整块文件存在且无标记块 → 追加到文件末尾用户原有内容原样保留文件存在且已有标记块 → 原地替换标记块用于升级路由表版本。卸载只摘自己注入的卸载是幂等性最容易翻车的地方。规则派的卸载逻辑是用正则把BEGIN到END之间的块抠掉其余字节一个不动constBLOCK_RE/\n?!-- AGENT-POJIA:BEGIN[\s\S]*?AGENT-POJIA:END --\n?/;asyncfunctionuninstallRules(file:string){constcontentawaitfs.readFile(file,utf8);constcleanedcontent.replace(BLOCK_RE,\n);awaitfs.writeFile(file,cleaned);}目录派卸载就只删我们部署的那 65 个技能目录名按清单逐个删不做rm -rf整个 skills 目录——用户自己的技能必须毫发无损。卸载跑两遍和跑一遍结果一致这才叫幂等。四、Ed25519 激活码体系工具是收费的需要激活码。设计目标是私钥离线、验证完全本地、激活码可以直接复制粘贴。方案是 Ed25519 签名的两段式 tokenpayload_base64url.signature_base64urlpayloadJSON 明文授权类型、到期时间、机器指纹哈希base64url 编码signature私钥对 payload 的 Ed25519 签名同样 base64url验证客户端内嵌公钥收到 token 后拆两段、验签、解析 payload。全程不联网。为什么选 Ed25519 而不是 RSA公钥 32 字节、签名 64 字节内嵌进客户端几乎不占体积验签用 Node 自带crypto或 tweetnacl 都行importnaclfromtweetnacl;functionverifyToken(token:string):Payload|null{const[payloadB64,sigB64]token.split(.);if(!payloadB64||!sigB64)returnnull;constpayloadb64urlDecode(payloadB64);constsigb64urlDecode(sigB64);if(!nacl.sign.detached.verify(payload,sig,PUBLIC_KEY))returnnull;returnJSON.parse(Buffer.from(payload).toString(utf8));}签发侧是一台离线机器私钥永远不出那台机器。激活码泄露了最多是单码多用不影响签发体系本身——对个人开发者这个威胁模型够了。五、打包踩坑全文最有价值的部分坑 1360 安全卫士内核驱动拦截 d3dcompiler_47.dll这是整个项目里最离谱的一次排查。现象在一台装了 360 安全卫士的 Windows 机器上electron-builder 打包必定失败报错指向写d3dcompiler_47.dll时被拒。一开始以为是权限或者杀软误报做了三组对照实验才看清真相实验操作结果A在目标目录直接写入d3dcompiler_47.dll拒绝B同样内容写入改名test123.dll成功C在 Temp 目录写d3dcompiler_47.dll再 rename 到目标目录成功结论很清晰360 的内核驱动按「文件名 路径」规则拦截——在打包输出目录创建名为d3dcompiler_47.dll的文件会被拦但 Temp 目录豁免rename 操作也豁免。它不查文件内容只查路径规则。绕过方案分两步不用 electron-builder 自带的 electron 解压流程改用Python 手工解官方 Electron zip。解压时捕获PermissionError命中就降级为先写xxx.tmp再os.replace()改名——正好落在 rename 豁免的路径上defsafe_write(target:Path,data:bytes):try:target.write_bytes(data)exceptPermissionError:tmptarget.with_suffix(target.suffix.tmp)tmp.write_bytes(data)os.replace(tmp,target)# rename 豁免解压出干净的 Electron 目录后用electron-builder --prepackaged直接对现成目录打包跳过它内部的下载/解压环节。两步组合下来360 的拦截规则一次都没再触发。这类按文件名路径的内核级拦截日志里看不到驱动名没有对照实验根本定位不到——如果你也在国内环境分发 Electron 应用这个坑迟早会遇到。坑 2Windows 上手工给 macOS 打包第二个坑更工程化我没有 Macmac 版是在 Windows 上组装的。Electron 官方发布的 darwin zip 里有两样东西在 Windows 上会丢符号链接.app包内Electron Framework.framework等框架目录里有大量符号链接Versions/Current - A这类。Python 的zipfile.extractall()在 Windows 上不会创建符号链接而是把符号链接落成一个普通小文件内容是链接目标的路径字符串。解压完体积正常、文件数量正常但拷到 Mac 上直接「应用已损坏」。执行位zip 的external_attr高 16 位存 Unix 权限但 Windows 上os.chmod根本写不出x执行位。解法不依赖解压直接从官方 zip 的元数据里读「符号链接表 权限表」重打包时按表还原。importzipfiledefread_link_and_perm_tables(src_zip:str):links,perms{},{}withzipfile.ZipFile(src_zip)asz:forinfoinz.infolist():modeinfo.external_attr16perms[info.filename]modeif(mode0o170000)0o120000:# S_IFLNKlinks[info.filename]z.read(info.filename).decode()returnlinks,perms拿到两张表后解压普通文件、按perms表还原权限、按links表重建符号链接在重打包的 zip 里以正确的external_attr写回条目。最终验证符号链接14 个全部还原Electron主可执行文件和各Framework权限均为0o755。这样产出的 mac zip在 Mac 上解压后结构和官方包逐字节一致签名公证环节没有再出幺蛾子。六、结尾成品上面这套东西最后做成了「AGENT 破甲」核心是给 AI 装上51 个逆向工程技能二进制 / APK / 固件 / JS / 协议分析 / CTF 等方向外加 11 个智能助手和 3 个工具包自动检测 8 类客户端一键部署卸载零残留。官网 agnetpojia.top¥19.9 体验 37 天。利益相关作者本人。检测、部署、卸载的核心代码都贴在上面了不感兴趣的拿来抄思路也行打包那两节建议收藏——那才是真正熬出来的部分。