Skip to content

feat: resolve contact names for chat and message display#77

Closed
jsindy wants to merge 1 commit into
openclaw:mainfrom
jsindy:feat/contact-name-resolution
Closed

feat: resolve contact names for chat and message display#77
jsindy wants to merge 1 commit into
openclaw:mainfrom
jsindy:feat/contact-name-resolution

Conversation

@jsindy
Copy link
Copy Markdown
Contributor

@jsindy jsindy commented Mar 28, 2026

Problem

Phone numbers and emails in chat listings, message history, and watch streams are displayed as raw handles (+14155551212) with no way to identify who they belong to. The chats command's display_name field is usually empty for 1:1 conversations since Messages.app doesn't populate it. There's also no way to send a message by contact name — --to requires a phone number or email.

Solution

Add macOS Contacts framework integration that resolves handles to display names from the system address book.

New: ContactResolver in IMsgCore

  • Preloads all contacts into phone→name and email→name dictionaries on init (~10-50ms for typical address books)
  • O(1) lookups via E.164-normalized phone numbers and lowercased emails
  • ContactResolving protocol with NoOpContactResolver fallback when Contacts access is denied
  • Uses existing PhoneNumberNormalizer for consistent phone normalization

Enriched output across all surfaces

Surface Before After
imsg chats [1] (+14155551212) [1] John Smith (+14155551212)
imsg history [recv] +14155551212: hello [recv] John Smith: hello
imsg watch [recv] +14155551212: hello [recv] John Smith: hello
JSON output "sender": "+14155551212" "sender": "+14155551212", "sender_name": "John Smith"
RPC chats no contact info "contact_name": "John Smith" for 1:1 chats

Send by contact name

imsg send --to "John Smith" --text "hey"
# Resolves to +14155551212 and sends

imsg send --to "John" --text "hey"
# Error if multiple contacts match:
#   Multiple contacts match "John":
#     John Smith: +14155551212
#     John Doe: +15559876543
#   Specify a phone number or email instead.

The resolver is only created when --to looks like a name (not phone/email), so --chat-id sends skip Contacts entirely.

Changes

File Change
Sources/IMsgCore/ContactResolver.swift New — protocol, resolver, no-op fallback
Package.swift Link Contacts framework
Sources/imsg/Resources/Info.plist Add NSContactsUsageDescription
Resources/imsg.entitlements Add addressbook entitlement
Sources/imsg/OutputModels.swift Add optional sender_name / contact_name fields
Sources/imsg/RPCPayloads.swift Thread contact names through RPC payloads
Sources/imsg/RPCServer.swift Add contactResolver property
Sources/imsg/RPCServer+Handlers.swift Resolve names in all handlers
Sources/imsg/ChatTargetResolver.swift Add looksLikeName + resolveRecipientName
Sources/imsg/Commands/ChatsCommand.swift Enrich 1:1 chat names
Sources/imsg/Commands/HistoryCommand.swift Resolve sender names
Sources/imsg/Commands/WatchCommand.swift Resolve sender names in stream
Sources/imsg/Commands/SendCommand.swift Support --to "Name"
Sources/imsg/Commands/RpcCommand.swift Create resolver at server startup
Tests/ New tests for resolver, mock, name resolution, payloads

Caveats

  • Requires Contacts permission — macOS will prompt on first use
  • If denied, falls back silently to raw handles (no behavior change)
  • First run has ~10-50ms overhead to preload contacts; subsequent lookups are O(1)
  • --to "Name" requires an unambiguous match; multiple matches produce an error with candidates listed

Testing

  • swift build passes cleanly
  • Manual testing with 520 real contacts on macOS 15
  • Contact names resolve correctly for 1:1 chats in chats, history, watch
  • JSON output includes sender_name and contact_name fields
  • --to "Name" resolves and sends correctly
  • Permission denial gracefully falls back to raw handles

Add macOS Contacts framework integration to resolve phone numbers and
emails to contact display names. Names appear in chat listings, message
history, watch streams, and all RPC payloads.

- Add ContactResolver in IMsgCore with eager preload of all contacts
- Enrich chats, history, watch, and RPC output with sender/contact names
- Support `--to "Name"` in send command for name-based recipient lookup
- Graceful fallback to raw handles when Contacts access is denied
@barronlroth
Copy link
Copy Markdown

barronlroth commented Apr 3, 2026

We need this! LGTM.

@steipete
Copy link
Copy Markdown
Collaborator

steipete commented May 4, 2026

Thanks @jsindy. I landed a combined maintainer version on main in 5203f74.

I kept the core shape from this PR: optional Contacts lookup, additive JSON/RPC fields (contact_name, sender_name, reaction sender_name), plain-output contact labels, and unambiguous send-by-contact-name. I also rebased it onto the current command/RPC surface, kept raw chat identifiers visible in chats output, added docs/changelog/entitlement updates, and injected a mock resolver in tests so the suite does not prompt for real Contacts.

Proof: local make lint, make build, make test, git diff --check; GitHub CI is green on 5203f74 (run 25308475901). Added co-author credit in the landed commit and changelog. Closing this PR as superseded by main.

@steipete steipete closed this May 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants