1039 lines
37 KiB
PowerShell
1039 lines
37 KiB
PowerShell
#Requires -Version 5.1
|
||
<#
|
||
.SYNOPSIS
|
||
Claude Code Installer for Windows
|
||
A colorized, interactive installer with full system detection
|
||
|
||
.DESCRIPTION
|
||
This script automates the installation of Claude Code on Windows systems.
|
||
|
||
Features:
|
||
- Detects Windows version and architecture
|
||
- Checks/removes existing Node.js and npm installations
|
||
- Installs Node.js 20 LTS
|
||
- Installs Claude Code
|
||
- Configures permissions interactively
|
||
|
||
.NOTES
|
||
Author: BitMaster
|
||
Repository: https://git.bitmaster.cc/BitMaster/claude
|
||
License: MIT
|
||
|
||
.EXAMPLE
|
||
.\claude-code-installer.ps1
|
||
|
||
.EXAMPLE
|
||
# Run directly from web
|
||
irm https://git.bitmaster.cc/BitMaster/claude/raw/branch/main/claude-code-installer.ps1 | iex
|
||
#>
|
||
|
||
# Ensure we're running with proper execution
|
||
$ErrorActionPreference = "Stop"
|
||
$ProgressPreference = "SilentlyContinue"
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Configuration
|
||
#-------------------------------------------------------------------------------
|
||
|
||
$script:BoxWidth = 65 # Inner width of boxes
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Color and Display Functions
|
||
#-------------------------------------------------------------------------------
|
||
|
||
function Write-ColorText {
|
||
param(
|
||
[string]$Text,
|
||
[ConsoleColor]$Color = "White",
|
||
[switch]$NoNewline
|
||
)
|
||
if ($NoNewline) {
|
||
Write-Host $Text -ForegroundColor $Color -NoNewline
|
||
} else {
|
||
Write-Host $Text -ForegroundColor $Color
|
||
}
|
||
}
|
||
|
||
function Write-BoxTop {
|
||
param([int]$Width = $script:BoxWidth)
|
||
$line = "─" * $Width
|
||
Write-ColorText " ┌$line┐" -Color White
|
||
}
|
||
|
||
function Write-BoxMiddle {
|
||
param([int]$Width = $script:BoxWidth)
|
||
$line = "─" * $Width
|
||
Write-ColorText " ├$line┤" -Color White
|
||
}
|
||
|
||
function Write-BoxBottom {
|
||
param([int]$Width = $script:BoxWidth)
|
||
$line = "─" * $Width
|
||
Write-ColorText " └$line┘" -Color White
|
||
}
|
||
|
||
function Write-BoxTitle {
|
||
param(
|
||
[string]$Title,
|
||
[int]$Width = $script:BoxWidth
|
||
)
|
||
$padding = $Width - $Title.Length - 2
|
||
$leftPad = [math]::Floor($padding / 2)
|
||
$rightPad = $padding - $leftPad
|
||
|
||
Write-ColorText " │" -Color White -NoNewline
|
||
Write-ColorText (" " * ($leftPad + 1)) -Color White -NoNewline
|
||
Write-ColorText $Title -Color White -NoNewline
|
||
Write-ColorText (" " * ($rightPad + 1)) -Color White -NoNewline
|
||
Write-ColorText "│" -Color White
|
||
}
|
||
|
||
function Write-BoxEmpty {
|
||
param([int]$Width = $script:BoxWidth)
|
||
Write-ColorText " │" -Color White -NoNewline
|
||
Write-ColorText (" " * $Width) -Color White -NoNewline
|
||
Write-ColorText "│" -Color White
|
||
}
|
||
|
||
function Write-BoxContent {
|
||
param(
|
||
[string]$Label,
|
||
[ConsoleColor]$LabelColor = "Cyan",
|
||
[string]$Value,
|
||
[ConsoleColor]$ValueColor = "Green",
|
||
[int]$LabelWidth = 16,
|
||
[int]$Width = $script:BoxWidth
|
||
)
|
||
|
||
$paddedLabel = $Label.PadRight($LabelWidth)
|
||
$remainingWidth = $Width - $LabelWidth - 4 # 4 for prefix spacing
|
||
$paddedValue = $Value
|
||
if ($Value.Length -gt $remainingWidth) {
|
||
$paddedValue = $Value.Substring(0, $remainingWidth - 3) + "..."
|
||
}
|
||
$totalPadding = $Width - $paddedLabel.Length - $paddedValue.Length - 2
|
||
if ($totalPadding -lt 0) { $totalPadding = 0 }
|
||
|
||
Write-ColorText " │ " -Color White -NoNewline
|
||
Write-ColorText $paddedLabel -Color $LabelColor -NoNewline
|
||
Write-ColorText $paddedValue -Color $ValueColor -NoNewline
|
||
Write-ColorText (" " * $totalPadding) -Color White -NoNewline
|
||
Write-ColorText " │" -Color White
|
||
}
|
||
|
||
function Write-BoxLine {
|
||
param(
|
||
[string]$Text,
|
||
[ConsoleColor]$Color = "White",
|
||
[int]$Width = $script:BoxWidth,
|
||
[int]$Indent = 1
|
||
)
|
||
|
||
$indentStr = " " * $Indent
|
||
$maxTextWidth = $Width - $Indent - 1
|
||
$displayText = $Text
|
||
if ($Text.Length -gt $maxTextWidth) {
|
||
$displayText = $Text.Substring(0, $maxTextWidth - 3) + "..."
|
||
}
|
||
$padding = $Width - $Indent - $displayText.Length - 1
|
||
if ($padding -lt 0) { $padding = 0 }
|
||
|
||
Write-ColorText " │" -Color White -NoNewline
|
||
Write-ColorText $indentStr -Color White -NoNewline
|
||
Write-ColorText $displayText -Color $Color -NoNewline
|
||
Write-ColorText (" " * $padding) -Color White -NoNewline
|
||
Write-ColorText " │" -Color White
|
||
}
|
||
|
||
function Write-BoxLineMultiColor {
|
||
param(
|
||
[array]$Parts, # Array of @{Text=""; Color="White"}
|
||
[int]$Width = $script:BoxWidth,
|
||
[int]$Indent = 1
|
||
)
|
||
|
||
$indentStr = " " * $Indent
|
||
$totalLength = $Indent
|
||
foreach ($part in $Parts) {
|
||
$totalLength += $part.Text.Length
|
||
}
|
||
$padding = $Width - $totalLength - 1
|
||
if ($padding -lt 0) { $padding = 0 }
|
||
|
||
Write-ColorText " │" -Color White -NoNewline
|
||
Write-ColorText $indentStr -Color White -NoNewline
|
||
foreach ($part in $Parts) {
|
||
Write-ColorText $part.Text -Color $part.Color -NoNewline
|
||
}
|
||
Write-ColorText (" " * $padding) -Color White -NoNewline
|
||
Write-ColorText " │" -Color White
|
||
}
|
||
|
||
function Write-Banner {
|
||
Clear-Host
|
||
Write-Host ""
|
||
Write-ColorText " ╔═══════════════════════════════════════════════════════════════════╗" -Color Cyan
|
||
Write-ColorText " ║ ║" -Color Cyan
|
||
Write-ColorText " ║ " -Color Cyan -NoNewline
|
||
Write-ColorText "██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗" -Color Cyan -NoNewline
|
||
Write-ColorText " ║" -Color Cyan
|
||
Write-ColorText " ║ " -Color Cyan -NoNewline
|
||
Write-ColorText "██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝" -Color Cyan -NoNewline
|
||
Write-ColorText " ║" -Color Cyan
|
||
Write-ColorText " ║ " -Color Cyan -NoNewline
|
||
Write-ColorText "██║ ██║ ███████║██║ ██║██║ ██║█████╗" -Color Cyan -NoNewline
|
||
Write-ColorText " ║" -Color Cyan
|
||
Write-ColorText " ║ " -Color Cyan -NoNewline
|
||
Write-ColorText "██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝" -Color Cyan -NoNewline
|
||
Write-ColorText " ║" -Color Cyan
|
||
Write-ColorText " ║ " -Color Cyan -NoNewline
|
||
Write-ColorText "╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗" -Color Cyan -NoNewline
|
||
Write-ColorText " ║" -Color Cyan
|
||
Write-ColorText " ║ " -Color Cyan -NoNewline
|
||
Write-ColorText "╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝" -Color Cyan -NoNewline
|
||
Write-ColorText " ║" -Color Cyan
|
||
Write-ColorText " ║ ║" -Color Cyan
|
||
Write-ColorText " ║ " -Color Cyan -NoNewline
|
||
Write-ColorText "C O D E I N S T A L L E R" -Color White -NoNewline
|
||
Write-ColorText " ║" -Color Cyan
|
||
Write-ColorText " ║ ║" -Color Cyan
|
||
Write-ColorText " ║ " -Color Cyan -NoNewline
|
||
Write-ColorText "[ Windows Edition ]" -Color Yellow -NoNewline
|
||
Write-ColorText " ║" -Color Cyan
|
||
Write-ColorText " ║ ║" -Color Cyan
|
||
Write-ColorText " ╚═══════════════════════════════════════════════════════════════════╝" -Color Cyan
|
||
Write-Host ""
|
||
}
|
||
|
||
function Write-Section {
|
||
param([string]$Title)
|
||
Write-Host ""
|
||
Write-ColorText " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -Color Blue
|
||
Write-ColorText " $Title" -Color White
|
||
Write-ColorText " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -Color Blue
|
||
Write-Host ""
|
||
}
|
||
|
||
function Write-Step {
|
||
param([string]$Message)
|
||
Write-ColorText " → " -Color Cyan -NoNewline
|
||
Write-ColorText $Message -Color White
|
||
}
|
||
|
||
function Write-Success {
|
||
param([string]$Message)
|
||
Write-ColorText " ✓ " -Color Green -NoNewline
|
||
Write-ColorText $Message -Color Green
|
||
}
|
||
|
||
function Write-Error2 {
|
||
param([string]$Message)
|
||
Write-ColorText " ✗ " -Color Red -NoNewline
|
||
Write-ColorText $Message -Color Red
|
||
}
|
||
|
||
function Write-Warning2 {
|
||
param([string]$Message)
|
||
Write-ColorText " ⚠ " -Color Yellow -NoNewline
|
||
Write-ColorText $Message -Color Yellow
|
||
}
|
||
|
||
function Write-Info {
|
||
param([string]$Message)
|
||
Write-ColorText " ℹ " -Color Blue -NoNewline
|
||
Write-ColorText $Message -Color DarkGray
|
||
}
|
||
|
||
function Confirm-Action {
|
||
param(
|
||
[string]$Prompt,
|
||
[bool]$DefaultYes = $true
|
||
)
|
||
|
||
if ($DefaultYes) {
|
||
$options = "[Y/n]"
|
||
} else {
|
||
$options = "[y/N]"
|
||
}
|
||
|
||
Write-ColorText " ? " -Color Yellow -NoNewline
|
||
Write-ColorText "$Prompt " -Color White -NoNewline
|
||
Write-ColorText $options -Color DarkGray -NoNewline
|
||
Write-ColorText ": " -Color White -NoNewline
|
||
|
||
$response = Read-Host
|
||
|
||
if ([string]::IsNullOrWhiteSpace($response)) {
|
||
return $DefaultYes
|
||
}
|
||
|
||
return $response -match "^[Yy]"
|
||
}
|
||
|
||
function Wait-ForKey {
|
||
Write-Host ""
|
||
Write-ColorText " Press any key to continue..." -Color DarkGray
|
||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||
Write-Host ""
|
||
}
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# System Detection Functions
|
||
#-------------------------------------------------------------------------------
|
||
|
||
function Get-SystemInfo {
|
||
Write-Section "⚙ System Detection"
|
||
|
||
Write-Step "Identifying your Windows system..."
|
||
Start-Sleep -Milliseconds 500
|
||
|
||
# Get Windows version info
|
||
$os = Get-CimInstance -ClassName Win32_OperatingSystem
|
||
$cs = Get-CimInstance -ClassName Win32_ComputerSystem
|
||
|
||
$script:WindowsVersion = $os.Caption
|
||
$script:WindowsBuild = $os.BuildNumber
|
||
$script:WindowsArch = $cs.SystemType
|
||
$script:ComputerName = $cs.Name
|
||
$script:TotalRAM = [math]::Round($cs.TotalPhysicalMemory / 1GB, 2)
|
||
|
||
# Determine architecture for Node.js download
|
||
if ($env:PROCESSOR_ARCHITECTURE -eq "AMD64" -or $env:PROCESSOR_ARCHITEW6432 -eq "AMD64") {
|
||
$script:Arch = "x64"
|
||
$script:ArchDisplay = "64-bit (x64)"
|
||
} elseif ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") {
|
||
$script:Arch = "arm64"
|
||
$script:ArchDisplay = "ARM64"
|
||
} else {
|
||
$script:Arch = "x86"
|
||
$script:ArchDisplay = "32-bit (x86)"
|
||
}
|
||
|
||
# Check if running as Administrator
|
||
$script:IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||
|
||
# Check PowerShell version
|
||
$script:PSVersion = $PSVersionTable.PSVersion.ToString()
|
||
|
||
# Display detected information
|
||
Write-Host ""
|
||
Write-BoxTop
|
||
Write-BoxTitle -Title "System Information"
|
||
Write-BoxMiddle
|
||
Write-BoxContent -Label "Windows:" -Value $WindowsVersion
|
||
Write-BoxContent -Label "Build:" -Value $WindowsBuild
|
||
Write-BoxContent -Label "Architecture:" -Value $ArchDisplay
|
||
Write-BoxContent -Label "Computer:" -Value $ComputerName
|
||
Write-BoxContent -Label "RAM:" -Value "$TotalRAM GB"
|
||
Write-BoxContent -Label "PowerShell:" -Value "v$PSVersion"
|
||
|
||
if ($IsAdmin) {
|
||
Write-BoxContent -Label "Admin Rights:" -Value "Yes" -ValueColor Green
|
||
} else {
|
||
Write-BoxContent -Label "Admin Rights:" -Value "No" -ValueColor Yellow
|
||
}
|
||
Write-BoxBottom
|
||
Write-Host ""
|
||
|
||
Write-Success "System detected successfully!"
|
||
Start-Sleep -Seconds 1
|
||
}
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Node.js Detection and Installation
|
||
#-------------------------------------------------------------------------------
|
||
|
||
function Test-NodeInstallation {
|
||
Write-Section "📦 Node.js & npm Detection"
|
||
|
||
Write-Step "Checking for existing Node.js installation..."
|
||
Start-Sleep -Milliseconds 500
|
||
|
||
$script:NodeInstalled = $false
|
||
$script:NpmInstalled = $false
|
||
$script:NodeVersion = ""
|
||
$script:NpmVersion = ""
|
||
$script:NeedsUpgrade = $false
|
||
|
||
# Check Node.js
|
||
try {
|
||
$nodeOutput = & node --version 2>$null
|
||
if ($nodeOutput) {
|
||
$script:NodeInstalled = $true
|
||
$script:NodeVersion = $nodeOutput
|
||
}
|
||
} catch {
|
||
$script:NodeInstalled = $false
|
||
}
|
||
|
||
# Check npm
|
||
try {
|
||
$npmOutput = & npm --version 2>$null
|
||
if ($npmOutput) {
|
||
$script:NpmInstalled = $true
|
||
$script:NpmVersion = $npmOutput
|
||
}
|
||
} catch {
|
||
$script:NpmInstalled = $false
|
||
}
|
||
|
||
# Display current status
|
||
Write-Host ""
|
||
Write-BoxTop
|
||
Write-BoxTitle -Title "Current Installation Status"
|
||
Write-BoxMiddle
|
||
|
||
if ($NodeInstalled) {
|
||
# Check if version is sufficient (need v18+)
|
||
$versionNum = $NodeVersion -replace "v", ""
|
||
$majorVersion = [int]($versionNum.Split('.')[0])
|
||
|
||
if ($majorVersion -ge 18) {
|
||
Write-BoxLineMultiColor -Parts @(
|
||
@{Text="✓ "; Color="Green"},
|
||
@{Text="Node.js: "; Color="White"},
|
||
@{Text="$NodeVersion"; Color="Green"},
|
||
@{Text=" (Compatible)"; Color="Green"}
|
||
)
|
||
} else {
|
||
Write-BoxLineMultiColor -Parts @(
|
||
@{Text="⚠ "; Color="Yellow"},
|
||
@{Text="Node.js: "; Color="White"},
|
||
@{Text="$NodeVersion"; Color="Yellow"},
|
||
@{Text=" (Needs upgrade to v18+)"; Color="Yellow"}
|
||
)
|
||
$script:NeedsUpgrade = $true
|
||
}
|
||
} else {
|
||
Write-BoxLineMultiColor -Parts @(
|
||
@{Text="✗ "; Color="Red"},
|
||
@{Text="Node.js: "; Color="White"},
|
||
@{Text="Not installed"; Color="Red"}
|
||
)
|
||
$script:NeedsUpgrade = $true
|
||
}
|
||
|
||
if ($NpmInstalled) {
|
||
Write-BoxLineMultiColor -Parts @(
|
||
@{Text="✓ "; Color="Green"},
|
||
@{Text="npm: "; Color="White"},
|
||
@{Text="v$NpmVersion"; Color="Green"}
|
||
)
|
||
} else {
|
||
Write-BoxLineMultiColor -Parts @(
|
||
@{Text="✗ "; Color="Red"},
|
||
@{Text="npm: "; Color="White"},
|
||
@{Text="Not installed"; Color="Red"}
|
||
)
|
||
}
|
||
|
||
Write-BoxBottom
|
||
Write-Host ""
|
||
|
||
# Determine action needed
|
||
if ($NodeInstalled -and $NeedsUpgrade) {
|
||
Write-Warning2 "Your Node.js version is incompatible with Claude Code."
|
||
Write-Info "Claude Code requires Node.js version 18.0.0 or higher."
|
||
Write-Host ""
|
||
|
||
if (Confirm-Action "Would you like to uninstall the existing version and install Node.js 20 LTS?") {
|
||
Uninstall-Node
|
||
Install-Node
|
||
} else {
|
||
Write-Error2 "Cannot proceed without Node.js 18+. Exiting."
|
||
exit 1
|
||
}
|
||
} elseif (-not $NodeInstalled) {
|
||
Write-Info "Node.js is not installed on your system."
|
||
Write-Host ""
|
||
|
||
if (Confirm-Action "Would you like to install Node.js 20 LTS?") {
|
||
Install-Node
|
||
} else {
|
||
Write-Error2 "Cannot proceed without Node.js. Exiting."
|
||
exit 1
|
||
}
|
||
} else {
|
||
Write-Success "Node.js installation is compatible!"
|
||
Write-Host ""
|
||
|
||
if (Confirm-Action "Would you like to reinstall Node.js anyway (fresh installation)?" -DefaultYes $false) {
|
||
Uninstall-Node
|
||
Install-Node
|
||
}
|
||
}
|
||
}
|
||
|
||
function Uninstall-Node {
|
||
Write-Section "⚙ Removing Existing Node.js Installation"
|
||
|
||
Write-Step "Searching for Node.js installations..."
|
||
Write-Host ""
|
||
|
||
# Check for Node.js in common locations
|
||
$nodePaths = @(
|
||
"$env:ProgramFiles\nodejs",
|
||
"${env:ProgramFiles(x86)}\nodejs",
|
||
"$env:LOCALAPPDATA\Programs\nodejs"
|
||
)
|
||
|
||
# Try to uninstall via Windows installer
|
||
Write-ColorText " → " -Color Yellow -NoNewline
|
||
Write-ColorText "Checking for installed Node.js package..." -Color White
|
||
|
||
$uninstallKeys = @(
|
||
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
|
||
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
|
||
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
|
||
)
|
||
|
||
$nodePackage = $null
|
||
foreach ($key in $uninstallKeys) {
|
||
$nodePackage = Get-ItemProperty $key -ErrorAction SilentlyContinue |
|
||
Where-Object { $_.DisplayName -like "*Node.js*" -or $_.DisplayName -like "*Node*" } |
|
||
Select-Object -First 1
|
||
if ($nodePackage) { break }
|
||
}
|
||
|
||
if ($nodePackage -and $nodePackage.UninstallString) {
|
||
Write-ColorText " → " -Color Yellow -NoNewline
|
||
Write-ColorText "Found Node.js installation. Uninstalling..." -Color White
|
||
|
||
try {
|
||
$uninstallCmd = $nodePackage.UninstallString
|
||
if ($uninstallCmd -match "msiexec") {
|
||
# MSI uninstall
|
||
$productCode = $uninstallCmd -replace ".*(\{[A-F0-9-]+\}).*", '$1'
|
||
if ($productCode -match "^\{") {
|
||
Start-Process "msiexec.exe" -ArgumentList "/x $productCode /qn" -Wait -NoNewWindow
|
||
}
|
||
} else {
|
||
Start-Process $uninstallCmd -ArgumentList "/S" -Wait -NoNewWindow -ErrorAction SilentlyContinue
|
||
}
|
||
Write-ColorText " done" -Color Green
|
||
} catch {
|
||
Write-ColorText " manual removal required" -Color Yellow
|
||
}
|
||
}
|
||
|
||
# Remove Node.js directories
|
||
foreach ($path in $nodePaths) {
|
||
if (Test-Path $path) {
|
||
Write-ColorText " → " -Color Yellow -NoNewline
|
||
Write-ColorText "Removing $path..." -Color White -NoNewline
|
||
try {
|
||
Remove-Item -Path $path -Recurse -Force -ErrorAction Stop
|
||
Write-ColorText " done" -Color Green
|
||
} catch {
|
||
Write-ColorText " failed (may need admin rights)" -Color Red
|
||
}
|
||
}
|
||
}
|
||
|
||
# Remove npm cache and global packages
|
||
$npmPaths = @(
|
||
"$env:APPDATA\npm",
|
||
"$env:APPDATA\npm-cache",
|
||
"$env:LOCALAPPDATA\npm-cache"
|
||
)
|
||
|
||
foreach ($path in $npmPaths) {
|
||
if (Test-Path $path) {
|
||
Write-ColorText " → " -Color Yellow -NoNewline
|
||
Write-ColorText "Removing $path..." -Color White -NoNewline
|
||
try {
|
||
Remove-Item -Path $path -Recurse -Force -ErrorAction Stop
|
||
Write-ColorText " done" -Color Green
|
||
} catch {
|
||
Write-ColorText " skipped" -Color Yellow
|
||
}
|
||
}
|
||
}
|
||
|
||
# Remove from PATH (current session)
|
||
$env:Path = ($env:Path.Split(';') | Where-Object { $_ -notlike "*nodejs*" -and $_ -notlike "*npm*" }) -join ';'
|
||
|
||
Write-Host ""
|
||
Write-Success "Node.js removal completed!"
|
||
Start-Sleep -Seconds 1
|
||
}
|
||
|
||
function Install-Node {
|
||
Write-Section "📦 Installing Node.js 20 LTS"
|
||
|
||
Write-Step "Preparing to install Node.js 20 LTS..."
|
||
Write-Host ""
|
||
|
||
# Determine download URL based on architecture
|
||
$nodeVersion = "20.19.0" # LTS version
|
||
|
||
switch ($Arch) {
|
||
"x64" { $installerFile = "node-v$nodeVersion-x64.msi" }
|
||
"x86" { $installerFile = "node-v$nodeVersion-x86.msi" }
|
||
"arm64" { $installerFile = "node-v$nodeVersion-arm64.msi" }
|
||
default { $installerFile = "node-v$nodeVersion-x64.msi" }
|
||
}
|
||
|
||
$downloadUrl = "https://nodejs.org/dist/v$nodeVersion/$installerFile"
|
||
$installerPath = "$env:TEMP\$installerFile"
|
||
|
||
Write-ColorText " → " -Color Yellow -NoNewline
|
||
Write-ColorText "Downloading Node.js v$nodeVersion ($Arch)..." -Color White
|
||
Write-Info " URL: $downloadUrl"
|
||
|
||
try {
|
||
# Download with progress
|
||
$webClient = New-Object System.Net.WebClient
|
||
$webClient.DownloadFile($downloadUrl, $installerPath)
|
||
Write-ColorText " → " -Color Yellow -NoNewline
|
||
Write-ColorText "Download complete!" -Color Green
|
||
} catch {
|
||
Write-Error2 "Failed to download Node.js installer."
|
||
Write-Info "Please check your internet connection and try again."
|
||
Write-Info "Or download manually from: https://nodejs.org/"
|
||
exit 1
|
||
}
|
||
|
||
# Install Node.js
|
||
Write-Host ""
|
||
Write-ColorText " → " -Color Yellow -NoNewline
|
||
Write-ColorText "Installing Node.js (this may take a minute)..." -Color White
|
||
|
||
try {
|
||
$installArgs = "/i `"$installerPath`" /qn /norestart"
|
||
$process = Start-Process "msiexec.exe" -ArgumentList $installArgs -Wait -PassThru -NoNewWindow
|
||
|
||
if ($process.ExitCode -eq 0) {
|
||
Write-ColorText " → " -Color Yellow -NoNewline
|
||
Write-ColorText "Installation successful!" -Color Green
|
||
} elseif ($process.ExitCode -eq 3010) {
|
||
Write-ColorText " → " -Color Yellow -NoNewline
|
||
Write-ColorText "Installation successful (restart may be required)" -Color Yellow
|
||
} else {
|
||
throw "MSI installer returned code: $($process.ExitCode)"
|
||
}
|
||
} catch {
|
||
Write-Error2 "Installation failed: $_"
|
||
Write-Info "Try running the installer manually: $installerPath"
|
||
exit 1
|
||
}
|
||
|
||
# Refresh environment variables
|
||
Write-ColorText " → " -Color Yellow -NoNewline
|
||
Write-ColorText "Refreshing environment variables..." -Color White
|
||
|
||
# Add Node.js to PATH for current session
|
||
$nodePath = "$env:ProgramFiles\nodejs"
|
||
if (Test-Path $nodePath) {
|
||
$env:Path = "$nodePath;$env:APPDATA\npm;$env:Path"
|
||
}
|
||
|
||
# Also try to refresh from registry
|
||
$machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine")
|
||
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
||
$env:Path = "$machinePath;$userPath"
|
||
|
||
Write-ColorText " done" -Color Green
|
||
|
||
# Cleanup
|
||
Write-ColorText " → " -Color Yellow -NoNewline
|
||
Write-ColorText "Cleaning up installer..." -Color White -NoNewline
|
||
Remove-Item -Path $installerPath -Force -ErrorAction SilentlyContinue
|
||
Write-ColorText " done" -Color Green
|
||
|
||
Write-Host ""
|
||
Write-Success "Node.js installation completed!"
|
||
Start-Sleep -Seconds 1
|
||
}
|
||
|
||
function Test-NodeVerification {
|
||
Write-Section "✓ Verifying Installation"
|
||
|
||
Write-Step "Checking Node.js and npm versions..."
|
||
Write-Host ""
|
||
Start-Sleep -Milliseconds 500
|
||
|
||
# Refresh PATH
|
||
$nodePath = "$env:ProgramFiles\nodejs"
|
||
if (Test-Path $nodePath) {
|
||
$env:Path = "$nodePath;$env:APPDATA\npm;$env:Path"
|
||
}
|
||
|
||
$nodeOk = $false
|
||
$npmOk = $false
|
||
|
||
# Verify Node.js
|
||
try {
|
||
$newNodeVersion = & "$nodePath\node.exe" --version 2>$null
|
||
if (-not $newNodeVersion) {
|
||
$newNodeVersion = & node --version 2>$null
|
||
}
|
||
|
||
if ($newNodeVersion) {
|
||
$versionNum = $newNodeVersion -replace "v", ""
|
||
$majorVersion = [int]($versionNum.Split('.')[0])
|
||
|
||
if ($majorVersion -ge 18) {
|
||
Write-ColorText " ✓ " -Color Green -NoNewline
|
||
Write-ColorText "Node.js: " -Color White -NoNewline
|
||
Write-ColorText "$newNodeVersion" -Color Green -NoNewline
|
||
Write-ColorText " (Compatible!)" -Color Green
|
||
$nodeOk = $true
|
||
} else {
|
||
Write-ColorText " ✗ " -Color Red -NoNewline
|
||
Write-ColorText "Node.js: " -Color White -NoNewline
|
||
Write-ColorText "$newNodeVersion (Too old - need v18+)" -Color Red
|
||
}
|
||
} else {
|
||
throw "No version output"
|
||
}
|
||
} catch {
|
||
Write-ColorText " ✗ " -Color Red -NoNewline
|
||
Write-ColorText "Node.js: " -Color White -NoNewline
|
||
Write-ColorText "Not found!" -Color Red
|
||
}
|
||
|
||
# Verify npm
|
||
try {
|
||
$newNpmVersion = & "$nodePath\npm.cmd" --version 2>$null
|
||
if (-not $newNpmVersion) {
|
||
$newNpmVersion = & npm --version 2>$null
|
||
}
|
||
|
||
if ($newNpmVersion) {
|
||
Write-ColorText " ✓ " -Color Green -NoNewline
|
||
Write-ColorText "npm: " -Color White -NoNewline
|
||
Write-ColorText "v$newNpmVersion" -Color Green
|
||
$npmOk = $true
|
||
} else {
|
||
throw "No version output"
|
||
}
|
||
} catch {
|
||
Write-ColorText " ✗ " -Color Red -NoNewline
|
||
Write-ColorText "npm: " -Color White -NoNewline
|
||
Write-ColorText "Not found!" -Color Red
|
||
}
|
||
|
||
# Show paths
|
||
Write-Host ""
|
||
Write-Info "Node path: $nodePath\node.exe"
|
||
Write-Info "npm path: $nodePath\npm.cmd"
|
||
|
||
Write-Host ""
|
||
|
||
if ($nodeOk -and $npmOk) {
|
||
Write-Success "All requirements verified successfully!"
|
||
return $true
|
||
} else {
|
||
Write-Error2 "Verification failed! Please check the installation."
|
||
return $false
|
||
}
|
||
}
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Claude Code Installation
|
||
#-------------------------------------------------------------------------------
|
||
|
||
function Install-ClaudeCode {
|
||
Write-Section "🚀 Installing Claude Code"
|
||
|
||
Write-Step "Installing Claude Code via npm..."
|
||
Write-Host ""
|
||
|
||
# Ensure npm is in PATH
|
||
$nodePath = "$env:ProgramFiles\nodejs"
|
||
$npmPath = "$env:APPDATA\npm"
|
||
$env:Path = "$nodePath;$npmPath;$env:Path"
|
||
|
||
Write-ColorText " → " -Color Yellow -NoNewline
|
||
Write-ColorText "Running npm install..." -Color White
|
||
Write-Host ""
|
||
|
||
try {
|
||
# Install Claude Code globally
|
||
$npmCmd = if (Test-Path "$nodePath\npm.cmd") { "$nodePath\npm.cmd" } else { "npm" }
|
||
|
||
$process = Start-Process -FilePath $npmCmd -ArgumentList "install", "-g", "@anthropic-ai/claude-code" -Wait -PassThru -NoNewWindow -RedirectStandardOutput "$env:TEMP\npm-out.txt" -RedirectStandardError "$env:TEMP\npm-err.txt"
|
||
|
||
# Display output
|
||
if (Test-Path "$env:TEMP\npm-out.txt") {
|
||
$output = Get-Content "$env:TEMP\npm-out.txt" -Raw
|
||
if ($output) {
|
||
$output.Split("`n") | ForEach-Object {
|
||
if ($_ -match "added|packages") {
|
||
Write-ColorText " $_" -Color Green
|
||
} elseif ($_ -match "WARN") {
|
||
Write-ColorText " $_" -Color Yellow
|
||
} elseif ($_.Trim()) {
|
||
Write-ColorText " $_" -Color DarkGray
|
||
}
|
||
}
|
||
}
|
||
Remove-Item "$env:TEMP\npm-out.txt" -Force -ErrorAction SilentlyContinue
|
||
}
|
||
|
||
if (Test-Path "$env:TEMP\npm-err.txt") {
|
||
$errOutput = Get-Content "$env:TEMP\npm-err.txt" -Raw
|
||
if ($errOutput -and $errOutput.Trim()) {
|
||
$errOutput.Split("`n") | ForEach-Object {
|
||
if ($_ -match "ERR|error") {
|
||
Write-ColorText " $_" -Color Red
|
||
} elseif ($_ -match "WARN") {
|
||
Write-ColorText " $_" -Color Yellow
|
||
}
|
||
}
|
||
}
|
||
Remove-Item "$env:TEMP\npm-err.txt" -Force -ErrorAction SilentlyContinue
|
||
}
|
||
|
||
if ($process.ExitCode -ne 0) {
|
||
throw "npm install failed with exit code: $($process.ExitCode)"
|
||
}
|
||
} catch {
|
||
Write-Error2 "Failed to install Claude Code: $_"
|
||
Write-Info "Try running manually: npm install -g @anthropic-ai/claude-code"
|
||
return $false
|
||
}
|
||
|
||
Write-Host ""
|
||
|
||
# Verify installation
|
||
$claudePath = "$env:APPDATA\npm\claude.cmd"
|
||
if (Test-Path $claudePath) {
|
||
Write-Success "Claude Code installed successfully!"
|
||
Write-Info "Location: $claudePath"
|
||
return $true
|
||
} else {
|
||
# Check if claude is in PATH
|
||
$claudeInPath = Get-Command claude -ErrorAction SilentlyContinue
|
||
if ($claudeInPath) {
|
||
Write-Success "Claude Code installed successfully!"
|
||
Write-Info "Location: $($claudeInPath.Source)"
|
||
return $true
|
||
} else {
|
||
Write-Warning2 "Claude Code may have been installed but is not in PATH."
|
||
Write-Info "Try restarting your terminal or running: refreshenv"
|
||
return $true
|
||
}
|
||
}
|
||
}
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Permission Configuration
|
||
#-------------------------------------------------------------------------------
|
||
|
||
function Set-ClaudePermissions {
|
||
Write-Section "🛡 Permission Configuration"
|
||
|
||
Write-Host " Claude Code can run with different permission levels:"
|
||
Write-Host ""
|
||
|
||
Write-BoxTop
|
||
Write-BoxEmpty
|
||
|
||
# Option 1 - Normal Mode
|
||
Write-BoxLineMultiColor -Parts @(
|
||
@{Text="1."; Color="Cyan"},
|
||
@{Text=" Normal Mode"; Color="White"},
|
||
@{Text=" (Recommended for most users)"; Color="DarkGray"}
|
||
)
|
||
Write-BoxLine -Text "Claude will ask permission before file edits and commands" -Color DarkGray -Indent 4
|
||
Write-BoxLineMultiColor -Parts @(
|
||
@{Text="✓ Safe ✓ Interactive ✓ Full control"; Color="Green"}
|
||
) -Indent 4
|
||
Write-BoxEmpty
|
||
|
||
# Option 2 - Auto-Accept Edits
|
||
Write-BoxLineMultiColor -Parts @(
|
||
@{Text="2."; Color="Cyan"},
|
||
@{Text=" Auto-Accept Edits"; Color="White"}
|
||
)
|
||
Write-BoxLine -Text "Automatically accept file edits, ask for commands" -Color DarkGray -Indent 4
|
||
Write-BoxLineMultiColor -Parts @(
|
||
@{Text="⚡ Faster editing ✓ Command safety"; Color="Yellow"}
|
||
) -Indent 4
|
||
Write-BoxEmpty
|
||
|
||
# Option 3 - Full Auto Mode
|
||
Write-BoxLineMultiColor -Parts @(
|
||
@{Text="3."; Color="Cyan"},
|
||
@{Text=" Full Auto Mode"; Color="White"},
|
||
@{Text=" (--dangerously-skip-permissions)"; Color="DarkGray"}
|
||
)
|
||
Write-BoxLine -Text "Skip all permission prompts (use with caution!)" -Color DarkGray -Indent 4
|
||
Write-BoxLineMultiColor -Parts @(
|
||
@{Text="⚠ No confirmations ⚠ Full system access"; Color="Red"}
|
||
) -Indent 4
|
||
Write-BoxEmpty
|
||
|
||
# Option 4 - Don't start
|
||
Write-BoxLineMultiColor -Parts @(
|
||
@{Text="4."; Color="Cyan"},
|
||
@{Text=" Don't start Claude"; Color="White"}
|
||
)
|
||
Write-BoxLine -Text "Exit installer without starting Claude" -Color DarkGray -Indent 4
|
||
Write-BoxEmpty
|
||
|
||
Write-BoxBottom
|
||
Write-Host ""
|
||
|
||
while ($true) {
|
||
Write-ColorText " ? " -Color Yellow -NoNewline
|
||
Write-ColorText "Select permission mode [" -Color White -NoNewline
|
||
Write-ColorText "1" -Color Green -NoNewline
|
||
Write-ColorText "/" -Color White -NoNewline
|
||
Write-ColorText "2" -Color Cyan -NoNewline
|
||
Write-ColorText "/" -Color White -NoNewline
|
||
Write-ColorText "3" -Color Red -NoNewline
|
||
Write-ColorText "/" -Color White -NoNewline
|
||
Write-ColorText "4" -Color White -NoNewline
|
||
Write-ColorText "]: " -Color White -NoNewline
|
||
|
||
$choice = Read-Host
|
||
|
||
switch ($choice) {
|
||
"1" {
|
||
$script:PermissionMode = "normal"
|
||
$script:ClaudeCmd = "claude"
|
||
Write-Success "Selected: Normal Mode"
|
||
return
|
||
}
|
||
"2" {
|
||
$script:PermissionMode = "auto-edit"
|
||
$script:ClaudeCmd = "claude --auto-accept-edits"
|
||
Write-Success "Selected: Auto-Accept Edits"
|
||
return
|
||
}
|
||
"3" {
|
||
$script:PermissionMode = "full-auto"
|
||
Write-Host ""
|
||
Write-Warning2 "⚠️ WARNING: Full Auto Mode gives Claude unrestricted access!"
|
||
Write-ColorText " This mode will:" -Color Red
|
||
Write-ColorText " • Execute commands without confirmation" -Color Red
|
||
Write-ColorText " • Modify files without asking" -Color Red
|
||
Write-ColorText " • Have full access to your system" -Color Red
|
||
Write-Host ""
|
||
|
||
if (Confirm-Action "Are you sure you want to use Full Auto Mode?" -DefaultYes $false) {
|
||
$script:ClaudeCmd = "claude --dangerously-skip-permissions"
|
||
Write-Success "Selected: Full Auto Mode"
|
||
return
|
||
} else {
|
||
Write-Host ""
|
||
Write-Info "Please select a different mode."
|
||
Write-Host ""
|
||
}
|
||
}
|
||
"4" {
|
||
$script:PermissionMode = "exit"
|
||
$script:ClaudeCmd = ""
|
||
Write-Info "Installation complete. You can start Claude anytime with: claude"
|
||
return
|
||
}
|
||
default {
|
||
Write-Error2 "Invalid choice. Please enter 1, 2, 3, or 4."
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Launch Claude Code
|
||
#-------------------------------------------------------------------------------
|
||
|
||
function Start-Claude {
|
||
if ($PermissionMode -eq "exit") {
|
||
Write-Section "✓ Installation Complete!"
|
||
Write-ColorText " Claude Code has been successfully installed!" -Color Green
|
||
Write-Host ""
|
||
Write-ColorText " To start Claude Code, open a new terminal and run:" -Color White
|
||
Write-Host ""
|
||
Write-ColorText " claude" -Color Cyan -NoNewline
|
||
Write-ColorText " # Normal mode" -Color DarkGray
|
||
Write-ColorText " claude --auto-accept-edits" -Color Cyan -NoNewline
|
||
Write-ColorText " # Auto-accept file edits" -Color DarkGray
|
||
Write-ColorText " claude --dangerously-skip-permissions" -Color Cyan -NoNewline
|
||
Write-ColorText " # Full auto mode" -Color DarkGray
|
||
Write-Host ""
|
||
Write-Info "If 'claude' is not recognized, restart your terminal to refresh PATH."
|
||
Write-Host ""
|
||
Write-Success "Thank you for installing Claude Code!"
|
||
return
|
||
}
|
||
|
||
Write-Section "🚀 Launching Claude Code"
|
||
|
||
Write-ColorText " Starting Claude Code with: " -Color White -NoNewline
|
||
Write-ColorText $ClaudeCmd -Color Cyan
|
||
Write-Host ""
|
||
Write-ColorText " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -Color DarkGray
|
||
Write-Host ""
|
||
|
||
Start-Sleep -Seconds 1
|
||
|
||
# Ensure PATH is set
|
||
$npmPath = "$env:APPDATA\npm"
|
||
$env:Path = "$npmPath;$env:Path"
|
||
|
||
# Launch Claude
|
||
try {
|
||
if ($PermissionMode -eq "normal") {
|
||
& claude
|
||
} elseif ($PermissionMode -eq "auto-edit") {
|
||
& claude --auto-accept-edits
|
||
} else {
|
||
& claude --dangerously-skip-permissions
|
||
}
|
||
} catch {
|
||
Write-Error2 "Failed to start Claude Code."
|
||
Write-Info "Try opening a new terminal and running: $ClaudeCmd"
|
||
}
|
||
}
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Main Script Execution
|
||
#-------------------------------------------------------------------------------
|
||
|
||
function Main {
|
||
# Check Windows version
|
||
if ([Environment]::OSVersion.Platform -ne "Win32NT") {
|
||
Write-Error2 "This script is designed for Windows only."
|
||
exit 1
|
||
}
|
||
|
||
# Display banner
|
||
Write-Banner
|
||
|
||
Write-ColorText " Welcome to the Claude Code Installer!" -Color White
|
||
Write-Host ""
|
||
Write-ColorText " This script will:" -Color DarkGray
|
||
Write-ColorText " 1. Detect your Windows system" -Color DarkGray
|
||
Write-ColorText " 2. Check/install Node.js 20 LTS" -Color DarkGray
|
||
Write-ColorText " 3. Install Claude Code" -Color DarkGray
|
||
Write-ColorText " 4. Configure permissions" -Color DarkGray
|
||
Write-Host ""
|
||
|
||
if (-not (Confirm-Action "Ready to begin installation?")) {
|
||
Write-Host ""
|
||
Write-Info "Installation cancelled. Goodbye!"
|
||
exit 0
|
||
}
|
||
|
||
# Run installation steps
|
||
Get-SystemInfo
|
||
Test-NodeInstallation
|
||
|
||
if (-not (Test-NodeVerification)) {
|
||
exit 1
|
||
}
|
||
|
||
if (-not (Install-ClaudeCode)) {
|
||
exit 1
|
||
}
|
||
|
||
Set-ClaudePermissions
|
||
Start-Claude
|
||
}
|
||
|
||
# Run main function
|
||
Main |