refactor: 拆分 claude-dev-stack 为 windows-dev-stack 和 wsl-dev-stack

将原 claude-dev-stack 目录拆分为独立的 Windows 和 WSL 部署栈,便于分别维护和使用。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-29 01:11:20 +08:00
parent e8693dad2a
commit dd3eb24d0f
488 changed files with 33927 additions and 0 deletions

View File

@@ -0,0 +1,741 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Windows 原生 Claude Code CLI + GitHub Copilot CLI + MCP + Rust Token Killer 一键部署脚本
.DESCRIPTION
不依赖 WSL2全部组件原生运行在 Windows 上。完成以下步骤:
1. Node.js LTS 检测 / 安装winget
2. Claude Code CLI (@anthropic-ai/claude-code) 安装
3. GitHub CLI + Copilot CLI 扩展安装winget + gh extension
4. Unity MCP Server 安装 & 写入各 AI 客户端 MCP 配置
5. Godot MCP Pro 安装(本地包)& 写入各 AI 客户端 MCP 配置
6. Rust 工具链 + RTK (Rust Token Killer) 安装
7. RTK hook 注册到 Claude Code + Copilot Instructions 生成
8. PowerShell Profile 快捷命令写入
.PARAMETER BridgePort
Unity MCP Bridge 端口,默认 7890
.PARAMETER SkipFirewall
跳过防火墙规则配置
.NOTES
防火墙规则需要管理员权限,其余步骤普通用户即可运行。
脚本可重复运行(幂等),已安装的组件自动跳过。
#>
param(
[int] $BridgePort = 7890,
[string]$GodotMcpVersion = "godot-mcp-pro-v1.14.1",
[switch]$SkipFirewall
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# ──────────────────────────────────────────────────────────────
# 颜色日志辅助
# ──────────────────────────────────────────────────────────────
function Write-Step { param($msg) Write-Host "`n==== $msg ====" -ForegroundColor Cyan }
function Write-OK { param($msg) Write-Host " [OK] $msg" -ForegroundColor Green }
function Write-Info { param($msg) Write-Host " [..] $msg" -ForegroundColor DarkGray }
function Write-Warn { param($msg) Write-Host " [!!] $msg" -ForegroundColor Yellow }
function Write-Fail { param($msg) Write-Host " [ERR] $msg" -ForegroundColor Red }
# ──────────────────────────────────────────────────────────────
# 加载 .env 配置
# ──────────────────────────────────────────────────────────────
$ScriptDir = $PSScriptRoot
$EnvFile = Join-Path $ScriptDir ".env"
$EnvExample = Join-Path $ScriptDir ".env.example"
if (-not (Test-Path $EnvFile)) {
if (Test-Path $EnvExample) {
Copy-Item $EnvExample $EnvFile
Write-Warn "已从 .env.example 创建 .env请按需填写后重新运行"
Write-Warn " notepad `"$EnvFile`""
exit 0
}
}
$Cfg = @{}
if (Test-Path $EnvFile) {
Get-Content $EnvFile | Where-Object { $_ -match "^\s*[^#].*=" } | ForEach-Object {
$parts = $_ -split "=", 2
$key = $parts[0].Trim()
$val = $parts[1].Trim().Trim('"').Trim("'")
$Cfg[$key] = $val
}
Write-Info "已加载配置:$EnvFile"
}
$ANTHROPIC_API_KEY = $Cfg["ANTHROPIC_API_KEY"] ?? ""
$ANTHROPIC_AUTH_TOKEN = $Cfg["ANTHROPIC_AUTH_TOKEN"] ?? ""
$ANTHROPIC_BASE_URL = $Cfg["ANTHROPIC_BASE_URL"] ?? "https://api.lmuai.com"
$CLAUDE_MODEL = $Cfg["CLAUDE_MODEL"] ?? "claude-sonnet-4-6"
$BRIDGE_PORT_CFG = $Cfg["BRIDGE_PORT"] ?? "$BridgePort"
$UnityHubPath = $Cfg["UNITY_HUB_PATH"] ?? "C:\Program Files\Unity Hub\Unity Hub.exe"
$UnityMcpInstallDir = $Cfg["UNITY_MCP_INSTALL_DIR"] ?? "$env:USERPROFILE\unity-mcp-server"
$GodotMcpPackageDir = $Cfg["GODOT_MCP_PACKAGE_DIR"] ?? "."
# 参数 BridgePort 优先于 .env
if ($BridgePort -eq 7890 -and $BRIDGE_PORT_CFG -ne "7890") { $BridgePort = [int]$BRIDGE_PORT_CFG }
# 判断认证方式
$UseLmuAuth = ($ANTHROPIC_AUTH_TOKEN -ne "" -and $ANTHROPIC_BASE_URL -ne "https://api.anthropic.com")
# ──────────────────────────────────────────────────────────────
# Banner
# ──────────────────────────────────────────────────────────────
Write-Host ""
Write-Host "╔══════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host "║ Windows Native: Claude Code + Copilot CLI + MCP + RTK ║" -ForegroundColor Cyan
Write-Host "║ Unity MCP · Godot MCP Pro · Rust Token Killer ║" -ForegroundColor Cyan
Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
Write-Host ""
# ──────────────────────────────────────────────────────────────
# 辅助:刷新当前进程 PATH
# ──────────────────────────────────────────────────────────────
function Refresh-Path {
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" +
[System.Environment]::GetEnvironmentVariable("Path", "User")
}
# ──────────────────────────────────────────────────────────────
# 辅助winget 安装(静默)
# ──────────────────────────────────────────────────────────────
function Install-Winget {
param([string]$Id, [string]$DisplayName)
$wg = Get-Command winget -ErrorAction SilentlyContinue
if (-not $wg) {
Write-Warn "未找到 winget请手动安装 $DisplayName"
return $false
}
Write-Info "winget install $Id ..."
winget install --id $Id --silent --accept-source-agreements --accept-package-agreements 2>&1 | Out-Null
Refresh-Path
return $true
}
# ──────────────────────────────────────────────────────────────
# 辅助:写入 / 合并 MCP 配置文件JSON
# ──────────────────────────────────────────────────────────────
function Merge-McpConfig {
param(
[string] $ConfigFile,
[hashtable] $Entry,
[string] $Label,
[string] $ServerName = "unity-mcp"
)
$dir = Split-Path $ConfigFile
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
function ConvertTo-Hashtable($obj) {
if ($obj -is [System.Management.Automation.PSCustomObject]) {
$h = @{}
foreach ($p in $obj.PSObject.Properties) { $h[$p.Name] = ConvertTo-Hashtable $p.Value }
return $h
} elseif ($obj -is [System.Collections.IEnumerable] -and $obj -isnot [string]) {
return @($obj | ForEach-Object { ConvertTo-Hashtable $_ })
}
return $obj
}
$cfg = @{ mcpServers = @{} }
if (Test-Path $ConfigFile) {
try {
$raw = Get-Content $ConfigFile -Raw | ConvertFrom-Json
$converted = ConvertTo-Hashtable $raw
if ($converted -is [hashtable]) { $cfg = $converted }
} catch {}
}
if (-not $cfg.ContainsKey("mcpServers")) { $cfg["mcpServers"] = @{} }
$cfg["mcpServers"][$ServerName] = $Entry
$cfg | ConvertTo-Json -Depth 10 | Set-Content $ConfigFile -Encoding UTF8
Write-OK "$Label -> $ConfigFile"
}
# ══════════════════════════════════════════════════════════════
# Step 1: Node.js LTS
# ══════════════════════════════════════════════════════════════
Write-Step "1/8 Node.js LTS"
$nodeCmd = Get-Command node -ErrorAction SilentlyContinue
if ($nodeCmd) {
Write-OK "Node.js 已安装: $(& node --version)"
} else {
Write-Info "通过 winget 安装 Node.js LTS..."
$ok = Install-Winget "OpenJS.NodeJS.LTS" "Node.js LTS"
$nodeCmd = Get-Command node -ErrorAction SilentlyContinue
if ($nodeCmd) {
Write-OK "Node.js 安装完成: $(& node --version)"
} else {
Write-Warn "未能自动安装 Node.js请手动安装后重新运行"
Write-Warn " https://nodejs.org/zh-cn/download 或 winget install OpenJS.NodeJS.LTS"
}
}
# ══════════════════════════════════════════════════════════════
# Step 2: Claude Code CLI
# ══════════════════════════════════════════════════════════════
Write-Step "2/8 Claude Code CLI"
$claudeCmd = Get-Command claude -ErrorAction SilentlyContinue
if ($claudeCmd) {
Write-OK "Claude Code 已安装: $(& claude --version 2>&1 | Select-Object -First 1)"
} else {
if (Get-Command node -ErrorAction SilentlyContinue) {
Write-Info "安装 @anthropic-ai/claude-code ..."
& npm install -g @anthropic-ai/claude-code --quiet 2>&1 | Out-Null
Refresh-Path
$claudeCmd = Get-Command claude -ErrorAction SilentlyContinue
if ($claudeCmd) {
Write-OK "Claude Code 安装完成: $(& claude --version 2>&1 | Select-Object -First 1)"
} else {
Write-Warn "安装后未找到 claude 命令,请重开终端后重试"
}
} else {
Write-Warn "Node.js 未就绪,跳过 Claude Code 安装"
}
}
# ── 写入 Claude Code 配置settings.json──────────────────
$mcpAllowedTools = @(
"Bash","Read","Write","Edit","MultiEdit","Glob","Grep","LS","WebFetch","TodoRead","TodoWrite",
# Unity MCP
"mcp__unity-mcp__unity_editor_ping","mcp__unity-mcp__unity_editor_state","mcp__unity-mcp__unity_project_info",
"mcp__unity-mcp__unity_scene_info","mcp__unity-mcp__unity_scene_open","mcp__unity-mcp__unity_scene_save",
"mcp__unity-mcp__unity_scene_new","mcp__unity-mcp__unity_scene_hierarchy","mcp__unity-mcp__unity_scene_stats",
"mcp__unity-mcp__unity_gameobject_create","mcp__unity-mcp__unity_gameobject_delete",
"mcp__unity-mcp__unity_gameobject_info","mcp__unity-mcp__unity_gameobject_set_transform",
"mcp__unity-mcp__unity_gameobject_duplicate","mcp__unity-mcp__unity_gameobject_set_active",
"mcp__unity-mcp__unity_gameobject_reparent",
"mcp__unity-mcp__unity_component_add","mcp__unity-mcp__unity_component_remove",
"mcp__unity-mcp__unity_component_get_properties","mcp__unity-mcp__unity_component_set_property",
"mcp__unity-mcp__unity_component_set_reference","mcp__unity-mcp__unity_component_batch_wire",
"mcp__unity-mcp__unity_component_get_referenceable",
"mcp__unity-mcp__unity_asset_list","mcp__unity-mcp__unity_asset_import",
"mcp__unity-mcp__unity_asset_delete","mcp__unity-mcp__unity_asset_create_prefab",
"mcp__unity-mcp__unity_asset_instantiate_prefab",
"mcp__unity-mcp__unity_script_create","mcp__unity-mcp__unity_script_read",
"mcp__unity-mcp__unity_script_update","mcp__unity-mcp__unity_execute_code",
"mcp__unity-mcp__unity_material_create","mcp__unity-mcp__unity_renderer_set_material",
"mcp__unity-mcp__unity_build","mcp__unity-mcp__unity_play_mode",
"mcp__unity-mcp__unity_console_log","mcp__unity-mcp__unity_console_clear",
"mcp__unity-mcp__unity_get_compilation_errors",
"mcp__unity-mcp__unity_execute_menu_item","mcp__unity-mcp__unity_undo",
"mcp__unity-mcp__unity_redo","mcp__unity-mcp__unity_undo_history",
"mcp__unity-mcp__unity_selection_get","mcp__unity-mcp__unity_selection_set",
"mcp__unity-mcp__unity_selection_focus_scene_view","mcp__unity-mcp__unity_selection_find_by_type",
"mcp__unity-mcp__unity_search_by_component","mcp__unity-mcp__unity_search_by_tag",
"mcp__unity-mcp__unity_search_by_layer","mcp__unity-mcp__unity_search_by_name",
"mcp__unity-mcp__unity_search_assets","mcp__unity-mcp__unity_search_missing_references",
"mcp__unity-mcp__unity_screenshot_game","mcp__unity-mcp__unity_screenshot_scene",
"mcp__unity-mcp__unity_graphics_scene_capture","mcp__unity-mcp__unity_graphics_game_capture",
"mcp__unity-mcp__unity_prefab_info","mcp__unity-mcp__unity_set_object_reference",
"mcp__unity-mcp__unity_packages_list","mcp__unity-mcp__unity_packages_add",
"mcp__unity-mcp__unity_packages_remove","mcp__unity-mcp__unity_packages_search",
"mcp__unity-mcp__unity_packages_info",
"mcp__unity-mcp__unity_queue_info","mcp__unity-mcp__unity_agents_list","mcp__unity-mcp__unity_agent_log",
"mcp__unity-mcp__unity_list_advanced_tools","mcp__unity-mcp__unity_advanced_tool",
"mcp__unity-mcp__unity_hub_list_editors","mcp__unity-mcp__unity_hub_available_releases",
"mcp__unity-mcp__unity_hub_install_editor","mcp__unity-mcp__unity_hub_install_modules",
"mcp__unity-mcp__unity_hub_get_install_path","mcp__unity-mcp__unity_hub_set_install_path",
"mcp__unity-mcp__unity_list_instances","mcp__unity-mcp__unity_select_instance",
"mcp__unity-mcp__unity_get_project_context",
# Godot MCP Pro
"mcp__godot-mcp-pro__get_project_info","mcp__godot-mcp-pro__get_filesystem_tree",
"mcp__godot-mcp-pro__search_files","mcp__godot-mcp-pro__search_in_files",
"mcp__godot-mcp-pro__get_project_settings","mcp__godot-mcp-pro__get_project_statistics",
"mcp__godot-mcp-pro__uid_to_project_path","mcp__godot-mcp-pro__project_path_to_uid",
"mcp__godot-mcp-pro__get_scene_tree","mcp__godot-mcp-pro__get_scene_file_content",
"mcp__godot-mcp-pro__get_scene_exports","mcp__godot-mcp-pro__get_scene_dependencies",
"mcp__godot-mcp-pro__create_scene","mcp__godot-mcp-pro__open_scene",
"mcp__godot-mcp-pro__save_scene","mcp__godot-mcp-pro__add_scene_instance",
"mcp__godot-mcp-pro__get_node_properties","mcp__godot-mcp-pro__get_node_groups",
"mcp__godot-mcp-pro__get_signals","mcp__godot-mcp-pro__find_nodes_in_group",
"mcp__godot-mcp-pro__find_nodes_by_type","mcp__godot-mcp-pro__find_signal_connections",
"mcp__godot-mcp-pro__find_node_references",
"mcp__godot-mcp-pro__add_node","mcp__godot-mcp-pro__duplicate_node",
"mcp__godot-mcp-pro__move_node","mcp__godot-mcp-pro__rename_node",
"mcp__godot-mcp-pro__update_property","mcp__godot-mcp-pro__add_resource",
"mcp__godot-mcp-pro__set_anchor_preset",
"mcp__godot-mcp-pro__connect_signal","mcp__godot-mcp-pro__disconnect_signal",
"mcp__godot-mcp-pro__set_node_groups",
"mcp__godot-mcp-pro__batch_set_property","mcp__godot-mcp-pro__cross_scene_set_property",
"mcp__godot-mcp-pro__list_scripts","mcp__godot-mcp-pro__read_script",
"mcp__godot-mcp-pro__get_open_scripts","mcp__godot-mcp-pro__validate_script",
"mcp__godot-mcp-pro__create_script","mcp__godot-mcp-pro__edit_script",
"mcp__godot-mcp-pro__attach_script",
"mcp__godot-mcp-pro__get_editor_errors","mcp__godot-mcp-pro__get_output_log",
"mcp__godot-mcp-pro__get_editor_screenshot","mcp__godot-mcp-pro__get_editor_performance",
"mcp__godot-mcp-pro__clear_output","mcp__godot-mcp-pro__reload_plugin",
"mcp__godot-mcp-pro__reload_project",
"mcp__godot-mcp-pro__read_resource","mcp__godot-mcp-pro__get_resource_preview",
"mcp__godot-mcp-pro__create_resource","mcp__godot-mcp-pro__edit_resource",
"mcp__godot-mcp-pro__read_shader","mcp__godot-mcp-pro__get_shader_params",
"mcp__godot-mcp-pro__create_shader","mcp__godot-mcp-pro__edit_shader",
"mcp__godot-mcp-pro__assign_shader_material","mcp__godot-mcp-pro__set_shader_param",
"mcp__godot-mcp-pro__list_animations","mcp__godot-mcp-pro__get_animation_info",
"mcp__godot-mcp-pro__create_animation","mcp__godot-mcp-pro__add_animation_track",
"mcp__godot-mcp-pro__set_animation_keyframe",
"mcp__godot-mcp-pro__get_animation_tree_structure",
"mcp__godot-mcp-pro__create_animation_tree","mcp__godot-mcp-pro__add_state_machine_state",
"mcp__godot-mcp-pro__add_state_machine_transition",
"mcp__godot-mcp-pro__set_blend_tree_node","mcp__godot-mcp-pro__set_tree_parameter",
"mcp__godot-mcp-pro__get_audio_bus_layout","mcp__godot-mcp-pro__get_audio_info",
"mcp__godot-mcp-pro__add_audio_bus","mcp__godot-mcp-pro__set_audio_bus",
"mcp__godot-mcp-pro__add_audio_bus_effect","mcp__godot-mcp-pro__add_audio_player",
"mcp__godot-mcp-pro__get_input_actions","mcp__godot-mcp-pro__set_input_action",
"mcp__godot-mcp-pro__get_physics_layers","mcp__godot-mcp-pro__get_collision_info",
"mcp__godot-mcp-pro__set_physics_layers",
"mcp__godot-mcp-pro__add_mesh_instance","mcp__godot-mcp-pro__setup_lighting",
"mcp__godot-mcp-pro__set_material_3d","mcp__godot-mcp-pro__setup_environment",
"mcp__godot-mcp-pro__setup_camera_3d","mcp__godot-mcp-pro__add_gridmap",
"mcp__godot-mcp-pro__setup_collision","mcp__godot-mcp-pro__add_raycast",
"mcp__godot-mcp-pro__setup_physics_body",
"mcp__godot-mcp-pro__get_navigation_info",
"mcp__godot-mcp-pro__setup_navigation_region","mcp__godot-mcp-pro__bake_navigation_mesh",
"mcp__godot-mcp-pro__setup_navigation_agent","mcp__godot-mcp-pro__set_navigation_layers",
"mcp__godot-mcp-pro__get_particle_info",
"mcp__godot-mcp-pro__create_particles","mcp__godot-mcp-pro__set_particle_material",
"mcp__godot-mcp-pro__set_particle_color_gradient","mcp__godot-mcp-pro__apply_particle_preset",
"mcp__godot-mcp-pro__get_theme_info",
"mcp__godot-mcp-pro__create_theme","mcp__godot-mcp-pro__set_theme_color",
"mcp__godot-mcp-pro__set_theme_constant","mcp__godot-mcp-pro__set_theme_font_size",
"mcp__godot-mcp-pro__set_theme_stylebox",
"mcp__godot-mcp-pro__tilemap_get_cell","mcp__godot-mcp-pro__tilemap_get_info",
"mcp__godot-mcp-pro__tilemap_get_used_cells",
"mcp__godot-mcp-pro__tilemap_set_cell","mcp__godot-mcp-pro__tilemap_fill_rect",
"mcp__godot-mcp-pro__get_autoload","mcp__godot-mcp-pro__add_autoload",
"mcp__godot-mcp-pro__set_project_setting",
"mcp__godot-mcp-pro__find_unused_resources","mcp__godot-mcp-pro__analyze_signal_flow",
"mcp__godot-mcp-pro__analyze_scene_complexity","mcp__godot-mcp-pro__find_script_references",
"mcp__godot-mcp-pro__detect_circular_dependencies",
"mcp__godot-mcp-pro__get_performance_monitors",
"mcp__godot-mcp-pro__get_export_info","mcp__godot-mcp-pro__list_export_presets",
"mcp__godot-mcp-pro__play_scene","mcp__godot-mcp-pro__stop_scene",
"mcp__godot-mcp-pro__get_game_scene_tree","mcp__godot-mcp-pro__get_game_node_properties",
"mcp__godot-mcp-pro__get_game_screenshot","mcp__godot-mcp-pro__set_game_node_property",
"mcp__godot-mcp-pro__find_nodes_by_script",
"mcp__godot-mcp-pro__simulate_key","mcp__godot-mcp-pro__simulate_mouse_click",
"mcp__godot-mcp-pro__simulate_mouse_move","mcp__godot-mcp-pro__simulate_action",
"mcp__godot-mcp-pro__simulate_sequence",
"mcp__godot-mcp-pro__capture_frames","mcp__godot-mcp-pro__record_frames",
"mcp__godot-mcp-pro__monitor_properties","mcp__godot-mcp-pro__batch_get_properties",
"mcp__godot-mcp-pro__start_recording","mcp__godot-mcp-pro__stop_recording",
"mcp__godot-mcp-pro__replay_recording",
"mcp__godot-mcp-pro__find_ui_elements","mcp__godot-mcp-pro__click_button_by_text",
"mcp__godot-mcp-pro__wait_for_node","mcp__godot-mcp-pro__find_nearby_nodes",
"mcp__godot-mcp-pro__navigate_to","mcp__godot-mcp-pro__move_to",
"mcp__godot-mcp-pro__run_test_scenario","mcp__godot-mcp-pro__assert_node_state",
"mcp__godot-mcp-pro__assert_screen_text","mcp__godot-mcp-pro__run_stress_test",
"mcp__godot-mcp-pro__get_test_report","mcp__godot-mcp-pro__compare_screenshots"
)
$claudeSettingsJson = if ($UseLmuAuth) {
[ordered]@{
env = [ordered]@{
ANTHROPIC_BASE_URL = $ANTHROPIC_BASE_URL
ANTHROPIC_AUTH_TOKEN = $ANTHROPIC_AUTH_TOKEN
API_TIMEOUT_MS = "3000000"
CLAUDE_CODE_ATTRIBUTION_HEADER = "0"
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1"
}
model = $CLAUDE_MODEL
allowedTools = $mcpAllowedTools
} | ConvertTo-Json -Depth 4
} else {
[ordered]@{ model = $CLAUDE_MODEL; allowedTools = $mcpAllowedTools } | ConvertTo-Json -Depth 3
}
$claudeDir = "$env:USERPROFILE\.claude"
if (-not (Test-Path $claudeDir)) { New-Item -ItemType Directory -Path $claudeDir | Out-Null }
$claudeSettingsJson | Set-Content "$claudeDir\settings.json" -Encoding UTF8
if ($UseLmuAuth) {
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $null, "User")
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", $null, "User")
[System.Environment]::SetEnvironmentVariable("CLAUDE_MODEL", $null, "User")
} elseif ($ANTHROPIC_API_KEY) {
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $ANTHROPIC_API_KEY, "User")
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", $ANTHROPIC_BASE_URL, "User")
[System.Environment]::SetEnvironmentVariable("CLAUDE_MODEL", $CLAUDE_MODEL, "User")
}
Write-OK "Claude Code 配置已写入 $claudeDir\settings.json"
# ══════════════════════════════════════════════════════════════
# Step 3: GitHub CLI + Copilot CLI 扩展
# ══════════════════════════════════════════════════════════════
Write-Step "3/8 GitHub CLI + Copilot CLI"
$ghCmd = Get-Command gh -ErrorAction SilentlyContinue
if ($ghCmd) {
Write-OK "GitHub CLI 已安装: $(& gh --version 2>&1 | Select-Object -First 1)"
} else {
Write-Info "通过 winget 安装 GitHub CLI..."
$ok = Install-Winget "GitHub.cli" "GitHub CLI"
$ghCmd = Get-Command gh -ErrorAction SilentlyContinue
if ($ghCmd) {
Write-OK "GitHub CLI 安装完成: $(& gh --version 2>&1 | Select-Object -First 1)"
} else {
Write-Warn "GitHub CLI 安装后未找到请重开终端或手动安装winget install GitHub.cli"
}
}
if ($ghCmd) {
$extList = & gh extension list 2>&1 | Out-String
if ($extList -match "github/gh-copilot") {
Write-OK "GitHub Copilot CLI 扩展已安装"
} else {
Write-Info "安装 GitHub Copilot CLI 扩展..."
try {
& gh extension install github/gh-copilot 2>&1 | Out-Null
Write-OK "GitHub Copilot CLI 扩展安装完成入口gh copilot"
} catch {
Write-Warn "安装失败(可能需要先 gh auth login请手动运行gh extension install github/gh-copilot"
}
}
}
# ══════════════════════════════════════════════════════════════
# Step 4: Unity MCP Server
# ══════════════════════════════════════════════════════════════
Write-Step "4/8 Unity MCP Server"
Write-Info "安装目录: $UnityMcpInstallDir"
$gitCmd = Get-Command git -ErrorAction SilentlyContinue
if (-not $gitCmd) {
Write-Warn "未找到 git跳过 Unity MCP Server 安装"
Write-Warn "请安装 Git for Windowswinget install Git.Git"
$UnityMcpReady = $false
} else {
if (Test-Path "$UnityMcpInstallDir\.git") {
Write-Info "已存在,更新至最新..."
Push-Location $UnityMcpInstallDir
try { & git pull --quiet 2>&1 | Out-Null } finally { Pop-Location }
} else {
if (Test-Path $UnityMcpInstallDir) { Remove-Item $UnityMcpInstallDir -Recurse -Force }
& git clone --quiet --depth 1 https://github.com/AnkleBreaker-Studio/unity-mcp-server.git $UnityMcpInstallDir
}
if (Get-Command node -ErrorAction SilentlyContinue) {
Push-Location $UnityMcpInstallDir
try { & npm install --prefer-offline --quiet 2>&1 | Out-Null } finally { Pop-Location }
Write-OK "Unity MCP Server 就绪"
$UnityMcpReady = $true
} else {
Write-Warn "Node.js 未就绪,跳过 npm install"
$UnityMcpReady = $false
}
}
# ── 确定入口文件 ──────────────────────────────────────────────
$unityEntry = ""
if ($UnityMcpReady) {
foreach ($f in @("src/index.js","build/index.js","index.js")) {
$fp = Join-Path $UnityMcpInstallDir $f
if (Test-Path $fp) { $unityEntry = $fp -replace "\\","/"; break }
}
}
# ── 写入 MCP 配置(四大 AI 客户端 + Claude Code CLI─────────
if ($unityEntry) {
$unityMcpEntry = @{
command = "node"
args = @($unityEntry)
env = @{ UNITY_HUB_PATH = $UnityHubPath; UNITY_BRIDGE_PORT = "$BridgePort" }
}
Merge-McpConfig "$env:APPDATA\Claude\claude_desktop_config.json" $unityMcpEntry "Claude Desktop"
Merge-McpConfig "$env:USERPROFILE\.cursor\mcp.json" $unityMcpEntry "Cursor"
$wsDirC = if (Test-Path "$env:APPDATA\Windsurf") { "$env:APPDATA\Windsurf" } else { "$env:USERPROFILE\.codeium\windsurf" }
Merge-McpConfig "$wsDirC\mcp_config.json" $unityMcpEntry "Windsurf"
Merge-McpConfig "$env:APPDATA\Code\User\mcp.json" $unityMcpEntry "VS Code"
# Claude Code CLIWindows
if (Get-Command claude -ErrorAction SilentlyContinue) {
& claude mcp remove unity-mcp --scope user 2>&1 | Out-Null
& claude mcp add --scope user unity-mcp node $unityEntry -e "UNITY_BRIDGE_PORT=$BridgePort" 2>&1 | Out-Null
Write-OK "Claude Code CLI MCP -> unity-mcp"
}
}
# ══════════════════════════════════════════════════════════════
# Step 5: Godot MCP Pro
# ══════════════════════════════════════════════════════════════
Write-Step "5/8 Godot MCP Pro"
# 解析 Godot MCP Pro 本地包路径(相对于本脚本目录)
$godotPkgBase = if ([System.IO.Path]::IsPathRooted($GodotMcpPackageDir)) {
$GodotMcpPackageDir
} else {
[System.IO.Path]::GetFullPath((Join-Path $ScriptDir $GodotMcpPackageDir))
}
$godotSrcDir = Join-Path $godotPkgBase "$GodotMcpVersion\server"
$godotWinDir = "$env:USERPROFILE\godot-mcp-pro"
if (-not (Test-Path $godotSrcDir)) {
Write-Warn "未找到 Godot MCP Pro 源目录: $godotSrcDir"
Write-Warn "请确认 .env 中 GODOT_MCP_PACKAGE_DIR 指向包含 $GodotMcpVersion\ 的目录"
$godotEntry = ""
} else {
Write-Info "同步 server/ -> $godotWinDir"
$rcArgs = @($godotSrcDir, $godotWinDir, "/E", "/XD", "node_modules",
"/NFL", "/NDL", "/NJH", "/NJS", "/nc", "/ns", "/np")
& robocopy @rcArgs 2>&1 | Out-Null
if (Get-Command node -ErrorAction SilentlyContinue) {
Push-Location $godotWinDir
try { & node build/setup.js install 2>&1 | Out-Null } finally { Pop-Location }
Write-OK "Godot MCP Pro 就绪: $godotWinDir"
} else {
Write-Warn "Node.js 未就绪,跳过 Godot MCP Pro setup"
}
$godotEntry = ($godotWinDir -replace "\\","/") + "/build/index.js"
$godotMcpEntry = @{ command = "node"; args = @($godotEntry) }
Merge-McpConfig "$env:APPDATA\Claude\claude_desktop_config.json" $godotMcpEntry "Claude Desktop (Godot)" "godot-mcp-pro"
Merge-McpConfig "$env:USERPROFILE\.cursor\mcp.json" $godotMcpEntry "Cursor (Godot)" "godot-mcp-pro"
$wsDirG = if (Test-Path "$env:APPDATA\Windsurf") { "$env:APPDATA\Windsurf" } else { "$env:USERPROFILE\.codeium\windsurf" }
Merge-McpConfig "$wsDirG\mcp_config.json" $godotMcpEntry "Windsurf (Godot)" "godot-mcp-pro"
Merge-McpConfig "$env:APPDATA\Code\User\mcp.json" $godotMcpEntry "VS Code (Godot)" "godot-mcp-pro"
if (Get-Command claude -ErrorAction SilentlyContinue) {
& claude mcp remove godot-mcp-pro --scope user 2>&1 | Out-Null
& claude mcp add --scope user godot-mcp-pro node $godotEntry 2>&1 | Out-Null
Write-OK "Claude Code CLI MCP -> godot-mcp-pro"
}
}
# ══════════════════════════════════════════════════════════════
# Step 6: 防火墙放行 MCP Bridge 端口
# ══════════════════════════════════════════════════════════════
Write-Step "6/8 Windows 防火墙规则"
if ($SkipFirewall) {
Write-Warn "已跳过防火墙配置(-SkipFirewall"
} else {
$portRange = "$BridgePort-$($BridgePort + 9)"
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if ($isAdmin) {
foreach ($dir in @("Inbound","Outbound")) {
$name = "Unity MCP Bridge $dir"
Get-NetFirewallRule -DisplayName $name -ErrorAction SilentlyContinue |
Remove-NetFirewallRule -ErrorAction SilentlyContinue
New-NetFirewallRule -DisplayName $name -Direction $dir `
-LocalPort $portRange -Protocol TCP -Action Allow -Profile Any | Out-Null
Write-OK "防火墙规则: $name (TCP $portRange)"
}
} else {
Write-Warn "非管理员权限,尝试以提升权限添加防火墙规则..."
$fwCmd = @"
`$p = '$portRange'
foreach (`$d in @('Inbound','Outbound')) {
`$n = "Unity MCP Bridge `$d"
Get-NetFirewallRule -DisplayName `$n -ErrorAction SilentlyContinue | Remove-NetFirewallRule -ErrorAction SilentlyContinue
New-NetFirewallRule -DisplayName `$n -Direction `$d -LocalPort `$p -Protocol TCP -Action Allow -Profile Any | Out-Null
Write-Host "OK: `$n"
}
Start-Sleep 1
"@
$enc = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($fwCmd))
$proc = Start-Process powershell.exe -ArgumentList "-NoProfile -EncodedCommand $enc" `
-Verb RunAs -Wait -PassThru -ErrorAction SilentlyContinue
if ($proc -and $proc.ExitCode -eq 0) {
Write-OK "防火墙规则已添加 (TCP $portRange)"
} else {
Write-Warn "防火墙配置未成功请手动以管理员运行New-NetFirewallRule ... -LocalPort $portRange"
}
}
}
# ══════════════════════════════════════════════════════════════
# Step 7: Rust 工具链 & RTK
# ══════════════════════════════════════════════════════════════
Write-Step "7/8 Rust 工具链 & Token Killer"
# 若 .env 配置了国内镜像,提前注入环境变量
$rustupMirror = $Cfg["RUSTUP_DIST_SERVER"] ?? ""
$rustupRoot = $Cfg["RUSTUP_UPDATE_ROOT"] ?? ""
if ($rustupMirror) { $env:RUSTUP_DIST_SERVER = $rustupMirror }
if ($rustupRoot) { $env:RUSTUP_UPDATE_ROOT = $rustupRoot }
# 先把 cargo\bin 加入 PATHrustup 通过 winget 安装后 PATH 需刷新)
$cargoBin = "$env:USERPROFILE\.cargo\bin"
if (Test-Path $cargoBin) { $env:Path = "$cargoBin;$env:Path" }
Refresh-Path
$cargoCmd = Get-Command cargo -ErrorAction SilentlyContinue
if ($cargoCmd) {
# 检查是否有默认 toolchain若没有则安装 stable
$rustcVer = & rustc --version 2>&1 | Out-String
if ($rustcVer -match "no default is configured" -or $rustcVer -match "error:") {
Write-Info "rustup 无默认 toolchain安装 stable..."
& rustup toolchain install stable 2>&1 | Select-Object -Last 3
& rustup default stable 2>&1 | Out-Null
Refresh-Path
if (Test-Path $cargoBin) { $env:Path = "$cargoBin;$env:Path" }
$rustcVer = & rustc --version 2>&1 | Out-String
}
Write-OK "Rust 已安装: $($rustcVer.Trim())"
} else {
# 检查 winget 是否已记录安装(避免重复安装时阻塞)
$wgList = winget list --id Rustlang.Rustup --accept-source-agreements 2>&1 | Out-String
if ($wgList -match "Rustlang.Rustup") {
Write-Info "rustup 已安装,但当前会话 PATH 未更新。已将 $cargoBin 加入 PATH"
# rustup 首次运行会初始化 stable 工具链
if (Test-Path "$cargoBin\rustup.exe") { & "$cargoBin\rustup.exe" show 2>&1 | Out-Null }
$cargoCmd = Get-Command cargo -ErrorAction SilentlyContinue
if ($cargoCmd) { Write-OK "Rust: $(& rustc --version 2>&1)" }
} else {
Write-Info "通过 winget 安装 Rust (rustup)..."
$ok = Install-Winget "Rustlang.Rustup" "Rustup"
if (Test-Path $cargoBin) { $env:Path = "$cargoBin;$env:Path" }
Refresh-Path
$cargoCmd = Get-Command cargo -ErrorAction SilentlyContinue
if ($cargoCmd) {
Write-OK "Rust 安装完成: $(& rustc --version 2>&1)"
} else {
Write-Warn "Rust 安装后未找到 cargo请关闭并重开终端后继续"
Write-Warn " 或手动安装https://rustup.rs"
}
}
}
# 先把 ~/.local/bin 加入 PATH预编译包安装目录
$localBin = "$env:USERPROFILE\.local\bin"
if (Test-Path $localBin) { $env:Path = "$localBin;$env:Path" }
$rtkCmd = Get-Command rtk -ErrorAction SilentlyContinue
if ($rtkCmd) {
Write-OK "rtk 已安装: $(& rtk --version 2>&1)"
} else {
# 从 GitHub Releases 下载预编译包(比 cargo install 快得多,且无需编译环境)
Write-Info "安装 rtk (Rust Token Killer) - 下载预编译包..."
try {
$rtkVer = "v0.42.0"
$rtkUrl = "https://github.com/rtk-ai/rtk/releases/download/$rtkVer/rtk-x86_64-pc-windows-msvc.zip"
New-Item -ItemType Directory -Force -Path $localBin | Out-Null
$rtkZip = "$env:TEMP\rtk-windows.zip"
Invoke-WebRequest -Uri $rtkUrl -OutFile $rtkZip -UseBasicParsing
Expand-Archive -Path $rtkZip -DestinationPath $localBin -Force
Remove-Item $rtkZip -ErrorAction SilentlyContinue
$env:Path = "$localBin;$env:Path"
$rtkCmd = Get-Command rtk -ErrorAction SilentlyContinue
if ($rtkCmd) {
Write-OK "rtk 安装完成: $(& rtk --version 2>&1)"
} else {
Write-Warn "rtk 安装失败可手动运行cargo install --git https://github.com/rtk-ai/rtk"
}
} catch {
Write-Warn "rtk 下载失败: $_"
Write-Warn "可手动运行cargo install --git https://github.com/rtk-ai/rtk"
}
}
# ── rtk init -g注册 Claude Code PreToolUse hook ─────────────
if (Get-Command rtk -ErrorAction SilentlyContinue) {
Write-Info "注册 rtk hook 到 Claude Code..."
# 先禁用遥测,避免交互式确认提示阻塞管道
$prevEAP = $ErrorActionPreference
$ErrorActionPreference = "Continue"
& rtk telemetry disable 2>&1 | Out-Null
try {
& rtk init -g --auto-patch 2>&1 | Out-Null
Write-OK "rtk hook 已注册(重启 Claude Code 后生效)"
} catch {
Write-Warn "rtk init 异常hook 可能已注册): $_"
} finally {
$ErrorActionPreference = $prevEAP
}
# ── rtk Copilot Instructions写入 .github/copilot-instructions.md ──
# 使用已有的(位于仓库根 .github/),仅在缺失时生成
$copilotDir = Join-Path $ScriptDir ".." ".github"
$copilotInst = Join-Path $copilotDir "copilot-instructions.md"
if (-not (Test-Path $copilotInst)) {
Write-Info "生成 .github/copilot-instructions.md ..."
if (-not (Test-Path $copilotDir)) { New-Item -ItemType Directory -Path $copilotDir | Out-Null }
& rtk init -g --copilot 2>&1 | Out-Null
Write-OK "Copilot Instructions 已生成"
} else {
Write-OK "Copilot Instructions 已存在,跳过生成"
}
}
# ══════════════════════════════════════════════════════════════
# Step 8: PowerShell Profile 快捷命令
# ══════════════════════════════════════════════════════════════
Write-Step "8/8 PowerShell Profile"
$pwshProfile = "$env:USERPROFILE\Documents\PowerShell\Microsoft.PowerShell_profile.ps1"
$profileDir = Split-Path $pwshProfile
if (-not (Test-Path $profileDir)) { New-Item -ItemType Directory -Path $profileDir | Out-Null }
$profileBlock = @"
# Windows Dev Stack (generated by windows-dev-stack\deploy.ps1)
`$env:ANTHROPIC_BASE_URL = "$ANTHROPIC_BASE_URL"
$(if ($ANTHROPIC_API_KEY) { "`$env:ANTHROPIC_API_KEY = `"$ANTHROPIC_API_KEY`"" } else { "# ANTHROPIC_API_KEY= (配置 .env 后重新运行)" })
`$env:CLAUDE_MODEL = "$CLAUDE_MODEL"
# rtk / cargo PATH PATH
`$_localBin = "`$env:USERPROFILE\.local\bin"
if ((Test-Path `$_localBin) -and `$env:Path -notlike "*.local\bin*") { `$env:Path = "`$_localBin;`$env:Path" }
`$_cargoBin = "`$env:USERPROFILE\.cargo\bin"
if ((Test-Path `$_cargoBin) -and `$env:Path -notlike "*cargo\bin*") { `$env:Path = "`$_cargoBin;`$env:Path" }
function unity-mcp-status { Invoke-RestMethod http://127.0.0.1:$BridgePort/api/ping -ErrorAction SilentlyContinue }
#
"@
$existing = if (Test-Path $pwshProfile) { Get-Content $pwshProfile -Raw } else { "" }
if ($existing -match "Windows Dev Stack") {
# 注意:直接用 -replace 会将 $_ 等解释为正则反向引用导致内容爆炸
# 使用 MatchEvaluator scriptblock 让替换字符串按字面量处理
$blockCapture = $profileBlock
$existing = [regex]::Replace($existing, '(?ms)# ── Windows Dev Stack.*?# ─+', { $blockCapture })
Set-Content $pwshProfile $existing -Encoding UTF8
} else {
Add-Content $pwshProfile $profileBlock -Encoding UTF8
}
$policy = Get-ExecutionPolicy -Scope CurrentUser
if ($policy -in @("Restricted","Undefined")) {
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force
}
Write-OK "PowerShell Profile 已配置: $pwshProfile"
# ══════════════════════════════════════════════════════════════
# 安装总结
# ══════════════════════════════════════════════════════════════
Write-Host ""
Write-Host "╔══════════════════════════════════════════════════════════════╗" -ForegroundColor Green
Write-Host "║ ✅ Windows 原生全栈部署完成 ║" -ForegroundColor Green
Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Green
Write-Host ""
$nodeVer = if (Get-Command node -ErrorAction SilentlyContinue) { & node --version 2>&1 } else { "(未安装)" }
$claudeVer = if (Get-Command claude -ErrorAction SilentlyContinue) { & claude --version 2>&1 | Select-Object -First 1 } else { "(未安装)" }
$ghVer = if (Get-Command gh -ErrorAction SilentlyContinue) { & gh --version 2>&1 | Select-Object -First 1 } else { "(未安装)" }
$rustVer = if (Get-Command rustc -ErrorAction SilentlyContinue) { & rustc --version 2>&1 } else { "(未安装)" }
$rtkVer = if (Get-Command rtk -ErrorAction SilentlyContinue) { & rtk --version 2>&1 } else { "(未安装)" }
$items = @(
@("Node.js", $nodeVer),
@("Claude Code", $claudeVer),
@("GitHub CLI", $ghVer),
@("Rust", $rustVer),
@("rtk", $rtkVer),
@("Unity MCP", $(if ($unityEntry) { $unityEntry } else { "(未安装)" })),
@("Godot MCP Pro", $(if ($godotEntry) { $godotEntry } else { "(未安装)" })),
@("Bridge Port", "TCP $BridgePort (防火墙已放行 $BridgePort-$($BridgePort+9))")
)
foreach ($item in $items) {
$label = $item[0].PadRight(14)
Write-Host " $label $($item[1])" -ForegroundColor White
}
Write-Host ""
Write-Host " 后续手动步骤:" -ForegroundColor Yellow
Write-Host " 1. gh auth login # GitHub CLI 登录(首次)" -ForegroundColor DarkGray
Write-Host " 2. Unity Editor 安装 unity-mcp-plugin" -ForegroundColor DarkGray
Write-Host " https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git" -ForegroundColor DarkGray
Write-Host " 3. Godot 项目:复制 addons/godot_mcp/ 并在 Plugins 中启用" -ForegroundColor DarkGray
Write-Host " 源:$(Join-Path $godotPkgBase "$GodotMcpVersion\addons\godot_mcp")" -ForegroundColor DarkGray
Write-Host " 4. 重启 AI 客户端Claude Desktop / Cursor / Windsurf / VS Code" -ForegroundColor DarkGray
Write-Host ""