# Ping installer for Windows. Members run the line shown on their install page, in PowerShell: # irm https://pondletter.com/ping-install.ps1 -OutFile $env:TEMP\ping-install.ps1; & $env:TEMP\ping-install.ps1 # Read this first if you like; it is short. What it does, and nothing else: # 1. installs uv (a Python package manager) for your user if you do not have it; # 2. downloads Ping and its checksum list with your token, checks the SHA-256, and installs it with its own Python; # 3. fetches your member key (tied to your membership email) into Ping's folder, where Ping picks it up once; # 4. adds a Start menu shortcut and opens Ping. # No administrator rights. Everything lives in your user folder. # Uninstall: uv tool uninstall ping ; Remove-Item -Recurse ~\.ping ; remove the Start menu shortcut "Ping" # Your token is personal and expires in 30 minutes; the member key it fetches is tied to your account. param([string]$Token = $env:PING_TOKEN, [string]$Base = "https://pondletter.com", [string]$Wheel = $env:PING_WHEEL, [string]$DataDir = (Join-Path $env:USERPROFILE ".ping")) $ErrorActionPreference = "Stop" $WheelName = "ping-latest-py3-none-any.whl" function Say($m) { Write-Host $m -ForegroundColor Cyan } function Fail($m) { Write-Host "Ping installer: $m" -ForegroundColor Red; exit 1 } if (-not $Wheel -and -not $Token) { Fail "the download token is missing. Copy the whole command from your install page: $Base/install-ping/" } $tmp = Join-Path $env:TEMP ("ping-" + [guid]::NewGuid().ToString("N")) New-Item -ItemType Directory -Force $tmp | Out-Null function Fetch($name, $dest) { try { Invoke-WebRequest "$Base/ping-dl/$Token/$name" -OutFile $dest -UseBasicParsing } catch { $code = $_.Exception.Response.StatusCode.value__ if ($code -eq 410) { Fail "this install link has expired (they last 30 minutes). Open $Base/install-ping/ for a fresh one." } Fail "download of $name failed (HTTP $code). Open $Base/install-ping/ and copy the command again." } } # 1. uv $uv = Get-Command uv -ErrorAction SilentlyContinue if ($uv) { $uvPath = $uv.Source } else { Say "Installing uv (Python manager) for this user" $env:UV_NO_MODIFY_PATH = "1" irm https://astral.sh/uv/install.ps1 | iex | Out-Null $uvPath = Join-Path $env:USERPROFILE ".local\bin\uv.exe" if (-not (Test-Path $uvPath)) { $uvPath = Join-Path $env:LOCALAPPDATA "uv\uv.exe" } if (-not (Test-Path $uvPath)) { Fail "uv did not install. See https://docs.astral.sh/uv/ and run this again." } } # 2. Ping, verified against the published checksum if (-not $Wheel) { Say "Downloading Ping" Fetch "SHA256SUMS" (Join-Path $tmp "SHA256SUMS") $sums = Get-Content (Join-Path $tmp "SHA256SUMS") # The list names the versioned file; uv needs the version in the filename, so save the download under that name. $realLine = $sums | Where-Object { $_ -match " ping-\d\S*\.whl$" } | Select-Object -First 1 if (-not $realLine) { Fail "the checksum list did not name a versioned wheel." } $realName = ($realLine -split "\s+")[1] $line = $sums | Where-Object { $_ -match " $([regex]::Escape($WheelName))$" } | Select-Object -First 1 if (-not $line) { Fail "the checksum list did not mention $WheelName." } $want = ($line -split "\s+")[0].ToLower() Fetch $WheelName (Join-Path $tmp $realName) $have = (Get-FileHash (Join-Path $tmp $realName) -Algorithm SHA256).Hash.ToLower() if ($want -ne $have) { Fail "checksum mismatch for $realName (expected $want, got $have). Nothing was installed." } Say "Checksum verified: $have ($realName)" $Wheel = Join-Path $tmp $realName } Say "Installing Ping with its own Python 3.12" & $uvPath tool install --quiet --python 3.12 --force --reinstall "ping[app] @ $Wheel" if ($LASTEXITCODE -ne 0) { Fail "uv could not install Ping (exit $LASTEXITCODE)." } $bin = (& $uvPath tool dir --bin).Trim() $pingApp = Join-Path $bin "ping-app.exe" if (-not (Test-Path $pingApp)) { Fail "install finished but the launcher was not found at $pingApp" } # 3. Your member key, handed to Ping once (it moves it into Windows Credential Manager and deletes the file) if ($Token) { New-Item -ItemType Directory -Force $DataDir | Out-Null Fetch "member-key" (Join-Path $DataDir "member-key.bootstrap") Say "Member key fetched for this account" } # 4. Start menu shortcut $programs = [Environment]::GetFolderPath("Programs") $lnk = Join-Path $programs "Ping.lnk" $shell = New-Object -ComObject WScript.Shell $sc = $shell.CreateShortcut($lnk) $sc.TargetPath = $pingApp $sc.Description = "Ping: paper-trading bot for Kalshi, by Pondletter" $iconPath = Join-Path $DataDir "icon.ico" try { if ($Token) { Fetch "icon.ico" $iconPath; $sc.IconLocation = $iconPath } } catch {} $sc.Save() Say "Added Ping to the Start menu" Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue # 5. Open it Say "Starting Ping (it opens in your browser; look for the icon in the system tray)" Start-Process -FilePath $pingApp -WindowStyle Hidden Write-Host "" Write-Host "Done. To run Ping later: Start menu > Ping. To update: copy a fresh command from $Base/install-ping/ and run it again." Write-Host "To uninstall: uv tool uninstall ping. Where the code lives (read it any time): $((& $uvPath tool dir).Trim())\ping"