diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c79479de..6c2e8c912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Nix mode: opt-in declarative config + read-only settings UI when `CLAWDIS_NIX_MODE=1` (thanks @joshp123 for the persistence — earned my trust; I'll merge these going forward). - Agent runtime: accept legacy `Z_AI_API_KEY` for Z.AI provider auth (maps to `ZAI_API_KEY`). - Signal: add `signal-cli` JSON-RPC support for send/receive via the Signal provider. +- iMessage: add imsg JSON-RPC integration (stdio), chat_id routing, and group chat support. - Chat UI: add recent-session dropdown switcher (main first) in macOS/iOS/Android + Control UI. - Discord: allow agent-triggered reactions via `clawdis_discord` when enabled, and surface message ids in context. - Tests: add a Z.AI live test gate for smoke validation when keys are present. diff --git a/README.md b/README.md index b1bd9c6ac..a8cb7900b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@

**Clawdis** is a *personal AI assistant* you run on your own devices. -It answers you on the surfaces you already use (WhatsApp, Telegram, Discord, WebChat), can speak and listen on macOS/iOS, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant. +It answers you on the surfaces you already use (WhatsApp, Telegram, Discord, iMessage, WebChat), can speak and listen on macOS/iOS, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant. If you want a private, single-user assistant that feels local, fast, and always-on, this is it. @@ -45,7 +45,7 @@ Your surfaces ## What Clawdis does - **Personal assistant** — one user, one identity, one memory surface. -- **Multi-surface inbox** — WhatsApp, Telegram, Discord, WebChat, macOS, iOS. Signal support via `signal-cli` (see `docs/signal.md`). +- **Multi-surface inbox** — WhatsApp, Telegram, Discord, iMessage, WebChat, macOS, iOS. Signal support via `signal-cli` (see `docs/signal.md`). iMessage uses `imsg` (see `docs/imessage.md`). - **Voice wake + push-to-talk** — local speech recognition on macOS/iOS. - **Canvas** — a live visual workspace you can drive from the agent. - **Automation-ready** — browser control, media handling, and tool streaming. diff --git a/docs/agent-send.md b/docs/agent-send.md index 097f72d18..08f0124ec 100644 --- a/docs/agent-send.md +++ b/docs/agent-send.md @@ -18,4 +18,4 @@ read_when: - Output: - Default: prints text (and `MEDIA:` lines) to stdout. - `--json`: prints structured payloads + meta. -- Optional: `--deliver` sends the reply back to the selected provider (requires `--to` for WhatsApp). +- Optional: `--deliver` sends the reply back to the selected provider (`whatsapp`, `telegram`, `discord`, `signal`, `imessage`). diff --git a/docs/configuration.md b/docs/configuration.md index b23df2acc..421bf6a04 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -88,7 +88,7 @@ Allowlist of E.164 phone numbers that may trigger auto-replies. ### `routing.groupChat` -Group messages default to **require mention** (either metadata mention or regex patterns). +Group messages default to **require mention** (either metadata mention or regex patterns). Applies to WhatsApp, Telegram, Discord, and iMessage group chats. ```json5 { diff --git a/docs/cron.md b/docs/cron.md index eeb7b152a..f225f09e8 100644 --- a/docs/cron.md +++ b/docs/cron.md @@ -75,7 +75,7 @@ Each job is a JSON object with stable keys (unknown keys ignored for forward com - For `sessionTarget:"main"`, `wakeMode` controls whether we trigger the heartbeat immediately or just enqueue and wait. - `payload` (one of) - `{"kind":"systemEvent","text":string}` (enqueue as `System:`) - - `{"kind":"agentTurn","message":string,"deliver"?:boolean,"channel"?: "last"|"whatsapp"|"telegram","to"?:string,"timeoutSeconds"?:number}` + - `{"kind":"agentTurn","message":string,"deliver"?:boolean,"channel"?: "last"|"whatsapp"|"telegram"|"discord"|"signal"|"imessage","to"?:string,"timeoutSeconds"?:number}` - `isolation` (optional; only meaningful for isolated jobs) - `{"postToMainPrefix"?: string}` - `runtime` (optional) @@ -264,7 +264,7 @@ Add a `cron` command group (all commands should also support `--json` where sens - `--wake now|next-heartbeat` - payload flags (choose one): - `--system-event ""` - - `--message "" [--deliver] [--channel last|whatsapp|telegram|discord] [--to ]` + - `--message "" [--deliver] [--channel last|whatsapp|telegram|discord|signal|imessage] [--to ]` - `clawdis cron edit ...` (patch-by-flags, non-interactive) - `clawdis cron rm ` diff --git a/docs/groups.md b/docs/groups.md new file mode 100644 index 000000000..bc5aa96eb --- /dev/null +++ b/docs/groups.md @@ -0,0 +1,56 @@ +--- +summary: "Group chat behavior across surfaces (WhatsApp/Telegram/Discord/iMessage)" +read_when: + - Changing group chat behavior or mention gating +--- +# Groups + +Clawdis treats group chats consistently across surfaces: WhatsApp, Telegram, Discord, iMessage. + +## Session keys +- Group sessions use `group:` in `ctx.From`. +- Direct chats use the main session (or per-sender if configured). +- Heartbeats are skipped for group sessions. + +## Mention gating (default) +Group messages require a mention unless overridden per group. + +```json5 +{ + routing: { + groupChat: { + requireMention: true, + mentionPatterns: ["@clawd", "clawdbot", "\\+15555550123"], + historyLimit: 50 + } + } +} +``` + +Notes: +- `mentionPatterns` are case-insensitive regexes. +- Surfaces that provide explicit mentions still pass; patterns are a fallback. + +## Activation (owner-only) +Group owners can toggle per-group activation: +- `/activation mention` +- `/activation always` + +Owner is determined by `routing.allowFrom` (or the bot’s default identity when unset). + +## Context fields +Group inbound payloads set: +- `ChatType=group` +- `GroupSubject` (if known) +- `GroupMembers` (if known) +- `WasMentioned` (mention gating result) + +The agent system prompt includes a group intro on the first turn of a new group session. + +## iMessage specifics +- Prefer `chat_id:` when routing or allowlisting. +- List chats: `imsg chats --limit 20`. +- Group replies always go back to the same `chat_id`. + +## WhatsApp specifics +See `docs/group-messages.md` for WhatsApp-only behavior (history injection, mention handling details). diff --git a/docs/imessage.md b/docs/imessage.md new file mode 100644 index 000000000..ff0cf9e23 --- /dev/null +++ b/docs/imessage.md @@ -0,0 +1,63 @@ +--- +summary: "iMessage support via imsg (JSON-RPC over stdio), setup, and chat_id routing" +read_when: + - Setting up iMessage support + - Debugging iMessage send/receive +--- +# iMessage (imsg) + +Status: external CLI integration. No daemon. + +## Model +- Clawdis spawns `imsg rpc` as a child process. +- JSON-RPC runs over stdin/stdout (one JSON object per line). +- Gateway owns the process; no TCP port needed. + +## Requirements +- macOS with Messages signed in. +- Full Disk Access for Clawdis + the `imsg` binary (Messages DB access). +- Automation permission for Messages when sending. + +## Config + +```json5 +{ + imessage: { + enabled: true, + cliPath: "imsg", + dbPath: "~/Library/Messages/chat.db", + allowFrom: ["+15555550123", "user@example.com", "chat_id:123"], + includeAttachments: false, + mediaMaxMb: 16, + service: "auto", + region: "US" + } +} +``` + +Notes: +- `allowFrom` accepts handles (phone/email) or `chat_id:` entries. +- `service` defaults to `auto` (use `imessage` or `sms` to pin). +- `region` is only used for SMS targeting. + +## Addressing / targets + +Prefer `chat_id` for stable routing: +- `chat_id:123` (preferred) +- `chat_guid:...` (fallback) +- `chat_identifier:...` (fallback) +- direct handles: `imessage:+1555` / `sms:+1555` / `user@example.com` + +List chats: +``` +imsg chats --limit 20 +``` + +## Group chat behavior +- Group messages set `ChatType=group`, `GroupSubject`, and `GroupMembers`. +- Group activation respects `routing.groupChat.requireMention` and `mentionPatterns`. +- Replies go back to the same `chat_id` (group or direct). + +## Troubleshooting +- `clawdis gateway call providers.status --params '{"probe":true}'` +- Verify `imsg` is on PATH and has access to Messages DB. diff --git a/docs/index.md b/docs/index.md index 98bbc0e71..72ede08e6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,7 +13,7 @@ read_when:

- WhatsApp + Telegram + Discord gateway for AI agents (Pi).
+ WhatsApp + Telegram + Discord + iMessage gateway for AI agents (Pi).
Send a message, get an agent response — from your pocket.

@@ -23,7 +23,7 @@ read_when: Clawd setup

-CLAWDIS bridges WhatsApp (via WhatsApp Web / Baileys), Telegram (Bot API / grammY), and Discord (Bot API / discord.js) to coding agents like [Pi](https://github.com/badlogic/pi-mono). +CLAWDIS bridges WhatsApp (via WhatsApp Web / Baileys), Telegram (Bot API / grammY), Discord (Bot API / discord.js), and iMessage (imsg CLI) to coding agents like [Pi](https://github.com/badlogic/pi-mono). It’s built for [Clawd](https://clawd.me), a space lobster who needed a TARDIS. ## How it works @@ -61,6 +61,7 @@ Most operations flow through the **Gateway** (`clawdis gateway`), a single long- - 📱 **WhatsApp Integration** — Uses Baileys for WhatsApp Web protocol - ✈️ **Telegram Bot** — DMs + groups via grammY - 🎮 **Discord Bot** — DMs + guild channels via discord.js +- 💬 **iMessage** — Local imsg CLI integration (macOS) - 🤖 **Agent bridge** — Pi (RPC mode) with tool streaming - 💬 **Sessions** — Direct chats collapse into shared `main` (default); groups are isolated - 👥 **Group Chat Support** — Mention-based by default; owner can toggle `/activation always|mention` @@ -121,6 +122,7 @@ Example: - [Skills](./skills.md) - [Skills config](./skills-config.md) - [Workspace templates](./templates/AGENTS.md) + - [RPC adapters](./rpc.md) - [Gateway runbook](./gateway.md) - [Nodes (iOS/Android)](./nodes.md) - [Web surfaces (Control UI)](./web.md) @@ -131,7 +133,9 @@ Example: - [Control UI (browser)](./control-ui.md) - [Telegram](./telegram.md) - [Discord](./discord.md) - - [Group messages](./group-messages.md) + - [iMessage](./imessage.md) + - [Groups](./groups.md) + - [WhatsApp group messages](./group-messages.md) - [Media: images](./images.md) - [Media: audio](./audio.md) - Ops and safety: diff --git a/docs/rpc.md b/docs/rpc.md new file mode 100644 index 000000000..08ff5abd4 --- /dev/null +++ b/docs/rpc.md @@ -0,0 +1,35 @@ +--- +summary: "RPC adapters for external CLIs (signal-cli, imsg) and gateway patterns" +read_when: + - Adding or changing external CLI integrations + - Debugging RPC adapters (signal-cli, imsg) +--- +# RPC adapters + +Clawdis integrates external CLIs via JSON-RPC. Two patterns are used today. + +## Pattern A: HTTP daemon (signal-cli) +- `signal-cli` runs as a daemon with JSON-RPC over HTTP. +- Event stream is SSE (`/api/v1/events`). +- Health probe: `/api/v1/check`. +- Clawdis owns lifecycle when `signal.autoStart=true`. + +See `docs/signal.md` for setup and endpoints. + +## Pattern B: stdio child process (imsg) +- Clawdis spawns `imsg rpc` as a child process. +- JSON-RPC is line-delimited over stdin/stdout (one JSON object per line). +- No TCP port, no daemon required. + +Core methods used: +- `watch.subscribe` → notifications (`method: "message"`) +- `watch.unsubscribe` +- `send` +- `chats.list` (probe/diagnostics) + +See `docs/imessage.md` for setup and addressing (`chat_id` preferred). + +## Adapter guidelines +- Gateway owns the process (start/stop tied to provider lifecycle). +- Keep RPC clients resilient: timeouts, restart on exit. +- Prefer stable IDs (e.g., `chat_id`) over display strings. diff --git a/docs/surface.md b/docs/surface.md index 2967d9afc..9135e80bc 100644 --- a/docs/surface.md +++ b/docs/surface.md @@ -9,12 +9,12 @@ Updated: 2025-12-07 Goal: make replies deterministic per channel while keeping one shared context for direct chats. -- **Surfaces** (channel labels): `whatsapp`, `webchat`, `telegram`, `discord`, `voice`, etc. Add `Surface` to inbound `MsgContext` so templates/agents can log which channel a turn came from. Routing is fixed: replies go back to the origin surface; the model doesn’t choose. +- **Surfaces** (channel labels): `whatsapp`, `webchat`, `telegram`, `discord`, `imessage`, `voice`, etc. Add `Surface` to inbound `MsgContext` so templates/agents can log which channel a turn came from. Routing is fixed: replies go back to the origin surface; the model doesn’t choose. - **Reply context:** inbound replies include `ReplyToId`, `ReplyToBody`, and `ReplyToSender`, and the quoted context is appended to `Body` as a `[Replying to ...]` block. - **Canonical direct session:** All direct chats collapse into the single `main` session by default (no config needed). Groups stay `group:`, so they remain isolated. - **Session store:** Keys are resolved via `resolveSessionKey(scope, ctx, mainKey)`; the agent JSONL path lives under `~/.clawdis/sessions/.jsonl`. - **WebChat:** Always attaches to `main`, loads the full session transcript so desktop reflects cross-surface history, and writes new turns back to the same session. - **Implementation hints:** - - Set `Surface` in each ingress (WhatsApp gateway, WebChat bridge, Telegram, Discord). + - Set `Surface` in each ingress (WhatsApp gateway, WebChat bridge, Telegram, Discord, iMessage). - Keep routing deterministic: originate → same surface. Use the gateway WebSocket for sends; avoid side channels. - Do not let the agent emit “send to X” decisions; keep that policy in the host code. diff --git a/docs/wizard.md b/docs/wizard.md index cccd74bda..3e319fd6d 100644 --- a/docs/wizard.md +++ b/docs/wizard.md @@ -65,6 +65,7 @@ It does **not** install or change anything on the remote host. - Telegram: bot token. - Discord: bot token. - Signal: optional `signal-cli` install + account config. + - iMessage: local `imsg` CLI path + DB access. 6) **Daemon install** - macOS: LaunchAgent @@ -131,7 +132,7 @@ Typical fields in `~/.clawdis/clawdis.json`: - `agent.workspace` - `agent.model` / `models.providers` (if Minimax chosen) - `gateway.*` (mode, bind, auth, tailscale) -- `telegram.botToken`, `discord.token`, `signal.*` +- `telegram.botToken`, `discord.token`, `signal.*`, `imessage.*` - `skills.install.nodeManager` - `wizard.lastRunAt` - `wizard.lastRunVersion` @@ -146,5 +147,5 @@ Sessions are stored under `~/.clawdis/sessions/`. - macOS app onboarding: `docs/onboarding.md` - Config reference: `docs/configuration.md` -- Providers: `docs/whatsapp.md`, `docs/telegram.md`, `docs/discord.md`, `docs/signal.md` +- Providers: `docs/whatsapp.md`, `docs/telegram.md`, `docs/discord.md`, `docs/signal.md`, `docs/imessage.md` - Skills: `docs/skills.md`, `docs/skills-config.md`