216 lines
8.5 KiB
PowerShell
216 lines
8.5 KiB
PowerShell
|
|
# =============================================================================
|
|||
|
|
# 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
|
|||
|
|
}
|