Skip to main content
Tendril is a native macOS personal CRM built on top of the address book you already have. It reads your contacts through Apple’s Contacts framework, so it sees everything your Mac syncs, iCloud included. On top of that it adds its own layer: notes, reminders, groups, relationship links, a record of how often you talk to each person, and a guess at who knows whom.
Tendril never writes to your address book. Everything it adds is stored in one JSON file on your Mac. You merge duplicates and add new cards in Contacts.app yourself, on purpose.

What it does

Install

Requires macOS 26 or later on Apple silicon, and a Swift 6.2 toolchain.
build.sh compiles a release build, assembles and signs the bundle, and copies it to /Applications if Tendril is already installed there. Pass INSTALL=1 to install it the first time.
Sign with an Apple Development certificate if you have one, because build.sh picks it up automatically. Keychain access and Full Disk Access are tied to the code signature. With ad-hoc signing, macOS asks for both again after every rebuild.

First launch

The first launch shows the macOS Contacts permission prompt. If you dismiss it, turn access back on under System Settings › Privacy & Security › Contacts, then reopen Tendril. To confirm that your contacts loaded, check Tendril’s log:
You should see access granted=true status=3, then loaded N contacts.
Contacts access needs an entitlement, not just a usage string. The app is signed with the hardened runtime, which only allows Contacts access with com.apple.security.personal-information.addressbook. Without that entitlement, CNContactStore.requestAccess throws CNError 100 and the permission prompt never appears, so the app looks like it has no contacts.SwiftPM apps don’t bring their own window to the front. When launched with open, the window gets created but never shows. AppDelegate activates the app and orders the window front explicitly. Tendril remembers where you put the window, and only positions it itself on first run or when its saved display is no longer connected.

The screens

All Contacts

The address book, searchable and sortable. Selecting a contact shows its interactions, details, online profiles, groups, reminders, notes, connections and enrichment suggestions.

Unsaved

Correspondents with no card, busiest first. You can filter by at least 1, 3, 10 or 25 interactions, and dismiss people you don’t want to see.

Duplicates

Clusters of cards that share a name, email or phone. Clusters that share an email or phone count as high confidence and are listed first.

Reminders

Every open reminder across all contacts, soonest first.

Connections

A force-directed graph of your contacts. Links you drew are solid, and inferred ones are dashed.

Sources

Connect mail, Google Calendar, iMessage and Beeper, check each source’s status, and rebuild the interaction index.

Storage

All of Tendril’s own data is in one file:
Credentials are kept in the macOS keychain, never in that file. This includes IMAP app passwords, Google OAuth clients and refresh tokens, and the Beeper access token. Google access tokens are only kept in memory.
When you add a field to the stored payload, make it optional. Swift’s generated decoder throws on a missing key even if the property has a default. Tendril would then start with an empty store, and the next save would erase every note in an older tendril.json.

What leaves your Mac

Tendril builds its index locally and keeps the results on your Mac. It only makes network requests to services you connect, plus Gravatar when you run enrichment:

Limits

  • Contacts can’t be merged in the app. Merging would need CNSaveRequest and write access to Contacts, which Tendril doesn’t request. Use Contacts.app.
  • Accepted enrichment values stay in Tendril. They are not written back to the contact card.
  • “Introduced by” can’t be saved on a link yet. A link stores two contacts and a kind, so converting an inferred introduction into a link loses who made it.
  • The store rewrites the whole file on every change. That’s fine at the current size. Once notes get long, it should move to SQLite or SwiftData.
  • There’s no DMG build yet. For now you build from source.

Next steps

Sources

Connecting mail, calendar, iMessage and Beeper, and how each one is counted.

Connections

How Tendril infers who knows whom, and what the confidence levels mean.

Enrichment

Where suggestions come from, and how accepting and rejecting them works.