# ============================================================================= # Bookworm .claude 同步 - 阶段 2: 目标机器接收 + 路径改写 + 依赖重建 # 在目标机器 (janson9527us) 上运行 # ============================================================================= [CmdletBinding()] param( [string]$SourceIP = '192.168.10.17', [int]$Port = 8765, [string]$SourceUser = 'leesu', [switch]$KeepCredentials, # 保留 .credentials.json (默认会删让其重登) [switch]$SkipRebuild # 跳过 npm/venv 重建 ) $ErrorActionPreference = 'Stop' $dst = "$env:USERPROFILE\.claude" $tmpZip = "$env:TEMP\claude-sync.zip" $url = "http://${SourceIP}:${Port}/claude-sync.zip" $targetUser = $env:USERNAME Write-Host "==================== 阶段 2: 接收 + 重建 ====================" -ForegroundColor Cyan Write-Host " 源: $url" Write-Host " 目标: $dst" Write-Host " 路径改写: $SourceUser → $targetUser" Write-Host "" # ---------- 0) 前置检查 ---------- Write-Host "[0/6] 前置检查..." -ForegroundColor Yellow $nodeOk = $null -ne (Get-Command node -ErrorAction SilentlyContinue) $pythonOk = $null -ne (Get-Command python -ErrorAction SilentlyContinue) Write-Host " Node.js: $(if ($nodeOk) {'✅ '+(node --version)} else {'❌ 未安装 (hooks 必须有 Node 18+)'})" Write-Host " Python: $(if ($pythonOk) {'✅ '+(python --version)} else {'⚠️ 未安装 (mcp-servers 重建需要)'})" if (-not $nodeOk) { Write-Host "❌ 缺 Node.js, 请先 winget install OpenJS.NodeJS.LTS 后重新打开 PowerShell 再跑" -ForegroundColor Red exit 1 } # ---------- 1) 备份并清空目标 ---------- Write-Host "[1/6] 备份并清空目标目录..." -ForegroundColor Yellow if (Test-Path $dst) { $bak = "$dst.old.$(Get-Date -Format yyyyMMddHHmmss)" Move-Item $dst $bak -Force Write-Host " 旧 .claude 已备份到: $bak" } New-Item $dst -ItemType Directory -Force | Out-Null # ---------- 2) 下载 ---------- Write-Host "[2/6] 下载 zip..." -ForegroundColor Yellow try { $progressPreference = 'SilentlyContinue' Invoke-WebRequest -Uri $url -OutFile $tmpZip -UseBasicParsing $progressPreference = 'Continue' $size = [Math]::Round((Get-Item $tmpZip).Length / 1MB, 1) Write-Host " ✅ 下载完成: $size MB" } catch { Write-Host "❌ 下载失败: $_" -ForegroundColor Red Write-Host " 检查源机器是否在跑 1-pack-and-serve.ps1 + 防火墙是否放行 $Port" -ForegroundColor Yellow exit 1 } # ---------- 3) 解压 ---------- Write-Host "[3/6] 解压..." -ForegroundColor Yellow Expand-Archive -Path $tmpZip -DestinationPath $dst -Force Write-Host " ✅ 解压完成" # ---------- 4) 路径改写 ---------- Write-Host "[4/6] 路径改写 ($SourceUser → $targetUser)..." -ForegroundColor Yellow # 收集要改写的配置文件 (扩展名匹配) $rewriteTargets = @() foreach ($pattern in @('*.json','*.md','CLAUDE.md','*.jsonl')) { $rewriteTargets += Get-ChildItem $dst -Filter $pattern -File -ErrorAction SilentlyContinue } # 排除大文件(>5MB 的 jsonl 历史不必改) $rewriteTargets = $rewriteTargets | Where-Object { $_.Length -lt 5MB } $rewriteCount = 0 foreach ($f in $rewriteTargets) { try { $content = Get-Content $f.FullName -Raw -Encoding UTF8 -ErrorAction Stop $orig = $content $content = $content ` -replace [regex]::Escape("C:/Users/$SourceUser/"), "C:/Users/$targetUser/" ` -replace [regex]::Escape("C:\Users\$SourceUser\"), "C:\Users\$targetUser\" ` -replace [regex]::Escape("C:\\Users\\$SourceUser\\"), "C:\\Users\\$targetUser\\" ` -replace [regex]::Escape("/c/Users/$SourceUser/"), "/c/Users/$targetUser/" if ($content -ne $orig) { Set-Content $f.FullName $content -Encoding UTF8 -NoNewline $rewriteCount++ } } catch { # 跳过二进制或编码异常的文件 } } Write-Host " ✅ 改写 $rewriteCount 个文件" # 残留检查 $leftovers = Get-ChildItem $dst -Filter "*.json" -File -ErrorAction SilentlyContinue | Where-Object { $_.Length -lt 5MB } | Select-String -Pattern "Users[/\\]$SourceUser" -List if ($leftovers) { Write-Host " ⚠️ 以下文件仍含 $SourceUser 路径残留:" -ForegroundColor Yellow $leftovers | ForEach-Object { Write-Host " - $($_.Path)" } } # 删除 OAuth 凭证 (机器绑定) if (-not $KeepCredentials) { Remove-Item "$dst\.credentials.json" -Force -ErrorAction SilentlyContinue Write-Host " 🔑 已删 .credentials.json (启动后请 claude login 重登)" } # ---------- 5) 重建运行时依赖 ---------- if ($SkipRebuild) { Write-Host "[5/6] 跳过依赖重建 (-SkipRebuild 已指定)" -ForegroundColor Yellow } else { Write-Host "[5/6] 重建运行时依赖..." -ForegroundColor Yellow # 5.1) 根 node_modules if (Test-Path "$dst\package.json") { Write-Host " 📦 根目录 npm install..." Push-Location $dst npm install --no-audit --no-fund --silent 2>&1 | Out-Null Pop-Location Write-Host " ✅ 完成" } # 5.2) 各 skill 的 node_modules Get-ChildItem "$dst\skills" -Directory -ErrorAction SilentlyContinue | ForEach-Object { if (Test-Path "$($_.FullName)\package.json") { Write-Host " 📦 skill: $($_.Name) npm install..." Push-Location $_.FullName npm install --no-audit --no-fund --silent 2>&1 | Out-Null Pop-Location } } # 5.3) 各 mcp-server 的 Python venv if ($pythonOk) { Get-ChildItem "$dst\mcp-servers" -Directory -ErrorAction SilentlyContinue | ForEach-Object { $serverDir = $_.FullName $serverName = $_.Name $hasPyProject = (Test-Path "$serverDir\pyproject.toml") -or (Test-Path "$serverDir\requirements.txt") -or (Test-Path "$serverDir\setup.py") if (-not $hasPyProject) { return } Write-Host " 🐍 mcp-server: $serverName 创建 venv..." Push-Location $serverDir try { python -m venv .venv 2>&1 | Out-Null $pip = "$serverDir\.venv\Scripts\pip.exe" & $pip install --upgrade pip --quiet 2>&1 | Out-Null if (Test-Path "pyproject.toml") { & $pip install -e . --quiet 2>&1 | Out-Null } elseif (Test-Path "requirements.txt") { & $pip install -r requirements.txt --quiet 2>&1 | Out-Null } Write-Host " ✅ $serverName 完成" } catch { Write-Host " ⚠️ $serverName 失败: $_" -ForegroundColor Yellow } finally { Pop-Location } } } else { Write-Host " ⚠️ Python 未安装, 跳过 mcp-servers venv 重建" -ForegroundColor Yellow } } # ---------- 6) 验证 ---------- Write-Host "[6/6] 干跑核心 hooks 验证..." -ForegroundColor Yellow $coreHooks = @( 'stop-dispatcher.js', 'block-dangerous-commands.js', 'block-sensitive-files.js' ) $allOk = $true foreach ($h in $coreHooks) { $p = "$dst\hooks\$h" if (-not (Test-Path $p)) { Write-Host " ❌ 缺失: $h" -ForegroundColor Red $allOk = $false continue } $output = '{}' | node $p 2>&1 $code = $LASTEXITCODE # 0 = 通过, 2 = 非阻塞警告 (block-* hooks 常用), 都算 OK if ($code -in 0,2) { Write-Host " ✅ $h (exit=$code)" } else { Write-Host " ❌ $h (exit=$code)" -ForegroundColor Red Write-Host " $output" -ForegroundColor DarkGray $allOk = $false } } # 清理 zip Remove-Item $tmpZip -Force -ErrorAction SilentlyContinue Write-Host "" if ($allOk) { Write-Host "============== ✅ 同步完成 ==============" -ForegroundColor Green Write-Host " 下一步:" Write-Host " 1. 关闭所有 Claude Code 窗口后重开" if (-not $KeepCredentials) { Write-Host " 2. 运行 'claude login' 完成登录" } Write-Host " 3. 启动新会话, 应看到 [BOOKWORM_SESSION_START] 横幅" Write-Host "========================================" -ForegroundColor Green } else { Write-Host "============== ⚠️ 同步完成但有问题 ==============" -ForegroundColor Yellow Write-Host " 有 hook 验证失败, 请检查上面 ❌ 的错误信息" Write-Host " 备份目录: $env:USERPROFILE\.claude.old.*" Write-Host "================================================" -ForegroundColor Yellow }