#requires -Version 5.1 <# Rabijinnie's Space:直接下载发布程序,不调用 claude.ai/install.ps1。 #> [CmdletBinding()] param( [Parameter(Position = 0)] [ValidatePattern('\A(latest|stable|[0-9]+\.[0-9]+\.[0-9]+)\z')] [string]$Target = 'latest', [switch]$DryRun ) # 独立作用域:兼容 irm | iex,不退出读者的 PowerShell。 & { param([string]$InstallTarget, [switch]$Preview) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $stageDir = $null $versionTemp = $null $binTemp = $null try { if ($env:OS -ne 'Windows_NT') { throw '此脚本用于 Windows。Mac / Linux / WSL 请使用 cc.sh。' } if (-not [Environment]::Is64BitProcess) { throw '请使用 64 位 Windows PowerShell 或 PowerShell 7。' } if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64' -or $env:PROCESSOR_ARCHITEW6432 -eq 'ARM64') { $platform = 'win32-arm64' } else { $platform = 'win32-x64' } $repo = 'https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases' Write-Host "Claude Code 直接安装`n所选版本:$InstallTarget`n系统架构:$platform`n程序源:$repo" Write-Host '流程:读取对应版本清单 → 下载程序 → 校验 SHA256 和大小 → 安装。' if ($Preview) { return } [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 $stageDir = Join-Path ([IO.Path]::GetTempPath()) ('claude-code-setup-' + [Guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $stageDir | Out-Null function Get-ReleaseFile { param([string]$Url, [string]$Destination, [int]$Timeout = 120) for ($attempt = 1; $attempt -le 3; $attempt++) { try { # 固定 HTTPS 发布桶,无需跟随重定向或关闭证书校验。 Invoke-WebRequest -Uri $Url -OutFile $Destination -UseBasicParsing -TimeoutSec $Timeout -MaximumRedirection 0 if ((Get-Item -LiteralPath $Destination).Length -eq 0) { throw '下载内容为空。' } return } catch { if ($attempt -eq 3) { throw ('下载失败:' + $Url + '。请检查版本是否存在、程序源连接及网络设置。' + $_.Exception.Message) } Start-Sleep -Seconds 2 } } } function Read-ReleaseText { param([string]$Path) $text = [IO.File]::ReadAllText($Path).Trim() if ($text -match '(?is)^\s*<(!doctype\s+html|html(\s|>))') { throw '程序源返回了 HTML 网页。请检查网络认证、代理或访问限制。' } return $text } Write-Host "`n[1/4] 读取版本与校验清单……" $version = $InstallTarget if ($InstallTarget -eq 'latest' -or $InstallTarget -eq 'stable') { $channelPath = Join-Path $stageDir 'channel' Get-ReleaseFile -Url "$repo/$InstallTarget" -Destination $channelPath $version = Read-ReleaseText $channelPath } if ($version -notmatch '\A[0-9]+\.[0-9]+\.[0-9]+\z') { throw '程序源返回了无效版本号。' } Write-Host "实际下载版本:$version" $manifestPath = Join-Path $stageDir 'manifest.json' Get-ReleaseFile -Url "$repo/$version/manifest.json" -Destination $manifestPath $manifest = (Read-ReleaseText $manifestPath) | ConvertFrom-Json if (-not $manifest.PSObject.Properties['version'] -or $manifest.version -cne $version) { throw '清单版本与所选版本不一致。' } if (-not $manifest.PSObject.Properties['platforms'] -or -not $manifest.platforms.PSObject.Properties[$platform]) { throw "这个版本没有 $platform 的安装文件。" } $entry = $manifest.platforms.PSObject.Properties[$platform].Value if (-not $entry.PSObject.Properties['checksum'] -or [string]$entry.checksum -notmatch '\A[a-fA-F0-9]{64}\z') { throw '清单中的 SHA256 格式无效。' } $checksum = ([string]$entry.checksum).ToLowerInvariant() [long]$expectedSize = 0 if (-not $entry.PSObject.Properties['size'] -or -not [long]::TryParse([string]$entry.size, [ref]$expectedSize) -or $expectedSize -le 0) { throw '清单中的文件大小无效。' } if (-not $entry.PSObject.Properties['binary'] -or $entry.binary -cne 'claude.exe') { throw '清单中的程序名称无效。' } Write-Host "`n[2/4] 下载程序(约 $expectedSize 字节,耗时取决于网络)……" $downloadUrl = "$repo/$version/$platform/claude.exe" Write-Host "下载地址:$downloadUrl" $binaryPath = Join-Path $stageDir 'claude.exe' Get-ReleaseFile -Url $downloadUrl -Destination $binaryPath -Timeout 1800 if ((Get-Item -LiteralPath $binaryPath).Length -ne $expectedSize) { throw '文件大小不匹配,未安装。' } $actualHash = (Get-FileHash -LiteralPath $binaryPath -Algorithm SHA256).Hash.ToLowerInvariant() if ($actualHash -cne $checksum) { throw 'SHA256 校验失败,未运行程序、未替换已有安装。' } Write-Host 'SHA256 与文件大小校验通过。' Write-Host "`n[3/4] 检查程序并安装……" $versionOutput = & $binaryPath --version if ($LASTEXITCODE -ne 0) { throw '下载的程序无法启动,未替换已有安装。请检查系统版本和运行依赖。' } $versionText = ($versionOutput -join "`n").Trim() if ($versionText -notmatch '\A([0-9]+\.[0-9]+\.[0-9]+)\s+\(Claude Code\)') { throw '程序没有返回预期版本信息,未安装。' } if ($Matches[1] -cne $version) { throw '程序实际版本与清单不一致,未安装。' } $versionsDir = Join-Path $env:USERPROFILE '.local\share\claude\versions' $binDir = Join-Path $env:USERPROFILE '.local\bin' New-Item -ItemType Directory -Force -Path $versionsDir, $binDir | Out-Null $finalPath = Join-Path $versionsDir "$version.exe" $linkPath = Join-Path $binDir 'claude.exe' if ((Test-Path -LiteralPath $finalPath -PathType Container) -or (Test-Path -LiteralPath $linkPath -PathType Container)) { throw '程序目标位置是一个目录,请检查现有安装。' } $suffix = [Guid]::NewGuid().ToString('N') $versionTemp = Join-Path $versionsDir ".claude-install-$suffix.exe" Copy-Item -LiteralPath $binaryPath -Destination $versionTemp if (Test-Path -LiteralPath $finalPath) { $versionBackup = "$finalPath.backup.$suffix" [IO.File]::Replace($versionTemp, $finalPath, $versionBackup) } else { [IO.File]::Move($versionTemp, $finalPath) } $versionTemp = $null $binTemp = Join-Path $binDir ".claude-install-$suffix.exe" Copy-Item -LiteralPath $finalPath -Destination $binTemp if (Test-Path -LiteralPath $linkPath) { $binBackup = "$linkPath.backup.$suffix" [IO.File]::Replace($binTemp, $linkPath, $binBackup) Write-Host "原命令已备份:$binBackup" } else { [IO.File]::Move($binTemp, $linkPath) } $binTemp = $null Write-Host "`n[4/4] 安装完成`n$versionText`n位置:$linkPath" Write-Host '已有账号与 API 配置已保留。本脚本不修改自动更新设置;指定版本不等于永久锁定版本。' Write-Host '打开新终端,输入 claude 就能启动。若提示找不到命令,请按教程添加用户 PATH,或先运行:' Write-Host '$env:Path = "$env:USERPROFILE\.local\bin;$env:Path"' Write-Host '再运行 claude --version 和 claude doctor。' } catch { throw ('安装未完成:' + $_.Exception.Message + ' 如果提示程序被占用,请关闭 Claude Code 后重试。不会自动换装其他版本。') } finally { foreach ($tempFile in @($versionTemp, $binTemp)) { if ($tempFile -and (Test-Path -LiteralPath $tempFile)) { Remove-Item -LiteralPath $tempFile -Force -ErrorAction SilentlyContinue } } if ($stageDir -and (Test-Path -LiteralPath $stageDir)) { Remove-Item -LiteralPath $stageDir -Recurse -Force -ErrorAction SilentlyContinue } } } -InstallTarget $Target -Preview:$DryRun