macsync/scripts
2026-05-15 17:05:39 -07:00
..
import-existing-names.swift apricot baseline: contacts-sync-core + BlobSyncManager + embedding/search/sync-history 2026-05-15 17:05:39 -07:00
migrate-photos.ts plum baseline: Phase 1/3/4/5 work (BaseSyncManager, SendQueue layer, ireminders, inotes) 2026-05-15 17:05:13 -07:00
package.json plum baseline: Phase 1/3/4/5 work (BaseSyncManager, SendQueue layer, ireminders, inotes) 2026-05-15 17:05:13 -07:00
README.md apricot baseline: contacts-sync-core + BlobSyncManager + embedding/search/sync-history 2026-05-15 17:05:39 -07:00
restore-contacts.swift apricot baseline: contacts-sync-core + BlobSyncManager + embedding/search/sync-history 2026-05-15 17:05:39 -07:00
tsconfig.json plum baseline: Phase 1/3/4/5 work (BaseSyncManager, SendQueue layer, ireminders, inotes) 2026-05-15 17:05:13 -07:00

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

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 <lilith-sync> 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:

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:

{"identifier":"ABC-123","givenName":"Molly","familyName":"Smith","phoneNumbers":["+14155551234"],"emailAddresses":[],"note":"","capturedAt":"2026-04-24T09:47:12Z"}