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.
- package/cli.mjs +204 -89
- 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
|
|
3
|
+
* AgentAudit CLI — Security scanner for AI tools
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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(
|
|
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
|
|
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} —
|
|
1610
|
-
console.log(
|
|
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
|
-
|
|
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}
|
|
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(`
|
|
1705
|
-
console.log(` ${c.cyan}
|
|
1706
|
-
console.log(` ${c.cyan}
|
|
1707
|
-
console.log(` ${c.cyan}
|
|
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}
|
|
1717
|
-
console.log(` ${c.
|
|
1718
|
-
console.log(` ${c.
|
|
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}
|
|
1723
|
-
console.log(` ${c.
|
|
1724
|
-
console.log(` ${c.
|
|
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}
|
|
1727
|
-
console.log(` ${c.dim}
|
|
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}
|
|
1730
|
-
console.log(` agentaudit`);
|
|
1731
|
-
console.log(` agentaudit
|
|
1732
|
-
console.log(` agentaudit
|
|
1733
|
-
console.log(` agentaudit
|
|
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}
|
|
1737
|
-
console.log(` ${c.dim}ANTHROPIC_API_KEY
|
|
1738
|
-
console.log(` ${c.dim}
|
|
1739
|
-
console.log(` ${c.dim}
|
|
1740
|
-
console.log(` ${c.dim}
|
|
1741
|
-
console.log(` ${c.dim}
|
|
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/
|
|
1778
|
-
{ name: 'OpenRouter', env: 'OPENROUTER_API_KEY', key: keys.openrouterKey, testUrl: 'https://openrouter.ai/api/v1/
|
|
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
|
|
1802
|
-
|
|
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
|
-
|
|
1813
|
-
console.log(` ${c.
|
|
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
|
|
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}
|
|
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}
|
|
1863
|
-
console.log(`
|
|
1864
|
-
console.log(`
|
|
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}
|
|
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
|
-
|
|
1931
|
-
|
|
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
|
|