# Abacus installer for Windows: the abacus CLI and, if you like, the desktop # app, from their latest GitHub releases. In PowerShell: # # irm https://abacus.empero.org/install.ps1 | iex # asks # $env:ABACUS_INSTALL="cli"; irm https://abacus.empero.org/install.ps1 | iex # the CLI # $env:ABACUS_INSTALL="desktop"; irm https://abacus.empero.org/install.ps1 | iex # CLI + app # # Run as a file it takes -Cli, -Desktop or -Update (what `abacus update` runs: # the CLI, and the desktop app if it is installed). ABACUS_INSTALL_DIR moves # the CLI (default ~\.local\bin). param( [switch]$Cli, [switch]$Desktop, [switch]$Update ) # Piped into iex, this runs in your own session: put its preferences back after. $AbacusPreferences = @($ErrorActionPreference, $ProgressPreference) $ErrorActionPreference = "Stop" # Windows PowerShell's progress bar slows downloads to a crawl. $ProgressPreference = "SilentlyContinue" [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 $CliRepo = "empero-org/abacus" $DesktopRepo = "empero-org/abacus-desktop" $CliAsset = "abacus-x86_64-pc-windows-msvc.exe" $DesktopAsset = "Abacus-win-x64.exe" function Say([string]$Text) { Write-Host $Text } function Warn([string]$Text) { Write-Host "warn: $Text" -ForegroundColor Yellow } # The newest release's tag, from the redirect releases/latest answers with # (none of GitHub's small API allowance), or from the API. function Get-LatestTag([string]$Repo) { try { $response = Invoke-WebRequest -UseBasicParsing -Method Head -Uri "https://github.com/$Repo/releases/latest" $uri = $response.BaseResponse.ResponseUri if (-not $uri) { $uri = $response.BaseResponse.RequestMessage.RequestUri } $url = "$uri" if ($url -match "/tag/([^/]+)$") { return $Matches[1] } } catch {} return (Invoke-RestMethod -UseBasicParsing -Uri "https://api.github.com/repos/$Repo/releases/latest").tag_name } # Fetch a release file and check it against the release's SHA256SUMS. function Get-ReleaseFile([string]$Repo, [string]$Tag, [string]$Asset, [string]$OutFile) { $base = "https://github.com/$Repo/releases/download/$Tag" Invoke-WebRequest -UseBasicParsing -Uri "$base/$Asset" -OutFile $OutFile $sums = Join-Path $script:Temp "SHA256SUMS-$($Repo.Split('/')[1])" if (-not (Test-Path $sums)) { try { Invoke-WebRequest -UseBasicParsing -Uri "$base/SHA256SUMS" -OutFile $sums } catch { Set-Content $sums "" } } $want = $null foreach ($line in Get-Content $sums) { $fields = $line.Trim() -split "\s+" if ($fields.Count -ge 2 -and $fields[1].TrimStart("*") -eq $Asset) { $want = $fields[0].ToLower() } } if (-not $want) { Warn "no checksum published for $Asset; not verified" return } $got = (Get-FileHash -Algorithm SHA256 $OutFile).Hash.ToLower() if ($got -ne $want) { throw "checksum mismatch for $Asset" } } # --- the CLI ------------------------------------------------------------------- function Get-CliVersion([string]$Exe) { if (-not (Test-Path $Exe)) { return "" } try { $output = & $Exe --version 2>$null if ("$output" -match "(\d+\.\d+\.\d+)") { return $Matches[1] } } catch {} return "" } function Install-Cli([string]$Dir) { $tag = Get-LatestTag $CliRepo if (-not $tag) { throw "could not find the latest abacus release" } $version = $tag.TrimStart("v") $exe = Join-Path $Dir "abacus.exe" if ((Get-CliVersion $exe) -eq $version) { Say "abacus $version is up to date." return } Say "Downloading abacus $version for Windows..." $download = Join-Path $script:Temp "abacus.exe" Get-ReleaseFile $CliRepo $tag $CliAsset $download New-Item -ItemType Directory -Force -Path $Dir | Out-Null # A running abacus.exe (the one running `abacus update`, say) cannot be # overwritten, but it can be moved aside. $old = "$exe.old" Remove-Item $old -Force -ErrorAction SilentlyContinue if (Test-Path $exe) { Move-Item -Force $exe $old } Move-Item -Force $download $exe Remove-Item $old -Force -ErrorAction SilentlyContinue Say "Installed abacus $version in $Dir." } function Add-ToPath([string]$Dir) { $userPath = [Environment]::GetEnvironmentVariable("Path", "User") $entries = @() if ($userPath) { $entries = $userPath.Split(";") | Where-Object { $_ } } if ($entries -notcontains $Dir) { [Environment]::SetEnvironmentVariable("Path", (($entries + $Dir) -join ";"), "User") Say "Added $Dir to your PATH; new terminals will find abacus." } if (($env:Path.Split(";")) -notcontains $Dir) { $env:Path = "$env:Path;$Dir" } } # --- the desktop app ----------------------------------------------------------- # Where the app's own installer recorded it: per user, under "Abacus". function Get-DesktopVersion { $keys = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*", "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" foreach ($key in $keys) { $entry = Get-ItemProperty $key -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -match "^Abacus( \d|$)" -and $_.Publisher -match "Empero" } | Select-Object -First 1 if ($entry) { return "$($entry.DisplayVersion)" } } $exe = Join-Path $env:LOCALAPPDATA "Programs\Abacus\Abacus.exe" if ((Test-Path $exe) -and ((Get-Item $exe).VersionInfo.ProductVersion -match "^(\d+\.\d+\.\d+)")) { return $Matches[1] } return "" } function Install-Desktop { $tag = Get-LatestTag $DesktopRepo if (-not $tag) { throw "could not find the latest desktop app release" } $version = $tag.TrimStart("v") if ((Get-DesktopVersion) -eq $version) { Say "The desktop app $version is up to date." return } Say "Downloading the desktop app $version..." $setup = Join-Path $script:Temp $DesktopAsset Get-ReleaseFile $DesktopRepo $tag $DesktopAsset $setup Say "Installing the desktop app..." $process = Start-Process -FilePath $setup -ArgumentList "/S" -Wait -PassThru if ($process.ExitCode -ne 0) { throw "the desktop app installer failed (exit code $($process.ExitCode))" } Say "Installed the desktop app $version. Open Abacus from the Start menu." } # --- go ------------------------------------------------------------------------ function Install-Abacus { $arch = $env:PROCESSOR_ARCHITEW6432 if (-not $arch) { $arch = $env:PROCESSOR_ARCHITECTURE } # Windows on Arm runs the x64 build. if ($arch -ne "AMD64" -and $arch -ne "ARM64") { throw "unsupported processor: $arch (Abacus needs 64-bit Windows)" } $choice = "$env:ABACUS_INSTALL".ToLower() if ($Cli) { $choice = "cli" } if ($Desktop) { $choice = "desktop" } if ($Update) { $choice = "update" } if ($choice -and @("cli", "desktop", "update") -notcontains $choice) { throw "ABACUS_INSTALL must be cli, desktop or update, not '$choice'" } if (-not $choice) { if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected) { Say "Install Abacus:" Say " 1) the abacus CLI and the desktop app" Say " 2) the abacus CLI only" $answer = Read-Host "Choose 1 or 2 [1]" switch ("$answer".Trim()) { "" { $choice = "desktop" } "1" { $choice = "desktop" } "2" { $choice = "cli" } default { throw "please answer 1 or 2" } } Say "" } else { $choice = "cli" } } if (-not (Get-Command git -ErrorAction SilentlyContinue)) { Warn "abacus works best with git. Install it with: winget install --id Git.Git -e" } $dir = $env:ABACUS_INSTALL_DIR if (-not $dir) { $dir = Join-Path $HOME ".local\bin" } $script:Temp = Join-Path ([IO.Path]::GetTempPath()) ("abacus-install-" + [Guid]::NewGuid()) New-Item -ItemType Directory -Path $script:Temp | Out-Null try { Install-Cli $dir Add-ToPath $dir if ($choice -eq "desktop") { Install-Desktop } if ($choice -eq "update" -and (Get-DesktopVersion)) { Install-Desktop } } finally { Remove-Item -Recurse -Force $script:Temp -ErrorAction SilentlyContinue } if ($choice -eq "update") { return } Say "" Say "Next, in a new terminal:" Say " abacus setup # pick a provider and a model" Say " abacus # start in a project" Say "" Say "Sync sessions across devices with 'abacus sync login'." Say "Update later with 'abacus update'." } # Piped into iex this runs in your own session, so a failure must not `exit` # it: say what went wrong and stop. Run as a file, exit with an error code. $AbacusFailed = $false try { Install-Abacus } catch { Write-Host "error: $($_.Exception.Message)" -ForegroundColor Red $AbacusFailed = $true } finally { $ErrorActionPreference, $ProgressPreference = $AbacusPreferences } if ($AbacusFailed -and $PSCommandPath) { exit 1 }