Files
StudioLift/scripts/install-skills.ps1

192 lines
7.4 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# =====================================================================
# Trae CN 技能安装器Windows
# 作用:把本项目 skills\ 下全部技能安装到用户级技能目录
# %USERPROFILE%\.trae-cn\skills\
# 默认安装全部技能;也可用 -Skills 只装一部分,或用 -DryRun 预览。
# 用法:双击同目录下的 install-skills.bat推荐
# powershell -NoProfile -ExecutionPolicy Bypass -File install-skills.ps1
# powershell ... -Skills yt-studio-url-builder,youtube-studio-csv-download
# powershell ... -DryRun
# =====================================================================
param(
# 只安装这些技能(逗号/分号/空格分隔,匹配技能文件夹名,不区分大小写)。缺省=全部。
[string[]]$Skills,
# 只预览将要安装/备份/跳过的技能,不实际复制。
[switch]$DryRun
)
$ErrorActionPreference = "Stop"
# --- 1. 定位源目录与目标目录 ---
$ScriptDir = $PSScriptRoot
$ProjectRoot = Split-Path -Parent $ScriptDir
$SkillsSource = Join-Path $ProjectRoot "skills"
$TraeSkills = Join-Path $env:USERPROFILE ".trae-cn\skills"
# 备份放在技能目录外,避免被 Trae 当成技能重复扫描
$BackupRoot = Join-Path $env:USERPROFILE ".trae-cn\skills-backup"
Write-Host "=== Trae CN 技能安装器 ===" -ForegroundColor Cyan
Write-Host "技能源目录: $SkillsSource"
Write-Host "安装目标: $TraeSkills"
# 把 -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并校验其与文件夹名一致 ---
# 预测性关键Trae 以 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. 计算操作(备份/安装)并可选预览 ---
function Test-SkillInstalled([string]$skillName) {
return Test-Path (Join-Path $TraeSkills $skillName)
}
$toBackup = @($targets | Where-Object { Test-SkillInstalled $_.Name })
$toFresh = @($targets | Where-Object { -not (Test-SkillInstalled $_.Name) })
if ($DryRun) {
Write-Host "== 预览(未执行)==" -ForegroundColor Cyan
if ($toFresh.Count -gt 0) {
Write-Host (" 新安装 {0}: {1}" -f $toFresh.Count, ($toFresh.Name -join ", "))
}
if ($toBackup.Count -gt 0) {
Write-Host (" 覆盖安装(旧版将备份){0}: {1}" -f $toBackup.Count, ($toBackup.Name -join ", "))
}
Write-Host " 总技能数: $($targets.Count)"
Write-Host "同步目录: $TraeSkills"
Write-Host "备份目录: $BackupRoot"
Write-Host "[DryRun] 完成,未做任何修改。" -ForegroundColor Green
exit 0
}
# --- 5. 执行安装 ---
New-Item -ItemType Directory -Force -Path $TraeSkills | Out-Null
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$backupCount = 0
foreach ($skill in $targets) {
$dest = Join-Path $TraeSkills $skill.Name
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
$backupCount++
Write-Host "[备份] $($skill.Name) 旧版 -> $backupPath" -ForegroundColor Yellow
}
Copy-Item -Path $skill.FullName -Destination $dest -Recurse -Force
Write-Host "[安装] $($skill.Name)" -ForegroundColor Green
}
# --- 6. 校验安装结果 ---
Write-Host ""
Write-Host "=== 校验结果 ==="
$failed = @()
foreach ($skill in $targets) {
if (Test-Path (Join-Path $TraeSkills "$($skill.Name)\SKILL.md")) {
Write-Host " [OK] $($skill.Name)" -ForegroundColor Green
} else {
Write-Host " [FAIL] $($skill.Name)" -ForegroundColor Red
$failed += $skill.Name
}
}
# --- 7. 汇总与后续提示 ---
Write-Host ""
if ($failed.Count -gt 0) {
Write-Host ("安装失败: {0}" -f ($failed -join ", ")) -ForegroundColor Red
exit 2
}
Write-Host "全部安装成功。" -ForegroundColor Green
Write-Host ("本次:新建 {0} 个,覆盖 {1} 个(备份到技能目录外的 backups" -f $toFresh.Count, $toBackup.Count) -ForegroundColor Green
if ($warnCount -gt 0) {
Write-Host ("注意:{0} 处 name/文件夹不一致或缺 name 的可疑技能(见上方警告)。" -f $warnCount) -ForegroundColor Yellow
}
if ($backupCount -gt 0) {
Write-Host "旧版本备份于: $BackupRoot(确认新版可用后可手动删除)" -ForegroundColor Yellow
}
Write-Host ""
Write-Host "下一步: 重启 Trae CN 或新建会话后,在对话中提及技能名或相关意图即可触发:"
foreach ($s in $targets) {
Write-Host " - $($s.Name)"
}