agentaudit 3.13.0 → 3.13.2

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 (2) hide show
  1. package/cli.mjs +111 -9
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -852,17 +852,45 @@ function padLeft(str, len) {
852
852
  return diff > 0 ? ' '.repeat(diff) + str : str;
853
853
  }
854
854
 
855
+ // Truncate a string with ANSI codes to maxLen visible characters
856
+ function truncateAnsi(str, maxLen) {
857
+ if (maxLen <= 0) return '';
858
+ let vis = 0;
859
+ let result = '';
860
+ let i = 0;
861
+ while (i < str.length) {
862
+ if (str[i] === '\x1b') {
863
+ const m = str.slice(i).match(/^\x1b\[[0-9;]*[a-zA-Z]/);
864
+ if (m) { result += m[0]; i += m[0].length; continue; }
865
+ }
866
+ if (vis >= maxLen) break;
867
+ result += str[i];
868
+ vis++;
869
+ i++;
870
+ }
871
+ return result + c.reset;
872
+ }
873
+
855
874
  function drawBox(title, contentLines, width) {
856
875
  const inner = width - 4; // 2 for "│ " + 2 for " │"
857
876
  const totalDash = inner + 2; // total horizontal line chars between corners
858
877
  const lines = [];
859
- const titleStr = title ? ` ${title} ` : '';
860
- const titleLen = visLen(titleStr);
878
+ let titleStr = title ? ` ${title} ` : '';
879
+ let titleLen = visLen(titleStr);
880
+ // Clamp title if wider than available border space
881
+ if (titleLen >= totalDash - 1) {
882
+ const maxTitle = Math.max(1, totalDash - 4);
883
+ titleStr = ` ${title.slice(0, maxTitle)}… `;
884
+ titleLen = visLen(titleStr);
885
+ }
861
886
  // Top: ╭─ Title ────────────╮ (1 dash before title + title + remaining dashes)
862
- const topDash = BOX.h.repeat(Math.max(1, totalDash - 1 - titleLen));
887
+ const topDash = BOX.h.repeat(Math.max(0, totalDash - 1 - titleLen));
863
888
  lines.push(` ${BOX.tl}${c.dim}${BOX.h}${c.reset}${c.bold}${titleStr}${c.reset}${c.dim}${topDash}${c.reset}${BOX.tr}`);
864
889
  for (const line of contentLines) {
865
- lines.push(` ${BOX.v} ${padRight(line, inner + 1)}${BOX.v}`);
890
+ // Truncate content that exceeds box inner width
891
+ const vl = visLen(line);
892
+ const display = vl > inner ? truncateAnsi(line, inner - 1) + '…' : line;
893
+ lines.push(` ${BOX.v} ${padRight(display, inner + 1)}${BOX.v}`);
866
894
  }
867
895
  lines.push(` ${BOX.bl}${c.dim}${BOX.h.repeat(totalDash)}${c.reset}${BOX.br}`);
868
896
  return lines;
@@ -4152,6 +4180,7 @@ async function checkPackage(name) {
4152
4180
  if (!jsonMode) {
4153
4181
  console.log(` ${c.yellow}Not found${c.reset} — package "${name}" hasn't been audited yet.`);
4154
4182
  console.log(` ${c.dim}Run: agentaudit audit <repo-url> for a deep LLM audit${c.reset}`);
4183
+ await suggestSimilarPackages(name);
4155
4184
  }
4156
4185
  return null;
4157
4186
  }
@@ -4219,7 +4248,10 @@ function renderOverviewTab(data, width) {
4219
4248
  const idx = leaderboard.findIndex(e => e.agent_name === creds.agent_name);
4220
4249
  if (idx >= 0) rank = `#${idx + 1} of ${leaderboard.length}`;
4221
4250
  }
4222
- profileLines.push(`${c.bold}${creds.agent_name}${c.reset}${' '.repeat(Math.max(1, halfW - 14 - visLen(creds.agent_name) - visLen(rank)))}${c.dim}${rank}${c.reset}`);
4251
+ const nameVis = visLen(creds.agent_name);
4252
+ const rankVis = visLen(rank);
4253
+ const nameGap = Math.max(1, halfW - nameVis - rankVis);
4254
+ profileLines.push(`${c.bold}${creds.agent_name}${c.reset}${' '.repeat(nameGap)}${c.dim}${rank}${c.reset}`);
4223
4255
  profileLines.push(`Points ${c.bold}${fmtNum(agent.total_points)}${c.reset}`);
4224
4256
  profileLines.push(`Audits ${c.bold}${fmtNum(agent.total_reports)}${c.reset}`);
4225
4257
  profileLines.push(`Findings ${c.bold}${fmtNum(agent.total_findings_submitted)}${c.reset} ${c.dim}(${fmtNum(agent.total_findings_confirmed)} confirmed)${c.reset}`);
@@ -4254,6 +4286,15 @@ function renderOverviewTab(data, width) {
4254
4286
  regLines.push(`${c.dim}Could not load registry stats${c.reset}`);
4255
4287
  }
4256
4288
 
4289
+ // Local history stats
4290
+ const localHistory = loadHistory(50);
4291
+ const verifiedCount = localHistory.filter(h => h.verification).length;
4292
+ const localStats = {
4293
+ total: localHistory.length,
4294
+ verified: verifiedCount,
4295
+ lastAudit: localHistory[0] ? timeAgo(localHistory[0].timestamp || localHistory[0].date) : null,
4296
+ };
4297
+
4257
4298
  const boxW = halfW + 4;
4258
4299
  const profileBox = drawBox('Your Profile', profileLines, boxW);
4259
4300
  const registryBox = drawBox('Registry', regLines, boxW);
@@ -4261,8 +4302,13 @@ function renderOverviewTab(data, width) {
4261
4302
  // Side by side if wide enough, stacked otherwise
4262
4303
  if (width >= boxW * 2 + 4) {
4263
4304
  const maxLen = Math.max(profileBox.length, registryBox.length);
4264
- while (profileBox.length < maxLen) profileBox.push(` ${BOX.v} ${' '.repeat(halfW + 1)}${BOX.v}`);
4265
- while (registryBox.length < maxLen) registryBox.push(` ${BOX.v} ${' '.repeat(halfW + 1)}${BOX.v}`);
4305
+ // Insert filler lines BEFORE the bottom border (last line), not after
4306
+ while (profileBox.length < maxLen) {
4307
+ profileBox.splice(profileBox.length - 1, 0, ` ${BOX.v} ${' '.repeat(halfW + 1)}${BOX.v}`);
4308
+ }
4309
+ while (registryBox.length < maxLen) {
4310
+ registryBox.splice(registryBox.length - 1, 0, ` ${BOX.v} ${' '.repeat(halfW + 1)}${BOX.v}`);
4311
+ }
4266
4312
  for (let i = 0; i < maxLen; i++) {
4267
4313
  lines.push(profileBox[i] + ' ' + registryBox[i].trimStart());
4268
4314
  }
@@ -4270,6 +4316,24 @@ function renderOverviewTab(data, width) {
4270
4316
  lines.push(...profileBox, '', ...registryBox);
4271
4317
  }
4272
4318
 
4319
+ // Local history section
4320
+ lines.push('');
4321
+ if (localStats.total > 0) {
4322
+ const histParts = [`${c.bold}${localStats.total}${c.reset} local audits`];
4323
+ if (localStats.verified > 0) histParts.push(`${c.green}${localStats.verified} verified${c.reset}`);
4324
+ if (localStats.lastAudit) histParts.push(`${c.dim}last: ${localStats.lastAudit}${c.reset}`);
4325
+ lines.push(` ${c.dim}Local:${c.reset} ${histParts.join(` ${c.dim}│${c.reset} `)}`);
4326
+ }
4327
+
4328
+ // Quick actions
4329
+ lines.push('');
4330
+ lines.push(` ${c.bold}Quick Actions${c.reset}`);
4331
+ lines.push(` ${c.cyan}agentaudit audit <url>${c.reset} ${c.dim}Deep LLM security audit${c.reset}`);
4332
+ lines.push(` ${c.cyan}agentaudit audit <url> --verify${c.reset} ${c.dim}Audit + adversarial verification${c.reset}`);
4333
+ lines.push(` ${c.cyan}agentaudit audit <url> --remote${c.reset} ${c.dim}Server-side scan (no API key)${c.reset}`);
4334
+ lines.push(` ${c.cyan}agentaudit consensus <pkg>${c.reset} ${c.dim}Cross-model consensus view${c.reset}`);
4335
+ lines.push(` ${c.cyan}agentaudit search <query>${c.reset} ${c.dim}Search the registry${c.reset}`);
4336
+
4273
4337
  return lines;
4274
4338
  }
4275
4339
 
@@ -4291,7 +4355,7 @@ function renderLeaderboardTab(data, width, opts = {}) {
4291
4355
  const entry = leaderboard[i];
4292
4356
  const name = (entry.agent_name || '').slice(0, maxNameW);
4293
4357
  const isMe = creds && entry.agent_name === creds.agent_name;
4294
- const prefix = i < 3 ? ` ${medals[i]} ` : ` ${c.dim}#${String(i + 1).padStart(2)}${c.reset} `;
4358
+ const prefix = i < 3 ? ` ${medals[i]} ` : ` ${c.dim}#${String(i + 1).padStart(2)}${c.reset} `;
4295
4359
  const nameStr = isMe ? `${c.green}${c.bold}${name}${c.reset}` : name;
4296
4360
  const bar = renderBar(entry.total_points || 0, maxPts, barW);
4297
4361
  const pts = padLeft(`${fmtNum(entry.total_points || 0)} pts`, 12);
@@ -4592,6 +4656,31 @@ function renderSearchTab(searchState, width) {
4592
4656
  return lines;
4593
4657
  }
4594
4658
 
4659
+ async function suggestSimilarPackages(slug) {
4660
+ if (jsonMode || quietMode) return;
4661
+ try {
4662
+ const res = await fetch(`${REGISTRY_URL}/api/lookup?hash=${encodeURIComponent(slug)}`, {
4663
+ signal: AbortSignal.timeout(5_000),
4664
+ });
4665
+ if (!res.ok) return;
4666
+ const data = await res.json();
4667
+ // API returns { reports: [...], findings: [...], total_matches }
4668
+ const reports = data.reports || [];
4669
+ if (reports.length === 0) return;
4670
+ console.log();
4671
+ console.log(` ${c.dim}Did you mean one of these?${c.reset}`);
4672
+ const shown = reports.slice(0, 5);
4673
+ for (const p of shown) {
4674
+ const name = p.skill_slug || p.slug || '?';
4675
+ const risk = p.risk_score ?? 0;
4676
+ const badge = risk === 0 ? `${c.green}safe${c.reset}` : risk <= 25 ? `${c.green}score ${100 - risk}${c.reset}` : risk <= 50 ? `${c.yellow}score ${100 - risk}${c.reset}` : `${c.red}score ${100 - risk}${c.reset}`;
4677
+ console.log(` ${c.cyan}${name}${c.reset} ${badge}`);
4678
+ }
4679
+ if (data.total_matches > 5) console.log(` ${c.dim}...and ${data.total_matches - 5} more${c.reset}`);
4680
+ console.log(` ${c.dim}Use: ${c.cyan}agentaudit search <query>${c.dim} to find packages${c.reset}`);
4681
+ } catch { /* ignore */ }
4682
+ }
4683
+
4595
4684
  async function searchCommand(args) {
4596
4685
  const query = args.filter(a => !a.startsWith('--')).join(' ').trim();
4597
4686
 
@@ -4698,7 +4787,7 @@ async function leaderboardCommand(args) {
4698
4787
  const entry = data[i];
4699
4788
  const name = (entry.agent_name || '').slice(0, 20);
4700
4789
  const isMe = creds && entry.agent_name === creds.agent_name;
4701
- const prefix = i < 3 ? ` ${medals[i]} ` : ` #${String(i + 1).padStart(2)} `;
4790
+ const prefix = i < 3 ? ` ${medals[i]} ` : ` ${c.dim}#${String(i + 1).padStart(2)}${c.reset} `;
4702
4791
  const nameStr = isMe ? `${c.green}${c.bold}${name}${c.reset}` : name;
4703
4792
  const bar = renderBar(entry.total_points || 0, maxPts, barW);
4704
4793
  const pts = `${fmtNum(entry.total_points || 0)} pts`;
@@ -5564,9 +5653,22 @@ async function main() {
5564
5653
  } else {
5565
5654
  console.log(` ${c.red}API error (HTTP ${res.status})${c.reset}`);
5566
5655
  }
5656
+ // Suggest similar packages via search
5657
+ await suggestSimilarPackages(slug);
5567
5658
  return;
5568
5659
  }
5569
5660
  const data = await res.json();
5661
+
5662
+ // Check if package actually has any reports
5663
+ if ((!data.total_reports && data.total_reports !== undefined) || (data.total_reports === 0 && (!data.findings || data.findings.length === 0))) {
5664
+ if (jsonMode) { console.log(JSON.stringify(data, null, 2)); return; }
5665
+ console.log(` ${c.yellow}No reports found${c.reset} — "${slug}" hasn't been audited yet.`);
5666
+ console.log(` ${c.dim}Run: ${c.cyan}agentaudit audit <repo-url>${c.dim} to create the first audit${c.reset}`);
5667
+ // Suggest similar packages
5668
+ await suggestSimilarPackages(slug);
5669
+ return;
5670
+ }
5671
+
5570
5672
  if (jsonMode) { console.log(JSON.stringify(data, null, 2)); return; }
5571
5673
 
5572
5674
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.13.0",
3
+ "version": "3.13.2",
4
4
  "description": "Security scanner for AI agent packages — CLI + MCP server",
5
5
  "type": "module",
6
6
  "bin": {