agentaudit 3.9.19 → 3.9.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/cli.mjs +204 -89
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -1,18 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * AgentAudit CLI — Security scanner for AI packages
3
+ * AgentAudit CLI — Security scanner for AI tools
4
4
  *
5
- * Usage:
6
- * agentaudit Discover local MCP servers
7
- * agentaudit discover [--quick|--deep] Find MCP servers in AI editors
8
- * agentaudit scan <repo-url> [--deep] Quick scan (or deep audit with --deep)
9
- * agentaudit audit <repo-url> Deep LLM-powered security audit
10
- * agentaudit lookup <name> Look up package in registry
11
- * agentaudit check <name|url> Lookup + auto-audit if not found
12
- * agentaudit status Check configured API keys + providers
13
- * agentaudit setup Register + configure API key
5
+ * Scan & Audit: scan <url>, audit <url>, discover
6
+ * Registry: check <name|url>, lookup <name>
7
+ * Setup: status, setup, config
14
8
  *
15
- * Global flags: --json, --quiet, --no-color
9
+ * Global flags: --json, --quiet, --no-color, --provider, --debug, --export
16
10
  */
17
11
 
18
12
  import fs from 'fs';
@@ -27,7 +21,7 @@ const SKILL_DIR = path.resolve(__dirname);
27
21
  const REGISTRY_URL = 'https://agentaudit.dev';
28
22
 
29
23
  // ── Provider resolution ────
30
- function resolveProvider(preferred, keys) {
24
+ function resolveProvider(flagOverride, keys) {
31
25
  const orModel = process.env.OPENROUTER_MODEL || 'anthropic/claude-sonnet-4';
32
26
  const ollamaModel = process.env.OLLAMA_MODEL || 'llama3.1';
33
27
  const ollamaHost = process.env.OLLAMA_HOST || 'http://localhost:11434';
@@ -45,6 +39,12 @@ function resolveProvider(preferred, keys) {
45
39
  // Aliases
46
40
  const aliases = { claude: 'anthropic', gpt: 'openai', 'gpt-4o': 'openai', 'gpt4': 'openai', or: 'openrouter', local: 'ollama' };
47
41
 
42
+ // Priority: --provider flag > AGENTAUDIT_PROVIDER env > config file > auto-detect
43
+ const preferred = flagOverride
44
+ || process.env.AGENTAUDIT_PROVIDER?.toLowerCase()
45
+ || loadConfig()?.preferred_provider
46
+ || null;
47
+
48
48
  if (preferred) {
49
49
  const resolved = aliases[preferred] || preferred;
50
50
  const p = providers[resolved];
@@ -58,6 +58,7 @@ function resolveProvider(preferred, keys) {
58
58
  // ── Global flags (set in main before command routing) ────
59
59
  let jsonMode = false;
60
60
  let quietMode = false;
61
+ let modelOverride = null; // --model flag or AGENTAUDIT_MODEL env or config
61
62
 
62
63
  // ── ANSI Colors (respects NO_COLOR and --no-color) ───────
63
64
 
@@ -129,6 +130,24 @@ function saveCredentials(data) {
129
130
  } catch {}
130
131
  }
131
132
 
133
+ const USER_CONFIG_FILE = path.join(USER_CRED_DIR, 'config.json');
134
+
135
+ function loadConfig() {
136
+ try {
137
+ if (fs.existsSync(USER_CONFIG_FILE)) {
138
+ return JSON.parse(fs.readFileSync(USER_CONFIG_FILE, 'utf8'));
139
+ }
140
+ } catch {}
141
+ return {};
142
+ }
143
+
144
+ function saveConfig(data) {
145
+ const existing = loadConfig();
146
+ const merged = { ...existing, ...data };
147
+ fs.mkdirSync(USER_CRED_DIR, { recursive: true });
148
+ fs.writeFileSync(USER_CONFIG_FILE, JSON.stringify(merged, null, 2), { mode: 0o600 });
149
+ }
150
+
132
151
  function askQuestion(question) {
133
152
  const rl = createInterface({ input: process.stdin, output: process.stdout });
134
153
  return new Promise(resolve => rl.question(question, answer => { rl.close(); resolve(answer.trim()); }));
@@ -312,6 +331,28 @@ async function setupCommand() {
312
331
  console.log();
313
332
  }
314
333
 
334
+ // ── Structured error output ─────────────────────────────
335
+
336
+ function emitError(code, message, hint, exitCode = 2) {
337
+ if (jsonMode) {
338
+ process.stderr.write(JSON.stringify({ error: true, code, message, hint: hint || undefined, exitCode }) + '\n');
339
+ }
340
+ process.exitCode = exitCode;
341
+ }
342
+
343
+ // ── Levenshtein distance for typo correction ────────────
344
+
345
+ function levenshtein(a, b) {
346
+ const m = a.length, n = b.length;
347
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
348
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
349
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
350
+ for (let i = 1; i <= m; i++)
351
+ for (let j = 1; j <= n; j++)
352
+ dp[i][j] = Math.min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + (a[i-1] !== b[j-1] ? 1 : 0));
353
+ return dp[m][n];
354
+ }
355
+
315
356
  // ── Helpers ──────────────────────────────────────────────
316
357
 
317
358
  function getVersion() {
@@ -324,8 +365,8 @@ function getVersion() {
324
365
  function banner() {
325
366
  if (quietMode || jsonMode) return;
326
367
  console.log();
327
- console.log(` ${c.bold}${c.cyan}AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset}`);
328
- console.log(` ${c.dim}Security scanner for AI packages${c.reset}`);
368
+ console.log(` 🛡 ${c.bold}${c.cyan}AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset}`);
369
+ console.log(` ${c.dim}Security scanner for AI tools${c.reset}`);
329
370
  console.log();
330
371
  }
331
372
 
@@ -1446,7 +1487,7 @@ async function auditRepo(url) {
1446
1487
  'content-type': 'application/json',
1447
1488
  },
1448
1489
  body: JSON.stringify({
1449
- model: 'claude-sonnet-4-20250514',
1490
+ model: modelOverride || 'claude-sonnet-4-20250514',
1450
1491
  max_tokens: 8192,
1451
1492
  system: systemPrompt,
1452
1493
  messages: [{ role: 'user', content: userMessage }],
@@ -1469,22 +1510,22 @@ async function auditRepo(url) {
1469
1510
  switch (resolvedProvider.id) {
1470
1511
  case 'openrouter':
1471
1512
  apiUrl = 'https://openrouter.ai/api/v1/chat/completions';
1472
- modelName = process.env.OPENROUTER_MODEL || 'anthropic/claude-sonnet-4';
1513
+ modelName = modelOverride || process.env.OPENROUTER_MODEL || 'anthropic/claude-sonnet-4';
1473
1514
  authHeaders = { 'Authorization': `Bearer ${resolvedProvider.key}`, 'HTTP-Referer': 'https://agentaudit.dev', 'X-Title': 'AgentAudit' };
1474
1515
  break;
1475
1516
  case 'ollama':
1476
1517
  apiUrl = `${resolvedProvider.host}/v1/chat/completions`;
1477
- modelName = resolvedProvider.model;
1518
+ modelName = modelOverride || resolvedProvider.model;
1478
1519
  authHeaders = {};
1479
1520
  break;
1480
1521
  case 'custom':
1481
1522
  apiUrl = resolvedProvider.url.endsWith('/chat/completions') ? resolvedProvider.url : `${resolvedProvider.url.replace(/\/$/, '')}/chat/completions`;
1482
- modelName = resolvedProvider.model;
1523
+ modelName = modelOverride || resolvedProvider.model;
1483
1524
  authHeaders = resolvedProvider.key ? { 'Authorization': `Bearer ${resolvedProvider.key}` } : {};
1484
1525
  break;
1485
1526
  default: // openai
1486
1527
  apiUrl = 'https://api.openai.com/v1/chat/completions';
1487
- modelName = 'gpt-4o';
1528
+ modelName = modelOverride || 'gpt-4o';
1488
1529
  authHeaders = { 'Authorization': `Bearer ${resolvedProvider.key}` };
1489
1530
  }
1490
1531
 
@@ -1606,8 +1647,11 @@ async function checkPackage(name) {
1606
1647
  console.log();
1607
1648
  return await auditRepo(name);
1608
1649
  }
1609
- console.log(` ${c.yellow}Not found${c.reset} — package "${name}" hasn't been audited yet.`);
1610
- console.log(` ${c.dim}Run: agentaudit audit <repo-url> for a deep LLM audit${c.reset}`);
1650
+ console.log(` ${c.yellow}Not found${c.reset} — "${name}" hasn't been audited yet.`);
1651
+ console.log();
1652
+ console.log(` ${c.dim}Next steps:${c.reset}`);
1653
+ console.log(` ${c.cyan}agentaudit audit <repo-url>${c.reset} ${c.dim}Run a deep audit yourself${c.reset}`);
1654
+ console.log(` ${c.cyan}agentaudit scan <repo-url>${c.reset} ${c.dim}Quick static check (no API key)${c.reset}`);
1611
1655
  }
1612
1656
  return null;
1613
1657
  }
@@ -1688,9 +1732,25 @@ async function main() {
1688
1732
  quietMode = rawArgs.includes('--quiet') || rawArgs.includes('-q');
1689
1733
  // --no-color already handled at top level for `c` object
1690
1734
 
1735
+ // --model flag: --model=<name> or --model <name>
1736
+ const modelFlagIdx = rawArgs.findIndex(a => a === '--model');
1737
+ const modelFlagEq = rawArgs.find(a => a.startsWith('--model='));
1738
+ modelOverride = modelFlagEq?.split('=')[1]
1739
+ || (modelFlagIdx >= 0 ? rawArgs[modelFlagIdx + 1] : null)
1740
+ || process.env.AGENTAUDIT_MODEL
1741
+ || loadConfig()?.preferred_model
1742
+ || null;
1743
+
1691
1744
  // Strip global flags from args
1692
1745
  const globalFlags = new Set(['--json', '--quiet', '-q', '--no-color']);
1693
- const args = rawArgs.filter(a => !globalFlags.has(a));
1746
+ let args = rawArgs.filter(a => !globalFlags.has(a));
1747
+ // Strip --model and its value
1748
+ args = args.filter((a, i, arr) => {
1749
+ if (a.startsWith('--model=')) return false;
1750
+ if (a === '--model') { arr[i + 1] = '__skip__'; return false; }
1751
+ if (a === '__skip__') return false;
1752
+ return true;
1753
+ });
1694
1754
 
1695
1755
  if (args[0] === '-v' || args[0] === '--version') {
1696
1756
  console.log(`agentaudit ${getVersion()}`);
@@ -1699,52 +1759,44 @@ async function main() {
1699
1759
 
1700
1760
  if (args[0] === '--help' || args[0] === '-h') {
1701
1761
  banner();
1702
- console.log(` ${c.bold}Commands:${c.reset}`);
1762
+ console.log(` ${c.bold}USAGE${c.reset}`);
1763
+ console.log(` ${c.cyan}agentaudit${c.reset} <command> [options]`);
1703
1764
  console.log();
1704
- console.log(` ${c.cyan}agentaudit${c.reset} Discover MCP servers (same as discover)`);
1705
- console.log(` ${c.cyan}agentaudit discover${c.reset} Find MCP servers in your AI editors (Cursor, Claude, VS Code, Windsurf)`);
1706
- console.log(` ${c.cyan}agentaudit discover --quick${c.reset} Discover + auto-scan all servers`);
1707
- console.log(` ${c.cyan}agentaudit discover --deep${c.reset} Discover + select servers to deep-audit`);
1708
- console.log(` ${c.cyan}agentaudit scan${c.reset} <url> [url...] Quick static scan (regex, local)`);
1709
- console.log(` ${c.cyan}agentaudit scan${c.reset} <url> ${c.dim}--deep${c.reset} Deep audit (same as audit)`);
1710
- console.log(` ${c.cyan}agentaudit audit${c.reset} <url> [url...] Deep LLM-powered security audit`);
1711
- console.log(` ${c.cyan}agentaudit lookup${c.reset} <name> Look up package in registry`);
1712
- console.log(` ${c.cyan}agentaudit check${c.reset} <name|url> Lookup + auto-audit if not found`);
1713
- console.log(` ${c.cyan}agentaudit status${c.reset} Check API keys + active provider`);
1714
- console.log(` ${c.cyan}agentaudit setup${c.reset} Register + configure API key`);
1765
+ console.log(` ${c.bold}SCAN & AUDIT${c.reset}`);
1766
+ console.log(` ${c.cyan}scan${c.reset} <url> [url...] Quick static analysis ${c.dim}(~2s, no API key)${c.reset}`);
1767
+ console.log(` ${c.cyan}audit${c.reset} <url> [url...] Deep LLM security audit ${c.dim}(~30s)${c.reset}`);
1768
+ console.log(` ${c.cyan}discover${c.reset} Find MCP servers in your editors`);
1715
1769
  console.log();
1716
- console.log(` ${c.bold}Global flags:${c.reset}`);
1717
- console.log(` ${c.dim}--json Output JSON to stdout (machine-readable)${c.reset}`);
1718
- console.log(` ${c.dim}--quiet Suppress banner and tree visualization${c.reset}`);
1719
- console.log(` ${c.dim}--no-color Disable ANSI colors (also: NO_COLOR env)${c.reset}`);
1720
- console.log(` ${c.dim}--provider Force LLM provider (anthropic, openai, openrouter)${c.reset}`);
1770
+ console.log(` ${c.bold}REGISTRY${c.reset}`);
1771
+ console.log(` ${c.cyan}check${c.reset} <name|url> Look up or auto-audit package`);
1772
+ console.log(` ${c.cyan}lookup${c.reset} <name> Look up package in registry`);
1721
1773
  console.log();
1722
- console.log(` ${c.bold}Quick Scan${c.reset} vs ${c.bold}Deep Audit${c.reset}:`);
1723
- console.log(` ${c.dim}scan = fast regex-based static analysis (~2s)${c.reset}`);
1724
- console.log(` ${c.dim}audit = deep LLM analysis with 3-pass methodology (~30s)${c.reset}`);
1774
+ console.log(` ${c.bold}SETUP${c.reset}`);
1775
+ console.log(` ${c.cyan}status${c.reset} Check providers & API keys`);
1776
+ console.log(` ${c.cyan}setup${c.reset} Register & configure`);
1777
+ console.log(` ${c.cyan}config set${c.reset} <key> <value> Set default provider/options`);
1725
1778
  console.log();
1726
- console.log(` ${c.bold}Exit codes:${c.reset}`);
1727
- console.log(` ${c.dim}0 = clean / success 1 = findings detected 2 = error${c.reset}`);
1779
+ console.log(` ${c.bold}OPTIONS${c.reset}`);
1780
+ console.log(` ${c.dim}--json Machine-readable JSON output${c.reset}`);
1781
+ console.log(` ${c.dim}--quiet Suppress banner${c.reset}`);
1782
+ console.log(` ${c.dim}--no-color Disable colors ${c.reset}${c.dim}(also: NO_COLOR=1)${c.reset}`);
1783
+ console.log(` ${c.dim}--provider <p> Force provider ${c.reset}${c.dim}(anthropic|openai|openrouter|ollama|custom)${c.reset}`);
1784
+ console.log(` ${c.dim}--model <m> Override model ${c.reset}${c.dim}(e.g. gpt-4o-mini, claude-3.5-sonnet)${c.reset}`);
1785
+ console.log(` ${c.dim}--export Export audit payload to markdown${c.reset}`);
1786
+ console.log(` ${c.dim}--debug Show raw LLM response on errors${c.reset}`);
1728
1787
  console.log();
1729
- console.log(` ${c.bold}Examples:${c.reset}`);
1730
- console.log(` agentaudit`);
1731
- console.log(` agentaudit discover --quick`);
1732
- console.log(` agentaudit scan https://github.com/owner/repo`);
1733
- console.log(` agentaudit audit https://github.com/owner/repo`);
1734
- console.log(` agentaudit lookup fastmcp --json`);
1788
+ console.log(` ${c.bold}EXAMPLES${c.reset}`);
1789
+ console.log(` ${c.dim}$${c.reset} agentaudit scan https://github.com/owner/repo`);
1790
+ console.log(` ${c.dim}$${c.reset} agentaudit audit https://github.com/owner/repo`);
1791
+ console.log(` ${c.dim}$${c.reset} agentaudit check fastmcp`);
1792
+ console.log(` ${c.dim}$${c.reset} agentaudit status`);
1735
1793
  console.log();
1736
- console.log(` ${c.bold}For deep audits,${c.reset} set an LLM provider (any one):`);
1737
- console.log(` ${c.dim}ANTHROPIC_API_KEY Anthropic Claude (recommended)${c.reset}`);
1738
- console.log(` ${c.dim}OPENAI_API_KEY OpenAI GPT-4o${c.reset}`);
1739
- console.log(` ${c.dim}OPENROUTER_API_KEY 200+ models (+ OPENROUTER_MODEL)${c.reset}`);
1740
- console.log(` ${c.dim}OLLAMA_MODEL Local Ollama (+ OLLAMA_HOST)${c.reset}`);
1741
- console.log(` ${c.dim}LLM_API_URL Any OpenAI-compatible API (+ LLM_API_KEY, LLM_MODEL)${c.reset}`);
1742
- console.log();
1743
- console.log(` ${c.dim}Run ${c.cyan}agentaudit status${c.dim} to check your configured providers.${c.reset}`);
1744
- console.log();
1745
- console.log(` ${c.bold}Or use as MCP server${c.reset} in Cursor/Claude ${c.dim}(no extra API key needed):${c.reset}`);
1746
- console.log(` ${c.dim}Add to your MCP config:${c.reset}`);
1747
- console.log(` ${c.dim}{ "agentaudit": { "command": "npx", "args": ["-y", "agentaudit"] } }${c.reset}`);
1794
+ console.log(` ${c.bold}PROVIDERS${c.reset} ${c.dim}(set any one for deep audits)${c.reset}`);
1795
+ console.log(` ${c.dim}ANTHROPIC_API_KEY · OPENAI_API_KEY · OPENROUTER_API_KEY · OLLAMA_MODEL · LLM_API_URL${c.reset}`);
1796
+ console.log(` ${c.dim}Set default: AGENTAUDIT_PROVIDER=openai AGENTAUDIT_MODEL=gpt-4o-mini${c.reset}`);
1797
+ console.log(` ${c.dim}Or persist: agentaudit config set provider openai${c.reset}`);
1798
+ console.log(` ${c.dim} agentaudit config set model gpt-4o-mini${c.reset}`);
1799
+ console.log(` ${c.dim}Run ${c.cyan}agentaudit status${c.dim} to check configuration.${c.reset}`);
1748
1800
  console.log();
1749
1801
  process.exitCode = 0; return;
1750
1802
  }
@@ -1774,8 +1826,8 @@ async function main() {
1774
1826
 
1775
1827
  const checks = [
1776
1828
  { name: 'Anthropic', env: 'ANTHROPIC_API_KEY', key: keys.anthropicKey, testUrl: 'https://api.anthropic.com/v1/messages', testHeaders: (k) => ({ 'x-api-key': k, 'anthropic-version': '2023-06-01', 'content-type': 'application/json' }), testBody: JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] }) },
1777
- { name: 'OpenAI', env: 'OPENAI_API_KEY', key: keys.openaiKey, testUrl: 'https://api.openai.com/v1/models', testHeaders: (k) => ({ 'Authorization': `Bearer ${k}` }), testBody: null },
1778
- { name: 'OpenRouter', env: 'OPENROUTER_API_KEY', key: keys.openrouterKey, testUrl: 'https://openrouter.ai/api/v1/models', testHeaders: (k) => ({ 'Authorization': `Bearer ${k}` }), testBody: null },
1829
+ { name: 'OpenAI', env: 'OPENAI_API_KEY', key: keys.openaiKey, testUrl: 'https://api.openai.com/v1/chat/completions', testHeaders: (k) => ({ 'Authorization': `Bearer ${k}`, 'Content-Type': 'application/json' }), testBody: JSON.stringify({ model: 'gpt-4o-mini', max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] }) },
1830
+ { name: 'OpenRouter', env: 'OPENROUTER_API_KEY', key: keys.openrouterKey, testUrl: 'https://openrouter.ai/api/v1/chat/completions', testHeaders: (k) => ({ 'Authorization': `Bearer ${k}`, 'Content-Type': 'application/json', 'HTTP-Referer': 'https://agentaudit.dev', 'X-Title': 'AgentAudit' }), testBody: JSON.stringify({ model: 'openai/gpt-4o-mini', max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] }) },
1779
1831
  { name: 'Ollama', env: 'OLLAMA_MODEL', key: ollamaModel, testUrl: `${ollamaHost}/api/tags`, testHeaders: () => ({}), testBody: null },
1780
1832
  { name: 'Custom', env: 'LLM_API_URL', key: customUrl, testUrl: customUrl ? `${customUrl.replace(/\/$/, '')}/models` : null, testHeaders: (k) => process.env.LLM_API_KEY ? ({ 'Authorization': `Bearer ${process.env.LLM_API_KEY}` }) : {}, testBody: null },
1781
1833
  ];
@@ -1798,8 +1850,25 @@ async function main() {
1798
1850
  process.stdout.write(`\r ${c.green}●${c.reset} ${p.name.padEnd(12)} ${c.dim}${masked}${c.reset} ${c.green}valid ✓${c.reset} \n`);
1799
1851
  } else {
1800
1852
  const body = await res.json().catch(() => ({}));
1801
- const errMsg = body?.error?.message || `HTTP ${res.status}`;
1802
- process.stdout.write(`\r ${c.red}●${c.reset} ${p.name.padEnd(12)} ${c.dim}${masked}${c.reset} ${c.red}invalid ✗${c.reset} ${c.dim}(${errMsg})${c.reset} \n`);
1853
+ const rawMsg = body?.error?.message || body?.message || `HTTP ${res.status}`;
1854
+ // Detect specific error types for clearer messages
1855
+ const lcMsg = rawMsg.toLowerCase();
1856
+ let errMsg = rawMsg;
1857
+ let hint = '';
1858
+ if (lcMsg.includes('credit') || lcMsg.includes('balance') || lcMsg.includes('quota') || lcMsg.includes('billing') || lcMsg.includes('exceeded') || lcMsg.includes('insufficient')) {
1859
+ errMsg = 'no credits';
1860
+ if (p.name === 'Anthropic') hint = `\n ${c.dim}└─ Add credits: console.anthropic.com/settings/plans${c.reset}`;
1861
+ else if (p.name === 'OpenAI') hint = `\n ${c.dim}└─ Check usage: platform.openai.com/usage${c.reset}`;
1862
+ else if (p.name === 'OpenRouter') hint = `\n ${c.dim}└─ Check balance: openrouter.ai/credits${c.reset}`;
1863
+ } else if (res.status === 401 || lcMsg.includes('invalid') || lcMsg.includes('unauthorized') || lcMsg.includes('authentication')) {
1864
+ errMsg = 'invalid key';
1865
+ if (p.name === 'Anthropic') hint = `\n ${c.dim}└─ Check key: console.anthropic.com/settings/keys${c.reset}`;
1866
+ else if (p.name === 'OpenAI') hint = `\n ${c.dim}└─ Check key: platform.openai.com/api-keys${c.reset}`;
1867
+ } else if (res.status === 429) {
1868
+ errMsg = 'rate limited';
1869
+ hint = `\n ${c.dim}└─ Try again in a moment${c.reset}`;
1870
+ }
1871
+ process.stdout.write(`\r ${c.red}●${c.reset} ${p.name.padEnd(12)} ${c.dim}${masked}${c.reset} ${c.red}✖ ${errMsg}${c.reset}${hint} \n`);
1803
1872
  }
1804
1873
  } catch (e) {
1805
1874
  process.stdout.write(`\r ${c.red}●${c.reset} ${p.name.padEnd(12)} ${c.dim}${masked}${c.reset} ${c.red}error ✗${c.reset} ${c.dim}(${e.message})${c.reset} \n`);
@@ -1809,10 +1878,15 @@ async function main() {
1809
1878
  const resolved = resolveProvider(null, keys);
1810
1879
  console.log();
1811
1880
  if (resolved) {
1812
- console.log(` ${c.bold}Active provider:${c.reset} ${resolved.label}`);
1813
- console.log(` ${c.dim}Override with: --provider=openai | --provider=openrouter | --provider=anthropic${c.reset}`);
1881
+ const activeModel = modelOverride || process.env.AGENTAUDIT_MODEL || loadConfig()?.preferred_model;
1882
+ console.log(` ${c.bold}Active:${c.reset} ${c.green}${resolved.label}${c.reset}${activeModel ? ` ${c.dim}model: ${activeModel}${c.reset}` : ''}`);
1883
+ console.log(` ${c.dim}Override: --provider=<name> --model=<name>${c.reset}`);
1884
+ console.log(` ${c.dim}Set default: agentaudit config set provider <name>${c.reset}`);
1885
+ console.log(` ${c.dim} agentaudit config set model <name>${c.reset}`);
1814
1886
  } else {
1815
- console.log(` ${c.yellow}No LLM provider configured.${c.reset} Set one of the API keys above for deep audits.`);
1887
+ console.log(` ${c.yellow}No working LLM provider.${c.reset} Deep audits require one.`);
1888
+ console.log(` ${c.dim}Set a key: export ANTHROPIC_API_KEY=sk-ant-...${c.reset}`);
1889
+ console.log(` ${c.dim}Or scan without LLM: agentaudit scan <url>${c.reset}`);
1816
1890
  }
1817
1891
 
1818
1892
  // AgentAudit registry key
@@ -1836,10 +1910,53 @@ async function main() {
1836
1910
  return;
1837
1911
  }
1838
1912
 
1913
+ if (command === 'config') {
1914
+ const subCmd = targets[0];
1915
+ if (subCmd === 'set' && targets[1] === 'provider' && targets[2]) {
1916
+ const validProviders = ['anthropic', 'openai', 'openrouter', 'ollama', 'custom', 'claude', 'gpt'];
1917
+ const val = targets[2].toLowerCase();
1918
+ if (!validProviders.includes(val)) {
1919
+ console.log(` ${c.red}✖ Unknown provider: ${val}${c.reset}`);
1920
+ console.log(` ${c.dim}Valid: anthropic, openai, openrouter, ollama, custom${c.reset}`);
1921
+ process.exitCode = 2; return;
1922
+ }
1923
+ saveConfig({ preferred_provider: val });
1924
+ console.log(` ${c.green}✔${c.reset} Default provider set to: ${c.bold}${val}${c.reset}`);
1925
+ console.log(` ${c.dim}Override per-command: --provider=<name>${c.reset}`);
1926
+ console.log(` ${c.dim}Or env: AGENTAUDIT_PROVIDER=<name>${c.reset}`);
1927
+ } else if (subCmd === 'set' && targets[1] === 'model' && targets[2]) {
1928
+ const val = targets[2];
1929
+ saveConfig({ preferred_model: val });
1930
+ console.log(` ${c.green}✔${c.reset} Default model set to: ${c.bold}${val}${c.reset}`);
1931
+ console.log(` ${c.dim}Override per-command: --model=<name>${c.reset}`);
1932
+ console.log(` ${c.dim}Or env: AGENTAUDIT_MODEL=<name>${c.reset}`);
1933
+ } else if (subCmd === 'get' || !subCmd) {
1934
+ const cfg = loadConfig();
1935
+ console.log(` ${c.bold}Config:${c.reset} ${USER_CONFIG_FILE}`);
1936
+ if (Object.keys(cfg).length === 0) {
1937
+ console.log(` ${c.dim}(empty — using defaults)${c.reset}`);
1938
+ } else {
1939
+ for (const [k, v] of Object.entries(cfg)) {
1940
+ console.log(` ${c.dim}${k}:${c.reset} ${v}`);
1941
+ }
1942
+ }
1943
+ } else if (subCmd === 'reset') {
1944
+ try { fs.unlinkSync(USER_CONFIG_FILE); } catch {}
1945
+ console.log(` ${c.green}✔${c.reset} Config reset to defaults.`);
1946
+ } else {
1947
+ console.log(` ${c.red}✖ Unknown config command${c.reset}`);
1948
+ console.log(` ${c.dim}Usage: agentaudit config set provider <name>${c.reset}`);
1949
+ console.log(` ${c.dim} agentaudit config get${c.reset}`);
1950
+ console.log(` ${c.dim} agentaudit config reset${c.reset}`);
1951
+ }
1952
+ return;
1953
+ }
1954
+
1839
1955
  if (command === 'lookup' || command === 'check') {
1840
1956
  const names = targets.filter(t => !t.startsWith('--'));
1841
1957
  if (names.length === 0) {
1842
- console.log(` ${c.red}Error: package name required${c.reset}`);
1958
+ console.log(` ${c.red}✖ Package name or URL required${c.reset}`);
1959
+ console.log(` ${c.dim}Usage: agentaudit check <name|url>${c.reset}`);
1843
1960
  process.exitCode = 2;
1844
1961
  return;
1845
1962
  }
@@ -1852,31 +1969,18 @@ async function main() {
1852
1969
  console.log(JSON.stringify(results.length === 1 ? (results[0] || { error: 'not_found' }) : results, null, 2));
1853
1970
  }
1854
1971
  process.exitCode = 0; return;
1855
- return;
1856
1972
  }
1857
1973
 
1858
1974
  if (command === 'scan') {
1859
- const deepFlag = targets.includes('--deep');
1860
1975
  const urls = targets.filter(t => !t.startsWith('--'));
1861
1976
  if (urls.length === 0) {
1862
- console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
1863
- console.log(` ${c.dim}Tip: use ${c.cyan}agentaudit discover${c.dim} to find & check locally installed MCP servers${c.reset}`);
1864
- console.log(` ${c.dim}Tip: use ${c.cyan}agentaudit audit <url>${c.dim} for a deep LLM-powered audit${c.reset}`);
1977
+ console.log(` ${c.red}✖ Repository URL required${c.reset}`);
1978
+ console.log(` ${c.dim}Usage: agentaudit scan <url>${c.reset}`);
1979
+ console.log(` ${c.dim}Or discover local servers: ${c.cyan}agentaudit discover${c.reset}`);
1865
1980
  process.exitCode = 2;
1866
1981
  return;
1867
1982
  }
1868
1983
 
1869
- // --deep redirects to audit flow
1870
- if (deepFlag) {
1871
- let hasFindings = false;
1872
- for (const url of urls) {
1873
- const report = await auditRepo(url);
1874
- if (report?.findings?.length > 0) hasFindings = true;
1875
- }
1876
- process.exitCode = hasFindings ? 1 : 0;
1877
- return;
1878
- }
1879
-
1880
1984
  const results = [];
1881
1985
  let hadErrors = false;
1882
1986
  for (const url of urls) {
@@ -1913,7 +2017,8 @@ async function main() {
1913
2017
  if (command === 'audit') {
1914
2018
  const urls = targets.filter(t => !t.startsWith('--'));
1915
2019
  if (urls.length === 0) {
1916
- console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
2020
+ console.log(` ${c.red}✖ Repository URL required${c.reset}`);
2021
+ console.log(` ${c.dim}Usage: agentaudit audit <url>${c.reset}`);
1917
2022
  process.exitCode = 2;
1918
2023
  return;
1919
2024
  }
@@ -1927,8 +2032,18 @@ async function main() {
1927
2032
  return;
1928
2033
  }
1929
2034
 
1930
- console.log(` ${c.red}Unknown command: ${command}${c.reset}`);
1931
- console.log(` ${c.dim}Run agentaudit --help for usage${c.reset}`);
2035
+ // Typo correction via Levenshtein distance
2036
+ const knownCommands = ['discover', 'scan', 'audit', 'check', 'lookup', 'status', 'setup', 'config'];
2037
+ const suggestion = knownCommands
2038
+ .map(cmd => ({ cmd, dist: levenshtein(command, cmd) }))
2039
+ .filter(x => x.dist <= 3)
2040
+ .sort((a, b) => a.dist - b.dist)[0];
2041
+
2042
+ console.log(` ${c.red}✖ Unknown command: ${command}${c.reset}`);
2043
+ if (suggestion) {
2044
+ console.log(` ${c.dim}Did you mean: ${c.cyan}agentaudit ${suggestion.cmd}${c.reset}${c.dim}?${c.reset}`);
2045
+ }
2046
+ console.log(` ${c.dim}Run ${c.cyan}agentaudit --help${c.dim} for usage${c.reset}`);
1932
2047
  process.exitCode = 2;
1933
2048
  }
1934
2049
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.9.19",
3
+ "version": "3.9.21",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {