Installs just this skill. Get the whole plugin for auto-invocation.
β‘ How it fires
How this skill gets triggered: by you, by Claude, or both.
Fires itselfClaude auto-loads it when your prompt matches the work.
You can call itInvoke it directly when you want it.
Slash command/add-telegram
ποΈ Context preview
The summary Claude sees to decide when to auto-load this skill.
Add Telegram channel integration via Chat SDK.
π Stats
Stars30,325
Forks12,869
LanguageTypeScript
LicenseMIT
π¦ Ships with nanoclaw
</> SKILL.md
add-telegram.SKILL.md
---name: add-telegram
description: Add Telegram channel integration via Chat SDK.
---# Add Telegram Channel
Adds Telegram bot support via the Chat SDK bridge. NanoClaw doesn't ship
channels in trunk β this skill copies the Telegram adapter, its
formatting/pairing helpers, and their tests in from the `channels` branch. The
`pair-telegram` setup step is maintained in trunk, so it is not copied here.
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. Copy the adapter, helpers, and tests
Fetch the `channels` branch and copy the Telegram adapter, its pairing and
markdown-sanitize helpers (with their tests), and the registration test into
place (overwrite β the branch is canonical):
```nc:copy from-branch:channels
src/channels/telegram.ts
src/channels/telegram-pairing.ts
src/channels/telegram-pairing.test.ts
src/channels/telegram-markdown-sanitize.ts
src/channels/telegram-markdown-sanitize.test.ts
Pinned to an exact version β the supply-chain policy rejects ranges and `latest`:
```nc:dep
@chat-adapter/telegram@4.29.0
```
### 5. Build and validate
Build first: it guards the typed `createChatSdkBridge(...)` core call and proves
the dependency is installed. Then run the one integration test.
```nc:run effect:build
pnpm run build
```
```nc:run effect:test
pnpm exec vitest run src/channels/telegram-registration.test.ts
```
`telegram-registration.test.ts` imports the real channel barrel and asserts the
registry contains `telegram`. It goes red if the import line is deleted or drifts,
if the barrel fails to evaluate, or if `@chat-adapter/telegram` isn't installed
(the import throws) β so it also covers the dependency from step 4. End-to-end
delivery against a real bot is verified manually once the service runs.
## Credentials
Bot creation in Telegram is human and interactive β no parser can click through
BotFather. The adapter is installed and registered, but it can't receive a
message until the bot exists. Tell the user:
```nc:operator
Create the Telegram bot:
1. Open Telegram and message @BotFather β Telegram's official bot for creating bots.
2. Send /newbot and follow the prompts: a friendly name, then a username that must end in "bot".
3. Copy the bot token it gives you (looks like 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11a).
4. Planning to use the bot in group chats? Send /mybots β your bot β Bot Settings β Group Privacy β Turn off, so the bot can see all messages and not just @mentions.
```
Collect the bot token and store it β the bridge reads it from `.env` (set-if-absent,
so a value you've already filled in is never overwritten) and syncs it to the
Restart the service so it loads the Telegram adapter and the token you just
stored, and wait for its CLI socket. The adapter must be live and polling before
pairing β it's the thing that observes the code you send:
```nc:run effect:restart
bash setup/lib/restart.sh
```
## Pair your chat
Telegram tokens carry no user binding, so the agent proves you own the chat with
a one-time pairing handshake: it issues a 4-digit code, you send those exact 4
digits to the bot from the chat you want to register, and the live adapter
matches them. Open the bot first so you're on the right screen when the code
appears. Tell the user:
```nc:operator
Open @{{bot_username}} (https://telegram.me/{{bot_username}}) in Telegram now and keep it on screen β a 4-digit pairing code is about to appear in this terminal. When it does, send just those 4 digits to the bot as a message (in a group chat with Group Privacy on, prefix them with @{{bot_username}}). A wrong guess is rejected and a fresh code is issued automatically.
```
Run the pairing handshake. It prints the code, streams "waitingβ¦" and wrong-code
feedback while it watches for your message, and resolves your chat address
`telegram:<chatId>` plus your Telegram user id once the code matches:
pnpm exec tsx setup/index.ts --step pair-telegram -- --intent main
```
`owner_handle` (your Telegram user id) and `platform_id` (`telegram:<chatId>`)
are what the owner-wiring step needs. The greeting goes out over the same chat as
soon as pairing completes.
## Next Steps
If you're in the middle of `/setup`, return to the setup flow now. Otherwise wire
this channel with `/init-first-agent` (or `/manage-channels`).
## Channel Info
- **type**: `telegram`
- **terminology**: Telegram calls them "groups" and "chats." A "group" has multiple members; a "chat" is a 1:1 conversation with the bot.
- **platform-id-format**: `telegram:{chatId}` (e.g. `telegram:123456789` for a DM, `telegram:-1001234567890` for a group β negative chat IDs are groups/channels).
- **how-to-find-id**: Do NOT ask the user for a chat ID. Telegram registration uses pairing β run `pnpm exec tsx setup/index.ts --step pair-telegram -- --intent <main|wire-to:folder|new-agent:folder>`. The step prints a 4-digit code (and re-prints a fresh one if a wrong code invalidates it, up to 5 times); tell the user to send just those 4 digits from the chat they want to register (DM the bot for `main`, post in the group otherwise; with Group Privacy ON, prefix `@<botname> CODE`). Success emits a `PAIR_TELEGRAM` block with `STATUS=success`, `PLATFORM_ID`, `IS_GROUP`, `ADMIN_USER_ID` (the bare Telegram user id) and `PAIRED_USER_ID` (the `telegram:`-prefixed form). The service must be running β the polling adapter is what observes the code.
- **supports-threads**: no
- **typical-use**: Interactive chat β direct messages or small groups
- **default-isolation**: Same agent group if you're the only participant across multiple chats. Separate agent group if different people are in different groups.
## Troubleshooting
**The bot token paste is rejected.** A BotFather token is `<numeric bot id>:<35+ character secret>` β e.g. `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11a`. Pasting only the part after the colon, or the bot's @username, won't pass. Recover the full token any time by sending `/token` to @BotFather.
**`getMe` fails.** The token was revoked (a `/revoke` or a fresh `/token` invalidates the old value) or picked up whitespace in the paste. Get the current token from BotFather and re-paste it.
**Pairing never completes.** The live adapter is what observes the code, so the service must be running β the restart step comes before pairing for exactly this reason. Send *just* the 4 digits from the exact chat you want registered; in a group with Group Privacy on, prefix them with `@<botname>`. Wrong guesses are fine (a fresh code is issued, up to 5 times), but a dead adapter waits forever.
**The bot ignores group messages.** Group Privacy is on, so the bot only sees @-mentions and replies. BotFather β `/mybots` β your bot β Bot Settings β Group Privacy β Turn off β then remove and re-add the bot to the group so the change takes effect.
**Everything green but no replies.** Run `pnpm exec vitest run src/channels/telegram-registration.test.ts` β red means the barrel import or the `@chat-adapter/telegram` install drifted, so re-run the Apply steps. If green, restart again (`bash setup/lib/restart.sh`) and check `logs/nanoclaw.error.log` for token errors.