Text your Mac. Run an AI agent.
A Swift daemon that turns iMessage into a remote terminal for Claude Code. Send a message from your phone, your Mac picks it up, spawns Claude Code in your project directory, and texts you back the result.
No terminal. No SSH. No laptop open. Just iMessage.
iPhone (iMessage) → Mac (iRelay daemon) → Claude Code → response → iMessage
Send something like irelayy fix the failing test from your phone. Your Mac runs Claude Code against your project and texts you back what it did.
No web UI, no port forwarding, no VPN. iMessage as a transport layer.
Important: The Mac running iRelay must be signed into a different Apple ID than the device you message it from. You're sending an iMessage between two separate accounts — your phone texts the Mac, not itself.
Each response is prefixed with session info: iRelay [session-id] #message-number (context-size). Sessions persist across messages so context carries over. Built-in commands:
irelayy clear/reset— wipe session and start a new one (new session ID)irelayy save— persist session to~/.irelay/sessions/as markdown
block-beta
columns 1
block:gateway["iRelay Gateway (Hummingbird HTTP Server)"]
columns 3
SM["Session Manager"] AR["Agent Router"] CS["Cron Scheduler"]
end
block:channels["Channels"]
columns 5
iMessage Telegram Slack Discord more1["..."]
end
block:providers["LLM Providers"]
columns 4
Claude OpenAI Ollama Gemini
end
Storage["Storage (GRDB/SQLite)"]
| Channel | Status | Transport |
|---|---|---|
| iMessage | ✅ Tested | Messages.framework / AppleScript |
| Telegram | 🚧 Untested | Bot API (HTTPS + long polling / webhook) |
| Slack | 🚧 Untested | Web API + Events API / Socket Mode |
| Discord | REST send works, Gateway receive TBD | |
| Signal | 🚧 Untested | signal-cli subprocess |
| Matrix | 🚧 Untested | Client-Server API + /sync polling |
| IRC | 🚧 Untested | Raw TCP (Foundation streams) |
| WebChat | 🚧 Untested | Hummingbird REST + HTML UI |
| 🚧 Untested | Business Cloud API + webhook |
Not planned: Email, SMS/Twilio, Microsoft Teams, Google Chat, Facebook Messenger, Mattermost, Line, Viber, Zulip.
| Provider | Status | Streaming |
|---|---|---|
| Claude | ✅ Tested | SSE via URLSession |
| OpenAI | 🚧 Untested | SSE via URLSession |
| Ollama | 🚧 Untested | NDJSON (no tool support) |
| Gemini | 🚧 Untested | SSE via URLSession |
Only Claude is wired into serve and agent-bridge commands today. The other three providers compile and implement the LLMProvider protocol but are not yet registered in any CLI command.
| Module | Status | Notes |
|---|---|---|
| Gateway | ✅ Working | Hummingbird HTTP (webhooks, health, status) |
| Sessions | ✅ Working | GRDB-backed, persistent |
| Agents | ✅ Working | Routing, history, streaming |
| AgentSpawner | ✅ Working | Subprocess execution with concurrency limits |
| Storage | ✅ Working | GRDB/SQLite with migrations |
| Networking | ✅ Working | HTTP client + SSE streaming |
| Security | ✅ Working | macOS Keychain |
| Services | ✅ Working | Full orchestration pipeline |
| Voice | ✅ Working | macOS say command |
| MCPSupport | ✅ Working | JSON-RPC stdio client |
| Memory | FTS5 search works, vector embeddings TBD | |
| Scheduling | Interval/daily works, cron parsing TBD |
make build # release build
make build-debug # debug build
make test # run tests
make lint # swiftlint
make format # swiftformat
make install # install to /usr/local/binirelay serve # start gateway + all channels
irelay chat # interactive CLI chat
irelay agent-bridge # iMessage ↔ Claude Code bridgeTBD:
irelay config— manage agents, channels, providers (#46)irelay status— show gateway + channel status (#47)irelay daemon install/uninstall— LaunchAgent management (#48)
TBD:
Extreme Packaging — single Package.swift in Packages/, Main.xcworkspace at root. 29 SPM library targets, 1 executable.
iRelay/
├── Packages/
│ ├── Sources/
│ │ ├── Shared/ # Models, config, constants
│ │ ├── Storage/ # GRDB/SQLite persistence
│ │ ├── Networking/ # HTTP, SSE, WebSocket helpers
│ │ ├── Gateway/ # Hummingbird HTTP server
│ │ ├── Sessions/ # Session management + routing
│ │ ├── Agents/ # Agent config + multi-agent routing
│ │ ├── ChannelKit/ # Channel protocol (abstraction)
│ │ ├── IMessageChannel/ # Native iMessage
│ │ ├── TelegramChannel/ # Telegram Bot API
│ │ ├── ... # 7 more channel implementations
│ │ ├── ProviderKit/ # LLM provider protocol (abstraction)
│ │ ├── ClaudeProvider/ # Anthropic Messages API
│ │ ├── ... # 3 more provider implementations
│ │ ├── Voice/ # macOS TTS
│ │ ├── Memory/ # FTS search + recall
│ │ └── CLI/ # ArgumentParser entry point
│ └── Tests/
├── Apps/ # macOS + iOS app targets (TBD)
└── Main.xcworkspace
See ARCHITECTURE.md for full details — protocols, dependency graph, and implementation notes.
- Swift 6.0+
- macOS 14+ (Sonoma) / iOS 17+
- Xcode 16+
Two daemons on the same Mac listening to iMessage would fight over every message. iRelay claims any message starting with irelayy (double y). Everything else gets ignored. Simple namespace partitioning over a shared channel.
Read the full writeup: iRelay: Text Your Mac, Run an AI Agent
MIT
