A macOS menu-bar daemon that lets you drive Claude Code by texting iMessage from your iPhone, Apple Watch, iPad, or CarPlay.
Uses your existing Claude Code subscription via the Claude CLI.
- You iMessage yourself (from iPhone, Watch, Mac, wherever).
- Apple's sync replicates the message to your Mac's
chat.db. - Echo notices via FSEvents, picks up the new row.
- Echo shells out to
claude -p <prompt> --resume <session_id>— Claude Code handles everything: conversation state, tools, file edits, permissions, git, all of it. - Echo streams the response back as iMessage(s).
- Apple syncs the replies to every device you own.
No servers, no webhooks, no open ports, no API keys. Your Mac + your Claude Code subscription.
- macOS 13+ (Ventura or later)
- Claude Code installed and logged in
- Swift toolchain (comes with Xcode Command Line Tools)
swift build -c releaseBinary: .build/release/echo
.build/release/echo runFirst run writes a template to ~/.echo/config.json and exits. Fill it in:
{
"chat_identifier": "+15551234567",
"chat_db_path": "/Users/you/Library/Messages/chat.db",
"claude_binary": "claude",
"project_dir": "/Users/you/Documents/Projects",
"permission_mode": "bypassPermissions",
"model": null,
"daily_budget_usd": 10.00
}- chat_identifier — handle Echo listens on and replies to. Typically your own phone number or email. Find with:
sqlite3 ~/Library/Messages/chat.db "SELECT chat_identifier, display_name FROM chat ORDER BY ROWID DESC LIMIT 10"
- project_dir — working directory
clauderuns in. Pick the project you want Echo to operate on. Claude can alwayscdelsewhere via bash. - permission_mode — one of
default,acceptEdits,plan,bypassPermissions. For unattended iMessage use,bypassPermissionsis what you want (otherwise Claude will hang waiting for a prompt that never comes). - model —
nulluses Claude Code's default. Set a specific id if you want. - daily_budget_usd — soft cap. Warns at 80%, pauses at 100%.
- Full Disk Access — System Settings → Privacy & Security → Full Disk Access → add the
echobinary. Required to readchat.db. - Automation → Messages — granted on first send; you'll see a prompt.
.build/release/echo runMenu bar shows ◎. Text the configured number from any Apple device. Expect a reply in 3–10 seconds depending on the task.
.build/release/echo installWrites a launchd plist and bootstraps it. Echo starts automatically at login.
.build/release/echo uninstall # to remove/help list commands
/new <text> force a new session
/end close the current session
/budget show today's spend
/remember <text> append to ~/.echo/memory.md
/forget wipe memory (backed up)
User-defined macros: drop ~/.echo/macros.json like
{ "standup": "What did I work on yesterday? Summarize git activity from the last 24h." }Then text /standup to expand to that prompt.
Every top-level (non-reply) iMessage starts a new Claude Code session. Reply-to any message in a thread from your phone → Echo resumes that session via claude --resume. Claude Code owns the conversation state; Echo just stores the opaque session id.
~/.echo/memory.md is passed to Claude on every call via --append-system-prompt. Edit freely, or use /remember and /forget over iMessage. Useful for "I prefer terse replies" or "when I say 'the app', I mean the Swift project at …".
tail -f ~/.echo/logs/echo.log