pentesting 0.40.7 → 0.43.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.7";
317
+ var APP_VERSION = "0.43.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();
@@ -2175,11 +2210,54 @@ var GRAPH_LIMITS = {
2175
2210
  /** Maximum chains to show in prompt */
2176
2211
  PROMPT_CHAINS: 5,
2177
2212
  /** Maximum unexploited vulns to show in prompt */
2178
- 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"
2179
2248
  };
2249
+ var IMPACT_WEIGHT = { critical: 4, high: 3, medium: 2, low: 1 };
2180
2250
  var AttackGraph = class {
2181
2251
  nodes = /* @__PURE__ */ new Map();
2182
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
+ }
2183
2261
  /**
2184
2262
  * Add a node to the attack graph.
2185
2263
  */
@@ -2191,7 +2269,7 @@ var AttackGraph = class {
2191
2269
  type,
2192
2270
  label,
2193
2271
  data,
2194
- exploited: false,
2272
+ status: NODE_STATUS.DISCOVERED,
2195
2273
  discoveredAt: Date.now()
2196
2274
  });
2197
2275
  }
@@ -2203,34 +2281,106 @@ var AttackGraph = class {
2203
2281
  addEdge(fromId, toId, relation, confidence = 0.5) {
2204
2282
  const exists = this.edges.some((e) => e.from === fromId && e.to === toId && e.relation === relation);
2205
2283
  if (!exists) {
2206
- this.edges.push({ from: fromId, to: toId, relation, confidence });
2284
+ this.edges.push({ from: fromId, to: toId, relation, confidence, status: EDGE_STATUS.UNTESTED });
2207
2285
  }
2208
2286
  }
2209
2287
  /**
2210
- * Mark a node as exploited.
2288
+ * Get node by ID.
2211
2289
  */
2212
- 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) {
2213
2298
  const node = this.nodes.get(nodeId);
2214
- 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
+ }
2215
2303
  }
2216
2304
  /**
2217
- * Get node by ID.
2305
+ * Mark a node as successfully exploited/achieved.
2218
2306
  */
2219
- getNode(nodeId) {
2220
- 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 });
2221
2369
  }
2222
2370
  /**
2223
2371
  * Record a service discovery and auto-create edges.
2224
2372
  */
2225
2373
  addService(host, port, service, version) {
2226
- const serviceId = this.addNode("service", `${host}:${port}`, {
2374
+ const hostId = this.addHost(host);
2375
+ const serviceId = this.addNode(NODE_TYPE.SERVICE, `${host}:${port}`, {
2227
2376
  host,
2228
2377
  port,
2229
2378
  service,
2230
2379
  version
2231
2380
  });
2381
+ this.addEdge(hostId, serviceId, "has_service", 0.95);
2232
2382
  if (version) {
2233
- const vulnId = this.addNode("vulnerability", `CVE search: ${service} ${version}`, {
2383
+ const vulnId = this.addNode(NODE_TYPE.VULNERABILITY, `CVE search: ${service} ${version}`, {
2234
2384
  service,
2235
2385
  version,
2236
2386
  status: GRAPH_STATUS.NEEDS_SEARCH
@@ -2251,7 +2401,7 @@ var AttackGraph = class {
2251
2401
  for (const [id, node] of this.nodes) {
2252
2402
  if (node.type === "service") {
2253
2403
  const svc = String(node.data.service || "");
2254
- 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))) {
2255
2405
  this.addEdge(credId, id, "can_try_on", 0.6);
2256
2406
  }
2257
2407
  }
@@ -2290,7 +2440,7 @@ var AttackGraph = class {
2290
2440
  level,
2291
2441
  via
2292
2442
  });
2293
- this.markExploited(accessId);
2443
+ this.markSucceeded(accessId);
2294
2444
  if (["root", "admin", "SYSTEM", "Administrator"].includes(level)) {
2295
2445
  const lootId = this.addNode("loot", `flags on ${host}`, {
2296
2446
  host,
@@ -2301,104 +2451,302 @@ var AttackGraph = class {
2301
2451
  return accessId;
2302
2452
  }
2303
2453
  /**
2304
- * Recommend attack chains based on current graph state.
2305
- * Returns chains sorted by probability (highest first).
2454
+ * Record OSINT discovery (Docker image, GitHub repo, company info, etc.)
2306
2455
  */
2307
- recommendChains() {
2308
- const chains = [];
2309
- for (const edge of this.edges) {
2310
- if (edge.relation === "can_try_on") {
2311
- const cred = this.nodes.get(edge.from);
2312
- const service = this.nodes.get(edge.to);
2313
- if (cred && service && !service.exploited) {
2314
- chains.push({
2315
- steps: [cred, service],
2316
- description: `Credential spray: ${cred.label} \u2192 ${service.label}`,
2317
- probability: edge.confidence,
2318
- impact: "high"
2319
- });
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);
2320
2468
  }
2321
2469
  }
2322
2470
  }
2323
- for (const edge of this.edges) {
2324
- if (edge.relation === "leads_to") {
2325
- const vuln = this.nodes.get(edge.from);
2326
- const access = this.nodes.get(edge.to);
2327
- if (vuln && access && !vuln.exploited) {
2328
- chains.push({
2329
- steps: [vuln, access],
2330
- description: `Exploit: ${vuln.label} \u2192 ${access.label}`,
2331
- probability: edge.confidence,
2332
- impact: "critical"
2333
- });
2334
- }
2335
- }
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);
2336
2489
  }
2337
- for (const edge of this.edges) {
2338
- if (edge.relation === "can_access") {
2339
- const access = this.nodes.get(edge.from);
2340
- const loot = this.nodes.get(edge.to);
2341
- if (access && loot && access.exploited && !loot.exploited) {
2342
- chains.push({
2343
- steps: [access, loot],
2344
- description: `Collect: ${access.label} \u2192 ${loot.label}`,
2345
- probability: edge.confidence,
2346
- impact: "high"
2347
- });
2348
- }
2349
- }
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
+ });
2350
2520
  }
2351
- const impactWeight = { critical: 4, high: 3, medium: 2, low: 1 };
2352
- chains.sort(
2353
- (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
2354
2523
  );
2355
- 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);
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;
2356
2544
  }
2545
+ // ─── Prompt Generation ──────────────────────────────────────
2357
2546
  /**
2358
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
2359
2552
  */
2360
2553
  toPrompt() {
2361
2554
  if (this.nodes.size === 0) return "";
2362
2555
  const lines = ["<attack-graph>"];
2363
2556
  const nodesByType = {};
2557
+ const nodesByStatus = {};
2364
2558
  for (const node of this.nodes.values()) {
2365
2559
  nodesByType[node.type] = (nodesByType[node.type] || 0) + 1;
2560
+ nodesByStatus[node.status] = (nodesByStatus[node.status] || 0) + 1;
2366
2561
  }
2367
2562
  lines.push(`Nodes: ${Object.entries(nodesByType).map(([t, c]) => `${c} ${t}s`).join(", ")}`);
2368
- 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)`);
2369
2565
  const chains = this.recommendChains();
2370
2566
  if (chains.length > 0) {
2371
2567
  lines.push("");
2372
- lines.push("RECOMMENDED NEXT ACTIONS:");
2568
+ lines.push("RECOMMENDED ATTACK PATHS (try these):");
2373
2569
  for (let i = 0; i < Math.min(GRAPH_LIMITS.PROMPT_CHAINS, chains.length); i++) {
2374
2570
  const c = chains[i];
2375
2571
  const prob = (c.probability * 100).toFixed(0);
2376
- 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}`);
2377
2580
  }
2378
2581
  }
2379
- 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);
2380
2583
  if (unexploitedVulns.length > 0) {
2381
2584
  lines.push("");
2382
2585
  lines.push(`UNEXPLOITED VULNERABILITIES (${unexploitedVulns.length}):`);
2383
2586
  for (const v of unexploitedVulns.slice(0, GRAPH_LIMITS.PROMPT_VULNS)) {
2384
- 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}]`);
2385
2609
  }
2386
2610
  }
2387
2611
  lines.push("</attack-graph>");
2388
2612
  return lines.join("\n");
2389
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 ──────────────────────────────────────────────────
2390
2712
  /**
2391
2713
  * Get graph stats for metrics display.
2392
2714
  */
2393
2715
  getStats() {
2394
- 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;
2395
2718
  return {
2396
2719
  nodes: this.nodes.size,
2397
2720
  edges: this.edges.length,
2398
- exploited,
2721
+ succeeded,
2722
+ failed,
2399
2723
  chains: this.recommendChains().length
2400
2724
  };
2401
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
+ }
2402
2750
  };
2403
2751
 
2404
2752
  // src/shared/utils/agent-memory.ts
@@ -2494,6 +2842,10 @@ var WorkingMemory = class {
2494
2842
  getEntries() {
2495
2843
  return [...this.entries];
2496
2844
  }
2845
+ /** Clear all working memory entries. */
2846
+ clear() {
2847
+ this.entries = [];
2848
+ }
2497
2849
  };
2498
2850
  var EpisodicMemory = class {
2499
2851
  events = [];
@@ -2549,6 +2901,10 @@ var EpisodicMemory = class {
2549
2901
  lines.push("</session-timeline>");
2550
2902
  return lines.join("\n");
2551
2903
  }
2904
+ /** Clear all episodic events. */
2905
+ clear() {
2906
+ this.events = [];
2907
+ }
2552
2908
  };
2553
2909
  var MEMORY_DIR = "/tmp/pentesting-memory";
2554
2910
  var MEMORY_FILE = join3(MEMORY_DIR, "persistent-knowledge.json");
@@ -2733,6 +3089,10 @@ var DynamicTechniqueLibrary = class {
2733
3089
  getAll() {
2734
3090
  return [...this.techniques];
2735
3091
  }
3092
+ /** Clear all learned techniques. */
3093
+ clear() {
3094
+ this.techniques = [];
3095
+ }
2736
3096
  /**
2737
3097
  * Format for prompt injection.
2738
3098
  */
@@ -2841,6 +3201,32 @@ var SharedState = class {
2841
3201
  challengeAnalysis: null
2842
3202
  };
2843
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
+ }
2844
3230
  // --- Mission & Persistent Context ---
2845
3231
  setMissionSummary(summary) {
2846
3232
  this.data.missionSummary = summary;
@@ -3351,6 +3737,11 @@ var NOISE_CLASSIFICATION = {
3351
3737
  };
3352
3738
 
3353
3739
  // src/shared/utils/binary-analysis.ts
3740
+ var RELRO_STATUS = {
3741
+ NO: "no",
3742
+ PARTIAL: "partial",
3743
+ FULL: "full"
3744
+ };
3354
3745
  function parseChecksec(output) {
3355
3746
  const info = {};
3356
3747
  if (/x86-64|amd64/i.test(output)) {
@@ -3404,10 +3795,10 @@ function suggestExploitTechniques(info) {
3404
3795
  if (info.pie === true && info.canary === false) {
3405
3796
  suggestions.push("PIE enabled \u2192 Need info leak first (partial overwrite, format string)");
3406
3797
  }
3407
- if (info.relro === "partial") {
3798
+ if (info.relro === RELRO_STATUS.PARTIAL) {
3408
3799
  suggestions.push("Partial RELRO \u2192 GOT overwrite possible");
3409
3800
  }
3410
- if (info.relro === "full") {
3801
+ if (info.relro === RELRO_STATUS.FULL) {
3411
3802
  suggestions.push("Full RELRO \u2192 GOT is read-only, try __malloc_hook/__free_hook or stack-based attacks");
3412
3803
  }
3413
3804
  if (info.canary === true) {
@@ -3442,6 +3833,11 @@ function formatBinaryAnalysis(info) {
3442
3833
  }
3443
3834
 
3444
3835
  // src/shared/utils/structured-output.ts
3836
+ var PORT_STATE = {
3837
+ OPEN: "open",
3838
+ CLOSED: "closed",
3839
+ FILTERED: "filtered"
3840
+ };
3445
3841
  function extractNmapStructured(output) {
3446
3842
  const ports = [];
3447
3843
  const hosts = [];
@@ -3459,8 +3855,8 @@ function extractNmapStructured(output) {
3459
3855
  while ((match = hostRegex.exec(output)) !== null) {
3460
3856
  hosts.push(match[1]);
3461
3857
  }
3462
- const openCount = ports.filter((p) => p.state === "open").length;
3463
- 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(", ")}`;
3464
3860
  return {
3465
3861
  summary,
3466
3862
  structured: { openPorts: ports, hosts }
@@ -4032,7 +4428,7 @@ Resource Safety:
4032
4428
  const action = params.action;
4033
4429
  const processId = params.process_id;
4034
4430
  switch (action) {
4035
- case "list": {
4431
+ case PROCESS_ACTIONS.LIST: {
4036
4432
  const procs = listBackgroundProcesses();
4037
4433
  if (procs.length === 0) {
4038
4434
  return { success: true, output: "No background processes running.\nNo ports in use." };
@@ -4063,7 +4459,7 @@ Ports in use: ${usedPorts.join(", ")}` : "\nNo ports in use.";
4063
4459
  ${lines.join("\n\n")}${portLine}${eventLines}`
4064
4460
  };
4065
4461
  }
4066
- case "status": {
4462
+ case PROCESS_ACTIONS.STATUS: {
4067
4463
  if (!processId) return { success: false, output: "", error: "Missing required parameter: process_id" };
4068
4464
  const output = getProcessOutput(processId);
4069
4465
  if (!output) return { success: false, output: "", error: `Process ${processId} not found.` };
@@ -4103,7 +4499,7 @@ ${output.stdout.slice(-SYSTEM_LIMITS.MAX_STDOUT_SLICE) || "(empty)"}
4103
4499
  ${output.stderr.slice(-SYSTEM_LIMITS.MAX_STDERR_SLICE) || "(empty)"}` + connectionHint + interactHint
4104
4500
  };
4105
4501
  }
4106
- case "interact": {
4502
+ case PROCESS_ACTIONS.INTERACT: {
4107
4503
  if (!processId) return { success: false, output: "", error: "Missing process_id for interact" };
4108
4504
  const cmd = params.command;
4109
4505
  if (!cmd) return { success: false, output: "", error: "Missing command for interact. Provide the command to execute on the target." };
@@ -4121,7 +4517,7 @@ ${result2.newOutput}
4121
4517
  ${result2.output}`
4122
4518
  };
4123
4519
  }
4124
- case "promote": {
4520
+ case PROCESS_ACTIONS.PROMOTE: {
4125
4521
  if (!processId) return { success: false, output: "", error: "Missing process_id for promote" };
4126
4522
  const desc = params.description;
4127
4523
  const success = promoteToShell(processId, desc);
@@ -4142,11 +4538,11 @@ Next steps:
4142
4538
  5. Begin post-exploitation enumeration: whoami, sudo -l, etc.`
4143
4539
  };
4144
4540
  }
4145
- case "stop": {
4541
+ case PROCESS_ACTIONS.STOP: {
4146
4542
  if (!processId) return { success: false, output: "", error: "Missing process_id" };
4147
4543
  return stopBackgroundProcess(processId);
4148
4544
  }
4149
- case "stop_all": {
4545
+ case PROCESS_ACTIONS.STOP_ALL: {
4150
4546
  const procs = listBackgroundProcesses();
4151
4547
  if (procs.length === 0) return { success: true, output: "No background processes to stop." };
4152
4548
  const activeShells = procs.filter((p) => p.role === PROCESS_ROLES.ACTIVE_SHELL && p.isRunning);
@@ -5217,6 +5613,11 @@ async function searchWithPlaywright(query) {
5217
5613
  }
5218
5614
 
5219
5615
  // src/engine/tools-mid.ts
5616
+ var PORT_STATE2 = {
5617
+ OPEN: "open",
5618
+ CLOSED: "closed",
5619
+ FILTERED: "filtered"
5620
+ };
5220
5621
  function getErrorMessage2(error) {
5221
5622
  return error instanceof Error ? error.message : String(error);
5222
5623
  }
@@ -5249,7 +5650,7 @@ async function parseNmap(xmlPath) {
5249
5650
  const serviceMatch = portMatch[0].match(/<service[^>]*name="([^"]*)"(?:[^>]*version="([^"]*)")?/);
5250
5651
  const service = serviceMatch ? serviceMatch[1] : void 0;
5251
5652
  const version = serviceMatch && serviceMatch[2] ? serviceMatch[2] : void 0;
5252
- if (state === "open") {
5653
+ if (state === PORT_STATE2.OPEN) {
5253
5654
  ports.push({ port, protocol, state, service, version });
5254
5655
  results.summary.openPorts++;
5255
5656
  if (service) results.summary.servicesFound++;
@@ -5819,6 +6220,54 @@ For CVEs not in this list, use web_search to find them.`
5819
6220
  ];
5820
6221
 
5821
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
+ };
5822
6271
  function urlEncode(s) {
5823
6272
  return [...s].map((c) => {
5824
6273
  const code = c.charCodeAt(0);
@@ -5879,12 +6328,12 @@ function whitespaceAlt(s) {
5879
6328
  const alt = alts[Math.floor(Math.random() * alts.length)];
5880
6329
  return s.replace(/ /g, alt);
5881
6330
  }
5882
- function charFunction(s, dbType = "mysql") {
6331
+ function charFunction(s, dbType = DATABASE_TYPE.MYSQL) {
5883
6332
  const chars = [...s].map((c) => c.charCodeAt(0));
5884
- if (dbType === "mysql") {
6333
+ if (dbType === DATABASE_TYPE.MYSQL) {
5885
6334
  return `CHAR(${chars.join(",")})`;
5886
6335
  }
5887
- if (dbType === "mssql") {
6336
+ if (dbType === DATABASE_TYPE.MSSQL) {
5888
6337
  return chars.map((c) => `CHAR(${c})`).join("+");
5889
6338
  }
5890
6339
  return chars.map((c) => `CHR(${c})`).join("||");
@@ -5969,78 +6418,78 @@ function mutatePayload(request) {
5969
6418
  const activeTransforms = transforms || getDefaultTransforms(context);
5970
6419
  for (const transform of activeTransforms) {
5971
6420
  switch (transform) {
5972
- case "url":
6421
+ case TRANSFORM_TYPE.URL:
5973
6422
  variants.push({ payload: urlEncode(payload), transform: "url", description: "URL encoded (special chars only)" });
5974
6423
  variants.push({ payload: urlEncodeAll(payload), transform: "url_full", description: "URL encoded (all chars)" });
5975
6424
  break;
5976
- case "double_url":
6425
+ case TRANSFORM_TYPE.DOUBLE_URL:
5977
6426
  variants.push({ payload: doubleUrlEncode(payload), transform: "double_url", description: "Double URL encoded" });
5978
6427
  break;
5979
- case "triple_url":
6428
+ case TRANSFORM_TYPE.TRIPLE_URL:
5980
6429
  variants.push({ payload: tripleUrlEncode(payload), transform: "triple_url", description: "Triple URL encoded" });
5981
6430
  break;
5982
- case "unicode":
6431
+ case TRANSFORM_TYPE.UNICODE:
5983
6432
  variants.push({ payload: unicodeEncode(payload), transform: "unicode", description: "Unicode escape sequences" });
5984
6433
  break;
5985
- case "html_entity_dec":
6434
+ case TRANSFORM_TYPE.HTML_ENTITY_DEC:
5986
6435
  variants.push({ payload: htmlEntityDec(payload), transform: "html_entity_dec", description: "HTML decimal entities" });
5987
6436
  break;
5988
- case "html_entity_hex":
6437
+ case TRANSFORM_TYPE.HTML_ENTITY_HEX:
5989
6438
  variants.push({ payload: htmlEntityHex(payload), transform: "html_entity_hex", description: "HTML hex entities" });
5990
6439
  break;
5991
- case "hex":
6440
+ case TRANSFORM_TYPE.HEX:
5992
6441
  variants.push({ payload: hexEncode(payload), transform: "hex", description: "Hex encoded" });
5993
6442
  break;
5994
- case "octal":
6443
+ case TRANSFORM_TYPE.OCTAL:
5995
6444
  variants.push({ payload: octalEncode(payload), transform: "octal", description: "Octal encoded" });
5996
6445
  break;
5997
- case "base64":
6446
+ case TRANSFORM_TYPE.BASE64:
5998
6447
  variants.push({ payload: base64Encode(payload), transform: "base64", description: "Base64 encoded" });
5999
6448
  break;
6000
- case "case_swap":
6449
+ case TRANSFORM_TYPE.CASE_SWAP:
6001
6450
  for (let i = 0; i < 3; i++) {
6002
6451
  variants.push({ payload: caseSwap(payload), transform: "case_swap", description: `Case variation ${i + 1}` });
6003
6452
  }
6004
6453
  break;
6005
- case "comment_insert":
6454
+ case TRANSFORM_TYPE.COMMENT_INSERT:
6006
6455
  variants.push({ payload: commentInsert(payload), transform: "comment_insert", description: "SQL comments in keywords" });
6007
6456
  break;
6008
- case "whitespace_alt":
6457
+ case TRANSFORM_TYPE.WHITESPACE_ALT:
6009
6458
  variants.push({ payload: whitespaceAlt(payload), transform: "whitespace_alt", description: "Alternative whitespace chars" });
6010
6459
  variants.push({ payload: payload.replace(/ /g, "/**/"), transform: "whitespace_comment", description: "Comment as whitespace" });
6011
6460
  variants.push({ payload: payload.replace(/ /g, "+"), transform: "whitespace_plus", description: "Plus as whitespace" });
6012
6461
  break;
6013
- case "char_function":
6462
+ case TRANSFORM_TYPE.CHAR_FUNCTION:
6014
6463
  variants.push({ payload: charFunction(payload, "mysql"), transform: "char_mysql", description: "MySQL CHAR() encoding" });
6015
6464
  variants.push({ payload: charFunction(payload, "mssql"), transform: "char_mssql", description: "MSSQL CHAR() encoding" });
6016
6465
  variants.push({ payload: charFunction(payload, "pg"), transform: "char_pg", description: "PostgreSQL CHR() encoding" });
6017
6466
  break;
6018
- case "concat_split":
6467
+ case TRANSFORM_TYPE.CONCAT_SPLIT:
6019
6468
  variants.push({ payload: concatSplit(payload), transform: "concat_split", description: "String split via CONCAT" });
6020
6469
  break;
6021
- case "null_byte":
6470
+ case TRANSFORM_TYPE.NULL_BYTE:
6022
6471
  variants.push({ payload: nullByteAppend(payload), transform: "null_byte", description: "Null byte appended" });
6023
6472
  variants.push({ payload: payload + "%00.jpg", transform: "null_byte_ext", description: "Null byte + fake extension" });
6024
6473
  break;
6025
- case "reverse":
6474
+ case TRANSFORM_TYPE.REVERSE:
6026
6475
  variants.push({ payload: reversePayload(payload), transform: "reverse", description: "Reversed (use with rev | sh)" });
6027
6476
  break;
6028
- case "utf8_overlong":
6477
+ case TRANSFORM_TYPE.UTF8_OVERLONG:
6029
6478
  variants.push({ payload: utf8Overlong(payload), transform: "utf8_overlong", description: "UTF-8 overlong sequences" });
6030
6479
  break;
6031
- case "mixed_encoding":
6480
+ case TRANSFORM_TYPE.MIXED_ENCODING:
6032
6481
  variants.push({ payload: mixedEncoding(payload), transform: "mixed_encoding", description: "Mixed encoding (partial)" });
6033
6482
  break;
6034
- case "tag_alternative":
6035
- case "event_handler":
6036
- case "js_alternative":
6483
+ case TRANSFORM_TYPE.TAG_ALTERNATIVE:
6484
+ case TRANSFORM_TYPE.EVENT_HANDLER:
6485
+ case TRANSFORM_TYPE.JS_ALTERNATIVE:
6037
6486
  variants.push(...generateXssAlternatives(payload));
6038
6487
  break;
6039
- case "keyword_bypass":
6488
+ case TRANSFORM_TYPE.KEYWORD_BYPASS:
6040
6489
  variants.push({ payload: keywordBypass(payload), transform: "keyword_bypass", description: "Quote-inserted keyword bypass" });
6041
6490
  variants.push({ payload: spaceBypass(payload), transform: "space_bypass", description: "$IFS space bypass" });
6042
6491
  break;
6043
- case "space_bypass":
6492
+ case TRANSFORM_TYPE.SPACE_BYPASS:
6044
6493
  variants.push({ payload: payload.replace(/ /g, "${IFS}"), transform: "ifs", description: "$IFS space bypass" });
6045
6494
  variants.push({ payload: payload.replace(/ /g, "%09"), transform: "tab", description: "Tab space bypass" });
6046
6495
  variants.push({ payload: payload.replace(/ /g, "<"), transform: "redirect", description: "Redirect as separator" });
@@ -6062,52 +6511,52 @@ function mutatePayload(request) {
6062
6511
  }
6063
6512
  function getDefaultTransforms(context) {
6064
6513
  switch (context) {
6065
- case "url_path":
6066
- case "url_param":
6067
- return ["url", "double_url", "unicode", "utf8_overlong", "mixed_encoding", "null_byte"];
6068
- case "html_body":
6069
- return ["html_entity_dec", "html_entity_hex", "unicode", "tag_alternative", "event_handler", "js_alternative"];
6070
- case "html_attr":
6071
- return ["html_entity_dec", "html_entity_hex", "event_handler"];
6072
- case "js_string":
6073
- return ["unicode", "hex", "base64", "js_alternative"];
6074
- case "sql_string":
6075
- case "sql_numeric":
6076
- return ["case_swap", "comment_insert", "whitespace_alt", "char_function", "concat_split", "url", "double_url"];
6077
- case "shell_cmd":
6078
- return ["keyword_bypass", "space_bypass", "base64", "reverse", "hex", "octal"];
6079
- case "xml_body":
6080
- return ["html_entity_dec", "html_entity_hex", "unicode"];
6081
- case "json_body":
6082
- return ["unicode"];
6083
- case "http_header":
6084
- case "cookie":
6085
- 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];
6086
6535
  default:
6087
- 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];
6088
6537
  }
6089
6538
  }
6090
6539
  function getContextRecommendations(context, variantCount) {
6091
6540
  const recs = [];
6092
6541
  recs.push(`Generated ${variantCount} variants for context: ${context}`);
6093
6542
  switch (context) {
6094
- case "url_param":
6543
+ case PAYLOAD_CONTEXT.URL_PARAM:
6095
6544
  recs.push("If all URL encoding fails: try HTTP Parameter Pollution (send same param twice)");
6096
6545
  recs.push("Try switching GET \u2192 POST or changing Content-Type");
6097
6546
  recs.push("Check if WebSocket endpoint exists (often unfiltered)");
6098
6547
  break;
6099
- case "sql_string":
6100
- case "sql_numeric":
6548
+ case PAYLOAD_CONTEXT.SQL_STRING:
6549
+ case PAYLOAD_CONTEXT.SQL_NUMERIC:
6101
6550
  recs.push("If inline comments fail: try MySQL version comments /*!50000SELECT*/");
6102
6551
  recs.push("Try time-based blind if error/union payloads are blocked");
6103
6552
  recs.push("Use sqlmap with --tamper scripts for automated bypass");
6104
6553
  break;
6105
- case "html_body":
6554
+ case PAYLOAD_CONTEXT.HTML_BODY:
6106
6555
  recs.push("If common XSS tags blocked: try SVG, MathML, mutation XSS");
6107
6556
  recs.push("Check CSP header \u2014 it determines what JS execution is possible");
6108
6557
  recs.push("Try DOM-based XSS via document.location, innerHTML, postMessage");
6109
6558
  break;
6110
- case "shell_cmd":
6559
+ case PAYLOAD_CONTEXT.SHELL_CMD:
6111
6560
  recs.push("Use ${IFS} for space, quotes for keyword bypass, base64 for full obfuscation");
6112
6561
  recs.push("Try: echo PAYLOAD_BASE64 | base64 -d | sh");
6113
6562
  recs.push("Variable concatenation: a=c;b=at;$a$b /etc/passwd");
@@ -6237,8 +6686,8 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
6237
6686
  }
6238
6687
  },
6239
6688
  execute: async (p) => {
6240
- const { existsSync: existsSync9, statSync: statSync2, readdirSync: readdirSync3 } = await import("fs");
6241
- const { join: join11 } = await import("path");
6689
+ const { existsSync: existsSync10, statSync: statSync2, readdirSync: readdirSync3 } = await import("fs");
6690
+ const { join: join12 } = await import("path");
6242
6691
  const category = p.category || "";
6243
6692
  const search = p.search || "";
6244
6693
  const minSize = p.min_size || 0;
@@ -6284,7 +6733,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
6284
6733
  results.push("");
6285
6734
  };
6286
6735
  const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
6287
- if (depth > maxDepth || !existsSync9(dirPath)) return;
6736
+ if (depth > maxDepth || !existsSync10(dirPath)) return;
6288
6737
  let entries;
6289
6738
  try {
6290
6739
  entries = readdirSync3(dirPath, { withFileTypes: true });
@@ -6293,7 +6742,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
6293
6742
  }
6294
6743
  for (const entry of entries) {
6295
6744
  if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
6296
- const fullPath = join11(dirPath, entry.name);
6745
+ const fullPath = join12(dirPath, entry.name);
6297
6746
  if (entry.isDirectory()) {
6298
6747
  scanDir(fullPath, maxDepth, depth + 1);
6299
6748
  continue;
@@ -6345,6 +6794,9 @@ var globalCredentialHandler = null;
6345
6794
  function setCredentialHandler(handler) {
6346
6795
  globalCredentialHandler = handler;
6347
6796
  }
6797
+ function clearCredentialHandler() {
6798
+ globalCredentialHandler = null;
6799
+ }
6348
6800
  async function requestCredential(request) {
6349
6801
  if (!globalCredentialHandler) {
6350
6802
  return {
@@ -7542,6 +7994,11 @@ var FILE_SHARING_CONFIG = {
7542
7994
  };
7543
7995
 
7544
7996
  // src/domains/cloud/tools.ts
7997
+ var CLOUD_PROVIDER = {
7998
+ AWS: "aws",
7999
+ AZURE: "azure",
8000
+ GCP: "gcp"
8001
+ };
7545
8002
  var CLOUD_TOOLS = [
7546
8003
  {
7547
8004
  name: TOOL_NAMES.AWS_S3_CHECK,
@@ -7564,8 +8021,8 @@ var CLOUD_TOOLS = [
7564
8021
  required: ["provider"],
7565
8022
  execute: async (params) => {
7566
8023
  const provider = params.provider;
7567
- if (provider === "aws") return await runCommand("curl", ["http://169.254.169.254/latest/meta-data/"]);
7568
- 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"]);
7569
8026
  return await runCommand("curl", ["-H", "Metadata-Flavor: Google", "http://metadata.google.internal/computeMetadata/v1/"]);
7570
8027
  }
7571
8028
  }
@@ -8223,6 +8680,12 @@ var LLM_ERROR_TYPES = {
8223
8680
 
8224
8681
  // src/engine/llm-types.ts
8225
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
+ };
8226
8689
  var LLMError = class extends Error {
8227
8690
  /** Structured error information */
8228
8691
  errorInfo;
@@ -8245,10 +8708,10 @@ function classifyError(error) {
8245
8708
  if (statusCode === HTTP_STATUS.BAD_REQUEST) {
8246
8709
  return { type: LLM_ERROR_TYPES.INVALID_REQUEST, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Modify request" };
8247
8710
  }
8248
- 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) {
8249
8712
  return { type: LLM_ERROR_TYPES.NETWORK_ERROR, message: errorMessage, isRetryable: true, suggestedAction: "Check network" };
8250
8713
  }
8251
- if (errorMessage.toLowerCase().includes("timeout") || e.code === "UND_ERR_CONNECT_TIMEOUT") {
8714
+ if (errorMessage.toLowerCase().includes("timeout") || e.code === NETWORK_ERROR_CODES.CONNECT_TIMEOUT) {
8252
8715
  return { type: LLM_ERROR_TYPES.TIMEOUT, message: errorMessage, isRetryable: true, suggestedAction: "Retry" };
8253
8716
  }
8254
8717
  return { type: LLM_ERROR_TYPES.UNKNOWN, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Analyze error" };
@@ -8594,7 +9057,7 @@ var __filename2 = fileURLToPath3(import.meta.url);
8594
9057
  var __dirname3 = dirname4(__filename2);
8595
9058
 
8596
9059
  // src/engine/state-persistence.ts
8597
- 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";
8598
9061
  import { join as join9 } from "path";
8599
9062
  function saveState(state) {
8600
9063
  const sessionsDir = WORKSPACE.SESSIONS;
@@ -8676,6 +9139,28 @@ function loadState(state) {
8676
9139
  return false;
8677
9140
  }
8678
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
+ }
8679
9164
 
8680
9165
  // src/agents/tool-error-enrichment.ts
8681
9166
  function enrichToolErrorContext(ctx) {
@@ -10199,13 +10684,21 @@ var PHASE_TECHNIQUE_MAP = {
10199
10684
  };
10200
10685
  var PromptBuilder = class {
10201
10686
  state;
10687
+ strategist = null;
10202
10688
  constructor(state) {
10203
10689
  this.state = state;
10204
10690
  }
10205
10691
  /**
10206
- * Build complete prompt for LLM iteration.
10692
+ * Attach a Strategist instance for meta-prompting.
10693
+ * Called by MainAgent after construction.
10694
+ */
10695
+ setStrategist(strategist) {
10696
+ this.strategist = strategist;
10697
+ }
10698
+ /**
10699
+ * Build complete prompt for LLM iteration (async).
10207
10700
  *
10208
- * Layers (phase-aware, enhanced with improvements #2,#3,#6,#7,#8,#12,#14):
10701
+ * Layers (phase-aware, enhanced with D-CIPHER meta-prompting):
10209
10702
  * 1. base.md — Core identity, rules, autonomy, shell management (~7.6K tok)
10210
10703
  * 2. Phase-specific prompt — current phase's full specialist knowledge (~2K tok)
10211
10704
  * 3. Core methodology — strategy, orchestrator, evasion (always loaded, ~12K tok)
@@ -10221,9 +10714,10 @@ var PromptBuilder = class {
10221
10714
  * 12. Session timeline (#12: episodic memory)
10222
10715
  * 13. Learned techniques (#7: dynamic technique library)
10223
10716
  * 14. Persistent memory (#12: cross-session knowledge)
10224
- * 15. User context
10717
+ * 15. STRATEGIC DIRECTIVE — LLM-generated tactical instructions (D-CIPHER)
10718
+ * 16. User context
10225
10719
  */
10226
- build(userInput, phase) {
10720
+ async build(userInput, phase) {
10227
10721
  const fragments = [
10228
10722
  this.loadPromptFile(PROMPT_PATHS.BASE),
10229
10723
  this.loadCtfModePrompt(),
@@ -10246,10 +10740,14 @@ var PromptBuilder = class {
10246
10740
  // #12
10247
10741
  this.getDynamicTechniquesFragment(),
10248
10742
  // #7
10249
- this.getPersistentMemoryFragment(),
10743
+ this.getPersistentMemoryFragment()
10250
10744
  // #12
10251
- PROMPT_DEFAULTS.USER_CONTEXT(userInput)
10252
10745
  ];
10746
+ const strategistDirective = await this.getStrategistFragment();
10747
+ if (strategistDirective) {
10748
+ fragments.push(strategistDirective);
10749
+ }
10750
+ fragments.push(PROMPT_DEFAULTS.USER_CONTEXT(userInput));
10253
10751
  return fragments.filter((f) => !!f).join("\n\n");
10254
10752
  }
10255
10753
  /**
@@ -10439,11 +10937,213 @@ ${lines.join("\n")}
10439
10937
  }
10440
10938
  return this.state.persistentMemory.toPrompt(services);
10441
10939
  }
10940
+ // --- D-CIPHER: Strategist Meta-Prompting ---
10941
+ /**
10942
+ * Generate strategic directive via Strategist LLM.
10943
+ * Called every turn. Returns '' if no strategist is attached or call fails.
10944
+ */
10945
+ async getStrategistFragment() {
10946
+ if (!this.strategist) return "";
10947
+ return this.strategist.generateDirective();
10948
+ }
10949
+ };
10950
+
10951
+ // src/agents/strategist.ts
10952
+ import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
10953
+ import { join as join11, dirname as dirname6 } from "path";
10954
+ import { fileURLToPath as fileURLToPath5 } from "url";
10955
+
10956
+ // src/shared/constants/strategist.ts
10957
+ var STRATEGIST_LIMITS = {
10958
+ /** Maximum characters of state context sent to Strategist LLM.
10959
+ * WHY: Keeps Strategist input focused and affordable (~3-5K tokens).
10960
+ * Full state can be 20K+; Strategist needs summary, not everything. */
10961
+ MAX_INPUT_CHARS: 15e3,
10962
+ /** Maximum characters for the Strategist's response.
10963
+ * WHY: Directives should be terse and actionable (~500-800 tokens).
10964
+ * Longer = more cost, less focus. */
10965
+ MAX_OUTPUT_CHARS: 3e3,
10966
+ /** Maximum lines in the directive output.
10967
+ * WHY: Forces concise, prioritized directives. */
10968
+ MAX_DIRECTIVE_LINES: 40
10969
+ };
10970
+
10971
+ // src/agents/strategist.ts
10972
+ var __dirname5 = dirname6(fileURLToPath5(import.meta.url));
10973
+ var STRATEGIST_PROMPT_PATH = join11(__dirname5, "prompts", "strategist-system.md");
10974
+ var Strategist = class {
10975
+ llm;
10976
+ state;
10977
+ systemPrompt;
10978
+ // Metrics
10979
+ totalTokenCost = 0;
10980
+ totalCalls = 0;
10981
+ lastDirective = null;
10982
+ constructor(llm, state) {
10983
+ this.llm = llm;
10984
+ this.state = state;
10985
+ this.systemPrompt = this.loadSystemPrompt();
10986
+ }
10987
+ /**
10988
+ * Generate a fresh strategic directive for this turn.
10989
+ * Called every iteration by PromptBuilder.
10990
+ *
10991
+ * @returns Formatted directive string for prompt injection, or '' on failure
10992
+ */
10993
+ async generateDirective() {
10994
+ try {
10995
+ const input = this.buildInput();
10996
+ const directive = await this.callLLM(input);
10997
+ this.lastDirective = directive;
10998
+ this.totalCalls++;
10999
+ debugLog("general", "Strategist directive generated", {
11000
+ tokens: directive.tokenCost,
11001
+ totalCalls: this.totalCalls,
11002
+ contentLength: directive.content.length
11003
+ });
11004
+ return this.formatForPrompt(directive);
11005
+ } catch (err) {
11006
+ debugLog("general", "Strategist failed \u2014 agent will proceed without directive", {
11007
+ error: String(err)
11008
+ });
11009
+ if (this.lastDirective?.content) {
11010
+ return this.formatForPrompt(this.lastDirective, true);
11011
+ }
11012
+ return "";
11013
+ }
11014
+ }
11015
+ // ─── Input Construction ─────────────────────────────────────
11016
+ /**
11017
+ * Build the user message for the Strategist LLM.
11018
+ * Assembles state, memory, attack graph, and timeline into a focused context.
11019
+ */
11020
+ buildInput() {
11021
+ const sections = [];
11022
+ sections.push("## Engagement State");
11023
+ sections.push(this.state.toPrompt());
11024
+ const timeline = this.state.episodicMemory.toPrompt();
11025
+ if (timeline) {
11026
+ sections.push("");
11027
+ sections.push("## Recent Actions");
11028
+ sections.push(timeline);
11029
+ }
11030
+ const failures = this.state.workingMemory.toPrompt();
11031
+ if (failures) {
11032
+ sections.push("");
11033
+ sections.push("## Failed Attempts (DO NOT REPEAT THESE)");
11034
+ sections.push(failures);
11035
+ }
11036
+ const graph = this.state.attackGraph.toPrompt();
11037
+ if (graph) {
11038
+ sections.push("");
11039
+ sections.push("## Attack Graph");
11040
+ sections.push(graph);
11041
+ }
11042
+ const techniques = this.state.dynamicTechniques.toPrompt();
11043
+ if (techniques) {
11044
+ sections.push("");
11045
+ sections.push("## Learned Techniques");
11046
+ sections.push(techniques);
11047
+ }
11048
+ sections.push("");
11049
+ sections.push("## Time");
11050
+ sections.push(this.state.getTimeStatus());
11051
+ const analysis = this.state.getChallengeAnalysis();
11052
+ if (analysis && analysis.primaryType !== "unknown") {
11053
+ sections.push("");
11054
+ sections.push(`## Challenge Type: ${analysis.primaryType.toUpperCase()} (${(analysis.confidence * 100).toFixed(0)}%)`);
11055
+ sections.push(analysis.strategySuggestion);
11056
+ }
11057
+ let input = sections.join("\n");
11058
+ if (input.length > STRATEGIST_LIMITS.MAX_INPUT_CHARS) {
11059
+ input = input.slice(0, STRATEGIST_LIMITS.MAX_INPUT_CHARS) + "\n\n... [state truncated for Strategist context]";
11060
+ }
11061
+ return input;
11062
+ }
11063
+ // ─── LLM Call ───────────────────────────────────────────────
11064
+ async callLLM(input) {
11065
+ const messages = [{
11066
+ role: "user",
11067
+ content: `Analyze this penetration test situation and write a tactical directive for the attack agent.
11068
+
11069
+ ${input}`
11070
+ }];
11071
+ const response = await this.llm.generateResponse(
11072
+ messages,
11073
+ void 0,
11074
+ // No tools — strategy generation only
11075
+ this.systemPrompt
11076
+ );
11077
+ let content = response.content || "";
11078
+ if (content.length > STRATEGIST_LIMITS.MAX_OUTPUT_CHARS) {
11079
+ content = content.slice(0, STRATEGIST_LIMITS.MAX_OUTPUT_CHARS) + "\n... [directive truncated]";
11080
+ }
11081
+ const cost = response.usage ? response.usage.input_tokens + response.usage.output_tokens : 0;
11082
+ this.totalTokenCost += cost;
11083
+ return {
11084
+ content,
11085
+ generatedAt: Date.now(),
11086
+ tokenCost: cost
11087
+ };
11088
+ }
11089
+ // ─── Formatting ─────────────────────────────────────────────
11090
+ /**
11091
+ * Format directive for injection into the attack agent's system prompt.
11092
+ */
11093
+ formatForPrompt(directive, isStale = false) {
11094
+ if (!directive.content) return "";
11095
+ const age = Math.floor((Date.now() - directive.generatedAt) / 6e4);
11096
+ const staleWarning = isStale ? `
11097
+ NOTE: This directive is from ${age}min ago (Strategist call failed this turn). Verify assumptions are still valid.` : "";
11098
+ return [
11099
+ "<strategic-directive>",
11100
+ "TACTICAL DIRECTIVE (generated by Strategist LLM \u2014 follow these priorities):",
11101
+ "",
11102
+ directive.content,
11103
+ staleWarning,
11104
+ "</strategic-directive>"
11105
+ ].filter(Boolean).join("\n");
11106
+ }
11107
+ // ─── System Prompt Loading ──────────────────────────────────
11108
+ loadSystemPrompt() {
11109
+ try {
11110
+ if (existsSync9(STRATEGIST_PROMPT_PATH)) {
11111
+ return readFileSync6(STRATEGIST_PROMPT_PATH, "utf-8");
11112
+ }
11113
+ } catch {
11114
+ }
11115
+ return FALLBACK_SYSTEM_PROMPT;
11116
+ }
11117
+ // ─── Public API ─────────────────────────────────────────────
11118
+ /** Get total token cost of all Strategist calls this session. */
11119
+ getTotalTokenCost() {
11120
+ return this.totalTokenCost;
11121
+ }
11122
+ /** Get number of Strategist calls this session. */
11123
+ getTotalCalls() {
11124
+ return this.totalCalls;
11125
+ }
11126
+ /** Get the most recent directive (for TUI display). */
11127
+ getLastDirective() {
11128
+ return this.lastDirective;
11129
+ }
11130
+ /** Reset strategist state (for /clear command). */
11131
+ reset() {
11132
+ this.lastDirective = null;
11133
+ this.totalTokenCost = 0;
11134
+ this.totalCalls = 0;
11135
+ }
10442
11136
  };
11137
+ var FALLBACK_SYSTEM_PROMPT = `You are a penetration testing strategist.
11138
+ Analyze the situation and write a precise tactical directive for the attack agent.
11139
+ Be specific: name exact tools, commands, and parameters.
11140
+ Maximum 30 lines. Prioritize by probability of success.
11141
+ NEVER suggest approaches listed in the failed attempts section.`;
10443
11142
 
10444
11143
  // src/agents/main-agent.ts
10445
11144
  var MainAgent = class extends CoreAgent {
10446
11145
  promptBuilder;
11146
+ strategist;
10447
11147
  approvalGate;
10448
11148
  scopeGuard;
10449
11149
  userInput = "";
@@ -10452,6 +11152,8 @@ var MainAgent = class extends CoreAgent {
10452
11152
  this.approvalGate = approvalGate;
10453
11153
  this.scopeGuard = scopeGuard;
10454
11154
  this.promptBuilder = new PromptBuilder(state);
11155
+ this.strategist = new Strategist(this.llm, state);
11156
+ this.promptBuilder.setStrategist(this.strategist);
10455
11157
  }
10456
11158
  /**
10457
11159
  * Public entry point for running the agent.
@@ -10462,7 +11164,8 @@ var MainAgent = class extends CoreAgent {
10462
11164
  this.emitStart(userInput);
10463
11165
  this.initializeTask();
10464
11166
  try {
10465
- const result2 = await this.run(userInput, this.getCurrentPrompt());
11167
+ const initialPrompt = await this.getCurrentPrompt();
11168
+ const result2 = await this.run(userInput, initialPrompt);
10466
11169
  return result2.output;
10467
11170
  } finally {
10468
11171
  try {
@@ -10478,9 +11181,10 @@ var MainAgent = class extends CoreAgent {
10478
11181
  /**
10479
11182
  * Override step to rebuild prompt dynamically each iteration.
10480
11183
  * This ensures the agent always sees the latest state, phase, and active processes.
11184
+ * The Strategist LLM generates a fresh tactical directive every turn.
10481
11185
  */
10482
11186
  async step(iteration, messages, _unusedPrompt, progress) {
10483
- const dynamicPrompt = this.getCurrentPrompt();
11187
+ const dynamicPrompt = await this.getCurrentPrompt();
10484
11188
  const result2 = await super.step(iteration, messages, dynamicPrompt, progress);
10485
11189
  this.emitStateChange();
10486
11190
  return result2;
@@ -10509,7 +11213,7 @@ var MainAgent = class extends CoreAgent {
10509
11213
  saveCurrentState() {
10510
11214
  return saveState(this.state);
10511
11215
  }
10512
- getCurrentPrompt() {
11216
+ async getCurrentPrompt() {
10513
11217
  const phase = this.state.getPhase() || PHASES.RECON;
10514
11218
  return this.promptBuilder.build(this.userInput, phase);
10515
11219
  }
@@ -10554,6 +11258,18 @@ var MainAgent = class extends CoreAgent {
10554
11258
  isCtfMode() {
10555
11259
  return this.state.isCtfMode();
10556
11260
  }
11261
+ /**
11262
+ * Full session reset — clears state, workspace files, and background processes.
11263
+ * Used by /clear command for a complete fresh start.
11264
+ */
11265
+ async resetSession() {
11266
+ await cleanupAllProcesses().catch(() => {
11267
+ });
11268
+ this.state.reset();
11269
+ this.userInput = "";
11270
+ this.strategist.reset();
11271
+ return clearWorkspace();
11272
+ }
10557
11273
  setScope(allowed, exclusions = []) {
10558
11274
  this.state.setScope({
10559
11275
  allowedCidrs: allowed.filter((a) => a.includes("/")),
@@ -10573,6 +11289,10 @@ var MainAgent = class extends CoreAgent {
10573
11289
  });
10574
11290
  this.emitStateChange();
10575
11291
  }
11292
+ /** Get the Strategist instance (for TUI metrics display). */
11293
+ getStrategist() {
11294
+ return this.strategist;
11295
+ }
10576
11296
  };
10577
11297
 
10578
11298
  // src/agents/factory.ts
@@ -10668,13 +11388,13 @@ var THEME = {
10668
11388
  secondary: "#cbd5e1",
10669
11389
  // Brighter slate
10670
11390
  muted: "#94a3b8",
10671
- // Lighter blue-gray (was too dark)
10672
- accent: "#38bdf8",
10673
- // Sky blue
11391
+ // Lighter blue-gray
11392
+ accent: "#1d4ed8",
11393
+ // Blue 700 - deep blue
10674
11394
  highlight: "#ffffff"
10675
11395
  // Pure white
10676
11396
  },
10677
- // Status colors
11397
+ // Status colors (deep blue-focused)
10678
11398
  status: {
10679
11399
  success: "#10b981",
10680
11400
  // Emerald
@@ -10682,10 +11402,10 @@ var THEME = {
10682
11402
  // Amber
10683
11403
  error: "#ef4444",
10684
11404
  // Red
10685
- info: "#0ea5e9",
10686
- // Sky
10687
- running: "#38bdf8",
10688
- // Bright Sky
11405
+ info: "#1d4ed8",
11406
+ // Blue 700
11407
+ running: "#1e40af",
11408
+ // Blue 800 (AI activity)
10689
11409
  pending: "#64748b"
10690
11410
  // Slate
10691
11411
  },
@@ -10699,27 +11419,29 @@ var THEME = {
10699
11419
  // Orange
10700
11420
  low: "#eab308",
10701
11421
  // Yellow
10702
- info: "#3b82f6"
10703
- // Blue
11422
+ info: "#1d4ed8"
11423
+ // Blue 700
10704
11424
  },
10705
11425
  // Border colors
10706
11426
  border: {
10707
11427
  default: "#1e293b",
10708
- focus: "#38bdf8",
11428
+ focus: "#3b82f6",
11429
+ // Blue 500 (UI decorative)
10709
11430
  error: "#ef4444",
10710
11431
  success: "#22c55e"
10711
11432
  },
10712
- // Phase colors
11433
+ // Phase colors (deep blue-focused)
10713
11434
  phase: {
10714
11435
  recon: "#94a3b8",
10715
- enum: "#38bdf8",
11436
+ enum: "#1d4ed8",
11437
+ // Blue 700 (phase indicator)
10716
11438
  vuln: "#f59e0b",
10717
11439
  exploit: "#ef4444",
10718
11440
  privesc: "#8b5cf6",
10719
11441
  persist: "#22c55e",
10720
11442
  report: "#64748b"
10721
11443
  },
10722
- // Accent colors
11444
+ // Accent colors (NO cyan/teal - pure blue palette)
10723
11445
  accent: {
10724
11446
  pink: "#f472b6",
10725
11447
  rose: "#fb7185",
@@ -10727,9 +11449,8 @@ var THEME = {
10727
11449
  purple: "#a78bfa",
10728
11450
  violet: "#8b5cf6",
10729
11451
  indigo: "#818cf8",
10730
- blue: "#60a5fa",
10731
- cyan: "#22d3ee",
10732
- teal: "#2dd4bf",
11452
+ blue: "#1d4ed8",
11453
+ // Blue 700 - primary accent
10733
11454
  emerald: "#34d399",
10734
11455
  green: "#4ade80",
10735
11456
  lime: "#a3e635",
@@ -10738,34 +11459,35 @@ var THEME = {
10738
11459
  orange: "#fb923c",
10739
11460
  red: "#f87171"
10740
11461
  },
10741
- // Gradients
11462
+ // Gradients (deep blue-focused)
10742
11463
  gradient: {
10743
11464
  cyber: [
10744
- "#38bdf8",
10745
- // Sky 400
10746
- "#34c6f4",
10747
- "#30d0f0",
10748
- "#2cd9ec",
10749
- "#28e3e8",
10750
- "#22d3ee",
10751
- // Cyan 400
10752
- "#25dbd6",
10753
- "#28e4be",
10754
- "#2dd4bf",
10755
- // Teal 400
10756
- "#31dbac",
10757
- "#34d399"
10758
- // Emerald 400
11465
+ "#3b82f6",
11466
+ // Blue 500
11467
+ "#3584f4",
11468
+ "#2f86f2",
11469
+ "#2988f0",
11470
+ "#238aee",
11471
+ "#1d8cec",
11472
+ // Mid blue
11473
+ "#1d7ad8",
11474
+ "#1d78c6",
11475
+ "#1d76b4",
11476
+ "#1d74a2",
11477
+ "#1e40af"
11478
+ // Blue 800
10759
11479
  ],
10760
11480
  danger: ["#ef4444", "#7f1d1d"],
10761
11481
  success: ["#22c55e", "#14532d"],
10762
11482
  gold: ["#f59e0b", "#78350f"],
10763
11483
  royal: ["#818cf8", "#312e81"]
10764
11484
  },
10765
- // Spinner color (Sky blue)
10766
- spinner: "#38bdf8",
10767
- // Identity color
10768
- identity: "#38bdf8"
11485
+ // Spinner color (deep blue — UI feedback)
11486
+ spinner: "#3b82f6",
11487
+ // Blue 500
11488
+ // Identity color (branded accent - deep blue)
11489
+ identity: "#1d4ed8"
11490
+ // Blue 700
10769
11491
  };
10770
11492
  var ASCII_BANNER = `
10771
11493
  ____ __ __ _
@@ -10811,39 +11533,39 @@ var ICONS = {
10811
11533
  // src/platform/tui/constants/display.ts
10812
11534
  var TUI_DISPLAY_LIMITS = {
10813
11535
  /** Reasoning buffer size for display */
10814
- reasoningBuffer: 1e3,
11536
+ reasoningBuffer: 2e3,
10815
11537
  /** Reasoning preview length */
10816
- reasoningPreview: 300,
11538
+ reasoningPreview: 500,
10817
11539
  /** Reasoning history slice for display */
10818
- reasoningHistorySlice: 500,
11540
+ reasoningHistorySlice: 1e3,
10819
11541
  /** Tool input preview length (for command display) */
10820
- toolInputPreview: 100,
11542
+ toolInputPreview: 200,
10821
11543
  /** Tool input truncated length */
10822
- toolInputTruncated: 97,
10823
- /** Tool output preview length */
10824
- toolOutputPreview: 500,
11544
+ toolInputTruncated: 197,
11545
+ /** Tool output preview length - increased to show full output */
11546
+ toolOutputPreview: 2e3,
10825
11547
  /** Error message preview length */
10826
- errorPreview: 300,
11548
+ errorPreview: 500,
10827
11549
  /** Status thought preview length */
10828
- statusThoughtPreview: 100,
11550
+ statusThoughtPreview: 150,
10829
11551
  /** Status thought truncated length */
10830
- statusThoughtTruncated: 97,
11552
+ statusThoughtTruncated: 147,
10831
11553
  /** Retry error preview length */
10832
- retryErrorPreview: 60,
11554
+ retryErrorPreview: 100,
10833
11555
  /** Retry error truncated length */
10834
- retryErrorTruncated: 57,
11556
+ retryErrorTruncated: 97,
10835
11557
  /** Timer update interval in ms */
10836
11558
  timerInterval: 1e3,
10837
11559
  /** Exit delay in ms */
10838
11560
  exitDelay: 100,
10839
11561
  /** Purpose text max length before truncation */
10840
- purposeMaxLength: 30,
11562
+ purposeMaxLength: 50,
10841
11563
  /** Purpose text truncated length */
10842
- purposeTruncated: 27,
11564
+ purposeTruncated: 47,
10843
11565
  /** Tool detail preview length in flow display */
10844
- toolDetailPreview: 100,
11566
+ toolDetailPreview: 200,
10845
11567
  /** Observe detail preview length in flow display */
10846
- observeDetailPreview: 80,
11568
+ observeDetailPreview: 150,
10847
11569
  /** Max flow nodes to display */
10848
11570
  maxFlowNodes: 50,
10849
11571
  /** Max stopped processes to show */
@@ -10858,7 +11580,7 @@ var MESSAGE_STYLES = {
10858
11580
  error: THEME.status.error,
10859
11581
  tool: THEME.status.running,
10860
11582
  result: THEME.text.muted,
10861
- status: THEME.accent.cyan
11583
+ status: THEME.accent.blue
10862
11584
  },
10863
11585
  prefixes: {
10864
11586
  user: "\u276F",
@@ -10871,30 +11593,46 @@ var MESSAGE_STYLES = {
10871
11593
  status: "\u25C8"
10872
11594
  }
10873
11595
  };
11596
+ var COMMAND_DEFINITIONS = [
11597
+ { name: "target", alias: "t", args: "<ip>", description: "Set target IP or domain" },
11598
+ { name: "start", alias: "s", args: "[goal]", description: "Start autonomous pentest" },
11599
+ { name: "findings", alias: "f", description: "Show discovered vulnerabilities" },
11600
+ { name: "graph", alias: "g", description: "Visualize the attack graph" },
11601
+ { name: "paths", alias: "p", description: "Show ranked attack paths" },
11602
+ { name: "assets", alias: "a", description: "List background processes" },
11603
+ { name: "logs", alias: "l", args: "<id>", description: "Show logs for an asset" },
11604
+ { name: "ctf", description: "Toggle CTF mode" },
11605
+ { name: "auto", description: "Toggle auto-approve mode" },
11606
+ { name: "clear", alias: "c", description: "Reset session & clean workspace" },
11607
+ { name: "help", alias: "h", description: "Show detailed help" },
11608
+ { name: "exit", alias: "q", description: "Exit the application" }
11609
+ ];
11610
+ function getMatchingCommands(partial) {
11611
+ const lower = partial.toLowerCase();
11612
+ if (!lower) return COMMAND_DEFINITIONS;
11613
+ return COMMAND_DEFINITIONS.filter(
11614
+ (cmd) => cmd.name.startsWith(lower) || cmd.alias && cmd.alias.startsWith(lower)
11615
+ );
11616
+ }
10874
11617
  var HELP_TEXT = `
10875
11618
  \u2500\u2500 Commands \u2500\u2500
10876
- /target <ip> Set target IP/domain
10877
- /start [goal] Start autonomous pentest (auto-approve enabled)
10878
- /findings Show discovered vulnerabilities
10879
- /assets List active background processes (listeners, shells)
10880
- /logs <id> Show full logs for a specific asset
10881
- /ctf Toggle CTF mode (default: ON)
10882
- /auto Toggle auto-approve mode
10883
- /clear Clear screen
10884
- /exit Exit
11619
+ ${COMMAND_DEFINITIONS.map((cmd) => {
11620
+ const usage = `/${cmd.name}${cmd.args ? " " + cmd.args : ""}`;
11621
+ const alias = cmd.alias ? ` (/${cmd.alias})` : "";
11622
+ return `${usage.padEnd(18)}${alias.padEnd(8)} ${cmd.description}`;
11623
+ }).join("\n")}
10885
11624
 
10886
11625
  \u2500\u2500 Features \u2500\u2500
10887
11626
  \u2022 Auto-install missing tools (apt/brew)
10888
11627
  \u2022 Transparent command execution
10889
11628
  \u2022 Interactive sudo password input
10890
11629
  \u2022 CTF mode: Auto flag detection & CTF-specific prompts
11630
+ \u2022 Attack graph: Tracks discovered paths & prevents repeating failures
10891
11631
 
10892
- \u2500\u2500 Examples \u2500\u2500
10893
- /target 192.168.1.1
10894
- /start "Full pentest on target"
10895
- /findings
10896
- /ctf (toggle CTF/standard mode)
10897
- /auto (toggle approval mode)
11632
+ \u2500\u2500 Tips \u2500\u2500
11633
+ \u2022 Type / and press Tab to autocomplete commands
11634
+ \u2022 /clear resets the entire session (messages, state, .pentesting data)
11635
+ \u2022 /start automatically enables auto-approve for hands-free operation
10898
11636
  `;
10899
11637
 
10900
11638
  // src/platform/tui/hooks/useAgentState.ts
@@ -11041,10 +11779,10 @@ var useAgentEvents = (agent, eventsRef, state) => {
11041
11779
  setCurrentTokens(tokenAccumRef.current + stepTokens);
11042
11780
  };
11043
11781
  const onFlagFound = (e) => {
11044
- addMessage("system", `\u{1F3F4} FLAG FOUND: ${e.data.flag} (total: ${e.data.totalFlags})`);
11782
+ addMessage("system", `[FLAG] ${e.data.flag} (total: ${e.data.totalFlags})`);
11045
11783
  };
11046
11784
  const onPhaseChange = (e) => {
11047
- addMessage("system", `\u{1F504} Phase: ${e.data.fromPhase} \u2192 ${e.data.toPhase} (${e.data.reason})`);
11785
+ addMessage("system", `[Phase] ${e.data.fromPhase} -> ${e.data.toPhase} (${e.data.reason})`);
11048
11786
  const s = agent.getState();
11049
11787
  setStats({
11050
11788
  phase: e.data.toPhase,
@@ -11113,6 +11851,9 @@ var useAgentEvents = (agent, eventsRef, state) => {
11113
11851
  return () => {
11114
11852
  events.removeAllListeners();
11115
11853
  clearAllTimers();
11854
+ clearInputHandler();
11855
+ clearCredentialHandler();
11856
+ clearCommandEventEmitter();
11116
11857
  };
11117
11858
  }, [
11118
11859
  agent,
@@ -11186,18 +11927,18 @@ Options: ${request.options.join(", ")}`;
11186
11927
  }
11187
11928
  function getCommandEventIcon(eventType) {
11188
11929
  const icons = {
11189
- [COMMAND_EVENT_TYPES.TOOL_MISSING]: "\u26A0\uFE0F",
11190
- [COMMAND_EVENT_TYPES.TOOL_INSTALL]: "\u{1F4E6}",
11191
- [COMMAND_EVENT_TYPES.TOOL_INSTALLED]: "\u2705",
11192
- [COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED]: "\u274C",
11193
- [COMMAND_EVENT_TYPES.TOOL_RETRY]: "\u{1F504}",
11194
- [COMMAND_EVENT_TYPES.COMMAND_START]: "\u25B6",
11195
- [COMMAND_EVENT_TYPES.COMMAND_SUCCESS]: "\u2713",
11196
- [COMMAND_EVENT_TYPES.COMMAND_FAILED]: "\u2717",
11197
- [COMMAND_EVENT_TYPES.COMMAND_ERROR]: "\u274C",
11198
- [COMMAND_EVENT_TYPES.INPUT_REQUIRED]: "\u{1F510}"
11930
+ [COMMAND_EVENT_TYPES.TOOL_MISSING]: "[!]",
11931
+ [COMMAND_EVENT_TYPES.TOOL_INSTALL]: "[install]",
11932
+ [COMMAND_EVENT_TYPES.TOOL_INSTALLED]: "[ok]",
11933
+ [COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED]: "[fail]",
11934
+ [COMMAND_EVENT_TYPES.TOOL_RETRY]: "[retry]",
11935
+ [COMMAND_EVENT_TYPES.COMMAND_START]: "[>]",
11936
+ [COMMAND_EVENT_TYPES.COMMAND_SUCCESS]: "[ok]",
11937
+ [COMMAND_EVENT_TYPES.COMMAND_FAILED]: "[x]",
11938
+ [COMMAND_EVENT_TYPES.COMMAND_ERROR]: "[err]",
11939
+ [COMMAND_EVENT_TYPES.INPUT_REQUIRED]: "[auth]"
11199
11940
  };
11200
- return icons[eventType] || "\u2022";
11941
+ return icons[eventType] || "[-]";
11201
11942
  }
11202
11943
 
11203
11944
  // src/platform/tui/hooks/useAgent.ts
@@ -11218,6 +11959,7 @@ var useAgent = (shouldAutoApprove, target) => {
11218
11959
  inputRequest,
11219
11960
  setInputRequest,
11220
11961
  stats,
11962
+ setStats,
11221
11963
  lastResponseMetaRef,
11222
11964
  addMessage,
11223
11965
  manageTimer,
@@ -11265,6 +12007,17 @@ var useAgent = (shouldAutoApprove, target) => {
11265
12007
  addMessage("system", "Input cancelled");
11266
12008
  }
11267
12009
  }, [setInputRequest, addMessage]);
12010
+ const refreshStats = useCallback2(() => {
12011
+ const s = agent.getState();
12012
+ setStats({
12013
+ phase: s.getPhase() || PHASES.RECON,
12014
+ targets: s.getTargets().size,
12015
+ findings: s.getFindings().length,
12016
+ todo: s.getTodo().length
12017
+ });
12018
+ resetCumulativeCounters();
12019
+ setCurrentStatus("");
12020
+ }, [agent, setStats, resetCumulativeCounters, setCurrentStatus]);
11268
12021
  return {
11269
12022
  agent,
11270
12023
  messages,
@@ -11280,11 +12033,13 @@ var useAgent = (shouldAutoApprove, target) => {
11280
12033
  executeTask,
11281
12034
  abort,
11282
12035
  cancelInputRequest,
11283
- addMessage
12036
+ addMessage,
12037
+ refreshStats
11284
12038
  };
11285
12039
  };
11286
12040
 
11287
12041
  // src/platform/tui/components/MessageList.tsx
12042
+ import { memo } from "react";
11288
12043
  import { Box as Box2, Text as Text2, Static } from "ink";
11289
12044
 
11290
12045
  // src/platform/tui/components/inline-status.tsx
@@ -11302,7 +12057,7 @@ function formatDuration2(ms) {
11302
12057
  }
11303
12058
  function getRoleColor(role) {
11304
12059
  const roleColors = {
11305
- listener: THEME.accent.cyan,
12060
+ listener: THEME.accent.blue,
11306
12061
  active_shell: THEME.accent.green,
11307
12062
  server: THEME.accent.blue,
11308
12063
  sniffer: THEME.accent.amber,
@@ -11447,7 +12202,7 @@ function parseStatusContent(content) {
11447
12202
  }
11448
12203
  return null;
11449
12204
  }
11450
- var MessageList = ({ messages }) => {
12205
+ var MessageList = memo(({ messages }) => {
11451
12206
  return /* @__PURE__ */ jsx2(Static, { items: messages, children: (msg) => {
11452
12207
  if (msg.type === "status") {
11453
12208
  const statusData = parseStatusContent(msg.content);
@@ -11468,18 +12223,19 @@ var MessageList = ({ messages }) => {
11468
12223
  msg.content
11469
12224
  ] }) }, msg.id);
11470
12225
  } });
11471
- };
12226
+ });
11472
12227
 
11473
12228
  // src/platform/tui/components/StatusDisplay.tsx
12229
+ import { memo as memo3 } from "react";
11474
12230
  import { Box as Box3, Text as Text4 } from "ink";
11475
12231
 
11476
12232
  // src/platform/tui/components/MusicSpinner.tsx
11477
- import { useState as useState3, useEffect as useEffect3 } from "react";
12233
+ import { useState as useState3, useEffect as useEffect3, memo as memo2 } from "react";
11478
12234
  import { Text as Text3 } from "ink";
11479
12235
  import { jsx as jsx3 } from "react/jsx-runtime";
11480
12236
  var FRAMES = ["\u2669", "\u266A", "\u266B", "\u266C", "\u266B", "\u266A"];
11481
- var INTERVAL = 400;
11482
- var MusicSpinner = ({ color }) => {
12237
+ var INTERVAL = 600;
12238
+ var MusicSpinner = memo2(({ color }) => {
11483
12239
  const [index, setIndex] = useState3(0);
11484
12240
  useEffect3(() => {
11485
12241
  const timer = setInterval(() => {
@@ -11488,11 +12244,11 @@ var MusicSpinner = ({ color }) => {
11488
12244
  return () => clearInterval(timer);
11489
12245
  }, []);
11490
12246
  return /* @__PURE__ */ jsx3(Text3, { color, children: FRAMES[index] });
11491
- };
12247
+ });
11492
12248
 
11493
12249
  // src/platform/tui/components/StatusDisplay.tsx
11494
12250
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
11495
- var StatusDisplay = ({
12251
+ var StatusDisplay = memo3(({
11496
12252
  retryState,
11497
12253
  isProcessing,
11498
12254
  currentStatus,
@@ -11514,7 +12270,7 @@ var StatusDisplay = ({
11514
12270
  " \xB7 ",
11515
12271
  truncateError(retryState.error)
11516
12272
  ] }),
11517
- /* @__PURE__ */ jsxs3(Text4, { color: THEME.accent.cyan, bold: true, children: [
12273
+ /* @__PURE__ */ jsxs3(Text4, { color: THEME.accent.blue, bold: true, children: [
11518
12274
  " \xB7 ",
11519
12275
  retryState.countdown,
11520
12276
  "s"
@@ -11532,13 +12288,15 @@ var StatusDisplay = ({
11532
12288
  ] })
11533
12289
  ] })
11534
12290
  ] });
11535
- };
12291
+ });
11536
12292
 
11537
12293
  // src/platform/tui/components/ChatInput.tsx
11538
- import { Box as Box4, Text as Text5 } from "ink";
12294
+ import { useMemo, useCallback as useCallback3, useRef as useRef3, memo as memo4 } from "react";
12295
+ import { Box as Box4, Text as Text5, useInput } from "ink";
11539
12296
  import TextInput from "ink-text-input";
11540
12297
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
11541
- var ChatInput = ({
12298
+ var MAX_SUGGESTIONS = 6;
12299
+ var ChatInput = memo4(({
11542
12300
  value,
11543
12301
  onChange,
11544
12302
  onSubmit,
@@ -11548,46 +12306,104 @@ var ChatInput = ({
11548
12306
  setSecretInput,
11549
12307
  onSecretSubmit
11550
12308
  }) => {
11551
- return /* @__PURE__ */ jsx5(
11552
- Box4,
11553
- {
11554
- borderStyle: "single",
11555
- borderColor: inputRequest.isActive ? THEME.status.warning : THEME.border.default,
11556
- paddingX: 1,
11557
- children: inputRequest.isActive ? /* @__PURE__ */ jsxs4(Box4, { children: [
11558
- /* @__PURE__ */ jsx5(Text5, { color: THEME.status.warning, children: "\u{1F512}" }),
11559
- /* @__PURE__ */ jsxs4(Text5, { color: THEME.text.muted, children: [
11560
- " ",
11561
- inputRequest.prompt
11562
- ] }),
11563
- /* @__PURE__ */ jsx5(
11564
- TextInput,
11565
- {
11566
- value: secretInput,
11567
- onChange: setSecretInput,
11568
- onSubmit: onSecretSubmit,
11569
- placeholder: "...",
11570
- mask: inputRequest.isPassword ? "\u2022" : void 0
11571
- }
11572
- )
11573
- ] }) : /* @__PURE__ */ jsxs4(Box4, { children: [
11574
- /* @__PURE__ */ jsx5(Text5, { color: THEME.text.secondary, children: "\u25B8" }),
11575
- /* @__PURE__ */ jsx5(Text5, { children: " " }),
11576
- /* @__PURE__ */ jsx5(
11577
- TextInput,
11578
- {
11579
- value,
11580
- onChange,
11581
- onSubmit,
11582
- placeholder
11583
- }
11584
- )
11585
- ] })
11586
- }
11587
- );
11588
- };
12309
+ const isSlashMode = value.startsWith("/");
12310
+ const partialCmd = isSlashMode ? value.slice(1).split(" ")[0] : "";
12311
+ const hasArgs = isSlashMode && value.includes(" ");
12312
+ const suggestions = useMemo(() => {
12313
+ if (!isSlashMode || hasArgs) return [];
12314
+ return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
12315
+ }, [isSlashMode, partialCmd, hasArgs]);
12316
+ const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
12317
+ const suggestionsRef = useRef3(suggestions);
12318
+ suggestionsRef.current = suggestions;
12319
+ const isSlashModeRef = useRef3(isSlashMode);
12320
+ isSlashModeRef.current = isSlashMode;
12321
+ const hasArgsRef = useRef3(hasArgs);
12322
+ hasArgsRef.current = hasArgs;
12323
+ const inputRequestRef = useRef3(inputRequest);
12324
+ inputRequestRef.current = inputRequest;
12325
+ const onChangeRef = useRef3(onChange);
12326
+ onChangeRef.current = onChange;
12327
+ useInput(useCallback3((_input, key) => {
12328
+ if (inputRequestRef.current.isActive) return;
12329
+ if (key.tab && isSlashModeRef.current && !hasArgsRef.current && suggestionsRef.current.length > 0) {
12330
+ const best = suggestionsRef.current[0];
12331
+ const argsHint = best.args ? " " : "";
12332
+ onChangeRef.current(`/${best.name}${argsHint}`);
12333
+ }
12334
+ }, []));
12335
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
12336
+ showPreview && /* @__PURE__ */ jsx5(
12337
+ Box4,
12338
+ {
12339
+ flexDirection: "column",
12340
+ borderStyle: "single",
12341
+ borderColor: THEME.border.default,
12342
+ paddingX: 1,
12343
+ marginBottom: 0,
12344
+ children: suggestions.map((cmd, i) => {
12345
+ const isFirst = i === 0;
12346
+ const nameColor = isFirst ? THEME.text.accent : THEME.text.secondary;
12347
+ const aliasText = cmd.alias ? ` /${cmd.alias}` : "";
12348
+ const argsText = cmd.args ? ` ${cmd.args}` : "";
12349
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
12350
+ /* @__PURE__ */ jsxs4(Text5, { color: nameColor, bold: isFirst, children: [
12351
+ "/",
12352
+ cmd.name
12353
+ ] }),
12354
+ /* @__PURE__ */ jsx5(Text5, { color: THEME.text.muted, children: argsText }),
12355
+ aliasText && /* @__PURE__ */ jsx5(Text5, { color: THEME.text.muted, children: aliasText }),
12356
+ /* @__PURE__ */ jsxs4(Text5, { color: THEME.text.muted, children: [
12357
+ " \u2014 ",
12358
+ cmd.description
12359
+ ] }),
12360
+ isFirst && /* @__PURE__ */ jsx5(Text5, { color: THEME.accent.blue, children: " [Tab]" })
12361
+ ] }, cmd.name);
12362
+ })
12363
+ }
12364
+ ),
12365
+ /* @__PURE__ */ jsx5(
12366
+ Box4,
12367
+ {
12368
+ borderStyle: "single",
12369
+ borderColor: inputRequest.isActive ? THEME.status.warning : THEME.border.default,
12370
+ paddingX: 1,
12371
+ children: inputRequest.isActive ? /* @__PURE__ */ jsxs4(Box4, { children: [
12372
+ /* @__PURE__ */ jsx5(Text5, { color: THEME.status.warning, children: "[auth]" }),
12373
+ /* @__PURE__ */ jsxs4(Text5, { color: THEME.text.muted, children: [
12374
+ " ",
12375
+ inputRequest.prompt
12376
+ ] }),
12377
+ /* @__PURE__ */ jsx5(
12378
+ TextInput,
12379
+ {
12380
+ value: secretInput,
12381
+ onChange: setSecretInput,
12382
+ onSubmit: onSecretSubmit,
12383
+ placeholder: "...",
12384
+ mask: inputRequest.isPassword ? "\u2022" : void 0
12385
+ }
12386
+ )
12387
+ ] }) : /* @__PURE__ */ jsxs4(Box4, { children: [
12388
+ /* @__PURE__ */ jsx5(Text5, { color: THEME.text.secondary, children: "\u25B8" }),
12389
+ /* @__PURE__ */ jsx5(Text5, { children: " " }),
12390
+ /* @__PURE__ */ jsx5(
12391
+ TextInput,
12392
+ {
12393
+ value,
12394
+ onChange,
12395
+ onSubmit,
12396
+ placeholder
12397
+ }
12398
+ )
12399
+ ] })
12400
+ }
12401
+ )
12402
+ ] });
12403
+ });
11589
12404
 
11590
12405
  // src/platform/tui/components/footer.tsx
12406
+ import { memo as memo5 } from "react";
11591
12407
  import { Box as Box5, Text as Text6 } from "ink";
11592
12408
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
11593
12409
  var formatElapsed = (totalSeconds) => {
@@ -11600,7 +12416,7 @@ var formatElapsed = (totalSeconds) => {
11600
12416
  }
11601
12417
  return `${minutes}:${pad(seconds)}`;
11602
12418
  };
11603
- var Footer = ({ phase, targets, findings, todo, elapsedTime, isProcessing }) => {
12419
+ var Footer = memo5(({ phase, targets, findings, todo, elapsedTime, isProcessing }) => {
11604
12420
  return /* @__PURE__ */ jsxs5(
11605
12421
  Box5,
11606
12422
  {
@@ -11611,7 +12427,7 @@ var Footer = ({ phase, targets, findings, todo, elapsedTime, isProcessing }) =>
11611
12427
  /* @__PURE__ */ jsxs5(Box5, { gap: 2, children: [
11612
12428
  /* @__PURE__ */ jsxs5(Text6, { color: THEME.text.muted, children: [
11613
12429
  "Phase: ",
11614
- /* @__PURE__ */ jsx6(Text6, { color: THEME.accent.cyan, children: phase })
12430
+ /* @__PURE__ */ jsx6(Text6, { color: THEME.accent.blue, children: phase })
11615
12431
  ] }),
11616
12432
  /* @__PURE__ */ jsxs5(Text6, { color: THEME.text.muted, children: [
11617
12433
  "Targets: ",
@@ -11633,7 +12449,7 @@ var Footer = ({ phase, targets, findings, todo, elapsedTime, isProcessing }) =>
11633
12449
  ]
11634
12450
  }
11635
12451
  );
11636
- };
12452
+ });
11637
12453
  var footer_default = Footer;
11638
12454
 
11639
12455
  // src/platform/tui/app.tsx
@@ -11658,15 +12474,16 @@ var App = ({ autoApprove = false, target }) => {
11658
12474
  executeTask,
11659
12475
  abort,
11660
12476
  cancelInputRequest,
11661
- addMessage
12477
+ addMessage,
12478
+ refreshStats
11662
12479
  } = useAgent(autoApproveMode, target);
11663
- const isProcessingRef = useRef3(isProcessing);
12480
+ const isProcessingRef = useRef4(isProcessing);
11664
12481
  isProcessingRef.current = isProcessing;
11665
- const autoApproveModeRef = useRef3(autoApproveMode);
12482
+ const autoApproveModeRef = useRef4(autoApproveMode);
11666
12483
  autoApproveModeRef.current = autoApproveMode;
11667
- const inputRequestRef = useRef3(inputRequest);
12484
+ const inputRequestRef = useRef4(inputRequest);
11668
12485
  inputRequestRef.current = inputRequest;
11669
- const handleExit = useCallback3(() => {
12486
+ const handleExit = useCallback4(() => {
11670
12487
  const ir = inputRequestRef.current;
11671
12488
  if (ir.isActive && ir.resolve) ir.resolve(null);
11672
12489
  cleanupAllProcesses().catch(() => {
@@ -11674,16 +12491,31 @@ var App = ({ autoApprove = false, target }) => {
11674
12491
  exit();
11675
12492
  setTimeout(() => process.exit(0), DISPLAY_LIMITS.EXIT_DELAY);
11676
12493
  }, [exit]);
11677
- const handleCommand = useCallback3(async (cmd, args) => {
12494
+ const handleCommand = useCallback4(async (cmd, args) => {
11678
12495
  switch (cmd) {
11679
12496
  case UI_COMMANDS.HELP:
11680
12497
  case UI_COMMANDS.HELP_SHORT:
11681
12498
  addMessage("system", HELP_TEXT);
11682
12499
  break;
11683
12500
  case UI_COMMANDS.CLEAR:
11684
- case UI_COMMANDS.CLEAR_SHORT:
12501
+ case UI_COMMANDS.CLEAR_SHORT: {
12502
+ if (isProcessingRef.current) {
12503
+ addMessage("error", "Cannot /clear while agent is running. Press Esc to abort first.");
12504
+ break;
12505
+ }
11685
12506
  setMessages([]);
12507
+ const result2 = await agent.resetSession();
12508
+ if (result2.cleared.length > 0) {
12509
+ addMessage("system", `[reset] Session cleared: ${result2.cleared.join(", ")}`);
12510
+ } else {
12511
+ addMessage("system", "[reset] Session clean");
12512
+ }
12513
+ if (result2.errors.length > 0) {
12514
+ addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
12515
+ }
12516
+ refreshStats();
11686
12517
  break;
12518
+ }
11687
12519
  case UI_COMMANDS.TARGET:
11688
12520
  case UI_COMMANDS.TARGET_SHORT:
11689
12521
  if (!args[0]) {
@@ -11703,7 +12535,7 @@ var App = ({ autoApprove = false, target }) => {
11703
12535
  if (!autoApproveModeRef.current) {
11704
12536
  setAutoApproveMode(true);
11705
12537
  agent.setAutoApprove(true);
11706
- addMessage("system", "\u{1F680} Autonomous mode enabled (auto-approve ON)");
12538
+ addMessage("system", "[auto] Autonomous mode enabled");
11707
12539
  }
11708
12540
  addMessage("system", "Starting penetration test...");
11709
12541
  const targets = Array.from(agent.getState().getTargets().keys());
@@ -11741,9 +12573,17 @@ ${procData.stdout || "(no output)"}
11741
12573
  break;
11742
12574
  case UI_COMMANDS.CTF:
11743
12575
  const ctfEnabled = agent.toggleCtfMode();
11744
- addMessage("system", ctfEnabled ? "\u{1F3F4} CTF mode ON \u2014 flag detection active, CTF prompts loaded" : "\u{1F512} CTF mode OFF \u2014 standard pentest mode");
12576
+ addMessage("system", ctfEnabled ? "[CTF] Mode ON - flag detection active" : "[CTF] Mode OFF - standard pentest");
12577
+ break;
12578
+ case UI_COMMANDS.GRAPH:
12579
+ case UI_COMMANDS.GRAPH_SHORT:
12580
+ addMessage("system", agent.getState().attackGraph.toASCII());
12581
+ break;
12582
+ case UI_COMMANDS.PATHS:
12583
+ case UI_COMMANDS.PATHS_SHORT:
12584
+ addMessage("system", agent.getState().attackGraph.toPathsList());
11745
12585
  break;
11746
- case "auto":
12586
+ case UI_COMMANDS.AUTO:
11747
12587
  setAutoApproveMode((prev) => {
11748
12588
  const newVal = !prev;
11749
12589
  agent.setAutoApprove(newVal);
@@ -11759,8 +12599,8 @@ ${procData.stdout || "(no output)"}
11759
12599
  default:
11760
12600
  addMessage("error", `Unknown command: /${cmd}`);
11761
12601
  }
11762
- }, [agent, addMessage, executeTask, setMessages, handleExit]);
11763
- const handleSubmit = useCallback3(async (value) => {
12602
+ }, [agent, addMessage, executeTask, setMessages, handleExit, refreshStats]);
12603
+ const handleSubmit = useCallback4(async (value) => {
11764
12604
  const trimmed = value.trim();
11765
12605
  if (!trimmed) return;
11766
12606
  setInput("");
@@ -11772,7 +12612,7 @@ ${procData.stdout || "(no output)"}
11772
12612
  await executeTask(trimmed);
11773
12613
  }
11774
12614
  }, [addMessage, executeTask, handleCommand]);
11775
- const handleSecretSubmit = useCallback3((value) => {
12615
+ const handleSecretSubmit = useCallback4((value) => {
11776
12616
  const ir = inputRequestRef.current;
11777
12617
  if (!ir.isActive || !ir.resolve) return;
11778
12618
  const displayText = ir.isPassword ? "\u2022".repeat(value.length) : value;
@@ -11782,7 +12622,7 @@ ${procData.stdout || "(no output)"}
11782
12622
  setInputRequest({ isActive: false, prompt: "", isPassword: false, resolve: null });
11783
12623
  setSecretInput("");
11784
12624
  }, [addMessage, setInputRequest]);
11785
- useInput(useCallback3((ch, key) => {
12625
+ useInput2(useCallback4((ch, key) => {
11786
12626
  if (key.escape) {
11787
12627
  if (inputRequestRef.current.isActive) cancelInputRequest();
11788
12628
  else if (isProcessingRef.current) abort();
@@ -11873,7 +12713,7 @@ program.command("interactive", { isDefault: true }).alias("i").description("Star
11873
12713
  console.clear();
11874
12714
  console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
11875
12715
  console.log(
11876
- " " + chalk.hex(THEME.text.secondary)(`v${APP_VERSION}`) + chalk.hex(THEME.text.muted)(" \u2502 ") + chalk.hex(THEME.text.accent)("Type /help for commands") + "\n"
12716
+ " " + chalk.hex(THEME.text.secondary)(`v${APP_VERSION}`) + chalk.hex(THEME.text.muted)(" \u2502 ") + chalk.hex(THEME.accent.blue)("Type /help for commands") + "\n"
11877
12717
  );
11878
12718
  if (skipPermissions) {
11879
12719
  console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions"));
@@ -11897,7 +12737,7 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
11897
12737
  if (skipPermissions) {
11898
12738
  console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions\n"));
11899
12739
  }
11900
- console.log(chalk.hex(THEME.text.accent)(`[target] Objective: ${objective}
12740
+ console.log(chalk.hex(THEME.accent.blue)(`[target] Objective: ${objective}
11901
12741
  `));
11902
12742
  const agent = AgentFactory.createMainAgent(skipPermissions);
11903
12743
  if (skipPermissions) {
@@ -11919,7 +12759,7 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
11919
12759
  if (options.output) {
11920
12760
  const fs = await import("fs/promises");
11921
12761
  await fs.writeFile(options.output, JSON.stringify({ result: result2 }, null, 2));
11922
- console.log(chalk.hex(THEME.text.accent)(`
12762
+ console.log(chalk.hex(THEME.accent.blue)(`
11923
12763
  [+] Report saved to: ${options.output}`));
11924
12764
  }
11925
12765
  await shutdown(0);
@@ -11934,7 +12774,7 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
11934
12774
  const opts = program.opts();
11935
12775
  const skipPermissions = opts.dangerouslySkipPermissions || false;
11936
12776
  console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
11937
- console.log(chalk.hex(THEME.text.accent)(`
12777
+ console.log(chalk.hex(THEME.accent.blue)(`
11938
12778
  [scan] Target: ${target} (${options.scanType})
11939
12779
  `));
11940
12780
  const agent = AgentFactory.createMainAgent(skipPermissions);
@@ -11958,7 +12798,7 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
11958
12798
  program.command("help-extended").description("Show extended help with examples").action(() => {
11959
12799
  console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
11960
12800
  console.log(`
11961
- ${chalk.hex(THEME.text.accent)(APP_NAME + " - Autonomous Penetration Testing AI")}
12801
+ ${chalk.hex(THEME.accent.blue)(APP_NAME + " - Autonomous Penetration Testing AI")}
11962
12802
 
11963
12803
  ${chalk.hex(THEME.status.warning)("Usage:")}
11964
12804
 
@@ -11968,24 +12808,24 @@ ${chalk.hex(THEME.status.warning)("Usage:")}
11968
12808
 
11969
12809
  ${chalk.hex(THEME.status.warning)("Commands:")}
11970
12810
 
11971
- ${chalk.hex(THEME.text.accent)("pentesting")} Interactive TUI mode
11972
- ${chalk.hex(THEME.text.accent)("pentesting run <objective>")} Run single objective
11973
- ${chalk.hex(THEME.text.accent)("pentesting scan <target>")} Quick scan target
12811
+ ${chalk.hex(THEME.accent.blue)("pentesting")} Interactive TUI mode
12812
+ ${chalk.hex(THEME.accent.blue)("pentesting run <objective>")} Run single objective
12813
+ ${chalk.hex(THEME.accent.blue)("pentesting scan <target>")} Quick scan target
11974
12814
 
11975
12815
  ${chalk.hex(THEME.status.warning)("Options:")}
11976
12816
 
11977
- ${chalk.hex(THEME.text.accent)("--dangerously-skip-permissions")} Skip all permission prompts
11978
- ${chalk.hex(THEME.text.accent)("-t, --target <ip>")} Set target
11979
- ${chalk.hex(THEME.text.accent)("-o, --output <file>")} Save results to file
12817
+ ${chalk.hex(THEME.accent.blue)("--dangerously-skip-permissions")} Skip all permission prompts
12818
+ ${chalk.hex(THEME.accent.blue)("-t, --target <ip>")} Set target
12819
+ ${chalk.hex(THEME.accent.blue)("-o, --output <file>")} Save results to file
11980
12820
 
11981
12821
  ${chalk.hex(THEME.status.warning)("Interactive Commands:")}
11982
12822
 
11983
- ${chalk.hex(THEME.text.accent)("/target <ip>")} Set target
11984
- ${chalk.hex(THEME.text.accent)("/start")} Start autonomous mode
11985
- ${chalk.hex(THEME.text.accent)("/config")} Manage configuration
11986
- ${chalk.hex(THEME.text.accent)("/hint <text>")} Provide hint
11987
- ${chalk.hex(THEME.text.accent)("/findings")} Show findings
11988
- ${chalk.hex(THEME.text.accent)("/reset")} Reset session
12823
+ ${chalk.hex(THEME.accent.blue)("/target <ip>")} Set target
12824
+ ${chalk.hex(THEME.accent.blue)("/start")} Start autonomous mode
12825
+ ${chalk.hex(THEME.accent.blue)("/config")} Manage configuration
12826
+ ${chalk.hex(THEME.accent.blue)("/hint <text>")} Provide hint
12827
+ ${chalk.hex(THEME.accent.blue)("/findings")} Show findings
12828
+ ${chalk.hex(THEME.accent.blue)("/reset")} Reset session
11989
12829
 
11990
12830
  ${chalk.hex(THEME.status.warning)("Examples:")}
11991
12831
 
@@ -12000,9 +12840,9 @@ ${chalk.hex(THEME.status.warning)("Examples:")}
12000
12840
 
12001
12841
  ${chalk.hex(THEME.status.warning)("Environment:")}
12002
12842
 
12003
- ${chalk.hex(THEME.text.accent)("PENTEST_API_KEY")} Required - LLM API key
12004
- ${chalk.hex(THEME.text.accent)("PENTEST_BASE_URL")} Optional - AI API base URL
12005
- ${chalk.hex(THEME.text.accent)("PENTEST_MODEL")} Optional - Model override
12843
+ ${chalk.hex(THEME.accent.blue)("PENTEST_API_KEY")} Required - LLM API key
12844
+ ${chalk.hex(THEME.accent.blue)("PENTEST_BASE_URL")} Optional - AI API base URL
12845
+ ${chalk.hex(THEME.accent.blue)("PENTEST_MODEL")} Optional - Model override
12006
12846
  `);
12007
12847
  });
12008
12848
  program.parse();