diff --git a/.claude/skills/add-gchat-v2/SKILL.md b/.claude/skills/add-gchat-v2/SKILL.md new file mode 100644 index 0000000..cf1a573 --- /dev/null +++ b/.claude/skills/add-gchat-v2/SKILL.md @@ -0,0 +1,78 @@ +--- +name: add-gchat-v2 +description: Add Google Chat channel integration to NanoClaw v2 via Chat SDK. +--- + +# Add Google Chat Channel (v2) + +This skill adds Google Chat support to NanoClaw v2 using the Chat SDK bridge. + +## Phase 1: Pre-flight + +Check if `src/channels/gchat-v2.ts` exists and the import is uncommented in `src/channels/index.ts`. If both are in place, skip to Phase 3. + +## Phase 2: Apply Code Changes + +### Install the adapter package + +```bash +npm install @chat-adapter/gchat +``` + +### Enable the channel + +Uncomment the Google Chat import in `src/channels/index.ts`: + +```typescript +import './gchat-v2.js'; +``` + +### Build + +```bash +npm run build +``` + +## Phase 3: Setup + +### Create Google Chat App + +> 1. Go to [Google Cloud Console](https://console.cloud.google.com) +> 2. Create or select a project +> 3. Enable the **Google Chat API** +> 4. Go to **Google Chat API** > **Configuration**: +> - App name and description +> - Connection settings: select **HTTP endpoint URL** and set to `https://your-domain/webhook/gchat` +> 5. Create a **Service Account**: +> - Go to **IAM & Admin** > **Service Accounts** > **Create Service Account** +> - Grant the Chat Bot role +> - Create a JSON key and download it + +### Configure environment + +Add the service account JSON as a single-line string to `.env`: + +```bash +GCHAT_CREDENTIALS={"type":"service_account","project_id":"...","private_key":"...","client_email":"..."} +``` + +Sync to container: `mkdir -p data/env && cp .env data/env/env` + +### Build and restart + +```bash +npm run build +launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS +# systemctl --user restart nanoclaw # Linux +``` + +## Phase 4: Verify + +> Add the bot to a Google Chat space, then send a message or @mention the bot. + +## Removal + +1. Comment out `import './gchat-v2.js'` in `src/channels/index.ts` +2. Remove `GCHAT_CREDENTIALS` from `.env` +3. `npm uninstall @chat-adapter/gchat` +4. Rebuild and restart diff --git a/.claude/skills/add-github-v2/SKILL.md b/.claude/skills/add-github-v2/SKILL.md new file mode 100644 index 0000000..44e7a41 --- /dev/null +++ b/.claude/skills/add-github-v2/SKILL.md @@ -0,0 +1,80 @@ +--- +name: add-github-v2 +description: Add GitHub channel integration to NanoClaw v2 via Chat SDK. PR comment threads as conversations. +--- + +# Add GitHub Channel (v2) + +This skill adds GitHub support to NanoClaw v2 using the Chat SDK bridge. The agent can participate in PR comment threads. + +## Phase 1: Pre-flight + +Check if `src/channels/github-v2.ts` exists and the import is uncommented in `src/channels/index.ts`. If both are in place, skip to Phase 3. + +## Phase 2: Apply Code Changes + +### Install the adapter package + +```bash +npm install @chat-adapter/github +``` + +### Enable the channel + +Uncomment the GitHub import in `src/channels/index.ts`: + +```typescript +import './github-v2.js'; +``` + +### Build + +```bash +npm run build +``` + +## Phase 3: Setup + +### Create GitHub credentials + +> 1. Go to [GitHub Settings > Developer Settings > Personal Access Tokens](https://github.com/settings/tokens) +> 2. Create a **Fine-grained token** with: +> - Repository access: select the repos you want the bot to monitor +> - Permissions: **Pull requests** (Read & Write), **Issues** (Read & Write) +> 3. Copy the token +> 4. Set up a webhook on your repo(s): +> - Go to **Settings** > **Webhooks** > **Add webhook** +> - Payload URL: `https://your-domain/webhook/github` +> - Content type: `application/json` +> - Secret: generate a random string +> - Events: select **Issue comments**, **Pull request review comments** + +### Configure environment + +Add to `.env`: + +```bash +GITHUB_TOKEN=github_pat_... +GITHUB_WEBHOOK_SECRET=your-webhook-secret +``` + +Sync to container: `mkdir -p data/env && cp .env data/env/env` + +### Build and restart + +```bash +npm run build +launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS +# systemctl --user restart nanoclaw # Linux +``` + +## Phase 4: Verify + +> @mention the bot in a PR comment or issue comment. The bot should respond within a few seconds. + +## Removal + +1. Comment out `import './github-v2.js'` in `src/channels/index.ts` +2. Remove `GITHUB_TOKEN` and `GITHUB_WEBHOOK_SECRET` from `.env` +3. `npm uninstall @chat-adapter/github` +4. Rebuild and restart diff --git a/.claude/skills/add-imessage-v2/SKILL.md b/.claude/skills/add-imessage-v2/SKILL.md new file mode 100644 index 0000000..33121ee --- /dev/null +++ b/.claude/skills/add-imessage-v2/SKILL.md @@ -0,0 +1,86 @@ +--- +name: add-imessage-v2 +description: Add iMessage channel integration to NanoClaw v2 via Chat SDK. Local (macOS) or remote (Photon API) mode. +--- + +# Add iMessage Channel (v2) + +This skill adds iMessage support to NanoClaw v2 using the Chat SDK bridge. Supports local mode (macOS with Full Disk Access) and remote mode (via Photon API). + +## Phase 1: Pre-flight + +Check if `src/channels/imessage-v2.ts` exists and the import is uncommented in `src/channels/index.ts`. If both are in place, skip to Phase 3. + +## Phase 2: Apply Code Changes + +### Install the adapter package + +```bash +npm install chat-adapter-imessage +``` + +### Enable the channel + +Uncomment the iMessage import in `src/channels/index.ts`: + +```typescript +import './imessage-v2.js'; +``` + +### Build + +```bash +npm run build +``` + +## Phase 3: Setup + +### Local Mode (macOS) + +> **Requirements**: macOS with Full Disk Access granted to your terminal/Node.js process. +> +> 1. Go to **System Settings** > **Privacy & Security** > **Full Disk Access** +> 2. Add your terminal app (Terminal, iTerm2, etc.) or the Node.js binary +> 3. The adapter reads directly from the iMessage database on disk + +### Remote Mode (Photon API) + +> 1. Set up a [Photon](https://photon.im) account +> 2. Get your server URL and API key + +### Configure environment + +**Local mode** — add to `.env`: + +```bash +IMESSAGE_ENABLED=true +IMESSAGE_LOCAL=true +``` + +**Remote mode** — add to `.env`: + +```bash +IMESSAGE_LOCAL=false +IMESSAGE_SERVER_URL=https://your-photon-server.com +IMESSAGE_API_KEY=your-api-key +``` + +Sync to container: `mkdir -p data/env && cp .env data/env/env` + +### Build and restart + +```bash +npm run build +launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS +``` + +## Phase 4: Verify + +> Send an iMessage to the account running NanoClaw. The bot should respond within a few seconds. + +## Removal + +1. Comment out `import './imessage-v2.js'` in `src/channels/index.ts` +2. Remove iMessage env vars from `.env` +3. `npm uninstall chat-adapter-imessage` +4. Rebuild and restart diff --git a/.claude/skills/add-linear-v2/SKILL.md b/.claude/skills/add-linear-v2/SKILL.md new file mode 100644 index 0000000..9ba6f8a --- /dev/null +++ b/.claude/skills/add-linear-v2/SKILL.md @@ -0,0 +1,77 @@ +--- +name: add-linear-v2 +description: Add Linear channel integration to NanoClaw v2 via Chat SDK. Issue comment threads as conversations. +--- + +# Add Linear Channel (v2) + +This skill adds Linear support to NanoClaw v2 using the Chat SDK bridge. The agent can participate in issue comment threads. + +## Phase 1: Pre-flight + +Check if `src/channels/linear-v2.ts` exists and the import is uncommented in `src/channels/index.ts`. If both are in place, skip to Phase 3. + +## Phase 2: Apply Code Changes + +### Install the adapter package + +```bash +npm install @chat-adapter/linear +``` + +### Enable the channel + +Uncomment the Linear import in `src/channels/index.ts`: + +```typescript +import './linear-v2.js'; +``` + +### Build + +```bash +npm run build +``` + +## Phase 3: Setup + +### Create Linear credentials + +> 1. Go to [Linear Settings > API](https://linear.app/settings/api) +> 2. Create a **Personal API Key** (or use an OAuth application for team-wide access) +> 3. Copy the API key +> 4. Set up a webhook: +> - Go to **Settings** > **API** > **Webhooks** > **New webhook** +> - URL: `https://your-domain/webhook/linear` +> - Select events: **Comment** (created, updated) +> - Copy the signing secret + +### Configure environment + +Add to `.env`: + +```bash +LINEAR_API_KEY=lin_api_... +LINEAR_WEBHOOK_SECRET=your-webhook-secret +``` + +Sync to container: `mkdir -p data/env && cp .env data/env/env` + +### Build and restart + +```bash +npm run build +launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS +# systemctl --user restart nanoclaw # Linux +``` + +## Phase 4: Verify + +> @mention the bot in a Linear issue comment. The bot should respond within a few seconds. + +## Removal + +1. Comment out `import './linear-v2.js'` in `src/channels/index.ts` +2. Remove `LINEAR_API_KEY` and `LINEAR_WEBHOOK_SECRET` from `.env` +3. `npm uninstall @chat-adapter/linear` +4. Rebuild and restart diff --git a/.claude/skills/add-matrix-v2/SKILL.md b/.claude/skills/add-matrix-v2/SKILL.md new file mode 100644 index 0000000..1e4848f --- /dev/null +++ b/.claude/skills/add-matrix-v2/SKILL.md @@ -0,0 +1,77 @@ +--- +name: add-matrix-v2 +description: Add Matrix channel integration to NanoClaw v2 via Chat SDK. Works with any Matrix homeserver (Element, Beeper, etc.). +--- + +# Add Matrix Channel (v2) + +This skill adds Matrix support to NanoClaw v2 using the Chat SDK bridge. Works with any Matrix homeserver. + +## Phase 1: Pre-flight + +Check if `src/channels/matrix-v2.ts` exists and the import is uncommented in `src/channels/index.ts`. If both are in place, skip to Phase 3. + +## Phase 2: Apply Code Changes + +### Install the adapter package + +```bash +npm install @beeper/chat-adapter-matrix +``` + +### Enable the channel + +Uncomment the Matrix import in `src/channels/index.ts`: + +```typescript +import './matrix-v2.js'; +``` + +### Build + +```bash +npm run build +``` + +## Phase 3: Setup + +### Create Matrix bot account + +> 1. Register a bot account on your Matrix homeserver (e.g., via Element) +> 2. Get the homeserver URL (e.g., `https://matrix.org` or your self-hosted URL) +> 3. Get an access token: +> - In Element: **Settings** > **Help & About** > **Access Token** (advanced) +> - Or via API: `curl -XPOST 'https://matrix.org/_matrix/client/r0/login' -d '{"type":"m.login.password","user":"botuser","password":"..."}'` +> 4. Note the bot's user ID (e.g., `@botuser:matrix.org`) + +### Configure environment + +Add to `.env`: + +```bash +MATRIX_BASE_URL=https://matrix.org +MATRIX_ACCESS_TOKEN=your-access-token +MATRIX_USER_ID=@botuser:matrix.org +MATRIX_BOT_USERNAME=botuser +``` + +Sync to container: `mkdir -p data/env && cp .env data/env/env` + +### Build and restart + +```bash +npm run build +launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS +# systemctl --user restart nanoclaw # Linux +``` + +## Phase 4: Verify + +> Invite the bot to a Matrix room and send a message. The bot should respond within a few seconds. + +## Removal + +1. Comment out `import './matrix-v2.js'` in `src/channels/index.ts` +2. Remove `MATRIX_BASE_URL`, `MATRIX_ACCESS_TOKEN`, `MATRIX_USER_ID`, `MATRIX_BOT_USERNAME` from `.env` +3. `npm uninstall @beeper/chat-adapter-matrix` +4. Rebuild and restart diff --git a/.claude/skills/add-resend-v2/SKILL.md b/.claude/skills/add-resend-v2/SKILL.md new file mode 100644 index 0000000..f858037 --- /dev/null +++ b/.claude/skills/add-resend-v2/SKILL.md @@ -0,0 +1,79 @@ +--- +name: add-resend-v2 +description: Add Resend (email) channel integration to NanoClaw v2 via Chat SDK. +--- + +# Add Resend Email Channel (v2) + +This skill adds email support via Resend to NanoClaw v2 using the Chat SDK bridge. + +## Phase 1: Pre-flight + +Check if `src/channels/resend-v2.ts` exists and the import is uncommented in `src/channels/index.ts`. If both are in place, skip to Phase 3. + +## Phase 2: Apply Code Changes + +### Install the adapter package + +```bash +npm install @resend/chat-sdk-adapter +``` + +### Enable the channel + +Uncomment the Resend import in `src/channels/index.ts`: + +```typescript +import './resend-v2.js'; +``` + +### Build + +```bash +npm run build +``` + +## Phase 3: Setup + +### Create Resend credentials + +> 1. Go to [resend.com](https://resend.com) and create an account +> 2. Add and verify your sending domain +> 3. Go to **API Keys** and create a new key +> 4. Set up a webhook: +> - Go to **Webhooks** > **Add webhook** +> - URL: `https://your-domain/webhook/resend` +> - Events: select **email.received** (for inbound email) +> - Copy the signing secret + +### Configure environment + +Add to `.env`: + +```bash +RESEND_API_KEY=re_... +RESEND_FROM_ADDRESS=bot@yourdomain.com +RESEND_FROM_NAME=NanoClaw +RESEND_WEBHOOK_SECRET=your-webhook-secret +``` + +Sync to container: `mkdir -p data/env && cp .env data/env/env` + +### Build and restart + +```bash +npm run build +launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS +# systemctl --user restart nanoclaw # Linux +``` + +## Phase 4: Verify + +> Send an email to the configured from address. The bot should respond via email within a few seconds. + +## Removal + +1. Comment out `import './resend-v2.js'` in `src/channels/index.ts` +2. Remove `RESEND_API_KEY`, `RESEND_FROM_ADDRESS`, `RESEND_FROM_NAME`, `RESEND_WEBHOOK_SECRET` from `.env` +3. `npm uninstall @resend/chat-sdk-adapter` +4. Rebuild and restart diff --git a/.claude/skills/add-slack-v2/SKILL.md b/.claude/skills/add-slack-v2/SKILL.md new file mode 100644 index 0000000..c5b5a17 --- /dev/null +++ b/.claude/skills/add-slack-v2/SKILL.md @@ -0,0 +1,81 @@ +--- +name: add-slack-v2 +description: Add Slack channel integration to NanoClaw v2 via Chat SDK. +--- + +# Add Slack Channel (v2) + +This skill adds Slack support to NanoClaw v2 using the Chat SDK bridge. + +## Phase 1: Pre-flight + +Check if `src/channels/slack-v2.ts` exists and the import is uncommented in `src/channels/index.ts`. If both are in place, skip to Phase 3. + +## Phase 2: Apply Code Changes + +### Install the adapter package + +```bash +npm install @chat-adapter/slack +``` + +### Enable the channel + +Uncomment the Slack import in `src/channels/index.ts`: + +```typescript +import './slack-v2.js'; +``` + +### Build + +```bash +npm run build +``` + +## Phase 3: Setup + +### Create Slack App (if needed) + +If the user doesn't have a 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** +> 6. Go to **Event Subscriptions**, enable events, and subscribe to: +> - `message.channels`, `message.groups`, `message.im`, `app_mention` +> 7. Set the Request URL to your webhook endpoint (e.g., `https://your-domain/webhook/slack`) + +### 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` + +### Build and restart + +```bash +npm run build +launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS +# systemctl --user restart nanoclaw # Linux +``` + +## Phase 4: Verify + +> Add the bot to a Slack channel, then send a message or @mention the bot. +> The bot should respond within a few seconds. + +## Removal + +1. Comment out `import './slack-v2.js'` in `src/channels/index.ts` +2. Remove `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET` from `.env` +3. `npm uninstall @chat-adapter/slack` +4. Rebuild and restart diff --git a/.claude/skills/add-teams-v2/SKILL.md b/.claude/skills/add-teams-v2/SKILL.md new file mode 100644 index 0000000..78f9650 --- /dev/null +++ b/.claude/skills/add-teams-v2/SKILL.md @@ -0,0 +1,75 @@ +--- +name: add-teams-v2 +description: Add Microsoft Teams channel integration to NanoClaw v2 via Chat SDK. +--- + +# Add Microsoft Teams Channel (v2) + +This skill adds Microsoft Teams support to NanoClaw v2 using the Chat SDK bridge. + +## Phase 1: Pre-flight + +Check if `src/channels/teams-v2.ts` exists and the import is uncommented in `src/channels/index.ts`. If both are in place, skip to Phase 3. + +## Phase 2: Apply Code Changes + +### Install the adapter package + +```bash +npm install @chat-adapter/teams +``` + +### Enable the channel + +Uncomment the Teams import in `src/channels/index.ts`: + +```typescript +import './teams-v2.js'; +``` + +### Build + +```bash +npm run build +``` + +## Phase 3: Setup + +### Create Teams Bot + +> 1. Go to [Azure Portal](https://portal.azure.com) > **Azure Bot** > **Create** +> 2. Fill in the bot details and create +> 3. Go to **Configuration**: +> - Messaging endpoint: `https://your-domain/webhook/teams` +> 4. Go to **Channels** > add **Microsoft Teams** +> 5. Note the **Microsoft App ID** and **Password** (from the bot's Azure AD app registration) + +### Configure environment + +Add to `.env`: + +```bash +TEAMS_APP_ID=your-app-id +TEAMS_APP_PASSWORD=your-app-password +``` + +Sync to container: `mkdir -p data/env && cp .env data/env/env` + +### Build and restart + +```bash +npm run build +launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS +# systemctl --user restart nanoclaw # Linux +``` + +## Phase 4: Verify + +> Add the bot to a Teams channel or send it a direct message. The bot should respond within a few seconds. + +## Removal + +1. Comment out `import './teams-v2.js'` in `src/channels/index.ts` +2. Remove `TEAMS_APP_ID` and `TEAMS_APP_PASSWORD` from `.env` +3. `npm uninstall @chat-adapter/teams` +4. Rebuild and restart diff --git a/.claude/skills/add-telegram-v2/SKILL.md b/.claude/skills/add-telegram-v2/SKILL.md new file mode 100644 index 0000000..7bcc079 --- /dev/null +++ b/.claude/skills/add-telegram-v2/SKILL.md @@ -0,0 +1,82 @@ +--- +name: add-telegram-v2 +description: Add Telegram channel integration to NanoClaw v2 via Chat SDK. +--- + +# Add Telegram Channel (v2) + +This skill adds Telegram support to NanoClaw v2 using the Chat SDK bridge. + +## Phase 1: Pre-flight + +Check if `src/channels/telegram-v2.ts` exists and the import is uncommented in `src/channels/index.ts`. If both are in place, skip to Phase 3. + +## Phase 2: Apply Code Changes + +### Install the adapter package + +```bash +npm install @chat-adapter/telegram +``` + +### Enable the channel + +Uncomment the Telegram import in `src/channels/index.ts`: + +```typescript +import './telegram-v2.js'; +``` + +### Build + +```bash +npm run build +``` + +## Phase 3: Setup + +### 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`) + +### Disable Group Privacy (for group chats) + +> **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` + +### Build and restart + +```bash +npm run build +launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS +# systemctl --user restart nanoclaw # Linux +``` + +## Phase 4: Verify + +> Send a message to your bot in Telegram (search for its username). +> For groups: add the bot to a group and send a message. +> The bot should respond within a few seconds. + +## Removal + +1. Comment out `import './telegram-v2.js'` in `src/channels/index.ts` +2. Remove `TELEGRAM_BOT_TOKEN` from `.env` +3. `npm uninstall @chat-adapter/telegram` +4. Rebuild and restart diff --git a/.claude/skills/add-webex-v2/SKILL.md b/.claude/skills/add-webex-v2/SKILL.md new file mode 100644 index 0000000..65f0dcf --- /dev/null +++ b/.claude/skills/add-webex-v2/SKILL.md @@ -0,0 +1,75 @@ +--- +name: add-webex-v2 +description: Add Webex channel integration to NanoClaw v2 via Chat SDK. +--- + +# Add Webex Channel (v2) + +This skill adds Cisco Webex support to NanoClaw v2 using the Chat SDK bridge. + +## Phase 1: Pre-flight + +Check if `src/channels/webex-v2.ts` exists and the import is uncommented in `src/channels/index.ts`. If both are in place, skip to Phase 3. + +## Phase 2: Apply Code Changes + +### Install the adapter package + +```bash +npm install @bitbasti/chat-adapter-webex +``` + +### Enable the channel + +Uncomment the Webex import in `src/channels/index.ts`: + +```typescript +import './webex-v2.js'; +``` + +### Build + +```bash +npm run build +``` + +## Phase 3: Setup + +### Create Webex Bot + +> 1. Go to [developer.webex.com](https://developer.webex.com/my-apps/new/bot) +> 2. Create a new bot and copy the **Bot Access Token** +> 3. Set up a webhook: +> - Use the Webex API to create a webhook pointing to `https://your-domain/webhook/webex` +> - Or use the Webex Developer Portal +> - Set a webhook secret for signature verification + +### Configure environment + +Add to `.env`: + +```bash +WEBEX_BOT_TOKEN=your-bot-token +WEBEX_WEBHOOK_SECRET=your-webhook-secret +``` + +Sync to container: `mkdir -p data/env && cp .env data/env/env` + +### Build and restart + +```bash +npm run build +launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS +# systemctl --user restart nanoclaw # Linux +``` + +## Phase 4: Verify + +> Add the bot to a Webex space or send it a direct message. The bot should respond within a few seconds. + +## Removal + +1. Comment out `import './webex-v2.js'` in `src/channels/index.ts` +2. Remove `WEBEX_BOT_TOKEN` and `WEBEX_WEBHOOK_SECRET` from `.env` +3. `npm uninstall @bitbasti/chat-adapter-webex` +4. Rebuild and restart diff --git a/.claude/skills/add-whatsapp-cloud-v2/SKILL.md b/.claude/skills/add-whatsapp-cloud-v2/SKILL.md new file mode 100644 index 0000000..32a08ae --- /dev/null +++ b/.claude/skills/add-whatsapp-cloud-v2/SKILL.md @@ -0,0 +1,82 @@ +--- +name: add-whatsapp-cloud-v2 +description: Add WhatsApp Business Cloud API channel to NanoClaw v2 via Chat SDK. Official Meta API (not Baileys). +--- + +# Add WhatsApp Cloud API Channel (v2) + +This skill adds WhatsApp support via the official Meta WhatsApp Business Cloud API. This is different from the Baileys-based WhatsApp adapter (which uses WhatsApp Web protocol). + +## Phase 1: Pre-flight + +Check if `src/channels/whatsapp-cloud-v2.ts` exists and the import is uncommented in `src/channels/index.ts`. If both are in place, skip to Phase 3. + +## Phase 2: Apply Code Changes + +### Install the adapter package + +```bash +npm install @chat-adapter/whatsapp +``` + +### Enable the channel + +Uncomment the WhatsApp Cloud API import in `src/channels/index.ts`: + +```typescript +import './whatsapp-cloud-v2.js'; +``` + +### Build + +```bash +npm run build +``` + +## Phase 3: Setup + +### Create WhatsApp Business App + +> 1. Go to [Meta for Developers](https://developers.facebook.com/apps/) and create an app (type: Business) +> 2. Add the **WhatsApp** product +> 3. Go to **WhatsApp** > **API Setup**: +> - Note the **Phone Number ID** (not the phone number itself) +> - Generate a **permanent System User access token** with `whatsapp_business_messaging` permission +> 4. Go to **WhatsApp** > **Configuration**: +> - Set webhook URL: `https://your-domain/webhook/whatsapp` +> - Set a **Verify Token** (any random string you choose) +> - Subscribe to webhook fields: `messages` +> 5. Copy the **App Secret** from **Settings** > **Basic** + +### Configure environment + +Add to `.env`: + +```bash +WHATSAPP_ACCESS_TOKEN=your-system-user-access-token +WHATSAPP_PHONE_NUMBER_ID=your-phone-number-id +WHATSAPP_APP_SECRET=your-app-secret +WHATSAPP_VERIFY_TOKEN=your-verify-token +``` + +Sync to container: `mkdir -p data/env && cp .env data/env/env` + +### Build and restart + +```bash +npm run build +launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS +# systemctl --user restart nanoclaw # Linux +``` + +## Phase 4: Verify + +> Send a message to your WhatsApp Business number. The bot should respond within a few seconds. +> Note: WhatsApp Cloud API only supports 1:1 DMs, not group chats. + +## Removal + +1. Comment out `import './whatsapp-cloud-v2.js'` in `src/channels/index.ts` +2. Remove `WHATSAPP_ACCESS_TOKEN`, `WHATSAPP_PHONE_NUMBER_ID`, `WHATSAPP_APP_SECRET`, `WHATSAPP_VERIFY_TOKEN` from `.env` +3. `npm uninstall @chat-adapter/whatsapp` +4. Rebuild and restart diff --git a/docs/v2-checklist.md b/docs/v2-checklist.md new file mode 100644 index 0000000..80f91d9 --- /dev/null +++ b/docs/v2-checklist.md @@ -0,0 +1,189 @@ +# NanoClaw v2 Checklist + +Status: [x] done, [~] partial, [ ] not started + +--- + +## Core Architecture + +- [x] Session DB replaces IPC (messages_in / messages_out as sole IO) +- [x] Central DB (agent groups, messaging groups, sessions, routing) +- [x] Host sweep (stale detection, retry with backoff, recurrence scheduling) +- [x] Active delivery polling (1s for running sessions) +- [x] Sweep delivery polling (60s across all sessions) +- [x] Container runner with session DB mounting +- [x] Per-session container lifecycle and idle timeout +- [x] Session resume (sessionId + resumeAt across queries) +- [x] Graceful shutdown (SIGTERM/SIGINT handlers) +- [x] Orphan container cleanup on startup + +## Agent Runner (Container) + +- [x] Poll loop (pending messages, status transitions, idle detection) +- [x] Concurrent follow-up polling while agent is thinking +- [x] Message formatter (chat, task, webhook, system kinds) +- [x] Command categorization (admin, filtered, passthrough) +- [x] Transcript archiving (pre-compact hook) +- [x] XML message formatting with sender, timestamp +- [~] Media handling inbound (formatter references attachments, no download-from-URL) + +## Agent Providers + +- [x] Claude provider (Agent SDK, tool allowlist, message stream, session resume) +- [x] Mock provider (testing) +- [x] Provider factory +- [ ] Codex provider +- [ ] OpenCode provider + +## Channel Adapters + +- [x] Channel adapter interface (setup, deliver, teardown, typing) +- [x] Chat SDK bridge (generic, works with any Chat SDK adapter) +- [x] Chat SDK SQLite state adapter (KV, subscriptions, locks, lists) +- [x] Discord via Chat SDK +- [~] Slack via Chat SDK (adapter + skill written, not tested) +- [~] Telegram via Chat SDK (adapter + skill written, not tested) +- [~] Microsoft Teams via Chat SDK (adapter + skill written, not tested) +- [~] Google Chat via Chat SDK (adapter + skill written, not tested) +- [~] Linear via Chat SDK (adapter + skill written, not tested) +- [~] GitHub via Chat SDK (adapter + skill written, not tested) +- [~] WhatsApp Cloud API via Chat SDK (adapter + skill written, not tested) +- [~] Resend (email) via Chat SDK (adapter + skill written, not tested) +- [~] Matrix via Chat SDK (adapter + skill written, not tested) +- [~] Webex via Chat SDK (adapter + skill written, not tested) +- [~] iMessage via Chat SDK (adapter + skill written, not tested) +- [x] Backward compatibility with native channels (old adapters still work) +- [ ] Setup flow wired to v2 channels +- [ ] Setup communicates each group is a different agent, distinct names +- [ ] Setup vs production channel separation +- [ ] Generate visual diagram of customized instance at end of setup + +## Routing + +- [x] Inbound routing (platform ID + thread ID -> agent group -> session) +- [x] Auto-create messaging group on first message +- [x] Session resolution (shared vs per-thread modes) +- [x] Message writing to session DB with seq numbering +- [x] Container waking on new message +- [~] Trigger rule matching (router picks highest-priority agent, regex/mention matching TODO) + +## Rich Messaging + +- [x] Interactive cards with buttons (ask_user_question) +- [x] Native platform rendering (Discord embeds, buttons) +- [x] Message editing +- [x] Emoji reactions +- [x] File sending from agent (outbox -> delivery) +- [x] File upload delivery (buffer-based via adapter) +- [x] Markdown formatting +- [~] Formatted /usage, /context, /cost output (commands pass through, no rich card formatting) +- [ ] Context window visibility: show position in context, approaching compaction, when compaction happens, post-compaction state +- [ ] Threading and replies support + +## MCP Tools (Container) + +- [x] send_message (text, optional cross-channel targeting) +- [x] send_file (copy to outbox, write messages_out) +- [x] edit_message +- [x] add_reaction +- [x] send_card +- [x] ask_user_question (blocking poll for response) +- [x] schedule_task (with process_after and recurrence) +- [x] list_tasks +- [x] cancel_task / pause_task / resume_task +- [x] send_to_agent (writes message, routing incomplete) + +## Scheduling + +- [x] One-shot scheduled messages (process_after / deliver_after) +- [x] Recurring tasks via cron expressions +- [x] Host sweep picks up due messages and advances recurrence +- [x] Scheduled outbound messages (no container wake needed) +- [~] Pre-agent scripts (task kind with script field, documented but not verified) + +## Permissions and Approval Flows + +- [x] Admin user ID per group +- [x] Admin-only command filtering in container +- [ ] Approval flow (sensitive action -> card to admin -> approve/reject -> execute) +- [ ] Role definitions beyond admin (custom roles, per-group permissions) +- [ ] Configurable sensitive action list +- [ ] Non-main groups requesting sensitive actions +- [ ] Agent requests dependency/package install (persists via Dockerfile change, requires approval) +- [ ] Agent self-modification flow: + - [ ] Agent requests code changes by delegating to a builder agent + - [ ] Builder agent has write access to the requesting agent's code and Dockerfile + - [ ] Approval modes: approve per-edit as builder works, or approve full diff at the end + - [ ] Diff review card sent to admin showing all proposed changes + - [ ] On approval: apply edits, rebuild container image, restart agent + - [ ] On rejection: discard changes, notify requesting agent + +## Agent-to-Agent Communication + +- [~] send_to_agent MCP tool (writes message, host-side routing TODO) +- [ ] Host delivery to target agent's session DB +- [ ] Agent spawning a new sub-agent +- [ ] Internal-only agents (no channel attached) +- [ ] Permission delegation from parent to child agent +- [ ] Specialist sub-agents (browser agent, dev agent — user's agent delegates with request/approval) + +## In-Chat Agent Management + +- [x] /clear (resets session) +- [x] /compact (triggers context compaction) +- [~] /context (passes through, no rich formatting) +- [~] /usage (passes through, no rich formatting) +- [~] /cost (passes through, no rich formatting) +- [ ] Smooth session transitions: load context into new sessions, solve cold start problem +- [ ] MCP/package installation from chat +- [ ] Browse MCP marketplace / skills repository from chat + +## Webhook Ingestion + +- [ ] Generic webhook endpoint for external events +- [ ] GitHub webhook handling +- [ ] CI/CD notification handling +- [ ] Webhook -> messages_in routing + +## System Actions + +- [ ] register_group from inside agent (stub exists) +- [ ] reset_session from inside agent (stub exists) + +## Integrations + +- [ ] Vercel CLI integration in setup process +- [ ] Skills for deploying and managing Vercel websites from chat +- [ ] Office 365 integration (create/edit documents with inline suggestions) + +## Memory + +- [ ] Shared memory with approval flow (write to global memory requires admin approval) + +## Migration + +- [ ] v1 -> v2 migration skill +- [ ] Database migration (v1 SQLite -> v2 central DB + session DBs) +- [ ] Channel credential preservation +- [ ] Custom skill/code porting + +## Testing + +- [x] DB layer tests (agent groups, messaging groups, sessions, pending questions) +- [x] Channel registry tests +- [x] Poll loop / formatter tests +- [x] Integration test (container agent-runner) +- [x] Host core tests +- [ ] End-to-end flow tests (message in -> agent -> message out -> delivery) +- [ ] Delivery polling tests +- [ ] Host sweep tests (stale detection, recurrence) +- [ ] Multi-channel integration tests + +## Rollout + +- [ ] Internal testing across all channels +- [ ] Migration skill built and tested +- [ ] PR factory migrated as validation +- [ ] Blog post / announcement +- [ ] Video demos of key flows +- [ ] Vercel coordination diff --git a/src/channels/gchat-v2.ts b/src/channels/gchat-v2.ts new file mode 100644 index 0000000..48376f2 --- /dev/null +++ b/src/channels/gchat-v2.ts @@ -0,0 +1,20 @@ +/** + * Google Chat channel adapter (v2) — uses Chat SDK bridge. + * Self-registers on import. + */ +import { createGoogleChatAdapter } from '@chat-adapter/gchat'; + +import { readEnvFile } from '../env.js'; +import { createChatSdkBridge } from './chat-sdk-bridge.js'; +import { registerChannelAdapter } from './channel-registry.js'; + +registerChannelAdapter('gchat', { + factory: () => { + const env = readEnvFile(['GCHAT_CREDENTIALS']); + if (!env.GCHAT_CREDENTIALS) return null; + const gchatAdapter = createGoogleChatAdapter({ + credentials: JSON.parse(env.GCHAT_CREDENTIALS), + }); + return createChatSdkBridge({ adapter: gchatAdapter, concurrency: 'concurrent' }); + }, +}); diff --git a/src/channels/github-v2.ts b/src/channels/github-v2.ts new file mode 100644 index 0000000..19b90d2 --- /dev/null +++ b/src/channels/github-v2.ts @@ -0,0 +1,22 @@ +/** + * GitHub channel adapter (v2) — uses Chat SDK bridge. + * PR comment threads as conversations. + * Self-registers on import. + */ +import { createGitHubAdapter } from '@chat-adapter/github'; + +import { readEnvFile } from '../env.js'; +import { createChatSdkBridge } from './chat-sdk-bridge.js'; +import { registerChannelAdapter } from './channel-registry.js'; + +registerChannelAdapter('github', { + factory: () => { + const env = readEnvFile(['GITHUB_TOKEN', 'GITHUB_WEBHOOK_SECRET']); + if (!env.GITHUB_TOKEN) return null; + const githubAdapter = createGitHubAdapter({ + token: env.GITHUB_TOKEN, + webhookSecret: env.GITHUB_WEBHOOK_SECRET, + }); + return createChatSdkBridge({ adapter: githubAdapter, concurrency: 'queue' }); + }, +}); diff --git a/src/channels/imessage-v2.ts b/src/channels/imessage-v2.ts new file mode 100644 index 0000000..a31a76d --- /dev/null +++ b/src/channels/imessage-v2.ts @@ -0,0 +1,25 @@ +/** + * iMessage channel adapter (v2) — uses Chat SDK bridge. + * Supports local mode (macOS Full Disk Access) and remote mode (Photon API). + * Self-registers on import. + */ +import { createiMessageAdapter } from 'chat-adapter-imessage'; + +import { readEnvFile } from '../env.js'; +import { createChatSdkBridge } from './chat-sdk-bridge.js'; +import { registerChannelAdapter } from './channel-registry.js'; + +registerChannelAdapter('imessage', { + factory: () => { + const env = readEnvFile(['IMESSAGE_ENABLED', 'IMESSAGE_LOCAL', 'IMESSAGE_SERVER_URL', 'IMESSAGE_API_KEY']); + const isLocal = env.IMESSAGE_LOCAL !== 'false'; + if (isLocal && !env.IMESSAGE_ENABLED) return null; + if (!isLocal && !env.IMESSAGE_SERVER_URL) return null; + const imessageAdapter = createiMessageAdapter({ + local: isLocal, + serverUrl: env.IMESSAGE_SERVER_URL, + apiKey: env.IMESSAGE_API_KEY, + }); + return createChatSdkBridge({ adapter: imessageAdapter, concurrency: 'concurrent' }); + }, +}); diff --git a/src/channels/index.ts b/src/channels/index.ts index 44f4f55..bad8090 100644 --- a/src/channels/index.ts +++ b/src/channels/index.ts @@ -1,12 +1,42 @@ // Channel self-registration barrel file. -// Each import triggers the channel module's registerChannel() call. +// Each import triggers the channel module's registerChannelAdapter() call. // discord - -// gmail +// import './discord-v2.js'; // slack +// import './slack-v2.js'; // telegram +// import './telegram-v2.js'; -// whatsapp +// github +// import './github-v2.js'; + +// linear +// import './linear-v2.js'; + +// google chat +// import './gchat-v2.js'; + +// microsoft teams +// import './teams-v2.js'; + +// whatsapp cloud api +// import './whatsapp-cloud-v2.js'; + +// resend (email) +// import './resend-v2.js'; + +// matrix +// import './matrix-v2.js'; + +// webex +// import './webex-v2.js'; + +// imessage +// import './imessage-v2.js'; + +// gmail (native, no Chat SDK) + +// whatsapp baileys (native, no Chat SDK) diff --git a/src/channels/linear-v2.ts b/src/channels/linear-v2.ts new file mode 100644 index 0000000..11014f8 --- /dev/null +++ b/src/channels/linear-v2.ts @@ -0,0 +1,22 @@ +/** + * Linear channel adapter (v2) — uses Chat SDK bridge. + * Issue comment threads as conversations. + * Self-registers on import. + */ +import { createLinearAdapter } from '@chat-adapter/linear'; + +import { readEnvFile } from '../env.js'; +import { createChatSdkBridge } from './chat-sdk-bridge.js'; +import { registerChannelAdapter } from './channel-registry.js'; + +registerChannelAdapter('linear', { + factory: () => { + const env = readEnvFile(['LINEAR_API_KEY', 'LINEAR_WEBHOOK_SECRET']); + if (!env.LINEAR_API_KEY) return null; + const linearAdapter = createLinearAdapter({ + apiKey: env.LINEAR_API_KEY, + webhookSecret: env.LINEAR_WEBHOOK_SECRET, + }); + return createChatSdkBridge({ adapter: linearAdapter, concurrency: 'queue' }); + }, +}); diff --git a/src/channels/matrix-v2.ts b/src/channels/matrix-v2.ts new file mode 100644 index 0000000..a286fda --- /dev/null +++ b/src/channels/matrix-v2.ts @@ -0,0 +1,23 @@ +/** + * Matrix channel adapter (v2) — uses Chat SDK bridge. + * Self-registers on import. + */ +import { createMatrixAdapter } from '@beeper/chat-adapter-matrix'; + +import { readEnvFile } from '../env.js'; +import { createChatSdkBridge } from './chat-sdk-bridge.js'; +import { registerChannelAdapter } from './channel-registry.js'; + +registerChannelAdapter('matrix', { + factory: () => { + const env = readEnvFile(['MATRIX_BASE_URL', 'MATRIX_ACCESS_TOKEN', 'MATRIX_USER_ID', 'MATRIX_BOT_USERNAME']); + if (!env.MATRIX_BASE_URL) return null; + // Matrix adapter reads from process.env directly + process.env.MATRIX_BASE_URL = env.MATRIX_BASE_URL; + if (env.MATRIX_ACCESS_TOKEN) process.env.MATRIX_ACCESS_TOKEN = env.MATRIX_ACCESS_TOKEN; + if (env.MATRIX_USER_ID) process.env.MATRIX_USER_ID = env.MATRIX_USER_ID; + if (env.MATRIX_BOT_USERNAME) process.env.MATRIX_BOT_USERNAME = env.MATRIX_BOT_USERNAME; + const matrixAdapter = createMatrixAdapter(); + return createChatSdkBridge({ adapter: matrixAdapter, concurrency: 'concurrent' }); + }, +}); diff --git a/src/channels/resend-v2.ts b/src/channels/resend-v2.ts new file mode 100644 index 0000000..5dfe5ab --- /dev/null +++ b/src/channels/resend-v2.ts @@ -0,0 +1,23 @@ +/** + * Resend (email) channel adapter (v2) — uses Chat SDK bridge. + * Self-registers on import. + */ +import { createResendAdapter } from '@resend/chat-sdk-adapter'; + +import { readEnvFile } from '../env.js'; +import { createChatSdkBridge } from './chat-sdk-bridge.js'; +import { registerChannelAdapter } from './channel-registry.js'; + +registerChannelAdapter('resend', { + factory: () => { + const env = readEnvFile(['RESEND_API_KEY', 'RESEND_FROM_ADDRESS', 'RESEND_FROM_NAME', 'RESEND_WEBHOOK_SECRET']); + if (!env.RESEND_API_KEY) return null; + const resendAdapter = createResendAdapter({ + apiKey: env.RESEND_API_KEY, + fromAddress: env.RESEND_FROM_ADDRESS, + fromName: env.RESEND_FROM_NAME, + webhookSecret: env.RESEND_WEBHOOK_SECRET, + }); + return createChatSdkBridge({ adapter: resendAdapter, concurrency: 'queue' }); + }, +}); diff --git a/src/channels/slack-v2.ts b/src/channels/slack-v2.ts new file mode 100644 index 0000000..1413c05 --- /dev/null +++ b/src/channels/slack-v2.ts @@ -0,0 +1,21 @@ +/** + * Slack channel adapter (v2) — uses Chat SDK bridge. + * Self-registers on import. + */ +import { createSlackAdapter } from '@chat-adapter/slack'; + +import { readEnvFile } from '../env.js'; +import { createChatSdkBridge } from './chat-sdk-bridge.js'; +import { registerChannelAdapter } from './channel-registry.js'; + +registerChannelAdapter('slack', { + factory: () => { + const env = readEnvFile(['SLACK_BOT_TOKEN', 'SLACK_SIGNING_SECRET']); + if (!env.SLACK_BOT_TOKEN) return null; + const slackAdapter = createSlackAdapter({ + botToken: env.SLACK_BOT_TOKEN, + signingSecret: env.SLACK_SIGNING_SECRET, + }); + return createChatSdkBridge({ adapter: slackAdapter, concurrency: 'concurrent' }); + }, +}); diff --git a/src/channels/teams-v2.ts b/src/channels/teams-v2.ts new file mode 100644 index 0000000..591c5c7 --- /dev/null +++ b/src/channels/teams-v2.ts @@ -0,0 +1,21 @@ +/** + * Microsoft Teams channel adapter (v2) — uses Chat SDK bridge. + * Self-registers on import. + */ +import { createTeamsAdapter } from '@chat-adapter/teams'; + +import { readEnvFile } from '../env.js'; +import { createChatSdkBridge } from './chat-sdk-bridge.js'; +import { registerChannelAdapter } from './channel-registry.js'; + +registerChannelAdapter('teams', { + factory: () => { + const env = readEnvFile(['TEAMS_APP_ID', 'TEAMS_APP_PASSWORD']); + if (!env.TEAMS_APP_ID) return null; + const teamsAdapter = createTeamsAdapter({ + appId: env.TEAMS_APP_ID, + appPassword: env.TEAMS_APP_PASSWORD, + }); + return createChatSdkBridge({ adapter: teamsAdapter, concurrency: 'concurrent' }); + }, +}); diff --git a/src/channels/telegram-v2.ts b/src/channels/telegram-v2.ts new file mode 100644 index 0000000..c4ae5fe --- /dev/null +++ b/src/channels/telegram-v2.ts @@ -0,0 +1,21 @@ +/** + * Telegram channel adapter (v2) — uses Chat SDK bridge. + * Self-registers on import. + */ +import { createTelegramAdapter } from '@chat-adapter/telegram'; + +import { readEnvFile } from '../env.js'; +import { createChatSdkBridge } from './chat-sdk-bridge.js'; +import { registerChannelAdapter } from './channel-registry.js'; + +registerChannelAdapter('telegram', { + factory: () => { + const env = readEnvFile(['TELEGRAM_BOT_TOKEN']); + if (!env.TELEGRAM_BOT_TOKEN) return null; + const telegramAdapter = createTelegramAdapter({ + botToken: env.TELEGRAM_BOT_TOKEN, + mode: 'polling', + }); + return createChatSdkBridge({ adapter: telegramAdapter, concurrency: 'concurrent' }); + }, +}); diff --git a/src/channels/webex-v2.ts b/src/channels/webex-v2.ts new file mode 100644 index 0000000..63f1870 --- /dev/null +++ b/src/channels/webex-v2.ts @@ -0,0 +1,21 @@ +/** + * Webex channel adapter (v2) — uses Chat SDK bridge. + * Self-registers on import. + */ +import { createWebexAdapter } from '@bitbasti/chat-adapter-webex'; + +import { readEnvFile } from '../env.js'; +import { createChatSdkBridge } from './chat-sdk-bridge.js'; +import { registerChannelAdapter } from './channel-registry.js'; + +registerChannelAdapter('webex', { + factory: () => { + const env = readEnvFile(['WEBEX_BOT_TOKEN', 'WEBEX_WEBHOOK_SECRET']); + if (!env.WEBEX_BOT_TOKEN) return null; + const webexAdapter = createWebexAdapter({ + botToken: env.WEBEX_BOT_TOKEN, + webhookSecret: env.WEBEX_WEBHOOK_SECRET, + }); + return createChatSdkBridge({ adapter: webexAdapter, concurrency: 'concurrent' }); + }, +}); diff --git a/src/channels/whatsapp-cloud-v2.ts b/src/channels/whatsapp-cloud-v2.ts new file mode 100644 index 0000000..74b8160 --- /dev/null +++ b/src/channels/whatsapp-cloud-v2.ts @@ -0,0 +1,24 @@ +/** + * WhatsApp Cloud API channel adapter (v2) — uses Chat SDK bridge. + * Uses the official Meta WhatsApp Business Cloud API (not Baileys). + * Self-registers on import. + */ +import { createWhatsAppAdapter } from '@chat-adapter/whatsapp'; + +import { readEnvFile } from '../env.js'; +import { createChatSdkBridge } from './chat-sdk-bridge.js'; +import { registerChannelAdapter } from './channel-registry.js'; + +registerChannelAdapter('whatsapp-cloud', { + factory: () => { + const env = readEnvFile(['WHATSAPP_ACCESS_TOKEN', 'WHATSAPP_PHONE_NUMBER_ID', 'WHATSAPP_APP_SECRET', 'WHATSAPP_VERIFY_TOKEN']); + if (!env.WHATSAPP_ACCESS_TOKEN) return null; + const whatsappAdapter = createWhatsAppAdapter({ + accessToken: env.WHATSAPP_ACCESS_TOKEN, + phoneNumberId: env.WHATSAPP_PHONE_NUMBER_ID, + appSecret: env.WHATSAPP_APP_SECRET, + verifyToken: env.WHATSAPP_VERIFY_TOKEN, + }); + return createChatSdkBridge({ adapter: whatsappAdapter, concurrency: 'concurrent' }); + }, +});