Completes the cutover started on the PC side: phone_app was still pinned to crdt_sync_dart-v0.2.0, so the Flutter half was writing only to GitHub even though _sync.py had already moved. pushWith() now takes a RemoteStore instead of a GitHubClient, so one call serves Firebase, GitHub, or the MirrorStore that writes both; syncBackend() picks which, and returns the bare GitHub client when this device has not been set up for Firebase -- the normal cutover state, and also the rollback. The remote path is byte-identical on both backends, so only the transport differs and the PC's parse is untouched. Settings gained the Firebase section that actually calls saveAccount(). Its absence was the bug found in diet-guard: without it openFirebase() always returned null, so the app was a Firebase no-op on real hardware no matter what the tests said. Signing in happens immediately on Connect, so a wrong password surfaces on-screen rather than as a silent failure at push time. The three UI problems kuhy hit in diet-guard are avoided here rather than repeated: GitHub is demoted into "Advanced (GitHub mirror)" instead of sitting beside Firebase as a peer choice, the connected account renders as read-only text with a Disconnect button rather than an empty-looking form, and the status card reports the Firebase state it is actually reading. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017FH32BgGQbQLDstBhR26Xa |
||
|---|---|---|
| .github/workflows | ||
| docs/explanations | ||
| phone_app | ||
| scripts | ||
| wake_alarm | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| CLAUDE.md | ||
| DESIGN_AUDIT_TODO.md | ||
| install.sh | ||
| MIGRATION.md | ||
| pyproject.toml | ||
| README.md | ||
| requirements.txt | ||
| shutdown-wrapper.sh | ||
| sleep-hook.sh | ||
| wake-alarm-fans.sh | ||
| wake-alarm-trigger.service | ||
| wake-alarm-trigger.timer | ||
| wake-alarm.service | ||
wake_alarm
A phone-driven wake alarm for an always-on PC. The alarm time is whatever the
companion phone app last synced (via the shared crdt_sync transport over a
private GitHub repo); a per-minute systemd user timer fires at exactly that
time, showing a fullscreen, challenge-dismissed alarm with fan ramp and
optional TP-Link Tapo P110 smart-plug control.
Change the alarm on the phone at any hour and the PC follows within ~5
minutes. If the PC does happen to be powered down, a shutdown wrapper
hibernates with rtcwake set to the same synced time, as a fallback.
Install
bash install.sh
This installs the package + dependencies into system Python's user
site-packages (the systemd units run /usr/bin/python directly, not a venv —
see CLAUDE.md), installs and enables the per-minute trigger timer, the
systemd-sleep resume hook, the shutdown wrapper that hibernates with
rtcwake as a fallback, the fan-ramp script, and (optionally) python-kasa
for smart-plug control.
Reading the phone's alarm needs a GitHub token with repo scope, at
~/.config/wake_alarm/sync.json as {"token": "..."}. If that file is
absent, a sibling app's ~/.config/{diet_guard,screen_locker}/sync_token is
used instead, so one token covers every app syncing through kuhyx/syncs.
Usage
python -m wake_alarm._alarm --demo # test the alarm window (safe, closeable)
python -m wake_alarm._sync --show # what alarm time the PC resolves
python -m wake_alarm._trigger --dry-run # what the next tick would do
journalctl --user -fu wake-alarm-trigger.service # watch it live
The alarm fires automatically once installed; no manual invocation is needed in normal operation.
Development
python -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/pre-commit install && .venv/bin/pre-commit install --hook-type pre-push
.venv/bin/python -m pytest wake_alarm/tests/ --cov=wake_alarm --cov-branch --cov-fail-under=100
See CLAUDE.md for scheduling details, the hibernate/rtcwake mechanism,
the cross-repo workout_log.json read, and production deployment gotchas.