← back to Butlr
scripts/diagnose-audio.js
78 lines
#!/usr/bin/env node
// One-shot prod diagnostic: why didn't audio play for the last call?
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
console.log('═══ DIAGNOSE AUDIO PATH ═══\n');
// 1. What calls are in calls.json?
console.log('── calls.json ──');
try {
const calls = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'data', 'calls.json'), 'utf8'));
console.log(` total: ${calls.length}`);
for (const c of calls.slice(0, 8)) {
console.log(` ${c.id} | ${c.business_name} | status=${c.status} | created=${(c.created_at || '?').slice(0, 19)} | user_id=${c.user_id || '-'} | twilio_sid=${(c.twilio_call_sid || '').slice(0, 12)}`);
}
} catch (e) { console.log(' ERR:', e.message); }
// 2. pm2 logs since last call
console.log('\n── pm2 logs: WS / Stream / listen-bridge (last 200) ──');
try {
const logs = execSync('pm2 logs butlr --lines 400 --nostream --raw 2>&1', { encoding: 'utf8', maxBuffer: 5_000_000 });
const lines = logs.split('\n');
const interesting = lines.filter(l =>
/stream|listen-bridge|websocket|wss|\/stream\/|connected.*ws|browser-listener|gather|ai-agent|twiml/i.test(l)
);
for (const line of interesting.slice(-40)) console.log(' ', line);
} catch (e) { console.log(' ERR:', e.message); }
// 3. Last few errors
console.log('\n── pm2 logs: errors / warnings (last 20) ──');
try {
const logs = execSync('pm2 logs butlr --lines 400 --nostream --raw 2>&1', { encoding: 'utf8', maxBuffer: 5_000_000 });
const lines = logs.split('\n');
const errors = lines.filter(l => /error|ECONN|ETIMED|EADDR|warn|EPIPE/i.test(l));
for (const line of errors.slice(-20)) console.log(' ', line);
} catch (e) { console.log(' ERR:', e.message); }
// 4. Recordings dir
console.log('\n── data/recordings/ ──');
try {
const dir = path.join(__dirname, '..', 'data', 'recordings');
const files = fs.readdirSync(dir).map(f => {
const s = fs.statSync(path.join(dir, f));
return ` ${f.padEnd(28)} | ${(s.size/1024).toFixed(1)} KB | ${s.mtime.toISOString().slice(0,19)}`;
});
for (const f of files.slice(0, 12)) console.log(f);
if (files.length > 12) console.log(` ... (+${files.length - 12} more)`);
} catch (e) { console.log(' ERR:', e.message); }
// 5. Audio cache
console.log('\n── data/audio-cache/ (ElevenLabs announces) ──');
try {
const dir = path.join(__dirname, '..', 'data', 'audio-cache');
const files = fs.readdirSync(dir);
console.log(` count: ${files.length}`);
for (const f of files.slice(0, 6)) console.log(` ${f}`);
} catch (e) { console.log(' ERR:', e.message); }
// 6. Twilio recordings via API for the latest call
console.log('\n── Twilio API: recordings for last call ──');
try {
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
const sid = process.env.TWILIO_ACCOUNT_SID;
const tok = process.env.TWILIO_AUTH_TOKEN;
if (!sid || !tok) { console.log(' no twilio creds'); }
else {
const out = execSync(`curl -s -u ${sid}:${tok} "https://api.twilio.com/2010-04-01/Accounts/${sid}/Recordings.json?PageSize=5"`, { encoding: 'utf8' });
const j = JSON.parse(out);
for (const r of j.recordings || []) {
console.log(` ${r.sid} | CallSid=${r.call_sid} | dur=${r.duration}s | status=${r.status} | date=${r.date_created}`);
}
}
} catch (e) { console.log(' ERR:', e.message); }
console.log('\n═══ END ═══');