Files
AI不止语 d475a81e3e fix(install): 支持 Hermes 多 profile,避免 skill 装错位置 (#102)
install.sh / install.ps1 原先只把 Hermes skill 装到根 <base>/skills,
多 profile 环境下 profiles/<name>/skills 完全不会被写入,导致静默错装。

- 新增 --profile / -Profile 指定目标 profile → <base>/profiles/<name>/skills
  (PowerShell 用 $HermesProfile + [Alias('Profile')] 规避自动变量 $Profile 遮蔽)
- 存在 profile 目录但未指定时报错退出并列出现有 profile,不再静默装到根目录
- <base> 统一解析 HERMES_HOME > $LOCALAPPDATA/hermes > ~/.hermes,detect 同步识别
- all 模式下单个工具失败不再中断其余安装,按失败数返回非零退出码
- 补充 --help / 用法说明;PowerShell 端 Join-Path 跨平台化

Closes #102

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 00:19:50 +08:00

536 lines
25 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.
# install.ps1 — 将智能体安装到本地 AI 工具中(Windows 版)
#
# 读取 integrations/ 中的转换文件,复制到各工具的配置目录。
# 请先运行 scripts\convert.ps1 生成集成文件。
#
# 用法:
# .\scripts\install.ps1 [-Tool <名称>] [-Profile <名称>] [-Help]
#
# 支持的工具:
# claude-code — 复制到 %USERPROFILE%\.claude\agents\
# copilot — 复制到 %USERPROFILE%\.github\agents\ 和 .copilot\agents\
# antigravity — 复制到 %USERPROFILE%\.gemini\antigravity\skills\
# gemini-cli — 安装到 %USERPROFILE%\.gemini\extensions\agency-agents\
# opencode — 复制到 .opencode\agents\(当前目录)
# cursor — 复制到 .cursor\rules\(当前目录)
# trae — 复制到 .trae\rules\(当前目录)
# aider — 复制 CONVENTIONS.md(当前目录)
# windsurf — 复制 .windsurfrules(当前目录)
# openclaw — 复制到 %USERPROFILE%\.openclaw\agency-agents\
# qwen — 复制到 .qwen\agents\(项目级)
# codex — 复制到 .codex\agents\(项目级)
# deerflow — 复制到 DeerFlow custom skills 目录(项目级)
# workbuddy — 复制到 %USERPROFILE%\.workbuddy\skills\(全局)
# hermes — 复制到 %USERPROFILE%\.hermes\skills\(全局)
# kiro — 复制到 %USERPROFILE%\.kiro\agents\(全局)
# all — 安装所有已检测到的工具(默认)
#
# Hermes 专属参数:
# -Category <名称[,名称...]> 只安装某一分类下的 skills,可传多个分类。
# Discord 模式下 Hermes 会把每个 skill 注册为斜杠命令,
# 总 JSON 超过 8000 字符会被 Discord API 拒绝 (error 50035)
# 若需要在 Discord 中使用建议按分类分批安装。
# -Profile <名称> 多 profile 环境下指定安装到哪个 profileissue #102)。
# Hermes 每个 profile 有独立 skill 库,路径为
# <base>\profiles\<名称>\skills\
# 其中 <base> = HERMES_HOME > $LOCALAPPDATA\hermes > ~\.hermes。
# 未指定 -Profile 却检测到已存在 profile 目录时会报错退出,
# 避免静默装到根目录。例如:-Tool hermes -Profile work
param(
[string]$Tool = "all",
[string[]]$Category = @(),
# 用 $HermesProfile 而非 $Profile$Profile 是 PowerShell 自动变量,避免遮蔽(issue #102)
[Alias('Profile')]
[string]$HermesProfile = "",
[switch]$Help
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# --- 路径 ---
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$RepoRoot = Split-Path -Parent $ScriptDir
$Integrations = Join-Path $RepoRoot "integrations"
$Home_ = $env:USERPROFILE
$AllTools = @(
"claude-code","copilot","antigravity","gemini-cli","opencode","openclaw",
"cursor","trae","aider","windsurf","qwen","codex","deerflow","workbuddy","hermes","kiro"
)
# --- 颜色输出 ---
function Write-OK { param($msg) Write-Host "[OK] $msg" -ForegroundColor Green }
function Write-Warn { param($msg) Write-Host "[!!] $msg" -ForegroundColor Yellow }
function Write-Err { param($msg) Write-Host "[ERR] $msg" -ForegroundColor Red }
function Write-Header { param($msg) Write-Host "`n$msg" -ForegroundColor White }
function Write-Dim { param($msg) Write-Host " $msg" -ForegroundColor DarkGray }
# --- 用法 ---
if ($Help) {
Get-Content $MyInvocation.MyCommand.Path | Select-Object -Skip 2 -First 36 |
ForEach-Object { $_ -replace '^# ?','' }
exit 0
}
# --- 检测 integrations/ ---
function Check-Integrations {
if (-not (Test-Path $Integrations)) {
Write-Err "integrations\ 不存在。请先运行 .\scripts\convert.ps1"
exit 1
}
}
# --- 工具检测 ---
function Detect-Tool {
param([string]$ToolName)
switch ($ToolName) {
"claude-code" { Test-Path (Join-Path $Home_ ".claude") }
"copilot" { (Get-Command code -ErrorAction SilentlyContinue) -or
(Test-Path (Join-Path $Home_ ".github")) -or
(Test-Path (Join-Path $Home_ ".copilot")) }
"antigravity" { Test-Path (Join-Path $Home_ ".gemini\antigravity\skills") }
"gemini-cli" { (Get-Command gemini -ErrorAction SilentlyContinue) -or
(Test-Path (Join-Path $Home_ ".gemini")) }
"opencode" { (Get-Command opencode -ErrorAction SilentlyContinue) -or
(Test-Path (Join-Path $Home_ ".config\opencode")) }
"cursor" { (Get-Command cursor -ErrorAction SilentlyContinue) -or
(Test-Path (Join-Path $Home_ ".cursor")) }
"trae" { (Get-Command trae -ErrorAction SilentlyContinue) -or
(Test-Path (Join-Path $Home_ ".trae")) }
"aider" { Get-Command aider -ErrorAction SilentlyContinue }
"windsurf" { (Get-Command windsurf -ErrorAction SilentlyContinue) -or
(Test-Path (Join-Path $Home_ ".codeium")) }
"openclaw" { (Get-Command openclaw -ErrorAction SilentlyContinue) -or
(Test-Path (Join-Path $Home_ ".openclaw")) }
"qwen" { (Get-Command qwen -ErrorAction SilentlyContinue) -or
(Test-Path (Join-Path $Home_ ".qwen")) }
"codex" { Get-Command codex -ErrorAction SilentlyContinue }
"deerflow" { (Get-Command deerflow -ErrorAction SilentlyContinue) -or
(Test-Path (Join-Path $Home_ ".deerflow")) }
"workbuddy" { (Get-Command workbuddy -ErrorAction SilentlyContinue) -or
(Test-Path (Join-Path $Home_ ".workbuddy")) }
"hermes" { (Get-Command hermes -ErrorAction SilentlyContinue) -or
(Test-Path (Join-Path $Home_ ".hermes")) -or
($env:HERMES_HOME -and (Test-Path $env:HERMES_HOME)) -or
($env:LOCALAPPDATA -and (Test-Path (Join-Path $env:LOCALAPPDATA "hermes"))) }
"kiro" { (Get-Command kiro -ErrorAction SilentlyContinue) -or
(Get-Command kiro-cli -ErrorAction SilentlyContinue) -or
(Test-Path (Join-Path $Home_ ".kiro")) }
default { $false }
}
}
function Get-ToolLabel {
param([string]$ToolName)
switch ($ToolName) {
"claude-code" { "Claude Code (%USERPROFILE%\.claude\agents)" }
"copilot" { "Copilot (%USERPROFILE%\.github + .copilot)" }
"antigravity" { "Antigravity (%USERPROFILE%\.gemini\antigravity)" }
"gemini-cli" { "Gemini CLI (gemini 扩展)" }
"opencode" { "OpenCode (.opencode\agents)" }
"openclaw" { "OpenClaw (%USERPROFILE%\.openclaw)" }
"cursor" { "Cursor (.cursor\rules)" }
"trae" { "Trae (.trae\rules)" }
"aider" { "Aider (CONVENTIONS.md)" }
"windsurf" { "Windsurf (.windsurfrules)" }
"qwen" { "Qwen Code (.qwen\agents)" }
"codex" { "Codex CLI (.codex\agents)" }
"deerflow" { "DeerFlow (skills\custom)" }
"workbuddy" { "WorkBuddy (%USERPROFILE%\.workbuddy\skills)" }
"hermes" { "Hermes Agent (%USERPROFILE%\.hermes\skills)" }
"kiro" { "Kiro (%USERPROFILE%\.kiro\agents)" }
default { $ToolName }
}
}
# --- 安装器 ---
function Install-ClaudeCode {
$dest = Join-Path $Home_ ".claude\agents"
New-Item -ItemType Directory -Force -Path $dest | Out-Null
$count = 0
foreach ($dir in @("academic","design","engineering","finance","game-development","hr","legal",
"marketing","paid-media","sales","product","project-management",
"supply-chain","testing","support","spatial-computing","specialized")) {
$dirPath = Join-Path $RepoRoot $dir
if (-not (Test-Path $dirPath)) { continue }
Get-ChildItem -Path $dirPath -Filter "*.md" -Recurse | ForEach-Object {
$firstLine = Get-Content $_.FullName -TotalCount 1
if ($firstLine -eq "---") { Copy-Item $_.FullName -Destination $dest; $count++ }
}
}
Write-OK "Claude Code: $count 个智能体 -> $dest"
}
function Install-Copilot {
$dest1 = Join-Path $Home_ ".github\agents"
$dest2 = Join-Path $Home_ ".copilot\agents"
New-Item -ItemType Directory -Force -Path $dest1 | Out-Null
New-Item -ItemType Directory -Force -Path $dest2 | Out-Null
$count = 0
foreach ($dir in @("academic","design","engineering","finance","game-development","hr","legal",
"marketing","paid-media","sales","product","project-management",
"supply-chain","testing","support","spatial-computing","specialized")) {
$dirPath = Join-Path $RepoRoot $dir
if (-not (Test-Path $dirPath)) { continue }
Get-ChildItem -Path $dirPath -Filter "*.md" -Recurse | ForEach-Object {
$firstLine = Get-Content $_.FullName -TotalCount 1
if ($firstLine -eq "---") {
Copy-Item $_.FullName -Destination $dest1
Copy-Item $_.FullName -Destination $dest2
$count++
}
}
}
Write-OK "Copilot: $count 个智能体 -> $dest1 + $dest2"
}
function Install-Antigravity {
$src = Join-Path $Integrations "antigravity"
$dest = Join-Path $Home_ ".gemini\antigravity\skills"
if (-not (Test-Path $src)) { Write-Err "integrations\antigravity 不存在,请先运行 convert.ps1"; return }
$count = 0
Get-ChildItem -Path $src -Directory | ForEach-Object {
$skillDest = Join-Path $dest $_.Name
New-Item -ItemType Directory -Force -Path $skillDest | Out-Null
Copy-Item (Join-Path $_.FullName "SKILL.md") -Destination $skillDest
$count++
}
Write-OK "Antigravity: $count 个 skills -> $dest"
}
function Install-GeminiCli {
$src = Join-Path $Integrations "gemini-cli"
$dest = Join-Path $Home_ ".gemini\extensions\agency-agents"
if (-not (Test-Path $src)) { Write-Err "integrations\gemini-cli 不存在,请先运行 convert.ps1"; return }
New-Item -ItemType Directory -Force -Path (Join-Path $dest "skills") | Out-Null
Copy-Item (Join-Path $src "gemini-extension.json") -Destination $dest
$count = 0
Get-ChildItem -Path (Join-Path $src "skills") -Directory | ForEach-Object {
$skillDest = Join-Path (Join-Path $dest "skills") $_.Name
New-Item -ItemType Directory -Force -Path $skillDest | Out-Null
Copy-Item (Join-Path $_.FullName "SKILL.md") -Destination $skillDest
$count++
}
Write-OK "Gemini CLI: $count 个 skills -> $dest"
}
function Install-OpenCode {
$src = Join-Path $Integrations "opencode\agents"
$dest = Join-Path (Get-Location) ".opencode\agents"
if (-not (Test-Path $src)) { Write-Err "integrations\opencode 不存在,请先运行 convert.ps1"; return }
New-Item -ItemType Directory -Force -Path $dest | Out-Null
$count = (Get-ChildItem -Path $src -Filter "*.md" | ForEach-Object { Copy-Item $_.FullName -Destination $dest; 1 } | Measure-Object -Sum).Sum
Write-OK "OpenCode: $count 个智能体 -> $dest"
Write-Warn "OpenCode: 项目级安装,请在项目根目录运行。"
}
function Install-OpenClaw {
$src = Join-Path $Integrations "openclaw"
$dest = Join-Path $Home_ ".openclaw\agency-agents"
if (-not (Test-Path $src)) { Write-Err "integrations\openclaw 不存在,请先运行 convert.ps1"; return }
$count = 0
Get-ChildItem -Path $src -Directory | ForEach-Object {
$wsDest = Join-Path $dest $_.Name
New-Item -ItemType Directory -Force -Path $wsDest | Out-Null
Copy-Item (Join-Path $_.FullName "SOUL.md") -Destination $wsDest
Copy-Item (Join-Path $_.FullName "AGENTS.md") -Destination $wsDest
Copy-Item (Join-Path $_.FullName "IDENTITY.md") -Destination $wsDest
# 注册到 openclaw(如果可用)
if (Get-Command openclaw -ErrorAction SilentlyContinue) {
$existing = & openclaw agents list 2>$null
if ($existing -notmatch $_.Name) {
& openclaw agents add $_.Name --workspace $wsDest --non-interactive 2>$null
} else {
Write-Dim "跳过已注册: $($_.Name)"
}
}
$count++
}
Write-OK "OpenClaw: $count 个工作空间 -> $dest"
if (Get-Command openclaw -ErrorAction SilentlyContinue) {
Write-Warn "OpenClaw: 运行 'openclaw gateway restart' 激活新智能体"
}
}
function Install-Cursor {
$src = Join-Path $Integrations "cursor\rules"
$dest = Join-Path (Get-Location) ".cursor\rules"
if (-not (Test-Path $src)) { Write-Err "integrations\cursor 不存在,请先运行 convert.ps1"; return }
New-Item -ItemType Directory -Force -Path $dest | Out-Null
$count = (Get-ChildItem -Path $src -Filter "*.mdc" | ForEach-Object { Copy-Item $_.FullName -Destination $dest; 1 } | Measure-Object -Sum).Sum
Write-OK "Cursor: $count 个规则 -> $dest"
Write-Warn "Cursor: 项目级安装,请在项目根目录运行。"
}
function Install-Trae {
$src = Join-Path $Integrations "trae\rules"
$dest = Join-Path (Get-Location) ".trae\rules"
if (-not (Test-Path $src)) { Write-Err "integrations\trae 不存在,请先运行 convert.ps1"; return }
New-Item -ItemType Directory -Force -Path $dest | Out-Null
$count = (Get-ChildItem -Path $src -Filter "*.md" | ForEach-Object { Copy-Item $_.FullName -Destination $dest; 1 } | Measure-Object -Sum).Sum
Write-OK "Trae: $count 个规则 -> $dest"
Write-Warn "Trae: 项目级安装,请在项目根目录运行。"
}
function Install-Aider {
$src = Join-Path $Integrations "aider\CONVENTIONS.md"
$dest = Join-Path (Get-Location) "CONVENTIONS.md"
if (-not (Test-Path $src)) { Write-Err "integrations\aider\CONVENTIONS.md 不存在,请先运行 convert.ps1"; return }
if (Test-Path $dest) { Write-Warn "Aider: CONVENTIONS.md 已存在,删除后重试。"; return }
Copy-Item $src -Destination $dest
Write-OK "Aider: 已安装 -> $dest"
Write-Warn "Aider: 项目级安装,请在项目根目录运行。"
}
function Install-Windsurf {
$src = Join-Path $Integrations "windsurf\.windsurfrules"
$dest = Join-Path (Get-Location) ".windsurfrules"
if (-not (Test-Path $src)) { Write-Err "integrations\windsurf\.windsurfrules 不存在,请先运行 convert.ps1"; return }
if (Test-Path $dest) { Write-Warn "Windsurf: .windsurfrules 已存在,删除后重试。"; return }
Copy-Item $src -Destination $dest
Write-OK "Windsurf: 已安装 -> $dest"
Write-Warn "Windsurf: 项目级安装,请在项目根目录运行。"
}
function Install-Qwen {
$src = Join-Path $Integrations "qwen\agents"
$dest = Join-Path (Get-Location) ".qwen\agents"
if (-not (Test-Path $src)) { Write-Err "integrations\qwen 不存在,请先运行 convert.ps1"; return }
New-Item -ItemType Directory -Force -Path $dest | Out-Null
$count = (Get-ChildItem -Path $src -Filter "*.md" | ForEach-Object { Copy-Item $_.FullName -Destination $dest; 1 } | Measure-Object -Sum).Sum
Write-OK "Qwen Code: $count 个智能体 -> $dest"
Write-Warn "Qwen Code: 项目级安装,请在项目根目录运行。"
}
function Install-Codex {
$src = Join-Path $Integrations "codex\agents"
$dest = Join-Path (Get-Location) ".codex\agents"
if (-not (Test-Path $src)) { Write-Err "integrations\codex 不存在,请先运行 convert.ps1"; return }
New-Item -ItemType Directory -Force -Path $dest | Out-Null
$count = (Get-ChildItem -Path $src -Filter "*.toml" | ForEach-Object { Copy-Item $_.FullName -Destination $dest; 1 } | Measure-Object -Sum).Sum
Write-OK "Codex CLI: $count 个智能体 -> $dest"
Write-Warn "Codex CLI: 项目级安装,请在项目根目录运行。"
}
function Install-DeerFlow {
$src = Join-Path $Integrations "deerflow"
$dest = if ($env:DEERFLOW_SKILLS_DIR) { $env:DEERFLOW_SKILLS_DIR } else { Join-Path (Get-Location) "skills\custom" }
if (-not (Test-Path $src)) { Write-Err "integrations\deerflow 不存在,请先运行 convert.ps1"; return }
$count = 0
Get-ChildItem -Path $src -Directory | ForEach-Object {
$skillFile = Join-Path $_.FullName "SKILL.md"
if (Test-Path $skillFile) {
$skillDest = Join-Path $dest $_.Name
New-Item -ItemType Directory -Force -Path $skillDest | Out-Null
Copy-Item $skillFile -Destination $skillDest
$count++
}
}
Write-OK "DeerFlow: $count 个 skills -> $dest"
Write-Warn "DeerFlow: 设置 `$env:DEERFLOW_SKILLS_DIR 可自定义路径。"
}
function Install-WorkBuddy {
$src = Join-Path $Integrations "workbuddy"
$dest = Join-Path $Home_ ".workbuddy\skills"
if (-not (Test-Path $src)) { Write-Err "integrations\workbuddy 不存在,请先运行 convert.ps1 -Tool workbuddy"; return }
$count = 0
Get-ChildItem -Path $src -Directory | ForEach-Object {
$skillFile = Join-Path $_.FullName "SKILL.md"
if (Test-Path $skillFile) {
$skillDest = Join-Path $dest $_.Name
New-Item -ItemType Directory -Force -Path $skillDest | Out-Null
Copy-Item $skillFile -Destination $skillDest
$count++
}
}
Write-OK "WorkBuddy: $count 个 skills -> $dest"
}
# Hermes 安装根目录(不含 profile):HERMES_HOME > $LOCALAPPDATA\hermes > ~\.hermesissue #82/#102
function Get-HermesBaseDir {
if ($env:HERMES_HOME) { return $env:HERMES_HOME }
if ($env:LOCALAPPDATA -and (Test-Path (Join-Path $env:LOCALAPPDATA "hermes"))) {
return (Join-Path $env:LOCALAPPDATA "hermes")
}
return (Join-Path $Home_ ".hermes")
}
# 现有 profile 名称(<base>\profiles\<name>\
function Get-HermesProfileNames {
param($base)
$pdir = Join-Path $base "profiles"
if (-not (Test-Path $pdir)) { return @() }
@(Get-ChildItem -Path $pdir -Directory -ErrorAction SilentlyContinue | ForEach-Object Name)
}
function Install-Hermes {
$src = Join-Path $Integrations "hermes"
if (-not (Test-Path $src)) { Write-Err "integrations\hermes 不存在,请先运行 convert.ps1 -Tool hermes"; $script:FailedTools += "hermes"; return }
# 安装目录解析(issue #82 / #102):
# 1. -Profile <name> -> <base>\profiles\<name>\skills
# 2. 存在 profile 目录但未指定 -Profile -> 报错退出(避免静默装到根目录)
# 3. 默认 -> <base>\skills
$base = Get-HermesBaseDir
$profileNote = ""
if ($HermesProfile) {
$profileDir = Join-Path (Join-Path $base "profiles") $HermesProfile
$dest = Join-Path $profileDir "skills"
$profileNote = " [profile: $HermesProfile]"
if (-not (Test-Path $profileDir)) {
$existing = (Get-HermesProfileNames $base) -join ", "
Write-Warn "profile '$HermesProfile' 尚不存在,将新建目录 $base\profiles\$HermesProfile\。"
Write-Warn "请确认名称无误(现有 profile: $existing)。"
}
} else {
$names = @(Get-HermesProfileNames $base)
if ($names.Count -gt 0) {
Write-Err "检测到 Hermes profile 目录($base\profiles\),但未指定 -Profile。"
Write-Err "为避免装错位置,请用 -Profile <名称> 指定目标 profile。"
Write-Err "现有 profile: $($names -join ', ')"
Write-Err "或设置 HERMES_HOME 指向单一 profile 根目录后重试。"
$script:FailedTools += "hermes"
return
}
$dest = Join-Path $base "skills"
}
$filterNote = ""
if ($Category.Count -gt 0) {
foreach ($c in $Category) {
if (-not (Test-Path (Join-Path $src $c))) {
$avail = (Get-ChildItem -Path $src -Directory | ForEach-Object Name) -join ", "
Write-Err "hermes 分类不存在: $c(可选: $avail"
$script:FailedTools += "hermes"
return
}
}
$filterNote = " [分类: $($Category -join ', ')]"
}
$count = 0
# 保留两级目录结构:category/skill-name/SKILL.md
Get-ChildItem -Path $src -Directory | ForEach-Object {
$catName = $_.Name
if ($Category.Count -gt 0 -and ($Category -notcontains $catName)) { return }
Get-ChildItem -Path $_.FullName -Directory | ForEach-Object {
$skillFile = Join-Path $_.FullName "SKILL.md"
if (Test-Path $skillFile) {
$skillDest = Join-Path $dest "$catName\$($_.Name)"
New-Item -ItemType Directory -Force -Path $skillDest | Out-Null
Copy-Item $skillFile -Destination $skillDest
$count++
}
}
}
Write-OK "Hermes Agent: $count 个 skills -> $dest$filterNote$profileNote"
if ($Category.Count -eq 0 -and $count -gt 80) {
Write-Warn "Hermes Discord 模式对斜杠命令总长有 8000 字符上限(error 50035)。"
Write-Warn "若要在 Discord 中使用,建议用 -Category <名称> 按分类分批安装。"
}
}
function Install-Kiro {
$src = Join-Path $Integrations "kiro"
$dest = Join-Path $Home_ ".kiro\agents"
if (-not (Test-Path $src)) { Write-Err "integrations\kiro 不存在,请先运行 convert.ps1"; return }
New-Item -ItemType Directory -Force -Path $dest | Out-Null
# Kiro 自定义智能体格式:.md 文件(带 YAML frontmatter
# 参考:https://kiro.dev/docs/chat/subagents/
$count = (Get-ChildItem -Path $src -Filter "*.md" | ForEach-Object { Copy-Item $_.FullName -Destination $dest; 1 } | Measure-Object -Sum).Sum
Write-OK "Kiro: $count 个智能体 -> $dest"
Write-Warn "提示: Kiro 会自动识别 ~/.kiro/agents/ 下的 .md 文件作为自定义子智能体"
Write-Warn "提示: 在对话中使用 '/<agent-name>' 或让 Kiro 自动选择合适的子智能体"
}
function Install-Tool {
param([string]$ToolName)
switch ($ToolName) {
"claude-code" { Install-ClaudeCode }
"copilot" { Install-Copilot }
"antigravity" { Install-Antigravity }
"gemini-cli" { Install-GeminiCli }
"opencode" { Install-OpenCode }
"openclaw" { Install-OpenClaw }
"cursor" { Install-Cursor }
"trae" { Install-Trae }
"aider" { Install-Aider }
"windsurf" { Install-Windsurf }
"qwen" { Install-Qwen }
"codex" { Install-Codex }
"deerflow" { Install-DeerFlow }
"workbuddy" { Install-WorkBuddy }
"hermes" { Install-Hermes }
"kiro" { Install-Kiro }
}
}
# --- 入口 ---
Check-Integrations
if ($Category.Count -gt 0 -and $Tool -ne "hermes") {
Write-Warn "-Category 仅对 -Tool hermes 生效,已忽略。"
$Category = @()
}
if ($HermesProfile -and $Tool -ne "hermes") {
Write-Warn "-Profile 仅对 -Tool hermes 生效,已忽略。"
$HermesProfile = ""
}
$selectedTools = @()
if ($Tool -ne "all") {
if ($Tool -notin $AllTools) {
Write-Err "未知工具 '$Tool'。可选: $($AllTools -join ', ')"
exit 1
}
$selectedTools = @($Tool)
} else {
Write-Header "AI 智能体专家团队 -- 扫描已安装的工具..."
Write-Host ""
foreach ($t in $AllTools) {
if (Detect-Tool $t) {
$selectedTools += $t
Write-Host " [*] $(Get-ToolLabel $t) 已检测到" -ForegroundColor Green
} else {
Write-Host " [ ] $(Get-ToolLabel $t) 未找到" -ForegroundColor DarkGray
}
}
}
if ($selectedTools.Count -eq 0) {
Write-Warn "未选择或检测到任何工具。"
Write-Dim "提示: 使用 -Tool <名称> 强制安装指定工具。"
Write-Dim "可选: $($AllTools -join ', ')"
exit 0
}
Write-Host ""
Write-Header "AI 智能体专家团队 -- 安装智能体"
Write-Host " 仓库: $RepoRoot"
Write-Host " 安装到: $($selectedTools -join ', ')"
Write-Host ""
# 单个工具失败(如 Hermes 多 profile 未指定 -Profile)不应中断其余工具的安装(issue #102)
$script:FailedTools = @()
foreach ($t in $selectedTools) {
Install-Tool $t
}
$failedCount = @($script:FailedTools).Count
$installedCount = $selectedTools.Count - $failedCount
Write-Host ""
if ($failedCount -gt 0) {
Write-Warn "完成:已安装 $installedCount 个工具,$failedCount 个未完成(见上方提示)。"
} else {
Write-OK "完成!已安装 $installedCount 个工具。"
}
Write-Host ""
Write-Dim "运行 .\scripts\convert.ps1 重新生成集成文件。"
Write-Host ""
if ($failedCount -gt 0) { exit 1 }