From 00fb1bee4a061c33894c2b761682801a90580e82 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Fri, 17 Apr 2026 14:38:19 +0300 Subject: [PATCH] =?UTF-8?q?chore(skills):=20rename=20add-*-v2=20=E2=86=92?= =?UTF-8?q?=20add-*=20and=20drop=20dead=20v1=20channel=20skills?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed 13 skill folders to drop the -v2 suffix (the v2/v1 distinction isn't load-bearing anymore — there is no v1 runtime). Deleted the four v1 channel skills that occupied the rename target paths (add-discord, add-slack, add-telegram, add-whatsapp); they targeted src/v1 which is reference-only per CLAUDE.md. Skill content still says "v2" in places — that's a follow-up commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/skills/add-discord-v2/SKILL.md | 98 ------ .../{add-discord-v2 => add-discord}/REMOVE.md | 0 .claude/skills/add-discord/SKILL.md | 215 +++--------- .../{add-discord-v2 => add-discord}/VERIFY.md | 0 .../{add-gchat-v2 => add-gchat}/REMOVE.md | 0 .../{add-gchat-v2 => add-gchat}/SKILL.md | 0 .../{add-gchat-v2 => add-gchat}/VERIFY.md | 0 .../{add-github-v2 => add-github}/REMOVE.md | 0 .../{add-github-v2 => add-github}/SKILL.md | 0 .../{add-github-v2 => add-github}/VERIFY.md | 0 .../REMOVE.md | 0 .../SKILL.md | 0 .../VERIFY.md | 0 .../{add-linear-v2 => add-linear}/REMOVE.md | 0 .../{add-linear-v2 => add-linear}/SKILL.md | 0 .../{add-linear-v2 => add-linear}/VERIFY.md | 0 .../{add-matrix-v2 => add-matrix}/REMOVE.md | 0 .../{add-matrix-v2 => add-matrix}/SKILL.md | 0 .../{add-matrix-v2 => add-matrix}/VERIFY.md | 0 .../{add-resend-v2 => add-resend}/REMOVE.md | 0 .../{add-resend-v2 => add-resend}/SKILL.md | 0 .../{add-resend-v2 => add-resend}/VERIFY.md | 0 .claude/skills/add-slack-v2/SKILL.md | 112 ------ .../{add-slack-v2 => add-slack}/REMOVE.md | 0 .claude/skills/add-slack/SKILL.md | 219 ++++-------- .../{add-slack-v2 => add-slack}/VERIFY.md | 0 .../{add-teams-v2 => add-teams}/REMOVE.md | 0 .../{add-teams-v2 => add-teams}/SKILL.md | 0 .../{add-teams-v2 => add-teams}/VERIFY.md | 0 .claude/skills/add-telegram-v2/SKILL.md | 108 ------ .../REMOVE.md | 0 .claude/skills/add-telegram/SKILL.md | 228 ++++-------- .../VERIFY.md | 0 .../{add-webex-v2 => add-webex}/REMOVE.md | 0 .../{add-webex-v2 => add-webex}/SKILL.md | 0 .../{add-webex-v2 => add-webex}/VERIFY.md | 0 .../REMOVE.md | 0 .../SKILL.md | 0 .../VERIFY.md | 0 .claude/skills/add-whatsapp-v2/SKILL.md | 264 -------------- .claude/skills/add-whatsapp/SKILL.md | 330 ++++++------------ 41 files changed, 289 insertions(+), 1285 deletions(-) delete mode 100644 .claude/skills/add-discord-v2/SKILL.md rename .claude/skills/{add-discord-v2 => add-discord}/REMOVE.md (100%) rename .claude/skills/{add-discord-v2 => add-discord}/VERIFY.md (100%) rename .claude/skills/{add-gchat-v2 => add-gchat}/REMOVE.md (100%) rename .claude/skills/{add-gchat-v2 => add-gchat}/SKILL.md (100%) rename .claude/skills/{add-gchat-v2 => add-gchat}/VERIFY.md (100%) rename .claude/skills/{add-github-v2 => add-github}/REMOVE.md (100%) rename .claude/skills/{add-github-v2 => add-github}/SKILL.md (100%) rename .claude/skills/{add-github-v2 => add-github}/VERIFY.md (100%) rename .claude/skills/{add-imessage-v2 => add-imessage}/REMOVE.md (100%) rename .claude/skills/{add-imessage-v2 => add-imessage}/SKILL.md (100%) rename .claude/skills/{add-imessage-v2 => add-imessage}/VERIFY.md (100%) rename .claude/skills/{add-linear-v2 => add-linear}/REMOVE.md (100%) rename .claude/skills/{add-linear-v2 => add-linear}/SKILL.md (100%) rename .claude/skills/{add-linear-v2 => add-linear}/VERIFY.md (100%) rename .claude/skills/{add-matrix-v2 => add-matrix}/REMOVE.md (100%) rename .claude/skills/{add-matrix-v2 => add-matrix}/SKILL.md (100%) rename .claude/skills/{add-matrix-v2 => add-matrix}/VERIFY.md (100%) rename .claude/skills/{add-resend-v2 => add-resend}/REMOVE.md (100%) rename .claude/skills/{add-resend-v2 => add-resend}/SKILL.md (100%) rename .claude/skills/{add-resend-v2 => add-resend}/VERIFY.md (100%) delete mode 100644 .claude/skills/add-slack-v2/SKILL.md rename .claude/skills/{add-slack-v2 => add-slack}/REMOVE.md (100%) rename .claude/skills/{add-slack-v2 => add-slack}/VERIFY.md (100%) rename .claude/skills/{add-teams-v2 => add-teams}/REMOVE.md (100%) rename .claude/skills/{add-teams-v2 => add-teams}/SKILL.md (100%) rename .claude/skills/{add-teams-v2 => add-teams}/VERIFY.md (100%) delete mode 100644 .claude/skills/add-telegram-v2/SKILL.md rename .claude/skills/{add-telegram-v2 => add-telegram}/REMOVE.md (100%) rename .claude/skills/{add-telegram-v2 => add-telegram}/VERIFY.md (100%) rename .claude/skills/{add-webex-v2 => add-webex}/REMOVE.md (100%) rename .claude/skills/{add-webex-v2 => add-webex}/SKILL.md (100%) rename .claude/skills/{add-webex-v2 => add-webex}/VERIFY.md (100%) rename .claude/skills/{add-whatsapp-cloud-v2 => add-whatsapp-cloud}/REMOVE.md (100%) rename .claude/skills/{add-whatsapp-cloud-v2 => add-whatsapp-cloud}/SKILL.md (100%) rename .claude/skills/{add-whatsapp-cloud-v2 => add-whatsapp-cloud}/VERIFY.md (100%) delete mode 100644 .claude/skills/add-whatsapp-v2/SKILL.md diff --git a/.claude/skills/add-discord-v2/SKILL.md b/.claude/skills/add-discord-v2/SKILL.md deleted file mode 100644 index 9b41a8b..0000000 --- a/.claude/skills/add-discord-v2/SKILL.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -name: add-discord-v2 -description: Add Discord bot channel integration to NanoClaw v2 via Chat SDK. ---- - -# Add Discord Channel - -Adds Discord bot support to NanoClaw v2. Discord is built in — no adapter package to install. - -## Install - -v2 trunk doesn't ship channels. This skill copies the Discord adapter in from the `channels` branch. - -### Pre-flight (idempotent) - -Skip to **Credentials** if all of these are already in place: - -- `src/channels/discord.ts` exists -- `src/channels/index.ts` contains `import './discord.js';` -- `@chat-adapter/discord` is listed in `package.json` dependencies - -Otherwise continue. Every step below is safe to re-run. - -### 1. Fetch the channels branch - -```bash -git fetch origin channels -``` - -### 2. Copy the adapter - -```bash -git show origin/channels:src/channels/discord.ts > src/channels/discord.ts -``` - -### 3. Append the self-registration import - -Append to `src/channels/index.ts` (skip if the line is already present): - -```typescript -import './discord.js'; -``` - -### 4. Install the adapter package (pinned) - -```bash -pnpm install @chat-adapter/discord@4.26.0 -``` - -### 5. Build - -```bash -pnpm run build -``` - -## Credentials - -### Create Discord Bot - -1. Go to the [Discord Developer Portal](https://discord.com/developers/applications) -2. Click **New Application** and give it a name (e.g., "NanoClaw Assistant") -3. From the **General Information** tab, copy the **Application ID** and **Public Key** -4. Go to the **Bot** tab and click **Add Bot** if needed -5. Copy the Bot Token (click **Reset Token** if you need a new one — you can only see it once) -6. Under **Privileged Gateway Intents**, enable **Message Content Intent** -7. Go to **OAuth2** > **URL Generator**: - - Scopes: select `bot` - - Bot Permissions: select `Send Messages`, `Read Message History`, `Add Reactions`, `Attach Files`, `Use Slash Commands` -8. Copy the generated URL and open it in your browser to invite the bot to your server - -### Configure environment - -All three values are required — the adapter will fail to start without `DISCORD_PUBLIC_KEY` and `DISCORD_APPLICATION_ID`. - -Add to `.env`: - -```bash -DISCORD_BOT_TOKEN=your-bot-token -DISCORD_APPLICATION_ID=your-application-id -DISCORD_PUBLIC_KEY=your-public-key -``` - -Sync to container: `mkdir -p data/env && cp .env data/env/env` - -## Next Steps - -If you're in the middle of `/setup`, return to the setup flow now. - -Otherwise, run `/manage-channels` to wire this channel to an agent group. - -## Channel Info - -- **type**: `discord` -- **terminology**: Discord has "servers" (also called "guilds") containing "channels." Text channels start with #. The bot can also receive direct messages. -- **how-to-find-id**: Enable Developer Mode in Discord (Settings > App Settings > Advanced > Developer Mode). Then right-click a server and select "Copy Server ID" for the guild ID, and right-click the text channel and select "Copy Channel ID." The platform ID format used in registration is `discord:{guildId}:{channelId}` — both IDs are required. -- **supports-threads**: yes -- **typical-use**: Interactive chat — server channels or direct messages -- **default-isolation**: Same agent group for your personal server. Separate agent group for servers with different communities or where different members have different information boundaries. diff --git a/.claude/skills/add-discord-v2/REMOVE.md b/.claude/skills/add-discord/REMOVE.md similarity index 100% rename from .claude/skills/add-discord-v2/REMOVE.md rename to .claude/skills/add-discord/REMOVE.md diff --git a/.claude/skills/add-discord/SKILL.md b/.claude/skills/add-discord/SKILL.md index 9ca6f21..9b41a8b 100644 --- a/.claude/skills/add-discord/SKILL.md +++ b/.claude/skills/add-discord/SKILL.md @@ -1,203 +1,98 @@ --- -name: add-discord -description: Add Discord bot channel integration to NanoClaw. +name: add-discord-v2 +description: Add Discord bot channel integration to NanoClaw v2 via Chat SDK. --- # Add Discord Channel -This skill adds Discord support to NanoClaw, then walks through interactive setup. +Adds Discord bot support to NanoClaw v2. Discord is built in — no adapter package to install. -## Phase 1: Pre-flight +## Install -### Check if already applied +v2 trunk doesn't ship channels. This skill copies the Discord adapter in from the `channels` branch. -Check if `src/channels/discord.ts` exists. If it does, skip to Phase 3 (Setup). The code changes are already in place. +### Pre-flight (idempotent) -### Ask the user +Skip to **Credentials** if all of these are already in place: -Use `AskUserQuestion` to collect configuration: +- `src/channels/discord.ts` exists +- `src/channels/index.ts` contains `import './discord.js';` +- `@chat-adapter/discord` is listed in `package.json` dependencies -AskUserQuestion: Do you have a Discord bot token, or do you need to create one? +Otherwise continue. Every step below is safe to re-run. -If they have one, collect it now. If not, we'll create one in Phase 3. - -## Phase 2: Apply Code Changes - -### Ensure channel remote +### 1. Fetch the channels branch ```bash -git remote -v +git fetch origin channels ``` -If `discord` is missing, add it: +### 2. Copy the adapter ```bash -git remote add discord https://github.com/qwibitai/nanoclaw-discord.git +git show origin/channels:src/channels/discord.ts > src/channels/discord.ts ``` -### Merge the skill branch +### 3. Append the self-registration import -```bash -git fetch discord main -git merge discord/main || { - git checkout --theirs pnpm-lock.yaml - git add pnpm-lock.yaml - git merge --continue -} +Append to `src/channels/index.ts` (skip if the line is already present): + +```typescript +import './discord.js'; ``` -This merges in: -- `src/channels/discord.ts` (DiscordChannel class with self-registration via `registerChannel`) -- `src/channels/discord.test.ts` (unit tests with discord.js mock) -- `import './discord.js'` appended to the channel barrel file `src/channels/index.ts` -- `discord.js` npm dependency in `package.json` -- `DISCORD_BOT_TOKEN` in `.env.example` - -If the merge reports conflicts, resolve them by reading the conflicted files and understanding the intent of both sides. - -### Validate code changes +### 4. Install the adapter package (pinned) + +```bash +pnpm install @chat-adapter/discord@4.26.0 +``` + +### 5. Build ```bash -pnpm install pnpm run build -pnpm exec vitest run src/channels/discord.test.ts ``` -All tests must pass (including the new Discord tests) and build must be clean before proceeding. +## Credentials -## Phase 3: Setup +### Create Discord Bot -### Create Discord Bot (if needed) - -If the user doesn't have a bot token, tell them: - -> I need you to create a Discord bot: -> -> 1. Go to the [Discord Developer Portal](https://discord.com/developers/applications) -> 2. Click **New Application** and give it a name (e.g., "Andy Assistant") -> 3. Go to the **Bot** tab on the left sidebar -> 4. Click **Reset Token** to generate a new bot token — copy it immediately (you can only see it once) -> 5. Under **Privileged Gateway Intents**, enable: -> - **Message Content Intent** (required to read message text) -> - **Server Members Intent** (optional, for member display names) -> 6. Go to **OAuth2** > **URL Generator**: -> - Scopes: select `bot` -> - Bot Permissions: select `Send Messages`, `Read Message History`, `View Channels` -> - Copy the generated URL and open it in your browser to invite the bot to your server - -Wait for the user to provide the token. +1. Go to the [Discord Developer Portal](https://discord.com/developers/applications) +2. Click **New Application** and give it a name (e.g., "NanoClaw Assistant") +3. From the **General Information** tab, copy the **Application ID** and **Public Key** +4. Go to the **Bot** tab and click **Add Bot** if needed +5. Copy the Bot Token (click **Reset Token** if you need a new one — you can only see it once) +6. Under **Privileged Gateway Intents**, enable **Message Content Intent** +7. Go to **OAuth2** > **URL Generator**: + - Scopes: select `bot` + - Bot Permissions: select `Send Messages`, `Read Message History`, `Add Reactions`, `Attach Files`, `Use Slash Commands` +8. Copy the generated URL and open it in your browser to invite the bot to your server ### Configure environment +All three values are required — the adapter will fail to start without `DISCORD_PUBLIC_KEY` and `DISCORD_APPLICATION_ID`. + Add to `.env`: ```bash -DISCORD_BOT_TOKEN= +DISCORD_BOT_TOKEN=your-bot-token +DISCORD_APPLICATION_ID=your-application-id +DISCORD_PUBLIC_KEY=your-public-key ``` -Channels auto-enable when their credentials are present — no extra configuration needed. +Sync to container: `mkdir -p data/env && cp .env data/env/env` -Sync to container environment: +## Next Steps -```bash -mkdir -p data/env && cp .env data/env/env -``` +If you're in the middle of `/setup`, return to the setup flow now. -The container reads environment from `data/env/env`, not `.env` directly. +Otherwise, run `/manage-channels` to wire this channel to an agent group. -### Build and restart +## Channel Info -```bash -pnpm run build -launchctl kickstart -k gui/$(id -u)/com.nanoclaw -``` - -## Phase 4: Registration - -### Get Channel ID - -Tell the user: - -> To get the channel ID for registration: -> -> 1. In Discord, go to **User Settings** > **Advanced** > Enable **Developer Mode** -> 2. Right-click the text channel you want the bot to respond in -> 3. Click **Copy Channel ID** -> -> The channel ID will be a long number like `1234567890123456`. - -Wait for the user to provide the channel ID (format: `dc:1234567890123456`). - -### Register the channel - -The channel ID, name, and folder name are needed. Use `pnpm exec tsx setup/index.ts --step register` with the appropriate flags. - -For a main channel (responds to all messages): - -```bash -pnpm exec tsx setup/index.ts --step register -- --jid "dc:" --name " #" --folder "discord_main" --trigger "@${ASSISTANT_NAME}" --channel discord --no-trigger-required --is-main -``` - -For additional channels (trigger-only): - -```bash -pnpm exec tsx setup/index.ts --step register -- --jid "dc:" --name " #" --folder "discord_" --trigger "@${ASSISTANT_NAME}" --channel discord -``` - -## Phase 5: Verify - -### Test the connection - -Tell the user: - -> Send a message in your registered Discord channel: -> - For main channel: Any message works -> - For non-main: @mention the bot in Discord -> -> The bot should respond within a few seconds. - -### Check logs if needed - -```bash -tail -f logs/nanoclaw.log -``` - -## Troubleshooting - -### Bot not responding - -1. Check `DISCORD_BOT_TOKEN` is set in `.env` AND synced to `data/env/env` -2. Check channel is registered: `sqlite3 store/messages.db "SELECT * FROM registered_groups WHERE jid LIKE 'dc:%'"` -3. For non-main channels: message must include trigger pattern (@mention the bot) -4. Service is running: `launchctl list | grep nanoclaw` -5. Verify the bot has been invited to the server (check OAuth2 URL was used) - -### Bot only responds to @mentions - -This is the default behavior for non-main channels (`requiresTrigger: true`). To change: -- Update the registered group's `requiresTrigger` to `false` -- Or register the channel as the main channel - -### Message Content Intent not enabled - -If the bot connects but can't read messages, ensure: -1. Go to [Discord Developer Portal](https://discord.com/developers/applications) -2. Select your application > **Bot** tab -3. Under **Privileged Gateway Intents**, enable **Message Content Intent** -4. Restart NanoClaw - -### Getting Channel ID - -If you can't copy the channel ID: -- Ensure **Developer Mode** is enabled: User Settings > Advanced > Developer Mode -- Right-click the channel name in the server sidebar > Copy Channel ID - -## After Setup - -The Discord bot supports: -- Text messages in registered channels -- Attachment descriptions (images, videos, files shown as placeholders) -- Reply context (shows who the user is replying to) -- @mention translation (Discord `<@botId>` → NanoClaw trigger format) -- Message splitting for responses over 2000 characters -- Typing indicators while the agent processes +- **type**: `discord` +- **terminology**: Discord has "servers" (also called "guilds") containing "channels." Text channels start with #. The bot can also receive direct messages. +- **how-to-find-id**: Enable Developer Mode in Discord (Settings > App Settings > Advanced > Developer Mode). Then right-click a server and select "Copy Server ID" for the guild ID, and right-click the text channel and select "Copy Channel ID." The platform ID format used in registration is `discord:{guildId}:{channelId}` — both IDs are required. +- **supports-threads**: yes +- **typical-use**: Interactive chat — server channels or direct messages +- **default-isolation**: Same agent group for your personal server. Separate agent group for servers with different communities or where different members have different information boundaries. diff --git a/.claude/skills/add-discord-v2/VERIFY.md b/.claude/skills/add-discord/VERIFY.md similarity index 100% rename from .claude/skills/add-discord-v2/VERIFY.md rename to .claude/skills/add-discord/VERIFY.md diff --git a/.claude/skills/add-gchat-v2/REMOVE.md b/.claude/skills/add-gchat/REMOVE.md similarity index 100% rename from .claude/skills/add-gchat-v2/REMOVE.md rename to .claude/skills/add-gchat/REMOVE.md diff --git a/.claude/skills/add-gchat-v2/SKILL.md b/.claude/skills/add-gchat/SKILL.md similarity index 100% rename from .claude/skills/add-gchat-v2/SKILL.md rename to .claude/skills/add-gchat/SKILL.md diff --git a/.claude/skills/add-gchat-v2/VERIFY.md b/.claude/skills/add-gchat/VERIFY.md similarity index 100% rename from .claude/skills/add-gchat-v2/VERIFY.md rename to .claude/skills/add-gchat/VERIFY.md diff --git a/.claude/skills/add-github-v2/REMOVE.md b/.claude/skills/add-github/REMOVE.md similarity index 100% rename from .claude/skills/add-github-v2/REMOVE.md rename to .claude/skills/add-github/REMOVE.md diff --git a/.claude/skills/add-github-v2/SKILL.md b/.claude/skills/add-github/SKILL.md similarity index 100% rename from .claude/skills/add-github-v2/SKILL.md rename to .claude/skills/add-github/SKILL.md diff --git a/.claude/skills/add-github-v2/VERIFY.md b/.claude/skills/add-github/VERIFY.md similarity index 100% rename from .claude/skills/add-github-v2/VERIFY.md rename to .claude/skills/add-github/VERIFY.md diff --git a/.claude/skills/add-imessage-v2/REMOVE.md b/.claude/skills/add-imessage/REMOVE.md similarity index 100% rename from .claude/skills/add-imessage-v2/REMOVE.md rename to .claude/skills/add-imessage/REMOVE.md diff --git a/.claude/skills/add-imessage-v2/SKILL.md b/.claude/skills/add-imessage/SKILL.md similarity index 100% rename from .claude/skills/add-imessage-v2/SKILL.md rename to .claude/skills/add-imessage/SKILL.md diff --git a/.claude/skills/add-imessage-v2/VERIFY.md b/.claude/skills/add-imessage/VERIFY.md similarity index 100% rename from .claude/skills/add-imessage-v2/VERIFY.md rename to .claude/skills/add-imessage/VERIFY.md diff --git a/.claude/skills/add-linear-v2/REMOVE.md b/.claude/skills/add-linear/REMOVE.md similarity index 100% rename from .claude/skills/add-linear-v2/REMOVE.md rename to .claude/skills/add-linear/REMOVE.md diff --git a/.claude/skills/add-linear-v2/SKILL.md b/.claude/skills/add-linear/SKILL.md similarity index 100% rename from .claude/skills/add-linear-v2/SKILL.md rename to .claude/skills/add-linear/SKILL.md diff --git a/.claude/skills/add-linear-v2/VERIFY.md b/.claude/skills/add-linear/VERIFY.md similarity index 100% rename from .claude/skills/add-linear-v2/VERIFY.md rename to .claude/skills/add-linear/VERIFY.md diff --git a/.claude/skills/add-matrix-v2/REMOVE.md b/.claude/skills/add-matrix/REMOVE.md similarity index 100% rename from .claude/skills/add-matrix-v2/REMOVE.md rename to .claude/skills/add-matrix/REMOVE.md diff --git a/.claude/skills/add-matrix-v2/SKILL.md b/.claude/skills/add-matrix/SKILL.md similarity index 100% rename from .claude/skills/add-matrix-v2/SKILL.md rename to .claude/skills/add-matrix/SKILL.md diff --git a/.claude/skills/add-matrix-v2/VERIFY.md b/.claude/skills/add-matrix/VERIFY.md similarity index 100% rename from .claude/skills/add-matrix-v2/VERIFY.md rename to .claude/skills/add-matrix/VERIFY.md diff --git a/.claude/skills/add-resend-v2/REMOVE.md b/.claude/skills/add-resend/REMOVE.md similarity index 100% rename from .claude/skills/add-resend-v2/REMOVE.md rename to .claude/skills/add-resend/REMOVE.md diff --git a/.claude/skills/add-resend-v2/SKILL.md b/.claude/skills/add-resend/SKILL.md similarity index 100% rename from .claude/skills/add-resend-v2/SKILL.md rename to .claude/skills/add-resend/SKILL.md diff --git a/.claude/skills/add-resend-v2/VERIFY.md b/.claude/skills/add-resend/VERIFY.md similarity index 100% rename from .claude/skills/add-resend-v2/VERIFY.md rename to .claude/skills/add-resend/VERIFY.md diff --git a/.claude/skills/add-slack-v2/SKILL.md b/.claude/skills/add-slack-v2/SKILL.md deleted file mode 100644 index 7946c87..0000000 --- a/.claude/skills/add-slack-v2/SKILL.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -name: add-slack-v2 -description: Add Slack channel integration to NanoClaw v2 via Chat SDK. ---- - -# Add Slack Channel - -Adds Slack support to NanoClaw v2 using the Chat SDK bridge. - -## Install - -v2 trunk doesn't ship channels. This skill copies the Slack adapter in from the `channels` branch. - -### Pre-flight (idempotent) - -Skip to **Credentials** if all of these are already in place: - -- `src/channels/slack.ts` exists -- `src/channels/index.ts` contains `import './slack.js';` -- `@chat-adapter/slack` is listed in `package.json` dependencies - -Otherwise continue. Every step below is safe to re-run. - -### 1. Fetch the channels branch - -```bash -git fetch origin channels -``` - -### 2. Copy the adapter - -```bash -git show origin/channels:src/channels/slack.ts > src/channels/slack.ts -``` - -### 3. Append the self-registration import - -Append to `src/channels/index.ts` (skip if the line is already present): - -```typescript -import './slack.js'; -``` - -### 4. Install the adapter package (pinned) - -```bash -pnpm install @chat-adapter/slack@4.26.0 -``` - -### 5. Build - -```bash -pnpm run build -``` - -## Credentials - -### Create Slack App - -1. Go to [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App** > **From scratch** -2. Name it (e.g., "NanoClaw") and select your workspace -3. Go to **OAuth & Permissions** and add Bot Token Scopes: - - `chat:write`, `channels:history`, `groups:history`, `im:history`, `channels:read`, `groups:read`, `users:read`, `reactions:write` -4. Click **Install to Workspace** and copy the **Bot User OAuth Token** (`xoxb-...`) -5. Go to **Basic Information** and copy the **Signing Secret** - -### Enable DMs - -6. Go to **App Home** and enable the **Messages Tab** -7. Check **"Allow users to send Slash commands and messages from the messages tab"** - -### Event Subscriptions - -8. Go to **Event Subscriptions** and toggle **Enable Events** -9. Set the **Request URL** to `https://your-domain/webhook/slack` — Slack will send a verification challenge; it must pass before you can save -10. Under **Subscribe to bot events**, add: - - `message.channels`, `message.groups`, `message.im`, `app_mention` -11. Click **Save Changes** -12. Slack will show a banner asking you to **reinstall the app** — click it to apply the new event subscriptions - -### Configure environment - -Add to `.env`: - -```bash -SLACK_BOT_TOKEN=xoxb-your-bot-token -SLACK_SIGNING_SECRET=your-signing-secret -``` - -Sync to container: `mkdir -p data/env && cp .env data/env/env` - -### Webhook server - -The Chat SDK bridge automatically starts a shared webhook server on port 3000 (configurable via `WEBHOOK_PORT` env var). The server handles `/webhook/slack` for Slack and other webhook-based adapters. This port must be publicly reachable from the internet for Slack to deliver events. - -If running locally, discuss options for exposing the server — e.g. ngrok (`ngrok http 3000`), Cloudflare Tunnel, or a reverse proxy on a VPS. The resulting public URL becomes the base for `https://your-domain/webhook/slack`. - -## Next Steps - -If you're in the middle of `/setup`, return to the setup flow now. - -Otherwise, run `/manage-channels` to wire this channel to an agent group. - -## Channel Info - -- **type**: `slack` -- **terminology**: Slack has "workspaces" containing "channels." Channels can be public (#general) or private. The bot can also receive direct messages. -- **platform-id-format**: `slack:{channelId}` for channels (e.g., `slack:C0123ABC`), `slack:{dmId}` for DMs (e.g., `slack:D0ARWEBLV63`) -- **how-to-find-id**: Right-click a channel name > "View channel details" — the Channel ID is at the bottom (starts with C). For DMs, the ID starts with D. Or copy the channel link — the ID is the last segment of the URL. -- **supports-threads**: yes -- **typical-use**: Interactive chat — team channels or direct messages -- **default-isolation**: Same agent group for channels where you're the primary user. Separate agent group for channels with different teams or sensitive contexts. diff --git a/.claude/skills/add-slack-v2/REMOVE.md b/.claude/skills/add-slack/REMOVE.md similarity index 100% rename from .claude/skills/add-slack-v2/REMOVE.md rename to .claude/skills/add-slack/REMOVE.md diff --git a/.claude/skills/add-slack/SKILL.md b/.claude/skills/add-slack/SKILL.md index 5f7fb5c..7946c87 100644 --- a/.claude/skills/add-slack/SKILL.md +++ b/.claude/skills/add-slack/SKILL.md @@ -1,80 +1,82 @@ --- -name: add-slack -description: Add Slack as a channel. Can replace WhatsApp entirely or run alongside it. Uses Socket Mode (no public URL needed). +name: add-slack-v2 +description: Add Slack channel integration to NanoClaw v2 via Chat SDK. --- # Add Slack Channel -This skill adds Slack support to NanoClaw, then walks through interactive setup. +Adds Slack support to NanoClaw v2 using the Chat SDK bridge. -## Phase 1: Pre-flight +## Install -### Check if already applied +v2 trunk doesn't ship channels. This skill copies the Slack adapter in from the `channels` branch. -Check if `src/channels/slack.ts` exists. If it does, skip to Phase 3 (Setup). The code changes are already in place. +### Pre-flight (idempotent) -### Ask the user +Skip to **Credentials** if all of these are already in place: -**Do they already have a Slack app configured?** If yes, collect the Bot Token and App Token now. If no, we'll create one in Phase 3. +- `src/channels/slack.ts` exists +- `src/channels/index.ts` contains `import './slack.js';` +- `@chat-adapter/slack` is listed in `package.json` dependencies -## Phase 2: Apply Code Changes +Otherwise continue. Every step below is safe to re-run. -### Ensure channel remote +### 1. Fetch the channels branch ```bash -git remote -v +git fetch origin channels ``` -If `slack` is missing, add it: +### 2. Copy the adapter ```bash -git remote add slack https://github.com/qwibitai/nanoclaw-slack.git +git show origin/channels:src/channels/slack.ts > src/channels/slack.ts ``` -### Merge the skill branch +### 3. Append the self-registration import -```bash -git fetch slack main -git merge slack/main || { - git checkout --theirs pnpm-lock.yaml - git add pnpm-lock.yaml - git merge --continue -} +Append to `src/channels/index.ts` (skip if the line is already present): + +```typescript +import './slack.js'; ``` -This merges in: -- `src/channels/slack.ts` (SlackChannel class with self-registration via `registerChannel`) -- `src/channels/slack.test.ts` (46 unit tests) -- `import './slack.js'` appended to the channel barrel file `src/channels/index.ts` -- `@slack/bolt` npm dependency in `package.json` -- `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN` in `.env.example` - -If the merge reports conflicts, resolve them by reading the conflicted files and understanding the intent of both sides. - -### Validate code changes +### 4. Install the adapter package (pinned) + +```bash +pnpm install @chat-adapter/slack@4.26.0 +``` + +### 5. Build ```bash -pnpm install pnpm run build -pnpm exec vitest run src/channels/slack.test.ts ``` -All tests must pass (including the new Slack tests) and build must be clean before proceeding. +## Credentials -## Phase 3: Setup +### Create Slack App -### Create Slack App (if needed) +1. Go to [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App** > **From scratch** +2. Name it (e.g., "NanoClaw") and select your workspace +3. Go to **OAuth & Permissions** and add Bot Token Scopes: + - `chat:write`, `channels:history`, `groups:history`, `im:history`, `channels:read`, `groups:read`, `users:read`, `reactions:write` +4. Click **Install to Workspace** and copy the **Bot User OAuth Token** (`xoxb-...`) +5. Go to **Basic Information** and copy the **Signing Secret** -If the user doesn't have a Slack app, share [SLACK_SETUP.md](SLACK_SETUP.md) which has step-by-step instructions with screenshots guidance, troubleshooting, and a token reference table. +### Enable DMs -Quick summary of what's needed: -1. Create a Slack app at [api.slack.com/apps](https://api.slack.com/apps) -2. Enable Socket Mode and generate an App-Level Token (`xapp-...`) -3. Subscribe to bot events: `message.channels`, `message.groups`, `message.im` -4. Add OAuth scopes: `chat:write`, `channels:history`, `groups:history`, `im:history`, `channels:read`, `groups:read`, `users:read` -5. Install to workspace and copy the Bot Token (`xoxb-...`) +6. Go to **App Home** and enable the **Messages Tab** +7. Check **"Allow users to send Slash commands and messages from the messages tab"** -Wait for the user to provide both tokens. +### Event Subscriptions + +8. Go to **Event Subscriptions** and toggle **Enable Events** +9. Set the **Request URL** to `https://your-domain/webhook/slack` — Slack will send a verification challenge; it must pass before you can save +10. Under **Subscribe to bot events**, add: + - `message.channels`, `message.groups`, `message.im`, `app_mention` +11. Click **Save Changes** +12. Slack will show a banner asking you to **reinstall the app** — click it to apply the new event subscriptions ### Configure environment @@ -82,126 +84,29 @@ Add to `.env`: ```bash SLACK_BOT_TOKEN=xoxb-your-bot-token -SLACK_APP_TOKEN=xapp-your-app-token +SLACK_SIGNING_SECRET=your-signing-secret ``` -Channels auto-enable when their credentials are present — no extra configuration needed. +Sync to container: `mkdir -p data/env && cp .env data/env/env` -Sync to container environment: +### Webhook server -```bash -mkdir -p data/env && cp .env data/env/env -``` +The Chat SDK bridge automatically starts a shared webhook server on port 3000 (configurable via `WEBHOOK_PORT` env var). The server handles `/webhook/slack` for Slack and other webhook-based adapters. This port must be publicly reachable from the internet for Slack to deliver events. -The container reads environment from `data/env/env`, not `.env` directly. +If running locally, discuss options for exposing the server — e.g. ngrok (`ngrok http 3000`), Cloudflare Tunnel, or a reverse proxy on a VPS. The resulting public URL becomes the base for `https://your-domain/webhook/slack`. -### Build and restart +## Next Steps -```bash -pnpm run build -launchctl kickstart -k gui/$(id -u)/com.nanoclaw -``` +If you're in the middle of `/setup`, return to the setup flow now. -## Phase 4: Registration +Otherwise, run `/manage-channels` to wire this channel to an agent group. -### Get Channel ID +## Channel Info -Tell the user: - -> 1. Add the bot to a Slack channel (right-click channel → **View channel details** → **Integrations** → **Add apps**) -> 2. In that channel, the channel ID is in the URL when you open it in a browser: `https://app.slack.com/client/T.../C0123456789` — the `C...` part is the channel ID -> 3. Alternatively, right-click the channel name → **Copy link** — the channel ID is the last path segment -> -> The JID format for NanoClaw is: `slack:C0123456789` - -Wait for the user to provide the channel ID. - -### Register the channel - -The channel ID, name, and folder name are needed. Use `pnpm exec tsx setup/index.ts --step register` with the appropriate flags. - -For a main channel (responds to all messages): - -```bash -pnpm exec tsx setup/index.ts --step register -- --jid "slack:" --name "" --folder "slack_main" --trigger "@${ASSISTANT_NAME}" --channel slack --no-trigger-required --is-main -``` - -For additional channels (trigger-only): - -```bash -pnpm exec tsx setup/index.ts --step register -- --jid "slack:" --name "" --folder "slack_" --trigger "@${ASSISTANT_NAME}" --channel slack -``` - -## Phase 5: Verify - -### Test the connection - -Tell the user: - -> Send a message in your registered Slack channel: -> - For main channel: Any message works -> - For non-main: `@ hello` (using the configured trigger word) -> -> The bot should respond within a few seconds. - -### Check logs if needed - -```bash -tail -f logs/nanoclaw.log -``` - -## Troubleshooting - -### Bot not responding - -1. Check `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN` are set in `.env` AND synced to `data/env/env` -2. Check channel is registered: `sqlite3 store/messages.db "SELECT * FROM registered_groups WHERE jid LIKE 'slack:%'"` -3. For non-main channels: message must include trigger pattern -4. Service is running: `launchctl list | grep nanoclaw` - -### Bot connected but not receiving messages - -1. Verify Socket Mode is enabled in the Slack app settings -2. Verify the bot is subscribed to the correct events (`message.channels`, `message.groups`, `message.im`) -3. Verify the bot has been added to the channel -4. Check that the bot has the required OAuth scopes - -### Bot not seeing messages in channels - -By default, bots only see messages in channels they've been explicitly added to. Make sure to: -1. Add the bot to each channel you want it to monitor -2. Check the bot has `channels:history` and/or `groups:history` scopes - -### "missing_scope" errors - -If the bot logs `missing_scope` errors: -1. Go to **OAuth & Permissions** in your Slack app settings -2. Add the missing scope listed in the error message -3. **Reinstall the app** to your workspace — scope changes require reinstallation -4. Copy the new Bot Token (it changes on reinstall) and update `.env` -5. Sync: `mkdir -p data/env && cp .env data/env/env` -6. Restart: `launchctl kickstart -k gui/$(id -u)/com.nanoclaw` - -### Getting channel ID - -If the channel ID is hard to find: -- In Slack desktop: right-click channel → **Copy link** → extract the `C...` ID from the URL -- In Slack web: the URL shows `https://app.slack.com/client/TXXXXXXX/C0123456789` -- Via API: `curl -s -H "Authorization: Bearer $SLACK_BOT_TOKEN" "https://slack.com/api/conversations.list" | jq '.channels[] | {id, name}'` - -## After Setup - -The Slack channel supports: -- **Public channels** — Bot must be added to the channel -- **Private channels** — Bot must be invited to the channel -- **Direct messages** — Users can DM the bot directly -- **Multi-channel** — Can run alongside WhatsApp or other channels (auto-enabled by credentials) - -## Known Limitations - -- **Threads are flattened** — Threaded replies are delivered to the agent as regular channel messages. The agent sees them but has no awareness they originated in a thread. Responses always go to the channel, not back into the thread. Users in a thread will need to check the main channel for the bot's reply. Full thread-aware routing (respond in-thread) requires pipeline-wide changes: database schema, `NewMessage` type, `Channel.sendMessage` interface, and routing logic. -- **No typing indicator** — Slack's Bot API does not expose a typing indicator endpoint. The `setTyping()` method is a no-op. Users won't see "bot is typing..." while the agent works. -- **Message splitting is naive** — Long messages are split at a fixed 4000-character boundary, which may break mid-word or mid-sentence. A smarter split (on paragraph or sentence boundaries) would improve readability. -- **No file/image handling** — The bot only processes text content. File uploads, images, and rich message blocks are not forwarded to the agent. -- **Channel metadata sync is unbounded** — `syncChannelMetadata()` paginates through all channels the bot is a member of, but has no upper bound or timeout. Workspaces with thousands of channels may experience slow startup. -- **Workspace admin policies not detected** — If the Slack workspace restricts bot app installation, the setup will fail at the "Install to Workspace" step with no programmatic detection or guidance. See SLACK_SETUP.md troubleshooting section. +- **type**: `slack` +- **terminology**: Slack has "workspaces" containing "channels." Channels can be public (#general) or private. The bot can also receive direct messages. +- **platform-id-format**: `slack:{channelId}` for channels (e.g., `slack:C0123ABC`), `slack:{dmId}` for DMs (e.g., `slack:D0ARWEBLV63`) +- **how-to-find-id**: Right-click a channel name > "View channel details" — the Channel ID is at the bottom (starts with C). For DMs, the ID starts with D. Or copy the channel link — the ID is the last segment of the URL. +- **supports-threads**: yes +- **typical-use**: Interactive chat — team channels or direct messages +- **default-isolation**: Same agent group for channels where you're the primary user. Separate agent group for channels with different teams or sensitive contexts. diff --git a/.claude/skills/add-slack-v2/VERIFY.md b/.claude/skills/add-slack/VERIFY.md similarity index 100% rename from .claude/skills/add-slack-v2/VERIFY.md rename to .claude/skills/add-slack/VERIFY.md diff --git a/.claude/skills/add-teams-v2/REMOVE.md b/.claude/skills/add-teams/REMOVE.md similarity index 100% rename from .claude/skills/add-teams-v2/REMOVE.md rename to .claude/skills/add-teams/REMOVE.md diff --git a/.claude/skills/add-teams-v2/SKILL.md b/.claude/skills/add-teams/SKILL.md similarity index 100% rename from .claude/skills/add-teams-v2/SKILL.md rename to .claude/skills/add-teams/SKILL.md diff --git a/.claude/skills/add-teams-v2/VERIFY.md b/.claude/skills/add-teams/VERIFY.md similarity index 100% rename from .claude/skills/add-teams-v2/VERIFY.md rename to .claude/skills/add-teams/VERIFY.md diff --git a/.claude/skills/add-telegram-v2/SKILL.md b/.claude/skills/add-telegram-v2/SKILL.md deleted file mode 100644 index 053d7a2..0000000 --- a/.claude/skills/add-telegram-v2/SKILL.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -name: add-telegram-v2 -description: Add Telegram channel integration to NanoClaw v2 via Chat SDK. ---- - -# Add Telegram Channel - -Adds Telegram bot support to NanoClaw v2 using the Chat SDK bridge. - -## Install - -v2 trunk doesn't ship channels. This skill copies the Telegram adapter, its formatting/pairing helpers, their tests, and the `pair-telegram` setup step in from the `channels` branch. - -### Pre-flight (idempotent) - -Skip to **Credentials** if all of these are already in place: - -- `src/channels/telegram.ts`, `telegram-pairing.ts`, `telegram-markdown-sanitize.ts` (and their `.test.ts` siblings) all exist -- `src/channels/index.ts` contains `import './telegram.js';` -- `setup/pair-telegram.ts` exists and `setup/index.ts`'s `STEPS` map contains `'pair-telegram':` -- `@chat-adapter/telegram` is listed in `package.json` dependencies - -Otherwise continue. Every step below is safe to re-run. - -### 1. Fetch the channels branch - -```bash -git fetch origin channels -``` - -### 2. Copy the adapter, helpers, tests, and setup step - -```bash -git show origin/channels:src/channels/telegram.ts > src/channels/telegram.ts -git show origin/channels:src/channels/telegram-pairing.ts > src/channels/telegram-pairing.ts -git show origin/channels:src/channels/telegram-pairing.test.ts > src/channels/telegram-pairing.test.ts -git show origin/channels:src/channels/telegram-markdown-sanitize.ts > src/channels/telegram-markdown-sanitize.ts -git show origin/channels:src/channels/telegram-markdown-sanitize.test.ts > src/channels/telegram-markdown-sanitize.test.ts -git show origin/channels:setup/pair-telegram.ts > setup/pair-telegram.ts -``` - -### 3. Append the self-registration import - -Append to `src/channels/index.ts` (skip if already present): - -```typescript -import './telegram.js'; -``` - -### 4. Register the setup step - -In `setup/index.ts`, add this entry to the `STEPS` map (right after the `register` line is fine; skip if already present): - -```typescript -'pair-telegram': () => import('./pair-telegram.js'), -``` - -### 5. Install the adapter package (pinned) - -```bash -pnpm install @chat-adapter/telegram@4.26.0 -``` - -### 6. Build - -```bash -pnpm run build -``` - -## Credentials - -### Create Telegram Bot - -1. Open Telegram and search for `@BotFather` -2. Send `/newbot` and follow the prompts: - - Bot name: Something friendly (e.g., "NanoClaw Assistant") - - Bot username: Must end with "bot" (e.g., "nanoclaw_bot") -3. Copy the bot token (looks like `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`) - -**Important for group chats**: By default, Telegram bots only see @mentions and commands in groups. To let the bot see all messages: - -1. Open `@BotFather` > `/mybots` > select your bot -2. **Bot Settings** > **Group Privacy** > **Turn off** - -### Configure environment - -Add to `.env`: - -```bash -TELEGRAM_BOT_TOKEN=your-bot-token -``` - -Sync to container: `mkdir -p data/env && cp .env data/env/env` - -## Next Steps - -If you're in the middle of `/setup`, return to the setup flow now. - -Otherwise, run `/manage-channels` to wire this channel to an agent group. - -## 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. -- **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 `, show the user the 4-digit `CODE` from the `PAIR_TELEGRAM_ISSUED` block (follow the `REMINDER_TO_ASSISTANT` line in that block), and tell them to send just the 4 digits as a message from the chat they want to register (DM the bot for `main`, post in the group otherwise). In groups with Group Privacy ON, prefix with the bot handle: `@ CODE`. Wrong guesses invalidate the code — if a `PAIR_TELEGRAM_ATTEMPT` block arrives with a mismatched `RECEIVED_CODE`, a `PAIR_TELEGRAM_NEW_CODE` block will follow automatically (up to 5 regenerations); show the new code. On `PAIR_TELEGRAM STATUS=failed ERROR=max-regenerations-exceeded`, ask the user if they want to try again and re-invoke the step — each invocation starts a fresh 5-attempt batch. Success emits `PAIR_TELEGRAM STATUS=success` with `PLATFORM_ID`, `IS_GROUP`, and `ADMIN_USER_ID`. The service must be running for this to work (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. diff --git a/.claude/skills/add-telegram-v2/REMOVE.md b/.claude/skills/add-telegram/REMOVE.md similarity index 100% rename from .claude/skills/add-telegram-v2/REMOVE.md rename to .claude/skills/add-telegram/REMOVE.md diff --git a/.claude/skills/add-telegram/SKILL.md b/.claude/skills/add-telegram/SKILL.md index da760ee..053d7a2 100644 --- a/.claude/skills/add-telegram/SKILL.md +++ b/.claude/skills/add-telegram/SKILL.md @@ -1,214 +1,108 @@ --- -name: add-telegram -description: Add Telegram as a channel. Can replace WhatsApp entirely or run alongside it. Also configurable as a control-only channel (triggers actions) or passive channel (receives notifications only). +name: add-telegram-v2 +description: Add Telegram channel integration to NanoClaw v2 via Chat SDK. --- # Add Telegram Channel -This skill adds Telegram support to NanoClaw, then walks through interactive setup. +Adds Telegram bot support to NanoClaw v2 using the Chat SDK bridge. -## Phase 1: Pre-flight +## Install -### Check if already applied +v2 trunk doesn't ship channels. This skill copies the Telegram adapter, its formatting/pairing helpers, their tests, and the `pair-telegram` setup step in from the `channels` branch. -Check if `src/channels/telegram.ts` exists. If it does, skip to Phase 3 (Setup). The code changes are already in place. +### Pre-flight (idempotent) -### Ask the user +Skip to **Credentials** if all of these are already in place: -Use `AskUserQuestion` to collect configuration: +- `src/channels/telegram.ts`, `telegram-pairing.ts`, `telegram-markdown-sanitize.ts` (and their `.test.ts` siblings) all exist +- `src/channels/index.ts` contains `import './telegram.js';` +- `setup/pair-telegram.ts` exists and `setup/index.ts`'s `STEPS` map contains `'pair-telegram':` +- `@chat-adapter/telegram` is listed in `package.json` dependencies -AskUserQuestion: Do you have a Telegram bot token, or do you need to create one? +Otherwise continue. Every step below is safe to re-run. -If they have one, collect it now. If not, we'll create one in Phase 3. - -## Phase 2: Apply Code Changes - -### Ensure channel remote +### 1. Fetch the channels branch ```bash -git remote -v +git fetch origin channels ``` -If `telegram` is missing, add it: +### 2. Copy the adapter, helpers, tests, and setup step ```bash -git remote add telegram https://github.com/qwibitai/nanoclaw-telegram.git +git show origin/channels:src/channels/telegram.ts > src/channels/telegram.ts +git show origin/channels:src/channels/telegram-pairing.ts > src/channels/telegram-pairing.ts +git show origin/channels:src/channels/telegram-pairing.test.ts > src/channels/telegram-pairing.test.ts +git show origin/channels:src/channels/telegram-markdown-sanitize.ts > src/channels/telegram-markdown-sanitize.ts +git show origin/channels:src/channels/telegram-markdown-sanitize.test.ts > src/channels/telegram-markdown-sanitize.test.ts +git show origin/channels:setup/pair-telegram.ts > setup/pair-telegram.ts ``` -### Merge the skill branch +### 3. Append the self-registration import -```bash -git fetch telegram main -git merge telegram/main || { - git checkout --theirs pnpm-lock.yaml - git add pnpm-lock.yaml - git merge --continue -} +Append to `src/channels/index.ts` (skip if already present): + +```typescript +import './telegram.js'; ``` -This merges in: -- `src/channels/telegram.ts` (TelegramChannel class with self-registration via `registerChannel`) -- `src/channels/telegram.test.ts` (unit tests with grammy mock) -- `import './telegram.js'` appended to the channel barrel file `src/channels/index.ts` -- `grammy` npm dependency in `package.json` -- `TELEGRAM_BOT_TOKEN` in `.env.example` +### 4. Register the setup step -If the merge reports conflicts, resolve them by reading the conflicted files and understanding the intent of both sides. +In `setup/index.ts`, add this entry to the `STEPS` map (right after the `register` line is fine; skip if already present): -### Validate code changes +```typescript +'pair-telegram': () => import('./pair-telegram.js'), +``` + +### 5. Install the adapter package (pinned) + +```bash +pnpm install @chat-adapter/telegram@4.26.0 +``` + +### 6. Build ```bash -pnpm install pnpm run build -pnpm exec vitest run src/channels/telegram.test.ts ``` -All tests must pass (including the new Telegram tests) and build must be clean before proceeding. +## Credentials -## Phase 3: Setup +### Create Telegram Bot -### Create Telegram Bot (if needed) +1. Open Telegram and search for `@BotFather` +2. Send `/newbot` and follow the prompts: + - Bot name: Something friendly (e.g., "NanoClaw Assistant") + - Bot username: Must end with "bot" (e.g., "nanoclaw_bot") +3. Copy the bot token (looks like `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`) -If the user doesn't have a bot token, tell them: +**Important for group chats**: By default, Telegram bots only see @mentions and commands in groups. To let the bot see all messages: -> I need you to create a Telegram bot: -> -> 1. Open Telegram and search for `@BotFather` -> 2. Send `/newbot` and follow prompts: -> - Bot name: Something friendly (e.g., "Andy Assistant") -> - Bot username: Must end with "bot" (e.g., "andy_ai_bot") -> 3. Copy the bot token (looks like `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`) - -Wait for the user to provide the token. +1. Open `@BotFather` > `/mybots` > select your bot +2. **Bot Settings** > **Group Privacy** > **Turn off** ### Configure environment Add to `.env`: ```bash -TELEGRAM_BOT_TOKEN= +TELEGRAM_BOT_TOKEN=your-bot-token ``` -Channels auto-enable when their credentials are present — no extra configuration needed. +Sync to container: `mkdir -p data/env && cp .env data/env/env` -Sync to container environment: +## Next Steps -```bash -mkdir -p data/env && cp .env data/env/env -``` +If you're in the middle of `/setup`, return to the setup flow now. -The container reads environment from `data/env/env`, not `.env` directly. +Otherwise, run `/manage-channels` to wire this channel to an agent group. -### Disable Group Privacy (for group chats) +## Channel Info -Tell the user: - -> **Important for group chats**: By default, Telegram bots only see @mentions and commands in groups. To let the bot see all messages: -> -> 1. Open Telegram and search for `@BotFather` -> 2. Send `/mybots` and select your bot -> 3. Go to **Bot Settings** > **Group Privacy** > **Turn off** -> -> This is optional if you only want trigger-based responses via @mentioning the bot. - -### Build and restart - -```bash -pnpm run build -launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS -# Linux: systemctl --user restart nanoclaw -``` - -## Phase 4: Registration - -### Get Chat ID - -Tell the user: - -> 1. Open your bot in Telegram (search for its username) -> 2. Send `/chatid` — it will reply with the chat ID -> 3. For groups: add the bot to the group first, then send `/chatid` in the group - -Wait for the user to provide the chat ID (format: `tg:123456789` or `tg:-1001234567890`). - -### Register the chat - -The chat ID, name, and folder name are needed. Use `pnpm exec tsx setup/index.ts --step register` with the appropriate flags. - -For a main chat (responds to all messages): - -```bash -pnpm exec tsx setup/index.ts --step register -- --jid "tg:" --name "" --folder "telegram_main" --trigger "@${ASSISTANT_NAME}" --channel telegram --no-trigger-required --is-main -``` - -For additional chats (trigger-only): - -```bash -pnpm exec tsx setup/index.ts --step register -- --jid "tg:" --name "" --folder "telegram_" --trigger "@${ASSISTANT_NAME}" --channel telegram -``` - -## Phase 5: Verify - -### Test the connection - -Tell the user: - -> Send a message to your registered Telegram chat: -> - For main chat: Any message works -> - For non-main: `@Andy hello` or @mention the bot -> -> The bot should respond within a few seconds. - -### Check logs if needed - -```bash -tail -f logs/nanoclaw.log -``` - -## Troubleshooting - -### Bot not responding - -Check: -1. `TELEGRAM_BOT_TOKEN` is set in `.env` AND synced to `data/env/env` -2. Chat is registered in SQLite (check with: `sqlite3 store/messages.db "SELECT * FROM registered_groups WHERE jid LIKE 'tg:%'"`) -3. For non-main chats: message includes trigger pattern -4. Service is running: `launchctl list | grep nanoclaw` (macOS) or `systemctl --user status nanoclaw` (Linux) - -### Bot only responds to @mentions in groups - -Group Privacy is enabled (default). Fix: -1. `@BotFather` > `/mybots` > select bot > **Bot Settings** > **Group Privacy** > **Turn off** -2. Remove and re-add the bot to the group (required for the change to take effect) - -### Getting chat ID - -If `/chatid` doesn't work: -- Verify token: `curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getMe"` -- Check bot is started: `tail -f logs/nanoclaw.log` - -## After Setup - -If running `pnpm run dev` while the service is active: -```bash -# macOS: -launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist -pnpm run dev -# When done testing: -launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist -# Linux: -# systemctl --user stop nanoclaw -# pnpm run dev -# systemctl --user start nanoclaw -``` - -## Removal - -To remove Telegram integration: - -1. Delete `src/channels/telegram.ts` and `src/channels/telegram.test.ts` -2. Remove `import './telegram.js'` from `src/channels/index.ts` -3. Remove `TELEGRAM_BOT_TOKEN` from `.env` -4. Remove Telegram registrations from SQLite: `sqlite3 store/messages.db "DELETE FROM registered_groups WHERE jid LIKE 'tg:%'"` -5. Uninstall: `pnpm uninstall grammy` -6. Rebuild: `pnpm run build && launchctl kickstart -k gui/$(id -u)/com.nanoclaw` (macOS) or `pnpm run build && systemctl --user restart nanoclaw` (Linux) +- **type**: `telegram` +- **terminology**: Telegram calls them "groups" and "chats." A "group" has multiple members; a "chat" is a 1:1 conversation with the bot. +- **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 `, show the user the 4-digit `CODE` from the `PAIR_TELEGRAM_ISSUED` block (follow the `REMINDER_TO_ASSISTANT` line in that block), and tell them to send just the 4 digits as a message from the chat they want to register (DM the bot for `main`, post in the group otherwise). In groups with Group Privacy ON, prefix with the bot handle: `@ CODE`. Wrong guesses invalidate the code — if a `PAIR_TELEGRAM_ATTEMPT` block arrives with a mismatched `RECEIVED_CODE`, a `PAIR_TELEGRAM_NEW_CODE` block will follow automatically (up to 5 regenerations); show the new code. On `PAIR_TELEGRAM STATUS=failed ERROR=max-regenerations-exceeded`, ask the user if they want to try again and re-invoke the step — each invocation starts a fresh 5-attempt batch. Success emits `PAIR_TELEGRAM STATUS=success` with `PLATFORM_ID`, `IS_GROUP`, and `ADMIN_USER_ID`. The service must be running for this to work (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. diff --git a/.claude/skills/add-telegram-v2/VERIFY.md b/.claude/skills/add-telegram/VERIFY.md similarity index 100% rename from .claude/skills/add-telegram-v2/VERIFY.md rename to .claude/skills/add-telegram/VERIFY.md diff --git a/.claude/skills/add-webex-v2/REMOVE.md b/.claude/skills/add-webex/REMOVE.md similarity index 100% rename from .claude/skills/add-webex-v2/REMOVE.md rename to .claude/skills/add-webex/REMOVE.md diff --git a/.claude/skills/add-webex-v2/SKILL.md b/.claude/skills/add-webex/SKILL.md similarity index 100% rename from .claude/skills/add-webex-v2/SKILL.md rename to .claude/skills/add-webex/SKILL.md diff --git a/.claude/skills/add-webex-v2/VERIFY.md b/.claude/skills/add-webex/VERIFY.md similarity index 100% rename from .claude/skills/add-webex-v2/VERIFY.md rename to .claude/skills/add-webex/VERIFY.md diff --git a/.claude/skills/add-whatsapp-cloud-v2/REMOVE.md b/.claude/skills/add-whatsapp-cloud/REMOVE.md similarity index 100% rename from .claude/skills/add-whatsapp-cloud-v2/REMOVE.md rename to .claude/skills/add-whatsapp-cloud/REMOVE.md diff --git a/.claude/skills/add-whatsapp-cloud-v2/SKILL.md b/.claude/skills/add-whatsapp-cloud/SKILL.md similarity index 100% rename from .claude/skills/add-whatsapp-cloud-v2/SKILL.md rename to .claude/skills/add-whatsapp-cloud/SKILL.md diff --git a/.claude/skills/add-whatsapp-cloud-v2/VERIFY.md b/.claude/skills/add-whatsapp-cloud/VERIFY.md similarity index 100% rename from .claude/skills/add-whatsapp-cloud-v2/VERIFY.md rename to .claude/skills/add-whatsapp-cloud/VERIFY.md diff --git a/.claude/skills/add-whatsapp-v2/SKILL.md b/.claude/skills/add-whatsapp-v2/SKILL.md deleted file mode 100644 index 8d90b62..0000000 --- a/.claude/skills/add-whatsapp-v2/SKILL.md +++ /dev/null @@ -1,264 +0,0 @@ ---- -name: add-whatsapp-v2 -description: Add WhatsApp channel to NanoClaw v2 using native Baileys adapter. Direct connection — no Chat SDK bridge. Uses QR code or pairing code for authentication. ---- - -# Add WhatsApp Channel - -Adds WhatsApp support to NanoClaw v2 using the native Baileys adapter (no Chat SDK bridge). - -## Install - -v2 trunk doesn't ship channels. This skill copies the native WhatsApp (Baileys) adapter and its `whatsapp-auth` setup step in from the `channels` branch. No Chat SDK bridge. - -### Pre-flight (idempotent) - -Skip to **Credentials** if all of these are already in place: - -- `src/channels/whatsapp.ts` exists -- `src/channels/index.ts` contains `import './whatsapp.js';` -- `setup/whatsapp-auth.ts` and `setup/groups.ts` both exist -- `setup/index.ts`'s `STEPS` map contains both `'whatsapp-auth':` and `groups:` -- `@whiskeysockets/baileys`, `qrcode`, `pino` are listed in `package.json` dependencies - -Otherwise continue. Every step below is safe to re-run. - -### 1. Fetch the channels branch - -```bash -git fetch origin channels -``` - -### 2. Copy the adapter and setup steps - -```bash -git show origin/channels:src/channels/whatsapp.ts > src/channels/whatsapp.ts -git show origin/channels:setup/whatsapp-auth.ts > setup/whatsapp-auth.ts -git show origin/channels:setup/groups.ts > setup/groups.ts -``` - -### 3. Append the self-registration import - -Append to `src/channels/index.ts` (skip if already present): - -```typescript -import './whatsapp.js'; -``` - -### 4. Register the setup steps - -In `setup/index.ts`, add these entries to the `STEPS` map (skip lines already present): - -```typescript -groups: () => import('./groups.js'), -'whatsapp-auth': () => import('./whatsapp-auth.js'), -``` - -### 5. Install the adapter packages (pinned) - -```bash -pnpm install @whiskeysockets/baileys@6.17.16 qrcode@1.5.4 @types/qrcode@1.5.6 pino@9.6.0 -``` - -### 6. Build - -```bash -pnpm run build -``` - -## Credentials - -WhatsApp uses linked-device authentication — no API key, just a one-time pairing from your phone. - -### Check current state - -Check if WhatsApp is already authenticated. If `store/auth/creds.json` exists, skip to "Shared vs dedicated number". - -```bash -test -f store/auth/creds.json && echo "WhatsApp auth exists" || echo "No WhatsApp auth" -``` - -### Detect environment - -Check whether the environment is headless (no display server): - -```bash -[[ -z "$DISPLAY" && -z "$WAYLAND_DISPLAY" && "$OSTYPE" != darwin* ]] && echo "IS_HEADLESS=true" || echo "IS_HEADLESS=false" -``` - -### Ask the user - -Use `AskUserQuestion` to collect configuration. **Adapt auth options based on environment:** - -If IS_HEADLESS=true AND not WSL → AskUserQuestion: How do you want to authenticate WhatsApp? -- **Pairing code** (Recommended) - Enter a numeric code on your phone (no camera needed, requires phone number) -- **QR code in terminal** - Displays QR code in the terminal (can be too small on some displays) - -Otherwise (macOS, desktop Linux, or WSL) → AskUserQuestion: How do you want to authenticate WhatsApp? -- **QR code in browser** (Recommended) - Opens a browser window with a large, scannable QR code -- **Pairing code** - Enter a numeric code on your phone (no camera needed, requires phone number) -- **QR code in terminal** - Displays QR code in the terminal (can be too small on some displays) - -If they chose pairing code: - -AskUserQuestion: What is your phone number? (Digits only — country code followed by your 10-digit number, no + prefix, spaces, or dashes. Example: 14155551234 where 1 is the US country code and 4155551234 is the phone number.) - -### Clean previous auth state (if re-authenticating) - -```bash -rm -rf store/auth/ -``` - -### Run WhatsApp authentication - -For QR code in browser (recommended): - -```bash -pnpm exec tsx setup/index.ts --step whatsapp-auth -- --method qr-browser -``` - -(Bash timeout: 150000ms) - -Tell the user: - -> A browser window will open with a QR code. -> -> 1. Open WhatsApp > **Settings** > **Linked Devices** > **Link a Device** -> 2. Scan the QR code in the browser -> 3. The page will show "Authenticated!" when done - -For QR code in terminal: - -```bash -pnpm exec tsx setup/index.ts --step whatsapp-auth -- --method qr-terminal -``` - -(Bash timeout: 150000ms) - -Tell the user: - -> 1. Open WhatsApp > **Settings** > **Linked Devices** > **Link a Device** -> 2. Scan the QR code displayed in the terminal - -For pairing code: - -Tell the user to have WhatsApp open on **Settings > Linked Devices > Link a Device**, ready to tap **"Link with phone number instead"** — the code expires in ~60 seconds and must be entered immediately. - -Run the auth process in the background and poll `store/pairing-code.txt` for the code: - -```bash -rm -f store/pairing-code.txt && pnpm exec tsx setup/index.ts --step whatsapp-auth -- --method pairing-code --phone > /tmp/wa-auth.log 2>&1 & -``` - -Then immediately poll for the code (do NOT wait for the background command to finish): - -```bash -for i in $(seq 1 20); do [ -f store/pairing-code.txt ] && cat store/pairing-code.txt && break; sleep 1; done -``` - -Display the code to the user the moment it appears. Tell them: - -> **Enter this code now** — it expires in ~60 seconds. -> -> 1. Open WhatsApp > **Settings** > **Linked Devices** > **Link a Device** -> 2. Tap **Link with phone number instead** -> 3. Enter the code immediately - -After the user enters the code, poll for authentication to complete: - -```bash -for i in $(seq 1 60); do grep -q 'STATUS: authenticated' /tmp/wa-auth.log 2>/dev/null && echo "authenticated" && break; grep -q 'STATUS: failed' /tmp/wa-auth.log 2>/dev/null && echo "failed" && break; sleep 2; done -``` - -**If failed:** logged_out → delete `store/auth/` and re-run. timeout → ask user, offer retry. - -### Verify authentication succeeded - -```bash -test -f store/auth/creds.json && echo "Authentication successful" || echo "Authentication failed" -``` - -### Shared vs dedicated number - -AskUserQuestion: Is this a shared phone number (personal WhatsApp) or a dedicated number? -- **Shared number** — your personal WhatsApp (bot prefixes messages with its name) -- **Dedicated number** — a separate phone/SIM for the assistant - -If dedicated, add to `.env`: - -```bash -ASSISTANT_HAS_OWN_NUMBER=true -``` - -## Next Steps - -If you're in the middle of `/setup`, return to the setup flow now. - -Otherwise, run `/manage-channels` to wire this channel to an agent group. - -## Channel Info - -- **type**: `whatsapp` -- **terminology**: WhatsApp calls them "groups" and "chats." A "chat" is a 1:1 DM; a "group" has multiple members. -- **how-to-find-id**: DMs use `@s.whatsapp.net` (e.g. `14155551234@s.whatsapp.net`). Groups use `@g.us`. To find your number: `node -e "const c=JSON.parse(require('fs').readFileSync('store/auth/creds.json','utf-8'));console.log(c.me?.id?.split(':')[0]+'@s.whatsapp.net')"`. Groups are auto-discovered — check `sqlite3 data/v2.db "SELECT platform_id, name FROM messaging_groups WHERE channel_type='whatsapp' AND is_group=1"`. -- **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. - -### Features - -- Markdown formatting — `**bold**`→`*bold*`, `*italic*`→`_italic_`, headings→bold, code blocks preserved -- Approval questions — `ask_user_question` renders with `/approve`, `/reject` slash commands -- File attachments — send and receive images, video, audio, documents -- Reactions — send emoji reactions on messages -- Typing indicators — composing presence updates -- Credential requests — text fallback (WhatsApp has no modal support) - -Not supported (WhatsApp linked device limitation): edit messages, delete messages. - -## Troubleshooting - -### QR code expired - -QR codes expire after ~60 seconds. Re-run the auth command: - -```bash -rm -rf store/auth/ && pnpm exec tsx setup/index.ts --step whatsapp-auth -- --method qr-browser -``` - -### Pairing code not working - -Codes expire in ~60 seconds. Delete auth and retry: - -```bash -rm -rf store/auth/ && pnpm exec tsx setup/index.ts --step whatsapp-auth -- --method pairing-code --phone -``` - -Ensure: digits only (no `+`), phone has internet, WhatsApp is updated. - -If pairing code keeps failing, switch to QR-browser auth instead: - -```bash -rm -rf store/auth/ && pnpm exec tsx setup/index.ts --step whatsapp-auth -- --method qr-browser -``` - -### "waiting for this message" on reactions - -Signal sessions corrupted from rapid restarts. Clear sessions: - -```bash -systemctl --user stop nanoclaw -rm store/auth/session-*.json -systemctl --user start nanoclaw -``` - -### Bot not responding - -1. Auth exists: `test -f store/auth/creds.json` -2. Connected: `grep "Connected to WhatsApp" logs/nanoclaw.log | tail -1` -3. Channel wired: `sqlite3 data/v2.db "SELECT mg.platform_id, mg.name FROM messaging_groups mg JOIN messaging_group_agents mga ON mg.id=mga.messaging_group_id WHERE mg.channel_type='whatsapp'"` -4. Service running: `systemctl --user status nanoclaw` - -### "conflict" disconnection - -Two instances connected with same credentials. Ensure only one NanoClaw process is running. diff --git a/.claude/skills/add-whatsapp/SKILL.md b/.claude/skills/add-whatsapp/SKILL.md index 0ece169..8d90b62 100644 --- a/.claude/skills/add-whatsapp/SKILL.md +++ b/.claude/skills/add-whatsapp/SKILL.md @@ -1,20 +1,81 @@ --- -name: add-whatsapp -description: Add WhatsApp as a channel. Can replace other channels entirely or run alongside them. Uses QR code or pairing code for authentication. +name: add-whatsapp-v2 +description: Add WhatsApp channel to NanoClaw v2 using native Baileys adapter. Direct connection — no Chat SDK bridge. Uses QR code or pairing code for authentication. --- # Add WhatsApp Channel -This skill adds WhatsApp support to NanoClaw. It installs the WhatsApp channel code, dependencies, and guides through authentication, registration, and configuration. +Adds WhatsApp support to NanoClaw v2 using the native Baileys adapter (no Chat SDK bridge). -## Phase 1: Pre-flight +## Install + +v2 trunk doesn't ship channels. This skill copies the native WhatsApp (Baileys) adapter and its `whatsapp-auth` setup step in from the `channels` branch. No Chat SDK bridge. + +### Pre-flight (idempotent) + +Skip to **Credentials** if all of these are already in place: + +- `src/channels/whatsapp.ts` exists +- `src/channels/index.ts` contains `import './whatsapp.js';` +- `setup/whatsapp-auth.ts` and `setup/groups.ts` both exist +- `setup/index.ts`'s `STEPS` map contains both `'whatsapp-auth':` and `groups:` +- `@whiskeysockets/baileys`, `qrcode`, `pino` are listed in `package.json` dependencies + +Otherwise continue. Every step below is safe to re-run. + +### 1. Fetch the channels branch + +```bash +git fetch origin channels +``` + +### 2. Copy the adapter and setup steps + +```bash +git show origin/channels:src/channels/whatsapp.ts > src/channels/whatsapp.ts +git show origin/channels:setup/whatsapp-auth.ts > setup/whatsapp-auth.ts +git show origin/channels:setup/groups.ts > setup/groups.ts +``` + +### 3. Append the self-registration import + +Append to `src/channels/index.ts` (skip if already present): + +```typescript +import './whatsapp.js'; +``` + +### 4. Register the setup steps + +In `setup/index.ts`, add these entries to the `STEPS` map (skip lines already present): + +```typescript +groups: () => import('./groups.js'), +'whatsapp-auth': () => import('./whatsapp-auth.js'), +``` + +### 5. Install the adapter packages (pinned) + +```bash +pnpm install @whiskeysockets/baileys@6.17.16 qrcode@1.5.4 @types/qrcode@1.5.6 pino@9.6.0 +``` + +### 6. Build + +```bash +pnpm run build +``` + +## Credentials + +WhatsApp uses linked-device authentication — no API key, just a one-time pairing from your phone. ### Check current state -Check if WhatsApp is already configured. If `store/auth/` exists with credential files, skip to Phase 4 (Registration) or Phase 5 (Verify). +Check if WhatsApp is already authenticated. If `store/auth/creds.json` exists, skip to "Shared vs dedicated number". ```bash -ls store/auth/creds.json 2>/dev/null && echo "WhatsApp auth exists" || echo "No WhatsApp auth" +test -f store/auth/creds.json && echo "WhatsApp auth exists" || echo "No WhatsApp auth" ``` ### Detect environment @@ -42,57 +103,6 @@ If they chose pairing code: AskUserQuestion: What is your phone number? (Digits only — country code followed by your 10-digit number, no + prefix, spaces, or dashes. Example: 14155551234 where 1 is the US country code and 4155551234 is the phone number.) -## Phase 2: Apply Code Changes - -Check if `src/channels/whatsapp.ts` already exists. If it does, skip to Phase 3 (Authentication). - -### Ensure channel remote - -```bash -git remote -v -``` - -If `whatsapp` is missing, add it: - -```bash -git remote add whatsapp https://github.com/qwibitai/nanoclaw-whatsapp.git -``` - -### Merge the skill branch - -```bash -git fetch whatsapp main -git merge whatsapp/main || { - git checkout --theirs pnpm-lock.yaml - git add pnpm-lock.yaml - git merge --continue -} -``` - -This merges in: -- `src/channels/whatsapp.ts` (WhatsAppChannel class with self-registration via `registerChannel`) -- `src/channels/whatsapp.test.ts` (41 unit tests) -- `src/whatsapp-auth.ts` (standalone WhatsApp authentication script) -- `setup/whatsapp-auth.ts` (WhatsApp auth setup step) -- `import './whatsapp.js'` appended to the channel barrel file `src/channels/index.ts` -- `'whatsapp-auth'` step added to `setup/index.ts` -- `@whiskeysockets/baileys`, `qrcode`, `qrcode-terminal` npm dependencies in `package.json` -- `ASSISTANT_HAS_OWN_NUMBER` in `.env.example` - -If the merge reports conflicts, resolve them by reading the conflicted files and understanding the intent of both sides. - -### Validate code changes - -```bash -pnpm install -pnpm run build -pnpm exec vitest run src/channels/whatsapp.test.ts -``` - -All tests must pass and build must be clean before proceeding. - -## Phase 3: Authentication - ### Clean previous auth state (if re-authenticating) ```bash @@ -123,7 +133,9 @@ For QR code in terminal: pnpm exec tsx setup/index.ts --step whatsapp-auth -- --method qr-terminal ``` -Tell the user to run `pnpm run auth` in another terminal, then: +(Bash timeout: 150000ms) + +Tell the user: > 1. Open WhatsApp > **Settings** > **Linked Devices** > **Link a Device** > 2. Scan the QR code displayed in the terminal @@ -155,10 +167,10 @@ Display the code to the user the moment it appears. Tell them: After the user enters the code, poll for authentication to complete: ```bash -for i in $(seq 1 60); do grep -q 'AUTH_STATUS: authenticated' /tmp/wa-auth.log 2>/dev/null && echo "authenticated" && break; grep -q 'AUTH_STATUS: failed' /tmp/wa-auth.log 2>/dev/null && echo "failed" && break; sleep 2; done +for i in $(seq 1 60); do grep -q 'STATUS: authenticated' /tmp/wa-auth.log 2>/dev/null && echo "authenticated" && break; grep -q 'STATUS: failed' /tmp/wa-auth.log 2>/dev/null && echo "failed" && break; sleep 2; done ``` -**If failed:** qr_timeout → re-run. logged_out → delete `store/auth/` and re-run. 515 → re-run. timeout → ask user, offer retry. +**If failed:** logged_out → delete `store/auth/` and re-run. timeout → ask user, offer retry. ### Verify authentication succeeded @@ -166,128 +178,43 @@ for i in $(seq 1 60); do grep -q 'AUTH_STATUS: authenticated' /tmp/wa-auth.log 2 test -f store/auth/creds.json && echo "Authentication successful" || echo "Authentication failed" ``` -### Configure environment +### Shared vs dedicated number -Channels auto-enable when their credentials are present — WhatsApp activates when `store/auth/creds.json` exists. +AskUserQuestion: Is this a shared phone number (personal WhatsApp) or a dedicated number? +- **Shared number** — your personal WhatsApp (bot prefixes messages with its name) +- **Dedicated number** — a separate phone/SIM for the assistant -Sync to container environment: +If dedicated, add to `.env`: ```bash -mkdir -p data/env && cp .env data/env/env +ASSISTANT_HAS_OWN_NUMBER=true ``` -## Phase 4: Registration +## Next Steps -### Configure trigger and channel type +If you're in the middle of `/setup`, return to the setup flow now. -Get the bot's WhatsApp number: `node -e "const c=require('./store/auth/creds.json');console.log(c.me.id.split(':')[0].split('@')[0])"` +Otherwise, run `/manage-channels` to wire this channel to an agent group. -AskUserQuestion: Is this a shared phone number (personal WhatsApp) or a dedicated number (separate device)? -- **Shared number** - Your personal WhatsApp number (recommended: use self-chat or a solo group) -- **Dedicated number** - A separate phone/SIM for the assistant +## Channel Info -AskUserQuestion: What trigger word should activate the assistant? -- **@Andy** - Default trigger -- **@Claw** - Short and easy -- **@Claude** - Match the AI name +- **type**: `whatsapp` +- **terminology**: WhatsApp calls them "groups" and "chats." A "chat" is a 1:1 DM; a "group" has multiple members. +- **how-to-find-id**: DMs use `@s.whatsapp.net` (e.g. `14155551234@s.whatsapp.net`). Groups use `@g.us`. To find your number: `node -e "const c=JSON.parse(require('fs').readFileSync('store/auth/creds.json','utf-8'));console.log(c.me?.id?.split(':')[0]+'@s.whatsapp.net')"`. Groups are auto-discovered — check `sqlite3 data/v2.db "SELECT platform_id, name FROM messaging_groups WHERE channel_type='whatsapp' AND is_group=1"`. +- **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. -AskUserQuestion: What should the assistant call itself? -- **Andy** - Default name -- **Claw** - Short and easy -- **Claude** - Match the AI name +### Features -AskUserQuestion: Where do you want to chat with the assistant? +- Markdown formatting — `**bold**`→`*bold*`, `*italic*`→`_italic_`, headings→bold, code blocks preserved +- Approval questions — `ask_user_question` renders with `/approve`, `/reject` slash commands +- File attachments — send and receive images, video, audio, documents +- Reactions — send emoji reactions on messages +- Typing indicators — composing presence updates +- Credential requests — text fallback (WhatsApp has no modal support) -**Shared number options:** -- **Self-chat** (Recommended) - Chat in your own "Message Yourself" conversation -- **Solo group** - A group with just you and the linked device -- **Existing group** - An existing WhatsApp group - -**Dedicated number options:** -- **DM with bot** (Recommended) - Direct message the bot's number -- **Solo group** - A group with just you and the bot -- **Existing group** - An existing WhatsApp group - -### Get the JID - -**Self-chat:** JID = your phone number with `@s.whatsapp.net`. Extract from auth credentials: - -```bash -node -e "const c=JSON.parse(require('fs').readFileSync('store/auth/creds.json','utf-8'));console.log(c.me?.id?.split(':')[0]+'@s.whatsapp.net')" -``` - -**DM with bot:** Ask for the bot's phone number. JID = `NUMBER@s.whatsapp.net` - -**Group (solo, existing):** Run group sync and list available groups: - -```bash -pnpm exec tsx setup/index.ts --step groups -pnpm exec tsx setup/index.ts --step groups --list -``` - -The output shows `JID|GroupName` pairs. Present candidates as AskUserQuestion (names only, not JIDs). - -### Register the chat - -```bash -pnpm exec tsx setup/index.ts --step register \ - --jid "" \ - --name "" \ - --trigger "@" \ - --folder "whatsapp_main" \ - --channel whatsapp \ - --assistant-name "" \ - --is-main \ - --no-trigger-required # Only for main/self-chat -``` - -For additional groups (trigger-required): - -```bash -pnpm exec tsx setup/index.ts --step register \ - --jid "" \ - --name "" \ - --trigger "@" \ - --folder "whatsapp_" \ - --channel whatsapp -``` - -## Phase 5: Verify - -### Build and restart - -```bash -pnpm run build -``` - -Restart the service: - -```bash -# macOS (launchd) -launchctl kickstart -k gui/$(id -u)/com.nanoclaw - -# Linux (systemd) -systemctl --user restart nanoclaw - -# Linux (nohup fallback) -bash start-nanoclaw.sh -``` - -### Test the connection - -Tell the user: - -> Send a message to your registered WhatsApp chat: -> - For self-chat / main: Any message works -> - For groups: Use the trigger word (e.g., "@Andy hello") -> -> The assistant should respond within a few seconds. - -### Check logs if needed - -```bash -tail -f logs/nanoclaw.log -``` +Not supported (WhatsApp linked device limitation): edit messages, delete messages. ## Troubleshooting @@ -296,21 +223,18 @@ tail -f logs/nanoclaw.log QR codes expire after ~60 seconds. Re-run the auth command: ```bash -rm -rf store/auth/ && pnpm exec tsx src/whatsapp-auth.ts +rm -rf store/auth/ && pnpm exec tsx setup/index.ts --step whatsapp-auth -- --method qr-browser ``` ### Pairing code not working -Codes expire in ~60 seconds. To retry: +Codes expire in ~60 seconds. Delete auth and retry: ```bash -rm -rf store/auth/ && pnpm exec tsx src/whatsapp-auth.ts --pairing-code --phone +rm -rf store/auth/ && pnpm exec tsx setup/index.ts --step whatsapp-auth -- --method pairing-code --phone ``` -Enter the code **immediately** when it appears. Also ensure: -1. Phone number is digits only — country code + number, no `+` prefix (e.g., `14155551234` where `1` is country code, `4155551234` is the number) -2. Phone has internet access -3. WhatsApp is updated to the latest version +Ensure: digits only (no `+`), phone has internet, WhatsApp is updated. If pairing code keeps failing, switch to QR-browser auth instead: @@ -318,55 +242,23 @@ If pairing code keeps failing, switch to QR-browser auth instead: rm -rf store/auth/ && pnpm exec tsx setup/index.ts --step whatsapp-auth -- --method qr-browser ``` -### "conflict" disconnection +### "waiting for this message" on reactions -This happens when two instances connect with the same credentials. Ensure only one NanoClaw process is running: +Signal sessions corrupted from rapid restarts. Clear sessions: ```bash -pkill -f "node dist/index.js" -# Then restart +systemctl --user stop nanoclaw +rm store/auth/session-*.json +systemctl --user start nanoclaw ``` ### Bot not responding -Check: -1. Auth credentials exist: `ls store/auth/creds.json` -3. Chat is registered: `sqlite3 store/messages.db "SELECT * FROM registered_groups WHERE jid LIKE '%whatsapp%' OR jid LIKE '%@g.us' OR jid LIKE '%@s.whatsapp.net'"` -4. Service is running: `launchctl list | grep nanoclaw` (macOS) or `systemctl --user status nanoclaw` (Linux) -5. Logs: `tail -50 logs/nanoclaw.log` +1. Auth exists: `test -f store/auth/creds.json` +2. Connected: `grep "Connected to WhatsApp" logs/nanoclaw.log | tail -1` +3. Channel wired: `sqlite3 data/v2.db "SELECT mg.platform_id, mg.name FROM messaging_groups mg JOIN messaging_group_agents mga ON mg.id=mga.messaging_group_id WHERE mg.channel_type='whatsapp'"` +4. Service running: `systemctl --user status nanoclaw` -### Group names not showing +### "conflict" disconnection -Run group metadata sync: - -```bash -pnpm exec tsx setup/index.ts --step groups -``` - -This fetches all group names from WhatsApp. Runs automatically every 24 hours. - -## After Setup - -If running `pnpm run dev` while the service is active: - -```bash -# macOS: -launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist -pnpm run dev -# When done testing: -launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist - -# Linux: -# systemctl --user stop nanoclaw -# pnpm run dev -# systemctl --user start nanoclaw -``` - -## Removal - -To remove WhatsApp integration: - -1. Delete auth credentials: `rm -rf store/auth/` -2. Remove WhatsApp registrations: `sqlite3 store/messages.db "DELETE FROM registered_groups WHERE jid LIKE '%@g.us' OR jid LIKE '%@s.whatsapp.net'"` -3. Sync env: `mkdir -p data/env && cp .env data/env/env` -4. Rebuild and restart: `pnpm run build && launchctl kickstart -k gui/$(id -u)/com.nanoclaw` (macOS) or `pnpm run build && systemctl --user restart nanoclaw` (Linux) +Two instances connected with same credentials. Ensure only one NanoClaw process is running.