// Board-originated sends in pi session files: timestamps/roles only, no content beyond 80 chars of the send. import { readFileSync, readdirSync, statSync } from 'node:fs'; import { join } from 'node:path'; const roots = process.argv.slice(2); const walk = d => readdirSync(d).flatMap(n => { const p = join(d, n); const s = statSync(p); return s.isDirectory() ? walk(p) : n.endsWith('.jsonl') ? [p] : []; }); for (const root of roots) for (const f of walk(root)) { const E = readFileSync(f, 'utf8').split('\n').filter(Boolean).map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean); E.forEach((e, i) => { if (e.type !== 'message' || e.message.role !== 'user') return; const c = e.message.content; const t = typeof c === 'string' ? c : (c || []).filter(x => x.type === 'text').map(x => x.text).join('\n'); if (!/control-board ->/.test(t)) return; const after = E.slice(i + 1).filter(x => x.type === 'message'); const firstA = after.find(x => x.message.role === 'assistant' && x.message.stopReason === 'stop'); const nextUser = after.find(x => x.message.role === 'user'); console.log(JSON.stringify({ file: f.replace(process.env.HOME, '~'), sent: e.timestamp, head: t.slice(0, 80).replace(/\n/g, ' '), firstStopAnswer: firstA?.timestamp ?? null, answerChars: firstA ? firstA.message.content.filter(x => x.type === 'text').map(x => x.text).join('\n').length : null, nextUser: nextUser?.timestamp ?? null })); }); }