agentaudit 3.12.5 → 3.12.7
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 +186 -11
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
* activity Your recent audits & findings
|
|
16
16
|
* search <query> Search packages in registry
|
|
17
17
|
* model [name|reset] Configure LLM provider + model
|
|
18
|
-
*
|
|
18
|
+
* login Sign in with GitHub (opens browser, auto-creates API key)
|
|
19
|
+
* setup Manual login — paste an API key from agentaudit.dev
|
|
19
20
|
* status Show current config + auth status
|
|
20
21
|
* profile Your profile — rank, points, audit stats
|
|
21
22
|
* help [command] Show help
|
|
@@ -579,6 +580,117 @@ async function setupCommand() {
|
|
|
579
580
|
console.log();
|
|
580
581
|
}
|
|
581
582
|
|
|
583
|
+
// ── Login via GitHub Device Flow ─────────────────────────
|
|
584
|
+
|
|
585
|
+
async function loginCommand() {
|
|
586
|
+
console.log(` ${c.bold}AgentAudit Login${c.reset}`);
|
|
587
|
+
console.log(` ${c.dim}Sign in with GitHub to upload audit reports${c.reset}`);
|
|
588
|
+
console.log();
|
|
589
|
+
|
|
590
|
+
const existing = loadCredentials();
|
|
591
|
+
if (existing) {
|
|
592
|
+
console.log(` ${icons.safe} Already logged in as ${c.bold}${existing.agent_name}${c.reset}`);
|
|
593
|
+
console.log(` ${c.dim}Key: ${existing.api_key.slice(0, 12)}...${c.reset}`);
|
|
594
|
+
console.log();
|
|
595
|
+
const answer = await askQuestion(` Re-authenticate? ${c.dim}(y/N)${c.reset} `);
|
|
596
|
+
if (answer.toLowerCase() !== 'y') {
|
|
597
|
+
console.log(` ${c.dim}Keeping existing login.${c.reset}`);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
console.log();
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Step 1: Start device flow
|
|
604
|
+
process.stdout.write(` Starting login flow...`);
|
|
605
|
+
let deviceData;
|
|
606
|
+
try {
|
|
607
|
+
const res = await fetch(`${REGISTRY_URL}/api/auth/device`, {
|
|
608
|
+
method: 'POST',
|
|
609
|
+
headers: { 'Content-Type': 'application/json' },
|
|
610
|
+
signal: AbortSignal.timeout(10_000),
|
|
611
|
+
});
|
|
612
|
+
deviceData = await res.json();
|
|
613
|
+
if (!res.ok || !deviceData.device_code) {
|
|
614
|
+
console.log(` ${c.red}failed${c.reset}`);
|
|
615
|
+
console.log(` ${c.red}${deviceData.error || 'Could not start login flow'}${c.reset}`);
|
|
616
|
+
console.log(` ${c.dim}Fallback: run ${c.cyan}agentaudit setup${c.dim} to paste an API key manually${c.reset}`);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
console.log(` ${c.green}ok${c.reset}`);
|
|
620
|
+
} catch (err) {
|
|
621
|
+
console.log(` ${c.red}failed${c.reset}`);
|
|
622
|
+
console.log(` ${c.red}Could not reach ${REGISTRY_URL}${c.reset}`);
|
|
623
|
+
console.log(` ${c.dim}Fallback: run ${c.cyan}agentaudit setup${c.dim} to paste an API key manually${c.reset}`);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Step 2: Open browser
|
|
628
|
+
const verifyUrl = deviceData.verification_url;
|
|
629
|
+
console.log();
|
|
630
|
+
console.log(` ${c.bold}Open this URL in your browser:${c.reset}`);
|
|
631
|
+
console.log(` ${c.cyan}${verifyUrl}${c.reset}`);
|
|
632
|
+
console.log();
|
|
633
|
+
|
|
634
|
+
// Try to auto-open browser
|
|
635
|
+
try {
|
|
636
|
+
const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
637
|
+
const { exec } = await import('child_process');
|
|
638
|
+
exec(`${openCmd} "${verifyUrl}"`);
|
|
639
|
+
console.log(` ${c.dim}(Browser should open automatically)${c.reset}`);
|
|
640
|
+
} catch {}
|
|
641
|
+
|
|
642
|
+
// Step 3: Poll for authorization
|
|
643
|
+
console.log(` ${c.dim}Waiting for GitHub authorization...${c.reset}`);
|
|
644
|
+
console.log();
|
|
645
|
+
|
|
646
|
+
const interval = (deviceData.interval || 5) * 1000;
|
|
647
|
+
const maxAttempts = Math.ceil((deviceData.expires_in || 900) / (interval / 1000));
|
|
648
|
+
|
|
649
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
650
|
+
await new Promise(r => setTimeout(r, interval));
|
|
651
|
+
|
|
652
|
+
try {
|
|
653
|
+
const res = await fetch(`${REGISTRY_URL}/api/auth/device?device_code=${deviceData.device_code}`, {
|
|
654
|
+
signal: AbortSignal.timeout(10_000),
|
|
655
|
+
});
|
|
656
|
+
const data = await res.json();
|
|
657
|
+
|
|
658
|
+
if (res.ok && data.api_key) {
|
|
659
|
+
// Success!
|
|
660
|
+
saveCredentials({ api_key: data.api_key, agent_name: data.agent_name });
|
|
661
|
+
console.log(` ${c.green}${icons.safe} Logged in as ${c.bold}${data.agent_name}${c.reset}`);
|
|
662
|
+
console.log(` ${c.dim}Key saved to: ${USER_CRED_FILE}${c.reset}`);
|
|
663
|
+
console.log();
|
|
664
|
+
console.log(` ${c.bold}Ready!${c.reset} You can now:`);
|
|
665
|
+
console.log(` ${c.dim}•${c.reset} Audit packages: ${c.cyan}agentaudit audit <repo-url>${c.reset}`);
|
|
666
|
+
console.log(` ${c.dim}•${c.reset} Quick scan: ${c.cyan}agentaudit scan <repo-url>${c.reset}`);
|
|
667
|
+
console.log(` ${c.dim}•${c.reset} Check registry: ${c.cyan}agentaudit check <name>${c.reset}`);
|
|
668
|
+
console.log();
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (data.error === 'authorization_pending') {
|
|
673
|
+
process.stdout.write(`\r ${c.dim}Waiting... (${attempt + 1}/${maxAttempts})${c.reset} `);
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (data.error === 'expired_token') {
|
|
678
|
+
console.log(`\n ${c.red}Login expired. Run ${c.cyan}agentaudit login${c.red} again.${c.reset}`);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Unknown error
|
|
683
|
+
console.log(`\n ${c.red}${data.error || 'Unknown error'}${c.reset}`);
|
|
684
|
+
return;
|
|
685
|
+
} catch {
|
|
686
|
+
// Network error during poll — continue trying
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
console.log(`\n ${c.red}Login timed out. Run ${c.cyan}agentaudit login${c.red} again.${c.reset}`);
|
|
692
|
+
}
|
|
693
|
+
|
|
582
694
|
// ── Helpers ──────────────────────────────────────────────
|
|
583
695
|
|
|
584
696
|
function validateGitUrl(url) {
|
|
@@ -2587,10 +2699,43 @@ function loadAuditPrompt() {
|
|
|
2587
2699
|
return null;
|
|
2588
2700
|
}
|
|
2589
2701
|
|
|
2702
|
+
// Known context window sizes (input tokens) for common models
|
|
2703
|
+
const MODEL_CONTEXT_LIMITS = {
|
|
2704
|
+
'claude-sonnet-4': 200000, 'claude-opus-4': 200000, 'claude-haiku-4': 200000,
|
|
2705
|
+
'claude-3.5-sonnet': 200000, 'claude-3-haiku': 200000,
|
|
2706
|
+
'gpt-4o': 128000, 'gpt-4o-mini': 128000, 'gpt-4-turbo': 128000, 'gpt-4': 8192,
|
|
2707
|
+
'gemini-2.5-flash': 1048576, 'gemini-2.5-pro': 1048576, 'gemini-2.0-flash': 1048576,
|
|
2708
|
+
'deepseek-chat': 64000, 'deepseek-reasoner': 64000,
|
|
2709
|
+
'mistral-large': 128000, 'mistral-small': 32000,
|
|
2710
|
+
};
|
|
2711
|
+
|
|
2712
|
+
function estimateTokens(text) { return Math.ceil(text.length / 3.5); }
|
|
2713
|
+
|
|
2714
|
+
function checkContextLimit(model, systemPrompt, userMessage) {
|
|
2715
|
+
const modelKey = Object.keys(MODEL_CONTEXT_LIMITS).find(k => model.toLowerCase().includes(k.toLowerCase()));
|
|
2716
|
+
if (!modelKey) return null; // unknown model, skip check
|
|
2717
|
+
const limit = MODEL_CONTEXT_LIMITS[modelKey];
|
|
2718
|
+
const estimated = estimateTokens(systemPrompt) + estimateTokens(userMessage);
|
|
2719
|
+
if (estimated > limit * 0.9) {
|
|
2720
|
+
return { estimated, limit, pct: Math.round(estimated / limit * 100) };
|
|
2721
|
+
}
|
|
2722
|
+
return null;
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2590
2725
|
async function callLlm(llmConfig, systemPrompt, userMessage) {
|
|
2591
2726
|
const apiKey = process.env[llmConfig.key];
|
|
2592
2727
|
if (!apiKey) return { error: `Missing API key: ${llmConfig.key}` };
|
|
2593
2728
|
const start = Date.now();
|
|
2729
|
+
|
|
2730
|
+
// Context window warning
|
|
2731
|
+
const ctxCheck = checkContextLimit(llmConfig.model, systemPrompt, userMessage);
|
|
2732
|
+
if (ctxCheck) {
|
|
2733
|
+
console.log(` ${c.yellow}⚠ Input ~${Math.round(ctxCheck.estimated/1000)}k tokens (${ctxCheck.pct}% of ${Math.round(ctxCheck.limit/1000)}k context window)${c.reset}`);
|
|
2734
|
+
if (ctxCheck.pct > 100) {
|
|
2735
|
+
return { error: `Input too large (~${Math.round(ctxCheck.estimated/1000)}k tokens) for ${llmConfig.model} (${Math.round(ctxCheck.limit/1000)}k context limit). Try a smaller package or a model with a larger context window.` };
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2594
2739
|
let _text = '';
|
|
2595
2740
|
try {
|
|
2596
2741
|
let data;
|
|
@@ -2607,14 +2752,19 @@ async function callLlm(llmConfig, systemPrompt, userMessage) {
|
|
|
2607
2752
|
return { error: friendly?.text || data.error.message || JSON.stringify(data.error), hint: friendly?.hint, duration: Date.now() - start };
|
|
2608
2753
|
}
|
|
2609
2754
|
_text = data.content?.[0]?.text || '';
|
|
2755
|
+
if (data.stop_reason === 'max_tokens') {
|
|
2756
|
+
console.log(` ${c.red}✗ Output truncated — model hit max_tokens limit (${data.usage?.output_tokens || '?'} tokens). Results may be incomplete.${c.reset}`);
|
|
2757
|
+
console.log(` ${c.dim} Hint: Try a model with higher output capacity, or scan a smaller package.${c.reset}`);
|
|
2758
|
+
}
|
|
2610
2759
|
const report = extractJSON(_text);
|
|
2611
2760
|
if (report) {
|
|
2612
2761
|
report.audit_model = data.model || llmConfig.model;
|
|
2613
2762
|
report.audit_provider = llmConfig.provider;
|
|
2614
2763
|
if (data.id) report.provider_msg_id = data.id;
|
|
2615
2764
|
if (data.usage) { report.input_tokens = data.usage.input_tokens; report.output_tokens = data.usage.output_tokens; }
|
|
2765
|
+
if (data.stop_reason === 'max_tokens') report.output_truncated = true;
|
|
2616
2766
|
}
|
|
2617
|
-
return { report, text: _text, duration: Date.now() - start };
|
|
2767
|
+
return { report, text: _text, duration: Date.now() - start, truncated: data.stop_reason === 'max_tokens' };
|
|
2618
2768
|
} else if (llmConfig.type === 'gemini') {
|
|
2619
2769
|
const res = await fetch(`${llmConfig.url}/${llmConfig.model}:generateContent?key=${apiKey}`, {
|
|
2620
2770
|
method: 'POST',
|
|
@@ -2632,13 +2782,19 @@ async function callLlm(llmConfig, systemPrompt, userMessage) {
|
|
|
2632
2782
|
return { error: friendly?.text || data.error.message || JSON.stringify(data.error), hint: friendly?.hint, duration: Date.now() - start };
|
|
2633
2783
|
}
|
|
2634
2784
|
_text = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
|
2785
|
+
const geminiFinish = data.candidates?.[0]?.finishReason;
|
|
2786
|
+
if (geminiFinish === 'MAX_TOKENS') {
|
|
2787
|
+
console.log(` ${c.red}✗ Output truncated — model hit maxOutputTokens limit (${data.usageMetadata?.candidatesTokenCount || '?'} tokens). Results may be incomplete.${c.reset}`);
|
|
2788
|
+
console.log(` ${c.dim} Hint: Try a model with higher output capacity, or scan a smaller package.${c.reset}`);
|
|
2789
|
+
}
|
|
2635
2790
|
const report = extractJSON(_text);
|
|
2636
2791
|
if (report) {
|
|
2637
2792
|
report.audit_model = data.modelVersion || llmConfig.model;
|
|
2638
2793
|
report.audit_provider = llmConfig.provider;
|
|
2639
2794
|
if (data.usageMetadata) { report.input_tokens = data.usageMetadata.promptTokenCount; report.output_tokens = data.usageMetadata.candidatesTokenCount; }
|
|
2795
|
+
if (geminiFinish === 'MAX_TOKENS') report.output_truncated = true;
|
|
2640
2796
|
}
|
|
2641
|
-
return { report, text: _text, duration: Date.now() - start };
|
|
2797
|
+
return { report, text: _text, duration: Date.now() - start, truncated: geminiFinish === 'MAX_TOKENS' };
|
|
2642
2798
|
} else {
|
|
2643
2799
|
const headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' };
|
|
2644
2800
|
if (llmConfig.provider === 'openrouter') { headers['HTTP-Referer'] = 'https://agentaudit.dev'; headers['X-Title'] = 'AgentAudit CLI'; }
|
|
@@ -2654,6 +2810,11 @@ async function callLlm(llmConfig, systemPrompt, userMessage) {
|
|
|
2654
2810
|
return { error: friendly?.text || data.error.message || JSON.stringify(data.error), hint: friendly?.hint, duration: Date.now() - start };
|
|
2655
2811
|
}
|
|
2656
2812
|
_text = data.choices?.[0]?.message?.content || '';
|
|
2813
|
+
const oaiFinish = data.choices?.[0]?.finish_reason;
|
|
2814
|
+
if (oaiFinish === 'length') {
|
|
2815
|
+
console.log(` ${c.red}✗ Output truncated — model hit max_tokens limit (${data.usage?.completion_tokens || '?'} tokens). Results may be incomplete.${c.reset}`);
|
|
2816
|
+
console.log(` ${c.dim} Hint: Try a model with higher output capacity, or scan a smaller package.${c.reset}`);
|
|
2817
|
+
}
|
|
2657
2818
|
const report = extractJSON(_text);
|
|
2658
2819
|
if (report) {
|
|
2659
2820
|
report.audit_model = data.model || llmConfig.model;
|
|
@@ -2661,12 +2822,13 @@ async function callLlm(llmConfig, systemPrompt, userMessage) {
|
|
|
2661
2822
|
if (data.id) report.provider_msg_id = data.id;
|
|
2662
2823
|
if (data.system_fingerprint) report.provider_fingerprint = data.system_fingerprint;
|
|
2663
2824
|
if (data.usage) { report.input_tokens = data.usage.prompt_tokens; report.output_tokens = data.usage.completion_tokens; }
|
|
2825
|
+
if (oaiFinish === 'length') report.output_truncated = true;
|
|
2664
2826
|
}
|
|
2665
|
-
return { report, text: _text, duration: Date.now() - start };
|
|
2827
|
+
return { report, text: _text, duration: Date.now() - start, truncated: oaiFinish === 'length' };
|
|
2666
2828
|
}
|
|
2667
2829
|
} catch (err) {
|
|
2668
2830
|
const dur = Date.now() - start;
|
|
2669
|
-
if (err.name === 'TimeoutError' || err.message?.includes('timeout')) return { error: 'Request timed out (
|
|
2831
|
+
if (err.name === 'TimeoutError' || err.message?.includes('timeout')) return { error: 'Request timed out (180s)', hint: 'Try again or use a faster model', duration: dur };
|
|
2670
2832
|
if (err.code === 'ENOTFOUND' || err.code === 'ECONNREFUSED' || err.message?.includes('fetch failed')) return { error: `Network error: could not reach ${llmConfig.provider}`, hint: 'Check your internet connection', duration: dur };
|
|
2671
2833
|
return { error: err.message, duration: dur };
|
|
2672
2834
|
}
|
|
@@ -3076,7 +3238,7 @@ async function auditRepo(url) {
|
|
|
3076
3238
|
for (const { report } of reports) await uploadReport(report, creds);
|
|
3077
3239
|
console.log(` ${c.dim}Reports: ${REGISTRY_URL}/packages/${slug}${c.reset}`);
|
|
3078
3240
|
} else if (!noUpload && !creds) {
|
|
3079
|
-
console.log(` ${c.dim}Run ${c.cyan}agentaudit
|
|
3241
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit login${c.dim} to upload reports to agentaudit.dev${c.reset}`);
|
|
3080
3242
|
}
|
|
3081
3243
|
|
|
3082
3244
|
console.log();
|
|
@@ -3139,6 +3301,13 @@ async function auditRepo(url) {
|
|
|
3139
3301
|
|
|
3140
3302
|
console.log(` ${c.green}done${c.reset} ${c.dim}(${elapsed(start)})${c.reset}`);
|
|
3141
3303
|
|
|
3304
|
+
if (llmResult.truncated) {
|
|
3305
|
+
console.log();
|
|
3306
|
+
console.log(` ${c.yellow}⚠ WARNING: The model's output was truncated (hit token limit).${c.reset}`);
|
|
3307
|
+
console.log(` ${c.yellow} Some findings may be missing from this scan.${c.reset}`);
|
|
3308
|
+
console.log(` ${c.dim} Tip: Try a model with more output capacity or scan a smaller package.${c.reset}`);
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3142
3311
|
const report = llmResult.report;
|
|
3143
3312
|
if (!report) {
|
|
3144
3313
|
console.log(` ${c.red}Could not parse LLM response as JSON${c.reset}`);
|
|
@@ -3208,11 +3377,11 @@ async function auditRepo(url) {
|
|
|
3208
3377
|
console.log(` ${c.dim}Report: ${REGISTRY_URL}/packages/${slug}${c.reset}`);
|
|
3209
3378
|
} else {
|
|
3210
3379
|
console.log(` ${c.red}invalid key${c.reset}`);
|
|
3211
|
-
console.log(` ${c.dim}Run ${c.cyan}agentaudit
|
|
3380
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit login${c.dim} to sign in.${c.reset}`);
|
|
3212
3381
|
}
|
|
3213
3382
|
}
|
|
3214
3383
|
} else {
|
|
3215
|
-
console.log(` ${c.dim}Run ${c.cyan}agentaudit
|
|
3384
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit login${c.dim} to sign in and upload reports${c.reset}`);
|
|
3216
3385
|
}
|
|
3217
3386
|
|
|
3218
3387
|
console.log();
|
|
@@ -3313,7 +3482,7 @@ function renderOverviewTab(data, width) {
|
|
|
3313
3482
|
profileLines.push(sevParts.join(' ') || `${c.dim}no findings yet${c.reset}`);
|
|
3314
3483
|
} else {
|
|
3315
3484
|
profileLines.push(`${c.dim}Not logged in${c.reset}`);
|
|
3316
|
-
profileLines.push(`${c.dim}Run ${c.cyan}agentaudit
|
|
3485
|
+
profileLines.push(`${c.dim}Run ${c.cyan}agentaudit login${c.dim} to sign in${c.reset}`);
|
|
3317
3486
|
}
|
|
3318
3487
|
|
|
3319
3488
|
// Registry box
|
|
@@ -3534,7 +3703,7 @@ async function activityCommand(args) {
|
|
|
3534
3703
|
} else {
|
|
3535
3704
|
banner();
|
|
3536
3705
|
console.log(` ${c.yellow}Not logged in${c.reset}`);
|
|
3537
|
-
console.log(` ${c.dim}Run ${c.cyan}agentaudit
|
|
3706
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit login${c.dim} to sign in${c.reset}`);
|
|
3538
3707
|
}
|
|
3539
3708
|
return;
|
|
3540
3709
|
}
|
|
@@ -4410,7 +4579,8 @@ async function main() {
|
|
|
4410
4579
|
console.log();
|
|
4411
4580
|
console.log(` ${c.bold}CONFIGURATION${c.reset}`);
|
|
4412
4581
|
console.log(` ${c.cyan}model${c.reset} Configure LLM provider + model`);
|
|
4413
|
-
console.log(` ${c.cyan}
|
|
4582
|
+
console.log(` ${c.cyan}login${c.reset} Sign in with GitHub (opens browser)`);
|
|
4583
|
+
console.log(` ${c.cyan}setup${c.reset} Manual login — paste API key`);
|
|
4414
4584
|
console.log(` ${c.cyan}status${c.reset} Show current config + auth status`);
|
|
4415
4585
|
console.log(` ${c.cyan}profile${c.reset} Your profile — rank, points, audit stats`);
|
|
4416
4586
|
console.log();
|
|
@@ -4569,6 +4739,11 @@ async function main() {
|
|
|
4569
4739
|
return;
|
|
4570
4740
|
}
|
|
4571
4741
|
|
|
4742
|
+
if (command === 'login') {
|
|
4743
|
+
await loginCommand();
|
|
4744
|
+
return;
|
|
4745
|
+
}
|
|
4746
|
+
|
|
4572
4747
|
if (command === 'status' || command === 'whoami') {
|
|
4573
4748
|
// ── Status / diagnostic overview ──
|
|
4574
4749
|
const config = loadLlmConfig();
|