agentaudit 3.10.3 → 3.10.4
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 +513 -32
- 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
|
|
|
@@ -2078,6 +2113,20 @@ async function fetchDashboardData() {
|
|
|
2078
2113
|
fetches.push(Promise.resolve(null));
|
|
2079
2114
|
}
|
|
2080
2115
|
const [stats, leaderboard, benchmark, agent] = await Promise.all(fetches);
|
|
2116
|
+
// Update profile cache if we have agent data
|
|
2117
|
+
if (agent && creds) {
|
|
2118
|
+
let rank = null;
|
|
2119
|
+
if (Array.isArray(leaderboard)) {
|
|
2120
|
+
const idx = leaderboard.findIndex(e => e.agent_name === creds.agent_name);
|
|
2121
|
+
if (idx >= 0) rank = idx + 1;
|
|
2122
|
+
}
|
|
2123
|
+
saveProfileCache({
|
|
2124
|
+
agent_name: creds.agent_name,
|
|
2125
|
+
rank,
|
|
2126
|
+
total_points: agent.total_points || 0,
|
|
2127
|
+
total_reports: agent.total_reports || 0,
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2081
2130
|
return { stats, leaderboard, benchmark, agent, creds };
|
|
2082
2131
|
}
|
|
2083
2132
|
|
|
@@ -2173,7 +2222,7 @@ function renderLeaderboardTab(data, width, opts = {}) {
|
|
|
2173
2222
|
const pts = padLeft(`${fmtNum(entry.total_points || 0)} pts`, 12);
|
|
2174
2223
|
const audits = padLeft(`${fmtNum(entry.total_reports || 0)} audits`, 12);
|
|
2175
2224
|
const extra = entry.monthly_reports != null ? padLeft(`${fmtNum(entry.monthly_reports)} this mo`, 12) : '';
|
|
2176
|
-
lines.push(`${prefix}${padRight(nameStr, maxNameW
|
|
2225
|
+
lines.push(`${prefix}${padRight(nameStr, maxNameW)} ${bar} ${pts} ${audits}${extra}`);
|
|
2177
2226
|
|
|
2178
2227
|
// Separator after top 3
|
|
2179
2228
|
if (i === 2 && leaderboard.length > 3) {
|
|
@@ -2204,20 +2253,27 @@ function renderBenchmarkTab(data, width) {
|
|
|
2204
2253
|
lines.push('');
|
|
2205
2254
|
|
|
2206
2255
|
// Header
|
|
2207
|
-
const nameW =
|
|
2208
|
-
const hdr = ` ${padRight(`${c.bold}Model${c.reset}`, nameW + 9)} ${padRight('Audits',
|
|
2256
|
+
const nameW = 28;
|
|
2257
|
+
const hdr = ` ${padRight(`${c.bold}Model${c.reset}`, nameW + 9)} ${padRight('Audits', 7)} ${padRight('Risk', 5)} ${padRight('Detection', 16)} Severity`;
|
|
2209
2258
|
lines.push(hdr);
|
|
2210
|
-
lines.push(` ${c.dim}${'─'.repeat(Math.min(width - 4,
|
|
2259
|
+
lines.push(` ${c.dim}${'─'.repeat(Math.min(width - 4, 86))}${c.reset}`);
|
|
2211
2260
|
|
|
2212
2261
|
for (const m of benchmark.models) {
|
|
2213
2262
|
const name = (m.audit_model || 'unknown').slice(0, nameW - 2);
|
|
2214
|
-
const audits = padLeft(fmtNum(m.total_audits),
|
|
2263
|
+
const audits = padLeft(fmtNum(m.total_audits), 5);
|
|
2215
2264
|
const riskVal = parseFloat(m.avg_risk_score) || 0;
|
|
2216
2265
|
const riskColor = riskVal <= 20 ? c.green : riskVal <= 40 ? c.yellow : c.red;
|
|
2217
2266
|
const risk = `${riskColor}${padLeft(String(Math.round(riskVal)), 3)}${c.reset}`;
|
|
2218
2267
|
const detection = renderGauge(m.detection_rate || 0, 100, 10);
|
|
2219
|
-
|
|
2220
|
-
|
|
2268
|
+
// Severity as compact text instead of dots
|
|
2269
|
+
const sev = m.severity_breakdown || {};
|
|
2270
|
+
const sevParts = [];
|
|
2271
|
+
if (sev.critical) sevParts.push(`${c.red}${sev.critical}C${c.reset}`);
|
|
2272
|
+
if (sev.high) sevParts.push(`${c.red}${sev.high}H${c.reset}`);
|
|
2273
|
+
if (sev.medium) sevParts.push(`${c.yellow}${sev.medium}M${c.reset}`);
|
|
2274
|
+
if (sev.low) sevParts.push(`${c.blue}${sev.low}L${c.reset}`);
|
|
2275
|
+
const sevStr = sevParts.length > 0 ? sevParts.join(' ') : `${c.dim}—${c.reset}`;
|
|
2276
|
+
lines.push(` ${padRight(name, nameW)} ${audits} ${risk} ${detection} ${sevStr}`);
|
|
2221
2277
|
}
|
|
2222
2278
|
|
|
2223
2279
|
// Vulnerability landscape
|
|
@@ -2249,6 +2305,274 @@ function renderBenchmarkTab(data, width) {
|
|
|
2249
2305
|
return lines;
|
|
2250
2306
|
}
|
|
2251
2307
|
|
|
2308
|
+
function timeAgo(dateStr) {
|
|
2309
|
+
if (!dateStr) return '';
|
|
2310
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
2311
|
+
const mins = Math.floor(diff / 60000);
|
|
2312
|
+
if (mins < 60) return `${mins}m ago`;
|
|
2313
|
+
const hours = Math.floor(mins / 60);
|
|
2314
|
+
if (hours < 24) return `${hours}h ago`;
|
|
2315
|
+
const days = Math.floor(hours / 24);
|
|
2316
|
+
if (days < 30) return `${days}d ago`;
|
|
2317
|
+
return `${Math.floor(days / 30)}mo ago`;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
function renderActivityTab(data, width) {
|
|
2321
|
+
const { agent, creds } = data;
|
|
2322
|
+
const lines = [];
|
|
2323
|
+
|
|
2324
|
+
if (!creds || !agent) {
|
|
2325
|
+
lines.push(` ${c.dim}Not logged in${c.reset}`);
|
|
2326
|
+
lines.push(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to see your activity${c.reset}`);
|
|
2327
|
+
return lines;
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
// Recent Audits
|
|
2331
|
+
const reports = agent.recent_reports || [];
|
|
2332
|
+
const auditLines = [];
|
|
2333
|
+
if (reports.length > 0) {
|
|
2334
|
+
for (const r of reports.slice(0, 10)) {
|
|
2335
|
+
const time = padRight(timeAgo(r.created_at), 8);
|
|
2336
|
+
const name = padRight((r.skill_slug || r.package_name || '').slice(0, 20), 20);
|
|
2337
|
+
const score = r.risk_score ?? r.latest_risk_score ?? 0;
|
|
2338
|
+
let status;
|
|
2339
|
+
if (score === 0) status = `${c.green}safe${c.reset} `;
|
|
2340
|
+
else if (score <= 30) status = `${c.yellow}caution${c.reset}`;
|
|
2341
|
+
else status = `${c.red}unsafe${c.reset} `;
|
|
2342
|
+
const findCount = r.finding_count != null ? `${r.finding_count} findings` : '';
|
|
2343
|
+
const model = r.audit_model ? `${c.dim}${(r.audit_model || '').slice(0, 14)}${c.reset}` : '';
|
|
2344
|
+
auditLines.push(`${c.dim}${time}${c.reset} ${name} ${status} ${padRight(findCount, 12)} ${model}`);
|
|
2345
|
+
}
|
|
2346
|
+
} else {
|
|
2347
|
+
auditLines.push(`${c.dim}No audits yet — run ${c.cyan}agentaudit audit <url>${c.dim} to get started${c.reset}`);
|
|
2348
|
+
}
|
|
2349
|
+
lines.push(...drawBox('Recent Audits', auditLines, Math.min(width - 4, 72)));
|
|
2350
|
+
lines.push('');
|
|
2351
|
+
|
|
2352
|
+
// Recent Findings
|
|
2353
|
+
const findings = agent.recent_findings || [];
|
|
2354
|
+
const findingLines = [];
|
|
2355
|
+
if (findings.length > 0) {
|
|
2356
|
+
for (const f of findings.slice(0, 10)) {
|
|
2357
|
+
const asfId = padRight(f.asf_id || f.id || '', 16);
|
|
2358
|
+
const sev = severityIcon(f.severity);
|
|
2359
|
+
const sevLabel = padRight(f.severity || '', 9);
|
|
2360
|
+
const title = (f.title || f.pattern_id || '').slice(0, 40);
|
|
2361
|
+
findingLines.push(`${asfId} ${sev} ${sevLabel} ${title}`);
|
|
2362
|
+
}
|
|
2363
|
+
} else {
|
|
2364
|
+
findingLines.push(`${c.dim}No findings yet${c.reset}`);
|
|
2365
|
+
}
|
|
2366
|
+
lines.push(...drawBox('Recent Findings', findingLines, Math.min(width - 4, 72)));
|
|
2367
|
+
|
|
2368
|
+
return lines;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
async function activityCommand(args) {
|
|
2372
|
+
const creds = loadCredentials();
|
|
2373
|
+
|
|
2374
|
+
if (!creds?.agent_name || creds.agent_name === 'env') {
|
|
2375
|
+
if (jsonMode) {
|
|
2376
|
+
console.log(JSON.stringify({ error: 'not_logged_in' }));
|
|
2377
|
+
} else {
|
|
2378
|
+
banner();
|
|
2379
|
+
console.log(` ${c.yellow}Not logged in${c.reset}`);
|
|
2380
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to create an account${c.reset}`);
|
|
2381
|
+
}
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
if (!quietMode && !jsonMode) {
|
|
2386
|
+
banner();
|
|
2387
|
+
process.stdout.write(` ${c.dim}Loading activity...${c.reset}`);
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
let agentData;
|
|
2391
|
+
try {
|
|
2392
|
+
const res = await fetch(`${REGISTRY_URL}/api/agents/${encodeURIComponent(creds.agent_name)}`, {
|
|
2393
|
+
headers: { 'Authorization': `Bearer ${creds.api_key}` },
|
|
2394
|
+
signal: AbortSignal.timeout(15_000),
|
|
2395
|
+
});
|
|
2396
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2397
|
+
agentData = await res.json();
|
|
2398
|
+
} catch (err) {
|
|
2399
|
+
if (!jsonMode) {
|
|
2400
|
+
process.stdout.write('\r\x1b[2K');
|
|
2401
|
+
console.log(` ${c.red}Failed to load activity: ${err.message}${c.reset}`);
|
|
2402
|
+
}
|
|
2403
|
+
process.exitCode = 1;
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
if (jsonMode) {
|
|
2408
|
+
console.log(JSON.stringify(agentData, null, 2));
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
process.stdout.write('\r\x1b[2K');
|
|
2413
|
+
|
|
2414
|
+
// Update profile cache
|
|
2415
|
+
try {
|
|
2416
|
+
const lbRes = await fetch(`${REGISTRY_URL}/api/leaderboard?limit=100`, { signal: AbortSignal.timeout(10_000) }).then(r => r.ok ? r.json() : null);
|
|
2417
|
+
let rank = null;
|
|
2418
|
+
if (Array.isArray(lbRes)) {
|
|
2419
|
+
const idx = lbRes.findIndex(e => e.agent_name === creds.agent_name);
|
|
2420
|
+
if (idx >= 0) rank = idx + 1;
|
|
2421
|
+
}
|
|
2422
|
+
saveProfileCache({
|
|
2423
|
+
agent_name: creds.agent_name,
|
|
2424
|
+
rank,
|
|
2425
|
+
total_points: agentData.total_points || 0,
|
|
2426
|
+
total_reports: agentData.total_reports || 0,
|
|
2427
|
+
});
|
|
2428
|
+
} catch {}
|
|
2429
|
+
|
|
2430
|
+
const width = process.stdout.columns || 80;
|
|
2431
|
+
const activityLines = renderActivityTab({ agent: agentData, creds }, width);
|
|
2432
|
+
console.log(` ${c.bold}Activity${c.reset} ${c.dim}${creds.agent_name}${c.reset}`);
|
|
2433
|
+
console.log();
|
|
2434
|
+
for (const line of activityLines) console.log(line);
|
|
2435
|
+
console.log();
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
function renderSearchResults(data, width) {
|
|
2439
|
+
const lines = [];
|
|
2440
|
+
const boxW = Math.min(width - 4, 72);
|
|
2441
|
+
|
|
2442
|
+
// Lookup API returns { reports: [], findings: [], total_matches }
|
|
2443
|
+
const lookupData = Array.isArray(data) ? data[0] : data;
|
|
2444
|
+
const reports = lookupData?.reports || [];
|
|
2445
|
+
const findings = lookupData?.findings || [];
|
|
2446
|
+
const total = lookupData?.total_matches || (reports.length + findings.length);
|
|
2447
|
+
|
|
2448
|
+
if (total === 0) return lines;
|
|
2449
|
+
|
|
2450
|
+
// Packages (from reports)
|
|
2451
|
+
if (reports.length > 0) {
|
|
2452
|
+
const pkgLines = [];
|
|
2453
|
+
for (const r of reports.slice(0, 10)) {
|
|
2454
|
+
const name = padRight((r.skill_slug || '').slice(0, 22), 22);
|
|
2455
|
+
const riskScore = r.risk_score ?? 0;
|
|
2456
|
+
let badge;
|
|
2457
|
+
if (riskScore === 0) badge = `${c.green}SAFE${c.reset} `;
|
|
2458
|
+
else if (riskScore <= 30) badge = `${c.yellow}CAUTION${c.reset}`;
|
|
2459
|
+
else badge = `${c.red}UNSAFE${c.reset} `;
|
|
2460
|
+
const time = padRight(timeAgo(r.created_at), 8);
|
|
2461
|
+
pkgLines.push(`${name} ${badge} ${c.dim}${time}${c.reset}`);
|
|
2462
|
+
}
|
|
2463
|
+
lines.push(...drawBox(`Packages (${reports.length})`, pkgLines, boxW));
|
|
2464
|
+
lines.push('');
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
// Findings
|
|
2468
|
+
if (findings.length > 0) {
|
|
2469
|
+
const findLines = [];
|
|
2470
|
+
for (const f of findings.slice(0, 10)) {
|
|
2471
|
+
const asfId = padRight(f.asf_id || '', 16);
|
|
2472
|
+
const sev = severityIcon(f.severity);
|
|
2473
|
+
const sevLabel = padRight(f.severity || '', 9);
|
|
2474
|
+
const title = (f.title || '').slice(0, 36);
|
|
2475
|
+
findLines.push(`${asfId} ${sev} ${sevLabel} ${title}`);
|
|
2476
|
+
}
|
|
2477
|
+
lines.push(...drawBox(`Findings (${findings.length})`, findLines, boxW));
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
return lines;
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
function renderSearchTab(searchState, width) {
|
|
2484
|
+
const { query, results, loading, error } = searchState;
|
|
2485
|
+
const lines = [];
|
|
2486
|
+
|
|
2487
|
+
// Search input
|
|
2488
|
+
lines.push(` ${c.bold}Search:${c.reset} ${query || ''}${c.dim}\u2588${c.reset}`);
|
|
2489
|
+
lines.push('');
|
|
2490
|
+
|
|
2491
|
+
if (loading) {
|
|
2492
|
+
lines.push(` ${c.dim}Searching...${c.reset}`);
|
|
2493
|
+
return lines;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
if (error) {
|
|
2497
|
+
lines.push(` ${c.red}${error}${c.reset}`);
|
|
2498
|
+
return lines;
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
if (results) {
|
|
2502
|
+
const resultLines = renderSearchResults(results, width);
|
|
2503
|
+
if (resultLines.length > 0) {
|
|
2504
|
+
lines.push(...resultLines);
|
|
2505
|
+
} else {
|
|
2506
|
+
lines.push(` ${c.dim}No results found for "${query}"${c.reset}`);
|
|
2507
|
+
}
|
|
2508
|
+
} else if (!query) {
|
|
2509
|
+
lines.push(` ${c.dim}Type to search packages in the registry${c.reset}`);
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
lines.push('');
|
|
2513
|
+
lines.push(` ${c.dim}Type to search \u2502 Enter=search \u2502 Esc=clear \u2502 Tab=switch tab${c.reset}`);
|
|
2514
|
+
|
|
2515
|
+
return lines;
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
async function searchCommand(args) {
|
|
2519
|
+
const query = args.filter(a => !a.startsWith('--')).join(' ').trim();
|
|
2520
|
+
|
|
2521
|
+
if (!query) {
|
|
2522
|
+
if (jsonMode) {
|
|
2523
|
+
console.log(JSON.stringify({ error: 'query_required' }));
|
|
2524
|
+
} else {
|
|
2525
|
+
banner();
|
|
2526
|
+
console.log(` ${c.red}Error: search query required${c.reset}`);
|
|
2527
|
+
console.log(` ${c.dim}Usage: ${c.cyan}agentaudit search <query>${c.dim} \u2014 e.g. agentaudit search fastmcp${c.reset}`);
|
|
2528
|
+
}
|
|
2529
|
+
process.exitCode = 2;
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
if (!quietMode && !jsonMode) {
|
|
2534
|
+
banner();
|
|
2535
|
+
process.stdout.write(` ${c.dim}Searching "${query}"...${c.reset}`);
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
let data;
|
|
2539
|
+
try {
|
|
2540
|
+
const res = await fetch(`${REGISTRY_URL}/api/lookup?hash=${encodeURIComponent(query)}`, {
|
|
2541
|
+
signal: AbortSignal.timeout(15_000),
|
|
2542
|
+
});
|
|
2543
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2544
|
+
data = await res.json();
|
|
2545
|
+
} catch (err) {
|
|
2546
|
+
if (!jsonMode) {
|
|
2547
|
+
process.stdout.write('\r\x1b[2K');
|
|
2548
|
+
console.log(` ${c.red}Search failed: ${err.message}${c.reset}`);
|
|
2549
|
+
}
|
|
2550
|
+
process.exitCode = 1;
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
if (jsonMode) {
|
|
2555
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
process.stdout.write('\r\x1b[2K');
|
|
2560
|
+
console.log(` ${c.bold}Search${c.reset} ${c.dim}"${query}"${c.reset}`);
|
|
2561
|
+
console.log();
|
|
2562
|
+
|
|
2563
|
+
const width = process.stdout.columns || 80;
|
|
2564
|
+
const resultLines = renderSearchResults(data, width);
|
|
2565
|
+
if (resultLines.length === 0) {
|
|
2566
|
+
console.log(` ${c.dim}No results found${c.reset}`);
|
|
2567
|
+
console.log(` ${c.dim}Tip: try ${c.cyan}agentaudit scan <repo-url>${c.dim} to audit a new package${c.reset}`);
|
|
2568
|
+
console.log();
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
for (const line of resultLines) console.log(line);
|
|
2573
|
+
console.log();
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2252
2576
|
async function leaderboardCommand(args) {
|
|
2253
2577
|
const tabArg = args.find((a, i) => args[i - 1] === '--tab') || 'overall';
|
|
2254
2578
|
const showAll = args.includes('--all');
|
|
@@ -2302,7 +2626,7 @@ async function leaderboardCommand(args) {
|
|
|
2302
2626
|
const bar = renderBar(entry.total_points || 0, maxPts, barW);
|
|
2303
2627
|
const pts = `${fmtNum(entry.total_points || 0)} pts`;
|
|
2304
2628
|
const audits = `${fmtNum(entry.total_reports || 0)} audits`;
|
|
2305
|
-
console.log(`${prefix}${padRight(nameStr, 22
|
|
2629
|
+
console.log(`${prefix}${padRight(nameStr, 22)} ${bar} ${padLeft(pts, 12)} ${padLeft(audits, 10)}`);
|
|
2306
2630
|
if (i === 2 && data.length > 3) {
|
|
2307
2631
|
console.log(` ${c.dim}${'─'.repeat(74)}${c.reset}`);
|
|
2308
2632
|
}
|
|
@@ -2356,11 +2680,19 @@ async function dashboardCommand() {
|
|
|
2356
2680
|
{ key: '1', label: 'Overview' },
|
|
2357
2681
|
{ key: '2', label: 'Leaderboard' },
|
|
2358
2682
|
{ key: '3', label: 'Benchmark' },
|
|
2683
|
+
{ key: '4', label: 'Activity' },
|
|
2684
|
+
{ key: '5', label: 'Search' },
|
|
2359
2685
|
];
|
|
2360
2686
|
let activeTab = 0;
|
|
2361
2687
|
let scrollOffset = 0;
|
|
2362
2688
|
let running = true;
|
|
2363
2689
|
|
|
2690
|
+
// Search tab state
|
|
2691
|
+
let searchQuery = '';
|
|
2692
|
+
let searchResults = null;
|
|
2693
|
+
let searchLoading = false;
|
|
2694
|
+
let searchError = null;
|
|
2695
|
+
|
|
2364
2696
|
// Enter alt screen
|
|
2365
2697
|
process.stdout.write(term.altScreenOn + term.hideCursor);
|
|
2366
2698
|
|
|
@@ -2373,6 +2705,25 @@ async function dashboardCommand() {
|
|
|
2373
2705
|
// Fetch data
|
|
2374
2706
|
const data = await fetchDashboardData();
|
|
2375
2707
|
|
|
2708
|
+
async function doSearch() {
|
|
2709
|
+
if (!searchQuery.trim()) return;
|
|
2710
|
+
searchLoading = true;
|
|
2711
|
+
searchError = null;
|
|
2712
|
+
render();
|
|
2713
|
+
try {
|
|
2714
|
+
const res = await fetch(`${REGISTRY_URL}/api/lookup?hash=${encodeURIComponent(searchQuery.trim())}`, {
|
|
2715
|
+
signal: AbortSignal.timeout(15_000),
|
|
2716
|
+
});
|
|
2717
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2718
|
+
searchResults = await res.json();
|
|
2719
|
+
} catch (err) {
|
|
2720
|
+
searchError = `Search failed: ${err.message}`;
|
|
2721
|
+
searchResults = null;
|
|
2722
|
+
}
|
|
2723
|
+
searchLoading = false;
|
|
2724
|
+
if (running) render();
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2376
2727
|
function render() {
|
|
2377
2728
|
const cols = process.stdout.columns || 80;
|
|
2378
2729
|
const rows = process.stdout.rows || 24;
|
|
@@ -2392,7 +2743,7 @@ async function dashboardCommand() {
|
|
|
2392
2743
|
} else {
|
|
2393
2744
|
tabBar += `${c.dim} [${tab.key}] ${tab.label} ${c.reset}`;
|
|
2394
2745
|
}
|
|
2395
|
-
if (i < tabs.length - 1) tabBar += `${c.dim}
|
|
2746
|
+
if (i < tabs.length - 1) tabBar += `${c.dim}\u2500${c.reset}`;
|
|
2396
2747
|
}
|
|
2397
2748
|
output += tabBar + '\n\n';
|
|
2398
2749
|
|
|
@@ -2408,6 +2759,12 @@ async function dashboardCommand() {
|
|
|
2408
2759
|
case 2:
|
|
2409
2760
|
contentLines = renderBenchmarkTab(data, cols);
|
|
2410
2761
|
break;
|
|
2762
|
+
case 3:
|
|
2763
|
+
contentLines = renderActivityTab(data, cols);
|
|
2764
|
+
break;
|
|
2765
|
+
case 4:
|
|
2766
|
+
contentLines = renderSearchTab({ query: searchQuery, results: searchResults, loading: searchLoading, error: searchError }, cols);
|
|
2767
|
+
break;
|
|
2411
2768
|
}
|
|
2412
2769
|
|
|
2413
2770
|
// Apply scroll
|
|
@@ -2422,7 +2779,11 @@ async function dashboardCommand() {
|
|
|
2422
2779
|
// Footer
|
|
2423
2780
|
output += '\n';
|
|
2424
2781
|
const scrollInfo = contentLines.length > availableRows ? ` ${c.dim}[${scrollOffset + 1}-${Math.min(scrollOffset + availableRows, contentLines.length)}/${contentLines.length}]${c.reset}` : '';
|
|
2425
|
-
|
|
2782
|
+
const isSearchTab = activeTab === 4;
|
|
2783
|
+
const footerHint = isSearchTab
|
|
2784
|
+
? `${c.dim}\u2190\u2192 tab Enter=search Esc=clear q quit${c.reset}`
|
|
2785
|
+
: `${c.dim}\u2190\u2192 tab \u2191\u2193 scroll 1-5 jump q quit${c.reset}`;
|
|
2786
|
+
output += ` ${footerHint}${scrollInfo}\n`;
|
|
2426
2787
|
|
|
2427
2788
|
process.stdout.write(output);
|
|
2428
2789
|
}
|
|
@@ -2439,37 +2800,103 @@ async function dashboardCommand() {
|
|
|
2439
2800
|
|
|
2440
2801
|
function onKeypress(key) {
|
|
2441
2802
|
if (!running) return;
|
|
2803
|
+
const isSearchTab = activeTab === 4;
|
|
2804
|
+
|
|
2805
|
+
// Ctrl+C always quits
|
|
2806
|
+
if (key === '\x03') {
|
|
2807
|
+
cleanup();
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
// Search tab: route printable keys to search input
|
|
2812
|
+
if (isSearchTab) {
|
|
2813
|
+
// Escape: clear search
|
|
2814
|
+
if (key === '\x1b' && key.length === 1) {
|
|
2815
|
+
searchQuery = '';
|
|
2816
|
+
searchResults = null;
|
|
2817
|
+
searchError = null;
|
|
2818
|
+
scrollOffset = 0;
|
|
2819
|
+
render();
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
// Enter: trigger search
|
|
2824
|
+
if (key === '\r' || key === '\n') {
|
|
2825
|
+
doSearch();
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
// Backspace / Delete
|
|
2830
|
+
if (key === '\x7f' || key === '\b') {
|
|
2831
|
+
if (searchQuery.length > 0) {
|
|
2832
|
+
searchQuery = searchQuery.slice(0, -1);
|
|
2833
|
+
render();
|
|
2834
|
+
}
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
// Tab: switch to next tab
|
|
2839
|
+
if (key === '\t') {
|
|
2840
|
+
activeTab = (activeTab + 1) % tabs.length;
|
|
2841
|
+
scrollOffset = 0;
|
|
2842
|
+
render();
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// Arrow keys still navigate
|
|
2847
|
+
if (key === '\x1b[C') { activeTab = (activeTab + 1) % tabs.length; scrollOffset = 0; render(); return; }
|
|
2848
|
+
if (key === '\x1b[D') { activeTab = (activeTab - 1 + tabs.length) % tabs.length; scrollOffset = 0; render(); return; }
|
|
2849
|
+
if (key === '\x1b[A') { scrollOffset = Math.max(0, scrollOffset - 1); render(); return; }
|
|
2850
|
+
if (key === '\x1b[B') { scrollOffset++; render(); return; }
|
|
2851
|
+
|
|
2852
|
+
// q quits only if search buffer is empty
|
|
2853
|
+
if (key === 'q' && searchQuery.length === 0) {
|
|
2854
|
+
cleanup();
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
// Printable ASCII → append to search query
|
|
2859
|
+
if (key.length === 1 && key.charCodeAt(0) >= 32 && key.charCodeAt(0) <= 126) {
|
|
2860
|
+
searchQuery += key;
|
|
2861
|
+
render();
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2442
2866
|
|
|
2443
|
-
//
|
|
2444
|
-
|
|
2867
|
+
// Non-search tabs: normal keypress handling
|
|
2868
|
+
// q = quit
|
|
2869
|
+
if (key === 'q') {
|
|
2445
2870
|
cleanup();
|
|
2446
2871
|
return;
|
|
2447
2872
|
}
|
|
2448
2873
|
|
|
2449
2874
|
// Tab navigation
|
|
2450
|
-
if (key === '\x1b[C' || key === '\t') {
|
|
2875
|
+
if (key === '\x1b[C' || key === '\t') {
|
|
2451
2876
|
activeTab = (activeTab + 1) % tabs.length;
|
|
2452
2877
|
scrollOffset = 0;
|
|
2453
2878
|
render();
|
|
2454
2879
|
return;
|
|
2455
2880
|
}
|
|
2456
|
-
if (key === '\x1b[D') {
|
|
2881
|
+
if (key === '\x1b[D') {
|
|
2457
2882
|
activeTab = (activeTab - 1 + tabs.length) % tabs.length;
|
|
2458
2883
|
scrollOffset = 0;
|
|
2459
2884
|
render();
|
|
2460
2885
|
return;
|
|
2461
2886
|
}
|
|
2462
2887
|
|
|
2463
|
-
// Number keys
|
|
2464
|
-
if (key
|
|
2465
|
-
|
|
2466
|
-
|
|
2888
|
+
// Number keys 1-5
|
|
2889
|
+
if (key >= '1' && key <= '5') {
|
|
2890
|
+
const idx = parseInt(key, 10) - 1;
|
|
2891
|
+
if (idx < tabs.length) { activeTab = idx; scrollOffset = 0; render(); }
|
|
2892
|
+
return;
|
|
2893
|
+
}
|
|
2467
2894
|
|
|
2468
2895
|
// 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; }
|
|
2896
|
+
if (key === '\x1b[A' || key === 'k') { scrollOffset = Math.max(0, scrollOffset - 1); render(); return; }
|
|
2897
|
+
if (key === '\x1b[B' || key === 'j') { scrollOffset++; render(); return; }
|
|
2898
|
+
if (key === '\x1b[5~') { scrollOffset = Math.max(0, scrollOffset - 10); render(); return; }
|
|
2899
|
+
if (key === '\x1b[6~') { scrollOffset += 10; render(); return; }
|
|
2473
2900
|
}
|
|
2474
2901
|
|
|
2475
2902
|
// Setup input
|
|
@@ -2636,9 +3063,9 @@ async function main() {
|
|
|
2636
3063
|
`Uses alternate screen buffer — your scrollback stays intact.`,
|
|
2637
3064
|
``,
|
|
2638
3065
|
`${c.bold}Navigation:${c.reset}`,
|
|
2639
|
-
`
|
|
2640
|
-
` 1-
|
|
2641
|
-
`
|
|
3066
|
+
` \u2190\u2192 / Tab Switch between tabs`,
|
|
3067
|
+
` 1-5 Jump to tab by number`,
|
|
3068
|
+
` \u2191\u2193 / j/k Scroll content`,
|
|
2642
3069
|
` PgUp/PgDn Scroll fast`,
|
|
2643
3070
|
` q / Ctrl+C Quit`,
|
|
2644
3071
|
``,
|
|
@@ -2646,6 +3073,8 @@ async function main() {
|
|
|
2646
3073
|
` [1] Overview Your profile + registry stats`,
|
|
2647
3074
|
` [2] Leaderboard Top contributors ranking`,
|
|
2648
3075
|
` [3] Benchmark LLM model performance comparison`,
|
|
3076
|
+
` [4] Activity Your recent audits & findings`,
|
|
3077
|
+
` [5] Search Search packages (interactive input)`,
|
|
2649
3078
|
``,
|
|
2650
3079
|
`${c.bold}Aliases:${c.reset} dashboard, dash`,
|
|
2651
3080
|
],
|
|
@@ -2684,6 +3113,41 @@ async function main() {
|
|
|
2684
3113
|
` agentaudit benchmark --json`,
|
|
2685
3114
|
],
|
|
2686
3115
|
bench: null, // alias → benchmark
|
|
3116
|
+
activity: [
|
|
3117
|
+
`${c.bold}agentaudit activity${c.reset} [options]`,
|
|
3118
|
+
``,
|
|
3119
|
+
`Show your recent audits and findings from the AgentAudit registry.`,
|
|
3120
|
+
`Requires being logged in (run ${c.cyan}agentaudit setup${c.reset} first).`,
|
|
3121
|
+
``,
|
|
3122
|
+
`${c.bold}Options:${c.reset}`,
|
|
3123
|
+
` --json Machine-readable JSON output`,
|
|
3124
|
+
``,
|
|
3125
|
+
`${c.bold}Aliases:${c.reset} activity, my`,
|
|
3126
|
+
``,
|
|
3127
|
+
`${c.bold}Examples:${c.reset}`,
|
|
3128
|
+
` agentaudit activity`,
|
|
3129
|
+
` agentaudit activity --json`,
|
|
3130
|
+
` agentaudit my`,
|
|
3131
|
+
],
|
|
3132
|
+
my: null, // alias → activity
|
|
3133
|
+
search: [
|
|
3134
|
+
`${c.bold}agentaudit search${c.reset} <query> [options]`,
|
|
3135
|
+
``,
|
|
3136
|
+
`Search for packages in the AgentAudit registry by name, ASF-ID, or hash.`,
|
|
3137
|
+
``,
|
|
3138
|
+
`${c.bold}Options:${c.reset}`,
|
|
3139
|
+
` --json Machine-readable JSON output`,
|
|
3140
|
+
``,
|
|
3141
|
+
`${c.bold}Aliases:${c.reset} search, find`,
|
|
3142
|
+
``,
|
|
3143
|
+
`${c.bold}Examples:${c.reset}`,
|
|
3144
|
+
` agentaudit search fastmcp`,
|
|
3145
|
+
` agentaudit search mcp-server`,
|
|
3146
|
+
` agentaudit search ASF-2025-0001`,
|
|
3147
|
+
` agentaudit search fastmcp --json`,
|
|
3148
|
+
` agentaudit find fastmcp`,
|
|
3149
|
+
],
|
|
3150
|
+
find: null, // alias → search
|
|
2687
3151
|
};
|
|
2688
3152
|
|
|
2689
3153
|
// Show subcommand help: `agentaudit help <cmd>` or `agentaudit <cmd> --help`
|
|
@@ -2691,7 +3155,7 @@ async function main() {
|
|
|
2691
3155
|
let helpLines = subcommandHelp[cmd];
|
|
2692
3156
|
if (helpLines === null) {
|
|
2693
3157
|
// alias redirect
|
|
2694
|
-
const aliases = { check: 'lookup', dash: 'dashboard', lb: 'leaderboard', bench: 'benchmark' };
|
|
3158
|
+
const aliases = { check: 'lookup', dash: 'dashboard', lb: 'leaderboard', bench: 'benchmark', my: 'activity', find: 'search' };
|
|
2695
3159
|
helpLines = subcommandHelp[aliases[cmd] || cmd];
|
|
2696
3160
|
}
|
|
2697
3161
|
if (!helpLines) {
|
|
@@ -2739,6 +3203,8 @@ async function main() {
|
|
|
2739
3203
|
console.log(` ${c.cyan}dashboard${c.reset} Interactive dashboard (full-screen)`);
|
|
2740
3204
|
console.log(` ${c.cyan}leaderboard${c.reset} Top contributors ranking`);
|
|
2741
3205
|
console.log(` ${c.cyan}benchmark${c.reset} LLM model performance comparison`);
|
|
3206
|
+
console.log(` ${c.cyan}activity${c.reset} Your recent audits & findings`);
|
|
3207
|
+
console.log(` ${c.cyan}search${c.reset} <query> Search packages in registry`);
|
|
2742
3208
|
console.log();
|
|
2743
3209
|
console.log(` ${c.bold}CONFIGURATION${c.reset}`);
|
|
2744
3210
|
console.log(` ${c.cyan}model${c.reset} Configure LLM provider + model`);
|
|
@@ -2784,6 +3250,14 @@ async function main() {
|
|
|
2784
3250
|
await benchmarkCommand(targets);
|
|
2785
3251
|
return;
|
|
2786
3252
|
}
|
|
3253
|
+
if (command === 'activity' || command === 'my') {
|
|
3254
|
+
await activityCommand(targets);
|
|
3255
|
+
return;
|
|
3256
|
+
}
|
|
3257
|
+
if (command === 'search' || command === 'find') {
|
|
3258
|
+
await searchCommand(targets);
|
|
3259
|
+
return;
|
|
3260
|
+
}
|
|
2787
3261
|
|
|
2788
3262
|
banner();
|
|
2789
3263
|
|
|
@@ -2859,13 +3333,20 @@ async function main() {
|
|
|
2859
3333
|
fetch(`${REGISTRY_URL}/api/leaderboard?limit=100`, { signal: AbortSignal.timeout(10_000) }).then(r => r.ok ? r.json() : null),
|
|
2860
3334
|
]);
|
|
2861
3335
|
if (agentRes) {
|
|
2862
|
-
|
|
2863
|
-
let rank = '-';
|
|
3336
|
+
let rank = null;
|
|
2864
3337
|
if (Array.isArray(lbRes)) {
|
|
2865
3338
|
const idx = lbRes.findIndex(e => e.agent_name === creds.agent_name);
|
|
2866
|
-
if (idx >= 0) rank =
|
|
3339
|
+
if (idx >= 0) rank = idx + 1;
|
|
2867
3340
|
}
|
|
2868
|
-
|
|
3341
|
+
// Update profile cache
|
|
3342
|
+
saveProfileCache({
|
|
3343
|
+
agent_name: creds.agent_name,
|
|
3344
|
+
rank,
|
|
3345
|
+
total_points: agentRes.total_points || 0,
|
|
3346
|
+
total_reports: agentRes.total_reports || 0,
|
|
3347
|
+
});
|
|
3348
|
+
console.log(` ${c.bold}Profile${c.reset}`);
|
|
3349
|
+
console.log(` Rank ${c.bold}${rank ? `#${rank} of ${lbRes.length}` : '-'}${c.reset}`);
|
|
2869
3350
|
console.log(` Points ${c.bold}${fmtNum(agentRes.total_points || 0)}${c.reset}`);
|
|
2870
3351
|
console.log(` Audits ${c.bold}${fmtNum(agentRes.total_reports || 0)}${c.reset}`);
|
|
2871
3352
|
console.log(` Findings ${c.bold}${fmtNum(agentRes.total_findings_submitted || 0)}${c.reset} ${c.dim}(${fmtNum(agentRes.total_findings_confirmed || 0)} confirmed)${c.reset}`);
|