* refactor: implement channel architecture and dynamic setup - Introduced ChannelRegistry for dynamic channel loading - Decoupled WhatsApp from core index.ts and config.ts - Updated setup wizard to support ENABLED_CHANNELS selection - Refactored IPC and group registration to be channel-aware - Verified with 359 passing tests and clean typecheck * style: fix formatting in config.ts to pass CI * refactor(setup): full platform-agnostic transformation - Harmonized all instructional text and help prompts - Implemented conditional guards for WhatsApp-specific steps - Normalized CLI terminology across all 4 initial channels - Unified troubleshooting and verification logic - Verified 369 tests pass with clean typecheck * feat(skills): transform WhatsApp into a pluggable skill - Created .claude/skills/add-whatsapp with full 5-phase interactive setup - Fixed TS7006 'implicit any' error in IpcDeps - Added auto-creation of STORE_DIR to prevent crashes on fresh installs - Verified with 369 passing tests and clean typecheck * refactor(skills): move WhatsApp from core to pluggable skill - Move src/channels/whatsapp.ts to add-whatsapp skill add/ folder - Move src/channels/whatsapp.test.ts to skill add/ folder - Move src/whatsapp-auth.ts to skill add/ folder - Create modify/ for barrel file (src/channels/index.ts) - Create tests/ with skill package validation test - Update manifest with adds/modifies lists - Remove WhatsApp deps from core package.json (now skill-managed) - Remove WhatsApp-specific ghost language from types.ts - Update SKILL.md to reflect skill-apply workflow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(skills): move setup/whatsapp-auth.ts into WhatsApp skill The WhatsApp auth setup step is channel-specific — move it from core to the add-whatsapp skill so core stays minimal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(skills): convert Telegram skill to pluggable channel pattern Replace the old direct-integration approach (modifying src/index.ts, src/config.ts, src/routing.test.ts) with self-registration via the channel registry, matching the WhatsApp skill pattern. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(skills): fix add-whatsapp build failure and improve auth flow - Add missing @types/qrcode-terminal to manifest npm_dependencies (build failed after skill apply without it) - Make QR-browser the recommended auth method (terminal QR too small, pairing codes expire too fast) - Remove "replace vs alongside" question — channels are additive - Add pairing code retry guidance and QR-browser fallback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove hardcoded WhatsApp default and stale Baileys comment - ENABLED_CHANNELS now defaults to empty (fresh installs must configure channels explicitly via /setup; existing installs already have .env) - Remove Baileys-specific comment from storeMessageDirect() in db.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(skills): convert Discord, Slack, Gmail skills to pluggable channel pattern All channel skills now use the same self-registration pattern: - registerChannel() factory at module load time - Barrel file append (src/channels/index.ts) instead of orchestrator modifications - No more *_ONLY flags (DISCORD_ONLY, SLACK_ONLY) — use ENABLED_CHANNELS instead - Removed ~2500 lines of old modify/ files (src/index.ts, src/config.ts, src/routing.test.ts) Gmail retains its container-runner.ts and agent-runner modifications (MCP mount + server config) since those are independent of channel wiring. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: use getRegisteredChannels instead of ENABLED_CHANNELS Remove the ENABLED_CHANNELS env var entirely. The orchestrator now iterates getRegisteredChannelNames() from the channel registry — channels self-register via barrel imports and their factories return null when credentials are missing, so unconfigured channels are skipped automatically. Deleted setup/channels.ts (and its tests) since its sole purpose was writing ENABLED_CHANNELS to .env. Refactored verify, groups, and environment setup steps to detect channels by credential presence instead of reading ENABLED_CHANNELS. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add breaking change notice and whatsapp migration instructions CHANGELOG.md documents the pluggable channel architecture shift and provides migration steps for existing WhatsApp users. CLAUDE.md updated: Quick Context reflects multi-channel architecture, Key Files lists registry.ts instead of whatsapp.ts, and a new Troubleshooting section directs users to /add-whatsapp if WhatsApp stops connecting after upgrade. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: rewrite READMEs for pluggable multi-channel architecture Reflects the architectural shift from a hardcoded WhatsApp bot to a pluggable channel platform. Adds upgrading notice, Mermaid architecture diagram, CI/License/TypeScript/PRs badges, and clarifies that slash commands run inside the Claude Code CLI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: move pluggable channel architecture details to SPEC.md Revert READMEs to original tone with only two targeted changes: - Add upgrading notice for WhatsApp breaking change - Mention pluggable channels in "What It Supports" Move Mermaid diagram, channel registry internals, factory pattern explanation, and self-registration walkthrough into docs/SPEC.md. Update stale WhatsApp-specific references in SPEC.md to be channel-agnostic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: move upgrading notice to CHANGELOG, add changelog link Remove the "Upgrading from Pre-Pluggable Versions" section from README.md — breaking change details belong in the CHANGELOG. Add a Changelog section linking to CHANGELOG.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: expand CHANGELOG with full PR #500 changes Cover all changes: channel registry, WhatsApp moved to skill, removed core dependencies, all 5 skills simplified, orchestrator refactored, setup decoupled. Use Claude Code CLI instructions for migration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: bump version to 1.2.0 for pluggable channel architecture Minor version bump — new functionality (pluggable channels) with a managed migration path for existing WhatsApp users. Update version references in CHANGELOG and update skill. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix skill application * fix: use slotted barrel file to prevent channel merge conflicts Pre-allocate a named comment slot for each channel in src/channels/index.ts, separated by blank lines. Each skill's modify file only touches its own slot, so three-way merges never conflict when applying multiple channels. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve real chat ID during setup for token-based channels Instead of registering with `pending@telegram` (which never matches incoming messages), the setup skill now runs an inline bot that waits for the user to send /chatid, capturing the real chat ID before registration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: setup delegates to channel skills, fix group sync and Discord metadata - Restructure setup SKILL.md to delegate channel setup to individual channel skills (/add-whatsapp, /add-telegram, etc.) instead of reimplementing auth/registration inline with broken placeholder JIDs - Move channel selection to step 5 where it's immediately acted on - Fix setup/groups.ts: write sync script to temp file instead of passing via node -e which broke on shell escaping of newlines - Fix Discord onChatMetadata missing channel and isGroup parameters - Add .tmp-* to .gitignore for temp sync script cleanup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: align add-whatsapp skill with main setup patterns Add headless detection for auth method selection, structured inline error handling, dedicated number DM flow, and reorder questions to match main's trigger-first flow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add missing auth script to package.json The add-whatsapp skill adds src/whatsapp-auth.ts but doesn't add the corresponding npm script. Setup and SKILL.md reference `npm run auth` for WhatsApp QR terminal authentication. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: update Discord skill tests to match onChatMetadata signature The onChatMetadata callback now takes 5 arguments (jid, timestamp, name, channel, isGroup) but the Discord skill tests only expected 3. This caused skill application to roll back on test failure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: replace 'pluggable' jargon with clearer language User-facing text now says "multi-channel" or describes what it does. Developer-facing text uses "self-registering" or "channel registry". Also removes extra badge row from README. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: align Chinese README with English version Remove extra badges, replace pluggable jargon, remove upgrade section (now in CHANGELOG), add missing intro line and changelog section, fix setup FAQ answer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: warn on installed-but-unconfigured channels instead of silent skip Channels with missing credentials now emit WARN logs naming the exact missing variable, so misconfigurations surface instead of being hidden. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: simplify changelog to one-liner with compare link Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add isMain flag and channel-prefixed group folders Replace MAIN_GROUP_FOLDER constant with explicit isMain boolean on RegisteredGroup. Group folders now use channel prefix convention (e.g., whatsapp_main, telegram_family-chat) to prevent cross-channel collisions. - Add isMain to RegisteredGroup type and SQLite schema (with migration) - Replace all folder-based main group checks with group.isMain - Add --is-main flag to setup/register.ts - Strip isMain from IPC payload (defense in depth) - Update MCP tool description for channel-prefixed naming - Update all channel SKILL.md files and documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: gavrielc <gabicohen22@yahoo.com> Co-authored-by: Koshkoshinski <daniel.milliner@gmail.com>
346 lines
9.4 KiB
Markdown
346 lines
9.4 KiB
Markdown
---
|
|
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.
|
|
---
|
|
|
|
# Add WhatsApp Channel
|
|
|
|
This skill adds WhatsApp support to NanoClaw. It installs the WhatsApp channel code, dependencies, and guides through authentication, registration, and configuration.
|
|
|
|
## Phase 1: Pre-flight
|
|
|
|
### 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).
|
|
|
|
```bash
|
|
ls store/auth/creds.json 2>/dev/null && 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? (Include country code without +, e.g., 1234567890)
|
|
|
|
## Phase 2: Verify Code
|
|
|
|
Apply the skill to install the WhatsApp channel code and dependencies:
|
|
|
|
```bash
|
|
npx tsx scripts/apply-skill.ts .claude/skills/add-whatsapp
|
|
```
|
|
|
|
Verify the code was placed correctly:
|
|
|
|
```bash
|
|
test -f src/channels/whatsapp.ts && echo "WhatsApp channel code present" || echo "ERROR: WhatsApp channel code missing — re-run skill apply"
|
|
```
|
|
|
|
### Verify dependencies
|
|
|
|
```bash
|
|
node -e "require('@whiskeysockets/baileys')" 2>/dev/null && echo "Baileys installed" || echo "Installing Baileys..."
|
|
```
|
|
|
|
If not installed:
|
|
|
|
```bash
|
|
npm install @whiskeysockets/baileys qrcode qrcode-terminal
|
|
```
|
|
|
|
### Validate build
|
|
|
|
```bash
|
|
npm run build
|
|
```
|
|
|
|
Build must be clean before proceeding.
|
|
|
|
## Phase 3: Authentication
|
|
|
|
### Clean previous auth state (if re-authenticating)
|
|
|
|
```bash
|
|
rm -rf store/auth/
|
|
```
|
|
|
|
### Run WhatsApp authentication
|
|
|
|
For QR code in browser (recommended):
|
|
|
|
```bash
|
|
npx 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
|
|
npx tsx setup/index.ts --step whatsapp-auth -- --method qr-terminal
|
|
```
|
|
|
|
Tell the user to run `npm run auth` in another terminal, then:
|
|
|
|
> 1. Open WhatsApp > **Settings** > **Linked Devices** > **Link a Device**
|
|
> 2. Scan the QR code displayed in the terminal
|
|
|
|
For pairing code:
|
|
|
|
```bash
|
|
npx tsx setup/index.ts --step whatsapp-auth -- --method pairing-code --phone <their-phone-number>
|
|
```
|
|
|
|
(Bash timeout: 150000ms). Display PAIRING_CODE from output.
|
|
|
|
Tell the user:
|
|
|
|
> A pairing code will appear. **Enter it within 60 seconds** — codes expire quickly.
|
|
>
|
|
> 1. Open WhatsApp > **Settings** > **Linked Devices** > **Link a Device**
|
|
> 2. Tap **Link with phone number instead**
|
|
> 3. Enter the code immediately
|
|
>
|
|
> If the code expires, re-run the command — a new code will be generated.
|
|
|
|
**If failed:** qr_timeout → re-run. logged_out → delete `store/auth/` and re-run. 515 → re-run. timeout → ask user, offer retry.
|
|
|
|
### Verify authentication succeeded
|
|
|
|
```bash
|
|
test -f store/auth/creds.json && echo "Authentication successful" || echo "Authentication failed"
|
|
```
|
|
|
|
### Configure environment
|
|
|
|
Channels auto-enable when their credentials are present — WhatsApp activates when `store/auth/creds.json` exists.
|
|
|
|
Sync to container environment:
|
|
|
|
```bash
|
|
mkdir -p data/env && cp .env data/env/env
|
|
```
|
|
|
|
## Phase 4: Registration
|
|
|
|
### Configure trigger and channel type
|
|
|
|
Get the bot's WhatsApp number: `node -e "const c=require('./store/auth/creds.json');console.log(c.me.id.split(':')[0].split('@')[0])"`
|
|
|
|
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
|
|
|
|
AskUserQuestion: What trigger word should activate the assistant?
|
|
- **@Andy** - Default trigger
|
|
- **@Claw** - Short and easy
|
|
- **@Claude** - Match the AI name
|
|
|
|
AskUserQuestion: What should the assistant call itself?
|
|
- **Andy** - Default name
|
|
- **Claw** - Short and easy
|
|
- **Claude** - Match the AI name
|
|
|
|
AskUserQuestion: Where do you want to chat with the assistant?
|
|
|
|
**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
|
|
npx tsx setup/index.ts --step groups
|
|
npx 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
|
|
npx tsx setup/index.ts --step register \
|
|
--jid "<jid>" \
|
|
--name "<chat-name>" \
|
|
--trigger "@<trigger>" \
|
|
--folder "whatsapp_main" \
|
|
--channel whatsapp \
|
|
--assistant-name "<name>" \
|
|
--is-main \
|
|
--no-trigger-required # Only for main/self-chat
|
|
```
|
|
|
|
For additional groups (trigger-required):
|
|
|
|
```bash
|
|
npx tsx setup/index.ts --step register \
|
|
--jid "<group-jid>" \
|
|
--name "<group-name>" \
|
|
--trigger "@<trigger>" \
|
|
--folder "whatsapp_<group-name>" \
|
|
--channel whatsapp
|
|
```
|
|
|
|
## Phase 5: Verify
|
|
|
|
### Build and restart
|
|
|
|
```bash
|
|
npm 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
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### QR code expired
|
|
|
|
QR codes expire after ~60 seconds. Re-run the auth command:
|
|
|
|
```bash
|
|
rm -rf store/auth/ && npx tsx src/whatsapp-auth.ts
|
|
```
|
|
|
|
### Pairing code not working
|
|
|
|
Codes expire in ~60 seconds. To retry:
|
|
|
|
```bash
|
|
rm -rf store/auth/ && npx tsx src/whatsapp-auth.ts --pairing-code --phone <phone>
|
|
```
|
|
|
|
Enter the code **immediately** when it appears. Also ensure:
|
|
1. Phone number includes country code without `+` (e.g., `1234567890`)
|
|
2. Phone has internet access
|
|
3. WhatsApp is updated to the latest version
|
|
|
|
If pairing code keeps failing, switch to QR-browser auth instead:
|
|
|
|
```bash
|
|
rm -rf store/auth/ && npx tsx setup/index.ts --step whatsapp-auth -- --method qr-browser
|
|
```
|
|
|
|
### "conflict" disconnection
|
|
|
|
This happens when two instances connect with the same credentials. Ensure only one NanoClaw process is running:
|
|
|
|
```bash
|
|
pkill -f "node dist/index.js"
|
|
# Then restart
|
|
```
|
|
|
|
### 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`
|
|
|
|
### Group names not showing
|
|
|
|
Run group metadata sync:
|
|
|
|
```bash
|
|
npx tsx setup/index.ts --step groups
|
|
```
|
|
|
|
This fetches all group names from WhatsApp. Runs automatically every 24 hours.
|
|
|
|
## After Setup
|
|
|
|
If running `npm run dev` while the service is active:
|
|
|
|
```bash
|
|
# macOS:
|
|
launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist
|
|
npm run dev
|
|
# When done testing:
|
|
launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist
|
|
|
|
# Linux:
|
|
# systemctl --user stop nanoclaw
|
|
# npm 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: `npm run build && launchctl kickstart -k gui/$(id -u)/com.nanoclaw` (macOS) or `npm run build && systemctl --user restart nanoclaw` (Linux)
|