iMessage (imsg)
Status: external CLI integration. Gateway spawns imsg rpc (JSON-RPC over stdio).
Quick setup (beginner)
Section titled “Quick setup (beginner)”- Ensure Messages is signed in on this Mac.
- Install
imsg:brew install steipete/tap/imsg
- Configure Kurahi with
channels.imessage.cliPathandchannels.imessage.dbPath. - Start the gateway and approve any macOS prompts (Automation + Full Disk Access).
Minimal config:
{ channels: { imessage: { enabled: true, cliPath: "/usr/local/bin/imsg", dbPath: "/Users/<you>/Library/Messages/chat.db", }, },}What it is
Section titled “What it is”- iMessage channel backed by
imsgon macOS. - Deterministic routing: replies always go back to iMessage.
- DMs share the agent’s main session; groups are isolated (
agent:<agentId>:imessage:group:<chat_id>). - If a multi-participant thread arrives with
is_group=false, you can still isolate it bychat_idusingchannels.imessage.groups(see “Group-ish threads” below).
Config writes
Section titled “Config writes”By default, iMessage is allowed to write config updates triggered by /config set|unset (requires commands.config: true).
Disable with:
{ channels: { imessage: { configWrites: false } },}Requirements
Section titled “Requirements”- macOS with Messages signed in.
- Full Disk Access for Kurahi +
imsg(Messages DB access). - Automation permission when sending.
channels.imessage.cliPathcan point to any command that proxies stdin/stdout (for example, a wrapper script that SSHes to another Mac and runsimsg rpc).
Setup (fast path)
Section titled “Setup (fast path)”- Ensure Messages is signed in on this Mac.
- Configure iMessage and start the gateway.
Dedicated bot macOS user (for isolated identity)
Section titled “Dedicated bot macOS user (for isolated identity)”If you want the bot to send from a separate iMessage identity (and keep your personal Messages clean), use a dedicated Apple ID + a dedicated macOS user.
- Create a dedicated Apple ID (example:
my-cool-bot@icloud.com).- Apple may require a phone number for verification / 2FA.
- Create a macOS user (example:
KoLohome) and sign into it. - Open Messages in that macOS user and sign into iMessage using the bot Apple ID.
- Enable Remote Login (System Settings → General → Sharing → Remote Login).
- Install
imsg:brew install steipete/tap/imsg
- Set up SSH so
ssh <bot-macos-user>@localhost trueworks without a password. - Point
channels.imessage.accounts.bot.cliPathat an SSH wrapper that runsimsgas the bot user.
First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the bot macOS user. If imsg rpc looks stuck or exits, log into that user (Screen Sharing helps), run a one-time imsg chats --limit 1 / imsg send ..., approve prompts, then retry.
Example wrapper (chmod +x). Replace <bot-macos-user> with your actual macOS username:
#!/usr/bin/env bashset -euo pipefail
# Run an interactive SSH once first to accept host keys:# ssh <bot-macos-user>@localhost trueexec /usr/bin/ssh -o BatchMode=yes -o ConnectTimeout=5 -T <bot-macos-user>@localhost \ "/usr/local/bin/imsg" "$@"Example config:
{ channels: { imessage: { enabled: true, accounts: { bot: { name: "Bot", enabled: true, cliPath: "/path/to/imsg-bot", dbPath: "/Users/<bot-macos-user>/Library/Messages/chat.db", }, }, }, },}For single-account setups, use flat options (channels.imessage.cliPath, channels.imessage.dbPath) instead of the accounts map.
Remote/SSH variant (optional)
Section titled “Remote/SSH variant (optional)”If you want iMessage on another Mac, set channels.imessage.cliPath to a wrapper that runs imsg on the remote macOS host over SSH. Kurahi only needs stdio.
Example wrapper:
#!/usr/bin/env bashexec ssh -T gateway-host imsg "$@"Remote attachments: When cliPath points to a remote host via SSH, attachment paths in the Messages database reference files on the remote machine. Kurahi can automatically fetch these over SCP by setting channels.imessage.remoteHost:
{ channels: { imessage: { cliPath: "~/imsg-ssh", // SSH wrapper to remote Mac remoteHost: "user@gateway-host", // for SCP file transfer includeAttachments: true, }, },}If remoteHost is not set, Kurahi attempts to auto-detect it by parsing the SSH command in your wrapper script. Explicit configuration is recommended for reliability.
Remote Mac via Tailscale (example)
Section titled “Remote Mac via Tailscale (example)”If the Gateway runs on a Linux host/VM but iMessage must run on a Mac, Tailscale is the simplest bridge: the Gateway talks to the Mac over the tailnet, runs imsg via SSH, and SCPs attachments back.
Architecture:
┌──────────────────────────────┐ SSH (imsg rpc) ┌──────────────────────────┐│ Gateway host (Linux/VM) │──────────────────────────────────▶│ Mac with Messages + imsg ││ - kurahi gateway │ SCP (attachments) │ - Messages signed in ││ - channels.imessage.cliPath │◀──────────────────────────────────│ - Remote Login enabled │└──────────────────────────────┘ └──────────────────────────┘ ▲ │ Tailscale tailnet (hostname or 100.x.y.z) ▼ user@gateway-hostConcrete config example (Tailscale hostname):
{ channels: { imessage: { enabled: true, cliPath: "~/.kolo/scripts/imsg-ssh", remoteHost: "bot@mac-mini.tailnet-1234.ts.net", includeAttachments: true, dbPath: "/Users/bot/Library/Messages/chat.db", }, },}Example wrapper (~/.kolo/scripts/imsg-ssh):
#!/usr/bin/env bashexec ssh -T bot@mac-mini.tailnet-1234.ts.net imsg "$@"Notes:
- Ensure the Mac is signed in to Messages, and Remote Login is enabled.
- Use SSH keys so
ssh bot@mac-mini.tailnet-1234.ts.networks without prompts. remoteHostshould match the SSH target so SCP can fetch attachments.
Multi-account support: use channels.imessage.accounts with per-account config and optional name. See gateway/configuration for the shared pattern. Don’t commit ~/.kolo/kolo.json (it often contains tokens).
Access control (DMs + groups)
Section titled “Access control (DMs + groups)”DMs:
- Default:
channels.imessage.dmPolicy = "pairing". - Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
- Approve via:
kurahi pairing list imessagekurahi pairing approve imessage <CODE>
- Pairing is the default token exchange for iMessage DMs. Details: Pairing
Groups:
channels.imessage.groupPolicy = open | allowlist | disabled.channels.imessage.groupAllowFromcontrols who can trigger in groups whenallowlistis set.- Mention gating uses
agents.list[].groupChat.mentionPatterns(ormessages.groupChat.mentionPatterns) because iMessage has no native mention metadata. - Multi-agent override: set per-agent patterns on
agents.list[].groupChat.mentionPatterns.
How it works (behavior)
Section titled “How it works (behavior)”imsgstreams message events; the gateway normalizes them into the shared channel envelope.- Replies always route back to the same chat id or handle.
Group-ish threads (is_group=false)
Section titled “Group-ish threads (is_group=false)”Some iMessage threads can have multiple participants but still arrive with is_group=false depending on how Messages stores the chat identifier.
If you explicitly configure a chat_id under channels.imessage.groups, Kurahi treats that thread as a “group” for:
- session isolation (separate
agent:<agentId>:imessage:group:<chat_id>session key) - group allowlisting / mention gating behavior
Example:
{ channels: { imessage: { groupPolicy: "allowlist", groupAllowFrom: ["+15555550123"], groups: { "42": { requireMention: false }, }, }, },}This is useful when you want an isolated personality/model for a specific thread (see Multi-agent routing). For filesystem isolation, see Sandboxing.
Media + limits
Section titled “Media + limits”- Optional attachment ingestion via
channels.imessage.includeAttachments. - Media cap via
channels.imessage.mediaMaxMb.
Limits
Section titled “Limits”- Outbound text is chunked to
channels.imessage.textChunkLimit(default 4000). - Optional newline chunking: set
channels.imessage.chunkMode="newline"to split on blank lines (paragraph boundaries) before length chunking. - Media uploads are capped by
channels.imessage.mediaMaxMb(default 16).
Addressing / delivery targets
Section titled “Addressing / delivery targets”Prefer chat_id for stable routing:
chat_id:123(preferred)chat_guid:...chat_identifier:...- direct handles:
imessage:+1555/sms:+1555/user@example.com
List chats:
imsg chats --limit 20Configuration reference (iMessage)
Section titled “Configuration reference (iMessage)”Full configuration: Configuration
Provider options:
channels.imessage.enabled: enable/disable channel startup.channels.imessage.cliPath: path toimsg.channels.imessage.dbPath: Messages DB path.channels.imessage.remoteHost: SSH host for SCP attachment transfer whencliPathpoints to a remote Mac (e.g.,user@gateway-host). Auto-detected from SSH wrapper if not set.channels.imessage.service:imessage | sms | auto.channels.imessage.region: SMS region.channels.imessage.dmPolicy:pairing | allowlist | open | disabled(default: pairing).channels.imessage.allowFrom: DM allowlist (handles, emails, E.164 numbers, orchat_id:*).openrequires"*". iMessage has no usernames; use handles or chat targets.channels.imessage.groupPolicy:open | allowlist | disabled(default: allowlist).channels.imessage.groupAllowFrom: group sender allowlist.channels.imessage.historyLimit/channels.imessage.accounts.*.historyLimit: max group messages to include as context (0 disables).channels.imessage.dmHistoryLimit: DM history limit in user turns. Per-user overrides:channels.imessage.dms["<handle>"].historyLimit.channels.imessage.groups: per-group defaults + allowlist (use"*"for global defaults).channels.imessage.includeAttachments: ingest attachments into context.channels.imessage.mediaMaxMb: inbound/outbound media cap (MB).channels.imessage.textChunkLimit: outbound chunk size (chars).channels.imessage.chunkMode:length(default) ornewlineto split on blank lines (paragraph boundaries) before length chunking.
Related global options:
agents.list[].groupChat.mentionPatterns(ormessages.groupChat.mentionPatterns).messages.responsePrefix.