agentaudit 3.10.1 → 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 +916 -48
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* AgentAudit CLI — Security scanner for AI packages
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* Usage: agentaudit <command> [options]
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* discover Find MCP servers in AI editors
|
|
9
|
+
* scan <url> [url...] Quick static scan (regex)
|
|
10
|
+
* audit <url> [url...] Deep LLM-powered security audit
|
|
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
|
|
15
|
+
* model [name|reset] Configure LLM provider + model
|
|
16
|
+
* setup Log in to agentaudit.dev (for report uploads)
|
|
17
|
+
* status Show current config + auth status
|
|
18
|
+
* help [command] Show help
|
|
19
|
+
*
|
|
20
|
+
* Flags: --json, --quiet, --no-color, --no-upload, --model, --export, --debug
|
|
14
21
|
*/
|
|
15
22
|
|
|
16
23
|
import fs from 'fs';
|
|
@@ -413,12 +420,13 @@ async function registerAgent(agentName) {
|
|
|
413
420
|
}
|
|
414
421
|
|
|
415
422
|
async function setupCommand() {
|
|
416
|
-
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}`);
|
|
417
425
|
console.log();
|
|
418
426
|
|
|
419
427
|
const existing = loadCredentials();
|
|
420
428
|
if (existing) {
|
|
421
|
-
console.log(` ${icons.safe} Already
|
|
429
|
+
console.log(` ${icons.safe} Already logged in as ${c.bold}${existing.agent_name}${c.reset}`);
|
|
422
430
|
console.log(` ${c.dim}Key: ${existing.api_key.slice(0, 8)}...${c.reset}`);
|
|
423
431
|
console.log();
|
|
424
432
|
const answer = await askQuestion(` Reconfigure? ${c.dim}(y/N)${c.reset} `);
|
|
@@ -563,6 +571,120 @@ function severityIcon(sev) {
|
|
|
563
571
|
}
|
|
564
572
|
}
|
|
565
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
|
+
|
|
566
688
|
// ── File Collection (same logic as MCP server) ──────────
|
|
567
689
|
|
|
568
690
|
function formatApiError(error, provider, statusCode) {
|
|
@@ -1936,6 +2058,443 @@ async function checkPackage(name) {
|
|
|
1936
2058
|
return data;
|
|
1937
2059
|
}
|
|
1938
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
|
+
|
|
1939
2498
|
// ── Main ────────────────────────────────────────────────
|
|
1940
2499
|
|
|
1941
2500
|
async function main() {
|
|
@@ -1959,53 +2518,251 @@ async function main() {
|
|
|
1959
2518
|
const modelIdx = args.indexOf('--model');
|
|
1960
2519
|
if (modelIdx !== -1) args.splice(modelIdx, 2);
|
|
1961
2520
|
|
|
2521
|
+
// Detect per-command --help BEFORE stripping (e.g. `agentaudit model --help`)
|
|
2522
|
+
const wantsHelp = args.includes('--help') || args.includes('-h');
|
|
2523
|
+
// Strip --help/-h from args for routing
|
|
2524
|
+
args = args.filter(a => a !== '--help' && a !== '-h');
|
|
2525
|
+
|
|
1962
2526
|
if (args[0] === '-v' || args[0] === '--version') {
|
|
1963
2527
|
console.log(`agentaudit ${getVersion()}`);
|
|
1964
2528
|
process.exitCode = 0; return;
|
|
1965
2529
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
2530
|
+
|
|
2531
|
+
// Subcommand help definitions
|
|
2532
|
+
const subcommandHelp = {
|
|
2533
|
+
discover: [
|
|
2534
|
+
`${c.bold}agentaudit discover${c.reset} [options]`,
|
|
2535
|
+
``,
|
|
2536
|
+
`Find MCP servers configured in your AI editors (Cursor, Claude Desktop, VS Code, Windsurf).`,
|
|
2537
|
+
``,
|
|
2538
|
+
`${c.bold}Options:${c.reset}`,
|
|
2539
|
+
` --quick, -s Auto-scan all discovered servers (regex-based)`,
|
|
2540
|
+
` --deep, -a Interactively select servers for deep LLM audit`,
|
|
2541
|
+
``,
|
|
2542
|
+
`${c.bold}Examples:${c.reset}`,
|
|
2543
|
+
` agentaudit discover`,
|
|
2544
|
+
` agentaudit discover --quick`,
|
|
2545
|
+
` agentaudit discover --deep`,
|
|
2546
|
+
],
|
|
2547
|
+
scan: [
|
|
2548
|
+
`${c.bold}agentaudit scan${c.reset} <url> [url...] [options]`,
|
|
2549
|
+
``,
|
|
2550
|
+
`Quick regex-based static security scan (~2s). Checks for 15+ patterns`,
|
|
2551
|
+
`(command injection, eval, hardcoded secrets, path traversal, etc.)`,
|
|
2552
|
+
``,
|
|
2553
|
+
`${c.bold}Options:${c.reset}`,
|
|
2554
|
+
` --deep Run deep LLM audit instead (same as \`agentaudit audit\`)`,
|
|
2555
|
+
``,
|
|
2556
|
+
`${c.bold}Examples:${c.reset}`,
|
|
2557
|
+
` agentaudit scan https://github.com/owner/repo`,
|
|
2558
|
+
` agentaudit scan https://github.com/a/b https://github.com/c/d`,
|
|
2559
|
+
` agentaudit scan https://github.com/owner/repo --deep`,
|
|
2560
|
+
],
|
|
2561
|
+
audit: [
|
|
2562
|
+
`${c.bold}agentaudit audit${c.reset} <url> [url...] [options]`,
|
|
2563
|
+
``,
|
|
2564
|
+
`Deep LLM-powered 3-pass security audit (~30s). Requires an LLM API key.`,
|
|
2565
|
+
``,
|
|
2566
|
+
`${c.bold}Options:${c.reset}`,
|
|
2567
|
+
` --model <name> Override LLM model for this run`,
|
|
2568
|
+
` --no-upload Skip uploading report to registry`,
|
|
2569
|
+
` --export Export audit payload as markdown (for manual LLM review)`,
|
|
2570
|
+
` --debug Show raw LLM response on parse errors`,
|
|
2571
|
+
``,
|
|
2572
|
+
`${c.bold}Examples:${c.reset}`,
|
|
2573
|
+
` agentaudit audit https://github.com/owner/repo`,
|
|
2574
|
+
` agentaudit audit https://github.com/owner/repo --no-upload`,
|
|
2575
|
+
` agentaudit audit https://github.com/owner/repo --model gpt-4o`,
|
|
2576
|
+
` agentaudit audit https://github.com/owner/repo --export`,
|
|
2577
|
+
],
|
|
2578
|
+
lookup: [
|
|
2579
|
+
`${c.bold}agentaudit lookup${c.reset} <name> [options]`,
|
|
2580
|
+
``,
|
|
2581
|
+
`Look up a package in the AgentAudit registry. Shows trust score,`,
|
|
2582
|
+
`findings, confidence level, and audit history.`,
|
|
2583
|
+
``,
|
|
2584
|
+
`${c.bold}Aliases:${c.reset} lookup, check`,
|
|
2585
|
+
``,
|
|
2586
|
+
`${c.bold}Examples:${c.reset}`,
|
|
2587
|
+
` agentaudit lookup fastmcp`,
|
|
2588
|
+
` agentaudit lookup @anthropic/mcp-server-filesystem --json`,
|
|
2589
|
+
` agentaudit check fastmcp`,
|
|
2590
|
+
],
|
|
2591
|
+
check: null, // alias → lookup
|
|
2592
|
+
model: [
|
|
2593
|
+
`${c.bold}agentaudit model${c.reset} [name|reset]`,
|
|
2594
|
+
``,
|
|
2595
|
+
`Configure LLM provider and model. Interactive two-step menu when`,
|
|
2596
|
+
`called without arguments: choose provider, then choose model.`,
|
|
2597
|
+
``,
|
|
2598
|
+
`${c.bold}Usage:${c.reset}`,
|
|
2599
|
+
` agentaudit model Interactive provider + model menu`,
|
|
2600
|
+
` agentaudit model <name> Set model directly (keeps current provider)`,
|
|
2601
|
+
` agentaudit model reset Reset provider + model to defaults`,
|
|
2602
|
+
``,
|
|
2603
|
+
`${c.bold}Examples:${c.reset}`,
|
|
2604
|
+
` agentaudit model`,
|
|
2605
|
+
` agentaudit model gpt-4o`,
|
|
2606
|
+
` agentaudit model qwen/qwen3-coder`,
|
|
2607
|
+
` agentaudit model reset`,
|
|
2608
|
+
],
|
|
2609
|
+
setup: [
|
|
2610
|
+
`${c.bold}agentaudit setup${c.reset}`,
|
|
2611
|
+
``,
|
|
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.`,
|
|
2616
|
+
``,
|
|
2617
|
+
`${c.bold}Examples:${c.reset}`,
|
|
2618
|
+
` agentaudit setup`,
|
|
2619
|
+
],
|
|
2620
|
+
status: [
|
|
2621
|
+
`${c.bold}agentaudit status${c.reset}`,
|
|
2622
|
+
``,
|
|
2623
|
+
`Show current configuration: active LLM provider, model, account`,
|
|
2624
|
+
`status, and which API keys are available.`,
|
|
2625
|
+
``,
|
|
2626
|
+
`${c.bold}Aliases:${c.reset} status, whoami`,
|
|
2627
|
+
``,
|
|
2628
|
+
`${c.bold}Examples:${c.reset}`,
|
|
2629
|
+
` agentaudit status`,
|
|
2630
|
+
` agentaudit status --json`,
|
|
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
|
|
2687
|
+
};
|
|
2688
|
+
|
|
2689
|
+
// Show subcommand help: `agentaudit help <cmd>` or `agentaudit <cmd> --help`
|
|
2690
|
+
function showSubcommandHelp(cmd) {
|
|
2691
|
+
let helpLines = subcommandHelp[cmd];
|
|
2692
|
+
if (helpLines === null) {
|
|
2693
|
+
// alias redirect
|
|
2694
|
+
const aliases = { check: 'lookup', dash: 'dashboard', lb: 'leaderboard', bench: 'benchmark' };
|
|
2695
|
+
helpLines = subcommandHelp[aliases[cmd] || cmd];
|
|
2696
|
+
}
|
|
2697
|
+
if (!helpLines) {
|
|
2698
|
+
console.log(` ${c.red}No help available for "${cmd}"${c.reset}`);
|
|
2699
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit help${c.dim} for available commands${c.reset}`);
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
banner();
|
|
2703
|
+
for (const line of helpLines) console.log(` ${line}`);
|
|
2704
|
+
console.log();
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
// Handle: `agentaudit <cmd> --help` (wantsHelp + has a command)
|
|
2708
|
+
if (wantsHelp && args[0] && args[0] !== '--help' && args[0] !== '-h') {
|
|
2709
|
+
const cmd = args[0];
|
|
2710
|
+
if (subcommandHelp[cmd] !== undefined) {
|
|
2711
|
+
showSubcommandHelp(cmd);
|
|
2712
|
+
process.exitCode = 0; return;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
// Handle: `agentaudit help [cmd]`
|
|
2717
|
+
if (args[0] === 'help') {
|
|
2718
|
+
const helpCmd = args[1];
|
|
2719
|
+
if (helpCmd && subcommandHelp[helpCmd] !== undefined) {
|
|
2720
|
+
showSubcommandHelp(helpCmd);
|
|
2721
|
+
process.exitCode = 0; return;
|
|
2722
|
+
}
|
|
2723
|
+
// No subcommand or unknown → show main help
|
|
2724
|
+
wantsHelp || (args[0] = '--help'); // fall through to main help
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
if (args[0] === '--help' || args[0] === '-h' || args[0] === 'help' || (wantsHelp && args.length === 0)) {
|
|
1968
2728
|
banner();
|
|
1969
|
-
console.log(` ${c.bold}
|
|
2729
|
+
console.log(` ${c.bold}USAGE${c.reset}`);
|
|
2730
|
+
console.log(` agentaudit <command> [options]`);
|
|
1970
2731
|
console.log();
|
|
1971
|
-
console.log(`
|
|
1972
|
-
console.log(` ${c.cyan}
|
|
1973
|
-
console.log(` ${c.cyan}
|
|
1974
|
-
console.log(` ${c.cyan}
|
|
1975
|
-
console.log(` ${c.cyan}
|
|
1976
|
-
console.log(` ${c.cyan}agentaudit scan${c.reset} <url> ${c.dim}--deep${c.reset} Deep audit (same as audit)`);
|
|
1977
|
-
console.log(` ${c.cyan}agentaudit audit${c.reset} <url> [url...] Deep LLM-powered security audit`);
|
|
1978
|
-
console.log(` ${c.cyan}agentaudit lookup${c.reset} <name> Look up package in registry`);
|
|
1979
|
-
console.log(` ${c.cyan}agentaudit model${c.reset} Configure LLM provider + model interactively`);
|
|
1980
|
-
console.log(` ${c.cyan}agentaudit model${c.reset} <name> Set model directly (e.g. gpt-4o)`);
|
|
1981
|
-
console.log(` ${c.cyan}agentaudit setup${c.reset} Create account / enter API key (for report uploads)`);
|
|
2732
|
+
console.log(` ${c.bold}SCAN & AUDIT${c.reset}`);
|
|
2733
|
+
console.log(` ${c.cyan}discover${c.reset} Find MCP servers in your AI editors`);
|
|
2734
|
+
console.log(` ${c.cyan}scan${c.reset} <url> [url...] Quick static scan (regex, ~2s)`);
|
|
2735
|
+
console.log(` ${c.cyan}audit${c.reset} <url> [url...] Deep LLM-powered security audit (~30s)`);
|
|
2736
|
+
console.log(` ${c.cyan}lookup${c.reset} <name> Look up package in registry`);
|
|
1982
2737
|
console.log();
|
|
1983
|
-
console.log(` ${c.bold}
|
|
1984
|
-
console.log(` ${c.
|
|
1985
|
-
console.log(` ${c.
|
|
1986
|
-
console.log(` ${c.
|
|
1987
|
-
console.log(` ${c.dim}--model <name> Override LLM model for this run${c.reset}`);
|
|
1988
|
-
console.log(` ${c.dim}--no-upload Skip uploading report to registry${c.reset}`);
|
|
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`);
|
|
1989
2742
|
console.log();
|
|
1990
|
-
console.log(` ${c.bold}
|
|
1991
|
-
console.log(` ${c.
|
|
1992
|
-
console.log(` ${c.
|
|
2743
|
+
console.log(` ${c.bold}CONFIGURATION${c.reset}`);
|
|
2744
|
+
console.log(` ${c.cyan}model${c.reset} Configure LLM provider + model`);
|
|
2745
|
+
console.log(` ${c.cyan}setup${c.reset} Log in to agentaudit.dev (for report uploads)`);
|
|
2746
|
+
console.log(` ${c.cyan}status${c.reset} Show current config + auth status`);
|
|
1993
2747
|
console.log();
|
|
1994
|
-
console.log(` ${c.bold}
|
|
1995
|
-
console.log(` ${c.dim}
|
|
2748
|
+
console.log(` ${c.bold}FLAGS${c.reset}`);
|
|
2749
|
+
console.log(` ${c.dim}--json Machine-readable JSON output${c.reset}`);
|
|
2750
|
+
console.log(` ${c.dim}--quiet Suppress banner${c.reset}`);
|
|
2751
|
+
console.log(` ${c.dim}--no-color Disable ANSI colors (also: NO_COLOR env)${c.reset}`);
|
|
2752
|
+
console.log(` ${c.dim}--model <name> Override LLM model for this run${c.reset}`);
|
|
2753
|
+
console.log(` ${c.dim}--no-upload Skip uploading report to registry${c.reset}`);
|
|
2754
|
+
console.log(` ${c.dim}--export Export audit payload as markdown${c.reset}`);
|
|
2755
|
+
console.log(` ${c.dim}--debug Show raw LLM response on parse errors${c.reset}`);
|
|
1996
2756
|
console.log();
|
|
1997
|
-
console.log(` ${c.bold}
|
|
1998
|
-
console.log(` agentaudit`);
|
|
2757
|
+
console.log(` ${c.bold}EXAMPLES${c.reset}`);
|
|
1999
2758
|
console.log(` agentaudit discover --quick`);
|
|
2000
2759
|
console.log(` agentaudit scan https://github.com/owner/repo`);
|
|
2001
2760
|
console.log(` agentaudit audit https://github.com/owner/repo`);
|
|
2002
2761
|
console.log(` agentaudit lookup fastmcp --json`);
|
|
2003
2762
|
console.log();
|
|
2004
|
-
console.log(` ${c.bold}
|
|
2005
|
-
console.log(`
|
|
2006
|
-
console.log();
|
|
2007
|
-
console.log(` ${c.bold}Or use as MCP server${c.reset} in Cursor/Claude ${c.dim}(no extra API key needed):${c.reset}`);
|
|
2008
|
-
console.log(` ${c.dim}{ "agentaudit": { "command": "npx", "args": ["-y", "agentaudit"] } }${c.reset}`);
|
|
2763
|
+
console.log(` ${c.bold}LEARN MORE${c.reset}`);
|
|
2764
|
+
console.log(` ${c.dim}agentaudit help <command>${c.reset} Help for a specific command`);
|
|
2765
|
+
console.log(` ${c.dim}https://agentaudit.dev${c.reset} Documentation`);
|
|
2009
2766
|
console.log();
|
|
2010
2767
|
process.exitCode = 0; return;
|
|
2011
2768
|
}
|
|
@@ -2013,14 +2770,123 @@ async function main() {
|
|
|
2013
2770
|
// Default no-arg → discover
|
|
2014
2771
|
const command = args.length === 0 ? 'discover' : args[0];
|
|
2015
2772
|
const targets = args.slice(1);
|
|
2016
|
-
|
|
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
|
+
|
|
2017
2788
|
banner();
|
|
2018
|
-
|
|
2789
|
+
|
|
2019
2790
|
if (command === 'setup') {
|
|
2020
2791
|
await setupCommand();
|
|
2021
2792
|
return;
|
|
2022
2793
|
}
|
|
2023
2794
|
|
|
2795
|
+
if (command === 'status' || command === 'whoami') {
|
|
2796
|
+
// ── Status / diagnostic overview ──
|
|
2797
|
+
const config = loadLlmConfig();
|
|
2798
|
+
const creds = loadCredentials();
|
|
2799
|
+
const resolved = resolveProvider();
|
|
2800
|
+
|
|
2801
|
+
console.log(` ${c.bold}Status${c.reset}`);
|
|
2802
|
+
console.log();
|
|
2803
|
+
|
|
2804
|
+
// Provider + Model
|
|
2805
|
+
const prefProvider = config?.preferred_provider;
|
|
2806
|
+
const prefModel = config?.llm_model;
|
|
2807
|
+
if (prefProvider && resolved) {
|
|
2808
|
+
console.log(` Provider ${c.bold}${resolved.name}${c.reset} ${c.dim}(${resolved.key})${c.reset} ${c.green}✔${c.reset}`);
|
|
2809
|
+
} else if (resolved) {
|
|
2810
|
+
console.log(` Provider ${c.bold}${resolved.name}${c.reset} ${c.dim}(auto-detected via ${resolved.key})${c.reset} ${c.green}✔${c.reset}`);
|
|
2811
|
+
} else {
|
|
2812
|
+
console.log(` Provider ${c.yellow}none${c.reset} ${c.dim}— set an API key or run ${c.cyan}agentaudit model${c.dim}${c.reset}`);
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
if (prefModel) {
|
|
2816
|
+
console.log(` Model ${c.bold}${prefModel}${c.reset} ${c.dim}(custom)${c.reset}`);
|
|
2817
|
+
} else if (resolved) {
|
|
2818
|
+
console.log(` Model ${c.dim}${resolved.model} (provider default)${c.reset}`);
|
|
2819
|
+
} else {
|
|
2820
|
+
console.log(` Model ${c.dim}—${c.reset}`);
|
|
2821
|
+
}
|
|
2822
|
+
console.log();
|
|
2823
|
+
|
|
2824
|
+
// Account / auth
|
|
2825
|
+
if (creds) {
|
|
2826
|
+
console.log(` Account ${c.bold}${creds.agent_name}${c.reset} ${c.green}✔ logged in${c.reset}`);
|
|
2827
|
+
console.log(` ${c.dim} Key: ${creds.api_key.slice(0, 12)}...${c.reset}`);
|
|
2828
|
+
} else {
|
|
2829
|
+
console.log(` Account ${c.yellow}not configured${c.reset} ${c.dim}— run ${c.cyan}agentaudit setup${c.dim} to create one${c.reset}`);
|
|
2830
|
+
}
|
|
2831
|
+
console.log();
|
|
2832
|
+
|
|
2833
|
+
// All provider keys
|
|
2834
|
+
const seen = new Set();
|
|
2835
|
+
const keyLines = [];
|
|
2836
|
+
for (const p of LLM_PROVIDERS) {
|
|
2837
|
+
if (seen.has(p.provider)) continue;
|
|
2838
|
+
seen.add(p.provider);
|
|
2839
|
+
const keys = LLM_PROVIDERS.filter(x => x.provider === p.provider);
|
|
2840
|
+
const hasKey = keys.some(x => process.env[x.key]);
|
|
2841
|
+
keyLines.push({ name: p.name, key: p.key, hasKey });
|
|
2842
|
+
}
|
|
2843
|
+
console.log(` ${c.bold}API Keys${c.reset}`);
|
|
2844
|
+
for (const k of keyLines) {
|
|
2845
|
+
const icon = k.hasKey ? `${c.green}✔${c.reset}` : `${c.dim}✗${c.reset}`;
|
|
2846
|
+
const label = k.hasKey ? k.name : `${c.dim}${k.name}${c.reset}`;
|
|
2847
|
+
console.log(` ${icon} ${label} ${c.dim}${k.key}${c.reset}`);
|
|
2848
|
+
}
|
|
2849
|
+
console.log();
|
|
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
|
+
|
|
2877
|
+
// JSON mode
|
|
2878
|
+
if (jsonMode) {
|
|
2879
|
+
const status = {
|
|
2880
|
+
version: getVersion(),
|
|
2881
|
+
provider: resolved ? { name: resolved.name, provider: resolved.provider, key: resolved.key, model: prefModel || resolved.model } : null,
|
|
2882
|
+
account: creds ? { agent_name: creds.agent_name, has_key: true } : null,
|
|
2883
|
+
api_keys: keyLines.reduce((acc, k) => { acc[k.key] = k.hasKey; return acc; }, {}),
|
|
2884
|
+
};
|
|
2885
|
+
console.log(JSON.stringify(status, null, 2));
|
|
2886
|
+
}
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2024
2890
|
if (command === 'model') {
|
|
2025
2891
|
const newModel = targets.filter(t => !t.startsWith('--'))[0];
|
|
2026
2892
|
const config = loadLlmConfig();
|
|
@@ -2195,6 +3061,7 @@ async function main() {
|
|
|
2195
3061
|
const names = targets.filter(t => !t.startsWith('--'));
|
|
2196
3062
|
if (names.length === 0) {
|
|
2197
3063
|
console.log(` ${c.red}Error: package name required${c.reset}`);
|
|
3064
|
+
console.log(` ${c.dim}Usage: ${c.cyan}agentaudit lookup <name>${c.dim} — e.g. agentaudit lookup fastmcp${c.reset}`);
|
|
2198
3065
|
process.exitCode = 2;
|
|
2199
3066
|
return;
|
|
2200
3067
|
}
|
|
@@ -2207,7 +3074,6 @@ async function main() {
|
|
|
2207
3074
|
console.log(JSON.stringify(results.length === 1 ? (results[0] || { error: 'not_found' }) : results, null, 2));
|
|
2208
3075
|
}
|
|
2209
3076
|
process.exitCode = 0; return;
|
|
2210
|
-
return;
|
|
2211
3077
|
}
|
|
2212
3078
|
|
|
2213
3079
|
if (command === 'scan') {
|
|
@@ -2269,6 +3135,8 @@ async function main() {
|
|
|
2269
3135
|
const urls = targets.filter(t => !t.startsWith('--'));
|
|
2270
3136
|
if (urls.length === 0) {
|
|
2271
3137
|
console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
|
|
3138
|
+
console.log(` ${c.dim}Usage: ${c.cyan}agentaudit audit <url>${c.dim} — e.g. agentaudit audit https://github.com/owner/repo${c.reset}`);
|
|
3139
|
+
console.log(` ${c.dim}Tip: use ${c.cyan}agentaudit discover --deep${c.dim} to find & audit locally installed MCP servers${c.reset}`);
|
|
2272
3140
|
process.exitCode = 2;
|
|
2273
3141
|
return;
|
|
2274
3142
|
}
|
|
@@ -2283,7 +3151,7 @@ async function main() {
|
|
|
2283
3151
|
}
|
|
2284
3152
|
|
|
2285
3153
|
console.log(` ${c.red}Unknown command: ${command}${c.reset}`);
|
|
2286
|
-
console.log(` ${c.dim}Run agentaudit
|
|
3154
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit help${c.dim} for available commands${c.reset}`);
|
|
2287
3155
|
process.exitCode = 2;
|
|
2288
3156
|
}
|
|
2289
3157
|
|