bloby-bot 0.23.7 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -221,6 +221,13 @@ export class ChannelManager {
|
|
|
221
221
|
this.statusListeners.push(listener);
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
/** Request a pairing code for phone-number-based linking (mobile alternative to QR) */
|
|
225
|
+
async requestWhatsAppPairingCode(phoneNumber: string): Promise<string> {
|
|
226
|
+
const provider = this.providers.get('whatsapp') as WhatsAppChannel | undefined;
|
|
227
|
+
if (!provider) throw new Error('WhatsApp not initialized — call connect first');
|
|
228
|
+
return provider.requestPairingCode(phoneNumber);
|
|
229
|
+
}
|
|
230
|
+
|
|
224
231
|
/** Delete WhatsApp credentials and disconnect */
|
|
225
232
|
async logoutWhatsApp(): Promise<void> {
|
|
226
233
|
const provider = this.providers.get('whatsapp') as WhatsAppChannel | undefined;
|
|
@@ -181,6 +181,23 @@ export class WhatsAppChannel implements ChannelProvider {
|
|
|
181
181
|
return this.qrSvg;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
/** Request a pairing code for phone-number-based linking (alternative to QR scan) */
|
|
185
|
+
async requestPairingCode(phoneNumber: string): Promise<string> {
|
|
186
|
+
if (!this.sock) throw new Error('WhatsApp socket not initialized — call connect() first');
|
|
187
|
+
if (this.connected) throw new Error('Already connected — no pairing needed');
|
|
188
|
+
|
|
189
|
+
// Digits only, no + or dashes
|
|
190
|
+
const cleaned = phoneNumber.replace(/[^0-9]/g, '');
|
|
191
|
+
if (cleaned.length < 8 || cleaned.length > 15) {
|
|
192
|
+
throw new Error('Invalid phone number — use digits with country code (e.g. 5511999998888)');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
log.info(`[whatsapp] Requesting pairing code for ${cleaned}...`);
|
|
196
|
+
const code = await this.sock.requestPairingCode(cleaned);
|
|
197
|
+
log.info(`[whatsapp] Pairing code generated: ${code}`);
|
|
198
|
+
return code;
|
|
199
|
+
}
|
|
200
|
+
|
|
184
201
|
hasCredentials(): boolean {
|
|
185
202
|
return fs.existsSync(path.join(AUTH_DIR, 'creds.json'));
|
|
186
203
|
}
|
package/supervisor/index.ts
CHANGED
|
@@ -355,6 +355,7 @@ export async function startSupervisor() {
|
|
|
355
355
|
'POST /api/channels/whatsapp/disconnect',
|
|
356
356
|
'POST /api/channels/whatsapp/logout',
|
|
357
357
|
'POST /api/channels/whatsapp/configure',
|
|
358
|
+
'POST /api/channels/whatsapp/pairing-code',
|
|
358
359
|
'POST /api/channels/send',
|
|
359
360
|
];
|
|
360
361
|
|
|
@@ -463,8 +464,8 @@ export async function startSupervisor() {
|
|
|
463
464
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Space+Grotesk:wght@600;700&display=swap" rel="stylesheet">
|
|
464
465
|
<style>
|
|
465
466
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
466
|
-
body{background:#212121;color:#f5f5f5;font-family:'Inter',system-ui,-apple-system,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100dvh;margin:0;overflow:hidden}
|
|
467
|
-
.container{display:flex;flex-direction:column;align-items:center;max-width:360px;width:100%;padding:
|
|
467
|
+
body{background:#212121;color:#f5f5f5;font-family:'Inter',system-ui,-apple-system,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100dvh;margin:0;overflow-x:hidden}
|
|
468
|
+
.container{display:flex;flex-direction:column;align-items:center;max-width:360px;width:100%;padding:20px}
|
|
468
469
|
|
|
469
470
|
.qr-card{background:#2a2a2a;border:1px solid rgba(255,255,255,0.08);border-radius:20px;padding:28px;width:100%;box-shadow:0 0 0 1px rgba(175,39,227,0.1),0 0 20px -5px rgba(175,39,227,0.15);animation:fade-up .5s ease-out both}
|
|
470
471
|
.qr-inner{background:#fff;border-radius:12px;padding:16px}
|
|
@@ -472,6 +473,32 @@ export async function startSupervisor() {
|
|
|
472
473
|
|
|
473
474
|
.scan-hint{margin-top:20px;font-size:13px;color:#666;text-align:center;animation:fade-up .5s ease-out .2s both}
|
|
474
475
|
|
|
476
|
+
.divider{display:flex;align-items:center;gap:12px;width:100%;margin:24px 0;animation:fade-up .5s ease-out .3s both}
|
|
477
|
+
.divider-line{flex:1;height:1px;background:rgba(255,255,255,0.08)}
|
|
478
|
+
.divider-text{font-size:12px;color:#555;text-transform:uppercase;letter-spacing:0.5px}
|
|
479
|
+
|
|
480
|
+
.phone-section{width:100%;animation:fade-up .5s ease-out .4s both}
|
|
481
|
+
.phone-toggle{background:none;border:none;color:#888;font-size:13px;cursor:pointer;font-family:inherit;padding:4px 0;transition:color .2s;width:100%;text-align:center}
|
|
482
|
+
.phone-toggle:hover{color:#AF27E3}
|
|
483
|
+
|
|
484
|
+
.phone-form{display:none;width:100%;margin-top:16px;animation:fade-up .3s ease-out both}
|
|
485
|
+
.phone-form.visible{display:flex;flex-direction:column;align-items:center;gap:12px}
|
|
486
|
+
.phone-input-wrap{display:flex;gap:8px;width:100%}
|
|
487
|
+
.phone-input{flex:1;background:#2a2a2a;border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:12px 14px;color:#f5f5f5;font-size:15px;font-family:inherit;outline:none;transition:border-color .2s}
|
|
488
|
+
.phone-input:focus{border-color:#AF27E3}
|
|
489
|
+
.phone-input::placeholder{color:#555}
|
|
490
|
+
.phone-btn{background:linear-gradient(135deg,#AF27E3,#FB4072);border:none;border-radius:10px;padding:12px 20px;color:#fff;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;white-space:nowrap;transition:opacity .2s}
|
|
491
|
+
.phone-btn:hover{opacity:.9}
|
|
492
|
+
.phone-btn:disabled{opacity:.5;cursor:not-allowed}
|
|
493
|
+
.phone-hint{font-size:12px;color:#555;text-align:center}
|
|
494
|
+
|
|
495
|
+
.code-display{display:none;width:100%;margin-top:16px;text-align:center;animation:fade-up .3s ease-out both}
|
|
496
|
+
.code-display.visible{display:block}
|
|
497
|
+
.code-value{font-family:'Space Grotesk',monospace;font-size:32px;font-weight:700;letter-spacing:6px;background:linear-gradient(135deg,#04D1FE,#AF27E3);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin:12px 0}
|
|
498
|
+
.code-steps{font-size:12px;color:#666;line-height:1.8;text-align:left;margin-top:12px;padding:0 8px}
|
|
499
|
+
.code-steps li{margin-bottom:2px}
|
|
500
|
+
.code-error{color:#FB4072;font-size:13px;margin-top:8px}
|
|
501
|
+
|
|
475
502
|
.confetti-wrap{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;overflow:hidden}
|
|
476
503
|
.confetti-dot{position:absolute;width:8px;height:8px;border-radius:50%;top:-10px;animation:confetti-fall 2s ease-out forwards}
|
|
477
504
|
@keyframes confetti-fall{0%{opacity:1;transform:translateY(0) translateX(0) rotate(0) scale(1)}100%{opacity:0;transform:translateY(100vh) translateX(var(--drift)) rotate(360deg) scale(.5)}}
|
|
@@ -497,14 +524,104 @@ ${connected
|
|
|
497
524
|
</div>
|
|
498
525
|
<script>async function relink(){await fetch('/api/channels/whatsapp/logout',{method:'POST'});await fetch('/api/channels/whatsapp/connect',{method:'POST'});setTimeout(()=>location.reload(),2000)}</script>`
|
|
499
526
|
: qr
|
|
500
|
-
? `<div class="qr-card"><div class="qr-inner">${qr}</div></div
|
|
527
|
+
? `<div class="qr-card"><div class="qr-inner">${qr}</div></div>
|
|
528
|
+
<p class="scan-hint">Scan with WhatsApp to link</p>
|
|
529
|
+
|
|
530
|
+
<div class="divider"><span class="divider-line"></span><span class="divider-text">or</span><span class="divider-line"></span></div>
|
|
531
|
+
|
|
532
|
+
<div class="phone-section">
|
|
533
|
+
<button class="phone-toggle" onclick="togglePhoneForm()">Link with phone number instead</button>
|
|
534
|
+
<div id="phoneForm" class="phone-form">
|
|
535
|
+
<div class="phone-input-wrap">
|
|
536
|
+
<input id="phoneInput" class="phone-input" type="tel" placeholder="5511999998888" inputmode="numeric" pattern="[0-9]*">
|
|
537
|
+
<button id="phoneBtn" class="phone-btn" onclick="requestCode()">Get code</button>
|
|
538
|
+
</div>
|
|
539
|
+
<p class="phone-hint">Digits only, with country code. No + or dashes.</p>
|
|
540
|
+
</div>
|
|
541
|
+
<div id="codeDisplay" class="code-display">
|
|
542
|
+
<p style="font-size:13px;color:#999">Enter this code in WhatsApp:</p>
|
|
543
|
+
<div id="codeValue" class="code-value"></div>
|
|
544
|
+
<ol class="code-steps">
|
|
545
|
+
<li>Open <b>WhatsApp</b> on your phone</li>
|
|
546
|
+
<li>Go to <b>Settings > Linked Devices</b></li>
|
|
547
|
+
<li>Tap <b>Link a Device</b></li>
|
|
548
|
+
<li>Tap <b>Link with phone number instead</b></li>
|
|
549
|
+
<li>Enter the code above</li>
|
|
550
|
+
</ol>
|
|
551
|
+
<div id="codeError" class="code-error"></div>
|
|
552
|
+
<button class="phone-toggle" style="margin-top:12px" onclick="resetPhoneForm()">Try again</button>
|
|
553
|
+
</div>
|
|
554
|
+
</div>`
|
|
501
555
|
: '<p class="loading">Starting WhatsApp...</p>'}
|
|
502
556
|
</div>
|
|
503
|
-
${!connected ?
|
|
557
|
+
${!connected ? `<script>
|
|
558
|
+
setTimeout(()=>location.reload(),4000);
|
|
559
|
+
|
|
560
|
+
function togglePhoneForm(){
|
|
561
|
+
document.getElementById('phoneForm').classList.toggle('visible');
|
|
562
|
+
}
|
|
563
|
+
function resetPhoneForm(){
|
|
564
|
+
document.getElementById('codeDisplay').classList.remove('visible');
|
|
565
|
+
document.getElementById('phoneForm').classList.add('visible');
|
|
566
|
+
document.getElementById('phoneInput').value='';
|
|
567
|
+
document.getElementById('codeError').textContent='';
|
|
568
|
+
}
|
|
569
|
+
async function requestCode(){
|
|
570
|
+
const input=document.getElementById('phoneInput');
|
|
571
|
+
const btn=document.getElementById('phoneBtn');
|
|
572
|
+
const phone=input.value.replace(/[^0-9]/g,'');
|
|
573
|
+
if(phone.length<8){input.style.borderColor='#FB4072';return}
|
|
574
|
+
btn.disabled=true;btn.textContent='Requesting...';
|
|
575
|
+
try{
|
|
576
|
+
const res=await fetch('/api/channels/whatsapp/pairing-code',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({phoneNumber:phone})});
|
|
577
|
+
const data=await res.json();
|
|
578
|
+
if(!res.ok) throw new Error(data.error||'Request failed');
|
|
579
|
+
document.getElementById('phoneForm').classList.remove('visible');
|
|
580
|
+
const display=document.getElementById('codeDisplay');
|
|
581
|
+
display.classList.add('visible');
|
|
582
|
+
const formatted=data.code.match(/.{1,4}/g).join(' - ');
|
|
583
|
+
document.getElementById('codeValue').textContent=formatted;
|
|
584
|
+
}catch(err){
|
|
585
|
+
const errEl=document.getElementById('codeError');
|
|
586
|
+
if(!document.getElementById('codeDisplay').classList.contains('visible')){
|
|
587
|
+
const hint=document.querySelector('.phone-hint');
|
|
588
|
+
if(hint) hint.textContent=err.message;
|
|
589
|
+
hint.style.color='#FB4072';
|
|
590
|
+
}else{
|
|
591
|
+
errEl.textContent=err.message;
|
|
592
|
+
}
|
|
593
|
+
}finally{btn.disabled=false;btn.textContent='Get code'}
|
|
594
|
+
}
|
|
595
|
+
document.getElementById('phoneInput')?.addEventListener('keydown',e=>{if(e.key==='Enter')requestCode()});
|
|
596
|
+
</script>` : ''}
|
|
504
597
|
</body></html>`);
|
|
505
598
|
return;
|
|
506
599
|
}
|
|
507
600
|
|
|
601
|
+
// POST /api/channels/whatsapp/pairing-code — request phone-number pairing code (mobile alternative to QR)
|
|
602
|
+
if (req.method === 'POST' && channelPath === '/api/channels/whatsapp/pairing-code') {
|
|
603
|
+
let body = '';
|
|
604
|
+
req.on('data', (chunk: Buffer) => { body += chunk.toString(); });
|
|
605
|
+
req.on('end', async () => {
|
|
606
|
+
try {
|
|
607
|
+
const { phoneNumber } = JSON.parse(body);
|
|
608
|
+
if (!phoneNumber) {
|
|
609
|
+
res.writeHead(400);
|
|
610
|
+
res.end(JSON.stringify({ error: 'Missing phoneNumber' }));
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const code = await channelManager.requestWhatsAppPairingCode(phoneNumber);
|
|
614
|
+
res.writeHead(200);
|
|
615
|
+
res.end(JSON.stringify({ ok: true, code }));
|
|
616
|
+
} catch (err: any) {
|
|
617
|
+
const status = err.message?.includes('rate') ? 429 : 500;
|
|
618
|
+
res.writeHead(status);
|
|
619
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
508
625
|
// POST /api/channels/whatsapp/connect — start WhatsApp connection (triggers QR)
|
|
509
626
|
if (req.method === 'POST' && channelPath === '/api/channels/whatsapp/connect') {
|
|
510
627
|
(async () => {
|
|
@@ -266,32 +266,17 @@ If something fails, own it: "Hmm, that didn't work. Let me try again."
|
|
|
266
266
|
|
|
267
267
|
## Skills
|
|
268
268
|
|
|
269
|
-
Skills live in `skills/` — each skill is a folder with instructions and resources
|
|
269
|
+
Skills live in `skills/` — each skill is a folder with instructions and resources. Each skill has a `SKILL.md` with instructions for you on how to use it.
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
skills/
|
|
273
|
-
whatsapp-clinic/
|
|
274
|
-
SKILL.md # Instructions for you (how to use this skill)
|
|
275
|
-
SCRIPT.md # Customer-facing prompt (loaded as system prompt in business mode)
|
|
276
|
-
files/ # RAG documents, FAQs, etc.
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
Only ONE skill can be active for customer-facing mode at a time. The active skill is set in the channel config (`channels.whatsapp.skill`). When your human asks to switch skills, update the config:
|
|
280
|
-
```bash
|
|
281
|
-
curl -s -X POST http://localhost:7400/api/channels/whatsapp/configure \
|
|
282
|
-
-H "Content-Type: application/json" -d '{"skill":"whatsapp-clinic"}'
|
|
283
|
-
```
|
|
271
|
+
Your installed skills and their SKILL.md contents are injected into your context automatically. If your human asks you to update a skill's behavior, edit the files INSIDE `skills/{skill-name}/`.
|
|
284
272
|
|
|
285
273
|
**IMPORTANT: When editing skill files, always use the full path inside the skill directory.**
|
|
286
|
-
- Correct: `skills/
|
|
274
|
+
- Correct: `skills/my-skill/SCRIPT.md`
|
|
287
275
|
- Wrong: `SCRIPT.md` (this writes to workspace root!)
|
|
288
276
|
|
|
289
|
-
|
|
277
|
+
## Channels (WhatsApp, Telegram, Discord, etc.)
|
|
290
278
|
|
|
291
|
-
**
|
|
292
|
-
- `MYSELF.md`, `MYHUMAN.md`, `MEMORY.md` — about YOU and your human. Always yours.
|
|
293
|
-
- `skills/{name}/SCRIPT.md` — business logic for customer interactions. Belongs to the skill.
|
|
294
|
-
- `whatsapp/{phone}.md` — customer conversation logs. Your memory of each customer.
|
|
279
|
+
You can communicate through messaging channels beyond the chat bubble. Channel support is provided by **skills** — if your human wants to use WhatsApp, Telegram, Discord, or any other channel, check the Bloby Marketplace for the corresponding skill. They install it, the skill teaches you everything you need to know about that channel.
|
|
295
280
|
|
|
296
281
|
## Marketplace — Getting New Skills
|
|
297
282
|
|
|
@@ -319,94 +304,6 @@ For a machine-readable catalog: `GET https://bloby.bot/api/marketplace/products`
|
|
|
319
304
|
|
|
320
305
|
---
|
|
321
306
|
|
|
322
|
-
## Channels (WhatsApp, Telegram, etc.)
|
|
323
|
-
|
|
324
|
-
You can communicate through messaging channels beyond the chat bubble. Currently supported: **WhatsApp**.
|
|
325
|
-
|
|
326
|
-
### CRITICAL: How WhatsApp Responses Work
|
|
327
|
-
|
|
328
|
-
**Your text response IS the WhatsApp reply.** When you receive a message tagged with `[WhatsApp | ...]`, the supervisor takes whatever you respond with and sends it directly to WhatsApp. You do NOT need to use curl or `/api/channels/send` to reply — just respond normally as if you're talking to the person.
|
|
329
|
-
|
|
330
|
-
**Do NOT use `/api/channels/send` to reply to incoming WhatsApp messages.** That endpoint is ONLY for proactive messages (during pulse, cron, or when you want to initiate a conversation). If you use it to reply, the person will get duplicate messages.
|
|
331
|
-
|
|
332
|
-
**Adjust your style for WhatsApp:** Keep messages shorter and more conversational than chat. No markdown headers, no code blocks unless asked. Think texting, not email.
|
|
333
|
-
|
|
334
|
-
### Channel Config
|
|
335
|
-
|
|
336
|
-
Your channel configuration is injected below (if any channels are configured). It comes from `~/.bloby/config.json` — a file OUTSIDE your workspace that the supervisor manages.
|
|
337
|
-
|
|
338
|
-
### How Channels Work
|
|
339
|
-
|
|
340
|
-
When a message arrives via WhatsApp, the supervisor wraps it with context:
|
|
341
|
-
```
|
|
342
|
-
[WhatsApp | 5511999888777 | customer | Alice]
|
|
343
|
-
Hi, I'd like to schedule an appointment.
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
The format is: `[Channel | phone | role | name (optional)]`
|
|
347
|
-
|
|
348
|
-
- **role=admin**: This is your human or an authorized admin. Use your normal personality, full capabilities, main system prompt.
|
|
349
|
-
- **role=customer**: This is someone else messaging. Follow the instructions from the active skill's SCRIPT.md (loaded as your system prompt).
|
|
350
|
-
|
|
351
|
-
### WhatsApp Modes
|
|
352
|
-
|
|
353
|
-
**Channel Mode** (default): Your human's own WhatsApp number. Only self-chat triggers you — messages from other people are completely ignored. This is "just talk to me" mode.
|
|
354
|
-
|
|
355
|
-
**Business Mode**: Bloby has its own dedicated number. Numbers in the `admins` array get admin access (main system prompt). Everyone else is a customer (support prompt).
|
|
356
|
-
|
|
357
|
-
### Setting Up WhatsApp
|
|
358
|
-
|
|
359
|
-
When your human asks to configure WhatsApp:
|
|
360
|
-
1. Start the connection: `curl -s -X POST http://localhost:7400/api/channels/whatsapp/connect`
|
|
361
|
-
2. Tell them to open the QR page: `http://localhost:7400/api/channels/whatsapp/qr-page` (Don't mention the URL until you are actually starting the connection)
|
|
362
|
-
3. They scan the QR with their WhatsApp app
|
|
363
|
-
4. The default mode is **channel** (self-chat only)
|
|
364
|
-
|
|
365
|
-
To switch to **business mode** with admin numbers:
|
|
366
|
-
```bash
|
|
367
|
-
curl -s -X POST http://localhost:7400/api/channels/whatsapp/configure \
|
|
368
|
-
-H "Content-Type: application/json" \
|
|
369
|
-
-d '{"mode":"business","admins":["+17865551234","+5511999887766"]}'
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
### Sending Proactive Messages
|
|
373
|
-
|
|
374
|
-
To INITIATE a WhatsApp message (during pulse, cron, or when you want to reach out first):
|
|
375
|
-
```bash
|
|
376
|
-
curl -s -X POST http://localhost:7400/api/channels/send \
|
|
377
|
-
-H "Content-Type: application/json" \
|
|
378
|
-
-d '{"channel":"whatsapp","to":"5511999888777","text":"Your appointment is confirmed for tomorrow at 2pm."}'
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
**Remember:** This is ONLY for starting new conversations or sending unprompted messages. When replying to an incoming message, just respond normally — the supervisor handles delivery.
|
|
382
|
-
|
|
383
|
-
### Customer Conversation Logs
|
|
384
|
-
|
|
385
|
-
When you finish a conversation with a **customer** via WhatsApp, save a summary to `whatsapp/{phone}.md`:
|
|
386
|
-
- Key details from the conversation
|
|
387
|
-
- Outcome (appointment scheduled, question answered, etc.)
|
|
388
|
-
- Any follow-ups needed
|
|
389
|
-
- Timestamp
|
|
390
|
-
|
|
391
|
-
This is your memory of that customer. Next time they message, read their file first.
|
|
392
|
-
|
|
393
|
-
### Channel API Reference
|
|
394
|
-
|
|
395
|
-
| Endpoint | Method | Purpose |
|
|
396
|
-
|----------|--------|---------|
|
|
397
|
-
| `/api/channels/status` | GET | List all channel statuses |
|
|
398
|
-
| `/api/channels/whatsapp/qr` | GET | Get current QR code SVG |
|
|
399
|
-
| `/api/channels/whatsapp/qr-page` | GET | Standalone QR scanning page |
|
|
400
|
-
| `/api/channels/whatsapp/connect` | POST | Start WhatsApp (triggers QR if needed) |
|
|
401
|
-
| `/api/channels/whatsapp/disconnect` | POST | Disconnect WhatsApp |
|
|
402
|
-
| `/api/channels/whatsapp/logout` | POST | Disconnect + delete credentials |
|
|
403
|
-
| `/api/channels/whatsapp/configure` | POST | Set mode + admins array |
|
|
404
|
-
| `/api/channels/send` | POST | Send proactive message via any channel |
|
|
405
|
-
|
|
406
|
-
All endpoints are on `http://localhost:7400`.
|
|
407
|
-
|
|
408
|
-
---
|
|
409
|
-
|
|
410
307
|
## Dashboard Linking
|
|
411
308
|
|
|
412
309
|
When your human gives you a claim code (format: XXXX-XXXX-XXXX-XXXX) to link you to their bloby.bot dashboard, read your relay token from `~/.bloby/config.json` (field: `relay.token`) and verify it: `curl -s -X POST https://api.bloby.bot/api/claim/verify -H "Content-Type: application/json" -H "Authorization: Bearer <relay_token>" -d '{"code":"<THE_CODE>"}'`. Tell your human whether it succeeded or failed.
|
|
@@ -8,40 +8,84 @@ Gives your agent a WhatsApp number. Connect via QR code, send and receive messag
|
|
|
8
8
|
|
|
9
9
|
None.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
---
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## How Responses Work
|
|
14
|
+
|
|
15
|
+
**Your text response IS the WhatsApp reply.** When you receive a message tagged with `[WhatsApp | ...]`, the supervisor takes whatever you respond with and sends it directly to WhatsApp. You do NOT need to use curl or `/api/channels/send` to reply — just respond normally.
|
|
16
|
+
|
|
17
|
+
**Do NOT use `/api/channels/send` to reply to incoming WhatsApp messages.** That endpoint is ONLY for proactive messages (during pulse, cron, or when you want to initiate a conversation). If you use it to reply, the person will get duplicate messages.
|
|
18
|
+
|
|
19
|
+
**Adjust your style for WhatsApp:** Keep messages shorter and more conversational than chat. No markdown headers, no code blocks unless asked. Think texting, not email.
|
|
20
|
+
|
|
21
|
+
---
|
|
14
22
|
|
|
15
|
-
|
|
23
|
+
## How Messages Arrive
|
|
24
|
+
|
|
25
|
+
When a message arrives via WhatsApp, the supervisor wraps it with context:
|
|
16
26
|
|
|
17
27
|
```
|
|
18
|
-
|
|
28
|
+
[WhatsApp | 5511999888777 | customer | Alice]
|
|
29
|
+
Hi, I'd like to schedule an appointment.
|
|
19
30
|
```
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
The format is: `[WhatsApp | phone | role | name (optional)]`
|
|
33
|
+
|
|
34
|
+
- **role=admin**: This is your human or an authorized admin. Use your normal personality, full capabilities, main system prompt.
|
|
35
|
+
- **role=customer**: This is someone else messaging. Follow the instructions from the active skill's SCRIPT.md (loaded as your system prompt for that conversation).
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Channel Config
|
|
40
|
+
|
|
41
|
+
Your channel configuration is injected into your context (if any channels are configured). It comes from `~/.bloby/config.json` — a file OUTSIDE your workspace that the supervisor manages.
|
|
22
42
|
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Modes
|
|
46
|
+
|
|
47
|
+
**Channel Mode** (default): Your human's own WhatsApp number. Only self-chat (messages your human sends to themselves) triggers you — messages from other people are completely ignored. This is "just talk to me" mode.
|
|
48
|
+
|
|
49
|
+
**Business Mode**: Bloby has its own dedicated WhatsApp number. Numbers in the `admins` array get admin access (main system prompt). Everyone else is a customer and gets the support prompt from the active skill's SCRIPT.md.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Setup
|
|
54
|
+
|
|
55
|
+
### 1. Connect WhatsApp
|
|
56
|
+
|
|
57
|
+
When your human asks to configure WhatsApp:
|
|
58
|
+
|
|
59
|
+
1. Start the connection:
|
|
23
60
|
```bash
|
|
24
|
-
curl -s -X POST http://localhost:
|
|
61
|
+
curl -s -X POST http://localhost:7400/api/channels/whatsapp/connect
|
|
25
62
|
```
|
|
26
63
|
|
|
27
|
-
|
|
64
|
+
2. Tell them to open the QR page: `http://localhost:7400/api/channels/whatsapp/qr-page`
|
|
65
|
+
(Don't mention the URL until you are actually starting the connection)
|
|
66
|
+
|
|
67
|
+
3. They scan the QR with their WhatsApp app
|
|
28
68
|
|
|
29
|
-
|
|
69
|
+
4. The default mode is **channel** (self-chat only)
|
|
30
70
|
|
|
31
|
-
|
|
71
|
+
If the QR page doesn't load, make sure you initiated the connection first (step 1).
|
|
32
72
|
|
|
33
|
-
**
|
|
73
|
+
**On mobile?** The QR page also offers a "Link with phone number instead" option. The user enters their phone number, gets an 8-character code, and types it into WhatsApp (Settings > Linked Devices > Link a Device > "Link with phone number instead"). No camera needed.
|
|
74
|
+
|
|
75
|
+
### 2. Choose a Mode
|
|
76
|
+
|
|
77
|
+
**Channel mode** (default) — personal assistant. Only self-chat triggers the agent:
|
|
34
78
|
|
|
35
79
|
```bash
|
|
36
|
-
curl -s -X POST http://localhost:
|
|
80
|
+
curl -s -X POST http://localhost:7400/api/channels/whatsapp/configure \
|
|
37
81
|
-H "Content-Type: application/json" \
|
|
38
82
|
-d '{"mode":"channel"}'
|
|
39
83
|
```
|
|
40
84
|
|
|
41
|
-
**Business mode** — customer-facing. The agent responds to incoming messages from customers using a skill's SCRIPT.md. Admin numbers get full agent access
|
|
85
|
+
**Business mode** — customer-facing. The agent responds to incoming messages from customers using a skill's SCRIPT.md. Admin numbers get full agent access:
|
|
42
86
|
|
|
43
87
|
```bash
|
|
44
|
-
curl -s -X POST http://localhost:
|
|
88
|
+
curl -s -X POST http://localhost:7400/api/channels/whatsapp/configure \
|
|
45
89
|
-H "Content-Type: application/json" \
|
|
46
90
|
-d '{"mode":"business","admins":["ADMIN_PHONE_1","ADMIN_PHONE_2"],"skill":"SKILL_FOLDER_NAME"}'
|
|
47
91
|
```
|
|
@@ -50,60 +94,91 @@ Replace `ADMIN_PHONE_1` with the human's phone number (digits only, with country
|
|
|
50
94
|
|
|
51
95
|
### 3. Verify
|
|
52
96
|
|
|
53
|
-
Check connection status:
|
|
54
|
-
|
|
55
97
|
```bash
|
|
56
|
-
curl -s http://localhost:
|
|
98
|
+
curl -s http://localhost:7400/api/channels/status
|
|
57
99
|
```
|
|
58
100
|
|
|
59
101
|
Expected: `"channel":"whatsapp","connected":true`
|
|
60
102
|
|
|
61
|
-
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Business Mode — Active Skill
|
|
106
|
+
|
|
107
|
+
Only ONE skill can be active for customer-facing mode at a time. The active skill is set in the channel config (`channels.whatsapp.skill`). When your human asks to switch skills:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
curl -s -X POST http://localhost:7400/api/channels/whatsapp/configure \
|
|
111
|
+
-H "Content-Type: application/json" -d '{"skill":"whatsapp-clinic"}'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The active skill should have:
|
|
115
|
+
- `SCRIPT.md` — the customer-facing system prompt (loaded automatically for customer conversations)
|
|
116
|
+
- Optionally a `customer_data/` directory — per-customer memory files (named by phone number, e.g. `5511999887766.md`)
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Sending Proactive Messages
|
|
62
121
|
|
|
63
|
-
|
|
122
|
+
To INITIATE a WhatsApp message (during pulse, cron, or when you want to reach out first):
|
|
64
123
|
|
|
65
124
|
```bash
|
|
66
|
-
curl -s -X POST http://localhost:
|
|
125
|
+
curl -s -X POST http://localhost:7400/api/channels/send \
|
|
67
126
|
-H "Content-Type: application/json" \
|
|
68
|
-
-d '{"channel":"whatsapp","to":"
|
|
127
|
+
-d '{"channel":"whatsapp","to":"5511999888777","text":"Your appointment is confirmed for tomorrow at 2pm."}'
|
|
69
128
|
```
|
|
70
129
|
|
|
71
130
|
Phone number format: digits with country code (e.g. `5511999887766`). The system normalizes to WhatsApp JID format automatically.
|
|
72
131
|
|
|
73
|
-
|
|
132
|
+
**Remember:** This is ONLY for starting new conversations or sending unprompted messages. When replying to an incoming message, just respond normally — the supervisor handles delivery.
|
|
74
133
|
|
|
75
|
-
|
|
134
|
+
---
|
|
76
135
|
|
|
77
|
-
|
|
136
|
+
## Customer Conversation Logs
|
|
78
137
|
|
|
79
|
-
|
|
138
|
+
When you finish a conversation with a **customer** via WhatsApp, save a summary to `whatsapp/{phone}.md`:
|
|
139
|
+
- Key details from the conversation
|
|
140
|
+
- Outcome (appointment scheduled, question answered, etc.)
|
|
141
|
+
- Any follow-ups needed
|
|
142
|
+
- Timestamp
|
|
80
143
|
|
|
81
|
-
|
|
144
|
+
This is your memory of that customer. Next time they message, read their file first.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Voice Notes
|
|
149
|
+
|
|
150
|
+
Voice messages are automatically transcribed via Whisper and delivered as text. No extra setup needed if Whisper is configured on the supervisor.
|
|
151
|
+
|
|
152
|
+
## Typing Indicator
|
|
82
153
|
|
|
83
154
|
The agent automatically shows "typing..." to the recipient while composing a response. This is handled by the supervisor — no action needed from you.
|
|
84
155
|
|
|
85
|
-
|
|
156
|
+
## Message Buffering (Business Mode)
|
|
86
157
|
|
|
87
|
-
In business mode, rapid messages from the same customer are debounced (4
|
|
158
|
+
In business mode, rapid messages from the same customer are debounced (4-second window) and delivered together. The system maintains a 30-message conversation buffer per customer.
|
|
88
159
|
|
|
89
|
-
|
|
160
|
+
## Concurrent Conversations (Business Mode)
|
|
90
161
|
|
|
91
162
|
Up to 5 customer conversations can run in parallel. Additional messages queue automatically.
|
|
92
163
|
|
|
164
|
+
---
|
|
165
|
+
|
|
93
166
|
## Account Management
|
|
94
167
|
|
|
95
168
|
**Disconnect** (keep credentials for later):
|
|
96
169
|
```bash
|
|
97
|
-
curl -s -X POST http://localhost:
|
|
170
|
+
curl -s -X POST http://localhost:7400/api/channels/whatsapp/disconnect
|
|
98
171
|
```
|
|
99
172
|
|
|
100
173
|
**Logout** (delete credentials, requires new QR scan):
|
|
101
174
|
```bash
|
|
102
|
-
curl -s -X POST http://localhost:
|
|
175
|
+
curl -s -X POST http://localhost:7400/api/channels/whatsapp/logout
|
|
103
176
|
```
|
|
104
177
|
|
|
105
178
|
**Switch accounts** (relink): Use the "Relink" button on the QR page, or logout + connect again.
|
|
106
179
|
|
|
180
|
+
---
|
|
181
|
+
|
|
107
182
|
## Human Interaction
|
|
108
183
|
|
|
109
184
|
- The human must scan the QR code with their phone — this cannot be automated
|
|
@@ -111,7 +186,27 @@ curl -s -X POST http://localhost:3000/api/channels/whatsapp/logout
|
|
|
111
186
|
- In business mode, explain to the human that admin numbers get full agent access while all other numbers get the customer-facing skill
|
|
112
187
|
- If the human asks about privacy: credentials are stored locally at `~/.bloby/channels/whatsapp/auth/`, never sent to external servers
|
|
113
188
|
|
|
114
|
-
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## API Reference
|
|
192
|
+
|
|
193
|
+
| Endpoint | Method | Purpose |
|
|
194
|
+
|----------|--------|---------|
|
|
195
|
+
| `/api/channels/status` | GET | List all channel statuses |
|
|
196
|
+
| `/api/channels/whatsapp/qr` | GET | Get current QR code SVG |
|
|
197
|
+
| `/api/channels/whatsapp/qr-page` | GET | Standalone QR scanning page |
|
|
198
|
+
| `/api/channels/whatsapp/connect` | POST | Start WhatsApp (triggers QR if needed) |
|
|
199
|
+
| `/api/channels/whatsapp/disconnect` | POST | Disconnect WhatsApp |
|
|
200
|
+
| `/api/channels/whatsapp/logout` | POST | Disconnect + delete credentials |
|
|
201
|
+
| `/api/channels/whatsapp/configure` | POST | Set mode + admins + skill |
|
|
202
|
+
| `/api/channels/whatsapp/pairing-code` | POST | Get 8-char pairing code (mobile linking) |
|
|
203
|
+
| `/api/channels/send` | POST | Send proactive message via channel |
|
|
204
|
+
|
|
205
|
+
All endpoints are on `http://localhost:7400`.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Technical Notes
|
|
115
210
|
|
|
116
211
|
- Baileys is a reverse-engineering of WhatsApp Web. It can break if WhatsApp changes their protocol. Reconnection is automatic on network drops.
|
|
117
212
|
- If you get error 401 (loggedOut), credentials were invalidated — the human needs to re-scan QR.
|