import { prisma, disconnectDb } from '../src/db'; import * as os from 'os'; import * as fs from 'fs'; import * as path from 'path'; import * as readline from 'readline'; import { execSync, execFileSync } from 'child_process'; const LOG_FILE_PATH = path.join(process.cwd(), 'logs', 'app.log'); const USAGE_HISTORY_PATH = path.join(process.cwd(), 'logs', 'usage-history.jsonl'); const maskEmail = (email: string): string => { const [local, domain] = email.split('@'); if (!domain) return '***'; const maskedLocal = local.length <= 2 ? '*'.repeat(local.length) : local[0] + '*'.repeat(local.length - 2) + local[local.length - 1]; return `${maskedLocal}@${domain}`; }; const parseIntArg = (args: string[], flag: string, defaultVal: number): number => { const idx = args.indexOf(flag); if (idx === -1 || idx + 1 >= args.length) return defaultVal; const val = parseInt(args[idx + 1], 10); if (isNaN(val) || val < 0) { console.error( `Invalid value for ${flag}: expected a non-negative integer, got "${args[idx + 1]}"`, ); process.exit(1); } return val; }; const formatBytes = (bytes: number, decimals = 2): string => { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; }; // --- Query result interfaces --- interface DbSizeRow { size: string; } interface TableSizeRow { table: string; size: string; } interface ActiveCountRow { device_count: bigint; ops_count: bigint; } interface CountRow { count: bigint; } interface UserStorageRow { id: number; email: string; ops_bytes: bigint; ops_count: bigint; snapshot_bytes: bigint; total_bytes: bigint; } interface OperationRow { id: number; user_id: number; action_type: string; op_type: string; entity_type: string; entity_id: string | null; payload_bytes: bigint; payload_json_length: bigint; received_at: bigint; } interface EntityTypeBreakdownRow { entity_type: string; count: bigint; total_bytes: bigint; avg_bytes: bigint; max_bytes: bigint; } interface LargestOperationRow { id: number; action_type: string; entity_type: string; entity_id: string | null; payload_bytes: bigint; payload: unknown; } interface RecentUserRow { id: number; email: string; created_at: Date; last_active: bigint; device_count: bigint; ops_7d: bigint; } interface EngagedUserRow { id: number; email: string; active_days: bigint; ops_count: bigint; } interface ActiveCountsRow { active_24h: bigint; active_7d: bigint; active_30d: bigint; } interface ActiveDeviceUserRow { id: number; email: string; devices: bigint; last_seen: bigint; } // --- Snapshot types for JSONL history --- interface UsageSnapshotUser { id: number; email: string; bytes: number; opsBytes: number; opsCount: number; snapshotBytes: number; } interface UsageSnapshot { timestamp: string; totalBytes: number; userCount: number; users: UsageSnapshotUser[]; } const showStats = async (): Promise => { console.log('\n--- System Vitals ---'); console.log(`Hostname: ${os.hostname()}`); console.log(`OS: ${os.type()} ${os.release()} (${os.arch()})`); console.log(`CPUs: ${os.cpus().length}`); const loadAvg = os.loadavg(); console.log( `Load Avg: ${loadAvg[0].toFixed(2)}, ${loadAvg[1].toFixed(2)}, ${loadAvg[2].toFixed(2)}`, ); const totalMem = os.totalmem(); const freeMem = os.freemem(); const usedMem = totalMem - freeMem; console.log(`Memory: ${formatBytes(usedMem)} used / ${formatBytes(totalMem)} total`); console.log('\n--- Database Connection ---'); try { await prisma.$queryRaw`SELECT 1`; console.log('Status: Connected ✅'); // Get DB Size const dbSizeResult: DbSizeRow[] = await prisma.$queryRaw` SELECT pg_size_pretty(pg_database_size(current_database())) as size; `; console.log(`DB Size: ${dbSizeResult[0]?.size}`); // Get table sizes const tableSizes: TableSizeRow[] = await prisma.$queryRaw` SELECT relname as table, pg_size_pretty(pg_total_relation_size(relid)) as size FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 5; `; if (tableSizes.length > 0) { console.log('\nTop tables by size:'); tableSizes.forEach((t) => console.log(` ${t.table}: ${t.size}`)); } } catch (error) { console.log('Status: Disconnected ❌'); console.error('Error:', error); } // Disk space console.log('\n--- Disk Space ---'); try { const dfOutput = execSync('df -h / 2>/dev/null || echo "N/A"', { encoding: 'utf-8' }); const lines = dfOutput.trim().split('\n'); if (lines.length >= 2) { const parts = lines[1].split(/\s+/); if (parts.length >= 5) { console.log( `Root filesystem: ${parts[2]} used / ${parts[1]} total (${parts[4]} used)`, ); } } // Data directory size const dataDir = process.env.DATA_DIR || './data'; if (fs.existsSync(dataDir)) { let duOutput: string; try { duOutput = execFileSync('du', ['-sh', dataDir], { encoding: 'utf-8' }).trim(); } catch { duOutput = 'N/A'; } const size = duOutput.split('\t')[0]; console.log(`Data directory: ${size}`); } } catch { console.log('Could not determine disk space'); } }; const showUsage = async (saveHistory = true, showFullEmails = false): Promise => { console.log('\n--- User Storage Usage (Top 20) ---'); try { // Aggregate per-user op size in a single pass; correlated subqueries here // scan the full operations table per user and hang on large DBs. const users: UserStorageRow[] = await prisma.$queryRaw` WITH ops_per_user AS ( SELECT user_id, SUM(pg_column_size(payload))::bigint AS ops_bytes, COUNT(*)::bigint AS ops_count FROM operations GROUP BY user_id ) SELECT u.id, u.email, COALESCE(o.ops_bytes, 0) as ops_bytes, COALESCE(o.ops_count, 0) as ops_count, COALESCE(LENGTH(s.snapshot_data), 0) as snapshot_bytes, (COALESCE(o.ops_bytes, 0) + COALESCE(LENGTH(s.snapshot_data), 0)) as total_bytes FROM users u LEFT JOIN ops_per_user o ON o.user_id = u.id LEFT JOIN user_sync_state s ON u.id = s.user_id ORDER BY total_bytes DESC LIMIT 20; `; if (users.length === 0) { console.log('No users found.'); return; } const displayEmail = (email: string): string => showFullEmails ? email : maskEmail(email); const usersData = users.map((u) => ({ id: u.id, email: u.email, bytes: Number(u.total_bytes), opsBytes: Number(u.ops_bytes), opsCount: Number(u.ops_count), snapshotBytes: Number(u.snapshot_bytes), })); console.table( usersData.map((u) => ({ ID: u.id, Email: displayEmail(u.email), Ops: u.opsCount, OpsSize: formatBytes(u.opsBytes), AvgOp: u.opsCount > 0 ? formatBytes(u.opsBytes / u.opsCount) : '-', Snapshot: formatBytes(u.snapshotBytes), Total: formatBytes(u.bytes), })), ); const totalBytes = usersData.reduce((sum: number, u) => sum + u.bytes, 0); console.log(`\nTotal: ${formatBytes(totalBytes)} across ${usersData.length} users`); // Save snapshot to history if (saveHistory) { const snapshotUsers = usersData.map((u) => ({ ...u, email: displayEmail(u.email), })); const snapshot = { timestamp: new Date().toISOString(), totalBytes, userCount: usersData.length, users: snapshotUsers, }; const logsDir = path.dirname(USAGE_HISTORY_PATH); if (!fs.existsSync(logsDir)) { fs.mkdirSync(logsDir, { recursive: true }); } fs.appendFileSync(USAGE_HISTORY_PATH, JSON.stringify(snapshot) + '\n'); console.log(`\nSnapshot saved to ${USAGE_HISTORY_PATH}`); } } catch (error) { console.error('Error fetching usage data:', error); } }; const showUsageHistory = async (args: string[]): Promise => { console.log('\n--- Usage History ---'); if (!fs.existsSync(USAGE_HISTORY_PATH)) { console.log('No history yet. Run "usage" command to start tracking.'); return; } const showFullEmails = args.includes('--unmask'); const tailCount = parseIntArg(args, '--tail', 10); const content = fs.readFileSync(USAGE_HISTORY_PATH, 'utf-8'); const lines = content.trim().split('\n').filter(Boolean); const snapshots: UsageSnapshot[] = lines .slice(-tailCount) .map((line) => JSON.parse(line)); if (snapshots.length === 0) { console.log('No snapshots found.'); return; } console.table( snapshots.map((s) => ({ Date: new Date(s.timestamp).toLocaleString(), Users: s.userCount, Total: formatBytes(s.totalBytes), })), ); // Show growth if we have multiple snapshots if (snapshots.length >= 2) { const first = snapshots[0]; const last = snapshots[snapshots.length - 1]; const growth = last.totalBytes - first.totalBytes; const days = (new Date(last.timestamp).getTime() - new Date(first.timestamp).getTime()) / (1000 * 60 * 60 * 24); console.log( `\nGrowth over ${days.toFixed(1)} days: ${growth >= 0 ? '+' : ''}${formatBytes(growth)}`, ); if (days > 0) { console.log(`Average: ${formatBytes(growth / days)}/day`); } // Per-user pivot table: rows = users, columns = snapshots const allUserIds = new Set(); const userEmails = new Map(); for (const snap of snapshots) { for (const u of snap.users) { allUserIds.add(String(u.id)); userEmails.set(String(u.id), u.email); } } const displayEmail = (email: string): string => showFullEmails ? email : maskEmail(email); // Build column headers (short date format) const colHeaders = snapshots.map((s) => { const d = new Date(s.timestamp); return `${d.getMonth() + 1}/${d.getDate()}`; }); // Build rows with raw byte values for sorting const rows = Array.from(allUserIds).map((id) => { const row: Record = { Email: displayEmail(userEmails.get(id) ?? id), }; let latestBytes = 0; snapshots.forEach((snap, i: number) => { const user = snap.users.find((u) => String(u.id) === id); row[colHeaders[i]] = user ? formatBytes(user.bytes) : '-'; if (i === snapshots.length - 1) { latestBytes = user ? user.bytes : 0; } }); return { row, latestBytes }; }); // Sort by latest snapshot size (descending) using raw bytes rows.sort((a, b) => b.latestBytes - a.latestBytes); console.log('\n--- Per-User History ---'); console.table(rows.map((r) => r.row)); } }; const showLogs = async (args: string[]): Promise => { console.log('\n--- Server Logs ---'); if (!fs.existsSync(LOG_FILE_PATH)) { console.error(`Log file not found at: ${LOG_FILE_PATH}`); console.error( 'Ensure LOG_TO_FILE=true is set in .env and the server has written logs.', ); return; } const searchIndex = args.indexOf('--search'); const searchTerm = searchIndex !== -1 ? args[searchIndex + 1] : null; const tailCount = parseIntArg(args, '--tail', 100); const onlyErrors = args.includes('--error'); const fileStream = fs.createReadStream(LOG_FILE_PATH); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity, }); const filteredLines: string[] = []; for await (const line of rl) { let include = true; if (onlyErrors && !line.includes('"level":"ERROR"') && !line.includes('[ERROR]')) { include = false; } if (searchTerm && !line.toLowerCase().includes(searchTerm.toLowerCase())) { include = false; } if (include) { filteredLines.push(line); if (filteredLines.length > tailCount) { filteredLines.shift(); // Keep only the last N lines } } } filteredLines.forEach((line) => console.log(line)); }; const showOps = async (args: string[]): Promise => { console.log('\n--- Recent Operations Analysis ---'); try { const tailCount = parseIntArg(args, '--tail', 50); const userId = parseIntArg(args, '--user', -1); const hasUserFilter = userId >= 0; // Get recent operations with sizes let ops: OperationRow[]; if (hasUserFilter) { ops = await prisma.$queryRaw` SELECT o.id, o.user_id, o.action_type, o.op_type, o.entity_type, o.entity_id, pg_column_size(o.payload) as payload_bytes, LENGTH(o.payload::text) as payload_json_length, o.received_at FROM operations o WHERE o.user_id = ${userId} ORDER BY o.server_seq DESC LIMIT ${tailCount}; `; } else { ops = await prisma.$queryRaw` SELECT o.id, o.user_id, o.action_type, o.op_type, o.entity_type, o.entity_id, pg_column_size(o.payload) as payload_bytes, LENGTH(o.payload::text) as payload_json_length, o.received_at FROM operations o ORDER BY o.server_seq DESC LIMIT ${tailCount}; `; } if (ops.length === 0) { console.log('No operations found.'); return; } console.table( ops.map((o) => ({ User: o.user_id, Action: o.action_type.substring(0, 40), Entity: `${o.entity_type}:${(o.entity_id || '*').substring(0, 15)}`, PayloadSize: formatBytes(Number(o.payload_bytes)), JSONLen: Number(o.payload_json_length), Time: new Date(Number(o.received_at)).toLocaleTimeString(), })), ); // Summary by entity type let byType: EntityTypeBreakdownRow[]; if (hasUserFilter) { byType = await prisma.$queryRaw` SELECT o.entity_type, COUNT(*) as count, SUM(pg_column_size(o.payload)) as total_bytes, AVG(pg_column_size(o.payload)) as avg_bytes, MAX(pg_column_size(o.payload)) as max_bytes FROM operations o WHERE o.user_id = ${userId} GROUP BY o.entity_type ORDER BY total_bytes DESC; `; } else { byType = await prisma.$queryRaw` SELECT o.entity_type, COUNT(*) as count, SUM(pg_column_size(o.payload)) as total_bytes, AVG(pg_column_size(o.payload)) as avg_bytes, MAX(pg_column_size(o.payload)) as max_bytes FROM operations o GROUP BY o.entity_type ORDER BY total_bytes DESC; `; } console.log('\n--- Breakdown by Entity Type ---'); console.table( byType.map((t) => ({ Type: t.entity_type, Count: Number(t.count), Total: formatBytes(Number(t.total_bytes)), Avg: formatBytes(Number(t.avg_bytes)), Max: formatBytes(Number(t.max_bytes)), })), ); // Show largest single operation let largest: LargestOperationRow[]; if (hasUserFilter) { largest = await prisma.$queryRaw` SELECT o.id, o.action_type, o.entity_type, o.entity_id, pg_column_size(o.payload) as payload_bytes, o.payload FROM operations o WHERE o.user_id = ${userId} ORDER BY pg_column_size(o.payload) DESC LIMIT 1; `; } else { largest = await prisma.$queryRaw` SELECT o.id, o.action_type, o.entity_type, o.entity_id, pg_column_size(o.payload) as payload_bytes, o.payload FROM operations o ORDER BY pg_column_size(o.payload) DESC LIMIT 1; `; } if (largest.length > 0) { const op = largest[0]; console.log('\n--- Largest Operation ---'); console.log(`ID: ${op.id}`); console.log(`Action: ${op.action_type}`); console.log(`Entity: ${op.entity_type}:${op.entity_id || '*'}`); console.log(`Size: ${formatBytes(Number(op.payload_bytes))}`); // Show keys in the payload const payload = op.payload as Record | null; if (payload && typeof payload === 'object') { console.log('\nPayload structure:'); const analyzePayload = ( obj: Record, prefix = '', depth = 0, ): void => { if (depth > 10) return; for (const key of Object.keys(obj)) { const val = obj[key]; const valStr = JSON.stringify(val); const size = new TextEncoder().encode(valStr).length; if (size > 1000) { console.log( ` ${prefix}${key}: ${formatBytes(size)} (${typeof val}${Array.isArray(val) ? `[${val.length}]` : ''})`, ); if (typeof val === 'object' && val !== null && !Array.isArray(val)) { analyzePayload(val as Record, prefix + ' ', depth + 1); } } } }; analyzePayload(payload); } } } catch (error) { console.error('Error fetching operations:', error); } }; const showActiveUsers = async (args: string[]): Promise => { console.log('\n--- Active Users Report ---'); try { const showFullEmails = args.includes('--unmask'); const now = Date.now(); const ONE_DAY = 24 * 60 * 60 * 1000; const engagedThreshold = parseIntArg(args, '--threshold', 3); const recentLimit = parseIntArg(args, '--limit', 30); const displayEmail = (email: string): string => showFullEmails ? email : maskEmail(email); // Total registered users const totalUsers = await prisma.user.count(); const verifiedUsers = await prisma.user.count({ where: { isVerified: 1 } }); console.log(`\nTotal registered users: ${totalUsers}`); console.log(`Verified users: ${verifiedUsers}`); // Active users by time period // "Device activity" = device heartbeats (includes idle polling) // "Sync operations" = actual data changes pushed to server const periods = [ { label: 'Last 24 hours', ms: ONE_DAY }, { label: 'Last 7 days', ms: 7 * ONE_DAY }, { label: 'Last 30 days', ms: 30 * ONE_DAY }, { label: 'Last 90 days', ms: 90 * ONE_DAY }, ]; console.log('\n--- Active Users (by device heartbeat / by sync operations) ---'); for (const period of periods) { const threshold = BigInt(now - period.ms); const result: ActiveCountRow[] = await prisma.$queryRaw` SELECT (SELECT COUNT(DISTINCT user_id) FROM sync_devices WHERE last_seen_at > ${threshold}) as device_count, (SELECT COUNT(DISTINCT user_id) FROM operations WHERE received_at > ${threshold}) as ops_count; `; const devices = Number(result[0]?.device_count ?? 0); const ops = Number(result[0]?.ops_count ?? 0); console.log(` ${period.label}: ${devices} connected / ${ops} syncing`); } // New users by time period console.log('\n--- New Registrations ---'); const regPeriods = [ { label: 'Last 24 hours', ms: ONE_DAY }, { label: 'Last 7 days', ms: 7 * ONE_DAY }, { label: 'Last 30 days', ms: 30 * ONE_DAY }, ]; for (const period of regPeriods) { const since = new Date(now - period.ms); const count = await prisma.user.count({ where: { createdAt: { gte: since } }, }); console.log(` ${period.label}: ${count} new users`); } // Recently active users table (last 7 days) const sevenDaysAgo = BigInt(now - 7 * ONE_DAY); // Get total count first const totalActive: CountRow[] = await prisma.$queryRaw` SELECT COUNT(DISTINCT u.id) as count FROM users u INNER JOIN sync_devices d ON u.id = d.user_id WHERE d.last_seen_at > ${sevenDaysAgo}; `; const totalActiveCount = Number(totalActive[0]?.count ?? 0); const recentUsers: RecentUserRow[] = await prisma.$queryRaw` SELECT u.id, u.email, u.created_at, MAX(d.last_seen_at) as last_active, COUNT(DISTINCT d.client_id) as device_count, COALESCE(COUNT(o.id), 0) as ops_7d FROM users u INNER JOIN sync_devices d ON u.id = d.user_id LEFT JOIN operations o ON u.id = o.user_id AND o.received_at > ${sevenDaysAgo} WHERE d.last_seen_at > ${sevenDaysAgo} GROUP BY u.id, u.email, u.created_at ORDER BY last_active DESC LIMIT ${recentLimit}; `; if (recentUsers.length > 0) { const suffix = totalActiveCount > recentUsers.length ? ` (showing ${recentUsers.length} of ${totalActiveCount})` : ''; console.log(`\n--- Recently Active Users (last 7 days)${suffix} ---`); console.table( recentUsers.map((u) => ({ ID: u.id, Email: displayEmail(u.email), Devices: Number(u.device_count), 'Ops (7d)': Number(u.ops_7d), 'Last Active': new Date(Number(u.last_active)).toLocaleString(), Registered: new Date(u.created_at).toLocaleDateString(), })), ); } // Engaged users: active on N+ distinct days (UTC) in the last 2 weeks const twoWeeksAgo = BigInt(now - 14 * ONE_DAY); const engagedUsers: EngagedUserRow[] = await prisma.$queryRaw` SELECT u.id, u.email, COUNT(DISTINCT (TO_TIMESTAMP(o.received_at::double precision / 1000) AT TIME ZONE 'UTC')::date) as active_days, COUNT(*) as ops_count FROM users u INNER JOIN operations o ON u.id = o.user_id WHERE o.received_at > ${twoWeeksAgo} GROUP BY u.id, u.email HAVING COUNT(DISTINCT (TO_TIMESTAMP(o.received_at::double precision / 1000) AT TIME ZONE 'UTC')::date) >= ${engagedThreshold} ORDER BY active_days DESC, ops_count DESC; `; console.log( `\n--- Engaged Users (${engagedThreshold}+ active days in last 2 weeks, UTC) ---`, ); console.log(`Count: ${engagedUsers.length}`); if (engagedUsers.length > 0) { console.table( engagedUsers.map((u) => ({ ID: u.id, Email: displayEmail(u.email), 'Active Days': Number(u.active_days), 'Ops (2w)': Number(u.ops_count), })), ); } // Users who never synced (no device ever registered) const neverSynced: CountRow[] = await prisma.$queryRaw` SELECT COUNT(*) as count FROM users u LEFT JOIN sync_devices d ON u.id = d.user_id WHERE d.user_id IS NULL; `; console.log( `\nUsers who never registered a device: ${Number(neverSynced[0]?.count ?? 0)}`, ); } catch (error) { console.error('Error fetching active users:', error); } }; // Fast variant of active-users that touches only sync_devices + users. // Avoids the operations table entirely so it stays fast on large DBs. const showActiveUsersQuick = async (args: string[]): Promise => { console.log('\n--- Active Users (quick: sync_devices only) ---'); try { const showFullEmails = args.includes('--unmask'); const limit = parseIntArg(args, '--limit', 50); const displayEmail = (email: string): string => showFullEmails ? email : maskEmail(email); const now = Date.now(); const ONE_DAY = 24 * 60 * 60 * 1000; const since24h = BigInt(now - ONE_DAY); const since7d = BigInt(now - 7 * ONE_DAY); const since30d = BigInt(now - 30 * ONE_DAY); const counts: ActiveCountsRow[] = await prisma.$queryRaw` SELECT COUNT(DISTINCT user_id) FILTER (WHERE last_seen_at > ${since24h}) AS active_24h, COUNT(DISTINCT user_id) FILTER (WHERE last_seen_at > ${since7d}) AS active_7d, COUNT(DISTINCT user_id) FILTER (WHERE last_seen_at > ${since30d}) AS active_30d FROM sync_devices; `; const c = counts[0]; console.log(` Last 24 hours: ${Number(c?.active_24h ?? 0)}`); console.log(` Last 7 days: ${Number(c?.active_7d ?? 0)}`); console.log(` Last 30 days: ${Number(c?.active_30d ?? 0)}`); const users: ActiveDeviceUserRow[] = await prisma.$queryRaw` SELECT u.id, u.email, COUNT(DISTINCT d.client_id) AS devices, MAX(d.last_seen_at) AS last_seen FROM sync_devices d JOIN users u ON u.id = d.user_id WHERE d.last_seen_at > ${since7d} GROUP BY u.id, u.email ORDER BY MAX(d.last_seen_at) DESC LIMIT ${limit}; `; if (users.length === 0) { console.log('\nNo users active in the last 7 days.'); return; } console.log(`\n--- Top ${users.length} users by last device activity (7d) ---`); console.table( users.map((u) => ({ ID: u.id, Email: displayEmail(u.email), Devices: Number(u.devices), 'Last Seen': new Date(Number(u.last_seen)).toLocaleString(), })), ); } catch (error) { console.error('Error fetching active users (quick):', error); } }; const main = async (): Promise => { const args = process.argv.slice(2); const command = args[0]; const showFullEmails = args.includes('--unmask'); try { switch (command) { case 'stats': await showStats(); break; case 'usage': await showUsage(!args.includes('--no-save'), showFullEmails); break; case 'usage-history': await showUsageHistory(args); break; case 'logs': await showLogs(args); break; case 'ops': await showOps(args); break; case 'active-users': await showActiveUsers(args); break; case 'active-users-quick': await showActiveUsersQuick(args); break; default: console.log('SuperSync Monitor CLI'); console.log('Usage: npm run monitor -- [flags]'); console.log('\nCommands:'); console.log(' stats Show system vitals and DB status'); console.log(' usage Show top 20 users by storage (saves snapshot)'); console.log(' --no-save Skip saving snapshot to history'); console.log(' usage-history Show usage over time'); console.log(' --tail Show last n snapshots (default 10)'); console.log(' active-users Show active user counts and recent activity'); console.log(' --threshold Engaged users day threshold (default 3)'); console.log(' --limit Recently active users limit (default 30)'); console.log( ' active-users-quick Fast active-user listing (sync_devices only; skips operations)', ); console.log(' --limit Top users limit (default 50)'); console.log(' logs Show server logs'); console.log(' --tail Show last n lines (default 100)'); console.log(' --search "s" Filter logs by term'); console.log(' --error Show only errors'); console.log(' ops Analyze recent operations'); console.log(' --tail Show last n ops (default 50)'); console.log(' --user Filter by user ID'); console.log('\nGlobal flags:'); console.log(' --unmask Show full email addresses (masked by default)'); break; } } catch (err) { console.error('Unexpected error:', err); } finally { await disconnectDb(); } }; main();