A self-hosted Python service that pulls events from Google Calendar, computes optimal departure times factoring NYC multimodal transit, and pushes daily digests and per-event notifications to Telegram.
note: vibe coded to high heck and back. this is exclusively for me to have less adhd time blindness :)
oauth— interactive Google Calendar OAuth setupinit-db— initialize the SQLite schemamorning— run the morning digest jobpoll— run the per-minute poll looptomorrow— push tomorrow's earliest prep time to the configured HA script (pull-model alarm)plan— replan a single event (debug)digest-preview— print today's digest from cache without sendingstatus— show next event, pending pings, and job heartbeats (--jsonfor machine output)adjust EVENT_ID --add-prep N— shift a plan's prep time by N minutessnooze/mute/unmute/undo— adjust or suppress an event's pingsconfig show/config set KEY VALUE/config unset KEY/config reset— view or edit allowlisted config fieldsgeocode-cache— inspect or clear the geocode cachemta-alerts— print current MTA alertstest-notify— emit a test message via the configured notifierwhere— print the latest stored current location
See examples/config.toml and examples/env.example for the full configuration schema. Architecture and implementation notes live in AGENTS.md.
commutecompass ships an OpenClaw skill at skills/commutecompass/ so you can interact with it from chat — "what's on for today?", "shift my next event prep 45 min earlier", "set quiet hours to 23:00".
OpenClaw also owns the Telegram bot. Set [notify].mode = "stdout" in config.toml (the default in examples/config.toml) and commutecompass will emit each would-be Telegram message to stdout wrapped in delimiters. Pipe through contrib/openclaw-send.sh to forward each message to openclaw message send:
0 6 * * * COMMUTECOMPASS_CONFIG=/etc/commutecompass/config.toml \
commutecompass morning | \
OPENCLAW_TARGET=$CHAT_ID /opt/commutecompass/contrib/openclaw-send.sh
* * * * * COMMUTECOMPASS_CONFIG=/etc/commutecompass/config.toml \
commutecompass poll | \
OPENCLAW_TARGET=$CHAT_ID /opt/commutecompass/contrib/openclaw-send.shPoint your OpenClaw instance at skills/commutecompass/, and the chat commands are live. The scripts shell out to commutecompass-skill, a wrapper the NixOS module installs when services.commutecompass.skill.users is set; it sources the secrets env file and points at /etc/commutecompass/config.toml, so the calling session needs no preamble. Outside NixOS, ship an equivalent wrapper on PATH. The legacy direct-Telegram path is still available via [notify].mode = "telegram" if you'd rather not depend on OpenClaw.
CommuteCompass already pulls live location from Home Assistant when [home_assistant].enabled = true. With the optional [home_assistant.alarm] block it will additionally POST to an HA service every time a prep or leave ping fires — so HA can wake you up with a real alarm without changing your existing notification channel. The same HOME_ASSISTANT_TOKEN is reused; no new env var.
[home_assistant.alarm]
enabled = true
service = "script.commute_alarm" # any "domain.service"
kinds = ["prep", "leave"] # which ping kinds trigger the alarm
# Optional pass-through merged into the HA service payload
[home_assistant.alarm.extra_data.data.push.sound]
critical = 1
name = "alarm.caf"The recommended pattern is to point service at a small HA script you own and chain the loud parts there — iOS does not let third-party apps create real Clock-app alarms programmatically, so something like Pushcut (its "Notification Server" sustains a custom loud tone until dismissed) plus an HA Companion critical notification fallback is the canonical "wake-from-a-nap" setup. A minimal HA script:
script:
commute_alarm:
sequence:
- service: notify.pushcut_my_iphone
data:
title: "{{ title }}"
message: "{{ message }}"
data:
sound: "alarm-loop" # a Pushcut alarm sound
- service: notify.mobile_app_my_iphone # belt-and-suspenders critical push
data:
title: "{{ title }}"
message: "{{ message }}"
data:
push:
sound:
critical: 1
name: "alarm.caf"
volume: 1.0CommuteCompass POSTs {"title": "CommuteCompass", "message": "<ping body>", ...extra_data} to the configured service. If the call fails, the primary notifier's send is not rolled back — the ping is still marked fired and won't repeat next minute.
iOS won't let any third party create real Clock-app alarms — only Shortcuts running on-device can. The commutecompass tomorrow subcommand bridges that gap without keeping the phone in the loop in real time:
- CommuteCompass plans tomorrow's events evening-of, picks the earliest
prep_at, and POSTs it to an HA script. - The HA script stores the datetime in an
input_datetimehelper. - A daily 21:00 Shortcuts automation on your iPhone (set to "Run Immediately") reads the helper via the HA REST API and creates a Clock-app alarm at that time. No tapping required.
Enable it with:
[home_assistant.tomorrow]
enabled = true
script = "script.commute_set_tomorrow_alarm"Drop examples/ha/commute_tomorrow_alarm.yaml into your HA config (it includes both the input_datetime helper and the script, plus the Shortcuts recipe in comments at the bottom). Then schedule commutecompass tomorrow to run on an evening systemd timer (e.g. 20:45 NYC); it skips silently when there's nothing on the calendar.
nix flake check
nix build .#packages.x86_64-linux.default