# ===================================================================== # 技能安装器(Windows,多 agent 目标) # 作用:把本项目 skills\ 下全部技能安装到指定的用户级技能目录: # trae-cn -> %USERPROFILE%\.trae-cn\skills\ (Trae CN / Trae SOLO) # zcode -> %USERPROFILE%\.zcode\skills\ (ZCode CLI) # 默认安装全部技能到全部已知目录;也可用 -Agent、-Skills 只装一部分, # 或用 -DryRun 预览。 # 用法:双击同目录下的 install-skills.bat(推荐),或 # powershell -NoProfile -ExecutionPolicy Bypass -File install-skills.ps1 # powershell ... -Agent zcode -Skills yt-studio-url-builder,youtube-studio-csv-download # powershell ... -Agent all -DryRun # ===================================================================== param( # 目标 agent:trae-cn(默认)/ zcode / all。all = 装到下面全部已知目标。 [string]$Agent = "trae-cn", # 只安装这些技能(逗号/分号/空格分隔,匹配技能文件夹名,不区分大小写)。缺省=全部。 [string[]]$Skills, # 只预览将要安装/备份/跳过的技能,不实际复制。 [switch]$DryRun ) $ErrorActionPreference = "Stop" # --- 已知目标:agent 名 -> 用户级技能目录 ----------------------------------- # 新增 agent 支持时在此登记即可,其余逻辑不变。 $AgentTargets = @{ "trae-cn" = ".trae-cn\skills" "zcode" = ".zcode\skills" } # --- 1. 解析目标目录(可能多个) --- if ($AgentTargets.ContainsKey($Agent)) { $destRelList = @($AgentTargets[$Agent]) } elseif ($Agent -eq "all") { $destRelList = @($AgentTargets.Values) } else { Write-Host "[错误] 未知 agent: $Agent(可选: $($AgentTargets.Keys -join ', ') | all)" -ForegroundColor Red exit 1 } $ScriptDir = $PSScriptRoot $ProjectRoot = Split-Path -Parent $ScriptDir $SkillsSource = Join-Path $ProjectRoot "skills" Write-Host "=== 技能安装器 ===" -ForegroundColor Cyan Write-Host "技能源目录: $SkillsSource" Write-Host ("安装目标: " + (($destRelList | ForEach-Object { "$env:USERPROFILE\$_" }) -join ", ")) # 把 -Skills 参数拆成规范化名字列表(兼容逗号/分号/空格分隔) $skillFilter = @() if ($Skills) { $skillFilter = @( $Skills | ForEach-Object { $_ -split '[,;]' } | ForEach-Object { $_.Trim() } | Where-Object { $_ } ) } # --- 2. 校验源目录并收集技能(含 SKILL.md 的子目录才算) --- if (-not (Test-Path $SkillsSource)) { Write-Host "[错误] 找不到技能源目录: $SkillsSource" -ForegroundColor Red exit 1 } $allSkills = @(Get-ChildItem -Path $SkillsSource -Directory | Where-Object { Test-Path (Join-Path $_.FullName "SKILL.md") }) if ($allSkills.Count -eq 0) { Write-Host "[错误] 源目录下没有可用技能(每个技能文件夹需包含 SKILL.md)。" -ForegroundColor Red exit 1 } # 可选:漏了 SKILL.md 的目录会被跳过;给个提示,避免以为是 bug $skippedDirs = @(Get-ChildItem -Path $SkillsSource -Directory | Where-Object { -not (Test-Path (Join-Path $_.FullName "SKILL.md")) }) foreach ($d in $skippedDirs) { Write-Host "[跳过] $($d.Name)(缺 SKILL.md,不作为技能)" -ForegroundColor DarkGray } # 按 -Skills 过滤 $targets = if ($skillFilter.Count -gt 0) { $wanted = @($skillFilter | ForEach-Object { $_.ToLowerInvariant() }) @($allSkills | Where-Object { $wanted -contains $_.Name.ToLowerInvariant() }) } else { $allSkills } # 过滤后校验:用户指定的技能名是否存在 if ($skillFilter.Count -gt 0 -and $targets.Count -lt $skillFilter.Count) { $found = @($targets | ForEach-Object { $_.Name.ToLowerInvariant() }) $missing = @($skillFilter | Where-Object { $found -notcontains $_.ToLowerInvariant() }) Write-Host ("[警告] 未找到这些技能: {0}" -f ($missing -join ", ")) -ForegroundColor Yellow } if ($targets.Count -eq 0) { Write-Host "[错误] 没有待安装的技能(-Skills 指定的技能不存在,或源目录无可用技能)。" -ForegroundColor Red exit 1 } # --- 3. 读取每个技能的 frontmatter name,并校验其与文件夹名一致 --- # 预测性关键:各 agent 以 frontmatter 的 name 作为技能名,若与文件夹名不一致会引发歧义/旧名残留。 $warnCount = 0 foreach ($skill in $targets) { $skillFile = Join-Path $skill.FullName "SKILL.md" $fmName = $null try { $lines = Get-Content -Path $skillFile -TotalCount 6 -Encoding UTF8 foreach ($l in $lines) { if ($l -match '^\s*name\s*[:=]\s*["'']([^"'']+)["'']') { $fmName = $Matches[1] break } } } catch { # 读取失败不阻塞安装 } if ($fmName -and $fmName -ne $skill.Name) { $warnCount++ Write-Host ("[警告] {0}: frontmatter name 为 {1},与文件夹名不一致。" -f $skill.Name, $fmName) -ForegroundColor Yellow } elseif (-not $fmName) { $warnCount++ Write-Host ("[警告] {0}: SKILL.md 顶部未解析到 name,安装后可能无法触发。" -f $skill.Name) -ForegroundColor Yellow } } Write-Host ("发现 {0} 个技能: {1}" -f $targets.Count, ($targets.Name -join ", ")) Write-Host "" # --- 4. 对每个目标目录执行安装(备份旧版 -> 复制) --- $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" $totalBackup = 0 $totalFresh = 0 $failedTargets = @() foreach ($destRel in $destRelList) { $DestSkills = Join-Path $env:USERPROFILE $destRel $BackupRoot = (Join-Path $env:USERPROFILE ($destRel -replace '\\skills$', '')) + "\skills-backup" if ($DryRun) { Write-Host "== 预览 [$env:USERPROFILE\$destRel](未执行)==" -ForegroundColor Cyan $freshNames = @($targets | Where-Object { -not (Test-Path (Join-Path $DestSkills $_.Name)) }) $backupNames = @($targets | Where-Object { Test-Path (Join-Path $DestSkills $_.Name) }) if ($freshNames.Count -gt 0) { Write-Host (" 新安装 {0}: {1}" -f $freshNames.Count, ($freshNames.Name -join ", ")) } if ($backupNames.Count -gt 0) { Write-Host (" 覆盖安装(旧版将备份){0}: {1}" -f $backupNames.Count, ($backupNames.Name -join ", ")) } continue } Write-Host "--- 安装到 $DestSkills ---" -ForegroundColor Cyan New-Item -ItemType Directory -Force -Path $DestSkills | Out-Null foreach ($skill in $targets) { $dest = Join-Path $DestSkills $skill.Name try { if (Test-Path $dest) { New-Item -ItemType Directory -Force -Path $BackupRoot | Out-Null $backupPath = Join-Path $BackupRoot ("{0}-{1}" -f $skill.Name, $timestamp) Move-Item -Path $dest -Destination $backupPath $totalBackup++ Write-Host "[备份] $($skill.Name) 旧版 -> $backupPath" -ForegroundColor Yellow } Copy-Item -Path $skill.FullName -Destination $dest -Recurse -Force $totalFresh++ Write-Host "[安装] $($skill.Name)" -ForegroundColor Green # 校验该技能落盘完整 if (-not (Test-Path (Join-Path $DestSkills "$($skill.Name)\SKILL.md"))) { throw "SKILL.md 未落盘" } } catch { Write-Host "[FAIL] $($skill.Name) -> $DestSkills : $_" -ForegroundColor Red $failedTargets += "$destRel/$($skill.Name)" } } } # --- 5. 预览模式收尾 --- if ($DryRun) { Write-Host "" Write-Host "[DryRun] 完成,未做任何修改。" -ForegroundColor Green exit 0 } # --- 6. 汇总与后续提示 --- Write-Host "" if ($failedTargets.Count -gt 0) { Write-Host ("安装失败: {0}" -f ($failedTargets -join ", ")) -ForegroundColor Red exit 2 } Write-Host "全部安装成功。" -ForegroundColor Green Write-Host ("本次共写入 {0} 个技能实例(新装含覆盖),备份 {1} 个旧版。" -f ($totalFresh), $totalBackup) -ForegroundColor Green if ($warnCount -gt 0) { Write-Host ("注意:{0} 处 name/文件夹不一致或缺 name 的可疑技能(见上方警告)。" -f $warnCount) -ForegroundColor Yellow } if ($totalBackup -gt 0) { Write-Host "旧版本备份于各目标目录旁的 skills-backup(确认新版可用后可手动删除)" -ForegroundColor Yellow } Write-Host "" Write-Host "下一步: 重启对应 agent 或新建会话后,在对话中提及技能名或相关意图即可触发:" foreach ($s in $targets) { Write-Host " - $($s.Name)" }