$ErrorActionPreference = 'Stop' [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 if (-not (Get-Command node -ErrorAction SilentlyContinue)) { throw 'Install Node.js 24 LTS with npm first: https://nodejs.org/' } if (-not (Get-Command npm.cmd -ErrorAction SilentlyContinue)) { throw 'npm is required. Install Node.js 24 LTS: https://nodejs.org/' } $vimoxNodeVersion = [version](& node -p process.versions.node) if ($LASTEXITCODE -ne 0 -or -not (($vimoxNodeVersion.Major -eq 22 -and $vimoxNodeVersion.Minor -ge 19) -or $vimoxNodeVersion.Major -ge 24)) { throw 'Requires Node.js 22.19+ (22.x) or 24+. Install Node.js 24 LTS.' } $vimoxPrefix = if ($env:VIMOX_INSTALL_PREFIX) { $env:VIMOX_INSTALL_PREFIX } else { Join-Path $env:LOCALAPPDATA 'Vimox\cli' } if (-not [IO.Path]::IsPathRooted($vimoxPrefix)) { throw 'VIMOX_INSTALL_PREFIX must be an absolute path.' } $vimoxRelease = Invoke-RestMethod -Uri 'https://tui.vimox.cn/downloads/latest.json' if ($vimoxRelease.version -notmatch '^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$' -or $vimoxRelease.sha256 -notmatch '^[a-f0-9]{64}$') { throw 'Invalid release information.' } $vimoxTemp = Join-Path ([IO.Path]::GetTempPath()) ('vimox-download-' + [guid]::NewGuid().ToString('N')) [void][IO.Directory]::CreateDirectory($vimoxTemp) $vimoxPackage = Join-Path $vimoxTemp ('vimox-' + $vimoxRelease.version + '.tgz') Invoke-WebRequest -UseBasicParsing -Uri ('https://tui.vimox.cn/downloads/vimox-' + $vimoxRelease.version + '.tgz') -OutFile $vimoxPackage if ((Get-FileHash -LiteralPath $vimoxPackage -Algorithm SHA256).Hash.ToLowerInvariant() -ne $vimoxRelease.sha256) { throw 'Package SHA-256 verification failed.' } Write-Host ('Installing Vimox ' + $vimoxRelease.version + ' to ' + $vimoxPrefix + ' ...') $vimoxRegistry = if ($env:VIMOX_NPM_REGISTRY) { $env:VIMOX_NPM_REGISTRY.TrimEnd('/') } else { $null } if ($vimoxRegistry -and $vimoxRegistry -notmatch '^https://[A-Za-z0-9.-]+(?::[0-9]+)?$') { throw 'VIMOX_NPM_REGISTRY must be a single HTTPS registry URL.' } if (-not $vimoxRegistry) { # Probe both registries concurrently. This follows the user's network path and # avoids a location lookup or a hard-coded country decision. $vimoxCandidates = @('https://registry.npmmirror.com', 'https://registry.npmjs.org') Add-Type -AssemblyName System.Net.Http $vimoxHttp = [Net.Http.HttpClient]::new() $vimoxHttp.Timeout = [TimeSpan]::FromSeconds(5) $vimoxRequests = @($vimoxCandidates | ForEach-Object { $vimoxHttp.GetAsync($_ + '/@deepseek-ai%2fdsh') }) try { $vimoxPending = [Collections.Generic.List[Threading.Tasks.Task[Net.Http.HttpResponseMessage]]]::new() foreach ($vimoxRequest in $vimoxRequests) { [void]$vimoxPending.Add($vimoxRequest) } while ($vimoxPending.Count -gt 0 -and -not $vimoxRegistry) { $vimoxReady = [Threading.Tasks.Task]::WhenAny($vimoxPending).GetAwaiter().GetResult() $vimoxIndex = [Array]::IndexOf($vimoxRequests, $vimoxReady) [void]$vimoxPending.Remove($vimoxReady) if ($vimoxReady.Status -eq 'RanToCompletion' -and $vimoxReady.Result.IsSuccessStatusCode) { try { $vimoxProbe = $vimoxReady.Result.Content.ReadAsStringAsync().GetAwaiter().GetResult() | ConvertFrom-Json if ($vimoxProbe.name -eq '@deepseek-ai/dsh') { $vimoxRegistry = $vimoxCandidates[$vimoxIndex] } } catch { <# A proxy error page is not a usable registry. #> } } } } finally { $vimoxHttp.Dispose() } if (-not $vimoxRegistry) { $vimoxRegistry = 'https://registry.npmjs.org' } } Write-Host ('Using npm registry: ' + $vimoxRegistry) $vimoxNpmArgs = @('install', '--global', '--prefix', $vimoxPrefix, '--prefer-offline', '--no-audit', '--no-fund', '--fetch-timeout=30000', '--fetch-retries=1', '--fetch-retry-mintimeout=1000', '--fetch-retry-maxtimeout=3000', '--allow-scripts=vimox,@deepseek-ai/dsh-subprocess-local,koffi,node-pty,@google/genai,protobufjs', $vimoxPackage) & npm.cmd @vimoxNpmArgs ('--registry=' + $vimoxRegistry) if ($LASTEXITCODE -ne 0 -and -not $env:VIMOX_NPM_REGISTRY) { $vimoxRegistry = if ($vimoxRegistry -eq 'https://registry.npmjs.org') { 'https://registry.npmmirror.com' } else { 'https://registry.npmjs.org' } Write-Host ('Retrying with npm registry: ' + $vimoxRegistry) & npm.cmd @vimoxNpmArgs ('--registry=' + $vimoxRegistry) } if ($LASTEXITCODE -ne 0) { throw 'Vimox installation failed.' } $vimoxInstalled = Get-Content -Raw -LiteralPath (Join-Path $vimoxPrefix 'node_modules\vimox\package.json') | ConvertFrom-Json if ($vimoxInstalled.version -ne $vimoxRelease.version) { throw 'Installed version does not match the downloaded release.' } if ($env:VIMOX_INSTALL_NO_PATH -ne '1') { $vimoxUserPath = [Environment]::GetEnvironmentVariable('Path', 'User') $vimoxPathParts = @($vimoxUserPath -split ';' | Where-Object { $_ -and $_.TrimEnd('\') -ine $vimoxPrefix.TrimEnd('\') }) [Environment]::SetEnvironmentVariable('Path', ((@($vimoxPrefix) + $vimoxPathParts) -join ';'), 'User') } $env:Path = $vimoxPrefix + ';' + $env:Path & (Join-Path $vimoxPrefix 'vimox.cmd') --version if ($LASTEXITCODE -ne 0) { throw 'Installed Vimox could not start.' } Write-Host 'Installed. Reopen your terminal, enter your project directory and run: vimox'