agentaudit 3.10.3 → 3.10.5
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 +99 -54
- package/cli.mjs +525 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,6 +30,7 @@ and supply chain attacks. Powered by regex static analysis and deep LLM audits.
|
|
|
30
30
|
- [What It Detects](#-what-it-detects)
|
|
31
31
|
- [How the 3-Pass Audit Works](#-how-the-3-pass-audit-works)
|
|
32
32
|
- [CI/CD Integration](#-cicd-integration)
|
|
33
|
+
- [Dashboard & Community](#-dashboard--community)
|
|
33
34
|
- [Configuration](#-configuration)
|
|
34
35
|
- [Requirements](#-requirements)
|
|
35
36
|
- [FAQ](#-faq)
|
|
@@ -76,8 +77,7 @@ agentaudit lookup fastmcp
|
|
|
76
77
|
|
|
77
78
|
**Example output:**
|
|
78
79
|
```
|
|
79
|
-
AgentAudit v3.
|
|
80
|
-
Security scanner for AI packages
|
|
80
|
+
⛨ AgentAudit v3.10.4 │ my-scanner #3 · 280pts · 19 audits
|
|
81
81
|
|
|
82
82
|
Discovering MCP servers in your AI editors...
|
|
83
83
|
|
|
@@ -93,6 +93,8 @@ agentaudit lookup fastmcp
|
|
|
93
93
|
Looking for general package scanning? Try `pip audit` or `npm audit`.
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
> **Enhanced banner:** When logged in, the banner shows your agent name, rank, points, and audit count. Run `agentaudit setup` to create an account.
|
|
97
|
+
|
|
96
98
|
### Option B: MCP Server in your AI editor
|
|
97
99
|
|
|
98
100
|
Add AgentAudit as an MCP server — your AI agent can then discover, scan, and audit packages using its own LLM. **No extra API key needed.**
|
|
@@ -197,6 +199,8 @@ Then ask your agent: *"Check which MCP servers I have installed and audit any un
|
|
|
197
199
|
|
|
198
200
|
## 📋 Commands Reference
|
|
199
201
|
|
|
202
|
+
### Scan & Audit
|
|
203
|
+
|
|
200
204
|
| Command | Description | Example |
|
|
201
205
|
|---------|-------------|---------|
|
|
202
206
|
| `agentaudit` | Discover MCP servers (default, same as `discover`) | `agentaudit` |
|
|
@@ -207,18 +211,36 @@ Then ask your agent: *"Check which MCP servers I have installed and audit any un
|
|
|
207
211
|
| `agentaudit scan <url> --deep` | Deep audit (same as `audit`) | `agentaudit scan https://github.com/owner/repo --deep` |
|
|
208
212
|
| `agentaudit audit <url>` | Deep LLM-powered 3-pass audit (~30s) | `agentaudit audit https://github.com/owner/repo` |
|
|
209
213
|
| `agentaudit lookup <name>` | Look up package in trust registry | `agentaudit lookup fastmcp` |
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
214
|
+
|
|
215
|
+
### Community
|
|
216
|
+
|
|
217
|
+
| Command | Alias | Description |
|
|
218
|
+
|---------|-------|-------------|
|
|
219
|
+
| `agentaudit dashboard` | `dash` | Interactive full-screen TUI with 5 tabs (Overview, Leaderboard, Benchmark, Activity, Search) |
|
|
220
|
+
| `agentaudit leaderboard` | `lb` | Top contributors ranking (pipe-friendly) |
|
|
221
|
+
| `agentaudit benchmark` | `bench` | LLM model audit performance comparison |
|
|
222
|
+
| `agentaudit activity` | `my` | Your recent audits & findings |
|
|
223
|
+
| `agentaudit search <query>` | `find` | Search packages in the registry by name, ASF-ID, or hash |
|
|
224
|
+
|
|
225
|
+
### Configuration
|
|
226
|
+
|
|
227
|
+
| Command | Alias | Description |
|
|
228
|
+
|---------|-------|-------------|
|
|
229
|
+
| `agentaudit model` | — | Interactive LLM provider + model configuration |
|
|
230
|
+
| `agentaudit setup` | — | Register agent + configure API key for registry uploads |
|
|
231
|
+
| `agentaudit status` | `whoami` | Show current config, API keys, and personal stats |
|
|
213
232
|
|
|
214
233
|
### Global Flags
|
|
215
234
|
|
|
216
235
|
| Flag | Description |
|
|
217
236
|
|------|-------------|
|
|
218
237
|
| `--json` | Output machine-readable JSON to stdout |
|
|
219
|
-
| `--quiet` / `-q` | Suppress banner and decorative output
|
|
238
|
+
| `--quiet` / `-q` | Suppress banner and decorative output |
|
|
220
239
|
| `--no-color` | Disable ANSI colors (also respects `NO_COLOR` env var) |
|
|
221
|
-
| `--
|
|
240
|
+
| `--model <name>` | Override LLM model for this run |
|
|
241
|
+
| `--no-upload` | Skip uploading report to registry |
|
|
242
|
+
| `--export` | Export audit payload as markdown |
|
|
243
|
+
| `--debug` | Show raw LLM response on parse errors |
|
|
222
244
|
| `--help` / `-h` | Show help text |
|
|
223
245
|
| `-v` / `--version` | Show version |
|
|
224
246
|
|
|
@@ -418,6 +440,41 @@ agentaudit lookup fastmcp --json
|
|
|
418
440
|
|
|
419
441
|
---
|
|
420
442
|
|
|
443
|
+
## 📊 Dashboard & Community
|
|
444
|
+
|
|
445
|
+
AgentAudit includes a full-screen interactive dashboard and standalone community commands.
|
|
446
|
+
|
|
447
|
+
### Interactive Dashboard
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
agentaudit dashboard # or: agentaudit dash
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
5-tab TUI with keyboard navigation (←→ tabs, ↑↓ scroll, 1-5 jump, q quit):
|
|
454
|
+
|
|
455
|
+
| Tab | Content |
|
|
456
|
+
|-----|---------|
|
|
457
|
+
| **[1] Overview** | Your profile (rank, points, audits, severity breakdown) + registry stats |
|
|
458
|
+
| **[2] Leaderboard** | Top contributors with medal rankings and bar charts |
|
|
459
|
+
| **[3] Benchmark** | LLM model audit performance comparison |
|
|
460
|
+
| **[4] Activity** | Your recent audits and findings |
|
|
461
|
+
| **[5] Search** | Interactive package search (type to search, Enter to submit) |
|
|
462
|
+
|
|
463
|
+
### Standalone Commands
|
|
464
|
+
|
|
465
|
+
All community commands work without the dashboard (pipe-friendly, supports `--json`):
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
agentaudit leaderboard # Top contributors
|
|
469
|
+
agentaudit leaderboard --tab monthly --json # Monthly rankings as JSON
|
|
470
|
+
agentaudit benchmark # Model comparison
|
|
471
|
+
agentaudit activity # Your recent audits & findings
|
|
472
|
+
agentaudit search fastmcp # Search registry by name/ASF-ID
|
|
473
|
+
agentaudit search fastmcp --json # Machine-readable search results
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
421
478
|
## ⚙️ Configuration
|
|
422
479
|
|
|
423
480
|
### Credentials
|
|
@@ -430,23 +487,34 @@ Run `agentaudit setup` to configure interactively, or set via environment:
|
|
|
430
487
|
export AGENTAUDIT_API_KEY=asf_your_key_here
|
|
431
488
|
```
|
|
432
489
|
|
|
433
|
-
###
|
|
490
|
+
### LLM Providers (13 supported)
|
|
491
|
+
|
|
492
|
+
AgentAudit supports 13 LLM providers for deep audits. Set one API key — the CLI auto-detects it. Use `agentaudit model` to choose provider + model interactively, or `agentaudit status` to check your setup.
|
|
493
|
+
|
|
494
|
+
| Variable | Provider | Default Model |
|
|
495
|
+
|----------|----------|---------------|
|
|
496
|
+
| `ANTHROPIC_API_KEY` | Anthropic (Claude) | `claude-sonnet-4-20250514` |
|
|
497
|
+
| `GEMINI_API_KEY` | Google (Gemini) | `gemini-2.5-flash` |
|
|
498
|
+
| `OPENAI_API_KEY` | OpenAI (GPT-4o) | `gpt-4o` |
|
|
499
|
+
| `DEEPSEEK_API_KEY` | DeepSeek | `deepseek-chat` |
|
|
500
|
+
| `MISTRAL_API_KEY` | Mistral | `mistral-large-latest` |
|
|
501
|
+
| `GROQ_API_KEY` | Groq | `llama-3.3-70b-versatile` |
|
|
502
|
+
| `XAI_API_KEY` | xAI (Grok) | `grok-3` |
|
|
503
|
+
| `TOGETHER_API_KEY` | Together AI | `Llama-3.3-70B-Instruct-Turbo` |
|
|
504
|
+
| `FIREWORKS_API_KEY` | Fireworks AI | `llama-v3p3-70b-instruct` |
|
|
505
|
+
| `CEREBRAS_API_KEY` | Cerebras | `llama-3.3-70b` |
|
|
506
|
+
| `ZAI_API_KEY` | Zhipu AI (GLM) | `glm-4.7` |
|
|
507
|
+
| `OPENROUTER_API_KEY` | OpenRouter | `anthropic/claude-sonnet-4` |
|
|
508
|
+
|
|
509
|
+
### Other Environment Variables
|
|
434
510
|
|
|
435
511
|
| Variable | Description |
|
|
436
512
|
|----------|-------------|
|
|
437
|
-
| `AGENTAUDIT_API_KEY` | API key for registry
|
|
438
|
-
| `
|
|
439
|
-
| `OPENAI_API_KEY` | OpenAI API key for deep audits (GPT-4o) |
|
|
440
|
-
| `OPENROUTER_API_KEY` | OpenRouter API key (access 200+ models) |
|
|
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 |
|
|
513
|
+
| `AGENTAUDIT_API_KEY` | API key for registry uploads (or use `agentaudit setup`) |
|
|
514
|
+
| `AGENTAUDIT_MODEL` | Override LLM model (same as `--model` flag) |
|
|
447
515
|
| `NO_COLOR` | Disable ANSI colors ([no-color.org](https://no-color.org)) |
|
|
448
516
|
|
|
449
|
-
> **Provider priority:**
|
|
517
|
+
> **Provider priority:** Set `preferred_provider` via `agentaudit model`, or the CLI picks the first available key. Override per-run with `--model <name>`.
|
|
450
518
|
|
|
451
519
|
---
|
|
452
520
|
|
|
@@ -477,49 +545,26 @@ Or use without installing: `npx agentaudit`
|
|
|
477
545
|
|
|
478
546
|
### Setting up your LLM key for deep audits
|
|
479
547
|
|
|
480
|
-
The `audit` command supports **
|
|
481
|
-
|
|
482
|
-
```bash
|
|
483
|
-
# Linux / macOS
|
|
484
|
-
export ANTHROPIC_API_KEY=sk-ant-... # Recommended (Claude Sonnet)
|
|
485
|
-
export OPENAI_API_KEY=sk-... # Alternative (GPT-4o)
|
|
486
|
-
export OPENROUTER_API_KEY=sk-or-... # 200+ models via OpenRouter
|
|
487
|
-
|
|
488
|
-
# Windows (PowerShell)
|
|
489
|
-
$env:ANTHROPIC_API_KEY = "sk-ant-..."
|
|
490
|
-
$env:OPENAI_API_KEY = "sk-..."
|
|
491
|
-
$env:OPENROUTER_API_KEY = "sk-or-..."
|
|
492
|
-
|
|
493
|
-
# Windows (CMD)
|
|
494
|
-
set ANTHROPIC_API_KEY=sk-ant-...
|
|
495
|
-
set OPENAI_API_KEY=sk-...
|
|
496
|
-
set OPENROUTER_API_KEY=sk-or-...
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
**Provider priority:** Anthropic > OpenAI > OpenRouter > Custom > Ollama. Override with `--provider=<name>`.
|
|
500
|
-
|
|
501
|
-
**OpenRouter model selection:** By default uses `anthropic/claude-sonnet-4`. Override with:
|
|
502
|
-
```bash
|
|
503
|
-
export OPENROUTER_MODEL=google/gemini-2.5-pro # or any model on openrouter.ai
|
|
504
|
-
```
|
|
548
|
+
The `audit` command supports **13 LLM providers**. Set one API key and AgentAudit auto-detects it:
|
|
505
549
|
|
|
506
|
-
**Local with Ollama (free, no API key):**
|
|
507
550
|
```bash
|
|
508
|
-
|
|
509
|
-
|
|
551
|
+
# Set any one of these (Anthropic recommended)
|
|
552
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
553
|
+
export OPENAI_API_KEY=sk-...
|
|
554
|
+
export GEMINI_API_KEY=...
|
|
555
|
+
export DEEPSEEK_API_KEY=...
|
|
556
|
+
# ... or any of the 13 supported providers (see Configuration section)
|
|
510
557
|
```
|
|
511
|
-
> Note: Local models produce lower quality audits than Claude/GPT-4o. Use for quick checks, not production security audits.
|
|
512
558
|
|
|
513
|
-
**
|
|
559
|
+
**Interactive setup:**
|
|
514
560
|
```bash
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
agentaudit audit https://github.com/owner/repo
|
|
561
|
+
agentaudit model # 2-step menu: pick provider → pick model
|
|
562
|
+
agentaudit status # check which keys are set + current config
|
|
518
563
|
```
|
|
519
564
|
|
|
520
|
-
**
|
|
565
|
+
**Override per-run:**
|
|
521
566
|
```bash
|
|
522
|
-
agentaudit
|
|
567
|
+
agentaudit audit https://github.com/owner/repo --model gpt-4o
|
|
523
568
|
```
|
|
524
569
|
|
|
525
570
|
**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.
|
package/cli.mjs
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* dashboard Interactive full-screen dashboard
|
|
13
13
|
* leaderboard Top contributors ranking
|
|
14
14
|
* benchmark LLM model performance comparison
|
|
15
|
+
* activity Your recent audits & findings
|
|
16
|
+
* search <query> Search packages in registry
|
|
15
17
|
* model [name|reset] Configure LLM provider + model
|
|
16
18
|
* setup Log in to agentaudit.dev (for report uploads)
|
|
17
19
|
* status Show current config + auth status
|
|
@@ -145,6 +147,7 @@ const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
|
145
147
|
const USER_CRED_DIR = path.join(xdgConfig, 'agentaudit');
|
|
146
148
|
const USER_CRED_FILE = path.join(USER_CRED_DIR, 'credentials.json');
|
|
147
149
|
const SKILL_CRED_FILE = path.join(SKILL_DIR, 'config', 'credentials.json');
|
|
150
|
+
const PROFILE_CACHE_FILE = path.join(USER_CRED_DIR, 'profile-cache.json');
|
|
148
151
|
|
|
149
152
|
function loadCredentials() {
|
|
150
153
|
for (const f of [SKILL_CRED_FILE, USER_CRED_FILE]) {
|
|
@@ -227,6 +230,29 @@ function saveCredentials(data) {
|
|
|
227
230
|
} catch {}
|
|
228
231
|
}
|
|
229
232
|
|
|
233
|
+
function loadProfileCache() {
|
|
234
|
+
try {
|
|
235
|
+
if (!fs.existsSync(PROFILE_CACHE_FILE)) return null;
|
|
236
|
+
const data = JSON.parse(fs.readFileSync(PROFILE_CACHE_FILE, 'utf8'));
|
|
237
|
+
// TTL: 10 minutes
|
|
238
|
+
if (data.fetched_at && Date.now() - data.fetched_at < 10 * 60 * 1000) return data;
|
|
239
|
+
return null; // expired
|
|
240
|
+
} catch { return null; }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function saveProfileCache(data) {
|
|
244
|
+
try {
|
|
245
|
+
fs.mkdirSync(USER_CRED_DIR, { recursive: true });
|
|
246
|
+
fs.writeFileSync(PROFILE_CACHE_FILE, JSON.stringify({
|
|
247
|
+
agent_name: data.agent_name,
|
|
248
|
+
rank: data.rank,
|
|
249
|
+
total_points: data.total_points,
|
|
250
|
+
total_reports: data.total_reports,
|
|
251
|
+
fetched_at: Date.now(),
|
|
252
|
+
}, null, 2), { mode: 0o600 });
|
|
253
|
+
} catch {}
|
|
254
|
+
}
|
|
255
|
+
|
|
230
256
|
function askQuestion(question) {
|
|
231
257
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
232
258
|
return new Promise(resolve => rl.question(question, answer => { rl.close(); resolve(answer.trim()); }));
|
|
@@ -527,8 +553,17 @@ function getVersion() {
|
|
|
527
553
|
function banner() {
|
|
528
554
|
if (quietMode || jsonMode) return;
|
|
529
555
|
console.log();
|
|
530
|
-
|
|
531
|
-
|
|
556
|
+
const cache = loadProfileCache();
|
|
557
|
+
if (cache) {
|
|
558
|
+
const rankStr = cache.rank != null ? `#${cache.rank}` : '';
|
|
559
|
+
const ptsStr = `${fmtNum(cache.total_points)}pts`;
|
|
560
|
+
const auditsStr = `${fmtNum(cache.total_reports)} audits`;
|
|
561
|
+
const profile = [cache.agent_name, rankStr, ptsStr, auditsStr].filter(Boolean).join(' \u00b7 ');
|
|
562
|
+
console.log(` ${c.bold}${c.cyan}\u26e8 AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset} ${c.dim}\u2502${c.reset} ${profile}`);
|
|
563
|
+
} else {
|
|
564
|
+
console.log(` ${c.bold}${c.cyan}AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset}`);
|
|
565
|
+
console.log(` ${c.dim}Security scanner for AI packages${c.reset}`);
|
|
566
|
+
}
|
|
532
567
|
console.log();
|
|
533
568
|
}
|
|
534
569
|
|
|
@@ -1761,8 +1796,9 @@ async function auditRepo(url) {
|
|
|
1761
1796
|
`After analysis, respond with ONLY a valid JSON object. No markdown fences, no explanation, no text before or after. Just the raw JSON:`,
|
|
1762
1797
|
`{ "skill_slug": "${slug}", "source_url": "${url}", "package_type": "<mcp-server|agent-skill|library|cli-tool>",`,
|
|
1763
1798
|
` "risk_score": <0-100>, "result": "<safe|caution|unsafe>", "max_severity": "<none|low|medium|high|critical>",`,
|
|
1764
|
-
` "findings_count": <n>, "findings": [{ "
|
|
1765
|
-
` "description": "...", "file": "...", "line": <n>, "
|
|
1799
|
+
` "findings_count": <n>, "findings": [{ "pattern_id": "CMD_INJECT_001", "title": "...", "severity": "...", "category": "...",`,
|
|
1800
|
+
` "cwe_id": "CWE-78", "description": "...", "file": "...", "line": <n>, "content": "...", "remediation": "...",`,
|
|
1801
|
+
` "confidence": "high|medium|low", "by_design": false, "score_impact": -15 }] }`,
|
|
1766
1802
|
``,
|
|
1767
1803
|
`## Source Code`,
|
|
1768
1804
|
codeBlock,
|
|
@@ -1930,6 +1966,10 @@ async function auditRepo(url) {
|
|
|
1930
1966
|
return null;
|
|
1931
1967
|
}
|
|
1932
1968
|
|
|
1969
|
+
// Add scan metadata for benchmarking
|
|
1970
|
+
report.audit_duration_ms = Date.now() - start;
|
|
1971
|
+
report.files_scanned = files.length;
|
|
1972
|
+
|
|
1933
1973
|
// Display results
|
|
1934
1974
|
console.log();
|
|
1935
1975
|
const riskScore = report.risk_score || 0;
|
|
@@ -1973,7 +2013,12 @@ async function auditRepo(url) {
|
|
|
1973
2013
|
console.log(` ${c.green}done${c.reset}`);
|
|
1974
2014
|
console.log(` ${c.dim}Report: ${REGISTRY_URL}/skills/${slug}${c.reset}`);
|
|
1975
2015
|
} else {
|
|
2016
|
+
let errBody = '';
|
|
2017
|
+
try { errBody = await res.text(); } catch {}
|
|
1976
2018
|
console.log(` ${c.yellow}failed (HTTP ${res.status})${c.reset}`);
|
|
2019
|
+
if (errBody && process.argv.includes('--debug')) {
|
|
2020
|
+
console.log(` ${c.dim}Server: ${errBody.slice(0, 300)}${c.reset}`);
|
|
2021
|
+
}
|
|
1977
2022
|
}
|
|
1978
2023
|
} catch (err) {
|
|
1979
2024
|
console.log(` ${c.yellow}failed${c.reset}`);
|
|
@@ -2078,6 +2123,20 @@ async function fetchDashboardData() {
|
|
|
2078
2123
|
fetches.push(Promise.resolve(null));
|
|
2079
2124
|
}
|
|
2080
2125
|
const [stats, leaderboard, benchmark, agent] = await Promise.all(fetches);
|
|
2126
|
+
// Update profile cache if we have agent data
|
|
2127
|
+
if (agent && creds) {
|
|
2128
|
+
let rank = null;
|
|
2129
|
+
if (Array.isArray(leaderboard)) {
|
|
2130
|
+
const idx = leaderboard.findIndex(e => e.agent_name === creds.agent_name);
|
|
2131
|
+
if (idx >= 0) rank = idx + 1;
|
|
2132
|
+
}
|
|
2133
|
+
saveProfileCache({
|
|
2134
|
+
agent_name: creds.agent_name,
|
|
2135
|
+
rank,
|
|
2136
|
+
total_points: agent.total_points || 0,
|
|
2137
|
+
total_reports: agent.total_reports || 0,
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2081
2140
|
return { stats, leaderboard, benchmark, agent, creds };
|
|
2082
2141
|
}
|
|
2083
2142
|
|
|
@@ -2173,7 +2232,7 @@ function renderLeaderboardTab(data, width, opts = {}) {
|
|
|
2173
2232
|
const pts = padLeft(`${fmtNum(entry.total_points || 0)} pts`, 12);
|
|
2174
2233
|
const audits = padLeft(`${fmtNum(entry.total_reports || 0)} audits`, 12);
|
|
2175
2234
|
const extra = entry.monthly_reports != null ? padLeft(`${fmtNum(entry.monthly_reports)} this mo`, 12) : '';
|
|
2176
|
-
lines.push(`${prefix}${padRight(nameStr, maxNameW
|
|
2235
|
+
lines.push(`${prefix}${padRight(nameStr, maxNameW)} ${bar} ${pts} ${audits}${extra}`);
|
|
2177
2236
|
|
|
2178
2237
|
// Separator after top 3
|
|
2179
2238
|
if (i === 2 && leaderboard.length > 3) {
|
|
@@ -2204,20 +2263,27 @@ function renderBenchmarkTab(data, width) {
|
|
|
2204
2263
|
lines.push('');
|
|
2205
2264
|
|
|
2206
2265
|
// Header
|
|
2207
|
-
const nameW =
|
|
2208
|
-
const hdr = ` ${padRight(`${c.bold}Model${c.reset}`, nameW + 9)} ${padRight('Audits',
|
|
2266
|
+
const nameW = 28;
|
|
2267
|
+
const hdr = ` ${padRight(`${c.bold}Model${c.reset}`, nameW + 9)} ${padRight('Audits', 7)} ${padRight('Risk', 5)} ${padRight('Detection', 16)} Severity`;
|
|
2209
2268
|
lines.push(hdr);
|
|
2210
|
-
lines.push(` ${c.dim}${'─'.repeat(Math.min(width - 4,
|
|
2269
|
+
lines.push(` ${c.dim}${'─'.repeat(Math.min(width - 4, 86))}${c.reset}`);
|
|
2211
2270
|
|
|
2212
2271
|
for (const m of benchmark.models) {
|
|
2213
2272
|
const name = (m.audit_model || 'unknown').slice(0, nameW - 2);
|
|
2214
|
-
const audits = padLeft(fmtNum(m.total_audits),
|
|
2273
|
+
const audits = padLeft(fmtNum(m.total_audits), 5);
|
|
2215
2274
|
const riskVal = parseFloat(m.avg_risk_score) || 0;
|
|
2216
2275
|
const riskColor = riskVal <= 20 ? c.green : riskVal <= 40 ? c.yellow : c.red;
|
|
2217
2276
|
const risk = `${riskColor}${padLeft(String(Math.round(riskVal)), 3)}${c.reset}`;
|
|
2218
2277
|
const detection = renderGauge(m.detection_rate || 0, 100, 10);
|
|
2219
|
-
|
|
2220
|
-
|
|
2278
|
+
// Severity as compact text instead of dots
|
|
2279
|
+
const sev = m.severity_breakdown || {};
|
|
2280
|
+
const sevParts = [];
|
|
2281
|
+
if (sev.critical) sevParts.push(`${c.red}${sev.critical}C${c.reset}`);
|
|
2282
|
+
if (sev.high) sevParts.push(`${c.red}${sev.high}H${c.reset}`);
|
|
2283
|
+
if (sev.medium) sevParts.push(`${c.yellow}${sev.medium}M${c.reset}`);
|
|
2284
|
+
if (sev.low) sevParts.push(`${c.blue}${sev.low}L${c.reset}`);
|
|
2285
|
+
const sevStr = sevParts.length > 0 ? sevParts.join(' ') : `${c.dim}—${c.reset}`;
|
|
2286
|
+
lines.push(` ${padRight(name, nameW)} ${audits} ${risk} ${detection} ${sevStr}`);
|
|
2221
2287
|
}
|
|
2222
2288
|
|
|
2223
2289
|
// Vulnerability landscape
|
|
@@ -2249,6 +2315,274 @@ function renderBenchmarkTab(data, width) {
|
|
|
2249
2315
|
return lines;
|
|
2250
2316
|
}
|
|
2251
2317
|
|
|
2318
|
+
function timeAgo(dateStr) {
|
|
2319
|
+
if (!dateStr) return '';
|
|
2320
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
2321
|
+
const mins = Math.floor(diff / 60000);
|
|
2322
|
+
if (mins < 60) return `${mins}m ago`;
|
|
2323
|
+
const hours = Math.floor(mins / 60);
|
|
2324
|
+
if (hours < 24) return `${hours}h ago`;
|
|
2325
|
+
const days = Math.floor(hours / 24);
|
|
2326
|
+
if (days < 30) return `${days}d ago`;
|
|
2327
|
+
return `${Math.floor(days / 30)}mo ago`;
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
function renderActivityTab(data, width) {
|
|
2331
|
+
const { agent, creds } = data;
|
|
2332
|
+
const lines = [];
|
|
2333
|
+
|
|
2334
|
+
if (!creds || !agent) {
|
|
2335
|
+
lines.push(` ${c.dim}Not logged in${c.reset}`);
|
|
2336
|
+
lines.push(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to see your activity${c.reset}`);
|
|
2337
|
+
return lines;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
// Recent Audits
|
|
2341
|
+
const reports = agent.recent_reports || [];
|
|
2342
|
+
const auditLines = [];
|
|
2343
|
+
if (reports.length > 0) {
|
|
2344
|
+
for (const r of reports.slice(0, 10)) {
|
|
2345
|
+
const time = padRight(timeAgo(r.created_at), 8);
|
|
2346
|
+
const name = padRight((r.skill_slug || r.package_name || '').slice(0, 20), 20);
|
|
2347
|
+
const score = r.risk_score ?? r.latest_risk_score ?? 0;
|
|
2348
|
+
let status;
|
|
2349
|
+
if (score === 0) status = `${c.green}safe${c.reset} `;
|
|
2350
|
+
else if (score <= 30) status = `${c.yellow}caution${c.reset}`;
|
|
2351
|
+
else status = `${c.red}unsafe${c.reset} `;
|
|
2352
|
+
const findCount = r.finding_count != null ? `${r.finding_count} findings` : '';
|
|
2353
|
+
const model = r.audit_model ? `${c.dim}${(r.audit_model || '').slice(0, 14)}${c.reset}` : '';
|
|
2354
|
+
auditLines.push(`${c.dim}${time}${c.reset} ${name} ${status} ${padRight(findCount, 12)} ${model}`);
|
|
2355
|
+
}
|
|
2356
|
+
} else {
|
|
2357
|
+
auditLines.push(`${c.dim}No audits yet — run ${c.cyan}agentaudit audit <url>${c.dim} to get started${c.reset}`);
|
|
2358
|
+
}
|
|
2359
|
+
lines.push(...drawBox('Recent Audits', auditLines, Math.min(width - 4, 72)));
|
|
2360
|
+
lines.push('');
|
|
2361
|
+
|
|
2362
|
+
// Recent Findings
|
|
2363
|
+
const findings = agent.recent_findings || [];
|
|
2364
|
+
const findingLines = [];
|
|
2365
|
+
if (findings.length > 0) {
|
|
2366
|
+
for (const f of findings.slice(0, 10)) {
|
|
2367
|
+
const asfId = padRight(f.asf_id || f.id || '', 16);
|
|
2368
|
+
const sev = severityIcon(f.severity);
|
|
2369
|
+
const sevLabel = padRight(f.severity || '', 9);
|
|
2370
|
+
const title = (f.title || f.pattern_id || '').slice(0, 40);
|
|
2371
|
+
findingLines.push(`${asfId} ${sev} ${sevLabel} ${title}`);
|
|
2372
|
+
}
|
|
2373
|
+
} else {
|
|
2374
|
+
findingLines.push(`${c.dim}No findings yet${c.reset}`);
|
|
2375
|
+
}
|
|
2376
|
+
lines.push(...drawBox('Recent Findings', findingLines, Math.min(width - 4, 72)));
|
|
2377
|
+
|
|
2378
|
+
return lines;
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
async function activityCommand(args) {
|
|
2382
|
+
const creds = loadCredentials();
|
|
2383
|
+
|
|
2384
|
+
if (!creds?.agent_name || creds.agent_name === 'env') {
|
|
2385
|
+
if (jsonMode) {
|
|
2386
|
+
console.log(JSON.stringify({ error: 'not_logged_in' }));
|
|
2387
|
+
} else {
|
|
2388
|
+
banner();
|
|
2389
|
+
console.log(` ${c.yellow}Not logged in${c.reset}`);
|
|
2390
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to create an account${c.reset}`);
|
|
2391
|
+
}
|
|
2392
|
+
return;
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
if (!quietMode && !jsonMode) {
|
|
2396
|
+
banner();
|
|
2397
|
+
process.stdout.write(` ${c.dim}Loading activity...${c.reset}`);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
let agentData;
|
|
2401
|
+
try {
|
|
2402
|
+
const res = await fetch(`${REGISTRY_URL}/api/agents/${encodeURIComponent(creds.agent_name)}`, {
|
|
2403
|
+
headers: { 'Authorization': `Bearer ${creds.api_key}` },
|
|
2404
|
+
signal: AbortSignal.timeout(15_000),
|
|
2405
|
+
});
|
|
2406
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2407
|
+
agentData = await res.json();
|
|
2408
|
+
} catch (err) {
|
|
2409
|
+
if (!jsonMode) {
|
|
2410
|
+
process.stdout.write('\r\x1b[2K');
|
|
2411
|
+
console.log(` ${c.red}Failed to load activity: ${err.message}${c.reset}`);
|
|
2412
|
+
}
|
|
2413
|
+
process.exitCode = 1;
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
if (jsonMode) {
|
|
2418
|
+
console.log(JSON.stringify(agentData, null, 2));
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
process.stdout.write('\r\x1b[2K');
|
|
2423
|
+
|
|
2424
|
+
// Update profile cache
|
|
2425
|
+
try {
|
|
2426
|
+
const lbRes = await fetch(`${REGISTRY_URL}/api/leaderboard?limit=100`, { signal: AbortSignal.timeout(10_000) }).then(r => r.ok ? r.json() : null);
|
|
2427
|
+
let rank = null;
|
|
2428
|
+
if (Array.isArray(lbRes)) {
|
|
2429
|
+
const idx = lbRes.findIndex(e => e.agent_name === creds.agent_name);
|
|
2430
|
+
if (idx >= 0) rank = idx + 1;
|
|
2431
|
+
}
|
|
2432
|
+
saveProfileCache({
|
|
2433
|
+
agent_name: creds.agent_name,
|
|
2434
|
+
rank,
|
|
2435
|
+
total_points: agentData.total_points || 0,
|
|
2436
|
+
total_reports: agentData.total_reports || 0,
|
|
2437
|
+
});
|
|
2438
|
+
} catch {}
|
|
2439
|
+
|
|
2440
|
+
const width = process.stdout.columns || 80;
|
|
2441
|
+
const activityLines = renderActivityTab({ agent: agentData, creds }, width);
|
|
2442
|
+
console.log(` ${c.bold}Activity${c.reset} ${c.dim}${creds.agent_name}${c.reset}`);
|
|
2443
|
+
console.log();
|
|
2444
|
+
for (const line of activityLines) console.log(line);
|
|
2445
|
+
console.log();
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
function renderSearchResults(data, width) {
|
|
2449
|
+
const lines = [];
|
|
2450
|
+
const boxW = Math.min(width - 4, 72);
|
|
2451
|
+
|
|
2452
|
+
// Lookup API returns { reports: [], findings: [], total_matches }
|
|
2453
|
+
const lookupData = Array.isArray(data) ? data[0] : data;
|
|
2454
|
+
const reports = lookupData?.reports || [];
|
|
2455
|
+
const findings = lookupData?.findings || [];
|
|
2456
|
+
const total = lookupData?.total_matches || (reports.length + findings.length);
|
|
2457
|
+
|
|
2458
|
+
if (total === 0) return lines;
|
|
2459
|
+
|
|
2460
|
+
// Packages (from reports)
|
|
2461
|
+
if (reports.length > 0) {
|
|
2462
|
+
const pkgLines = [];
|
|
2463
|
+
for (const r of reports.slice(0, 10)) {
|
|
2464
|
+
const name = padRight((r.skill_slug || '').slice(0, 22), 22);
|
|
2465
|
+
const riskScore = r.risk_score ?? 0;
|
|
2466
|
+
let badge;
|
|
2467
|
+
if (riskScore === 0) badge = `${c.green}SAFE${c.reset} `;
|
|
2468
|
+
else if (riskScore <= 30) badge = `${c.yellow}CAUTION${c.reset}`;
|
|
2469
|
+
else badge = `${c.red}UNSAFE${c.reset} `;
|
|
2470
|
+
const time = padRight(timeAgo(r.created_at), 8);
|
|
2471
|
+
pkgLines.push(`${name} ${badge} ${c.dim}${time}${c.reset}`);
|
|
2472
|
+
}
|
|
2473
|
+
lines.push(...drawBox(`Packages (${reports.length})`, pkgLines, boxW));
|
|
2474
|
+
lines.push('');
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
// Findings
|
|
2478
|
+
if (findings.length > 0) {
|
|
2479
|
+
const findLines = [];
|
|
2480
|
+
for (const f of findings.slice(0, 10)) {
|
|
2481
|
+
const asfId = padRight(f.asf_id || '', 16);
|
|
2482
|
+
const sev = severityIcon(f.severity);
|
|
2483
|
+
const sevLabel = padRight(f.severity || '', 9);
|
|
2484
|
+
const title = (f.title || '').slice(0, 36);
|
|
2485
|
+
findLines.push(`${asfId} ${sev} ${sevLabel} ${title}`);
|
|
2486
|
+
}
|
|
2487
|
+
lines.push(...drawBox(`Findings (${findings.length})`, findLines, boxW));
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
return lines;
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
function renderSearchTab(searchState, width) {
|
|
2494
|
+
const { query, results, loading, error } = searchState;
|
|
2495
|
+
const lines = [];
|
|
2496
|
+
|
|
2497
|
+
// Search input
|
|
2498
|
+
lines.push(` ${c.bold}Search:${c.reset} ${query || ''}${c.dim}\u2588${c.reset}`);
|
|
2499
|
+
lines.push('');
|
|
2500
|
+
|
|
2501
|
+
if (loading) {
|
|
2502
|
+
lines.push(` ${c.dim}Searching...${c.reset}`);
|
|
2503
|
+
return lines;
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
if (error) {
|
|
2507
|
+
lines.push(` ${c.red}${error}${c.reset}`);
|
|
2508
|
+
return lines;
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
if (results) {
|
|
2512
|
+
const resultLines = renderSearchResults(results, width);
|
|
2513
|
+
if (resultLines.length > 0) {
|
|
2514
|
+
lines.push(...resultLines);
|
|
2515
|
+
} else {
|
|
2516
|
+
lines.push(` ${c.dim}No results found for "${query}"${c.reset}`);
|
|
2517
|
+
}
|
|
2518
|
+
} else if (!query) {
|
|
2519
|
+
lines.push(` ${c.dim}Type to search packages in the registry${c.reset}`);
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
lines.push('');
|
|
2523
|
+
lines.push(` ${c.dim}Type to search \u2502 Enter=search \u2502 Esc=clear \u2502 Tab=switch tab${c.reset}`);
|
|
2524
|
+
|
|
2525
|
+
return lines;
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
async function searchCommand(args) {
|
|
2529
|
+
const query = args.filter(a => !a.startsWith('--')).join(' ').trim();
|
|
2530
|
+
|
|
2531
|
+
if (!query) {
|
|
2532
|
+
if (jsonMode) {
|
|
2533
|
+
console.log(JSON.stringify({ error: 'query_required' }));
|
|
2534
|
+
} else {
|
|
2535
|
+
banner();
|
|
2536
|
+
console.log(` ${c.red}Error: search query required${c.reset}`);
|
|
2537
|
+
console.log(` ${c.dim}Usage: ${c.cyan}agentaudit search <query>${c.dim} \u2014 e.g. agentaudit search fastmcp${c.reset}`);
|
|
2538
|
+
}
|
|
2539
|
+
process.exitCode = 2;
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
if (!quietMode && !jsonMode) {
|
|
2544
|
+
banner();
|
|
2545
|
+
process.stdout.write(` ${c.dim}Searching "${query}"...${c.reset}`);
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
let data;
|
|
2549
|
+
try {
|
|
2550
|
+
const res = await fetch(`${REGISTRY_URL}/api/lookup?hash=${encodeURIComponent(query)}`, {
|
|
2551
|
+
signal: AbortSignal.timeout(15_000),
|
|
2552
|
+
});
|
|
2553
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2554
|
+
data = await res.json();
|
|
2555
|
+
} catch (err) {
|
|
2556
|
+
if (!jsonMode) {
|
|
2557
|
+
process.stdout.write('\r\x1b[2K');
|
|
2558
|
+
console.log(` ${c.red}Search failed: ${err.message}${c.reset}`);
|
|
2559
|
+
}
|
|
2560
|
+
process.exitCode = 1;
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
if (jsonMode) {
|
|
2565
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
process.stdout.write('\r\x1b[2K');
|
|
2570
|
+
console.log(` ${c.bold}Search${c.reset} ${c.dim}"${query}"${c.reset}`);
|
|
2571
|
+
console.log();
|
|
2572
|
+
|
|
2573
|
+
const width = process.stdout.columns || 80;
|
|
2574
|
+
const resultLines = renderSearchResults(data, width);
|
|
2575
|
+
if (resultLines.length === 0) {
|
|
2576
|
+
console.log(` ${c.dim}No results found${c.reset}`);
|
|
2577
|
+
console.log(` ${c.dim}Tip: try ${c.cyan}agentaudit scan <repo-url>${c.dim} to audit a new package${c.reset}`);
|
|
2578
|
+
console.log();
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
for (const line of resultLines) console.log(line);
|
|
2583
|
+
console.log();
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2252
2586
|
async function leaderboardCommand(args) {
|
|
2253
2587
|
const tabArg = args.find((a, i) => args[i - 1] === '--tab') || 'overall';
|
|
2254
2588
|
const showAll = args.includes('--all');
|
|
@@ -2302,7 +2636,7 @@ async function leaderboardCommand(args) {
|
|
|
2302
2636
|
const bar = renderBar(entry.total_points || 0, maxPts, barW);
|
|
2303
2637
|
const pts = `${fmtNum(entry.total_points || 0)} pts`;
|
|
2304
2638
|
const audits = `${fmtNum(entry.total_reports || 0)} audits`;
|
|
2305
|
-
console.log(`${prefix}${padRight(nameStr, 22
|
|
2639
|
+
console.log(`${prefix}${padRight(nameStr, 22)} ${bar} ${padLeft(pts, 12)} ${padLeft(audits, 10)}`);
|
|
2306
2640
|
if (i === 2 && data.length > 3) {
|
|
2307
2641
|
console.log(` ${c.dim}${'─'.repeat(74)}${c.reset}`);
|
|
2308
2642
|
}
|
|
@@ -2356,11 +2690,19 @@ async function dashboardCommand() {
|
|
|
2356
2690
|
{ key: '1', label: 'Overview' },
|
|
2357
2691
|
{ key: '2', label: 'Leaderboard' },
|
|
2358
2692
|
{ key: '3', label: 'Benchmark' },
|
|
2693
|
+
{ key: '4', label: 'Activity' },
|
|
2694
|
+
{ key: '5', label: 'Search' },
|
|
2359
2695
|
];
|
|
2360
2696
|
let activeTab = 0;
|
|
2361
2697
|
let scrollOffset = 0;
|
|
2362
2698
|
let running = true;
|
|
2363
2699
|
|
|
2700
|
+
// Search tab state
|
|
2701
|
+
let searchQuery = '';
|
|
2702
|
+
let searchResults = null;
|
|
2703
|
+
let searchLoading = false;
|
|
2704
|
+
let searchError = null;
|
|
2705
|
+
|
|
2364
2706
|
// Enter alt screen
|
|
2365
2707
|
process.stdout.write(term.altScreenOn + term.hideCursor);
|
|
2366
2708
|
|
|
@@ -2373,6 +2715,25 @@ async function dashboardCommand() {
|
|
|
2373
2715
|
// Fetch data
|
|
2374
2716
|
const data = await fetchDashboardData();
|
|
2375
2717
|
|
|
2718
|
+
async function doSearch() {
|
|
2719
|
+
if (!searchQuery.trim()) return;
|
|
2720
|
+
searchLoading = true;
|
|
2721
|
+
searchError = null;
|
|
2722
|
+
render();
|
|
2723
|
+
try {
|
|
2724
|
+
const res = await fetch(`${REGISTRY_URL}/api/lookup?hash=${encodeURIComponent(searchQuery.trim())}`, {
|
|
2725
|
+
signal: AbortSignal.timeout(15_000),
|
|
2726
|
+
});
|
|
2727
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2728
|
+
searchResults = await res.json();
|
|
2729
|
+
} catch (err) {
|
|
2730
|
+
searchError = `Search failed: ${err.message}`;
|
|
2731
|
+
searchResults = null;
|
|
2732
|
+
}
|
|
2733
|
+
searchLoading = false;
|
|
2734
|
+
if (running) render();
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2376
2737
|
function render() {
|
|
2377
2738
|
const cols = process.stdout.columns || 80;
|
|
2378
2739
|
const rows = process.stdout.rows || 24;
|
|
@@ -2392,7 +2753,7 @@ async function dashboardCommand() {
|
|
|
2392
2753
|
} else {
|
|
2393
2754
|
tabBar += `${c.dim} [${tab.key}] ${tab.label} ${c.reset}`;
|
|
2394
2755
|
}
|
|
2395
|
-
if (i < tabs.length - 1) tabBar += `${c.dim}
|
|
2756
|
+
if (i < tabs.length - 1) tabBar += `${c.dim}\u2500${c.reset}`;
|
|
2396
2757
|
}
|
|
2397
2758
|
output += tabBar + '\n\n';
|
|
2398
2759
|
|
|
@@ -2408,6 +2769,12 @@ async function dashboardCommand() {
|
|
|
2408
2769
|
case 2:
|
|
2409
2770
|
contentLines = renderBenchmarkTab(data, cols);
|
|
2410
2771
|
break;
|
|
2772
|
+
case 3:
|
|
2773
|
+
contentLines = renderActivityTab(data, cols);
|
|
2774
|
+
break;
|
|
2775
|
+
case 4:
|
|
2776
|
+
contentLines = renderSearchTab({ query: searchQuery, results: searchResults, loading: searchLoading, error: searchError }, cols);
|
|
2777
|
+
break;
|
|
2411
2778
|
}
|
|
2412
2779
|
|
|
2413
2780
|
// Apply scroll
|
|
@@ -2422,7 +2789,11 @@ async function dashboardCommand() {
|
|
|
2422
2789
|
// Footer
|
|
2423
2790
|
output += '\n';
|
|
2424
2791
|
const scrollInfo = contentLines.length > availableRows ? ` ${c.dim}[${scrollOffset + 1}-${Math.min(scrollOffset + availableRows, contentLines.length)}/${contentLines.length}]${c.reset}` : '';
|
|
2425
|
-
|
|
2792
|
+
const isSearchTab = activeTab === 4;
|
|
2793
|
+
const footerHint = isSearchTab
|
|
2794
|
+
? `${c.dim}\u2190\u2192 tab Enter=search Esc=clear q quit${c.reset}`
|
|
2795
|
+
: `${c.dim}\u2190\u2192 tab \u2191\u2193 scroll 1-5 jump q quit${c.reset}`;
|
|
2796
|
+
output += ` ${footerHint}${scrollInfo}\n`;
|
|
2426
2797
|
|
|
2427
2798
|
process.stdout.write(output);
|
|
2428
2799
|
}
|
|
@@ -2439,37 +2810,103 @@ async function dashboardCommand() {
|
|
|
2439
2810
|
|
|
2440
2811
|
function onKeypress(key) {
|
|
2441
2812
|
if (!running) return;
|
|
2813
|
+
const isSearchTab = activeTab === 4;
|
|
2442
2814
|
|
|
2443
|
-
//
|
|
2444
|
-
if (key === '
|
|
2815
|
+
// Ctrl+C always quits
|
|
2816
|
+
if (key === '\x03') {
|
|
2817
|
+
cleanup();
|
|
2818
|
+
return;
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
// Search tab: route printable keys to search input
|
|
2822
|
+
if (isSearchTab) {
|
|
2823
|
+
// Escape: clear search
|
|
2824
|
+
if (key === '\x1b' && key.length === 1) {
|
|
2825
|
+
searchQuery = '';
|
|
2826
|
+
searchResults = null;
|
|
2827
|
+
searchError = null;
|
|
2828
|
+
scrollOffset = 0;
|
|
2829
|
+
render();
|
|
2830
|
+
return;
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
// Enter: trigger search
|
|
2834
|
+
if (key === '\r' || key === '\n') {
|
|
2835
|
+
doSearch();
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
// Backspace / Delete
|
|
2840
|
+
if (key === '\x7f' || key === '\b') {
|
|
2841
|
+
if (searchQuery.length > 0) {
|
|
2842
|
+
searchQuery = searchQuery.slice(0, -1);
|
|
2843
|
+
render();
|
|
2844
|
+
}
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
// Tab: switch to next tab
|
|
2849
|
+
if (key === '\t') {
|
|
2850
|
+
activeTab = (activeTab + 1) % tabs.length;
|
|
2851
|
+
scrollOffset = 0;
|
|
2852
|
+
render();
|
|
2853
|
+
return;
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
// Arrow keys still navigate
|
|
2857
|
+
if (key === '\x1b[C') { activeTab = (activeTab + 1) % tabs.length; scrollOffset = 0; render(); return; }
|
|
2858
|
+
if (key === '\x1b[D') { activeTab = (activeTab - 1 + tabs.length) % tabs.length; scrollOffset = 0; render(); return; }
|
|
2859
|
+
if (key === '\x1b[A') { scrollOffset = Math.max(0, scrollOffset - 1); render(); return; }
|
|
2860
|
+
if (key === '\x1b[B') { scrollOffset++; render(); return; }
|
|
2861
|
+
|
|
2862
|
+
// q quits only if search buffer is empty
|
|
2863
|
+
if (key === 'q' && searchQuery.length === 0) {
|
|
2864
|
+
cleanup();
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
// Printable ASCII → append to search query
|
|
2869
|
+
if (key.length === 1 && key.charCodeAt(0) >= 32 && key.charCodeAt(0) <= 126) {
|
|
2870
|
+
searchQuery += key;
|
|
2871
|
+
render();
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
// Non-search tabs: normal keypress handling
|
|
2878
|
+
// q = quit
|
|
2879
|
+
if (key === 'q') {
|
|
2445
2880
|
cleanup();
|
|
2446
2881
|
return;
|
|
2447
2882
|
}
|
|
2448
2883
|
|
|
2449
2884
|
// Tab navigation
|
|
2450
|
-
if (key === '\x1b[C' || key === '\t') {
|
|
2885
|
+
if (key === '\x1b[C' || key === '\t') {
|
|
2451
2886
|
activeTab = (activeTab + 1) % tabs.length;
|
|
2452
2887
|
scrollOffset = 0;
|
|
2453
2888
|
render();
|
|
2454
2889
|
return;
|
|
2455
2890
|
}
|
|
2456
|
-
if (key === '\x1b[D') {
|
|
2891
|
+
if (key === '\x1b[D') {
|
|
2457
2892
|
activeTab = (activeTab - 1 + tabs.length) % tabs.length;
|
|
2458
2893
|
scrollOffset = 0;
|
|
2459
2894
|
render();
|
|
2460
2895
|
return;
|
|
2461
2896
|
}
|
|
2462
2897
|
|
|
2463
|
-
// Number keys
|
|
2464
|
-
if (key
|
|
2465
|
-
|
|
2466
|
-
|
|
2898
|
+
// Number keys 1-5
|
|
2899
|
+
if (key >= '1' && key <= '5') {
|
|
2900
|
+
const idx = parseInt(key, 10) - 1;
|
|
2901
|
+
if (idx < tabs.length) { activeTab = idx; scrollOffset = 0; render(); }
|
|
2902
|
+
return;
|
|
2903
|
+
}
|
|
2467
2904
|
|
|
2468
2905
|
// Scroll
|
|
2469
|
-
if (key === '\x1b[A' || key === 'k') { scrollOffset = Math.max(0, scrollOffset - 1); render(); return; }
|
|
2470
|
-
if (key === '\x1b[B' || key === 'j') { scrollOffset++; render(); return; }
|
|
2471
|
-
if (key === '\x1b[5~') { scrollOffset = Math.max(0, scrollOffset - 10); render(); return; }
|
|
2472
|
-
if (key === '\x1b[6~') { scrollOffset += 10; render(); return; }
|
|
2906
|
+
if (key === '\x1b[A' || key === 'k') { scrollOffset = Math.max(0, scrollOffset - 1); render(); return; }
|
|
2907
|
+
if (key === '\x1b[B' || key === 'j') { scrollOffset++; render(); return; }
|
|
2908
|
+
if (key === '\x1b[5~') { scrollOffset = Math.max(0, scrollOffset - 10); render(); return; }
|
|
2909
|
+
if (key === '\x1b[6~') { scrollOffset += 10; render(); return; }
|
|
2473
2910
|
}
|
|
2474
2911
|
|
|
2475
2912
|
// Setup input
|
|
@@ -2636,9 +3073,9 @@ async function main() {
|
|
|
2636
3073
|
`Uses alternate screen buffer — your scrollback stays intact.`,
|
|
2637
3074
|
``,
|
|
2638
3075
|
`${c.bold}Navigation:${c.reset}`,
|
|
2639
|
-
`
|
|
2640
|
-
` 1-
|
|
2641
|
-
`
|
|
3076
|
+
` \u2190\u2192 / Tab Switch between tabs`,
|
|
3077
|
+
` 1-5 Jump to tab by number`,
|
|
3078
|
+
` \u2191\u2193 / j/k Scroll content`,
|
|
2642
3079
|
` PgUp/PgDn Scroll fast`,
|
|
2643
3080
|
` q / Ctrl+C Quit`,
|
|
2644
3081
|
``,
|
|
@@ -2646,6 +3083,8 @@ async function main() {
|
|
|
2646
3083
|
` [1] Overview Your profile + registry stats`,
|
|
2647
3084
|
` [2] Leaderboard Top contributors ranking`,
|
|
2648
3085
|
` [3] Benchmark LLM model performance comparison`,
|
|
3086
|
+
` [4] Activity Your recent audits & findings`,
|
|
3087
|
+
` [5] Search Search packages (interactive input)`,
|
|
2649
3088
|
``,
|
|
2650
3089
|
`${c.bold}Aliases:${c.reset} dashboard, dash`,
|
|
2651
3090
|
],
|
|
@@ -2684,6 +3123,41 @@ async function main() {
|
|
|
2684
3123
|
` agentaudit benchmark --json`,
|
|
2685
3124
|
],
|
|
2686
3125
|
bench: null, // alias → benchmark
|
|
3126
|
+
activity: [
|
|
3127
|
+
`${c.bold}agentaudit activity${c.reset} [options]`,
|
|
3128
|
+
``,
|
|
3129
|
+
`Show your recent audits and findings from the AgentAudit registry.`,
|
|
3130
|
+
`Requires being logged in (run ${c.cyan}agentaudit setup${c.reset} first).`,
|
|
3131
|
+
``,
|
|
3132
|
+
`${c.bold}Options:${c.reset}`,
|
|
3133
|
+
` --json Machine-readable JSON output`,
|
|
3134
|
+
``,
|
|
3135
|
+
`${c.bold}Aliases:${c.reset} activity, my`,
|
|
3136
|
+
``,
|
|
3137
|
+
`${c.bold}Examples:${c.reset}`,
|
|
3138
|
+
` agentaudit activity`,
|
|
3139
|
+
` agentaudit activity --json`,
|
|
3140
|
+
` agentaudit my`,
|
|
3141
|
+
],
|
|
3142
|
+
my: null, // alias → activity
|
|
3143
|
+
search: [
|
|
3144
|
+
`${c.bold}agentaudit search${c.reset} <query> [options]`,
|
|
3145
|
+
``,
|
|
3146
|
+
`Search for packages in the AgentAudit registry by name, ASF-ID, or hash.`,
|
|
3147
|
+
``,
|
|
3148
|
+
`${c.bold}Options:${c.reset}`,
|
|
3149
|
+
` --json Machine-readable JSON output`,
|
|
3150
|
+
``,
|
|
3151
|
+
`${c.bold}Aliases:${c.reset} search, find`,
|
|
3152
|
+
``,
|
|
3153
|
+
`${c.bold}Examples:${c.reset}`,
|
|
3154
|
+
` agentaudit search fastmcp`,
|
|
3155
|
+
` agentaudit search mcp-server`,
|
|
3156
|
+
` agentaudit search ASF-2025-0001`,
|
|
3157
|
+
` agentaudit search fastmcp --json`,
|
|
3158
|
+
` agentaudit find fastmcp`,
|
|
3159
|
+
],
|
|
3160
|
+
find: null, // alias → search
|
|
2687
3161
|
};
|
|
2688
3162
|
|
|
2689
3163
|
// Show subcommand help: `agentaudit help <cmd>` or `agentaudit <cmd> --help`
|
|
@@ -2691,7 +3165,7 @@ async function main() {
|
|
|
2691
3165
|
let helpLines = subcommandHelp[cmd];
|
|
2692
3166
|
if (helpLines === null) {
|
|
2693
3167
|
// alias redirect
|
|
2694
|
-
const aliases = { check: 'lookup', dash: 'dashboard', lb: 'leaderboard', bench: 'benchmark' };
|
|
3168
|
+
const aliases = { check: 'lookup', dash: 'dashboard', lb: 'leaderboard', bench: 'benchmark', my: 'activity', find: 'search' };
|
|
2695
3169
|
helpLines = subcommandHelp[aliases[cmd] || cmd];
|
|
2696
3170
|
}
|
|
2697
3171
|
if (!helpLines) {
|
|
@@ -2739,6 +3213,8 @@ async function main() {
|
|
|
2739
3213
|
console.log(` ${c.cyan}dashboard${c.reset} Interactive dashboard (full-screen)`);
|
|
2740
3214
|
console.log(` ${c.cyan}leaderboard${c.reset} Top contributors ranking`);
|
|
2741
3215
|
console.log(` ${c.cyan}benchmark${c.reset} LLM model performance comparison`);
|
|
3216
|
+
console.log(` ${c.cyan}activity${c.reset} Your recent audits & findings`);
|
|
3217
|
+
console.log(` ${c.cyan}search${c.reset} <query> Search packages in registry`);
|
|
2742
3218
|
console.log();
|
|
2743
3219
|
console.log(` ${c.bold}CONFIGURATION${c.reset}`);
|
|
2744
3220
|
console.log(` ${c.cyan}model${c.reset} Configure LLM provider + model`);
|
|
@@ -2784,6 +3260,14 @@ async function main() {
|
|
|
2784
3260
|
await benchmarkCommand(targets);
|
|
2785
3261
|
return;
|
|
2786
3262
|
}
|
|
3263
|
+
if (command === 'activity' || command === 'my') {
|
|
3264
|
+
await activityCommand(targets);
|
|
3265
|
+
return;
|
|
3266
|
+
}
|
|
3267
|
+
if (command === 'search' || command === 'find') {
|
|
3268
|
+
await searchCommand(targets);
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
2787
3271
|
|
|
2788
3272
|
banner();
|
|
2789
3273
|
|
|
@@ -2859,13 +3343,20 @@ async function main() {
|
|
|
2859
3343
|
fetch(`${REGISTRY_URL}/api/leaderboard?limit=100`, { signal: AbortSignal.timeout(10_000) }).then(r => r.ok ? r.json() : null),
|
|
2860
3344
|
]);
|
|
2861
3345
|
if (agentRes) {
|
|
2862
|
-
|
|
2863
|
-
let rank = '-';
|
|
3346
|
+
let rank = null;
|
|
2864
3347
|
if (Array.isArray(lbRes)) {
|
|
2865
3348
|
const idx = lbRes.findIndex(e => e.agent_name === creds.agent_name);
|
|
2866
|
-
if (idx >= 0) rank =
|
|
3349
|
+
if (idx >= 0) rank = idx + 1;
|
|
2867
3350
|
}
|
|
2868
|
-
|
|
3351
|
+
// Update profile cache
|
|
3352
|
+
saveProfileCache({
|
|
3353
|
+
agent_name: creds.agent_name,
|
|
3354
|
+
rank,
|
|
3355
|
+
total_points: agentRes.total_points || 0,
|
|
3356
|
+
total_reports: agentRes.total_reports || 0,
|
|
3357
|
+
});
|
|
3358
|
+
console.log(` ${c.bold}Profile${c.reset}`);
|
|
3359
|
+
console.log(` Rank ${c.bold}${rank ? `#${rank} of ${lbRes.length}` : '-'}${c.reset}`);
|
|
2869
3360
|
console.log(` Points ${c.bold}${fmtNum(agentRes.total_points || 0)}${c.reset}`);
|
|
2870
3361
|
console.log(` Audits ${c.bold}${fmtNum(agentRes.total_reports || 0)}${c.reset}`);
|
|
2871
3362
|
console.log(` Findings ${c.bold}${fmtNum(agentRes.total_findings_submitted || 0)}${c.reset} ${c.dim}(${fmtNum(agentRes.total_findings_confirmed || 0)} confirmed)${c.reset}`);
|