|
| 1 | +@echo off |
| 2 | +setlocal enabledelayedexpansion |
| 3 | + |
| 4 | +REM Claude Code Windows CMD Bootstrap Script |
| 5 | +REM Installs Claude Code for environments where PowerShell is not available |
| 6 | + |
| 7 | +REM Parse command line argument |
| 8 | +set "TARGET=%~1" |
| 9 | +if "!TARGET!"=="" set "TARGET=latest" |
| 10 | + |
| 11 | +REM Validate target parameter |
| 12 | +if /i "!TARGET!"=="stable" goto :target_valid |
| 13 | +if /i "!TARGET!"=="latest" goto :target_valid |
| 14 | +echo !TARGET! | findstr /r "^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*" >nul |
| 15 | +if !ERRORLEVEL! equ 0 goto :target_valid |
| 16 | + |
| 17 | +echo Usage: %0 [stable^|latest^|VERSION] >&2 |
| 18 | +echo Example: %0 1.0.58 >&2 |
| 19 | +exit /b 1 |
| 20 | + |
| 21 | +:target_valid |
| 22 | + |
| 23 | +REM Check for 64-bit Windows |
| 24 | +if /i "%PROCESSOR_ARCHITECTURE%"=="AMD64" goto :arch_valid |
| 25 | +if /i "%PROCESSOR_ARCHITECTURE%"=="ARM64" goto :arch_valid |
| 26 | +if /i "%PROCESSOR_ARCHITEW6432%"=="AMD64" goto :arch_valid |
| 27 | +if /i "%PROCESSOR_ARCHITEW6432%"=="ARM64" goto :arch_valid |
| 28 | + |
| 29 | +echo Claude Code does not support 32-bit Windows. Please use a 64-bit version of Windows. >&2 |
| 30 | +exit /b 1 |
| 31 | + |
| 32 | +:arch_valid |
| 33 | + |
| 34 | +REM Set constants |
| 35 | +set "GCS_BUCKET=https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases" |
| 36 | +set "DOWNLOAD_DIR=%USERPROFILE%\.claude\downloads" |
| 37 | +set "PLATFORM=win32-x64" |
| 38 | + |
| 39 | +REM Create download directory |
| 40 | +if not exist "!DOWNLOAD_DIR!" mkdir "!DOWNLOAD_DIR!" |
| 41 | + |
| 42 | +REM Check for curl availability |
| 43 | +curl --version >nul 2>&1 |
| 44 | +if !ERRORLEVEL! neq 0 ( |
| 45 | + echo curl is required but not available. Please install curl or use PowerShell installer. >&2 |
| 46 | + exit /b 1 |
| 47 | +) |
| 48 | + |
| 49 | +REM Always download latest version (which has the most up-to-date installer) |
| 50 | +call :download_file "!GCS_BUCKET!/latest" "!DOWNLOAD_DIR!\latest" |
| 51 | +if !ERRORLEVEL! neq 0 ( |
| 52 | + echo Failed to get latest version >&2 |
| 53 | + exit /b 1 |
| 54 | +) |
| 55 | + |
| 56 | +REM Read version from file |
| 57 | +set /p VERSION=<"!DOWNLOAD_DIR!\latest" |
| 58 | +del "!DOWNLOAD_DIR!\latest" |
| 59 | + |
| 60 | +REM Download manifest |
| 61 | +call :download_file "!GCS_BUCKET!/!VERSION!/manifest.json" "!DOWNLOAD_DIR!\manifest.json" |
| 62 | +if !ERRORLEVEL! neq 0 ( |
| 63 | + echo Failed to get manifest >&2 |
| 64 | + exit /b 1 |
| 65 | +) |
| 66 | + |
| 67 | +REM Extract checksum from manifest |
| 68 | +call :parse_manifest "!DOWNLOAD_DIR!\manifest.json" "!PLATFORM!" |
| 69 | +if !ERRORLEVEL! neq 0 ( |
| 70 | + echo Platform !PLATFORM! not found in manifest >&2 |
| 71 | + del "!DOWNLOAD_DIR!\manifest.json" 2>nul |
| 72 | + exit /b 1 |
| 73 | +) |
| 74 | +del "!DOWNLOAD_DIR!\manifest.json" |
| 75 | + |
| 76 | +REM Download binary |
| 77 | +set "BINARY_PATH=!DOWNLOAD_DIR!\claude-!VERSION!-!PLATFORM!.exe" |
| 78 | +call :download_file "!GCS_BUCKET!/!VERSION!/!PLATFORM!/claude.exe" "!BINARY_PATH!" |
| 79 | +if !ERRORLEVEL! neq 0 ( |
| 80 | + echo Failed to download binary >&2 |
| 81 | + if exist "!BINARY_PATH!" del "!BINARY_PATH!" |
| 82 | + exit /b 1 |
| 83 | +) |
| 84 | + |
| 85 | +REM Verify checksum |
| 86 | +call :verify_checksum "!BINARY_PATH!" "!EXPECTED_CHECKSUM!" |
| 87 | +if !ERRORLEVEL! neq 0 ( |
| 88 | + echo Checksum verification failed >&2 |
| 89 | + del "!BINARY_PATH!" |
| 90 | + exit /b 1 |
| 91 | +) |
| 92 | + |
| 93 | +REM Run claude install to set up launcher and shell integration |
| 94 | +echo Setting up Claude Code... |
| 95 | +"!BINARY_PATH!" install "!TARGET!" |
| 96 | +set "INSTALL_RESULT=!ERRORLEVEL!" |
| 97 | + |
| 98 | +REM Clean up downloaded file |
| 99 | +REM Wait a moment for any file handles to be released |
| 100 | +timeout /t 1 /nobreak >nul 2>&1 |
| 101 | +del /f "!BINARY_PATH!" >nul 2>&1 |
| 102 | +if exist "!BINARY_PATH!" ( |
| 103 | + echo Warning: Could not remove temporary file: !BINARY_PATH! |
| 104 | +) |
| 105 | + |
| 106 | +if !INSTALL_RESULT! neq 0 ( |
| 107 | + echo Installation failed >&2 |
| 108 | + exit /b 1 |
| 109 | +) |
| 110 | + |
| 111 | +echo. |
| 112 | +echo Installation complete^^! |
| 113 | +echo. |
| 114 | +exit /b 0 |
| 115 | + |
| 116 | +REM ============================================================================ |
| 117 | +REM SUBROUTINES |
| 118 | +REM ============================================================================ |
| 119 | + |
| 120 | +:download_file |
| 121 | +REM Downloads a file using curl |
| 122 | +REM Args: %1=URL, %2=OutputPath |
| 123 | +set "URL=%~1" |
| 124 | +set "OUTPUT=%~2" |
| 125 | + |
| 126 | +curl -fsSL "!URL!" -o "!OUTPUT!" |
| 127 | +exit /b !ERRORLEVEL! |
| 128 | + |
| 129 | +:parse_manifest |
| 130 | +REM Parse JSON manifest to extract checksum for platform |
| 131 | +REM Args: %1=ManifestPath, %2=Platform |
| 132 | +set "MANIFEST_PATH=%~1" |
| 133 | +set "PLATFORM_NAME=%~2" |
| 134 | +set "EXPECTED_CHECKSUM=" |
| 135 | + |
| 136 | +REM Use findstr to find platform section, then look for checksum |
| 137 | +set "FOUND_PLATFORM=" |
| 138 | +set "IN_PLATFORM_SECTION=" |
| 139 | + |
| 140 | +REM Read the manifest line by line |
| 141 | +for /f "usebackq tokens=*" %%i in ("!MANIFEST_PATH!") do ( |
| 142 | + set "LINE=%%i" |
| 143 | + |
| 144 | + REM Check if this line contains our platform |
| 145 | + echo !LINE! | findstr /c:"\"%PLATFORM_NAME%\":" >nul |
| 146 | + if !ERRORLEVEL! equ 0 ( |
| 147 | + set "IN_PLATFORM_SECTION=1" |
| 148 | + ) |
| 149 | + |
| 150 | + REM If we're in the platform section, look for checksum |
| 151 | + if defined IN_PLATFORM_SECTION ( |
| 152 | + echo !LINE! | findstr /c:"\"checksum\":" >nul |
| 153 | + if !ERRORLEVEL! equ 0 ( |
| 154 | + REM Extract checksum value |
| 155 | + for /f "tokens=2 delims=:" %%j in ("!LINE!") do ( |
| 156 | + set "CHECKSUM_PART=%%j" |
| 157 | + REM Remove quotes, whitespace, and comma |
| 158 | + set "CHECKSUM_PART=!CHECKSUM_PART: =!" |
| 159 | + set "CHECKSUM_PART=!CHECKSUM_PART:"=!" |
| 160 | + set "CHECKSUM_PART=!CHECKSUM_PART:,=!" |
| 161 | + |
| 162 | + REM Check if it looks like a SHA256 (64 hex chars) |
| 163 | + if not "!CHECKSUM_PART!"=="" ( |
| 164 | + call :check_length "!CHECKSUM_PART!" 64 |
| 165 | + if !ERRORLEVEL! equ 0 ( |
| 166 | + set "EXPECTED_CHECKSUM=!CHECKSUM_PART!" |
| 167 | + exit /b 0 |
| 168 | + ) |
| 169 | + ) |
| 170 | + ) |
| 171 | + ) |
| 172 | + |
| 173 | + REM Check if we've left the platform section (closing brace) |
| 174 | + echo !LINE! | findstr /c:"}" >nul |
| 175 | + if !ERRORLEVEL! equ 0 set "IN_PLATFORM_SECTION=" |
| 176 | + ) |
| 177 | +) |
| 178 | + |
| 179 | +if "!EXPECTED_CHECKSUM!"=="" exit /b 1 |
| 180 | +exit /b 0 |
| 181 | + |
| 182 | +:check_length |
| 183 | +REM Check if string length equals expected length |
| 184 | +REM Args: %1=String, %2=ExpectedLength |
| 185 | +set "STR=%~1" |
| 186 | +set "EXPECTED_LEN=%~2" |
| 187 | +set "LEN=0" |
| 188 | +:count_loop |
| 189 | +if "!STR:~%LEN%,1!"=="" goto :count_done |
| 190 | +set /a LEN+=1 |
| 191 | +goto :count_loop |
| 192 | +:count_done |
| 193 | +if %LEN%==%EXPECTED_LEN% exit /b 0 |
| 194 | +exit /b 1 |
| 195 | + |
| 196 | +:verify_checksum |
| 197 | +REM Verify file checksum using certutil |
| 198 | +REM Args: %1=FilePath, %2=ExpectedChecksum |
| 199 | +set "FILE_PATH=%~1" |
| 200 | +set "EXPECTED=%~2" |
| 201 | + |
| 202 | +for /f "skip=1 tokens=*" %%i in ('certutil -hashfile "!FILE_PATH!" SHA256') do ( |
| 203 | + set "ACTUAL=%%i" |
| 204 | + set "ACTUAL=!ACTUAL: =!" |
| 205 | + if "!ACTUAL!"=="CertUtil:Thecommandcompletedsuccessfully." goto :verify_done |
| 206 | + if "!ACTUAL!" neq "" ( |
| 207 | + if /i "!ACTUAL!"=="!EXPECTED!" ( |
| 208 | + exit /b 0 |
| 209 | + ) else ( |
| 210 | + exit /b 1 |
| 211 | + ) |
| 212 | + ) |
| 213 | +) |
| 214 | + |
| 215 | +:verify_done |
| 216 | +exit /b 1 |
0 commit comments