<# .SYNOPSIS Ziro installer for Windows. .DESCRIPTION Installs ziro into its own virtual environment and puts a `ziro` shim on your user PATH. Needs nothing but Python 3.11.9+; no pipx, no uv, no admin rights. iwr -useb https://ziro-agent.com/install.ps1 | iex Served as a static asset by the web app (apps/web), which is why it lives in its public/ directory. It is the ONLY copy in the repo: edit it here and it ships on the next deploy. To run it straight from a checkout: .\apps\web\public\install.ps1 .PARAMETER Version Install a specific version instead of the latest. .PARAMETER Uninstall Remove the install and its shims. Settings and data are kept. .PARAMETER NoModifyPath Install without touching your PATH. The script prints the directory to add. .NOTES When piped to Invoke-Expression there is no way to pass parameters, so every option also reads an environment variable: ZIRO_VERSION, ZIRO_UNINSTALL, ZIRO_NO_MODIFY_PATH, ZIRO_HOME, ZIRO_BIN_DIR, ZIRO_INDEX_URL, ZIRO_EXTRA_INDEX, ZIRO_PYTHON. #> param( [string] $Version, [switch] $Uninstall, [switch] $NoModifyPath ) $ErrorActionPreference = 'Stop' # Parameters win; environment variables are the fallback for the piped case. if (-not $Version) { $Version = $env:ZIRO_VERSION } if (-not $Uninstall) { $Uninstall = [bool]$env:ZIRO_UNINSTALL } if (-not $NoModifyPath) { $NoModifyPath = [bool]$env:ZIRO_NO_MODIFY_PATH } $ZiroHome = if ($env:ZIRO_HOME) { $env:ZIRO_HOME } else { Join-Path $env:USERPROFILE '.ziro' } $BinDir = if ($env:ZIRO_BIN_DIR) { $env:ZIRO_BIN_DIR } else { Join-Path $env:LOCALAPPDATA 'Programs\ziro\bin' } $IndexUrl = if ($env:ZIRO_INDEX_URL) { $env:ZIRO_INDEX_URL } else { 'https://test.pypi.org/simple/' } $ExtraIndex = if ($env:ZIRO_EXTRA_INDEX) { $env:ZIRO_EXTRA_INDEX } else { 'https://pypi.org/simple/' } $VenvDir = Join-Path $ZiroHome 'venv' $MinPy = [version]'3.11.9' # Entry points from pyproject [project.scripts]. `ziro` is the one users type. $Shims = @('ziro', 'ziro-once', 'manage-agents') function Write-Step { param([string] $Message) Write-Host "==> " -ForegroundColor Green -NoNewline; Write-Host $Message } function Write-Warn { param([string] $Message) Write-Host "warning: " -ForegroundColor Yellow -NoNewline; Write-Host $Message } function Stop-With { param([string] $Message) Write-Host "error: " -ForegroundColor Red -NoNewline; Write-Host $Message; exit 1 } function Remove-Ziro { Write-Step "Removing shims from $BinDir" foreach ($name in $Shims) { $cmd = Join-Path $BinDir "$name.cmd" if (Test-Path $cmd) { # Only remove a shim that points into our venv. Never touch an # unrelated file that happens to share the name. if ((Get-Content $cmd -Raw) -like "*$VenvDir*") { Remove-Item $cmd -Force Write-Host " removed $cmd" } else { Write-Warn "left $cmd alone: it does not point at $VenvDir" } } } if (Test-Path $VenvDir) { Write-Step "Removing $VenvDir" Remove-Item $VenvDir -Recurse -Force } Write-Host "" Write-Host "Uninstalled." -ForegroundColor Green # Config, memories, and threads are deliberately kept. Losing a user's # conversation history to an uninstall would be unrecoverable. if (Test-Path $ZiroHome) { Write-Host "Your settings and data are still in $ZiroHome." -ForegroundColor DarkGray Write-Host "Delete it yourself if you want them gone." -ForegroundColor DarkGray } } # Return the path of the first interpreter satisfying $MinPy, or $null. function Find-Python { $candidates = @() if ($env:ZIRO_PYTHON) { $candidates += $env:ZIRO_PYTHON } # The py launcher is the reliable way to reach a specific version on Windows. foreach ($v in '3.14', '3.13', '3.12', '3.11') { $py = Get-Command 'py' -ErrorAction SilentlyContinue if ($py) { $candidates += "$($py.Source)|-$v" } } $candidates += 'python', 'python3' foreach ($entry in $candidates) { $exe, $arg = $entry -split '\|', 2 $cmd = Get-Command $exe -ErrorAction SilentlyContinue if (-not $cmd -and -not (Test-Path $exe)) { continue } $exePath = if ($cmd) { $cmd.Source } else { $exe } $argv = @() if ($arg) { $argv += $arg } $argv += @('-c', 'import sys; print(sys.executable) if sys.version_info >= (3,11,9) else sys.exit(1)') try { $found = & $exePath @argv 2>$null if ($LASTEXITCODE -eq 0 -and $found) { return $found.Trim() } } catch { continue } } return $null } if ($Uninstall) { Remove-Ziro; exit 0 } Write-Host "" Write-Host " Ziro" -ForegroundColor Green -NoNewline Write-Host " installer" Write-Host "" Write-Step "Looking for Python $MinPy or newer" $python = Find-Python if (-not $python) { Stop-With @" no Python $MinPy+ found. Install it from https://www.python.org/downloads/ or run: winget install Python.Python.3.13 Then run this script again. Set `$env:ZIRO_PYTHON to force a specific interpreter. "@ } $pyVersion = & $python -c 'import platform; print(platform.python_version())' Write-Host " $python ($pyVersion)" Write-Step "Creating the environment in $VenvDir" New-Item -ItemType Directory -Force -Path $ZiroHome | Out-Null if (Test-Path $VenvDir) { Write-Host " reusing the existing environment" -ForegroundColor DarkGray } else { & $python -m venv $VenvDir if ($LASTEXITCODE -ne 0) { Stop-With "could not create a virtual environment in $VenvDir" } } $VenvPy = Join-Path $VenvDir 'Scripts\python.exe' if (-not (Test-Path $VenvPy)) { Stop-With "the environment looks broken: no interpreter at $VenvPy" } Write-Step "Installing ziro" $spec = if ($Version) { "ziro==$Version" } else { 'ziro' } & $VenvPy -m pip install --upgrade --quiet --disable-pip-version-check pip if ($LASTEXITCODE -ne 0) { Write-Warn "could not upgrade pip in the environment; continuing" } # TestPyPI does not mirror third-party dependencies, so those resolve from prod # PyPI. This matches what `ziro upgrade` does (app/cli/upgrade.py). & $VenvPy -m pip install --upgrade --disable-pip-version-check ` --index-url $IndexUrl --extra-index-url $ExtraIndex $spec if ($LASTEXITCODE -ne 0) { Stop-With "pip could not install $spec" } Write-Step "Linking into $BinDir" New-Item -ItemType Directory -Force -Path $BinDir | Out-Null foreach ($name in $Shims) { $target = Join-Path $VenvDir "Scripts\$name.exe" if (Test-Path $target) { # A .cmd shim keeps the venv's python, pip, and friends off the user's # PATH; only the entry points we choose are exposed. @" @echo off "$target" %* "@ | Set-Content -Path (Join-Path $BinDir "$name.cmd") -Encoding ASCII Write-Host " $(Join-Path $BinDir "$name.cmd")" } } Write-Step "Verifying" # Quote style matters here: Windows PowerShell 5.1 strips double quotes out of a # native command's arguments, so python received version(ziro) and died with a # NameError. Keep the Python string in SINGLE quotes inside a double-quoted # PowerShell string, which survives both 5.1 and pwsh 7+. $installed = & $VenvPy -c "from importlib.metadata import version; print(version('ziro'))" if ($LASTEXITCODE -ne 0 -or -not $installed) { Stop-With "ziro was not importable after install" } if (-not (Test-Path (Join-Path $BinDir 'ziro.cmd'))) { Stop-With "the ziro shim is missing from $BinDir" } $installed = $installed.Trim() Write-Host " ziro $installed" Write-Host "" Write-Host "Installed ziro $installed" -ForegroundColor Green Write-Host "" $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') $onPath = ($userPath -split ';' | Where-Object { $_.TrimEnd('\') -ieq $BinDir.TrimEnd('\') }).Count -gt 0 if (-not $onPath) { if ($NoModifyPath) { Write-Warn "$BinDir is not on your PATH and -NoModifyPath was set." Write-Host "Add it yourself, or run ziro by full path:" Write-Host "" Write-Host " $(Join-Path $BinDir 'ziro.cmd')" Write-Host "" } else { Write-Step "Adding $BinDir to your user PATH" $newPath = if ([string]::IsNullOrEmpty($userPath)) { $BinDir } else { "$($userPath.TrimEnd(';'));$BinDir" } [Environment]::SetEnvironmentVariable('Path', $newPath, 'User') # Also update this session so the user can run ziro without reopening. $env:Path = "$env:Path;$BinDir" Write-Host "" Write-Warn "PATH was changed. It applies to new terminals automatically." Write-Host "This session was updated too, so you can run ziro right now." Write-Host "" } } Write-Host "Run " -NoNewline Write-Host "ziro" -ForegroundColor Green -NoNewline Write-Host " to start. First launch walks you through setup." Write-Host ""