import { Command } from 'commander'; import chalk from 'chalk'; import { requireConfig } from '../config'; import { apiRequest } from '../api'; interface AuditEvent { id: string; timestamp: string; action: string; agentId?: string; tenantId?: string; outcome: string; details?: Record; } interface AuditLogsResponse { events: AuditEvent[]; nextCursor?: string; } function formatEvent(event: AuditEvent): string { const ts = chalk.dim(new Date(event.timestamp).toLocaleString()); const outcome = event.outcome === 'success' ? chalk.green(event.outcome) : chalk.red(event.outcome); const action = chalk.cyan(event.action); const agentPart = event.agentId !== undefined ? ' ' + chalk.dim('agent=' + event.agentId) : ''; return `${ts} ${action} outcome=${outcome}${agentPart} id=${chalk.dim(event.id)}`; } export function registerTailAuditLog(program: Command): void { program .command('tail-audit-log') .description( 'Poll and stream audit log events every 5 seconds (Ctrl+C to stop)', ) .option('--agent-id ', 'Filter events for a specific agent ID') .action(async (options: { agentId?: string }) => { const config = requireConfig(); console.log( chalk.bold('Tailing audit log') + (options.agentId !== undefined ? chalk.dim(` (agent: ${options.agentId})`) : '') + chalk.dim(' — press Ctrl+C to stop'), ); console.log(chalk.dim('─'.repeat(60))); const seenIds = new Set(); let cursor: string | undefined; let running = true; process.on('SIGINT', () => { running = false; console.log(); console.log(chalk.dim('Stopped.')); process.exit(0); }); while (running) { try { const params: Record = {}; if (options.agentId !== undefined) { params['agentId'] = options.agentId; } if (cursor !== undefined) { params['cursor'] = cursor; } // Request events from the last poll window params['limit'] = '50'; const data = await apiRequest( config, '/audit/logs', { params }, ); const events: AuditEvent[] = Array.isArray(data) ? data : (data as AuditLogsResponse).events ?? []; if (!Array.isArray(data) && (data as AuditLogsResponse).nextCursor !== undefined) { cursor = (data as AuditLogsResponse).nextCursor; } for (const event of events) { if (!seenIds.has(event.id)) { seenIds.add(event.id); console.log(formatEvent(event)); } } // Keep the seenIds set bounded to avoid unbounded memory growth if (seenIds.size > 10_000) { const arr = Array.from(seenIds); const keep = arr.slice(arr.length - 5_000); seenIds.clear(); for (const id of keep) seenIds.add(id); } } catch (err) { console.error( chalk.yellow('⚠') + ' Poll error: ' + (err instanceof Error ? err.message : String(err)), ); } // Wait 5 seconds between polls await new Promise((resolve) => { const timer = setTimeout(resolve, 5000); // Allow the timer to be garbage-collected if process exits if (typeof timer.unref === 'function') timer.unref(); }); } }); }