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.
Files changed (3) hide show
  1. package/README.md +99 -54
  2. package/cli.mjs +525 -34
  3. 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.9.8
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
- | `agentaudit check <name\|url>` | Lookup + auto-audit if not found | `agentaudit check https://github.com/owner/repo` |
211
- | `agentaudit status` | Check API keys + active LLM provider | `agentaudit status` |
212
- | `agentaudit setup` | Register agent + configure API key | `agentaudit setup` |
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 (show findings only) |
238
+ | `--quiet` / `-q` | Suppress banner and decorative output |
220
239
  | `--no-color` | Disable ANSI colors (also respects `NO_COLOR` env var) |
221
- | `--provider <name>` | Force LLM provider (`anthropic`, `openai`, `openrouter`, `ollama`, `custom`) |
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
- ### Environment Variables
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 access |
438
- | `ANTHROPIC_API_KEY` | Anthropic API key for deep audits (Claude) -- recommended |
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:** Anthropic > OpenAI > OpenRouter > Custom > Ollama. Override with `--provider=ollama` etc.
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 **any LLM provider**. Set one of these environment variables:
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
- export OLLAMA_MODEL=llama3.1 # or qwen2.5-coder, deepseek-r1, etc.
509
- agentaudit audit https://github.com/owner/repo
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
- **Any OpenAI-compatible API:**
559
+ **Interactive setup:**
514
560
  ```bash
515
- export LLM_API_URL=http://localhost:1234/v1 # LM Studio, vLLM, etc.
516
- export LLM_MODEL=my-model
517
- agentaudit audit https://github.com/owner/repo
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
- **Check your setup:**
565
+ **Override per-run:**
521
566
  ```bash
522
- agentaudit status # validates all configured API keys
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
- console.log(` ${c.bold}${c.cyan}AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset}`);
531
- console.log(` ${c.dim}Security scanner for AI packages${c.reset}`);
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": [{ "id": "...", "title": "...", "severity": "...", "category": "...",`,
1765
- ` "description": "...", "file": "...", "line": <n>, "remediation": "...", "confidence": "...", "is_by_design": false }] }`,
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 + (isMe ? 14 : 0))} ${bar} ${pts} ${audits}${extra}`);
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 = 26;
2208
- const hdr = ` ${padRight(`${c.bold}Model${c.reset}`, nameW + 9)} ${padRight('Audits', 8)} ${padRight('Risk', 8)} ${padRight('Detection', 16)} Severity`;
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, 80))}${c.reset}`);
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), 6);
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
- const dots = severityDots(m.severity_breakdown);
2220
- lines.push(` ${padRight(name, nameW)} ${audits} ${risk} ${detection} ${dots}`);
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 + (isMe ? 14 : 0))} ${bar} ${padLeft(pts, 12)} ${padLeft(audits, 10)}`);
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}──${c.reset}`;
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
- output += ` ${c.dim}←→ tab ↑↓ scroll 1-3 jump q quit${c.reset}${scrollInfo}\n`;
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
- // q or Ctrl+C = quit
2444
- if (key === 'q' || key === '\x03') {
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') { // Right arrow or Tab
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') { // Left arrow
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 === '1') { activeTab = 0; scrollOffset = 0; render(); return; }
2465
- if (key === '2') { activeTab = 1; scrollOffset = 0; render(); return; }
2466
- if (key === '3') { activeTab = 2; scrollOffset = 0; render(); return; }
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; } // Up
2470
- if (key === '\x1b[B' || key === 'j') { scrollOffset++; render(); return; } // Down
2471
- if (key === '\x1b[5~') { scrollOffset = Math.max(0, scrollOffset - 10); render(); return; } // PageUp
2472
- if (key === '\x1b[6~') { scrollOffset += 10; render(); return; } // PageDown
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
- ` ←→ / Tab Switch between tabs`,
2640
- ` 1-3 Jump to tab by number`,
2641
- ` ↑↓ / j/k Scroll content`,
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
- console.log(` ${c.bold}Profile${c.reset}`);
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 = `#${idx + 1} of ${lbRes.length}`;
3349
+ if (idx >= 0) rank = idx + 1;
2867
3350
  }
2868
- console.log(` Rank ${c.bold}${rank}${c.reset}`);
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}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.10.3",
3
+ "version": "3.10.5",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {