macsync/docs/dev-setup.md
Natalie 242d7cd1a8 feat(send-queue): burst-friendly outbound send-rate cap (default 10/5min)
The /client/imessage/send-queue/pending endpoint released up to 50 queued
sends per poll, so an enqueued burst all fired at once. Add a configurable
release cap: the endpoint now returns at most (maxSends − sent-in-window)
queued items, so a burst queues and drips out at the configured rate.

- macsync.send_rate_config single-row table, default max_sends=10,
  window_seconds=300 (10 per 5 min).
- entities/send-queue repo: getSendRateConfig / setSendRateConfig /
  countSentWithin.
- Admin control: GET/PUT /admin/send-rate-limit (service-token auth) so the
  cap is adjustable at runtime (wired to MCP via quinn.api separately).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 15:35:18 -04:00

4.8 KiB

Dev setup

How to get @mac-sync running on a development machine.

Prerequisites

  • macOS 13+ (matches Package.swift:7 platform requirement)
  • Xcode command-line tools (swift --version >= 5.9, per Package.swift:1)
  • Bun — server and web runtime (src/server/package.json:7-10, web/package.json:7-10)
  • PostgreSQL 14+ with gen_random_uuid() available (used in SendQueueRepo.ts:152)
  • For the Linux server target: systemd (app.manifest.yaml:42)

Clone and resolve packages

cd ~/Code/@applications
git clone <repo> @mac-sync
cd @mac-sync
swift package resolve   # pulls GRDB, Alamofire, SwiftyJSON, Swifter

Local SwiftPM dependencies (Package.swift:9-15) live in ~/Code/@packages/@swift/:

  • agent-core, menu-bar, sync-framework, logging, settings
  • @tray/tray-resources

If swift package resolve complains about a missing local package, clone the corresponding repo into ~/Code/@packages/@swift/<scope>/<name>/.

Build the Mac client

make build       # Makefile:59-61 -> swift build --product MacSyncApp
make run         # Makefile:78-79 -> .build/debug/MacSyncApp

make build produces .build/debug/MacSyncApp. The app is a menu-bar executable; it stays in the dock-less tray once running. Look for the MacSyncShared/Storage/ActivityLog entries in stderr to confirm modules are ticking.

Tests:

make test        # Makefile:71-73 -> swift test (all test targets in Package.swift:113-157)

Boot the server

cd src/server
cp .env.example .env       # if present; otherwise set vars manually
bun install
bun run dev                # src/server/package.json:7

Required environment (read by src/server/src/app/config.ts):

  • DATABASE_URL=postgres://…/icloud
  • SSO_VALIDATE_URL — endpoint hit by the ssoRequired middleware for /my/* (src/server/src/app/server.ts:79)
  • SERVICE_TOKEN — bearer for /admin/* (src/server/src/app/server.ts:83)

The server listens on the port from app.manifest.yaml:44 (3201 in prod). First boot runs all migrations in order: src/server/src/app/server.ts:36-52.

Health checks:

curl http://localhost:3201/health         # server.ts:56
curl http://localhost:3201/health/deep    # server.ts:57-66 — also pings the DB

Boot the web SPA

cd web
bun install
bun run dev       # vite --port 5200 (web/package.json:7)

The dev server hot-reloads against the API on localhost:3201. For production the SPA is built (bun run build) into web/dist/ and that directory is shipped inside the Mac app bundle (webapp/ subresource) or served by the server.

Wire the Mac to the server

LocalWebServer (@packages/shared/Sources/MacSyncShared/WebServer/LocalWebServer.swift:42-110) exposes the dev SPA on http://localhost:8765 and an editable settings endpoint:

curl http://localhost:8765/api/settings
# {"serverURL":"http://localhost:3201","webServerPort":8765, …}

curl -X PUT http://localhost:8765/api/settings \
  -H 'content-type: application/json' \
  -d '{"serverURL":"http://localhost:3201"}'

For local end-to-end work, set serverURL to http://localhost:3201 and restart MacSyncApp so DeviceRegistration re-registers against the local backend.

Permissions to grant on first run

Each module needs an OS permission. Approve when prompted:

  • iMessage: Full Disk Access (to read ~/Library/Messages/chat.db). Module surfaces .fullDiskAccessRequired if missing (@packages/imessage/Sources/IMessageSync/SyncManager.swift:36).
  • iPhoto: Photos library access (PHPhotoLibrary). Modal handled by IPhotoSync.requestAuthorization().
  • iMail: Automation > Mail.
  • iCal: Calendars (EKEventStore.requestFullAccessToEvents).
  • iReminders: Reminders (EKEventStore.requestFullAccessToReminders).
  • iNotes: Automation > Notes (@packages/inotes/Sources/INoteSync/SyncManager.swift:96-100).

If you decline, the module sets its error state (e.g. .calendarAccessRequired) and skips cycles until you re-grant. Click the menu-bar status row to deep-link into System Settings.

Running a single test target

swift test --filter MacSyncSharedTests
swift test --filter IMessageSyncTests
swift test --filter ICalSyncTests

All test targets are declared in Package.swift:113-157.

Common dev problems

  • make build fails with "no such package": the local SwiftPM siblings under ~/Code/@packages/@swift/ are missing. Clone them, then swift package resolve again.
  • bun run dev (server) fails on gen_random_uuid(): enable the pgcrypto extension on the Postgres database (or use Postgres 13+).
  • The Mac app starts but the menu icon never appears: check stderr for LocalWebServer: webapp directory not found (LocalWebServer.swift:151). In dev, the resolver expects <repo>/web/dist/; run cd web && bun run build once.