add-imessage
This Claude Code skill integrates iMessage messaging into NanoClaw via the Chat SDK by copying the iMessage adapter from a separate branch and installing its dependencies. Use this when adding native iMessage channel support to a NanoClaw instance, selecting either local mode for macOS with Full Disk Access permissions or remote mode via the Photon API for other platforms.
git clone --depth 1 https://github.com/nanocoai/nanoclaw /tmp/add-imessage && cp -r /tmp/add-imessage/.claude/skills/add-imessage ~/.claude/skills/add-imessageSKILL.md
# Add iMessage
NanoClaw talks to iMessage through a single **`imessage`** channel with two
pluggable backends:
- **Local (this Mac)** — the Chat SDK bridge over `chat-adapter-imessage`,
reading this Mac's signed-in iMessage account (`chat.db`). macOS only; the
Node binary needs Full Disk Access.
- **Hosted iMessage (via photon.codes)** — a native adapter over Photon's
`spectrum-ts` gRPC stream. The hosted service owns the iMessage line, so
there's no Mac relay, webhook, or public URL. Works on any OS, and a
device-login flow provisions everything for you.
Both register the same `imessage` channel type; only one runs per install.
NanoClaw doesn't ship channels in trunk — this skill copies the unified
`imessage` adapter in from the `channels` branch. Full reference:
[docs/imessage.md](docs.md).
The mechanical steps under **Apply** carry `nc:` directive fences: an agent reads
the prose and applies them, and a parser can apply them deterministically from
the same document. Every directive is idempotent, so the whole skill is safe to
re-run; anything a parser can't apply falls back to the prose beside it.
## Apply
### 1. Choose a backend
Pick the backend first — it decides which package gets installed and which
walkthrough runs below (the other backend's steps are skipped):
```nc:prompt backend validate:^(local|hosted)$
How should iMessage run — `local` (this Mac's signed-in iMessage account; macOS only, needs Full Disk Access) or `hosted` (a managed line via photon.codes; works on any OS)?
```
The local backend only works on a Mac — it reads this machine's iMessage
`chat.db` directly, and there is no such database off macOS. On any other OS,
stop here and choose `hosted` instead; otherwise you'd write a local config
that can never receive a message:
```nc:run effect:check when:backend=local
[ "$(uname)" = Darwin ]
```
### 2. Copy the adapter
Fetch the `channels` branch and copy the unified iMessage adapter and its tests
into `src/channels/`:
```nc:copy from-branch:channels
src/channels/imessage.ts
src/channels/imessage.test.ts
src/channels/imessage-registration.test.ts
```
### 3. Register the adapter
Append the self-registration import to the channel barrel (skipped if the line
is already present). This one line is the skill's only reach-in into core:
```nc:append to:src/channels/index.ts
import './imessage.js';
```
### 4. Install the chosen backend's package
Pinned to an exact version — the supply-chain policy rejects ranges and
`latest`. Install only the chosen backend's package.
**Local** — the Chat SDK iMessage adapter:
```nc:dep when:backend=local
chat-adapter-imessage@0.1.1
```
**Hosted** — Photon's Spectrum SDK:
```nc:dep when:backend=hosted
spectrum-ts@11.0.0
```
> Pin exactly. `spectrum-ts` ships breaking majors (v11 is what the adapter
> targets); don't `@latest`. NanoClaw's pnpm gate (`minimumReleaseAge`) requires
> a version ≥3 days old — both pins clear it. A fresher pin needs human sign-off
> before a `minimumReleaseAgeExclude` entry (CLAUDE.md → Supply Chain Security).
### 5. Build and validate
Build guards the typed `createChatSdkBridge(...)` core call used by the local
backend, and the registration test proves the channel is wired:
```nc:run effect:build
pnpm run build
```
```nc:run effect:test
pnpm exec vitest run src/channels/imessage-registration.test.ts
```
Both must be clean. `imessage-registration.test.ts` imports the real channel
barrel and asserts the registry contains `imessage` — it goes red if the
`import './imessage.js';` line is missing or the barrel fails to evaluate. The
adapter loads neither backend's SDK at import (hosted `spectrum-ts` only in
`setup()`, local `chat-adapter-imessage` only in the factory), so the test
needs no package.
For the hosted backend, also run the full adapter suite — it includes an
integration block that exercises the real installed `spectrum-ts` (version,
exports, builders) and auto-skips when the package is absent:
```nc:run effect:test when:backend=hosted
pnpm exec vitest run src/channels/imessage.test.ts
```
## Local backend: Full Disk Access (macOS)
The adapter reads this Mac's `chat.db`, which requires Full Disk Access granted
to the Node binary the host runs under. The Node path is buried deep (e.g.
`~/.nvm/versions/node/v22.x.x/bin/node`), so open its folder in Finder to make
the drag-and-drop target obvious. Harmless off a desktop (SSH/headless) — it
just no-ops:
```nc:run effect:external when:backend=local
open "$(dirname "$(which node)")" 2>/dev/null || true
```
Then tell the user:
```nc:operator when:backend=local
Grant Full Disk Access to Node so iMessage can read your chat history:
1. Open System Settings > Privacy & Security > Full Disk Access.
2. Click +, then drag the "node" file from the Finder window that just opened.
3. Toggle it on, then come back here.
```
Stop and wait for the user to confirm Full Disk Access is granted before
continuing.
Now select the local backend in `.env`. The configure script owns this
upsert-and-remove (a plain set-if-absent env write can neither replace a stale
value nor delete a key, and a lingering hosted selector would shadow the
choice):
```nc:run effect:external when:backend=local
bash setup/channels/imessage-configure.sh local
```
## Hosted backend: device login (via photon.codes)
The provisioning flow needs the phone number you send iMessages from — it
registers that number with your project so the hosted line recognises you:
```nc:prompt owner_handle normalize:trim validate:^\+\d{8,15}$ when:backend=hosted
The phone number you iMessage from, in E.164 format — + followed by country code and number, no spaces or dashes (e.g. +14155551234).
```
Tell the user what's about to happen:
```nc:operator when:backend=hosted
Connect your hosted iMessage line (photon.codes):
1. A login URL and a short code will print below.
2. Open the URL in a browser, approve the device, and enter the code.
3. Setup then registers your number and printsAdd Atomic Chat MCP server so the container agent can call local models served by the Atomic Chat desktop app via its OpenAI-compatible API.
Use Codex (OpenAI's codex app-server) as a full agent provider — planning, tool orchestration, MCP tools, server-side history, session resume — alongside or instead of Claude. ChatGPT subscription or OpenAI API key, vault-only via OneCLI. Per-group via `ncl groups config update --provider codex`. Distinct from using OpenAI as an MCP tool (where Claude remains the planner).
Add a monitoring dashboard to NanoClaw. Installs @nanoco/nanoclaw-dashboard and a pusher that sends periodic JSON snapshots.
Add DeltaChat channel integration via @deltachat/stdio-rpc-server. Native adapter — no Chat SDK bridge. Email-based messaging with end-to-end encryption.
Add Discord bot channel integration via Chat SDK.
Add Emacs as a channel. Opens an interactive chat buffer and org-mode integration so you can talk to NanoClaw from within Emacs (Doom, Spacemacs, or vanilla). Local HTTP bridge — no bot token or external service needed.
Add Google Calendar as an MCP tool (list calendars, list/search/create events, free/busy queries) using OneCLI-managed OAuth. Multi-calendar and multi-account supported. Mirrors /add-gmail-tool's stub pattern — no raw credentials ever reach the container; OneCLI injects real tokens at request time.
Add Google Chat channel integration via Chat SDK.