agentaudit 3.0.0 → 3.2.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.
Files changed (3) hide show
  1. package/README.md +26 -7
  2. package/cli.mjs +468 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -169,22 +169,41 @@ The MCP server finds credentials automatically from:
169
169
  ## CLI Reference
170
170
 
171
171
  ```
172
- agentaudit setup Register + configure API key
173
- agentaudit scan <url> [url...] Scan Git repositories
172
+ agentaudit discover Find local MCP servers + check registry
173
+ agentaudit scan <url> [url...] Quick static scan (regex, ~2s)
174
+ agentaudit audit <url> [url...] Deep LLM-powered audit (~30s)
174
175
  agentaudit check <name> Look up package in registry
175
- agentaudit --help Show help
176
+ agentaudit setup Register + configure API key
176
177
  ```
177
178
 
179
+ ### `scan` vs `audit`
180
+
181
+ | | `scan` | `audit` |
182
+ |--|--------|---------|
183
+ | **How** | Regex-based static analysis | LLM 3-pass analysis (UNDERSTAND → DETECT → CLASSIFY) |
184
+ | **Speed** | ~2 seconds | ~30 seconds |
185
+ | **Depth** | Pattern matching | Semantic code understanding |
186
+ | **Needs API key** | No | Yes (`ANTHROPIC_API_KEY` or `OPENAI_API_KEY`) |
187
+ | **Upload to registry** | No | Yes (with `agentaudit setup`) |
188
+
189
+ Use `scan` for quick checks, `audit` for thorough analysis.
190
+
178
191
  ### Examples
179
192
 
180
193
  ```bash
181
- # Scan a single repo
194
+ # Discover all MCP servers on your machine
195
+ agentaudit discover
196
+
197
+ # Quick scan
182
198
  agentaudit scan https://github.com/jlowin/fastmcp
183
199
 
184
- # Scan multiple repos at once
185
- agentaudit scan https://github.com/owner/repo1 https://github.com/owner/repo2
200
+ # Deep audit (requires ANTHROPIC_API_KEY or OPENAI_API_KEY)
201
+ agentaudit audit https://github.com/jlowin/fastmcp
202
+
203
+ # Export audit for manual LLM review (no API key needed)
204
+ agentaudit audit https://github.com/owner/repo --export
186
205
 
187
- # Check registry for existing audit
206
+ # Check registry
188
207
  agentaudit check mongodb-mcp-server
189
208
  ```
190
209
 
package/cli.mjs CHANGED
@@ -602,6 +602,440 @@ async function scanRepo(url) {
602
602
  return { slug, url, info, files: files.length, findings, registryData, duration };
603
603
  }
604
604
 
605
+ // ── Discover local MCP configs ──────────────────────────
606
+
607
+ function findMcpConfigs() {
608
+ const home = process.env.HOME || process.env.USERPROFILE || '';
609
+ const platform = process.platform;
610
+
611
+ // All known MCP config locations
612
+ const candidates = [
613
+ // Claude Desktop
614
+ { name: 'Claude Desktop', path: path.join(home, '.claude', 'mcp.json') },
615
+ { name: 'Claude Desktop', path: path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json') },
616
+ { name: 'Claude Desktop', path: path.join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json') },
617
+ { name: 'Claude Desktop', path: path.join(home, '.config', 'claude', 'claude_desktop_config.json') },
618
+ // Cursor
619
+ { name: 'Cursor', path: path.join(home, '.cursor', 'mcp.json') },
620
+ // Windsurf / Codeium
621
+ { name: 'Windsurf', path: path.join(home, '.codeium', 'windsurf', 'mcp_config.json') },
622
+ // VS Code
623
+ { name: 'VS Code', path: path.join(home, '.vscode', 'mcp.json') },
624
+ // Continue.dev
625
+ { name: 'Continue', path: path.join(home, '.continue', 'config.json') },
626
+ ];
627
+
628
+ // Also scan workspace .cursor/mcp.json, .vscode/mcp.json in cwd
629
+ const cwd = process.cwd();
630
+ candidates.push(
631
+ { name: 'Cursor (project)', path: path.join(cwd, '.cursor', 'mcp.json') },
632
+ { name: 'VS Code (project)', path: path.join(cwd, '.vscode', 'mcp.json') },
633
+ );
634
+
635
+ const found = [];
636
+ for (const c of candidates) {
637
+ if (fs.existsSync(c.path)) {
638
+ try {
639
+ const content = JSON.parse(fs.readFileSync(c.path, 'utf8'));
640
+ found.push({ ...c, content });
641
+ } catch {}
642
+ }
643
+ }
644
+ return found;
645
+ }
646
+
647
+ function extractServersFromConfig(config) {
648
+ // Handle both { mcpServers: {...} } and { servers: {...} } formats
649
+ const servers = config.mcpServers || config.servers || {};
650
+ const result = [];
651
+
652
+ for (const [name, serverConfig] of Object.entries(servers)) {
653
+ const info = {
654
+ name,
655
+ command: serverConfig.command || null,
656
+ args: serverConfig.args || [],
657
+ url: serverConfig.url || null,
658
+ sourceUrl: null,
659
+ };
660
+
661
+ // Try to extract source URL from args (common patterns)
662
+ const allArgs = [info.command, ...info.args].filter(Boolean).join(' ');
663
+
664
+ // npx package-name → npm package
665
+ const npxMatch = allArgs.match(/npx\s+(?:-y\s+)?(@?[a-z0-9][\w./-]*)/i);
666
+ if (npxMatch) info.npmPackage = npxMatch[1];
667
+
668
+ // node /path/to/something → try to find package.json
669
+ const nodePathMatch = allArgs.match(/node\s+["']?([^"'\s]+)/);
670
+ if (nodePathMatch) {
671
+ const scriptPath = nodePathMatch[1];
672
+ // Walk up to find package.json with repository
673
+ let dir = path.dirname(path.resolve(scriptPath));
674
+ for (let i = 0; i < 5; i++) {
675
+ const pkgPath = path.join(dir, 'package.json');
676
+ if (fs.existsSync(pkgPath)) {
677
+ try {
678
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
679
+ if (pkg.repository?.url) {
680
+ info.sourceUrl = pkg.repository.url.replace(/^git\+/, '').replace(/\.git$/, '');
681
+ }
682
+ if (pkg.name) info.npmPackage = pkg.name;
683
+ } catch {}
684
+ break;
685
+ }
686
+ const parent = path.dirname(dir);
687
+ if (parent === dir) break;
688
+ dir = parent;
689
+ }
690
+ }
691
+
692
+ // python/uvx with package name
693
+ const pyMatch = allArgs.match(/(?:uvx|pip run|python -m)\s+(@?[a-z0-9][\w./-]*)/i);
694
+ if (pyMatch) info.pyPackage = pyMatch[1];
695
+
696
+ result.push(info);
697
+ }
698
+ return result;
699
+ }
700
+
701
+ function serverSlug(server) {
702
+ // Try to derive a slug for registry lookup
703
+ if (server.npmPackage) return server.npmPackage.replace(/^@/, '').replace(/\//g, '-');
704
+ if (server.pyPackage) return server.pyPackage.replace(/[^a-z0-9-]/gi, '-');
705
+ return server.name.toLowerCase().replace(/[^a-z0-9-]/gi, '-');
706
+ }
707
+
708
+ async function discoverCommand() {
709
+ console.log(` ${c.bold}Discovering local MCP servers...${c.reset}`);
710
+ console.log();
711
+
712
+ const configs = findMcpConfigs();
713
+
714
+ if (configs.length === 0) {
715
+ console.log(` ${c.yellow}No MCP configurations found.${c.reset}`);
716
+ console.log(` ${c.dim}Searched: Claude Desktop, Cursor, Windsurf, VS Code${c.reset}`);
717
+ console.log();
718
+ console.log(` ${c.dim}MCP config locations:${c.reset}`);
719
+ console.log(` ${c.dim} Claude: ~/.claude/mcp.json${c.reset}`);
720
+ console.log(` ${c.dim} Cursor: ~/.cursor/mcp.json${c.reset}`);
721
+ console.log(` ${c.dim} Windsurf: ~/.codeium/windsurf/mcp_config.json${c.reset}`);
722
+ console.log(` ${c.dim} VS Code: ~/.vscode/mcp.json${c.reset}`);
723
+ console.log();
724
+ return;
725
+ }
726
+
727
+ let totalServers = 0;
728
+ let checkedServers = 0;
729
+ let auditedServers = 0;
730
+ let unauditedServers = 0;
731
+
732
+ for (const config of configs) {
733
+ const servers = extractServersFromConfig(config.content);
734
+ const serverCount = servers.length;
735
+ totalServers += serverCount;
736
+
737
+ const countLabel = serverCount === 0
738
+ ? `${c.dim}no servers${c.reset}`
739
+ : `found ${c.bold}${serverCount}${c.reset} server${serverCount > 1 ? 's' : ''}`;
740
+
741
+ console.log(`${icons.bullet} Scanning ${c.bold}${config.name}${c.reset} ${c.dim}${config.path}${c.reset} ${countLabel}`);
742
+
743
+ if (serverCount === 0) {
744
+ console.log();
745
+ continue;
746
+ }
747
+
748
+ console.log();
749
+
750
+ for (let i = 0; i < servers.length; i++) {
751
+ const server = servers[i];
752
+ const isLast = i === servers.length - 1;
753
+ const branch = isLast ? icons.treeLast : icons.tree;
754
+ const pipe = isLast ? ' ' : `${icons.pipe} `;
755
+
756
+ const slug = serverSlug(server);
757
+ checkedServers++;
758
+
759
+ // Registry lookup
760
+ const registryData = await checkRegistry(slug);
761
+
762
+ // Also try with server name directly
763
+ let regData = registryData;
764
+ if (!regData && slug !== server.name.toLowerCase()) {
765
+ regData = await checkRegistry(server.name.toLowerCase());
766
+ }
767
+
768
+ // Determine source display
769
+ let sourceLabel = '';
770
+ if (server.npmPackage) sourceLabel = `${c.dim}npm:${server.npmPackage}${c.reset}`;
771
+ else if (server.pyPackage) sourceLabel = `${c.dim}pip:${server.pyPackage}${c.reset}`;
772
+ else if (server.command) sourceLabel = `${c.dim}${[server.command, ...server.args.slice(0, 2)].join(' ')}${c.reset}`;
773
+
774
+ if (regData) {
775
+ auditedServers++;
776
+ const riskScore = regData.risk_score ?? regData.latest_risk_score ?? 0;
777
+ const hasOfficial = regData.has_official_audit;
778
+ console.log(`${branch} ${c.bold}${server.name}${c.reset} ${sourceLabel}`);
779
+ console.log(`${pipe} ${riskBadge(riskScore)} Risk ${riskScore} ${hasOfficial ? `${c.green}✔ official${c.reset} ` : ''}${c.dim}${REGISTRY_URL}/skills/${slug}${c.reset}`);
780
+ } else {
781
+ unauditedServers++;
782
+ console.log(`${branch} ${c.bold}${server.name}${c.reset} ${sourceLabel}`);
783
+ console.log(`${pipe} ${c.yellow}⚠ not audited${c.reset} ${c.dim}Run: agentaudit audit <source-url>${c.reset}`);
784
+ }
785
+
786
+ if (server.sourceUrl) {
787
+ console.log(`${pipe} ${c.dim}source: ${server.sourceUrl}${c.reset}`);
788
+ }
789
+ }
790
+
791
+ console.log();
792
+ }
793
+
794
+ // Summary
795
+ console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
796
+ console.log(` ${c.bold}Summary${c.reset} ${totalServers} server${totalServers !== 1 ? 's' : ''} across ${configs.length} config${configs.length !== 1 ? 's' : ''}`);
797
+ console.log();
798
+ if (auditedServers > 0) console.log(` ${icons.safe} ${c.green}${auditedServers} audited${c.reset}`);
799
+ if (unauditedServers > 0) console.log(` ${icons.caution} ${c.yellow}${unauditedServers} not audited${c.reset}`);
800
+ console.log();
801
+
802
+ if (unauditedServers > 0) {
803
+ console.log(` ${c.dim}To audit unaudited servers, run:${c.reset}`);
804
+ console.log(` ${c.cyan}agentaudit scan <github-url>${c.reset}`);
805
+ console.log();
806
+ }
807
+ }
808
+
809
+ // ── Audit command (deep LLM-powered) ────────────────────
810
+
811
+ function loadAuditPrompt() {
812
+ const promptPath = path.join(SKILL_DIR, 'prompts', 'audit-prompt.md');
813
+ if (fs.existsSync(promptPath)) return fs.readFileSync(promptPath, 'utf8');
814
+ return null;
815
+ }
816
+
817
+ async function auditRepo(url) {
818
+ const start = Date.now();
819
+ const slug = slugFromUrl(url);
820
+
821
+ console.log(`${icons.scan} ${c.bold}Auditing ${slug}${c.reset} ${c.dim}${url}${c.reset}`);
822
+ console.log(`${icons.pipe} ${c.dim}Deep LLM-powered analysis (3-pass: UNDERSTAND → DETECT → CLASSIFY)${c.reset}`);
823
+ console.log();
824
+
825
+ // Step 1: Clone
826
+ process.stdout.write(` ${c.dim}[1/4]${c.reset} Cloning repository...`);
827
+ const tmpDir = fs.mkdtempSync('/tmp/agentaudit-');
828
+ const repoPath = path.join(tmpDir, 'repo');
829
+ try {
830
+ execSync(`git clone --depth 1 "${url}" "${repoPath}" 2>/dev/null`, {
831
+ timeout: 30_000, stdio: 'pipe',
832
+ });
833
+ console.log(` ${c.green}done${c.reset}`);
834
+ } catch {
835
+ console.log(` ${c.red}failed${c.reset}`);
836
+ return null;
837
+ }
838
+
839
+ // Step 2: Collect files
840
+ process.stdout.write(` ${c.dim}[2/4]${c.reset} Collecting source files...`);
841
+ const files = collectFiles(repoPath);
842
+ console.log(` ${c.green}${files.length} files${c.reset}`);
843
+
844
+ // Step 3: Build audit payload
845
+ process.stdout.write(` ${c.dim}[3/4]${c.reset} Preparing audit payload...`);
846
+ const auditPrompt = loadAuditPrompt();
847
+
848
+ let codeBlock = '';
849
+ for (const file of files) {
850
+ codeBlock += `\n### FILE: ${file.path}\n\`\`\`\n${file.content}\n\`\`\`\n`;
851
+ }
852
+ console.log(` ${c.green}done${c.reset}`);
853
+
854
+ // Step 4: LLM Analysis
855
+ // Check for API keys to determine which LLM to use
856
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
857
+ const openaiKey = process.env.OPENAI_API_KEY;
858
+
859
+ if (!anthropicKey && !openaiKey) {
860
+ // No LLM API key — output the prepared audit for piping or MCP use
861
+ console.log();
862
+ console.log(` ${c.yellow}No LLM API key found.${c.reset} To run the audit automatically, set one of:`);
863
+ console.log(` ${c.dim}export ANTHROPIC_API_KEY=sk-ant-...${c.reset}`);
864
+ console.log(` ${c.dim}export OPENAI_API_KEY=sk-...${c.reset}`);
865
+ console.log();
866
+ console.log(` ${c.bold}Alternatives:${c.reset}`);
867
+ console.log(` ${c.dim}1.${c.reset} Use the MCP server in Claude/Cursor — your agent runs the audit automatically`);
868
+ console.log(` ${c.dim}2.${c.reset} Export for manual review: ${c.cyan}agentaudit audit ${url} --export${c.reset}`);
869
+ console.log();
870
+
871
+ // Check if --export flag
872
+ if (process.argv.includes('--export')) {
873
+ const exportPath = path.join(process.cwd(), `audit-${slug}.md`);
874
+ const exportContent = [
875
+ `# Security Audit: ${slug}`,
876
+ `**Source:** ${url}`,
877
+ `**Files:** ${files.length}`,
878
+ ``,
879
+ `## Audit Instructions`,
880
+ ``,
881
+ auditPrompt || '(audit prompt not found)',
882
+ ``,
883
+ `## Report Format`,
884
+ ``,
885
+ `After analysis, produce a JSON report:`,
886
+ '```json',
887
+ `{ "skill_slug": "${slug}", "source_url": "${url}", "risk_score": 0, "result": "safe", "findings": [] }`,
888
+ '```',
889
+ ``,
890
+ `## Source Code`,
891
+ ``,
892
+ codeBlock,
893
+ ].join('\n');
894
+ fs.writeFileSync(exportPath, exportContent);
895
+ console.log(` ${icons.safe} Exported to ${c.bold}${exportPath}${c.reset}`);
896
+ console.log(` ${c.dim}Paste this into any LLM (Claude, ChatGPT, etc.) for analysis${c.reset}`);
897
+ }
898
+
899
+ // Cleanup
900
+ try { execSync(`rm -rf "${tmpDir}"`, { stdio: 'pipe' }); } catch {}
901
+ return null;
902
+ }
903
+
904
+ // We have an API key — run LLM audit
905
+ process.stdout.write(` ${c.dim}[4/4]${c.reset} Running LLM analysis...`);
906
+
907
+ const systemPrompt = auditPrompt || 'You are a security auditor. Analyze the code and report findings as JSON.';
908
+ const userMessage = [
909
+ `Audit this package: **${slug}** (${url})`,
910
+ ``,
911
+ `After analysis, respond with ONLY a JSON object (no markdown, no explanation):`,
912
+ '```',
913
+ `{ "skill_slug": "${slug}", "source_url": "${url}", "package_type": "<mcp-server|agent-skill|library|cli-tool>",`,
914
+ ` "risk_score": <0-100>, "result": "<safe|caution|unsafe>", "max_severity": "<none|low|medium|high|critical>",`,
915
+ ` "findings_count": <n>, "findings": [{ "id": "...", "title": "...", "severity": "...", "category": "...",`,
916
+ ` "description": "...", "file": "...", "line": <n>, "remediation": "...", "confidence": "...", "is_by_design": false }] }`,
917
+ '```',
918
+ ``,
919
+ `## Source Code`,
920
+ codeBlock,
921
+ ].join('\n');
922
+
923
+ let report = null;
924
+
925
+ try {
926
+ if (anthropicKey) {
927
+ const res = await fetch('https://api.anthropic.com/v1/messages', {
928
+ method: 'POST',
929
+ headers: {
930
+ 'x-api-key': anthropicKey,
931
+ 'anthropic-version': '2023-06-01',
932
+ 'content-type': 'application/json',
933
+ },
934
+ body: JSON.stringify({
935
+ model: 'claude-sonnet-4-20250514',
936
+ max_tokens: 8192,
937
+ system: systemPrompt,
938
+ messages: [{ role: 'user', content: userMessage }],
939
+ }),
940
+ signal: AbortSignal.timeout(120_000),
941
+ });
942
+ const data = await res.json();
943
+ const text = data.content?.[0]?.text || '';
944
+ // Extract JSON from response
945
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
946
+ if (jsonMatch) report = JSON.parse(jsonMatch[0]);
947
+ } else if (openaiKey) {
948
+ const res = await fetch('https://api.openai.com/v1/chat/completions', {
949
+ method: 'POST',
950
+ headers: {
951
+ 'Authorization': `Bearer ${openaiKey}`,
952
+ 'Content-Type': 'application/json',
953
+ },
954
+ body: JSON.stringify({
955
+ model: 'gpt-4o',
956
+ max_tokens: 8192,
957
+ messages: [
958
+ { role: 'system', content: systemPrompt },
959
+ { role: 'user', content: userMessage },
960
+ ],
961
+ }),
962
+ signal: AbortSignal.timeout(120_000),
963
+ });
964
+ const data = await res.json();
965
+ const text = data.choices?.[0]?.message?.content || '';
966
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
967
+ if (jsonMatch) report = JSON.parse(jsonMatch[0]);
968
+ }
969
+
970
+ console.log(` ${c.green}done${c.reset} ${c.dim}(${elapsed(start)})${c.reset}`);
971
+ } catch (err) {
972
+ console.log(` ${c.red}failed${c.reset}`);
973
+ console.log(` ${c.red}${err.message}${c.reset}`);
974
+ try { execSync(`rm -rf "${tmpDir}"`, { stdio: 'pipe' }); } catch {}
975
+ return null;
976
+ }
977
+
978
+ // Cleanup repo
979
+ try { execSync(`rm -rf "${tmpDir}"`, { stdio: 'pipe' }); } catch {}
980
+
981
+ if (!report) {
982
+ console.log(` ${c.red}Could not parse LLM response as JSON${c.reset}`);
983
+ return null;
984
+ }
985
+
986
+ // Display results
987
+ console.log();
988
+ const riskScore = report.risk_score || 0;
989
+ console.log(` ${riskBadge(riskScore)} Risk ${riskScore}/100 ${c.bold}${report.result || 'unknown'}${c.reset}`);
990
+ console.log();
991
+
992
+ if (report.findings && report.findings.length > 0) {
993
+ console.log(` ${c.bold}Findings (${report.findings.length})${c.reset}`);
994
+ console.log();
995
+ for (const f of report.findings) {
996
+ const sc = severityColor(f.severity);
997
+ console.log(` ${severityIcon(f.severity)} ${sc}${(f.severity || '').toUpperCase().padEnd(8)}${c.reset} ${f.title}`);
998
+ if (f.file) console.log(` ${c.dim}${f.file}${f.line ? ':' + f.line : ''}${c.reset}`);
999
+ if (f.description) console.log(` ${c.dim}${f.description.slice(0, 120)}${c.reset}`);
1000
+ console.log();
1001
+ }
1002
+ } else {
1003
+ console.log(` ${c.green}No findings — package looks clean.${c.reset}`);
1004
+ console.log();
1005
+ }
1006
+
1007
+ // Upload to registry
1008
+ const creds = loadCredentials();
1009
+ if (creds) {
1010
+ process.stdout.write(` Uploading report to registry...`);
1011
+ try {
1012
+ const res = await fetch(`${REGISTRY_URL}/api/reports`, {
1013
+ method: 'POST',
1014
+ headers: {
1015
+ 'Authorization': `Bearer ${creds.api_key}`,
1016
+ 'Content-Type': 'application/json',
1017
+ },
1018
+ body: JSON.stringify(report),
1019
+ signal: AbortSignal.timeout(15_000),
1020
+ });
1021
+ if (res.ok) {
1022
+ const data = await res.json();
1023
+ console.log(` ${c.green}done${c.reset}`);
1024
+ console.log(` ${c.dim}Report: ${REGISTRY_URL}/skills/${slug}${c.reset}`);
1025
+ } else {
1026
+ console.log(` ${c.yellow}failed (HTTP ${res.status})${c.reset}`);
1027
+ }
1028
+ } catch (err) {
1029
+ console.log(` ${c.yellow}failed${c.reset}`);
1030
+ }
1031
+ } else {
1032
+ console.log(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to upload reports to the registry${c.reset}`);
1033
+ }
1034
+
1035
+ console.log();
1036
+ return report;
1037
+ }
1038
+
605
1039
  // ── Check command ───────────────────────────────────────
606
1040
 
607
1041
  async function checkPackage(name) {
@@ -611,7 +1045,7 @@ async function checkPackage(name) {
611
1045
  const data = await checkRegistry(name);
612
1046
  if (!data) {
613
1047
  console.log(` ${c.yellow}Not found${c.reset} — package "${name}" hasn't been audited yet.`);
614
- console.log(` ${c.dim}Run: agentaudit scan <repo-url> to audit it${c.reset}`);
1048
+ console.log(` ${c.dim}Run: agentaudit audit <repo-url> for a deep LLM audit${c.reset}`);
615
1049
  return;
616
1050
  }
617
1051
 
@@ -631,15 +1065,22 @@ async function main() {
631
1065
 
632
1066
  if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
633
1067
  banner();
634
- console.log(` ${c.bold}Usage:${c.reset}`);
635
- console.log(` agentaudit setup Register + configure API key`);
636
- console.log(` agentaudit scan <repo-url> [repo-url...] Scan repositories`);
637
- console.log(` agentaudit check <package-name> Look up in registry`);
1068
+ console.log(` ${c.bold}Commands:${c.reset}`);
1069
+ console.log();
1070
+ console.log(` ${c.cyan}agentaudit discover${c.reset} Find local MCP servers + check registry`);
1071
+ console.log(` ${c.cyan}agentaudit scan${c.reset} <url> [url...] Quick static scan (regex, local)`);
1072
+ console.log(` ${c.cyan}agentaudit audit${c.reset} <url> [url...] Deep LLM-powered security audit`);
1073
+ console.log(` ${c.cyan}agentaudit check${c.reset} <name> Look up package in registry`);
1074
+ console.log(` ${c.cyan}agentaudit setup${c.reset} Register + configure API key`);
1075
+ console.log();
1076
+ console.log(` ${c.bold}scan${c.reset} vs ${c.bold}audit${c.reset}:`);
1077
+ console.log(` ${c.dim}scan = fast regex-based static analysis (~2s)${c.reset}`);
1078
+ console.log(` ${c.dim}audit = deep LLM analysis with 3-pass methodology (~30s)${c.reset}`);
638
1079
  console.log();
639
1080
  console.log(` ${c.bold}Examples:${c.reset}`);
640
- console.log(` agentaudit setup`);
1081
+ console.log(` agentaudit discover`);
641
1082
  console.log(` agentaudit scan https://github.com/owner/repo`);
642
- console.log(` agentaudit scan repo1.git repo2.git repo3.git`);
1083
+ console.log(` agentaudit audit https://github.com/owner/repo`);
643
1084
  console.log(` agentaudit check fastmcp`);
644
1085
  console.log();
645
1086
  process.exit(0);
@@ -655,6 +1096,11 @@ async function main() {
655
1096
  return;
656
1097
  }
657
1098
 
1099
+ if (command === 'discover') {
1100
+ await discoverCommand();
1101
+ return;
1102
+ }
1103
+
658
1104
  if (command === 'check') {
659
1105
  if (targets.length === 0) {
660
1106
  console.log(` ${c.red}Error: package name required${c.reset}`);
@@ -667,6 +1113,8 @@ async function main() {
667
1113
  if (command === 'scan') {
668
1114
  if (targets.length === 0) {
669
1115
  console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
1116
+ console.log(` ${c.dim}Tip: use ${c.cyan}agentaudit discover${c.dim} to find & check locally installed MCP servers${c.reset}`);
1117
+ console.log(` ${c.dim}Tip: use ${c.cyan}agentaudit audit <url>${c.dim} for a deep LLM-powered audit${c.reset}`);
670
1118
  process.exit(1);
671
1119
  }
672
1120
 
@@ -682,6 +1130,19 @@ async function main() {
682
1130
  return;
683
1131
  }
684
1132
 
1133
+ if (command === 'audit') {
1134
+ const urls = targets.filter(t => !t.startsWith('--'));
1135
+ if (urls.length === 0) {
1136
+ console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
1137
+ process.exit(1);
1138
+ }
1139
+
1140
+ for (const url of urls) {
1141
+ await auditRepo(url);
1142
+ }
1143
+ return;
1144
+ }
1145
+
685
1146
  console.log(` ${c.red}Unknown command: ${command}${c.reset}`);
686
1147
  console.log(` ${c.dim}Run agentaudit --help for usage${c.reset}`);
687
1148
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {