Files
clawdmeter/daemon/README-windows.md
T
kvenanzi eb70456755 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
2026-06-02 16:39:07 -05:00

224 lines
9.3 KiB
Markdown

# Windows Setup and Run Guide
This guide covers running the Clawdmeter Windows daemon on native Windows hardware.
It includes the turnkey `install-windows.ps1` bootstrap (tray icon + login autostart),
the manual-run fallback, and how to manage or remove autostart.
---
## Prerequisites
| Requirement | Details |
|-------------|---------|
| **Native Windows** | Must run on real Windows — not WSL. The script prints a warning and BLE will not work under WSL. |
| **Python 3.11+** | Download from [python.org](https://www.python.org/downloads/) if not already installed. Ensure "Add python.exe to PATH" is checked during install. |
| **Claude Code installed** | Install Claude Code and complete `claude login` so credentials exist on disk. |
| **Clawdmeter powered on** | The device must be powered on and in range before the daemon starts. |
| **Paired with Windows Bluetooth** | Pair the device once via **Settings → Bluetooth & devices → Add device** (see [Pair the device](#pair-the-device-one-time)). This is required — the device is a bonded BLE HID keyboard, so pairing enables its physical buttons and keeps a persistent connection that shows your last usage even when the daemon is stopped. |
### Where are my credentials?
`claude login` writes the OAuth token to (first match wins):
1. `%USERPROFILE%\.claude\.credentials.json` — primary path (confirmed by Claude Code docs)
2. `%LOCALAPPDATA%\Claude\.credentials.json` — fallback
3. `%APPDATA%\Claude\.credentials.json` — fallback
The daemon probes these paths in order. You can also set `CLAUDE_CREDENTIALS_PATH` to an
absolute path or `CLAUDE_CONFIG_DIR` to a directory to override the search entirely.
> **Security note:** The credentials file contains your OAuth token. Never share its contents
> or embed it in scripts. The daemon reads it from disk and uses it only as the API
> `Authorization` header — the token is never written to any log, tooltip, or notification.
---
## Pair the device (one time)
The Clawdmeter is a **bonded BLE HID keyboard** as well as a usage display — its firmware
enables bonding (`NimBLEDevice::setSecurityAuth`) and advertises the HID service so its
physical buttons act as a keyboard (Space / Shift+Tab). Pair it with Windows **once**,
before running the daemon:
1. Put the device on its Bluetooth waiting screen (powered on, not yet connected).
2. Open **Settings → Bluetooth & devices → Add device → Bluetooth**.
3. Select **Claude Controller** and complete pairing.
**Why this is required:**
- **Keyboard buttons** — HID over BLE requires bonding on Windows. Without pairing, the
device's buttons won't reach the PC.
- **Persistent point-in-time view** — once paired, Windows maintains the BLE link and
auto-reconnects the device whenever it is in range. This is intentional: the device keeps
showing your **last-synced** usage even after you Quit the daemon, as a glanceable
point-in-time view. Quitting the daemon releases only its data connection — it does **not**
drop the Windows pairing, so the device stays connected to Windows.
To undo, use **Settings → Bluetooth & devices → (device) → Remove device**. Removing the
pairing disables the keyboard buttons.
---
## Setup (one time)
Open a PowerShell terminal and `cd` to the repository root.
**1. Create a virtual environment**
```powershell
python -m venv .venv
```
**2. Activate it**
```powershell
.venv\Scripts\Activate.ps1
```
If you see a scripts-execution-policy error, run:
```powershell
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
```
Then repeat the `Activate.ps1` step.
**3. Install dependencies**
```powershell
pip install -r daemon\requirements-windows.txt
```
This installs `bleak` (WinRT BLE) and `httpx` (async HTTP for the Anthropic API).
---
## Running the daemon
With the venv active and the Clawdmeter powered on:
```powershell
python daemon\claude_usage_daemon_windows.py
```
### Expected console output
```
[HH:MM:SS] === Claude Usage Tracker Daemon (BLE, Windows) ===
[HH:MM:SS] Poll interval: 60s
[HH:MM:SS] Scanning for 'Claude Controller' (8.0s)...
[HH:MM:SS] Found: XX:XX:XX:XX:XX:XX
[HH:MM:SS] Connecting to XX:XX:XX:XX:XX:XX...
[HH:MM:SS] Connected
[HH:MM:SS] Sending: {"s":42,"sr":180,"w":17,"wr":8820,"st":"active","ok":true}
```
- **The device must be paired with Windows first** (see [Pair the device](#pair-the-device-one-time)).
The daemon then connects over that existing link via `BleakScanner` + `BleakClient`; it does
not pop its own pairing dialog.
- After `Connected`, the daemon polls the Anthropic API immediately and sends the first
payload within a few seconds of connect (warm token path). With a valid, non-expired token
the device should leave its waiting screen and show session + weekly percentages within
about 10 seconds of launch.
- The daemon then re-polls every 60 seconds while connected. If the device fires a refresh
request (e.g., after a button press), an immediate re-poll occurs without waiting for the
60-second interval.
- If the device disconnects or goes out of range, the daemon logs `Device disconnected` and
re-scans automatically with exponential backoff (starting at 1 second, capped at 60 seconds).
### Stopping
Press **Ctrl+C** in the terminal. The daemon logs `Daemon stopping` and exits cleanly.
---
## Troubleshooting
| Symptom | Likely cause | Fix |
|---------|-------------|-----|
| `Warning: running under Linux/WSL` | Running in WSL, not native Windows | Run from a native PowerShell or Command Prompt on Windows |
| `Scanning for 'Claude Controller'… Device not found` | Clawdmeter is off, out of range, or showing a non-Bluetooth screen | Power on the device and ensure it is on the Bluetooth waiting screen |
| `No token; skipping poll` | No credentials file found at any candidate path | Confirm `claude login` ran on this machine; check `%USERPROFILE%\.claude\.credentials.json` exists |
| `API HTTP 401` | Token expired | Re-run `claude login` in a terminal to refresh the token, then restart the daemon |
| `Connection failed` | WinRT BLE initialisation issue | Ensure Windows Bluetooth is on; try toggling Bluetooth off/on in Windows Settings |
---
## Tray icon, login autostart, and turnkey install
### One-command install (recommended)
> **Copy the repo to a native Windows path first.** Clone or copy this repository
> to a Windows location such as `%USERPROFILE%\Clawdmeter` — **not** a WSL share
> (`\\wsl$\...` or `\\wsl.localhost\...`). Installing from the WSL share would point
> the virtual environment and the login-autostart entry at a path that disappears when
> WSL shuts down, defeating the whole point of the Windows daemon. The installer
> detects a WSL path and refuses to run, telling you how to relocate.
>
> ```powershell
> Copy-Item -Recurse '\\wsl.localhost\Ubuntu\home\<you>\repos\Clawdmeter' "$env:USERPROFILE\Clawdmeter"
> cd "$env:USERPROFILE\Clawdmeter"
> ```
Run this once from the repository root in PowerShell (a native Windows path):
```powershell
powershell -ExecutionPolicy Bypass -File install-windows.ps1
```
The script does four things in order and logs progress at each step:
1. Creates a Python virtual environment at `.venv`.
2. Installs dependencies from `daemon\requirements-windows.txt` (bleak, httpx, pystray, Pillow).
3. Registers the tray app to launch automatically at login via `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` — per-user, no admin required.
4. Launches the tray app immediately (headless — no console window).
The script downloads nothing from the internet. It only installs the packages listed in
the in-repo `daemon\requirements-windows.txt`.
### Tray icon and status
After install, the Clawdmeter icon appears in the Windows notification area:
| State | Icon bubble | Tooltip |
|-------|-------------|---------|
| Connected | green | `Connected · last update HH:MM` |
| Scanning | amber | `Scanning…` |
| Error | red | `Error: token expired — run claude login` |
Hover over the icon to see the current status tooltip. A notification fires once when the
daemon first enters the Error state (e.g. after a token expiry).
### Tray menu
Right-click the tray icon for the menu:
- **Status header** (non-clickable) — live status + last data sync time.
- **Start at login** (checkable toggle) — enables or disables autostart at runtime.
Reflects the current registry state each time the menu opens.
- **Quit** — stops the daemon cleanly and exits with no lingering process. It releases the
daemon's own data connection but does **not** drop the Windows Bluetooth pairing — the
device stays connected to Windows and keeps showing your last-synced usage (point-in-time
view).
### Disabling or removing autostart
Use the tray menu toggle, or remove the registry value manually:
```powershell
reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v Clawdmeter /f
```
### WSL independence
The daemon operates fully independently of WSL. The token is read from native Windows
credential paths (`%USERPROFILE%\.claude\.credentials.json` and fallbacks); BLE uses
the WinRT stack directly. Running `wsl --shutdown` does not affect the BLE link, and
the daemon starts correctly even in a fresh Windows session where WSL has never been
launched.
---
## What is NOT covered here
- PyInstaller / one-file `.exe` packaging — v2
- MAC-address cache / sleep-wake reconnect hardening — Phase 3