# Siphrix installer - Windows 10/11, Python 3.11+ # # What this does, in order, and nothing else: # 1. Finds Python 3.11+, and offers to fetch it if it is missing. # 2. Installs the siphrix package from PyPI into your user site. # 3. Asks whether this machine belongs to a company account, then runs # `siphrix set-up` - which senses what is on this machine (Claude # Code, Codex, VS Code, browsers, local models), shows a plan, and # changes nothing until you agree. # # Read it all before running it. It is served as plain text so you can: # irm https://siphrix.com/install.ps1 | iex # is convenient; opening this URL in a browser first is wiser. # # The whole body lives inside a function on purpose. Run through `iex`, # a top-level `exit` terminates the PowerShell HOST - the window closes, # taking the error message with it, and the person is left with a # vanished terminal and no idea why. `return` inside a function ends the # install and leaves the explanation on screen, which is the difference # between a failure and a mystery. function Invoke-SiphrixInstall { # Deliberately NOT "Stop". Native programs on this path - winget, pip - # write ordinary progress to stderr, and under Stop that becomes a # terminating error that kills a working install. Exit codes are # checked explicitly instead, which is the honest way to read a # console program anyway. $ErrorActionPreference = "Continue" function Show-Problem([string]$msg) { Write-Host "" Write-Host " x $msg" -ForegroundColor Red Write-Host "" } function Find-Python { foreach ($candidate in @("py", "python", "python3")) { try { $v = & $candidate -c "import sys;print('%d.%d' % sys.version_info[:2])" 2>$null if ($v -and ([version]$v -ge [version]"3.11")) { return $candidate } } catch {} } return $null } Write-Host "" Write-Host " SIPHRIX - AI Action Audit & Risk Monitor" -ForegroundColor Cyan Write-Host " Records what your AI does. Blocks nothing." -ForegroundColor DarkGray Write-Host "" # --- 1. Python ---------------------------------------------------------- $py = Find-Python if (-not $py) { # Offered, never assumed. Installing a language runtime is a change to # somebody's machine, and on a work laptop it is a change their IT # department has opinions about. Write-Host " Python 3.11+ is required and was not found." -ForegroundColor Yellow Write-Host "" Write-Host " [1] Install it now (winget install Python.Python.3.12)" Write-Host " [2] I will install it myself - stop here" Write-Host "" $choice = Read-Host " Choose [1]" if ($choice -eq "2") { Show-Problem "Install Python from https://python.org/downloads (tick 'Add to PATH'), then run this again." return } if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { Show-Problem "winget is not available on this machine. Install Python from https://python.org/downloads (tick 'Add to PATH'), then run this again." return } # `--source winget` and `-e` are both load-bearing, and both were # learned from a machine rather than a manual. # # Without the source, winget also searches `msstore`. When that source # fails - an expired certificate, a proxy, a VM whose clock has # drifted - winget does not fall back to the source that worked. It # reports the package as ambiguous and refuses: # # Failed when searching source: msstore # The following packages were found among the working sources. # Please specify one of them using the --source option to proceed. # # Python is in the community repository, never the Store, so naming # the source removes a dependency on an unrelated service being # healthy. `-e` matches the id exactly rather than as a search term. Write-Host " ... installing Python via winget (this takes a minute)" winget install -e --id Python.Python.3.12 --source winget --silent --accept-source-agreements --accept-package-agreements $wingetCode = $LASTEXITCODE if ($wingetCode -ne 0) { Show-Problem "winget could not install Python (exit code $wingetCode). Install it from https://python.org/downloads - tick 'Add python.exe to PATH' on the first screen - then run this again." return } # winget does not refresh this shell's PATH, so re-read it rather than # sending somebody away to open a new terminal and start over. $machinePath = [System.Environment]::GetEnvironmentVariable("Path", "Machine") $userPath = [System.Environment]::GetEnvironmentVariable("Path", "User") $env:Path = "$machinePath;$userPath" $py = Find-Python if (-not $py) { Show-Problem "Python installed, but this window's PATH does not show it yet. Close PowerShell, open a new one, and run this again - the install will pick up from here." return } } Write-Host " OK Python $(& $py -c 'import sys;print(sys.version.split()[0])') found" -ForegroundColor Green # --- 2. The package ----------------------------------------------------- Write-Host " ... installing siphrix from PyPI" & $py -m pip install --upgrade --user --quiet siphrix if ($LASTEXITCODE -ne 0) { Show-Problem "pip could not install siphrix. Check your network and try again." return } $ver = & $py -c "import siphrix;print(siphrix.__version__)" 2>$null Write-Host " OK siphrix $ver installed" -ForegroundColor Green # --- 3. Company or personal --------------------------------------------- # One question, asked outright. The alternative is guessing from # something - a domain-joined machine, a corporate PATH - and a wrong # guess either sends somebody's records to a console they did not # choose, or leaves a company laptop invisible to the people # answerable for it. Neither is recoverable by the person answering. Write-Host "" Write-Host " ---------------------------------------------------------" Write-Host " Is this machine part of a company account?" Write-Host "" Write-Host " [1] Yes - I have a connection code" Write-Host " [2] No - personal use, nothing leaves this machine" Write-Host "" $mode = Read-Host " Choose [2]" Write-Host " ---------------------------------------------------------" Write-Host "" $code = "" $server = "https://siphrix.com" if ($mode -eq "1") { $code = (Read-Host " Paste your connection code").Trim() $entered = (Read-Host " Console [https://siphrix.com]").Trim() if ($entered) { $server = $entered } } Write-Host "" if ($code) { & $py -m siphrix set-up --code $code --server $server } else { & $py -m siphrix set-up --personal } # --- 4. Say the commands that actually work on THIS machine ------------- # pip puts `siphrix.exe` in a Scripts directory that is very often not # on PATH, and prints a warning most people scroll past. Printing # "siphrix coverage" regardless would hand somebody three commands that # answer "not recognized" a minute after they installed an audit tool. $bare = $null try { $bare = (Get-Command siphrix -ErrorAction SilentlyContinue) } catch {} Write-Host "" Write-Host " Done. Useful next steps:" -ForegroundColor Cyan if ($bare) { Write-Host " siphrix coverage what is recorded here, and what is not" Write-Host " siphrix console open the local console" Write-Host " siphrix doctor check the install end to end" } else { Write-Host " py -m siphrix coverage what is recorded here, and what is not" Write-Host " py -m siphrix console open the local console" Write-Host " py -m siphrix doctor check the install end to end" Write-Host "" Write-Host " (the bare 'siphrix' command needs this on your PATH:" -ForegroundColor DarkGray # Found, not guessed. `sysconfig.get_path('scripts')` is the base # install's directory, but this installer uses `pip --user`, which # puts the entry point somewhere else entirely - Roaming rather than # Local\Programs on Windows. Printing the base path sent somebody to # add a directory that does not contain siphrix.exe, so the advice # cost them a PATH edit and still left the command broken. $finder = "import glob,os,sysconfig" + ";c=[sysconfig.get_path('scripts',sysconfig.get_preferred_scheme('user'))," + "sysconfig.get_path('scripts')]" + ";print(next((d for d in c if glob.glob(os.path.join(d,'siphrix*'))),c[0]))" $scripts = & $py -c $finder 2>$null if ($scripts) { Write-Host " $scripts)" -ForegroundColor DarkGray } } Write-Host "" } Invoke-SiphrixInstall