agentaudit 3.7.1 → 3.8.0

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 +150 -2
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -93,6 +93,111 @@ function askQuestion(question) {
93
93
  return new Promise(resolve => rl.question(question, answer => { rl.close(); resolve(answer.trim()); }));
94
94
  }
95
95
 
96
+ /**
97
+ * Interactive multi-select in terminal. No dependencies.
98
+ * items: [{ label, sublabel?, value, checked? }]
99
+ * Returns: array of selected values
100
+ */
101
+ function multiSelect(items, { title = 'Select items', hint = 'Space=toggle ↑↓=move a=all n=none Enter=confirm' } = {}) {
102
+ return new Promise((resolve) => {
103
+ if (!process.stdin.isTTY) {
104
+ // Non-interactive: return all items
105
+ resolve(items.map(i => i.value));
106
+ return;
107
+ }
108
+
109
+ const selected = new Set(items.filter(i => i.checked).map((_, idx) => idx));
110
+ let cursor = 0;
111
+
112
+ const render = () => {
113
+ // Move cursor up to overwrite previous render
114
+ process.stdout.write(`\x1b[${items.length + 3}A\x1b[J`);
115
+ draw();
116
+ };
117
+
118
+ const draw = () => {
119
+ console.log(` ${c.bold}${title}${c.reset} ${c.dim}(${selected.size}/${items.length} selected)${c.reset}`);
120
+ console.log(` ${c.dim}${hint}${c.reset}`);
121
+ console.log();
122
+ for (let i = 0; i < items.length; i++) {
123
+ const item = items[i];
124
+ const isCursor = i === cursor;
125
+ const isSelected = selected.has(i);
126
+ const pointer = isCursor ? `${c.cyan}❯${c.reset}` : ' ';
127
+ const checkbox = isSelected ? `${c.green}◉${c.reset}` : `${c.dim}○${c.reset}`;
128
+ const label = isCursor ? `${c.bold}${item.label}${c.reset}` : item.label;
129
+ const sub = item.sublabel ? ` ${c.dim}${item.sublabel}${c.reset}` : '';
130
+ console.log(` ${pointer} ${checkbox} ${label}${sub}`);
131
+ }
132
+ };
133
+
134
+ // Initial draw
135
+ draw();
136
+
137
+ process.stdin.setRawMode(true);
138
+ process.stdin.resume();
139
+ process.stdin.setEncoding('utf8');
140
+
141
+ const onData = (key) => {
142
+ // Ctrl+C
143
+ if (key === '\x03') {
144
+ process.stdin.setRawMode(false);
145
+ process.stdin.pause();
146
+ process.stdin.removeListener('data', onData);
147
+ console.log();
148
+ process.exit(0);
149
+ }
150
+
151
+ // Enter
152
+ if (key === '\r' || key === '\n') {
153
+ process.stdin.setRawMode(false);
154
+ process.stdin.pause();
155
+ process.stdin.removeListener('data', onData);
156
+ resolve(items.filter((_, i) => selected.has(i)).map(i => i.value));
157
+ return;
158
+ }
159
+
160
+ // Space — toggle
161
+ if (key === ' ') {
162
+ if (selected.has(cursor)) selected.delete(cursor);
163
+ else selected.add(cursor);
164
+ render();
165
+ return;
166
+ }
167
+
168
+ // a — select all
169
+ if (key === 'a') {
170
+ for (let i = 0; i < items.length; i++) selected.add(i);
171
+ render();
172
+ return;
173
+ }
174
+
175
+ // n — select none
176
+ if (key === 'n') {
177
+ selected.clear();
178
+ render();
179
+ return;
180
+ }
181
+
182
+ // Arrow up / k
183
+ if (key === '\x1b[A' || key === 'k') {
184
+ cursor = (cursor - 1 + items.length) % items.length;
185
+ render();
186
+ return;
187
+ }
188
+
189
+ // Arrow down / j
190
+ if (key === '\x1b[B' || key === 'j') {
191
+ cursor = (cursor + 1) % items.length;
192
+ render();
193
+ return;
194
+ }
195
+ };
196
+
197
+ process.stdin.on('data', onData);
198
+ });
199
+ }
200
+
96
201
  async function registerAgent(agentName) {
97
202
  const res = await fetch(`${REGISTRY_URL}/api/register`, {
98
203
  method: 'POST',
@@ -834,6 +939,7 @@ async function resolveSourceUrl(server) {
834
939
 
835
940
  async function discoverCommand(options = {}) {
836
941
  const autoScan = options.scan || false;
942
+ const interactiveAudit = options.audit || false;
837
943
 
838
944
  console.log(` ${c.bold}Discovering local MCP servers...${c.reset}`);
839
945
  console.log();
@@ -1001,6 +1107,45 @@ async function discoverCommand(options = {}) {
1001
1107
  console.log(` ${c.dim}No scannable source URLs found.${c.reset}`);
1002
1108
  console.log();
1003
1109
  }
1110
+ } else if (interactiveAudit && allServersWithUrls.length > 0) {
1111
+ // Interactive multi-select for audit
1112
+ const isCloneable = (url) => /^https?:\/\/(github\.com|gitlab\.com|bitbucket\.org)\//i.test(url);
1113
+ const auditCandidates = [];
1114
+ const seen = new Set();
1115
+ for (const s of allServersWithUrls) {
1116
+ if (!s.sourceUrl || !isCloneable(s.sourceUrl)) continue;
1117
+ if (seen.has(s.sourceUrl)) continue;
1118
+ seen.add(s.sourceUrl);
1119
+ auditCandidates.push(s);
1120
+ }
1121
+
1122
+ if (auditCandidates.length > 0) {
1123
+ console.log();
1124
+ const items = auditCandidates.map(s => ({
1125
+ label: s.name,
1126
+ sublabel: s.hasAudit ? `${c.green}✔ audited${c.reset} ${s.sourceUrl}` : s.sourceUrl,
1127
+ value: s,
1128
+ checked: !s.hasAudit, // Pre-select unaudited
1129
+ }));
1130
+
1131
+ const selected = await multiSelect(items, {
1132
+ title: 'Select servers to audit',
1133
+ hint: 'Space=toggle ↑↓=move a=all n=none Enter=confirm',
1134
+ });
1135
+
1136
+ if (selected.length > 0) {
1137
+ console.log();
1138
+ console.log(` ${c.bold}Auditing ${selected.length} server${selected.length !== 1 ? 's' : ''}...${c.reset}`);
1139
+ console.log();
1140
+ for (const s of selected) {
1141
+ await auditRepo(s.sourceUrl);
1142
+ console.log();
1143
+ }
1144
+ } else {
1145
+ console.log();
1146
+ console.log(` ${c.dim}No servers selected.${c.reset}`);
1147
+ }
1148
+ }
1004
1149
  } else if (unauditedServers > 0) {
1005
1150
  if (unauditedWithUrls.length > 0) {
1006
1151
  console.log(` ${c.dim}To audit unaudited servers:${c.reset}`);
@@ -1012,7 +1157,8 @@ async function discoverCommand(options = {}) {
1012
1157
  console.log(` ${c.cyan}agentaudit audit <source-url>${c.reset}`);
1013
1158
  }
1014
1159
  console.log();
1015
- console.log(` ${c.dim}Or run ${c.cyan}agentaudit discover --scan${c.dim} to auto-scan all servers${c.reset}`);
1160
+ console.log(` ${c.dim}Or run ${c.cyan}agentaudit discover --scan${c.dim} to quick-scan all servers${c.reset}`);
1161
+ console.log(` ${c.dim}Or run ${c.cyan}agentaudit discover --audit${c.dim} to select & deep-audit interactively${c.reset}`);
1016
1162
  console.log();
1017
1163
  }
1018
1164
  }
@@ -1305,6 +1451,7 @@ async function main() {
1305
1451
  console.log();
1306
1452
  console.log(` ${c.cyan}agentaudit discover${c.reset} Find local MCP servers + check registry`);
1307
1453
  console.log(` ${c.cyan}agentaudit discover --scan${c.reset} Discover + auto-scan all servers`);
1454
+ console.log(` ${c.cyan}agentaudit discover --audit${c.reset} Discover + select servers to deep-audit`);
1308
1455
  console.log(` ${c.cyan}agentaudit scan${c.reset} <url> [url...] Quick static scan (regex, local)`);
1309
1456
  console.log(` ${c.cyan}agentaudit audit${c.reset} <url> [url...] Deep LLM-powered security audit`);
1310
1457
  console.log(` ${c.cyan}agentaudit check${c.reset} <name> Look up package in registry`);
@@ -1349,7 +1496,8 @@ async function main() {
1349
1496
 
1350
1497
  if (command === 'discover') {
1351
1498
  const scanFlag = targets.includes('--scan') || targets.includes('-s');
1352
- await discoverCommand({ scan: scanFlag });
1499
+ const auditFlag = targets.includes('--audit') || targets.includes('-a');
1500
+ await discoverCommand({ scan: scanFlag, audit: auditFlag });
1353
1501
  return;
1354
1502
  }
1355
1503
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.7.1",
3
+ "version": "3.8.0",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {