Windows support#52
Merged
Merged
Conversation
Windows Credential Manager caps a single credential at roughly 2,560
bytes stored as UTF-16, about 1,280 characters. The vault JSON holds
Garmin's two OAuth tokens plus Strava's, which can exceed that and make
keyring.set_password fail outright on Windows.
When the serialized vault fits in CHUNK_LIMIT characters it keeps today's
single-entry shape byte for byte, so existing installs never migrate.
When it does not, the payload is split across numbered "vault:i" entries
and the "vault" entry holds a {"__chunks__": N} header. Chunks are
written before the header so a racing reader sees either the old vault or
a complete new one, and a save probes and deletes stale higher-numbered
chunks left by an earlier, larger vault. A read with a missing chunk is
treated as malformed, same as malformed JSON: warn and return empty.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Notifications and the scheduled-sync agent (install/uninstall/status/offer) were macOS-specific code scattered across cli/shared.py, cli/maintenance.py, and cli/doctor.py. Collect them behind a new eufy_sync/platform_support/ package with one small interface, chosen once from platform.system(): Darwin uses the macOS Launch Agent and osascript notifications, everything else uses a no-op generic layer. This is the structural step that lets a windows.py drop in next without touching call sites. macOS behavior is a move-only relocation: every string, plist byte, and launchctl/osascript/terminal-notifier invocation is byte-identical. Call sites in app.py, updater.py, doctor.py, and maintenance.py now route through the platform layer; the maintenance agent functions became one-line delegates so the CLI flag routing is unchanged. Tests that patched the moved symbols were retargeted to their new import paths with no assertion changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comments naming future work go stale the moment it lands; say what the selection does today instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Register a Task Scheduler entry that runs the sync every four hours through a small VBScript wrapper. schtasks would flash a console window on each run; launching the sync from WScript.Shell.Run with a window style of 0 keeps it invisible, and routing through the wrapper points the task at a filename whose bytes stay stable across pipx/uv upgrades that swap the underlying binary. The wrapper is written and compared as raw bytes so its CRLF line endings survive the round-trip on every platform, which keeps the stable-bytes rule honest instead of rewriting the file on every run. The uninstall summary line loses its macOS-specific "Launch Agent" wording now that the agent is one of several per-platform schedulers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Windows notify entry point was a no-op, so a failed sync on Windows gave the user nothing visible to react to. Send a native toast through the PowerShell WinRT notification APIs instead. The title and message are XML-escaped and the script is handed to powershell as a base64 -EncodedCommand in UTF-16-LE, so no shell quoting layer can misread the text and XML specials cannot break the toast markup. Failures are swallowed behind a timeout: a notification is a courtesy and must never turn a completed sync into a crash. The command argument is accepted for interface parity but ignored, since v1 toasts are not clickable and call sites already carry the fix command in the message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The toast XML sits inside a PowerShell here-string, which terminates on any line starting with '@. Raw exception text reaches notify (the failure call sites pass str(e)[:200]), and exception text can carry newlines and content from network responses, so a message containing a newline followed by '@ would close the here-string early and hand the remainder to PowerShell as code. Collapse all whitespace runs in the title and message to single spaces before the XML escape, so the here-string can only terminate at the template's own '@. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A running eufy-sync.exe holds an open handle on its own file, so pip, pipx, or uv cannot overwrite it while the process is alive. The inline subprocess.run path works on macOS and Linux but fails on Windows. Branch on platform.system() == "Windows" and launch the same update command in a new console that first waits for this process to exit, then runs the upgrade, then pauses so any error stays on screen. The Windows creationflags are read through getattr so the suite still imports on macOS. The macOS and Linux path is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Windows support already landed in code on this branch. This is the surface: add windows-latest to the CI matrix and the Windows classifier so the platform is tested and advertised, rewrite the argparse help strings to drop macOS-specific "Launch Agent" wording in favor of what the flags do, and document the Windows install path, Scheduled Task automatic sync, and Credential Manager storage in the README. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Final-review fixes before merging Windows support. - Pin the macOS platform implementation in the seven launch-agent CLI tests via a shared fixture, so they exercise the macOS module on a Windows CI runner instead of the real schtasks path. - Guard the seven POSIX file-mode assertions behind os.name != "nt"; Windows reports 666/777 regardless of the mode passed. Behavioral assertions still run everywhere. - Normalize backslashes before the uv-tool marker match so uv installs on Windows are detected instead of falling through to a pip path that cannot work in a uv venv. - Quote each spaced argv element in the detached Windows update command (and the copy-paste failure message) so a Python path with spaces does not split. - Quote the wrapper path in the printed manual schtasks fallback, matching the programmatic /TR value, so a cmd.exe paste survives a spaced path. - Correct the Windows module docstring to describe notify as a native PowerShell WinRT toast rather than a no-op stub. - Translate a keyring failure during chunk reassembly into the same unreadable-keychain RuntimeError as the initial read, so a partial read is never saved back over the real vault. A missing chunk keeps its malformed-vault handling. - Add a boundary test proving a vault serialized to exactly CHUNK_LIMIT stays a single entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five tests encoded assumptions about the machine running them, so they failed on windows-latest even though the product code is correct: - test_generate_plist_contains_log_path asserted a hardcoded forward-slash log path, but the plist embeds str(shared.LOG_FILE), which renders with backslashes on Windows. Assert against the actual patched LOG_FILE path instead; the check (the plist embeds the real log path) is unchanged. - Four _self_update tests (pinned pipx, uv tool, pip fallback, failed install) cover the inline update path but never pinned the platform, so a Windows host routed them into the detached-console branch and their subprocess.run mocks were never called. Pin platform.system to Darwin, matching how the Windows-branch tests pin Windows. Test-only change; no product code touched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…53) Findings from the Windows test pass: --update-password printed raw 429 warnings from garminconnect's per-strategy fallback (paths that never succeed), a wrong password led to an MFA prompt for an email that never arrives, and Ctrl+C at that prompt dumped a traceback. - Configure logging once at CLI entry so every path that reaches a Garmin login (setup, --update-password, --reauth, sync) quiets the garminconnect logger, not just --reauth and sync. - The MFA prompt now says what to do when no email arrives, and Enter or Ctrl+C cancels cleanly with a message naming --update-password. - Definitively rejected credentials skip the browser fallback, which would autofill the same bad password and fail minutes later. - Ctrl+C anywhere in the CLI prints "Cancelled." and exits 130 instead of a traceback. Co-authored-by: Elias Sturim <267715325+sturimcode@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Changes from the version the pass ran with: uv installs via winget (the install script's ExecutionPolicy ByPass gets refused in auto mode), Step 2 records per-target counts and flags nonzero Strava uploads (Strava-side dedup is unverified), and Step 5 injects the wrong password via --update-password because --reauth silently reuses a stored password. Step 5 also lists the pre-PR-53 failure modes as regressions to capture and the two known cosmetic issues. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t of the tree, 1.8.0 The headless failure message now says --reauth garmin, matching the target the toast and log both point at. Test-run checklists and reports are working files, not project docs, so they leave docs/. Version to 1.8.0 in both places. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Full Windows support: install, sync, automatic sync every 4 hours, failure notifications, doctor checks, and self-update.
Release as 1.8.0 after a manual pass on real Windows hardware.
🤖 Generated with Claude Code