v1.9.0
- Sidebar-navigation redesign: NavigationSplitView with Routes / Schedule / Activity Log replaces the 2x2 panel-card grid. Primary actions moved to the toolbar (dry-run/write, sync status, Sync Now); Export/Import/Reveal Log/Cleanup/Refresh/Recheck Permission moved to a toolbar overflow menu. - Routes are now a collapsed-by-default list -- click a row to expand its editor instead of always showing every field for every route. - Schedule view leads with live auto-sync status; the manual launchd schedule is demoted to an explicit optional section underneath. - Activity Log is readable rows (status icon + text) with a search filter and a Clear button, instead of a monospaced text dump. - Menu bar dropdown got icons and real Cmd+,/Cmd+Q shortcuts. - Explored as a design mockup first (published as an Artifact, light+dark), direction confirmed before writing any SwiftUI. Fixed along the way: routes were being silently dropped on most launches. reloadCalendars() pruned routes against a freshly-created EKEventStore's calendar fetch right after a permission grant, which can under-report calendars for a moment (especially remote/Exchange accounts) before they finish loading -- wrongly concluding a route's calendar was gone. Only survived because of a routes.v1 legacy backup key that itself never got updated after its first write, so any real route edit made in an affected session would have been silently reverted on the next launch. Fixed by skipping the prune specifically on the post-grant reload (reloadCalendars(pruneRoutes: false)) and keeping the legacy backup current on every save instead of frozen. All unit tests pass. Verified on a real install: routes stopped disappearing across repeated launches (checked via --status and reading the UserDefaults plist directly). 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.