# mac-sync scripts ## restore-contacts.swift Restores the macOS Contacts store from a full vCard backup produced by `ContactsBackup.dumpFullVCard`. These backups are written by the mac-sync app every time the contact-renderer poller runs its first iteration for a session. ### Backup locations ``` ~/Library/Application Support/com.lilith.mac-sync/contact-backups/ full-YYYYMMDD-HHmmss.vcf # full store snapshot rollback-YYYYMMDD-HHmmss.jsonl # per-contact pre-state for selective restore diff-YYYYMMDD-HHmmss.jsonl # dry-run report of "what would change" ``` ### Restore full store ```bash swift scripts/restore-contacts.swift \ ~/Library/Application\ Support/com.lilith.mac-sync/contact-backups/full-20260424-094712.vcf ``` The script imports every contact in the .vcf. It does NOT wipe the store first — restored contacts are added with new identifiers. For a clean rollback after a bad write run, delete the lilith-sync-managed contacts first (search Contacts for the airport prefix `[LAX]`/`[???]`/etc or the emoji glyphs 🔥 👸 🌹), then run this script. ### Permissions The Terminal (or whichever shell host you're in) must have Contacts access: - System Settings → Privacy & Security → Contacts → enable Terminal - Or: swift will prompt on first invocation; click Allow. ### macOS 14+ notes entitlement Reading and writing `CNContact.note` requires the `com.apple.developer.contacts.notes` entitlement (already in `src/client/Resources/LilithMacSync.entitlements`). This is a restricted entitlement — Apple gates it behind a provisioning profile request. If the signing cert doesn't grant it, the ContactsWriter notes merge silently no-ops: `givenName` + `familyName` still land, but the `` block never appears in Contacts.app. Symptom to watch: first canary contact shows the airport prefix + emojis but an empty notes field. Workaround if the entitlement isn't available: drop the notes branch by setting `ContactsWriter.mergeNote` to return an empty string — the first/last name rendering is the bulk of the value anyway. ### Dry-run diff review Before any real write run, the contact-renderer poller writes a dry-run diff: ```bash jq -c '.' ~/Library/Application\ Support/com.lilith.mac-sync/contact-backups/diff-*.jsonl | less ``` Each line is `{handle, before: {...}, after: {...}}`. Skim it before lifting the `MAC_SYNC_CONTACTS_WRITE=1` gate. ### Selective rollback To restore a single contact from the rollback JSONL (without re-importing the whole .vcf), extract the pre-state block and apply it manually via Contacts.app or via a one-off swift snippet using `CNMutableContact`. The JSONL schema: ```json {"identifier":"ABC-123","givenName":"Molly","familyName":"Smith","phoneNumbers":["+14155551234"],"emailAddresses":[],"note":"","capturedAt":"2026-04-24T09:47:12Z"} ```