thEgallery
← back
c

Calopod

in progress· Sep 2026
View on GitHub
Calendar syncPrivacy-firstmacOSSwiftEventKit
See more on the project site

The architecture starts with one sync engine

Manual sync and automatic sync call the same coordinator. The background agent does not shell out to the CLI, and the CLI does not maintain a second planning path. Both open the same state, acquire the same process lock, refresh the same bounded window, and execute the same deterministic plan.

calopod CLI ───────┐
                   ├─ Sync engine ─ Provider adapters
background agent ──┘       │
                           ├─ Planner and ownership checks
                           ├─ SQLite operational ledger
                           └─ Keychain secrets and signing keys

Both entry points share one sync path, so there is only one definition of what a sync does.

A small shared core

The Swift package separates domain types, provider contracts, persistence, planning, runtime coordination, and terminal presentation. EventKit, Google Calendar, and Microsoft Graph adapters translate provider behavior into the same normalized calendar and managed-event contract.

That boundary keeps reconciliation reusable by the later Mac and iPhone apps. Provider and storage code do not depend on terminal output, and the command layer is responsible only for validation and presentation.

Normalize first, plan second

Each provider refreshes a bounded time range into normalized SQLite state. Stable provider identity, recurring-instance identity, cancellation state, availability, timestamps, and supported presentation fields all land in one common shape. The planner then compares those source instances with verified managed targets and emits creates, updates, removals, repairs, or no-ops.

Dry-run and apply use the same plan. Dry-run stops before provider mutations; apply writes through the selected adapter, rereads the target, and records the result in a durable operation journal. An interrupted batch can resume without treating every missing local acknowledgement as a new event.

Ownership is the delete permission

A target event is managed only when its signed Calopod marker and local mapping agree. A valid marker can help recover a lost mapping, but an ambiguous or foreign event stops the mutation rather than being guessed into ownership. Updates keep provider identity, and removals require positive ownership proof.

Managed mirrors are removed from source ingestion so a Main or Busy target cannot feed its own copies back into the planner. Known direct-provider and EventKit overlap is blocked at calendar selection, and source-to-target cycles are rejected before a run begins.

Safety

Calopod only removes an event when it can prove it created it.

Rules redact before transport

Busy is enforced while constructing the target payload. Only timing, all-day behavior, availability, the generic title, and opaque ownership provenance survive. Sensitive fields are not written and are not included in diagnostics.

Full details can carry supported title, timing, location, and plain-text notes. EventKit attendee and organizer fields are read-only, so participant context stays in notes rather than creating invitees or triggering invitation changes. Alerts are intentionally omitted to avoid duplicate notifications.

Safety

Busy copies are built without the source details, so there is nothing to leak later.

EventKit gets a stable permission host

macOS calendar permission belongs to an application identity, not an arbitrary terminal process. Calopod ships a small signed host app that performs every EventKit read and managed write. Both the CLI and LaunchAgent resolve that host through validated absolute paths, which matters because a LaunchAgent has a sparse environment and no dependable working directory.

The technical preview is signed with Developer ID, notarized, and built to keep the EventKit designated requirement stable across compatible replacements. That avoids turning every upgrade into a new Calendar permission identity.

Persistence has two jobs

GRDB-backed SQLite stores configuration, normalized events, provider cursors, sync runs, operation state, and managed-event mappings. Credentials and ownership signing keys stay outside that database in Keychain. A local backup is therefore a recovery checkpoint for the same installation, not a portable bundle of secrets. The database itself is owner-only but not encrypted, and it holds cached event details, including private events, so FileVault is what protects it at rest. Backups are encrypted with a key in Keychain, and an encrypted database is planned.

The release installer replaces service files separately from state. Removal unloads the per-user service and deletes its managed payload while preserving SQLite and Keychain material, so an intentional reinstall can resume the same configuration and ownership.

The inbox reads the same cache

The inbox is built from the same normalized events. Each refresh records what it saw for every occurrence, so Calopod can tell a change that came after your reply from one that came before, and it says so when the order is uncertain. Acknowledging a change only marks that revision as seen on the Mac.

Replies are the one write to a source event. A Yes, No, or Maybe goes out for a single occurrence on Google or Microsoft, and only when you choose it. Calopod records the intent before sending and the result afterward. If the outcome is unknown it checks before trying again, so a reply is never sent twice by mistake.

The runner stays bounded

The background agent runs in the signed-in user's launchd session with no root requirement. Each pass uses explicit lookback and horizon bounds, then waits for the configured interval plus bounded jitter. Permission, provider, or state failures still go through that wait, so a broken account cannot create a busy loop.

A shared file lock rejects overlapping writers. Wake starts one fresh bounded run rather than replaying every missed interval. Agent output is intentionally small: aggregate operation counts, warning count, and a generic failure category, with no event content or provider identifiers.

The current boundary

The first packaged release is a terminal-first preview for Apple Silicon Macs running macOS 14 or newer. It uses calendar accounts already configured in macOS. The native Mac interface, iPhone flow, public provider registrations, and final distribution are later milestones, not hidden promises inside this build. The source is public on GitHub under the MIT license.