Skip to content

Repository files navigation

Chatoyant

sha-toy-ant: having a shifting inner light, like a gem turned over in the hand.

ChatGPT's Apple Messages plugin searches your texts, catches you up, drafts and sends replies, and routes every one of them through OpenAI's servers. Chatoyant does the same job on your Mac with an open-weight model: nothing is uploaded, no account, and it works with Wi-Fi off.

Chatoyant demo

curl -fsSL https://raw.githubusercontent.com/psamme/chatoyant/main/install.sh | sh
cd ~/chatoyant && python3 chatoyant.py chat "what did I miss since yesterday"

Not ready to point it at your real inbox? python3 chatoyant.py demo-chat runs the same assistant on a generated fake one: a week of texts from made-up friends, a landlord, a dentist, two group chats and a photo. The GIF above is that inbox, recorded with vhs docs/demo.tape and docs/retime.py; there is an mp4 of the same run at docs/demo.mp4.

Manual install: clone the repo, brew install ollama, ollama pull qwen3:8b, ollama pull gemma3:4b, then python3 chatoyant.py doctor to check Full Disk Access, Ollama and models in one go.

What it can do

Things you say to it, with the tool it reaches for:

  • "what did I miss since yesterday" catch_up
  • "what's going on in tahoe crew" read_conversation
  • "when did Maya mention the dentist" search_messages, semantic_search
  • "tell him count me in" (a draft in your own texting voice) my_voice, send_message
  • "text Maya, Theo and Luca that tahoe is on" send_message, one 1:1 text each, never a new group
  • "summarize my unread messages and tell me if any need a response" catch_up, every unread thread of any age
  • "what's in the photo Luca sent" find_photos, view_image
  • "find a free slot Thursday and put it on the calendar" calendar_events, add_calendar_event
  • "what's my texting ratio with Maya" texting_stats
  • "what was everyone texting me about a year ago today" on_this_day
  • "who reacted to my last message in family" read_conversation
  • "what's Jordan's number" find_contact

Sending is gated. CHATOYANT_SEND=ask (the default) opens a native macOS dialog showing the exact recipient and text, and nothing goes out until you click Send. off returns drafts only. on sends without asking, for automations you fully trust. The model cannot set this flag itself.

Inside the chat: /model switches models, /send ask|off|on changes the gate, /clear starts over, /help lists the rest. Ctrl-D quits.

Every message it quotes gets a small number. /goto 2 opens Messages.app on that exact message (highlighted, scrolled into view); /find bring a sweater does the same for any words you remember; /open maya opens a thread. Messages has no link to a single message, so /goto drives its search field for you, which needs Accessibility permission for your terminal (System Settings > Privacy & Security > Accessibility) and was built on macOS 26. If that step cannot run, it still lands you on the thread and says why. In terminals that support links (iTerm2, Ghostty, Kitty, WezTerm, VS Code) each quote is also cmd-clickable and opens its conversation.

How it stays private

  • chat.db is opened read-only (?mode=ro). Nothing is ever written to it.
  • The only network call is to Ollama at 127.0.0.1:11434, or whatever OLLAMA_HOST you set. The host is printed at startup so you can see it.
  • stdout and logs carry counts and errors, never message content.
  • Sending goes through a native confirmation dialog with the exact text.
  • No dependencies beyond Python 3.10 and Ollama (numpy only if you build the semantic index). Nothing to audit but this repo.
  • Photo thumbnails and descriptions are cached in .cache/, gitignored, deletable any time. Everything generated from your archive is gitignored.

Requirements: macOS, Full Disk Access for the terminal or app launching it (System Settings > Privacy & Security), and a one-time Messages automation prompt on first send. Calendar uses a small EventKit helper compiled on first use; macOS asks once.

Use it from Claude Desktop, Cursor or LM Studio

Chatoyant is also an MCP server, so the same tools work inside any app that speaks MCP.

python3 chatoyant.py mcp-config   # prints the JSON to paste into the app

It talks to its client over stdio only. The privacy story then depends on the model the client uses: attach a cloud model (Claude, GPT, Gemini) and your texts go to that cloud, tool call by tool call. Attach a local one (LM Studio, Jan, Ollama in Cursor) and they don't.

Models and speed

chatoyant chat uses gpt-oss:20b if you have it pulled (reasoning capped at low), otherwise qwen3:8b. Photos go through gemma3:4b. Override with CHATOYANT_MODEL and CHATOYANT_VISION. The model is loaded while you type and stays warm between questions.

An M4 with Low Power Mode off does about 26 tok/s on gpt-oss:20b and a full catch-up in about 30 s. With Low Power Mode on, everything runs 3-4x slower; chatoyant chat warns you when it is on. Turn it off in System Settings > Battery.

Keeping the search index current

ollama pull nomic-embed-text
python3 chatoyant.py index            # embed your archive once (~30 min)
python3 chatoyant.py update           # add new texts only (seconds)
python3 chatoyant.py watch --install  # run update whenever Messages writes

Every other tool reads chat.db live; the index only matters for semantic_search and the archive app below.

The archive app

Chatoyant also builds a local app over your iMessage, Instagram and Google Chat history: a decade of texting in charts, yearbook superlatives judged by a local model, a memories page for what you and your people said on this day every year, close reads of the people you text most, and an ask-your-history chat with sources. This half also lives on its own at psamme/message-stats.

Memories

python3 chatoyant.py setup    # point it at your Instagram / Takeout exports (optional)
python3 chatoyant.py demo     # tour with fake data
python3 chatoyant.py build    # your real archive -> app/index.html
python3 chatoyant.py serve    # ask-your-history UI at http://127.0.0.1:8765

Screenshots show generated fake data.

Compared with the ChatGPT plugin

ChatGPT Apple Messages plugin Chatoyant
Search, catch up, draft, send with approval yes yes
Photos and calendar yes yes
Where your texts are processed OpenAI's servers your Mac
Account or subscription ChatGPT plan none
Works offline no yes
Writing quality of drafts frontier model open 8B-20B model, plainer
Native window yes terminal, or your MCP client
Texting stats, on-this-day, semantic search over your whole history no yes
Source you can read no one Python file per piece, MIT

Doctor

python3 chatoyant.py doctor

Checks Full Disk Access, that chat.db opens read-only, that Ollama is reachable at the printed host, which models are pulled, and whether Low Power Mode is on. Run it first if anything feels off.

FAQ

Does it send my texts anywhere? No. The only outbound connection is to Ollama on your own machine. Three ways to check, pick your favorite: run lsof -i -P | grep -i python while it answers a question and you will see one connection to 127.0.0.1:11434; put Little Snitch or LuLu in front of it; or turn Wi-Fi off and keep asking questions.

Can it read messages I've deleted? Only what is still in chat.db. It reads the same database Messages.app does, nothing more.

Does it work with SMS and group chats? Yes. Anything in Messages.app is fair game, including reactions and attachments.

Will it send something by accident? Not with the default ask setting: every send is a native dialog you have to click. demo-chat sets sending to off.

Contributing

The tool surface is one list in assistant.py: add a method and a schema entry and it appears in both the CLI and the MCP server. Run python3 -m unittest discover tests before opening a PR. MIT.

About

Your messages, your machine, your model. A fully local iMessage assistant (search, catch up, draft, send) for open-weight models via Ollama or MCP, plus local-first stats and memories over iMessage, Instagram and Google Chat. Nothing ever leaves your computer.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages