cli, an always-on local-terminal channel that needs no credentials. Every platform adapter (Telegram, Discord, Slack, WhatsApp, …) lives on the channels branch of the repo and is installed on demand with an /add-<channel> skill. Your fork only contains the channels you actually use.
How a message flows
Adapters self-register with the channel registry on import. At startup the host instantiates every registered adapter whose credentials are present (a factory that finds no credentials returnsnull and the channel is skipped with a warning). From there, every channel takes the same path:
The adapter normalizes platform events into inbound messages; the router matches them to a wiring (which agent group handles which chat) and writes them into the right session; the agent’s reply drains through the delivery module, which looks up the owning adapter in the registry — by the messaging group’s instance (which defaults to the channel type) — and calls deliver(), so a reply always leaves through the same adapter the message arrived on.
Adapters come in two flavors:
- Chat SDK adapters — wrap a Chat SDK platform adapter (the
chatnpm package and its@chat-adapter/*family) via the bridge insrc/channels/chat-sdk-bridge.ts. The bridge handles mention detection, attachments, reply context, interactive question cards, and splitting long messages to fit platform limits. Discord, Slack, Telegram, Teams, and most others use this pattern. - Native adapters — implement the
ChannelAdapterinterface directly. Used where no Chat SDK adapter fits: WhatsApp (Baileys), Signal (signal-cli), WeChat, Delta Chat, Emacs, and the built-in CLI channel.
What an /add-<channel> skill does
Each install skill follows the same pattern (see /add-telegram for the canonical example):
- Fetch the
channelsbranch —git fetch origin channels - Copy the adapter —
git show origin/channels:src/channels/<name>.ts > src/channels/<name>.ts, along with its tests, helpers, and any setup steps - Wire the barrel — append
import './<name>.js';tosrc/channels/index.tsso the adapter self-registers at startup - Install the platform package, pinned — e.g.
pnpm install @chat-adapter/telegram@<exact-version>(each skill pins its own version) - Build and validate —
pnpm run buildplus the channel’s registration test, which asserts the adapter actually lands in the registry - Collect credentials — bot tokens, QR pairing, linked devices, whatever the platform needs, into
.env
/add-<channel> skill is also how you pick up adapter improvements from the channels branch.
Adapter catalog
Everything on thechannels branch right now. “Setup wizard” means the channel has a guided flow in setup/channels/ that the first-run setup offers; channels without one are installed with their /add-<channel> skill directly.
| Channel | Setup wizard? | Install | Notes |
|---|---|---|---|
| Yes | /add-whatsapp | Native Baileys (WhatsApp Web protocol); QR or pairing-code login | |
| WhatsApp Cloud | — | /add-whatsapp-cloud | Chat SDK; Meta’s official Cloud API |
| Telegram | Yes | /add-telegram | Chat SDK; BotFather token, pair-code chat registration |
| Discord | Yes | /add-discord | Chat SDK; Gateway listener — no public URL needed; thread-based |
| Slack | Yes | /add-slack | Chat SDK; events delivered to the webhook server; thread-based |
| Signal | Yes | /add-signal | Native; requires signal-cli with a linked account |
| iMessage | Yes | /add-imessage | Chat SDK; local mode (macOS Full Disk Access) or remote mode (Photon API) |
| Microsoft Teams | Yes | /add-teams | Chat SDK; webhook-based; thread-based |
| Google Chat | — | /add-gchat | Chat SDK; thread-based |
| GitHub | — | /add-github | Chat SDK; issue and PR conversations via webhook |
| Linear | — | /add-linear | Chat SDK; issue threads |
| Matrix | — | /add-matrix | Chat SDK; Element and self-hosted homeservers |
| Webex | — | /add-webex | Chat SDK; thread-based |
| — | /add-wechat | Native; Tencent’s official iLink Bot API, QR login | |
| Delta Chat | — | /add-deltachat | Native; chat over email (IMAP/SMTP) |
| Emacs | — | /add-emacs | Native; localhost HTTP bridge for the nanoclaw.el client |
| Email (Resend) | — | /add-resend | Chat SDK; email via Resend |
cli channel isn’t in this table because it ships on trunk — see CLI channel.
The webhook server
Chat SDK channels that receive platform events over HTTP (Slack, Teams, GitHub, and others) share one webhook server. It starts lazily when the first such adapter registers and routes by path:instance and listens on /webhook/{instance} with its own signing secret. Modules that need a non–Chat SDK endpoint (a custom GitHub webhook, a payment provider, a health check) register a raw handler on the same server with registerWebhookHandler(path, handler) instead of opening a second port — raw routes take priority over adapter routes.
The port comes from the WEBHOOK_PORT environment variable, default 3000. Point the platform’s event subscription URL at https://<your-public-host>/webhook/<channel> (e.g. /webhook/slack) — you’ll need a public URL or a tunnel for these channels. Discord is the exception: it uses a persistent Gateway listener instead of inbound webhooks, so no public URL is required. Telegram (long polling), WhatsApp, Signal, and WeChat hold their own outbound connections, so they don’t need one either.
Wiring channels to agents
Installing a channel gets messages flowing into NanoClaw; wiring decides which agent group handles them. A wiring connects a messaging group (a chat, channel, or thread on a platform) to an agent group — the same chat can be wired to multiple agents and vice versa. Run/manage-channels in Claude Code to do this conversationally. It:
- Shows current state — wired, configured-but-unwired, and unconfigured channels, plus privileged users
- Walks you through registering new chats, including platform-specific ID discovery (Telegram uses a pairing code, so you never hunt for chat IDs by hand)
- Asks the isolation question for each new wiring: same conversation, same agent with separate conversations, or a fully separate agent
- Moves existing channels between agent groups
ncl CLI exposes the same data: ncl wirings lists and edits the wiring rows directly. The isolation choice is the session_mode on each wiring — see the entity model for what shared, agent-shared, and per-thread mean.
If you’re setting up your very first channel, /init-first-agent handles the whole flow — agent group creation, wiring, owner promotion, and an end-to-end delivery test.
Channel guides
Baileys or Cloud API
Telegram
BotFather token, pair-code registration
Discord
Gateway listener, threads
Slack
Events webhook, threads
Signal
signal-cli linked device
iMessage
macOS local or remote mode
Microsoft Teams
Bot registration, webhook
CLI
Built-in terminal channel
More channels
Matrix, GitHub, Linear, WeChat, and the rest