# Smart Tree Windows Installer Script
#
# This script installs the 'st' binary on Windows
#
# Usage:
# iwr -useb https://raw.githubusercontent.com/8b-is/smart-tree/main/scripts/install.ps1 | iex
#
# You can customize the installation by setting environment variables:
# - $env:INSTALL_DIR: The directory to install the binary to (default: $env:LOCALAPPDATA\Programs\st)
# - $env:VERSION: The version to install (default: latest)
param(
[string]$InstallDir = "$env:LOCALAPPDATA\Programs\st",
[string]$Version = ""
)
# Configuration
$GitHubRepo = "8b-is/smart-tree"
$BinaryName = "st.exe"
# Terminal colors
function Write-Info {
param([string]$Message)
Write-Host "[INFO] $Message" -ForegroundColor Blue
}
function Write-Success {
param([string]$Message)
Write-Host "[SUCCESS] $Message" -ForegroundColor Green
}
function Write-Error {
param([string]$Message)
Write-Host "[ERROR] $Message" -ForegroundColor Red
throw $Message
}
function Write-Warning {
param([string]$Message)
Write-Host "[WARNING] $Message" -ForegroundColor Yellow
}
# Function to check if a release has assets
function Test-ReleaseAssets {
param([string]$VersionTag)
try {
$releaseInfo = Invoke-RestMethod -Uri "https://api.github.com/repos/$GitHubRepo/releases/tags/$VersionTag"
return $releaseInfo.assets.Count -gt 0
} catch {
return $false
}
}
# Function to get releases with assets
function Get-ReleasesWithAssets {
try {
# Get first 10 releases with pagination for efficiency
$releases = Invoke-RestMethod -Uri "https://api.github.com/repos/$GitHubRepo/releases?per_page=10"
return $releases | Where-Object { $_.assets.Count -gt 0 } | Select-Object -ExpandProperty tag_name
} catch {
Write-Error "Failed to fetch releases from GitHub API"
}
}
# Function to select version interactively
function Select-Version {
param([string]$LatestVersion)
Write-Warning "The latest version ($LatestVersion) doesn't have any release binaries yet."
Write-Info "Fetching other available versions with binaries..."
$versions = Get-ReleasesWithAssets
if ($versions.Count -eq 0) {
Write-Error "No releases with binaries found!"
}
Write-Info "Available versions with binaries:"
for ($i = 0; $i -lt $versions.Count; $i++) {
Write-Host " $($i + 1). $($versions[$i])"
}
while ($true) {
$selection = Read-Host "Select a version (1-$($versions.Count)) or 'q' to quit"
if ($selection -eq 'q') {
Write-Info "Installation cancelled."
exit 0
}
# Safe integer parsing
$selectionNum = 0
if ([System.Int32]::TryParse($selection, [ref]$selectionNum) -and
$selectionNum -ge 1 -and $selectionNum -le $versions.Count) {
return $versions[$selectionNum - 1]
} else {
Write-Warning "Invalid selection. Please try again."
}
}
}
# Main Installation Logic
Write-Info "Smart Tree Windows Installer"
Write-Info "Repository: $GitHubRepo"
Write-Info ""
# 1. Detect Architecture
$arch = $env:PROCESSOR_ARCHITECTURE
switch ($arch) {
"AMD64" { $targetArch = "x86_64" }
"ARM64" { $targetArch = "aarch64" }
default {
Write-Error "Unsupported architecture: $arch"
}
}
Write-Info "Detected architecture: $targetArch"
# 2. Determine Version to Install
if ([string]::IsNullOrEmpty($Version)) {
Write-Info "Fetching the latest version number..."
try {
$latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/$GitHubRepo/releases/latest"
$latestVersion = $latestRelease.tag_name
if ([string]::IsNullOrEmpty($latestVersion)) {
Write-Error "Could not fetch the latest version. Please check the repository path and your network connection."
}
# Check if latest version has assets
if (-not (Test-ReleaseAssets -VersionTag $latestVersion)) {
$Version = Select-Version -LatestVersion $latestVersion
} else {
$Version = $latestVersion
Write-Info "Latest version is $Version"
}
} catch {
Write-Error "Failed to fetch the latest version: $_"
}
} else {
Write-Info "Installing specified version: $Version"
# Check if specified version has assets
if (-not (Test-ReleaseAssets -VersionTag $Version)) {
Write-Warning "Version $Version doesn't have any release binaries."
$response = Read-Host "Would you like to select another version? (y/n)"
if ($response -eq 'y' -or $response -eq 'Y') {
$Version = Select-Version -LatestVersion $Version
} else {
Write-Error "Cannot install version without binaries."
}
}
}
# 3. Construct Download URL
$archiveName = "st-$Version-$targetArch-pc-windows-msvc.zip"
$downloadUrl = "https://github.com/$GitHubRepo/releases/download/$Version/$archiveName"
# 4. Download and Extract
$tempDir = New-Item -ItemType Directory -Path "$env:TEMP\st-install-$(Get-Random)" -Force
$archivePath = Join-Path $tempDir $archiveName
Write-Info "Downloading from $downloadUrl"
try {
# Use WebRequest for better progress display
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath -UseBasicParsing
$ProgressPreference = 'Continue'
} catch {
Write-Error "Download failed: $_"
}
Write-Info "Extracting the binary..."
try {
Expand-Archive -Path $archivePath -DestinationPath $tempDir -Force
} catch {
Write-Error "Extraction failed: $_"
}
# 5. Install the binary
Write-Info "Installing to $InstallDir..."
# Create install directory if it doesn't exist
if (-not (Test-Path $InstallDir)) {
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
}
$binaryPath = Join-Path $tempDir $BinaryName
if (-not (Test-Path $binaryPath)) {
# Sometimes the binary is in a subdirectory
$binaryPath = Get-ChildItem -Path $tempDir -Filter $BinaryName -Recurse | Select-Object -First 1 -ExpandProperty FullName
if ([string]::IsNullOrEmpty($binaryPath)) {
Write-Error "Could not find the '$BinaryName' binary in the downloaded archive."
}
}
$destinationPath = Join-Path $InstallDir $BinaryName
Copy-Item -Path $binaryPath -Destination $destinationPath -Force
# 6. Update PATH if necessary
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
if ($userPath -notlike "*$InstallDir*") {
Write-Info "Adding $InstallDir to your PATH..."
# Handle trailing semicolon properly
$newPath = if ($userPath.TrimEnd().EndsWith(';')) {
"$userPath$InstallDir"
} else {
"$userPath;$InstallDir"
}
[Environment]::SetEnvironmentVariable("Path", $newPath, "User")
Write-Success "Added to PATH. Please restart your terminal for changes to take effect."
# Update current session PATH (check if not already present)
if ($env:Path -notlike "*$InstallDir*") {
$env:Path = "$env:Path;$InstallDir"
}
} else {
Write-Info "$InstallDir is already in your PATH"
}
# 7. Verify Installation
Write-Info "Verifying installation..."
try {
$versionOutput = & $destinationPath --version
Write-Success "Successfully installed st to $destinationPath"
Write-Info "Version: $versionOutput"
Write-Info "You can now use the 'st' command!"
} catch {
Write-Warning "Installation completed but verification failed. You may need to restart your terminal."
}
# Clean up
Remove-Item -Path $tempDir -Recurse -Force
# Show next steps
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " Smart Tree installed successfully! 🌳" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
Write-Info "Next steps:"
Write-Host " 1. Restart your terminal (or open a new one) to use 'st'" -ForegroundColor Yellow
Write-Host " 2. Try: st --help" -ForegroundColor Yellow
Write-Host " 3. Try: st --version" -ForegroundColor Yellow
Write-Host " 4. Try: st . (analyze current directory)" -ForegroundColor Yellow
Write-Host ""
Write-Host "For MCP integration with Claude Desktop:" -ForegroundColor Cyan
Write-Host " Run: st --mcp-config" -ForegroundColor Yellow
Write-Host ""
Write-Host "Thank you for installing Smart Tree! 🎸" -ForegroundColor Green