怎样让 AI Skill 在多个 Agent 之间共享
我这台电脑上装了不少自己写的 Skill,WorkBuddy、Trae、Codex、Loomy、WorkBuddy 国际版,基本是同时开着用的。最早没当回事,觉得 skill 装了就是装了。后来接连踩了三个坑,才意识到这事儿不解决,越用越烦:
- 切一次 WorkBuddy 账号登录,手头的 skill 没了;
- 市场里装的插件型 skill,换个账号授权态就没了;
- 换一个 Agent 客户端,又得把同一批 skill 重装一遍。
skill 被三层东西绑死了:账号、Agent、机器。装得越多,越像给自己挖了个坑。
这篇文章就讲我是怎么把这事儿理顺的——一个 skill 仓库,多个 Agent 靠「软链接」共用,换电脑、换账号都能快速恢复。
如果你还没搞清 Skill 本身该怎么写,可以先看我上一篇「怎样设计一个真正好用的 AI Skill」。本篇只解决「skill 怎么在多端共享」这一个具体问题。
一、先把问题拆开:到底被什么绑住了
三层绑定,每一层的表现和根因都不一样,得分别对付。
绑定层 表现 根因
账号绑定 市场装的 skill 切账号就没了 插件型 skill 住在 plugins/cache,带着授权态,跟登录账号走
Agent 绑定 换一个客户端,skill 一个都不在 每个客户端只读自己目录下的 skills/,彼此不互通
机器绑定 换电脑,目录全没了 目录挂在 C:\Users\用户名 下,跟这台机器绑定
我原先以为是「我装的方式不对」,后来才看明白:前两层是软件设计层面的隔离(账号态、客户端各自为政),第三层是物理位置的问题。它们其实可以用同一套思路解决,不用分别折腾。
二、关键发现:其实各家吃的是同一套标准
我逐个去翻了 WorkBuddy、Trae、Loomy、Codex、Claude Code 的配置目录,结论是:它们全都读 SKILL.md 这种文件夹,只是目录位置不同。
Agent 全局 skills 目录
WorkBuddy `.workbuddy\skills`
WorkBuddy 国际版 `.workbuddy-ai\skills`
CodeBuddy `.codebuddy\skills`
Trae CN `.trae-cn\skills`
Loomy `C:\Users\Public\Loomy\<实例id>\opencode\skills`
Codex `.codex\skills`
Claude Code `.claude\skills`
跨运行时 `.agents\skills`(Codex / Gemini / Copilot CLI 也会读)(最后那个 .agents\skills 是 Codex 等 CLI 的跨运行时路径,顺手也加上,不亏。)
既然格式一样、只是目录不同,思路就顺了:把 skill 集中放在一个「中枢目录」,再用链接挂到各家目录里。改一处,全端生效;装一次,八个客户端都能用。
三、链接:是软链接,不是快捷方式
这里得先说清一个容易混的概念,因为方案就建立在这上面。Windows 上有三种「看起来像链接」的东西:
1. Junction(目录联接)—— 我用的就是这个
文件系统层的链接,只针对目录。程序读 ~/.claude/skills/pdf/ 时,系统自动转到真源目录,程序完全无感知——它以为那就是个普通文件夹,还不需要管理员权限。
2. Symbolic Link(符号链接)
真正意义上的软链接,文件目录都能链,跨盘跨网络都行,但创建默认要管理员权限或开开发者模式。macOS / Linux 的 ln -s 就是它。
3. 快捷方式(.lnk)—— 没用
快捷方式只是个 2KB 小文件。程序读到它,不会自动跳转,除非程序专门写代码去解析它。AI 客户端不会认,所以快捷方式在这个场景里派不上用场。
一句人话总结:我们要的就是「一个文件夹,同时在八个地方出现」,八处的名字指向同一份实体文件,改哪个都是改同一份。junction 是只链目录的简化版软链接,够用且免权限,最合适。
我用的是逐 skill 联接(每个 skill 单独建一条链接),不是把整个
skills目录整目录联接。两个原因:一是各客户端的skills目录本身保持真实目录,避免某些客户端的文件扫描器跳过重解析点;二是 WorkBuddy 自己的元数据文件(migration.json、lock 之类)能原样留在它自己目录里,不会污染到 Claude / Codex。
四、中枢长啥样
按这个思路,结构是这样:
C:\Users\miyi2\AI-Skills\ ← 唯一真源,所有 skill 住这里
├─ editaplot\SKILL.md
├─ youtube-transcribe\SKILL.md
├─ skill-maker\SKILL.md
└─ ... (32 个)
├─ _plugin-vault\ ← 市场插件 skill 的固化副本(不对外分发)
└─ _tools\
└─ Sync-SkillsHub.ps1 ← 核心同步脚本
每个 skill 建一条 junction(不是复制),挂到下面八个位置:
~/.workbuddy/skills/ ~/.workbuddy-ai/skills/
~/.codebuddy/skills/ ~/.trae-cn/skills/
~/.loomy/skills/ ~/.codex/skills/
~/.claude/skills/ ~/.agents/skills/
_plugin-vault 是给「账号绑定」那个坑准备的:市场插件 skill 原本住在 plugins\cache,跟着账号走。我把它们复制一份固化进保险库,脱离缓存,之后就不随登录态失效了。它们故意不参与分发,避免和已加载的插件重名打架——真要用,手动从中枢取出来改名即可。
五、同步脚本怎么写
核心就一个 PowerShell 脚本,分三段跑:
脚本逻辑 干啥
Stage 1 harvest 把各客户端里已有的 skill 收拢进中枢(移动,不是删除,可逆)
Stage 2 link 把中枢里每个 skill 用 junction 挂到八个客户端目录
Stage 3 git 可选:把中枢提交进 git,方便换机器恢复
几个关键点说一下:
- 可重复跑。加了 skill 就再跑一次;删了 skill,脚本会自动把对应链接拆掉,不留僵尸。
- 不重复 harvest 链接。早期版本会把已建好的 junction 又当「待回收 skill」处理一遍,日志刷 256 行,极端情况会误移文件。后来加了个判断:碰到链接就跳过。
- 路径不写死。脚本默认从
$env:USERPROFILE取当前用户目录,换电脑用户名不是miyi2也能用;换 Windows 账号时加-UserHome "C:\Users\新用户名"即可。 - Loomy 特殊处理。Loomy 的 skill 目录在
C:\Users\Public下,而且实例 id 升级后可能变;它启动时还会用模板覆盖opencode.json的skills.paths,所以没法靠配置文件加目录,只能直接把链接建进那个目录。
跑起来的命令就一行:
powershell -ExecutionPolicy Bypass -File "C:\Users\miyi2\AI-Skills\_tools\Sync-SkillsHub.ps1" -IncludePluginCache -WithGit
-IncludePluginCache 固化市场插件,-WithGit 顺手做版本备份。不想带 git 就去掉后面那个开关。
六、跑完是什么样
验证结果:8 个目录各 32 条链接,一共 256 条,且都能正常读到 SKILL.md。
.workbuddy\skills 32 ← WorkBuddy
.workbuddy-ai\skills 32 ← 国际版
.codebuddy\skills 32
.trae-cn\skills 32 ← Trae CN
.loomy\skills 32 ← Loomy
.codex\skills 32 ← Codex
.claude\skills 32 ← Claude Code(多送的)
.agents\skills 32 ← 跨运行时路径
保险库里另存了 44 个市场插件 skill 的副本(pdf、westock、tencent-docs 这些)。
重要一步:跑完重启 Trae / Loomy / Codex / Claude Code。WorkBuddy 这边的路径没变,基本无感;但其他客户端要等它们启动时重新扫描 skills 目录才会认。
七、换电脑 / 换账号:三步恢复
这是「机器绑定」那层的解法。先说一个容易误解的点:junction 链接是不带走的。git 里存的是 32 个 skill 的文件本身,链接是每台电脑本地重建的。所以逻辑是「搬文件 → 新电脑跑一次脚本 → 链接自动生成」。
搬运方式 怎么做 适合
git 推私有远端(推荐) Gitee / GitHub 建个私有仓库,git remote add 再 push 长期多台机器、以后还要同步
整个目录拷贝 U 盘 / 网盘,把 AI-Skills 整目录搬过去 一次性、不想折腾 git
同机换 Windows 账号 不用搬,脚本加 -UserHome "C:\Users\新用户名" 只是换登录用户
新电脑三步:
- 装好各 Agent 客户端(WorkBuddy、Trae、Loomy 等各装一次,让它们生成自己的配置目录);
- 把
AI-Skills放到C:\Users\<新用户名>\AI-Skills; - 双击
_tools\SyncSkillsHub.bat(或直接跑脚本)→ 8 个目录各 32 条链接自动建好,重启所有 Agent 即可。
新电脑少装了某些 Agent 也不影响。脚本照样会给它们建好目录和链接,等你哪天装了直接就能用,不会报错中断。
八、几个特别容易踩的坑
坑 1:直接往 Agent 自己的 skills 目录放真文件夹。 那里每一项都应该是链接。你手动塞个真文件夹进去,它不会同步回中枢,时间一长中枢和客户端就对不上了。记住:真文件夹只进 AI-Skills\,客户端目录里只放链接。
坑 2:以为链接能跟着 git 走。 git 不存 junction,只存文件。换电脑如果只 clone 不跑脚本,八个目录全是空的。链接必须靠脚本在本地重建,这是设计如此,不是 bug。
坑 3:市场插件 skill 跟账号走。 不固化进 _plugin-vault 的话,切账号授权态一丢就没了。已经处理,但提醒你以后从市场新装的,记得再跑一次 -IncludePluginCache。
坑 4:Loomy 实例 id 会变。 升级后 C:\Users\Public\Loomy\6d02254fbb40 那段 id 可能变,脚本里那行要跟着改。其他目录都是按用户目录拼的,不受影响。
坑 5:依赖过不去。 skill 文件能过去,但底层工具过不去。我这边有 16 个左右 skill 需要重装依赖:转录类(yt-dlp、ffmpeg)、channel-keyword-post、knowledge-base-management、code-mentor、ppt-generator、tencent-channel 等。另外 editaplot 要装 Origin、qgis-map-workflow 要装 QGIS、quarkclouddrive 和 tencent-channel 要重新登录。换电脑别指望开箱即用,这些得单独补。
九、最后说一句
我原先以为「共享 skill」是个配置问题,搞个同步盘或者软链一下就行。真做下来才发现,它其实是在同时解开三层绑定:账号态、客户端隔离、机器位置。junction 解决「一处改多处生效」,git 解决「换机器能复原」,保险库解决「市场插件不随账号消失」——三件事各管一摊,合起来才稳。
回头看,值钱的经验就几条:
- skill 的真源只有一份,客户端目录里不要放真文件夹;
- 链接本地重建,文件才进版本库,别指望把链接 commit 走;
- 市场装的 skill 默认跟账号走,要用就先固化;
- 底层依赖是另一回事,文件同步救不了「没装 ffmpeg」。
当你开始用「中枢 + 链接 + 版本库」这套思路管 skill,就不太会再被「换账号没了」「换个客户端重装」这类事烦到了。这大概才是多 Agent 玩家真正该有的状态。
附录脚本
Sync-SkillsHub.ps1
当前完整内容(我本机在跑的版本)
#Requires -Version 5.1
<#
.SYNOPSIS
Skills Hub - one source of truth for SKILL.md folders, shared by every AI client.
.DESCRIPTION
Stage 1 harvest : move existing skills from each client into the hub
Stage 2 link : junction every hub skill into each client's skills dir
Stage 3 git : optional versioning / cross-machine restore
Clients covered
WorkBuddy .workbuddy\skills
WorkBuddy AI .workbuddy-ai\skills
CodeBuddy .codebuddy\skills
Trae CN .trae-cn\skills
Loomy <Loomy install dir>\opencode\skills (see $targets below)
Codex CLI .codex\skills
Claude Code .claude\skills
cross-runtime .agents\skills (read by Codex / Gemini / Copilot CLI)
Safe to re-run at any time. Run again after adding or removing skills in the hub.
#>
param(
[string]$Hub = (Join-Path $env:USERPROFILE "AI-Skills"),
[string]$UserHome = $env:USERPROFILE,
[switch]$IncludePluginCache,
[switch]$WithGit
)
$ErrorActionPreference = "Stop"
$Reparse = [IO.FileAttributes]::ReparsePoint
# NOTE about Loomy:
# 1. Loomy loads skills from <Loomy install dir>\opencode\skills. The opencode.json
# skills.paths cannot be used to add extra dirs: Loomy's template-sync mechanism
# overwrites that file on startup. So we junction hub skills directly into that dir.
# 2. The instance id "6d02254fbb40" may change after a Loomy upgrade - update the line
# below if it does.
# 3. Inside the Loomy runtime, $env:USERPROFILE points to the Loomy data dir, NOT the
# real user profile. Always pass -Hub and -UserHome explicitly when run from Loomy.
$targets = @(
"$UserHome\.workbuddy\skills",
"$UserHome\.workbuddy-ai\skills",
"$UserHome\.codebuddy\skills",
"$UserHome\.trae-cn\skills",
"C:\Users\Public\Loomy\6d02254fbb40\opencode\skills",
"$UserHome\.codex\skills",
"$UserHome\.claude\skills",
"$UserHome\.agents\skills"
)
function Test-Link([string]$p) {
if (-not (Test-Path -LiteralPath $p)) { return $false }
return (((Get-Item -LiteralPath $p -Force).Attributes -band $Reparse) -ne 0)
}
function Remove-Link([string]$p) {
cmd /c "rmdir `"$p`"" 2>&1 | Out-Null
}
function Test-Skill([string]$p) {
return (Test-Path -LiteralPath (Join-Path $p "SKILL.md"))
}
Write-Host ""
Write-Host "== Skills Hub ==" -ForegroundColor Cyan
Write-Host "Hub: $Hub" -ForegroundColor Cyan
Write-Host ""
# --- Stage 1: harvest -------------------------------------------------------
if (-not (Test-Path -LiteralPath $Hub)) {
New-Item -ItemType Directory -Path $Hub -Force | Out-Null
}
foreach ($t in $targets) {
if (-not (Test-Path -LiteralPath $t)) { continue }
if (Test-Link $t) { continue }
# Safety: only harvest agent dirs under $UserHome. Agents installed outside the
# user profile (e.g. Loomy under C:\Users\Public) must never be moved into the hub.
if (-not $t.StartsWith($UserHome, [System.StringComparison]::OrdinalIgnoreCase)) { continue }
# Skip junctions: those are links we created ourselves, never harvest them back.
Get-ChildItem -LiteralPath $t -Force -Directory -ErrorAction SilentlyContinue | Where-Object {
$_.Name -notlike ".*" -and -not (Test-Link $_.FullName) -and (Test-Skill $_.FullName)
} | ForEach-Object {
$dest = Join-Path $Hub $_.Name
if (-not (Test-Path -LiteralPath $dest)) {
Write-Host " harvest : $($_.Name)" -ForegroundColor Yellow
Move-Item -LiteralPath $_.FullName -Destination $dest
}
}
}
if ($IncludePluginCache) {
# Marketplace / plugin skills live inside the plugin cache and can vanish when
# you switch accounts. Copy them into a vault so they never disappear.
# The vault is NOT linked into any client (no SKILL.md at its top level),
# so it can never clash with the plugin that is already loaded.
$vault = Join-Path $Hub "_plugin-vault"
if (-not (Test-Path -LiteralPath $vault)) {
New-Item -ItemType Directory -Path $vault -Force | Out-Null
}
$cache = "$UserHome\.workbuddy\plugins\cache"
if (Test-Path -LiteralPath $cache) {
Get-ChildItem -LiteralPath $cache -Recurse -Filter "SKILL.md" -File -ErrorAction SilentlyContinue | ForEach-Object {
$d = $_.Directory
$dest = Join-Path $vault $d.Name
if (-not (Test-Path -LiteralPath $dest)) {
Write-Host " vault plugin : $($d.Name)" -ForegroundColor Yellow
Copy-Item -LiteralPath $d.FullName -Destination $dest -Recurse
}
}
}
}
# --- Stage 2: link ----------------------------------------------------------
$skills = @(Get-ChildItem -LiteralPath $Hub -Force -Directory | Where-Object {
$_.Name -notlike ".*" -and (Test-Skill $_.FullName)
})
Write-Host ""
Write-Host "Hub holds $($skills.Count) skills. Linking into every client..." -ForegroundColor Cyan
Write-Host ""
foreach ($t in $targets) {
if (Test-Link $t) { Remove-Link $t }
if (-not (Test-Path -LiteralPath $t)) {
New-Item -ItemType Directory -Path $t -Force | Out-Null
}
# drop junctions that no longer exist in the hub
Get-ChildItem -LiteralPath $t -Force -Directory -ErrorAction SilentlyContinue | ForEach-Object {
if ((Test-Link $_.FullName) -and -not (Test-Path -LiteralPath (Join-Path $Hub $_.Name))) {
Remove-Link $_.FullName
}
}
$n = 0
foreach ($s in $skills) {
$link = Join-Path $t $s.Name
if (Test-Link $link) { $n++; continue }
if (Test-Path -LiteralPath $link) {
Write-Host " real dir kept : $link" -ForegroundColor DarkGray
continue
}
New-Item -ItemType Junction -Path $link -Target $s.FullName | Out-Null
$n++
}
Write-Host (" {0,-3} linked -> {1}" -f $n, $t) -ForegroundColor Green
}
# --- Stage 3: git -----------------------------------------------------------
if ($WithGit) {
$gitDir = Join-Path $Hub ".git"
if (-not (Test-Path -LiteralPath $gitDir)) {
Push-Location $Hub
git init -q
git add -A
git commit -q -m "init skills hub"
Pop-Location
Write-Host ""
Write-Host "git repo created in hub." -ForegroundColor Green
}
else {
Push-Location $Hub
git add -A
git commit -q -m "sync $(Get-Date -Format 'yyyy-MM-dd HH:mm')"
Pop-Location
Write-Host ""
Write-Host "hub committed to git." -ForegroundColor Green
}
}
Write-Host ""
Write-Host "Done. Restart WorkBuddy / Claude Code / Codex so they rescan skills." -ForegroundColor Cyan
SyncSkillsHub.bat
作用:做环境预处理,然后调用同目录下 PowerShell 脚本 Sync-SkillsHub.ps1,用来同步 AI-Skills 相关资源,
@echo off
chcp 65001 >nul
rem NOTE: pass -Hub / -UserHome explicitly - inside the Loomy runtime the
rem USERPROFILE env var points to the Loomy data dir, not the real user profile.
powershell -ExecutionPolicy Bypass -File "%~dp0Sync-SkillsHub.ps1" -Hub "%USERPROFILE%\AI-Skills" -UserHome "%USERPROFILE%"
pause