[CmdletBinding()] param( [string]$Version = '0.0.4-microsoft-preview.27', [switch]$SkipDependencies, [string]$InstallPrefix = (Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'Lambogenius\cli') ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest $releaseBase = 'https://tools.lambogenius.com/lambogenius-integration-migration' if ($env:OS -ne 'Windows_NT') { throw 'Use install-cli.sh on Linux/macOS.' } if ($Version -notmatch '^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$') { throw 'Invalid or unstamped release version.' } if ($releaseBase -notmatch '^https://') { throw 'Release URL must use HTTPS.' } if (-not [IO.Path]::IsPathRooted($InstallPrefix)) { throw 'InstallPrefix must be absolute.' } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 function Invoke-Checked([string]$Command, [string[]]$Arguments) { & $Command @Arguments if ($LASTEXITCODE -ne 0) { throw "$Command failed with exit code $LASTEXITCODE" } } function Update-ProcessPath { $env:PATH = "$InstallPrefix;" + [Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [Environment]::GetEnvironmentVariable('Path', 'User') $sevenZip = Join-Path $env:ProgramFiles '7-Zip' if (Test-Path -LiteralPath (Join-Path $sevenZip '7z.exe')) { $env:PATH += ";$sevenZip" } } function Install-Dependency([string]$Command, [string]$Package) { if (-not (Get-Command $Command -ErrorAction SilentlyContinue)) { Invoke-Checked 'winget' @('install', '--exact', '--id', $Package, '--source', 'winget', '--accept-source-agreements', '--accept-package-agreements') Update-ProcessPath } } if (-not $SkipDependencies) { Update-ProcessPath Write-Host 'Installing missing Node.js/npm, Git, PowerShell, Azure CLI/Bicep and 7-Zip. Windows may request administrator approval.' if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { throw 'Install App Installer (WinGet) from Microsoft, then rerun, or use -SkipDependencies.' } Install-Dependency 'node' 'OpenJS.NodeJS.LTS' if ([int]((& node -p 'process.versions.node.split(/[.]/)[0]') | Select-Object -Last 1) -lt 20) { Invoke-Checked 'winget' @('upgrade', '--exact', '--id', 'OpenJS.NodeJS.LTS', '--source', 'winget', '--accept-source-agreements', '--accept-package-agreements') Update-ProcessPath } Install-Dependency 'git' 'Git.Git' Install-Dependency 'pwsh' 'Microsoft.PowerShell' Install-Dependency 'az' 'Microsoft.AzureCLI' Install-Dependency '7z' '7zip.7zip' Invoke-Checked 'az' @('bicep', 'install') } foreach ($command in @('node', 'npm.cmd')) { if (-not (Get-Command $command -ErrorAction SilentlyContinue)) { throw "$command is required. Reopen your terminal after dependency installation and rerun." } } Invoke-Checked 'node' @('-e', 'process.exit(Number(process.versions.node.split(/[.]/)[0]) >= 20 ? 0 : 1)') $tempRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()) $installTemp = Join-Path $tempRoot ('li-install-' + [guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $installTemp | Out-Null try { $manifest = Invoke-RestMethod "$releaseBase/$Version/release.json" if ($manifest.version -ne $Version -or $manifest.sha256 -notmatch '^[a-f0-9]{64}$') { throw 'Invalid release manifest.' } $archive = Join-Path $installTemp 'cli.tgz' Invoke-WebRequest "$releaseBase/$Version/lambogenius-integration-migration-cli-$Version.tgz" -OutFile $archive -UseBasicParsing if ((Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash.ToLowerInvariant() -ne $manifest.sha256) { throw 'CLI checksum mismatch.' } Invoke-Checked 'npm.cmd' @('install', '--global', '--ignore-scripts', '--no-audit', '--no-fund', '--prefix', $InstallPrefix, $archive) $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') if ($InstallPrefix -notin ($userPath -split ';')) { [Environment]::SetEnvironmentVariable('Path', "$InstallPrefix;$userPath", 'User') } $sevenZip = Join-Path $env:ProgramFiles '7-Zip' $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') if ((Test-Path -LiteralPath (Join-Path $sevenZip '7z.exe')) -and $sevenZip -notin ($userPath -split ';')) { [Environment]::SetEnvironmentVariable('Path', "$userPath;$sevenZip", 'User') } Update-ProcessPath # PowerShell's built-in `li` alias shadows li.cmd. Use the explicit .cmd command. Invoke-Checked (Join-Path $InstallPrefix 'li.cmd') @('--version') Write-Host 'Installed. Reopen your terminal. In PowerShell use li.cmd (li is a built-in alias).' Write-Host 'Next: li.cmd auth --help; select/authenticate an AI provider.' Write-Host 'Generate a code at https://lambogenius.com/app/licensing, then: li.cmd license activate --code ' } finally { $resolvedTemp = [IO.Path]::GetFullPath($installTemp) if ($resolvedTemp.StartsWith($tempRoot, [StringComparison]::OrdinalIgnoreCase) -and [IO.Path]::GetFileName($resolvedTemp) -match '^li-install-[a-f0-9]{32}$') { Remove-Item -LiteralPath $resolvedTemp -Recurse -Force } }