# Mac Contacts

> CLI for reading and managing macOS Contacts (CNContactStore). Supports searching by name, email, phone number, city, or country; showing all fields for a contact including list membership; creating and updating contacts with name, org, phone, email, and postal address; deleting contacts; and managing group (list) membership. Use when asked to look up, add, edit, remove, or organise contacts on macOS, or when you need a contact's phone number, email, address, or which lists they belong to.

- Skill: `dvcrn/mac-contacts` (Agent Skill, multi-file: 12 files)
- Install (CLI): `npx skillmds@latest add dvcrn/mac-contacts`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dvcrn/mac-contacts/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: dvcrn (https://skillmd.com/u/dvcrn)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/dvcrn/mac-contacts

---


# mac-contacts

macOS Contacts CLI backed by `CNContactStore`. All reads use unified contact
views (iCloud + local + Exchange merged). All writes are atomic via
`CNSaveRequest`. Group membership removal uses `osascript` to work around a
silent no-op in `CNSaveRequest.removeMember_fromGroup_` for iCloud-backed
groups.

## Dependencies

```bash
pip install pyobjc-framework-Contacts
```

```bash
pip install pyyaml
```

Grant Contacts access to Terminal (or your agent host) when prompted on first
run, or via **System Settings → Privacy & Security → Contacts**.

## Invocation

```bash
python3 skill://mac-contacts/scripts/mac-contacts.py <subcommand> [options]
```

All examples below use `mac-contacts` as shorthand for the full invocation.

---

## Subcommands

### search

Search contacts. With a positional query, performs a single-pass search
across **name, organisation, note, email, phone** (digits normalised), and
**postal address** fields. Use explicit flags to restrict to a specific field.

```
search [QUERY]
       [--list LIST]
       [--email EMAIL]
       [--phone PHONE]
       [--city CITY]
       [--country COUNTRY]
```

| Flag | Description |
|------|-------------|
| `QUERY` | Comprehensive search across all fields. Phone digits are matched fuzzily (query digits must appear in contact's digit-stripped number; minimum 4 digits required for phone matching). |
| `--list LIST` | Return only contacts that are members of the named list/group. |
| `--email EMAIL` | Match by email address (uses the CNContact native email predicate — efficient). |
| `--phone PHONE` | Match by phone number; non-digit characters stripped before comparison. Minimum 4 digits. |
| `--city CITY` | Match by city in any postal address. |
| `--country COUNTRY` | Match by country in any postal address. |

**Examples:**

```bash
# Comprehensive — finds by name, org, email, phone, address
mac-contacts search "John"
mac-contacts search "john@example.com"   # auto-matches email
mac-contacts search "415-555"            # auto-matches phone (≥4 digits)
mac-contacts search "San Francisco"      # auto-matches city

# Explicit field targeting
mac-contacts search --email "john@acme.com"
mac-contacts search --phone "415"        # error: fewer than 4 digits
mac-contacts search --phone "4155551234"
mac-contacts search --city "London"
mac-contacts search --country "Germany"

# Filter to list members
mac-contacts search --list "Work"
```

Output per contact: name, organisation, phone(s), email(s), address(es).

---

### show

Show every available field for a contact, including list membership.

```
show NAME
```

Output includes: full name (with prefix/middle/suffix), nickname,
organisation, job title, department, phones, emails, postal addresses,
URLs, social profiles, birthday, dates, note (if readable), and **Lists**.

```bash
mac-contacts show "Jane Doe"
mac-contacts show "Apple"        # matches any contact whose name contains "Apple"
```

> **Note:** If multiple contacts match `NAME`, only the first result is shown.

---

### create

Create a new contact. All flags except `--first-name` are optional.

```
create --first-name NAME
       [--last-name NAME]
       [--organization ORG]
       [--email EMAIL]     (repeatable)
       [--phone PHONE]     (repeatable)
       [--street STREET]
       [--city CITY]
       [--state STATE]
       [--zip ZIP]
       [--country COUNTRY]
       [--url URL]         (repeatable)
       [--birthday DATE]
```

```bash
mac-contacts create --first-name "Jane" --last-name "Doe" \
    --organization "Acme" \
    --email "jane@acme.com" --email "jane.personal@gmail.com" \
    --phone "415-555-0100" \
    --street "123 Main St" --city "San Francisco" \
    --state "CA" --zip "94102" --country "United States" \
    --url "https://jane.acme.com" \
    --birthday "1985-03-22"
```

> **Note:** The `--note` flag is intentionally absent. Writing contact notes
> requires the `com.apple.developer.contacts.notes` entitlement, which
> Terminal-based tools do not hold.

> **`--birthday` format:** `YYYY-MM-DD` for a full date (e.g. `1985-03-22`),
> or `--MM-DD` to store month/day without a year (e.g. `--03-22`).

---

### update

Update fields on an existing contact. Phone and email values are **appended**
to existing values (not replaced). A new postal address block is appended if
any address flag is provided.

```
update NAME
       [--organization ORG]
       [--email EMAIL]     (repeatable, appends)
       [--phone PHONE]     (repeatable, appends)
       [--street STREET]
       [--city CITY]
       [--state STATE]
       [--zip ZIP]
       [--country COUNTRY]
       [--url URL]         (repeatable, appends)
       [--birthday DATE]   (replaces existing)
```

```bash
mac-contacts update "Jane Doe" --phone "415-555-0199"
mac-contacts update "Jane Doe" --organization "New Corp" --city "Oakland"
mac-contacts update "Jane Doe" --birthday "1985-03-22"
mac-contacts update "Jane Doe" --url "https://jane.dev"
```

---

### delete

Delete a contact. Prompts for confirmation unless `--force` is given.

```
delete NAME [--force]
```

```bash
mac-contacts delete "Jane Doe"           # prompts y/N
mac-contacts delete "Jane Doe" --force   # no prompt
```

---

### add_to_list

Add a contact to a named list (CNGroup). Creates the list if it does not
exist.

```
add_to_list NAME LIST
```

```bash
mac-contacts add_to_list "Jane Doe" "Work"
```

---

### remove_from_list

Remove a contact from a named list.

```
remove_from_list NAME LIST
```

```bash
mac-contacts remove_from_list "Jane Doe" "Work"
```

> **Implementation note:** Uses `osascript` (Contacts.app) because
> `CNSaveRequest.removeMember_fromGroup_` silently no-ops for iCloud-backed
> groups.

---

### list_groups

List all contact groups (lists) in the store.

```
list_groups
```

```bash
mac-contacts list_groups
```

---

## Output conventions

- `search`, `show`, and `list_groups` emit **YAML** (requires `pyyaml`).
  `search` and `list_groups` return a YAML list; `show` returns a single YAML mapping.
  Parse with `python3 -c "import sys,yaml; print(yaml.safe_load(sys.stdin))"` or `yq`.
- Success messages begin with `Success:`.
- Error messages begin with `Error:` or `[FATAL]`.
- `search` and `list_groups` exit with code 1 when no results are found.
- All other commands exit 1 on any failure.

## Known limitations

- **Notes (write):** Setting a contact note requires the
  `com.apple.developer.contacts.notes` entitlement. `create` and `update`
  do not expose `--note` for this reason. Existing notes on contacts are
  readable via `show`.
- **`update` replaces nothing:** Phone, email, and address values are always
  appended, never replaced. To change a value, delete and recreate the
  contact, or edit via Contacts.app.
- **`show` NAME matching:** Uses `CNContact.predicateForContactsMatchingName_`
  which matches substrings across name fields. If ambiguous, the first result
  is returned.

