"use strict"; /** * ABE CLI — command-line interface for running explorations. * Usage: abe run --url http://localhost:3000 */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const ExplorationEngine_1 = require("./core/ExplorationEngine"); const StateGraph_1 = require("./core/StateGraph"); const PlaywrightAgent_1 = require("./plugins/agents/PlaywrightAgent"); const ScreenshotCollector_1 = require("./plugins/collectors/ScreenshotCollector"); const NetworkCollector_1 = require("./plugins/collectors/NetworkCollector"); const DOMSnapshotCollector_1 = require("./plugins/collectors/DOMSnapshotCollector"); const MarkdownExporter_1 = require("./plugins/exporters/MarkdownExporter"); const JSONExporter_1 = require("./plugins/exporters/JSONExporter"); const PlaywrightReproducer_1 = require("./plugins/reproducers/PlaywrightReproducer"); const ExplorationConfig_1 = require("./core/ExplorationConfig"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const program = new commander_1.Command(); program .name('abe') .description('Autonomous Bug Explorer — explore web apps and find bugs') .version('0.1.0'); program .command('run') .description('Run an exploration session against a target URL') .requiredOption('--url ', 'Target URL to explore') .option('--seed ', 'Deterministic seed', parseInt, 42) .option('--max-states ', 'Max states to explore', parseInt, 50) .option('--max-depth ', 'Max click depth', parseInt, 5) .option('--allowed-domains ', 'Comma-separated allowed domains') .option('--excluded-paths ', 'Comma-separated excluded paths') .option('--action-delay ', 'Delay between actions in ms', parseInt, 500) .option('--session-timeout ', 'Session timeout in ms', parseInt, 300000) // Auth options .option('--auth-type ', 'Auth type: cookies | headers | login_flow') .option('--login-url ', 'Login page URL (for login_flow)') .option('--username ', 'Username (for login_flow)') .option('--password ', 'Password (for login_flow)') .option('--username-selector ', 'Username field selector (for login_flow)') .option('--password-selector ', 'Password field selector (for login_flow)') .option('--submit-selector ', 'Submit button selector (for login_flow)') // Output .option('--output ', 'Output format: human | json | junit', 'human') .option('--reports-dir ', 'Output directory for reports', './reports') // CI flags .option('--fail-on-anomaly', 'Exit 1 if any anomaly found') .option('--fail-on-severity ', 'Exit 1 if anomaly at or above severity found') // Remote server .option('--server ', 'Connect to remote ABE server instead of running inline') .option('--api-key ', 'API key for remote server') .action(async (opts) => { const startMs = Date.now(); // Build auth config let auth = null; if (opts.authType === 'login_flow') { auth = { type: 'login_flow', loginUrl: opts.loginUrl ?? '', usernameSelector: opts.usernameSelector ?? 'input[type="email"]', passwordSelector: opts.passwordSelector ?? 'input[type="password"]', submitSelector: opts.submitSelector ?? 'button[type="submit"]', username: opts.username ?? '', password: opts.password ?? '', }; } else if (opts.authType === 'headers') { auth = { type: 'headers', headers: {} }; } else if (opts.authType === 'cookies') { auth = { type: 'cookies', cookies: [] }; } const config = { ...ExplorationConfig_1.DEFAULT_EXPLORATION_CONFIG, maxStates: opts.maxStates, maxDepth: opts.maxDepth, actionDelayMs: opts.actionDelay, sessionTimeoutMs: opts.sessionTimeout, allowedDomains: opts.allowedDomains ? opts.allowedDomains.split(',').map((d) => d.trim()) : [new URL(opts.url).hostname], excludedPaths: opts.excludedPaths ? opts.excludedPaths.split(',').map((p) => p.trim()) : [], auth, }; // If remote server mode if (opts.server) { await runRemote(opts, config); return; } const anomalies = []; let exitCode = 0; let explorationError; try { const graph = new StateGraph_1.StateGraph(); const agent = new PlaywrightAgent_1.PlaywrightAgent({ seed: opts.seed, explorationConfig: config }); const engine = new ExplorationEngine_1.ExplorationEngine({ graph, agent, seed: opts.seed, url: opts.url, maxSteps: opts.maxStates, outputDir: opts.reportsDir, explorationConfig: config, collectors: [ new ScreenshotCollector_1.ScreenshotCollector(opts.reportsDir), new NetworkCollector_1.NetworkCollector(), new DOMSnapshotCollector_1.DOMSnapshotCollector(opts.reportsDir), ], exporters: [new MarkdownExporter_1.MarkdownExporter(), new JSONExporter_1.JSONExporter()], reproducer: new PlaywrightReproducer_1.PlaywrightReproducer(), events: { onAnomalyDetected: (_, anomaly) => { anomalies.push(anomaly); }, onSessionError: (_, error) => { explorationError = error; }, }, }); await engine.run(); } catch (err) { explorationError = err instanceof Error ? err.message : String(err); exitCode = 2; } if (explorationError && exitCode === 0) exitCode = 2; // Determine exit code from flags if (exitCode === 0 && opts.failOnAnomaly && anomalies.length > 0) { exitCode = 1; } if (exitCode === 0 && opts.failOnSeverity) { const severityRank = { low: 0, medium: 1, high: 2, critical: 3 }; const threshold = severityRank[opts.failOnSeverity] ?? 0; const failing = anomalies.filter((a) => (severityRank[a.severity] ?? 0) >= threshold); if (failing.length > 0) exitCode = 1; } const durationMs = Date.now() - startMs; const summary = { url: opts.url, duration_ms: durationMs, anomalies: anomalies.map((a) => ({ id: a.id, type: a.type, severity: a.severity, description: a.description, report_path: path.join(opts.reportsDir, a.id, 'report.json'), })), exit_code: exitCode, }; if (opts.output === 'json') { process.stdout.write(JSON.stringify(summary, null, 2) + '\n'); } else if (opts.output === 'junit') { const xml = buildJunit(summary, opts.url); const outPath = path.join(process.cwd(), 'abe-results.xml'); fs.writeFileSync(outPath, xml, 'utf8'); if (opts.output !== 'json') { console.log(`JUnit results written to ${outPath}`); } } else { if (anomalies.length === 0) { console.log(`✓ ABE finished. No anomalies found. (${durationMs}ms)`); } else { console.log(`⚠ ABE finished. ${anomalies.length} anomaly(ies) found:`); for (const a of anomalies) { console.log(` [${a.severity.toUpperCase()}] ${a.type}: ${a.description}`); } } } process.exit(exitCode); }); async function runRemote(opts, _config) { const serverUrl = opts['server']; const apiKey = opts['apiKey']; const url = opts['url']; const headers = { 'Content-Type': 'application/json' }; if (apiKey) headers['x-abe-api-key'] = apiKey; const res = await fetch(`${serverUrl}/api/sessions`, { method: 'POST', headers, body: JSON.stringify({ url }), }); if (!res.ok) { console.error(`Server error: ${res.status} ${await res.text()}`); process.exit(2); return; } const session = await res.json(); console.log(`Session started: ${session.sessionId}`); process.exit(0); } function buildJunit(summary, url) { const anomalyCount = summary.anomalies.length; const cases = summary.anomalies .map((a) => ` \n` + ` ${escapeXml(a.id)}\n` + ` `) .join('\n'); return `\n` + `\n` + cases + '\n' + `\n`; } function escapeXml(s) { return s .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } program.parse(process.argv);