Separate WhatsApp auth from daemon into standalone script
- Add src/auth.ts for interactive QR code authentication - Add `npm run auth` script - Update setup skill for current Node.js architecture - Daemon (src/index.ts) now only uses stored credentials Auth is run during setup; daemon assumes credentials exist and shows macOS notification if re-auth is needed. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,182 +5,187 @@ description: Run initial NanoClaw setup. Use when user wants to install dependen
|
|||||||
|
|
||||||
# NanoClaw Setup
|
# NanoClaw Setup
|
||||||
|
|
||||||
**IMPORTANT**: Run all commands automatically. Only pause for user action when physical interaction is required (scanning QR codes). Give clear instructions for exactly what the user needs to do.
|
Run all commands automatically. Only pause when user action is required (scanning QR codes).
|
||||||
|
|
||||||
## 1. Check Prerequisites
|
## 1. Install Dependencies
|
||||||
|
|
||||||
Run these checks. Install any that are missing:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 --version # Need 3.10+
|
npm install
|
||||||
node --version # Need 18+
|
|
||||||
uv --version
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If missing, install automatically:
|
## 2. WhatsApp Authentication
|
||||||
- **uv**: `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
|
||||||
- **node**: `brew install node`
|
|
||||||
- **python**: `brew install python@3.10`
|
|
||||||
|
|
||||||
## 2. Install Dependencies
|
|
||||||
|
|
||||||
Run all of these automatically:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Python dependencies
|
|
||||||
uv venv && source .venv/bin/activate && uv pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# WhatsApp bridge dependencies
|
|
||||||
cd bridge && npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create logs directory
|
|
||||||
mkdir -p logs
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. WhatsApp Authentication
|
|
||||||
|
|
||||||
**USER ACTION REQUIRED**
|
**USER ACTION REQUIRED**
|
||||||
|
|
||||||
Run the bridge in background and monitor for connection:
|
Run the authentication script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd bridge && node bridge.js > /tmp/bridge_output.log 2>&1 &
|
npm run auth
|
||||||
BRIDGE_PID=$!
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Tell the user:
|
Tell the user:
|
||||||
> A QR code will appear below. On your phone:
|
> A QR code will appear. On your phone:
|
||||||
> 1. Open WhatsApp
|
> 1. Open WhatsApp
|
||||||
> 2. Tap **Settings → Linked Devices → Link a Device**
|
> 2. Tap **Settings → Linked Devices → Link a Device**
|
||||||
> 3. Scan the QR code
|
> 3. Scan the QR code
|
||||||
|
|
||||||
Then poll for either QR code or successful connection (check every 2 seconds for up to 3 minutes):
|
Wait for the script to output "Successfully authenticated" then continue.
|
||||||
|
|
||||||
|
If it says "Already authenticated", skip to the next step.
|
||||||
|
|
||||||
|
## 3. Register Main Channel
|
||||||
|
|
||||||
|
Ask the user:
|
||||||
|
> Do you want to use your **personal chat** (message yourself) or a **WhatsApp group** as your main control channel?
|
||||||
|
|
||||||
|
For personal chat:
|
||||||
|
> Send any message to yourself in WhatsApp (the "Message Yourself" chat). Tell me when done.
|
||||||
|
|
||||||
|
For group:
|
||||||
|
> Send any message in the WhatsApp group you want to use as your main channel. Tell me when done.
|
||||||
|
|
||||||
|
After user confirms, start the app briefly to capture the message:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cat /tmp/bridge_output.log # Look for QR code or "Connected to WhatsApp!"
|
timeout 10 npm run dev || true
|
||||||
```
|
```
|
||||||
|
|
||||||
When you see "Connected to WhatsApp!" in the output, stop the bridge:
|
Then find the JID from the database:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kill $BRIDGE_PID
|
# For personal chat (ends with @s.whatsapp.net)
|
||||||
|
sqlite3 store/messages.db "SELECT DISTINCT chat_jid FROM messages WHERE chat_jid LIKE '%@s.whatsapp.net' ORDER BY timestamp DESC LIMIT 5"
|
||||||
|
|
||||||
|
# For group (ends with @g.us)
|
||||||
|
sqlite3 store/messages.db "SELECT DISTINCT chat_jid FROM messages WHERE chat_jid LIKE '%@g.us' ORDER BY timestamp DESC LIMIT 5"
|
||||||
```
|
```
|
||||||
|
|
||||||
Session persists until logged out from WhatsApp.
|
Get the assistant name from environment or default:
|
||||||
|
```bash
|
||||||
|
echo ${ASSISTANT_NAME:-Andy}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create/update `data/registered_groups.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"THE_JID_HERE": {
|
||||||
|
"name": "main",
|
||||||
|
"folder": "main",
|
||||||
|
"trigger": "@Andy",
|
||||||
|
"added_at": "2026-01-31T12:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensure the groups folder exists:
|
||||||
|
```bash
|
||||||
|
mkdir -p groups/main/logs
|
||||||
|
```
|
||||||
|
|
||||||
## 4. Gmail Authentication (Optional)
|
## 4. Gmail Authentication (Optional)
|
||||||
|
|
||||||
**Skip this step** unless user specifically needs Gmail integration. It requires Google Cloud Platform OAuth credentials setup.
|
Ask the user:
|
||||||
|
> Do you want to enable Gmail integration for reading/sending emails?
|
||||||
|
|
||||||
If needed, user must first:
|
If yes, they need Google Cloud Platform OAuth credentials first:
|
||||||
1. Create a GCP project
|
1. Create a GCP project at https://console.cloud.google.com
|
||||||
2. Enable Gmail API
|
2. Enable the Gmail API
|
||||||
3. Create OAuth 2.0 credentials
|
3. Create OAuth 2.0 credentials (Desktop app)
|
||||||
4. Download credentials to `~/.gmail-mcp/gcp-oauth.keys.json`
|
4. Download and save to `~/.gmail-mcp/gcp-oauth.keys.json`
|
||||||
|
|
||||||
Then run:
|
Then run:
|
||||||
```bash
|
```bash
|
||||||
npx -y @gongrzhe/server-gmail-autoauth-mcp
|
npx -y @gongrzhe/server-gmail-autoauth-mcp
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. Register Main Channel
|
This will open a browser for OAuth consent. After authorization, credentials are cached.
|
||||||
|
|
||||||
Ask the user:
|
## 5. Configure launchd Service
|
||||||
> Do you want to use a **personal chat** (message yourself) or a **WhatsApp group** as your main channel?
|
|
||||||
|
|
||||||
For personal chat:
|
Get the actual paths:
|
||||||
> Send a test message to yourself in WhatsApp. Tell me when done.
|
|
||||||
|
|
||||||
For group:
|
|
||||||
> Send a message in the WhatsApp group you want to use. Tell me when done.
|
|
||||||
|
|
||||||
After user confirms, find the JID:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# For personal chat
|
which node
|
||||||
sqlite3 bridge/store/messages.db "SELECT DISTINCT chat_jid FROM messages WHERE chat_jid NOT LIKE '%@g.us' ORDER BY rowid DESC LIMIT 5"
|
pwd
|
||||||
|
|
||||||
# For group
|
|
||||||
sqlite3 bridge/store/messages.db "SELECT DISTINCT chat_jid FROM messages WHERE chat_jid LIKE '%@g.us' ORDER BY rowid DESC LIMIT 5"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Read the assistant name from `src/config.py` (look for `ASSISTANT_NAME = "..."`).
|
Create the plist file at `~/Library/LaunchAgents/com.nanoclaw.plist`:
|
||||||
|
|
||||||
Then update `data/registered_groups.json`:
|
```xml
|
||||||
```json
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
{
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
"THE_JID_HERE": {
|
<plist version="1.0">
|
||||||
"name": "main",
|
<dict>
|
||||||
"folder": "main",
|
<key>Label</key>
|
||||||
"trigger": "@AssistantName",
|
<string>com.nanoclaw</string>
|
||||||
"added_at": "CURRENT_TIMESTAMP_ISO"
|
<key>ProgramArguments</key>
|
||||||
}
|
<array>
|
||||||
}
|
<string>NODE_PATH_HERE</string>
|
||||||
|
<string>PROJECT_PATH_HERE/dist/index.js</string>
|
||||||
|
</array>
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string>PROJECT_PATH_HERE</string>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<true/>
|
||||||
|
<key>EnvironmentVariables</key>
|
||||||
|
<dict>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>/usr/local/bin:/usr/bin:/bin:HOME_PATH_HERE/.local/bin</string>
|
||||||
|
<key>HOME</key>
|
||||||
|
<string>HOME_PATH_HERE</string>
|
||||||
|
</dict>
|
||||||
|
<key>StandardOutPath</key>
|
||||||
|
<string>PROJECT_PATH_HERE/logs/nanoclaw.log</string>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>PROJECT_PATH_HERE/logs/nanoclaw.error.log</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
```
|
```
|
||||||
|
|
||||||
## 6. Configure launchd
|
Replace the placeholders with actual paths from the commands above.
|
||||||
|
|
||||||
First, detect the actual paths:
|
Build and start the service:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
which node # Get actual node path (may be nvm, homebrew, etc.)
|
npm run build
|
||||||
|
mkdir -p logs
|
||||||
|
launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist
|
||||||
```
|
```
|
||||||
|
|
||||||
Create plist files directly in `~/Library/LaunchAgents/` with:
|
Verify it's running:
|
||||||
|
|
||||||
**com.nanoclaw.bridge.plist:**
|
|
||||||
- ProgramArguments: `[actual_node_path, /Users/.../nanoclaw/bridge/bridge.js]`
|
|
||||||
- WorkingDirectory: `/Users/.../nanoclaw/bridge`
|
|
||||||
- StandardOutPath/StandardErrorPath: `/Users/.../nanoclaw/logs/bridge.log` and `bridge.error.log`
|
|
||||||
|
|
||||||
**com.nanoclaw.router.plist:**
|
|
||||||
- ProgramArguments: `[/Users/.../nanoclaw/.venv/bin/python, -u, /Users/.../nanoclaw/src/router.py]`
|
|
||||||
- The `-u` flag is required for unbuffered output (so logs appear immediately)
|
|
||||||
- WorkingDirectory: `/Users/.../nanoclaw`
|
|
||||||
- EnvironmentVariables:
|
|
||||||
- `PATH`: `/Users/USERNAME/.local/bin:/usr/local/bin:/usr/bin:/bin` (must include path to `claude` CLI)
|
|
||||||
- `HOME`: `/Users/USERNAME` (required for Claude CLI to find its config)
|
|
||||||
- StandardOutPath/StandardErrorPath: `/Users/.../nanoclaw/logs/router.log` and `router.error.log`
|
|
||||||
|
|
||||||
**NOTE**: Do NOT set ANTHROPIC_API_KEY - the Claude CLI handles its own authentication.
|
|
||||||
|
|
||||||
Then load the services:
|
|
||||||
```bash
|
|
||||||
launchctl load ~/Library/LaunchAgents/com.nanoclaw.bridge.plist
|
|
||||||
launchctl load ~/Library/LaunchAgents/com.nanoclaw.router.plist
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify they're running:
|
|
||||||
```bash
|
```bash
|
||||||
launchctl list | grep nanoclaw
|
launchctl list | grep nanoclaw
|
||||||
```
|
```
|
||||||
|
|
||||||
## 7. Test
|
## 6. Test
|
||||||
|
|
||||||
Wait a few seconds for services to start, then tell the user:
|
Tell the user:
|
||||||
> Send `@AssistantName hello` in your registered chat/group.
|
> Send `@Andy hello` in your registered chat.
|
||||||
|
|
||||||
Check `logs/router.log` for activity:
|
Check the logs:
|
||||||
```bash
|
```bash
|
||||||
tail -f logs/router.log
|
tail -f logs/nanoclaw.log
|
||||||
```
|
```
|
||||||
|
|
||||||
If there are issues, also check:
|
The user should receive a response in WhatsApp.
|
||||||
- `logs/router.error.log`
|
|
||||||
- `logs/bridge.log`
|
|
||||||
- `logs/bridge.error.log`
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
**"Command failed with exit code 1"** - Usually means the Claude CLI isn't in PATH. Verify PATH in the router plist includes the directory containing `claude` (typically `~/.local/bin`).
|
**Service not starting**: Check `logs/nanoclaw.error.log`
|
||||||
|
|
||||||
**Messages received but no WhatsApp response** - Check that the bridge HTTP server is running:
|
**No response to messages**:
|
||||||
|
- Verify the trigger pattern matches (`@Andy` at start of message)
|
||||||
|
- Check that the chat JID is in `data/registered_groups.json`
|
||||||
|
- Check `logs/nanoclaw.log` for errors
|
||||||
|
|
||||||
|
**WhatsApp disconnected**:
|
||||||
|
- The service will show a macOS notification
|
||||||
|
- Run `npm run auth` to re-authenticate
|
||||||
|
- Restart the service: `launchctl kickstart -k gui/$(id -u)/com.nanoclaw`
|
||||||
|
|
||||||
|
**Unload service**:
|
||||||
```bash
|
```bash
|
||||||
curl -s http://127.0.0.1:3141/send -X POST -H "Content-Type: application/json" -d '{"jid":"test","message":"test"}'
|
launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist
|
||||||
```
|
```
|
||||||
Should return an error about invalid JID (not connection refused).
|
|
||||||
|
|
||||||
**Router not processing messages** - Check the trigger pattern matches. Messages must start with the trigger (e.g., `@Andy hello`).
|
|
||||||
|
|||||||
19
package-lock.json
generated
19
package-lock.json
generated
@@ -12,11 +12,13 @@
|
|||||||
"@whiskeysockets/baileys": "^7.0.0-rc.9",
|
"@whiskeysockets/baileys": "^7.0.0-rc.9",
|
||||||
"better-sqlite3": "^11.8.1",
|
"better-sqlite3": "^11.8.1",
|
||||||
"pino": "^9.6.0",
|
"pino": "^9.6.0",
|
||||||
"pino-pretty": "^13.0.0"
|
"pino-pretty": "^13.0.0",
|
||||||
|
"qrcode-terminal": "^0.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^7.6.12",
|
"@types/better-sqlite3": "^7.6.12",
|
||||||
"@types/node": "^22.10.0",
|
"@types/node": "^22.10.0",
|
||||||
|
"@types/qrcode-terminal": "^0.12.2",
|
||||||
"tsx": "^4.19.0",
|
"tsx": "^4.19.0",
|
||||||
"typescript": "^5.7.0"
|
"typescript": "^5.7.0"
|
||||||
},
|
},
|
||||||
@@ -1175,6 +1177,13 @@
|
|||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/qrcode-terminal": {
|
||||||
|
"version": "0.12.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/qrcode-terminal/-/qrcode-terminal-0.12.2.tgz",
|
||||||
|
"integrity": "sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@whiskeysockets/baileys": {
|
"node_modules/@whiskeysockets/baileys": {
|
||||||
"version": "7.0.0-rc.9",
|
"version": "7.0.0-rc.9",
|
||||||
"resolved": "https://registry.npmjs.org/@whiskeysockets/baileys/-/baileys-7.0.0-rc.9.tgz",
|
"resolved": "https://registry.npmjs.org/@whiskeysockets/baileys/-/baileys-7.0.0-rc.9.tgz",
|
||||||
@@ -1980,6 +1989,14 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/qrcode-terminal": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz",
|
||||||
|
"integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==",
|
||||||
|
"bin": {
|
||||||
|
"qrcode-terminal": "bin/qrcode-terminal.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/quick-format-unescaped": {
|
"node_modules/quick-format-unescaped": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
"dev": "tsx src/index.ts",
|
"dev": "tsx src/index.ts",
|
||||||
|
"auth": "tsx src/auth.ts",
|
||||||
"lint": "eslint src/",
|
"lint": "eslint src/",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
@@ -16,11 +17,13 @@
|
|||||||
"@whiskeysockets/baileys": "^7.0.0-rc.9",
|
"@whiskeysockets/baileys": "^7.0.0-rc.9",
|
||||||
"better-sqlite3": "^11.8.1",
|
"better-sqlite3": "^11.8.1",
|
||||||
"pino": "^9.6.0",
|
"pino": "^9.6.0",
|
||||||
"pino-pretty": "^13.0.0"
|
"pino-pretty": "^13.0.0",
|
||||||
|
"qrcode-terminal": "^0.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^7.6.12",
|
"@types/better-sqlite3": "^7.6.12",
|
||||||
"@types/node": "^22.10.0",
|
"@types/node": "^22.10.0",
|
||||||
|
"@types/qrcode-terminal": "^0.12.2",
|
||||||
"tsx": "^4.19.0",
|
"tsx": "^4.19.0",
|
||||||
"typescript": "^5.7.0"
|
"typescript": "^5.7.0"
|
||||||
},
|
},
|
||||||
|
|||||||
89
src/auth.ts
Normal file
89
src/auth.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* WhatsApp Authentication Script
|
||||||
|
*
|
||||||
|
* Run this during setup to authenticate with WhatsApp.
|
||||||
|
* Displays QR code, waits for scan, saves credentials, then exits.
|
||||||
|
*
|
||||||
|
* Usage: npx tsx src/auth.ts
|
||||||
|
*/
|
||||||
|
|
||||||
|
import makeWASocket, {
|
||||||
|
useMultiFileAuthState,
|
||||||
|
DisconnectReason,
|
||||||
|
makeCacheableSignalKeyStore,
|
||||||
|
} from '@whiskeysockets/baileys';
|
||||||
|
import pino from 'pino';
|
||||||
|
import qrcode from 'qrcode-terminal';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const AUTH_DIR = './store/auth';
|
||||||
|
|
||||||
|
const logger = pino({
|
||||||
|
level: 'warn', // Quiet logging - only show errors
|
||||||
|
});
|
||||||
|
|
||||||
|
async function authenticate(): Promise<void> {
|
||||||
|
fs.mkdirSync(AUTH_DIR, { recursive: true });
|
||||||
|
|
||||||
|
const { state, saveCreds } = await useMultiFileAuthState(AUTH_DIR);
|
||||||
|
|
||||||
|
// Check if already authenticated
|
||||||
|
if (state.creds.registered) {
|
||||||
|
console.log('✓ Already authenticated with WhatsApp');
|
||||||
|
console.log(' To re-authenticate, delete the store/auth folder and run again.');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Starting WhatsApp authentication...\n');
|
||||||
|
|
||||||
|
const sock = makeWASocket({
|
||||||
|
auth: {
|
||||||
|
creds: state.creds,
|
||||||
|
keys: makeCacheableSignalKeyStore(state.keys, logger),
|
||||||
|
},
|
||||||
|
printQRInTerminal: false,
|
||||||
|
logger,
|
||||||
|
browser: ['NanoClaw', 'Chrome', '1.0.0'],
|
||||||
|
});
|
||||||
|
|
||||||
|
sock.ev.on('connection.update', (update) => {
|
||||||
|
const { connection, lastDisconnect, qr } = update;
|
||||||
|
|
||||||
|
if (qr) {
|
||||||
|
console.log('Scan this QR code with WhatsApp:\n');
|
||||||
|
console.log(' 1. Open WhatsApp on your phone');
|
||||||
|
console.log(' 2. Tap Settings → Linked Devices → Link a Device');
|
||||||
|
console.log(' 3. Point your camera at the QR code below\n');
|
||||||
|
qrcode.generate(qr, { small: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection === 'close') {
|
||||||
|
const reason = (lastDisconnect?.error as any)?.output?.statusCode;
|
||||||
|
|
||||||
|
if (reason === DisconnectReason.loggedOut) {
|
||||||
|
console.log('\n✗ Logged out. Delete store/auth and try again.');
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
console.log('\n✗ Connection failed. Please try again.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection === 'open') {
|
||||||
|
console.log('\n✓ Successfully authenticated with WhatsApp!');
|
||||||
|
console.log(' Credentials saved to store/auth/');
|
||||||
|
console.log(' You can now start the NanoClaw service.\n');
|
||||||
|
|
||||||
|
// Give it a moment to save credentials, then exit
|
||||||
|
setTimeout(() => process.exit(0), 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sock.ev.on('creds.update', saveCreds);
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate().catch((err) => {
|
||||||
|
console.error('Authentication failed:', err.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user