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.
- package/cli.mjs +150 -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
|
|
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
|
-
|
|
1499
|
+
const auditFlag = targets.includes('--audit') || targets.includes('-a');
|
|
1500
|
+
await discoverCommand({ scan: scanFlag, audit: auditFlag });
|
|
1353
1501
|
return;
|
|
1354
1502
|
}
|
|
1355
1503
|
|