Daeseon Yoo
Back to project
·Update·2 min

Google connectors: one shared OAuth loopback, then Calendar + a selective Sheets reader

Added a shared Google OAuth2 (Desktop/loopback) module and two BYO connectors that merge into the same operating-memory graph: Calendar (read upcoming events + a gated quick-add) and a research-grounded SELECTIVE Sheets reader (one founder-chosen sheet via spreadsheets.readonly — deliberately NOT the CASA-reviewed drive.readonly). Compiles, 243 lib tests pass, renderer typechecks; live OAuth still pending the founder's first authorize.

Verified this turn: cargo test --lib → 243 passed, 0 failed (7 new across calendar/sheets/google_oauth); tsc -p apps/desktop/tsconfig.json --noEmit → 0 errors. NOT yet verified: a real browser OAuth grant + live API calls (needs the founder to click Authorize once).

Why one shared OAuth module

Calendar, Sheets, and (later) Drive all want the same Desktop-app loopback dance: open the consent screen → catch the 127.0.0.1 redirect → exchange the code → keep the refresh token in the OS keychain. Rather than copy that ~150 lines per connector, it lives once in google_oauth.rs as authorize(scope, refresh_key) + access_token(refresh_key). Each connector is then just its scope + REST:

The client id/secret come from ~/.secrets/api-keys.env (the established secret() pattern); refresh tokens never touch disk.

Why Sheets is deliberately selective

Research into solo-founder operations was blunt: the spreadsheet IS the founder's operating DB (runway/burn, customer list, metrics), so reading it is high value — but reading the whole Drive is both noise and a trap. drive.readonly is a restricted scope → CASA Tier 2/3 security review, weeks of latency, annual re-cert. So the connector reads exactly the one sheet the founder pastes a link to, via spreadsheets.readonly (a sheet-by-id scope that needs no security review). Selective by sheet; broad-by-range only for v1.

How they merge into memory

No new code path. Each connector exposes rows to connector.rs's existing data-driven dispatch (fetch_calendar_items / fetch_sheets_items), which maps them to the source-agnostic SourceItem → the same item_node → the same GraphStore. A calendar event lands as event/schedule; a sheet row as record/data. So they sort and search alongside Gmail and GitHub in one graph — the point being the unified memory, not the connector count.

What's next

These are READ (plus a gated calendar write). The differentiator — letting the founder's own Claude act in natural language (send a reply, add an event, set a "mail-from-X → this area" rule) — means exposing those gated actions as MCP tools, which is the next build.