# PrimeRouter — Claude Code CLI install + configure (Windows). $ErrorActionPreference = "Stop" function Say-Ok ($msg) { Write-Host "[OK] $msg" -ForegroundColor Green } function Say-Warn ($msg) { Write-Host "[!] $msg" -ForegroundColor Yellow } function Say-Die ($msg) { Write-Host "[X] $msg" -ForegroundColor Red; exit 1 } function Confirm-Action { param([string]$Prompt) # Set by one-click installer wrappers to run unattended. if ($env:PRIMEROUTER_AUTO_CONFIRM -eq "1") { return $true } $reply = Read-Host "$Prompt [y/N]" return ($reply -match '^[Yy]$') } $PrBaseUrl = "https://primerouter.ai" $InstallBase = "https://primerouter.ai/install" # --- Shared npm registry selection (keep identical across PrimeRouter scripts) --- # Prefers the mirror registry (consistently fast from our users' networks; # the default registry is often reachable but too slow) and falls back to # the default registry when the mirror is unreachable. Respects a registry # the user already configured in .npmrc. $NpmMirrorRegistry = "https://registry.npmmirror.com" function Get-NpmRegistryArgs { $configured = "" try { $configured = npm config get registry 2>$null } catch { $configured = "" } if ($configured -and $configured -notmatch '^https://registry\.npmjs\.org/?$') { return @() } try { Invoke-WebRequest -Uri "$NpmMirrorRegistry/-/ping" -TimeoutSec 8 -UseBasicParsing -ErrorAction Stop | Out-Null return @("--registry=$NpmMirrorRegistry") } catch { Say-Warn "$NpmMirrorRegistry is unreachable from this network; using the default npm registry." return @() } } # --- End shared npm registry selection --- # --- Shared Git Bash bootstrap (keep identical across PrimeRouter scripts) --- # Claude Code on Windows needs Git Bash or PowerShell 7 as its shell backend; # a fresh Windows machine ships neither (built-in Windows PowerShell 5.1 does # not count). Bootstrap a portable Git (per-user, no admin) when nothing # usable is present. Version + SHA256 are pinned so the download is # verifiable; bump them together. $PortableGitTag = "v2.55.0.windows.2" $PortableGitVersion = "2.55.0.2" $PortableGitSha = @{ "64-bit" = "b20d42da3afa228e9fa6174480de820282667e799440d655e308f700dfa0d0df" "arm64" = "65b913a56a62d7a91fc11a2eecb08422aaa34332d3b2ea39457d2eda02c2f99c" } function Ensure-ClaudeShellBackend { if ($env:CLAUDE_CODE_GIT_BASH_PATH -and (Test-Path $env:CLAUDE_CODE_GIT_BASH_PATH)) { return } if (Get-Command pwsh -ErrorAction SilentlyContinue) { return } # A regular Git for Windows install provides bin\bash.exe that Claude # Code discovers on its own. $gitCmd = Get-Command git -ErrorAction SilentlyContinue if ($gitCmd -and $gitCmd.Source) { $bash = Join-Path (Split-Path (Split-Path $gitCmd.Source -Parent) -Parent) "bin\bash.exe" if (Test-Path $bash) { return } } foreach ($candidate in @( "$env:ProgramFiles\Git\bin\bash.exe", "${env:ProgramFiles(x86)}\Git\bin\bash.exe", "$env:LOCALAPPDATA\Programs\Git\bin\bash.exe" )) { if ($candidate -and (Test-Path $candidate)) { return } } $gitDir = Join-Path $env:USERPROFILE ".primerouter\git" $bashPath = Join-Path $gitDir "bin\bash.exe" if (-not (Test-Path $bashPath)) { $arch = if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { "arm64" } else { "64-bit" } $fname = "PortableGit-$PortableGitVersion-$arch.7z.exe" Say-Warn "Claude Code needs Git Bash or PowerShell 7; neither was found." Say-Warn "Installing a portable Git into $gitDir (per-user, no admin needed)." $sfxPath = Join-Path $env:TEMP "primerouter-$fname" # The mirror comes first: it is consistently fast from our users' # networks, while github.com is often reachable but too slow. $downloadUrl = "https://registry.npmmirror.com/-/binary/git-for-windows/$PortableGitTag/$fname" try { Invoke-WebRequest -Method Head -Uri $downloadUrl -TimeoutSec 15 -ErrorAction Stop | Out-Null } catch { Say-Warn "The download mirror is unreachable from this network; using github.com instead." $downloadUrl = "https://github.com/git-for-windows/git/releases/download/$PortableGitTag/$fname" } try { Invoke-WebRequest -Uri $downloadUrl -OutFile $sfxPath -ErrorAction Stop } catch { if (Test-Path $sfxPath) { Remove-Item -Force $sfxPath } Say-Die "Failed to download ${fname}: $_" } $actual = (Get-FileHash -Path $sfxPath -Algorithm SHA256).Hash.ToLower() if ($actual -ne $PortableGitSha[$arch]) { Remove-Item -Force $sfxPath Say-Die "Checksum verification failed for $fname" } if (Test-Path $gitDir) { Remove-Item -Recurse -Force $gitDir } New-Item -ItemType Directory -Force -Path $gitDir | Out-Null # PortableGit is a 7-Zip self-extractor; -y -o extracts silently. $proc = Start-Process -FilePath $sfxPath -ArgumentList "-y", "-o`"$gitDir`"" -Wait -PassThru -NoNewWindow Remove-Item -Force $sfxPath if ($proc.ExitCode -ne 0 -or -not (Test-Path $bashPath)) { Say-Die "Portable Git extraction failed (exit code $($proc.ExitCode))." } } [System.Environment]::SetEnvironmentVariable("CLAUDE_CODE_GIT_BASH_PATH", $bashPath, "User") $env:CLAUDE_CODE_GIT_BASH_PATH = $bashPath Say-Ok "Git Bash ready at $bashPath (CLAUDE_CODE_GIT_BASH_PATH set for the current user)." } # --- End shared Git Bash bootstrap --- Ensure-ClaudeShellBackend # --- 1. Node.js --- $needNode = $false if (-not (Get-Command node -ErrorAction SilentlyContinue)) { $needNode = $true } else { $major = [int]((node -v).TrimStart('v').Split('.')[0]) if ($major -lt 22) { $needNode = $true } } if ($needNode) { Say-Warn "Node.js v22+ is required to install @anthropic-ai/claude-code." if (-not (Confirm-Action "Install Node.js LTS now?")) { Say-Die "Aborted. Install Node.js manually then re-run." } Invoke-Expression (Invoke-RestMethod -Uri "$InstallBase/nodejs.ps1") } # --- 2. claude-code CLI --- if (-not (Get-Command claude -ErrorAction SilentlyContinue)) { Say-Warn "Claude Code CLI (@anthropic-ai/claude-code) is not on PATH." if (-not (Confirm-Action "Install it globally via npm now?")) { Say-Die "Aborted. Install @anthropic-ai/claude-code manually then re-run." } $npmRegistryArgs = Get-NpmRegistryArgs npm install -g $npmRegistryArgs "@anthropic-ai/claude-code" } # --- 3. API key --- # Pre-seeded by personalized installers; prompt only when absent. $PrKey = $env:PRIMEROUTER_API_KEY if ([string]::IsNullOrWhiteSpace($PrKey)) { $secure = Read-Host "Paste your PrimeRouter API key" -AsSecureString $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($secure) $PrKey = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptr) [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ptr) } if ([string]::IsNullOrWhiteSpace($PrKey)) { Say-Die "API key cannot be empty." } # --- 4. Persist via user-scope env vars --- # CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: skip non-essential requests # (telemetry, error reporting, update checks). [System.Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", $PrBaseUrl, "User") [System.Environment]::SetEnvironmentVariable("ANTHROPIC_AUTH_TOKEN", $PrKey, "User") [System.Environment]::SetEnvironmentVariable("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1", "User") $env:ANTHROPIC_BASE_URL = $PrBaseUrl $env:ANTHROPIC_AUTH_TOKEN = $PrKey $env:CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1" Say-Ok "ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN set for the current user." Say-Ok "Claude Code is wired to $PrBaseUrl. Open a new terminal then run: claude"