diff --git a/README.md b/README.md index 9465283..e040015 100644 --- a/README.md +++ b/README.md @@ -85,10 +85,18 @@ You know your code better than any AI. CTX puts you in control: Download and install the tool using our installation script: +### Linux / Wsl + ```bash curl -sSL https://raw.githubusercontent.com/context-hub/generator/main/download-latest.sh | sh ``` +### Windows + +```bash +powershell -c "& ([ScriptBlock]::Create((irm 'https://raw.githubusercontent.com/context-hub/generator/main/download-latest.ps1'))) -AddToPath" +``` + This installs the `ctx` command to your system (typically in `/usr/local/bin`). > **Want more options?** See the complete [Installation Guide](https://docs.ctxgithub.com/getting-started.html) for diff --git a/download-latest.ps1 b/download-latest.ps1 new file mode 100644 index 0000000..978480c --- /dev/null +++ b/download-latest.ps1 @@ -0,0 +1,529 @@ +<# +.SYNOPSIS +Downloads and installs the latest version of CTX (Context Generator) on Windows. + +.DESCRIPTION +This script downloads and installs the CTX native binary from GitHub releases. +By default, installs to %LOCALAPPDATA%\ctx for the current user. + +EXECUTION POLICY: +If you get an execution policy error, run this command instead: + PowerShell -ExecutionPolicy Bypass -File download-latest.ps1 + +.PARAMETER Path +Installation directory. Defaults to $env:LOCALAPPDATA\ctx. + +.PARAMETER CurrentDir +Install to current directory instead of default location. + +.PARAMETER AddToPath +Add installation directory to PATH environment variable (default: $true for default location, $false for current directory). + +.PARAMETER Force +Force reinstallation even if same version exists. + +.PARAMETER Silent +Suppress interactive prompts and use defaults. + +.PARAMETER SkipVerification +Skip checksum verification and post-installation checks. + +.EXAMPLE +PowerShell -ExecutionPolicy Bypass -File download-latest.ps1 +Installs CTX to %LOCALAPPDATA%\ctx and adds to PATH. + +.EXAMPLE +.\download-latest.ps1 -CurrentDir +Installs CTX to current directory without adding to PATH. + +.EXAMPLE +.\download-latest.ps1 -CurrentDir -AddToPath +Installs CTX to current directory and adds to PATH. + +.EXAMPLE +.\download-latest.ps1 -Path "C:\Tools\ctx" +Installs CTX to custom location and adds to PATH. + +.EXAMPLE +.\download-latest.ps1 -Silent -Force +Silent reinstallation to default location. +#> + +[CmdletBinding(SupportsShouldProcess)] +param( + [Parameter(Position=0)] + [string]$Path = "", + + [Parameter()] + [switch]$CurrentDir, + + [Parameter()] + [switch]$AddToPath, + + [Parameter()] + [switch]$Force, + + [Parameter()] + [switch]$Silent, + + [Parameter()] + [switch]$SkipVerification +) + +#Requires -Version 5.1 + +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' + +# Constants +$Script:GITHUB_API = "https://api.github.com/repos/context-hub/generator/releases/latest" +$Script:GITHUB_REPO = "context-hub/generator" +$Script:BINARY_NAME = "ctx.exe" +$Script:ARCH = "amd64" +$Script:DEFAULT_INSTALL_PATH = Join-Path $env:LOCALAPPDATA "ctx" + +# Color output functions +function Write-ColorOutput { + param( + [Parameter(Mandatory=$true)] + [string]$Message, + [Parameter(Mandatory=$false)] + [ConsoleColor]$ForegroundColor = [ConsoleColor]::White + ) + + try { + $previousColor = $Host.UI.RawUI.ForegroundColor + $Host.UI.RawUI.ForegroundColor = $ForegroundColor + Write-Output $Message + $Host.UI.RawUI.ForegroundColor = $previousColor + } catch { + Write-Output $Message + } +} + +function Write-Success { + param([string]$Message) + Write-ColorOutput "[OK] $Message" -ForegroundColor Green +} + +function Write-Info { + param([string]$Message) + Write-ColorOutput "[INFO] $Message" -ForegroundColor Cyan +} + +function Write-Warning { + param([string]$Message) + Write-ColorOutput "[WARN] $Message" -ForegroundColor Yellow +} + +function Write-ErrorMessage { + param([string]$Message) + Write-ColorOutput "[ERROR] $Message" -ForegroundColor Red +} + +function Write-Step { + param([string]$Message) + Write-Output "" + Write-ColorOutput "==> $Message" -ForegroundColor Magenta +} + +# Get latest release information from GitHub +function Get-LatestRelease { + Write-Step "Fetching latest release information" + + try { + $headers = @{ + 'User-Agent' = 'CTX-Installer/1.0' + 'Accept' = 'application/vnd.github.v3+json' + } + + $response = Invoke-WebRequest -Uri $Script:GITHUB_API -Headers $headers -UseBasicParsing + $release = $response.Content | ConvertFrom-Json + + Write-Success "Found release: $($release.tag_name)" + return $release + + } catch { + throw "Failed to fetch release information: $_" + } +} + +# Find appropriate asset in release +function Get-ReleaseAsset { + param( + [Parameter(Mandatory=$true)] + $Release + ) + + $pattern = "ctx-.*-windows-$($Script:ARCH)\.exe$" + Write-Info "Looking for: $pattern" + + $asset = $Release.assets | Where-Object { $_.name -match $pattern } | Select-Object -First 1 + + if (-not $asset) { + Write-ErrorMessage "Available assets:" + foreach ($a in $Release.assets) { + Write-Info " - $($a.name)" + } + throw "Could not find Windows binary for $($Script:ARCH)" + } + + Write-Info "Selected: $($asset.name)" + return $asset +} + +# Download file +function Get-File { + param( + [Parameter(Mandatory=$true)] + [string]$Url, + [Parameter(Mandatory=$true)] + [string]$OutFile + ) + + Write-Info "Downloading from: $Url" + + $originalProgress = $ProgressPreference + $ProgressPreference = 'Continue' + + Invoke-WebRequest -Uri $Url -OutFile $OutFile -UseBasicParsing + + $ProgressPreference = $originalProgress + Write-Success "Downloaded successfully" +} + +# Verify file checksum +function Test-FileChecksum { + param( + [Parameter(Mandatory=$true)] + [string]$FilePath, + [Parameter(Mandatory=$true)] + [string]$AssetName, + [Parameter(Mandatory=$true)] + $Release + ) + + Write-Step "Verifying file integrity" + + try { + $checksumAsset = $Release.assets | Where-Object { $_.name -eq "$AssetName.sha256" } + + if (-not $checksumAsset) { + Write-Warning "No checksum file found" + return $false + } + + $checksumPath = "$FilePath.sha256" + Get-File -Url $checksumAsset.browser_download_url -OutFile $checksumPath + + $checksumContent = Get-Content $checksumPath -Raw + $expectedChecksum = $checksumContent.Split()[0].Trim().ToLower() + + $hash = Get-FileHash -Path $FilePath -Algorithm SHA256 + $actualChecksum = $hash.Hash.ToLower() + + Remove-Item $checksumPath -Force -ErrorAction SilentlyContinue + + if ($actualChecksum -eq $expectedChecksum) { + Write-Success "Checksum verified" + return $true + } else { + Write-ErrorMessage "Checksum mismatch!" + return $false + } + } catch { + Write-Warning "Could not verify checksum: $_" + return $false + } +} + +# Get installed version +function Get-InstalledVersion { + param([string]$BinaryPath) + + if (Test-Path $BinaryPath) { + try { + $versionOutput = & $BinaryPath --version 2>$null + if ($versionOutput -match 'CTX version (\S+)') { + return $matches[1] + } + } catch { + # Could not determine version + } + } + + return $null +} + +# Compare semantic versions +function Compare-Version { + param( + [string]$Version1, + [string]$Version2 + ) + + $v1 = $Version1 -replace '^v', '' + $v2 = $Version2 -replace '^v', '' + + try { + $ver1 = [version]$v1 + $ver2 = [version]$v2 + + if ($ver1 -gt $ver2) { return 1 } + elseif ($ver1 -lt $ver2) { return -1 } + else { return 0 } + } catch { + if ($v1 -eq $v2) { return 0 } + return -1 + } +} + +# Add to PATH environment variable +function Add-ToPath { + param( + [Parameter(Mandatory=$true)] + [string]$Directory + ) + + Write-Step "Adding to PATH" + + try { + $currentPath = [Environment]::GetEnvironmentVariable('Path', 'User') + $pathEntries = $currentPath -split ';' | Where-Object { $_ } + + $normalizedDir = $Directory.TrimEnd('\') + $alreadyExists = $pathEntries | Where-Object { + $_.TrimEnd('\') -eq $normalizedDir + } + + if ($alreadyExists) { + Write-Info "Directory already in PATH" + return $true + } + + $newPath = (@($pathEntries) + $Directory) -join ';' + [Environment]::SetEnvironmentVariable('Path', $newPath, 'User') + + $env:Path = [Environment]::GetEnvironmentVariable('Path', 'User') + ';' + + [Environment]::GetEnvironmentVariable('Path', 'Machine') + + Write-Success "Added to User PATH" + return $true + + } catch { + Write-Warning "Could not add to PATH: $_" + return $false + } +} + +# Verify installation +function Test-Installation { + param( + [Parameter(Mandatory=$true)] + [string]$BinaryPath + ) + + Write-Step "Verifying installation" + + if (-not (Test-Path $BinaryPath)) { + Write-ErrorMessage "Binary not found: $BinaryPath" + return $false + } + + try { + $output = & $BinaryPath --version 2>&1 + if ($output -match 'CTX version') { + Write-Success "Installation verified: $output" + return $true + } else { + Write-ErrorMessage "Unexpected output: $output" + return $false + } + } catch { + Write-ErrorMessage "Could not execute binary: $_" + return $false + } +} + +# Main installation logic +function Install-Ctx { + Write-Output "" + Write-ColorOutput "=== CTX Installer ===" -ForegroundColor Cyan + Write-ColorOutput "Context Generator for AI-Assisted Development" -ForegroundColor Gray + Write-Output "" + + # Determine installation path + $isCurrentDir = $false + $isDefaultLocation = $false + + if ($CurrentDir) { + $Path = Get-Location | Select-Object -ExpandProperty Path + $isCurrentDir = $true + Write-Info "Installing to current directory" + } elseif (-not $Path) { + $Path = $Script:DEFAULT_INSTALL_PATH + $isDefaultLocation = $true + Write-Info "Installing to default user location" + } else { + Write-Info "Installing to custom location" + } + + Write-Info "Installation path: $Path" + + # Determine AddToPath behavior if not explicitly set + $shouldAddToPath = $AddToPath + if (-not $PSBoundParameters.ContainsKey('AddToPath')) { + # Default: true for default/custom location, false for current directory + $shouldAddToPath = -not $isCurrentDir + } + + # Check for existing installation + $binaryPath = Join-Path $Path $Script:BINARY_NAME + $existingVersion = Get-InstalledVersion -BinaryPath $binaryPath + + if ($existingVersion) { + Write-Info "Existing installation: v$existingVersion" + } + + # Get latest release + $release = Get-LatestRelease + $latestVersion = $release.tag_name -replace '^v', '' + + # Check if upgrade needed + if ($existingVersion -and -not $Force) { + $comparison = Compare-Version -Version1 $latestVersion -Version2 $existingVersion + + if ($comparison -eq 0) { + Write-Info "CTX v$existingVersion is already installed (latest version)" + + if (-not $Silent) { + $response = Read-Host "Reinstall anyway? (y/N)" + if ($response -notmatch '^[Yy]') { + Write-Info "Installation cancelled" + return + } + } else { + Write-Info "Use -Force to reinstall" + return + } + } elseif ($comparison -lt 0) { + Write-Warning "Installed version (v$existingVersion) is newer than latest (v$latestVersion)" + + if (-not $Silent) { + $response = Read-Host "Downgrade? (y/N)" + if ($response -notmatch '^[Yy]') { + Write-Info "Installation cancelled" + return + } + } else { + Write-Info "Use -Force to downgrade" + return + } + } else { + Write-Info "Upgrading from v$existingVersion to v$latestVersion" + } + } + + # Find appropriate asset + $asset = Get-ReleaseAsset -Release $release + + # Create temporary directory + $tempDir = Join-Path $env:TEMP "ctx-install-$(Get-Random)" + New-Item -ItemType Directory -Path $tempDir -Force | Out-Null + + try { + # Download asset + Write-Step "Downloading CTX v$latestVersion" + $downloadPath = Join-Path $tempDir $asset.name + Get-File -Url $asset.browser_download_url -OutFile $downloadPath + + # Verify checksum + if (-not $SkipVerification) { + $checksumVerified = Test-FileChecksum -FilePath $downloadPath -AssetName $asset.name -Release $release + + if (-not $checksumVerified) { + Write-Warning "Checksum verification failed or not available" + if (-not $Silent) { + $response = Read-Host "Continue anyway? (y/N)" + if ($response -notmatch '^[Yy]') { + throw "Installation cancelled by user" + } + } + } + } + + # Create installation directory if needed + if (-not (Test-Path $Path)) { + New-Item -ItemType Directory -Path $Path -Force | Out-Null + Write-Success "Created installation directory" + } + + # Install binary + Write-Step "Installing CTX" + Copy-Item -Path $downloadPath -Destination $binaryPath -Force + Write-Success "Installed: $binaryPath" + + # Add to PATH if requested + if ($shouldAddToPath) { + Add-ToPath -Directory $Path | Out-Null + } + + # Verify installation + if (-not $SkipVerification) { + $verified = Test-Installation -BinaryPath $binaryPath + + if (-not $verified) { + throw "Installation verification failed" + } + } + + # Success message + Write-Output "" + Write-ColorOutput "=== Installation Complete ===" -ForegroundColor Green + Write-Success "CTX v$latestVersion installed successfully" + Write-Info "Location: $binaryPath" + + if ($shouldAddToPath) { + Write-Output "" + Write-Info "Restart your terminal to use 'ctx' command globally" + } else { + Write-Output "" + if ($isCurrentDir) { + Write-Info "Run: .\$($Script:BINARY_NAME)" + Write-Info "To add to PATH, use: -AddToPath parameter" + } else { + Write-Info "Run: $binaryPath" + } + } + + Write-Output "" + Write-ColorOutput "Get started: ctx --help" -ForegroundColor Cyan + Write-ColorOutput "Documentation: https://github.com/$Script:GITHUB_REPO" -ForegroundColor Gray + Write-Output "" + + } catch { + Write-Output "" + Write-ErrorMessage "Installation failed: $_" + throw + + } finally { + # Clean up temporary directory + if (Test-Path $tempDir) { + Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue + } + } +} + +# Execute installation +try { + Install-Ctx + exit 0 +} catch { + Write-Output "" + Write-ErrorMessage "Error: $_" + Write-Output "" + Write-ColorOutput "If you get an execution policy error, run:" -ForegroundColor Yellow + Write-ColorOutput "PowerShell -ExecutionPolicy Bypass -File download-latest.ps1" -ForegroundColor White + Write-Output "" + exit 1 +}