main
Mirror "Maybe" (tentative RSVP) events instead of dropping them. Previously, with "Mirror accepted events only" on, events you'd replied tentative to were skipped entirely. They're now mirrored with a "Maybe: " marker in the title (after the normal prefix) and written with tentative availability so Calendar shows them hatched. The marker is applied for any tentative RSVP regardless of the accepted-only setting; an accepted<-> tentative flip re-syncs the title. Not carried on the merge path (mergeGapMin > 0), which already discards per-event titles. Block gains a `tentative` flag (in ==/hash); accepted-only gate lets .tentative through alongside .accepted. All unit tests pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
BusyMirror
BusyMirror mirrors meetings between your calendars so your availability stays consistent across accounts/devices.
On macOS, BusyMirror runs as a standard app (Dock icon, ⌘Q to quit) and also has a menu bar icon for quick sync/status without opening the main window.
What it does (current)
- Route-driven mirroring (multi-source): define Source → Targets routes and run them in one go.
- Manual selection mirroring: pick a source and targets in the UI and run.
- Two privacy modes:
- Private (hide details): mirrors placeholders with prefix + placeholder title (e.g., "🪞 Busy").
- Mark Private: mirrors prefix + real title, but marks events Private on supported servers (best-effort).
- DRY-RUN mode: see what would be created/updated/deleted without writing.
- Activity Log in the app plus persistent file logging on disk.
- In-app scheduling: install or remove a
launchdLaunchAgent from theScheduled runssection. - Menu bar controls: trigger
Sync Now, open the main window, open Preferences, or quit. - Overlap modes:
allow,skipCovered,fillGaps. - Merge adjacent events with a configurable gap.
- Time window controls (days back/forward) and Work Hours filter.
- Accepted-only filter (mirror your accepted meetings only).
- Cleanup of placeholders, including auto-delete of mirrors whose source disappeared.
- Refresh Calendars prunes stale saved calendars and routes when calendars are removed from the system.
- Prefix-based tagging and loop guards to prevent re-mirroring mirrors.
- Settings: autosave/restore, Import/Export JSON, saved routes for scheduled/headless runs.
Why
Use one calendar’s confirmed meetings to block time in other calendars (e.g., corporate iPad vs. personal devices).
Build (macOS)
Option A — Xcode
- Open
BusyMirror.xcodeprojin Xcode. - Select the BusyMirror scheme → My Mac.
- Product → Build.
- Product → Archive → Distribute App → Copy App (no notarization) to export a
.app(or ZIP it for sharing).
Option B — Makefile (reproducible)
- Build Release:
make build-release - Package ZIP:
make package(createsBusyMirror-<version>-macOS.zip+.sha256) - Built app:
build/DerivedData/Build/Products/Release/BusyMirror.app
See CHANGELOG.md for notable changes.
CLI (optional)
- Run from Terminal with
--routesto mirror without the UI. Example:BusyMirror.app/Contents/MacOS/BusyMirror --routes "1->2,3; 4->5" --write 1 --days-forward 7 --mode allow --exit
- Run the routes already saved in the app settings:
BusyMirror.app/Contents/MacOS/BusyMirror --run-saved-routes --write 1 --exit
--helpprints full flag documentation.--list-calendars [--json]prints available calendars (index, id, title, source/type) and exits.--status [--json]prints last-run time/result, schedule state, and saved-route count and exits.- Flags exist for privacy, all-day, merge gap, days window, overlap mode, cleanup, and filters.
- Filters:
--exclude-titles "token1, token2"--exclude-organizers "alice@example.com, Example Org"- Tokens are comma or newline separated; matching is case-insensitive.
- Exit codes:
0success,2no calendar access,3no saved routes (with--run-saved-routes).
Logs
- BusyMirror now writes a persistent log file to
~/Library/Logs/BusyMirror/BusyMirror.log. - When the file grows large, the previous file is rotated to
~/Library/Logs/BusyMirror/BusyMirror.previous.log. launchdstdout/stderr for scheduled runs are also written in the same folder.- In the UI, use
Reveal Log Fileto open the current log directly in Finder.
Scheduling
- BusyMirror can create its own schedule from the app UI in
Scheduled runs. - Choose
Hourly,Daily, orWeekdays, then clickInstall Schedule. - The installed LaunchAgent runs:
/Applications/BusyMirror.app/Contents/MacOS/BusyMirror --run-saved-routes --write 1 --exit
- This is more stable than index-based
--routes, because it uses the routes and per-route options you already configured in the UI. - Hourly schedules use
launchdStartInterval; daily and weekday schedules useStartCalendarInterval. - You can remove the job from the same UI with
Remove Schedule, and inspect the generated plist withReveal LaunchAgent. - Note: scheduled headless runs depend on Calendar permission being granted to the installed app. Because these local builds are unsigned, macOS may require re-granting permission after replacing the app bundle with a new build.
Roadmap
See ROADMAP.md
License
MIT — see LICENSE.