Skip to content

feat(connectors): Apple Calendar connector for macOS#619

Open
Raldan951 wants to merge 1 commit into
open-jarvis:mainfrom
Raldan951:feat/apple-calendar-connector
Open

feat(connectors): Apple Calendar connector for macOS#619
Raldan951 wants to merge 1 commit into
open-jarvis:mainfrom
Raldan951:feat/apple-calendar-connector

Conversation

@Raldan951

@Raldan951 Raldan951 commented Jun 30, 2026

Copy link
Copy Markdown

Summary

  • Adds apple_calendar.py connector that reads macOS Calendar events directly from the local SQLite database — no API, no OAuth, no credentials
  • Registered as "apple_calendar" in ConnectorRegistry, auto-imported in connectors/__init__.py alongside apple_contacts
  • Follows the apple_contacts.py pattern exactly

Details

DB path: ~/Library/Group Containers/group.com.apple.calendar/Calendar.sqlitedb

Requires: Full Disk Access in System Settings → Privacy & Security → Full Disk Access (same requirement as apple_contacts, apple_notes, etc.)

What it yields: One Document per upcoming event with:

  • title = event summary
  • content = formatted card (date, calendar name, location, notes, attendees flag)
  • metadata = {summary, calendar, start, all_day, location, has_attendees}
  • doc_type = "calendar_event"

Configurable: days_ahead (default: 7) and days_behind (default: 1)

Timestamp handling: Apple epoch (2001-01-01 UTC), same convention as apple_contacts

mcp_tools(): exposes calendar_upcoming and calendar_search for agent use

Test plan

  • Verify AppleCalendarConnector().is_connected() returns True on macOS with Calendar data
  • list(connector.sync()) yields real events from system calendars (iCloud, subscribed, local)
  • Confirm hidden = 0 filter excludes deleted/declined events
  • Run jarvis digest --fresh with sources = ["apple_calendar"] in config — calendar section renders
  • Confirm no crash on machines without Calendar data (returns empty, not exception)

Tested on macOS 15 (Sequoia), M3 Ultra, iCloud-synced calendars (personal, subscribed F1/holiday feeds).

🤖 Generated with Claude Code

Reads events directly from the macOS Calendar SQLite database at
~/Library/Group Containers/group.com.apple.calendar/Calendar.sqlitedb
in read-only mode — no API, no OAuth, no app-specific password.

Features:
- Yields upcoming events as Documents with title, date, calendar name,
  location, and notes fields
- Configurable lookahead/lookbehind window (default: 7 days ahead, 1 behind)
- Apple epoch timestamp conversion (2001-01-01 UTC baseline)
- Joins CalendarItem + Calendar + Location tables
- Filters hidden/deleted events (hidden = 0)
- mcp_tools(): calendar_upcoming + calendar_search for agent use
- Registered as "apple_calendar" in ConnectorRegistry

Requires Full Disk Access granted to the terminal/app in
System Settings → Privacy & Security → Full Disk Access.

Tested on macOS 15 (Sequoia) with iCloud-synced calendars.
Follows the apple_contacts.py connector pattern exactly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

timestamp = _apple_ts(last_mod) if last_mod else _apple_ts(start_date)
if since is not None:
since_utc = since if since.tzinfo else since.replace(tzinfo=timezone.utc)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line trips ruff E501 in this repo (line too long). Please wrap it before merge.

@ElliotSlusky ElliotSlusky left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together. I like the direction here, especially keeping the Apple Calendar path local/no-auth and matching the existing Apple connector shape.

I found a few things that need fixing before this is mergeable:

  1. CI/lint currently fails on src/openjarvis/connectors/apple_calendar.py:230 with E501 line too long.
  2. Event display times are currently formatted from UTC datetimes and then labeled with the event timezone. That can show the wrong local time, e.g. a 9 AM Pacific event rendering as 4/5 PM with (America/Los_Angeles) appended. The formatter should convert through zoneinfo.ZoneInfo(start_tz) before rendering, with care for _float and all-day events.
  3. Recurring events appear likely to be missed because the query filters directly on CalendarItem.start_date BETWEEN ? AND ?. For recurring meetings created outside the lookahead window, Apple Calendar usually stores recurrence separately, so those upcoming occurrences may not show up.
  4. The mcp_tools() specs advertise calendar_upcoming and calendar_search, but this PR does not add executable tool implementations or register them. They may show up as connector metadata, but agents won’t be able to call them through the normal tool path yet.

Could you also add focused tests with a synthetic SQLite DB for basic sync, missing DB behavior, timezone formatting, and ideally recurrence behavior?

start_tz, _loc_id, has_attendees, _last_mod, _uuid, cal_title, _color,
) = row

start_dt = _apple_ts(start_ts)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This converts Apple timestamps to UTC and the formatter later renders that UTC value directly. For timed events with start_tz, this can display the wrong local wall time. Please convert to ZoneInfo(start_tz) before formatting, while preserving _float and all-day behavior.

c.color AS calendar_color
FROM CalendarItem ci
JOIN Calendar c ON ci.calendar_id = c.ROWID
WHERE ci.start_date BETWEEN ? AND ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filtering only on CalendarItem.start_date BETWEEN ? AND ? likely misses recurring events whose original start date is outside the window. Can you verify this against recurring meetings and either expand occurrences from Apple Calendar’s recurrence/occurrence tables or document/test the limitation?

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.

2 participants