agentaudit 3.10.2 → 3.10.4
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/README.md +99 -54
- package/cli.mjs +1151 -14
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -9,8 +9,13 @@
|
|
|
9
9
|
* scan <url> [url...] Quick static scan (regex)
|
|
10
10
|
* audit <url> [url...] Deep LLM-powered security audit
|
|
11
11
|
* lookup <name> Look up package in registry
|
|
12
|
+
* dashboard Interactive full-screen dashboard
|
|
13
|
+
* leaderboard Top contributors ranking
|
|
14
|
+
* benchmark LLM model performance comparison
|
|
15
|
+
* activity Your recent audits & findings
|
|
16
|
+
* search <query> Search packages in registry
|
|
12
17
|
* model [name|reset] Configure LLM provider + model
|
|
13
|
-
* setup
|
|
18
|
+
* setup Log in to agentaudit.dev (for report uploads)
|
|
14
19
|
* status Show current config + auth status
|
|
15
20
|
* help [command] Show help
|
|
16
21
|
*
|
|
@@ -142,6 +147,7 @@ const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
|
142
147
|
const USER_CRED_DIR = path.join(xdgConfig, 'agentaudit');
|
|
143
148
|
const USER_CRED_FILE = path.join(USER_CRED_DIR, 'credentials.json');
|
|
144
149
|
const SKILL_CRED_FILE = path.join(SKILL_DIR, 'config', 'credentials.json');
|
|
150
|
+
const PROFILE_CACHE_FILE = path.join(USER_CRED_DIR, 'profile-cache.json');
|
|
145
151
|
|
|
146
152
|
function loadCredentials() {
|
|
147
153
|
for (const f of [SKILL_CRED_FILE, USER_CRED_FILE]) {
|
|
@@ -224,6 +230,29 @@ function saveCredentials(data) {
|
|
|
224
230
|
} catch {}
|
|
225
231
|
}
|
|
226
232
|
|
|
233
|
+
function loadProfileCache() {
|
|
234
|
+
try {
|
|
235
|
+
if (!fs.existsSync(PROFILE_CACHE_FILE)) return null;
|
|
236
|
+
const data = JSON.parse(fs.readFileSync(PROFILE_CACHE_FILE, 'utf8'));
|
|
237
|
+
// TTL: 10 minutes
|
|
238
|
+
if (data.fetched_at && Date.now() - data.fetched_at < 10 * 60 * 1000) return data;
|
|
239
|
+
return null; // expired
|
|
240
|
+
} catch { return null; }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function saveProfileCache(data) {
|
|
244
|
+
try {
|
|
245
|
+
fs.mkdirSync(USER_CRED_DIR, { recursive: true });
|
|
246
|
+
fs.writeFileSync(PROFILE_CACHE_FILE, JSON.stringify({
|
|
247
|
+
agent_name: data.agent_name,
|
|
248
|
+
rank: data.rank,
|
|
249
|
+
total_points: data.total_points,
|
|
250
|
+
total_reports: data.total_reports,
|
|
251
|
+
fetched_at: Date.now(),
|
|
252
|
+
}, null, 2), { mode: 0o600 });
|
|
253
|
+
} catch {}
|
|
254
|
+
}
|
|
255
|
+
|
|
227
256
|
function askQuestion(question) {
|
|
228
257
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
229
258
|
return new Promise(resolve => rl.question(question, answer => { rl.close(); resolve(answer.trim()); }));
|
|
@@ -417,12 +446,13 @@ async function registerAgent(agentName) {
|
|
|
417
446
|
}
|
|
418
447
|
|
|
419
448
|
async function setupCommand() {
|
|
420
|
-
console.log(` ${c.bold}
|
|
449
|
+
console.log(` ${c.bold}AgentAudit Login${c.reset}`);
|
|
450
|
+
console.log(` ${c.dim}Create an account to upload audit reports to agentaudit.dev${c.reset}`);
|
|
421
451
|
console.log();
|
|
422
452
|
|
|
423
453
|
const existing = loadCredentials();
|
|
424
454
|
if (existing) {
|
|
425
|
-
console.log(` ${icons.safe} Already
|
|
455
|
+
console.log(` ${icons.safe} Already logged in as ${c.bold}${existing.agent_name}${c.reset}`);
|
|
426
456
|
console.log(` ${c.dim}Key: ${existing.api_key.slice(0, 8)}...${c.reset}`);
|
|
427
457
|
console.log();
|
|
428
458
|
const answer = await askQuestion(` Reconfigure? ${c.dim}(y/N)${c.reset} `);
|
|
@@ -523,8 +553,17 @@ function getVersion() {
|
|
|
523
553
|
function banner() {
|
|
524
554
|
if (quietMode || jsonMode) return;
|
|
525
555
|
console.log();
|
|
526
|
-
|
|
527
|
-
|
|
556
|
+
const cache = loadProfileCache();
|
|
557
|
+
if (cache) {
|
|
558
|
+
const rankStr = cache.rank != null ? `#${cache.rank}` : '';
|
|
559
|
+
const ptsStr = `${fmtNum(cache.total_points)}pts`;
|
|
560
|
+
const auditsStr = `${fmtNum(cache.total_reports)} audits`;
|
|
561
|
+
const profile = [cache.agent_name, rankStr, ptsStr, auditsStr].filter(Boolean).join(' \u00b7 ');
|
|
562
|
+
console.log(` ${c.bold}${c.cyan}\u26e8 AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset} ${c.dim}\u2502${c.reset} ${profile}`);
|
|
563
|
+
} else {
|
|
564
|
+
console.log(` ${c.bold}${c.cyan}AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset}`);
|
|
565
|
+
console.log(` ${c.dim}Security scanner for AI packages${c.reset}`);
|
|
566
|
+
}
|
|
528
567
|
console.log();
|
|
529
568
|
}
|
|
530
569
|
|
|
@@ -567,6 +606,120 @@ function severityIcon(sev) {
|
|
|
567
606
|
}
|
|
568
607
|
}
|
|
569
608
|
|
|
609
|
+
// ── TUI Rendering Helpers ───────────────────────────────
|
|
610
|
+
|
|
611
|
+
const term = {
|
|
612
|
+
clearScreen: '\x1b[2J\x1b[H',
|
|
613
|
+
hideCursor: '\x1b[?25l',
|
|
614
|
+
showCursor: '\x1b[?25h',
|
|
615
|
+
altScreenOn: '\x1b[?1049h',
|
|
616
|
+
altScreenOff: '\x1b[?1049l',
|
|
617
|
+
moveTo: (r, col) => `\x1b[${r};${col}H`,
|
|
618
|
+
clearLine: '\x1b[2K',
|
|
619
|
+
underline: '\x1b[4m',
|
|
620
|
+
noUnderline: '\x1b[24m',
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
const BOX = { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│', lt: '├', rt: '┤', tt: '┬', bt: '┴', x: '┼' };
|
|
624
|
+
|
|
625
|
+
// Strip ANSI escape codes for length calculations
|
|
626
|
+
function stripAnsi(str) {
|
|
627
|
+
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function visLen(str) {
|
|
631
|
+
return stripAnsi(str).length;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function padRight(str, len) {
|
|
635
|
+
const diff = len - visLen(str);
|
|
636
|
+
return diff > 0 ? str + ' '.repeat(diff) : str;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function padLeft(str, len) {
|
|
640
|
+
const diff = len - visLen(str);
|
|
641
|
+
return diff > 0 ? ' '.repeat(diff) + str : str;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function drawBox(title, contentLines, width) {
|
|
645
|
+
const inner = width - 4; // 2 for "│ " + 2 for " │"
|
|
646
|
+
const lines = [];
|
|
647
|
+
const titleStr = title ? ` ${title} ` : '';
|
|
648
|
+
const titleLen = visLen(titleStr);
|
|
649
|
+
const topDash = BOX.h.repeat(Math.max(1, inner + 2 - titleLen));
|
|
650
|
+
lines.push(` ${BOX.tl}${c.dim}─${c.reset}${c.bold}${titleStr}${c.reset}${c.dim}${topDash}${c.reset}${BOX.tr}`);
|
|
651
|
+
for (const line of contentLines) {
|
|
652
|
+
lines.push(` ${BOX.v} ${padRight(line, inner + 1)}${BOX.v}`);
|
|
653
|
+
}
|
|
654
|
+
lines.push(` ${BOX.bl}${c.dim}${BOX.h.repeat(inner + 2)}${c.reset}${BOX.br}`);
|
|
655
|
+
return lines;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// ████████░░░░ proportional bar
|
|
659
|
+
function renderBar(value, maxValue, maxWidth) {
|
|
660
|
+
if (maxValue <= 0 || value <= 0) return c.dim + '░'.repeat(maxWidth) + c.reset;
|
|
661
|
+
const filled = Math.min(Math.round((value / maxValue) * maxWidth), maxWidth);
|
|
662
|
+
const empty = maxWidth - filled;
|
|
663
|
+
return c.cyan + '█'.repeat(filled) + c.dim + '░'.repeat(empty) + c.reset;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// [████████░░] 89%
|
|
667
|
+
function renderGauge(value, max, width) {
|
|
668
|
+
const pct = max > 0 ? Math.round((value / max) * 100) : 0;
|
|
669
|
+
const inner = width - 2; // subtract brackets
|
|
670
|
+
const filled = Math.min(Math.round((pct / 100) * inner), inner);
|
|
671
|
+
const empty = inner - filled;
|
|
672
|
+
const color = pct >= 80 ? c.green : pct >= 50 ? c.yellow : c.red;
|
|
673
|
+
return `[${color}${'█'.repeat(filled)}${c.dim}${'░'.repeat(empty)}${c.reset}] ${pct}%`;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// ●●●○○ colored severity dots
|
|
677
|
+
function severityDots(breakdown) {
|
|
678
|
+
const parts = [];
|
|
679
|
+
const critical = breakdown?.critical || 0;
|
|
680
|
+
const high = breakdown?.high || 0;
|
|
681
|
+
const medium = breakdown?.medium || 0;
|
|
682
|
+
const low = breakdown?.low || 0;
|
|
683
|
+
for (let i = 0; i < Math.min(critical, 3); i++) parts.push(`${c.red}●${c.reset}`);
|
|
684
|
+
for (let i = 0; i < Math.min(high, 3); i++) parts.push(`${c.red}●${c.reset}`);
|
|
685
|
+
for (let i = 0; i < Math.min(medium, 2); i++) parts.push(`${c.yellow}●${c.reset}`);
|
|
686
|
+
for (let i = 0; i < Math.min(low, 2); i++) parts.push(`${c.blue}●${c.reset}`);
|
|
687
|
+
// fill remaining with empty dots up to 5
|
|
688
|
+
while (parts.length < 5) parts.push(`${c.dim}○${c.reset}`);
|
|
689
|
+
return parts.slice(0, 5).join('');
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// ▁▂▃▅▇█▆▃ mini sparkline
|
|
693
|
+
function sparkline(values) {
|
|
694
|
+
const chars = '▁▂▃▄▅▆▇█';
|
|
695
|
+
if (!values || values.length === 0) return '';
|
|
696
|
+
const max = Math.max(...values, 1);
|
|
697
|
+
return values.map(v => {
|
|
698
|
+
const idx = Math.min(Math.round((v / max) * (chars.length - 1)), chars.length - 1);
|
|
699
|
+
return chars[idx];
|
|
700
|
+
}).join('');
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function fmtNum(n) {
|
|
704
|
+
if (n == null) return '0';
|
|
705
|
+
return n.toLocaleString('en-US');
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function fmtPct(n) {
|
|
709
|
+
if (n == null) return '0%';
|
|
710
|
+
return Math.round(n) + '%';
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function dashboardBanner() {
|
|
714
|
+
const ver = getVersion();
|
|
715
|
+
return [
|
|
716
|
+
` ${BOX.tl}${c.dim}${BOX.h.repeat(35)}${c.reset}${BOX.tr}`,
|
|
717
|
+
` ${BOX.v} ${c.bold}${c.cyan}⛨ AgentAudit${c.reset} ${c.dim}v${ver}${c.reset}${' '.repeat(Math.max(0, 19 - ver.length))}${BOX.v}`,
|
|
718
|
+
` ${BOX.v} ${c.dim}Security Registry for AI Agents${c.reset} ${BOX.v}`,
|
|
719
|
+
` ${BOX.bl}${c.dim}${BOX.h.repeat(35)}${c.reset}${BOX.br}`,
|
|
720
|
+
];
|
|
721
|
+
}
|
|
722
|
+
|
|
570
723
|
// ── File Collection (same logic as MCP server) ──────────
|
|
571
724
|
|
|
572
725
|
function formatApiError(error, provider, statusCode) {
|
|
@@ -1940,6 +2093,835 @@ async function checkPackage(name) {
|
|
|
1940
2093
|
return data;
|
|
1941
2094
|
}
|
|
1942
2095
|
|
|
2096
|
+
// ── Dashboard / Leaderboard / Benchmark Commands ────────
|
|
2097
|
+
|
|
2098
|
+
async function fetchDashboardData() {
|
|
2099
|
+
const creds = loadCredentials();
|
|
2100
|
+
const fetches = [
|
|
2101
|
+
fetch(`${REGISTRY_URL}/api/stats`, { signal: AbortSignal.timeout(15_000) }).then(r => r.ok ? r.json() : null).catch(() => null),
|
|
2102
|
+
fetch(`${REGISTRY_URL}/api/leaderboard?limit=50`, { signal: AbortSignal.timeout(15_000) }).then(r => r.ok ? r.json() : null).catch(() => null),
|
|
2103
|
+
fetch(`${REGISTRY_URL}/api/benchmark`, { signal: AbortSignal.timeout(15_000) }).then(r => r.ok ? r.json() : null).catch(() => null),
|
|
2104
|
+
];
|
|
2105
|
+
if (creds?.agent_name && creds.agent_name !== 'env') {
|
|
2106
|
+
fetches.push(
|
|
2107
|
+
fetch(`${REGISTRY_URL}/api/agents/${encodeURIComponent(creds.agent_name)}`, {
|
|
2108
|
+
headers: { 'Authorization': `Bearer ${creds.api_key}` },
|
|
2109
|
+
signal: AbortSignal.timeout(15_000),
|
|
2110
|
+
}).then(r => r.ok ? r.json() : null).catch(() => null)
|
|
2111
|
+
);
|
|
2112
|
+
} else {
|
|
2113
|
+
fetches.push(Promise.resolve(null));
|
|
2114
|
+
}
|
|
2115
|
+
const [stats, leaderboard, benchmark, agent] = await Promise.all(fetches);
|
|
2116
|
+
// Update profile cache if we have agent data
|
|
2117
|
+
if (agent && creds) {
|
|
2118
|
+
let rank = null;
|
|
2119
|
+
if (Array.isArray(leaderboard)) {
|
|
2120
|
+
const idx = leaderboard.findIndex(e => e.agent_name === creds.agent_name);
|
|
2121
|
+
if (idx >= 0) rank = idx + 1;
|
|
2122
|
+
}
|
|
2123
|
+
saveProfileCache({
|
|
2124
|
+
agent_name: creds.agent_name,
|
|
2125
|
+
rank,
|
|
2126
|
+
total_points: agent.total_points || 0,
|
|
2127
|
+
total_reports: agent.total_reports || 0,
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
return { stats, leaderboard, benchmark, agent, creds };
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
function renderOverviewTab(data, width) {
|
|
2134
|
+
const { stats, agent, leaderboard, creds } = data;
|
|
2135
|
+
const lines = [];
|
|
2136
|
+
const halfW = Math.min(Math.floor((width - 6) / 2), 40);
|
|
2137
|
+
|
|
2138
|
+
// Profile box
|
|
2139
|
+
const profileLines = [];
|
|
2140
|
+
if (agent && creds) {
|
|
2141
|
+
// Find rank
|
|
2142
|
+
let rank = '-';
|
|
2143
|
+
if (leaderboard && Array.isArray(leaderboard)) {
|
|
2144
|
+
const idx = leaderboard.findIndex(e => e.agent_name === creds.agent_name);
|
|
2145
|
+
if (idx >= 0) rank = `#${idx + 1} of ${leaderboard.length}`;
|
|
2146
|
+
}
|
|
2147
|
+
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}`);
|
|
2148
|
+
profileLines.push(`Points ${c.bold}${fmtNum(agent.total_points)}${c.reset}`);
|
|
2149
|
+
profileLines.push(`Audits ${c.bold}${fmtNum(agent.total_reports)}${c.reset}`);
|
|
2150
|
+
profileLines.push(`Findings ${c.bold}${fmtNum(agent.total_findings_submitted)}${c.reset} ${c.dim}(${fmtNum(agent.total_findings_confirmed)} confirmed)${c.reset}`);
|
|
2151
|
+
const sev = agent.severity_breakdown || {};
|
|
2152
|
+
profileLines.push('');
|
|
2153
|
+
const sevParts = [];
|
|
2154
|
+
if (sev.critical) sevParts.push(`${c.red}${sev.critical} crit${c.reset}`);
|
|
2155
|
+
if (sev.high) sevParts.push(`${c.red}${sev.high} high${c.reset}`);
|
|
2156
|
+
if (sev.medium) sevParts.push(`${c.yellow}${sev.medium} med${c.reset}`);
|
|
2157
|
+
if (sev.low) sevParts.push(`${c.blue}${sev.low} low${c.reset}`);
|
|
2158
|
+
profileLines.push(sevParts.join(' ') || `${c.dim}no findings yet${c.reset}`);
|
|
2159
|
+
} else {
|
|
2160
|
+
profileLines.push(`${c.dim}Not logged in${c.reset}`);
|
|
2161
|
+
profileLines.push(`${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to create account${c.reset}`);
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
// Registry box
|
|
2165
|
+
const regLines = [];
|
|
2166
|
+
if (stats) {
|
|
2167
|
+
regLines.push(`Packages Audited ${c.bold}${fmtNum(stats.skills_audited)}${c.reset}`);
|
|
2168
|
+
regLines.push(`Total Findings ${c.bold}${fmtNum(stats.total_findings)}${c.reset}`);
|
|
2169
|
+
regLines.push(`Total Reports ${c.bold}${fmtNum(stats.total_reports)}${c.reset}`);
|
|
2170
|
+
regLines.push(`Contributors ${c.bold}${fmtNum(stats.reporters)}${c.reset}`);
|
|
2171
|
+
regLines.push(`Avg Trust Score ${c.bold}${stats.avg_trust_score || 0}${c.reset}`);
|
|
2172
|
+
regLines.push('');
|
|
2173
|
+
const parts = [];
|
|
2174
|
+
if (stats.safe_packages) parts.push(`${c.green}●${fmtNum(stats.safe_packages)} safe${c.reset}`);
|
|
2175
|
+
if (stats.caution_packages) parts.push(`${c.yellow}●${fmtNum(stats.caution_packages)} caution${c.reset}`);
|
|
2176
|
+
if (stats.unsafe_packages) parts.push(`${c.red}●${fmtNum(stats.unsafe_packages)} unsafe${c.reset}`);
|
|
2177
|
+
regLines.push(parts.join(' ') || `${c.dim}no packages yet${c.reset}`);
|
|
2178
|
+
} else {
|
|
2179
|
+
regLines.push(`${c.dim}Could not load registry stats${c.reset}`);
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
const boxW = halfW + 4;
|
|
2183
|
+
const profileBox = drawBox('Your Profile', profileLines, boxW);
|
|
2184
|
+
const registryBox = drawBox('Registry', regLines, boxW);
|
|
2185
|
+
|
|
2186
|
+
// Side by side if wide enough, stacked otherwise
|
|
2187
|
+
if (width >= boxW * 2 + 4) {
|
|
2188
|
+
const maxLen = Math.max(profileBox.length, registryBox.length);
|
|
2189
|
+
while (profileBox.length < maxLen) profileBox.push(` ${BOX.v} ${' '.repeat(halfW + 1)}${BOX.v}`);
|
|
2190
|
+
while (registryBox.length < maxLen) registryBox.push(` ${BOX.v} ${' '.repeat(halfW + 1)}${BOX.v}`);
|
|
2191
|
+
for (let i = 0; i < maxLen; i++) {
|
|
2192
|
+
lines.push(profileBox[i] + ' ' + registryBox[i].trimStart());
|
|
2193
|
+
}
|
|
2194
|
+
} else {
|
|
2195
|
+
lines.push(...profileBox, '', ...registryBox);
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
return lines;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
function renderLeaderboardTab(data, width, opts = {}) {
|
|
2202
|
+
const { leaderboard, creds } = data;
|
|
2203
|
+
const lines = [];
|
|
2204
|
+
const maxNameW = 20;
|
|
2205
|
+
const barW = Math.min(Math.max(10, width - 70), 30);
|
|
2206
|
+
|
|
2207
|
+
if (!leaderboard || !Array.isArray(leaderboard) || leaderboard.length === 0) {
|
|
2208
|
+
lines.push(` ${c.dim}No leaderboard data available${c.reset}`);
|
|
2209
|
+
return lines;
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
const maxPts = leaderboard[0]?.total_points || 1;
|
|
2213
|
+
const medals = ['🥇', '🥈', '🥉'];
|
|
2214
|
+
|
|
2215
|
+
for (let i = 0; i < leaderboard.length; i++) {
|
|
2216
|
+
const entry = leaderboard[i];
|
|
2217
|
+
const name = (entry.agent_name || '').slice(0, maxNameW);
|
|
2218
|
+
const isMe = creds && entry.agent_name === creds.agent_name;
|
|
2219
|
+
const prefix = i < 3 ? ` ${medals[i]} ` : ` ${c.dim}#${String(i + 1).padStart(2)}${c.reset} `;
|
|
2220
|
+
const nameStr = isMe ? `${c.green}${c.bold}${name}${c.reset}` : name;
|
|
2221
|
+
const bar = renderBar(entry.total_points || 0, maxPts, barW);
|
|
2222
|
+
const pts = padLeft(`${fmtNum(entry.total_points || 0)} pts`, 12);
|
|
2223
|
+
const audits = padLeft(`${fmtNum(entry.total_reports || 0)} audits`, 12);
|
|
2224
|
+
const extra = entry.monthly_reports != null ? padLeft(`${fmtNum(entry.monthly_reports)} this mo`, 12) : '';
|
|
2225
|
+
lines.push(`${prefix}${padRight(nameStr, maxNameW)} ${bar} ${pts} ${audits}${extra}`);
|
|
2226
|
+
|
|
2227
|
+
// Separator after top 3
|
|
2228
|
+
if (i === 2 && leaderboard.length > 3) {
|
|
2229
|
+
lines.push(` ${c.dim}${'─'.repeat(Math.min(width - 4, 76))}${c.reset}`);
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
// Highlight current user if not in top list
|
|
2234
|
+
if (creds && !leaderboard.find(e => e.agent_name === creds.agent_name)) {
|
|
2235
|
+
lines.push('');
|
|
2236
|
+
lines.push(` ${c.dim}← you are not on this leaderboard yet${c.reset}`);
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
return lines;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
function renderBenchmarkTab(data, width) {
|
|
2243
|
+
const { benchmark } = data;
|
|
2244
|
+
const lines = [];
|
|
2245
|
+
|
|
2246
|
+
if (!benchmark || !benchmark.models || benchmark.models.length === 0) {
|
|
2247
|
+
lines.push(` ${c.dim}No benchmark data available${c.reset}`);
|
|
2248
|
+
return lines;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
const overview = benchmark.overview || {};
|
|
2252
|
+
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`);
|
|
2253
|
+
lines.push('');
|
|
2254
|
+
|
|
2255
|
+
// Header
|
|
2256
|
+
const nameW = 28;
|
|
2257
|
+
const hdr = ` ${padRight(`${c.bold}Model${c.reset}`, nameW + 9)} ${padRight('Audits', 7)} ${padRight('Risk', 5)} ${padRight('Detection', 16)} Severity`;
|
|
2258
|
+
lines.push(hdr);
|
|
2259
|
+
lines.push(` ${c.dim}${'─'.repeat(Math.min(width - 4, 86))}${c.reset}`);
|
|
2260
|
+
|
|
2261
|
+
for (const m of benchmark.models) {
|
|
2262
|
+
const name = (m.audit_model || 'unknown').slice(0, nameW - 2);
|
|
2263
|
+
const audits = padLeft(fmtNum(m.total_audits), 5);
|
|
2264
|
+
const riskVal = parseFloat(m.avg_risk_score) || 0;
|
|
2265
|
+
const riskColor = riskVal <= 20 ? c.green : riskVal <= 40 ? c.yellow : c.red;
|
|
2266
|
+
const risk = `${riskColor}${padLeft(String(Math.round(riskVal)), 3)}${c.reset}`;
|
|
2267
|
+
const detection = renderGauge(m.detection_rate || 0, 100, 10);
|
|
2268
|
+
// Severity as compact text instead of dots
|
|
2269
|
+
const sev = m.severity_breakdown || {};
|
|
2270
|
+
const sevParts = [];
|
|
2271
|
+
if (sev.critical) sevParts.push(`${c.red}${sev.critical}C${c.reset}`);
|
|
2272
|
+
if (sev.high) sevParts.push(`${c.red}${sev.high}H${c.reset}`);
|
|
2273
|
+
if (sev.medium) sevParts.push(`${c.yellow}${sev.medium}M${c.reset}`);
|
|
2274
|
+
if (sev.low) sevParts.push(`${c.blue}${sev.low}L${c.reset}`);
|
|
2275
|
+
const sevStr = sevParts.length > 0 ? sevParts.join(' ') : `${c.dim}—${c.reset}`;
|
|
2276
|
+
lines.push(` ${padRight(name, nameW)} ${audits} ${risk} ${detection} ${sevStr}`);
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
// Vulnerability landscape
|
|
2280
|
+
const cats = benchmark.global_categories;
|
|
2281
|
+
if (cats && cats.length > 0) {
|
|
2282
|
+
lines.push('');
|
|
2283
|
+
const catTotal = cats.reduce((sum, cat) => sum + (cat.count || 0), 0) || 1;
|
|
2284
|
+
const catW = Math.min(width - 8, 56);
|
|
2285
|
+
const catLines = [];
|
|
2286
|
+
for (const cat of cats.slice(0, 6)) {
|
|
2287
|
+
const pct = Math.round((cat.count / catTotal) * 100);
|
|
2288
|
+
const barFill = Math.round((cat.count / catTotal) * (catW - 30));
|
|
2289
|
+
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)}`);
|
|
2290
|
+
}
|
|
2291
|
+
lines.push(...drawBox('Vulnerability Landscape', catLines, catW + 4));
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
// Cross-model findings
|
|
2295
|
+
const cross = benchmark.cross_model_findings;
|
|
2296
|
+
if (cross && cross.length > 0) {
|
|
2297
|
+
lines.push('');
|
|
2298
|
+
lines.push(` ${c.bold}Cross-Model Findings${c.reset} ${c.dim}(confirmed by multiple models)${c.reset}`);
|
|
2299
|
+
for (const cf of cross.slice(0, 5)) {
|
|
2300
|
+
const models = (cf.models || []).slice(0, 3).join(', ');
|
|
2301
|
+
lines.push(` ${c.dim}•${c.reset} ${cf.title || cf.pattern_id} ${c.dim}[${cf.model_count} models: ${models}]${c.reset}`);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
return lines;
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
function timeAgo(dateStr) {
|
|
2309
|
+
if (!dateStr) return '';
|
|
2310
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
2311
|
+
const mins = Math.floor(diff / 60000);
|
|
2312
|
+
if (mins < 60) return `${mins}m ago`;
|
|
2313
|
+
const hours = Math.floor(mins / 60);
|
|
2314
|
+
if (hours < 24) return `${hours}h ago`;
|
|
2315
|
+
const days = Math.floor(hours / 24);
|
|
2316
|
+
if (days < 30) return `${days}d ago`;
|
|
2317
|
+
return `${Math.floor(days / 30)}mo ago`;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
function renderActivityTab(data, width) {
|
|
2321
|
+
const { agent, creds } = data;
|
|
2322
|
+
const lines = [];
|
|
2323
|
+
|
|
2324
|
+
if (!creds || !agent) {
|
|
2325
|
+
lines.push(` ${c.dim}Not logged in${c.reset}`);
|
|
2326
|
+
lines.push(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to see your activity${c.reset}`);
|
|
2327
|
+
return lines;
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
// Recent Audits
|
|
2331
|
+
const reports = agent.recent_reports || [];
|
|
2332
|
+
const auditLines = [];
|
|
2333
|
+
if (reports.length > 0) {
|
|
2334
|
+
for (const r of reports.slice(0, 10)) {
|
|
2335
|
+
const time = padRight(timeAgo(r.created_at), 8);
|
|
2336
|
+
const name = padRight((r.skill_slug || r.package_name || '').slice(0, 20), 20);
|
|
2337
|
+
const score = r.risk_score ?? r.latest_risk_score ?? 0;
|
|
2338
|
+
let status;
|
|
2339
|
+
if (score === 0) status = `${c.green}safe${c.reset} `;
|
|
2340
|
+
else if (score <= 30) status = `${c.yellow}caution${c.reset}`;
|
|
2341
|
+
else status = `${c.red}unsafe${c.reset} `;
|
|
2342
|
+
const findCount = r.finding_count != null ? `${r.finding_count} findings` : '';
|
|
2343
|
+
const model = r.audit_model ? `${c.dim}${(r.audit_model || '').slice(0, 14)}${c.reset}` : '';
|
|
2344
|
+
auditLines.push(`${c.dim}${time}${c.reset} ${name} ${status} ${padRight(findCount, 12)} ${model}`);
|
|
2345
|
+
}
|
|
2346
|
+
} else {
|
|
2347
|
+
auditLines.push(`${c.dim}No audits yet — run ${c.cyan}agentaudit audit <url>${c.dim} to get started${c.reset}`);
|
|
2348
|
+
}
|
|
2349
|
+
lines.push(...drawBox('Recent Audits', auditLines, Math.min(width - 4, 72)));
|
|
2350
|
+
lines.push('');
|
|
2351
|
+
|
|
2352
|
+
// Recent Findings
|
|
2353
|
+
const findings = agent.recent_findings || [];
|
|
2354
|
+
const findingLines = [];
|
|
2355
|
+
if (findings.length > 0) {
|
|
2356
|
+
for (const f of findings.slice(0, 10)) {
|
|
2357
|
+
const asfId = padRight(f.asf_id || f.id || '', 16);
|
|
2358
|
+
const sev = severityIcon(f.severity);
|
|
2359
|
+
const sevLabel = padRight(f.severity || '', 9);
|
|
2360
|
+
const title = (f.title || f.pattern_id || '').slice(0, 40);
|
|
2361
|
+
findingLines.push(`${asfId} ${sev} ${sevLabel} ${title}`);
|
|
2362
|
+
}
|
|
2363
|
+
} else {
|
|
2364
|
+
findingLines.push(`${c.dim}No findings yet${c.reset}`);
|
|
2365
|
+
}
|
|
2366
|
+
lines.push(...drawBox('Recent Findings', findingLines, Math.min(width - 4, 72)));
|
|
2367
|
+
|
|
2368
|
+
return lines;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
async function activityCommand(args) {
|
|
2372
|
+
const creds = loadCredentials();
|
|
2373
|
+
|
|
2374
|
+
if (!creds?.agent_name || creds.agent_name === 'env') {
|
|
2375
|
+
if (jsonMode) {
|
|
2376
|
+
console.log(JSON.stringify({ error: 'not_logged_in' }));
|
|
2377
|
+
} else {
|
|
2378
|
+
banner();
|
|
2379
|
+
console.log(` ${c.yellow}Not logged in${c.reset}`);
|
|
2380
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to create an account${c.reset}`);
|
|
2381
|
+
}
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
if (!quietMode && !jsonMode) {
|
|
2386
|
+
banner();
|
|
2387
|
+
process.stdout.write(` ${c.dim}Loading activity...${c.reset}`);
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
let agentData;
|
|
2391
|
+
try {
|
|
2392
|
+
const res = await fetch(`${REGISTRY_URL}/api/agents/${encodeURIComponent(creds.agent_name)}`, {
|
|
2393
|
+
headers: { 'Authorization': `Bearer ${creds.api_key}` },
|
|
2394
|
+
signal: AbortSignal.timeout(15_000),
|
|
2395
|
+
});
|
|
2396
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2397
|
+
agentData = await res.json();
|
|
2398
|
+
} catch (err) {
|
|
2399
|
+
if (!jsonMode) {
|
|
2400
|
+
process.stdout.write('\r\x1b[2K');
|
|
2401
|
+
console.log(` ${c.red}Failed to load activity: ${err.message}${c.reset}`);
|
|
2402
|
+
}
|
|
2403
|
+
process.exitCode = 1;
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
if (jsonMode) {
|
|
2408
|
+
console.log(JSON.stringify(agentData, null, 2));
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
process.stdout.write('\r\x1b[2K');
|
|
2413
|
+
|
|
2414
|
+
// Update profile cache
|
|
2415
|
+
try {
|
|
2416
|
+
const lbRes = await fetch(`${REGISTRY_URL}/api/leaderboard?limit=100`, { signal: AbortSignal.timeout(10_000) }).then(r => r.ok ? r.json() : null);
|
|
2417
|
+
let rank = null;
|
|
2418
|
+
if (Array.isArray(lbRes)) {
|
|
2419
|
+
const idx = lbRes.findIndex(e => e.agent_name === creds.agent_name);
|
|
2420
|
+
if (idx >= 0) rank = idx + 1;
|
|
2421
|
+
}
|
|
2422
|
+
saveProfileCache({
|
|
2423
|
+
agent_name: creds.agent_name,
|
|
2424
|
+
rank,
|
|
2425
|
+
total_points: agentData.total_points || 0,
|
|
2426
|
+
total_reports: agentData.total_reports || 0,
|
|
2427
|
+
});
|
|
2428
|
+
} catch {}
|
|
2429
|
+
|
|
2430
|
+
const width = process.stdout.columns || 80;
|
|
2431
|
+
const activityLines = renderActivityTab({ agent: agentData, creds }, width);
|
|
2432
|
+
console.log(` ${c.bold}Activity${c.reset} ${c.dim}${creds.agent_name}${c.reset}`);
|
|
2433
|
+
console.log();
|
|
2434
|
+
for (const line of activityLines) console.log(line);
|
|
2435
|
+
console.log();
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
function renderSearchResults(data, width) {
|
|
2439
|
+
const lines = [];
|
|
2440
|
+
const boxW = Math.min(width - 4, 72);
|
|
2441
|
+
|
|
2442
|
+
// Lookup API returns { reports: [], findings: [], total_matches }
|
|
2443
|
+
const lookupData = Array.isArray(data) ? data[0] : data;
|
|
2444
|
+
const reports = lookupData?.reports || [];
|
|
2445
|
+
const findings = lookupData?.findings || [];
|
|
2446
|
+
const total = lookupData?.total_matches || (reports.length + findings.length);
|
|
2447
|
+
|
|
2448
|
+
if (total === 0) return lines;
|
|
2449
|
+
|
|
2450
|
+
// Packages (from reports)
|
|
2451
|
+
if (reports.length > 0) {
|
|
2452
|
+
const pkgLines = [];
|
|
2453
|
+
for (const r of reports.slice(0, 10)) {
|
|
2454
|
+
const name = padRight((r.skill_slug || '').slice(0, 22), 22);
|
|
2455
|
+
const riskScore = r.risk_score ?? 0;
|
|
2456
|
+
let badge;
|
|
2457
|
+
if (riskScore === 0) badge = `${c.green}SAFE${c.reset} `;
|
|
2458
|
+
else if (riskScore <= 30) badge = `${c.yellow}CAUTION${c.reset}`;
|
|
2459
|
+
else badge = `${c.red}UNSAFE${c.reset} `;
|
|
2460
|
+
const time = padRight(timeAgo(r.created_at), 8);
|
|
2461
|
+
pkgLines.push(`${name} ${badge} ${c.dim}${time}${c.reset}`);
|
|
2462
|
+
}
|
|
2463
|
+
lines.push(...drawBox(`Packages (${reports.length})`, pkgLines, boxW));
|
|
2464
|
+
lines.push('');
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
// Findings
|
|
2468
|
+
if (findings.length > 0) {
|
|
2469
|
+
const findLines = [];
|
|
2470
|
+
for (const f of findings.slice(0, 10)) {
|
|
2471
|
+
const asfId = padRight(f.asf_id || '', 16);
|
|
2472
|
+
const sev = severityIcon(f.severity);
|
|
2473
|
+
const sevLabel = padRight(f.severity || '', 9);
|
|
2474
|
+
const title = (f.title || '').slice(0, 36);
|
|
2475
|
+
findLines.push(`${asfId} ${sev} ${sevLabel} ${title}`);
|
|
2476
|
+
}
|
|
2477
|
+
lines.push(...drawBox(`Findings (${findings.length})`, findLines, boxW));
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
return lines;
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
function renderSearchTab(searchState, width) {
|
|
2484
|
+
const { query, results, loading, error } = searchState;
|
|
2485
|
+
const lines = [];
|
|
2486
|
+
|
|
2487
|
+
// Search input
|
|
2488
|
+
lines.push(` ${c.bold}Search:${c.reset} ${query || ''}${c.dim}\u2588${c.reset}`);
|
|
2489
|
+
lines.push('');
|
|
2490
|
+
|
|
2491
|
+
if (loading) {
|
|
2492
|
+
lines.push(` ${c.dim}Searching...${c.reset}`);
|
|
2493
|
+
return lines;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
if (error) {
|
|
2497
|
+
lines.push(` ${c.red}${error}${c.reset}`);
|
|
2498
|
+
return lines;
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
if (results) {
|
|
2502
|
+
const resultLines = renderSearchResults(results, width);
|
|
2503
|
+
if (resultLines.length > 0) {
|
|
2504
|
+
lines.push(...resultLines);
|
|
2505
|
+
} else {
|
|
2506
|
+
lines.push(` ${c.dim}No results found for "${query}"${c.reset}`);
|
|
2507
|
+
}
|
|
2508
|
+
} else if (!query) {
|
|
2509
|
+
lines.push(` ${c.dim}Type to search packages in the registry${c.reset}`);
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
lines.push('');
|
|
2513
|
+
lines.push(` ${c.dim}Type to search \u2502 Enter=search \u2502 Esc=clear \u2502 Tab=switch tab${c.reset}`);
|
|
2514
|
+
|
|
2515
|
+
return lines;
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
async function searchCommand(args) {
|
|
2519
|
+
const query = args.filter(a => !a.startsWith('--')).join(' ').trim();
|
|
2520
|
+
|
|
2521
|
+
if (!query) {
|
|
2522
|
+
if (jsonMode) {
|
|
2523
|
+
console.log(JSON.stringify({ error: 'query_required' }));
|
|
2524
|
+
} else {
|
|
2525
|
+
banner();
|
|
2526
|
+
console.log(` ${c.red}Error: search query required${c.reset}`);
|
|
2527
|
+
console.log(` ${c.dim}Usage: ${c.cyan}agentaudit search <query>${c.dim} \u2014 e.g. agentaudit search fastmcp${c.reset}`);
|
|
2528
|
+
}
|
|
2529
|
+
process.exitCode = 2;
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
if (!quietMode && !jsonMode) {
|
|
2534
|
+
banner();
|
|
2535
|
+
process.stdout.write(` ${c.dim}Searching "${query}"...${c.reset}`);
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
let data;
|
|
2539
|
+
try {
|
|
2540
|
+
const res = await fetch(`${REGISTRY_URL}/api/lookup?hash=${encodeURIComponent(query)}`, {
|
|
2541
|
+
signal: AbortSignal.timeout(15_000),
|
|
2542
|
+
});
|
|
2543
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2544
|
+
data = await res.json();
|
|
2545
|
+
} catch (err) {
|
|
2546
|
+
if (!jsonMode) {
|
|
2547
|
+
process.stdout.write('\r\x1b[2K');
|
|
2548
|
+
console.log(` ${c.red}Search failed: ${err.message}${c.reset}`);
|
|
2549
|
+
}
|
|
2550
|
+
process.exitCode = 1;
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
if (jsonMode) {
|
|
2555
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
process.stdout.write('\r\x1b[2K');
|
|
2560
|
+
console.log(` ${c.bold}Search${c.reset} ${c.dim}"${query}"${c.reset}`);
|
|
2561
|
+
console.log();
|
|
2562
|
+
|
|
2563
|
+
const width = process.stdout.columns || 80;
|
|
2564
|
+
const resultLines = renderSearchResults(data, width);
|
|
2565
|
+
if (resultLines.length === 0) {
|
|
2566
|
+
console.log(` ${c.dim}No results found${c.reset}`);
|
|
2567
|
+
console.log(` ${c.dim}Tip: try ${c.cyan}agentaudit scan <repo-url>${c.dim} to audit a new package${c.reset}`);
|
|
2568
|
+
console.log();
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
for (const line of resultLines) console.log(line);
|
|
2573
|
+
console.log();
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
async function leaderboardCommand(args) {
|
|
2577
|
+
const tabArg = args.find((a, i) => args[i - 1] === '--tab') || 'overall';
|
|
2578
|
+
const showAll = args.includes('--all');
|
|
2579
|
+
const limit = showAll ? 200 : 20;
|
|
2580
|
+
|
|
2581
|
+
if (!quietMode && !jsonMode) {
|
|
2582
|
+
banner();
|
|
2583
|
+
process.stdout.write(` ${c.dim}Loading leaderboard...${c.reset}`);
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
let data;
|
|
2587
|
+
try {
|
|
2588
|
+
const url = `${REGISTRY_URL}/api/leaderboard?tab=${encodeURIComponent(tabArg)}&limit=${limit}`;
|
|
2589
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(15_000) });
|
|
2590
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2591
|
+
data = await res.json();
|
|
2592
|
+
} catch (err) {
|
|
2593
|
+
if (!jsonMode) {
|
|
2594
|
+
process.stdout.write('\r\x1b[2K');
|
|
2595
|
+
console.log(` ${c.red}Failed to load leaderboard: ${err.message}${c.reset}`);
|
|
2596
|
+
}
|
|
2597
|
+
process.exitCode = 1;
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
if (jsonMode) {
|
|
2602
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
process.stdout.write('\r\x1b[2K');
|
|
2607
|
+
const creds = loadCredentials();
|
|
2608
|
+
console.log(` ${c.bold}Leaderboard${c.reset} ${c.dim}${tabArg}${c.reset}`);
|
|
2609
|
+
console.log();
|
|
2610
|
+
|
|
2611
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
2612
|
+
console.log(` ${c.dim}No data available${c.reset}`);
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
const maxPts = data[0]?.total_points || 1;
|
|
2617
|
+
const medals = ['🥇', '🥈', '🥉'];
|
|
2618
|
+
const barW = 24;
|
|
2619
|
+
|
|
2620
|
+
for (let i = 0; i < data.length; i++) {
|
|
2621
|
+
const entry = data[i];
|
|
2622
|
+
const name = (entry.agent_name || '').slice(0, 20);
|
|
2623
|
+
const isMe = creds && entry.agent_name === creds.agent_name;
|
|
2624
|
+
const prefix = i < 3 ? ` ${medals[i]} ` : ` #${String(i + 1).padStart(2)} `;
|
|
2625
|
+
const nameStr = isMe ? `${c.green}${c.bold}${name}${c.reset}` : name;
|
|
2626
|
+
const bar = renderBar(entry.total_points || 0, maxPts, barW);
|
|
2627
|
+
const pts = `${fmtNum(entry.total_points || 0)} pts`;
|
|
2628
|
+
const audits = `${fmtNum(entry.total_reports || 0)} audits`;
|
|
2629
|
+
console.log(`${prefix}${padRight(nameStr, 22)} ${bar} ${padLeft(pts, 12)} ${padLeft(audits, 10)}`);
|
|
2630
|
+
if (i === 2 && data.length > 3) {
|
|
2631
|
+
console.log(` ${c.dim}${'─'.repeat(74)}${c.reset}`);
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
console.log();
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
async function benchmarkCommand(args) {
|
|
2638
|
+
if (!quietMode && !jsonMode) {
|
|
2639
|
+
banner();
|
|
2640
|
+
process.stdout.write(` ${c.dim}Loading benchmark data...${c.reset}`);
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
let data;
|
|
2644
|
+
try {
|
|
2645
|
+
const res = await fetch(`${REGISTRY_URL}/api/benchmark`, { signal: AbortSignal.timeout(15_000) });
|
|
2646
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2647
|
+
data = await res.json();
|
|
2648
|
+
} catch (err) {
|
|
2649
|
+
if (!jsonMode) {
|
|
2650
|
+
process.stdout.write('\r\x1b[2K');
|
|
2651
|
+
console.log(` ${c.red}Failed to load benchmark: ${err.message}${c.reset}`);
|
|
2652
|
+
}
|
|
2653
|
+
process.exitCode = 1;
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
if (jsonMode) {
|
|
2658
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
process.stdout.write('\r\x1b[2K');
|
|
2663
|
+
const width = process.stdout.columns || 80;
|
|
2664
|
+
const benchLines = renderBenchmarkTab({ benchmark: data }, width);
|
|
2665
|
+
console.log(` ${c.bold}Model Benchmark${c.reset} ${c.dim}— LLM Audit Performance${c.reset}`);
|
|
2666
|
+
console.log();
|
|
2667
|
+
for (const line of benchLines) console.log(line);
|
|
2668
|
+
console.log();
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
async function dashboardCommand() {
|
|
2672
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
2673
|
+
console.log(`${c.red}Error: dashboard requires an interactive terminal${c.reset}`);
|
|
2674
|
+
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}`);
|
|
2675
|
+
process.exitCode = 1;
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
const tabs = [
|
|
2680
|
+
{ key: '1', label: 'Overview' },
|
|
2681
|
+
{ key: '2', label: 'Leaderboard' },
|
|
2682
|
+
{ key: '3', label: 'Benchmark' },
|
|
2683
|
+
{ key: '4', label: 'Activity' },
|
|
2684
|
+
{ key: '5', label: 'Search' },
|
|
2685
|
+
];
|
|
2686
|
+
let activeTab = 0;
|
|
2687
|
+
let scrollOffset = 0;
|
|
2688
|
+
let running = true;
|
|
2689
|
+
|
|
2690
|
+
// Search tab state
|
|
2691
|
+
let searchQuery = '';
|
|
2692
|
+
let searchResults = null;
|
|
2693
|
+
let searchLoading = false;
|
|
2694
|
+
let searchError = null;
|
|
2695
|
+
|
|
2696
|
+
// Enter alt screen
|
|
2697
|
+
process.stdout.write(term.altScreenOn + term.hideCursor);
|
|
2698
|
+
|
|
2699
|
+
// Loading screen
|
|
2700
|
+
process.stdout.write(term.clearScreen);
|
|
2701
|
+
const bannerLines = dashboardBanner();
|
|
2702
|
+
for (const line of bannerLines) process.stdout.write(line + '\n');
|
|
2703
|
+
process.stdout.write(`\n ${c.dim}Loading data...${c.reset}\n`);
|
|
2704
|
+
|
|
2705
|
+
// Fetch data
|
|
2706
|
+
const data = await fetchDashboardData();
|
|
2707
|
+
|
|
2708
|
+
async function doSearch() {
|
|
2709
|
+
if (!searchQuery.trim()) return;
|
|
2710
|
+
searchLoading = true;
|
|
2711
|
+
searchError = null;
|
|
2712
|
+
render();
|
|
2713
|
+
try {
|
|
2714
|
+
const res = await fetch(`${REGISTRY_URL}/api/lookup?hash=${encodeURIComponent(searchQuery.trim())}`, {
|
|
2715
|
+
signal: AbortSignal.timeout(15_000),
|
|
2716
|
+
});
|
|
2717
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2718
|
+
searchResults = await res.json();
|
|
2719
|
+
} catch (err) {
|
|
2720
|
+
searchError = `Search failed: ${err.message}`;
|
|
2721
|
+
searchResults = null;
|
|
2722
|
+
}
|
|
2723
|
+
searchLoading = false;
|
|
2724
|
+
if (running) render();
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
function render() {
|
|
2728
|
+
const cols = process.stdout.columns || 80;
|
|
2729
|
+
const rows = process.stdout.rows || 24;
|
|
2730
|
+
let output = term.clearScreen;
|
|
2731
|
+
|
|
2732
|
+
// Banner
|
|
2733
|
+
const bLines = dashboardBanner();
|
|
2734
|
+
for (const line of bLines) output += line + '\n';
|
|
2735
|
+
output += '\n';
|
|
2736
|
+
|
|
2737
|
+
// Tab bar
|
|
2738
|
+
let tabBar = ' ';
|
|
2739
|
+
for (let i = 0; i < tabs.length; i++) {
|
|
2740
|
+
const tab = tabs[i];
|
|
2741
|
+
if (i === activeTab) {
|
|
2742
|
+
tabBar += `${c.bold}${c.cyan}${term.underline} [${tab.key}] ${tab.label} ${term.noUnderline}${c.reset}`;
|
|
2743
|
+
} else {
|
|
2744
|
+
tabBar += `${c.dim} [${tab.key}] ${tab.label} ${c.reset}`;
|
|
2745
|
+
}
|
|
2746
|
+
if (i < tabs.length - 1) tabBar += `${c.dim}\u2500${c.reset}`;
|
|
2747
|
+
}
|
|
2748
|
+
output += tabBar + '\n\n';
|
|
2749
|
+
|
|
2750
|
+
// Tab content
|
|
2751
|
+
let contentLines = [];
|
|
2752
|
+
switch (activeTab) {
|
|
2753
|
+
case 0:
|
|
2754
|
+
contentLines = renderOverviewTab(data, cols);
|
|
2755
|
+
break;
|
|
2756
|
+
case 1:
|
|
2757
|
+
contentLines = renderLeaderboardTab(data, cols);
|
|
2758
|
+
break;
|
|
2759
|
+
case 2:
|
|
2760
|
+
contentLines = renderBenchmarkTab(data, cols);
|
|
2761
|
+
break;
|
|
2762
|
+
case 3:
|
|
2763
|
+
contentLines = renderActivityTab(data, cols);
|
|
2764
|
+
break;
|
|
2765
|
+
case 4:
|
|
2766
|
+
contentLines = renderSearchTab({ query: searchQuery, results: searchResults, loading: searchLoading, error: searchError }, cols);
|
|
2767
|
+
break;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
// Apply scroll
|
|
2771
|
+
const availableRows = rows - 10; // header + tab bar + footer
|
|
2772
|
+
const maxScroll = Math.max(0, contentLines.length - availableRows);
|
|
2773
|
+
scrollOffset = Math.min(scrollOffset, maxScroll);
|
|
2774
|
+
scrollOffset = Math.max(0, scrollOffset);
|
|
2775
|
+
|
|
2776
|
+
const visible = contentLines.slice(scrollOffset, scrollOffset + availableRows);
|
|
2777
|
+
for (const line of visible) output += line + '\n';
|
|
2778
|
+
|
|
2779
|
+
// Footer
|
|
2780
|
+
output += '\n';
|
|
2781
|
+
const scrollInfo = contentLines.length > availableRows ? ` ${c.dim}[${scrollOffset + 1}-${Math.min(scrollOffset + availableRows, contentLines.length)}/${contentLines.length}]${c.reset}` : '';
|
|
2782
|
+
const isSearchTab = activeTab === 4;
|
|
2783
|
+
const footerHint = isSearchTab
|
|
2784
|
+
? `${c.dim}\u2190\u2192 tab Enter=search Esc=clear q quit${c.reset}`
|
|
2785
|
+
: `${c.dim}\u2190\u2192 tab \u2191\u2193 scroll 1-5 jump q quit${c.reset}`;
|
|
2786
|
+
output += ` ${footerHint}${scrollInfo}\n`;
|
|
2787
|
+
|
|
2788
|
+
process.stdout.write(output);
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
function cleanup() {
|
|
2792
|
+
if (!running) return;
|
|
2793
|
+
running = false;
|
|
2794
|
+
process.stdout.write(term.showCursor + term.altScreenOff);
|
|
2795
|
+
try { process.stdin.setRawMode(false); } catch {}
|
|
2796
|
+
process.stdin.pause();
|
|
2797
|
+
process.stdin.removeListener('data', onKeypress);
|
|
2798
|
+
process.stdout.removeListener('resize', render);
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
function onKeypress(key) {
|
|
2802
|
+
if (!running) return;
|
|
2803
|
+
const isSearchTab = activeTab === 4;
|
|
2804
|
+
|
|
2805
|
+
// Ctrl+C always quits
|
|
2806
|
+
if (key === '\x03') {
|
|
2807
|
+
cleanup();
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
// Search tab: route printable keys to search input
|
|
2812
|
+
if (isSearchTab) {
|
|
2813
|
+
// Escape: clear search
|
|
2814
|
+
if (key === '\x1b' && key.length === 1) {
|
|
2815
|
+
searchQuery = '';
|
|
2816
|
+
searchResults = null;
|
|
2817
|
+
searchError = null;
|
|
2818
|
+
scrollOffset = 0;
|
|
2819
|
+
render();
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
// Enter: trigger search
|
|
2824
|
+
if (key === '\r' || key === '\n') {
|
|
2825
|
+
doSearch();
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
// Backspace / Delete
|
|
2830
|
+
if (key === '\x7f' || key === '\b') {
|
|
2831
|
+
if (searchQuery.length > 0) {
|
|
2832
|
+
searchQuery = searchQuery.slice(0, -1);
|
|
2833
|
+
render();
|
|
2834
|
+
}
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
// Tab: switch to next tab
|
|
2839
|
+
if (key === '\t') {
|
|
2840
|
+
activeTab = (activeTab + 1) % tabs.length;
|
|
2841
|
+
scrollOffset = 0;
|
|
2842
|
+
render();
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// Arrow keys still navigate
|
|
2847
|
+
if (key === '\x1b[C') { activeTab = (activeTab + 1) % tabs.length; scrollOffset = 0; render(); return; }
|
|
2848
|
+
if (key === '\x1b[D') { activeTab = (activeTab - 1 + tabs.length) % tabs.length; scrollOffset = 0; render(); return; }
|
|
2849
|
+
if (key === '\x1b[A') { scrollOffset = Math.max(0, scrollOffset - 1); render(); return; }
|
|
2850
|
+
if (key === '\x1b[B') { scrollOffset++; render(); return; }
|
|
2851
|
+
|
|
2852
|
+
// q quits only if search buffer is empty
|
|
2853
|
+
if (key === 'q' && searchQuery.length === 0) {
|
|
2854
|
+
cleanup();
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
// Printable ASCII → append to search query
|
|
2859
|
+
if (key.length === 1 && key.charCodeAt(0) >= 32 && key.charCodeAt(0) <= 126) {
|
|
2860
|
+
searchQuery += key;
|
|
2861
|
+
render();
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
// Non-search tabs: normal keypress handling
|
|
2868
|
+
// q = quit
|
|
2869
|
+
if (key === 'q') {
|
|
2870
|
+
cleanup();
|
|
2871
|
+
return;
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
// Tab navigation
|
|
2875
|
+
if (key === '\x1b[C' || key === '\t') {
|
|
2876
|
+
activeTab = (activeTab + 1) % tabs.length;
|
|
2877
|
+
scrollOffset = 0;
|
|
2878
|
+
render();
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
if (key === '\x1b[D') {
|
|
2882
|
+
activeTab = (activeTab - 1 + tabs.length) % tabs.length;
|
|
2883
|
+
scrollOffset = 0;
|
|
2884
|
+
render();
|
|
2885
|
+
return;
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
// Number keys 1-5
|
|
2889
|
+
if (key >= '1' && key <= '5') {
|
|
2890
|
+
const idx = parseInt(key, 10) - 1;
|
|
2891
|
+
if (idx < tabs.length) { activeTab = idx; scrollOffset = 0; render(); }
|
|
2892
|
+
return;
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
// Scroll
|
|
2896
|
+
if (key === '\x1b[A' || key === 'k') { scrollOffset = Math.max(0, scrollOffset - 1); render(); return; }
|
|
2897
|
+
if (key === '\x1b[B' || key === 'j') { scrollOffset++; render(); return; }
|
|
2898
|
+
if (key === '\x1b[5~') { scrollOffset = Math.max(0, scrollOffset - 10); render(); return; }
|
|
2899
|
+
if (key === '\x1b[6~') { scrollOffset += 10; render(); return; }
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
// Setup input
|
|
2903
|
+
process.stdin.setRawMode(true);
|
|
2904
|
+
process.stdin.resume();
|
|
2905
|
+
process.stdin.setEncoding('utf8');
|
|
2906
|
+
process.stdin.on('data', onKeypress);
|
|
2907
|
+
process.stdout.on('resize', render);
|
|
2908
|
+
|
|
2909
|
+
// Graceful cleanup
|
|
2910
|
+
process.on('exit', cleanup);
|
|
2911
|
+
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
2912
|
+
process.on('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
2913
|
+
|
|
2914
|
+
// Initial render
|
|
2915
|
+
render();
|
|
2916
|
+
|
|
2917
|
+
// Keep alive until user quits
|
|
2918
|
+
await new Promise(resolve => {
|
|
2919
|
+
const check = setInterval(() => {
|
|
2920
|
+
if (!running) { clearInterval(check); resolve(); }
|
|
2921
|
+
}, 100);
|
|
2922
|
+
});
|
|
2923
|
+
}
|
|
2924
|
+
|
|
1943
2925
|
// ── Main ────────────────────────────────────────────────
|
|
1944
2926
|
|
|
1945
2927
|
async function main() {
|
|
@@ -2054,9 +3036,10 @@ async function main() {
|
|
|
2054
3036
|
setup: [
|
|
2055
3037
|
`${c.bold}agentaudit setup${c.reset}`,
|
|
2056
3038
|
``,
|
|
2057
|
-
`
|
|
2058
|
-
`This
|
|
2059
|
-
|
|
3039
|
+
`Log in to agentaudit.dev — create an account or enter an existing API key.`,
|
|
3040
|
+
`This enables uploading audit reports to the public registry.`,
|
|
3041
|
+
``,
|
|
3042
|
+
`${c.bold}Note:${c.reset} This is NOT for LLM/provider configuration. Use ${c.cyan}agentaudit model${c.reset} for that.`,
|
|
2060
3043
|
``,
|
|
2061
3044
|
`${c.bold}Examples:${c.reset}`,
|
|
2062
3045
|
` agentaudit setup`,
|
|
@@ -2073,15 +3056,107 @@ async function main() {
|
|
|
2073
3056
|
` agentaudit status`,
|
|
2074
3057
|
` agentaudit status --json`,
|
|
2075
3058
|
],
|
|
3059
|
+
dashboard: [
|
|
3060
|
+
`${c.bold}agentaudit dashboard${c.reset}`,
|
|
3061
|
+
``,
|
|
3062
|
+
`Interactive full-screen dashboard with stats, leaderboard, and benchmark.`,
|
|
3063
|
+
`Uses alternate screen buffer — your scrollback stays intact.`,
|
|
3064
|
+
``,
|
|
3065
|
+
`${c.bold}Navigation:${c.reset}`,
|
|
3066
|
+
` \u2190\u2192 / Tab Switch between tabs`,
|
|
3067
|
+
` 1-5 Jump to tab by number`,
|
|
3068
|
+
` \u2191\u2193 / j/k Scroll content`,
|
|
3069
|
+
` PgUp/PgDn Scroll fast`,
|
|
3070
|
+
` q / Ctrl+C Quit`,
|
|
3071
|
+
``,
|
|
3072
|
+
`${c.bold}Tabs:${c.reset}`,
|
|
3073
|
+
` [1] Overview Your profile + registry stats`,
|
|
3074
|
+
` [2] Leaderboard Top contributors ranking`,
|
|
3075
|
+
` [3] Benchmark LLM model performance comparison`,
|
|
3076
|
+
` [4] Activity Your recent audits & findings`,
|
|
3077
|
+
` [5] Search Search packages (interactive input)`,
|
|
3078
|
+
``,
|
|
3079
|
+
`${c.bold}Aliases:${c.reset} dashboard, dash`,
|
|
3080
|
+
],
|
|
3081
|
+
dash: null, // alias → dashboard
|
|
3082
|
+
leaderboard: [
|
|
3083
|
+
`${c.bold}agentaudit leaderboard${c.reset} [options]`,
|
|
3084
|
+
``,
|
|
3085
|
+
`Show top contributors ranking. Pipe-friendly output (no alt-screen).`,
|
|
3086
|
+
``,
|
|
3087
|
+
`${c.bold}Options:${c.reset}`,
|
|
3088
|
+
` --tab <name> Category: overall (default), monthly, reviewers, verifiers, streaks`,
|
|
3089
|
+
` --all Show all entries (default: top 20)`,
|
|
3090
|
+
` --json Machine-readable JSON output`,
|
|
3091
|
+
``,
|
|
3092
|
+
`${c.bold}Aliases:${c.reset} leaderboard, lb`,
|
|
3093
|
+
``,
|
|
3094
|
+
`${c.bold}Examples:${c.reset}`,
|
|
3095
|
+
` agentaudit leaderboard`,
|
|
3096
|
+
` agentaudit leaderboard --tab monthly`,
|
|
3097
|
+
` agentaudit leaderboard --all --json`,
|
|
3098
|
+
],
|
|
3099
|
+
lb: null, // alias → leaderboard
|
|
3100
|
+
benchmark: [
|
|
3101
|
+
`${c.bold}agentaudit benchmark${c.reset} [options]`,
|
|
3102
|
+
``,
|
|
3103
|
+
`Compare LLM model performance across security audits.`,
|
|
3104
|
+
`Shows detection rates, severity breakdown, and vulnerability landscape.`,
|
|
3105
|
+
``,
|
|
3106
|
+
`${c.bold}Options:${c.reset}`,
|
|
3107
|
+
` --json Machine-readable JSON output`,
|
|
3108
|
+
``,
|
|
3109
|
+
`${c.bold}Aliases:${c.reset} benchmark, bench`,
|
|
3110
|
+
``,
|
|
3111
|
+
`${c.bold}Examples:${c.reset}`,
|
|
3112
|
+
` agentaudit benchmark`,
|
|
3113
|
+
` agentaudit benchmark --json`,
|
|
3114
|
+
],
|
|
3115
|
+
bench: null, // alias → benchmark
|
|
3116
|
+
activity: [
|
|
3117
|
+
`${c.bold}agentaudit activity${c.reset} [options]`,
|
|
3118
|
+
``,
|
|
3119
|
+
`Show your recent audits and findings from the AgentAudit registry.`,
|
|
3120
|
+
`Requires being logged in (run ${c.cyan}agentaudit setup${c.reset} first).`,
|
|
3121
|
+
``,
|
|
3122
|
+
`${c.bold}Options:${c.reset}`,
|
|
3123
|
+
` --json Machine-readable JSON output`,
|
|
3124
|
+
``,
|
|
3125
|
+
`${c.bold}Aliases:${c.reset} activity, my`,
|
|
3126
|
+
``,
|
|
3127
|
+
`${c.bold}Examples:${c.reset}`,
|
|
3128
|
+
` agentaudit activity`,
|
|
3129
|
+
` agentaudit activity --json`,
|
|
3130
|
+
` agentaudit my`,
|
|
3131
|
+
],
|
|
3132
|
+
my: null, // alias → activity
|
|
3133
|
+
search: [
|
|
3134
|
+
`${c.bold}agentaudit search${c.reset} <query> [options]`,
|
|
3135
|
+
``,
|
|
3136
|
+
`Search for packages in the AgentAudit registry by name, ASF-ID, or hash.`,
|
|
3137
|
+
``,
|
|
3138
|
+
`${c.bold}Options:${c.reset}`,
|
|
3139
|
+
` --json Machine-readable JSON output`,
|
|
3140
|
+
``,
|
|
3141
|
+
`${c.bold}Aliases:${c.reset} search, find`,
|
|
3142
|
+
``,
|
|
3143
|
+
`${c.bold}Examples:${c.reset}`,
|
|
3144
|
+
` agentaudit search fastmcp`,
|
|
3145
|
+
` agentaudit search mcp-server`,
|
|
3146
|
+
` agentaudit search ASF-2025-0001`,
|
|
3147
|
+
` agentaudit search fastmcp --json`,
|
|
3148
|
+
` agentaudit find fastmcp`,
|
|
3149
|
+
],
|
|
3150
|
+
find: null, // alias → search
|
|
2076
3151
|
};
|
|
2077
3152
|
|
|
2078
3153
|
// Show subcommand help: `agentaudit help <cmd>` or `agentaudit <cmd> --help`
|
|
2079
3154
|
function showSubcommandHelp(cmd) {
|
|
2080
3155
|
let helpLines = subcommandHelp[cmd];
|
|
2081
|
-
if (helpLines === null
|
|
3156
|
+
if (helpLines === null) {
|
|
2082
3157
|
// alias redirect
|
|
2083
|
-
const
|
|
2084
|
-
helpLines = subcommandHelp[
|
|
3158
|
+
const aliases = { check: 'lookup', dash: 'dashboard', lb: 'leaderboard', bench: 'benchmark', my: 'activity', find: 'search' };
|
|
3159
|
+
helpLines = subcommandHelp[aliases[cmd] || cmd];
|
|
2085
3160
|
}
|
|
2086
3161
|
if (!helpLines) {
|
|
2087
3162
|
console.log(` ${c.red}No help available for "${cmd}"${c.reset}`);
|
|
@@ -2124,9 +3199,16 @@ async function main() {
|
|
|
2124
3199
|
console.log(` ${c.cyan}audit${c.reset} <url> [url...] Deep LLM-powered security audit (~30s)`);
|
|
2125
3200
|
console.log(` ${c.cyan}lookup${c.reset} <name> Look up package in registry`);
|
|
2126
3201
|
console.log();
|
|
3202
|
+
console.log(` ${c.bold}COMMUNITY${c.reset}`);
|
|
3203
|
+
console.log(` ${c.cyan}dashboard${c.reset} Interactive dashboard (full-screen)`);
|
|
3204
|
+
console.log(` ${c.cyan}leaderboard${c.reset} Top contributors ranking`);
|
|
3205
|
+
console.log(` ${c.cyan}benchmark${c.reset} LLM model performance comparison`);
|
|
3206
|
+
console.log(` ${c.cyan}activity${c.reset} Your recent audits & findings`);
|
|
3207
|
+
console.log(` ${c.cyan}search${c.reset} <query> Search packages in registry`);
|
|
3208
|
+
console.log();
|
|
2127
3209
|
console.log(` ${c.bold}CONFIGURATION${c.reset}`);
|
|
2128
3210
|
console.log(` ${c.cyan}model${c.reset} Configure LLM provider + model`);
|
|
2129
|
-
console.log(` ${c.cyan}setup${c.reset}
|
|
3211
|
+
console.log(` ${c.cyan}setup${c.reset} Log in to agentaudit.dev (for report uploads)`);
|
|
2130
3212
|
console.log(` ${c.cyan}status${c.reset} Show current config + auth status`);
|
|
2131
3213
|
console.log();
|
|
2132
3214
|
console.log(` ${c.bold}FLAGS${c.reset}`);
|
|
@@ -2154,9 +3236,31 @@ async function main() {
|
|
|
2154
3236
|
// Default no-arg → discover
|
|
2155
3237
|
const command = args.length === 0 ? 'discover' : args[0];
|
|
2156
3238
|
const targets = args.slice(1);
|
|
2157
|
-
|
|
3239
|
+
|
|
3240
|
+
// Dashboard/Leaderboard/Benchmark — before banner() since dashboard uses alt-screen
|
|
3241
|
+
if (command === 'dashboard' || command === 'dash') {
|
|
3242
|
+
await dashboardCommand();
|
|
3243
|
+
return;
|
|
3244
|
+
}
|
|
3245
|
+
if (command === 'leaderboard' || command === 'lb') {
|
|
3246
|
+
await leaderboardCommand(targets);
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
if (command === 'benchmark' || command === 'bench') {
|
|
3250
|
+
await benchmarkCommand(targets);
|
|
3251
|
+
return;
|
|
3252
|
+
}
|
|
3253
|
+
if (command === 'activity' || command === 'my') {
|
|
3254
|
+
await activityCommand(targets);
|
|
3255
|
+
return;
|
|
3256
|
+
}
|
|
3257
|
+
if (command === 'search' || command === 'find') {
|
|
3258
|
+
await searchCommand(targets);
|
|
3259
|
+
return;
|
|
3260
|
+
}
|
|
3261
|
+
|
|
2158
3262
|
banner();
|
|
2159
|
-
|
|
3263
|
+
|
|
2160
3264
|
if (command === 'setup') {
|
|
2161
3265
|
await setupCommand();
|
|
2162
3266
|
return;
|
|
@@ -2218,6 +3322,39 @@ async function main() {
|
|
|
2218
3322
|
}
|
|
2219
3323
|
console.log();
|
|
2220
3324
|
|
|
3325
|
+
// Personal stats (from registry, silently skip on error)
|
|
3326
|
+
if (creds?.agent_name && creds.agent_name !== 'env') {
|
|
3327
|
+
try {
|
|
3328
|
+
const [agentRes, lbRes] = await Promise.all([
|
|
3329
|
+
fetch(`${REGISTRY_URL}/api/agents/${encodeURIComponent(creds.agent_name)}`, {
|
|
3330
|
+
headers: { 'Authorization': `Bearer ${creds.api_key}` },
|
|
3331
|
+
signal: AbortSignal.timeout(10_000),
|
|
3332
|
+
}).then(r => r.ok ? r.json() : null),
|
|
3333
|
+
fetch(`${REGISTRY_URL}/api/leaderboard?limit=100`, { signal: AbortSignal.timeout(10_000) }).then(r => r.ok ? r.json() : null),
|
|
3334
|
+
]);
|
|
3335
|
+
if (agentRes) {
|
|
3336
|
+
let rank = null;
|
|
3337
|
+
if (Array.isArray(lbRes)) {
|
|
3338
|
+
const idx = lbRes.findIndex(e => e.agent_name === creds.agent_name);
|
|
3339
|
+
if (idx >= 0) rank = idx + 1;
|
|
3340
|
+
}
|
|
3341
|
+
// Update profile cache
|
|
3342
|
+
saveProfileCache({
|
|
3343
|
+
agent_name: creds.agent_name,
|
|
3344
|
+
rank,
|
|
3345
|
+
total_points: agentRes.total_points || 0,
|
|
3346
|
+
total_reports: agentRes.total_reports || 0,
|
|
3347
|
+
});
|
|
3348
|
+
console.log(` ${c.bold}Profile${c.reset}`);
|
|
3349
|
+
console.log(` Rank ${c.bold}${rank ? `#${rank} of ${lbRes.length}` : '-'}${c.reset}`);
|
|
3350
|
+
console.log(` Points ${c.bold}${fmtNum(agentRes.total_points || 0)}${c.reset}`);
|
|
3351
|
+
console.log(` Audits ${c.bold}${fmtNum(agentRes.total_reports || 0)}${c.reset}`);
|
|
3352
|
+
console.log(` Findings ${c.bold}${fmtNum(agentRes.total_findings_submitted || 0)}${c.reset} ${c.dim}(${fmtNum(agentRes.total_findings_confirmed || 0)} confirmed)${c.reset}`);
|
|
3353
|
+
console.log();
|
|
3354
|
+
}
|
|
3355
|
+
} catch {}
|
|
3356
|
+
}
|
|
3357
|
+
|
|
2221
3358
|
// JSON mode
|
|
2222
3359
|
if (jsonMode) {
|
|
2223
3360
|
const status = {
|