# Geocentric Code installer for Windows (PowerShell 5.1 or newer). # # irm https://geocentricai.com/install.ps1 | iex # # It installs what Geocentric Code needs (Git, Python 3.9+, Ollama, ripgrep) # with winget, fetches the source from github.com/GeocentricAI/geocentric-code # into a private virtualenv, puts `geo` on your PATH, and downloads a local # model. Run it again at any time to update. # # Options. Piped installs cannot take parameters, so set environment variables # first, then run the same command: # # $env:GEO_NO_MODEL = '1' skip the language model download # $env:GEO_GATEWAY = '1' also install the web gateway (geo-gateway) # $env:GEO_YES = '1' never ask for confirmation # $env:GEO_DRY_RUN = '1' show what would happen and change nothing # $env:GEO_UNINSTALL = '1' remove Geocentric Code (keeps Ollama and models) # $env:GEO_MODEL = 'name' model to pull instead of the automatic pick # $env:GEO_HOME = 'path' install directory # # Saved to a file, the same options work as flags: # powershell -ExecutionPolicy Bypass -File install.ps1 --no-model --gateway $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' function Install-GeocentricCode { param( [bool]$NoModel, [bool]$Gateway, [bool]$Yes, [bool]$DryRun, [bool]$Uninstall ) $RepoUrl = if ($env:GEO_REPO) { $env:GEO_REPO } else { 'https://github.com/GeocentricAI/geocentric-code.git' } $Branch = if ($env:GEO_BRANCH) { $env:GEO_BRANCH } else { 'main' } $GeoHome = if ($env:GEO_HOME) { $env:GEO_HOME } else { Join-Path $env:LOCALAPPDATA 'GeocentricCode' } $BinDir = Join-Path $GeoHome 'bin' $OllamaUrl = 'http://127.0.0.1:11434' function Say([string]$m) { Write-Host "==> $m" -ForegroundColor Cyan } function Ok([string]$m) { Write-Host " ok $m" -ForegroundColor Green } function Warn([string]$m) { Write-Host " !! $m" -ForegroundColor Yellow } function Fail([string]$m) { throw $m } function Run([string]$label, [scriptblock]$block) { if ($DryRun) { Write-Host " [dry-run] $label" -ForegroundColor DarkGray; return } & $block } function Test-Cmd([string]$name) { return [bool](Get-Command $name -ErrorAction SilentlyContinue) } function Update-SessionPath { $machine = [Environment]::GetEnvironmentVariable('Path', 'Machine') $user = [Environment]::GetEnvironmentVariable('Path', 'User') $env:Path = "$machine;$user" } function Confirm-Step([string]$question) { if ($Yes -or $DryRun) { return $true } $answer = Read-Host "$question [Y/n]" return -not ($answer -match '^[Nn]') } function Invoke-Native([string]$exe, [string[]]$arguments) { & $exe @arguments if ($LASTEXITCODE -ne 0) { Fail "$exe failed with exit code $LASTEXITCODE" } } function Install-WithWinget([string]$id, [string]$label) { if (-not (Test-Cmd 'winget')) { Fail "winget is not available, so $label cannot be installed automatically. Install 'App Installer' from the Microsoft Store, then run this again." } Say "Installing $label" Run "winget install $id" { & winget install -e --id $id --silent --accept-package-agreements --accept-source-agreements | Out-Host } Update-SessionPath } function Find-Python { $candidates = @( @('py', '-3'), @('python'), @('python3') ) foreach ($c in $candidates) { if (-not (Test-Cmd $c[0])) { continue } try { $exe = $c[0] $extra = @() if ($c.Length -gt 1) { $extra = $c[1..($c.Length - 1)] } $out = & $exe @extra -c "import sys, venv, ensurepip; print('OK' if sys.version_info >= (3, 9) else 'OLD')" 2>$null if ($LASTEXITCODE -eq 0 -and "$out".Trim() -eq 'OK') { $found = @($exe) + $extra return ,$found } } catch { } } return $null } function Test-OllamaUp { try { Invoke-RestMethod -Uri "$OllamaUrl/api/version" -TimeoutSec 2 | Out-Null; return $true } catch { return $false } } function Get-RamGb { try { return [math]::Floor((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB) } catch { return 8 } } # ----------------------------------------------------------- uninstall if ($Uninstall) { Say 'Removing Geocentric Code' Run "Remove-Item $GeoHome" { if (Test-Path $GeoHome) { Remove-Item -Recurse -Force $GeoHome } } Run 'remove bin from user PATH' { $user = [Environment]::GetEnvironmentVariable('Path', 'User') if ($user) { $kept = ($user -split ';' | Where-Object { $_ -and ($_.TrimEnd('\') -ne $BinDir.TrimEnd('\')) }) -join ';' [Environment]::SetEnvironmentVariable('Path', $kept, 'User') } } Ok 'Removed. Ollama and your downloaded models were left in place.' return } if ($PSVersionTable.PSVersion.Major -lt 5) { Fail 'PowerShell 5.1 or newer is required.' } if ($DryRun) { Say 'Dry run: nothing will be changed' } Say "Geocentric Code installer (Windows, $env:PROCESSOR_ARCHITECTURE)" [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 # -------------------------------------------------------- dependencies $needGit = -not (Test-Cmd 'git') $py = Find-Python $needPython = ($null -eq $py) $needRg = -not (Test-Cmd 'rg') $needOllama = -not (Test-Cmd 'ollama') Say 'Checking what is already installed' if ($needGit) { Warn 'Git is missing' } else { Ok 'git' } if ($needPython) { Warn 'Python 3.9+ is missing' } else { Ok "python ($($py -join ' '))" } if ($needRg) { Warn 'ripgrep is missing (optional, makes search faster)' } else { Ok 'ripgrep' } if ($needOllama) { Warn 'Ollama is missing (runs the models)' } else { Ok 'ollama' } if ($needGit -or $needPython -or $needRg -or $needOllama) { if (-not (Confirm-Step 'Install the missing pieces with winget?')) { Fail 'Cancelled.' } if ($needGit) { Install-WithWinget 'Git.Git' 'Git' } if ($needPython) { Install-WithWinget 'Python.Python.3.12' 'Python 3.12' $py = Find-Python } if ($needRg) { try { Install-WithWinget 'BurntSushi.ripgrep.MSVC' 'ripgrep' } catch { Warn 'Could not install ripgrep. Geocentric falls back to a built-in search.' } } if ($needOllama) { Install-WithWinget 'Ollama.Ollama' 'Ollama' } } if (-not $DryRun) { if (-not (Test-Cmd 'git')) { Fail 'Git is still missing. Open a new PowerShell window and run this again.' } if ($null -eq $py) { Fail 'Python 3.9+ is still missing. Open a new PowerShell window and run this again.' } } elseif ($null -eq $py) { $py = @('python') } # -------------------------------------------------------------- source Say 'Fetching Geocentric Code from GitHub' $Src = Join-Path $GeoHome 'src' Run "mkdir $GeoHome" { New-Item -ItemType Directory -Force -Path $GeoHome | Out-Null } if (Test-Path (Join-Path $Src '.git')) { Run 'git fetch and reset' { Invoke-Native 'git' @('-C', $Src, 'fetch', '--quiet', '--depth', '1', 'origin', $Branch) Invoke-Native 'git' @('-C', $Src, 'reset', '--quiet', '--hard', 'FETCH_HEAD') } } else { Run "git clone $RepoUrl" { try { Invoke-Native 'git' @('clone', '--quiet', '--depth', '1', '--branch', $Branch, '--filter=blob:none', '--sparse', $RepoUrl, $Src) Invoke-Native 'git' @('-C', $Src, 'sparse-checkout', 'set', 'geocentric', 'geocentric-gateway') } catch { Warn 'Partial clone is not available here. Falling back to a full shallow clone.' if (Test-Path $Src) { Remove-Item -Recurse -Force $Src } Invoke-Native 'git' @('clone', '--quiet', '--depth', '1', '--branch', $Branch, $RepoUrl, $Src) } } } # ----------------------------------------------------------- virtualenv Say 'Installing Python dependencies' $Venv = Join-Path $GeoHome 'venv' $VenvPy = Join-Path $Venv 'Scripts\python.exe' Run 'python -m venv' { $extra = @() if ($py.Length -gt 1) { $extra = $py[1..($py.Length - 1)] } Invoke-Native $py[0] ($extra + @('-m', 'venv', $Venv)) Invoke-Native $VenvPy @('-m', 'pip', 'install', '--quiet', '--upgrade', 'pip') Invoke-Native $VenvPy @('-m', 'pip', 'install', '--quiet', '-e', $Src) if ($Gateway) { Invoke-Native $VenvPy @('-m', 'pip', 'install', '--quiet', '-e', (Join-Path $Src 'geocentric-gateway')) } } # ----------------------------------------------------- commands on PATH Say "Adding geo to $BinDir" Run 'write geo.cmd' { New-Item -ItemType Directory -Force -Path $BinDir | Out-Null $shim = "@echo off`r`n`"%~dp0..\venv\Scripts\geo.exe`" %*`r`n" [IO.File]::WriteAllText((Join-Path $BinDir 'geo.cmd'), $shim, [Text.Encoding]::ASCII) if ($Gateway) { $gshim = "@echo off`r`n`"%~dp0..\venv\Scripts\geo-gateway.exe`" %*`r`n" [IO.File]::WriteAllText((Join-Path $BinDir 'geo-gateway.cmd'), $gshim, [Text.Encoding]::ASCII) } } $needsRestart = $false $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') $onPath = $false if ($userPath) { $onPath = (($userPath -split ';') | ForEach-Object { $_.TrimEnd('\') }) -contains $BinDir.TrimEnd('\') } if (-not $onPath) { $needsRestart = $true Run 'add bin to user PATH' { $new = if ($userPath) { "$userPath;$BinDir" } else { $BinDir } [Environment]::SetEnvironmentVariable('Path', $new, 'User') } } # -------------------------------------------------------------- ollama if (-not $DryRun) { Update-SessionPath if (-not (Test-Cmd 'ollama')) { $guess = Join-Path $env:LOCALAPPDATA 'Programs\Ollama' if (Test-Path (Join-Path $guess 'ollama.exe')) { $env:Path = "$env:Path;$guess" } } } if ((Test-Cmd 'ollama') -and -not $DryRun) { if (-not (Test-OllamaUp)) { Say 'Starting Ollama' Start-Process -FilePath 'ollama' -ArgumentList 'serve' -WindowStyle Hidden | Out-Null for ($i = 0; $i -lt 40 -and -not (Test-OllamaUp); $i++) { Start-Sleep -Seconds 1 } } if (Test-OllamaUp) { Ok 'Ollama is running' } else { Warn 'Ollama did not respond yet. Start it from the Start menu.' } } # --------------------------------------------------------------- model if ($NoModel) { Say 'Skipping the model download (GEO_NO_MODEL)' } elseif (-not (Test-Cmd 'ollama') -and -not $DryRun) { Warn 'Ollama is not available, so no model was downloaded.' } else { $have = 0 if (-not $DryRun -and (Test-OllamaUp)) { $have = @(& ollama list 2>$null | Select-Object -Skip 1 | Where-Object { $_.Trim() }).Count } if ($have -gt 0) { Ok "Ollama already has $have model(s). Skipping the download." } else { $gb = Get-RamGb if ($env:GEO_MODEL) { $model = $env:GEO_MODEL; $size = '?' } elseif ($gb -ge 16) { $model = 'qwen2.5-coder:14b'; $size = '9 GB' } elseif ($gb -ge 8) { $model = 'qwen2.5-coder:7b'; $size = '4.7 GB' } else { $model = 'qwen2.5-coder:3b'; $size = '1.9 GB' } Say "Local model: $model (about $size for $gb GB of RAM)" if (Confirm-Step "Download $model now?") { if (-not $DryRun -and -not (Test-OllamaUp)) { Warn "Ollama is not running. Later, run: ollama pull $model" } else { Run "ollama pull $model" { Invoke-Native 'ollama' @('pull', $model) } } } else { Say "Skipped. Pull one later with: ollama pull $model" } } } # -------------------------------------------------------------- finish if ($DryRun) { Write-Host "`nDry run complete. Nothing was changed.`n"; return } & $VenvPy -c 'import geocentric' 2>$null if ($LASTEXITCODE -ne 0) { Fail "The install finished but Geocentric will not import. Remove $GeoHome and run this again." } $v = (& (Join-Path $Venv 'Scripts\geo.exe') --version 2>$null) Ok "$v" Write-Host '' Write-Host 'Geocentric Code is ready.' -ForegroundColor Green Write-Host '' if ($needsRestart) { Write-Host ' Open a new terminal so the PATH change takes effect, then:' } else { Write-Host ' Start it in any project folder:' } Write-Host '' Write-Host ' cd your-project' Write-Host ' geo' Write-Host '' Write-Host ' One-shot: geo "add a healthcheck endpoint"' Write-Host ' Help: geo --help' Write-Host ' Update: irm https://geocentricai.com/install.ps1 | iex' Write-Host '' } $opts = @{ NoModel = (($env:GEO_NO_MODEL -eq '1') -or ($args -contains '--no-model')) Gateway = (($env:GEO_GATEWAY -eq '1') -or ($args -contains '--gateway')) Yes = (($env:GEO_YES -eq '1') -or ($args -contains '--yes')) DryRun = (($env:GEO_DRY_RUN -eq '1') -or ($args -contains '--dry-run')) Uninstall = (($env:GEO_UNINSTALL -eq '1') -or ($args -contains '--uninstall')) } try { Install-GeocentricCode @opts } catch { Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red $global:LASTEXITCODE = 1 }