Reference google calendar client
Built 2026-04-30 to fix the colorId-disappearing problem. n8n's calendar workflows were doing four things wrong (see superseded note), so we cut them out entirely and went direct to the Google Calendar v3 REST API.
Where things live¶
- Client:
forge/scripts/forge_google_calendar.py - OAuth secrets:
~/.forge-secrets/google-calendar.env(chmod 600) GOOGLE_CALENDAR_CLIENT_ID,GOOGLE_CALENDAR_CLIENT_SECRET,GOOGLE_CALENDAR_REFRESH_TOKEN,GOOGLE_CALENDAR_SCOPE- Origin: extracted from n8n credential
t5YQ9F5vvgEFd6qL(Google Calendar account - [email protected]) viadocker exec n8n n8n export:credentials --decrypted - Scopes granted:
auth/calendar+auth/calendar.events(full read/write all calendars)
API surface¶
from forge_google_calendar import GCal
gc = GCal(calendar_id="primary") # or any calendar id
gc.create_event(summary, start, end, description=None, location=None,
color_id=None, time_zone="America/Chicago", extra=None)
gc.list_events(time_min, time_max, max_results=250, q=None, single_events=True)
gc.get_event(event_id)
gc.update_event(event_id, summary=None, start=None, end=None, ...) # PATCH
gc.delete_event(event_id)
start/end accept ISO strings (with T for timed, without for all-day) or pre-built {"dateTime": ..., "timeZone": ...} dicts. Access tokens auto-refresh; one in-memory cache per process.
Smoke test¶
forge_google_calendar.py self-test
# Creates a 30-min event with colorId=11, gets it, updates colorId to 9, deletes it.
Wired into¶
forge_telegram_inbox_brain.py, all 4 calendar tools (create/update/list/delete) plus tool_create_task auto-sync, tool_log_sleep, tool_log_time_block, tool_briefing, tool_weekly_review. Lazy-init via_gcal().forge_time_daily_aggregator.py, classify() now reads colorId first (via_COLOR_TO_CATEGORY), falls back to summary-text patterns for events created in Google Calendar UI.
Category to colorId map¶
Single source of truth is _CATEGORY_COLOR in forge_telegram_inbox_brain.py. Keep _COLOR_TO_CATEGORY in forge_time_daily_aggregator.py synced as the inverse.
| Category | colorId | Color name |
|---|---|---|
| Work / Forge | 9 | blueberry |
| Personal / Family | 10 | basil |
| Sleep | 1 | lavender |
| Errands | 5 | banana |
| Workout / Health | 11 | tomato |
| Entertainment | 6 | tangerine |
| Travel | 7 | peacock |
Why this is more sustainable than n8n¶
- No silent field-stripping (n8n was dropping colorId on create AND list)
- update_event actually works (n8n was returning 500 for every payload shape)
- delete uses standard REST
DELETE /events/{id}, no eventId-vs-event_id param quirks - All Google Calendar fields available (attendees, recurrence, reminders, conferenceData)
- One less hop (n8n → Google) so faster + more debuggable
- Auto access-token refresh; refresh_token is long-lived
OAuth bootstrap notes (for future migrations)¶
If the refresh_token ever revokes, three options to re-acquire: - (a) Re-run the OAuth flow in n8n UI for the existing credential and re-extract via export:credentials - (b) Build a one-off forge_google_oauth_bootstrap.py with the same client_id/secret - (c) Create a new OAuth client in Google Cloud Console (project tied to [email protected])
Cleanup TODO¶
The three n8n workflows (create-calendar-event, list-calendar-events, update-calendar-event, delete-calendar-event) are now unused by forge code. They can be archived/deleted in the n8n UI when convenient. The OAuth credential t5YQ9F5vvgEFd6qL should stay because retiring it might revoke the refresh_token Justin's forge backend now uses.
[Claude Code, 2026-04-30]