Skip to content

Publish /timebox schedules to Google Calendar#11

Merged
cristoforows merged 5 commits into
mainfrom
wesley/create-gcal-integration
Jun 27, 2026
Merged

Publish /timebox schedules to Google Calendar#11
cristoforows merged 5 commits into
mainfrom
wesley/create-gcal-integration

Conversation

@cristoforows

Copy link
Copy Markdown
Owner

What

Adds Google Calendar publishing to the /timebox feature, tailored to a personal-assistant use case.

Flow

/timebox → pick Office / WFH (inline keyboard) → pin target date + check the Target Calendar → if a schedule already exists for that day, confirm redo (overwrite vs keep) before collecting → collect tasks → /done generates the schedule and publishes it.

Scheduling

  • LLM owns placement. Fixed daily blocks — day window, lunch, dinner, and (Office-only) commute — are passed as overridable defaults; a task that states a conflicting time wins.
  • All anchors are configurable (TIMEBOX_DAY_START/END, TIMEBOX_LUNCH, TIMEBOX_DINNER, TIMEBOX_EAT_DURATION, TIMEBOX_COMMUTE_MORNING/EVENING, TIMEBOX_COMMUTE_DURATION).

Calendar writes

  • Target is a dedicated calendar by ID (TIMEBOX_CALENDAR_ID); never the primary.
  • Events are tagged extendedProperties.private.source=timebox. Publishing is clear-then-write, so re-runs converge on one schedule and never touch manual events (see docs/adr/0001).
  • Each slot is written independently; /done replies with a per-event ✅/❌ and keeps the buffer open to retry failures.

Config / setup

  • Adds the calendar.events OAuth scope → existing users must re-run /authenticate (the bot detects a missing scope and prompts).
  • Requires the Google Calendar API enabled in the same GCP project; same account as Drive, no second login.
  • Unset TIMEBOX_CALENDAR_ID → schedule is replied as text only (no calendar write).

Implementation

  • get_google_service() extracted into google_auth.py; drive_handler and the new calendar_handler both reuse it.
  • calendar_handler.py mirrors drive_handler (tag/list/clear/per-item write).
  • scheduler.generate_schedule now takes a DayConfig + mode; stays pure/offline-testable.

Tests

  • All Google I/O mocked — no live API. 22 passing (scheduler + new calendar_handler).

…nfig

- add calendar.events to OAuth SCOPES; tokens minted before need re-auth
- extract get_google_service() in google_auth; drive_handler reuses it
- add has_calendar_scope() to detect tokens missing calendar grant
- add TIMEBOX_CALENDAR_ID + fixed-block anchors (day start/end, lunch,
  dinner, eat/commute durations) to config and .env.example
- seed CONTEXT.md glossary and ADR-0001 (calendar idempotency)
generate_schedule now takes a DayConfig (day window, lunch, dinner, eat +
commute durations) and a mode. The LLM owns placement and emits the fixed
blocks as slots, overriding any when a user task states a conflicting time.
Commute blocks are injected only in office mode.
mirrors drive_handler over a calendar v3 service. Bot events carry a
private extendedProperties tag (source=timebox) so list/clear only ever
touch our own events. write_schedule attempts every item and reports
per-item ok/fail; slots crossing midnight roll the end into the next day.
Mocked tests, no live API.
/timebox now: pick Office/WFH (inline keyboard) -> pin target date and
check the Target Calendar -> if a schedule exists, confirm redo (keep vs
overwrite) before collecting -> /done generates with mode + fixed blocks,
clears prior bot events, writes each slot, and replies with per-event
✅/❌. Any failure keeps the buffer open so /done retries (clear-then-write
stays idempotent). Falls back to a text-only reply when no calendar is
configured, and asks for re-auth when the calendar scope is missing.
- webhook_server: add callback_query to allowed_updates so inline-button
  presses (/timebox Office/WFH, redo) reach the bot
- Dockerfile: copy the whole src/ dir so new modules (scheduler, timebox,
  calendar_handler, …) ship in the image instead of crashing on import
- docs: bring README/DEPLOY/DEVELOPMENT/.env.example in line with the
  current code — two run modes (polling + webhook), /timebox, calendar
  scope, OPENROUTER_API_KEY (required), TIMEBOX_* vars, correct
  DRIVE_FOLDER_NAME default, new module tree, deploy troubleshooting for
  the connection-refused and OAuth scope-change failures
- fix run_local.sh / start_webhook.sh to use src/ paths
- claude.md: drop the now-fixed helper-script gap

Also includes the in-flight branch work: /status --local marker, fly.toml
image bump to v1.0.6.
@cristoforows
cristoforows merged commit a8c623c into main Jun 27, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant