agentaudit 3.10.10 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.mjs +517 -97
- package/index.mjs +795 -659
- package/package.json +5 -1
- package/scan-tool-poisoning.mjs +297 -0
- package/tool-poisoning-detector.mjs +913 -0
package/cli.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Usage: agentaudit <command> [options]
|
|
6
6
|
*
|
|
7
7
|
* Commands:
|
|
8
|
-
* discover Find MCP servers
|
|
8
|
+
* discover Find MCP servers across all AI tools
|
|
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
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* model [name|reset] Configure LLM provider + model
|
|
18
18
|
* setup Log in to agentaudit.dev (for report uploads)
|
|
19
19
|
* status Show current config + auth status
|
|
20
|
+
* profile Your profile — rank, points, audit stats
|
|
20
21
|
* help [command] Show help
|
|
21
22
|
*
|
|
22
23
|
* Flags: --json, --quiet, --no-color, --no-upload, --model, --export, --debug
|
|
@@ -554,7 +555,7 @@ function banner() {
|
|
|
554
555
|
const ptsStr = `${fmtNum(cache.total_points)}pts`;
|
|
555
556
|
const auditsStr = `${fmtNum(cache.total_reports)} audits`;
|
|
556
557
|
const profile = [cache.agent_name, rankStr, ptsStr, auditsStr].filter(Boolean).join(' \u00b7 ');
|
|
557
|
-
console.log(` ${c.bold}${c.cyan}\
|
|
558
|
+
console.log(` ${c.bold}${c.cyan}\u25c6 AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset} ${c.dim}\u2502${c.reset} ${profile}`);
|
|
558
559
|
} else {
|
|
559
560
|
console.log(` ${c.bold}${c.cyan}AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset}`);
|
|
560
561
|
console.log(` ${c.dim}Security scanner for AI packages${c.reset}`);
|
|
@@ -575,10 +576,14 @@ function elapsed(startMs) {
|
|
|
575
576
|
}
|
|
576
577
|
|
|
577
578
|
function riskBadge(score) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
579
|
+
const badge = score === 0 ? `${c.bgGreen}${c.bold}${c.white} SAFE ${c.reset}`
|
|
580
|
+
: score <= 10 ? `${c.bgGreen}${c.white} LOW ${c.reset}`
|
|
581
|
+
: score <= 30 ? `${c.bgYellow}${c.bold} CAUTION ${c.reset}`
|
|
582
|
+
: `${c.bgRed}${c.bold}${c.white} UNSAFE ${c.reset}`;
|
|
583
|
+
const filled = Math.min(Math.round(score / 20), 5);
|
|
584
|
+
const gaugeColor = score <= 10 ? c.green : score <= 30 ? c.yellow : c.red;
|
|
585
|
+
const gauge = `${gaugeColor}${'▰'.repeat(filled)}${c.dim}${'▱'.repeat(5 - filled)}${c.reset}`;
|
|
586
|
+
return `${badge} ${gauge} ${score}/100`;
|
|
582
587
|
}
|
|
583
588
|
|
|
584
589
|
function severityColor(sev) {
|
|
@@ -695,6 +700,47 @@ function sparkline(values) {
|
|
|
695
700
|
}).join('');
|
|
696
701
|
}
|
|
697
702
|
|
|
703
|
+
// ─── Section Header ─────────── labeled divider
|
|
704
|
+
function sectionHeader(title, width = 60) {
|
|
705
|
+
const dashAfter = Math.max(3, width - 5 - title.length);
|
|
706
|
+
return ` ${c.dim}───${c.reset} ${c.bold}${title}${c.reset} ${c.dim}${'─'.repeat(dashAfter)}${c.reset}`;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// █████░░░░░░░░░░░░░░ coverage bar
|
|
710
|
+
function coverageBar(filled, total, width = 20) {
|
|
711
|
+
if (total === 0) return `${c.dim}${'░'.repeat(width)}${c.reset} 0/0`;
|
|
712
|
+
const barFilled = Math.max(filled > 0 ? 1 : 0, Math.round((filled / total) * width));
|
|
713
|
+
const barEmpty = width - barFilled;
|
|
714
|
+
const pct = Math.round((filled / total) * 100);
|
|
715
|
+
const color = pct >= 80 ? c.green : pct >= 50 ? c.yellow : c.red;
|
|
716
|
+
return `${color}${'█'.repeat(barFilled)}${c.dim}${'░'.repeat(barEmpty)}${c.reset} ${filled}/${total} (${pct}%)`;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Severity histogram for findings
|
|
720
|
+
function severityHistogram(findings) {
|
|
721
|
+
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
722
|
+
for (const f of findings) {
|
|
723
|
+
const sev = (f.severity || '').toLowerCase();
|
|
724
|
+
if (counts[sev] !== undefined) counts[sev]++;
|
|
725
|
+
}
|
|
726
|
+
const max = Math.max(...Object.values(counts), 1);
|
|
727
|
+
const lines = [];
|
|
728
|
+
for (const sev of ['critical', 'high', 'medium', 'low']) {
|
|
729
|
+
const count = counts[sev];
|
|
730
|
+
if (count === 0) continue;
|
|
731
|
+
const barLen = Math.max(1, Math.round((count / max) * 24));
|
|
732
|
+
const sc = severityColor(sev);
|
|
733
|
+
const label = sev.toUpperCase().padEnd(10);
|
|
734
|
+
lines.push(` ${sc}${label}${c.reset} ${sc}${'█'.repeat(barLen)}${c.reset}${' '.repeat(24 - barLen)} ${count}`);
|
|
735
|
+
}
|
|
736
|
+
return lines;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// ▰▰▰▱ step progress indicator
|
|
740
|
+
function stepProgress(current, total) {
|
|
741
|
+
return `${c.cyan}${'▰'.repeat(current)}${c.dim}${'▱'.repeat(total - current)}${c.reset}`;
|
|
742
|
+
}
|
|
743
|
+
|
|
698
744
|
function fmtNum(n) {
|
|
699
745
|
if (n == null) return '0';
|
|
700
746
|
return n.toLocaleString('en-US');
|
|
@@ -709,7 +755,7 @@ function dashboardBanner() {
|
|
|
709
755
|
const ver = getVersion();
|
|
710
756
|
return [
|
|
711
757
|
` ${BOX.tl}${c.dim}${BOX.h.repeat(35)}${c.reset}${BOX.tr}`,
|
|
712
|
-
` ${BOX.v} ${c.bold}${c.cyan}
|
|
758
|
+
` ${BOX.v} ${c.bold}${c.cyan}◆ AgentAudit${c.reset} ${c.dim}v${ver}${c.reset}${' '.repeat(Math.max(0, 19 - ver.length))}${BOX.v}`,
|
|
713
759
|
` ${BOX.v} ${c.dim}Security Registry for AI Agents${c.reset} ${BOX.v}`,
|
|
714
760
|
` ${BOX.bl}${c.dim}${BOX.h.repeat(35)}${c.reset}${BOX.br}`,
|
|
715
761
|
];
|
|
@@ -1120,31 +1166,38 @@ function printScanResult(url, info, files, findings, registryData, duration) {
|
|
|
1120
1166
|
console.log(`${icons.pipe} ${c.dim}(no tools or prompts detected)${c.reset}`);
|
|
1121
1167
|
}
|
|
1122
1168
|
|
|
1123
|
-
// Findings
|
|
1169
|
+
// Findings with severity stripe
|
|
1124
1170
|
if (findings.length > 0) {
|
|
1125
|
-
console.log(
|
|
1126
|
-
console.log(
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
const branch = isLast ? icons.treeLast : icons.tree;
|
|
1131
|
-
const pipeOrSpace = isLast ? ' ' : `${icons.pipe} `;
|
|
1171
|
+
console.log();
|
|
1172
|
+
console.log(sectionHeader(`Findings (${findings.length})`));
|
|
1173
|
+
console.log(` ${c.dim}static analysis — may include false positives${c.reset}`);
|
|
1174
|
+
console.log();
|
|
1175
|
+
for (const f of findings) {
|
|
1132
1176
|
const sc = severityColor(f.severity);
|
|
1133
|
-
console.log(
|
|
1134
|
-
console.log(
|
|
1177
|
+
console.log(` ${sc}┃${c.reset} ${sc}${f.severity.toUpperCase().padEnd(8)}${c.reset} ${c.bold}${f.title}${c.reset}`);
|
|
1178
|
+
console.log(` ${sc}┃${c.reset} ${c.dim}${f.file}:${f.line}${c.reset} ${c.dim}${f.snippet || ''}${c.reset}`);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Severity histogram
|
|
1182
|
+
const histLines = severityHistogram(findings);
|
|
1183
|
+
if (histLines.length > 1) {
|
|
1184
|
+
console.log();
|
|
1185
|
+
console.log(sectionHeader('Severity'));
|
|
1186
|
+
for (const line of histLines) console.log(line);
|
|
1135
1187
|
}
|
|
1136
1188
|
}
|
|
1137
|
-
|
|
1189
|
+
|
|
1138
1190
|
// Registry status
|
|
1139
|
-
console.log(
|
|
1191
|
+
console.log();
|
|
1192
|
+
console.log(sectionHeader('Registry'));
|
|
1140
1193
|
if (registryData) {
|
|
1141
1194
|
const rd = registryData;
|
|
1142
1195
|
const riskScore = rd.risk_score ?? rd.latest_risk_score ?? 0;
|
|
1143
|
-
console.log(
|
|
1196
|
+
console.log(` ${riskBadge(riskScore)} ${c.dim}${REGISTRY_URL}/packages/${slug}${c.reset}`);
|
|
1144
1197
|
} else {
|
|
1145
|
-
console.log(
|
|
1198
|
+
console.log(` ${c.dim}not audited yet${c.reset}`);
|
|
1146
1199
|
}
|
|
1147
|
-
|
|
1200
|
+
|
|
1148
1201
|
console.log();
|
|
1149
1202
|
}
|
|
1150
1203
|
|
|
@@ -1153,27 +1206,23 @@ function printSummary(results) {
|
|
|
1153
1206
|
const safe = results.filter(r => r.findings.length === 0).length;
|
|
1154
1207
|
const withFindings = total - safe;
|
|
1155
1208
|
const totalFindings = results.reduce((sum, r) => sum + r.findings.length, 0);
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
console.log(`
|
|
1209
|
+
const allFindings = results.flatMap(r => r.findings);
|
|
1210
|
+
|
|
1211
|
+
console.log(sectionHeader(`Summary — ${total} packages scanned`));
|
|
1159
1212
|
console.log();
|
|
1160
1213
|
if (safe > 0) console.log(` ${icons.safe} ${c.green}${safe} clean${c.reset}`);
|
|
1161
1214
|
if (withFindings > 0) console.log(` ${icons.caution} ${c.yellow}${withFindings} with findings${c.reset} (${totalFindings} total)`);
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
if (Object.keys(bySev).length > 0) {
|
|
1215
|
+
console.log();
|
|
1216
|
+
console.log(` ${coverageBar(safe, total)}`);
|
|
1217
|
+
|
|
1218
|
+
// Severity histogram
|
|
1219
|
+
const histLines = severityHistogram(allFindings);
|
|
1220
|
+
if (histLines.length > 0) {
|
|
1169
1221
|
console.log();
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
console.log(` ${severityIcon(sev)} ${bySev[sev]}× ${severityColor(sev)}${sev}${c.reset}`);
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1222
|
+
console.log(sectionHeader('Severity'));
|
|
1223
|
+
for (const line of histLines) console.log(line);
|
|
1175
1224
|
}
|
|
1176
|
-
|
|
1225
|
+
|
|
1177
1226
|
console.log();
|
|
1178
1227
|
}
|
|
1179
1228
|
|
|
@@ -1230,48 +1279,290 @@ async function scanRepo(url) {
|
|
|
1230
1279
|
|
|
1231
1280
|
// ── Discover local MCP configs ──────────────────────────
|
|
1232
1281
|
|
|
1282
|
+
/**
|
|
1283
|
+
* Minimal YAML parser — extracts MCP server list entries from
|
|
1284
|
+
* Continue.dev (mcpServers: list) and Goose (extensions: list).
|
|
1285
|
+
* Zero dependencies. Only handles the subset of YAML used by these tools.
|
|
1286
|
+
*/
|
|
1287
|
+
function parseSimpleYaml(text, rootKey) {
|
|
1288
|
+
const result = { mcpServers: {} };
|
|
1289
|
+
const lines = text.split('\n');
|
|
1290
|
+
let inSection = false;
|
|
1291
|
+
let currentName = null;
|
|
1292
|
+
let currentServer = {};
|
|
1293
|
+
let collectingArgs = false;
|
|
1294
|
+
let argsIndent = -1;
|
|
1295
|
+
|
|
1296
|
+
for (const line of lines) {
|
|
1297
|
+
const trimmed = line.trimEnd();
|
|
1298
|
+
if (trimmed === '' || /^\s*#/.test(trimmed)) continue;
|
|
1299
|
+
const indent = line.search(/\S/);
|
|
1300
|
+
|
|
1301
|
+
if (indent === 0 && trimmed === rootKey + ':') { inSection = true; continue; }
|
|
1302
|
+
if (indent === 0 && inSection && /^\w/.test(trimmed)) {
|
|
1303
|
+
if (currentName) result.mcpServers[currentName] = currentServer;
|
|
1304
|
+
break;
|
|
1305
|
+
}
|
|
1306
|
+
if (!inSection) continue;
|
|
1307
|
+
|
|
1308
|
+
const nameMatch = trimmed.match(/^\s*-\s+name:\s*(.+)/);
|
|
1309
|
+
if (nameMatch) {
|
|
1310
|
+
if (currentName) result.mcpServers[currentName] = currentServer;
|
|
1311
|
+
currentName = nameMatch[1].replace(/^["']|["']$/g, '');
|
|
1312
|
+
currentServer = {};
|
|
1313
|
+
collectingArgs = false;
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
if (collectingArgs && indent > argsIndent) {
|
|
1318
|
+
const argVal = trimmed.match(/^\s*-\s+(.+)/);
|
|
1319
|
+
if (argVal) {
|
|
1320
|
+
if (!currentServer.args) currentServer.args = [];
|
|
1321
|
+
currentServer.args.push(argVal[1].replace(/^["']|["']$/g, ''));
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
if (collectingArgs && indent <= argsIndent) collectingArgs = false;
|
|
1326
|
+
if (!currentName) continue;
|
|
1327
|
+
|
|
1328
|
+
const kvMatch = trimmed.match(/^\s+(command|cmd|type|url):\s*(.+)/);
|
|
1329
|
+
if (kvMatch) {
|
|
1330
|
+
collectingArgs = false;
|
|
1331
|
+
const key = kvMatch[1] === 'cmd' ? 'command' : kvMatch[1];
|
|
1332
|
+
currentServer[key] = kvMatch[2].replace(/^["']|["']$/g, '');
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
const argsMatch = trimmed.match(/^\s+(args):\s*(.*)/);
|
|
1337
|
+
if (argsMatch) {
|
|
1338
|
+
const inlineArr = argsMatch[2].match(/^\[(.+)\]$/);
|
|
1339
|
+
if (inlineArr) {
|
|
1340
|
+
currentServer.args = inlineArr[1].split(',').map(s => s.trim().replace(/^["']|["']$/g, ''));
|
|
1341
|
+
collectingArgs = false;
|
|
1342
|
+
} else {
|
|
1343
|
+
collectingArgs = true;
|
|
1344
|
+
argsIndent = indent;
|
|
1345
|
+
currentServer.args = [];
|
|
1346
|
+
}
|
|
1347
|
+
continue;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
if (currentName) result.mcpServers[currentName] = currentServer;
|
|
1351
|
+
return result;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
/**
|
|
1355
|
+
* Minimal TOML parser — extracts [mcp_servers.xxx] sections
|
|
1356
|
+
* from OpenAI Codex CLI config. Zero dependencies.
|
|
1357
|
+
*/
|
|
1358
|
+
function parseSimpleToml(text) {
|
|
1359
|
+
const result = { mcpServers: {} };
|
|
1360
|
+
const lines = text.split('\n');
|
|
1361
|
+
let currentName = null;
|
|
1362
|
+
let currentServer = {};
|
|
1363
|
+
|
|
1364
|
+
for (const line of lines) {
|
|
1365
|
+
const trimmed = line.trim();
|
|
1366
|
+
if (trimmed === '' || trimmed.startsWith('#')) continue;
|
|
1367
|
+
|
|
1368
|
+
const sectionMatch = trimmed.match(/^\[mcp_servers\.(.+)\]$/);
|
|
1369
|
+
if (sectionMatch) {
|
|
1370
|
+
if (currentName) result.mcpServers[currentName] = currentServer;
|
|
1371
|
+
currentName = sectionMatch[1];
|
|
1372
|
+
currentServer = {};
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
if (trimmed.startsWith('[') && !trimmed.startsWith('[mcp_servers.')) {
|
|
1376
|
+
if (currentName) result.mcpServers[currentName] = currentServer;
|
|
1377
|
+
currentName = null;
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
if (!currentName) continue;
|
|
1381
|
+
|
|
1382
|
+
const strMatch = trimmed.match(/^(command|url)\s*=\s*"(.+?)"/);
|
|
1383
|
+
if (strMatch) { currentServer[strMatch[1]] = strMatch[2]; continue; }
|
|
1384
|
+
|
|
1385
|
+
const argsMatch = trimmed.match(/^args\s*=\s*\[(.+)\]/);
|
|
1386
|
+
if (argsMatch) {
|
|
1387
|
+
currentServer.args = argsMatch[1].split(',').map(s => s.trim().replace(/^["']|["']$/g, ''));
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
const boolMatch = trimmed.match(/^enabled\s*=\s*(true|false)/);
|
|
1392
|
+
if (boolMatch && boolMatch[1] === 'false') currentServer.disabled = true;
|
|
1393
|
+
}
|
|
1394
|
+
if (currentName) result.mcpServers[currentName] = currentServer;
|
|
1395
|
+
return result;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
/**
|
|
1399
|
+
* Comprehensive MCP config discovery across all major AI editors & tools.
|
|
1400
|
+
*
|
|
1401
|
+
* Supports: Claude Desktop, Claude Code, Cursor, Windsurf, VS Code,
|
|
1402
|
+
* Cline, Roo Code, Amazon Q, Gemini CLI, Zed, Continue.dev, Goose,
|
|
1403
|
+
* OpenAI Codex CLI, Visual Studio — global + project-level configs.
|
|
1404
|
+
*/
|
|
1233
1405
|
function findMcpConfigs() {
|
|
1234
1406
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
1235
1407
|
const platform = process.platform;
|
|
1236
|
-
|
|
1237
|
-
|
|
1408
|
+
const cwd = process.cwd();
|
|
1409
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
1410
|
+
|
|
1411
|
+
// Platform-specific app data directory
|
|
1412
|
+
// macOS: ~/Library/Application Support, Windows: ~/AppData/Roaming, Linux: ~/.config
|
|
1413
|
+
const appData = platform === 'darwin'
|
|
1414
|
+
? path.join(home, 'Library', 'Application Support')
|
|
1415
|
+
: platform === 'win32'
|
|
1416
|
+
? path.join(home, 'AppData', 'Roaming')
|
|
1417
|
+
: xdgConfig;
|
|
1418
|
+
|
|
1419
|
+
// Each candidate: { name, path, format: 'json'|'yaml'|'toml', key: top-level key }
|
|
1238
1420
|
const candidates = [
|
|
1239
|
-
// Claude Desktop
|
|
1240
|
-
{ name: 'Claude Desktop', path: path.join(home, '
|
|
1241
|
-
{ name: 'Claude Desktop', path: path.join(home, '
|
|
1242
|
-
{ name: 'Claude Desktop', path: path.join(
|
|
1243
|
-
|
|
1244
|
-
//
|
|
1245
|
-
{ name: '
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
//
|
|
1249
|
-
{ name: '
|
|
1250
|
-
|
|
1251
|
-
|
|
1421
|
+
// ── Claude Desktop ──
|
|
1422
|
+
...(platform === 'darwin' ? [{ name: 'Claude Desktop', path: path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'), format: 'json', key: 'mcpServers' }] : []),
|
|
1423
|
+
...(platform === 'win32' ? [{ name: 'Claude Desktop', path: path.join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json'), format: 'json', key: 'mcpServers' }] : []),
|
|
1424
|
+
...(platform === 'linux' ? [{ name: 'Claude Desktop', path: path.join(xdgConfig, 'Claude', 'claude_desktop_config.json'), format: 'json', key: 'mcpServers' }] : []),
|
|
1425
|
+
|
|
1426
|
+
// ── Claude Code ──
|
|
1427
|
+
{ name: 'Claude Code', path: path.join(home, '.claude.json'), format: 'json', key: 'mcpServers' },
|
|
1428
|
+
{ name: 'Claude Code', path: path.join(home, '.claude', 'mcp.json'), format: 'json', key: 'mcpServers' },
|
|
1429
|
+
|
|
1430
|
+
// ── Cursor (global) ──
|
|
1431
|
+
{ name: 'Cursor', path: path.join(home, '.cursor', 'mcp.json'), format: 'json', key: 'mcpServers' },
|
|
1432
|
+
|
|
1433
|
+
// ── Windsurf / Codeium ──
|
|
1434
|
+
{ name: 'Windsurf', path: path.join(home, '.codeium', 'windsurf', 'mcp_config.json'), format: 'json', key: 'mcpServers' },
|
|
1435
|
+
|
|
1436
|
+
// ── VS Code (global mcp.json — uses 'servers' key) ──
|
|
1437
|
+
...(platform === 'darwin' ? [{ name: 'VS Code', path: path.join(home, 'Library', 'Application Support', 'Code', 'User', 'mcp.json'), format: 'json', key: 'servers' }] : []),
|
|
1438
|
+
...(platform === 'win32' ? [{ name: 'VS Code', path: path.join(home, 'AppData', 'Roaming', 'Code', 'User', 'mcp.json'), format: 'json', key: 'servers' }] : []),
|
|
1439
|
+
...(platform === 'linux' ? [{ name: 'VS Code', path: path.join(xdgConfig, 'Code', 'User', 'mcp.json'), format: 'json', key: 'servers' }] : []),
|
|
1440
|
+
|
|
1441
|
+
// ── VS Code settings.json (mcp.servers nested key) ──
|
|
1442
|
+
...(platform === 'darwin' ? [{ name: 'VS Code (settings)', path: path.join(home, 'Library', 'Application Support', 'Code', 'User', 'settings.json'), format: 'json', key: 'mcp.servers' }] : []),
|
|
1443
|
+
...(platform === 'win32' ? [{ name: 'VS Code (settings)', path: path.join(home, 'AppData', 'Roaming', 'Code', 'User', 'settings.json'), format: 'json', key: 'mcp.servers' }] : []),
|
|
1444
|
+
...(platform === 'linux' ? [{ name: 'VS Code (settings)', path: path.join(xdgConfig, 'Code', 'User', 'settings.json'), format: 'json', key: 'mcp.servers' }] : []),
|
|
1445
|
+
|
|
1446
|
+
// ── Cline (VS Code extension) ──
|
|
1447
|
+
...(platform === 'darwin' ? [{ name: 'Cline', path: path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'), format: 'json', key: 'mcpServers' }] : []),
|
|
1448
|
+
...(platform === 'win32' ? [{ name: 'Cline', path: path.join(home, 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'), format: 'json', key: 'mcpServers' }] : []),
|
|
1449
|
+
...(platform === 'linux' ? [{ name: 'Cline', path: path.join(xdgConfig, 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'), format: 'json', key: 'mcpServers' }] : []),
|
|
1450
|
+
|
|
1451
|
+
// ── Roo Code (VS Code extension) ──
|
|
1452
|
+
...(platform === 'darwin' ? [{ name: 'Roo Code', path: path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'settings', 'mcp_settings.json'), format: 'json', key: 'mcpServers' }] : []),
|
|
1453
|
+
...(platform === 'win32' ? [{ name: 'Roo Code', path: path.join(home, 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'settings', 'mcp_settings.json'), format: 'json', key: 'mcpServers' }] : []),
|
|
1454
|
+
...(platform === 'linux' ? [{ name: 'Roo Code', path: path.join(xdgConfig, 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'settings', 'mcp_settings.json'), format: 'json', key: 'mcpServers' }] : []),
|
|
1455
|
+
|
|
1456
|
+
// ── Amazon Q Developer ──
|
|
1457
|
+
{ name: 'Amazon Q', path: path.join(home, '.aws', 'amazonq', 'mcp.json'), format: 'json', key: 'mcpServers' },
|
|
1458
|
+
{ name: 'Amazon Q (IDE)', path: path.join(home, '.aws', 'amazonq', 'default.json'), format: 'json', key: 'mcpServers' },
|
|
1459
|
+
|
|
1460
|
+
// ── Gemini CLI ──
|
|
1461
|
+
{ name: 'Gemini CLI', path: path.join(home, '.gemini', 'settings.json'), format: 'json', key: 'mcpServers' },
|
|
1462
|
+
|
|
1463
|
+
// ── Zed (macOS + Linux only, uses 'context_servers' key) ──
|
|
1464
|
+
...(platform === 'darwin' ? [{ name: 'Zed', path: path.join(home, '.zed', 'settings.json'), format: 'json', key: 'context_servers' }] : []),
|
|
1465
|
+
...(platform === 'linux' ? [{ name: 'Zed', path: path.join(xdgConfig, 'zed', 'settings.json'), format: 'json', key: 'context_servers' }] : []),
|
|
1466
|
+
|
|
1467
|
+
// ── Continue.dev ──
|
|
1468
|
+
{ name: 'Continue', path: path.join(home, '.continue', 'config.json'), format: 'json', key: 'mcpServers' },
|
|
1469
|
+
{ name: 'Continue', path: path.join(home, '.continue', 'config.yaml'), format: 'yaml', key: 'mcpServers' },
|
|
1470
|
+
|
|
1471
|
+
// ── Goose (Block/Square) ──
|
|
1472
|
+
{ name: 'Goose', path: path.join(xdgConfig, 'goose', 'config.yaml'), format: 'yaml', key: 'extensions' },
|
|
1473
|
+
|
|
1474
|
+
// ── OpenAI Codex CLI ──
|
|
1475
|
+
{ name: 'Codex CLI', path: path.join(home, '.codex', 'config.toml'), format: 'toml', key: 'mcp_servers' },
|
|
1476
|
+
|
|
1477
|
+
// ── Visual Studio (Windows only) ──
|
|
1478
|
+
...(platform === 'win32' ? [{ name: 'Visual Studio', path: path.join(home, '.mcp.json'), format: 'json', key: 'mcpServers' }] : []),
|
|
1479
|
+
|
|
1480
|
+
// ── Project-level configs (cwd) ──
|
|
1481
|
+
{ name: 'Claude Code (project)', path: path.join(cwd, '.mcp.json'), format: 'json', key: 'mcpServers' },
|
|
1482
|
+
{ name: 'Cursor (project)', path: path.join(cwd, '.cursor', 'mcp.json'), format: 'json', key: 'mcpServers' },
|
|
1483
|
+
{ name: 'VS Code (project)', path: path.join(cwd, '.vscode', 'mcp.json'), format: 'json', key: 'servers' },
|
|
1484
|
+
{ name: 'Roo Code (project)', path: path.join(cwd, '.roo', 'mcp.json'), format: 'json', key: 'mcpServers' },
|
|
1485
|
+
{ name: 'Amazon Q (project)', path: path.join(cwd, '.amazonq', 'mcp.json'), format: 'json', key: 'mcpServers' },
|
|
1486
|
+
{ name: 'Gemini CLI (project)', path: path.join(cwd, '.gemini', 'settings.json'), format: 'json', key: 'mcpServers' },
|
|
1487
|
+
...(platform !== 'win32' ? [{ name: 'Zed (project)', path: path.join(cwd, '.zed', 'settings.json'), format: 'json', key: 'context_servers' }] : []),
|
|
1488
|
+
{ name: 'Codex CLI (project)', path: path.join(cwd, '.codex', 'config.toml'), format: 'toml', key: 'mcp_servers' },
|
|
1252
1489
|
];
|
|
1253
|
-
|
|
1254
|
-
//
|
|
1490
|
+
|
|
1491
|
+
// Test config override
|
|
1255
1492
|
if (process.env.AGENTAUDIT_TEST_CONFIG) {
|
|
1256
|
-
candidates.push({ name: 'Test Config', path: process.env.AGENTAUDIT_TEST_CONFIG });
|
|
1493
|
+
candidates.push({ name: 'Test Config', path: process.env.AGENTAUDIT_TEST_CONFIG, format: 'json', key: 'mcpServers' });
|
|
1257
1494
|
}
|
|
1258
|
-
|
|
1259
|
-
//
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1495
|
+
|
|
1496
|
+
// Continue.dev mcpServers drop-in directory (individual JSON files)
|
|
1497
|
+
const continueDropIn = path.join(home, '.continue', 'mcpServers');
|
|
1498
|
+
try {
|
|
1499
|
+
if (fs.existsSync(continueDropIn)) {
|
|
1500
|
+
for (const f of fs.readdirSync(continueDropIn)) {
|
|
1501
|
+
if (f.endsWith('.json')) {
|
|
1502
|
+
candidates.push({ name: 'Continue (drop-in)', path: path.join(continueDropIn, f), format: 'json', key: 'mcpServers' });
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
} catch {}
|
|
1507
|
+
|
|
1508
|
+
// Project-level Continue.dev drop-ins
|
|
1509
|
+
const cwdContinueDropIn = path.join(cwd, '.continue', 'mcpServers');
|
|
1510
|
+
try {
|
|
1511
|
+
if (fs.existsSync(cwdContinueDropIn)) {
|
|
1512
|
+
for (const f of fs.readdirSync(cwdContinueDropIn)) {
|
|
1513
|
+
if (f.endsWith('.json')) {
|
|
1514
|
+
candidates.push({ name: 'Continue (project drop-in)', path: path.join(cwdContinueDropIn, f), format: 'json', key: 'mcpServers' });
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
} catch {}
|
|
1519
|
+
|
|
1266
1520
|
const found = [];
|
|
1521
|
+
const seenPaths = new Set();
|
|
1522
|
+
|
|
1267
1523
|
for (const c of candidates) {
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1524
|
+
const resolved = path.resolve(c.path);
|
|
1525
|
+
if (seenPaths.has(resolved)) continue;
|
|
1526
|
+
if (!fs.existsSync(c.path)) continue;
|
|
1527
|
+
seenPaths.add(resolved);
|
|
1528
|
+
|
|
1529
|
+
try {
|
|
1530
|
+
const raw = fs.readFileSync(c.path, 'utf8');
|
|
1531
|
+
let content;
|
|
1532
|
+
|
|
1533
|
+
if (c.format === 'yaml') {
|
|
1534
|
+
content = parseSimpleYaml(raw, c.key);
|
|
1535
|
+
} else if (c.format === 'toml') {
|
|
1536
|
+
content = parseSimpleToml(raw);
|
|
1537
|
+
} else {
|
|
1538
|
+
content = JSON.parse(raw);
|
|
1539
|
+
// Normalize different JSON key structures to mcpServers
|
|
1540
|
+
if (c.key === 'mcp.servers' && content.mcp?.servers) {
|
|
1541
|
+
content = { mcpServers: content.mcp.servers };
|
|
1542
|
+
} else if (c.key === 'context_servers' && content.context_servers) {
|
|
1543
|
+
// Zed: normalize nested { command: { path, args } } → { command, args }
|
|
1544
|
+
const normalized = {};
|
|
1545
|
+
for (const [name, cfg] of Object.entries(content.context_servers)) {
|
|
1546
|
+
if (cfg.command && typeof cfg.command === 'object') {
|
|
1547
|
+
normalized[name] = { command: cfg.command.path || cfg.command.command, args: cfg.command.args || [], env: cfg.command.env || {} };
|
|
1548
|
+
} else {
|
|
1549
|
+
normalized[name] = cfg;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
content = { mcpServers: normalized };
|
|
1553
|
+
} else if (c.key === 'servers' && content.servers && !content.mcpServers) {
|
|
1554
|
+
content = { mcpServers: content.servers };
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// Only include configs that actually have servers
|
|
1559
|
+
const servers = content?.mcpServers || content?.servers || {};
|
|
1560
|
+
if (Object.keys(servers).length > 0) {
|
|
1561
|
+
found.push({ name: c.name, path: c.path, content });
|
|
1562
|
+
}
|
|
1563
|
+
} catch {}
|
|
1274
1564
|
}
|
|
1565
|
+
|
|
1275
1566
|
return found;
|
|
1276
1567
|
}
|
|
1277
1568
|
|
|
@@ -1450,7 +1741,7 @@ async function discoverCommand(options = {}) {
|
|
|
1450
1741
|
const interactiveAudit = options.audit || false;
|
|
1451
1742
|
|
|
1452
1743
|
if (!jsonMode) {
|
|
1453
|
-
console.log(` ${c.bold}Discovering MCP servers
|
|
1744
|
+
console.log(` ${c.bold}Discovering MCP servers across all AI tools...${c.reset}`);
|
|
1454
1745
|
console.log();
|
|
1455
1746
|
}
|
|
1456
1747
|
|
|
@@ -1458,13 +1749,16 @@ async function discoverCommand(options = {}) {
|
|
|
1458
1749
|
|
|
1459
1750
|
if (configs.length === 0) {
|
|
1460
1751
|
console.log(` ${c.yellow}No MCP configurations found.${c.reset}`);
|
|
1461
|
-
console.log(` ${c.dim}Searched: Claude Desktop, Cursor, Windsurf, VS Code
|
|
1752
|
+
console.log(` ${c.dim}Searched 15+ tools: Claude Desktop, Claude Code, Cursor, Windsurf, VS Code,${c.reset}`);
|
|
1753
|
+
console.log(` ${c.dim}Cline, Roo Code, Amazon Q, Gemini CLI, Zed, Continue.dev, Goose, Codex CLI${c.reset}`);
|
|
1462
1754
|
console.log();
|
|
1463
|
-
console.log(` ${c.dim}MCP config locations:${c.reset}`);
|
|
1464
|
-
console.log(` ${c.dim} Claude:
|
|
1465
|
-
console.log(` ${c.dim}
|
|
1466
|
-
console.log(` ${c.dim}
|
|
1467
|
-
console.log(` ${c.dim}
|
|
1755
|
+
console.log(` ${c.dim}Common MCP config locations:${c.reset}`);
|
|
1756
|
+
console.log(` ${c.dim} Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json${c.reset}`);
|
|
1757
|
+
console.log(` ${c.dim} Claude Code: ~/.claude.json${c.reset}`);
|
|
1758
|
+
console.log(` ${c.dim} Cursor: ~/.cursor/mcp.json${c.reset}`);
|
|
1759
|
+
console.log(` ${c.dim} Windsurf: ~/.codeium/windsurf/mcp_config.json${c.reset}`);
|
|
1760
|
+
console.log(` ${c.dim} VS Code: (platform)/Code/User/mcp.json${c.reset}`);
|
|
1761
|
+
console.log(` ${c.dim} Project-level: .mcp.json / .cursor/mcp.json / .vscode/mcp.json${c.reset}`);
|
|
1468
1762
|
console.log();
|
|
1469
1763
|
return;
|
|
1470
1764
|
}
|
|
@@ -1527,7 +1821,7 @@ async function discoverCommand(options = {}) {
|
|
|
1527
1821
|
const riskScore = regData.risk_score ?? regData.latest_risk_score ?? 0;
|
|
1528
1822
|
const hasOfficial = regData.has_official_audit;
|
|
1529
1823
|
console.log(`${branch} ${c.bold}${server.name}${c.reset} ${sourceLabel}`);
|
|
1530
|
-
console.log(`${pipe} ${riskBadge(riskScore)}
|
|
1824
|
+
console.log(`${pipe} ${riskBadge(riskScore)} ${hasOfficial ? `${c.green}✔ official${c.reset} ` : ''}${c.dim}${REGISTRY_URL}/packages/${slug}${c.reset}`);
|
|
1531
1825
|
if (resolvedUrl) allServersWithUrls.push({ name: server.name, sourceUrl: resolvedUrl, hasAudit: true, regData });
|
|
1532
1826
|
} else {
|
|
1533
1827
|
unauditedServers++;
|
|
@@ -1550,11 +1844,14 @@ async function discoverCommand(options = {}) {
|
|
|
1550
1844
|
}
|
|
1551
1845
|
|
|
1552
1846
|
// Summary
|
|
1553
|
-
console.log(
|
|
1554
|
-
console.log(` ${c.bold}Summary${c.reset} ${totalServers} server${totalServers !== 1 ? 's' : ''} across ${configs.length} config${configs.length !== 1 ? 's' : ''}`);
|
|
1847
|
+
console.log(sectionHeader(`Summary — ${totalServers} server${totalServers !== 1 ? 's' : ''} across ${configs.length} config${configs.length !== 1 ? 's' : ''}`));
|
|
1555
1848
|
console.log();
|
|
1556
1849
|
if (auditedServers > 0) console.log(` ${icons.safe} ${c.green}${auditedServers} audited${c.reset}`);
|
|
1557
1850
|
if (unauditedServers > 0) console.log(` ${icons.caution} ${c.yellow}${unauditedServers} not audited${c.reset}`);
|
|
1851
|
+
if (totalServers > 0) {
|
|
1852
|
+
console.log();
|
|
1853
|
+
console.log(` ${coverageBar(auditedServers, totalServers)}`);
|
|
1854
|
+
}
|
|
1558
1855
|
console.log();
|
|
1559
1856
|
|
|
1560
1857
|
// --scan: automatically scan all servers with resolved source URLs (git-cloneable only)
|
|
@@ -1570,8 +1867,8 @@ async function discoverCommand(options = {}) {
|
|
|
1570
1867
|
});
|
|
1571
1868
|
const skipped = allServersWithUrls.filter(s => s.sourceUrl && !isCloneable(s.sourceUrl));
|
|
1572
1869
|
if (dedupedTargets.length > 0) {
|
|
1573
|
-
console.log(
|
|
1574
|
-
console.log(` ${c.bold}${icons.scan}
|
|
1870
|
+
console.log(sectionHeader(`Auto-scanning ${dedupedTargets.length} server${dedupedTargets.length !== 1 ? 's' : ''}`));
|
|
1871
|
+
console.log(` ${c.bold}${icons.scan} Starting scans...${c.reset}`);
|
|
1575
1872
|
if (skipped.length > 0) {
|
|
1576
1873
|
console.log(` ${c.dim}(${skipped.length} skipped — no cloneable source URL)${c.reset}`);
|
|
1577
1874
|
}
|
|
@@ -1585,8 +1882,7 @@ async function discoverCommand(options = {}) {
|
|
|
1585
1882
|
|
|
1586
1883
|
if (scanResults.length > 1) {
|
|
1587
1884
|
// Print combined scan summary
|
|
1588
|
-
console.log(
|
|
1589
|
-
console.log(` ${c.bold}Scan Summary${c.reset} ${scanResults.length} server${scanResults.length !== 1 ? 's' : ''} scanned`);
|
|
1885
|
+
console.log(sectionHeader(`Scan Summary — ${scanResults.length} server${scanResults.length !== 1 ? 's' : ''} scanned`));
|
|
1590
1886
|
console.log();
|
|
1591
1887
|
|
|
1592
1888
|
let totalFindings = 0;
|
|
@@ -1695,7 +1991,7 @@ async function auditRepo(url) {
|
|
|
1695
1991
|
console.log();
|
|
1696
1992
|
|
|
1697
1993
|
// Step 1: Clone
|
|
1698
|
-
process.stdout.write(` ${
|
|
1994
|
+
process.stdout.write(` ${stepProgress(1, 4)} Cloning repository...`);
|
|
1699
1995
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agentaudit-'));
|
|
1700
1996
|
const repoPath = path.join(tmpDir, 'repo');
|
|
1701
1997
|
try {
|
|
@@ -1710,12 +2006,12 @@ async function auditRepo(url) {
|
|
|
1710
2006
|
}
|
|
1711
2007
|
|
|
1712
2008
|
// Step 2: Collect files
|
|
1713
|
-
process.stdout.write(` ${
|
|
2009
|
+
process.stdout.write(` ${stepProgress(2, 4)} Collecting source files...`);
|
|
1714
2010
|
const files = collectFiles(repoPath);
|
|
1715
2011
|
console.log(` ${c.green}${files.length} files${c.reset}`);
|
|
1716
2012
|
|
|
1717
2013
|
// Step 3: Build audit payload
|
|
1718
|
-
process.stdout.write(` ${
|
|
2014
|
+
process.stdout.write(` ${stepProgress(3, 4)} Preparing audit payload...`);
|
|
1719
2015
|
const auditPrompt = loadAuditPrompt();
|
|
1720
2016
|
|
|
1721
2017
|
let codeBlock = '';
|
|
@@ -1788,7 +2084,7 @@ async function auditRepo(url) {
|
|
|
1788
2084
|
|
|
1789
2085
|
// We have an API key — run LLM audit
|
|
1790
2086
|
const modelLabel = modelOverride ? `${activeProvider} → ${activeLlm.model}` : activeProvider;
|
|
1791
|
-
process.stdout.write(` ${
|
|
2087
|
+
process.stdout.write(` ${stepProgress(4, 4)} Running LLM analysis ${c.dim}(${modelLabel})${c.reset}...`);
|
|
1792
2088
|
|
|
1793
2089
|
const systemPrompt = auditPrompt || 'You are a security auditor. Analyze the code and report findings as JSON.';
|
|
1794
2090
|
const userMessage = [
|
|
@@ -2002,17 +2298,26 @@ async function auditRepo(url) {
|
|
|
2002
2298
|
// Display results
|
|
2003
2299
|
console.log();
|
|
2004
2300
|
const riskScore = report.risk_score || 0;
|
|
2005
|
-
console.log(
|
|
2301
|
+
console.log(sectionHeader('Result'));
|
|
2302
|
+
console.log(` ${riskBadge(riskScore)}`);
|
|
2006
2303
|
console.log();
|
|
2007
|
-
|
|
2304
|
+
|
|
2008
2305
|
if (report.findings && report.findings.length > 0) {
|
|
2009
|
-
console.log(`
|
|
2306
|
+
console.log(sectionHeader(`Findings (${report.findings.length})`));
|
|
2010
2307
|
console.log();
|
|
2011
2308
|
for (const f of report.findings) {
|
|
2012
2309
|
const sc = severityColor(f.severity);
|
|
2013
|
-
console.log(` ${
|
|
2014
|
-
if (f.file) console.log(`
|
|
2015
|
-
if (f.description) console.log(`
|
|
2310
|
+
console.log(` ${sc}┃${c.reset} ${sc}${(f.severity || '').toUpperCase().padEnd(8)}${c.reset} ${c.bold}${f.title}${c.reset}`);
|
|
2311
|
+
if (f.file) console.log(` ${sc}┃${c.reset} ${c.dim}${f.file}${f.line ? ':' + f.line : ''}${c.reset}`);
|
|
2312
|
+
if (f.description) console.log(` ${sc}┃${c.reset} ${c.dim}${f.description.slice(0, 120)}${c.reset}`);
|
|
2313
|
+
console.log();
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
// Severity histogram
|
|
2317
|
+
const histLines = severityHistogram(report.findings);
|
|
2318
|
+
if (histLines.length > 1) {
|
|
2319
|
+
console.log(sectionHeader('Severity'));
|
|
2320
|
+
for (const line of histLines) console.log(line);
|
|
2016
2321
|
console.log();
|
|
2017
2322
|
}
|
|
2018
2323
|
} else {
|
|
@@ -2247,7 +2552,7 @@ function renderLeaderboardTab(data, width, opts = {}) {
|
|
|
2247
2552
|
}
|
|
2248
2553
|
|
|
2249
2554
|
const maxPts = leaderboard[0]?.total_points || 1;
|
|
2250
|
-
const medals = [
|
|
2555
|
+
const medals = [`${c.yellow}★${c.reset}`, `${c.cyan}★${c.reset}`, `${c.magenta}★${c.reset}`];
|
|
2251
2556
|
|
|
2252
2557
|
for (let i = 0; i < leaderboard.length; i++) {
|
|
2253
2558
|
const entry = leaderboard[i];
|
|
@@ -2651,7 +2956,7 @@ async function leaderboardCommand(args) {
|
|
|
2651
2956
|
}
|
|
2652
2957
|
|
|
2653
2958
|
const maxPts = data[0]?.total_points || 1;
|
|
2654
|
-
const medals = [
|
|
2959
|
+
const medals = [`${c.yellow}★${c.reset}`, `${c.cyan}★${c.reset}`, `${c.magenta}★${c.reset}`];
|
|
2655
2960
|
const barW = 24;
|
|
2656
2961
|
|
|
2657
2962
|
for (let i = 0; i < data.length; i++) {
|
|
@@ -2997,7 +3302,9 @@ async function main() {
|
|
|
2997
3302
|
discover: [
|
|
2998
3303
|
`${c.bold}agentaudit discover${c.reset} [options]`,
|
|
2999
3304
|
``,
|
|
3000
|
-
`Find MCP servers
|
|
3305
|
+
`Find MCP servers across 15+ AI tools (Claude Desktop, Claude Code, Cursor, VS Code,`,
|
|
3306
|
+
`Windsurf, Cline, Roo Code, Amazon Q, Gemini CLI, Zed, Continue.dev, Goose, Codex CLI).`,
|
|
3307
|
+
`Checks global + project-level configs on macOS, Windows, and Linux.`,
|
|
3001
3308
|
``,
|
|
3002
3309
|
`${c.bold}Options:${c.reset}`,
|
|
3003
3310
|
` --quick, -s Auto-scan all discovered servers (regex-based)`,
|
|
@@ -3185,6 +3492,21 @@ async function main() {
|
|
|
3185
3492
|
` agentaudit find fastmcp`,
|
|
3186
3493
|
],
|
|
3187
3494
|
find: null, // alias → search
|
|
3495
|
+
profile: [
|
|
3496
|
+
`${c.bold}agentaudit profile${c.reset}`,
|
|
3497
|
+
``,
|
|
3498
|
+
`Show your AgentAudit profile — rank, points, audit stats,`,
|
|
3499
|
+
`and a link to your public profile on agentaudit.dev.`,
|
|
3500
|
+
``,
|
|
3501
|
+
`Requires being logged in (run ${c.cyan}agentaudit setup${c.reset} first).`,
|
|
3502
|
+
``,
|
|
3503
|
+
`${c.bold}Options:${c.reset}`,
|
|
3504
|
+
` --json Machine-readable JSON output`,
|
|
3505
|
+
``,
|
|
3506
|
+
`${c.bold}Examples:${c.reset}`,
|
|
3507
|
+
` agentaudit profile`,
|
|
3508
|
+
` agentaudit profile --json`,
|
|
3509
|
+
],
|
|
3188
3510
|
};
|
|
3189
3511
|
|
|
3190
3512
|
// Show subcommand help: `agentaudit help <cmd>` or `agentaudit <cmd> --help`
|
|
@@ -3247,6 +3569,7 @@ async function main() {
|
|
|
3247
3569
|
console.log(` ${c.cyan}model${c.reset} Configure LLM provider + model`);
|
|
3248
3570
|
console.log(` ${c.cyan}setup${c.reset} Log in to agentaudit.dev (for report uploads)`);
|
|
3249
3571
|
console.log(` ${c.cyan}status${c.reset} Show current config + auth status`);
|
|
3572
|
+
console.log(` ${c.cyan}profile${c.reset} Your profile — rank, points, audit stats`);
|
|
3250
3573
|
console.log();
|
|
3251
3574
|
console.log(` ${c.bold}FLAGS${c.reset}`);
|
|
3252
3575
|
console.log(` ${c.dim}--json Machine-readable JSON output${c.reset}`);
|
|
@@ -3405,6 +3728,103 @@ async function main() {
|
|
|
3405
3728
|
return;
|
|
3406
3729
|
}
|
|
3407
3730
|
|
|
3731
|
+
if (command === 'profile') {
|
|
3732
|
+
const creds = loadCredentials();
|
|
3733
|
+
if (!creds) {
|
|
3734
|
+
console.log(` ${c.yellow}Not logged in.${c.reset}`);
|
|
3735
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to link your API key.${c.reset}`);
|
|
3736
|
+
console.log();
|
|
3737
|
+
return;
|
|
3738
|
+
}
|
|
3739
|
+
|
|
3740
|
+
const agentName = creds.agent_name || 'unknown';
|
|
3741
|
+
console.log(` ${c.bold}Profile${c.reset} ${c.cyan}${agentName}${c.reset}`);
|
|
3742
|
+
console.log();
|
|
3743
|
+
|
|
3744
|
+
try {
|
|
3745
|
+
process.stdout.write(` ${c.dim}Fetching profile data...${c.reset}`);
|
|
3746
|
+
const [agentRes, lbRes] = await Promise.all([
|
|
3747
|
+
fetch(`${REGISTRY_URL}/api/agents/${encodeURIComponent(agentName)}`, {
|
|
3748
|
+
headers: { 'Authorization': `Bearer ${creds.api_key}` },
|
|
3749
|
+
signal: AbortSignal.timeout(10_000),
|
|
3750
|
+
}).then(r => r.ok ? r.json() : null),
|
|
3751
|
+
fetch(`${REGISTRY_URL}/api/leaderboard?limit=100`, {
|
|
3752
|
+
signal: AbortSignal.timeout(10_000),
|
|
3753
|
+
}).then(r => r.ok ? r.json() : null),
|
|
3754
|
+
]);
|
|
3755
|
+
process.stdout.write('\r\x1b[K');
|
|
3756
|
+
|
|
3757
|
+
if (!agentRes) {
|
|
3758
|
+
console.log(` ${c.yellow}Could not fetch profile data.${c.reset}`);
|
|
3759
|
+
console.log(` ${c.dim}Your account may not have submitted any audits yet.${c.reset}`);
|
|
3760
|
+
console.log();
|
|
3761
|
+
console.log(` ${c.dim}Web profile: ${c.cyan}${REGISTRY_URL}/profile${c.reset}`);
|
|
3762
|
+
console.log();
|
|
3763
|
+
return;
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
let rank = null;
|
|
3767
|
+
if (Array.isArray(lbRes)) {
|
|
3768
|
+
const idx = lbRes.findIndex(e => e.agent_name === agentName);
|
|
3769
|
+
if (idx >= 0) rank = idx + 1;
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3772
|
+
// Update cache
|
|
3773
|
+
saveProfileCache({
|
|
3774
|
+
agent_name: agentName,
|
|
3775
|
+
rank,
|
|
3776
|
+
total_points: agentRes.total_points || 0,
|
|
3777
|
+
total_reports: agentRes.total_reports || 0,
|
|
3778
|
+
});
|
|
3779
|
+
|
|
3780
|
+
if (jsonMode) {
|
|
3781
|
+
console.log(JSON.stringify({
|
|
3782
|
+
agent_name: agentName,
|
|
3783
|
+
rank: rank ? { position: rank, total: lbRes?.length || 0 } : null,
|
|
3784
|
+
total_points: agentRes.total_points || 0,
|
|
3785
|
+
total_reports: agentRes.total_reports || 0,
|
|
3786
|
+
total_findings_submitted: agentRes.total_findings_submitted || 0,
|
|
3787
|
+
total_findings_confirmed: agentRes.total_findings_confirmed || 0,
|
|
3788
|
+
profile_url: `${REGISTRY_URL}/profile`,
|
|
3789
|
+
}, null, 2));
|
|
3790
|
+
return;
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
const boxW = 44;
|
|
3794
|
+
const rankStr = rank ? `#${rank} of ${lbRes.length}` : '—';
|
|
3795
|
+
const pts = agentRes.total_points || 0;
|
|
3796
|
+
const audits = agentRes.total_reports || 0;
|
|
3797
|
+
const findingsSubmitted = agentRes.total_findings_submitted || 0;
|
|
3798
|
+
const findingsConfirmed = agentRes.total_findings_confirmed || 0;
|
|
3799
|
+
const ptsBar = renderBar(pts, Math.max(pts, 1000), boxW - 16);
|
|
3800
|
+
|
|
3801
|
+
const contentLines = [
|
|
3802
|
+
'',
|
|
3803
|
+
`${c.bold}${c.cyan}${agentName}${c.reset}${' '.repeat(Math.max(1, boxW - 6 - agentName.length - rankStr.length))}${c.bold}${rankStr}${c.reset}`,
|
|
3804
|
+
'',
|
|
3805
|
+
`Points ${c.bold}${padLeft(fmtNum(pts), 8)}${c.reset}`,
|
|
3806
|
+
`Audits ${c.bold}${padLeft(fmtNum(audits), 8)}${c.reset}`,
|
|
3807
|
+
`Findings ${c.bold}${padLeft(fmtNum(findingsSubmitted), 8)}${c.reset} ${c.dim}(${fmtNum(findingsConfirmed)} confirmed)${c.reset}`,
|
|
3808
|
+
'',
|
|
3809
|
+
ptsBar,
|
|
3810
|
+
'',
|
|
3811
|
+
`${c.dim}${REGISTRY_URL}/profile${c.reset}`,
|
|
3812
|
+
`${c.dim}Key: ${creds.api_key.slice(0, 12)}...${c.reset}`,
|
|
3813
|
+
'',
|
|
3814
|
+
];
|
|
3815
|
+
const boxLines = drawBox('Profile', contentLines, boxW + 4);
|
|
3816
|
+
for (const line of boxLines) console.log(line);
|
|
3817
|
+
console.log();
|
|
3818
|
+
} catch (err) {
|
|
3819
|
+
process.stdout.write('\r\x1b[K');
|
|
3820
|
+
console.log(` ${c.yellow}Failed to fetch profile: ${err.message}${c.reset}`);
|
|
3821
|
+
console.log();
|
|
3822
|
+
console.log(` ${c.dim}Web profile: ${c.cyan}${REGISTRY_URL}/profile${c.reset}`);
|
|
3823
|
+
console.log();
|
|
3824
|
+
}
|
|
3825
|
+
return;
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3408
3828
|
if (command === 'model') {
|
|
3409
3829
|
const newModel = targets.filter(t => !t.startsWith('--'))[0];
|
|
3410
3830
|
const config = loadLlmConfig();
|