agentaudit 3.10.0 → 3.10.1
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 +249 -110
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* agentaudit lookup <name> Look up package in registry
|
|
11
11
|
* agentaudit setup Register + configure API key
|
|
12
12
|
*
|
|
13
|
-
* Global flags: --json, --quiet, --no-color
|
|
13
|
+
* Global flags: --json, --quiet, --no-color, --no-upload
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import fs from 'fs';
|
|
@@ -49,6 +49,51 @@ const LLM_PROVIDERS = [
|
|
|
49
49
|
{ key: 'OPENROUTER_API_KEY', name: 'OpenRouter', provider: 'openrouter', type: 'openai', model: 'anthropic/claude-sonnet-4', url: 'https://openrouter.ai/api/v1/chat/completions' },
|
|
50
50
|
];
|
|
51
51
|
|
|
52
|
+
// ── Provider-specific model choices (for interactive menu) ──
|
|
53
|
+
const PROVIDER_MODELS = {
|
|
54
|
+
anthropic: [
|
|
55
|
+
{ label: 'claude-sonnet-4-20250514', sublabel: 'fast + smart (default)', value: 'claude-sonnet-4-20250514' },
|
|
56
|
+
{ label: 'claude-opus-4-20250514', sublabel: 'most capable', value: 'claude-opus-4-20250514' },
|
|
57
|
+
],
|
|
58
|
+
openai: [
|
|
59
|
+
{ label: 'gpt-4o', sublabel: 'fast multimodal (default)', value: 'gpt-4o' },
|
|
60
|
+
{ label: 'gpt-4.1', sublabel: 'latest', value: 'gpt-4.1' },
|
|
61
|
+
],
|
|
62
|
+
google: [
|
|
63
|
+
{ label: 'gemini-2.5-flash', sublabel: 'fast + cheap (default)', value: 'gemini-2.5-flash' },
|
|
64
|
+
{ label: 'gemini-2.5-pro', sublabel: 'most capable', value: 'gemini-2.5-pro' },
|
|
65
|
+
],
|
|
66
|
+
deepseek: [
|
|
67
|
+
{ label: 'deepseek-chat', sublabel: 'cost-effective (default)', value: 'deepseek-chat' },
|
|
68
|
+
],
|
|
69
|
+
mistral: [
|
|
70
|
+
{ label: 'mistral-large-latest', sublabel: 'EU-hosted (default)', value: 'mistral-large-latest' },
|
|
71
|
+
],
|
|
72
|
+
groq: [
|
|
73
|
+
{ label: 'llama-3.3-70b-versatile', sublabel: 'ultra-fast (default)', value: 'llama-3.3-70b-versatile' },
|
|
74
|
+
],
|
|
75
|
+
xai: [
|
|
76
|
+
{ label: 'grok-3', sublabel: 'real-time knowledge (default)', value: 'grok-3' },
|
|
77
|
+
],
|
|
78
|
+
together: [
|
|
79
|
+
{ label: 'meta-llama/Llama-3.3-70B-Instruct-Turbo', sublabel: 'open source (default)', value: 'meta-llama/Llama-3.3-70B-Instruct-Turbo' },
|
|
80
|
+
],
|
|
81
|
+
fireworks: [
|
|
82
|
+
{ label: 'accounts/fireworks/models/llama-v3p3-70b-instruct', sublabel: 'open source (default)', value: 'accounts/fireworks/models/llama-v3p3-70b-instruct' },
|
|
83
|
+
],
|
|
84
|
+
cerebras: [
|
|
85
|
+
{ label: 'llama-3.3-70b', sublabel: 'fast inference (default)', value: 'llama-3.3-70b' },
|
|
86
|
+
],
|
|
87
|
+
zhipu: [
|
|
88
|
+
{ label: 'glm-4.7', sublabel: 'Chinese language (default)', value: 'glm-4.7' },
|
|
89
|
+
],
|
|
90
|
+
openrouter: [
|
|
91
|
+
{ label: 'anthropic/claude-sonnet-4', sublabel: 'default', value: 'anthropic/claude-sonnet-4' },
|
|
92
|
+
{ label: 'qwen/qwen3-coder', sublabel: 'code specialist', value: 'qwen/qwen3-coder' },
|
|
93
|
+
{ label: 'meta-llama/Llama-3.3-70B', sublabel: 'open source', value: 'meta-llama/Llama-3.3-70B' },
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
|
|
52
97
|
// ── ANSI Colors (respects NO_COLOR and --no-color) ───────
|
|
53
98
|
|
|
54
99
|
const noColor = !!(process.env.NO_COLOR || process.argv.includes('--no-color'));
|
|
@@ -114,20 +159,23 @@ function loadLlmConfig() {
|
|
|
114
159
|
if (fs.existsSync(f)) {
|
|
115
160
|
try {
|
|
116
161
|
const data = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
117
|
-
if (data.llm_model
|
|
162
|
+
if (data.llm_model || data.preferred_provider) {
|
|
163
|
+
return { llm_model: data.llm_model || null, preferred_provider: data.preferred_provider || null };
|
|
164
|
+
}
|
|
118
165
|
} catch {}
|
|
119
166
|
}
|
|
120
167
|
}
|
|
121
168
|
return null;
|
|
122
169
|
}
|
|
123
170
|
|
|
124
|
-
function saveLlmConfig(model) {
|
|
171
|
+
function saveLlmConfig(model, provider) {
|
|
125
172
|
// Merge into existing credentials
|
|
126
173
|
let existing = {};
|
|
127
174
|
if (fs.existsSync(USER_CRED_FILE)) {
|
|
128
175
|
try { existing = JSON.parse(fs.readFileSync(USER_CRED_FILE, 'utf8')); } catch {}
|
|
129
176
|
}
|
|
130
|
-
existing.llm_model = model;
|
|
177
|
+
if (model !== undefined) existing.llm_model = model;
|
|
178
|
+
if (provider !== undefined) existing.preferred_provider = provider;
|
|
131
179
|
const json = JSON.stringify(existing, null, 2);
|
|
132
180
|
fs.mkdirSync(USER_CRED_DIR, { recursive: true });
|
|
133
181
|
fs.writeFileSync(USER_CRED_FILE, json, { mode: 0o600 });
|
|
@@ -136,12 +184,32 @@ function saveLlmConfig(model) {
|
|
|
136
184
|
if (fs.existsSync(SKILL_CRED_FILE)) {
|
|
137
185
|
try { skillExisting = JSON.parse(fs.readFileSync(SKILL_CRED_FILE, 'utf8')); } catch {}
|
|
138
186
|
}
|
|
139
|
-
skillExisting.llm_model = model;
|
|
187
|
+
if (model !== undefined) skillExisting.llm_model = model;
|
|
188
|
+
if (provider !== undefined) skillExisting.preferred_provider = provider;
|
|
140
189
|
fs.mkdirSync(path.dirname(SKILL_CRED_FILE), { recursive: true });
|
|
141
190
|
fs.writeFileSync(SKILL_CRED_FILE, JSON.stringify(skillExisting, null, 2), { mode: 0o600 });
|
|
142
191
|
} catch {}
|
|
143
192
|
}
|
|
144
193
|
|
|
194
|
+
function resolveProvider() {
|
|
195
|
+
const config = loadLlmConfig();
|
|
196
|
+
const preferred = config?.preferred_provider;
|
|
197
|
+
|
|
198
|
+
if (preferred) {
|
|
199
|
+
// Find provider by name, check if any of their keys is set
|
|
200
|
+
const match = LLM_PROVIDERS.find(p => p.provider === preferred && process.env[p.key]);
|
|
201
|
+
if (match) return match;
|
|
202
|
+
// Key missing for preferred provider — warn + fallback
|
|
203
|
+
const providerInfo = LLM_PROVIDERS.find(p => p.provider === preferred);
|
|
204
|
+
if (providerInfo && !quietMode) {
|
|
205
|
+
console.log(` ${c.yellow}Preferred provider "${providerInfo.name}" missing key (${providerInfo.key}), falling back...${c.reset}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Fallback: first match wins
|
|
210
|
+
return LLM_PROVIDERS.find(p => process.env[p.key]) || null;
|
|
211
|
+
}
|
|
212
|
+
|
|
145
213
|
function saveCredentials(data) {
|
|
146
214
|
const json = JSON.stringify(data, null, 2);
|
|
147
215
|
fs.mkdirSync(USER_CRED_DIR, { recursive: true });
|
|
@@ -398,31 +466,17 @@ async function setupCommand() {
|
|
|
398
466
|
|
|
399
467
|
console.log();
|
|
400
468
|
|
|
401
|
-
// ── LLM
|
|
469
|
+
// ── LLM configuration hint ──
|
|
402
470
|
const llmConfig = loadLlmConfig();
|
|
403
|
-
console.log(` ${c.bold}LLM
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
console.log();
|
|
409
|
-
}
|
|
410
|
-
console.log(` ${c.dim}
|
|
411
|
-
console.log(`
|
|
412
|
-
console.log(` ${c.dim}gpt-4o${c.reset} ${c.dim}(OpenAI)${c.reset}`);
|
|
413
|
-
console.log(` ${c.dim}gemini-2.5-flash${c.reset} ${c.dim}(Google)${c.reset}`);
|
|
414
|
-
console.log(` ${c.dim}qwen/qwen3.5-coder-32b${c.reset} ${c.dim}(OpenRouter)${c.reset}`);
|
|
415
|
-
console.log(` ${c.dim}deepseek-chat${c.reset} ${c.dim}(DeepSeek)${c.reset}`);
|
|
416
|
-
console.log();
|
|
417
|
-
const modelAnswer = await askQuestion(` Model ${c.dim}(Enter to keep default, or type model name)${c.reset}: `);
|
|
418
|
-
if (modelAnswer) {
|
|
419
|
-
saveLlmConfig(modelAnswer);
|
|
420
|
-
console.log(` ${icons.safe} Model set to ${c.bold}${modelAnswer}${c.reset}`);
|
|
421
|
-
} else if (llmConfig) {
|
|
422
|
-
console.log(` ${c.dim}Keeping: ${llmConfig.llm_model}${c.reset}`);
|
|
423
|
-
} else {
|
|
424
|
-
console.log(` ${c.dim}No custom model — will use provider default${c.reset}`);
|
|
425
|
-
}
|
|
471
|
+
console.log(` ${c.bold}LLM Configuration${c.reset}`);
|
|
472
|
+
if (llmConfig?.llm_model || llmConfig?.preferred_provider) {
|
|
473
|
+
const parts = [];
|
|
474
|
+
if (llmConfig.preferred_provider) parts.push(llmConfig.preferred_provider);
|
|
475
|
+
if (llmConfig.llm_model) parts.push(llmConfig.llm_model);
|
|
476
|
+
console.log(` ${icons.safe} Current: ${c.bold}${parts.join(' → ')}${c.reset}`);
|
|
477
|
+
}
|
|
478
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit model${c.dim} to configure your LLM provider and model.${c.reset}`);
|
|
479
|
+
console.log(` ${c.dim}Deep audits require an LLM API key in your environment.${c.reset}`);
|
|
426
480
|
console.log();
|
|
427
481
|
|
|
428
482
|
console.log(` ${c.bold}Ready!${c.reset} You can now:`);
|
|
@@ -1513,8 +1567,8 @@ async function auditRepo(url) {
|
|
|
1513
1567
|
console.log(` ${c.green}done${c.reset}`);
|
|
1514
1568
|
|
|
1515
1569
|
// Step 4: LLM Analysis
|
|
1516
|
-
//
|
|
1517
|
-
const activeLlm =
|
|
1570
|
+
// Resolve provider: preferred_provider from config → first match fallback
|
|
1571
|
+
const activeLlm = resolveProvider();
|
|
1518
1572
|
const llmApiKey = activeLlm ? process.env[activeLlm.key] : null;
|
|
1519
1573
|
const activeProvider = activeLlm ? activeLlm.name : null;
|
|
1520
1574
|
|
|
@@ -1529,34 +1583,16 @@ async function auditRepo(url) {
|
|
|
1529
1583
|
}
|
|
1530
1584
|
|
|
1531
1585
|
if (!activeLlm) {
|
|
1532
|
-
// No LLM API key —
|
|
1586
|
+
// No LLM API key — compact explanation
|
|
1533
1587
|
console.log();
|
|
1534
1588
|
console.log(` ${c.yellow}No LLM API key found.${c.reset} The ${c.bold}audit${c.reset} command needs an LLM to analyze code.`);
|
|
1535
1589
|
console.log();
|
|
1536
|
-
console.log(` ${c.bold}
|
|
1537
|
-
console.log(` ${c.
|
|
1538
|
-
console.log(` ${c.cyan}OPENAI_API_KEY${c.reset} OpenAI GPT-4o`);
|
|
1539
|
-
console.log(` ${c.cyan}GEMINI_API_KEY${c.reset} Google Gemini`);
|
|
1540
|
-
console.log(` ${c.cyan}DEEPSEEK_API_KEY${c.reset} DeepSeek`);
|
|
1541
|
-
console.log(` ${c.cyan}MISTRAL_API_KEY${c.reset} Mistral`);
|
|
1542
|
-
console.log(` ${c.cyan}XAI_API_KEY${c.reset} xAI Grok`);
|
|
1543
|
-
console.log(` ${c.cyan}GROQ_API_KEY${c.reset} Groq`);
|
|
1544
|
-
console.log(` ${c.cyan}TOGETHER_API_KEY${c.reset} Together AI`);
|
|
1545
|
-
console.log(` ${c.cyan}FIREWORKS_API_KEY${c.reset} Fireworks AI`);
|
|
1546
|
-
console.log(` ${c.cyan}CEREBRAS_API_KEY${c.reset} Cerebras`);
|
|
1547
|
-
console.log(` ${c.cyan}ZAI_API_KEY${c.reset} Zhipu AI (GLM)`);
|
|
1548
|
-
console.log(` ${c.cyan}OPENROUTER_API_KEY${c.reset} OpenRouter (any model)`);
|
|
1549
|
-
console.log();
|
|
1550
|
-
console.log(` ${c.dim}export ANTHROPIC_API_KEY=sk-ant-...${c.reset}`);
|
|
1551
|
-
console.log(` ${c.dim}export OPENAI_API_KEY=sk-...${c.reset}`);
|
|
1590
|
+
console.log(` ${c.bold}Set an API key${c.reset} (e.g. ${c.cyan}export OPENROUTER_API_KEY=sk-or-...${c.reset})`);
|
|
1591
|
+
console.log(` ${c.dim}Run "agentaudit model" to configure provider + model interactively${c.reset}`);
|
|
1552
1592
|
console.log();
|
|
1553
|
-
console.log(` ${c.bold}
|
|
1554
|
-
console.log(` ${c.
|
|
1555
|
-
console.log(` ${c.dim}
|
|
1556
|
-
console.log();
|
|
1557
|
-
console.log(` ${c.bold}Option 3: Use MCP in Claude/Cursor/Windsurf (no API key needed)${c.reset}`);
|
|
1558
|
-
console.log(` ${c.dim}Add AgentAudit as MCP server — your editor's agent runs the audit using its own LLM.${c.reset}`);
|
|
1559
|
-
console.log(` ${c.dim}Config: { "mcpServers": { "agentaudit": { "command": "npx", "args": ["-y", "agentaudit"] } } }${c.reset}`);
|
|
1593
|
+
console.log(` ${c.bold}Or export for manual review:${c.reset} ${c.cyan}agentaudit audit ${url} --export${c.reset}`);
|
|
1594
|
+
console.log(` ${c.bold}Or use as MCP server${c.reset} in Cursor/Claude ${c.dim}(no extra API key needed)${c.reset}`);
|
|
1595
|
+
console.log(` ${c.dim}{ "agentaudit": { "command": "npx", "args": ["-y", "agentaudit"] } }${c.reset}`);
|
|
1560
1596
|
console.log();
|
|
1561
1597
|
|
|
1562
1598
|
// Check if --export flag
|
|
@@ -1793,9 +1829,12 @@ async function auditRepo(url) {
|
|
|
1793
1829
|
console.log();
|
|
1794
1830
|
}
|
|
1795
1831
|
|
|
1796
|
-
// Upload to registry
|
|
1797
|
-
const
|
|
1798
|
-
|
|
1832
|
+
// Upload to registry (skip with --no-upload)
|
|
1833
|
+
const noUpload = process.argv.includes('--no-upload');
|
|
1834
|
+
let creds = loadCredentials();
|
|
1835
|
+
if (noUpload) {
|
|
1836
|
+
// Skip silently
|
|
1837
|
+
} else if (creds) {
|
|
1799
1838
|
process.stdout.write(` Uploading report to registry...`);
|
|
1800
1839
|
try {
|
|
1801
1840
|
const res = await fetch(`${REGISTRY_URL}/api/reports`, {
|
|
@@ -1817,8 +1856,51 @@ async function auditRepo(url) {
|
|
|
1817
1856
|
} catch (err) {
|
|
1818
1857
|
console.log(` ${c.yellow}failed${c.reset}`);
|
|
1819
1858
|
}
|
|
1859
|
+
} else if (process.stdin.isTTY) {
|
|
1860
|
+
// No credentials — offer inline registration
|
|
1861
|
+
console.log();
|
|
1862
|
+
console.log(` ${c.bold}Share this audit with the community?${c.reset}`);
|
|
1863
|
+
console.log(` ${c.dim}Uploading helps others assess package security. Account is free.${c.reset}`);
|
|
1864
|
+
console.log();
|
|
1865
|
+
console.log(` ${c.bold}1)${c.reset} Create account + upload ${c.dim}(free)${c.reset}`);
|
|
1866
|
+
console.log(` ${c.bold}2)${c.reset} Skip`);
|
|
1867
|
+
console.log();
|
|
1868
|
+
const uploadChoice = await askQuestion(` Choice ${c.dim}(1/2)${c.reset}: `);
|
|
1869
|
+
if (uploadChoice === '1') {
|
|
1870
|
+
const name = await askQuestion(` Agent name ${c.dim}(e.g. my-scanner, claude-desktop)${c.reset}: `);
|
|
1871
|
+
if (!name || !/^[a-zA-Z0-9._-]{2,64}$/.test(name)) {
|
|
1872
|
+
console.log(` ${c.red}Invalid name. Use 2-64 chars: letters, numbers, dash, underscore, dot.${c.reset}`);
|
|
1873
|
+
} else {
|
|
1874
|
+
try {
|
|
1875
|
+
process.stdout.write(` Registering ${c.bold}${name}${c.reset}...`);
|
|
1876
|
+
const regData = await registerAgent(name);
|
|
1877
|
+
saveCredentials({ api_key: regData.api_key, agent_name: regData.agent_name });
|
|
1878
|
+
console.log(` ${c.green}done!${c.reset}`);
|
|
1879
|
+
creds = { api_key: regData.api_key, agent_name: regData.agent_name };
|
|
1880
|
+
process.stdout.write(` Uploading report...`);
|
|
1881
|
+
const res = await fetch(`${REGISTRY_URL}/api/reports`, {
|
|
1882
|
+
method: 'POST',
|
|
1883
|
+
headers: {
|
|
1884
|
+
'Authorization': `Bearer ${creds.api_key}`,
|
|
1885
|
+
'Content-Type': 'application/json',
|
|
1886
|
+
},
|
|
1887
|
+
body: JSON.stringify(report),
|
|
1888
|
+
signal: AbortSignal.timeout(15_000),
|
|
1889
|
+
});
|
|
1890
|
+
if (res.ok) {
|
|
1891
|
+
console.log(` ${c.green}done${c.reset}`);
|
|
1892
|
+
console.log(` ${c.dim}Report: ${REGISTRY_URL}/skills/${slug}${c.reset}`);
|
|
1893
|
+
} else {
|
|
1894
|
+
console.log(` ${c.yellow}failed (HTTP ${res.status})${c.reset}`);
|
|
1895
|
+
}
|
|
1896
|
+
} catch (err) {
|
|
1897
|
+
console.log(` ${c.red}failed${c.reset}`);
|
|
1898
|
+
console.log(` ${c.dim}${err.message}${c.reset}`);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1820
1902
|
} else {
|
|
1821
|
-
console.log(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to
|
|
1903
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to create an account and upload reports${c.reset}`);
|
|
1822
1904
|
}
|
|
1823
1905
|
|
|
1824
1906
|
console.log();
|
|
@@ -1871,7 +1953,7 @@ async function main() {
|
|
|
1871
1953
|
// --no-color already handled at top level for `c` object
|
|
1872
1954
|
|
|
1873
1955
|
// Strip global flags from args (including --model <value>)
|
|
1874
|
-
const globalFlags = new Set(['--json', '--quiet', '-q', '--no-color']);
|
|
1956
|
+
const globalFlags = new Set(['--json', '--quiet', '-q', '--no-color', '--no-upload']);
|
|
1875
1957
|
let args = rawArgs.filter(a => !globalFlags.has(a));
|
|
1876
1958
|
// Remove --model <value> pair
|
|
1877
1959
|
const modelIdx = args.indexOf('--model');
|
|
@@ -1894,14 +1976,16 @@ async function main() {
|
|
|
1894
1976
|
console.log(` ${c.cyan}agentaudit scan${c.reset} <url> ${c.dim}--deep${c.reset} Deep audit (same as audit)`);
|
|
1895
1977
|
console.log(` ${c.cyan}agentaudit audit${c.reset} <url> [url...] Deep LLM-powered security audit`);
|
|
1896
1978
|
console.log(` ${c.cyan}agentaudit lookup${c.reset} <name> Look up package in registry`);
|
|
1897
|
-
console.log(` ${c.cyan}agentaudit model${c.reset}
|
|
1898
|
-
console.log(` ${c.cyan}agentaudit
|
|
1979
|
+
console.log(` ${c.cyan}agentaudit model${c.reset} Configure LLM provider + model interactively`);
|
|
1980
|
+
console.log(` ${c.cyan}agentaudit model${c.reset} <name> Set model directly (e.g. gpt-4o)`);
|
|
1981
|
+
console.log(` ${c.cyan}agentaudit setup${c.reset} Create account / enter API key (for report uploads)`);
|
|
1899
1982
|
console.log();
|
|
1900
1983
|
console.log(` ${c.bold}Global flags:${c.reset}`);
|
|
1901
1984
|
console.log(` ${c.dim}--json Output JSON to stdout (machine-readable)${c.reset}`);
|
|
1902
1985
|
console.log(` ${c.dim}--quiet Suppress banner and tree visualization${c.reset}`);
|
|
1903
1986
|
console.log(` ${c.dim}--no-color Disable ANSI colors (also: NO_COLOR env)${c.reset}`);
|
|
1904
|
-
console.log(` ${c.dim}--model <name> Override LLM model
|
|
1987
|
+
console.log(` ${c.dim}--model <name> Override LLM model for this run${c.reset}`);
|
|
1988
|
+
console.log(` ${c.dim}--no-upload Skip uploading report to registry${c.reset}`);
|
|
1905
1989
|
console.log();
|
|
1906
1990
|
console.log(` ${c.bold}Quick Scan${c.reset} vs ${c.bold}Deep Audit${c.reset}:`);
|
|
1907
1991
|
console.log(` ${c.dim}scan = fast regex-based static analysis (~2s)${c.reset}`);
|
|
@@ -1917,14 +2001,11 @@ async function main() {
|
|
|
1917
2001
|
console.log(` agentaudit audit https://github.com/owner/repo`);
|
|
1918
2002
|
console.log(` agentaudit lookup fastmcp --json`);
|
|
1919
2003
|
console.log();
|
|
1920
|
-
console.log(` ${c.bold}For deep audits,${c.reset} set
|
|
1921
|
-
console.log(`
|
|
1922
|
-
console.log(` ${c.dim}MISTRAL_API_KEY, XAI_API_KEY, GROQ_API_KEY, TOGETHER_API_KEY,${c.reset}`);
|
|
1923
|
-
console.log(` ${c.dim}FIREWORKS_API_KEY, CEREBRAS_API_KEY, ZAI_API_KEY, or OPENROUTER_API_KEY${c.reset}`);
|
|
2004
|
+
console.log(` ${c.bold}For deep audits,${c.reset} set an LLM API key (e.g. ${c.cyan}export OPENROUTER_API_KEY=sk-or-...${c.reset})`);
|
|
2005
|
+
console.log(` ${c.dim}Run "agentaudit model" to configure provider + model interactively${c.reset}`);
|
|
1924
2006
|
console.log();
|
|
1925
2007
|
console.log(` ${c.bold}Or use as MCP server${c.reset} in Cursor/Claude ${c.dim}(no extra API key needed):${c.reset}`);
|
|
1926
|
-
console.log(`
|
|
1927
|
-
console.log(` ${c.dim}{ "agentaudit": { "command": "npx", "args": ["-y", "agentaudit"] } }${c.reset}`);
|
|
2008
|
+
console.log(` ${c.dim}{ "agentaudit": { "command": "npx", "args": ["-y", "agentaudit"] } }${c.reset}`);
|
|
1928
2009
|
console.log();
|
|
1929
2010
|
process.exitCode = 0; return;
|
|
1930
2011
|
}
|
|
@@ -1942,23 +2023,27 @@ async function main() {
|
|
|
1942
2023
|
|
|
1943
2024
|
if (command === 'model') {
|
|
1944
2025
|
const newModel = targets.filter(t => !t.startsWith('--'))[0];
|
|
1945
|
-
const
|
|
2026
|
+
const config = loadLlmConfig();
|
|
2027
|
+
const current = config?.llm_model;
|
|
2028
|
+
const currentProvider = config?.preferred_provider;
|
|
1946
2029
|
|
|
1947
|
-
// Direct set: agentaudit model
|
|
2030
|
+
// Direct set: agentaudit model reset
|
|
1948
2031
|
if (newModel === 'reset' || newModel === 'clear') {
|
|
1949
2032
|
for (const f of [USER_CRED_FILE, SKILL_CRED_FILE]) {
|
|
1950
2033
|
if (fs.existsSync(f)) {
|
|
1951
2034
|
try {
|
|
1952
2035
|
const data = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
1953
2036
|
delete data.llm_model;
|
|
2037
|
+
delete data.preferred_provider;
|
|
1954
2038
|
fs.writeFileSync(f, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
1955
2039
|
} catch {}
|
|
1956
2040
|
}
|
|
1957
2041
|
}
|
|
1958
|
-
console.log(` ${icons.safe} Model reset to
|
|
2042
|
+
console.log(` ${icons.safe} Model + provider reset to defaults`);
|
|
1959
2043
|
return;
|
|
1960
2044
|
}
|
|
1961
2045
|
|
|
2046
|
+
// Direct set: agentaudit model <name> (sets model only, keeps provider)
|
|
1962
2047
|
if (newModel) {
|
|
1963
2048
|
saveLlmConfig(newModel);
|
|
1964
2049
|
console.log(` ${icons.safe} Model set to ${c.bold}${newModel}${c.reset}`);
|
|
@@ -1966,82 +2051,136 @@ async function main() {
|
|
|
1966
2051
|
return;
|
|
1967
2052
|
}
|
|
1968
2053
|
|
|
1969
|
-
// No argument — interactive menu
|
|
1970
|
-
|
|
1971
|
-
|
|
2054
|
+
// ── No argument — interactive two-step menu ──
|
|
2055
|
+
|
|
2056
|
+
// Build deduplicated provider list
|
|
2057
|
+
const seen = new Set();
|
|
2058
|
+
const providerList = [];
|
|
2059
|
+
for (const p of LLM_PROVIDERS) {
|
|
2060
|
+
if (seen.has(p.provider)) continue;
|
|
2061
|
+
seen.add(p.provider);
|
|
2062
|
+
// Check if ANY key for this provider is set
|
|
2063
|
+
const keys = LLM_PROVIDERS.filter(x => x.provider === p.provider);
|
|
2064
|
+
const hasKey = keys.some(x => process.env[x.key]);
|
|
2065
|
+
const keyName = p.key;
|
|
2066
|
+
providerList.push({
|
|
2067
|
+
label: p.name,
|
|
2068
|
+
sublabel: hasKey
|
|
2069
|
+
? `${keyName} ${c.green}✔${c.reset}`
|
|
2070
|
+
: `${keyName} ${c.red}✗${c.reset} ${c.dim}set: export ${keyName}=...${c.reset}`,
|
|
2071
|
+
value: p.provider,
|
|
2072
|
+
hasKey,
|
|
2073
|
+
keyName,
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
// Show status
|
|
2078
|
+
const resolved = resolveProvider();
|
|
2079
|
+
console.log(` ${c.bold}LLM Configuration${c.reset}`);
|
|
1972
2080
|
console.log();
|
|
1973
|
-
if (current) {
|
|
1974
|
-
|
|
2081
|
+
if (currentProvider && current) {
|
|
2082
|
+
const provInfo = LLM_PROVIDERS.find(p => p.provider === currentProvider);
|
|
2083
|
+
console.log(` Active: ${c.bold}${c.cyan}${provInfo?.name || currentProvider} → ${current}${c.reset}`);
|
|
2084
|
+
} else if (current) {
|
|
2085
|
+
console.log(` Active: ${c.bold}${c.cyan}${resolved?.name || 'auto'} → ${current}${c.reset}`);
|
|
2086
|
+
} else if (resolved) {
|
|
2087
|
+
console.log(` Active: ${c.dim}${resolved.name} → ${resolved.model} (defaults)${c.reset}`);
|
|
1975
2088
|
} else {
|
|
1976
|
-
console.log(`
|
|
1977
|
-
}
|
|
1978
|
-
if (activeLlm) {
|
|
1979
|
-
console.log(` Provider: ${c.dim}${activeLlm.name} (${activeLlm.key})${c.reset}`);
|
|
2089
|
+
console.log(` Active: ${c.yellow}no provider configured${c.reset}`);
|
|
1980
2090
|
}
|
|
1981
2091
|
console.log();
|
|
1982
2092
|
|
|
1983
|
-
//
|
|
2093
|
+
// Step A: Provider selection
|
|
2094
|
+
const providerChoices = [
|
|
2095
|
+
...providerList,
|
|
2096
|
+
{ label: '(keep current)', sublabel: '', value: '__keep__', hasKey: true },
|
|
2097
|
+
];
|
|
2098
|
+
|
|
2099
|
+
const selectedProvider = await singleSelect(providerChoices, {
|
|
2100
|
+
title: 'Choose provider',
|
|
2101
|
+
hint: '↑↓ move Enter select Esc cancel',
|
|
2102
|
+
});
|
|
2103
|
+
|
|
2104
|
+
if (selectedProvider === null) {
|
|
2105
|
+
console.log(` ${c.dim}Cancelled${c.reset}`);
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
if (selectedProvider === '__keep__') {
|
|
2110
|
+
console.log(` ${c.dim}Keeping current config${c.reset}`);
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
// Check if provider has key set
|
|
2115
|
+
const chosenEntry = providerList.find(p => p.value === selectedProvider);
|
|
2116
|
+
if (chosenEntry && !chosenEntry.hasKey) {
|
|
2117
|
+
console.log();
|
|
2118
|
+
console.log(` ${c.yellow}${chosenEntry.keyName} is not set.${c.reset}`);
|
|
2119
|
+
console.log(` ${c.dim}Run: export ${chosenEntry.keyName}=your-key-here${c.reset}`);
|
|
2120
|
+
console.log(` ${c.dim}Then run "agentaudit model" again.${c.reset}`);
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
// Step B: Model selection for chosen provider
|
|
2125
|
+
console.log();
|
|
2126
|
+
const providerModels = PROVIDER_MODELS[selectedProvider] || [];
|
|
1984
2127
|
const modelChoices = [
|
|
1985
|
-
|
|
1986
|
-
{ label: '
|
|
1987
|
-
{ label: '
|
|
1988
|
-
{ label: 'gpt-4.1', sublabel: 'OpenAI — latest', value: 'gpt-4.1' },
|
|
1989
|
-
{ label: 'gemini-2.5-flash', sublabel: 'Google — fast + cheap', value: 'gemini-2.5-flash' },
|
|
1990
|
-
{ label: 'gemini-2.5-pro', sublabel: 'Google — most capable', value: 'gemini-2.5-pro' },
|
|
1991
|
-
{ label: 'deepseek-chat', sublabel: 'DeepSeek — cost-effective', value: 'deepseek-chat' },
|
|
1992
|
-
{ label: 'qwen/qwen3-coder', sublabel: 'OpenRouter — code specialist', value: 'qwen/qwen3-coder' },
|
|
1993
|
-
{ label: 'mistral-large-latest', sublabel: 'Mistral — EU-hosted', value: 'mistral-large-latest' },
|
|
1994
|
-
{ label: 'grok-3', sublabel: 'xAI — real-time knowledge', value: 'grok-3' },
|
|
1995
|
-
{ label: 'meta-llama/Llama-3.3-70B-Instruct-Turbo', sublabel: 'Together AI — open source', value: 'meta-llama/Llama-3.3-70B-Instruct-Turbo' },
|
|
1996
|
-
{ label: 'llama-3.3-70b-versatile', sublabel: 'Groq — ultra-fast inference', value: 'llama-3.3-70b-versatile' },
|
|
1997
|
-
{ label: '(reset to provider default)', sublabel: '', value: '__reset__' },
|
|
1998
|
-
{ label: '(enter custom model name)', sublabel: '', value: '__custom__' },
|
|
2128
|
+
...providerModels,
|
|
2129
|
+
{ label: '(enter custom model name)', sublabel: '', value: '__custom__' },
|
|
2130
|
+
{ label: '(reset to provider default)', sublabel: '', value: '__reset__' },
|
|
1999
2131
|
];
|
|
2000
2132
|
|
|
2001
|
-
const
|
|
2002
|
-
|
|
2003
|
-
|
|
2133
|
+
const providerName = LLM_PROVIDERS.find(p => p.provider === selectedProvider)?.name || selectedProvider;
|
|
2134
|
+
const selectedModel = await singleSelect(modelChoices, {
|
|
2135
|
+
title: `Choose model for ${providerName}`,
|
|
2136
|
+
hint: '↑↓ move Enter select Esc cancel',
|
|
2004
2137
|
});
|
|
2005
2138
|
|
|
2006
|
-
if (
|
|
2139
|
+
if (selectedModel === null) {
|
|
2007
2140
|
console.log(` ${c.dim}Cancelled${c.reset}`);
|
|
2008
2141
|
return;
|
|
2009
2142
|
}
|
|
2010
2143
|
|
|
2011
|
-
if (
|
|
2144
|
+
if (selectedModel === '__reset__') {
|
|
2145
|
+
// Save provider, clear model (use provider default)
|
|
2146
|
+
saveLlmConfig(undefined, selectedProvider);
|
|
2147
|
+
// Also clear llm_model from both files
|
|
2012
2148
|
for (const f of [USER_CRED_FILE, SKILL_CRED_FILE]) {
|
|
2013
2149
|
if (fs.existsSync(f)) {
|
|
2014
2150
|
try {
|
|
2015
2151
|
const data = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
2016
2152
|
delete data.llm_model;
|
|
2153
|
+
data.preferred_provider = selectedProvider;
|
|
2017
2154
|
fs.writeFileSync(f, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
2018
2155
|
} catch {}
|
|
2019
2156
|
}
|
|
2020
2157
|
}
|
|
2158
|
+
const defaultModel = LLM_PROVIDERS.find(p => p.provider === selectedProvider)?.model;
|
|
2021
2159
|
console.log();
|
|
2022
|
-
console.log(` ${icons.safe}
|
|
2160
|
+
console.log(` ${icons.safe} Provider: ${c.bold}${providerName}${c.reset}, model: ${c.dim}${defaultModel} (default)${c.reset}`);
|
|
2023
2161
|
return;
|
|
2024
2162
|
}
|
|
2025
2163
|
|
|
2026
|
-
if (
|
|
2164
|
+
if (selectedModel === '__custom__') {
|
|
2027
2165
|
console.log();
|
|
2028
2166
|
const custom = await askQuestion(` Model name: `);
|
|
2029
2167
|
if (!custom) {
|
|
2030
2168
|
console.log(` ${c.dim}Cancelled${c.reset}`);
|
|
2031
2169
|
return;
|
|
2032
2170
|
}
|
|
2033
|
-
saveLlmConfig(custom);
|
|
2034
|
-
console.log(` ${icons.safe}
|
|
2035
|
-
if (current) console.log(` ${c.dim}Was: ${current}${c.reset}`);
|
|
2171
|
+
saveLlmConfig(custom, selectedProvider);
|
|
2172
|
+
console.log(` ${icons.safe} Provider: ${c.bold}${providerName}${c.reset}, model: ${c.bold}${custom}${c.reset}`);
|
|
2173
|
+
if (current) console.log(` ${c.dim}Was: ${currentProvider || 'auto'} → ${current}${c.reset}`);
|
|
2036
2174
|
return;
|
|
2037
2175
|
}
|
|
2038
2176
|
|
|
2039
|
-
|
|
2177
|
+
// Normal model selection
|
|
2178
|
+
saveLlmConfig(selectedModel, selectedProvider);
|
|
2040
2179
|
console.log();
|
|
2041
|
-
console.log(` ${icons.safe}
|
|
2042
|
-
if (current) console.log(` ${c.dim}Was: ${current}${c.reset}`);
|
|
2180
|
+
console.log(` ${icons.safe} Provider: ${c.bold}${providerName}${c.reset}, model: ${c.bold}${selectedModel}${c.reset}`);
|
|
2181
|
+
if (current) console.log(` ${c.dim}Was: ${currentProvider || 'auto'} → ${current}${c.reset}`);
|
|
2043
2182
|
console.log();
|
|
2044
|
-
console.log(` ${c.dim}Tip: agentaudit model <name> to set directly, or agentaudit model reset${c.reset}`);
|
|
2183
|
+
console.log(` ${c.dim}Tip: agentaudit model <name> to set model directly, or agentaudit model reset${c.reset}`);
|
|
2045
2184
|
return;
|
|
2046
2185
|
}
|
|
2047
2186
|
|