@agent-wall/cli 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1074 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/wrap.ts
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import { createRequire } from "module";
10
+ import {
11
+ StdioProxy,
12
+ PolicyEngine,
13
+ AuditLogger,
14
+ ResponseScanner,
15
+ InjectionDetector,
16
+ EgressControl,
17
+ KillSwitch,
18
+ ChainDetector,
19
+ DashboardServer,
20
+ loadPolicy,
21
+ createTerminalPromptHandler
22
+ } from "@agent-wall/core";
23
+ import chalk from "chalk";
24
+ async function wrapCommand(serverArgs, options) {
25
+ if (serverArgs.length === 0) {
26
+ process.stderr.write(
27
+ chalk.red(
28
+ "Error: No server command specified.\nUsage: agent-wall wrap -- <command> [args...]\n\nExample:\n agent-wall wrap -- npx @modelcontextprotocol/server-filesystem /home/user\n"
29
+ )
30
+ );
31
+ process.exit(1);
32
+ }
33
+ const command = serverArgs[0];
34
+ const args = serverArgs.slice(1);
35
+ const { config, filePath } = loadPolicy(options.config);
36
+ if (!options.silent) {
37
+ process.stderr.write(
38
+ chalk.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n")
39
+ );
40
+ process.stderr.write(
41
+ chalk.cyan("\u2551 ") + chalk.bold.white("Agent Wall") + chalk.cyan(" \u2014 Security Firewall for AI Agents \u2551\n")
42
+ );
43
+ process.stderr.write(
44
+ chalk.cyan("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n")
45
+ );
46
+ process.stderr.write(
47
+ chalk.cyan("\u2551 ") + chalk.gray("Policy: ") + chalk.white(filePath ?? "built-in defaults") + "\n"
48
+ );
49
+ process.stderr.write(
50
+ chalk.cyan("\u2551 ") + chalk.gray("Rules: ") + chalk.white(`${config.rules.length} loaded`) + "\n"
51
+ );
52
+ const rspScan = config.responseScanning;
53
+ if (rspScan?.enabled !== false) {
54
+ process.stderr.write(
55
+ chalk.cyan("\u2551 ") + chalk.gray("Scanner:") + chalk.white(" response scanning ON") + (rspScan?.detectPII ? chalk.yellow(" +PII") : "") + "\n"
56
+ );
57
+ }
58
+ process.stderr.write(
59
+ chalk.cyan("\u2551 ") + chalk.gray("Server: ") + chalk.white(`${command} ${args.join(" ")}`) + "\n"
60
+ );
61
+ process.stderr.write(
62
+ chalk.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n\n")
63
+ );
64
+ }
65
+ if (options.dryRun) {
66
+ process.stderr.write(
67
+ chalk.yellow(" --dry-run mode: preview only, server will not start\n\n")
68
+ );
69
+ process.stderr.write(
70
+ chalk.gray(" Default action: ") + chalk.white(config.defaultAction) + "\n"
71
+ );
72
+ if (config.globalRateLimit) {
73
+ process.stderr.write(
74
+ chalk.gray(" Rate limit: ") + chalk.white(
75
+ `${config.globalRateLimit.maxCalls} calls / ${config.globalRateLimit.windowSeconds}s`
76
+ ) + "\n"
77
+ );
78
+ }
79
+ process.stderr.write(
80
+ chalk.gray(" Rules loaded: ") + chalk.white(String(config.rules.length)) + "\n\n"
81
+ );
82
+ for (let i = 0; i < config.rules.length; i++) {
83
+ const rule = config.rules[i];
84
+ const icon = rule.action === "deny" ? chalk.red("\u2717") : rule.action === "allow" ? chalk.green("\u2713") : chalk.yellow("?");
85
+ process.stderr.write(
86
+ ` ${icon} ${chalk.bold(rule.name ?? `rule[${i}]`)}` + chalk.gray(` \u2192 ${rule.action}`) + chalk.gray(` (tool: ${rule.tool})`) + "\n"
87
+ );
88
+ }
89
+ process.stderr.write("\n");
90
+ process.exit(0);
91
+ }
92
+ const policyEngine = new PolicyEngine(config);
93
+ const securityConfig = config.security;
94
+ const logger = new AuditLogger({
95
+ stdout: !options.silent,
96
+ filePath: options.logFile,
97
+ redact: true,
98
+ signing: securityConfig?.signing ?? false,
99
+ signingKey: securityConfig?.signingKey
100
+ });
101
+ const responseScanner = config.responseScanning?.enabled !== false ? new ResponseScanner({
102
+ enabled: true,
103
+ maxResponseSize: config.responseScanning?.maxResponseSize,
104
+ oversizeAction: config.responseScanning?.oversizeAction,
105
+ detectSecrets: config.responseScanning?.detectSecrets ?? true,
106
+ detectPII: config.responseScanning?.detectPII ?? false,
107
+ base64Action: config.responseScanning?.base64Action,
108
+ maxPatterns: config.responseScanning?.maxPatterns,
109
+ patterns: config.responseScanning?.patterns
110
+ }) : void 0;
111
+ const injectionDetector = new InjectionDetector(securityConfig?.injectionDetection);
112
+ const egressControl = new EgressControl(securityConfig?.egressControl);
113
+ const killSwitch = new KillSwitch({
114
+ ...securityConfig?.killSwitch,
115
+ registerSignal: true
116
+ });
117
+ const chainDetector = new ChainDetector(securityConfig?.chainDetection);
118
+ if (!options.silent) {
119
+ const modules = [];
120
+ if (securityConfig?.injectionDetection?.enabled !== false) modules.push("injection");
121
+ if (securityConfig?.egressControl?.enabled !== false) modules.push("egress");
122
+ if (securityConfig?.killSwitch?.enabled !== false) modules.push("kill-switch");
123
+ if (securityConfig?.chainDetection?.enabled !== false) modules.push("chain");
124
+ if (securityConfig?.signing) modules.push("signing");
125
+ if (modules.length > 0) {
126
+ process.stderr.write(
127
+ chalk.cyan("\u2551 ") + chalk.gray("Security:") + chalk.white(` ${modules.join(", ")}`) + "\n"
128
+ );
129
+ }
130
+ }
131
+ const proxy = new StdioProxy({
132
+ command,
133
+ args,
134
+ policyEngine,
135
+ responseScanner,
136
+ logger,
137
+ injectionDetector,
138
+ egressControl,
139
+ killSwitch,
140
+ chainDetector,
141
+ onPrompt: createTerminalPromptHandler(),
142
+ onReady: () => {
143
+ if (!options.silent) {
144
+ process.stderr.write(
145
+ chalk.green("\u2713 ") + chalk.gray("MCP server started. Agent Wall is protecting.\n\n")
146
+ );
147
+ }
148
+ },
149
+ onExit: (code) => {
150
+ if (!options.silent) {
151
+ const stats = proxy.getStats();
152
+ process.stderr.write("\n");
153
+ process.stderr.write(
154
+ chalk.cyan("\u2500\u2500\u2500 Agent Wall Session Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n")
155
+ );
156
+ process.stderr.write(
157
+ chalk.gray(" Total calls: ") + chalk.white(String(stats.total)) + "\n"
158
+ );
159
+ process.stderr.write(
160
+ chalk.gray(" Forwarded: ") + chalk.green(String(stats.forwarded)) + "\n"
161
+ );
162
+ process.stderr.write(
163
+ chalk.gray(" Denied: ") + chalk.red(String(stats.denied)) + "\n"
164
+ );
165
+ process.stderr.write(
166
+ chalk.gray(" Prompted: ") + chalk.yellow(String(stats.prompted)) + "\n"
167
+ );
168
+ if (stats.scanned > 0) {
169
+ process.stderr.write(
170
+ chalk.gray(" Responses scanned: ") + chalk.white(String(stats.scanned)) + "\n"
171
+ );
172
+ if (stats.responseBlocked > 0) {
173
+ process.stderr.write(
174
+ chalk.gray(" Resp. blocked: ") + chalk.red(String(stats.responseBlocked)) + "\n"
175
+ );
176
+ }
177
+ if (stats.responseRedacted > 0) {
178
+ process.stderr.write(
179
+ chalk.gray(" Resp. redacted: ") + chalk.yellow(String(stats.responseRedacted)) + "\n"
180
+ );
181
+ }
182
+ }
183
+ process.stderr.write(
184
+ chalk.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n")
185
+ );
186
+ }
187
+ process.exit(code ?? 0);
188
+ },
189
+ onError: (error) => {
190
+ process.stderr.write(
191
+ chalk.red(`
192
+ Agent Wall Error: ${error.message}
193
+ `)
194
+ );
195
+ }
196
+ });
197
+ let policyWatcher = null;
198
+ if (filePath) {
199
+ try {
200
+ let debounceTimer = null;
201
+ policyWatcher = fs.watch(filePath, (eventType) => {
202
+ if (eventType !== "change") return;
203
+ if (debounceTimer) clearTimeout(debounceTimer);
204
+ debounceTimer = setTimeout(() => {
205
+ try {
206
+ const { config: newConfig } = loadPolicy(filePath);
207
+ policyEngine.updateConfig(newConfig);
208
+ if (responseScanner && newConfig.responseScanning) {
209
+ responseScanner.updateConfig(newConfig.responseScanning);
210
+ }
211
+ if (!options.silent) {
212
+ process.stderr.write(
213
+ chalk.green("\u2713 ") + chalk.gray("Policy reloaded from ") + chalk.white(filePath) + chalk.gray(` (${newConfig.rules.length} rules)
214
+ `)
215
+ );
216
+ }
217
+ } catch (err) {
218
+ if (!options.silent) {
219
+ const msg = err instanceof Error ? err.message : String(err);
220
+ process.stderr.write(
221
+ chalk.yellow(`\u26A0 Policy reload failed: ${msg}
222
+ `)
223
+ );
224
+ }
225
+ }
226
+ }, 300);
227
+ });
228
+ } catch {
229
+ }
230
+ }
231
+ let dashboardServer = null;
232
+ if (options.dashboard || options.dashboardPort) {
233
+ const dashboardPort = options.dashboardPort ?? 61100;
234
+ const staticDir = resolveDashboardAssets();
235
+ dashboardServer = new DashboardServer({
236
+ port: dashboardPort,
237
+ proxy,
238
+ killSwitch,
239
+ policyEngine,
240
+ logger,
241
+ staticDir
242
+ });
243
+ logger.setOnEntry((entry) => {
244
+ dashboardServer.handleAuditEntry(entry);
245
+ });
246
+ try {
247
+ await dashboardServer.start();
248
+ if (!options.silent) {
249
+ process.stderr.write(
250
+ chalk.cyan("\u2551 ") + chalk.gray("Dashboard:") + chalk.white(` http://localhost:${dashboardPort}`) + "\n"
251
+ );
252
+ }
253
+ } catch (err) {
254
+ const msg = err instanceof Error ? err.message : String(err);
255
+ process.stderr.write(
256
+ chalk.yellow(`\u26A0 Dashboard failed to start: ${msg}
257
+ `)
258
+ );
259
+ dashboardServer = null;
260
+ }
261
+ }
262
+ const shutdown = () => {
263
+ if (policyWatcher) {
264
+ policyWatcher.close();
265
+ policyWatcher = null;
266
+ }
267
+ dashboardServer?.stop();
268
+ proxy.stop();
269
+ };
270
+ process.on("SIGINT", shutdown);
271
+ process.on("SIGTERM", shutdown);
272
+ try {
273
+ await proxy.start();
274
+ } catch (error) {
275
+ const msg = error instanceof Error ? error.message : String(error);
276
+ process.stderr.write(
277
+ chalk.red(
278
+ `
279
+ Failed to start MCP server: ${msg}
280
+
281
+ Make sure the server command is correct:
282
+ ${command} ${args.join(" ")}
283
+ `
284
+ )
285
+ );
286
+ process.exit(1);
287
+ }
288
+ }
289
+ function resolveDashboardAssets() {
290
+ const bundledDir = path.join(path.dirname(new URL(import.meta.url).pathname), "dashboard");
291
+ if (fs.existsSync(path.join(bundledDir, "index.html"))) {
292
+ return bundledDir;
293
+ }
294
+ try {
295
+ const require2 = createRequire(import.meta.url);
296
+ const pkgPath = require2.resolve("@agent-wall/dashboard/package.json");
297
+ const distDir = path.join(path.dirname(pkgPath), "dist");
298
+ if (fs.existsSync(path.join(distDir, "index.html"))) {
299
+ return distDir;
300
+ }
301
+ } catch {
302
+ }
303
+ const monorepoDist = path.resolve(
304
+ path.dirname(new URL(import.meta.url).pathname),
305
+ "../../dashboard/dist"
306
+ );
307
+ if (fs.existsSync(path.join(monorepoDist, "index.html"))) {
308
+ return monorepoDist;
309
+ }
310
+ return void 0;
311
+ }
312
+
313
+ // src/commands/init.ts
314
+ import * as fs2 from "fs";
315
+ import * as path2 from "path";
316
+ import { generateDefaultConfigYaml } from "@agent-wall/core";
317
+ import chalk2 from "chalk";
318
+ function initCommand(options) {
319
+ const outputPath = path2.resolve(options.path ?? "agent-wall.yaml");
320
+ if (fs2.existsSync(outputPath) && !options.force) {
321
+ process.stderr.write(
322
+ chalk2.yellow(
323
+ `
324
+ \u26A0 File already exists: ${outputPath}
325
+ Use --force to overwrite.
326
+
327
+ `
328
+ )
329
+ );
330
+ process.exit(1);
331
+ }
332
+ const content = generateDefaultConfigYaml();
333
+ const dir = path2.dirname(outputPath);
334
+ if (!fs2.existsSync(dir)) {
335
+ fs2.mkdirSync(dir, { recursive: true });
336
+ }
337
+ fs2.writeFileSync(outputPath, content, "utf-8");
338
+ process.stderr.write(
339
+ chalk2.green("\n\u2713 ") + chalk2.white("Created ") + chalk2.bold(outputPath) + "\n\n" + chalk2.gray(" Next steps:\n") + chalk2.gray(" 1. Edit the rules to fit your project\n") + chalk2.gray(" 2. Wrap your MCP server:\n\n") + chalk2.white(
340
+ " agent-wall wrap -- npx @modelcontextprotocol/server-filesystem /path\n"
341
+ ) + "\n"
342
+ );
343
+ }
344
+
345
+ // src/commands/test.ts
346
+ import { PolicyEngine as PolicyEngine2, loadPolicy as loadPolicy2 } from "@agent-wall/core";
347
+ import chalk3 from "chalk";
348
+ function testCommand(options) {
349
+ if (!options.tool) {
350
+ process.stderr.write(
351
+ chalk3.red(
352
+ 'Error: --tool is required.\n\nUsage:\n agent-wall test --tool shell_exec --arg command="curl https://evil.com"\n agent-wall test --tool read_file --arg path=/home/.ssh/id_rsa\n'
353
+ )
354
+ );
355
+ process.exit(1);
356
+ }
357
+ const { config, filePath } = loadPolicy2(options.config);
358
+ const engine = new PolicyEngine2(config);
359
+ const args = {};
360
+ if (options.arg) {
361
+ for (const a of options.arg) {
362
+ const eqIndex = a.indexOf("=");
363
+ if (eqIndex === -1) {
364
+ process.stderr.write(
365
+ chalk3.red(`Invalid argument format: "${a}" (expected key=value)
366
+ `)
367
+ );
368
+ process.exit(1);
369
+ }
370
+ args[a.slice(0, eqIndex)] = a.slice(eqIndex + 1);
371
+ }
372
+ }
373
+ const toolCall = {
374
+ name: options.tool,
375
+ arguments: args
376
+ };
377
+ const verdict = engine.evaluate(toolCall);
378
+ process.stderr.write("\n");
379
+ process.stderr.write(
380
+ chalk3.gray("Policy: ") + chalk3.white(filePath ?? "built-in defaults") + "\n"
381
+ );
382
+ process.stderr.write(
383
+ chalk3.gray("Tool: ") + chalk3.white(toolCall.name) + "\n"
384
+ );
385
+ process.stderr.write(
386
+ chalk3.gray("Args: ") + chalk3.white(JSON.stringify(toolCall.arguments)) + "\n"
387
+ );
388
+ process.stderr.write("\n");
389
+ const actionColors = {
390
+ allow: chalk3.green,
391
+ deny: chalk3.red,
392
+ prompt: chalk3.yellow
393
+ };
394
+ const actionSymbols = {
395
+ allow: "\u2713 ALLOWED",
396
+ deny: "\u2717 DENIED",
397
+ prompt: "? PROMPT"
398
+ };
399
+ const color = actionColors[verdict.action];
400
+ process.stderr.write(
401
+ color(` ${actionSymbols[verdict.action]}`) + "\n"
402
+ );
403
+ if (verdict.rule) {
404
+ process.stderr.write(
405
+ chalk3.gray(" Rule: ") + chalk3.white(verdict.rule) + "\n"
406
+ );
407
+ }
408
+ process.stderr.write(
409
+ chalk3.gray(" Message: ") + chalk3.white(verdict.message) + "\n"
410
+ );
411
+ process.stderr.write("\n");
412
+ process.exit(verdict.action === "deny" ? 1 : 0);
413
+ }
414
+
415
+ // src/commands/audit.ts
416
+ import * as fs3 from "fs";
417
+ import chalk4 from "chalk";
418
+ function auditCommand(options) {
419
+ if (!options.log) {
420
+ process.stderr.write(
421
+ chalk4.red(
422
+ "Error: --log is required.\n\nUsage:\n agent-wall audit --log ./agent-wall.log\n agent-wall audit --log ./agent-wall.log --filter denied\n"
423
+ )
424
+ );
425
+ process.exit(1);
426
+ }
427
+ if (!fs3.existsSync(options.log)) {
428
+ process.stderr.write(
429
+ chalk4.red(`Error: Log file not found: ${options.log}
430
+ `)
431
+ );
432
+ process.exit(1);
433
+ }
434
+ const content = fs3.readFileSync(options.log, "utf-8");
435
+ const lines = content.trim().split("\n").filter(Boolean);
436
+ let entries = [];
437
+ for (const line of lines) {
438
+ try {
439
+ entries.push(JSON.parse(line));
440
+ } catch {
441
+ }
442
+ }
443
+ const filterAction = options.filter ?? "all";
444
+ if (filterAction !== "all") {
445
+ const actionMap = {
446
+ denied: "deny",
447
+ allowed: "allow",
448
+ prompted: "prompt"
449
+ };
450
+ const targetAction = actionMap[filterAction];
451
+ entries = entries.filter((e) => e.verdict?.action === targetAction);
452
+ }
453
+ if (options.last && options.last > 0) {
454
+ entries = entries.slice(-options.last);
455
+ }
456
+ if (entries.length === 0) {
457
+ process.stderr.write(chalk4.gray("\n No matching audit entries found.\n\n"));
458
+ process.exit(0);
459
+ }
460
+ if (options.json) {
461
+ process.stdout.write(JSON.stringify(entries, null, 2) + "\n");
462
+ process.exit(0);
463
+ }
464
+ process.stderr.write("\n");
465
+ process.stderr.write(
466
+ chalk4.cyan("\u2500\u2500\u2500 Agent Wall Audit Log \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n")
467
+ );
468
+ process.stderr.write(
469
+ chalk4.gray(` File: ${options.log} | Entries: ${entries.length}
470
+ `)
471
+ );
472
+ process.stderr.write(
473
+ chalk4.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n")
474
+ );
475
+ const actionColors = {
476
+ allow: chalk4.green,
477
+ deny: chalk4.red,
478
+ prompt: chalk4.yellow
479
+ };
480
+ const actionIcons = {
481
+ allow: "\u2713",
482
+ deny: "\u2717",
483
+ prompt: "?"
484
+ };
485
+ for (const entry of entries) {
486
+ const action = entry.verdict?.action ?? "unknown";
487
+ const color = actionColors[action] ?? chalk4.gray;
488
+ const icon = actionIcons[action] ?? "\xB7";
489
+ const time = entry.timestamp ? new Date(entry.timestamp).toLocaleTimeString() : "??:??:??";
490
+ process.stderr.write(
491
+ chalk4.gray(` ${time} `) + color(`${icon} ${action.toUpperCase().padEnd(6)} `) + chalk4.white(entry.tool ?? entry.method ?? "unknown") + "\n"
492
+ );
493
+ if (entry.arguments && Object.keys(entry.arguments).length > 0) {
494
+ const argStr = JSON.stringify(entry.arguments, null, 0);
495
+ process.stderr.write(
496
+ chalk4.gray(` Args: ${argStr.slice(0, 100)}${argStr.length > 100 ? "..." : ""}`) + "\n"
497
+ );
498
+ }
499
+ if (entry.verdict?.rule) {
500
+ process.stderr.write(
501
+ chalk4.gray(` Rule: ${entry.verdict.rule}`) + "\n"
502
+ );
503
+ }
504
+ process.stderr.write("\n");
505
+ }
506
+ const stats = {
507
+ allowed: entries.filter((e) => e.verdict?.action === "allow").length,
508
+ denied: entries.filter((e) => e.verdict?.action === "deny").length,
509
+ prompted: entries.filter((e) => e.verdict?.action === "prompt").length
510
+ };
511
+ process.stderr.write(
512
+ chalk4.cyan("\u2500\u2500\u2500 Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n")
513
+ );
514
+ process.stderr.write(
515
+ chalk4.green(` Allowed: ${stats.allowed}`) + " " + chalk4.red(`Denied: ${stats.denied}`) + " " + chalk4.yellow(`Prompted: ${stats.prompted}`) + "\n"
516
+ );
517
+ process.stderr.write(
518
+ chalk4.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n")
519
+ );
520
+ }
521
+
522
+ // src/commands/scan.ts
523
+ import * as fs4 from "fs";
524
+ import * as path3 from "path";
525
+ import * as os from "os";
526
+ import chalk5 from "chalk";
527
+ var RISKY_TOOLS = [
528
+ // ── Execution (Critical) ──────────────────────────────────────────
529
+ { pattern: "shell", risk: "critical", reason: "Arbitrary shell command execution" },
530
+ { pattern: "bash", risk: "critical", reason: "Arbitrary bash execution" },
531
+ { pattern: "exec", risk: "critical", reason: "Process execution" },
532
+ { pattern: "terminal", risk: "critical", reason: "Terminal access" },
533
+ // ── Filesystem ────────────────────────────────────────────────────
534
+ { pattern: "filesystem", risk: "high", reason: "Full filesystem read/write access" },
535
+ // ── Browser / Web ─────────────────────────────────────────────────
536
+ { pattern: "playwright", risk: "high", reason: "Browser automation via Playwright (prompt injection target)" },
537
+ { pattern: "puppeteer", risk: "high", reason: "Browser automation via Puppeteer (prompt injection target)" },
538
+ { pattern: "browser", risk: "high", reason: "Browser automation (prompt injection target)" },
539
+ { pattern: "fetch", risk: "medium", reason: "Outbound HTTP requests (exfiltration vector)" },
540
+ // ── Source Control ────────────────────────────────────────────────
541
+ { pattern: "github", risk: "medium", reason: "GitHub API access (code/issues/PRs)" },
542
+ { pattern: "gitlab", risk: "medium", reason: "GitLab API access (code/issues/MRs)" },
543
+ { pattern: "git", risk: "medium", reason: "Repository access and modification" },
544
+ // ── Databases ─────────────────────────────────────────────────────
545
+ { pattern: "postgres", risk: "high", reason: "PostgreSQL access" },
546
+ { pattern: "mysql", risk: "high", reason: "MySQL database access" },
547
+ { pattern: "mongodb", risk: "high", reason: "MongoDB database access" },
548
+ { pattern: "redis", risk: "medium", reason: "Redis data store access" },
549
+ { pattern: "sqlite", risk: "medium", reason: "SQLite database access" },
550
+ { pattern: "supabase", risk: "high", reason: "Supabase database/auth/storage access" },
551
+ { pattern: "neon", risk: "high", reason: "Neon serverless Postgres access" },
552
+ { pattern: "snowflake", risk: "high", reason: "Snowflake data warehouse access" },
553
+ { pattern: "database", risk: "high", reason: "Database query execution" },
554
+ // ── Infrastructure / Cloud ────────────────────────────────────────
555
+ { pattern: "docker", risk: "critical", reason: "Container management" },
556
+ { pattern: "kubernetes", risk: "critical", reason: "Cluster management" },
557
+ { pattern: "terraform", risk: "critical", reason: "Infrastructure-as-code provisioning" },
558
+ { pattern: "aws", risk: "critical", reason: "AWS cloud resource access" },
559
+ { pattern: "gcp", risk: "critical", reason: "Google Cloud resource access" },
560
+ { pattern: "azure", risk: "critical", reason: "Azure cloud resource access" },
561
+ { pattern: "cloudflare", risk: "high", reason: "Cloudflare infrastructure management" },
562
+ { pattern: "vercel", risk: "high", reason: "Vercel deployment/project management" },
563
+ { pattern: "netlify", risk: "high", reason: "Netlify deployment/project management" },
564
+ // ── Communication ─────────────────────────────────────────────────
565
+ { pattern: "slack", risk: "medium", reason: "Slack workspace messaging access" },
566
+ { pattern: "email", risk: "medium", reason: "Email send/read access" },
567
+ { pattern: "gmail", risk: "medium", reason: "Gmail account access" },
568
+ { pattern: "discord", risk: "medium", reason: "Discord messaging access" },
569
+ // ── Payment / Secrets ─────────────────────────────────────────────
570
+ { pattern: "stripe", risk: "critical", reason: "Payment processing / financial data" },
571
+ { pattern: "razorpay", risk: "critical", reason: "Payment processing / financial data" },
572
+ { pattern: "vault", risk: "critical", reason: "Secret management (HashiCorp Vault)" },
573
+ { pattern: "1password", risk: "critical", reason: "Password manager access" },
574
+ // ── Remote Access ─────────────────────────────────────────────────
575
+ { pattern: "ssh", risk: "critical", reason: "Remote server access via SSH" },
576
+ { pattern: "rdp", risk: "critical", reason: "Remote desktop access" },
577
+ // ── AI / LLM ──────────────────────────────────────────────────────
578
+ { pattern: "openai", risk: "medium", reason: "OpenAI API access (cost / data)" },
579
+ { pattern: "anthropic", risk: "medium", reason: "Anthropic API access (cost / data)" }
580
+ ];
581
+ function detectConfigPaths() {
582
+ const home = os.homedir();
583
+ const platform3 = os.platform();
584
+ const candidates = [
585
+ // ── Claude ────────────────────────────────────────────────────
586
+ // Claude Code
587
+ path3.join(home, ".claude", "mcp_servers.json"),
588
+ // Claude Desktop (macOS)
589
+ path3.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
590
+ // Claude Desktop (Windows)
591
+ path3.join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json"),
592
+ // Claude Desktop (Linux)
593
+ path3.join(home, ".config", "Claude", "claude_desktop_config.json"),
594
+ // ── Cursor ────────────────────────────────────────────────────
595
+ path3.join(home, ".cursor", "mcp.json"),
596
+ // ── VS Code / Copilot ─────────────────────────────────────────
597
+ // VS Code workspace-level MCP (vscode 1.99+)
598
+ path3.join(process.cwd(), ".vscode", "mcp.json"),
599
+ // ── Windsurf (Codeium) ────────────────────────────────────────
600
+ path3.join(home, ".codeium", "windsurf", "mcp_config.json"),
601
+ // ── Cline ─────────────────────────────────────────────────────
602
+ path3.join(home, ".cline", "mcp_settings.json"),
603
+ // ── Continue.dev ──────────────────────────────────────────────
604
+ path3.join(home, ".continue", "config.json"),
605
+ // ── Generic / project-level ───────────────────────────────────
606
+ path3.join(process.cwd(), ".mcp.json"),
607
+ path3.join(process.cwd(), "mcp.json")
608
+ ];
609
+ if (platform3 === "win32") {
610
+ candidates.push(
611
+ path3.join(home, "AppData", "Roaming", "Code", "User", "settings.json")
612
+ );
613
+ } else if (platform3 === "darwin") {
614
+ candidates.push(
615
+ path3.join(home, "Library", "Application Support", "Code", "User", "settings.json")
616
+ );
617
+ } else {
618
+ candidates.push(
619
+ path3.join(home, ".config", "Code", "User", "settings.json")
620
+ );
621
+ }
622
+ return candidates.filter((p) => fs4.existsSync(p));
623
+ }
624
+ function scanCommand(options) {
625
+ let configPaths = [];
626
+ if (options.config) {
627
+ if (!fs4.existsSync(options.config)) {
628
+ process.stderr.write(
629
+ chalk5.red(`Error: Config not found: ${options.config}
630
+ `)
631
+ );
632
+ process.exit(1);
633
+ }
634
+ configPaths = [options.config];
635
+ } else {
636
+ configPaths = detectConfigPaths();
637
+ }
638
+ if (configPaths.length === 0) {
639
+ if (options.json) {
640
+ process.stdout.write(JSON.stringify({ servers: [], totalRisks: 0 }, null, 2) + "\n");
641
+ } else {
642
+ process.stderr.write(
643
+ chalk5.yellow(
644
+ "\n\u26A0 No MCP configuration files found.\n\n" + chalk5.gray(
645
+ " Looked for config files from:\n \u2022 Claude Code ~/.claude/mcp_servers.json\n \u2022 Claude Desktop ~/Library/.../claude_desktop_config.json\n \u2022 Cursor ~/.cursor/mcp.json\n \u2022 VS Code / Copilot .vscode/mcp.json\n \u2022 Windsurf ~/.codeium/windsurf/mcp_config.json\n \u2022 Cline ~/.cline/mcp_settings.json\n \u2022 Continue.dev ~/.continue/config.json\n \u2022 Project-level .mcp.json, mcp.json\n\n Tip: pass --config <path> to scan a specific file.\n\n"
646
+ )
647
+ )
648
+ );
649
+ }
650
+ process.exit(0);
651
+ }
652
+ const results = [];
653
+ let totalRisks = 0;
654
+ for (const configPath of configPaths) {
655
+ let raw;
656
+ try {
657
+ const content = fs4.readFileSync(configPath, "utf-8");
658
+ raw = JSON.parse(content);
659
+ } catch {
660
+ if (!options.json) {
661
+ process.stderr.write(
662
+ chalk5.red(` Failed to parse: ${configPath}
663
+
664
+ `)
665
+ );
666
+ }
667
+ continue;
668
+ }
669
+ const servers = raw.mcpServers ?? raw;
670
+ for (const [name, config] of Object.entries(servers)) {
671
+ if (!config || typeof config !== "object" || !config.command) continue;
672
+ const serverStr = `${config.command} ${(config.args ?? []).join(" ")}`;
673
+ const risks = [];
674
+ for (const risky of RISKY_TOOLS) {
675
+ if (serverStr.toLowerCase().includes(risky.pattern)) {
676
+ risks.push({ level: risky.risk, reason: risky.reason });
677
+ }
678
+ }
679
+ const isProtected = serverStr.includes("agent-wall");
680
+ if (!isProtected) totalRisks += risks.length;
681
+ results.push({
682
+ name,
683
+ configFile: configPath,
684
+ command: serverStr,
685
+ protected: isProtected,
686
+ risks
687
+ });
688
+ }
689
+ }
690
+ if (options.json) {
691
+ process.stdout.write(
692
+ JSON.stringify({ servers: results, totalRisks }, null, 2) + "\n"
693
+ );
694
+ return;
695
+ }
696
+ process.stderr.write("\n");
697
+ process.stderr.write(
698
+ chalk5.cyan("\u2500\u2500\u2500 Agent Wall Security Scan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n")
699
+ );
700
+ let lastConfig = "";
701
+ for (const server of results) {
702
+ if (server.configFile !== lastConfig) {
703
+ lastConfig = server.configFile;
704
+ process.stderr.write(
705
+ chalk5.gray(" Config: ") + chalk5.white(server.configFile) + "\n\n"
706
+ );
707
+ }
708
+ const riskColor = server.risks.some((r) => r.level === "critical") ? chalk5.red : server.risks.some((r) => r.level === "high") ? chalk5.yellow : server.risks.length > 0 ? chalk5.gray : chalk5.green;
709
+ const statusIcon = server.protected ? chalk5.green("\u{1F6E1}") : server.risks.length > 0 ? chalk5.red("\u26A0") : chalk5.green("\u2713");
710
+ process.stderr.write(
711
+ ` ${statusIcon} ${chalk5.bold.white(server.name)}
712
+ `
713
+ );
714
+ process.stderr.write(
715
+ chalk5.gray(` Command: ${server.command.slice(0, 80)}
716
+ `)
717
+ );
718
+ if (server.protected) {
719
+ process.stderr.write(
720
+ chalk5.green(" Protected by Agent Wall \u2713\n")
721
+ );
722
+ } else if (server.risks.length > 0) {
723
+ for (const risk of server.risks) {
724
+ process.stderr.write(
725
+ riskColor(
726
+ ` ${risk.level.toUpperCase()}: ${risk.reason}
727
+ `
728
+ )
729
+ );
730
+ }
731
+ process.stderr.write(
732
+ chalk5.gray(
733
+ ` Fix: agent-wall wrap -- ${server.command}
734
+ `
735
+ )
736
+ );
737
+ } else {
738
+ process.stderr.write(chalk5.green(" No known risks detected\n"));
739
+ }
740
+ process.stderr.write("\n");
741
+ }
742
+ process.stderr.write(
743
+ chalk5.cyan("\u2500\u2500\u2500 Scan Results \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n")
744
+ );
745
+ process.stderr.write(
746
+ chalk5.gray(" MCP Servers: ") + chalk5.white(String(results.length)) + "\n"
747
+ );
748
+ process.stderr.write(
749
+ chalk5.gray(" Risks found: ") + (totalRisks > 0 ? chalk5.red(String(totalRisks)) : chalk5.green("0")) + "\n"
750
+ );
751
+ process.stderr.write(
752
+ chalk5.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n")
753
+ );
754
+ if (totalRisks > 0) {
755
+ process.stderr.write(
756
+ chalk5.yellow(
757
+ " Run 'agent-wall init' to create a policy config,\n then wrap your servers with 'agent-wall wrap'.\n\n"
758
+ )
759
+ );
760
+ }
761
+ }
762
+
763
+ // src/commands/validate.ts
764
+ import { loadPolicy as loadPolicy3, PolicyEngine as PolicyEngine3 } from "@agent-wall/core";
765
+ import chalk6 from "chalk";
766
+ function validateCommand(options) {
767
+ process.stderr.write("\n");
768
+ process.stderr.write(
769
+ chalk6.cyan("\u2500\u2500\u2500 Agent Wall Config Validation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n")
770
+ );
771
+ let config;
772
+ let filePath;
773
+ try {
774
+ const result = loadPolicy3(options.config);
775
+ config = result.config;
776
+ filePath = result.filePath;
777
+ } catch (error) {
778
+ process.stderr.write(
779
+ chalk6.red(" \u2717 ") + chalk6.red(`Failed to load config: ${error.message}
780
+
781
+ `)
782
+ );
783
+ process.exit(1);
784
+ }
785
+ process.stderr.write(
786
+ chalk6.green(" \u2713 ") + chalk6.white("Config loaded: ") + chalk6.gray(filePath ?? "built-in defaults") + "\n"
787
+ );
788
+ if (config.version !== 1) {
789
+ process.stderr.write(
790
+ chalk6.yellow(" \u26A0 ") + chalk6.yellow(`Unknown config version: ${config.version} (expected 1)
791
+ `)
792
+ );
793
+ } else {
794
+ process.stderr.write(
795
+ chalk6.green(" \u2713 ") + chalk6.white("Version: 1\n")
796
+ );
797
+ }
798
+ const validActions = ["allow", "deny", "prompt"];
799
+ if (!config.defaultAction || !validActions.includes(config.defaultAction)) {
800
+ process.stderr.write(
801
+ chalk6.red(" \u2717 ") + chalk6.red(`Invalid defaultAction: "${config.defaultAction}" (expected: allow, deny, prompt)
802
+ `)
803
+ );
804
+ } else {
805
+ process.stderr.write(
806
+ chalk6.green(" \u2713 ") + chalk6.white(`Default action: ${config.defaultAction}
807
+ `)
808
+ );
809
+ }
810
+ if (config.globalRateLimit) {
811
+ if (config.globalRateLimit.maxCalls <= 0) {
812
+ process.stderr.write(
813
+ chalk6.yellow(" \u26A0 ") + chalk6.yellow("Global rate limit maxCalls should be > 0\n")
814
+ );
815
+ } else {
816
+ process.stderr.write(
817
+ chalk6.green(" \u2713 ") + chalk6.white(
818
+ `Global rate limit: ${config.globalRateLimit.maxCalls} calls / ${config.globalRateLimit.windowSeconds}s
819
+ `
820
+ )
821
+ );
822
+ }
823
+ }
824
+ process.stderr.write(
825
+ chalk6.green(" \u2713 ") + chalk6.white(`Rules: ${config.rules.length} loaded
826
+ `)
827
+ );
828
+ let warnings = 0;
829
+ let errors = 0;
830
+ const ruleNames = /* @__PURE__ */ new Set();
831
+ for (let i = 0; i < config.rules.length; i++) {
832
+ const rule = config.rules[i];
833
+ const label = rule.name ?? `rule[${i}]`;
834
+ if (rule.name) {
835
+ if (ruleNames.has(rule.name)) {
836
+ process.stderr.write(
837
+ chalk6.yellow(" \u26A0 ") + chalk6.yellow(`Duplicate rule name: "${rule.name}"
838
+ `)
839
+ );
840
+ warnings++;
841
+ }
842
+ ruleNames.add(rule.name);
843
+ } else {
844
+ process.stderr.write(
845
+ chalk6.yellow(" \u26A0 ") + chalk6.yellow(`Rule at index ${i} has no name (recommended for audit logs)
846
+ `)
847
+ );
848
+ warnings++;
849
+ }
850
+ if (!validActions.includes(rule.action)) {
851
+ process.stderr.write(
852
+ chalk6.red(" \u2717 ") + chalk6.red(`${label}: invalid action "${rule.action}"
853
+ `)
854
+ );
855
+ errors++;
856
+ }
857
+ if (rule.tool && rule.tool.includes(" ")) {
858
+ process.stderr.write(
859
+ chalk6.yellow(" \u26A0 ") + chalk6.yellow(`${label}: tool pattern contains spaces \u2014 use "|" to separate alternatives
860
+ `)
861
+ );
862
+ warnings++;
863
+ }
864
+ if (rule.rateLimit) {
865
+ if (rule.rateLimit.maxCalls <= 0 || rule.rateLimit.windowSeconds <= 0) {
866
+ process.stderr.write(
867
+ chalk6.yellow(" \u26A0 ") + chalk6.yellow(`${label}: rate limit values should be > 0
868
+ `)
869
+ );
870
+ warnings++;
871
+ }
872
+ }
873
+ }
874
+ try {
875
+ new PolicyEngine3(config);
876
+ process.stderr.write(
877
+ chalk6.green(" \u2713 ") + chalk6.white("Policy engine: OK\n")
878
+ );
879
+ } catch (error) {
880
+ process.stderr.write(
881
+ chalk6.red(" \u2717 ") + chalk6.red(`Policy engine failed: ${error.message}
882
+ `)
883
+ );
884
+ errors++;
885
+ }
886
+ process.stderr.write("\n");
887
+ if (errors > 0) {
888
+ process.stderr.write(
889
+ chalk6.red(` ${errors} error(s), ${warnings} warning(s)
890
+
891
+ `)
892
+ );
893
+ process.exit(1);
894
+ } else if (warnings > 0) {
895
+ process.stderr.write(
896
+ chalk6.yellow(` ${warnings} warning(s), 0 errors \u2014 config is valid
897
+
898
+ `)
899
+ );
900
+ } else {
901
+ process.stderr.write(
902
+ chalk6.green(" \u2713 Config is valid \u2014 no issues found\n\n")
903
+ );
904
+ }
905
+ }
906
+
907
+ // src/commands/doctor.ts
908
+ import * as fs5 from "fs";
909
+ import * as os2 from "os";
910
+ import * as path4 from "path";
911
+ import chalk7 from "chalk";
912
+ import { loadPolicy as loadPolicy4, PolicyEngine as PolicyEngine4 } from "@agent-wall/core";
913
+ function doctorCommand(options) {
914
+ const checks = [];
915
+ process.stderr.write("\n");
916
+ process.stderr.write(
917
+ chalk7.cyan("\u2500\u2500\u2500 Agent Wall Doctor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n")
918
+ );
919
+ const nodeVersion = process.versions.node;
920
+ const [major] = nodeVersion.split(".").map(Number);
921
+ checks.push({
922
+ label: "Node.js version",
923
+ ok: major >= 18,
924
+ detail: `v${nodeVersion}${major < 18 ? " (requires >= 18)" : ""}`
925
+ });
926
+ let configOk = false;
927
+ let configDetail = "";
928
+ let ruleCount = 0;
929
+ try {
930
+ const { config, filePath } = loadPolicy4(options.config);
931
+ configOk = true;
932
+ ruleCount = config.rules.length;
933
+ configDetail = `${filePath ?? "built-in defaults"} (${ruleCount} rules)`;
934
+ const engine = new PolicyEngine4(config);
935
+ engine.evaluate({ name: "__doctor_test__", arguments: {} });
936
+ } catch (err) {
937
+ configDetail = err instanceof Error ? err.message : String(err);
938
+ }
939
+ checks.push({
940
+ label: "Policy config",
941
+ ok: configOk,
942
+ detail: configDetail
943
+ });
944
+ const home = os2.homedir();
945
+ const platform3 = os2.platform();
946
+ const mcpClients = [
947
+ { name: "Claude Code", path: path4.join(home, ".claude", "mcp_servers.json") },
948
+ { name: "Cursor", path: path4.join(home, ".cursor", "mcp.json") },
949
+ { name: "VS Code", path: path4.join(process.cwd(), ".vscode", "mcp.json") },
950
+ { name: "Windsurf", path: path4.join(home, ".codeium", "windsurf", "mcp_config.json") },
951
+ { name: "Cline", path: path4.join(home, ".cline", "mcp_settings.json") }
952
+ ];
953
+ if (platform3 === "win32") {
954
+ mcpClients.push({
955
+ name: "Claude Desktop",
956
+ path: path4.join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json")
957
+ });
958
+ } else if (platform3 === "darwin") {
959
+ mcpClients.push({
960
+ name: "Claude Desktop",
961
+ path: path4.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")
962
+ });
963
+ } else {
964
+ mcpClients.push({
965
+ name: "Claude Desktop",
966
+ path: path4.join(home, ".config", "Claude", "claude_desktop_config.json")
967
+ });
968
+ }
969
+ const detected = mcpClients.filter((c) => fs5.existsSync(c.path));
970
+ checks.push({
971
+ label: "MCP clients found",
972
+ ok: detected.length > 0,
973
+ detail: detected.length > 0 ? detected.map((c) => c.name).join(", ") : "None detected (run 'agent-wall scan' for details)"
974
+ });
975
+ const envVars = [];
976
+ if (process.env.AGENT_WALL_CONFIG) envVars.push("AGENT_WALL_CONFIG");
977
+ if (process.env.AGENT_WALL_LOG) envVars.push("AGENT_WALL_LOG");
978
+ checks.push({
979
+ label: "Env overrides",
980
+ ok: true,
981
+ // Always "ok" — just informational
982
+ detail: envVars.length > 0 ? envVars.join(", ") : "None set"
983
+ });
984
+ for (const check of checks) {
985
+ const icon = check.ok ? chalk7.green("\u2713") : chalk7.red("\u2717");
986
+ process.stderr.write(
987
+ ` ${icon} ${chalk7.bold.white(check.label)}
988
+ `
989
+ );
990
+ process.stderr.write(chalk7.gray(` ${check.detail}
991
+
992
+ `));
993
+ }
994
+ const failures = checks.filter((c) => !c.ok);
995
+ process.stderr.write(
996
+ chalk7.cyan("\u2500\u2500\u2500 Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n")
997
+ );
998
+ if (failures.length === 0) {
999
+ process.stderr.write(
1000
+ chalk7.green(" All checks passed. Agent Wall is ready.\n\n")
1001
+ );
1002
+ } else {
1003
+ process.stderr.write(
1004
+ chalk7.yellow(
1005
+ ` ${failures.length} issue(s) found:
1006
+ `
1007
+ )
1008
+ );
1009
+ for (const f of failures) {
1010
+ process.stderr.write(chalk7.yellow(` \u2022 ${f.label}: ${f.detail}
1011
+ `));
1012
+ }
1013
+ process.stderr.write("\n");
1014
+ }
1015
+ process.stderr.write(
1016
+ chalk7.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n")
1017
+ );
1018
+ }
1019
+
1020
+ // src/index.ts
1021
+ function envConfig(explicit) {
1022
+ return explicit ?? process.env.AGENT_WALL_CONFIG ?? void 0;
1023
+ }
1024
+ function envLogFile(explicit) {
1025
+ return explicit ?? process.env.AGENT_WALL_LOG ?? void 0;
1026
+ }
1027
+ var program = new Command();
1028
+ program.name("agent-wall").description(
1029
+ "Security firewall for AI agents \u2014 intercept MCP tool calls, enforce policies, block attacks."
1030
+ ).version("0.1.0");
1031
+ program.command("wrap").description("Wrap an MCP server with Agent Wall policy enforcement").option("-c, --config <path>", "Path to agent-wall.yaml config file").option("-l, --log-file <path>", "Path to write audit log (JSON lines)").option("-s, --silent", "Suppress Agent Wall output (only MCP protocol on stdout)").option("--dry-run", "Preview policy evaluation without starting the server").option("-d, --dashboard", "Launch real-time security dashboard").option("--dashboard-port <port>", "Dashboard port (default: 61100)", parseInt).argument("[serverArgs...]", "Server command and arguments (after --)").allowUnknownOption(true).action((serverArgs, opts) => {
1032
+ wrapCommand(serverArgs, {
1033
+ config: envConfig(opts.config),
1034
+ logFile: envLogFile(opts.logFile),
1035
+ silent: opts.silent,
1036
+ dryRun: opts.dryRun,
1037
+ dashboard: opts.dashboard,
1038
+ dashboardPort: opts.dashboardPort
1039
+ });
1040
+ });
1041
+ program.command("init").description("Generate a starter agent-wall.yaml configuration file").option("-p, --path <path>", "Output path (default: ./agent-wall.yaml)").option("-f, --force", "Overwrite existing file").action((opts) => {
1042
+ initCommand({ path: opts.path, force: opts.force });
1043
+ });
1044
+ program.command("test").description("Dry-run a tool call against your policy rules").option("-c, --config <path>", "Path to agent-wall.yaml config file").requiredOption("-t, --tool <name>", "Tool name to test").option(
1045
+ "-a, --arg <key=value>",
1046
+ "Tool argument (repeatable)",
1047
+ (val, prev) => [...prev, val],
1048
+ []
1049
+ ).action((opts) => {
1050
+ testCommand({ config: envConfig(opts.config), tool: opts.tool, arg: opts.arg });
1051
+ });
1052
+ program.command("audit").description("Display and analyze audit logs").requiredOption("-l, --log <path>", "Path to the audit log file").option(
1053
+ "-f, --filter <action>",
1054
+ "Filter by action: allowed, denied, prompted, all",
1055
+ "all"
1056
+ ).option("-n, --last <count>", "Show only the last N entries", parseInt).option("--json", "Output raw JSON").action((opts) => {
1057
+ auditCommand({
1058
+ log: opts.log,
1059
+ filter: opts.filter,
1060
+ last: opts.last,
1061
+ json: opts.json
1062
+ });
1063
+ });
1064
+ program.command("scan").description("Scan your MCP configuration for security risks").option("-c, --config <path>", "Path to MCP config file").option("--json", "Output results as JSON").action((opts) => {
1065
+ scanCommand({ config: opts.config, json: opts.json });
1066
+ });
1067
+ program.command("validate").description("Validate your policy configuration file").option("-c, --config <path>", "Path to agent-wall.yaml config file").action((opts) => {
1068
+ validateCommand({ config: envConfig(opts.config) });
1069
+ });
1070
+ program.command("doctor").description("Health check \u2014 verify config, environment, and MCP setup").option("-c, --config <path>", "Path to agent-wall.yaml config file").action((opts) => {
1071
+ doctorCommand({ config: envConfig(opts.config) });
1072
+ });
1073
+ program.parse();
1074
+ //# sourceMappingURL=index.js.map