agentaudit 3.9.18 → 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.
- package/README.md +32 -4
- package/cli.mjs +222 -107
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -208,6 +208,7 @@ Then ask your agent: *"Check which MCP servers I have installed and audit any un
|
|
|
208
208
|
| `agentaudit audit <url>` | Deep LLM-powered 3-pass audit (~30s) | `agentaudit audit https://github.com/owner/repo` |
|
|
209
209
|
| `agentaudit lookup <name>` | Look up package in trust registry | `agentaudit lookup fastmcp` |
|
|
210
210
|
| `agentaudit check <name\|url>` | Lookup + auto-audit if not found | `agentaudit check https://github.com/owner/repo` |
|
|
211
|
+
| `agentaudit status` | Check API keys + active LLM provider | `agentaudit status` |
|
|
211
212
|
| `agentaudit setup` | Register agent + configure API key | `agentaudit setup` |
|
|
212
213
|
|
|
213
214
|
### Global Flags
|
|
@@ -217,6 +218,7 @@ Then ask your agent: *"Check which MCP servers I have installed and audit any un
|
|
|
217
218
|
| `--json` | Output machine-readable JSON to stdout |
|
|
218
219
|
| `--quiet` / `-q` | Suppress banner and decorative output (show findings only) |
|
|
219
220
|
| `--no-color` | Disable ANSI colors (also respects `NO_COLOR` env var) |
|
|
221
|
+
| `--provider <name>` | Force LLM provider (`anthropic`, `openai`, `openrouter`, `ollama`, `custom`) |
|
|
220
222
|
| `--help` / `-h` | Show help text |
|
|
221
223
|
| `-v` / `--version` | Show version |
|
|
222
224
|
|
|
@@ -433,12 +435,19 @@ export AGENTAUDIT_API_KEY=asf_your_key_here
|
|
|
433
435
|
| Variable | Description |
|
|
434
436
|
|----------|-------------|
|
|
435
437
|
| `AGENTAUDIT_API_KEY` | API key for registry access |
|
|
436
|
-
| `ANTHROPIC_API_KEY` | Anthropic API key for deep audits (Claude) |
|
|
438
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key for deep audits (Claude) -- recommended |
|
|
437
439
|
| `OPENAI_API_KEY` | OpenAI API key for deep audits (GPT-4o) |
|
|
438
440
|
| `OPENROUTER_API_KEY` | OpenRouter API key (access 200+ models) |
|
|
439
441
|
| `OPENROUTER_MODEL` | Model to use via OpenRouter (default: `anthropic/claude-sonnet-4`) |
|
|
442
|
+
| `OLLAMA_MODEL` | Ollama model name for local audits (e.g. `llama3.1`, `qwen2.5-coder`) |
|
|
443
|
+
| `OLLAMA_HOST` | Ollama server URL (default: `http://localhost:11434`) |
|
|
444
|
+
| `LLM_API_URL` | Any OpenAI-compatible API endpoint (e.g. LM Studio, vLLM, Together, Groq) |
|
|
445
|
+
| `LLM_API_KEY` | API key for custom endpoint (optional if no auth needed) |
|
|
446
|
+
| `LLM_MODEL` | Model name for custom endpoint |
|
|
440
447
|
| `NO_COLOR` | Disable ANSI colors ([no-color.org](https://no-color.org)) |
|
|
441
448
|
|
|
449
|
+
> **Provider priority:** Anthropic > OpenAI > OpenRouter > Custom > Ollama. Override with `--provider=ollama` etc.
|
|
450
|
+
|
|
442
451
|
---
|
|
443
452
|
|
|
444
453
|
## 📦 Requirements
|
|
@@ -468,7 +477,7 @@ Or use without installing: `npx agentaudit`
|
|
|
468
477
|
|
|
469
478
|
### Setting up your LLM key for deep audits
|
|
470
479
|
|
|
471
|
-
The `audit` command supports **
|
|
480
|
+
The `audit` command supports **any LLM provider**. Set one of these environment variables:
|
|
472
481
|
|
|
473
482
|
```bash
|
|
474
483
|
# Linux / macOS
|
|
@@ -487,13 +496,32 @@ set OPENAI_API_KEY=sk-...
|
|
|
487
496
|
set OPENROUTER_API_KEY=sk-or-...
|
|
488
497
|
```
|
|
489
498
|
|
|
490
|
-
**Provider priority:** Anthropic > OpenAI > OpenRouter
|
|
499
|
+
**Provider priority:** Anthropic > OpenAI > OpenRouter > Custom > Ollama. Override with `--provider=<name>`.
|
|
491
500
|
|
|
492
|
-
**OpenRouter model selection:** By default
|
|
501
|
+
**OpenRouter model selection:** By default uses `anthropic/claude-sonnet-4`. Override with:
|
|
493
502
|
```bash
|
|
494
503
|
export OPENROUTER_MODEL=google/gemini-2.5-pro # or any model on openrouter.ai
|
|
495
504
|
```
|
|
496
505
|
|
|
506
|
+
**Local with Ollama (free, no API key):**
|
|
507
|
+
```bash
|
|
508
|
+
export OLLAMA_MODEL=llama3.1 # or qwen2.5-coder, deepseek-r1, etc.
|
|
509
|
+
agentaudit audit https://github.com/owner/repo
|
|
510
|
+
```
|
|
511
|
+
> Note: Local models produce lower quality audits than Claude/GPT-4o. Use for quick checks, not production security audits.
|
|
512
|
+
|
|
513
|
+
**Any OpenAI-compatible API:**
|
|
514
|
+
```bash
|
|
515
|
+
export LLM_API_URL=http://localhost:1234/v1 # LM Studio, vLLM, etc.
|
|
516
|
+
export LLM_MODEL=my-model
|
|
517
|
+
agentaudit audit https://github.com/owner/repo
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
**Check your setup:**
|
|
521
|
+
```bash
|
|
522
|
+
agentaudit status # validates all configured API keys
|
|
523
|
+
```
|
|
524
|
+
|
|
497
525
|
**Troubleshooting:** If you see `API error: Incorrect API key`, double-check your key is valid and has credits. Use `--debug` to see the full API response.
|
|
498
526
|
|
|
499
527
|
### What data is sent externally?
|
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,23 +21,38 @@ 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) {
|
|
25
|
+
const orModel = process.env.OPENROUTER_MODEL || 'anthropic/claude-sonnet-4';
|
|
26
|
+
const ollamaModel = process.env.OLLAMA_MODEL || 'llama3.1';
|
|
27
|
+
const ollamaHost = process.env.OLLAMA_HOST || 'http://localhost:11434';
|
|
28
|
+
const customUrl = process.env.LLM_API_URL;
|
|
29
|
+
const customKey = process.env.LLM_API_KEY;
|
|
30
|
+
const customModel = process.env.LLM_MODEL || 'default';
|
|
31
|
+
|
|
31
32
|
const providers = {
|
|
32
33
|
anthropic: keys.anthropicKey ? { id: 'anthropic', label: 'Anthropic (Claude)', key: keys.anthropicKey } : null,
|
|
33
34
|
openai: keys.openaiKey ? { id: 'openai', label: 'OpenAI (GPT-4o)', key: keys.openaiKey } : null,
|
|
34
|
-
openrouter: keys.openrouterKey ? { id: 'openrouter', label: `OpenRouter (${
|
|
35
|
+
openrouter: keys.openrouterKey ? { id: 'openrouter', label: `OpenRouter (${orModel})`, key: keys.openrouterKey } : null,
|
|
36
|
+
ollama: process.env.OLLAMA_MODEL || process.env.OLLAMA_HOST ? { id: 'ollama', label: `Ollama (${ollamaModel})`, key: null, host: ollamaHost, model: ollamaModel } : null,
|
|
37
|
+
custom: customUrl ? { id: 'custom', label: `Custom (${customModel})`, key: customKey, url: customUrl, model: customModel } : null,
|
|
35
38
|
};
|
|
36
39
|
// Aliases
|
|
37
|
-
const aliases = { claude: 'anthropic', gpt: 'openai', 'gpt-4o': 'openai', 'gpt4': 'openai', or: 'openrouter' };
|
|
40
|
+
const aliases = { claude: 'anthropic', gpt: 'openai', 'gpt-4o': 'openai', 'gpt4': 'openai', or: 'openrouter', local: 'ollama' };
|
|
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;
|
|
38
47
|
|
|
39
48
|
if (preferred) {
|
|
40
49
|
const resolved = aliases[preferred] || preferred;
|
|
41
50
|
const p = providers[resolved];
|
|
42
|
-
if (!p) return null;
|
|
51
|
+
if (!p) return null;
|
|
43
52
|
return p;
|
|
44
53
|
}
|
|
45
|
-
// Auto-detect: Anthropic > OpenAI > OpenRouter
|
|
46
|
-
return providers.anthropic || providers.openai || providers.openrouter || null;
|
|
54
|
+
// Auto-detect priority: Anthropic > OpenAI > OpenRouter > Custom > Ollama (local last — usually weaker)
|
|
55
|
+
return providers.anthropic || providers.openai || providers.openrouter || providers.custom || providers.ollama || null;
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
// ── Global flags (set in main before command routing) ────
|
|
@@ -120,6 +129,24 @@ function saveCredentials(data) {
|
|
|
120
129
|
} catch {}
|
|
121
130
|
}
|
|
122
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
|
+
|
|
123
150
|
function askQuestion(question) {
|
|
124
151
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
125
152
|
return new Promise(resolve => rl.question(question, answer => { rl.close(); resolve(answer.trim()); }));
|
|
@@ -303,6 +330,28 @@ async function setupCommand() {
|
|
|
303
330
|
console.log();
|
|
304
331
|
}
|
|
305
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
|
+
|
|
306
355
|
// ── Helpers ──────────────────────────────────────────────
|
|
307
356
|
|
|
308
357
|
function getVersion() {
|
|
@@ -315,8 +364,8 @@ function getVersion() {
|
|
|
315
364
|
function banner() {
|
|
316
365
|
if (quietMode || jsonMode) return;
|
|
317
366
|
console.log();
|
|
318
|
-
console.log(` ${c.bold}${c.cyan}AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset}`);
|
|
319
|
-
console.log(` ${c.dim}Security scanner for AI
|
|
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}`);
|
|
320
369
|
console.log();
|
|
321
370
|
}
|
|
322
371
|
|
|
@@ -1344,10 +1393,14 @@ async function auditRepo(url) {
|
|
|
1344
1393
|
if (!resolvedProvider) {
|
|
1345
1394
|
// No LLM API key — clear explanation
|
|
1346
1395
|
console.log();
|
|
1347
|
-
console.log(` ${c.yellow}No LLM
|
|
1396
|
+
console.log(` ${c.yellow}No LLM provider configured.${c.reset} The ${c.bold}audit${c.reset} command needs an LLM to analyze code.`);
|
|
1348
1397
|
console.log();
|
|
1349
|
-
console.log(` ${c.bold}Option 1: Set an API key${c.reset}`);
|
|
1350
|
-
console.log(`
|
|
1398
|
+
console.log(` ${c.bold}Option 1: Set an API key${c.reset} ${c.dim}(any one of these)${c.reset}`);
|
|
1399
|
+
console.log(` ${c.cyan}ANTHROPIC_API_KEY${c.reset} Anthropic Claude ${c.dim}(recommended)${c.reset}`);
|
|
1400
|
+
console.log(` ${c.cyan}OPENAI_API_KEY${c.reset} OpenAI GPT-4o`);
|
|
1401
|
+
console.log(` ${c.cyan}OPENROUTER_API_KEY${c.reset} OpenRouter ${c.dim}(200+ models)${c.reset}`);
|
|
1402
|
+
console.log(` ${c.cyan}OLLAMA_MODEL${c.reset} Ollama ${c.dim}(local, free, set model name)${c.reset}`);
|
|
1403
|
+
console.log(` ${c.cyan}LLM_API_URL${c.reset} Any OpenAI-compatible API ${c.dim}(+ LLM_API_KEY, LLM_MODEL)${c.reset}`);
|
|
1351
1404
|
console.log();
|
|
1352
1405
|
console.log(` ${c.dim}# Linux / macOS:${c.reset}`);
|
|
1353
1406
|
console.log(` ${c.dim}export ANTHROPIC_API_KEY=sk-ant-...${c.reset}`);
|
|
@@ -1451,19 +1504,33 @@ async function auditRepo(url) {
|
|
|
1451
1504
|
_lastLlmText = text;
|
|
1452
1505
|
report = extractJSON(text);
|
|
1453
1506
|
} else {
|
|
1454
|
-
// OpenAI or
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1507
|
+
// OpenAI, OpenRouter, Ollama, or Custom (all use OpenAI-compatible chat completions API)
|
|
1508
|
+
let apiUrl, modelName, authHeaders;
|
|
1509
|
+
switch (resolvedProvider.id) {
|
|
1510
|
+
case 'openrouter':
|
|
1511
|
+
apiUrl = 'https://openrouter.ai/api/v1/chat/completions';
|
|
1512
|
+
modelName = process.env.OPENROUTER_MODEL || 'anthropic/claude-sonnet-4';
|
|
1513
|
+
authHeaders = { 'Authorization': `Bearer ${resolvedProvider.key}`, 'HTTP-Referer': 'https://agentaudit.dev', 'X-Title': 'AgentAudit' };
|
|
1514
|
+
break;
|
|
1515
|
+
case 'ollama':
|
|
1516
|
+
apiUrl = `${resolvedProvider.host}/v1/chat/completions`;
|
|
1517
|
+
modelName = resolvedProvider.model;
|
|
1518
|
+
authHeaders = {};
|
|
1519
|
+
break;
|
|
1520
|
+
case 'custom':
|
|
1521
|
+
apiUrl = resolvedProvider.url.endsWith('/chat/completions') ? resolvedProvider.url : `${resolvedProvider.url.replace(/\/$/, '')}/chat/completions`;
|
|
1522
|
+
modelName = resolvedProvider.model;
|
|
1523
|
+
authHeaders = resolvedProvider.key ? { 'Authorization': `Bearer ${resolvedProvider.key}` } : {};
|
|
1524
|
+
break;
|
|
1525
|
+
default: // openai
|
|
1526
|
+
apiUrl = 'https://api.openai.com/v1/chat/completions';
|
|
1527
|
+
modelName = 'gpt-4o';
|
|
1528
|
+
authHeaders = { 'Authorization': `Bearer ${resolvedProvider.key}` };
|
|
1529
|
+
}
|
|
1459
1530
|
|
|
1460
1531
|
const res = await fetch(apiUrl, {
|
|
1461
1532
|
method: 'POST',
|
|
1462
|
-
headers: {
|
|
1463
|
-
'Authorization': `Bearer ${resolvedProvider.key}`,
|
|
1464
|
-
'Content-Type': 'application/json',
|
|
1465
|
-
...extraHeaders,
|
|
1466
|
-
},
|
|
1533
|
+
headers: { 'Content-Type': 'application/json', ...authHeaders },
|
|
1467
1534
|
body: JSON.stringify({
|
|
1468
1535
|
model: modelName,
|
|
1469
1536
|
max_tokens: 8192,
|
|
@@ -1472,7 +1539,7 @@ async function auditRepo(url) {
|
|
|
1472
1539
|
{ role: 'user', content: userMessage },
|
|
1473
1540
|
],
|
|
1474
1541
|
}),
|
|
1475
|
-
signal: AbortSignal.timeout(120_000),
|
|
1542
|
+
signal: AbortSignal.timeout(resolvedProvider.id === 'ollama' ? 300_000 : 120_000), // Ollama: 5min (local can be slow)
|
|
1476
1543
|
});
|
|
1477
1544
|
const data = await res.json();
|
|
1478
1545
|
if (data.error) {
|
|
@@ -1579,8 +1646,11 @@ async function checkPackage(name) {
|
|
|
1579
1646
|
console.log();
|
|
1580
1647
|
return await auditRepo(name);
|
|
1581
1648
|
}
|
|
1582
|
-
console.log(` ${c.yellow}Not found${c.reset} —
|
|
1583
|
-
console.log(
|
|
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}`);
|
|
1584
1654
|
}
|
|
1585
1655
|
return null;
|
|
1586
1656
|
}
|
|
@@ -1672,57 +1742,41 @@ async function main() {
|
|
|
1672
1742
|
|
|
1673
1743
|
if (args[0] === '--help' || args[0] === '-h') {
|
|
1674
1744
|
banner();
|
|
1675
|
-
console.log(` ${c.bold}
|
|
1676
|
-
console.log();
|
|
1677
|
-
console.log(` ${c.cyan}agentaudit${c.reset} Discover MCP servers (same as discover)`);
|
|
1678
|
-
console.log(` ${c.cyan}agentaudit discover${c.reset} Find MCP servers in your AI editors (Cursor, Claude, VS Code, Windsurf)`);
|
|
1679
|
-
console.log(` ${c.cyan}agentaudit discover --quick${c.reset} Discover + auto-scan all servers`);
|
|
1680
|
-
console.log(` ${c.cyan}agentaudit discover --deep${c.reset} Discover + select servers to deep-audit`);
|
|
1681
|
-
console.log(` ${c.cyan}agentaudit scan${c.reset} <url> [url...] Quick static scan (regex, local)`);
|
|
1682
|
-
console.log(` ${c.cyan}agentaudit scan${c.reset} <url> ${c.dim}--deep${c.reset} Deep audit (same as audit)`);
|
|
1683
|
-
console.log(` ${c.cyan}agentaudit audit${c.reset} <url> [url...] Deep LLM-powered security audit`);
|
|
1684
|
-
console.log(` ${c.cyan}agentaudit lookup${c.reset} <name> Look up package in registry`);
|
|
1685
|
-
console.log(` ${c.cyan}agentaudit check${c.reset} <name|url> Lookup + auto-audit if not found`);
|
|
1686
|
-
console.log(` ${c.cyan}agentaudit status${c.reset} Check API keys + active provider`);
|
|
1687
|
-
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]`);
|
|
1688
1747
|
console.log();
|
|
1689
|
-
console.log(` ${c.bold}
|
|
1690
|
-
console.log(` ${c.
|
|
1691
|
-
console.log(` ${c.
|
|
1692
|
-
console.log(` ${c.
|
|
1693
|
-
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`);
|
|
1694
1752
|
console.log();
|
|
1695
|
-
console.log(` ${c.bold}
|
|
1696
|
-
console.log(` ${c.
|
|
1697
|
-
console.log(` ${c.
|
|
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`);
|
|
1698
1756
|
console.log();
|
|
1699
|
-
console.log(` ${c.bold}
|
|
1700
|
-
console.log(` ${c.
|
|
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`);
|
|
1701
1761
|
console.log();
|
|
1702
|
-
console.log(` ${c.bold}
|
|
1703
|
-
console.log(`
|
|
1704
|
-
console.log(`
|
|
1705
|
-
console.log(`
|
|
1706
|
-
console.log(`
|
|
1707
|
-
console.log(`
|
|
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}`);
|
|
1708
1769
|
console.log();
|
|
1709
|
-
console.log(` ${c.bold}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
console.log(` ${c.dim}CMD: set ANTHROPIC_API_KEY=sk-ant-...${c.reset}`);
|
|
1715
|
-
console.log(` ${c.dim} set OPENAI_API_KEY=sk-...${c.reset}`);
|
|
1716
|
-
console.log(` ${c.dim} set OPENROUTER_API_KEY=sk-or-...${c.reset}`);
|
|
1717
|
-
} else {
|
|
1718
|
-
console.log(` ${c.dim}export ANTHROPIC_API_KEY=sk-ant-...${c.reset}`);
|
|
1719
|
-
console.log(` ${c.dim}export OPENAI_API_KEY=sk-...${c.reset}`);
|
|
1720
|
-
console.log(` ${c.dim}export OPENROUTER_API_KEY=sk-or-...${c.reset} ${c.dim}(200+ models, set OPENROUTER_MODEL to pick)${c.reset}`);
|
|
1721
|
-
}
|
|
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`);
|
|
1722
1775
|
console.log();
|
|
1723
|
-
console.log(` ${c.bold}
|
|
1724
|
-
console.log(` ${c.dim}
|
|
1725
|
-
console.log(` ${c.dim}
|
|
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}`);
|
|
1726
1780
|
console.log();
|
|
1727
1781
|
process.exitCode = 0; return;
|
|
1728
1782
|
}
|
|
@@ -1746,10 +1800,16 @@ async function main() {
|
|
|
1746
1800
|
openaiKey: process.env.OPENAI_API_KEY,
|
|
1747
1801
|
openrouterKey: process.env.OPENROUTER_API_KEY,
|
|
1748
1802
|
};
|
|
1803
|
+
const ollamaHost = process.env.OLLAMA_HOST || 'http://localhost:11434';
|
|
1804
|
+
const ollamaModel = process.env.OLLAMA_MODEL;
|
|
1805
|
+
const customUrl = process.env.LLM_API_URL;
|
|
1806
|
+
|
|
1749
1807
|
const checks = [
|
|
1750
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' }] }) },
|
|
1751
|
-
{ name: 'OpenAI', env: 'OPENAI_API_KEY', key: keys.openaiKey, testUrl: 'https://api.openai.com/v1/
|
|
1752
|
-
{ name: 'OpenRouter', env: 'OPENROUTER_API_KEY', key: keys.openrouterKey, testUrl: 'https://openrouter.ai/api/v1/
|
|
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' }] }) },
|
|
1811
|
+
{ name: 'Ollama', env: 'OLLAMA_MODEL', key: ollamaModel, testUrl: `${ollamaHost}/api/tags`, testHeaders: () => ({}), testBody: null },
|
|
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 },
|
|
1753
1813
|
];
|
|
1754
1814
|
|
|
1755
1815
|
for (const p of checks) {
|
|
@@ -1770,8 +1830,25 @@ async function main() {
|
|
|
1770
1830
|
process.stdout.write(`\r ${c.green}●${c.reset} ${p.name.padEnd(12)} ${c.dim}${masked}${c.reset} ${c.green}valid ✓${c.reset} \n`);
|
|
1771
1831
|
} else {
|
|
1772
1832
|
const body = await res.json().catch(() => ({}));
|
|
1773
|
-
const
|
|
1774
|
-
|
|
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`);
|
|
1775
1852
|
}
|
|
1776
1853
|
} catch (e) {
|
|
1777
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`);
|
|
@@ -1781,10 +1858,13 @@ async function main() {
|
|
|
1781
1858
|
const resolved = resolveProvider(null, keys);
|
|
1782
1859
|
console.log();
|
|
1783
1860
|
if (resolved) {
|
|
1784
|
-
console.log(` ${c.bold}Active
|
|
1785
|
-
console.log(` ${c.dim}Override
|
|
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}`);
|
|
1786
1864
|
} else {
|
|
1787
|
-
console.log(` ${c.yellow}No LLM provider
|
|
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}`);
|
|
1788
1868
|
}
|
|
1789
1869
|
|
|
1790
1870
|
// AgentAudit registry key
|
|
@@ -1808,10 +1888,47 @@ async function main() {
|
|
|
1808
1888
|
return;
|
|
1809
1889
|
}
|
|
1810
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
|
+
|
|
1811
1927
|
if (command === 'lookup' || command === 'check') {
|
|
1812
1928
|
const names = targets.filter(t => !t.startsWith('--'));
|
|
1813
1929
|
if (names.length === 0) {
|
|
1814
|
-
console.log(` ${c.red}
|
|
1930
|
+
console.log(` ${c.red}✖ Package name or URL required${c.reset}`);
|
|
1931
|
+
console.log(` ${c.dim}Usage: agentaudit check <name|url>${c.reset}`);
|
|
1815
1932
|
process.exitCode = 2;
|
|
1816
1933
|
return;
|
|
1817
1934
|
}
|
|
@@ -1824,31 +1941,18 @@ async function main() {
|
|
|
1824
1941
|
console.log(JSON.stringify(results.length === 1 ? (results[0] || { error: 'not_found' }) : results, null, 2));
|
|
1825
1942
|
}
|
|
1826
1943
|
process.exitCode = 0; return;
|
|
1827
|
-
return;
|
|
1828
1944
|
}
|
|
1829
1945
|
|
|
1830
1946
|
if (command === 'scan') {
|
|
1831
|
-
const deepFlag = targets.includes('--deep');
|
|
1832
1947
|
const urls = targets.filter(t => !t.startsWith('--'));
|
|
1833
1948
|
if (urls.length === 0) {
|
|
1834
|
-
console.log(` ${c.red}
|
|
1835
|
-
console.log(`
|
|
1836
|
-
console.log(`
|
|
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}`);
|
|
1837
1952
|
process.exitCode = 2;
|
|
1838
1953
|
return;
|
|
1839
1954
|
}
|
|
1840
1955
|
|
|
1841
|
-
// --deep redirects to audit flow
|
|
1842
|
-
if (deepFlag) {
|
|
1843
|
-
let hasFindings = false;
|
|
1844
|
-
for (const url of urls) {
|
|
1845
|
-
const report = await auditRepo(url);
|
|
1846
|
-
if (report?.findings?.length > 0) hasFindings = true;
|
|
1847
|
-
}
|
|
1848
|
-
process.exitCode = hasFindings ? 1 : 0;
|
|
1849
|
-
return;
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
1956
|
const results = [];
|
|
1853
1957
|
let hadErrors = false;
|
|
1854
1958
|
for (const url of urls) {
|
|
@@ -1885,7 +1989,8 @@ async function main() {
|
|
|
1885
1989
|
if (command === 'audit') {
|
|
1886
1990
|
const urls = targets.filter(t => !t.startsWith('--'));
|
|
1887
1991
|
if (urls.length === 0) {
|
|
1888
|
-
console.log(` ${c.red}
|
|
1992
|
+
console.log(` ${c.red}✖ Repository URL required${c.reset}`);
|
|
1993
|
+
console.log(` ${c.dim}Usage: agentaudit audit <url>${c.reset}`);
|
|
1889
1994
|
process.exitCode = 2;
|
|
1890
1995
|
return;
|
|
1891
1996
|
}
|
|
@@ -1899,8 +2004,18 @@ async function main() {
|
|
|
1899
2004
|
return;
|
|
1900
2005
|
}
|
|
1901
2006
|
|
|
1902
|
-
|
|
1903
|
-
|
|
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}`);
|
|
1904
2019
|
process.exitCode = 2;
|
|
1905
2020
|
}
|
|
1906
2021
|
|