Add native Windows host daemon (bleak/WinRT)
Adds a Windows port of the host daemon so the Clawdmeter stays connected on Windows independent of WSL. Mirrors the macOS daemon and speaks the existing GATT data service unchanged — no firmware changes. - claude_usage_daemon_windows.py: Windows-local OAuth token read + Anthropic poll + BLE scan/connect/write, with auto-reconnect (connect-retry wrapper, zombie-link break, split fast/slow backoff) - tray_windows.py: pystray login-startup tray app (status icon + Quit) - autostart_windows.py: winreg HKCU\Run autostart via pythonw.exe - icon_assets.py: per-state tray icons composited from logo.h - install-windows.ps1 + daemon/README-windows.md: turnkey setup - pytest suite: token / poll / reconnect / tray / autostart / no-WSL guard
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
# install-windows.ps1 - Clawdmeter Windows turnkey bootstrap (D-09)
|
||||
#
|
||||
# Creates a Python virtual environment, installs dependencies from
|
||||
# daemon\requirements-windows.txt, registers the tray app to launch at login
|
||||
# (HKCU\...\Run, no admin required), and starts the tray app immediately.
|
||||
#
|
||||
# Usage:
|
||||
# powershell -ExecutionPolicy Bypass -File install-windows.ps1
|
||||
#
|
||||
# Or, if you have already set a permissive execution policy:
|
||||
# .\install-windows.ps1
|
||||
#
|
||||
# To disable autostart later: right-click the tray icon -> uncheck "Start at login"
|
||||
# Or remove manually: reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v Clawdmeter /f
|
||||
#
|
||||
# Security: this script downloads nothing from the internet. It installs only
|
||||
# the packages listed in the in-repo daemon\requirements-windows.txt.
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Log {
|
||||
param([string]$Msg)
|
||||
$ts = Get-Date -Format "HH:mm:ss"
|
||||
Write-Host "[$ts] $Msg"
|
||||
}
|
||||
|
||||
$RepoRoot = $PSScriptRoot
|
||||
if (-not $RepoRoot) {
|
||||
$RepoRoot = (Get-Location).Path
|
||||
}
|
||||
|
||||
Log "=== Clawdmeter Windows Install ==="
|
||||
Log "Repository root: $RepoRoot"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Guard: refuse to install from a WSL path (APP-02 / SC#4 / SC#5)
|
||||
# ------------------------------------------------------------------
|
||||
# If $RepoRoot lives on the WSL share (\\wsl$\... or \\wsl.localhost\...),
|
||||
# the venv and the HKCU\Run autostart entry would both point at a path that
|
||||
# disappears when WSL is shut down -- exactly the WSL-dependence this project
|
||||
# exists to eliminate. Copy the repo to a native Windows path first.
|
||||
if ($RepoRoot -match '\\\\wsl(\$|\.localhost)\\') {
|
||||
throw @"
|
||||
Refusing to install from a WSL path:
|
||||
$RepoRoot
|
||||
|
||||
The Clawdmeter daemon must be WSL-independent. Installing from the WSL share
|
||||
would make the virtual environment and login-autostart entry point at a path
|
||||
that is unreachable once WSL shuts down.
|
||||
|
||||
Fix: copy this repository to a native Windows location and run the installer
|
||||
there, e.g.
|
||||
|
||||
Copy-Item -Recurse '$RepoRoot' "$env:USERPROFILE\Clawdmeter"
|
||||
cd "$env:USERPROFILE\Clawdmeter"
|
||||
powershell -ExecutionPolicy Bypass -File install-windows.ps1
|
||||
"@
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 1: Create virtual environment
|
||||
# ------------------------------------------------------------------
|
||||
$VenvDir = Join-Path $RepoRoot ".venv"
|
||||
if (Test-Path $VenvDir) {
|
||||
Log "Virtual environment already exists at .venv - skipping creation"
|
||||
} else {
|
||||
Log "Creating virtual environment at .venv ..."
|
||||
& python -m venv $VenvDir
|
||||
if ($LASTEXITCODE -ne 0) { throw "Failed to create virtual environment (exit $LASTEXITCODE)" }
|
||||
Log "Virtual environment created"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 2: Install dependencies
|
||||
# ------------------------------------------------------------------
|
||||
$PythonExe = Join-Path $VenvDir "Scripts\python.exe"
|
||||
$PythonwExe = Join-Path $VenvDir "Scripts\pythonw.exe"
|
||||
$RequirementsFile = Join-Path $RepoRoot "daemon\requirements-windows.txt"
|
||||
|
||||
Log "Installing dependencies from daemon\requirements-windows.txt ..."
|
||||
& $PythonExe -m pip install --quiet -r $RequirementsFile
|
||||
if ($LASTEXITCODE -ne 0) { throw "pip install failed (exit $LASTEXITCODE)" }
|
||||
Log "Dependencies installed"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 3: Register autostart (HKCU\Run, per-user, no admin needed)
|
||||
# ------------------------------------------------------------------
|
||||
# Derive all paths at install time - never hard-code an absolute path that
|
||||
# breaks when the repository is moved (CLAUDE.md "repoint ExecStart" lesson,
|
||||
# RESEARCH Anti-Pattern).
|
||||
$TrayScript = Join-Path $RepoRoot "daemon\tray_windows.py"
|
||||
|
||||
Log "Registering autostart (HKCU\Software\Microsoft\Windows\CurrentVersion\Run) ..."
|
||||
# Invoke the autostart helper via the just-created venv python so sys.executable
|
||||
# resolves to the venv's pythonw.exe (the path that will be written to the registry).
|
||||
& $PythonExe -c @"
|
||||
import sys, os
|
||||
sys.path.insert(0, r'$RepoRoot')
|
||||
import daemon.autostart_windows as a
|
||||
a.enable(tray_script=r'$TrayScript')
|
||||
"@
|
||||
if ($LASTEXITCODE -ne 0) { throw "Autostart registration failed (exit $LASTEXITCODE)" }
|
||||
Log "Autostart registered - Clawdmeter will launch automatically at next logon"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 4: Launch the tray app (headless - BASE pythonw.exe, no console window)
|
||||
# ------------------------------------------------------------------
|
||||
# Use the BASE interpreter's pythonw.exe, NOT the venv's Scripts\pythonw.exe.
|
||||
# The venv pythonw is a redirector stub that re-launches the CONSOLE python.exe
|
||||
# build as a child (a CPython venv-launcher bug), popping a black console window.
|
||||
# tray_windows.py adds the venv site-packages to sys.path itself, so the venv's
|
||||
# dependencies still resolve. (See autostart_windows._command - same rationale.)
|
||||
$BasePrefix = & $PythonExe -c "import sys; print(sys.base_exec_prefix)"
|
||||
$BasePythonw = Join-Path $BasePrefix "pythonw.exe"
|
||||
|
||||
Log "Launching tray app ..."
|
||||
$StartArgs = @{
|
||||
FilePath = $BasePythonw
|
||||
ArgumentList = "`"$TrayScript`""
|
||||
WorkingDirectory = $RepoRoot
|
||||
}
|
||||
Start-Process @StartArgs
|
||||
Log "Tray app started - look for the Clawdmeter icon in your notification area"
|
||||
Log "=== Install complete ==="
|
||||
Reference in New Issue
Block a user