← 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 ═══');