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.
Files changed (2) hide show
  1. package/cli.mjs +916 -48
  2. 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
- * agentaudit Discover local MCP servers
7
- * agentaudit discover [--quick|--deep] Find MCP servers in AI editors
8
- * agentaudit scan <repo-url> [--deep] Quick scan (or deep audit with --deep)
9
- * agentaudit audit <repo-url> Deep LLM-powered security audit
10
- * agentaudit lookup <name> Look up package in registry
11
- * agentaudit setup Register + configure API key
12
- *
13
- * Global flags: --json, --quiet, --no-color, --no-upload
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}Setup${c.reset}`);
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 configured as ${c.bold}${existing.agent_name}${c.reset}`);
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
- if (args[0] === '--help' || args[0] === '-h') {
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}Commands:${c.reset}`);
2729
+ console.log(` ${c.bold}USAGE${c.reset}`);
2730
+ console.log(` agentaudit <command> [options]`);
1970
2731
  console.log();
1971
- console.log(` ${c.cyan}agentaudit${c.reset} Discover MCP servers (same as discover)`);
1972
- console.log(` ${c.cyan}agentaudit discover${c.reset} Find MCP servers in your AI editors (Cursor, Claude, VS Code, Windsurf)`);
1973
- console.log(` ${c.cyan}agentaudit discover --quick${c.reset} Discover + auto-scan all servers`);
1974
- console.log(` ${c.cyan}agentaudit discover --deep${c.reset} Discover + select servers to deep-audit`);
1975
- console.log(` ${c.cyan}agentaudit scan${c.reset} <url> [url...] Quick static scan (regex, local)`);
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}Global flags:${c.reset}`);
1984
- console.log(` ${c.dim}--json Output JSON to stdout (machine-readable)${c.reset}`);
1985
- console.log(` ${c.dim}--quiet Suppress banner and tree visualization${c.reset}`);
1986
- console.log(` ${c.dim}--no-color Disable ANSI colors (also: NO_COLOR env)${c.reset}`);
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}Quick Scan${c.reset} vs ${c.bold}Deep Audit${c.reset}:`);
1991
- console.log(` ${c.dim}scan = fast regex-based static analysis (~2s)${c.reset}`);
1992
- console.log(` ${c.dim}audit = deep LLM analysis with 3-pass methodology (~30s)${c.reset}`);
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}Exit codes:${c.reset}`);
1995
- console.log(` ${c.dim}0 = clean / success 1 = findings detected 2 = error${c.reset}`);
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}Examples:${c.reset}`);
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}For deep audits,${c.reset} set an LLM API key (e.g. ${c.cyan}export OPENROUTER_API_KEY=sk-or-...${c.reset})`);
2005
- console.log(` ${c.dim}Run "agentaudit model" to configure provider + model interactively${c.reset}`);
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 --help for usage${c.reset}`);
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.10.1",
3
+ "version": "3.10.3",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {