imsgcrawl, a local-first crawler that reads Apple Messages (~/Library/Messages/chat.db) under Full Disk Access, decodes the message text, and archives it into a clean local SQLite database at ~/.imsgcrawl/archive.db. DuckBrain only ever reads that archive — it attaches it read-only and maps it to bronze, exactly like the other *crawl sources.
Install imsgcrawl
imsgcrawl isn’t on the Homebrew tap yet — install it from source (needs Go ≥ 1.26.5):duckbrain init offers to run this for you when you pick iMessage.
Requires Full Disk Access
macOS protectschat.db behind Full Disk Access (TCC). Because imsgcrawl is the tool that reads it, grant FDA to the app that runs imsgcrawl — your terminal (Ghostty, iTerm, Terminal), or bun for the streaming daemon:
System Settings → Privacy & Security → Full Disk Access → enable your terminal, then fully quit and reopen it.
Without it, imsgcrawl sync can’t read chat.db, so no archive is built and iMessage won’t appear in duckbrain sync status sources. duckbrain doctor warns when chat.db is present but unreadable.
What gets ingested
- Text lives in
attributedBody. ~98% of modern messages store their text in an Apple typedstream (NSAttributedString) blob, not thetextcolumn. imsgcrawl decodes it during sync, so the archive’stextcolumn holds resolved message text. - Attachments & tapbacks (reactions, edits) yield no text and are dropped.
- Incremental & idempotent. imsgcrawl keeps its archive up to date; DuckBrain re-ingests it and dedups on a stable per-message id, so nothing piles up. The streaming trigger lane watches the live
chat.dband refreshes the archive + re-ingests within about a minute of a new message.
Facet mapping
iMessage is macOS-only. Rows are
classification: confidential and never leave the machine.