pentesting 0.40.6 → 0.41.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/main.js CHANGED
@@ -13,8 +13,8 @@ import chalk from "chalk";
13
13
  import gradient from "gradient-string";
14
14
 
15
15
  // src/platform/tui/app.tsx
16
- import { useState as useState4, useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3 } from "react";
17
- import { Box as Box6, useInput, useApp } from "ink";
16
+ import { useState as useState4, useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4 } from "react";
17
+ import { Box as Box6, useInput as useInput2, useApp } from "ink";
18
18
 
19
19
  // src/platform/tui/hooks/useAgent.ts
20
20
  import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2 } from "react";
@@ -199,6 +199,14 @@ var EXIT_CODES = {
199
199
  /** Process killed by SIGKILL */
200
200
  SIGKILL: 137
201
201
  };
202
+ var PROCESS_ACTIONS = {
203
+ LIST: "list",
204
+ STATUS: "status",
205
+ INTERACT: "interact",
206
+ PROMOTE: "promote",
207
+ STOP: "stop",
208
+ STOP_ALL: "stop_all"
209
+ };
202
210
  var PROCESS_ROLES = {
203
211
  LISTENER: "listener",
204
212
  ACTIVE_SHELL: "active_shell",
@@ -306,7 +314,7 @@ var ORPHAN_PROCESS_NAMES = [
306
314
 
307
315
  // src/shared/constants/agent.ts
308
316
  var APP_NAME = "Pentest AI";
309
- var APP_VERSION = "0.40.6";
317
+ var APP_VERSION = "0.41.0";
310
318
  var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
311
319
  var LLM_ROLES = {
312
320
  SYSTEM: "system",
@@ -565,6 +573,11 @@ var UI_COMMANDS = {
565
573
  LOGS: "logs",
566
574
  LOGS_SHORT: "l",
567
575
  CTF: "ctf",
576
+ AUTO: "auto",
577
+ GRAPH: "graph",
578
+ GRAPH_SHORT: "g",
579
+ PATHS: "paths",
580
+ PATHS_SHORT: "p",
568
581
  EXIT: "exit",
569
582
  QUIT: "quit",
570
583
  EXIT_SHORT: "q"
@@ -958,6 +971,18 @@ function debugLog(category, message, data) {
958
971
  }
959
972
 
960
973
  // src/shared/utils/command-validator.ts
974
+ var SHELL_OPERATORS = {
975
+ AND: "&&",
976
+ OR: "||",
977
+ SEQUENCE: ";",
978
+ PIPE: "|",
979
+ BACKGROUND: "&"
980
+ };
981
+ var SHELL_CHARS = {
982
+ SINGLE_QUOTE: "'",
983
+ DOUBLE_QUOTE: '"',
984
+ BACKTICK: "`"
985
+ };
961
986
  function validateCommand(command) {
962
987
  if (!command || typeof command !== "string") {
963
988
  return { safe: false, error: "Empty or invalid command" };
@@ -997,13 +1022,13 @@ function splitChainedCommands(command) {
997
1022
  let i = 0;
998
1023
  while (i < command.length) {
999
1024
  const ch = command[i];
1000
- if (ch === "'" && !inDouble) {
1025
+ if (ch === SHELL_CHARS.SINGLE_QUOTE && !inDouble) {
1001
1026
  inSingle = !inSingle;
1002
1027
  current += ch;
1003
1028
  i++;
1004
1029
  continue;
1005
1030
  }
1006
- if (ch === '"' && !inSingle) {
1031
+ if (ch === SHELL_CHARS.DOUBLE_QUOTE && !inSingle) {
1007
1032
  inDouble = !inDouble;
1008
1033
  current += ch;
1009
1034
  i++;
@@ -1016,7 +1041,7 @@ function splitChainedCommands(command) {
1016
1041
  i += 2;
1017
1042
  continue;
1018
1043
  }
1019
- if (ch === ";") {
1044
+ if (ch === SHELL_OPERATORS.SEQUENCE) {
1020
1045
  if (current.trim()) parts.push(current.trim());
1021
1046
  current = "";
1022
1047
  i++;
@@ -1342,6 +1367,12 @@ var globalInputHandler = null;
1342
1367
  function setInputHandler(handler) {
1343
1368
  globalInputHandler = handler;
1344
1369
  }
1370
+ function clearInputHandler() {
1371
+ globalInputHandler = null;
1372
+ }
1373
+ function clearCommandEventEmitter() {
1374
+ globalEventEmitter = null;
1375
+ }
1345
1376
  async function runCommand(command, args = [], options = {}) {
1346
1377
  const fullCommand = args.length > 0 ? `${command} ${args.join(" ")}` : command;
1347
1378
  const validation = validateCommand(fullCommand);
@@ -1935,7 +1966,10 @@ async function cleanupAllProcesses() {
1935
1966
  if (cleanupDone) return;
1936
1967
  cleanupDone = true;
1937
1968
  const ids = Array.from(backgroundProcesses.keys());
1938
- if (ids.length === 0) return;
1969
+ if (ids.length === 0) {
1970
+ cleanupDone = false;
1971
+ return;
1972
+ }
1939
1973
  for (const [_id, proc] of backgroundProcesses) {
1940
1974
  if (!proc.hasExited) {
1941
1975
  proc.childPids = discoverAllDescendants(proc.pid);
@@ -1997,6 +2031,7 @@ async function cleanupAllProcesses() {
1997
2031
  } catch {
1998
2032
  }
1999
2033
  }
2034
+ cleanupDone = false;
2000
2035
  }
2001
2036
  function getResourceSummary() {
2002
2037
  const procs = listBackgroundProcesses();
@@ -2128,7 +2163,8 @@ var StateSerializer = class {
2128
2163
  if (important.length > 0) {
2129
2164
  lines.push(` Important Findings:`);
2130
2165
  for (const f of important.slice(0, DISPLAY_LIMITS.FINDING_PREVIEW)) {
2131
- lines.push(` [${f.severity.toUpperCase()}] ${f.title} (${f.category || "general"})`);
2166
+ const tactic = f.attackPattern ? ` [ATT&CK:${f.attackPattern}]` : "";
2167
+ lines.push(` [${f.severity.toUpperCase()}] ${f.title} (${f.category || "general"})${tactic}`);
2132
2168
  }
2133
2169
  }
2134
2170
  }
@@ -2174,11 +2210,54 @@ var GRAPH_LIMITS = {
2174
2210
  /** Maximum chains to show in prompt */
2175
2211
  PROMPT_CHAINS: 5,
2176
2212
  /** Maximum unexploited vulns to show in prompt */
2177
- PROMPT_VULNS: 5
2213
+ PROMPT_VULNS: 5,
2214
+ /** Maximum failed paths to show in prompt */
2215
+ PROMPT_FAILED: 5,
2216
+ /** Maximum DFS depth for chain discovery */
2217
+ MAX_CHAIN_DEPTH: 6,
2218
+ /** Maximum nodes to display in ASCII graph */
2219
+ ASCII_MAX_NODES: 30
2220
+ };
2221
+ var NODE_TYPE = {
2222
+ HOST: "host",
2223
+ SERVICE: "service",
2224
+ CREDENTIAL: "credential",
2225
+ VULNERABILITY: "vulnerability",
2226
+ ACCESS: "access",
2227
+ LOOT: "loot",
2228
+ OSINT: "osint"
2229
+ };
2230
+ var NODE_STATUS = {
2231
+ DISCOVERED: "discovered",
2232
+ ATTEMPTED: "attempted",
2233
+ SUCCEEDED: "succeeded",
2234
+ FAILED: "failed"
2235
+ };
2236
+ var EDGE_STATUS = {
2237
+ UNTESTED: "untested",
2238
+ TESTING: "testing",
2239
+ SUCCEEDED: "succeeded",
2240
+ FAILED: "failed"
2241
+ };
2242
+ var SEVERITY = {
2243
+ CRITICAL: "critical",
2244
+ HIGH: "high",
2245
+ MEDIUM: "medium",
2246
+ LOW: "low",
2247
+ INFO: "info"
2178
2248
  };
2249
+ var IMPACT_WEIGHT = { critical: 4, high: 3, medium: 2, low: 1 };
2179
2250
  var AttackGraph = class {
2180
2251
  nodes = /* @__PURE__ */ new Map();
2181
2252
  edges = [];
2253
+ failedPaths = [];
2254
+ // ─── Core Graph Operations ──────────────────────────────────
2255
+ /** Reset the graph to an empty state. */
2256
+ reset() {
2257
+ this.nodes.clear();
2258
+ this.edges = [];
2259
+ this.failedPaths = [];
2260
+ }
2182
2261
  /**
2183
2262
  * Add a node to the attack graph.
2184
2263
  */
@@ -2190,7 +2269,7 @@ var AttackGraph = class {
2190
2269
  type,
2191
2270
  label,
2192
2271
  data,
2193
- exploited: false,
2272
+ status: NODE_STATUS.DISCOVERED,
2194
2273
  discoveredAt: Date.now()
2195
2274
  });
2196
2275
  }
@@ -2202,34 +2281,106 @@ var AttackGraph = class {
2202
2281
  addEdge(fromId, toId, relation, confidence = 0.5) {
2203
2282
  const exists = this.edges.some((e) => e.from === fromId && e.to === toId && e.relation === relation);
2204
2283
  if (!exists) {
2205
- this.edges.push({ from: fromId, to: toId, relation, confidence });
2284
+ this.edges.push({ from: fromId, to: toId, relation, confidence, status: EDGE_STATUS.UNTESTED });
2206
2285
  }
2207
2286
  }
2208
2287
  /**
2209
- * Mark a node as exploited.
2288
+ * Get node by ID.
2210
2289
  */
2211
- markExploited(nodeId) {
2290
+ getNode(nodeId) {
2291
+ return this.nodes.get(nodeId);
2292
+ }
2293
+ // ─── Status Management ──────────────────────────────────────
2294
+ /**
2295
+ * Mark a node as being attempted (in-progress attack).
2296
+ */
2297
+ markAttempted(nodeId) {
2212
2298
  const node = this.nodes.get(nodeId);
2213
- if (node) node.exploited = true;
2299
+ if (node && node.status === NODE_STATUS.DISCOVERED) {
2300
+ node.status = NODE_STATUS.ATTEMPTED;
2301
+ node.attemptedAt = Date.now();
2302
+ }
2214
2303
  }
2215
2304
  /**
2216
- * Get node by ID.
2305
+ * Mark a node as successfully exploited/achieved.
2217
2306
  */
2218
- getNode(nodeId) {
2219
- return this.nodes.get(nodeId);
2307
+ markSucceeded(nodeId) {
2308
+ const node = this.nodes.get(nodeId);
2309
+ if (node) {
2310
+ node.status = NODE_STATUS.SUCCEEDED;
2311
+ node.resolvedAt = Date.now();
2312
+ }
2313
+ for (const edge of this.edges) {
2314
+ if (edge.to === nodeId && edge.status === EDGE_STATUS.TESTING) {
2315
+ edge.status = EDGE_STATUS.SUCCEEDED;
2316
+ }
2317
+ }
2318
+ }
2319
+ /**
2320
+ * Mark a node as failed (attack didn't work).
2321
+ */
2322
+ markFailed(nodeId, reason) {
2323
+ const node = this.nodes.get(nodeId);
2324
+ if (node) {
2325
+ node.status = NODE_STATUS.FAILED;
2326
+ node.resolvedAt = Date.now();
2327
+ node.failReason = reason;
2328
+ this.failedPaths.push(`${node.label}${reason ? ` (${reason})` : ""}`);
2329
+ }
2330
+ for (const edge of this.edges) {
2331
+ if (edge.to === nodeId && (edge.status === EDGE_STATUS.TESTING || edge.status === EDGE_STATUS.UNTESTED)) {
2332
+ edge.status = EDGE_STATUS.FAILED;
2333
+ edge.failReason = reason;
2334
+ }
2335
+ }
2336
+ }
2337
+ /**
2338
+ * Mark an edge as being tested.
2339
+ */
2340
+ markEdgeTesting(fromId, toId) {
2341
+ for (const edge of this.edges) {
2342
+ if (edge.from === fromId && edge.to === toId) {
2343
+ edge.status = EDGE_STATUS.TESTING;
2344
+ }
2345
+ }
2346
+ }
2347
+ /**
2348
+ * Mark an edge as failed.
2349
+ */
2350
+ markEdgeFailed(fromId, toId, reason) {
2351
+ for (const edge of this.edges) {
2352
+ if (edge.from === fromId && edge.to === toId) {
2353
+ edge.status = EDGE_STATUS.FAILED;
2354
+ edge.failReason = reason;
2355
+ }
2356
+ }
2357
+ this.failedPaths.push(`${fromId} \u2192 ${toId}${reason ? ` (${reason})` : ""}`);
2358
+ }
2359
+ // Backward-compatible alias
2360
+ markExploited(nodeId) {
2361
+ this.markSucceeded(nodeId);
2362
+ }
2363
+ // ─── Domain-Specific Registration ───────────────────────────
2364
+ /**
2365
+ * Record a host discovery.
2366
+ */
2367
+ addHost(ip, hostname) {
2368
+ return this.addNode(NODE_TYPE.HOST, ip, { ip, hostname });
2220
2369
  }
2221
2370
  /**
2222
2371
  * Record a service discovery and auto-create edges.
2223
2372
  */
2224
2373
  addService(host, port, service, version) {
2225
- const serviceId = this.addNode("service", `${host}:${port}`, {
2374
+ const hostId = this.addHost(host);
2375
+ const serviceId = this.addNode(NODE_TYPE.SERVICE, `${host}:${port}`, {
2226
2376
  host,
2227
2377
  port,
2228
2378
  service,
2229
2379
  version
2230
2380
  });
2381
+ this.addEdge(hostId, serviceId, "has_service", 0.95);
2231
2382
  if (version) {
2232
- const vulnId = this.addNode("vulnerability", `CVE search: ${service} ${version}`, {
2383
+ const vulnId = this.addNode(NODE_TYPE.VULNERABILITY, `CVE search: ${service} ${version}`, {
2233
2384
  service,
2234
2385
  version,
2235
2386
  status: GRAPH_STATUS.NEEDS_SEARCH
@@ -2250,7 +2401,7 @@ var AttackGraph = class {
2250
2401
  for (const [id, node] of this.nodes) {
2251
2402
  if (node.type === "service") {
2252
2403
  const svc = String(node.data.service || "");
2253
- if (["ssh", "ftp", "rdp", "smb", "http", "mysql", "postgresql", "mssql", "winrm"].some((s) => svc.includes(s))) {
2404
+ if (["ssh", "ftp", "rdp", "smb", "http", "mysql", "postgresql", "mssql", "winrm", "vnc", "telnet"].some((s) => svc.includes(s))) {
2254
2405
  this.addEdge(credId, id, "can_try_on", 0.6);
2255
2406
  }
2256
2407
  }
@@ -2289,7 +2440,7 @@ var AttackGraph = class {
2289
2440
  level,
2290
2441
  via
2291
2442
  });
2292
- this.markExploited(accessId);
2443
+ this.markSucceeded(accessId);
2293
2444
  if (["root", "admin", "SYSTEM", "Administrator"].includes(level)) {
2294
2445
  const lootId = this.addNode("loot", `flags on ${host}`, {
2295
2446
  host,
@@ -2300,104 +2451,302 @@ var AttackGraph = class {
2300
2451
  return accessId;
2301
2452
  }
2302
2453
  /**
2303
- * Recommend attack chains based on current graph state.
2304
- * Returns chains sorted by probability (highest first).
2454
+ * Record OSINT discovery (Docker image, GitHub repo, company info, etc.)
2305
2455
  */
2306
- recommendChains() {
2307
- const chains = [];
2308
- for (const edge of this.edges) {
2309
- if (edge.relation === "can_try_on") {
2310
- const cred = this.nodes.get(edge.from);
2311
- const service = this.nodes.get(edge.to);
2312
- if (cred && service && !service.exploited) {
2313
- chains.push({
2314
- steps: [cred, service],
2315
- description: `Credential spray: ${cred.label} \u2192 ${service.label}`,
2316
- probability: edge.confidence,
2317
- impact: "high"
2318
- });
2456
+ addOSINT(category, detail, data = {}) {
2457
+ const osintId = this.addNode("osint", `${category}: ${detail}`, {
2458
+ category,
2459
+ detail,
2460
+ ...data
2461
+ });
2462
+ for (const [id, node] of this.nodes) {
2463
+ if (node.type === "host" || node.type === "service") {
2464
+ const hostIp = String(node.data.ip || node.data.host || "");
2465
+ const hostname = String(node.data.hostname || "");
2466
+ if (hostIp && detail.includes(hostIp) || hostname && detail.includes(hostname)) {
2467
+ this.addEdge(osintId, id, "relates_to", 0.5);
2319
2468
  }
2320
2469
  }
2321
2470
  }
2322
- for (const edge of this.edges) {
2323
- if (edge.relation === "leads_to") {
2324
- const vuln = this.nodes.get(edge.from);
2325
- const access = this.nodes.get(edge.to);
2326
- if (vuln && access && !vuln.exploited) {
2327
- chains.push({
2328
- steps: [vuln, access],
2329
- description: `Exploit: ${vuln.label} \u2192 ${access.label}`,
2330
- probability: edge.confidence,
2331
- impact: "critical"
2332
- });
2333
- }
2334
- }
2471
+ return osintId;
2472
+ }
2473
+ // ─── Chain Discovery (DFS) ──────────────────────────────────
2474
+ /**
2475
+ * Find all viable attack paths using DFS.
2476
+ * Only follows edges that are untested or succeeded.
2477
+ * Prioritizes by (probability × impact).
2478
+ */
2479
+ recommendChains() {
2480
+ const chains = [];
2481
+ const visited = /* @__PURE__ */ new Set();
2482
+ const goalTypes = [NODE_TYPE.ACCESS, NODE_TYPE.LOOT];
2483
+ const entryNodes = Array.from(this.nodes.values()).filter(
2484
+ (n) => n.type === NODE_TYPE.HOST || n.type === NODE_TYPE.SERVICE || n.type === NODE_TYPE.CREDENTIAL || n.type === NODE_TYPE.OSINT
2485
+ );
2486
+ for (const entry of entryNodes) {
2487
+ visited.clear();
2488
+ this.dfsChains(entry.id, [], [], visited, chains, goalTypes);
2335
2489
  }
2336
- for (const edge of this.edges) {
2337
- if (edge.relation === "can_access") {
2338
- const access = this.nodes.get(edge.from);
2339
- const loot = this.nodes.get(edge.to);
2340
- if (access && loot && access.exploited && !loot.exploited) {
2341
- chains.push({
2342
- steps: [access, loot],
2343
- description: `Collect: ${access.label} \u2192 ${loot.label}`,
2344
- probability: edge.confidence,
2345
- impact: "high"
2346
- });
2347
- }
2348
- }
2490
+ const seen = /* @__PURE__ */ new Set();
2491
+ const unique = chains.filter((c) => {
2492
+ if (seen.has(c.description)) return false;
2493
+ seen.add(c.description);
2494
+ return true;
2495
+ });
2496
+ unique.sort(
2497
+ (a, b) => b.probability * (IMPACT_WEIGHT[b.impact] || 1) - a.probability * (IMPACT_WEIGHT[a.impact] || 1)
2498
+ );
2499
+ return unique.slice(0, GRAPH_LIMITS.MAX_CHAINS);
2500
+ }
2501
+ dfsChains(nodeId, path2, pathEdges, visited, results, goalTypes) {
2502
+ if (visited.has(nodeId)) return;
2503
+ if (path2.length >= GRAPH_LIMITS.MAX_CHAIN_DEPTH) return;
2504
+ const node = this.nodes.get(nodeId);
2505
+ if (!node) return;
2506
+ if (node.status === NODE_STATUS.FAILED) return;
2507
+ visited.add(nodeId);
2508
+ path2.push(node);
2509
+ if (goalTypes.includes(node.type) && node.status !== NODE_STATUS.SUCCEEDED && path2.length > 1) {
2510
+ const prob = pathEdges.reduce((acc, e) => acc * e.confidence, 1);
2511
+ const impact = this.estimateImpact(node);
2512
+ results.push({
2513
+ steps: [...path2],
2514
+ edges: [...pathEdges],
2515
+ description: path2.map((n) => n.label).join(" \u2192 "),
2516
+ probability: prob,
2517
+ impact,
2518
+ length: path2.length
2519
+ });
2349
2520
  }
2350
- const impactWeight = { critical: 4, high: 3, medium: 2, low: 1 };
2351
- chains.sort(
2352
- (a, b) => b.probability * (impactWeight[b.impact] || 1) - a.probability * (impactWeight[a.impact] || 1)
2521
+ const outEdges = this.edges.filter(
2522
+ (e) => e.from === nodeId && e.status !== EDGE_STATUS.FAILED
2353
2523
  );
2354
- return chains.slice(0, GRAPH_LIMITS.MAX_CHAINS);
2524
+ for (const edge of outEdges) {
2525
+ this.dfsChains(edge.to, path2, [...pathEdges, edge], visited, results, goalTypes);
2526
+ }
2527
+ path2.pop();
2528
+ visited.delete(nodeId);
2355
2529
  }
2530
+ estimateImpact(node) {
2531
+ if (node.type === NODE_TYPE.LOOT) return SEVERITY.CRITICAL;
2532
+ if (node.type === NODE_TYPE.ACCESS) {
2533
+ const level = String(node.data.level || "");
2534
+ if (["root", "SYSTEM", "Administrator", "admin"].includes(level)) return SEVERITY.CRITICAL;
2535
+ return SEVERITY.HIGH;
2536
+ }
2537
+ if (node.type === NODE_TYPE.VULNERABILITY) {
2538
+ const sev = String(node.data.severity || "");
2539
+ if (sev === SEVERITY.CRITICAL) return SEVERITY.CRITICAL;
2540
+ if (sev === SEVERITY.HIGH) return SEVERITY.HIGH;
2541
+ if (sev === SEVERITY.MEDIUM) return SEVERITY.MEDIUM;
2542
+ }
2543
+ return SEVERITY.LOW;
2544
+ }
2545
+ // ─── Prompt Generation ──────────────────────────────────────
2356
2546
  /**
2357
2547
  * Format attack graph status for prompt injection.
2548
+ * Provides LLM with:
2549
+ * - Available paths (sorted by probability)
2550
+ * - Failed paths (DO NOT retry)
2551
+ * - Unexploited vulnerabilities
2358
2552
  */
2359
2553
  toPrompt() {
2360
2554
  if (this.nodes.size === 0) return "";
2361
2555
  const lines = ["<attack-graph>"];
2362
2556
  const nodesByType = {};
2557
+ const nodesByStatus = {};
2363
2558
  for (const node of this.nodes.values()) {
2364
2559
  nodesByType[node.type] = (nodesByType[node.type] || 0) + 1;
2560
+ nodesByStatus[node.status] = (nodesByStatus[node.status] || 0) + 1;
2365
2561
  }
2366
2562
  lines.push(`Nodes: ${Object.entries(nodesByType).map(([t, c]) => `${c} ${t}s`).join(", ")}`);
2367
- lines.push(`Edges: ${this.edges.length} relationships`);
2563
+ lines.push(`Status: ${Object.entries(nodesByStatus).map(([s, c]) => `${c} ${s}`).join(", ")}`);
2564
+ lines.push(`Edges: ${this.edges.length} (${this.edges.filter((e) => e.status === EDGE_STATUS.FAILED).length} failed)`);
2368
2565
  const chains = this.recommendChains();
2369
2566
  if (chains.length > 0) {
2370
2567
  lines.push("");
2371
- lines.push("RECOMMENDED NEXT ACTIONS:");
2568
+ lines.push("RECOMMENDED ATTACK PATHS (try these):");
2372
2569
  for (let i = 0; i < Math.min(GRAPH_LIMITS.PROMPT_CHAINS, chains.length); i++) {
2373
2570
  const c = chains[i];
2374
2571
  const prob = (c.probability * 100).toFixed(0);
2375
- lines.push(` ${i + 1}. [${c.impact.toUpperCase()} | ${prob}%] ${c.description}`);
2572
+ lines.push(` PATH ${i + 1} [${c.impact.toUpperCase()} | ${prob}%]: ${c.description}`);
2573
+ }
2574
+ }
2575
+ if (this.failedPaths.length > 0) {
2576
+ lines.push("");
2577
+ lines.push("FAILED PATHS (DO NOT RETRY):");
2578
+ for (const fp of this.failedPaths.slice(-GRAPH_LIMITS.PROMPT_FAILED)) {
2579
+ lines.push(` \u2717 ${fp}`);
2376
2580
  }
2377
2581
  }
2378
- const unexploitedVulns = Array.from(this.nodes.values()).filter((n) => n.type === "vulnerability" && !n.exploited);
2582
+ const unexploitedVulns = Array.from(this.nodes.values()).filter((n) => n.type === NODE_TYPE.VULNERABILITY && n.status !== NODE_STATUS.SUCCEEDED && n.status !== NODE_STATUS.FAILED);
2379
2583
  if (unexploitedVulns.length > 0) {
2380
2584
  lines.push("");
2381
2585
  lines.push(`UNEXPLOITED VULNERABILITIES (${unexploitedVulns.length}):`);
2382
2586
  for (const v of unexploitedVulns.slice(0, GRAPH_LIMITS.PROMPT_VULNS)) {
2383
- lines.push(` - ${v.label} (${v.data.severity || "unknown"})`);
2587
+ const sev = v.data.severity || "unknown";
2588
+ const status = v.status === NODE_STATUS.ATTEMPTED ? " \u23F3 testing" : "";
2589
+ lines.push(` - ${v.label} (${sev})${status}`);
2590
+ }
2591
+ }
2592
+ const accessNodes = Array.from(this.nodes.values()).filter((n) => n.type === NODE_TYPE.ACCESS && n.status === NODE_STATUS.SUCCEEDED);
2593
+ if (accessNodes.length > 0) {
2594
+ lines.push("");
2595
+ lines.push("ACHIEVED ACCESS:");
2596
+ for (const a of accessNodes) {
2597
+ lines.push(` \u2713 ${a.label}`);
2598
+ }
2599
+ }
2600
+ const credNodes = Array.from(this.nodes.values()).filter((n) => n.type === NODE_TYPE.CREDENTIAL);
2601
+ if (credNodes.length > 0) {
2602
+ lines.push("");
2603
+ lines.push(`CREDENTIALS (${credNodes.length} \u2014 try on all services):`);
2604
+ for (const c of credNodes) {
2605
+ const source = c.data.source || "unknown";
2606
+ const sprayEdges = this.edges.filter((e) => e.from === c.id && e.relation === "can_try_on");
2607
+ const testedCount = sprayEdges.filter((e) => e.status !== EDGE_STATUS.UNTESTED).length;
2608
+ lines.push(` \u{1F511} ${c.label} (from: ${source}) [sprayed: ${testedCount}/${sprayEdges.length}]`);
2384
2609
  }
2385
2610
  }
2386
2611
  lines.push("</attack-graph>");
2387
2612
  return lines.join("\n");
2388
2613
  }
2614
+ // ─── TUI Visualization ──────────────────────────────────────
2615
+ /**
2616
+ * Generate ASCII visualization for TUI /graph command.
2617
+ */
2618
+ toASCII() {
2619
+ if (this.nodes.size === 0) return "(Empty attack graph \u2014 no discoveries yet)";
2620
+ const lines = [];
2621
+ lines.push("\u250C\u2500\u2500\u2500 Attack Graph \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\u2510");
2622
+ const groups = {};
2623
+ for (const node of this.nodes.values()) {
2624
+ if (!groups[node.type]) groups[node.type] = [];
2625
+ groups[node.type].push(node);
2626
+ }
2627
+ const typeIcons = {
2628
+ [NODE_TYPE.HOST]: "\u{1F5A5}",
2629
+ [NODE_TYPE.SERVICE]: "\u2699",
2630
+ [NODE_TYPE.VULNERABILITY]: "\u26A0",
2631
+ [NODE_TYPE.CREDENTIAL]: "\u{1F511}",
2632
+ [NODE_TYPE.ACCESS]: "\u{1F513}",
2633
+ [NODE_TYPE.LOOT]: "\u{1F3F4}",
2634
+ [NODE_TYPE.OSINT]: "\u{1F50D}"
2635
+ };
2636
+ const statusIcons = {
2637
+ [NODE_STATUS.DISCOVERED]: "\u25CB",
2638
+ [NODE_STATUS.ATTEMPTED]: "\u25D0",
2639
+ [NODE_STATUS.SUCCEEDED]: "\u25CF",
2640
+ [NODE_STATUS.FAILED]: "\u2717"
2641
+ };
2642
+ let nodeCount = 0;
2643
+ for (const [type, nodes] of Object.entries(groups)) {
2644
+ if (nodeCount >= GRAPH_LIMITS.ASCII_MAX_NODES) {
2645
+ lines.push(`\u2502 ... and ${this.nodes.size - nodeCount} more nodes`);
2646
+ break;
2647
+ }
2648
+ const icon = typeIcons[type] || "\xB7";
2649
+ lines.push(`\u2502`);
2650
+ lines.push(`\u2502 ${icon} ${type.toUpperCase()} (${nodes.length})`);
2651
+ for (const node of nodes) {
2652
+ if (nodeCount >= GRAPH_LIMITS.ASCII_MAX_NODES) break;
2653
+ const sIcon = statusIcons[node.status] || "?";
2654
+ const fail = node.status === NODE_STATUS.FAILED && node.failReason ? ` \u2014 ${node.failReason}` : "";
2655
+ const outEdges = this.edges.filter((e) => e.from === node.id);
2656
+ const edgeStr = outEdges.length > 0 ? ` \u2192 ${outEdges.map((e) => {
2657
+ const target = this.nodes.get(e.to);
2658
+ const eName = target ? target.label : e.to;
2659
+ const eStatus = e.status === EDGE_STATUS.FAILED ? " \u2717" : e.status === EDGE_STATUS.SUCCEEDED ? " \u2713" : "";
2660
+ return `${eName}${eStatus}`;
2661
+ }).join(", ")}` : "";
2662
+ lines.push(`\u2502 ${sIcon} ${node.label}${fail}${edgeStr}`);
2663
+ nodeCount++;
2664
+ }
2665
+ }
2666
+ const stats = this.getStats();
2667
+ lines.push(`\u2502`);
2668
+ lines.push(`\u251C\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\u2524`);
2669
+ lines.push(`\u2502 Nodes: ${stats.nodes} | Edges: ${stats.edges} | Succeeded: ${stats.succeeded} | Failed: ${stats.failed} | Chains: ${stats.chains}`);
2670
+ lines.push(`\u2514\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\u2518`);
2671
+ return lines.join("\n");
2672
+ }
2673
+ /**
2674
+ * Generate path listing for TUI /paths command.
2675
+ */
2676
+ toPathsList() {
2677
+ const chains = this.recommendChains();
2678
+ if (chains.length === 0) {
2679
+ if (this.nodes.size === 0) return "No attack graph data yet. Start reconnaissance first.";
2680
+ return "No viable attack paths found. All paths may be exhausted or failed.";
2681
+ }
2682
+ const lines = [];
2683
+ lines.push(`\u2500\u2500\u2500 ${chains.length} Attack Paths (sorted by priority) \u2500\u2500\u2500`);
2684
+ lines.push("");
2685
+ for (let i = 0; i < chains.length; i++) {
2686
+ const c = chains[i];
2687
+ const prob = (c.probability * 100).toFixed(0);
2688
+ const impactColors = {
2689
+ [SEVERITY.CRITICAL]: "\u{1F534}",
2690
+ [SEVERITY.HIGH]: "\u{1F7E0}",
2691
+ [SEVERITY.MEDIUM]: "\u{1F7E1}",
2692
+ [SEVERITY.LOW]: "\u{1F7E2}"
2693
+ };
2694
+ const impactIcon = impactColors[c.impact] || "\u26AA";
2695
+ lines.push(`${impactIcon} PATH ${i + 1} [${c.impact.toUpperCase()} | prob: ${prob}% | steps: ${c.length}]`);
2696
+ lines.push(` ${c.description}`);
2697
+ for (const edge of c.edges) {
2698
+ const statusTag = edge.status === EDGE_STATUS.UNTESTED ? " ?" : edge.status === EDGE_STATUS.TESTING ? " \u23F3" : edge.status === EDGE_STATUS.SUCCEEDED ? " \u2713" : " \u2717";
2699
+ lines.push(` ${statusTag} ${edge.from.split(":").pop()} \u2014[${edge.relation}]\u2192 ${edge.to.split(":").pop()}`);
2700
+ }
2701
+ lines.push("");
2702
+ }
2703
+ if (this.failedPaths.length > 0) {
2704
+ lines.push(`\u2500\u2500\u2500 Failed Paths (${this.failedPaths.length}) \u2500\u2500\u2500`);
2705
+ for (const fp of this.failedPaths) {
2706
+ lines.push(` \u2717 ${fp}`);
2707
+ }
2708
+ }
2709
+ return lines.join("\n");
2710
+ }
2711
+ // ─── Stats ──────────────────────────────────────────────────
2389
2712
  /**
2390
2713
  * Get graph stats for metrics display.
2391
2714
  */
2392
2715
  getStats() {
2393
- const exploited = Array.from(this.nodes.values()).filter((n) => n.exploited).length;
2716
+ const succeeded = Array.from(this.nodes.values()).filter((n) => n.status === NODE_STATUS.SUCCEEDED).length;
2717
+ const failed = Array.from(this.nodes.values()).filter((n) => n.status === NODE_STATUS.FAILED).length;
2394
2718
  return {
2395
2719
  nodes: this.nodes.size,
2396
2720
  edges: this.edges.length,
2397
- exploited,
2721
+ succeeded,
2722
+ failed,
2398
2723
  chains: this.recommendChains().length
2399
2724
  };
2400
2725
  }
2726
+ /**
2727
+ * Get all nodes (for serialization or external access).
2728
+ */
2729
+ getAllNodes() {
2730
+ return Array.from(this.nodes.values());
2731
+ }
2732
+ /**
2733
+ * Get all edges (for serialization or external access).
2734
+ */
2735
+ getAllEdges() {
2736
+ return [...this.edges];
2737
+ }
2738
+ /**
2739
+ * Get failed paths list.
2740
+ */
2741
+ getFailedPaths() {
2742
+ return [...this.failedPaths];
2743
+ }
2744
+ /**
2745
+ * Check if the graph has any data.
2746
+ */
2747
+ isEmpty() {
2748
+ return this.nodes.size === 0;
2749
+ }
2401
2750
  };
2402
2751
 
2403
2752
  // src/shared/utils/agent-memory.ts
@@ -2493,6 +2842,10 @@ var WorkingMemory = class {
2493
2842
  getEntries() {
2494
2843
  return [...this.entries];
2495
2844
  }
2845
+ /** Clear all working memory entries. */
2846
+ clear() {
2847
+ this.entries = [];
2848
+ }
2496
2849
  };
2497
2850
  var EpisodicMemory = class {
2498
2851
  events = [];
@@ -2548,6 +2901,10 @@ var EpisodicMemory = class {
2548
2901
  lines.push("</session-timeline>");
2549
2902
  return lines.join("\n");
2550
2903
  }
2904
+ /** Clear all episodic events. */
2905
+ clear() {
2906
+ this.events = [];
2907
+ }
2551
2908
  };
2552
2909
  var MEMORY_DIR = "/tmp/pentesting-memory";
2553
2910
  var MEMORY_FILE = join3(MEMORY_DIR, "persistent-knowledge.json");
@@ -2732,6 +3089,10 @@ var DynamicTechniqueLibrary = class {
2732
3089
  getAll() {
2733
3090
  return [...this.techniques];
2734
3091
  }
3092
+ /** Clear all learned techniques. */
3093
+ clear() {
3094
+ this.techniques = [];
3095
+ }
2735
3096
  /**
2736
3097
  * Format for prompt injection.
2737
3098
  */
@@ -2840,6 +3201,32 @@ var SharedState = class {
2840
3201
  challengeAnalysis: null
2841
3202
  };
2842
3203
  }
3204
+ /**
3205
+ * Reset all state to initial defaults.
3206
+ * Used by /clear command to start a fresh session without recreating the agent.
3207
+ */
3208
+ reset() {
3209
+ this.data = {
3210
+ engagement: null,
3211
+ targets: /* @__PURE__ */ new Map(),
3212
+ findings: [],
3213
+ loot: [],
3214
+ todo: [],
3215
+ actionLog: [],
3216
+ currentPhase: PHASES.RECON,
3217
+ missionSummary: "",
3218
+ missionChecklist: [],
3219
+ ctfMode: true,
3220
+ flags: [],
3221
+ startedAt: Date.now(),
3222
+ deadlineAt: 0,
3223
+ challengeAnalysis: null
3224
+ };
3225
+ this.attackGraph.reset();
3226
+ this.workingMemory.clear();
3227
+ this.episodicMemory.clear();
3228
+ this.dynamicTechniques.clear();
3229
+ }
2843
3230
  // --- Mission & Persistent Context ---
2844
3231
  setMissionSummary(summary) {
2845
3232
  this.data.missionSummary = summary;
@@ -3350,6 +3737,11 @@ var NOISE_CLASSIFICATION = {
3350
3737
  };
3351
3738
 
3352
3739
  // src/shared/utils/binary-analysis.ts
3740
+ var RELRO_STATUS = {
3741
+ NO: "no",
3742
+ PARTIAL: "partial",
3743
+ FULL: "full"
3744
+ };
3353
3745
  function parseChecksec(output) {
3354
3746
  const info = {};
3355
3747
  if (/x86-64|amd64/i.test(output)) {
@@ -3403,10 +3795,10 @@ function suggestExploitTechniques(info) {
3403
3795
  if (info.pie === true && info.canary === false) {
3404
3796
  suggestions.push("PIE enabled \u2192 Need info leak first (partial overwrite, format string)");
3405
3797
  }
3406
- if (info.relro === "partial") {
3798
+ if (info.relro === RELRO_STATUS.PARTIAL) {
3407
3799
  suggestions.push("Partial RELRO \u2192 GOT overwrite possible");
3408
3800
  }
3409
- if (info.relro === "full") {
3801
+ if (info.relro === RELRO_STATUS.FULL) {
3410
3802
  suggestions.push("Full RELRO \u2192 GOT is read-only, try __malloc_hook/__free_hook or stack-based attacks");
3411
3803
  }
3412
3804
  if (info.canary === true) {
@@ -3441,6 +3833,11 @@ function formatBinaryAnalysis(info) {
3441
3833
  }
3442
3834
 
3443
3835
  // src/shared/utils/structured-output.ts
3836
+ var PORT_STATE = {
3837
+ OPEN: "open",
3838
+ CLOSED: "closed",
3839
+ FILTERED: "filtered"
3840
+ };
3444
3841
  function extractNmapStructured(output) {
3445
3842
  const ports = [];
3446
3843
  const hosts = [];
@@ -3458,8 +3855,8 @@ function extractNmapStructured(output) {
3458
3855
  while ((match = hostRegex.exec(output)) !== null) {
3459
3856
  hosts.push(match[1]);
3460
3857
  }
3461
- const openCount = ports.filter((p) => p.state === "open").length;
3462
- const summary = `${hosts.length} host(s), ${openCount} open port(s): ${ports.filter((p) => p.state === "open").map((p) => `${p.port}/${p.service}`).join(", ")}`;
3858
+ const openCount = ports.filter((p) => p.state === PORT_STATE.OPEN).length;
3859
+ const summary = `${hosts.length} host(s), ${openCount} open port(s): ${ports.filter((p) => p.state === PORT_STATE.OPEN).map((p) => `${p.port}/${p.service}`).join(", ")}`;
3463
3860
  return {
3464
3861
  summary,
3465
3862
  structured: { openPorts: ports, hosts }
@@ -4031,7 +4428,7 @@ Resource Safety:
4031
4428
  const action = params.action;
4032
4429
  const processId = params.process_id;
4033
4430
  switch (action) {
4034
- case "list": {
4431
+ case PROCESS_ACTIONS.LIST: {
4035
4432
  const procs = listBackgroundProcesses();
4036
4433
  if (procs.length === 0) {
4037
4434
  return { success: true, output: "No background processes running.\nNo ports in use." };
@@ -4062,7 +4459,7 @@ Ports in use: ${usedPorts.join(", ")}` : "\nNo ports in use.";
4062
4459
  ${lines.join("\n\n")}${portLine}${eventLines}`
4063
4460
  };
4064
4461
  }
4065
- case "status": {
4462
+ case PROCESS_ACTIONS.STATUS: {
4066
4463
  if (!processId) return { success: false, output: "", error: "Missing required parameter: process_id" };
4067
4464
  const output = getProcessOutput(processId);
4068
4465
  if (!output) return { success: false, output: "", error: `Process ${processId} not found.` };
@@ -4102,7 +4499,7 @@ ${output.stdout.slice(-SYSTEM_LIMITS.MAX_STDOUT_SLICE) || "(empty)"}
4102
4499
  ${output.stderr.slice(-SYSTEM_LIMITS.MAX_STDERR_SLICE) || "(empty)"}` + connectionHint + interactHint
4103
4500
  };
4104
4501
  }
4105
- case "interact": {
4502
+ case PROCESS_ACTIONS.INTERACT: {
4106
4503
  if (!processId) return { success: false, output: "", error: "Missing process_id for interact" };
4107
4504
  const cmd = params.command;
4108
4505
  if (!cmd) return { success: false, output: "", error: "Missing command for interact. Provide the command to execute on the target." };
@@ -4120,7 +4517,7 @@ ${result2.newOutput}
4120
4517
  ${result2.output}`
4121
4518
  };
4122
4519
  }
4123
- case "promote": {
4520
+ case PROCESS_ACTIONS.PROMOTE: {
4124
4521
  if (!processId) return { success: false, output: "", error: "Missing process_id for promote" };
4125
4522
  const desc = params.description;
4126
4523
  const success = promoteToShell(processId, desc);
@@ -4141,11 +4538,11 @@ Next steps:
4141
4538
  5. Begin post-exploitation enumeration: whoami, sudo -l, etc.`
4142
4539
  };
4143
4540
  }
4144
- case "stop": {
4541
+ case PROCESS_ACTIONS.STOP: {
4145
4542
  if (!processId) return { success: false, output: "", error: "Missing process_id" };
4146
4543
  return stopBackgroundProcess(processId);
4147
4544
  }
4148
- case "stop_all": {
4545
+ case PROCESS_ACTIONS.STOP_ALL: {
4149
4546
  const procs = listBackgroundProcesses();
4150
4547
  if (procs.length === 0) return { success: true, output: "No background processes to stop." };
4151
4548
  const activeShells = procs.filter((p) => p.role === PROCESS_ROLES.ACTIVE_SHELL && p.isRunning);
@@ -4515,11 +4912,12 @@ Detail: ${detail}
4515
4912
  },
4516
4913
  {
4517
4914
  name: TOOL_NAMES.ADD_FINDING,
4518
- description: "Add a security finding",
4915
+ description: "Add a security finding. Always include attackPattern for MITRE ATT&CK mapping.",
4519
4916
  parameters: {
4520
4917
  title: { type: "string", description: "Finding title" },
4521
- severity: { type: "string", description: "Severity" },
4522
- affected: { type: "array", items: { type: "string" }, description: "Affected host:port" }
4918
+ severity: { type: "string", description: "Severity: critical, high, medium, low, info" },
4919
+ affected: { type: "array", items: { type: "string" }, description: "Affected host:port" },
4920
+ attackPattern: { type: "string", description: "MITRE ATT&CK tactic: initial_access, execution, persistence, privilege_escalation, defense_evasion, credential_access, discovery, lateral_movement, collection, exfiltration, command_and_control, impact" }
4523
4921
  },
4524
4922
  required: ["title", "severity"],
4525
4923
  execute: async (p) => {
@@ -4537,7 +4935,8 @@ Detail: ${detail}
4537
4935
  evidence,
4538
4936
  isVerified: validation.isVerified,
4539
4937
  remediation: "",
4540
- foundAt: Date.now()
4938
+ foundAt: Date.now(),
4939
+ ...p.attackPattern ? { attackPattern: p.attackPattern } : {}
4541
4940
  });
4542
4941
  const hasExploit = validation.isVerified;
4543
4942
  const target = affected[0] || "unknown";
@@ -5214,6 +5613,11 @@ async function searchWithPlaywright(query) {
5214
5613
  }
5215
5614
 
5216
5615
  // src/engine/tools-mid.ts
5616
+ var PORT_STATE2 = {
5617
+ OPEN: "open",
5618
+ CLOSED: "closed",
5619
+ FILTERED: "filtered"
5620
+ };
5217
5621
  function getErrorMessage2(error) {
5218
5622
  return error instanceof Error ? error.message : String(error);
5219
5623
  }
@@ -5246,7 +5650,7 @@ async function parseNmap(xmlPath) {
5246
5650
  const serviceMatch = portMatch[0].match(/<service[^>]*name="([^"]*)"(?:[^>]*version="([^"]*)")?/);
5247
5651
  const service = serviceMatch ? serviceMatch[1] : void 0;
5248
5652
  const version = serviceMatch && serviceMatch[2] ? serviceMatch[2] : void 0;
5249
- if (state === "open") {
5653
+ if (state === PORT_STATE2.OPEN) {
5250
5654
  ports.push({ port, protocol, state, service, version });
5251
5655
  results.summary.openPorts++;
5252
5656
  if (service) results.summary.servicesFound++;
@@ -5816,6 +6220,54 @@ For CVEs not in this list, use web_search to find them.`
5816
6220
  ];
5817
6221
 
5818
6222
  // src/shared/utils/payload-mutator.ts
6223
+ var TRANSFORM_TYPE = {
6224
+ URL: "url",
6225
+ DOUBLE_URL: "double_url",
6226
+ TRIPLE_URL: "triple_url",
6227
+ UNICODE: "unicode",
6228
+ HTML_ENTITY_DEC: "html_entity_dec",
6229
+ HTML_ENTITY_HEX: "html_entity_hex",
6230
+ HEX: "hex",
6231
+ OCTAL: "octal",
6232
+ BASE64: "base64",
6233
+ CASE_SWAP: "case_swap",
6234
+ COMMENT_INSERT: "comment_insert",
6235
+ WHITESPACE_ALT: "whitespace_alt",
6236
+ CHAR_FUNCTION: "char_function",
6237
+ CONCAT_SPLIT: "concat_split",
6238
+ NULL_BYTE: "null_byte",
6239
+ REVERSE: "reverse",
6240
+ UTF8_OVERLONG: "utf8_overlong",
6241
+ MIXED_ENCODING: "mixed_encoding",
6242
+ TAG_ALTERNATIVE: "tag_alternative",
6243
+ EVENT_HANDLER: "event_handler",
6244
+ KEYWORD_BYPASS: "keyword_bypass",
6245
+ SPACE_BYPASS: "space_bypass",
6246
+ NO_QUOTES: "no_quotes",
6247
+ JS_ALTERNATIVE: "js_alternative"
6248
+ };
6249
+ var PAYLOAD_CONTEXT = {
6250
+ URL_PATH: "url_path",
6251
+ URL_PARAM: "url_param",
6252
+ POST_BODY: "post_body",
6253
+ JSON_BODY: "json_body",
6254
+ XML_BODY: "xml_body",
6255
+ HTML_BODY: "html_body",
6256
+ HTML_ATTR: "html_attr",
6257
+ JS_STRING: "js_string",
6258
+ SQL_STRING: "sql_string",
6259
+ SQL_NUMERIC: "sql_numeric",
6260
+ SHELL_CMD: "shell_cmd",
6261
+ HTTP_HEADER: "http_header",
6262
+ COOKIE: "cookie",
6263
+ GENERIC: "generic"
6264
+ };
6265
+ var DATABASE_TYPE = {
6266
+ MYSQL: "mysql",
6267
+ MSSQL: "mssql",
6268
+ POSTGRESQL: "pg",
6269
+ ORACLE: "oracle"
6270
+ };
5819
6271
  function urlEncode(s) {
5820
6272
  return [...s].map((c) => {
5821
6273
  const code = c.charCodeAt(0);
@@ -5876,12 +6328,12 @@ function whitespaceAlt(s) {
5876
6328
  const alt = alts[Math.floor(Math.random() * alts.length)];
5877
6329
  return s.replace(/ /g, alt);
5878
6330
  }
5879
- function charFunction(s, dbType = "mysql") {
6331
+ function charFunction(s, dbType = DATABASE_TYPE.MYSQL) {
5880
6332
  const chars = [...s].map((c) => c.charCodeAt(0));
5881
- if (dbType === "mysql") {
6333
+ if (dbType === DATABASE_TYPE.MYSQL) {
5882
6334
  return `CHAR(${chars.join(",")})`;
5883
6335
  }
5884
- if (dbType === "mssql") {
6336
+ if (dbType === DATABASE_TYPE.MSSQL) {
5885
6337
  return chars.map((c) => `CHAR(${c})`).join("+");
5886
6338
  }
5887
6339
  return chars.map((c) => `CHR(${c})`).join("||");
@@ -5966,78 +6418,78 @@ function mutatePayload(request) {
5966
6418
  const activeTransforms = transforms || getDefaultTransforms(context);
5967
6419
  for (const transform of activeTransforms) {
5968
6420
  switch (transform) {
5969
- case "url":
6421
+ case TRANSFORM_TYPE.URL:
5970
6422
  variants.push({ payload: urlEncode(payload), transform: "url", description: "URL encoded (special chars only)" });
5971
6423
  variants.push({ payload: urlEncodeAll(payload), transform: "url_full", description: "URL encoded (all chars)" });
5972
6424
  break;
5973
- case "double_url":
6425
+ case TRANSFORM_TYPE.DOUBLE_URL:
5974
6426
  variants.push({ payload: doubleUrlEncode(payload), transform: "double_url", description: "Double URL encoded" });
5975
6427
  break;
5976
- case "triple_url":
6428
+ case TRANSFORM_TYPE.TRIPLE_URL:
5977
6429
  variants.push({ payload: tripleUrlEncode(payload), transform: "triple_url", description: "Triple URL encoded" });
5978
6430
  break;
5979
- case "unicode":
6431
+ case TRANSFORM_TYPE.UNICODE:
5980
6432
  variants.push({ payload: unicodeEncode(payload), transform: "unicode", description: "Unicode escape sequences" });
5981
6433
  break;
5982
- case "html_entity_dec":
6434
+ case TRANSFORM_TYPE.HTML_ENTITY_DEC:
5983
6435
  variants.push({ payload: htmlEntityDec(payload), transform: "html_entity_dec", description: "HTML decimal entities" });
5984
6436
  break;
5985
- case "html_entity_hex":
6437
+ case TRANSFORM_TYPE.HTML_ENTITY_HEX:
5986
6438
  variants.push({ payload: htmlEntityHex(payload), transform: "html_entity_hex", description: "HTML hex entities" });
5987
6439
  break;
5988
- case "hex":
6440
+ case TRANSFORM_TYPE.HEX:
5989
6441
  variants.push({ payload: hexEncode(payload), transform: "hex", description: "Hex encoded" });
5990
6442
  break;
5991
- case "octal":
6443
+ case TRANSFORM_TYPE.OCTAL:
5992
6444
  variants.push({ payload: octalEncode(payload), transform: "octal", description: "Octal encoded" });
5993
6445
  break;
5994
- case "base64":
6446
+ case TRANSFORM_TYPE.BASE64:
5995
6447
  variants.push({ payload: base64Encode(payload), transform: "base64", description: "Base64 encoded" });
5996
6448
  break;
5997
- case "case_swap":
6449
+ case TRANSFORM_TYPE.CASE_SWAP:
5998
6450
  for (let i = 0; i < 3; i++) {
5999
6451
  variants.push({ payload: caseSwap(payload), transform: "case_swap", description: `Case variation ${i + 1}` });
6000
6452
  }
6001
6453
  break;
6002
- case "comment_insert":
6454
+ case TRANSFORM_TYPE.COMMENT_INSERT:
6003
6455
  variants.push({ payload: commentInsert(payload), transform: "comment_insert", description: "SQL comments in keywords" });
6004
6456
  break;
6005
- case "whitespace_alt":
6457
+ case TRANSFORM_TYPE.WHITESPACE_ALT:
6006
6458
  variants.push({ payload: whitespaceAlt(payload), transform: "whitespace_alt", description: "Alternative whitespace chars" });
6007
6459
  variants.push({ payload: payload.replace(/ /g, "/**/"), transform: "whitespace_comment", description: "Comment as whitespace" });
6008
6460
  variants.push({ payload: payload.replace(/ /g, "+"), transform: "whitespace_plus", description: "Plus as whitespace" });
6009
6461
  break;
6010
- case "char_function":
6462
+ case TRANSFORM_TYPE.CHAR_FUNCTION:
6011
6463
  variants.push({ payload: charFunction(payload, "mysql"), transform: "char_mysql", description: "MySQL CHAR() encoding" });
6012
6464
  variants.push({ payload: charFunction(payload, "mssql"), transform: "char_mssql", description: "MSSQL CHAR() encoding" });
6013
6465
  variants.push({ payload: charFunction(payload, "pg"), transform: "char_pg", description: "PostgreSQL CHR() encoding" });
6014
6466
  break;
6015
- case "concat_split":
6467
+ case TRANSFORM_TYPE.CONCAT_SPLIT:
6016
6468
  variants.push({ payload: concatSplit(payload), transform: "concat_split", description: "String split via CONCAT" });
6017
6469
  break;
6018
- case "null_byte":
6470
+ case TRANSFORM_TYPE.NULL_BYTE:
6019
6471
  variants.push({ payload: nullByteAppend(payload), transform: "null_byte", description: "Null byte appended" });
6020
6472
  variants.push({ payload: payload + "%00.jpg", transform: "null_byte_ext", description: "Null byte + fake extension" });
6021
6473
  break;
6022
- case "reverse":
6474
+ case TRANSFORM_TYPE.REVERSE:
6023
6475
  variants.push({ payload: reversePayload(payload), transform: "reverse", description: "Reversed (use with rev | sh)" });
6024
6476
  break;
6025
- case "utf8_overlong":
6477
+ case TRANSFORM_TYPE.UTF8_OVERLONG:
6026
6478
  variants.push({ payload: utf8Overlong(payload), transform: "utf8_overlong", description: "UTF-8 overlong sequences" });
6027
6479
  break;
6028
- case "mixed_encoding":
6480
+ case TRANSFORM_TYPE.MIXED_ENCODING:
6029
6481
  variants.push({ payload: mixedEncoding(payload), transform: "mixed_encoding", description: "Mixed encoding (partial)" });
6030
6482
  break;
6031
- case "tag_alternative":
6032
- case "event_handler":
6033
- case "js_alternative":
6483
+ case TRANSFORM_TYPE.TAG_ALTERNATIVE:
6484
+ case TRANSFORM_TYPE.EVENT_HANDLER:
6485
+ case TRANSFORM_TYPE.JS_ALTERNATIVE:
6034
6486
  variants.push(...generateXssAlternatives(payload));
6035
6487
  break;
6036
- case "keyword_bypass":
6488
+ case TRANSFORM_TYPE.KEYWORD_BYPASS:
6037
6489
  variants.push({ payload: keywordBypass(payload), transform: "keyword_bypass", description: "Quote-inserted keyword bypass" });
6038
6490
  variants.push({ payload: spaceBypass(payload), transform: "space_bypass", description: "$IFS space bypass" });
6039
6491
  break;
6040
- case "space_bypass":
6492
+ case TRANSFORM_TYPE.SPACE_BYPASS:
6041
6493
  variants.push({ payload: payload.replace(/ /g, "${IFS}"), transform: "ifs", description: "$IFS space bypass" });
6042
6494
  variants.push({ payload: payload.replace(/ /g, "%09"), transform: "tab", description: "Tab space bypass" });
6043
6495
  variants.push({ payload: payload.replace(/ /g, "<"), transform: "redirect", description: "Redirect as separator" });
@@ -6059,52 +6511,52 @@ function mutatePayload(request) {
6059
6511
  }
6060
6512
  function getDefaultTransforms(context) {
6061
6513
  switch (context) {
6062
- case "url_path":
6063
- case "url_param":
6064
- return ["url", "double_url", "unicode", "utf8_overlong", "mixed_encoding", "null_byte"];
6065
- case "html_body":
6066
- return ["html_entity_dec", "html_entity_hex", "unicode", "tag_alternative", "event_handler", "js_alternative"];
6067
- case "html_attr":
6068
- return ["html_entity_dec", "html_entity_hex", "event_handler"];
6069
- case "js_string":
6070
- return ["unicode", "hex", "base64", "js_alternative"];
6071
- case "sql_string":
6072
- case "sql_numeric":
6073
- return ["case_swap", "comment_insert", "whitespace_alt", "char_function", "concat_split", "url", "double_url"];
6074
- case "shell_cmd":
6075
- return ["keyword_bypass", "space_bypass", "base64", "reverse", "hex", "octal"];
6076
- case "xml_body":
6077
- return ["html_entity_dec", "html_entity_hex", "unicode"];
6078
- case "json_body":
6079
- return ["unicode"];
6080
- case "http_header":
6081
- case "cookie":
6082
- return ["url", "double_url", "base64"];
6514
+ case PAYLOAD_CONTEXT.URL_PATH:
6515
+ case PAYLOAD_CONTEXT.URL_PARAM:
6516
+ return [TRANSFORM_TYPE.URL, TRANSFORM_TYPE.DOUBLE_URL, TRANSFORM_TYPE.UNICODE, TRANSFORM_TYPE.UTF8_OVERLONG, TRANSFORM_TYPE.MIXED_ENCODING, TRANSFORM_TYPE.NULL_BYTE];
6517
+ case PAYLOAD_CONTEXT.HTML_BODY:
6518
+ return [TRANSFORM_TYPE.HTML_ENTITY_DEC, TRANSFORM_TYPE.HTML_ENTITY_HEX, TRANSFORM_TYPE.UNICODE, TRANSFORM_TYPE.TAG_ALTERNATIVE, TRANSFORM_TYPE.EVENT_HANDLER, TRANSFORM_TYPE.JS_ALTERNATIVE];
6519
+ case PAYLOAD_CONTEXT.HTML_ATTR:
6520
+ return [TRANSFORM_TYPE.HTML_ENTITY_DEC, TRANSFORM_TYPE.HTML_ENTITY_HEX, TRANSFORM_TYPE.EVENT_HANDLER];
6521
+ case PAYLOAD_CONTEXT.JS_STRING:
6522
+ return [TRANSFORM_TYPE.UNICODE, TRANSFORM_TYPE.HEX, TRANSFORM_TYPE.BASE64, TRANSFORM_TYPE.JS_ALTERNATIVE];
6523
+ case PAYLOAD_CONTEXT.SQL_STRING:
6524
+ case PAYLOAD_CONTEXT.SQL_NUMERIC:
6525
+ return [TRANSFORM_TYPE.CASE_SWAP, TRANSFORM_TYPE.COMMENT_INSERT, TRANSFORM_TYPE.WHITESPACE_ALT, TRANSFORM_TYPE.CHAR_FUNCTION, TRANSFORM_TYPE.CONCAT_SPLIT, TRANSFORM_TYPE.URL, TRANSFORM_TYPE.DOUBLE_URL];
6526
+ case PAYLOAD_CONTEXT.SHELL_CMD:
6527
+ return [TRANSFORM_TYPE.KEYWORD_BYPASS, TRANSFORM_TYPE.SPACE_BYPASS, TRANSFORM_TYPE.BASE64, TRANSFORM_TYPE.REVERSE, TRANSFORM_TYPE.HEX, TRANSFORM_TYPE.OCTAL];
6528
+ case PAYLOAD_CONTEXT.XML_BODY:
6529
+ return [TRANSFORM_TYPE.HTML_ENTITY_DEC, TRANSFORM_TYPE.HTML_ENTITY_HEX, TRANSFORM_TYPE.UNICODE];
6530
+ case PAYLOAD_CONTEXT.JSON_BODY:
6531
+ return [TRANSFORM_TYPE.UNICODE];
6532
+ case PAYLOAD_CONTEXT.HTTP_HEADER:
6533
+ case PAYLOAD_CONTEXT.COOKIE:
6534
+ return [TRANSFORM_TYPE.URL, TRANSFORM_TYPE.DOUBLE_URL, TRANSFORM_TYPE.BASE64];
6083
6535
  default:
6084
- return ["url", "double_url", "html_entity_dec", "unicode", "base64", "case_swap"];
6536
+ return [TRANSFORM_TYPE.URL, TRANSFORM_TYPE.DOUBLE_URL, TRANSFORM_TYPE.HTML_ENTITY_DEC, TRANSFORM_TYPE.UNICODE, TRANSFORM_TYPE.BASE64, TRANSFORM_TYPE.CASE_SWAP];
6085
6537
  }
6086
6538
  }
6087
6539
  function getContextRecommendations(context, variantCount) {
6088
6540
  const recs = [];
6089
6541
  recs.push(`Generated ${variantCount} variants for context: ${context}`);
6090
6542
  switch (context) {
6091
- case "url_param":
6543
+ case PAYLOAD_CONTEXT.URL_PARAM:
6092
6544
  recs.push("If all URL encoding fails: try HTTP Parameter Pollution (send same param twice)");
6093
6545
  recs.push("Try switching GET \u2192 POST or changing Content-Type");
6094
6546
  recs.push("Check if WebSocket endpoint exists (often unfiltered)");
6095
6547
  break;
6096
- case "sql_string":
6097
- case "sql_numeric":
6548
+ case PAYLOAD_CONTEXT.SQL_STRING:
6549
+ case PAYLOAD_CONTEXT.SQL_NUMERIC:
6098
6550
  recs.push("If inline comments fail: try MySQL version comments /*!50000SELECT*/");
6099
6551
  recs.push("Try time-based blind if error/union payloads are blocked");
6100
6552
  recs.push("Use sqlmap with --tamper scripts for automated bypass");
6101
6553
  break;
6102
- case "html_body":
6554
+ case PAYLOAD_CONTEXT.HTML_BODY:
6103
6555
  recs.push("If common XSS tags blocked: try SVG, MathML, mutation XSS");
6104
6556
  recs.push("Check CSP header \u2014 it determines what JS execution is possible");
6105
6557
  recs.push("Try DOM-based XSS via document.location, innerHTML, postMessage");
6106
6558
  break;
6107
- case "shell_cmd":
6559
+ case PAYLOAD_CONTEXT.SHELL_CMD:
6108
6560
  recs.push("Use ${IFS} for space, quotes for keyword bypass, base64 for full obfuscation");
6109
6561
  recs.push("Try: echo PAYLOAD_BASE64 | base64 -d | sh");
6110
6562
  recs.push("Variable concatenation: a=c;b=at;$a$b /etc/passwd");
@@ -6342,6 +6794,9 @@ var globalCredentialHandler = null;
6342
6794
  function setCredentialHandler(handler) {
6343
6795
  globalCredentialHandler = handler;
6344
6796
  }
6797
+ function clearCredentialHandler() {
6798
+ globalCredentialHandler = null;
6799
+ }
6345
6800
  async function requestCredential(request) {
6346
6801
  if (!globalCredentialHandler) {
6347
6802
  return {
@@ -7539,6 +7994,11 @@ var FILE_SHARING_CONFIG = {
7539
7994
  };
7540
7995
 
7541
7996
  // src/domains/cloud/tools.ts
7997
+ var CLOUD_PROVIDER = {
7998
+ AWS: "aws",
7999
+ AZURE: "azure",
8000
+ GCP: "gcp"
8001
+ };
7542
8002
  var CLOUD_TOOLS = [
7543
8003
  {
7544
8004
  name: TOOL_NAMES.AWS_S3_CHECK,
@@ -7561,8 +8021,8 @@ var CLOUD_TOOLS = [
7561
8021
  required: ["provider"],
7562
8022
  execute: async (params) => {
7563
8023
  const provider = params.provider;
7564
- if (provider === "aws") return await runCommand("curl", ["http://169.254.169.254/latest/meta-data/"]);
7565
- if (provider === "azure") return await runCommand("curl", ["-H", "Metadata:true", "http://169.254.169.254/metadata/instance?api-version=2021-02-01"]);
8024
+ if (provider === CLOUD_PROVIDER.AWS) return await runCommand("curl", ["http://169.254.169.254/latest/meta-data/"]);
8025
+ if (provider === CLOUD_PROVIDER.AZURE) return await runCommand("curl", ["-H", "Metadata:true", "http://169.254.169.254/metadata/instance?api-version=2021-02-01"]);
7566
8026
  return await runCommand("curl", ["-H", "Metadata-Flavor: Google", "http://metadata.google.internal/computeMetadata/v1/"]);
7567
8027
  }
7568
8028
  }
@@ -8220,6 +8680,12 @@ var LLM_ERROR_TYPES = {
8220
8680
 
8221
8681
  // src/engine/llm-types.ts
8222
8682
  var HTTP_STATUS = { BAD_REQUEST: 400, UNAUTHORIZED: 401, FORBIDDEN: 403, RATE_LIMIT: 429 };
8683
+ var NETWORK_ERROR_CODES = {
8684
+ ECONNRESET: "ECONNRESET",
8685
+ ETIMEDOUT: "ETIMEDOUT",
8686
+ ENOTFOUND: "ENOTFOUND",
8687
+ CONNECT_TIMEOUT: "UND_ERR_CONNECT_TIMEOUT"
8688
+ };
8223
8689
  var LLMError = class extends Error {
8224
8690
  /** Structured error information */
8225
8691
  errorInfo;
@@ -8242,10 +8708,10 @@ function classifyError(error) {
8242
8708
  if (statusCode === HTTP_STATUS.BAD_REQUEST) {
8243
8709
  return { type: LLM_ERROR_TYPES.INVALID_REQUEST, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Modify request" };
8244
8710
  }
8245
- if (e.code === "ECONNRESET" || e.code === "ETIMEDOUT" || e.code === "ENOTFOUND") {
8711
+ if (e.code === NETWORK_ERROR_CODES.ECONNRESET || e.code === NETWORK_ERROR_CODES.ETIMEDOUT || e.code === NETWORK_ERROR_CODES.ENOTFOUND) {
8246
8712
  return { type: LLM_ERROR_TYPES.NETWORK_ERROR, message: errorMessage, isRetryable: true, suggestedAction: "Check network" };
8247
8713
  }
8248
- if (errorMessage.toLowerCase().includes("timeout") || e.code === "UND_ERR_CONNECT_TIMEOUT") {
8714
+ if (errorMessage.toLowerCase().includes("timeout") || e.code === NETWORK_ERROR_CODES.CONNECT_TIMEOUT) {
8249
8715
  return { type: LLM_ERROR_TYPES.TIMEOUT, message: errorMessage, isRetryable: true, suggestedAction: "Retry" };
8250
8716
  }
8251
8717
  return { type: LLM_ERROR_TYPES.UNKNOWN, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Analyze error" };
@@ -8591,7 +9057,7 @@ var __filename2 = fileURLToPath3(import.meta.url);
8591
9057
  var __dirname3 = dirname4(__filename2);
8592
9058
 
8593
9059
  // src/engine/state-persistence.ts
8594
- import { writeFileSync as writeFileSync6, readFileSync as readFileSync4, existsSync as existsSync6, readdirSync, statSync, unlinkSync as unlinkSync4 } from "fs";
9060
+ import { writeFileSync as writeFileSync6, readFileSync as readFileSync4, existsSync as existsSync6, readdirSync, statSync, unlinkSync as unlinkSync4, rmSync } from "fs";
8595
9061
  import { join as join9 } from "path";
8596
9062
  function saveState(state) {
8597
9063
  const sessionsDir = WORKSPACE.SESSIONS;
@@ -8673,6 +9139,28 @@ function loadState(state) {
8673
9139
  return false;
8674
9140
  }
8675
9141
  }
9142
+ function clearWorkspace() {
9143
+ const cleared = [];
9144
+ const errors = [];
9145
+ const dirsToClean = [
9146
+ { path: WORKSPACE.SESSIONS, label: "sessions" },
9147
+ { path: WORKSPACE.DEBUG, label: "debug logs" },
9148
+ { path: WORKSPACE.TEMP, label: "temp files" },
9149
+ { path: WORKSPACE.OUTPUTS, label: "outputs" }
9150
+ ];
9151
+ for (const dir of dirsToClean) {
9152
+ try {
9153
+ if (existsSync6(dir.path)) {
9154
+ rmSync(dir.path, { recursive: true, force: true });
9155
+ ensureDirExists(dir.path);
9156
+ cleared.push(dir.label);
9157
+ }
9158
+ } catch (err) {
9159
+ errors.push(`${dir.label}: ${err instanceof Error ? err.message : String(err)}`);
9160
+ }
9161
+ }
9162
+ return { cleared, errors };
9163
+ }
8676
9164
 
8677
9165
  // src/agents/tool-error-enrichment.ts
8678
9166
  function enrichToolErrorContext(ctx) {
@@ -10551,6 +11039,17 @@ var MainAgent = class extends CoreAgent {
10551
11039
  isCtfMode() {
10552
11040
  return this.state.isCtfMode();
10553
11041
  }
11042
+ /**
11043
+ * Full session reset — clears state, workspace files, and background processes.
11044
+ * Used by /clear command for a complete fresh start.
11045
+ */
11046
+ async resetSession() {
11047
+ await cleanupAllProcesses().catch(() => {
11048
+ });
11049
+ this.state.reset();
11050
+ this.userInput = "";
11051
+ return clearWorkspace();
11052
+ }
10554
11053
  setScope(allowed, exclusions = []) {
10555
11054
  this.state.setScope({
10556
11055
  allowedCidrs: allowed.filter((a) => a.includes("/")),
@@ -10679,10 +11178,10 @@ var THEME = {
10679
11178
  // Amber
10680
11179
  error: "#ef4444",
10681
11180
  // Red
10682
- info: "#0ea5e9",
10683
- // Sky
10684
- running: "#38bdf8",
10685
- // Bright Sky
11181
+ info: "#3b82f6",
11182
+ // Blue 500 (distinct from user sky)
11183
+ running: "#60a5fa",
11184
+ // Blue 400 (AI activity — distinct from user sky)
10686
11185
  pending: "#64748b"
10687
11186
  // Slate
10688
11187
  },
@@ -10702,14 +11201,16 @@ var THEME = {
10702
11201
  // Border colors
10703
11202
  border: {
10704
11203
  default: "#1e293b",
10705
- focus: "#38bdf8",
11204
+ focus: "#7dd3fc",
11205
+ // Sky 300 (softer, UI decorative)
10706
11206
  error: "#ef4444",
10707
11207
  success: "#22c55e"
10708
11208
  },
10709
11209
  // Phase colors
10710
11210
  phase: {
10711
11211
  recon: "#94a3b8",
10712
- enum: "#38bdf8",
11212
+ enum: "#60a5fa",
11213
+ // Blue 400 (phase indicator)
10713
11214
  vuln: "#f59e0b",
10714
11215
  exploit: "#ef4444",
10715
11216
  privesc: "#8b5cf6",
@@ -10759,10 +11260,12 @@ var THEME = {
10759
11260
  gold: ["#f59e0b", "#78350f"],
10760
11261
  royal: ["#818cf8", "#312e81"]
10761
11262
  },
10762
- // Spinner color (Sky blue)
10763
- spinner: "#38bdf8",
10764
- // Identity color
10765
- identity: "#38bdf8"
11263
+ // Spinner color (soft sky — UI feedback, not user input)
11264
+ spinner: "#7dd3fc",
11265
+ // Sky 300
11266
+ // Identity color (branded accent)
11267
+ identity: "#60a5fa"
11268
+ // Blue 400
10766
11269
  };
10767
11270
  var ASCII_BANNER = `
10768
11271
  ____ __ __ _
@@ -10868,30 +11371,46 @@ var MESSAGE_STYLES = {
10868
11371
  status: "\u25C8"
10869
11372
  }
10870
11373
  };
11374
+ var COMMAND_DEFINITIONS = [
11375
+ { name: "target", alias: "t", args: "<ip>", description: "Set target IP or domain" },
11376
+ { name: "start", alias: "s", args: "[goal]", description: "Start autonomous pentest" },
11377
+ { name: "findings", alias: "f", description: "Show discovered vulnerabilities" },
11378
+ { name: "graph", alias: "g", description: "Visualize the attack graph" },
11379
+ { name: "paths", alias: "p", description: "Show ranked attack paths" },
11380
+ { name: "assets", alias: "a", description: "List background processes" },
11381
+ { name: "logs", alias: "l", args: "<id>", description: "Show logs for an asset" },
11382
+ { name: "ctf", description: "Toggle CTF mode" },
11383
+ { name: "auto", description: "Toggle auto-approve mode" },
11384
+ { name: "clear", alias: "c", description: "Reset session & clean workspace" },
11385
+ { name: "help", alias: "h", description: "Show detailed help" },
11386
+ { name: "exit", alias: "q", description: "Exit the application" }
11387
+ ];
11388
+ function getMatchingCommands(partial) {
11389
+ const lower = partial.toLowerCase();
11390
+ if (!lower) return COMMAND_DEFINITIONS;
11391
+ return COMMAND_DEFINITIONS.filter(
11392
+ (cmd) => cmd.name.startsWith(lower) || cmd.alias && cmd.alias.startsWith(lower)
11393
+ );
11394
+ }
10871
11395
  var HELP_TEXT = `
10872
11396
  \u2500\u2500 Commands \u2500\u2500
10873
- /target <ip> Set target IP/domain
10874
- /start [goal] Start autonomous pentest (auto-approve enabled)
10875
- /findings Show discovered vulnerabilities
10876
- /assets List active background processes (listeners, shells)
10877
- /logs <id> Show full logs for a specific asset
10878
- /ctf Toggle CTF mode (default: ON)
10879
- /auto Toggle auto-approve mode
10880
- /clear Clear screen
10881
- /exit Exit
11397
+ ${COMMAND_DEFINITIONS.map((cmd) => {
11398
+ const usage = `/${cmd.name}${cmd.args ? " " + cmd.args : ""}`;
11399
+ const alias = cmd.alias ? ` (/${cmd.alias})` : "";
11400
+ return `${usage.padEnd(18)}${alias.padEnd(8)} ${cmd.description}`;
11401
+ }).join("\n")}
10882
11402
 
10883
11403
  \u2500\u2500 Features \u2500\u2500
10884
11404
  \u2022 Auto-install missing tools (apt/brew)
10885
11405
  \u2022 Transparent command execution
10886
11406
  \u2022 Interactive sudo password input
10887
11407
  \u2022 CTF mode: Auto flag detection & CTF-specific prompts
11408
+ \u2022 Attack graph: Tracks discovered paths & prevents repeating failures
10888
11409
 
10889
- \u2500\u2500 Examples \u2500\u2500
10890
- /target 192.168.1.1
10891
- /start "Full pentest on target"
10892
- /findings
10893
- /ctf (toggle CTF/standard mode)
10894
- /auto (toggle approval mode)
11410
+ \u2500\u2500 Tips \u2500\u2500
11411
+ \u2022 Type / and press Tab to autocomplete commands
11412
+ \u2022 /clear resets the entire session (messages, state, .pentesting data)
11413
+ \u2022 /start automatically enables auto-approve for hands-free operation
10895
11414
  `;
10896
11415
 
10897
11416
  // src/platform/tui/hooks/useAgentState.ts
@@ -11110,6 +11629,9 @@ var useAgentEvents = (agent, eventsRef, state) => {
11110
11629
  return () => {
11111
11630
  events.removeAllListeners();
11112
11631
  clearAllTimers();
11632
+ clearInputHandler();
11633
+ clearCredentialHandler();
11634
+ clearCommandEventEmitter();
11113
11635
  };
11114
11636
  }, [
11115
11637
  agent,
@@ -11215,6 +11737,7 @@ var useAgent = (shouldAutoApprove, target) => {
11215
11737
  inputRequest,
11216
11738
  setInputRequest,
11217
11739
  stats,
11740
+ setStats,
11218
11741
  lastResponseMetaRef,
11219
11742
  addMessage,
11220
11743
  manageTimer,
@@ -11262,6 +11785,17 @@ var useAgent = (shouldAutoApprove, target) => {
11262
11785
  addMessage("system", "Input cancelled");
11263
11786
  }
11264
11787
  }, [setInputRequest, addMessage]);
11788
+ const refreshStats = useCallback2(() => {
11789
+ const s = agent.getState();
11790
+ setStats({
11791
+ phase: s.getPhase() || PHASES.RECON,
11792
+ targets: s.getTargets().size,
11793
+ findings: s.getFindings().length,
11794
+ todo: s.getTodo().length
11795
+ });
11796
+ resetCumulativeCounters();
11797
+ setCurrentStatus("");
11798
+ }, [agent, setStats, resetCumulativeCounters, setCurrentStatus]);
11265
11799
  return {
11266
11800
  agent,
11267
11801
  messages,
@@ -11277,7 +11811,8 @@ var useAgent = (shouldAutoApprove, target) => {
11277
11811
  executeTask,
11278
11812
  abort,
11279
11813
  cancelInputRequest,
11280
- addMessage
11814
+ addMessage,
11815
+ refreshStats
11281
11816
  };
11282
11817
  };
11283
11818
 
@@ -11532,9 +12067,11 @@ var StatusDisplay = ({
11532
12067
  };
11533
12068
 
11534
12069
  // src/platform/tui/components/ChatInput.tsx
11535
- import { Box as Box4, Text as Text5 } from "ink";
12070
+ import { useMemo, useCallback as useCallback3, useRef as useRef3 } from "react";
12071
+ import { Box as Box4, Text as Text5, useInput } from "ink";
11536
12072
  import TextInput from "ink-text-input";
11537
12073
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
12074
+ var MAX_SUGGESTIONS = 6;
11538
12075
  var ChatInput = ({
11539
12076
  value,
11540
12077
  onChange,
@@ -11545,43 +12082,100 @@ var ChatInput = ({
11545
12082
  setSecretInput,
11546
12083
  onSecretSubmit
11547
12084
  }) => {
11548
- return /* @__PURE__ */ jsx5(
11549
- Box4,
11550
- {
11551
- borderStyle: "single",
11552
- borderColor: inputRequest.isActive ? THEME.status.warning : THEME.border.default,
11553
- paddingX: 1,
11554
- children: inputRequest.isActive ? /* @__PURE__ */ jsxs4(Box4, { children: [
11555
- /* @__PURE__ */ jsx5(Text5, { color: THEME.status.warning, children: "\u{1F512}" }),
11556
- /* @__PURE__ */ jsxs4(Text5, { color: THEME.text.muted, children: [
11557
- " ",
11558
- inputRequest.prompt
11559
- ] }),
11560
- /* @__PURE__ */ jsx5(
11561
- TextInput,
11562
- {
11563
- value: secretInput,
11564
- onChange: setSecretInput,
11565
- onSubmit: onSecretSubmit,
11566
- placeholder: "...",
11567
- mask: inputRequest.isPassword ? "\u2022" : void 0
11568
- }
11569
- )
11570
- ] }) : /* @__PURE__ */ jsxs4(Box4, { children: [
11571
- /* @__PURE__ */ jsx5(Text5, { color: THEME.text.secondary, children: "\u25B8" }),
11572
- /* @__PURE__ */ jsx5(Text5, { children: " " }),
11573
- /* @__PURE__ */ jsx5(
11574
- TextInput,
11575
- {
11576
- value,
11577
- onChange,
11578
- onSubmit,
11579
- placeholder
11580
- }
11581
- )
11582
- ] })
11583
- }
11584
- );
12085
+ const isSlashMode = value.startsWith("/");
12086
+ const partialCmd = isSlashMode ? value.slice(1).split(" ")[0] : "";
12087
+ const hasArgs = isSlashMode && value.includes(" ");
12088
+ const suggestions = useMemo(() => {
12089
+ if (!isSlashMode || hasArgs) return [];
12090
+ return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
12091
+ }, [isSlashMode, partialCmd, hasArgs]);
12092
+ const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
12093
+ const suggestionsRef = useRef3(suggestions);
12094
+ suggestionsRef.current = suggestions;
12095
+ const isSlashModeRef = useRef3(isSlashMode);
12096
+ isSlashModeRef.current = isSlashMode;
12097
+ const hasArgsRef = useRef3(hasArgs);
12098
+ hasArgsRef.current = hasArgs;
12099
+ const inputRequestRef = useRef3(inputRequest);
12100
+ inputRequestRef.current = inputRequest;
12101
+ const onChangeRef = useRef3(onChange);
12102
+ onChangeRef.current = onChange;
12103
+ useInput(useCallback3((_input, key) => {
12104
+ if (inputRequestRef.current.isActive) return;
12105
+ if (key.tab && isSlashModeRef.current && !hasArgsRef.current && suggestionsRef.current.length > 0) {
12106
+ const best = suggestionsRef.current[0];
12107
+ const argsHint = best.args ? " " : "";
12108
+ onChangeRef.current(`/${best.name}${argsHint}`);
12109
+ }
12110
+ }, []));
12111
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
12112
+ showPreview && /* @__PURE__ */ jsx5(
12113
+ Box4,
12114
+ {
12115
+ flexDirection: "column",
12116
+ borderStyle: "single",
12117
+ borderColor: THEME.border.default,
12118
+ paddingX: 1,
12119
+ marginBottom: 0,
12120
+ children: suggestions.map((cmd, i) => {
12121
+ const isFirst = i === 0;
12122
+ const nameColor = isFirst ? THEME.text.accent : THEME.text.secondary;
12123
+ const aliasText = cmd.alias ? ` /${cmd.alias}` : "";
12124
+ const argsText = cmd.args ? ` ${cmd.args}` : "";
12125
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
12126
+ /* @__PURE__ */ jsxs4(Text5, { color: nameColor, bold: isFirst, children: [
12127
+ "/",
12128
+ cmd.name
12129
+ ] }),
12130
+ /* @__PURE__ */ jsx5(Text5, { color: THEME.text.muted, children: argsText }),
12131
+ aliasText && /* @__PURE__ */ jsx5(Text5, { color: THEME.text.muted, children: aliasText }),
12132
+ /* @__PURE__ */ jsxs4(Text5, { color: THEME.text.muted, children: [
12133
+ " \u2014 ",
12134
+ cmd.description
12135
+ ] }),
12136
+ isFirst && /* @__PURE__ */ jsx5(Text5, { color: THEME.accent.cyan, children: " \u21E5 Tab" })
12137
+ ] }, cmd.name);
12138
+ })
12139
+ }
12140
+ ),
12141
+ /* @__PURE__ */ jsx5(
12142
+ Box4,
12143
+ {
12144
+ borderStyle: "single",
12145
+ borderColor: inputRequest.isActive ? THEME.status.warning : THEME.border.default,
12146
+ paddingX: 1,
12147
+ children: inputRequest.isActive ? /* @__PURE__ */ jsxs4(Box4, { children: [
12148
+ /* @__PURE__ */ jsx5(Text5, { color: THEME.status.warning, children: "\u{1F512}" }),
12149
+ /* @__PURE__ */ jsxs4(Text5, { color: THEME.text.muted, children: [
12150
+ " ",
12151
+ inputRequest.prompt
12152
+ ] }),
12153
+ /* @__PURE__ */ jsx5(
12154
+ TextInput,
12155
+ {
12156
+ value: secretInput,
12157
+ onChange: setSecretInput,
12158
+ onSubmit: onSecretSubmit,
12159
+ placeholder: "...",
12160
+ mask: inputRequest.isPassword ? "\u2022" : void 0
12161
+ }
12162
+ )
12163
+ ] }) : /* @__PURE__ */ jsxs4(Box4, { children: [
12164
+ /* @__PURE__ */ jsx5(Text5, { color: THEME.text.secondary, children: "\u25B8" }),
12165
+ /* @__PURE__ */ jsx5(Text5, { children: " " }),
12166
+ /* @__PURE__ */ jsx5(
12167
+ TextInput,
12168
+ {
12169
+ value,
12170
+ onChange,
12171
+ onSubmit,
12172
+ placeholder
12173
+ }
12174
+ )
12175
+ ] })
12176
+ }
12177
+ )
12178
+ ] });
11585
12179
  };
11586
12180
 
11587
12181
  // src/platform/tui/components/footer.tsx
@@ -11655,15 +12249,16 @@ var App = ({ autoApprove = false, target }) => {
11655
12249
  executeTask,
11656
12250
  abort,
11657
12251
  cancelInputRequest,
11658
- addMessage
12252
+ addMessage,
12253
+ refreshStats
11659
12254
  } = useAgent(autoApproveMode, target);
11660
- const isProcessingRef = useRef3(isProcessing);
12255
+ const isProcessingRef = useRef4(isProcessing);
11661
12256
  isProcessingRef.current = isProcessing;
11662
- const autoApproveModeRef = useRef3(autoApproveMode);
12257
+ const autoApproveModeRef = useRef4(autoApproveMode);
11663
12258
  autoApproveModeRef.current = autoApproveMode;
11664
- const inputRequestRef = useRef3(inputRequest);
12259
+ const inputRequestRef = useRef4(inputRequest);
11665
12260
  inputRequestRef.current = inputRequest;
11666
- const handleExit = useCallback3(() => {
12261
+ const handleExit = useCallback4(() => {
11667
12262
  const ir = inputRequestRef.current;
11668
12263
  if (ir.isActive && ir.resolve) ir.resolve(null);
11669
12264
  cleanupAllProcesses().catch(() => {
@@ -11671,16 +12266,31 @@ var App = ({ autoApprove = false, target }) => {
11671
12266
  exit();
11672
12267
  setTimeout(() => process.exit(0), DISPLAY_LIMITS.EXIT_DELAY);
11673
12268
  }, [exit]);
11674
- const handleCommand = useCallback3(async (cmd, args) => {
12269
+ const handleCommand = useCallback4(async (cmd, args) => {
11675
12270
  switch (cmd) {
11676
12271
  case UI_COMMANDS.HELP:
11677
12272
  case UI_COMMANDS.HELP_SHORT:
11678
12273
  addMessage("system", HELP_TEXT);
11679
12274
  break;
11680
12275
  case UI_COMMANDS.CLEAR:
11681
- case UI_COMMANDS.CLEAR_SHORT:
12276
+ case UI_COMMANDS.CLEAR_SHORT: {
12277
+ if (isProcessingRef.current) {
12278
+ addMessage("error", "Cannot /clear while agent is running. Press Esc to abort first.");
12279
+ break;
12280
+ }
11682
12281
  setMessages([]);
12282
+ const result2 = await agent.resetSession();
12283
+ if (result2.cleared.length > 0) {
12284
+ addMessage("system", `\u{1F9F9} Session reset \u2014 cleared: ${result2.cleared.join(", ")}`);
12285
+ } else {
12286
+ addMessage("system", "\u{1F9F9} Session reset \u2014 all clean");
12287
+ }
12288
+ if (result2.errors.length > 0) {
12289
+ addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
12290
+ }
12291
+ refreshStats();
11683
12292
  break;
12293
+ }
11684
12294
  case UI_COMMANDS.TARGET:
11685
12295
  case UI_COMMANDS.TARGET_SHORT:
11686
12296
  if (!args[0]) {
@@ -11715,7 +12325,7 @@ var App = ({ autoApprove = false, target }) => {
11715
12325
  break;
11716
12326
  }
11717
12327
  addMessage("system", `--- ${findings.length} Findings ---`);
11718
- findings.forEach((f) => addMessage("system", `[${f.severity}] ${f.title}`));
12328
+ findings.forEach((f) => addMessage("system", `[${f.severity}] ${f.title}${f.attackPattern ? ` (ATT&CK: ${f.attackPattern})` : ""}`));
11719
12329
  break;
11720
12330
  case UI_COMMANDS.ASSETS:
11721
12331
  case UI_COMMANDS.ASSETS_SHORT:
@@ -11740,7 +12350,15 @@ ${procData.stdout || "(no output)"}
11740
12350
  const ctfEnabled = agent.toggleCtfMode();
11741
12351
  addMessage("system", ctfEnabled ? "\u{1F3F4} CTF mode ON \u2014 flag detection active, CTF prompts loaded" : "\u{1F512} CTF mode OFF \u2014 standard pentest mode");
11742
12352
  break;
11743
- case "auto":
12353
+ case UI_COMMANDS.GRAPH:
12354
+ case UI_COMMANDS.GRAPH_SHORT:
12355
+ addMessage("system", agent.getState().attackGraph.toASCII());
12356
+ break;
12357
+ case UI_COMMANDS.PATHS:
12358
+ case UI_COMMANDS.PATHS_SHORT:
12359
+ addMessage("system", agent.getState().attackGraph.toPathsList());
12360
+ break;
12361
+ case UI_COMMANDS.AUTO:
11744
12362
  setAutoApproveMode((prev) => {
11745
12363
  const newVal = !prev;
11746
12364
  agent.setAutoApprove(newVal);
@@ -11756,8 +12374,8 @@ ${procData.stdout || "(no output)"}
11756
12374
  default:
11757
12375
  addMessage("error", `Unknown command: /${cmd}`);
11758
12376
  }
11759
- }, [agent, addMessage, executeTask, setMessages, handleExit]);
11760
- const handleSubmit = useCallback3(async (value) => {
12377
+ }, [agent, addMessage, executeTask, setMessages, handleExit, refreshStats]);
12378
+ const handleSubmit = useCallback4(async (value) => {
11761
12379
  const trimmed = value.trim();
11762
12380
  if (!trimmed) return;
11763
12381
  setInput("");
@@ -11769,7 +12387,7 @@ ${procData.stdout || "(no output)"}
11769
12387
  await executeTask(trimmed);
11770
12388
  }
11771
12389
  }, [addMessage, executeTask, handleCommand]);
11772
- const handleSecretSubmit = useCallback3((value) => {
12390
+ const handleSecretSubmit = useCallback4((value) => {
11773
12391
  const ir = inputRequestRef.current;
11774
12392
  if (!ir.isActive || !ir.resolve) return;
11775
12393
  const displayText = ir.isPassword ? "\u2022".repeat(value.length) : value;
@@ -11779,7 +12397,7 @@ ${procData.stdout || "(no output)"}
11779
12397
  setInputRequest({ isActive: false, prompt: "", isPassword: false, resolve: null });
11780
12398
  setSecretInput("");
11781
12399
  }, [addMessage, setInputRequest]);
11782
- useInput(useCallback3((ch, key) => {
12400
+ useInput2(useCallback4((ch, key) => {
11783
12401
  if (key.escape) {
11784
12402
  if (inputRequestRef.current.isActive) cancelInputRequest();
11785
12403
  else if (isProcessingRef.current) abort();
@@ -11870,7 +12488,7 @@ program.command("interactive", { isDefault: true }).alias("i").description("Star
11870
12488
  console.clear();
11871
12489
  console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
11872
12490
  console.log(
11873
- " " + chalk.hex(THEME.text.secondary)(`v${APP_VERSION}`) + chalk.hex(THEME.text.muted)(" \u2502 ") + chalk.hex(THEME.text.accent)("Type /help for commands") + "\n"
12491
+ " " + chalk.hex(THEME.text.secondary)(`v${APP_VERSION}`) + chalk.hex(THEME.text.muted)(" \u2502 ") + chalk.hex(THEME.accent.blue)("Type /help for commands") + "\n"
11874
12492
  );
11875
12493
  if (skipPermissions) {
11876
12494
  console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions"));
@@ -11894,7 +12512,7 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
11894
12512
  if (skipPermissions) {
11895
12513
  console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions\n"));
11896
12514
  }
11897
- console.log(chalk.hex(THEME.text.accent)(`[target] Objective: ${objective}
12515
+ console.log(chalk.hex(THEME.accent.blue)(`[target] Objective: ${objective}
11898
12516
  `));
11899
12517
  const agent = AgentFactory.createMainAgent(skipPermissions);
11900
12518
  if (skipPermissions) {
@@ -11916,7 +12534,7 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
11916
12534
  if (options.output) {
11917
12535
  const fs = await import("fs/promises");
11918
12536
  await fs.writeFile(options.output, JSON.stringify({ result: result2 }, null, 2));
11919
- console.log(chalk.hex(THEME.text.accent)(`
12537
+ console.log(chalk.hex(THEME.accent.blue)(`
11920
12538
  [+] Report saved to: ${options.output}`));
11921
12539
  }
11922
12540
  await shutdown(0);
@@ -11931,7 +12549,7 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
11931
12549
  const opts = program.opts();
11932
12550
  const skipPermissions = opts.dangerouslySkipPermissions || false;
11933
12551
  console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
11934
- console.log(chalk.hex(THEME.text.accent)(`
12552
+ console.log(chalk.hex(THEME.accent.blue)(`
11935
12553
  [scan] Target: ${target} (${options.scanType})
11936
12554
  `));
11937
12555
  const agent = AgentFactory.createMainAgent(skipPermissions);
@@ -11955,7 +12573,7 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
11955
12573
  program.command("help-extended").description("Show extended help with examples").action(() => {
11956
12574
  console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
11957
12575
  console.log(`
11958
- ${chalk.hex(THEME.text.accent)(APP_NAME + " - Autonomous Penetration Testing AI")}
12576
+ ${chalk.hex(THEME.accent.blue)(APP_NAME + " - Autonomous Penetration Testing AI")}
11959
12577
 
11960
12578
  ${chalk.hex(THEME.status.warning)("Usage:")}
11961
12579
 
@@ -11965,24 +12583,24 @@ ${chalk.hex(THEME.status.warning)("Usage:")}
11965
12583
 
11966
12584
  ${chalk.hex(THEME.status.warning)("Commands:")}
11967
12585
 
11968
- ${chalk.hex(THEME.text.accent)("pentesting")} Interactive TUI mode
11969
- ${chalk.hex(THEME.text.accent)("pentesting run <objective>")} Run single objective
11970
- ${chalk.hex(THEME.text.accent)("pentesting scan <target>")} Quick scan target
12586
+ ${chalk.hex(THEME.accent.blue)("pentesting")} Interactive TUI mode
12587
+ ${chalk.hex(THEME.accent.blue)("pentesting run <objective>")} Run single objective
12588
+ ${chalk.hex(THEME.accent.blue)("pentesting scan <target>")} Quick scan target
11971
12589
 
11972
12590
  ${chalk.hex(THEME.status.warning)("Options:")}
11973
12591
 
11974
- ${chalk.hex(THEME.text.accent)("--dangerously-skip-permissions")} Skip all permission prompts
11975
- ${chalk.hex(THEME.text.accent)("-t, --target <ip>")} Set target
11976
- ${chalk.hex(THEME.text.accent)("-o, --output <file>")} Save results to file
12592
+ ${chalk.hex(THEME.accent.blue)("--dangerously-skip-permissions")} Skip all permission prompts
12593
+ ${chalk.hex(THEME.accent.blue)("-t, --target <ip>")} Set target
12594
+ ${chalk.hex(THEME.accent.blue)("-o, --output <file>")} Save results to file
11977
12595
 
11978
12596
  ${chalk.hex(THEME.status.warning)("Interactive Commands:")}
11979
12597
 
11980
- ${chalk.hex(THEME.text.accent)("/target <ip>")} Set target
11981
- ${chalk.hex(THEME.text.accent)("/start")} Start autonomous mode
11982
- ${chalk.hex(THEME.text.accent)("/config")} Manage configuration
11983
- ${chalk.hex(THEME.text.accent)("/hint <text>")} Provide hint
11984
- ${chalk.hex(THEME.text.accent)("/findings")} Show findings
11985
- ${chalk.hex(THEME.text.accent)("/reset")} Reset session
12598
+ ${chalk.hex(THEME.accent.blue)("/target <ip>")} Set target
12599
+ ${chalk.hex(THEME.accent.blue)("/start")} Start autonomous mode
12600
+ ${chalk.hex(THEME.accent.blue)("/config")} Manage configuration
12601
+ ${chalk.hex(THEME.accent.blue)("/hint <text>")} Provide hint
12602
+ ${chalk.hex(THEME.accent.blue)("/findings")} Show findings
12603
+ ${chalk.hex(THEME.accent.blue)("/reset")} Reset session
11986
12604
 
11987
12605
  ${chalk.hex(THEME.status.warning)("Examples:")}
11988
12606
 
@@ -11997,9 +12615,9 @@ ${chalk.hex(THEME.status.warning)("Examples:")}
11997
12615
 
11998
12616
  ${chalk.hex(THEME.status.warning)("Environment:")}
11999
12617
 
12000
- ${chalk.hex(THEME.text.accent)("PENTEST_API_KEY")} Required - LLM API key
12001
- ${chalk.hex(THEME.text.accent)("PENTEST_BASE_URL")} Optional - AI API base URL
12002
- ${chalk.hex(THEME.text.accent)("PENTEST_MODEL")} Optional - Model override
12618
+ ${chalk.hex(THEME.accent.blue)("PENTEST_API_KEY")} Required - LLM API key
12619
+ ${chalk.hex(THEME.accent.blue)("PENTEST_BASE_URL")} Optional - AI API base URL
12620
+ ${chalk.hex(THEME.accent.blue)("PENTEST_MODEL")} Optional - Model override
12003
12621
  `);
12004
12622
  });
12005
12623
  program.parse();