fix: WhatsApp auth improvements and LID translation for DMs
- Add pairing code auth with 515 reconnect handling (Baileys stream
error after pairing is now retried instead of failing)
- Use Browsers.macOS('Chrome') identifier for WhatsApp compatibility
- Fix LID-to-phone translation for DMs using signalRepository.getPNForLID
- Strip device suffix (:0) from resolved phone JIDs
- Update setup skill with three auth options (browser QR, pairing code,
terminal QR), DM channel type, and LID troubleshooting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ description: Run initial NanoClaw setup. Use when user wants to install dependen
|
||||
|
||||
# NanoClaw Setup
|
||||
|
||||
Run all commands automatically. Only pause when user action is required (scanning QR codes).
|
||||
Run all commands automatically. Only pause when user action is required (WhatsApp authentication, configuration choices).
|
||||
|
||||
**UX Note:** When asking the user questions, prefer using the `AskUserQuestion` tool instead of just outputting text. This integrates with Claude's built-in question/answer system for a better experience.
|
||||
|
||||
@@ -89,7 +89,7 @@ Tell the user:
|
||||
> 1. Paste it here and I'll add it to `.env` for you, or
|
||||
> 2. Add it to `.env` yourself as `CLAUDE_CODE_OAUTH_TOKEN=<your-token>`
|
||||
|
||||
If they give you the token, add it to `.env`:
|
||||
If they give you the token, add it to `.env`. **Never echo the full token in commands or output** — use the Write tool to write the `.env` file directly, or tell the user to add it themselves:
|
||||
|
||||
```bash
|
||||
echo "CLAUDE_CODE_OAUTH_TOKEN=<token>" > .env
|
||||
@@ -114,7 +114,7 @@ Tell the user to add their key from https://console.anthropic.com/
|
||||
**Verify:**
|
||||
```bash
|
||||
KEY=$(grep "^ANTHROPIC_API_KEY=" .env | cut -d= -f2)
|
||||
[ -n "$KEY" ] && echo "API key configured: ${KEY:0:10}...${KEY: -4}" || echo "Missing"
|
||||
[ -n "$KEY" ] && echo "API key configured: ${KEY:0:7}..." || echo "Missing"
|
||||
```
|
||||
|
||||
## 4. Build Container Image
|
||||
@@ -141,23 +141,131 @@ fi
|
||||
|
||||
**USER ACTION REQUIRED**
|
||||
|
||||
**IMPORTANT:** Run this command in the **foreground**. The QR code is multi-line ASCII art that must be displayed in full. Do NOT run in background or truncate the output.
|
||||
The auth script supports two methods: QR code scanning and pairing code (phone number). Ask the user which they prefer.
|
||||
|
||||
Tell the user:
|
||||
> A QR code will appear below. On your phone:
|
||||
> 1. Open WhatsApp
|
||||
> 2. Tap **Settings → Linked Devices → Link a Device**
|
||||
> 3. Scan the QR code
|
||||
The auth script writes status to `store/auth-status.txt`:
|
||||
- `already_authenticated` — credentials already exist
|
||||
- `pairing_code:<CODE>` — pairing code generated, waiting for user to enter it
|
||||
- `authenticated` — successfully authenticated
|
||||
- `failed:<reason>` — authentication failed
|
||||
|
||||
Run with a long Bash tool timeout (120000ms) so the user has time to scan. Do NOT use the `timeout` shell command (it's not available on macOS).
|
||||
The script automatically handles error 515 (stream error after pairing) by reconnecting — this is normal and expected during pairing code auth.
|
||||
|
||||
### Ask the user which method to use
|
||||
|
||||
> How would you like to authenticate WhatsApp?
|
||||
>
|
||||
> 1. **QR code in browser** (Recommended) — Opens a page with the QR code to scan
|
||||
> 2. **Pairing code** — Enter a numeric code on your phone, no camera needed
|
||||
> 3. **QR code in terminal** — Run the auth command yourself in another terminal
|
||||
|
||||
### Option A: QR Code in Browser (Recommended)
|
||||
|
||||
Clean any stale auth state and start auth in background:
|
||||
|
||||
```bash
|
||||
rm -rf store/auth store/qr-data.txt store/auth-status.txt
|
||||
npm run auth
|
||||
```
|
||||
|
||||
Wait for the script to output "Successfully authenticated" then continue.
|
||||
Run this with `run_in_background: true`.
|
||||
|
||||
If it says "Already authenticated", skip to the next step.
|
||||
Poll for QR data (up to 15 seconds):
|
||||
|
||||
```bash
|
||||
for i in $(seq 1 15); do if [ -f store/qr-data.txt ]; then echo "qr_ready"; exit 0; fi; STATUS=$(cat store/auth-status.txt 2>/dev/null || echo "waiting"); if [ "$STATUS" = "already_authenticated" ]; then echo "$STATUS"; exit 0; fi; sleep 1; done; echo "timeout"
|
||||
```
|
||||
|
||||
If `already_authenticated`, skip to the next step.
|
||||
|
||||
If QR data is ready, generate the QR as SVG and inject it into the HTML template:
|
||||
|
||||
```bash
|
||||
node -e "
|
||||
const QR = require('qrcode');
|
||||
const fs = require('fs');
|
||||
const qrData = fs.readFileSync('store/qr-data.txt', 'utf8');
|
||||
QR.toString(qrData, { type: 'svg' }, (err, svg) => {
|
||||
if (err) process.exit(1);
|
||||
const template = fs.readFileSync('.claude/skills/setup/qr-auth.html', 'utf8');
|
||||
fs.writeFileSync('store/qr-auth.html', template.replace('{{QR_SVG}}', svg));
|
||||
console.log('done');
|
||||
});
|
||||
"
|
||||
```
|
||||
|
||||
Then open it:
|
||||
|
||||
```bash
|
||||
open store/qr-auth.html
|
||||
```
|
||||
|
||||
Tell the user:
|
||||
> A browser window should have opened with the QR code. It expires in about 60 seconds.
|
||||
>
|
||||
> Scan it with WhatsApp: **Settings → Linked Devices → Link a Device**
|
||||
|
||||
Then poll for completion (up to 120 seconds):
|
||||
|
||||
```bash
|
||||
for i in $(seq 1 60); do STATUS=$(cat store/auth-status.txt 2>/dev/null || echo "waiting"); if [ "$STATUS" = "authenticated" ] || [ "$STATUS" = "already_authenticated" ]; then echo "$STATUS"; exit 0; elif echo "$STATUS" | grep -q "^failed:"; then echo "$STATUS"; exit 0; fi; sleep 2; done; echo "timeout"
|
||||
```
|
||||
|
||||
- If `authenticated`, success — clean up with `rm -f store/qr-auth.html` and continue.
|
||||
- If `failed:qr_timeout`, offer to retry (re-run the auth and regenerate the HTML page).
|
||||
- If `failed:logged_out`, delete `store/auth/` and retry.
|
||||
|
||||
### Option B: Pairing Code
|
||||
|
||||
Ask the user for their phone number (with country code, no + or spaces, e.g. `14155551234`).
|
||||
|
||||
Clean any stale auth state and start:
|
||||
|
||||
```bash
|
||||
rm -rf store/auth store/qr-data.txt store/auth-status.txt
|
||||
npx tsx src/whatsapp-auth.ts --pairing-code --phone PHONE_NUMBER
|
||||
```
|
||||
|
||||
Run this with `run_in_background: true`.
|
||||
|
||||
Poll for the pairing code (up to 15 seconds):
|
||||
|
||||
```bash
|
||||
for i in $(seq 1 15); do STATUS=$(cat store/auth-status.txt 2>/dev/null || echo "waiting"); if echo "$STATUS" | grep -q "^pairing_code:"; then echo "$STATUS"; exit 0; elif [ "$STATUS" = "authenticated" ] || [ "$STATUS" = "already_authenticated" ]; then echo "$STATUS"; exit 0; elif echo "$STATUS" | grep -q "^failed:"; then echo "$STATUS"; exit 0; fi; sleep 1; done; echo "timeout"
|
||||
```
|
||||
|
||||
Extract the code from the status (e.g. `pairing_code:ABC12DEF` → `ABC12DEF`) and tell the user:
|
||||
|
||||
> Your pairing code: **CODE_HERE**
|
||||
>
|
||||
> 1. Open WhatsApp on your phone
|
||||
> 2. Tap **Settings → Linked Devices → Link a Device**
|
||||
> 3. Tap **"Link with phone number instead"**
|
||||
> 4. Enter the code: **CODE_HERE**
|
||||
|
||||
Then poll for completion (up to 120 seconds):
|
||||
|
||||
```bash
|
||||
for i in $(seq 1 60); do STATUS=$(cat store/auth-status.txt 2>/dev/null || echo "waiting"); if [ "$STATUS" = "authenticated" ] || [ "$STATUS" = "already_authenticated" ]; then echo "$STATUS"; exit 0; elif echo "$STATUS" | grep -q "^failed:"; then echo "$STATUS"; exit 0; fi; sleep 2; done; echo "timeout"
|
||||
```
|
||||
|
||||
- If `authenticated` or `already_authenticated`, success — continue to next step.
|
||||
- If `failed:logged_out`, delete `store/auth/` and retry.
|
||||
- If `failed:515` or timeout, the 515 reconnect should handle this automatically. If it persists, the user may need to temporarily stop other WhatsApp-connected apps on the same device.
|
||||
|
||||
### Option C: QR Code in Terminal
|
||||
|
||||
Tell the user to run the auth command in another terminal window:
|
||||
|
||||
> Open another terminal and run:
|
||||
> ```
|
||||
> cd PROJECT_PATH && npm run auth
|
||||
> ```
|
||||
> Scan the QR code that appears, then let me know when it says "Successfully authenticated".
|
||||
|
||||
Replace `PROJECT_PATH` with the actual project path (use `pwd`).
|
||||
|
||||
Wait for the user to confirm authentication succeeded, then continue to the next step.
|
||||
|
||||
## 6. Configure Assistant Name and Main Channel
|
||||
|
||||
@@ -191,10 +299,11 @@ Store their choice for use in the steps below.
|
||||
>
|
||||
> Options:
|
||||
> 1. Personal chat (Message Yourself) - Recommended
|
||||
> 2. Solo WhatsApp group (just me)
|
||||
> 3. Group with other people (I understand the security implications)
|
||||
> 2. DM with a specific phone number (e.g. your other phone)
|
||||
> 3. Solo WhatsApp group (just me)
|
||||
> 4. Group with other people (I understand the security implications)
|
||||
|
||||
If they choose option 3, ask a follow-up:
|
||||
If they choose option 4, ask a follow-up:
|
||||
|
||||
> You've chosen a group with other people. This means everyone in that group will have admin privileges over NanoClaw.
|
||||
>
|
||||
@@ -222,9 +331,13 @@ npm run dev
|
||||
|
||||
**For personal chat** (they chose option 1):
|
||||
|
||||
Personal chats are NOT synced to the database on startup — only groups are. Instead, ask the user for their phone number (with country code, no + or spaces, e.g. `14155551234`), then construct the JID as `{number}@s.whatsapp.net`.
|
||||
Personal chats are NOT synced to the database on startup — only groups are. The JID for "Message Yourself" is the bot's own number. Use the number from the WhatsApp auth step and construct the JID as `{number}@s.whatsapp.net`.
|
||||
|
||||
**For group** (they chose option 2 or 3):
|
||||
**For DM with a specific number** (they chose option 2):
|
||||
|
||||
Ask the user for the phone number (with country code, no + or spaces, e.g. `14155551234`), then construct the JID as `{number}@s.whatsapp.net`.
|
||||
|
||||
**For group** (they chose option 3 or 4):
|
||||
|
||||
Groups are synced on startup via `groupFetchAllParticipating`. Query the database for recent groups:
|
||||
```bash
|
||||
@@ -472,6 +585,12 @@ The user should receive a response in WhatsApp.
|
||||
- Check that the chat JID is in the database: `sqlite3 store/messages.db "SELECT * FROM registered_groups"`
|
||||
- Check `logs/nanoclaw.log` for errors
|
||||
|
||||
**Messages sent but not received by NanoClaw (DMs)**:
|
||||
- WhatsApp may use LID (Linked Identity) JIDs for DMs instead of phone numbers
|
||||
- Check logs for `Translated LID to phone JID` — if missing, the LID isn't being resolved
|
||||
- The `translateJid` method in `src/channels/whatsapp.ts` uses `sock.signalRepository.lidMapping.getPNForLID()` to resolve LIDs
|
||||
- Verify the registered JID doesn't have a device suffix (should be `number@s.whatsapp.net`, not `number:0@s.whatsapp.net`)
|
||||
|
||||
**WhatsApp disconnected**:
|
||||
- The service will show a macOS notification
|
||||
- Run `npm run auth` to re-authenticate
|
||||
|
||||
32
.claude/skills/setup/qr-auth.html
Normal file
32
.claude/skills/setup/qr-auth.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html><head><title>NanoClaw - WhatsApp Auth</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
|
||||
.card { background: white; border-radius: 16px; padding: 40px; box-shadow: 0 4px 24px rgba(0,0,0,0.1); text-align: center; max-width: 400px; }
|
||||
h2 { margin: 0 0 8px; }
|
||||
.timer { font-size: 18px; color: #666; margin: 12px 0; }
|
||||
.timer.urgent { color: #e74c3c; font-weight: bold; }
|
||||
.instructions { color: #666; font-size: 14px; margin-top: 16px; }
|
||||
svg { width: 280px; height: 280px; }
|
||||
</style></head><body>
|
||||
<div class="card">
|
||||
<h2>Scan with WhatsApp</h2>
|
||||
<div class="timer" id="timer">Expires in <span id="countdown">60</span>s</div>
|
||||
<div id="qr">{{QR_SVG}}</div>
|
||||
<div class="instructions">Settings → Linked Devices → Link a Device</div>
|
||||
</div>
|
||||
<script>
|
||||
let seconds = 60;
|
||||
const countdown = document.getElementById('countdown');
|
||||
const timer = document.getElementById('timer');
|
||||
const interval = setInterval(() => {
|
||||
seconds--;
|
||||
countdown.textContent = seconds;
|
||||
if (seconds <= 10) timer.classList.add('urgent');
|
||||
if (seconds <= 0) {
|
||||
clearInterval(interval);
|
||||
timer.textContent = 'QR code expired — re-run auth to get a new one';
|
||||
timer.classList.add('urgent');
|
||||
}
|
||||
}, 1000);
|
||||
</script></body></html>
|
||||
Reference in New Issue
Block a user