# TALLY Link installer for Windows 10/11. # irm https://tallymeter.io/link/install.ps1 | iex # Installs a small background helper in your own account (no administrator rights needed) that reads the # Claude and OpenAI (Codex) usage already on this PC and sends it to TALLY over the USB-C cable. # program %LOCALAPPDATA%\TALLY Link\tally_link.py # settings %USERPROFILE%\.tally-link\ # (c) 2026 Meta Studios Creative Agency, Tokyo & { $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 $BaseUrl = if ($env:TALLY_LINK_URL) { $env:TALLY_LINK_URL } else { "https://tallymeter.io/link" } $TaskName = "TALLY Link" $App = Join-Path $env:LOCALAPPDATA "TALLY Link" $Script = Join-Path $App "tally_link.py" $StartDir = [Environment]::GetFolderPath("Startup") $Startup = if ($StartDir) { Join-Path $StartDir "TALLY Link.lnk" } else { Join-Path $env:TEMP "TALLY Link.lnk" } function Say([string]$t) { Write-Host $t } function Step([string]$t) { Write-Host ""; Write-Host " $t" -ForegroundColor DarkYellow } # A working Python 3.9+ (not the Microsoft Store placeholder that only opens the Store) function Test-Python([string]$exe, [string[]]$pre = @()) { try { # Windows PowerShell 5.1 turns redirected stderr into errors, so relax that just here $out = & { $ErrorActionPreference = "Continue"; & $exe @pre -c "import sys; print(sys.executable if sys.version_info >= (3, 9) else '')" 2>$null } if ($LASTEXITCODE -eq 0 -and $out -and (Test-Path ($out.Trim()))) { return $out.Trim() } } catch { } return $null } function Find-Python { $saved = Join-Path $App "python.txt" if (Test-Path $saved) { $p = Test-Python (Get-Content $saved -Raw).Trim(); if ($p) { return $p } } $p = $null if (Get-Command py -ErrorAction SilentlyContinue) { $p = Test-Python "py" @("-3") } if (-not $p -and (Get-Command python -ErrorAction SilentlyContinue)) { $p = Test-Python "python" } if (-not $p) { foreach ($c in (Get-ChildItem (Join-Path $env:LOCALAPPDATA "Programs\Python\Python3*\python.exe") -ErrorAction SilentlyContinue | Sort-Object FullName -Descending)) { $p = Test-Python $c.FullName; if ($p) { break } } } return $p } function Install-Python { if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { throw "Python is not installed and winget is missing. Install Python 3 from https://www.python.org/downloads/windows/ (tick 'Add python.exe to PATH'), then run the TALLY Link install command again." } Step "Installing Python 3.12 for your account (from winget)..." & winget install --id Python.Python.3.12 -e --scope user --silent --accept-package-agreements --accept-source-agreements | Out-Host $p = Find-Python if (-not $p) { throw "Python was installed but could not be found yet. Sign out and back in, then run the TALLY Link install command again." } return $p } function Stop-TallyLink { try { Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue } catch { } Get-CimInstance Win32_Process -Filter "Name like 'python%.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -and $_.CommandLine -like "*tally_link.py*" -and $_.CommandLine -notlike "*--status*" } | ForEach-Object { try { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } catch { } } } function Register-Start([string]$pyw) { $me = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name try { $act = New-ScheduledTaskAction -Execute $pyw -Argument "`"$Script`"" -WorkingDirectory $App $trg = New-ScheduledTaskTrigger -AtLogOn -User $me $set = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable ` -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -MultipleInstances IgnoreNew $who = New-ScheduledTaskPrincipal -UserId $me -LogonType Interactive -RunLevel Limited Register-ScheduledTask -TaskName $TaskName -Action $act -Trigger $trg -Settings $set -Principal $who ` -Description "Sends your Claude and OpenAI usage to TALLY over USB." -Force | Out-Null if (Test-Path $Startup) { Remove-Item $Startup -Force } Start-ScheduledTask -TaskName $TaskName return "a task that starts when you sign in" } catch { # some PCs do not let ordinary accounts make tasks: a Startup shortcut does the same job $sh = New-Object -ComObject WScript.Shell $lnk = $sh.CreateShortcut($Startup) $lnk.TargetPath = $pyw; $lnk.Arguments = "`"$Script`""; $lnk.WorkingDirectory = $App $lnk.Description = "TALLY Link"; $lnk.WindowStyle = 7; $lnk.Save() Start-Process -FilePath $pyw -ArgumentList "`"$Script`"" -WorkingDirectory $App -WindowStyle Hidden return "a shortcut in your Startup folder" } } try { Say ""; Say " TALLY Link for Windows"; Say " ======================" New-Item -ItemType Directory -Force -Path $App | Out-Null Step "Downloading TALLY Link..." $man = Invoke-RestMethod -Uri "$BaseUrl/manifest.json" -UseBasicParsing $tmp = Join-Path $env:TEMP ("tally_link_" + [guid]::NewGuid().ToString("N") + ".py") Invoke-WebRequest -Uri "$BaseUrl/tally_link.py" -OutFile $tmp -UseBasicParsing $got = (Get-FileHash -Path $tmp -Algorithm SHA256).Hash.ToLower() if ($got -ne ([string]$man.sha256).ToLower()) { Remove-Item $tmp -Force; throw "the download did not match its checksum. Please try again in a minute." } Say " version $($man.version), checksum OK" Step "Looking for Python..." $py = Find-Python if (-not $py) { $py = Install-Python } Say " $py" Set-Content -Path (Join-Path $App "python.txt") -Value $py -Encoding UTF8 $pyw = Join-Path (Split-Path $py) "pythonw.exe" if (-not (Test-Path $pyw)) { $pyw = $py } Step "Adding what TALLY Link needs (pyserial)..." & { $ErrorActionPreference = "Continue"; & $py -m pip install --user --upgrade --disable-pip-version-check --quiet pyserial 2>$null } if ($LASTEXITCODE -ne 0) { throw "could not install pyserial (is the PC online?)." } & { $ErrorActionPreference = "Continue"; & $py -m pip install --user --upgrade --disable-pip-version-check --quiet cramjam pillow certifi 2>$null } if ($LASTEXITCODE -ne 0) { Say " (optional extras skipped - TALLY Link works without them)" } Step "Installing..." Stop-TallyLink if (Test-Path $Script) { $old = (Select-String -Path $Script -Pattern '^VERSION = "([^"]+)"' | Select-Object -First 1) if ($old -and $old.Matches[0].Groups[1].Value -ne $man.version) { Copy-Item $Script (Join-Path $App ("tally_link_" + $old.Matches[0].Groups[1].Value + ".py")) -Force } } Move-Item $tmp $Script -Force Say " $Script" Step "Starting TALLY Link..." $how = Register-Start $pyw Say " TALLY Link $($man.version) is running, and starts by itself with $how." Step "What TALLY Link can read on this PC:" Start-Sleep -Seconds 2 & { $ErrorActionPreference = "Continue"; & $py $Script --status 2>$null } | ForEach-Object { Say " $_" } Say "" Say " Done. Plug TALLY in with a USB-C data cable: your usage appears within about 20 seconds." Say " Remove it any time with: irm $BaseUrl/uninstall.ps1 | iex" Say "" } catch { if ($tmp -and (Test-Path $tmp)) { Remove-Item $tmp -Force -ErrorAction SilentlyContinue } Say ""; Write-Host " Something went wrong: $($_.Exception.Message)" -ForegroundColor Red; Say "" } }