Your AI copilot that lives in iMessage — morning briefings, restaurant picks, goal tracking, and it remembers everything.
Built on Photon’s iMessage stack for the Photon residency bar: typed, resilient, and fast enough to feel like texting instead of waiting on an app.
| Intent | Example | Behavior |
|---|---|---|
| Morning briefing | gm |
Weather, calendar, and current priority in one message |
| Restaurant pick | cozy ramen spot in the mission for 2 |
One confident recommendation with a Maps link |
| Goal capture | I want to journal every night |
Creates a goal and schedules nightly check-ins |
| Check-in replies | yes did it |
Updates streaks and responds with encouragement |
| Progress | how am I doing |
Concise progress summary instead of a raw data dump |
| General chat | what's the weather |
Falls through to the chat agent when it is not a briefing |
| Safety / control | help, forget me, gibberish |
Capability list, memory wipe, or graceful fallback |
- A three-stage router handles obvious intents without paying for LLM calls, then falls back to LLM classification only when necessary.
- Every service boundary uses typed interfaces,
Result<T, E>return values, retries, timeouts, and circuit breakers where external calls can fail. - Message batching, deduplication, per-user spend protection, and graceful shutdown are built into the runtime path rather than layered on later.
flowchart LR
Entry[index.ts] --> Config[config/config.ts]
Entry --> Container[src/container.ts]
Entry --> Handler[src/router/message-handler.ts]
Handler --> Router[src/router/intent-router.ts]
Handler --> Agents[src/agents/*]
Agents --> Services[src/services/*]
Agents --> Memory[src/memory/*]
Services --> Utils[src/utils/*]
Memory --> Utils
Main runtime flow:
- Validate configuration at startup with Zod.
- Build the DI container and warm caches / stores.
- Start the scheduler for accountability check-ins.
- Start the Photon iMessage watcher.
- Route inbound messages through batching, dedup, intent resolution, and the selected agent.
- Runtime: Bun
- Language: TypeScript with branded domain types
- Transport: Photon
@photon-ai/imessage-kit - Memory / goals: SQLite
- LLMs: OpenAI, Gemini, or Anthropic Claude
- External data: OpenWeatherMap, Google Places, macOS Calendar
.
├── index.ts
├── config/
│ └── config.ts
├── src/
│ ├── agents/
│ ├── container.ts
│ ├── memory/
│ ├── router/
│ ├── services/
│ ├── types/
│ └── utils/
└── tests/
├── fakes/
└── unit/
Important directories:
src/agents: briefing, restaurant, accountability, and general chat behavior.src/services: adapters and resilience wrappers for LLM, weather, places, calendar, scheduler, and goal extraction.src/memory: persistent conversation memory, preferences, user location, and goal state.src/utils: batching, caching, retry, timeout, circuit breaker, logging, parsing, rate limiting, and dedup.
Copy .env.example to .env.
| Variable | Required | Notes |
|---|---|---|
LLM_PROVIDER |
no | openai, gemini, or anthropic — defaults to gemini |
OPENAI_API_KEY |
if openai |
OpenAI API key |
OPENAI_MODEL |
no | Defaults to gpt-4o |
OPENAI_MAX_TOKENS |
no | Defaults to 512 |
GOOGLE_AI_API_KEY |
if gemini |
Google AI Studio key |
GEMINI_MODEL |
no | Defaults to gemini-3-flash-preview |
GEMINI_MAX_TOKENS |
no | Defaults to 512 |
ANTHROPIC_API_KEY |
if anthropic |
Anthropic API key |
ANTHROPIC_MODEL |
no | Defaults to claude-sonnet-4-6 |
ANTHROPIC_MAX_TOKENS |
no | Defaults to 512 |
OPENWEATHER_API_KEY |
no | Enables weather in briefings (optional) |
GOOGLE_PLACES_API_KEY |
no | Enables Places API for restaurants |
MEMORY_DB_PATH |
no | Defaults to ./data/memory.sqlite |
MEMORY_WINDOW_SIZE |
no | Defaults to 20 |
AGENT_PERSONA |
no | System prompt for agent personality |
DEFAULT_LAT / DEFAULT_LON / DEFAULT_CITY |
no | Default fallback location |
OWNER_PHONE |
no | Reserved for owner-level flows |
ALLOWED_CONTACTS |
no | Comma-separated allowlist |
DEBUG |
no | true enables verbose logs |
git clone https://github.com/Spkap/imessage-copilot.git
cd imessage-copilot
bun install
cp .env.example .env
bun run devYou should see startup logs in this order:
Config validated
Initializing container
DB initialized
APIs healthy enough for startup
Watcher active
✅ iMessage Copilot is live
Requirements:
- macOS with Messages.app signed in.
- Full Disk Access configured so Photon can read Messages data.
- Valid API keys if you want live LLM, weather, and places behavior.
bun run typecheck
bun run lint
bun run test
bun run validateCurrent test status for this branch: 97 passing unit tests.
- No
anyin source. - Branded primitives for domain values such as phone numbers and coordinates.
- Exhaustive discriminated-union handling via
assertNever. - Zod startup validation for environment configuration.
- Structured logging with correlation-friendly metadata.
- Deduplication and batching to avoid double-sends and response spam.
- Graceful shutdown on
SIGINTandSIGTERM.
This build is tuned around one simple bar: the reply should feel like something you would actually want to receive in Messages. That means concise language, decisive recommendations, human confirmations, and fallback behavior that degrades safely instead of becoming robotic.
- Demo & Testing Guide — Quick walkthrough of key features
- Contributing Guide — Adding new agents and capabilities
- Architecture Decisions — Why we chose Bun, Result types, DI pattern, and more
Built on Photon’s iMessage SDK work:
@photon-ai/imessage-kit@photon-ai/advanced-imessage-kit