diff --git a/claude-dev-stack/.env.example b/claude-dev-stack/.env.example new file mode 100644 index 0000000..000b3b1 --- /dev/null +++ b/claude-dev-stack/.env.example @@ -0,0 +1,39 @@ +# ============================================================= +# Claude Dev Stack 配置文件 +# 复制为 .env 并按需填写 +# ============================================================= + +# ── Claude / Anthropic API ────────────────────────────────── +# Anthropic 官方 API Key(从 https://console.anthropic.com 获取) +ANTHROPIC_API_KEY= + +# API Base URL(默认使用 Anthropic 官方;若使用中转代理请修改) +# 示例(DeepSeek 兼容接口): +# ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic +ANTHROPIC_BASE_URL=https://api.anthropic.com + +# 默认使用的模型 +# 可选:claude-opus-4-5 | claude-sonnet-4-5 | claude-haiku-4-5 +# deepseek-v3-0324(配合 DeepSeek 接口使用) +CLAUDE_MODEL=claude-opus-4-5 + +# ── WSL2 ──────────────────────────────────────────────────── +# WSL2 发行版名称(wsl --list 查看已安装发行版) +WSL_DISTRO=Ubuntu + +# 设为 true 可跳过 WSL2 安装步骤(已安装时使用) +SKIP_WSL_INSTALL=false + +# ── Unity MCP ─────────────────────────────────────────────── +# unity-mcp-server 自动克隆至 WSL2 ~/.mcp-servers/unity-mcp-server/ +# Unity Plugin 需手动通过 Package Manager 安装: +# https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git +# (无需额外配置) + +# ── Rust / Token Killer ────────────────────────────────────── +# (暂无需配置,预留扩展用) +# CARGO_REGISTRY_MIRROR=https://rsproxy.cn/ + +# ── Docker 镜像加速(可选)────────────────────────────────── +# 若 WSL2 内需要 Docker,可配置国内加速镜像(逗号分隔) +# DOCKER_REGISTRY_MIRRORS=https://docker.m.daocloud.io,https://hub-mirror.c.163.com diff --git a/claude-dev-stack/.gitignore b/claude-dev-stack/.gitignore new file mode 100644 index 0000000..2d7ec5c --- /dev/null +++ b/claude-dev-stack/.gitignore @@ -0,0 +1,2 @@ +.env +node_modules/ diff --git a/claude-dev-stack/README.md b/claude-dev-stack/README.md new file mode 100644 index 0000000..4e36275 --- /dev/null +++ b/claude-dev-stack/README.md @@ -0,0 +1,418 @@ +# Claude Dev Stack + +WSL2 + Claude Code CLI + Unity MCP + Rust Token Killer **全栈一键部署方案**,适用于 Windows 11 开发环境。 + +支持 **v2rayN 代理自动检测**、**WSL2 代理透传**、**防火墙自动放行**。 + +## 组件清单 + +| 组件 | 说明 | 安装位置 | +|------|------|---------| +| **WSL2** | Windows Subsystem for Linux 2 | Windows 功能 | +| **Claude Code CLI** | `@anthropic-ai/claude-code` | WSL2 npm global | +| **Unity MCP Server** | `AnkleBreaker-Studio/unity-mcp-server` — Claude ↔ Unity Editor 双向集成 | WSL2 独立 Node.js 服务 + Unity Plugin | +| **Rust** | rustup stable 工具链 | WSL2 `~/.cargo` | +| **RTK** | Rust Token Killer (`rtk`) — LLM token 统计与上下文优化 | WSL2 cargo bin | + +## 目录结构 + +``` +claude-dev-stack/ +├── deploy.ps1 # Windows 一键部署(PowerShell 5.1+) +├── wsl-setup.sh # WSL2 内部安装脚本(可单独运行) +├── .env.example # 配置模板 +└── README.md +``` + +--- + +## 快速开始 + +### ⚠️ 重要:首次 WSL2 用户初始化 + +WSL2 Ubuntu **首次启动**会弹出提示创建用户: + +``` +Provisioning the new WSL instance Ubuntu +Create a default Unix user account: <输入你的用户名,如 hornet> +New password: <输入密码> +Retype new password: <确认密码> +``` + +**必须先完成用户创建,再运行 `deploy.ps1`**,否则所有文件会安装到 `root` 用户下,部署后无法正常使用。 + +> 💡 如果已不小心在 OOBE 前运行了脚本,再次运行 `deploy.ps1` 即可自动用正确用户重新安装。 + +--- + +### 场景一:全新 Windows 系统(首次安装 WSL2) + +> 需要**管理员权限**启用 WSL2 Windows 功能 + +```powershell +# 1. 以管理员身份运行 PowerShell +cd path\to\claude-dev-stack + +# 2. 复制配置文件,填写 ANTHROPIC_API_KEY +cp .env.example .env +notepad .env + +# 3. 运行部署脚本(自动检测 v2rayN 代理) +pwsh .\deploy.ps1 + +# 手动指定代理端口 +pwsh .\deploy.ps1 -ProxyPort 10809 + +# 不使用代理(直连) +pwsh .\deploy.ps1 -ProxyPort -1 +``` + +首次安装 WSL2 特性后可能需要**重启系统**,重启后重新执行脚本。 + +### 场景二:已有 WSL2,跳过 WSL2 安装 + +```powershell +pwsh .\deploy.ps1 -SkipWSL +``` + +### 场景三:仅在 WSL2 内安装(无 Windows 脚本) + +```bash +# 在 WSL2 Ubuntu 终端中执行 +cp .env.example .env +nano .env +# 如需代理(端口由 deploy.ps1 写入 ~/.mcp-proxy.env,或手动指定) +PROXY_PORT=10809 bash wsl-setup.sh +``` + +--- + +## 代理说明(v2rayN) + +### 自动检测顺序 + +1. Windows 系统代理注册表(v2rayN「设为系统代理」时写入) +2. v2rayN 配置文件(`%APPDATA%\v2rayN\guiNConfig.json`) +3. 探测常用端口:`10809, 10808, 7890, 1080, 8080` + +### WSL2 代理透传原理 + +``` +WSL2 Ubuntu + → [Windows 主机 vEthernet(WSL) IP]:10809 + → v2rayN + → 互联网 +``` + +**必须**在 v2rayN 中开启: +> 参数设置 → **「允许来自局域网的连接」** ✅ + +脚本自动完成: +- 写入 WSL2 `~/.mcp-proxy.env`(每次 shell 启动自动生效) +- 配置 WSL2 `git` + `npm` 代理 +- `apt-get` 安装阶段同样走代理 + +### 代理覆盖范围 + +| 操作 | 是否走代理 | +|------|-----------| +| Windows `git clone` / `npm install` | ✅ | +| WSL2 `apt-get` / `git clone` / `npm install` / `cargo install` | ✅ | +| MCP Server ↔ AI 客户端(stdio) | ❌ 本地通信,无需代理 | +| MCP Server ↔ Unity Plugin(localhost) | ❌ 本地通信,无需代理 | + +--- + +## 配置说明(`.env`) + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `ANTHROPIC_API_KEY` | _(空)_ | Anthropic API Key,从 [console.anthropic.com](https://console.anthropic.com) 获取 | +| `ANTHROPIC_BASE_URL` | `https://api.anthropic.com` | API 基础 URL;中转代理可改为 DeepSeek 等兼容接口 | +| `CLAUDE_MODEL` | `claude-opus-4-5` | 默认使用的模型 | +| `WSL_DISTRO` | `Ubuntu` | WSL2 发行版名称 | +| `SKIP_WSL_INSTALL` | `false` | `true` 跳过 WSL2 安装步骤 | + +--- + +## Unity MCP 完整安装教程 + +> **MCP Server 仓库**:[AnkleBreaker-Studio/unity-mcp-server](https://github.com/AnkleBreaker-Studio/unity-mcp-server) +> **Unity Plugin 仓库**:[AnkleBreaker-Studio/unity-mcp-plugin](https://github.com/AnkleBreaker-Studio/unity-mcp-plugin) +https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git +> **要求**:Unity 2021.3+ + Node.js 18+ + +### 架构原理 + +Unity MCP 由**两个独立仓库**组成,各自独立安装: + +``` +Claude Code CLI + │ MCP 协议 (stdio) + ▼ +Node.js MCP Server ← 克隆自 unity-mcp-server,WSL2 中运行 +(~/.mcp-servers/unity-mcp-server/) ← deploy.ps1 自动完成克隆 + npm install + │ WebSocket / HTTP + ▼ +Unity Editor Plugin (C#) ← Package Manager 安装 unity-mcp-plugin + │ + ▼ +Unity 场景 / 对象 / 脚本 / 测试... +``` + +- **unity-mcp-server**:独立 Node.js MCP 服务,部署脚本自动克隆并安装依赖 +- **unity-mcp-plugin**:Unity C# 插件,需要手动通过 Package Manager 安装 + +--- + +### Step 1:运行部署脚本(自动完成服务器安装) + +```powershell +pwsh .\deploy.ps1 +``` + +脚本会自动: +1. 克隆 `unity-mcp-server` 到 WSL2 `~/.mcp-servers/unity-mcp-server/` +2. 执行 `npm install` 安装依赖 +3. 将 MCP Server 注册到 `%USERPROFILE%\.claude\claude_desktop_config.json` + +--- + +### Step 2:在 Unity Editor 安装 unity-mcp-plugin + +1. 打开 Unity Editor +2. 菜单:**Window → Package Manager** +3. 点击左上角 **"+"** 按钮 → **"Add package from git URL..."** +4. 输入以下 URL,点击 **Add**: + ``` + https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git + ``` +5. 等待包下载安装完成 + +安装完成后,菜单栏会出现 **Tools → Unity MCP** 选项。 + +--- + +### Step 3:启动 Unity Editor 端 MCP 服务 + +每次使用 Claude Code 操作 Unity 之前,需要先在 Unity 中启动服务: + +1. 打开 Unity Editor,确保项目已加载 +2. 菜单:**Tools → Unity MCP → Start Server**(具体菜单项名称以插件实际为准) +3. 状态变为绿色 / Connected 表示服务就绪 + +--- + +### Step 4:确认 MCP 配置 + +部署脚本已自动写入 Windows Claude Code 配置。确认内容: + +```powershell +cat "$env:USERPROFILE\.claude\claude_desktop_config.json" +``` + +应包含类似如下内容: + +```json +{ + "mcpServers": { + "unity-mcp": { + "command": "wsl", + "args": ["-d", "Ubuntu", "--", "node", "/home/用户名/.mcp-servers/unity-mcp-server/src/index.js"] + } + } +} +``` + +WSL2 内的配置(`~/.config/Claude/claude_desktop_config.json`)直接使用 `node`: + +```json +{ + "mcpServers": { + "unity-mcp": { + "command": "node", + "args": ["/home/用户名/.mcp-servers/unity-mcp-server/src/index.js"] + } + } +} +``` + +--- + +### Step 5:在 Claude Code 中使用 Unity MCP + +```bash +# 启动 Claude Code(WSL2 内) +claude + +# 验证 Unity MCP 连接 +> /mcp + +# 示例:操作 Unity 场景 +> 在场景中创建一个名为 Player 的空 GameObject +> 给 Player 添加 Rigidbody 组件,质量设为 5 +> 创建一个新场景 Level1,保存到 Assets/Scenes/ +> 运行所有 EditMode 测试 +``` + +--- + +### 可用 MCP 工具列表 + +| 工具 | 功能 | +|------|------| +| `execute_menu_item` | 执行 Unity 菜单项 | +| `select_gameobject` | 选中场景中的 GameObject | +| `update_gameobject` | 更新/创建 GameObject 属性 | +| `update_component` | 更新/添加组件 | +| `add_package` | 通过 Package Manager 安装包 | +| `run_tests` | 运行 Unity Test Runner 测试 | +| `add_asset_to_scene` | 将 Asset 添加到场景 | +| `create_prefab` | 创建 Prefab | +| `create_scene` / `load_scene` / `delete_scene` | 场景管理 | +| `get_gameobject` | 获取 GameObject 详细信息 | +| `get_console_logs` | 读取 Unity Console 日志 | +| `recompile_scripts` | 重新编译脚本 | +| `create_material` / `assign_material` | 材质管理 | +| `move/rotate/scale_gameobject` | Transform 操作 | +| `batch_execute` | 批量执行多个操作 | + +--- + +### 防火墙放行(Bridge 端口) + +`deploy.ps1` 会自动创建防火墙规则,放行 TCP **7890–7899**(Bridge Port 范围)。 + +手动放行: + +```powershell +New-NetFirewallRule -DisplayName "Unity MCP Bridge Inbound" ` + -Direction Inbound -Protocol TCP -LocalPort 7890-7899 -Action Allow -Profile Any +New-NetFirewallRule -DisplayName "Unity MCP Bridge Outbound" ` + -Direction Outbound -Protocol TCP -LocalPort 7890-7899 -Action Allow -Profile Any +``` + +验证 Bridge 是否可用: + +```powershell +# Unity Editor 打开后 +Invoke-WebRequest http://127.0.0.1:7890/api/ping +# 返回 200 OK 表示 Bridge 正常 +``` + +--- + +### 故障排查 + +**问题:Unity MCP 连接失败** +``` +解决: +1. 确认 Unity Editor 已打开,且 unity-mcp-plugin 已启动服务 +2. 测试 Bridge: Invoke-WebRequest http://127.0.0.1:7890/api/ping +3. 确认防火墙 TCP 7890 已放行 +4. 确认 MCP Server 进程正常:wsl -d Ubuntu -- node ~/.mcp-servers/unity-mcp-server/src/index.js +5. 重新执行 deploy.ps1 重新克隆并注册 MCP Server +``` + +**问题:更新 unity-mcp-server 后连接失败** +``` +解决: +wsl -d Ubuntu -- bash -c "cd ~/.mcp-servers/unity-mcp-server && git pull && npm install" +``` + +**问题:npm install 安装超时(国内网络)** +```bash +# 方案一:通过 v2rayN 代理(推荐) +pwsh .\deploy.ps1 -ProxyPort 10809 + +# 方案二:WSL2 内配置镜像 +npm config set registry https://registry.npmmirror.com +``` + +**问题:WSL2 无法连接 v2rayN 代理** +``` +解决: +1. v2rayN → 参数设置 → 勾选「允许来自局域网的连接」 +2. 确认防火墙未拦截 v2rayN 监听端口 +3. WSL2 内测试:curl -I https://github.com --proxy http://$(ip route | grep default | awk '{print $3}'):10809 +``` + +**问题:Unity 版本兼容性** +``` +请参考 unity-mcp-plugin 仓库的 README 了解所需 Unity 最低版本要求 +``` + +--- + +## 使用方式 + +### Claude Code CLI + +```bash +# 进入 WSL2 +wsl -d Ubuntu + +# 启动 Claude Code +claude + +# 或从 PowerShell 快捷命令(由 deploy.ps1 写入 profile) +claude-wsl +``` + +### RTK (Rust Token Killer) + +```bash +# 统计文件 token 数 +rtk myfile.txt + +# 从管道统计 +cat large_context.md | rtk + +# 分析代码文件 +find . -name "*.cs" | xargs cat | rtk +``` + +### 故障排查 — rtk 安装失败 + +```bash +cargo install --git https://github.com/rtk-ai/rtk +``` + +--- + +## 环境检测逻辑 + +脚本对每个组件均先检测是否已安装,**已安装则跳过**,实现幂等执行: + +``` +WSL2 → wsl --list 检测发行版 +Node.js → node --version +Claude Code → claude --version +Rust → rustc --version +rtk → rtk --version +Unity MCP → 检测 ~/.mcp-servers/unity-mcp-server/ +``` + +--- + +## 故障排查 + +### WSL2 安装失败 + +```powershell +Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -NoRestart +Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -NoRestart +# 重启后 +wsl --update +wsl --install -d Ubuntu +``` + +### Rust/cargo 安装慢 + +在 `.env` 或 `~/.bashrc` 中添加 RsProxy 镜像: + +```bash +export RUSTUP_DIST_SERVER=https://rsproxy.cn +export RUSTUP_UPDATE_ROOT=https://rsproxy.cn/rustup +``` + diff --git a/claude-dev-stack/deploy.ps1 b/claude-dev-stack/deploy.ps1 new file mode 100644 index 0000000..b556bdc --- /dev/null +++ b/claude-dev-stack/deploy.ps1 @@ -0,0 +1,786 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + WSL2 + Claude Code CLI + Unity MCP + Rust Token Killer 全栈一键部署脚本 +.DESCRIPTION + 自动完成以下步骤: + 0. 检测 v2rayN 代理端口,配置 Windows & WSL2 两侧代理 + 1. 启用 WSL2 功能 & 安装 Ubuntu 发行版 + 2. 检测/安装 Windows 本机 Node.js(AI 客户端 MCP 需要) + 3. WSL2 内安装 Node.js LTS(Claude Code CLI 需要) + 4. 安装 Claude Code CLI (@anthropic-ai/claude-code) + 5. 安装 Unity MCP Server(Windows + WSL2 双侧)& 写入各 AI 客户端配置 + 6. 配置 Windows 防火墙放行 MCP Bridge 端口 + 7. 安装 Rust 工具链 & Token Killer (rtk) + 8. 写入 PowerShell Profile 快捷命令 +.PARAMETER ProxyPort + v2rayN HTTP 代理端口;0 = 自动检测;-1 = 跳过代理 +.PARAMETER BridgePort + Unity MCP Bridge 端口,默认 7890 +.PARAMETER InstallDir + Windows 侧 unity-mcp-server 安装目录 +.PARAMETER UnityHubPath + Unity Hub 路径(写入 MCP 配置 env) +.PARAMETER SkipFirewall + 跳过防火墙规则配置 +.PARAMETER SkipWSL + 跳过 WSL2 安装步骤 +.NOTES + 首次安装 WSL2 需以管理员身份运行。 + 已有 WSL2 可普通终端运行,加 -SkipWSL 跳过 WSL2 安装检查。 +#> +param( + [int] $ProxyPort = 0, + [int] $BridgePort = 7890, + [string]$InstallDir = "$env:USERPROFILE\unity-mcp-server", + [string]$UnityHubPath = "C:\Program Files\Unity Hub\Unity Hub.exe", + [switch]$SkipFirewall, + [switch]$SkipWSL +) + +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 + } +} + +$Config = @{} +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("'") + $Config[$key] = $val + } + Write-Info "已加载配置:$EnvFile" +} + +$ANTHROPIC_API_KEY = if ($Config["ANTHROPIC_API_KEY"]) { $Config["ANTHROPIC_API_KEY"] } else { "" } +$ANTHROPIC_BASE_URL = if ($Config["ANTHROPIC_BASE_URL"]) { $Config["ANTHROPIC_BASE_URL"] } else { "https://api.anthropic.com" } +$CLAUDE_MODEL = if ($Config["CLAUDE_MODEL"]) { $Config["CLAUDE_MODEL"] } else { "claude-opus-4-5" } +$WSL_DISTRO = if ($Config["WSL_DISTRO"]) { $Config["WSL_DISTRO"] } else { "Ubuntu" } +$SKIP_WSL_INSTALL = if ($Config["SKIP_WSL_INSTALL"]) { $Config["SKIP_WSL_INSTALL"] } else { "false" } + +# ────────────────────────────────────────────────────────────── +# Banner +# ────────────────────────────────────────────────────────────── +Write-Host "" +Write-Host "╔══════════════════════════════════════════════════════════╗" -ForegroundColor Cyan +Write-Host "║ WSL2 + Claude Code CLI + Unity MCP + RTK 全栈部署 ║" -ForegroundColor Cyan +Write-Host "║ AnkleBreaker Unity MCP · v2rayN 代理自动检测 ║" -ForegroundColor Cyan +Write-Host "╚══════════════════════════════════════════════════════════╝" -ForegroundColor Cyan +Write-Host "" + +# ══════════════════════════════════════════════════════════════ +# Step 0: v2rayN 代理检测与配置 +# ══════════════════════════════════════════════════════════════ +Write-Step "0/8 v2rayN 代理检测" + +# ── 检测代理端口 ────────────────────────────────────────────── +function Get-V2RayNProxyPort { + # 1. Windows 系统代理注册表(v2rayN"设为系统代理"时写入) + try { + $ie = Get-ItemProperty ` + "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" ` + -ErrorAction SilentlyContinue + if ($ie.ProxyEnable -eq 1 -and $ie.ProxyServer -match ":(\d+)") { + return [int]$Matches[1] + } + } catch {} + + # 2. v2rayN 配置文件 + $cfgPaths = @( + "$env:APPDATA\v2rayN\guiNConfig.json", + "$env:LOCALAPPDATA\v2rayN\guiNConfig.json" + ) + foreach ($p in $cfgPaths) { + if (Test-Path $p) { + try { + $j = Get-Content $p -Raw | ConvertFrom-Json + if ($j.localPort) { return [int]$j.localPort } + if ($j.httpPort) { return [int]$j.httpPort } + } catch {} + } + } + + # 3. 探测常用端口 + foreach ($port in @(10809, 10808, 7890, 1080, 8080)) { + try { + $tcp = New-Object System.Net.Sockets.TcpClient + $ar = $tcp.BeginConnect("127.0.0.1", $port, $null, $null) + if ($ar.AsyncWaitHandle.WaitOne(300, $false)) { + $tcp.EndConnect($ar); $tcp.Close(); return $port + } + $tcp.Close() + } catch {} + } + return 0 +} + +function Set-WindowsProxy { + param([int]$Port) + $url = "http://127.0.0.1:$Port" + git config --global http.proxy $url 2>$null + git config --global https.proxy $url 2>$null + & npm config set proxy $url --location global 2>$null + & npm config set https-proxy $url --location global 2>$null + $env:http_proxy = $url; $env:https_proxy = $url + $env:HTTP_PROXY = $url; $env:HTTPS_PROXY = $url + Write-OK "Windows git/npm/env 代理 -> $url" +} + +function Clear-WindowsProxy { + git config --global --unset http.proxy 2>$null + git config --global --unset https.proxy 2>$null + & npm config delete proxy --location global 2>$null + & npm config delete https-proxy --location global 2>$null + "http_proxy","https_proxy","HTTP_PROXY","HTTPS_PROXY" | + ForEach-Object { Remove-Item "Env:\$_" -ErrorAction SilentlyContinue } +} + +$resolvedProxyPort = 0 + +if ($ProxyPort -eq -1) { + Write-Warn "已跳过代理配置(-ProxyPort -1)" +} elseif ($ProxyPort -gt 0) { + $resolvedProxyPort = $ProxyPort + Set-WindowsProxy -Port $resolvedProxyPort + Write-OK "使用指定代理端口: $resolvedProxyPort" +} else { + $resolvedProxyPort = Get-V2RayNProxyPort + if ($resolvedProxyPort -gt 0) { + Set-WindowsProxy -Port $resolvedProxyPort + Write-OK "自动检测到 v2rayN 代理端口: $resolvedProxyPort" + } else { + Write-Warn "未检测到活跃代理,将使用直连网络" + Write-Warn "如需代理,请指定: pwsh deploy.ps1 -ProxyPort 10809" + Write-Warn "⚠ 确保 v2rayN 已开启「允许来自局域网的连接」(WSL2 需要)" + } +} + +# ══════════════════════════════════════════════════════════════ +# Step 1: WSL2 安装 +# ══════════════════════════════════════════════════════════════ +Write-Step "1/8 WSL2 环境检测 & 安装" + +$distroInstalled = $false +try { + $dl = wsl --list --quiet 2>&1 | ForEach-Object { $_ -replace "`0","" } + if ($dl -match [regex]::Escape($WSL_DISTRO)) { $distroInstalled = $true } +} catch {} + +if ($distroInstalled) { + Write-OK "WSL2 + $WSL_DISTRO 已安装,跳过" +} elseif ($SkipWSL -or $SKIP_WSL_INSTALL -eq "true") { + Write-Warn "跳过 WSL2 安装" +} else { + $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() + ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + if (-not $isAdmin) { + Write-Fail "安装 WSL2 需要管理员权限,请以管理员身份运行 PowerShell" + Write-Info "如已安装 WSL2,请加 -SkipWSL 参数或在 .env 设置 SKIP_WSL_INSTALL=true" + exit 1 + } + $f1 = Get-WindowsOptionalFeature -Online -FeatureName "Microsoft-Windows-Subsystem-Linux" -ErrorAction SilentlyContinue + if ($f1.State -ne "Enabled") { + Enable-WindowsOptionalFeature -Online -FeatureName "Microsoft-Windows-Subsystem-Linux" -NoRestart | Out-Null + } + $f2 = Get-WindowsOptionalFeature -Online -FeatureName "VirtualMachinePlatform" -ErrorAction SilentlyContinue + if ($f2.State -ne "Enabled") { + Enable-WindowsOptionalFeature -Online -FeatureName "VirtualMachinePlatform" -NoRestart | Out-Null + } + wsl --update 2>&1 | Out-Null + wsl --set-default-version 2 2>&1 | Out-Null + wsl --install -d $WSL_DISTRO --no-launch 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Fail "WSL2 安装失败,系统可能需要重启后重新运行" + exit 1 + } + Write-OK "WSL2 $WSL_DISTRO 安装完成" + Write-Warn "首次安装可能需要重启,重启后重新运行脚本" +} + +# ── WSL2 执行辅助函数 ───────────────────────────────────────── +function Invoke-WSL { + param([string]$Command, [switch]$IgnoreError) + + if ($Command -match "`n") { + # 多行命令:base64 编码后在 bash 内解码执行,完全规避 PowerShell 管道 CRLF 问题 + $CleanCmd = $Command -replace "`r`n","`n" -replace "`r","`n" + $b64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($CleanCmd)) + $result = wsl -d $WSL_DISTRO -- bash -c "echo '$b64' | base64 -d | bash" 2>&1 | + ForEach-Object { ($_ -replace "`0","").ToString() } + } else { + $result = wsl -d $WSL_DISTRO -- bash -c $Command 2>&1 | + ForEach-Object { ($_ -replace "`0","").ToString() } + } + + if ($LASTEXITCODE -ne 0 -and -not $IgnoreError) { + Write-Fail "WSL 命令失败 (exit $LASTEXITCODE)" + Write-Fail ($result -join "`n") + exit 1 + } + return ($result -join "`n") +} + +# 用 exit code 方式测试连通性,避免输出编码干扰 +wsl -d $WSL_DISTRO -- bash -c "exit 0" 2>$null +if ($LASTEXITCODE -ne 0) { + Write-Fail "无法访问 WSL2 发行版 '$WSL_DISTRO'" + exit 1 +} +Write-OK "WSL2 ($WSL_DISTRO) 连接正常" + +# 获取 WSL2 默认登录用户(OOBE 完成后可能是非 root) +$wslUser = (wsl -d $WSL_DISTRO -- bash -c "whoami" 2>&1) -replace "`0","" | + Where-Object { $_ -match "^\w+$" } | Select-Object -First 1 +if (-not $wslUser) { $wslUser = "root" } +$wslUser = $wslUser.Trim() +Write-OK "WSL2 默认用户: $wslUser" +if ($wslUser -ne "root") { + Write-Info "非 root 用户,系统级命令将通过 sudo 执行" +} + +# ── Step 1a: 配置 .wslconfig(mirrored 网络模式) ──────────── +$wslCfgPath = "$env:USERPROFILE\.wslconfig" +$wslCfgContent = @" +[wsl2] +networkingMode=mirrored +dnsTunneling=true +firewall=true +autoProxy=true +"@ +$needRestart = $false +if (Test-Path $wslCfgPath) { + $existing = Get-Content $wslCfgPath -Raw + if ($existing -notmatch "networkingMode=mirrored") { + Set-Content $wslCfgPath $wslCfgContent -Encoding UTF8 + $needRestart = $true + Write-OK ".wslconfig 已更新 -> networkingMode=mirrored" + } +} else { + Set-Content $wslCfgPath $wslCfgContent -Encoding UTF8 + $needRestart = $true + Write-OK ".wslconfig 已创建 -> networkingMode=mirrored" +} +if ($needRestart) { + Write-Info "重启 WSL2 以应用镜像网络模式..." + wsl --shutdown 2>$null + Start-Sleep 3 +} + +# ── 配置 WSL2 侧代理(持久化到 ~/.bashrc) ─────────────────── +if ($resolvedProxyPort -gt 0) { + Write-Info "配置 WSL2 代理(端口 $resolvedProxyPort)..." + + # mirrored 模式下 127.0.0.1 即 Windows,直接用即可 + $proxyEnvContent = "# WSL2 mirrored networking: 127.0.0.1 = Windows host`nexport http_proxy=`"http://127.0.0.1:$resolvedProxyPort`"`nexport https_proxy=`"http://127.0.0.1:$resolvedProxyPort`"`nexport HTTP_PROXY=`"http://127.0.0.1:$resolvedProxyPort`"`nexport HTTPS_PROXY=`"http://127.0.0.1:$resolvedProxyPort`"`nexport no_proxy=`"localhost,127.0.0.1,::1`"`n" + + $tmpFile = "$env:TEMP\mcp-proxy.env" + $bytes = [System.Text.Encoding]::UTF8.GetBytes($proxyEnvContent) + [System.IO.File]::WriteAllBytes($tmpFile, $bytes) + Copy-Item $tmpFile "\\wsl.localhost\$WSL_DISTRO\tmp\mcp-proxy-deploy.env" -Force 2>$null + + Invoke-WSL "cp /tmp/mcp-proxy-deploy.env ~/.mcp-proxy.env && chmod 644 ~/.mcp-proxy.env && echo OK" | Out-Null + + # 写入 ~/.bashrc(幂等) + Invoke-WSL @' +if ! grep -q 'mcp-proxy.env' ~/.bashrc 2>/dev/null; then + printf '\n# Unity MCP WSL2 proxy (generated by deploy.ps1)\n[ -f ~/.mcp-proxy.env ] && . ~/.mcp-proxy.env\n' >> ~/.bashrc +fi +# 立即应用代理并配置 git +. ~/.mcp-proxy.env 2>/dev/null || true +if [ -n "$http_proxy" ]; then + git config --global http.proxy "$http_proxy" + git config --global https.proxy "$https_proxy" + echo "WSL2 git proxy set: $http_proxy" +fi +true +'@ -IgnoreError | Out-Null + + Write-OK "WSL2 代理已写入 ~/.mcp-proxy.env + ~/.bashrc" + Write-Warn "⚠ 请确保 v2rayN 已开启「允许来自局域网的连接」!" +} + +# ══════════════════════════════════════════════════════════════ +# Step 2: Windows 本机 Node.js 检查(AI 客户端 MCP 需要) +# ══════════════════════════════════════════════════════════════ +Write-Step "2/8 Windows Node.js 检查" + +$winNode = Get-Command node -ErrorAction SilentlyContinue +if ($winNode) { + $winNodeVer = & node --version + Write-OK "Windows Node.js 已安装: $winNodeVer" +} else { + Write-Warn "Windows 本机未检测到 Node.js" + Write-Info "尝试通过 winget 安装 Node.js LTS..." + $wingetCmd = Get-Command winget -ErrorAction SilentlyContinue + if ($wingetCmd) { + winget install --id OpenJS.NodeJS.LTS --silent --accept-source-agreements --accept-package-agreements 2>&1 | Out-Null + # 刷新 PATH + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + + [System.Environment]::GetEnvironmentVariable("Path","User") + $winNode = Get-Command node -ErrorAction SilentlyContinue + if ($winNode) { + Write-OK "Node.js 安装成功: $(& node --version)" + } else { + Write-Warn "winget 安装后未找到 node,请手动安装 Node.js 18+:" + Write-Warn " https://nodejs.org/zh-cn/download" + Write-Warn " 或: winget install OpenJS.NodeJS.LTS" + } + } else { + Write-Warn "未找到 winget,请手动安装 Node.js 18+:" + Write-Warn " https://nodejs.org/zh-cn/download" + } +} + +# 对 Windows npm 设置代理 +if ($resolvedProxyPort -gt 0 -and (Get-Command npm -ErrorAction SilentlyContinue)) { + $proxyUrl = "http://127.0.0.1:$resolvedProxyPort" + & npm config set proxy $proxyUrl --location global 2>$null + & npm config set https-proxy $proxyUrl --location global 2>$null +} + +# ══════════════════════════════════════════════════════════════ +# Step 3: WSL2 系统依赖 & Node.js +# ══════════════════════════════════════════════════════════════ +Write-Step "3/8 WSL2 Node.js LTS" + +# 检测 WSL2 原生 node(排除通过 WSL interop 调用的 Windows node,路径含 /mnt/) +$nodeVer = Invoke-WSL @' +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" +_n=$(which node 2>/dev/null) +if [ -n "$_n" ] && ! echo "$_n" | grep -q '/mnt/'; then + node --version +else + echo MISSING +fi +true +'@ -IgnoreError +if ($nodeVer -match "v\d+") { + Write-OK "WSL2 Node.js 已安装 (原生): $($nodeVer.Trim())" +} else { + Write-Info "安装 WSL2 Node.js LTS (via nvm)..." + $installNodeCmd = @' +. ~/.mcp-proxy.env 2>/dev/null || true +export DEBIAN_FRONTEND=noninteractive +sudo apt-get install -y -qq curl ca-certificates 2>/dev/null +# 安装 nvm +export NVM_DIR="$HOME/.nvm" +if [ ! -d "$NVM_DIR" ]; then + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash +fi +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" +# 安装 Node.js LTS +nvm install --lts +nvm use --lts +# 写入 .bashrc(幂等) +grep -q 'NVM_DIR' ~/.bashrc || cat >> ~/.bashrc << 'NVMEOF' + +# nvm (Node Version Manager) +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" +[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" +NVMEOF +node --version +npm --version +true +'@ + Invoke-WSL $installNodeCmd + Write-OK "WSL2 Node.js 安装完成" +} + +# ══════════════════════════════════════════════════════════════ +# Step 4: Claude Code CLI (WSL2) +# ══════════════════════════════════════════════════════════════ +Write-Step "4/8 Claude Code CLI" + +# 检测 WSL2 原生 claude(排除通过 WSL interop 调用的 Windows claude.exe,路径含 /mnt/) +$claudeVer = Invoke-WSL @' +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" +_c=$(which claude 2>/dev/null) +if [ -n "$_c" ] && ! echo "$_c" | grep -q '/mnt/'; then + claude --version 2>/dev/null +else + echo MISSING +fi +true +'@ -IgnoreError +if ($claudeVer -notmatch "MISSING") { + Write-OK "Claude Code 已安装 (WSL2 原生): $($claudeVer.Trim())" +} else { + Write-Info "安装 @anthropic-ai/claude-code (WSL2 原生)..." + $installClaudeCmd = @' +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" +. ~/.mcp-proxy.env 2>/dev/null || true +npm install -g @anthropic-ai/claude-code --quiet +which claude +claude --version +true +'@ + Invoke-WSL $installClaudeCmd + Write-OK "Claude Code 安装完成" +} + +# 写入 Claude 配置(WSL2 侧) +$writeClaudeSettingsCmd = @" +mkdir -p ~/.claude +cat > ~/.claude/settings.json << 'SETTINGS' +{"model": "$CLAUDE_MODEL"} +SETTINGS +true +"@ +Invoke-WSL $writeClaudeSettingsCmd -IgnoreError | Out-Null + +# 写入 bash 环境变量(WSL2 侧) +$profBlock = "" +if ($ANTHROPIC_API_KEY) { $profBlock += "export ANTHROPIC_API_KEY='$ANTHROPIC_API_KEY'\n" } +if ($ANTHROPIC_BASE_URL) { $profBlock += "export ANTHROPIC_BASE_URL='$ANTHROPIC_BASE_URL'\n" } +$profBlock += "export CLAUDE_MODEL='$CLAUDE_MODEL'\n" +$addEnvCmd = @" +if ! grep -q 'ANTHROPIC_API_KEY' ~/.bashrc 2>/dev/null; then + printf '\n# Claude Code CLI\n$profBlock' >> ~/.bashrc +fi +"@ +Invoke-WSL $addEnvCmd -IgnoreError | Out-Null + +# Windows 侧 Claude 配置 +$claudeDir = "$env:USERPROFILE\.claude" +if (-not (Test-Path $claudeDir)) { New-Item -ItemType Directory -Path $claudeDir | Out-Null } +@{ model = $CLAUDE_MODEL } | ConvertTo-Json | Set-Content "$claudeDir\settings.json" -Encoding UTF8 +if ($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 配置已写入(Windows + WSL2)" + +# ══════════════════════════════════════════════════════════════ +# Step 5: Unity MCP Server 安装 +# ══════════════════════════════════════════════════════════════ +Write-Step "5/8 Unity MCP Server" +Write-Info "MCP Server: https://github.com/AnkleBreaker-Studio/unity-mcp-server" +Write-Info "Unity Plugin: https://github.com/AnkleBreaker-Studio/unity-mcp-plugin" + +# ── 5a. Windows 侧安装(供 Claude Desktop / Cursor / Windsurf / VS Code)──── +Write-Info "安装 Windows 侧 MCP Server -> $InstallDir" +if (Test-Path "$InstallDir\.git") { + Write-Info "已存在,执行 git pull..." + Push-Location $InstallDir + try { git pull --quiet 2>&1 | Out-Null } finally { Pop-Location } +} else { + if (Test-Path $InstallDir) { Remove-Item $InstallDir -Recurse -Force } + git clone --quiet --depth 1 https://github.com/AnkleBreaker-Studio/unity-mcp-server.git $InstallDir +} + +$winNodeExists = (Get-Command node -ErrorAction SilentlyContinue) -ne $null +if ($winNodeExists) { + Push-Location $InstallDir + try { & npm install --prefer-offline --quiet 2>&1 | Out-Null } finally { Pop-Location } + Write-OK "Windows MCP Server 就绪: $InstallDir" +} else { + Write-Warn "Windows Node.js 未就绪,跳过 Windows 侧 npm install(请安装 Node.js 后重新运行)" +} + +# ── 5b. WSL2 侧安装(供 Claude Code CLI)───────────────────── +Write-Info "安装 WSL2 侧 MCP Server..." +$wslMcpDir = "~/.mcp-servers/unity-mcp-server" +$wslMcpCmd = @' +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" +. ~/.mcp-proxy.env 2>/dev/null || true +mkdir -p $HOME/.mcp-servers +if [ -d $HOME/.mcp-servers/unity-mcp-server/.git ]; then + git -C $HOME/.mcp-servers/unity-mcp-server pull --quiet 2>/dev/null || true +else + git clone --quiet --depth 1 https://github.com/AnkleBreaker-Studio/unity-mcp-server.git \ + $HOME/.mcp-servers/unity-mcp-server 2>/dev/null \ + || git -c http.proxy="" -c https.proxy="" clone --quiet --depth 1 \ + https://github.com/AnkleBreaker-Studio/unity-mcp-server.git \ + $HOME/.mcp-servers/unity-mcp-server +fi +if [ -d $HOME/.mcp-servers/unity-mcp-server ]; then + cd $HOME/.mcp-servers/unity-mcp-server + # 修复可能由 root 遗留的权限问题 + sudo chown -R $(whoami):$(whoami) . 2>/dev/null || true + npm install --prefer-offline --quiet 2>/dev/null || npm install --quiet +fi +echo MCP_WSL_OK +true +'@ +Invoke-WSL $wslMcpCmd + +$wslHome = (Invoke-WSL "echo `$HOME" -IgnoreError).Trim() +$wslScript = "$wslHome/.mcp-servers/unity-mcp-server/src/index.js" +Write-OK "WSL2 MCP Server 就绪: $wslScript" + +# ── 5c. 写入各 AI 客户端 MCP 配置 ─────────────────────────── +$winScript = ($InstallDir -replace "\\","/") + "/src/index.js" + +$mcpEntry = @{ + command = "node" + args = @($winScript) + env = @{ + UNITY_HUB_PATH = $UnityHubPath + UNITY_BRIDGE_PORT = "$BridgePort" + } +} + +$wslMcpEntry = @{ + command = "wsl" + args = @("-d", $WSL_DISTRO, "--", "node", $wslScript) + env = @{ + UNITY_BRIDGE_PORT = "$BridgePort" + } +} + +function Merge-McpConfig { + param([string]$ConfigFile, [hashtable]$Entry, [string]$Label) + $dir = Split-Path $ConfigFile + if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } + # PS5.1 兼容:手动将 PSCustomObject 转为普通 Hashtable + 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"]["unity-mcp"] = $Entry + $cfg | ConvertTo-Json -Depth 10 | Set-Content $ConfigFile -Encoding UTF8 + Write-OK "$Label -> $ConfigFile" +} + +# Claude Desktop(Windows) +Merge-McpConfig "$env:APPDATA\Claude\claude_desktop_config.json" $mcpEntry "Claude Desktop" +# Cursor +Merge-McpConfig "$env:USERPROFILE\.cursor\mcp.json" $mcpEntry "Cursor" +# Windsurf(两种路径) +$windsurfDir = if (Test-Path "$env:APPDATA\Windsurf") { "$env:APPDATA\Windsurf" } else { "$env:USERPROFILE\.codeium\windsurf" } +Merge-McpConfig "$windsurfDir\mcp_config.json" $mcpEntry "Windsurf" +# VS Code +Merge-McpConfig "$env:APPDATA\Code\User\mcp.json" $mcpEntry "VS Code" +# Claude Code CLI(WSL2)——用 `claude mcp add --scope user` 写入全局用户级 MCP 配置 +$wslMcpCfgCmd = @" +export NVM_DIR="`$HOME/.nvm" +[ -s "`$NVM_DIR/nvm.sh" ] && . "`$NVM_DIR/nvm.sh" +. ~/.mcp-proxy.env 2>/dev/null || true +# 先移除旧条目(幂等),再添加 +claude mcp remove unity-mcp --scope user 2>/dev/null || true +claude mcp add --scope user unity-mcp node $wslScript -e UNITY_BRIDGE_PORT=$BridgePort +echo "Claude Code MCP configured" +true +"@ +Invoke-WSL $wslMcpCfgCmd -IgnoreError | Out-Null +Write-OK "Claude Code CLI (WSL2) MCP -> unity-mcp (node $wslScript)" + +# ══════════════════════════════════════════════════════════════ +# Step 6: Windows 防火墙放行 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 "非管理员权限,使用提升权限添加防火墙规则..." + $cmd = @" +`$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($cmd)) + $proc = Start-Process powershell.exe -ArgumentList "-NoProfile -EncodedCommand $enc" ` + -Verb RunAs -Wait -PassThru + if ($proc.ExitCode -eq 0) { + Write-OK "防火墙规则已添加 (TCP $portRange)" + } else { + Write-Warn "防火墙配置可能未成功,请手动放行 TCP $portRange" + } + } +} + +# ══════════════════════════════════════════════════════════════ +# Step 7: Rust 工具链 & Token Killer (WSL2) +# ══════════════════════════════════════════════════════════════ +Write-Step "7/8 Rust & Token Killer" + +$rustVer = Invoke-WSL ". ~/.cargo/env 2>/dev/null; rustc --version 2>/dev/null || echo MISSING" -IgnoreError +if ($rustVer -notmatch "MISSING") { + Write-OK "Rust 已安装: $($rustVer.Trim())" +} else { + Write-Info "安装 Rust 工具链..." + $installRustCmd = @" +sudo apt-get install -y -qq build-essential pkg-config libssl-dev 2>/dev/null || true +# mirrored 模式下直接用 127.0.0.1,rustup 用 env 代理 +. ~/.mcp-proxy.env 2>/dev/null || true +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup-init.sh +sh /tmp/rustup-init.sh -y --default-toolchain stable --no-modify-path +source ~/.cargo/env +rustc --version +"@ + Invoke-WSL $installRustCmd + Invoke-WSL "grep -q 'cargo/env' ~/.bashrc || echo 'source ~/.cargo/env 2>/dev/null || true' >> ~/.bashrc" -IgnoreError | Out-Null + Write-OK "Rust 安装完成" +} + +$rtkVer = Invoke-WSL ". ~/.cargo/env 2>/dev/null; rtk --version 2>/dev/null || echo MISSING" -IgnoreError +if ($rtkVer -notmatch "MISSING") { + Write-OK "rtk 已安装: $($rtkVer.Trim())" +} else { + Write-Info "安装 rtk (Rust Token Killer)..." + $installRtkCmd = @" +. ~/.cargo/env 2>/dev/null || true +# mirrored 模式:CARGO_NET_GIT_FETCH_WITH_CLI 让 cargo 用系统 git(走代理) +# 但 git clone 到 github 时用直连(git proxy 不影响 https) +_saved_http=`$(git config --global http.proxy 2>/dev/null) +_saved_https=`$(git config --global https.proxy 2>/dev/null) +git config --global --unset http.proxy 2>/dev/null || true +git config --global --unset https.proxy 2>/dev/null || true +CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install --git https://github.com/rtk-ai/rtk 2>&1 | tail -5 +[ -n "`$_saved_http" ] && git config --global http.proxy "`$_saved_http" || true +[ -n "`$_saved_https" ] && git config --global https.proxy "`$_saved_https" || true +"@ + Invoke-WSL $installRtkCmd -IgnoreError + $rtkCheck = Invoke-WSL ". ~/.cargo/env 2>/dev/null; rtk --version 2>/dev/null || echo FAILED" -IgnoreError + if ($rtkCheck -match "FAILED") { + Write-Warn "rtk 安装失败,可手动运行: cargo install --git https://github.com/rtk-ai/rtk" + } else { + Write-OK "rtk 安装成功: $($rtkCheck.Trim())" + } +} + +# ══════════════════════════════════════════════════════════════ +# 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 = @" + +# ── Claude Dev Stack (generated by 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" +function claude-wsl { wsl -d $WSL_DISTRO -- bash -ic 'claude' } +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 "Claude Dev Stack") { + $existing = $existing -replace "(?ms)# ── Claude Dev Stack.*?# ─{60}", $profileBlock + 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 "║ ✅ 全栈部署完成 ║" -ForegroundColor Green +Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Green + +$items = @( + @("WSL2 内核", (Invoke-WSL "uname -r 2>/dev/null" -IgnoreError).Trim()), + @("Node.js (Win)", (& node --version 2>$null)), + @("Node.js (WSL)", $(if ($nodeVer -match "v\d+") { $nodeVer.Trim() } else { "" })), + @("Claude Code", $(if ($claudeVer -notmatch "MISSING") { $claudeVer.Trim() } else { "" })), + @("Rust", (Invoke-WSL ". ~/.cargo/env && rustc --version 2>/dev/null" -IgnoreError).Trim()), + @("rtk", (Invoke-WSL ". ~/.cargo/env && rtk --version 2>/dev/null" -IgnoreError).Trim()), + @("MCP Server", $InstallDir), + @("Bridge Port", "TCP $BridgePort (防火墙已放行 $BridgePort-$($BridgePort+9))") +) +foreach ($it in $items) { + Write-Host (" {0,-16} {1}" -f $it[0], $(if ($it[1]) { $it[1] } else { "(未安装)" })) -ForegroundColor White +} + +if ($resolvedProxyPort -gt 0) { + Write-Host "" + Write-Host " v2rayN 代理 127.0.0.1:$resolvedProxyPort (WSL2 通过 Windows 主机 IP 访问)" -ForegroundColor DarkGray +} + +Write-Host "" +Write-Host " ━━━━ 后续步骤 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan +Write-Host " 1) 在 Unity 项目中安装 MCP Plugin(每个项目一次):" -ForegroundColor White +Write-Host " Window > Package Manager > + > Add from git URL:" -ForegroundColor DarkGray +Write-Host " https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git" -ForegroundColor Yellow +Write-Host "" +Write-Host " 2) 打开 Unity 后确认 Bridge 在线(浏览器验证):" -ForegroundColor White +Write-Host " http://127.0.0.1:$BridgePort/api/ping" -ForegroundColor Yellow +Write-Host "" +Write-Host " 3) 重启 AI 客户端(Claude Desktop / Cursor / Windsurf)" -ForegroundColor White +Write-Host " MCP 配置已自动写入各客户端配置文件" -ForegroundColor DarkGray +Write-Host "" +Write-Host " 4) 在 Claude Code (WSL2) 中使用:" -ForegroundColor White +Write-Host " claude-wsl → claude → /mcp" -ForegroundColor DarkGray +Write-Host "" +if (-not $ANTHROPIC_API_KEY) { + Write-Warn " ⚠ 未设置 ANTHROPIC_API_KEY,请编辑 .env 后重新运行" +} +Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Green diff --git a/claude-dev-stack/package-lock.json b/claude-dev-stack/package-lock.json new file mode 100644 index 0000000..86a3ef4 --- /dev/null +++ b/claude-dev-stack/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "claude-dev-stack", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/claude-dev-stack/wsl-setup.sh b/claude-dev-stack/wsl-setup.sh new file mode 100644 index 0000000..9b5ce26 --- /dev/null +++ b/claude-dev-stack/wsl-setup.sh @@ -0,0 +1,272 @@ +#!/usr/bin/env bash +# ============================================================= +# WSL2 内部环境检测 & 安装脚本 +# 可单独在 WSL2 Ubuntu 中运行:bash wsl-setup.sh +# 也会被 deploy.ps1 自动调用(通过 Invoke-WSL) +# ============================================================= +set -euo pipefail + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' +CYAN='\033[0;36m'; NC='\033[0m' + +log() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } +step() { echo -e "\n${CYAN}====== $* ======${NC}"; } + +# ────────────────────────────────────────────────────────────── +# 加载 .env(与脚本同目录) +# ────────────────────────────────────────────────────────────── +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENV_FILE="$SCRIPT_DIR/.env" +ENV_EXAMPLE="$SCRIPT_DIR/.env.example" + +if [ ! -f "$ENV_FILE" ]; then + if [ -f "$ENV_EXAMPLE" ]; then + cp "$ENV_EXAMPLE" "$ENV_FILE" + warn "已从 .env.example 创建 .env,请按需编辑后重新运行" + exit 0 + fi +fi + +if [ -f "$ENV_FILE" ]; then + sed -i 's/\r$//' "$ENV_FILE" + set -a; source "$ENV_FILE"; set +a +fi + +ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-}" +ANTHROPIC_BASE_URL="${ANTHROPIC_BASE_URL:-https://api.anthropic.com}" +CLAUDE_MODEL="${CLAUDE_MODEL:-claude-opus-4-5}" +BRIDGE_PORT="${BRIDGE_PORT:-7890}" + +# ────────────────────────────────────────────────────────────── +# Step 1: v2rayN 代理配置(WSL2 侧) +# ────────────────────────────────────────────────────────────── +step "1/6 代理配置" + +# 优先读取 deploy.ps1 写入的 ~/.mcp-proxy.env +if [ -f ~/.mcp-proxy.env ]; then + source ~/.mcp-proxy.env 2>/dev/null || true + if [ -n "${http_proxy:-}" ]; then + log "代理已从 ~/.mcp-proxy.env 加载: $http_proxy" + fi +fi + +# 若代理未设置但提供了 PROXY_PORT,则动态检测 Windows 主机 IP +PROXY_PORT="${PROXY_PORT:-0}" +if [ -z "${http_proxy:-}" ] && [ "$PROXY_PORT" -gt 0 ] 2>/dev/null; then + WIN_HOST=$(ip route 2>/dev/null | grep default | awk '{print $3}' | head -1) + [ -z "$WIN_HOST" ] && WIN_HOST=$(grep nameserver /etc/resolv.conf 2>/dev/null | awk '{print $2}' | head -1) + if [ -n "$WIN_HOST" ]; then + export http_proxy="http://${WIN_HOST}:${PROXY_PORT}" + export https_proxy="$http_proxy" + export HTTP_PROXY="$http_proxy" + export HTTPS_PROXY="$http_proxy" + export no_proxy="localhost,127.0.0.1,::1" + log "WSL2 代理已设置: $http_proxy (Windows 主机: $WIN_HOST)" + else + warn "无法获取 Windows 主机 IP,代理未设置" + fi +fi + +# 写入 ~/.mcp-proxy.env 并持久化到 ~/.bashrc +if [ -n "${http_proxy:-}" ] && [ -z "$(grep -s 'mcp-proxy.env' ~/.bashrc)" ]; then + cat >> ~/.bashrc << 'BASHRCBLOCK' + +# Unity MCP WSL2 proxy (generated by wsl-setup.sh) +[ -f ~/.mcp-proxy.env ] && . ~/.mcp-proxy.env +BASHRCBLOCK + log "代理配置已写入 ~/.bashrc" +fi + +# 写入 git 代理 +if [ -n "${http_proxy:-}" ]; then + git config --global http.proxy "$http_proxy" 2>/dev/null || true + git config --global https.proxy "$https_proxy" 2>/dev/null || true + log "git 代理已配置" +fi + +if [ -z "${http_proxy:-}" ]; then + warn "未配置代理,将使用直连网络(如下载失败请通过 PROXY_PORT=10809 重新运行)" +fi + +# ────────────────────────────────────────────────────────────── +# Step 2: 系统依赖 +# ────────────────────────────────────────────────────────────── +step "2/6 系统依赖" + +export DEBIAN_FRONTEND=noninteractive +sudo apt-get update -qq +sudo apt-get install -y -qq \ + curl wget git build-essential pkg-config libssl-dev \ + ca-certificates gnupg lsb-release unzip python3 +log "系统依赖安装完成" + +# ────────────────────────────────────────────────────────────── +# Step 3: Node.js LTS +# ────────────────────────────────────────────────────────────── +step "3/6 Node.js LTS" + +if command -v node &>/dev/null; then + log "Node.js 已安装: $(node --version)" +else + log "通过 NodeSource 安装 Node.js LTS..." + curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - > /dev/null 2>&1 + sudo apt-get install -y -qq nodejs + log "Node.js 安装完成: $(node --version)" +fi + +# 设置 npm 代理 +if [ -n "${http_proxy:-}" ] && command -v npm &>/dev/null; then + npm config set proxy "$http_proxy" 2>/dev/null || true + npm config set https-proxy "$https_proxy" 2>/dev/null || true + log "npm 代理已配置" +fi + +# ────────────────────────────────────────────────────────────── +# Step 4: Claude Code CLI +# ────────────────────────────────────────────────────────────── +step "4/6 Claude Code CLI" + +if command -v claude &>/dev/null; then + log "Claude Code 已安装: $(claude --version 2>&1 | head -1)" +else + log "安装 @anthropic-ai/claude-code..." + sudo npm install -g @anthropic-ai/claude-code --quiet + log "Claude Code 安装完成: $(claude --version 2>&1 | head -1)" +fi + +mkdir -p ~/.claude +cat > ~/.claude/settings.json << SETTINGS +{"model": "$CLAUDE_MODEL"} +SETTINGS +log "已写入 ~/.claude/settings.json" + +PROFILE_BLOCK="" +[ -n "$ANTHROPIC_API_KEY" ] && PROFILE_BLOCK+="export ANTHROPIC_API_KEY='$ANTHROPIC_API_KEY'\n" +[ -n "$ANTHROPIC_BASE_URL" ] && PROFILE_BLOCK+="export ANTHROPIC_BASE_URL='$ANTHROPIC_BASE_URL'\n" +PROFILE_BLOCK+="export CLAUDE_MODEL='$CLAUDE_MODEL'\n" + +for rc in ~/.bashrc ~/.profile; do + if ! grep -q "ANTHROPIC_API_KEY" "$rc" 2>/dev/null; then + printf "\n# Claude Code CLI\n${PROFILE_BLOCK}" >> "$rc" + log "已追加环境变量到 $rc" + fi +done + +# ────────────────────────────────────────────────────────────── +# Step 5: Unity MCP Server +# ────────────────────────────────────────────────────────────── +step "5/6 Unity MCP Server (AnkleBreaker-Studio)" + +MCP_SERVER_DIR="$HOME/.mcp-servers/unity-mcp-server" +mkdir -p "$HOME/.mcp-servers" + +if [ -d "$MCP_SERVER_DIR/.git" ]; then + log "unity-mcp-server 已存在,更新至最新..." + git -C "$MCP_SERVER_DIR" pull --quiet +else + log "克隆 unity-mcp-server..." + git clone --quiet https://github.com/AnkleBreaker-Studio/unity-mcp-server.git "$MCP_SERVER_DIR" +fi + +log "安装 npm 依赖..." +(cd "$MCP_SERVER_DIR" && npm install --prefer-offline --quiet 2>/dev/null || npm install --quiet) + +# 确定入口文件 +if [ -f "$MCP_SERVER_DIR/src/index.js" ]; then + SERVER_ENTRY="$MCP_SERVER_DIR/src/index.js" +elif [ -f "$MCP_SERVER_DIR/build/index.js" ]; then + SERVER_ENTRY="$MCP_SERVER_DIR/build/index.js" +else + SERVER_ENTRY="$MCP_SERVER_DIR/index.js" +fi +log "MCP Server 入口:$SERVER_ENTRY" + +# 写入 Claude Code (WSL2) MCP 配置 +MCP_CONFIG_DIR="$HOME/.config/Claude" +MCP_CONFIG_FILE="$MCP_CONFIG_DIR/claude_desktop_config.json" +mkdir -p "$MCP_CONFIG_DIR" + +python3 - << PYEOF +import json, os + +cfg_file = "$MCP_CONFIG_FILE" +try: + with open(cfg_file) as f: + cfg = json.load(f) +except: + cfg = {} + +cfg.setdefault("mcpServers", {}) +cfg["mcpServers"]["unity-mcp"] = { + "command": "node", + "args": ["$SERVER_ENTRY"], + "env": {"UNITY_BRIDGE_PORT": "$BRIDGE_PORT"} +} + +with open(cfg_file, "w") as f: + json.dump(cfg, f, indent=2) +print("Claude Code (WSL2) MCP config written") +PYEOF + +log "MCP 配置已写入 $MCP_CONFIG_FILE" + +# ────────────────────────────────────────────────────────────── +# Step 6: Rust 工具链 & Token Killer +# ────────────────────────────────────────────────────────────── +step "6/6 Rust 工具链 & Token Killer" + +[ -f "$HOME/.cargo/env" ] && source "$HOME/.cargo/env" + +if command -v rustc &>/dev/null; then + log "Rust 已安装: $(rustc --version)" +else + log "通过 rustup 安装 Rust stable..." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --default-toolchain stable --no-modify-path + source "$HOME/.cargo/env" + log "Rust 安装完成: $(rustc --version)" +fi + +for rc in ~/.bashrc ~/.profile; do + grep -q 'cargo/env' "$rc" 2>/dev/null || echo 'source ~/.cargo/env 2>/dev/null || true' >> "$rc" +done + +if command -v rtk &>/dev/null; then + log "rtk 已安装: $(rtk --version 2>/dev/null || echo ok)" +else + log "安装 rtk (Rust Token Killer)..." + sudo apt-get install -y -qq pkg-config libssl-dev 2>/dev/null || true + if cargo install --git https://github.com/rtk-ai/rtk 2>&1 | tail -3; then + log "rtk 安装成功" + else + warn "rtk 安装失败,请手动运行: cargo install --git https://github.com/rtk-ai/rtk" + fi +fi + +# ────────────────────────────────────────────────────────────── +# 安装摘要 +# ────────────────────────────────────────────────────────────── +echo "" +echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" +echo -e "${GREEN} ✅ WSL2 环境部署完成${NC}" +echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" +echo -e " Node.js $(node --version 2>/dev/null || echo 未安装)" +echo -e " npm $(npm --version 2>/dev/null || echo 未安装)" +echo -e " Claude Code $(claude --version 2>/dev/null | head -1 || echo 未安装)" +echo -e " Rust $(rustc --version 2>/dev/null || echo 未安装)" +echo -e " rtk $(rtk --version 2>/dev/null || echo 未安装)" +echo -e " MCP Server $SERVER_ENTRY" +if [ -n "${http_proxy:-}" ]; then +echo -e " Proxy $http_proxy" +fi +echo "" +echo -e " ${YELLOW}⚠ 后续步骤(手动):${NC}" +echo -e " 1. Unity Package Manager 安装插件:" +echo -e " ${CYAN}https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git${NC}" +echo -e " 2. 确认 Unity Bridge 在线: http://127.0.0.1:${BRIDGE_PORT}/api/ping" +echo -e " 3. 重启 AI 客户端(Claude Desktop / Cursor / Windsurf)" +echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" +echo "" +log "请重新加载 shell: source ~/.bashrc" \ No newline at end of file