# pico-bridge installer for Windows: Claude chats and limits -> Raspberry Pi Pico over USB. # Install: irm https://pico.bralin.kz/install.ps1 | iex # Remove: & ([scriptblock]::Create((irm https://pico.bralin.kz/install.ps1))) -Uninstall param([switch]$Uninstall) $ErrorActionPreference = "Stop" $Base = "https://pico.bralin.kz" # Windows PowerShell 5.1 reads "irm" text without a charset as Latin-1, which breaks the # Russian messages. Then the one-letter marker below is several characters long: # fetch the script again as bytes, decode it as UTF-8 and run that instead. if ('ж'.Length -ne 1) { $bytes = (Invoke-WebRequest "$Base/install.ps1" -UseBasicParsing).RawContentStream.ToArray() $text = [Text.Encoding]::UTF8.GetString($bytes).TrimStart([char]0xFEFF) & ([scriptblock]::Create($text)) -Uninstall:$Uninstall return } # Not in AppData: programs started from the Claude desktop app (MSIX) see a private copy of it. $Dir = Join-Path $env:USERPROFILE "pico-bridge" $Task = "pico-bridge" function Say($text) { Write-Host " $text" } function Step($text) { Write-Host "`n> $text" -ForegroundColor DarkYellow } function Stop-Bridge { if (Get-ScheduledTask -TaskName $Task -ErrorAction SilentlyContinue) { Stop-ScheduledTask -TaskName $Task -ErrorAction SilentlyContinue } Get-CimInstance Win32_Process -Filter "Name='pythonw.exe' OR Name='python.exe'" | Where-Object { $_.CommandLine -match "pico_bridge\.py" } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } } if ($Uninstall) { Step "Удаляю pico-bridge" Stop-Bridge Unregister-ScheduledTask -TaskName $Task -Confirm:$false -ErrorAction SilentlyContinue if (Test-Path $Dir) { Remove-Item -Recurse -Force $Dir } Say "Готово: задача автозапуска и папка $Dir удалены." return } function Find-Python { # A real Python 3.9+; the WindowsApps "python.exe" is only a Store shortcut. $candidates = @() if (Get-Command py -ErrorAction SilentlyContinue) { $candidates += , @("py", "-3") } foreach ($name in "python", "python3") { $cmd = Get-Command $name -ErrorAction SilentlyContinue if ($cmd -and $cmd.Source -notmatch "WindowsApps") { $candidates += , @($cmd.Source) } } $candidates += Get-ChildItem "$env:LOCALAPPDATA\Programs\Python\Python3*\python.exe", ` "$env:LOCALAPPDATA\Programs\Thonny\python.exe" -ErrorAction SilentlyContinue | ForEach-Object { , @($_.FullName) } foreach ($c in $candidates) { $exe = $c[0]; $rest = @($c | Select-Object -Skip 1) try { $path = & $exe @rest -c "import sys; print(sys.executable if sys.version_info >= (3, 9) else '')" 2>$null if ($LASTEXITCODE -eq 0 -and $path) { return $path.Trim() } } catch { } } return $null } Write-Host "pico-bridge: чаты и лимиты Claude на экране Raspberry Pi Pico" -ForegroundColor DarkYellow Step "Ищу Python 3.9+" $python = Find-Python if (-not $python) { Say "Python не найден." if (Get-Command winget -ErrorAction SilentlyContinue) { $answer = Read-Host " Поставить Python 3.12 через winget? [Y/n]" if ($answer -notmatch "^[nNнН]") { winget install --id Python.Python.3.12 -e --scope user --accept-package-agreements --accept-source-agreements $env:Path = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine") $python = Find-Python } } if (-not $python) { throw "Нужен Python 3.9+: https://www.python.org/downloads/ (галочка 'Add to PATH'), потом запусти установку снова." } } Say $python Step "Готовлю папку $Dir" Stop-Bridge New-Item -ItemType Directory -Force $Dir | Out-Null if (-not (Test-Path "$Dir\venv\Scripts\pythonw.exe")) { & $python -m venv "$Dir\venv" if ($LASTEXITCODE -ne 0) { throw "Не получилось создать venv" } } $venvPython = "$Dir\venv\Scripts\python.exe" Step "Ставлю пакеты (pyserial и winrt для уведомлений Cowork)" & $venvPython -m pip install --quiet --disable-pip-version-check --upgrade pyserial ` winrt-runtime winrt-Windows.Foundation winrt-Windows.Foundation.Collections ` winrt-Windows.ApplicationModel winrt-Windows.UI.Notifications winrt-Windows.UI.Notifications.Management if ($LASTEXITCODE -ne 0) { throw "pip install не удался (нет интернета?)" } Step "Скачиваю демон" Invoke-WebRequest "$Base/pico_bridge.py" -OutFile "$Dir\pico_bridge.py" -UseBasicParsing Say "$Dir\pico_bridge.py" Step "Проверяю Claude Code" $claude = (Get-Command claude -ErrorAction SilentlyContinue).Source if (-not $claude -and (Test-Path "$env:USERPROFILE\.local\bin\claude.exe")) { $claude = "$env:USERPROFILE\.local\bin\claude.exe" } if ($claude) { Say "CLI: $claude" } else { Say "CLI claude не найден: лимиты работать не будут. Установка: https://claude.com/claude-code" } if (-not (Test-Path "$env:USERPROFILE\.claude\.credentials.json")) { Say "Нет входа в CLI: запусти 'claude' и введи /login (подписка Claude), иначе лимитов не будет." } Step "Включаю автозапуск (Планировщик задач, при входе в Windows)" $user = "$env:USERDOMAIN\$env:USERNAME" $action = New-ScheduledTaskAction -Execute "$Dir\venv\Scripts\pythonw.exe" -Argument "`"$Dir\pico_bridge.py`"" -WorkingDirectory $Dir $trigger = New-ScheduledTaskTrigger -AtLogOn -User $user $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries ` -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) ` -MultipleInstances IgnoreNew -StartWhenAvailable $principal = New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive -RunLevel Limited Register-ScheduledTask -TaskName $Task -Action $action -Trigger $trigger -Settings $settings -Principal $principal ` -Description "Claude chats and limits -> Raspberry Pi Pico over USB ($Base)" -Force | Out-Null Start-ScheduledTask -TaskName $Task Step "Что видит демон" & $venvPython "$Dir\pico_bridge.py" --list Write-Host "`nГотово. Демон работает и будет стартовать сам." -ForegroundColor Green Say "Лог: $Dir\state\bridge.log" Say "Для Thonny: $venvPython $Dir\pico_bridge.py --pause (потом --resume)" Say "Удалить: & ([scriptblock]::Create((irm $Base/install.ps1))) -Uninstall"