Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f81403745c | |||
| 58d88e9fa5 | |||
| 3ecf29f499 | |||
| eb643ac74d | |||
| df06564434 | |||
| 74b9949610 | |||
| 6676e62889 | |||
| d1fbd4c81f |
1
.gitignore
vendored
@@ -18,6 +18,7 @@ ExportOptions.plist
|
|||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
*.swp
|
*.swp
|
||||||
|
*.profraw
|
||||||
*.zip
|
*.zip
|
||||||
*.sha256
|
*.sha256
|
||||||
dist/
|
dist/
|
||||||
|
|||||||
@@ -410,7 +410,7 @@
|
|||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 5;
|
CURRENT_PROJECT_VERSION = 14;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = BusyMirror/Info.plist;
|
INFOPLIST_FILE = BusyMirror/Info.plist;
|
||||||
@@ -421,7 +421,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.2.3;
|
MARKETING_VERSION = 1.3.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror;
|
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
@@ -440,7 +440,7 @@
|
|||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 5;
|
CURRENT_PROJECT_VERSION = 14;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = BusyMirror/Info.plist;
|
INFOPLIST_FILE = BusyMirror/Info.plist;
|
||||||
@@ -451,7 +451,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.2.3;
|
MARKETING_VERSION = 1.3.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror;
|
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
|
|||||||
@@ -1,55 +1,15 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{ "filename" : "icon_16x16.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" },
|
||||||
"idiom" : "mac",
|
{ "filename" : "icon_32x32.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" },
|
||||||
"scale" : "1x",
|
{ "filename" : "icon_32x32.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" },
|
||||||
"size" : "16x16"
|
{ "filename" : "icon_64x64.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" },
|
||||||
},
|
{ "filename" : "icon_128x128.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" },
|
||||||
{
|
{ "filename" : "icon_256x256.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" },
|
||||||
"idiom" : "mac",
|
{ "filename" : "icon_256x256.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" },
|
||||||
"scale" : "2x",
|
{ "filename" : "icon_512x512.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" },
|
||||||
"size" : "16x16"
|
{ "filename" : "icon_512x512.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" },
|
||||||
},
|
{ "filename" : "icon_1024x1024.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" }
|
||||||
{
|
|
||||||
"idiom" : "mac",
|
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "32x32"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "mac",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "32x32"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "mac",
|
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "128x128"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "mac",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "128x128"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "mac",
|
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "256x256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "mac",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "256x256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "mac",
|
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "512x512"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "mac",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "512x512"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"author" : "xcode",
|
||||||
|
|||||||
BIN
BusyMirror/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png
Normal file
|
After Width: | Height: | Size: 654 KiB |
BIN
BusyMirror/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
BusyMirror/Assets.xcassets/AppIcon.appiconset/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
BusyMirror/Assets.xcassets/AppIcon.appiconset/icon_256x256.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
BusyMirror/Assets.xcassets/AppIcon.appiconset/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
BusyMirror/Assets.xcassets/AppIcon.appiconset/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
BusyMirror/Assets.xcassets/AppIcon.appiconset/icon_64x64.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
28
CHANGELOG.md
@@ -2,6 +2,33 @@
|
|||||||
|
|
||||||
All notable changes to BusyMirror will be documented in this file.
|
All notable changes to BusyMirror will be documented in this file.
|
||||||
|
|
||||||
|
## [1.3.6] - 2026-03-13
|
||||||
|
- Scheduling: add in-app `Scheduled runs` controls to install or remove a user `launchd` LaunchAgent from BusyMirror itself.
|
||||||
|
- Scheduling: support `Hourly`, `Daily`, and `Weekdays` schedules; hourly mode runs saved routes via `StartInterval`.
|
||||||
|
- UX: generate and ship a proper macOS app icon set for BusyMirror.
|
||||||
|
- Build: bump version to 1.3.6 (build 14).
|
||||||
|
|
||||||
|
## [1.3.4] - 2026-03-13
|
||||||
|
- Fix: route-scoped cleanup no longer deletes placeholders created by other source routes during the same multi-route run.
|
||||||
|
- Fix: stale calendars are pruned from saved selections and routes during refresh, and refresh now recreates `EKEventStore` for a hard reload.
|
||||||
|
- UX: the top bar `DRY RUN` / `WRITE` status pill is clickable, the left column keeps its own height on desktop, and the app can reveal its log file from the UI.
|
||||||
|
- Logging: mirror activity is persisted to `~/Library/Logs/BusyMirror/BusyMirror.log` with simple rotation to `BusyMirror.previous.log`.
|
||||||
|
- CLI: add `--run-saved-routes` so scheduled `launchd` runs can use the saved UI routes instead of fragile index-based route definitions.
|
||||||
|
|
||||||
|
## [1.3.1] - 2025-10-13
|
||||||
|
- Fix: auto-delete of mirrored placeholders when the source is removed now works even if no source instances remain in the window. Also cleans legacy mirrors without URLs by matching exact times.
|
||||||
|
|
||||||
|
## [1.3.2] - 2025-10-13
|
||||||
|
- New: Organizer filters — skip events by organizer (name/email/URL). UI under Options and persisted in settings.
|
||||||
|
- CLI: add `--exclude-organizers` (and `--exclude-titles`) flags to control filters when running headless.
|
||||||
|
|
||||||
|
## [1.2.4] - 2025-10-10
|
||||||
|
- Fix: enable “Mirror Now” when Routes are defined even if no Source/Targets are checked in the main window. Button now enables if either routes exist or a manual selection is present.
|
||||||
|
|
||||||
|
## [1.3.0] - 2025-10-10
|
||||||
|
- New: Mark Private option to mirror with prefix + real title and set event privacy on supported servers; available globally and per-route; persisted.
|
||||||
|
- Misc: calendar access fixes, concurrency annotations, accepted‑only filter, settings autosave/restore, Mirror Now enablement.
|
||||||
|
|
||||||
## [1.2.3] - 2025-10-10
|
## [1.2.3] - 2025-10-10
|
||||||
- Fix: reliably save and restore settings between runs via autosave of key options and restoration of source/target selections by persistent IDs.
|
- Fix: reliably save and restore settings between runs via autosave of key options and restoration of source/target selections by persistent IDs.
|
||||||
- UX: persist Source and Target selections; rebuild indices on launch so UI matches saved IDs.
|
- UX: persist Source and Target selections; rebuild indices on launch so UI matches saved IDs.
|
||||||
@@ -15,4 +42,3 @@ All notable changes to BusyMirror will be documented in this file.
|
|||||||
|
|
||||||
## [1.2.0] - 2024-09-29
|
## [1.2.0] - 2024-09-29
|
||||||
- Feature: multi-route mirroring, overlap modes, merge gaps, work hours filter, CLI support, export/import settings.
|
- Feature: multi-route mirroring, overlap modes, merge gaps, work hours filter, CLI support, export/import settings.
|
||||||
|
|
||||||
|
|||||||
47
README.md
@@ -2,13 +2,20 @@
|
|||||||
|
|
||||||
BusyMirror mirrors meetings between your calendars so your availability stays consistent across accounts/devices.
|
BusyMirror mirrors meetings between your calendars so your availability stays consistent across accounts/devices.
|
||||||
|
|
||||||
## What it does (current checkpoint)
|
## What it does (current)
|
||||||
- Manual “Run” to mirror events across selected routes (Source → Targets).
|
- Route-driven mirroring (multi-source): define Source → Targets routes and run them in one go.
|
||||||
- DRY-RUN mode shows what would happen.
|
- Manual selection mirroring: pick a source and targets in the UI and run.
|
||||||
- Prefix-based tagging of mirrored events.
|
- Two privacy modes:
|
||||||
- Cleanup of placeholders (with confirmation).
|
- Private (hide details): mirrors placeholders with prefix + placeholder title (e.g., "🪞 Busy").
|
||||||
- Loop/duplicate guards so mirrors don’t replicate themselves.
|
- Mark Private: mirrors prefix + real title, but marks events Private on supported servers (best-effort).
|
||||||
- Time window and merge-gap settings.
|
- DRY-RUN mode: see what would be created/updated/deleted without writing.
|
||||||
|
- 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.
|
||||||
|
- Prefix-based tagging and loop guards to prevent re-mirroring mirrors.
|
||||||
|
- Settings: autosave/restore, Import/Export JSON.
|
||||||
|
|
||||||
## Why
|
## Why
|
||||||
Use one calendar’s confirmed meetings to block time in other calendars (e.g., corporate iPad vs. personal devices).
|
Use one calendar’s confirmed meetings to block time in other calendars (e.g., corporate iPad vs. personal devices).
|
||||||
@@ -27,6 +34,32 @@ Option B — Makefile (reproducible)
|
|||||||
|
|
||||||
See `CHANGELOG.md` for notable changes.
|
See `CHANGELOG.md` for notable changes.
|
||||||
|
|
||||||
|
## CLI (optional)
|
||||||
|
- Run from Terminal with `--routes` to 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`
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
- In the UI, use `Reveal Log File` to open the current log directly in Finder.
|
||||||
|
|
||||||
|
## Scheduling
|
||||||
|
- BusyMirror can create its own schedule from the app UI in `Scheduled runs`.
|
||||||
|
- Choose `Hourly`, `Daily`, or `Weekdays`, then click `Install 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 `launchd` `StartInterval`; daily and weekday schedules use `StartCalendarInterval`.
|
||||||
|
- You can remove the job from the same UI with `Remove Schedule`.
|
||||||
|
- 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
|
## Roadmap
|
||||||
See [ROADMAP.md](ROADMAP.md)
|
See [ROADMAP.md](ROADMAP.md)
|
||||||
|
|
||||||
|
|||||||
21
ROADMAP.md
@@ -1,17 +1,26 @@
|
|||||||
# BusyMirror Roadmap
|
# BusyMirror Roadmap
|
||||||
|
|
||||||
|
## Shipped (highlights)
|
||||||
|
- Route-driven mirroring (multi-source)
|
||||||
|
- Accepted-only filter (mirror your accepted meetings)
|
||||||
|
- Persistent settings with autosave/restore; Import/Export JSON
|
||||||
|
- Overlap modes (allow, skipCovered, fillGaps) and merge-gap
|
||||||
|
- Work Hours filter and title-based skip filters
|
||||||
|
- Privacy: placeholders with prefix + customizable title
|
||||||
|
- 1.3.0: Mark Private option (global + per-route)
|
||||||
|
|
||||||
## Next
|
## Next
|
||||||
- Source filters (name patterns like `[HOLD]`, `#nomirror`)
|
- Auto-refresh calendars on `EKEventStoreChanged` (live refresh button-less)
|
||||||
- Mirror only **Accepted** meetings (exclude tentative/declined)
|
- Hint near "Mirror Now" indicating run mode (Routes vs Manual)
|
||||||
- Persistent settings (routes, window, prefix)
|
- Better server-side privacy mapping (per-provider heuristics)
|
||||||
- Import/Export settings (.busymirror.json)
|
|
||||||
|
|
||||||
## Then
|
## Then
|
||||||
- iOS/iPadOS app (Run Now, Shortcuts, iCloud sync)
|
- Signed/notarized binaries and release pipeline
|
||||||
- UI: route editor & clearer toggles
|
- CLI quality: friendlier `--routes` parsing and help flag
|
||||||
- “Dry-run by default” preference
|
- “Dry-run by default” preference
|
||||||
|
|
||||||
## Later
|
## Later
|
||||||
- Background monitoring (macOS)
|
- Background monitoring (macOS)
|
||||||
- Smarter cleanup & conflict resolution
|
- Smarter cleanup & conflict resolution
|
||||||
|
- iOS/iPadOS helper (Shortcuts integration)
|
||||||
- Profiles & MDM/Managed Config support
|
- Profiles & MDM/Managed Config support
|
||||||
|
|||||||
11
ReleaseNotes-1.2.4.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# BusyMirror 1.2.4 — 2025-10-10
|
||||||
|
|
||||||
|
Bugfix release improving route-driven mirroring.
|
||||||
|
|
||||||
|
Fixes
|
||||||
|
- Mirror Now is enabled when routes are defined, even if nothing is checked in the main window. This allows fully route-driven runs without requiring a temporary manual selection.
|
||||||
|
|
||||||
|
Build
|
||||||
|
- `make build-release`
|
||||||
|
- `make package` → BusyMirror-1.2.4-macOS.zip and .sha256
|
||||||
|
|
||||||
17
ReleaseNotes-1.3.0.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# BusyMirror 1.3.0 — 2025-10-10
|
||||||
|
|
||||||
|
New
|
||||||
|
- Mark Private option: mirror events with your prefix + real title while marking them Private on supported servers (e.g., Exchange). Co‑workers see the time block but not the details.
|
||||||
|
- Per-route and global toggles for Mark Private; persists in settings and export/import.
|
||||||
|
|
||||||
|
Fixes & improvements
|
||||||
|
- More reliable calendar loading after permission grant (reinit EKEventStore).
|
||||||
|
- Concurrency: `@MainActor` on permission/refresh methods.
|
||||||
|
- Accepted‑only filter via current user attendee `participantStatus`.
|
||||||
|
- Settings autosave and restore (including source/target selections by IDs).
|
||||||
|
- Mirror Now enabled when calendars available; routes or manual selection used as appropriate.
|
||||||
|
|
||||||
|
Build
|
||||||
|
- `make build-release`
|
||||||
|
- `make package` → BusyMirror-1.3.0-macOS.zip and .sha256
|
||||||
|
|
||||||
6
ReleaseNotes-1.3.1.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
BusyMirror 1.3.1 — Bugfix Release
|
||||||
|
|
||||||
|
- Fix: Auto-delete mirrored placeholders when the source event is removed.
|
||||||
|
- Triggers even if no source instances remain in the selected window.
|
||||||
|
- Also cleans legacy mirrors without mirror URLs by matching exact times.
|
||||||
|
|
||||||
11
ReleaseNotes-1.3.2.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
BusyMirror 1.3.2 — 2025-10-13
|
||||||
|
|
||||||
|
Changes
|
||||||
|
- Organizer filters: skip mirroring events whose organizer matches a name, email, or URL token. Case-insensitive. Configure in Options.
|
||||||
|
- CLI flags: `--exclude-organizers` and `--exclude-titles` accept comma/newline separated tokens. Example:
|
||||||
|
- `--routes "1->2" --write 1 --exclude-organizers "alice@example.com, Example Org" --exit`
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- Export/Import settings now includes organizer filters (backwards compatible).
|
||||||
|
- No changes to event URL format; feature is fully optional.
|
||||||
|
|
||||||
9
ReleaseNotes-1.3.3.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
BusyMirror 1.3.3 — 2025-10-13
|
||||||
|
|
||||||
|
Changes
|
||||||
|
- UI: Options panel is scrollable to ensure new filters are always visible on smaller windows.
|
||||||
|
- Organizer filter: skip by organizer name/email/URL; settings persisted; usable via CLI with `--exclude-organizers`.
|
||||||
|
|
||||||
|
Build
|
||||||
|
- Version bump to 1.3.3 (build stays 11).
|
||||||
|
|
||||||
11
ReleaseNotes-1.3.4.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
BusyMirror 1.3.4 - 2026-03-13
|
||||||
|
|
||||||
|
Changes
|
||||||
|
- Fix multi-route cleanup so one source route no longer deletes mirrored placeholders created by another route.
|
||||||
|
- Persist activity logs to `~/Library/Logs/BusyMirror/BusyMirror.log` and expose a `Reveal Log File` action in the app.
|
||||||
|
- Add `--run-saved-routes` for headless runs using the routes configured in the UI, which makes `launchd` scheduling practical.
|
||||||
|
- Improve calendar refresh by pruning stale saved identifiers and recreating the EventKit store.
|
||||||
|
- Keep the left column from stretching to match the routes/log column on desktop layouts.
|
||||||
|
|
||||||
|
Build
|
||||||
|
- Version bump to 1.3.4 (build 12).
|
||||||
9
ReleaseNotes-1.3.6.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
BusyMirror 1.3.6 - 2026-03-13
|
||||||
|
|
||||||
|
Changes
|
||||||
|
- Add in-app scheduling controls so BusyMirror can install and remove its own `launchd` LaunchAgent.
|
||||||
|
- Support hourly saved-route runs in addition to daily and weekday schedules.
|
||||||
|
- Ship a generated macOS app icon set for the app bundle and exported releases.
|
||||||
|
|
||||||
|
Build
|
||||||
|
- Version bump to 1.3.6 (build 14).
|
||||||