agentaudit 3.9.21 → 3.9.23

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 +148 -10
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -39,7 +39,7 @@ function resolveProvider(flagOverride, keys) {
39
39
  // Aliases
40
40
  const aliases = { claude: 'anthropic', gpt: 'openai', 'gpt-4o': 'openai', 'gpt4': 'openai', or: 'openrouter', local: 'ollama' };
41
41
 
42
- // Priority: --provider flag > AGENTAUDIT_PROVIDER env > config file > auto-detect
42
+ // Priority: --provider flag > AGENTAUDIT_PROVIDER env > config file > model-inferred > auto-detect
43
43
  const preferred = flagOverride
44
44
  || process.env.AGENTAUDIT_PROVIDER?.toLowerCase()
45
45
  || loadConfig()?.preferred_provider
@@ -51,6 +51,14 @@ function resolveProvider(flagOverride, keys) {
51
51
  if (!p) return null;
52
52
  return p;
53
53
  }
54
+
55
+ // Smart inference: if model override looks like "provider/model" (e.g. z-ai/glm-5, anthropic/claude-sonnet-4)
56
+ // → auto-select OpenRouter (which uses that format)
57
+ const activeModel = globalModelOverride || process.env.AGENTAUDIT_MODEL || loadConfig()?.preferred_model;
58
+ if (activeModel && activeModel.includes('/') && providers.openrouter) {
59
+ return providers.openrouter;
60
+ }
61
+
54
62
  // Auto-detect priority: Anthropic > OpenAI > OpenRouter > Custom > Ollama (local last — usually weaker)
55
63
  return providers.anthropic || providers.openai || providers.openrouter || providers.custom || providers.ollama || null;
56
64
  }
@@ -59,6 +67,7 @@ function resolveProvider(flagOverride, keys) {
59
67
  let jsonMode = false;
60
68
  let quietMode = false;
61
69
  let modelOverride = null; // --model flag or AGENTAUDIT_MODEL env or config
70
+ let globalModelOverride = null; // same, but set early for resolveProvider
62
71
 
63
72
  // ── ANSI Colors (respects NO_COLOR and --no-color) ───────
64
73
 
@@ -1457,8 +1466,22 @@ async function auditRepo(url) {
1457
1466
  return null;
1458
1467
  }
1459
1468
 
1469
+ // Determine actual model name for display
1470
+ let actualModel;
1471
+ if (resolvedProvider.id === 'anthropic') {
1472
+ actualModel = modelOverride || 'claude-sonnet-4-20250514';
1473
+ } else if (resolvedProvider.id === 'openrouter') {
1474
+ actualModel = modelOverride || process.env.OPENROUTER_MODEL || 'anthropic/claude-sonnet-4';
1475
+ } else if (resolvedProvider.id === 'openai') {
1476
+ actualModel = modelOverride || 'gpt-4o';
1477
+ } else if (resolvedProvider.id === 'ollama') {
1478
+ actualModel = modelOverride || resolvedProvider.model;
1479
+ } else {
1480
+ actualModel = modelOverride || resolvedProvider.model || 'unknown';
1481
+ }
1482
+
1460
1483
  // We have an API key — run LLM audit
1461
- process.stdout.write(` ${c.dim}[4/4]${c.reset} Running LLM analysis ${c.dim}(${activeProvider})${c.reset}...`);
1484
+ process.stdout.write(` ${c.dim}[4/4]${c.reset} Running LLM analysis ${c.dim}(${resolvedProvider.id}: ${actualModel})${c.reset}...`);
1462
1485
 
1463
1486
  const systemPrompt = auditPrompt || 'You are a security auditor. Analyze the code and report findings as JSON.';
1464
1487
  const userMessage = [
@@ -1579,7 +1602,11 @@ async function auditRepo(url) {
1579
1602
  // Display results
1580
1603
  console.log();
1581
1604
  const riskScore = report.risk_score || 0;
1582
- console.log(` ${riskBadge(riskScore)} Risk ${riskScore}/100 ${c.bold}${report.result || 'unknown'}${c.reset}`);
1605
+ const trustScore = 100 - riskScore;
1606
+ const trustColor = trustScore >= 70 ? c.green : trustScore >= 40 ? c.yellow : c.red;
1607
+ const trustLabel = trustScore >= 70 ? 'SAFE' : trustScore >= 40 ? 'CAUTION' : 'UNSAFE';
1608
+ console.log(` ${trustColor}${c.bold}${trustLabel}${c.reset} ${trustColor}Trust Score: ${trustScore}/100${c.reset} ${c.dim}(Risk: ${riskScore}/100)${c.reset}`);
1609
+ console.log(` ${c.dim}Model: ${resolvedProvider.id}/${actualModel} Duration: ${elapsed(start)}${c.reset}`);
1583
1610
  console.log();
1584
1611
 
1585
1612
  if (report.findings && report.findings.length > 0) {
@@ -1608,7 +1635,7 @@ async function auditRepo(url) {
1608
1635
  'Authorization': `Bearer ${creds.api_key}`,
1609
1636
  'Content-Type': 'application/json',
1610
1637
  },
1611
- body: JSON.stringify(report),
1638
+ body: JSON.stringify({ ...report, audit_model: actualModel, audit_provider: resolvedProvider.id }),
1612
1639
  signal: AbortSignal.timeout(15_000),
1613
1640
  });
1614
1641
  if (res.ok) {
@@ -1631,7 +1658,7 @@ async function auditRepo(url) {
1631
1658
 
1632
1659
  // ── Check command ───────────────────────────────────────
1633
1660
 
1634
- async function checkPackage(name) {
1661
+ async function checkPackage(name, { autoAudit = false } = {}) {
1635
1662
  if (!jsonMode) {
1636
1663
  console.log(`${icons.info} Looking up ${c.bold}${name}${c.reset} in registry...`);
1637
1664
  console.log();
@@ -1640,8 +1667,8 @@ async function checkPackage(name) {
1640
1667
  const data = await checkRegistry(name);
1641
1668
  if (!data) {
1642
1669
  if (!jsonMode) {
1643
- // If input looks like a URL, offer to auto-audit
1644
- if (name.includes('github.com') || name.includes('://')) {
1670
+ // Auto-audit: only when called from 'check' command AND input looks like a URL
1671
+ if (autoAudit && (name.includes('github.com') || name.includes('://'))) {
1645
1672
  console.log(` ${c.yellow}Not found in registry.${c.reset}`);
1646
1673
  console.log(` ${c.dim}Starting audit for ${name}...${c.reset}`);
1647
1674
  console.log();
@@ -1650,7 +1677,8 @@ async function checkPackage(name) {
1650
1677
  console.log(` ${c.yellow}✖ Not found${c.reset} — "${name}" hasn't been audited yet.`);
1651
1678
  console.log();
1652
1679
  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}`);
1680
+ console.log(` ${c.cyan}agentaudit check <repo-url>${c.reset} ${c.dim}Auto-lookup + audit if not found${c.reset}`);
1681
+ console.log(` ${c.cyan}agentaudit audit <repo-url>${c.reset} ${c.dim}Deep LLM audit${c.reset}`);
1654
1682
  console.log(` ${c.cyan}agentaudit scan <repo-url>${c.reset} ${c.dim}Quick static check (no API key)${c.reset}`);
1655
1683
  }
1656
1684
  return null;
@@ -1740,6 +1768,7 @@ async function main() {
1740
1768
  || process.env.AGENTAUDIT_MODEL
1741
1769
  || loadConfig()?.preferred_model
1742
1770
  || null;
1771
+ globalModelOverride = modelOverride;
1743
1772
 
1744
1773
  // Strip global flags from args
1745
1774
  const globalFlags = new Set(['--json', '--quiet', '-q', '--no-color']);
@@ -1774,6 +1803,7 @@ async function main() {
1774
1803
  console.log(` ${c.bold}SETUP${c.reset}`);
1775
1804
  console.log(` ${c.cyan}status${c.reset} Check providers & API keys`);
1776
1805
  console.log(` ${c.cyan}setup${c.reset} Register & configure`);
1806
+ console.log(` ${c.cyan}models${c.reset} List available LLM models`);
1777
1807
  console.log(` ${c.cyan}config set${c.reset} <key> <value> Set default provider/options`);
1778
1808
  console.log();
1779
1809
  console.log(` ${c.bold}OPTIONS${c.reset}`);
@@ -1910,6 +1940,113 @@ async function main() {
1910
1940
  return;
1911
1941
  }
1912
1942
 
1943
+ if (command === 'models') {
1944
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
1945
+ const openaiKey = process.env.OPENAI_API_KEY;
1946
+ const openrouterKey = process.env.OPENROUTER_API_KEY;
1947
+
1948
+ console.log(` ${c.bold}Available models by provider:${c.reset}`);
1949
+ console.log();
1950
+
1951
+ // Static lists for Anthropic (no list API)
1952
+ console.log(` ${c.bold}Anthropic${c.reset}${anthropicKey ? ` ${c.green}(configured)${c.reset}` : ` ${c.dim}(not configured)${c.reset}`}`);
1953
+ console.log(` ${c.dim}claude-sonnet-4-20250514${c.reset} ${c.dim}(default)${c.reset}`);
1954
+ console.log(` ${c.dim}claude-opus-4-20250514${c.reset}`);
1955
+ console.log(` ${c.dim}claude-haiku-3-20250514${c.reset}`);
1956
+ console.log();
1957
+
1958
+ // Static list for OpenAI
1959
+ console.log(` ${c.bold}OpenAI${c.reset}${openaiKey ? ` ${c.green}(configured)${c.reset}` : ` ${c.dim}(not configured)${c.reset}`}`);
1960
+ console.log(` ${c.dim}gpt-4o${c.reset} ${c.dim}(default)${c.reset}`);
1961
+ console.log(` ${c.dim}gpt-4o-mini${c.reset}`);
1962
+ console.log(` ${c.dim}gpt-4.1${c.reset}`);
1963
+ console.log(` ${c.dim}gpt-4.1-mini${c.reset}`);
1964
+ console.log(` ${c.dim}o3${c.reset}`);
1965
+ console.log(` ${c.dim}o4-mini${c.reset}`);
1966
+ console.log();
1967
+
1968
+ // OpenRouter — fetch from API
1969
+ console.log(` ${c.bold}OpenRouter${c.reset}${openrouterKey ? ` ${c.green}(configured)${c.reset}` : ` ${c.dim}(not configured)${c.reset}`}`);
1970
+ if (openrouterKey || targets.includes('--all')) {
1971
+ process.stdout.write(` ${c.dim}Fetching models...${c.reset}`);
1972
+ try {
1973
+ const res = await fetch('https://openrouter.ai/api/v1/models', {
1974
+ headers: openrouterKey ? { 'Authorization': `Bearer ${openrouterKey}` } : {},
1975
+ signal: AbortSignal.timeout(10_000),
1976
+ });
1977
+ const data = await res.json();
1978
+ const models = (data.data || [])
1979
+ .filter(m => m.id && !m.id.includes(':free') && !m.id.includes('/extended'))
1980
+ .sort((a, b) => (a.id || '').localeCompare(b.id || ''));
1981
+
1982
+ // Group by provider prefix
1983
+ const groups = {};
1984
+ for (const m of models) {
1985
+ const [prefix] = m.id.split('/');
1986
+ if (!groups[prefix]) groups[prefix] = [];
1987
+ groups[prefix].push(m);
1988
+ }
1989
+
1990
+ // Show popular ones first
1991
+ const popular = ['anthropic', 'openai', 'google', 'meta-llama', 'mistralai', 'deepseek'];
1992
+ const shown = new Set();
1993
+ process.stdout.write(`\r ${c.green}${models.length} models available${c.reset} \n`);
1994
+ console.log();
1995
+
1996
+ for (const prefix of popular) {
1997
+ if (!groups[prefix]) continue;
1998
+ shown.add(prefix);
1999
+ console.log(` ${c.bold}${prefix}${c.reset}`);
2000
+ for (const m of groups[prefix].slice(0, 5)) {
2001
+ console.log(` ${c.dim}${m.id}${c.reset}`);
2002
+ }
2003
+ if (groups[prefix].length > 5) {
2004
+ console.log(` ${c.dim}... and ${groups[prefix].length - 5} more${c.reset}`);
2005
+ }
2006
+ }
2007
+
2008
+ const otherCount = Object.keys(groups).filter(k => !shown.has(k)).length;
2009
+ if (otherCount > 0) {
2010
+ console.log();
2011
+ console.log(` ${c.dim}+ ${otherCount} more providers. Use --model=<provider/model>${c.reset}`);
2012
+ console.log(` ${c.dim}Full list: https://openrouter.ai/models${c.reset}`);
2013
+ }
2014
+ } catch (e) {
2015
+ process.stdout.write(`\r ${c.red}Failed to fetch: ${e.message}${c.reset} \n`);
2016
+ }
2017
+ } else {
2018
+ console.log(` ${c.dim}anthropic/claude-sonnet-4${c.reset} ${c.dim}(default)${c.reset}`);
2019
+ console.log(` ${c.dim}Set OPENROUTER_API_KEY to see all ${c.bold}200+${c.reset}${c.dim} models${c.reset}`);
2020
+ console.log(` ${c.dim}Or browse: https://openrouter.ai/models${c.reset}`);
2021
+ }
2022
+ console.log();
2023
+
2024
+ // Ollama
2025
+ const ollamaModel = process.env.OLLAMA_MODEL;
2026
+ const ollamaHost = process.env.OLLAMA_HOST || 'http://localhost:11434';
2027
+ console.log(` ${c.bold}Ollama${c.reset}${ollamaModel ? ` ${c.green}(configured: ${ollamaModel})${c.reset}` : ` ${c.dim}(not configured)${c.reset}`}`);
2028
+ if (ollamaModel || process.env.OLLAMA_HOST) {
2029
+ try {
2030
+ const res = await fetch(`${ollamaHost}/api/tags`, { signal: AbortSignal.timeout(5_000) });
2031
+ const data = await res.json();
2032
+ for (const m of (data.models || []).slice(0, 10)) {
2033
+ console.log(` ${c.dim}${m.name}${c.reset}`);
2034
+ }
2035
+ } catch {
2036
+ console.log(` ${c.dim}(Ollama not running at ${ollamaHost})${c.reset}`);
2037
+ }
2038
+ } else {
2039
+ console.log(` ${c.dim}Set OLLAMA_MODEL to use local models${c.reset}`);
2040
+ }
2041
+ console.log();
2042
+
2043
+ console.log(` ${c.bold}Set model:${c.reset}`);
2044
+ console.log(` ${c.cyan}agentaudit config set model <name>${c.reset}`);
2045
+ console.log(` ${c.cyan}agentaudit audit <url> --model <name>${c.reset}`);
2046
+ console.log(` ${c.dim}Or env: AGENTAUDIT_MODEL=<name>${c.reset}`);
2047
+ return;
2048
+ }
2049
+
1913
2050
  if (command === 'config') {
1914
2051
  const subCmd = targets[0];
1915
2052
  if (subCmd === 'set' && targets[1] === 'provider' && targets[2]) {
@@ -1961,8 +2098,9 @@ async function main() {
1961
2098
  return;
1962
2099
  }
1963
2100
  const results = [];
2101
+ const allowAutoAudit = command === 'check'; // only 'check' auto-audits, 'lookup' never does
1964
2102
  for (const t of names) {
1965
- const data = await checkPackage(t);
2103
+ const data = await checkPackage(t, { autoAudit: allowAutoAudit });
1966
2104
  results.push(data);
1967
2105
  }
1968
2106
  if (jsonMode) {
@@ -2033,7 +2171,7 @@ async function main() {
2033
2171
  }
2034
2172
 
2035
2173
  // Typo correction via Levenshtein distance
2036
- const knownCommands = ['discover', 'scan', 'audit', 'check', 'lookup', 'status', 'setup', 'config'];
2174
+ const knownCommands = ['discover', 'scan', 'audit', 'check', 'lookup', 'status', 'setup', 'config', 'models'];
2037
2175
  const suggestion = knownCommands
2038
2176
  .map(cmd => ({ cmd, dist: levenshtein(command, cmd) }))
2039
2177
  .filter(x => x.dist <= 3)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.9.21",
3
+ "version": "3.9.23",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {