@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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/wrap.ts","../src/commands/init.ts","../src/commands/test.ts","../src/commands/audit.ts","../src/commands/scan.ts","../src/commands/validate.ts","../src/commands/doctor.ts"],"sourcesContent":["/**\n * Agent Wall CLI\n *\n * Security firewall for AI agents.\n * Intercepts MCP tool calls, enforces policies, blocks attacks.\n *\n * Commands:\n * wrap — Wrap an MCP server with Agent Wall protection\n * init — Generate a starter agent-wall.yaml config\n * test — Dry-run a tool call against your policy rules\n * audit — Display and analyze audit logs\n * scan — Scan your MCP config for security risks\n * validate — Validate your policy configuration file\n */\n\nimport { Command } from \"commander\";\nimport { wrapCommand } from \"./commands/wrap.js\";\nimport { initCommand } from \"./commands/init.js\";\nimport { testCommand } from \"./commands/test.js\";\nimport { auditCommand } from \"./commands/audit.js\";\nimport { scanCommand } from \"./commands/scan.js\";\nimport { validateCommand } from \"./commands/validate.js\";\nimport { doctorCommand } from \"./commands/doctor.js\";\n\n/** Resolve env-var fallback for config path. */\nfunction envConfig(explicit?: string): string | undefined {\n return explicit ?? process.env.AGENT_WALL_CONFIG ?? undefined;\n}\n\n/** Resolve env-var fallback for log file path. */\nfunction envLogFile(explicit?: string): string | undefined {\n return explicit ?? process.env.AGENT_WALL_LOG ?? undefined;\n}\n\nconst program = new Command();\n\nprogram\n .name(\"agent-wall\")\n .description(\n \"Security firewall for AI agents — intercept MCP tool calls, enforce policies, block attacks.\"\n )\n .version(\"0.1.0\");\n\n// ── wrap ─────────────────────────────────────────────────────────────\n\nprogram\n .command(\"wrap\")\n .description(\"Wrap an MCP server with Agent Wall policy enforcement\")\n .option(\"-c, --config <path>\", \"Path to agent-wall.yaml config file\")\n .option(\"-l, --log-file <path>\", \"Path to write audit log (JSON lines)\")\n .option(\"-s, --silent\", \"Suppress Agent Wall output (only MCP protocol on stdout)\")\n .option(\"--dry-run\", \"Preview policy evaluation without starting the server\")\n .option(\"-d, --dashboard\", \"Launch real-time security dashboard\")\n .option(\"--dashboard-port <port>\", \"Dashboard port (default: 61100)\", parseInt)\n .argument(\"[serverArgs...]\", \"Server command and arguments (after --)\")\n .allowUnknownOption(true)\n .action((serverArgs: string[], opts) => {\n wrapCommand(serverArgs, {\n config: envConfig(opts.config),\n logFile: envLogFile(opts.logFile),\n silent: opts.silent,\n dryRun: opts.dryRun,\n dashboard: opts.dashboard,\n dashboardPort: opts.dashboardPort,\n });\n });\n\n// ── init ─────────────────────────────────────────────────────────────\n\nprogram\n .command(\"init\")\n .description(\"Generate a starter agent-wall.yaml configuration file\")\n .option(\"-p, --path <path>\", \"Output path (default: ./agent-wall.yaml)\")\n .option(\"-f, --force\", \"Overwrite existing file\")\n .action((opts) => {\n initCommand({ path: opts.path, force: opts.force });\n });\n\n// ── test ─────────────────────────────────────────────────────────────\n\nprogram\n .command(\"test\")\n .description(\"Dry-run a tool call against your policy rules\")\n .option(\"-c, --config <path>\", \"Path to agent-wall.yaml config file\")\n .requiredOption(\"-t, --tool <name>\", \"Tool name to test\")\n .option(\n \"-a, --arg <key=value>\",\n \"Tool argument (repeatable)\",\n (val: string, prev: string[]) => [...prev, val],\n [] as string[]\n )\n .action((opts) => {\n testCommand({ config: envConfig(opts.config), tool: opts.tool, arg: opts.arg });\n });\n\n// ── audit ────────────────────────────────────────────────────────────\n\nprogram\n .command(\"audit\")\n .description(\"Display and analyze audit logs\")\n .requiredOption(\"-l, --log <path>\", \"Path to the audit log file\")\n .option(\n \"-f, --filter <action>\",\n \"Filter by action: allowed, denied, prompted, all\",\n \"all\"\n )\n .option(\"-n, --last <count>\", \"Show only the last N entries\", parseInt)\n .option(\"--json\", \"Output raw JSON\")\n .action((opts) => {\n auditCommand({\n log: opts.log,\n filter: opts.filter,\n last: opts.last,\n json: opts.json,\n });\n });\n\n// ── scan ─────────────────────────────────────────────────────────────\n\nprogram\n .command(\"scan\")\n .description(\"Scan your MCP configuration for security risks\")\n .option(\"-c, --config <path>\", \"Path to MCP config file\")\n .option(\"--json\", \"Output results as JSON\")\n .action((opts) => {\n scanCommand({ config: opts.config, json: opts.json });\n });\n\n// ── validate ─────────────────────────────────────────────────────────\n\nprogram\n .command(\"validate\")\n .description(\"Validate your policy configuration file\")\n .option(\"-c, --config <path>\", \"Path to agent-wall.yaml config file\")\n .action((opts) => {\n validateCommand({ config: envConfig(opts.config) });\n });\n\n// ── doctor ───────────────────────────────────────────────────────────\n\nprogram\n .command(\"doctor\")\n .description(\"Health check — verify config, environment, and MCP setup\")\n .option(\"-c, --config <path>\", \"Path to agent-wall.yaml config file\")\n .action((opts) => {\n doctorCommand({ config: envConfig(opts.config) });\n });\n\n// ── Parse ────────────────────────────────────────────────────────────\n\nprogram.parse();\n","/**\n * agent-wall wrap — The main command.\n *\n * Wraps an MCP server command, intercepting all tool calls\n * through the Agent Wall policy engine.\n *\n * Usage:\n * agent-wall wrap -- npx @modelcontextprotocol/server-filesystem /path\n * agent-wall wrap -c agent-wall.yaml -- node my-mcp-server.js\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport {\n StdioProxy,\n PolicyEngine,\n AuditLogger,\n ResponseScanner,\n InjectionDetector,\n EgressControl,\n KillSwitch,\n ChainDetector,\n DashboardServer,\n loadPolicy,\n createTerminalPromptHandler,\n} from \"@agent-wall/core\";\nimport chalk from \"chalk\";\n\nexport interface WrapOptions {\n config?: string;\n logFile?: string;\n silent?: boolean;\n dryRun?: boolean;\n dashboard?: boolean;\n dashboardPort?: number;\n}\n\nexport async function wrapCommand(\n serverArgs: string[],\n options: WrapOptions\n): Promise<void> {\n if (serverArgs.length === 0) {\n process.stderr.write(\n chalk.red(\n \"Error: No server command specified.\\n\" +\n \"Usage: agent-wall wrap -- <command> [args...]\\n\\n\" +\n \"Example:\\n\" +\n \" agent-wall wrap -- npx @modelcontextprotocol/server-filesystem /home/user\\n\"\n )\n );\n process.exit(1);\n }\n\n const command = serverArgs[0];\n const args = serverArgs.slice(1);\n\n // Load policy\n const { config, filePath } = loadPolicy(options.config);\n\n if (!options.silent) {\n process.stderr.write(\n chalk.cyan(\"╔══════════════════════════════════════════════════╗\\n\")\n );\n process.stderr.write(\n chalk.cyan(\"║ \") +\n chalk.bold.white(\"Agent Wall\") +\n chalk.cyan(\" — Security Firewall for AI Agents ║\\n\")\n );\n process.stderr.write(\n chalk.cyan(\"╠══════════════════════════════════════════════════╣\\n\")\n );\n process.stderr.write(\n chalk.cyan(\"║ \") +\n chalk.gray(\"Policy: \") +\n chalk.white(filePath ?? \"built-in defaults\") +\n \"\\n\"\n );\n process.stderr.write(\n chalk.cyan(\"║ \") +\n chalk.gray(\"Rules: \") +\n chalk.white(`${config.rules.length} loaded`) +\n \"\\n\"\n );\n const rspScan = config.responseScanning;\n if (rspScan?.enabled !== false) {\n process.stderr.write(\n chalk.cyan(\"║ \") +\n chalk.gray(\"Scanner:\") +\n chalk.white(\" response scanning ON\") +\n (rspScan?.detectPII ? chalk.yellow(\" +PII\") : \"\") +\n \"\\n\"\n );\n }\n process.stderr.write(\n chalk.cyan(\"║ \") +\n chalk.gray(\"Server: \") +\n chalk.white(`${command} ${args.join(\" \")}`) +\n \"\\n\"\n );\n process.stderr.write(\n chalk.cyan(\"╚══════════════════════════════════════════════════╝\\n\\n\")\n );\n }\n\n // Dry-run mode: just show info and exit\n if (options.dryRun) {\n process.stderr.write(\n chalk.yellow(\" --dry-run mode: preview only, server will not start\\n\\n\")\n );\n process.stderr.write(\n chalk.gray(\" Default action: \") +\n chalk.white(config.defaultAction) +\n \"\\n\"\n );\n if (config.globalRateLimit) {\n process.stderr.write(\n chalk.gray(\" Rate limit: \") +\n chalk.white(\n `${config.globalRateLimit.maxCalls} calls / ${config.globalRateLimit.windowSeconds}s`\n ) +\n \"\\n\"\n );\n }\n process.stderr.write(\n chalk.gray(\" Rules loaded: \") +\n chalk.white(String(config.rules.length)) +\n \"\\n\\n\"\n );\n\n for (let i = 0; i < config.rules.length; i++) {\n const rule = config.rules[i];\n const icon =\n rule.action === \"deny\"\n ? chalk.red(\"✗\")\n : rule.action === \"allow\"\n ? chalk.green(\"✓\")\n : chalk.yellow(\"?\");\n process.stderr.write(\n ` ${icon} ${chalk.bold(rule.name ?? `rule[${i}]`)}` +\n chalk.gray(` → ${rule.action}`) +\n chalk.gray(` (tool: ${rule.tool})`) +\n \"\\n\"\n );\n }\n process.stderr.write(\"\\n\");\n process.exit(0);\n }\n\n // Create engine + logger + response scanner\n const policyEngine = new PolicyEngine(config);\n const securityConfig = config.security;\n const logger = new AuditLogger({\n stdout: !options.silent,\n filePath: options.logFile,\n redact: true,\n signing: securityConfig?.signing ?? false,\n signingKey: securityConfig?.signingKey,\n });\n\n // Create response scanner from policy config\n const responseScanner = config.responseScanning?.enabled !== false\n ? new ResponseScanner({\n enabled: true,\n maxResponseSize: config.responseScanning?.maxResponseSize,\n oversizeAction: config.responseScanning?.oversizeAction,\n detectSecrets: config.responseScanning?.detectSecrets ?? true,\n detectPII: config.responseScanning?.detectPII ?? false,\n base64Action: config.responseScanning?.base64Action,\n maxPatterns: config.responseScanning?.maxPatterns,\n patterns: config.responseScanning?.patterns,\n })\n : undefined;\n\n // Create security modules from config\n const injectionDetector = new InjectionDetector(securityConfig?.injectionDetection);\n const egressControl = new EgressControl(securityConfig?.egressControl);\n const killSwitch = new KillSwitch({\n ...securityConfig?.killSwitch,\n registerSignal: true,\n });\n const chainDetector = new ChainDetector(securityConfig?.chainDetection);\n\n // Show security module status\n if (!options.silent) {\n const modules: string[] = [];\n if (securityConfig?.injectionDetection?.enabled !== false) modules.push(\"injection\");\n if (securityConfig?.egressControl?.enabled !== false) modules.push(\"egress\");\n if (securityConfig?.killSwitch?.enabled !== false) modules.push(\"kill-switch\");\n if (securityConfig?.chainDetection?.enabled !== false) modules.push(\"chain\");\n if (securityConfig?.signing) modules.push(\"signing\");\n if (modules.length > 0) {\n process.stderr.write(\n chalk.cyan(\"║ \") +\n chalk.gray(\"Security:\") +\n chalk.white(` ${modules.join(\", \")}`) +\n \"\\n\"\n );\n }\n }\n\n // Create proxy\n const proxy = new StdioProxy({\n command,\n args,\n policyEngine,\n responseScanner,\n logger,\n injectionDetector,\n egressControl,\n killSwitch,\n chainDetector,\n onPrompt: createTerminalPromptHandler(),\n onReady: () => {\n if (!options.silent) {\n process.stderr.write(\n chalk.green(\"✓ \") +\n chalk.gray(\"MCP server started. Agent Wall is protecting.\\n\\n\")\n );\n }\n },\n onExit: (code) => {\n if (!options.silent) {\n const stats = proxy.getStats();\n process.stderr.write(\"\\n\");\n process.stderr.write(\n chalk.cyan(\"─── Agent Wall Session Summary ────────────────────\\n\")\n );\n process.stderr.write(\n chalk.gray(\" Total calls: \") +\n chalk.white(String(stats.total)) +\n \"\\n\"\n );\n process.stderr.write(\n chalk.gray(\" Forwarded: \") +\n chalk.green(String(stats.forwarded)) +\n \"\\n\"\n );\n process.stderr.write(\n chalk.gray(\" Denied: \") +\n chalk.red(String(stats.denied)) +\n \"\\n\"\n );\n process.stderr.write(\n chalk.gray(\" Prompted: \") +\n chalk.yellow(String(stats.prompted)) +\n \"\\n\"\n );\n if (stats.scanned > 0) {\n process.stderr.write(\n chalk.gray(\" Responses scanned: \") +\n chalk.white(String(stats.scanned)) +\n \"\\n\"\n );\n if (stats.responseBlocked > 0) {\n process.stderr.write(\n chalk.gray(\" Resp. blocked: \") +\n chalk.red(String(stats.responseBlocked)) +\n \"\\n\"\n );\n }\n if (stats.responseRedacted > 0) {\n process.stderr.write(\n chalk.gray(\" Resp. redacted: \") +\n chalk.yellow(String(stats.responseRedacted)) +\n \"\\n\"\n );\n }\n }\n process.stderr.write(\n chalk.cyan(\"─────────────────────────────────────────────────\\n\")\n );\n }\n process.exit(code ?? 0);\n },\n onError: (error) => {\n process.stderr.write(\n chalk.red(`\\nAgent Wall Error: ${error.message}\\n`)\n );\n },\n });\n\n // ── Policy hot-reload ────────────────────────────────────────────\n // Watch the policy file for changes and reload without restarting.\n let policyWatcher: fs.FSWatcher | null = null;\n if (filePath) {\n try {\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n policyWatcher = fs.watch(filePath, (eventType) => {\n if (eventType !== \"change\") return;\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n try {\n const { config: newConfig } = loadPolicy(filePath);\n policyEngine.updateConfig(newConfig);\n if (responseScanner && newConfig.responseScanning) {\n responseScanner.updateConfig(newConfig.responseScanning);\n }\n if (!options.silent) {\n process.stderr.write(\n chalk.green(\"✓ \") +\n chalk.gray(\"Policy reloaded from \") +\n chalk.white(filePath) +\n chalk.gray(` (${newConfig.rules.length} rules)\\n`)\n );\n }\n } catch (err: unknown) {\n if (!options.silent) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n chalk.yellow(`⚠ Policy reload failed: ${msg}\\n`)\n );\n }\n }\n }, 300); // Debounce: 300ms to handle rapid file saves\n });\n } catch {\n // File watching not available — continue without hot-reload\n }\n }\n\n // ── Dashboard ─────────────────────────────────────────────────────\n let dashboardServer: DashboardServer | null = null;\n if (options.dashboard || options.dashboardPort) {\n const dashboardPort = options.dashboardPort ?? 61100;\n const staticDir = resolveDashboardAssets();\n\n dashboardServer = new DashboardServer({\n port: dashboardPort,\n proxy,\n killSwitch,\n policyEngine,\n logger,\n staticDir,\n });\n\n // Wire audit logger to push entries to dashboard\n logger.setOnEntry((entry) => {\n dashboardServer!.handleAuditEntry(entry);\n });\n\n try {\n await dashboardServer.start();\n if (!options.silent) {\n process.stderr.write(\n chalk.cyan(\"║ \") +\n chalk.gray(\"Dashboard:\") +\n chalk.white(` http://localhost:${dashboardPort}`) +\n \"\\n\"\n );\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n chalk.yellow(`⚠ Dashboard failed to start: ${msg}\\n`)\n );\n dashboardServer = null;\n }\n }\n\n // Handle process signals\n const shutdown = () => {\n if (policyWatcher) {\n policyWatcher.close();\n policyWatcher = null;\n }\n dashboardServer?.stop();\n proxy.stop();\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Start the proxy\n try {\n await proxy.start();\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n process.stderr.write(\n chalk.red(\n `\\nFailed to start MCP server: ${msg}\\n\\n` +\n \"Make sure the server command is correct:\\n\" +\n ` ${command} ${args.join(\" \")}\\n`\n )\n );\n process.exit(1);\n }\n}\n\n// ── Dashboard Asset Resolution ──────────────────────────────────────\n\nfunction resolveDashboardAssets(): string | undefined {\n // 1. Check for bundled assets (shipped with npm package)\n const bundledDir = path.join(path.dirname(new URL(import.meta.url).pathname), \"dashboard\");\n if (fs.existsSync(path.join(bundledDir, \"index.html\"))) {\n return bundledDir;\n }\n\n // 2. Try to find @agent-wall/dashboard's built assets via require.resolve\n try {\n const require = createRequire(import.meta.url);\n const pkgPath = require.resolve(\"@agent-wall/dashboard/package.json\");\n const distDir = path.join(path.dirname(pkgPath), \"dist\");\n if (fs.existsSync(path.join(distDir, \"index.html\"))) {\n return distDir;\n }\n } catch {\n // Not installed or not built\n }\n\n // 3. Fallback: check monorepo path\n const monorepoDist = path.resolve(\n path.dirname(new URL(import.meta.url).pathname),\n \"../../dashboard/dist\"\n );\n if (fs.existsSync(path.join(monorepoDist, \"index.html\"))) {\n return monorepoDist;\n }\n\n return undefined;\n}\n","/**\n * agent-wall init — Generate a starter agent-wall.yaml config.\n *\n * Usage:\n * agent-wall init\n * agent-wall init --path ./config/agent-wall.yaml\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { generateDefaultConfigYaml } from \"@agent-wall/core\";\nimport chalk from \"chalk\";\n\nexport interface InitOptions {\n path?: string;\n force?: boolean;\n}\n\nexport function initCommand(options: InitOptions): void {\n const outputPath = path.resolve(options.path ?? \"agent-wall.yaml\");\n\n if (fs.existsSync(outputPath) && !options.force) {\n process.stderr.write(\n chalk.yellow(\n `\\n⚠ File already exists: ${outputPath}\\n` +\n \" Use --force to overwrite.\\n\\n\"\n )\n );\n process.exit(1);\n }\n\n const content = generateDefaultConfigYaml();\n const dir = path.dirname(outputPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(outputPath, content, \"utf-8\");\n\n process.stderr.write(\n chalk.green(\"\\n✓ \") +\n chalk.white(\"Created \") +\n chalk.bold(outputPath) +\n \"\\n\\n\" +\n chalk.gray(\" Next steps:\\n\") +\n chalk.gray(\" 1. Edit the rules to fit your project\\n\") +\n chalk.gray(\" 2. Wrap your MCP server:\\n\\n\") +\n chalk.white(\n \" agent-wall wrap -- npx @modelcontextprotocol/server-filesystem /path\\n\"\n ) +\n \"\\n\"\n );\n}\n","/**\n * agent-wall test — Dry-run tool calls against your policy rules.\n *\n * Tests specific tool calls against your agent-wall.yaml without\n * actually running an MCP server. Great for validating rules.\n *\n * Usage:\n * agent-wall test --tool read_file --arg path=/home/.ssh/id_rsa\n * agent-wall test --tool shell_exec --arg command=\"curl https://evil.com\"\n * agent-wall test --tool list_directory --arg path=/home/user/project\n */\n\nimport { PolicyEngine, loadPolicy, type ToolCallParams } from \"@agent-wall/core\";\nimport chalk from \"chalk\";\n\nexport interface TestOptions {\n config?: string;\n tool: string;\n arg?: string[];\n}\n\nexport function testCommand(options: TestOptions): void {\n if (!options.tool) {\n process.stderr.write(\n chalk.red(\n \"Error: --tool is required.\\n\\n\" +\n \"Usage:\\n\" +\n ' agent-wall test --tool shell_exec --arg command=\"curl https://evil.com\"\\n' +\n \" agent-wall test --tool read_file --arg path=/home/.ssh/id_rsa\\n\"\n )\n );\n process.exit(1);\n }\n\n // Load policy\n const { config, filePath } = loadPolicy(options.config);\n const engine = new PolicyEngine(config);\n\n // Parse arguments\n const args: Record<string, unknown> = {};\n if (options.arg) {\n for (const a of options.arg) {\n const eqIndex = a.indexOf(\"=\");\n if (eqIndex === -1) {\n process.stderr.write(\n chalk.red(`Invalid argument format: \"${a}\" (expected key=value)\\n`)\n );\n process.exit(1);\n }\n args[a.slice(0, eqIndex)] = a.slice(eqIndex + 1);\n }\n }\n\n // Build tool call\n const toolCall: ToolCallParams = {\n name: options.tool,\n arguments: args,\n };\n\n // Evaluate\n const verdict = engine.evaluate(toolCall);\n\n // Display result\n process.stderr.write(\"\\n\");\n process.stderr.write(\n chalk.gray(\"Policy: \") +\n chalk.white(filePath ?? \"built-in defaults\") +\n \"\\n\"\n );\n process.stderr.write(\n chalk.gray(\"Tool: \") + chalk.white(toolCall.name) + \"\\n\"\n );\n process.stderr.write(\n chalk.gray(\"Args: \") +\n chalk.white(JSON.stringify(toolCall.arguments)) +\n \"\\n\"\n );\n process.stderr.write(\"\\n\");\n\n const actionColors = {\n allow: chalk.green,\n deny: chalk.red,\n prompt: chalk.yellow,\n };\n\n const actionSymbols = {\n allow: \"✓ ALLOWED\",\n deny: \"✗ DENIED\",\n prompt: \"? PROMPT\",\n };\n\n const color = actionColors[verdict.action];\n process.stderr.write(\n color(` ${actionSymbols[verdict.action]}`) + \"\\n\"\n );\n if (verdict.rule) {\n process.stderr.write(\n chalk.gray(\" Rule: \") + chalk.white(verdict.rule) + \"\\n\"\n );\n }\n process.stderr.write(\n chalk.gray(\" Message: \") + chalk.white(verdict.message) + \"\\n\"\n );\n process.stderr.write(\"\\n\");\n\n // Exit with appropriate code\n process.exit(verdict.action === \"deny\" ? 1 : 0);\n}\n","/**\n * agent-wall audit — Display and analyze audit logs.\n *\n * Usage:\n * agent-wall audit --log ./agent-wall.log\n * agent-wall audit --log ./agent-wall.log --filter denied\n * agent-wall audit --log ./agent-wall.log --last 20\n */\n\nimport * as fs from \"node:fs\";\nimport chalk from \"chalk\";\nimport type { AuditEntry } from \"@agent-wall/core\";\n\nexport interface AuditOptions {\n log: string;\n filter?: \"allowed\" | \"denied\" | \"prompted\" | \"all\";\n last?: number;\n json?: boolean;\n}\n\nexport function auditCommand(options: AuditOptions): void {\n if (!options.log) {\n process.stderr.write(\n chalk.red(\n \"Error: --log is required.\\n\\n\" +\n \"Usage:\\n\" +\n \" agent-wall audit --log ./agent-wall.log\\n\" +\n \" agent-wall audit --log ./agent-wall.log --filter denied\\n\"\n )\n );\n process.exit(1);\n }\n\n if (!fs.existsSync(options.log)) {\n process.stderr.write(\n chalk.red(`Error: Log file not found: ${options.log}\\n`)\n );\n process.exit(1);\n }\n\n // Read and parse log entries (JSON lines format)\n const content = fs.readFileSync(options.log, \"utf-8\");\n const lines = content.trim().split(\"\\n\").filter(Boolean);\n\n let entries: AuditEntry[] = [];\n for (const line of lines) {\n try {\n entries.push(JSON.parse(line));\n } catch {\n // Skip malformed lines\n }\n }\n\n // Apply filters\n // Filter entries by action\n const filterAction = options.filter ?? \"all\";\n if (filterAction !== \"all\") {\n const actionMap: Record<string, string> = {\n denied: \"deny\",\n allowed: \"allow\",\n prompted: \"prompt\",\n };\n const targetAction = actionMap[filterAction];\n entries = entries.filter((e) => e.verdict?.action === targetAction);\n }\n\n // Apply --last limit\n if (options.last && options.last > 0) {\n entries = entries.slice(-options.last);\n }\n\n if (entries.length === 0) {\n process.stderr.write(chalk.gray(\"\\n No matching audit entries found.\\n\\n\"));\n process.exit(0);\n }\n\n // JSON output mode\n if (options.json) {\n process.stdout.write(JSON.stringify(entries, null, 2) + \"\\n\");\n process.exit(0);\n }\n\n // Pretty output\n process.stderr.write(\"\\n\");\n process.stderr.write(\n chalk.cyan(\"─── Agent Wall Audit Log ─────────────────────────\\n\")\n );\n process.stderr.write(\n chalk.gray(` File: ${options.log} | Entries: ${entries.length}\\n`)\n );\n process.stderr.write(\n chalk.cyan(\"─────────────────────────────────────────────────\\n\\n\")\n );\n\n const actionColors: Record<string, (s: string) => string> = {\n allow: chalk.green,\n deny: chalk.red,\n prompt: chalk.yellow,\n };\n\n const actionIcons: Record<string, string> = {\n allow: \"✓\",\n deny: \"✗\",\n prompt: \"?\",\n };\n\n for (const entry of entries) {\n const action = entry.verdict?.action ?? \"unknown\";\n const color = actionColors[action] ?? chalk.gray;\n const icon = actionIcons[action] ?? \"·\";\n const time = entry.timestamp\n ? new Date(entry.timestamp).toLocaleTimeString()\n : \"??:??:??\";\n\n process.stderr.write(\n chalk.gray(` ${time} `) +\n color(`${icon} ${(action.toUpperCase()).padEnd(6)} `) +\n chalk.white(entry.tool ?? entry.method ?? \"unknown\") +\n \"\\n\"\n );\n\n if (entry.arguments && Object.keys(entry.arguments).length > 0) {\n const argStr = JSON.stringify(entry.arguments, null, 0);\n process.stderr.write(\n chalk.gray(` Args: ${argStr.slice(0, 100)}${argStr.length > 100 ? \"...\" : \"\"}`) +\n \"\\n\"\n );\n }\n\n if (entry.verdict?.rule) {\n process.stderr.write(\n chalk.gray(` Rule: ${entry.verdict.rule}`) + \"\\n\"\n );\n }\n process.stderr.write(\"\\n\");\n }\n\n // Summary\n const stats = {\n allowed: entries.filter((e) => e.verdict?.action === \"allow\").length,\n denied: entries.filter((e) => e.verdict?.action === \"deny\").length,\n prompted: entries.filter((e) => e.verdict?.action === \"prompt\").length,\n };\n process.stderr.write(\n chalk.cyan(\"─── Summary ────────────────────────────────────\\n\")\n );\n process.stderr.write(\n chalk.green(` Allowed: ${stats.allowed}`) +\n \" \" +\n chalk.red(`Denied: ${stats.denied}`) +\n \" \" +\n chalk.yellow(`Prompted: ${stats.prompted}`) +\n \"\\n\"\n );\n process.stderr.write(\n chalk.cyan(\"─────────────────────────────────────────────────\\n\\n\")\n );\n}\n","/**\n * agent-wall scan — Scan your current MCP configuration for security risks.\n *\n * Reads Claude Code / Cursor MCP config files and reports\n * which servers have unrestricted access and would benefit from Agent Wall.\n *\n * Usage:\n * agent-wall scan\n * agent-wall scan --config ~/.claude/mcp_servers.json\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport chalk from \"chalk\";\n\nexport interface ScanOptions {\n config?: string;\n json?: boolean;\n}\n\ninterface McpServerConfig {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n}\n\n/**\n * Known risky MCP tool patterns based on the official MCP servers ecosystem.\n * Sources: modelcontextprotocol/servers registry + popular third-party servers.\n */\nconst RISKY_TOOLS = [\n // ── Execution (Critical) ──────────────────────────────────────────\n { pattern: \"shell\", risk: \"critical\", reason: \"Arbitrary shell command execution\" },\n { pattern: \"bash\", risk: \"critical\", reason: \"Arbitrary bash execution\" },\n { pattern: \"exec\", risk: \"critical\", reason: \"Process execution\" },\n { pattern: \"terminal\", risk: \"critical\", reason: \"Terminal access\" },\n\n // ── Filesystem ────────────────────────────────────────────────────\n { pattern: \"filesystem\", risk: \"high\", reason: \"Full filesystem read/write access\" },\n\n // ── Browser / Web ─────────────────────────────────────────────────\n { pattern: \"playwright\", risk: \"high\", reason: \"Browser automation via Playwright (prompt injection target)\" },\n { pattern: \"puppeteer\", risk: \"high\", reason: \"Browser automation via Puppeteer (prompt injection target)\" },\n { pattern: \"browser\", risk: \"high\", reason: \"Browser automation (prompt injection target)\" },\n { pattern: \"fetch\", risk: \"medium\", reason: \"Outbound HTTP requests (exfiltration vector)\" },\n\n // ── Source Control ────────────────────────────────────────────────\n { pattern: \"github\", risk: \"medium\", reason: \"GitHub API access (code/issues/PRs)\" },\n { pattern: \"gitlab\", risk: \"medium\", reason: \"GitLab API access (code/issues/MRs)\" },\n { pattern: \"git\", risk: \"medium\", reason: \"Repository access and modification\" },\n\n // ── Databases ─────────────────────────────────────────────────────\n { pattern: \"postgres\", risk: \"high\", reason: \"PostgreSQL access\" },\n { pattern: \"mysql\", risk: \"high\", reason: \"MySQL database access\" },\n { pattern: \"mongodb\", risk: \"high\", reason: \"MongoDB database access\" },\n { pattern: \"redis\", risk: \"medium\", reason: \"Redis data store access\" },\n { pattern: \"sqlite\", risk: \"medium\", reason: \"SQLite database access\" },\n { pattern: \"supabase\", risk: \"high\", reason: \"Supabase database/auth/storage access\" },\n { pattern: \"neon\", risk: \"high\", reason: \"Neon serverless Postgres access\" },\n { pattern: \"snowflake\", risk: \"high\", reason: \"Snowflake data warehouse access\" },\n { pattern: \"database\", risk: \"high\", reason: \"Database query execution\" },\n\n // ── Infrastructure / Cloud ────────────────────────────────────────\n { pattern: \"docker\", risk: \"critical\", reason: \"Container management\" },\n { pattern: \"kubernetes\", risk: \"critical\", reason: \"Cluster management\" },\n { pattern: \"terraform\", risk: \"critical\", reason: \"Infrastructure-as-code provisioning\" },\n { pattern: \"aws\", risk: \"critical\", reason: \"AWS cloud resource access\" },\n { pattern: \"gcp\", risk: \"critical\", reason: \"Google Cloud resource access\" },\n { pattern: \"azure\", risk: \"critical\", reason: \"Azure cloud resource access\" },\n { pattern: \"cloudflare\", risk: \"high\", reason: \"Cloudflare infrastructure management\" },\n { pattern: \"vercel\", risk: \"high\", reason: \"Vercel deployment/project management\" },\n { pattern: \"netlify\", risk: \"high\", reason: \"Netlify deployment/project management\" },\n\n // ── Communication ─────────────────────────────────────────────────\n { pattern: \"slack\", risk: \"medium\", reason: \"Slack workspace messaging access\" },\n { pattern: \"email\", risk: \"medium\", reason: \"Email send/read access\" },\n { pattern: \"gmail\", risk: \"medium\", reason: \"Gmail account access\" },\n { pattern: \"discord\", risk: \"medium\", reason: \"Discord messaging access\" },\n\n // ── Payment / Secrets ─────────────────────────────────────────────\n { pattern: \"stripe\", risk: \"critical\", reason: \"Payment processing / financial data\" },\n { pattern: \"razorpay\", risk: \"critical\", reason: \"Payment processing / financial data\" },\n { pattern: \"vault\", risk: \"critical\", reason: \"Secret management (HashiCorp Vault)\" },\n { pattern: \"1password\", risk: \"critical\", reason: \"Password manager access\" },\n\n // ── Remote Access ─────────────────────────────────────────────────\n { pattern: \"ssh\", risk: \"critical\", reason: \"Remote server access via SSH\" },\n { pattern: \"rdp\", risk: \"critical\", reason: \"Remote desktop access\" },\n\n // ── AI / LLM ──────────────────────────────────────────────────────\n { pattern: \"openai\", risk: \"medium\", reason: \"OpenAI API access (cost / data)\" },\n { pattern: \"anthropic\", risk: \"medium\", reason: \"Anthropic API access (cost / data)\" },\n];\n\nfunction detectConfigPaths(): string[] {\n const home = os.homedir();\n const platform = os.platform();\n const candidates: string[] = [\n // ── Claude ────────────────────────────────────────────────────\n // Claude Code\n path.join(home, \".claude\", \"mcp_servers.json\"),\n // Claude Desktop (macOS)\n path.join(home, \"Library\", \"Application Support\", \"Claude\", \"claude_desktop_config.json\"),\n // Claude Desktop (Windows)\n path.join(home, \"AppData\", \"Roaming\", \"Claude\", \"claude_desktop_config.json\"),\n // Claude Desktop (Linux)\n path.join(home, \".config\", \"Claude\", \"claude_desktop_config.json\"),\n\n // ── Cursor ────────────────────────────────────────────────────\n path.join(home, \".cursor\", \"mcp.json\"),\n\n // ── VS Code / Copilot ─────────────────────────────────────────\n // VS Code workspace-level MCP (vscode 1.99+)\n path.join(process.cwd(), \".vscode\", \"mcp.json\"),\n\n // ── Windsurf (Codeium) ────────────────────────────────────────\n path.join(home, \".codeium\", \"windsurf\", \"mcp_config.json\"),\n\n // ── Cline ─────────────────────────────────────────────────────\n path.join(home, \".cline\", \"mcp_settings.json\"),\n\n // ── Continue.dev ──────────────────────────────────────────────\n path.join(home, \".continue\", \"config.json\"),\n\n // ── Generic / project-level ───────────────────────────────────\n path.join(process.cwd(), \".mcp.json\"),\n path.join(process.cwd(), \"mcp.json\"),\n ];\n\n // VS Code user-level settings (platform-aware)\n if (platform === \"win32\") {\n candidates.push(\n path.join(home, \"AppData\", \"Roaming\", \"Code\", \"User\", \"settings.json\")\n );\n } else if (platform === \"darwin\") {\n candidates.push(\n path.join(home, \"Library\", \"Application Support\", \"Code\", \"User\", \"settings.json\")\n );\n } else {\n candidates.push(\n path.join(home, \".config\", \"Code\", \"User\", \"settings.json\")\n );\n }\n\n return candidates.filter((p) => fs.existsSync(p));\n}\n\nexport function scanCommand(options: ScanOptions): void {\n let configPaths: string[] = [];\n\n if (options.config) {\n if (!fs.existsSync(options.config)) {\n process.stderr.write(\n chalk.red(`Error: Config not found: ${options.config}\\n`)\n );\n process.exit(1);\n }\n configPaths = [options.config];\n } else {\n configPaths = detectConfigPaths();\n }\n\n if (configPaths.length === 0) {\n if (options.json) {\n process.stdout.write(JSON.stringify({ servers: [], totalRisks: 0 }, null, 2) + \"\\n\");\n } else {\n process.stderr.write(\n chalk.yellow(\n \"\\n⚠ No MCP configuration files found.\\n\\n\" +\n chalk.gray(\n \" Looked for config files from:\\n\" +\n \" • Claude Code ~/.claude/mcp_servers.json\\n\" +\n \" • Claude Desktop ~/Library/.../claude_desktop_config.json\\n\" +\n \" • Cursor ~/.cursor/mcp.json\\n\" +\n \" • VS Code / Copilot .vscode/mcp.json\\n\" +\n \" • Windsurf ~/.codeium/windsurf/mcp_config.json\\n\" +\n \" • Cline ~/.cline/mcp_settings.json\\n\" +\n \" • Continue.dev ~/.continue/config.json\\n\" +\n \" • Project-level .mcp.json, mcp.json\\n\\n\" +\n \" Tip: pass --config <path> to scan a specific file.\\n\\n\"\n )\n )\n );\n }\n process.exit(0);\n }\n\n // ── Collect results ──────────────────────────────────────────────\n interface ServerResult {\n name: string;\n configFile: string;\n command: string;\n protected: boolean;\n risks: Array<{ level: string; reason: string }>;\n }\n\n const results: ServerResult[] = [];\n let totalRisks = 0;\n\n for (const configPath of configPaths) {\n let raw: Record<string, unknown>;\n try {\n const content = fs.readFileSync(configPath, \"utf-8\");\n raw = JSON.parse(content);\n } catch {\n if (!options.json) {\n process.stderr.write(\n chalk.red(` Failed to parse: ${configPath}\\n\\n`)\n );\n }\n continue;\n }\n\n const servers: Record<string, McpServerConfig> =\n (raw.mcpServers as Record<string, McpServerConfig>) ??\n (raw as Record<string, McpServerConfig>);\n\n for (const [name, config] of Object.entries(servers)) {\n if (!config || typeof config !== \"object\" || !config.command) continue;\n\n const serverStr = `${config.command} ${(config.args ?? []).join(\" \")}`;\n const risks: Array<{ level: string; reason: string }> = [];\n\n for (const risky of RISKY_TOOLS) {\n if (serverStr.toLowerCase().includes(risky.pattern)) {\n risks.push({ level: risky.risk, reason: risky.reason });\n }\n }\n\n const isProtected = serverStr.includes(\"agent-wall\");\n if (!isProtected) totalRisks += risks.length;\n\n results.push({\n name,\n configFile: configPath,\n command: serverStr,\n protected: isProtected,\n risks,\n });\n }\n }\n\n // ── JSON output ──────────────────────────────────────────────────\n if (options.json) {\n process.stdout.write(\n JSON.stringify({ servers: results, totalRisks }, null, 2) + \"\\n\"\n );\n return;\n }\n\n // ── Pretty output ────────────────────────────────────────────────\n process.stderr.write(\"\\n\");\n process.stderr.write(\n chalk.cyan(\"─── Agent Wall Security Scan ─────────────────────\\n\\n\")\n );\n\n let lastConfig = \"\";\n for (const server of results) {\n if (server.configFile !== lastConfig) {\n lastConfig = server.configFile;\n process.stderr.write(\n chalk.gray(\" Config: \") + chalk.white(server.configFile) + \"\\n\\n\"\n );\n }\n\n const riskColor =\n server.risks.some((r) => r.level === \"critical\")\n ? chalk.red\n : server.risks.some((r) => r.level === \"high\")\n ? chalk.yellow\n : server.risks.length > 0\n ? chalk.gray\n : chalk.green;\n\n const statusIcon = server.protected\n ? chalk.green(\"🛡\")\n : server.risks.length > 0\n ? chalk.red(\"⚠\")\n : chalk.green(\"✓\");\n\n process.stderr.write(\n ` ${statusIcon} ${chalk.bold.white(server.name)}\\n`\n );\n process.stderr.write(\n chalk.gray(` Command: ${server.command.slice(0, 80)}\\n`)\n );\n\n if (server.protected) {\n process.stderr.write(\n chalk.green(\" Protected by Agent Wall ✓\\n\")\n );\n } else if (server.risks.length > 0) {\n for (const risk of server.risks) {\n process.stderr.write(\n riskColor(\n ` ${risk.level.toUpperCase()}: ${risk.reason}\\n`\n )\n );\n }\n process.stderr.write(\n chalk.gray(\n ` Fix: agent-wall wrap -- ${server.command}\\n`\n )\n );\n } else {\n process.stderr.write(chalk.green(\" No known risks detected\\n\"));\n }\n process.stderr.write(\"\\n\");\n }\n\n // Summary\n process.stderr.write(\n chalk.cyan(\"─── Scan Results ───────────────────────────────\\n\")\n );\n process.stderr.write(\n chalk.gray(\" MCP Servers: \") + chalk.white(String(results.length)) + \"\\n\"\n );\n process.stderr.write(\n chalk.gray(\" Risks found: \") +\n (totalRisks > 0\n ? chalk.red(String(totalRisks))\n : chalk.green(\"0\")) +\n \"\\n\"\n );\n process.stderr.write(\n chalk.cyan(\"─────────────────────────────────────────────────\\n\\n\")\n );\n\n if (totalRisks > 0) {\n process.stderr.write(\n chalk.yellow(\n \" Run 'agent-wall init' to create a policy config,\\n\" +\n \" then wrap your servers with 'agent-wall wrap'.\\n\\n\"\n )\n );\n }\n}\n","/**\n * agent-wall validate — Validate a policy configuration file.\n *\n * Checks the YAML syntax, Zod schema validation, and reports\n * any issues with your policy rules.\n *\n * Usage:\n * agent-wall validate\n * agent-wall validate --config ./custom-policy.yaml\n */\n\nimport { loadPolicy, PolicyEngine } from \"@agent-wall/core\";\nimport chalk from \"chalk\";\n\nexport interface ValidateOptions {\n config?: string;\n}\n\nexport function validateCommand(options: ValidateOptions): void {\n process.stderr.write(\"\\n\");\n process.stderr.write(\n chalk.cyan(\"─── Agent Wall Config Validation ─────────────────\\n\\n\")\n );\n\n let config;\n let filePath: string | null | undefined;\n\n try {\n const result = loadPolicy(options.config);\n config = result.config;\n filePath = result.filePath;\n } catch (error: any) {\n process.stderr.write(\n chalk.red(\" ✗ \") + chalk.red(`Failed to load config: ${error.message}\\n\\n`)\n );\n process.exit(1);\n }\n\n process.stderr.write(\n chalk.green(\" ✓ \") +\n chalk.white(\"Config loaded: \") +\n chalk.gray(filePath ?? \"built-in defaults\") +\n \"\\n\"\n );\n\n // Validate version\n if (config.version !== 1) {\n process.stderr.write(\n chalk.yellow(\" ⚠ \") +\n chalk.yellow(`Unknown config version: ${config.version} (expected 1)\\n`)\n );\n } else {\n process.stderr.write(\n chalk.green(\" ✓ \") + chalk.white(\"Version: 1\\n\")\n );\n }\n\n // Validate default action\n const validActions = [\"allow\", \"deny\", \"prompt\"];\n if (!config.defaultAction || !validActions.includes(config.defaultAction)) {\n process.stderr.write(\n chalk.red(\" ✗ \") +\n chalk.red(`Invalid defaultAction: \"${config.defaultAction}\" (expected: allow, deny, prompt)\\n`)\n );\n } else {\n process.stderr.write(\n chalk.green(\" ✓ \") +\n chalk.white(`Default action: ${config.defaultAction}\\n`)\n );\n }\n\n // Validate global rate limit\n if (config.globalRateLimit) {\n if (config.globalRateLimit.maxCalls <= 0) {\n process.stderr.write(\n chalk.yellow(\" ⚠ \") +\n chalk.yellow(\"Global rate limit maxCalls should be > 0\\n\")\n );\n } else {\n process.stderr.write(\n chalk.green(\" ✓ \") +\n chalk.white(\n `Global rate limit: ${config.globalRateLimit.maxCalls} calls / ${config.globalRateLimit.windowSeconds}s\\n`\n )\n );\n }\n }\n\n // Validate rules\n process.stderr.write(\n chalk.green(\" ✓ \") +\n chalk.white(`Rules: ${config.rules.length} loaded\\n`)\n );\n\n let warnings = 0;\n let errors = 0;\n const ruleNames = new Set<string>();\n\n for (let i = 0; i < config.rules.length; i++) {\n const rule = config.rules[i];\n const label = rule.name ?? `rule[${i}]`;\n\n // Check for duplicate names\n if (rule.name) {\n if (ruleNames.has(rule.name)) {\n process.stderr.write(\n chalk.yellow(\" ⚠ \") +\n chalk.yellow(`Duplicate rule name: \"${rule.name}\"\\n`)\n );\n warnings++;\n }\n ruleNames.add(rule.name);\n } else {\n process.stderr.write(\n chalk.yellow(\" ⚠ \") +\n chalk.yellow(`Rule at index ${i} has no name (recommended for audit logs)\\n`)\n );\n warnings++;\n }\n\n // Check action validity\n if (!validActions.includes(rule.action)) {\n process.stderr.write(\n chalk.red(\" ✗ \") +\n chalk.red(`${label}: invalid action \"${rule.action}\"\\n`)\n );\n errors++;\n }\n\n // Check tool pattern syntax (basic validation)\n if (rule.tool && rule.tool.includes(\" \")) {\n process.stderr.write(\n chalk.yellow(\" ⚠ \") +\n chalk.yellow(`${label}: tool pattern contains spaces — use \"|\" to separate alternatives\\n`)\n );\n warnings++;\n }\n\n // Check rate limit\n if (rule.rateLimit) {\n if (rule.rateLimit.maxCalls <= 0 || rule.rateLimit.windowSeconds <= 0) {\n process.stderr.write(\n chalk.yellow(\" ⚠ \") +\n chalk.yellow(`${label}: rate limit values should be > 0\\n`)\n );\n warnings++;\n }\n }\n }\n\n // Test that the engine initializes correctly\n try {\n new PolicyEngine(config);\n process.stderr.write(\n chalk.green(\" ✓ \") + chalk.white(\"Policy engine: OK\\n\")\n );\n } catch (error: any) {\n process.stderr.write(\n chalk.red(\" ✗ \") +\n chalk.red(`Policy engine failed: ${error.message}\\n`)\n );\n errors++;\n }\n\n // Summary\n process.stderr.write(\"\\n\");\n if (errors > 0) {\n process.stderr.write(\n chalk.red(` ${errors} error(s), ${warnings} warning(s)\\n\\n`)\n );\n process.exit(1);\n } else if (warnings > 0) {\n process.stderr.write(\n chalk.yellow(` ${warnings} warning(s), 0 errors — config is valid\\n\\n`)\n );\n } else {\n process.stderr.write(\n chalk.green(\" ✓ Config is valid — no issues found\\n\\n\")\n );\n }\n}\n","/**\n * agent-wall doctor — Health check for your Agent Wall setup.\n *\n * Verifies: config file exists & is valid, Node.js version,\n * MCP clients detected, and whether servers are wrapped.\n *\n * Usage:\n * agent-wall doctor\n * agent-wall doctor --config ./agent-wall.yaml\n */\n\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport chalk from \"chalk\";\nimport { loadPolicy, PolicyEngine } from \"@agent-wall/core\";\n\nexport interface DoctorOptions {\n config?: string;\n}\n\ninterface CheckResult {\n label: string;\n ok: boolean;\n detail: string;\n}\n\nexport function doctorCommand(options: DoctorOptions): void {\n const checks: CheckResult[] = [];\n\n process.stderr.write(\"\\n\");\n process.stderr.write(\n chalk.cyan(\"─── Agent Wall Doctor ───────────────────────────\\n\\n\")\n );\n\n // ── Check 1: Node.js version ────────────────────────────────────\n const nodeVersion = process.versions.node;\n const [major] = nodeVersion.split(\".\").map(Number);\n checks.push({\n label: \"Node.js version\",\n ok: major >= 18,\n detail: `v${nodeVersion}${major < 18 ? \" (requires >= 18)\" : \"\"}`,\n });\n\n // ── Check 2: Config file ────────────────────────────────────────\n let configOk = false;\n let configDetail = \"\";\n let ruleCount = 0;\n try {\n const { config, filePath } = loadPolicy(options.config);\n configOk = true;\n ruleCount = config.rules.length;\n configDetail = `${filePath ?? \"built-in defaults\"} (${ruleCount} rules)`;\n\n // Also validate the engine can initialize\n const engine = new PolicyEngine(config);\n engine.evaluate({ name: \"__doctor_test__\", arguments: {} });\n } catch (err) {\n configDetail = err instanceof Error ? err.message : String(err);\n }\n checks.push({\n label: \"Policy config\",\n ok: configOk,\n detail: configDetail,\n });\n\n // ── Check 3: MCP client configs detected ────────────────────────\n const home = os.homedir();\n const platform = os.platform();\n const mcpClients: Array<{ name: string; path: string }> = [\n { name: \"Claude Code\", path: path.join(home, \".claude\", \"mcp_servers.json\") },\n { name: \"Cursor\", path: path.join(home, \".cursor\", \"mcp.json\") },\n { name: \"VS Code\", path: path.join(process.cwd(), \".vscode\", \"mcp.json\") },\n { name: \"Windsurf\", path: path.join(home, \".codeium\", \"windsurf\", \"mcp_config.json\") },\n { name: \"Cline\", path: path.join(home, \".cline\", \"mcp_settings.json\") },\n ];\n if (platform === \"win32\") {\n mcpClients.push({\n name: \"Claude Desktop\",\n path: path.join(home, \"AppData\", \"Roaming\", \"Claude\", \"claude_desktop_config.json\"),\n });\n } else if (platform === \"darwin\") {\n mcpClients.push({\n name: \"Claude Desktop\",\n path: path.join(home, \"Library\", \"Application Support\", \"Claude\", \"claude_desktop_config.json\"),\n });\n } else {\n mcpClients.push({\n name: \"Claude Desktop\",\n path: path.join(home, \".config\", \"Claude\", \"claude_desktop_config.json\"),\n });\n }\n\n const detected = mcpClients.filter((c) => fs.existsSync(c.path));\n checks.push({\n label: \"MCP clients found\",\n ok: detected.length > 0,\n detail:\n detected.length > 0\n ? detected.map((c) => c.name).join(\", \")\n : \"None detected (run 'agent-wall scan' for details)\",\n });\n\n // ── Check 4: Environment variables ──────────────────────────────\n const envVars: string[] = [];\n if (process.env.AGENT_WALL_CONFIG) envVars.push(\"AGENT_WALL_CONFIG\");\n if (process.env.AGENT_WALL_LOG) envVars.push(\"AGENT_WALL_LOG\");\n checks.push({\n label: \"Env overrides\",\n ok: true, // Always \"ok\" — just informational\n detail: envVars.length > 0 ? envVars.join(\", \") : \"None set\",\n });\n\n // ── Print results ───────────────────────────────────────────────\n for (const check of checks) {\n const icon = check.ok ? chalk.green(\"✓\") : chalk.red(\"✗\");\n process.stderr.write(\n ` ${icon} ${chalk.bold.white(check.label)}\\n`\n );\n process.stderr.write(chalk.gray(` ${check.detail}\\n\\n`));\n }\n\n // ── Summary ─────────────────────────────────────────────────────\n const failures = checks.filter((c) => !c.ok);\n process.stderr.write(\n chalk.cyan(\"─── Summary ────────────────────────────────────\\n\")\n );\n if (failures.length === 0) {\n process.stderr.write(\n chalk.green(\" All checks passed. Agent Wall is ready.\\n\\n\")\n );\n } else {\n process.stderr.write(\n chalk.yellow(\n ` ${failures.length} issue(s) found:\\n`\n )\n );\n for (const f of failures) {\n process.stderr.write(chalk.yellow(` • ${f.label}: ${f.detail}\\n`));\n }\n process.stderr.write(\"\\n\");\n }\n process.stderr.write(\n chalk.cyan(\"─────────────────────────────────────────────────\\n\\n\")\n );\n}\n"],"mappings":"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","names":["require","fs","path","chalk","PolicyEngine","loadPolicy","chalk","fs","chalk","fs","path","chalk","platform","loadPolicy","PolicyEngine","chalk","fs","os","path","chalk","loadPolicy","PolicyEngine","platform"]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@agent-wall/cli",
3
+ "version": "0.1.0",
4
+ "description": "Security firewall for AI agents — intercept MCP tool calls, enforce policies, block attacks",
5
+ "license": "MIT",
6
+ "author": "Yalelet Dessalegn",
7
+ "homepage": "https://agent-wall.github.io/agent-wall/",
8
+ "bugs": {
9
+ "url": "https://github.com/agent-wall/agent-wall/issues"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/agent-wall/agent-wall"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "bin": {
19
+ "agent-wall": "./dist/index.js"
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "import": "./dist/index.js",
24
+ "types": "./dist/index.d.ts"
25
+ }
26
+ },
27
+ "dependencies": {
28
+ "commander": "^14.0.0",
29
+ "chalk": "^5.4.1",
30
+ "@agent-wall/core": "0.1.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^22.16.4",
34
+ "tsup": "^8.5.1",
35
+ "typescript": "^5.9.3",
36
+ "vitest": "^3.2.4"
37
+ },
38
+ "keywords": [
39
+ "agent-wall",
40
+ "mcp",
41
+ "ai-security",
42
+ "firewall",
43
+ "proxy",
44
+ "model-context-protocol",
45
+ "agent-security",
46
+ "llm",
47
+ "prompt-injection"
48
+ ],
49
+ "scripts": {
50
+ "build": "tsup && node -e \"const fs=require('fs'),p=require('path');const s=p.resolve(__dirname,'../dashboard/dist');const d=p.resolve(__dirname,'dist/dashboard');if(fs.existsSync(s)){fs.cpSync(s,d,{recursive:true});console.log('✓ Dashboard assets bundled')}else{console.log('⚠ Dashboard not built, skipping asset bundle')}\"",
51
+ "dev": "tsup --watch",
52
+ "test": "vitest run --passWithNoTests",
53
+ "test:watch": "vitest",
54
+ "typecheck": "tsc --noEmit",
55
+ "clean": "rm -rf dist"
56
+ }
57
+ }
@@ -0,0 +1,175 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import * as fs from "node:fs";
3
+ import { auditCommand } from "./audit.js";
4
+ import type { AuditEntry } from "@agent-wall/core";
5
+
6
+ vi.mock("node:fs");
7
+
8
+ const mockFs = vi.mocked(fs);
9
+
10
+ function makeEntry(overrides: Partial<AuditEntry> = {}): AuditEntry {
11
+ return {
12
+ timestamp: "2026-01-15T10:30:00.000Z",
13
+ sessionId: "test-session-1",
14
+ direction: "request",
15
+ method: "tools/call",
16
+ tool: "read_file",
17
+ arguments: { path: "/home/user/file.txt" },
18
+ verdict: { action: "allow", rule: "allow-reads", message: "Matched rule" },
19
+ ...overrides,
20
+ };
21
+ }
22
+
23
+ function makeLogContent(entries: AuditEntry[]): string {
24
+ return entries.map((e) => JSON.stringify(e)).join("\n");
25
+ }
26
+
27
+ describe("auditCommand", () => {
28
+ let exitSpy: any;
29
+ let stderrSpy: any;
30
+ let stdoutSpy: any;
31
+
32
+ beforeEach(() => {
33
+ exitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
34
+ throw new Error("process.exit");
35
+ });
36
+ stderrSpy = vi.spyOn(process.stderr, "write").mockReturnValue(true);
37
+ stdoutSpy = vi.spyOn(process.stdout, "write").mockReturnValue(true);
38
+ });
39
+
40
+ afterEach(() => {
41
+ vi.restoreAllMocks();
42
+ });
43
+
44
+ it("exits with error when --log is missing", () => {
45
+ expect(() =>
46
+ auditCommand({ log: "" })
47
+ ).toThrow("process.exit");
48
+ expect(exitSpy).toHaveBeenCalledWith(1);
49
+ });
50
+
51
+ it("exits with error when log file does not exist", () => {
52
+ mockFs.existsSync.mockReturnValue(false);
53
+ expect(() =>
54
+ auditCommand({ log: "./nonexistent.log" })
55
+ ).toThrow("process.exit");
56
+ expect(exitSpy).toHaveBeenCalledWith(1);
57
+ const allOutput = stderrSpy.mock.calls.map((c: any) => c[0]).join("");
58
+ expect(allOutput).toContain("not found");
59
+ });
60
+
61
+ it("displays audit entries in pretty format", () => {
62
+ const entries = [
63
+ makeEntry(),
64
+ makeEntry({
65
+ tool: "shell_exec",
66
+ verdict: { action: "deny", rule: "block-shell", message: "Blocked" },
67
+ }),
68
+ ];
69
+ mockFs.existsSync.mockReturnValue(true);
70
+ mockFs.readFileSync.mockReturnValue(makeLogContent(entries));
71
+
72
+ auditCommand({ log: "./test.log" });
73
+
74
+ const allOutput = stderrSpy.mock.calls.map((c: any) => c[0]).join("");
75
+ expect(allOutput).toContain("Audit Log");
76
+ expect(allOutput).toContain("read_file");
77
+ expect(allOutput).toContain("shell_exec");
78
+ expect(allOutput).toContain("Summary");
79
+ });
80
+
81
+ it("filters by denied entries", () => {
82
+ const entries = [
83
+ makeEntry({ tool: "read_file", verdict: { action: "allow", rule: null, message: "" } }),
84
+ makeEntry({
85
+ tool: "shell_exec",
86
+ verdict: { action: "deny", rule: "block-shell", message: "Blocked" },
87
+ }),
88
+ makeEntry({
89
+ tool: "write_file",
90
+ verdict: { action: "deny", rule: "block-writes", message: "Blocked" },
91
+ }),
92
+ ];
93
+ mockFs.existsSync.mockReturnValue(true);
94
+ mockFs.readFileSync.mockReturnValue(makeLogContent(entries));
95
+
96
+ auditCommand({ log: "./test.log", filter: "denied" });
97
+
98
+ const allOutput = stderrSpy.mock.calls.map((c: any) => c[0]).join("");
99
+ // Should show 2 denied entries, not the allowed one
100
+ expect(allOutput).toContain("Entries: 2");
101
+ });
102
+
103
+ it("applies --last limit", () => {
104
+ const entries = Array.from({ length: 10 }, (_, i) =>
105
+ makeEntry({ tool: `tool_${i}` })
106
+ );
107
+ mockFs.existsSync.mockReturnValue(true);
108
+ mockFs.readFileSync.mockReturnValue(makeLogContent(entries));
109
+
110
+ auditCommand({ log: "./test.log", last: 3 });
111
+
112
+ const allOutput = stderrSpy.mock.calls.map((c: any) => c[0]).join("");
113
+ expect(allOutput).toContain("Entries: 3");
114
+ });
115
+
116
+ it("outputs JSON when --json flag is set", () => {
117
+ const entries = [makeEntry()];
118
+ mockFs.existsSync.mockReturnValue(true);
119
+ mockFs.readFileSync.mockReturnValue(makeLogContent(entries));
120
+
121
+ expect(() =>
122
+ auditCommand({ log: "./test.log", json: true })
123
+ ).toThrow("process.exit");
124
+ expect(exitSpy).toHaveBeenCalledWith(0);
125
+
126
+ const jsonOutput = stdoutSpy.mock.calls.map((c: any) => c[0]).join("");
127
+ const parsed = JSON.parse(jsonOutput);
128
+ expect(Array.isArray(parsed)).toBe(true);
129
+ expect(parsed[0].tool).toBe("read_file");
130
+ });
131
+
132
+ it("shows empty state when no entries match filter", () => {
133
+ const entries = [
134
+ makeEntry({ verdict: { action: "allow", rule: null, message: "" } }),
135
+ ];
136
+ mockFs.existsSync.mockReturnValue(true);
137
+ mockFs.readFileSync.mockReturnValue(makeLogContent(entries));
138
+
139
+ expect(() =>
140
+ auditCommand({ log: "./test.log", filter: "denied" })
141
+ ).toThrow("process.exit");
142
+ expect(exitSpy).toHaveBeenCalledWith(0);
143
+ const allOutput = stderrSpy.mock.calls.map((c: any) => c[0]).join("");
144
+ expect(allOutput).toContain("No matching");
145
+ });
146
+
147
+ it("skips malformed JSON lines gracefully", () => {
148
+ const content = `${JSON.stringify(makeEntry())}\nINVALID_JSON\n${JSON.stringify(makeEntry({ tool: "write_file" }))}`;
149
+ mockFs.existsSync.mockReturnValue(true);
150
+ mockFs.readFileSync.mockReturnValue(content);
151
+
152
+ auditCommand({ log: "./test.log" });
153
+
154
+ const allOutput = stderrSpy.mock.calls.map((c: any) => c[0]).join("");
155
+ expect(allOutput).toContain("Entries: 2"); // Skips the invalid line
156
+ });
157
+
158
+ it("shows summary counts correctly", () => {
159
+ const entries = [
160
+ makeEntry({ verdict: { action: "allow", rule: null, message: "" } }),
161
+ makeEntry({ verdict: { action: "allow", rule: null, message: "" } }),
162
+ makeEntry({ verdict: { action: "deny", rule: "x", message: "" } }),
163
+ makeEntry({ verdict: { action: "prompt", rule: "y", message: "" } }),
164
+ ];
165
+ mockFs.existsSync.mockReturnValue(true);
166
+ mockFs.readFileSync.mockReturnValue(makeLogContent(entries));
167
+
168
+ auditCommand({ log: "./test.log" });
169
+
170
+ const allOutput = stderrSpy.mock.calls.map((c: any) => c[0]).join("");
171
+ expect(allOutput).toContain("Allowed: 2");
172
+ expect(allOutput).toContain("Denied: 1");
173
+ expect(allOutput).toContain("Prompted: 1");
174
+ });
175
+ });
@@ -0,0 +1,158 @@
1
+ /**
2
+ * agent-wall audit — Display and analyze audit logs.
3
+ *
4
+ * Usage:
5
+ * agent-wall audit --log ./agent-wall.log
6
+ * agent-wall audit --log ./agent-wall.log --filter denied
7
+ * agent-wall audit --log ./agent-wall.log --last 20
8
+ */
9
+
10
+ import * as fs from "node:fs";
11
+ import chalk from "chalk";
12
+ import type { AuditEntry } from "@agent-wall/core";
13
+
14
+ export interface AuditOptions {
15
+ log: string;
16
+ filter?: "allowed" | "denied" | "prompted" | "all";
17
+ last?: number;
18
+ json?: boolean;
19
+ }
20
+
21
+ export function auditCommand(options: AuditOptions): void {
22
+ if (!options.log) {
23
+ process.stderr.write(
24
+ chalk.red(
25
+ "Error: --log is required.\n\n" +
26
+ "Usage:\n" +
27
+ " agent-wall audit --log ./agent-wall.log\n" +
28
+ " agent-wall audit --log ./agent-wall.log --filter denied\n"
29
+ )
30
+ );
31
+ process.exit(1);
32
+ }
33
+
34
+ if (!fs.existsSync(options.log)) {
35
+ process.stderr.write(
36
+ chalk.red(`Error: Log file not found: ${options.log}\n`)
37
+ );
38
+ process.exit(1);
39
+ }
40
+
41
+ // Read and parse log entries (JSON lines format)
42
+ const content = fs.readFileSync(options.log, "utf-8");
43
+ const lines = content.trim().split("\n").filter(Boolean);
44
+
45
+ let entries: AuditEntry[] = [];
46
+ for (const line of lines) {
47
+ try {
48
+ entries.push(JSON.parse(line));
49
+ } catch {
50
+ // Skip malformed lines
51
+ }
52
+ }
53
+
54
+ // Apply filters
55
+ // Filter entries by action
56
+ const filterAction = options.filter ?? "all";
57
+ if (filterAction !== "all") {
58
+ const actionMap: Record<string, string> = {
59
+ denied: "deny",
60
+ allowed: "allow",
61
+ prompted: "prompt",
62
+ };
63
+ const targetAction = actionMap[filterAction];
64
+ entries = entries.filter((e) => e.verdict?.action === targetAction);
65
+ }
66
+
67
+ // Apply --last limit
68
+ if (options.last && options.last > 0) {
69
+ entries = entries.slice(-options.last);
70
+ }
71
+
72
+ if (entries.length === 0) {
73
+ process.stderr.write(chalk.gray("\n No matching audit entries found.\n\n"));
74
+ process.exit(0);
75
+ }
76
+
77
+ // JSON output mode
78
+ if (options.json) {
79
+ process.stdout.write(JSON.stringify(entries, null, 2) + "\n");
80
+ process.exit(0);
81
+ }
82
+
83
+ // Pretty output
84
+ process.stderr.write("\n");
85
+ process.stderr.write(
86
+ chalk.cyan("─── Agent Wall Audit Log ─────────────────────────\n")
87
+ );
88
+ process.stderr.write(
89
+ chalk.gray(` File: ${options.log} | Entries: ${entries.length}\n`)
90
+ );
91
+ process.stderr.write(
92
+ chalk.cyan("─────────────────────────────────────────────────\n\n")
93
+ );
94
+
95
+ const actionColors: Record<string, (s: string) => string> = {
96
+ allow: chalk.green,
97
+ deny: chalk.red,
98
+ prompt: chalk.yellow,
99
+ };
100
+
101
+ const actionIcons: Record<string, string> = {
102
+ allow: "✓",
103
+ deny: "✗",
104
+ prompt: "?",
105
+ };
106
+
107
+ for (const entry of entries) {
108
+ const action = entry.verdict?.action ?? "unknown";
109
+ const color = actionColors[action] ?? chalk.gray;
110
+ const icon = actionIcons[action] ?? "·";
111
+ const time = entry.timestamp
112
+ ? new Date(entry.timestamp).toLocaleTimeString()
113
+ : "??:??:??";
114
+
115
+ process.stderr.write(
116
+ chalk.gray(` ${time} `) +
117
+ color(`${icon} ${(action.toUpperCase()).padEnd(6)} `) +
118
+ chalk.white(entry.tool ?? entry.method ?? "unknown") +
119
+ "\n"
120
+ );
121
+
122
+ if (entry.arguments && Object.keys(entry.arguments).length > 0) {
123
+ const argStr = JSON.stringify(entry.arguments, null, 0);
124
+ process.stderr.write(
125
+ chalk.gray(` Args: ${argStr.slice(0, 100)}${argStr.length > 100 ? "..." : ""}`) +
126
+ "\n"
127
+ );
128
+ }
129
+
130
+ if (entry.verdict?.rule) {
131
+ process.stderr.write(
132
+ chalk.gray(` Rule: ${entry.verdict.rule}`) + "\n"
133
+ );
134
+ }
135
+ process.stderr.write("\n");
136
+ }
137
+
138
+ // Summary
139
+ const stats = {
140
+ allowed: entries.filter((e) => e.verdict?.action === "allow").length,
141
+ denied: entries.filter((e) => e.verdict?.action === "deny").length,
142
+ prompted: entries.filter((e) => e.verdict?.action === "prompt").length,
143
+ };
144
+ process.stderr.write(
145
+ chalk.cyan("─── Summary ────────────────────────────────────\n")
146
+ );
147
+ process.stderr.write(
148
+ chalk.green(` Allowed: ${stats.allowed}`) +
149
+ " " +
150
+ chalk.red(`Denied: ${stats.denied}`) +
151
+ " " +
152
+ chalk.yellow(`Prompted: ${stats.prompted}`) +
153
+ "\n"
154
+ );
155
+ process.stderr.write(
156
+ chalk.cyan("─────────────────────────────────────────────────\n\n")
157
+ );
158
+ }