agentaudit 3.9.19 → 3.9.20

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 +170 -83
  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];
@@ -129,6 +129,24 @@ function saveCredentials(data) {
129
129
  } catch {}
130
130
  }
131
131
 
132
+ const USER_CONFIG_FILE = path.join(USER_CRED_DIR, 'config.json');
133
+
134
+ function loadConfig() {
135
+ try {
136
+ if (fs.existsSync(USER_CONFIG_FILE)) {
137
+ return JSON.parse(fs.readFileSync(USER_CONFIG_FILE, 'utf8'));
138
+ }
139
+ } catch {}
140
+ return {};
141
+ }
142
+
143
+ function saveConfig(data) {
144
+ const existing = loadConfig();
145
+ const merged = { ...existing, ...data };
146
+ fs.mkdirSync(USER_CRED_DIR, { recursive: true });
147
+ fs.writeFileSync(USER_CONFIG_FILE, JSON.stringify(merged, null, 2), { mode: 0o600 });
148
+ }
149
+
132
150
  function askQuestion(question) {
133
151
  const rl = createInterface({ input: process.stdin, output: process.stdout });
134
152
  return new Promise(resolve => rl.question(question, answer => { rl.close(); resolve(answer.trim()); }));
@@ -312,6 +330,28 @@ async function setupCommand() {
312
330
  console.log();
313
331
  }
314
332
 
333
+ // ── Structured error output ─────────────────────────────
334
+
335
+ function emitError(code, message, hint, exitCode = 2) {
336
+ if (jsonMode) {
337
+ process.stderr.write(JSON.stringify({ error: true, code, message, hint: hint || undefined, exitCode }) + '\n');
338
+ }
339
+ process.exitCode = exitCode;
340
+ }
341
+
342
+ // ── Levenshtein distance for typo correction ────────────
343
+
344
+ function levenshtein(a, b) {
345
+ const m = a.length, n = b.length;
346
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
347
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
348
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
349
+ for (let i = 1; i <= m; i++)
350
+ for (let j = 1; j <= n; j++)
351
+ 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));
352
+ return dp[m][n];
353
+ }
354
+
315
355
  // ── Helpers ──────────────────────────────────────────────
316
356
 
317
357
  function getVersion() {
@@ -324,8 +364,8 @@ function getVersion() {
324
364
  function banner() {
325
365
  if (quietMode || jsonMode) return;
326
366
  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}`);
367
+ console.log(` 🛡 ${c.bold}${c.cyan}AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset}`);
368
+ console.log(` ${c.dim}Security scanner for AI tools${c.reset}`);
329
369
  console.log();
330
370
  }
331
371
 
@@ -1606,8 +1646,11 @@ async function checkPackage(name) {
1606
1646
  console.log();
1607
1647
  return await auditRepo(name);
1608
1648
  }
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}`);
1649
+ console.log(` ${c.yellow}Not found${c.reset} — "${name}" hasn't been audited yet.`);
1650
+ console.log();
1651
+ console.log(` ${c.dim}Next steps:${c.reset}`);
1652
+ console.log(` ${c.cyan}agentaudit audit <repo-url>${c.reset} ${c.dim}Run a deep audit yourself${c.reset}`);
1653
+ console.log(` ${c.cyan}agentaudit scan <repo-url>${c.reset} ${c.dim}Quick static check (no API key)${c.reset}`);
1611
1654
  }
1612
1655
  return null;
1613
1656
  }
@@ -1699,52 +1742,41 @@ async function main() {
1699
1742
 
1700
1743
  if (args[0] === '--help' || args[0] === '-h') {
1701
1744
  banner();
1702
- console.log(` ${c.bold}Commands:${c.reset}`);
1703
- 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`);
1745
+ console.log(` ${c.bold}USAGE${c.reset}`);
1746
+ console.log(` ${c.cyan}agentaudit${c.reset} <command> [options]`);
1715
1747
  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}`);
1748
+ console.log(` ${c.bold}SCAN & AUDIT${c.reset}`);
1749
+ console.log(` ${c.cyan}scan${c.reset} <url> [url...] Quick static analysis ${c.dim}(~2s, no API key)${c.reset}`);
1750
+ console.log(` ${c.cyan}audit${c.reset} <url> [url...] Deep LLM security audit ${c.dim}(~30s)${c.reset}`);
1751
+ console.log(` ${c.cyan}discover${c.reset} Find MCP servers in your editors`);
1721
1752
  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}`);
1753
+ console.log(` ${c.bold}REGISTRY${c.reset}`);
1754
+ console.log(` ${c.cyan}check${c.reset} <name|url> Look up or auto-audit package`);
1755
+ console.log(` ${c.cyan}lookup${c.reset} <name> Look up package in registry`);
1725
1756
  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}`);
1757
+ console.log(` ${c.bold}SETUP${c.reset}`);
1758
+ console.log(` ${c.cyan}status${c.reset} Check providers & API keys`);
1759
+ console.log(` ${c.cyan}setup${c.reset} Register & configure`);
1760
+ console.log(` ${c.cyan}config set${c.reset} <key> <value> Set default provider/options`);
1728
1761
  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`);
1762
+ console.log(` ${c.bold}OPTIONS${c.reset}`);
1763
+ console.log(` ${c.dim}--json Machine-readable JSON output${c.reset}`);
1764
+ console.log(` ${c.dim}--quiet Suppress banner${c.reset}`);
1765
+ console.log(` ${c.dim}--no-color Disable colors ${c.reset}${c.dim}(also: NO_COLOR=1)${c.reset}`);
1766
+ console.log(` ${c.dim}--provider <p> Force provider ${c.reset}${c.dim}(anthropic|openai|openrouter|ollama|custom)${c.reset}`);
1767
+ console.log(` ${c.dim}--export Export audit payload to markdown${c.reset}`);
1768
+ console.log(` ${c.dim}--debug Show raw LLM response on errors${c.reset}`);
1735
1769
  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}`);
1770
+ console.log(` ${c.bold}EXAMPLES${c.reset}`);
1771
+ console.log(` ${c.dim}$${c.reset} agentaudit scan https://github.com/owner/repo`);
1772
+ console.log(` ${c.dim}$${c.reset} agentaudit audit https://github.com/owner/repo`);
1773
+ console.log(` ${c.dim}$${c.reset} agentaudit check fastmcp`);
1774
+ console.log(` ${c.dim}$${c.reset} agentaudit status`);
1742
1775
  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}`);
1776
+ console.log(` ${c.bold}PROVIDERS${c.reset} ${c.dim}(set any one for deep audits)${c.reset}`);
1777
+ console.log(` ${c.dim}ANTHROPIC_API_KEY · OPENAI_API_KEY · OPENROUTER_API_KEY · OLLAMA_MODEL · LLM_API_URL${c.reset}`);
1778
+ console.log(` ${c.dim}Set default: AGENTAUDIT_PROVIDER=openai or agentaudit config set provider openai${c.reset}`);
1779
+ console.log(` ${c.dim}Run ${c.cyan}agentaudit status${c.dim} to check configuration.${c.reset}`);
1748
1780
  console.log();
1749
1781
  process.exitCode = 0; return;
1750
1782
  }
@@ -1774,8 +1806,8 @@ async function main() {
1774
1806
 
1775
1807
  const checks = [
1776
1808
  { 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 },
1809
+ { 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' }] }) },
1810
+ { 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
1811
  { name: 'Ollama', env: 'OLLAMA_MODEL', key: ollamaModel, testUrl: `${ollamaHost}/api/tags`, testHeaders: () => ({}), testBody: null },
1780
1812
  { 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
1813
  ];
@@ -1798,8 +1830,25 @@ async function main() {
1798
1830
  process.stdout.write(`\r ${c.green}●${c.reset} ${p.name.padEnd(12)} ${c.dim}${masked}${c.reset} ${c.green}valid ✓${c.reset} \n`);
1799
1831
  } else {
1800
1832
  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`);
1833
+ const rawMsg = body?.error?.message || body?.message || `HTTP ${res.status}`;
1834
+ // Detect specific error types for clearer messages
1835
+ const lcMsg = rawMsg.toLowerCase();
1836
+ let errMsg = rawMsg;
1837
+ let hint = '';
1838
+ if (lcMsg.includes('credit') || lcMsg.includes('balance') || lcMsg.includes('quota') || lcMsg.includes('billing') || lcMsg.includes('exceeded') || lcMsg.includes('insufficient')) {
1839
+ errMsg = 'no credits';
1840
+ if (p.name === 'Anthropic') hint = `\n ${c.dim}└─ Add credits: console.anthropic.com/settings/plans${c.reset}`;
1841
+ else if (p.name === 'OpenAI') hint = `\n ${c.dim}└─ Check usage: platform.openai.com/usage${c.reset}`;
1842
+ else if (p.name === 'OpenRouter') hint = `\n ${c.dim}└─ Check balance: openrouter.ai/credits${c.reset}`;
1843
+ } else if (res.status === 401 || lcMsg.includes('invalid') || lcMsg.includes('unauthorized') || lcMsg.includes('authentication')) {
1844
+ errMsg = 'invalid key';
1845
+ if (p.name === 'Anthropic') hint = `\n ${c.dim}└─ Check key: console.anthropic.com/settings/keys${c.reset}`;
1846
+ else if (p.name === 'OpenAI') hint = `\n ${c.dim}└─ Check key: platform.openai.com/api-keys${c.reset}`;
1847
+ } else if (res.status === 429) {
1848
+ errMsg = 'rate limited';
1849
+ hint = `\n ${c.dim}└─ Try again in a moment${c.reset}`;
1850
+ }
1851
+ 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
1852
  }
1804
1853
  } catch (e) {
1805
1854
  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 +1858,13 @@ async function main() {
1809
1858
  const resolved = resolveProvider(null, keys);
1810
1859
  console.log();
1811
1860
  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}`);
1861
+ console.log(` ${c.bold}Active:${c.reset} ${c.green}${resolved.label}${c.reset}`);
1862
+ console.log(` ${c.dim}Override: --provider=<name> or AGENTAUDIT_PROVIDER=<name>${c.reset}`);
1863
+ console.log(` ${c.dim}Set default: agentaudit config set provider <name>${c.reset}`);
1814
1864
  } else {
1815
- console.log(` ${c.yellow}No LLM provider configured.${c.reset} Set one of the API keys above for deep audits.`);
1865
+ console.log(` ${c.yellow}No working LLM provider.${c.reset} Deep audits require one.`);
1866
+ console.log(` ${c.dim}Set a key: export ANTHROPIC_API_KEY=sk-ant-...${c.reset}`);
1867
+ console.log(` ${c.dim}Or scan without LLM: agentaudit scan <url>${c.reset}`);
1816
1868
  }
1817
1869
 
1818
1870
  // AgentAudit registry key
@@ -1836,10 +1888,47 @@ async function main() {
1836
1888
  return;
1837
1889
  }
1838
1890
 
1891
+ if (command === 'config') {
1892
+ const subCmd = targets[0];
1893
+ if (subCmd === 'set' && targets[1] === 'provider' && targets[2]) {
1894
+ const validProviders = ['anthropic', 'openai', 'openrouter', 'ollama', 'custom', 'claude', 'gpt'];
1895
+ const val = targets[2].toLowerCase();
1896
+ if (!validProviders.includes(val)) {
1897
+ console.log(` ${c.red}✖ Unknown provider: ${val}${c.reset}`);
1898
+ console.log(` ${c.dim}Valid: anthropic, openai, openrouter, ollama, custom${c.reset}`);
1899
+ process.exitCode = 2; return;
1900
+ }
1901
+ saveConfig({ preferred_provider: val });
1902
+ console.log(` ${c.green}✔${c.reset} Default provider set to: ${c.bold}${val}${c.reset}`);
1903
+ console.log(` ${c.dim}Override per-command: --provider=<name>${c.reset}`);
1904
+ console.log(` ${c.dim}Or env: AGENTAUDIT_PROVIDER=<name>${c.reset}`);
1905
+ } else if (subCmd === 'get' || !subCmd) {
1906
+ const cfg = loadConfig();
1907
+ console.log(` ${c.bold}Config:${c.reset} ${USER_CONFIG_FILE}`);
1908
+ if (Object.keys(cfg).length === 0) {
1909
+ console.log(` ${c.dim}(empty — using defaults)${c.reset}`);
1910
+ } else {
1911
+ for (const [k, v] of Object.entries(cfg)) {
1912
+ console.log(` ${c.dim}${k}:${c.reset} ${v}`);
1913
+ }
1914
+ }
1915
+ } else if (subCmd === 'reset') {
1916
+ try { fs.unlinkSync(USER_CONFIG_FILE); } catch {}
1917
+ console.log(` ${c.green}✔${c.reset} Config reset to defaults.`);
1918
+ } else {
1919
+ console.log(` ${c.red}✖ Unknown config command${c.reset}`);
1920
+ console.log(` ${c.dim}Usage: agentaudit config set provider <name>${c.reset}`);
1921
+ console.log(` ${c.dim} agentaudit config get${c.reset}`);
1922
+ console.log(` ${c.dim} agentaudit config reset${c.reset}`);
1923
+ }
1924
+ return;
1925
+ }
1926
+
1839
1927
  if (command === 'lookup' || command === 'check') {
1840
1928
  const names = targets.filter(t => !t.startsWith('--'));
1841
1929
  if (names.length === 0) {
1842
- console.log(` ${c.red}Error: package name required${c.reset}`);
1930
+ console.log(` ${c.red}✖ Package name or URL required${c.reset}`);
1931
+ console.log(` ${c.dim}Usage: agentaudit check <name|url>${c.reset}`);
1843
1932
  process.exitCode = 2;
1844
1933
  return;
1845
1934
  }
@@ -1852,31 +1941,18 @@ async function main() {
1852
1941
  console.log(JSON.stringify(results.length === 1 ? (results[0] || { error: 'not_found' }) : results, null, 2));
1853
1942
  }
1854
1943
  process.exitCode = 0; return;
1855
- return;
1856
1944
  }
1857
1945
 
1858
1946
  if (command === 'scan') {
1859
- const deepFlag = targets.includes('--deep');
1860
1947
  const urls = targets.filter(t => !t.startsWith('--'));
1861
1948
  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}`);
1949
+ console.log(` ${c.red}✖ Repository URL required${c.reset}`);
1950
+ console.log(` ${c.dim}Usage: agentaudit scan <url>${c.reset}`);
1951
+ console.log(` ${c.dim}Or discover local servers: ${c.cyan}agentaudit discover${c.reset}`);
1865
1952
  process.exitCode = 2;
1866
1953
  return;
1867
1954
  }
1868
1955
 
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
1956
  const results = [];
1881
1957
  let hadErrors = false;
1882
1958
  for (const url of urls) {
@@ -1913,7 +1989,8 @@ async function main() {
1913
1989
  if (command === 'audit') {
1914
1990
  const urls = targets.filter(t => !t.startsWith('--'));
1915
1991
  if (urls.length === 0) {
1916
- console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
1992
+ console.log(` ${c.red}✖ Repository URL required${c.reset}`);
1993
+ console.log(` ${c.dim}Usage: agentaudit audit <url>${c.reset}`);
1917
1994
  process.exitCode = 2;
1918
1995
  return;
1919
1996
  }
@@ -1927,8 +2004,18 @@ async function main() {
1927
2004
  return;
1928
2005
  }
1929
2006
 
1930
- console.log(` ${c.red}Unknown command: ${command}${c.reset}`);
1931
- console.log(` ${c.dim}Run agentaudit --help for usage${c.reset}`);
2007
+ // Typo correction via Levenshtein distance
2008
+ const knownCommands = ['discover', 'scan', 'audit', 'check', 'lookup', 'status', 'setup', 'config'];
2009
+ const suggestion = knownCommands
2010
+ .map(cmd => ({ cmd, dist: levenshtein(command, cmd) }))
2011
+ .filter(x => x.dist <= 3)
2012
+ .sort((a, b) => a.dist - b.dist)[0];
2013
+
2014
+ console.log(` ${c.red}✖ Unknown command: ${command}${c.reset}`);
2015
+ if (suggestion) {
2016
+ console.log(` ${c.dim}Did you mean: ${c.cyan}agentaudit ${suggestion.cmd}${c.reset}${c.dim}?${c.reset}`);
2017
+ }
2018
+ console.log(` ${c.dim}Run ${c.cyan}agentaudit --help${c.dim} for usage${c.reset}`);
1932
2019
  process.exitCode = 2;
1933
2020
  }
1934
2021
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.9.19",
3
+ "version": "3.9.20",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {