agentaudit 3.10.2 → 3.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.mjs +668 -12
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
* scan <url> [url...] Quick static scan (regex)
|
|
10
10
|
* audit <url> [url...] Deep LLM-powered security audit
|
|
11
11
|
* lookup <name> Look up package in registry
|
|
12
|
+
* dashboard Interactive full-screen dashboard
|
|
13
|
+
* leaderboard Top contributors ranking
|
|
14
|
+
* benchmark LLM model performance comparison
|
|
12
15
|
* model [name|reset] Configure LLM provider + model
|
|
13
|
-
* setup
|
|
16
|
+
* setup Log in to agentaudit.dev (for report uploads)
|
|
14
17
|
* status Show current config + auth status
|
|
15
18
|
* help [command] Show help
|
|
16
19
|
*
|
|
@@ -417,12 +420,13 @@ async function registerAgent(agentName) {
|
|
|
417
420
|
}
|
|
418
421
|
|
|
419
422
|
async function setupCommand() {
|
|
420
|
-
console.log(` ${c.bold}
|
|
423
|
+
console.log(` ${c.bold}AgentAudit Login${c.reset}`);
|
|
424
|
+
console.log(` ${c.dim}Create an account to upload audit reports to agentaudit.dev${c.reset}`);
|
|
421
425
|
console.log();
|
|
422
426
|
|
|
423
427
|
const existing = loadCredentials();
|
|
424
428
|
if (existing) {
|
|
425
|
-
console.log(` ${icons.safe} Already
|
|
429
|
+
console.log(` ${icons.safe} Already logged in as ${c.bold}${existing.agent_name}${c.reset}`);
|
|
426
430
|
console.log(` ${c.dim}Key: ${existing.api_key.slice(0, 8)}...${c.reset}`);
|
|
427
431
|
console.log();
|
|
428
432
|
const answer = await askQuestion(` Reconfigure? ${c.dim}(y/N)${c.reset} `);
|
|
@@ -567,6 +571,120 @@ function severityIcon(sev) {
|
|
|
567
571
|
}
|
|
568
572
|
}
|
|
569
573
|
|
|
574
|
+
// ── TUI Rendering Helpers ───────────────────────────────
|
|
575
|
+
|
|
576
|
+
const term = {
|
|
577
|
+
clearScreen: '\x1b[2J\x1b[H',
|
|
578
|
+
hideCursor: '\x1b[?25l',
|
|
579
|
+
showCursor: '\x1b[?25h',
|
|
580
|
+
altScreenOn: '\x1b[?1049h',
|
|
581
|
+
altScreenOff: '\x1b[?1049l',
|
|
582
|
+
moveTo: (r, col) => `\x1b[${r};${col}H`,
|
|
583
|
+
clearLine: '\x1b[2K',
|
|
584
|
+
underline: '\x1b[4m',
|
|
585
|
+
noUnderline: '\x1b[24m',
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
const BOX = { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│', lt: '├', rt: '┤', tt: '┬', bt: '┴', x: '┼' };
|
|
589
|
+
|
|
590
|
+
// Strip ANSI escape codes for length calculations
|
|
591
|
+
function stripAnsi(str) {
|
|
592
|
+
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function visLen(str) {
|
|
596
|
+
return stripAnsi(str).length;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function padRight(str, len) {
|
|
600
|
+
const diff = len - visLen(str);
|
|
601
|
+
return diff > 0 ? str + ' '.repeat(diff) : str;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function padLeft(str, len) {
|
|
605
|
+
const diff = len - visLen(str);
|
|
606
|
+
return diff > 0 ? ' '.repeat(diff) + str : str;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function drawBox(title, contentLines, width) {
|
|
610
|
+
const inner = width - 4; // 2 for "│ " + 2 for " │"
|
|
611
|
+
const lines = [];
|
|
612
|
+
const titleStr = title ? ` ${title} ` : '';
|
|
613
|
+
const titleLen = visLen(titleStr);
|
|
614
|
+
const topDash = BOX.h.repeat(Math.max(1, inner + 2 - titleLen));
|
|
615
|
+
lines.push(` ${BOX.tl}${c.dim}─${c.reset}${c.bold}${titleStr}${c.reset}${c.dim}${topDash}${c.reset}${BOX.tr}`);
|
|
616
|
+
for (const line of contentLines) {
|
|
617
|
+
lines.push(` ${BOX.v} ${padRight(line, inner + 1)}${BOX.v}`);
|
|
618
|
+
}
|
|
619
|
+
lines.push(` ${BOX.bl}${c.dim}${BOX.h.repeat(inner + 2)}${c.reset}${BOX.br}`);
|
|
620
|
+
return lines;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// ████████░░░░ proportional bar
|
|
624
|
+
function renderBar(value, maxValue, maxWidth) {
|
|
625
|
+
if (maxValue <= 0 || value <= 0) return c.dim + '░'.repeat(maxWidth) + c.reset;
|
|
626
|
+
const filled = Math.min(Math.round((value / maxValue) * maxWidth), maxWidth);
|
|
627
|
+
const empty = maxWidth - filled;
|
|
628
|
+
return c.cyan + '█'.repeat(filled) + c.dim + '░'.repeat(empty) + c.reset;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// [████████░░] 89%
|
|
632
|
+
function renderGauge(value, max, width) {
|
|
633
|
+
const pct = max > 0 ? Math.round((value / max) * 100) : 0;
|
|
634
|
+
const inner = width - 2; // subtract brackets
|
|
635
|
+
const filled = Math.min(Math.round((pct / 100) * inner), inner);
|
|
636
|
+
const empty = inner - filled;
|
|
637
|
+
const color = pct >= 80 ? c.green : pct >= 50 ? c.yellow : c.red;
|
|
638
|
+
return `[${color}${'█'.repeat(filled)}${c.dim}${'░'.repeat(empty)}${c.reset}] ${pct}%`;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// ●●●○○ colored severity dots
|
|
642
|
+
function severityDots(breakdown) {
|
|
643
|
+
const parts = [];
|
|
644
|
+
const critical = breakdown?.critical || 0;
|
|
645
|
+
const high = breakdown?.high || 0;
|
|
646
|
+
const medium = breakdown?.medium || 0;
|
|
647
|
+
const low = breakdown?.low || 0;
|
|
648
|
+
for (let i = 0; i < Math.min(critical, 3); i++) parts.push(`${c.red}●${c.reset}`);
|
|
649
|
+
for (let i = 0; i < Math.min(high, 3); i++) parts.push(`${c.red}●${c.reset}`);
|
|
650
|
+
for (let i = 0; i < Math.min(medium, 2); i++) parts.push(`${c.yellow}●${c.reset}`);
|
|
651
|
+
for (let i = 0; i < Math.min(low, 2); i++) parts.push(`${c.blue}●${c.reset}`);
|
|
652
|
+
// fill remaining with empty dots up to 5
|
|
653
|
+
while (parts.length < 5) parts.push(`${c.dim}○${c.reset}`);
|
|
654
|
+
return parts.slice(0, 5).join('');
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// ▁▂▃▅▇█▆▃ mini sparkline
|
|
658
|
+
function sparkline(values) {
|
|
659
|
+
const chars = '▁▂▃▄▅▆▇█';
|
|
660
|
+
if (!values || values.length === 0) return '';
|
|
661
|
+
const max = Math.max(...values, 1);
|
|
662
|
+
return values.map(v => {
|
|
663
|
+
const idx = Math.min(Math.round((v / max) * (chars.length - 1)), chars.length - 1);
|
|
664
|
+
return chars[idx];
|
|
665
|
+
}).join('');
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function fmtNum(n) {
|
|
669
|
+
if (n == null) return '0';
|
|
670
|
+
return n.toLocaleString('en-US');
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function fmtPct(n) {
|
|
674
|
+
if (n == null) return '0%';
|
|
675
|
+
return Math.round(n) + '%';
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function dashboardBanner() {
|
|
679
|
+
const ver = getVersion();
|
|
680
|
+
return [
|
|
681
|
+
` ${BOX.tl}${c.dim}${BOX.h.repeat(35)}${c.reset}${BOX.tr}`,
|
|
682
|
+
` ${BOX.v} ${c.bold}${c.cyan}⛨ AgentAudit${c.reset} ${c.dim}v${ver}${c.reset}${' '.repeat(Math.max(0, 19 - ver.length))}${BOX.v}`,
|
|
683
|
+
` ${BOX.v} ${c.dim}Security Registry for AI Agents${c.reset} ${BOX.v}`,
|
|
684
|
+
` ${BOX.bl}${c.dim}${BOX.h.repeat(35)}${c.reset}${BOX.br}`,
|
|
685
|
+
];
|
|
686
|
+
}
|
|
687
|
+
|
|
570
688
|
// ── File Collection (same logic as MCP server) ──────────
|
|
571
689
|
|
|
572
690
|
function formatApiError(error, provider, statusCode) {
|
|
@@ -1940,6 +2058,443 @@ async function checkPackage(name) {
|
|
|
1940
2058
|
return data;
|
|
1941
2059
|
}
|
|
1942
2060
|
|
|
2061
|
+
// ── Dashboard / Leaderboard / Benchmark Commands ────────
|
|
2062
|
+
|
|
2063
|
+
async function fetchDashboardData() {
|
|
2064
|
+
const creds = loadCredentials();
|
|
2065
|
+
const fetches = [
|
|
2066
|
+
fetch(`${REGISTRY_URL}/api/stats`, { signal: AbortSignal.timeout(15_000) }).then(r => r.ok ? r.json() : null).catch(() => null),
|
|
2067
|
+
fetch(`${REGISTRY_URL}/api/leaderboard?limit=50`, { signal: AbortSignal.timeout(15_000) }).then(r => r.ok ? r.json() : null).catch(() => null),
|
|
2068
|
+
fetch(`${REGISTRY_URL}/api/benchmark`, { signal: AbortSignal.timeout(15_000) }).then(r => r.ok ? r.json() : null).catch(() => null),
|
|
2069
|
+
];
|
|
2070
|
+
if (creds?.agent_name && creds.agent_name !== 'env') {
|
|
2071
|
+
fetches.push(
|
|
2072
|
+
fetch(`${REGISTRY_URL}/api/agents/${encodeURIComponent(creds.agent_name)}`, {
|
|
2073
|
+
headers: { 'Authorization': `Bearer ${creds.api_key}` },
|
|
2074
|
+
signal: AbortSignal.timeout(15_000),
|
|
2075
|
+
}).then(r => r.ok ? r.json() : null).catch(() => null)
|
|
2076
|
+
);
|
|
2077
|
+
} else {
|
|
2078
|
+
fetches.push(Promise.resolve(null));
|
|
2079
|
+
}
|
|
2080
|
+
const [stats, leaderboard, benchmark, agent] = await Promise.all(fetches);
|
|
2081
|
+
return { stats, leaderboard, benchmark, agent, creds };
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
function renderOverviewTab(data, width) {
|
|
2085
|
+
const { stats, agent, leaderboard, creds } = data;
|
|
2086
|
+
const lines = [];
|
|
2087
|
+
const halfW = Math.min(Math.floor((width - 6) / 2), 40);
|
|
2088
|
+
|
|
2089
|
+
// Profile box
|
|
2090
|
+
const profileLines = [];
|
|
2091
|
+
if (agent && creds) {
|
|
2092
|
+
// Find rank
|
|
2093
|
+
let rank = '-';
|
|
2094
|
+
if (leaderboard && Array.isArray(leaderboard)) {
|
|
2095
|
+
const idx = leaderboard.findIndex(e => e.agent_name === creds.agent_name);
|
|
2096
|
+
if (idx >= 0) rank = `#${idx + 1} of ${leaderboard.length}`;
|
|
2097
|
+
}
|
|
2098
|
+
profileLines.push(`${c.bold}${creds.agent_name}${c.reset}${' '.repeat(Math.max(1, halfW - 14 - visLen(creds.agent_name) - visLen(rank)))}${c.dim}${rank}${c.reset}`);
|
|
2099
|
+
profileLines.push(`Points ${c.bold}${fmtNum(agent.total_points)}${c.reset}`);
|
|
2100
|
+
profileLines.push(`Audits ${c.bold}${fmtNum(agent.total_reports)}${c.reset}`);
|
|
2101
|
+
profileLines.push(`Findings ${c.bold}${fmtNum(agent.total_findings_submitted)}${c.reset} ${c.dim}(${fmtNum(agent.total_findings_confirmed)} confirmed)${c.reset}`);
|
|
2102
|
+
const sev = agent.severity_breakdown || {};
|
|
2103
|
+
profileLines.push('');
|
|
2104
|
+
const sevParts = [];
|
|
2105
|
+
if (sev.critical) sevParts.push(`${c.red}${sev.critical} crit${c.reset}`);
|
|
2106
|
+
if (sev.high) sevParts.push(`${c.red}${sev.high} high${c.reset}`);
|
|
2107
|
+
if (sev.medium) sevParts.push(`${c.yellow}${sev.medium} med${c.reset}`);
|
|
2108
|
+
if (sev.low) sevParts.push(`${c.blue}${sev.low} low${c.reset}`);
|
|
2109
|
+
profileLines.push(sevParts.join(' ') || `${c.dim}no findings yet${c.reset}`);
|
|
2110
|
+
} else {
|
|
2111
|
+
profileLines.push(`${c.dim}Not logged in${c.reset}`);
|
|
2112
|
+
profileLines.push(`${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to create account${c.reset}`);
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
// Registry box
|
|
2116
|
+
const regLines = [];
|
|
2117
|
+
if (stats) {
|
|
2118
|
+
regLines.push(`Packages Audited ${c.bold}${fmtNum(stats.skills_audited)}${c.reset}`);
|
|
2119
|
+
regLines.push(`Total Findings ${c.bold}${fmtNum(stats.total_findings)}${c.reset}`);
|
|
2120
|
+
regLines.push(`Total Reports ${c.bold}${fmtNum(stats.total_reports)}${c.reset}`);
|
|
2121
|
+
regLines.push(`Contributors ${c.bold}${fmtNum(stats.reporters)}${c.reset}`);
|
|
2122
|
+
regLines.push(`Avg Trust Score ${c.bold}${stats.avg_trust_score || 0}${c.reset}`);
|
|
2123
|
+
regLines.push('');
|
|
2124
|
+
const parts = [];
|
|
2125
|
+
if (stats.safe_packages) parts.push(`${c.green}●${fmtNum(stats.safe_packages)} safe${c.reset}`);
|
|
2126
|
+
if (stats.caution_packages) parts.push(`${c.yellow}●${fmtNum(stats.caution_packages)} caution${c.reset}`);
|
|
2127
|
+
if (stats.unsafe_packages) parts.push(`${c.red}●${fmtNum(stats.unsafe_packages)} unsafe${c.reset}`);
|
|
2128
|
+
regLines.push(parts.join(' ') || `${c.dim}no packages yet${c.reset}`);
|
|
2129
|
+
} else {
|
|
2130
|
+
regLines.push(`${c.dim}Could not load registry stats${c.reset}`);
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
const boxW = halfW + 4;
|
|
2134
|
+
const profileBox = drawBox('Your Profile', profileLines, boxW);
|
|
2135
|
+
const registryBox = drawBox('Registry', regLines, boxW);
|
|
2136
|
+
|
|
2137
|
+
// Side by side if wide enough, stacked otherwise
|
|
2138
|
+
if (width >= boxW * 2 + 4) {
|
|
2139
|
+
const maxLen = Math.max(profileBox.length, registryBox.length);
|
|
2140
|
+
while (profileBox.length < maxLen) profileBox.push(` ${BOX.v} ${' '.repeat(halfW + 1)}${BOX.v}`);
|
|
2141
|
+
while (registryBox.length < maxLen) registryBox.push(` ${BOX.v} ${' '.repeat(halfW + 1)}${BOX.v}`);
|
|
2142
|
+
for (let i = 0; i < maxLen; i++) {
|
|
2143
|
+
lines.push(profileBox[i] + ' ' + registryBox[i].trimStart());
|
|
2144
|
+
}
|
|
2145
|
+
} else {
|
|
2146
|
+
lines.push(...profileBox, '', ...registryBox);
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
return lines;
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
function renderLeaderboardTab(data, width, opts = {}) {
|
|
2153
|
+
const { leaderboard, creds } = data;
|
|
2154
|
+
const lines = [];
|
|
2155
|
+
const maxNameW = 20;
|
|
2156
|
+
const barW = Math.min(Math.max(10, width - 70), 30);
|
|
2157
|
+
|
|
2158
|
+
if (!leaderboard || !Array.isArray(leaderboard) || leaderboard.length === 0) {
|
|
2159
|
+
lines.push(` ${c.dim}No leaderboard data available${c.reset}`);
|
|
2160
|
+
return lines;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
const maxPts = leaderboard[0]?.total_points || 1;
|
|
2164
|
+
const medals = ['🥇', '🥈', '🥉'];
|
|
2165
|
+
|
|
2166
|
+
for (let i = 0; i < leaderboard.length; i++) {
|
|
2167
|
+
const entry = leaderboard[i];
|
|
2168
|
+
const name = (entry.agent_name || '').slice(0, maxNameW);
|
|
2169
|
+
const isMe = creds && entry.agent_name === creds.agent_name;
|
|
2170
|
+
const prefix = i < 3 ? ` ${medals[i]} ` : ` ${c.dim}#${String(i + 1).padStart(2)}${c.reset} `;
|
|
2171
|
+
const nameStr = isMe ? `${c.green}${c.bold}${name}${c.reset}` : name;
|
|
2172
|
+
const bar = renderBar(entry.total_points || 0, maxPts, barW);
|
|
2173
|
+
const pts = padLeft(`${fmtNum(entry.total_points || 0)} pts`, 12);
|
|
2174
|
+
const audits = padLeft(`${fmtNum(entry.total_reports || 0)} audits`, 12);
|
|
2175
|
+
const extra = entry.monthly_reports != null ? padLeft(`${fmtNum(entry.monthly_reports)} this mo`, 12) : '';
|
|
2176
|
+
lines.push(`${prefix}${padRight(nameStr, maxNameW + (isMe ? 14 : 0))} ${bar} ${pts} ${audits}${extra}`);
|
|
2177
|
+
|
|
2178
|
+
// Separator after top 3
|
|
2179
|
+
if (i === 2 && leaderboard.length > 3) {
|
|
2180
|
+
lines.push(` ${c.dim}${'─'.repeat(Math.min(width - 4, 76))}${c.reset}`);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
// Highlight current user if not in top list
|
|
2185
|
+
if (creds && !leaderboard.find(e => e.agent_name === creds.agent_name)) {
|
|
2186
|
+
lines.push('');
|
|
2187
|
+
lines.push(` ${c.dim}← you are not on this leaderboard yet${c.reset}`);
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
return lines;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
function renderBenchmarkTab(data, width) {
|
|
2194
|
+
const { benchmark } = data;
|
|
2195
|
+
const lines = [];
|
|
2196
|
+
|
|
2197
|
+
if (!benchmark || !benchmark.models || benchmark.models.length === 0) {
|
|
2198
|
+
lines.push(` ${c.dim}No benchmark data available${c.reset}`);
|
|
2199
|
+
return lines;
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
const overview = benchmark.overview || {};
|
|
2203
|
+
lines.push(` ${c.bold}${fmtNum(benchmark.models.length)}${c.reset} models ${c.dim}│${c.reset} ${c.bold}${fmtNum(overview.total_reports || 0)}${c.reset} audits ${c.dim}│${c.reset} ${c.bold}${fmtNum(overview.total_findings || 0)}${c.reset} findings`);
|
|
2204
|
+
lines.push('');
|
|
2205
|
+
|
|
2206
|
+
// Header
|
|
2207
|
+
const nameW = 26;
|
|
2208
|
+
const hdr = ` ${padRight(`${c.bold}Model${c.reset}`, nameW + 9)} ${padRight('Audits', 8)} ${padRight('Risk', 8)} ${padRight('Detection', 16)} Severity`;
|
|
2209
|
+
lines.push(hdr);
|
|
2210
|
+
lines.push(` ${c.dim}${'─'.repeat(Math.min(width - 4, 80))}${c.reset}`);
|
|
2211
|
+
|
|
2212
|
+
for (const m of benchmark.models) {
|
|
2213
|
+
const name = (m.audit_model || 'unknown').slice(0, nameW - 2);
|
|
2214
|
+
const audits = padLeft(fmtNum(m.total_audits), 6);
|
|
2215
|
+
const riskVal = parseFloat(m.avg_risk_score) || 0;
|
|
2216
|
+
const riskColor = riskVal <= 20 ? c.green : riskVal <= 40 ? c.yellow : c.red;
|
|
2217
|
+
const risk = `${riskColor}${padLeft(String(Math.round(riskVal)), 3)}${c.reset}`;
|
|
2218
|
+
const detection = renderGauge(m.detection_rate || 0, 100, 10);
|
|
2219
|
+
const dots = severityDots(m.severity_breakdown);
|
|
2220
|
+
lines.push(` ${padRight(name, nameW)} ${audits} ${risk} ${detection} ${dots}`);
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
// Vulnerability landscape
|
|
2224
|
+
const cats = benchmark.global_categories;
|
|
2225
|
+
if (cats && cats.length > 0) {
|
|
2226
|
+
lines.push('');
|
|
2227
|
+
const catTotal = cats.reduce((sum, cat) => sum + (cat.count || 0), 0) || 1;
|
|
2228
|
+
const catW = Math.min(width - 8, 56);
|
|
2229
|
+
const catLines = [];
|
|
2230
|
+
for (const cat of cats.slice(0, 6)) {
|
|
2231
|
+
const pct = Math.round((cat.count / catTotal) * 100);
|
|
2232
|
+
const barFill = Math.round((cat.count / catTotal) * (catW - 30));
|
|
2233
|
+
catLines.push(`${padRight(cat.category || 'Other', 22)} ${c.cyan}${'█'.repeat(Math.max(1, barFill))}${c.dim}${'░'.repeat(Math.max(0, catW - 30 - barFill))}${c.reset} ${padLeft(fmtPct(pct), 4)}`);
|
|
2234
|
+
}
|
|
2235
|
+
lines.push(...drawBox('Vulnerability Landscape', catLines, catW + 4));
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
// Cross-model findings
|
|
2239
|
+
const cross = benchmark.cross_model_findings;
|
|
2240
|
+
if (cross && cross.length > 0) {
|
|
2241
|
+
lines.push('');
|
|
2242
|
+
lines.push(` ${c.bold}Cross-Model Findings${c.reset} ${c.dim}(confirmed by multiple models)${c.reset}`);
|
|
2243
|
+
for (const cf of cross.slice(0, 5)) {
|
|
2244
|
+
const models = (cf.models || []).slice(0, 3).join(', ');
|
|
2245
|
+
lines.push(` ${c.dim}•${c.reset} ${cf.title || cf.pattern_id} ${c.dim}[${cf.model_count} models: ${models}]${c.reset}`);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
return lines;
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
async function leaderboardCommand(args) {
|
|
2253
|
+
const tabArg = args.find((a, i) => args[i - 1] === '--tab') || 'overall';
|
|
2254
|
+
const showAll = args.includes('--all');
|
|
2255
|
+
const limit = showAll ? 200 : 20;
|
|
2256
|
+
|
|
2257
|
+
if (!quietMode && !jsonMode) {
|
|
2258
|
+
banner();
|
|
2259
|
+
process.stdout.write(` ${c.dim}Loading leaderboard...${c.reset}`);
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
let data;
|
|
2263
|
+
try {
|
|
2264
|
+
const url = `${REGISTRY_URL}/api/leaderboard?tab=${encodeURIComponent(tabArg)}&limit=${limit}`;
|
|
2265
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(15_000) });
|
|
2266
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2267
|
+
data = await res.json();
|
|
2268
|
+
} catch (err) {
|
|
2269
|
+
if (!jsonMode) {
|
|
2270
|
+
process.stdout.write('\r\x1b[2K');
|
|
2271
|
+
console.log(` ${c.red}Failed to load leaderboard: ${err.message}${c.reset}`);
|
|
2272
|
+
}
|
|
2273
|
+
process.exitCode = 1;
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
if (jsonMode) {
|
|
2278
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
process.stdout.write('\r\x1b[2K');
|
|
2283
|
+
const creds = loadCredentials();
|
|
2284
|
+
console.log(` ${c.bold}Leaderboard${c.reset} ${c.dim}${tabArg}${c.reset}`);
|
|
2285
|
+
console.log();
|
|
2286
|
+
|
|
2287
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
2288
|
+
console.log(` ${c.dim}No data available${c.reset}`);
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
const maxPts = data[0]?.total_points || 1;
|
|
2293
|
+
const medals = ['🥇', '🥈', '🥉'];
|
|
2294
|
+
const barW = 24;
|
|
2295
|
+
|
|
2296
|
+
for (let i = 0; i < data.length; i++) {
|
|
2297
|
+
const entry = data[i];
|
|
2298
|
+
const name = (entry.agent_name || '').slice(0, 20);
|
|
2299
|
+
const isMe = creds && entry.agent_name === creds.agent_name;
|
|
2300
|
+
const prefix = i < 3 ? ` ${medals[i]} ` : ` #${String(i + 1).padStart(2)} `;
|
|
2301
|
+
const nameStr = isMe ? `${c.green}${c.bold}${name}${c.reset}` : name;
|
|
2302
|
+
const bar = renderBar(entry.total_points || 0, maxPts, barW);
|
|
2303
|
+
const pts = `${fmtNum(entry.total_points || 0)} pts`;
|
|
2304
|
+
const audits = `${fmtNum(entry.total_reports || 0)} audits`;
|
|
2305
|
+
console.log(`${prefix}${padRight(nameStr, 22 + (isMe ? 14 : 0))} ${bar} ${padLeft(pts, 12)} ${padLeft(audits, 10)}`);
|
|
2306
|
+
if (i === 2 && data.length > 3) {
|
|
2307
|
+
console.log(` ${c.dim}${'─'.repeat(74)}${c.reset}`);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
console.log();
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
async function benchmarkCommand(args) {
|
|
2314
|
+
if (!quietMode && !jsonMode) {
|
|
2315
|
+
banner();
|
|
2316
|
+
process.stdout.write(` ${c.dim}Loading benchmark data...${c.reset}`);
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
let data;
|
|
2320
|
+
try {
|
|
2321
|
+
const res = await fetch(`${REGISTRY_URL}/api/benchmark`, { signal: AbortSignal.timeout(15_000) });
|
|
2322
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2323
|
+
data = await res.json();
|
|
2324
|
+
} catch (err) {
|
|
2325
|
+
if (!jsonMode) {
|
|
2326
|
+
process.stdout.write('\r\x1b[2K');
|
|
2327
|
+
console.log(` ${c.red}Failed to load benchmark: ${err.message}${c.reset}`);
|
|
2328
|
+
}
|
|
2329
|
+
process.exitCode = 1;
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
if (jsonMode) {
|
|
2334
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
process.stdout.write('\r\x1b[2K');
|
|
2339
|
+
const width = process.stdout.columns || 80;
|
|
2340
|
+
const benchLines = renderBenchmarkTab({ benchmark: data }, width);
|
|
2341
|
+
console.log(` ${c.bold}Model Benchmark${c.reset} ${c.dim}— LLM Audit Performance${c.reset}`);
|
|
2342
|
+
console.log();
|
|
2343
|
+
for (const line of benchLines) console.log(line);
|
|
2344
|
+
console.log();
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
async function dashboardCommand() {
|
|
2348
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
2349
|
+
console.log(`${c.red}Error: dashboard requires an interactive terminal${c.reset}`);
|
|
2350
|
+
console.log(`${c.dim}Tip: use ${c.cyan}agentaudit leaderboard${c.dim} or ${c.cyan}agentaudit benchmark${c.dim} for non-interactive output${c.reset}`);
|
|
2351
|
+
process.exitCode = 1;
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
const tabs = [
|
|
2356
|
+
{ key: '1', label: 'Overview' },
|
|
2357
|
+
{ key: '2', label: 'Leaderboard' },
|
|
2358
|
+
{ key: '3', label: 'Benchmark' },
|
|
2359
|
+
];
|
|
2360
|
+
let activeTab = 0;
|
|
2361
|
+
let scrollOffset = 0;
|
|
2362
|
+
let running = true;
|
|
2363
|
+
|
|
2364
|
+
// Enter alt screen
|
|
2365
|
+
process.stdout.write(term.altScreenOn + term.hideCursor);
|
|
2366
|
+
|
|
2367
|
+
// Loading screen
|
|
2368
|
+
process.stdout.write(term.clearScreen);
|
|
2369
|
+
const bannerLines = dashboardBanner();
|
|
2370
|
+
for (const line of bannerLines) process.stdout.write(line + '\n');
|
|
2371
|
+
process.stdout.write(`\n ${c.dim}Loading data...${c.reset}\n`);
|
|
2372
|
+
|
|
2373
|
+
// Fetch data
|
|
2374
|
+
const data = await fetchDashboardData();
|
|
2375
|
+
|
|
2376
|
+
function render() {
|
|
2377
|
+
const cols = process.stdout.columns || 80;
|
|
2378
|
+
const rows = process.stdout.rows || 24;
|
|
2379
|
+
let output = term.clearScreen;
|
|
2380
|
+
|
|
2381
|
+
// Banner
|
|
2382
|
+
const bLines = dashboardBanner();
|
|
2383
|
+
for (const line of bLines) output += line + '\n';
|
|
2384
|
+
output += '\n';
|
|
2385
|
+
|
|
2386
|
+
// Tab bar
|
|
2387
|
+
let tabBar = ' ';
|
|
2388
|
+
for (let i = 0; i < tabs.length; i++) {
|
|
2389
|
+
const tab = tabs[i];
|
|
2390
|
+
if (i === activeTab) {
|
|
2391
|
+
tabBar += `${c.bold}${c.cyan}${term.underline} [${tab.key}] ${tab.label} ${term.noUnderline}${c.reset}`;
|
|
2392
|
+
} else {
|
|
2393
|
+
tabBar += `${c.dim} [${tab.key}] ${tab.label} ${c.reset}`;
|
|
2394
|
+
}
|
|
2395
|
+
if (i < tabs.length - 1) tabBar += `${c.dim}──${c.reset}`;
|
|
2396
|
+
}
|
|
2397
|
+
output += tabBar + '\n\n';
|
|
2398
|
+
|
|
2399
|
+
// Tab content
|
|
2400
|
+
let contentLines = [];
|
|
2401
|
+
switch (activeTab) {
|
|
2402
|
+
case 0:
|
|
2403
|
+
contentLines = renderOverviewTab(data, cols);
|
|
2404
|
+
break;
|
|
2405
|
+
case 1:
|
|
2406
|
+
contentLines = renderLeaderboardTab(data, cols);
|
|
2407
|
+
break;
|
|
2408
|
+
case 2:
|
|
2409
|
+
contentLines = renderBenchmarkTab(data, cols);
|
|
2410
|
+
break;
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
// Apply scroll
|
|
2414
|
+
const availableRows = rows - 10; // header + tab bar + footer
|
|
2415
|
+
const maxScroll = Math.max(0, contentLines.length - availableRows);
|
|
2416
|
+
scrollOffset = Math.min(scrollOffset, maxScroll);
|
|
2417
|
+
scrollOffset = Math.max(0, scrollOffset);
|
|
2418
|
+
|
|
2419
|
+
const visible = contentLines.slice(scrollOffset, scrollOffset + availableRows);
|
|
2420
|
+
for (const line of visible) output += line + '\n';
|
|
2421
|
+
|
|
2422
|
+
// Footer
|
|
2423
|
+
output += '\n';
|
|
2424
|
+
const scrollInfo = contentLines.length > availableRows ? ` ${c.dim}[${scrollOffset + 1}-${Math.min(scrollOffset + availableRows, contentLines.length)}/${contentLines.length}]${c.reset}` : '';
|
|
2425
|
+
output += ` ${c.dim}←→ tab ↑↓ scroll 1-3 jump q quit${c.reset}${scrollInfo}\n`;
|
|
2426
|
+
|
|
2427
|
+
process.stdout.write(output);
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
function cleanup() {
|
|
2431
|
+
if (!running) return;
|
|
2432
|
+
running = false;
|
|
2433
|
+
process.stdout.write(term.showCursor + term.altScreenOff);
|
|
2434
|
+
try { process.stdin.setRawMode(false); } catch {}
|
|
2435
|
+
process.stdin.pause();
|
|
2436
|
+
process.stdin.removeListener('data', onKeypress);
|
|
2437
|
+
process.stdout.removeListener('resize', render);
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
function onKeypress(key) {
|
|
2441
|
+
if (!running) return;
|
|
2442
|
+
|
|
2443
|
+
// q or Ctrl+C = quit
|
|
2444
|
+
if (key === 'q' || key === '\x03') {
|
|
2445
|
+
cleanup();
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// Tab navigation
|
|
2450
|
+
if (key === '\x1b[C' || key === '\t') { // Right arrow or Tab
|
|
2451
|
+
activeTab = (activeTab + 1) % tabs.length;
|
|
2452
|
+
scrollOffset = 0;
|
|
2453
|
+
render();
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
if (key === '\x1b[D') { // Left arrow
|
|
2457
|
+
activeTab = (activeTab - 1 + tabs.length) % tabs.length;
|
|
2458
|
+
scrollOffset = 0;
|
|
2459
|
+
render();
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
// Number keys
|
|
2464
|
+
if (key === '1') { activeTab = 0; scrollOffset = 0; render(); return; }
|
|
2465
|
+
if (key === '2') { activeTab = 1; scrollOffset = 0; render(); return; }
|
|
2466
|
+
if (key === '3') { activeTab = 2; scrollOffset = 0; render(); return; }
|
|
2467
|
+
|
|
2468
|
+
// Scroll
|
|
2469
|
+
if (key === '\x1b[A' || key === 'k') { scrollOffset = Math.max(0, scrollOffset - 1); render(); return; } // Up
|
|
2470
|
+
if (key === '\x1b[B' || key === 'j') { scrollOffset++; render(); return; } // Down
|
|
2471
|
+
if (key === '\x1b[5~') { scrollOffset = Math.max(0, scrollOffset - 10); render(); return; } // PageUp
|
|
2472
|
+
if (key === '\x1b[6~') { scrollOffset += 10; render(); return; } // PageDown
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
// Setup input
|
|
2476
|
+
process.stdin.setRawMode(true);
|
|
2477
|
+
process.stdin.resume();
|
|
2478
|
+
process.stdin.setEncoding('utf8');
|
|
2479
|
+
process.stdin.on('data', onKeypress);
|
|
2480
|
+
process.stdout.on('resize', render);
|
|
2481
|
+
|
|
2482
|
+
// Graceful cleanup
|
|
2483
|
+
process.on('exit', cleanup);
|
|
2484
|
+
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
2485
|
+
process.on('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
2486
|
+
|
|
2487
|
+
// Initial render
|
|
2488
|
+
render();
|
|
2489
|
+
|
|
2490
|
+
// Keep alive until user quits
|
|
2491
|
+
await new Promise(resolve => {
|
|
2492
|
+
const check = setInterval(() => {
|
|
2493
|
+
if (!running) { clearInterval(check); resolve(); }
|
|
2494
|
+
}, 100);
|
|
2495
|
+
});
|
|
2496
|
+
}
|
|
2497
|
+
|
|
1943
2498
|
// ── Main ────────────────────────────────────────────────
|
|
1944
2499
|
|
|
1945
2500
|
async function main() {
|
|
@@ -2054,9 +2609,10 @@ async function main() {
|
|
|
2054
2609
|
setup: [
|
|
2055
2610
|
`${c.bold}agentaudit setup${c.reset}`,
|
|
2056
2611
|
``,
|
|
2057
|
-
`
|
|
2058
|
-
`This
|
|
2059
|
-
|
|
2612
|
+
`Log in to agentaudit.dev — create an account or enter an existing API key.`,
|
|
2613
|
+
`This enables uploading audit reports to the public registry.`,
|
|
2614
|
+
``,
|
|
2615
|
+
`${c.bold}Note:${c.reset} This is NOT for LLM/provider configuration. Use ${c.cyan}agentaudit model${c.reset} for that.`,
|
|
2060
2616
|
``,
|
|
2061
2617
|
`${c.bold}Examples:${c.reset}`,
|
|
2062
2618
|
` agentaudit setup`,
|
|
@@ -2073,15 +2629,70 @@ async function main() {
|
|
|
2073
2629
|
` agentaudit status`,
|
|
2074
2630
|
` agentaudit status --json`,
|
|
2075
2631
|
],
|
|
2632
|
+
dashboard: [
|
|
2633
|
+
`${c.bold}agentaudit dashboard${c.reset}`,
|
|
2634
|
+
``,
|
|
2635
|
+
`Interactive full-screen dashboard with stats, leaderboard, and benchmark.`,
|
|
2636
|
+
`Uses alternate screen buffer — your scrollback stays intact.`,
|
|
2637
|
+
``,
|
|
2638
|
+
`${c.bold}Navigation:${c.reset}`,
|
|
2639
|
+
` ←→ / Tab Switch between tabs`,
|
|
2640
|
+
` 1-3 Jump to tab by number`,
|
|
2641
|
+
` ↑↓ / j/k Scroll content`,
|
|
2642
|
+
` PgUp/PgDn Scroll fast`,
|
|
2643
|
+
` q / Ctrl+C Quit`,
|
|
2644
|
+
``,
|
|
2645
|
+
`${c.bold}Tabs:${c.reset}`,
|
|
2646
|
+
` [1] Overview Your profile + registry stats`,
|
|
2647
|
+
` [2] Leaderboard Top contributors ranking`,
|
|
2648
|
+
` [3] Benchmark LLM model performance comparison`,
|
|
2649
|
+
``,
|
|
2650
|
+
`${c.bold}Aliases:${c.reset} dashboard, dash`,
|
|
2651
|
+
],
|
|
2652
|
+
dash: null, // alias → dashboard
|
|
2653
|
+
leaderboard: [
|
|
2654
|
+
`${c.bold}agentaudit leaderboard${c.reset} [options]`,
|
|
2655
|
+
``,
|
|
2656
|
+
`Show top contributors ranking. Pipe-friendly output (no alt-screen).`,
|
|
2657
|
+
``,
|
|
2658
|
+
`${c.bold}Options:${c.reset}`,
|
|
2659
|
+
` --tab <name> Category: overall (default), monthly, reviewers, verifiers, streaks`,
|
|
2660
|
+
` --all Show all entries (default: top 20)`,
|
|
2661
|
+
` --json Machine-readable JSON output`,
|
|
2662
|
+
``,
|
|
2663
|
+
`${c.bold}Aliases:${c.reset} leaderboard, lb`,
|
|
2664
|
+
``,
|
|
2665
|
+
`${c.bold}Examples:${c.reset}`,
|
|
2666
|
+
` agentaudit leaderboard`,
|
|
2667
|
+
` agentaudit leaderboard --tab monthly`,
|
|
2668
|
+
` agentaudit leaderboard --all --json`,
|
|
2669
|
+
],
|
|
2670
|
+
lb: null, // alias → leaderboard
|
|
2671
|
+
benchmark: [
|
|
2672
|
+
`${c.bold}agentaudit benchmark${c.reset} [options]`,
|
|
2673
|
+
``,
|
|
2674
|
+
`Compare LLM model performance across security audits.`,
|
|
2675
|
+
`Shows detection rates, severity breakdown, and vulnerability landscape.`,
|
|
2676
|
+
``,
|
|
2677
|
+
`${c.bold}Options:${c.reset}`,
|
|
2678
|
+
` --json Machine-readable JSON output`,
|
|
2679
|
+
``,
|
|
2680
|
+
`${c.bold}Aliases:${c.reset} benchmark, bench`,
|
|
2681
|
+
``,
|
|
2682
|
+
`${c.bold}Examples:${c.reset}`,
|
|
2683
|
+
` agentaudit benchmark`,
|
|
2684
|
+
` agentaudit benchmark --json`,
|
|
2685
|
+
],
|
|
2686
|
+
bench: null, // alias → benchmark
|
|
2076
2687
|
};
|
|
2077
2688
|
|
|
2078
2689
|
// Show subcommand help: `agentaudit help <cmd>` or `agentaudit <cmd> --help`
|
|
2079
2690
|
function showSubcommandHelp(cmd) {
|
|
2080
2691
|
let helpLines = subcommandHelp[cmd];
|
|
2081
|
-
if (helpLines === null
|
|
2692
|
+
if (helpLines === null) {
|
|
2082
2693
|
// alias redirect
|
|
2083
|
-
const
|
|
2084
|
-
helpLines = subcommandHelp[
|
|
2694
|
+
const aliases = { check: 'lookup', dash: 'dashboard', lb: 'leaderboard', bench: 'benchmark' };
|
|
2695
|
+
helpLines = subcommandHelp[aliases[cmd] || cmd];
|
|
2085
2696
|
}
|
|
2086
2697
|
if (!helpLines) {
|
|
2087
2698
|
console.log(` ${c.red}No help available for "${cmd}"${c.reset}`);
|
|
@@ -2124,9 +2735,14 @@ async function main() {
|
|
|
2124
2735
|
console.log(` ${c.cyan}audit${c.reset} <url> [url...] Deep LLM-powered security audit (~30s)`);
|
|
2125
2736
|
console.log(` ${c.cyan}lookup${c.reset} <name> Look up package in registry`);
|
|
2126
2737
|
console.log();
|
|
2738
|
+
console.log(` ${c.bold}COMMUNITY${c.reset}`);
|
|
2739
|
+
console.log(` ${c.cyan}dashboard${c.reset} Interactive dashboard (full-screen)`);
|
|
2740
|
+
console.log(` ${c.cyan}leaderboard${c.reset} Top contributors ranking`);
|
|
2741
|
+
console.log(` ${c.cyan}benchmark${c.reset} LLM model performance comparison`);
|
|
2742
|
+
console.log();
|
|
2127
2743
|
console.log(` ${c.bold}CONFIGURATION${c.reset}`);
|
|
2128
2744
|
console.log(` ${c.cyan}model${c.reset} Configure LLM provider + model`);
|
|
2129
|
-
console.log(` ${c.cyan}setup${c.reset}
|
|
2745
|
+
console.log(` ${c.cyan}setup${c.reset} Log in to agentaudit.dev (for report uploads)`);
|
|
2130
2746
|
console.log(` ${c.cyan}status${c.reset} Show current config + auth status`);
|
|
2131
2747
|
console.log();
|
|
2132
2748
|
console.log(` ${c.bold}FLAGS${c.reset}`);
|
|
@@ -2154,9 +2770,23 @@ async function main() {
|
|
|
2154
2770
|
// Default no-arg → discover
|
|
2155
2771
|
const command = args.length === 0 ? 'discover' : args[0];
|
|
2156
2772
|
const targets = args.slice(1);
|
|
2157
|
-
|
|
2773
|
+
|
|
2774
|
+
// Dashboard/Leaderboard/Benchmark — before banner() since dashboard uses alt-screen
|
|
2775
|
+
if (command === 'dashboard' || command === 'dash') {
|
|
2776
|
+
await dashboardCommand();
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
if (command === 'leaderboard' || command === 'lb') {
|
|
2780
|
+
await leaderboardCommand(targets);
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
if (command === 'benchmark' || command === 'bench') {
|
|
2784
|
+
await benchmarkCommand(targets);
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2158
2788
|
banner();
|
|
2159
|
-
|
|
2789
|
+
|
|
2160
2790
|
if (command === 'setup') {
|
|
2161
2791
|
await setupCommand();
|
|
2162
2792
|
return;
|
|
@@ -2218,6 +2848,32 @@ async function main() {
|
|
|
2218
2848
|
}
|
|
2219
2849
|
console.log();
|
|
2220
2850
|
|
|
2851
|
+
// Personal stats (from registry, silently skip on error)
|
|
2852
|
+
if (creds?.agent_name && creds.agent_name !== 'env') {
|
|
2853
|
+
try {
|
|
2854
|
+
const [agentRes, lbRes] = await Promise.all([
|
|
2855
|
+
fetch(`${REGISTRY_URL}/api/agents/${encodeURIComponent(creds.agent_name)}`, {
|
|
2856
|
+
headers: { 'Authorization': `Bearer ${creds.api_key}` },
|
|
2857
|
+
signal: AbortSignal.timeout(10_000),
|
|
2858
|
+
}).then(r => r.ok ? r.json() : null),
|
|
2859
|
+
fetch(`${REGISTRY_URL}/api/leaderboard?limit=100`, { signal: AbortSignal.timeout(10_000) }).then(r => r.ok ? r.json() : null),
|
|
2860
|
+
]);
|
|
2861
|
+
if (agentRes) {
|
|
2862
|
+
console.log(` ${c.bold}Profile${c.reset}`);
|
|
2863
|
+
let rank = '-';
|
|
2864
|
+
if (Array.isArray(lbRes)) {
|
|
2865
|
+
const idx = lbRes.findIndex(e => e.agent_name === creds.agent_name);
|
|
2866
|
+
if (idx >= 0) rank = `#${idx + 1} of ${lbRes.length}`;
|
|
2867
|
+
}
|
|
2868
|
+
console.log(` Rank ${c.bold}${rank}${c.reset}`);
|
|
2869
|
+
console.log(` Points ${c.bold}${fmtNum(agentRes.total_points || 0)}${c.reset}`);
|
|
2870
|
+
console.log(` Audits ${c.bold}${fmtNum(agentRes.total_reports || 0)}${c.reset}`);
|
|
2871
|
+
console.log(` Findings ${c.bold}${fmtNum(agentRes.total_findings_submitted || 0)}${c.reset} ${c.dim}(${fmtNum(agentRes.total_findings_confirmed || 0)} confirmed)${c.reset}`);
|
|
2872
|
+
console.log();
|
|
2873
|
+
}
|
|
2874
|
+
} catch {}
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2221
2877
|
// JSON mode
|
|
2222
2878
|
if (jsonMode) {
|
|
2223
2879
|
const status = {
|