agentaudit 3.13.1 → 3.13.3

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 +89 -12
  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;
@@ -3660,12 +3688,17 @@ async function auditRepo(url) {
3660
3688
  if (modelNames.length === 1) {
3661
3689
  activeLlm = resolveModel(modelNames[0]);
3662
3690
  } else {
3663
- activeLlm = resolveProvider();
3664
3691
  // Model override: --model flag > AGENTAUDIT_MODEL env > credentials.json > provider default
3665
3692
  const modelArgIdx2 = process.argv.indexOf('--model');
3666
3693
  const modelFlag2 = modelArgIdx2 !== -1 ? process.argv[modelArgIdx2 + 1] : null;
3667
3694
  const modelOverride = modelFlag2 || process.env.AGENTAUDIT_MODEL || loadLlmConfig()?.llm_model || null;
3668
- if (activeLlm && modelOverride) activeLlm.model = modelOverride;
3695
+ if (modelOverride) {
3696
+ // Route through resolveModel() so slash-models go to OpenRouter, prefixes to native providers
3697
+ activeLlm = resolveModel(modelOverride);
3698
+ }
3699
+ if (!activeLlm) {
3700
+ activeLlm = resolveProvider();
3701
+ }
3669
3702
  }
3670
3703
 
3671
3704
  if (!activeLlm) {
@@ -4220,7 +4253,10 @@ function renderOverviewTab(data, width) {
4220
4253
  const idx = leaderboard.findIndex(e => e.agent_name === creds.agent_name);
4221
4254
  if (idx >= 0) rank = `#${idx + 1} of ${leaderboard.length}`;
4222
4255
  }
4223
- 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}`);
4256
+ const nameVis = visLen(creds.agent_name);
4257
+ const rankVis = visLen(rank);
4258
+ const nameGap = Math.max(1, halfW - nameVis - rankVis);
4259
+ profileLines.push(`${c.bold}${creds.agent_name}${c.reset}${' '.repeat(nameGap)}${c.dim}${rank}${c.reset}`);
4224
4260
  profileLines.push(`Points ${c.bold}${fmtNum(agent.total_points)}${c.reset}`);
4225
4261
  profileLines.push(`Audits ${c.bold}${fmtNum(agent.total_reports)}${c.reset}`);
4226
4262
  profileLines.push(`Findings ${c.bold}${fmtNum(agent.total_findings_submitted)}${c.reset} ${c.dim}(${fmtNum(agent.total_findings_confirmed)} confirmed)${c.reset}`);
@@ -4255,6 +4291,15 @@ function renderOverviewTab(data, width) {
4255
4291
  regLines.push(`${c.dim}Could not load registry stats${c.reset}`);
4256
4292
  }
4257
4293
 
4294
+ // Local history stats
4295
+ const localHistory = loadHistory(50);
4296
+ const verifiedCount = localHistory.filter(h => h.verification).length;
4297
+ const localStats = {
4298
+ total: localHistory.length,
4299
+ verified: verifiedCount,
4300
+ lastAudit: localHistory[0] ? timeAgo(localHistory[0].timestamp || localHistory[0].date) : null,
4301
+ };
4302
+
4258
4303
  const boxW = halfW + 4;
4259
4304
  const profileBox = drawBox('Your Profile', profileLines, boxW);
4260
4305
  const registryBox = drawBox('Registry', regLines, boxW);
@@ -4262,8 +4307,13 @@ function renderOverviewTab(data, width) {
4262
4307
  // Side by side if wide enough, stacked otherwise
4263
4308
  if (width >= boxW * 2 + 4) {
4264
4309
  const maxLen = Math.max(profileBox.length, registryBox.length);
4265
- while (profileBox.length < maxLen) profileBox.push(` ${BOX.v} ${' '.repeat(halfW + 1)}${BOX.v}`);
4266
- while (registryBox.length < maxLen) registryBox.push(` ${BOX.v} ${' '.repeat(halfW + 1)}${BOX.v}`);
4310
+ // Insert filler lines BEFORE the bottom border (last line), not after
4311
+ while (profileBox.length < maxLen) {
4312
+ profileBox.splice(profileBox.length - 1, 0, ` ${BOX.v} ${' '.repeat(halfW + 1)}${BOX.v}`);
4313
+ }
4314
+ while (registryBox.length < maxLen) {
4315
+ registryBox.splice(registryBox.length - 1, 0, ` ${BOX.v} ${' '.repeat(halfW + 1)}${BOX.v}`);
4316
+ }
4267
4317
  for (let i = 0; i < maxLen; i++) {
4268
4318
  lines.push(profileBox[i] + ' ' + registryBox[i].trimStart());
4269
4319
  }
@@ -4271,6 +4321,24 @@ function renderOverviewTab(data, width) {
4271
4321
  lines.push(...profileBox, '', ...registryBox);
4272
4322
  }
4273
4323
 
4324
+ // Local history section
4325
+ lines.push('');
4326
+ if (localStats.total > 0) {
4327
+ const histParts = [`${c.bold}${localStats.total}${c.reset} local audits`];
4328
+ if (localStats.verified > 0) histParts.push(`${c.green}${localStats.verified} verified${c.reset}`);
4329
+ if (localStats.lastAudit) histParts.push(`${c.dim}last: ${localStats.lastAudit}${c.reset}`);
4330
+ lines.push(` ${c.dim}Local:${c.reset} ${histParts.join(` ${c.dim}│${c.reset} `)}`);
4331
+ }
4332
+
4333
+ // Quick actions
4334
+ lines.push('');
4335
+ lines.push(` ${c.bold}Quick Actions${c.reset}`);
4336
+ lines.push(` ${c.cyan}agentaudit audit <url>${c.reset} ${c.dim}Deep LLM security audit${c.reset}`);
4337
+ lines.push(` ${c.cyan}agentaudit audit <url> --verify${c.reset} ${c.dim}Audit + adversarial verification${c.reset}`);
4338
+ lines.push(` ${c.cyan}agentaudit audit <url> --remote${c.reset} ${c.dim}Server-side scan (no API key)${c.reset}`);
4339
+ lines.push(` ${c.cyan}agentaudit consensus <pkg>${c.reset} ${c.dim}Cross-model consensus view${c.reset}`);
4340
+ lines.push(` ${c.cyan}agentaudit search <query>${c.reset} ${c.dim}Search the registry${c.reset}`);
4341
+
4274
4342
  return lines;
4275
4343
  }
4276
4344
 
@@ -4292,7 +4360,7 @@ function renderLeaderboardTab(data, width, opts = {}) {
4292
4360
  const entry = leaderboard[i];
4293
4361
  const name = (entry.agent_name || '').slice(0, maxNameW);
4294
4362
  const isMe = creds && entry.agent_name === creds.agent_name;
4295
- const prefix = i < 3 ? ` ${medals[i]} ` : ` ${c.dim}#${String(i + 1).padStart(2)}${c.reset} `;
4363
+ const prefix = i < 3 ? ` ${medals[i]} ` : ` ${c.dim}#${String(i + 1).padStart(2)}${c.reset} `;
4296
4364
  const nameStr = isMe ? `${c.green}${c.bold}${name}${c.reset}` : name;
4297
4365
  const bar = renderBar(entry.total_points || 0, maxPts, barW);
4298
4366
  const pts = padLeft(`${fmtNum(entry.total_points || 0)} pts`, 12);
@@ -4724,7 +4792,7 @@ async function leaderboardCommand(args) {
4724
4792
  const entry = data[i];
4725
4793
  const name = (entry.agent_name || '').slice(0, 20);
4726
4794
  const isMe = creds && entry.agent_name === creds.agent_name;
4727
- const prefix = i < 3 ? ` ${medals[i]} ` : ` #${String(i + 1).padStart(2)} `;
4795
+ const prefix = i < 3 ? ` ${medals[i]} ` : ` ${c.dim}#${String(i + 1).padStart(2)}${c.reset} `;
4728
4796
  const nameStr = isMe ? `${c.green}${c.bold}${name}${c.reset}` : name;
4729
4797
  const bar = renderBar(entry.total_points || 0, maxPts, barW);
4730
4798
  const pts = `${fmtNum(entry.total_points || 0)} pts`;
@@ -6250,7 +6318,16 @@ async function main() {
6250
6318
  }
6251
6319
 
6252
6320
  if (command === 'audit') {
6253
- const urls = targets.filter(t => !t.startsWith('--'));
6321
+ // Extract URLs: skip option flags and their values
6322
+ const optionsWithValues = new Set(['--model', '--verify', '--format', '--models']);
6323
+ const urls = [];
6324
+ for (let i = 0; i < targets.length; i++) {
6325
+ if (targets[i].startsWith('--')) {
6326
+ if (optionsWithValues.has(targets[i]) && i + 1 < targets.length) i++; // skip next (value)
6327
+ continue;
6328
+ }
6329
+ urls.push(targets[i]);
6330
+ }
6254
6331
  if (urls.length === 0) {
6255
6332
  console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
6256
6333
  console.log(` ${c.dim}Usage: ${c.cyan}agentaudit audit <url>${c.dim} — e.g. agentaudit audit https://github.com/owner/repo${c.reset}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.13.1",
3
+ "version": "3.13.3",
4
4
  "description": "Security scanner for AI agent packages — CLI + MCP server",
5
5
  "type": "module",
6
6
  "bin": {