pentesting 0.41.0 → 0.44.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
@@ -10,7 +10,6 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
10
10
  import { render } from "ink";
11
11
  import { Command } from "commander";
12
12
  import chalk from "chalk";
13
- import gradient from "gradient-string";
14
13
 
15
14
  // src/platform/tui/app.tsx
16
15
  import { useState as useState4, useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4 } from "react";
@@ -307,14 +306,12 @@ var ORPHAN_PROCESS_NAMES = [
307
306
  "dnsspoof",
308
307
  "tcpdump",
309
308
  "tshark",
310
- "socat",
311
- "nc",
312
- "python"
309
+ "socat"
313
310
  ];
314
311
 
315
312
  // src/shared/constants/agent.ts
316
313
  var APP_NAME = "Pentest AI";
317
- var APP_VERSION = "0.41.0";
314
+ var APP_VERSION = "0.44.0";
318
315
  var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
319
316
  var LLM_ROLES = {
320
317
  SYSTEM: "system",
@@ -342,6 +339,146 @@ var SENSITIVE_INPUT_TYPES = [
342
339
  INPUT_TYPES.CREDENTIAL
343
340
  ];
344
341
 
342
+ // src/shared/utils/time-strategy.ts
343
+ var PHASE_RATIOS = {
344
+ /** Sprint ends at 25% of total time */
345
+ SPRINT_END: 0.25,
346
+ /** Exploit phase ends at 50% of total time */
347
+ EXPLOIT_END: 0.5,
348
+ /** Creative phase ends at 75% of total time */
349
+ CREATIVE_END: 0.75
350
+ // Harvest: 75%-100%
351
+ };
352
+ var DEFAULT_DURATION_SEC = 86400;
353
+ var MIN_DURATION_SEC = 300;
354
+ var MAX_DURATION_SEC = 604800;
355
+ var MS_PER_SEC = 1e3;
356
+ function getSessionDurationMs() {
357
+ const envValue = process.env.PENTEST_DURATION;
358
+ if (envValue) {
359
+ const parsed = parseInt(envValue, 10);
360
+ if (!isNaN(parsed) && parsed > 0) {
361
+ const clamped = Math.max(MIN_DURATION_SEC, Math.min(MAX_DURATION_SEC, parsed));
362
+ return clamped * MS_PER_SEC;
363
+ }
364
+ }
365
+ return DEFAULT_DURATION_SEC * MS_PER_SEC;
366
+ }
367
+ function computeDeadline(startedAt) {
368
+ return startedAt + getSessionDurationMs();
369
+ }
370
+ function formatDuration(totalSec) {
371
+ if (totalSec <= 0) return "0s";
372
+ const h = Math.floor(totalSec / 3600);
373
+ const m = Math.floor(totalSec % 3600 / 60);
374
+ const s = Math.floor(totalSec % 60);
375
+ if (h > 0 && m > 0) return `${h}h${m}m`;
376
+ if (h > 0) return `${h}h`;
377
+ if (m > 0 && s > 0) return `${m}m${s}s`;
378
+ if (m > 0) return `${m}m`;
379
+ return `${s}s`;
380
+ }
381
+ function getTimeAdaptiveStrategy(elapsedMs, deadlineMs) {
382
+ let totalMs;
383
+ let remainingMs;
384
+ if (deadlineMs > 0) {
385
+ remainingMs = Math.max(0, deadlineMs - Date.now());
386
+ totalMs = elapsedMs + remainingMs;
387
+ } else {
388
+ totalMs = getSessionDurationMs();
389
+ remainingMs = Math.max(0, totalMs - elapsedMs);
390
+ }
391
+ if (totalMs <= 0) totalMs = 1;
392
+ const progress = Math.min(1, elapsedMs / totalMs);
393
+ const remainingSec = Math.floor(remainingMs / MS_PER_SEC);
394
+ const totalDurationSec = Math.floor(totalMs / MS_PER_SEC);
395
+ const elapsedStr = formatDuration(Math.floor(elapsedMs / MS_PER_SEC));
396
+ const remainStr = formatDuration(remainingSec);
397
+ const totalStr = formatDuration(totalDurationSec);
398
+ const pctStr = `${Math.floor(progress * 100)}%`;
399
+ if (progress >= PHASE_RATIOS.CREATIVE_END) {
400
+ return {
401
+ phase: "harvest",
402
+ label: "HARVEST",
403
+ urgency: "critical",
404
+ progress,
405
+ totalDurationSec,
406
+ remainingSec,
407
+ directive: [
408
+ `\u{1F3C1} HARVEST MODE [${pctStr} elapsed, ${remainStr} of ${totalStr} remaining]:`,
409
+ "- STOP exploring new attack vectors. Exploit what you HAVE.",
410
+ "- Submit ALL discovered flags immediately",
411
+ "- Check obvious flag locations: /root/flag.txt, user.txt, env vars, DB tables",
412
+ "- Re-check previously accessed systems for missed flags/proof",
413
+ "- Collect and record ALL evidence and proof files",
414
+ "- Write final report with all findings",
415
+ "- Credential spray ALL discovered creds on ALL remaining services",
416
+ remainingSec <= 300 ? "- \u26A0\uFE0F FINAL 5 MINUTES \u2014 submit everything NOW, stop all scans" : "- Focus on highest-confidence paths only"
417
+ ].join("\n")
418
+ };
419
+ }
420
+ if (progress >= PHASE_RATIOS.EXPLOIT_END) {
421
+ return {
422
+ phase: "creative",
423
+ label: "CREATIVE",
424
+ urgency: "high",
425
+ progress,
426
+ totalDurationSec,
427
+ remainingSec,
428
+ directive: [
429
+ `\u{1F52C} CREATIVE MODE [${pctStr} elapsed, ${remainStr} of ${totalStr} remaining]:`,
430
+ "- Try advanced techniques: chained exploits, custom tools, race conditions",
431
+ "- Binary analysis for SUID/custom binaries, kernel exploits for privesc",
432
+ "- Protocol-level attacks: SMB relay, Kerberoasting, ADCS abuse",
433
+ '- Search for latest bypasses: web_search("{defense} bypass {year}")',
434
+ "- If stuck >5min on any vector, SWITCH immediately",
435
+ "- Start preparing evidence collection for final phase",
436
+ "- If all targets are compromised \u2192 skip to harvest immediately"
437
+ ].join("\n")
438
+ };
439
+ }
440
+ if (progress >= PHASE_RATIOS.SPRINT_END) {
441
+ return {
442
+ phase: "exploit",
443
+ label: "EXPLOIT",
444
+ urgency: "medium",
445
+ progress,
446
+ totalDurationSec,
447
+ remainingSec,
448
+ directive: [
449
+ `\u{1F3AF} EXPLOIT MODE [${pctStr} elapsed, ${remainStr} of ${totalStr} remaining]:`,
450
+ "- Concentrate on discovered vectors with highest success probability",
451
+ "- Run parallel exploitation attempts on multiple targets",
452
+ "- Search for CVE PoCs for every identified service+version",
453
+ "- Chain: credentials \u2192 spray everywhere, LFI \u2192 config \u2192 DB creds",
454
+ "- Keep background scans running for new targets",
455
+ "- Time-box each vector: 10min max then switch",
456
+ "- If all vectors exhausted early \u2192 move to creative techniques immediately"
457
+ ].join("\n")
458
+ };
459
+ }
460
+ return {
461
+ phase: "sprint",
462
+ label: "SPRINT",
463
+ urgency: "low",
464
+ progress,
465
+ totalDurationSec,
466
+ remainingSec,
467
+ directive: [
468
+ `\u26A1 SPRINT MODE [${pctStr} elapsed, ${remainStr} of ${totalStr} remaining]:`,
469
+ "- RustScan first for fast port discovery \u2192 then nmap -Pn -sV -sC on found ports",
470
+ "- ALWAYS use nmap -Pn (no exceptions \u2014 firewalls block ICMP)",
471
+ "- Run ALL scans in parallel (rustscan, nmap UDP, web fuzzing, CVE search)",
472
+ "- Try default/weak credentials on every discovered service IMMEDIATELY",
473
+ "- Check for exposed files (.env, .git, backup.sql, phpinfo)",
474
+ "- Anonymous access: FTP, SMB null session, Redis no auth, MongoDB",
475
+ "- OSINT: web_search for target intel, shodan, github repos",
476
+ "- Goal: Maximum attack surface discovery + instant wins",
477
+ "- \u26A1 If recon completes early \u2192 ATTACK IMMEDIATELY, do not wait for this phase to end"
478
+ ].join("\n")
479
+ };
480
+ }
481
+
345
482
  // src/shared/utils/id.ts
346
483
  var ID_DEFAULT_RADIX = 36;
347
484
  var ID_DEFAULT_LENGTH = 6;
@@ -2027,7 +2164,7 @@ async function cleanupAllProcesses() {
2027
2164
  backgroundProcesses.clear();
2028
2165
  for (const name of ORPHAN_PROCESS_NAMES) {
2029
2166
  try {
2030
- execSync2(`pkill -f "${name}" 2>/dev/null || true`, { stdio: "ignore", timeout: SYSTEM_LIMITS.PROCESS_OP_TIMEOUT_MS });
2167
+ execSync2(`pkill -x "${name}" 2>/dev/null || true`, { stdio: "ignore", timeout: SYSTEM_LIMITS.PROCESS_OP_TIMEOUT_MS });
2031
2168
  } catch {
2032
2169
  }
2033
2170
  }
@@ -2652,6 +2789,22 @@ var AttackGraph = class {
2652
2789
  if (nodeCount >= GRAPH_LIMITS.ASCII_MAX_NODES) break;
2653
2790
  const sIcon = statusIcons[node.status] || "?";
2654
2791
  const fail = node.status === NODE_STATUS.FAILED && node.failReason ? ` \u2014 ${node.failReason}` : "";
2792
+ let detail = "";
2793
+ if (type === NODE_TYPE.CREDENTIAL) {
2794
+ const source = node.data.source ? ` (from: ${node.data.source})` : "";
2795
+ detail = source;
2796
+ } else if (type === NODE_TYPE.VULNERABILITY) {
2797
+ const sev = node.data.severity ? ` [${String(node.data.severity).toUpperCase()}]` : "";
2798
+ const exploit = node.data.hasExploit ? " [EXPLOIT]" : "";
2799
+ const target = node.data.target ? ` \u2192 ${node.data.target}` : "";
2800
+ detail = `${sev}${exploit}${target}`;
2801
+ } else if (type === NODE_TYPE.ACCESS) {
2802
+ const via = node.data.via ? ` via ${node.data.via}` : "";
2803
+ detail = via;
2804
+ } else if (type === NODE_TYPE.SERVICE) {
2805
+ const ver = node.data.version ? ` (${node.data.version})` : "";
2806
+ detail = ver;
2807
+ }
2655
2808
  const outEdges = this.edges.filter((e) => e.from === node.id);
2656
2809
  const edgeStr = outEdges.length > 0 ? ` \u2192 ${outEdges.map((e) => {
2657
2810
  const target = this.nodes.get(e.to);
@@ -2659,10 +2812,19 @@ var AttackGraph = class {
2659
2812
  const eStatus = e.status === EDGE_STATUS.FAILED ? " \u2717" : e.status === EDGE_STATUS.SUCCEEDED ? " \u2713" : "";
2660
2813
  return `${eName}${eStatus}`;
2661
2814
  }).join(", ")}` : "";
2662
- lines.push(`\u2502 ${sIcon} ${node.label}${fail}${edgeStr}`);
2815
+ lines.push(`\u2502 ${sIcon} ${node.label}${detail}${fail}${edgeStr}`);
2663
2816
  nodeCount++;
2664
2817
  }
2665
2818
  }
2819
+ const succeededNodes = Array.from(this.nodes.values()).filter((n) => n.status === NODE_STATUS.SUCCEEDED);
2820
+ if (succeededNodes.length > 0) {
2821
+ lines.push(`\u2502`);
2822
+ lines.push(`\u251C\u2500\u2500\u2500 Exploited \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`);
2823
+ for (const n of succeededNodes) {
2824
+ const via = n.data.via ? ` via ${n.data.via}` : "";
2825
+ lines.push(`\u2502 \u25CF ${n.label}${via}`);
2826
+ }
2827
+ }
2666
2828
  const stats = this.getStats();
2667
2829
  lines.push(`\u2502`);
2668
2830
  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`);
@@ -3183,6 +3345,7 @@ var SharedState = class {
3183
3345
  // ─── Improvement #7: Dynamic Technique Library ────────────────
3184
3346
  dynamicTechniques = new DynamicTechniqueLibrary();
3185
3347
  constructor() {
3348
+ const now = Date.now();
3186
3349
  this.data = {
3187
3350
  engagement: null,
3188
3351
  targets: /* @__PURE__ */ new Map(),
@@ -3196,8 +3359,10 @@ var SharedState = class {
3196
3359
  ctfMode: true,
3197
3360
  // CTF mode ON by default
3198
3361
  flags: [],
3199
- startedAt: Date.now(),
3200
- deadlineAt: 0,
3362
+ startedAt: now,
3363
+ // Auto-configure from PENTEST_DURATION env (seconds).
3364
+ // Defaults to 24h if not set. Can be overridden via setDeadline().
3365
+ deadlineAt: computeDeadline(now),
3201
3366
  challengeAnalysis: null
3202
3367
  };
3203
3368
  }
@@ -3206,6 +3371,7 @@ var SharedState = class {
3206
3371
  * Used by /clear command to start a fresh session without recreating the agent.
3207
3372
  */
3208
3373
  reset() {
3374
+ const now = Date.now();
3209
3375
  this.data = {
3210
3376
  engagement: null,
3211
3377
  targets: /* @__PURE__ */ new Map(),
@@ -3218,8 +3384,8 @@ var SharedState = class {
3218
3384
  missionChecklist: [],
3219
3385
  ctfMode: true,
3220
3386
  flags: [],
3221
- startedAt: Date.now(),
3222
- deadlineAt: 0,
3387
+ startedAt: now,
3388
+ deadlineAt: computeDeadline(now),
3223
3389
  challengeAnalysis: null
3224
3390
  };
3225
3391
  this.attackGraph.reset();
@@ -3404,24 +3570,28 @@ var SharedState = class {
3404
3570
  if (this.data.deadlineAt === 0) return 0;
3405
3571
  return Math.max(0, this.data.deadlineAt - Date.now());
3406
3572
  }
3407
- /** Get time status string for prompt injection */
3573
+ /** Get time status string for prompt injection (ratio-based, aligned with PHASE_RATIOS) */
3408
3574
  getTimeStatus() {
3409
3575
  const elapsed = this.getElapsedMs();
3410
3576
  const mins = Math.floor(elapsed / 6e4);
3411
3577
  let status = `\u23F1 Elapsed: ${mins}min`;
3412
3578
  if (this.data.deadlineAt > 0) {
3413
3579
  const remainingMs = this.getRemainingMs();
3580
+ const totalMs = elapsed + remainingMs;
3581
+ const progress = totalMs > 0 ? elapsed / totalMs : 1;
3414
3582
  const remainingMins = Math.floor(remainingMs / 6e4);
3415
- if (remainingMins <= 0) {
3583
+ if (remainingMs <= 0) {
3416
3584
  status += " | \u26A0\uFE0F TIME IS UP \u2014 submit any flags you have NOW";
3417
- } else if (remainingMins <= 15) {
3585
+ } else if (progress >= 0.95) {
3418
3586
  status += ` | \u{1F534} ${remainingMins}min LEFT \u2014 FINAL PUSH: submit flags, check obvious locations, env vars, DBs`;
3419
- } else if (remainingMins <= 30) {
3420
- status += ` | \u{1F7E1} ${remainingMins}min LEFT \u2014 wrap up current vector, ensure all findings are captured`;
3421
- } else if (remainingMins <= 60) {
3422
- status += ` | \u{1F7E0} ${remainingMins}min LEFT \u2014 focus on highest-probability vectors only`;
3587
+ } else if (progress >= 0.75) {
3588
+ status += ` | \u{1F7E1} ${remainingMins}min LEFT \u2014 HARVEST: wrap up, collect evidence, submit flags`;
3589
+ } else if (progress >= 0.5) {
3590
+ status += ` | \u{1F7E0} ${remainingMins}min LEFT \u2014 CREATIVE: advanced techniques, switch if stuck`;
3591
+ } else if (progress >= 0.25) {
3592
+ status += ` | \u{1F535} ${remainingMins}min LEFT \u2014 EXPLOIT: focus on discovered vectors`;
3423
3593
  } else {
3424
- status += ` | \u{1F7E2} ${remainingMins}min remaining`;
3594
+ status += ` | \u{1F7E2} ${remainingMins}min remaining \u2014 SPRINT: recon + quick wins`;
3425
3595
  }
3426
3596
  }
3427
3597
  return status;
@@ -4912,26 +5082,31 @@ Detail: ${detail}
4912
5082
  },
4913
5083
  {
4914
5084
  name: TOOL_NAMES.ADD_FINDING,
4915
- description: "Add a security finding. Always include attackPattern for MITRE ATT&CK mapping.",
5085
+ description: `Add a security finding with full details.
5086
+ ALWAYS provide: description (HOW you exploited it, step-by-step), evidence (actual command output proving success), and attackPattern (MITRE ATT&CK tactic).
5087
+ Findings without evidence are marked as UNVERIFIED and have low credibility.`,
4916
5088
  parameters: {
4917
- title: { type: "string", description: "Finding title" },
5089
+ title: { type: "string", description: 'Concise finding title (e.g., "Path Traversal via /download endpoint")' },
4918
5090
  severity: { type: "string", description: "Severity: critical, high, medium, low, info" },
4919
- affected: { type: "array", items: { type: "string" }, description: "Affected host:port" },
5091
+ affected: { type: "array", items: { type: "string" }, description: 'Affected host:port or URLs (e.g., ["ctf.example.com:443/download"])' },
5092
+ description: { type: "string", description: "Detailed description: what the vulnerability is, how you exploited it step-by-step, what access it gives, and the impact. Include credentials found, methods used, and exploitation chain." },
5093
+ evidence: { type: "array", items: { type: "string" }, description: 'Actual command outputs proving the finding (e.g., ["curl output showing /etc/passwd", "uid=0(root) gid=0(root)"]). Copy real output here.' },
4920
5094
  attackPattern: { type: "string", description: "MITRE ATT&CK tactic: initial_access, execution, persistence, privilege_escalation, defense_evasion, credential_access, discovery, lateral_movement, collection, exfiltration, command_and_control, impact" }
4921
5095
  },
4922
- required: ["title", "severity"],
5096
+ required: ["title", "severity", "description", "evidence"],
4923
5097
  execute: async (p) => {
4924
5098
  const evidence = p.evidence || [];
4925
5099
  const title = p.title;
4926
5100
  const severity = p.severity;
4927
5101
  const affected = p.affected || [];
5102
+ const description = p.description || "";
4928
5103
  const validation = validateFinding(evidence, severity);
4929
5104
  state.addFinding({
4930
5105
  id: generateId(AGENT_LIMITS.ID_RADIX, AGENT_LIMITS.ID_LENGTH),
4931
5106
  title,
4932
5107
  severity,
4933
5108
  affected,
4934
- description: p.description || "",
5109
+ description,
4935
5110
  evidence,
4936
5111
  isVerified: validation.isVerified,
4937
5112
  remediation: "",
@@ -6686,8 +6861,8 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
6686
6861
  }
6687
6862
  },
6688
6863
  execute: async (p) => {
6689
- const { existsSync: existsSync9, statSync: statSync2, readdirSync: readdirSync3 } = await import("fs");
6690
- const { join: join11 } = await import("path");
6864
+ const { existsSync: existsSync10, statSync: statSync2, readdirSync: readdirSync3 } = await import("fs");
6865
+ const { join: join12 } = await import("path");
6691
6866
  const category = p.category || "";
6692
6867
  const search = p.search || "";
6693
6868
  const minSize = p.min_size || 0;
@@ -6733,7 +6908,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
6733
6908
  results.push("");
6734
6909
  };
6735
6910
  const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
6736
- if (depth > maxDepth || !existsSync9(dirPath)) return;
6911
+ if (depth > maxDepth || !existsSync10(dirPath)) return;
6737
6912
  let entries;
6738
6913
  try {
6739
6914
  entries = readdirSync3(dirPath, { withFileTypes: true });
@@ -6742,7 +6917,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
6742
6917
  }
6743
6918
  for (const entry of entries) {
6744
6919
  if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
6745
- const fullPath = join11(dirPath, entry.name);
6920
+ const fullPath = join12(dirPath, entry.name);
6746
6921
  if (entry.isDirectory()) {
6747
6922
  scanDir(fullPath, maxDepth, depth + 1);
6748
6923
  continue;
@@ -8686,6 +8861,11 @@ var NETWORK_ERROR_CODES = {
8686
8861
  ENOTFOUND: "ENOTFOUND",
8687
8862
  CONNECT_TIMEOUT: "UND_ERR_CONNECT_TIMEOUT"
8688
8863
  };
8864
+ var TRANSIENT_NETWORK_ERRORS = [
8865
+ NETWORK_ERROR_CODES.ECONNRESET,
8866
+ NETWORK_ERROR_CODES.ETIMEDOUT,
8867
+ NETWORK_ERROR_CODES.ENOTFOUND
8868
+ ];
8689
8869
  var LLMError = class extends Error {
8690
8870
  /** Structured error information */
8691
8871
  errorInfo;
@@ -8696,8 +8876,11 @@ var LLMError = class extends Error {
8696
8876
  }
8697
8877
  };
8698
8878
  function classifyError(error) {
8879
+ if (error === null || error === void 0) {
8880
+ return { type: LLM_ERROR_TYPES.UNKNOWN, message: String(error), isRetryable: false, suggestedAction: "Analyze error" };
8881
+ }
8699
8882
  const e = error;
8700
- const statusCode = e.status || e.statusCode;
8883
+ const statusCode = e?.status || e?.statusCode;
8701
8884
  const errorMessage = e?.error?.error?.message || e?.error?.message || e?.message || String(error);
8702
8885
  if (statusCode === HTTP_STATUS.RATE_LIMIT || errorMessage.toLowerCase().includes("rate limit")) {
8703
8886
  return { type: LLM_ERROR_TYPES.RATE_LIMIT, message: errorMessage, statusCode, isRetryable: true, suggestedAction: "Wait and retry" };
@@ -8708,7 +8891,7 @@ function classifyError(error) {
8708
8891
  if (statusCode === HTTP_STATUS.BAD_REQUEST) {
8709
8892
  return { type: LLM_ERROR_TYPES.INVALID_REQUEST, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Modify request" };
8710
8893
  }
8711
- if (e.code === NETWORK_ERROR_CODES.ECONNRESET || e.code === NETWORK_ERROR_CODES.ETIMEDOUT || e.code === NETWORK_ERROR_CODES.ENOTFOUND) {
8894
+ if (e.code && TRANSIENT_NETWORK_ERRORS.includes(e.code)) {
8712
8895
  return { type: LLM_ERROR_TYPES.NETWORK_ERROR, message: errorMessage, isRetryable: true, suggestedAction: "Check network" };
8713
8896
  }
8714
8897
  if (errorMessage.toLowerCase().includes("timeout") || e.code === NETWORK_ERROR_CODES.CONNECT_TIMEOUT) {
@@ -10317,100 +10500,6 @@ var INITIAL_TASKS = {
10317
10500
  RECON: "Initial reconnaissance and target discovery"
10318
10501
  };
10319
10502
 
10320
- // src/shared/utils/time-strategy.ts
10321
- var TIME_CONSTANTS = {
10322
- /** Milliseconds per minute */
10323
- MS_PER_MINUTE: 6e4,
10324
- /** Minutes threshold for sprint-to-exploit switch (no deadline mode) */
10325
- SPRINT_MINUTES: 10,
10326
- /** Deadline percentage thresholds for phase transitions */
10327
- PHASE_SPRINT: 0.25,
10328
- PHASE_EXPLOIT: 0.5,
10329
- PHASE_CREATIVE: 0.75
10330
- };
10331
- function getTimeAdaptiveStrategy(elapsedMs, deadlineMs) {
10332
- if (deadlineMs === 0) {
10333
- const mins = Math.floor(elapsedMs / TIME_CONSTANTS.MS_PER_MINUTE);
10334
- if (mins < TIME_CONSTANTS.SPRINT_MINUTES) {
10335
- return {
10336
- phase: "sprint",
10337
- label: "SPRINT",
10338
- urgency: "low",
10339
- directive: `\u26A1 SPRINT MODE (${mins}min elapsed): Parallel reconnaissance. Full port scan + service enumeration + default credentials. Attack immediately on any finding.`
10340
- };
10341
- }
10342
- return {
10343
- phase: "exploit",
10344
- label: "EXPLOIT",
10345
- urgency: "low",
10346
- directive: `\u{1F3AF} EXPLOIT MODE (${mins}min elapsed): Focus on discovered vectors. Chain findings. Build custom tools if needed.`
10347
- };
10348
- }
10349
- const totalMs = deadlineMs - (deadlineMs > Date.now() ? Date.now() - elapsedMs : 0);
10350
- const remainingMs = Math.max(0, deadlineMs - Date.now());
10351
- const pct = totalMs > 0 ? 1 - remainingMs / totalMs : 1;
10352
- const remainingMins = Math.floor(remainingMs / TIME_CONSTANTS.MS_PER_MINUTE);
10353
- if (pct < TIME_CONSTANTS.PHASE_SPRINT) {
10354
- return {
10355
- phase: "sprint",
10356
- label: "SPRINT",
10357
- urgency: "low",
10358
- directive: [
10359
- `\u26A1 SPRINT MODE (${remainingMins}min remaining):`,
10360
- "- Run ALL scans in parallel (nmap TCP/UDP, web fuzzing, CVE search)",
10361
- "- Try default/weak credentials on every discovered service IMMEDIATELY",
10362
- "- Check for exposed files (.env, .git, backup.sql, phpinfo)",
10363
- "- Anonymous access: FTP, SMB null session, Redis no auth, MongoDB",
10364
- "- Goal: Maximum attack surface discovery + instant wins"
10365
- ].join("\n")
10366
- };
10367
- }
10368
- if (pct < TIME_CONSTANTS.PHASE_EXPLOIT) {
10369
- return {
10370
- phase: "exploit",
10371
- label: "EXPLOIT",
10372
- urgency: "medium",
10373
- directive: [
10374
- `\u{1F3AF} EXPLOIT MODE (${remainingMins}min remaining):`,
10375
- "- Concentrate on discovered vectors with highest success probability",
10376
- "- Run parallel exploitation attempts on multiple targets",
10377
- "- Search for CVE PoCs for every identified service+version",
10378
- "- Chain: credentials \u2192 spray everywhere, LFI \u2192 config \u2192 DB creds",
10379
- "- Keep background scans running for new targets"
10380
- ].join("\n")
10381
- };
10382
- }
10383
- if (pct < TIME_CONSTANTS.PHASE_CREATIVE) {
10384
- return {
10385
- phase: "creative",
10386
- label: "CREATIVE",
10387
- urgency: "high",
10388
- directive: [
10389
- `\u{1F52C} CREATIVE MODE (${remainingMins}min remaining):`,
10390
- "- Try advanced techniques: chained exploits, custom tools, race conditions",
10391
- "- Binary analysis for SUID/custom binaries, kernel exploits for privesc",
10392
- "- Protocol-level attacks: SMB relay, Kerberoasting, ADCS abuse",
10393
- '- Search for latest bypasses: web_search("{defense} bypass 2025")',
10394
- "- If stuck >5min on any vector, SWITCH immediately"
10395
- ].join("\n")
10396
- };
10397
- }
10398
- return {
10399
- phase: "harvest",
10400
- label: "HARVEST",
10401
- urgency: "critical",
10402
- directive: [
10403
- `\u{1F3C1} HARVEST MODE (${remainingMins}min remaining \u2014 FINAL PUSH):`,
10404
- "- STOP exploring new vectors. Focus on what you HAVE.",
10405
- "- Submit ALL discovered flags immediately",
10406
- "- Check obvious flag locations: /root/flag.txt, env vars, DB tables",
10407
- "- Re-check previously accessed systems for missed flags",
10408
- "- Collect and record ALL evidence and proof files",
10409
- "- Write final report with all findings"
10410
- ].join("\n")
10411
- };
10412
- }
10413
-
10414
10503
  // src/shared/constants/service-ports.ts
10415
10504
  var SERVICE_PORTS = {
10416
10505
  SSH: 22,
@@ -10684,13 +10773,21 @@ var PHASE_TECHNIQUE_MAP = {
10684
10773
  };
10685
10774
  var PromptBuilder = class {
10686
10775
  state;
10776
+ strategist = null;
10687
10777
  constructor(state) {
10688
10778
  this.state = state;
10689
10779
  }
10690
10780
  /**
10691
- * Build complete prompt for LLM iteration.
10781
+ * Attach a Strategist instance for meta-prompting.
10782
+ * Called by MainAgent after construction.
10783
+ */
10784
+ setStrategist(strategist) {
10785
+ this.strategist = strategist;
10786
+ }
10787
+ /**
10788
+ * Build complete prompt for LLM iteration (async).
10692
10789
  *
10693
- * Layers (phase-aware, enhanced with improvements #2,#3,#6,#7,#8,#12,#14):
10790
+ * Layers (phase-aware, enhanced with D-CIPHER meta-prompting):
10694
10791
  * 1. base.md — Core identity, rules, autonomy, shell management (~7.6K tok)
10695
10792
  * 2. Phase-specific prompt — current phase's full specialist knowledge (~2K tok)
10696
10793
  * 3. Core methodology — strategy, orchestrator, evasion (always loaded, ~12K tok)
@@ -10706,9 +10803,10 @@ var PromptBuilder = class {
10706
10803
  * 12. Session timeline (#12: episodic memory)
10707
10804
  * 13. Learned techniques (#7: dynamic technique library)
10708
10805
  * 14. Persistent memory (#12: cross-session knowledge)
10709
- * 15. User context
10806
+ * 15. STRATEGIC DIRECTIVE — LLM-generated tactical instructions (D-CIPHER)
10807
+ * 16. User context
10710
10808
  */
10711
- build(userInput, phase) {
10809
+ async build(userInput, phase) {
10712
10810
  const fragments = [
10713
10811
  this.loadPromptFile(PROMPT_PATHS.BASE),
10714
10812
  this.loadCtfModePrompt(),
@@ -10731,10 +10829,14 @@ var PromptBuilder = class {
10731
10829
  // #12
10732
10830
  this.getDynamicTechniquesFragment(),
10733
10831
  // #7
10734
- this.getPersistentMemoryFragment(),
10832
+ this.getPersistentMemoryFragment()
10735
10833
  // #12
10736
- PROMPT_DEFAULTS.USER_CONTEXT(userInput)
10737
10834
  ];
10835
+ const strategistDirective = await this.getStrategistFragment();
10836
+ if (strategistDirective) {
10837
+ fragments.push(strategistDirective);
10838
+ }
10839
+ fragments.push(PROMPT_DEFAULTS.USER_CONTEXT(userInput));
10738
10840
  return fragments.filter((f) => !!f).join("\n\n");
10739
10841
  }
10740
10842
  /**
@@ -10924,11 +11026,217 @@ ${lines.join("\n")}
10924
11026
  }
10925
11027
  return this.state.persistentMemory.toPrompt(services);
10926
11028
  }
11029
+ // --- D-CIPHER: Strategist Meta-Prompting ---
11030
+ /**
11031
+ * Generate strategic directive via Strategist LLM.
11032
+ * Called every turn. Returns '' if no strategist is attached or call fails.
11033
+ */
11034
+ async getStrategistFragment() {
11035
+ if (!this.strategist) return "";
11036
+ return this.strategist.generateDirective();
11037
+ }
11038
+ };
11039
+
11040
+ // src/agents/strategist.ts
11041
+ import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
11042
+ import { join as join11, dirname as dirname6 } from "path";
11043
+ import { fileURLToPath as fileURLToPath5 } from "url";
11044
+
11045
+ // src/shared/constants/strategist.ts
11046
+ var STRATEGIST_LIMITS = {
11047
+ /** Maximum characters of state context sent to Strategist LLM.
11048
+ * WHY: Keeps Strategist input focused and affordable (~3-5K tokens).
11049
+ * Full state can be 20K+; Strategist needs summary, not everything. */
11050
+ MAX_INPUT_CHARS: 15e3,
11051
+ /** Maximum characters for the Strategist's response.
11052
+ * WHY: Directives should be terse and actionable (~800-1500 tokens).
11053
+ * Enhanced format includes SITUATION, priorities, EXHAUSTED, and SEARCH ORDERS. */
11054
+ MAX_OUTPUT_CHARS: 5e3,
11055
+ /** Maximum lines in the directive output.
11056
+ * WHY: Forces concise, prioritized directives while allowing
11057
+ * structured format (priorities + exhausted + search orders). */
11058
+ MAX_DIRECTIVE_LINES: 60
11059
+ };
11060
+
11061
+ // src/agents/strategist.ts
11062
+ var __dirname5 = dirname6(fileURLToPath5(import.meta.url));
11063
+ var STRATEGIST_PROMPT_PATH = join11(__dirname5, "prompts", "strategist-system.md");
11064
+ var Strategist = class {
11065
+ llm;
11066
+ state;
11067
+ systemPrompt;
11068
+ // Metrics
11069
+ totalTokenCost = 0;
11070
+ totalCalls = 0;
11071
+ lastDirective = null;
11072
+ constructor(llm, state) {
11073
+ this.llm = llm;
11074
+ this.state = state;
11075
+ this.systemPrompt = this.loadSystemPrompt();
11076
+ }
11077
+ /**
11078
+ * Generate a fresh strategic directive for this turn.
11079
+ * Called every iteration by PromptBuilder.
11080
+ *
11081
+ * @returns Formatted directive string for prompt injection, or '' on failure
11082
+ */
11083
+ async generateDirective() {
11084
+ try {
11085
+ const input = this.buildInput();
11086
+ const directive = await this.callLLM(input);
11087
+ this.lastDirective = directive;
11088
+ this.totalCalls++;
11089
+ debugLog("general", "Strategist directive generated", {
11090
+ tokens: directive.tokenCost,
11091
+ totalCalls: this.totalCalls,
11092
+ contentLength: directive.content.length
11093
+ });
11094
+ return this.formatForPrompt(directive);
11095
+ } catch (err) {
11096
+ debugLog("general", "Strategist failed \u2014 agent will proceed without directive", {
11097
+ error: String(err)
11098
+ });
11099
+ if (this.lastDirective?.content) {
11100
+ return this.formatForPrompt(this.lastDirective, true);
11101
+ }
11102
+ return "";
11103
+ }
11104
+ }
11105
+ // ─── Input Construction ─────────────────────────────────────
11106
+ /**
11107
+ * Build the user message for the Strategist LLM.
11108
+ * Assembles state, memory, attack graph, and timeline into a focused context.
11109
+ */
11110
+ buildInput() {
11111
+ const sections = [];
11112
+ sections.push("## Engagement State");
11113
+ sections.push(this.state.toPrompt());
11114
+ const timeline = this.state.episodicMemory.toPrompt();
11115
+ if (timeline) {
11116
+ sections.push("");
11117
+ sections.push("## Recent Actions");
11118
+ sections.push(timeline);
11119
+ }
11120
+ const failures = this.state.workingMemory.toPrompt();
11121
+ if (failures) {
11122
+ sections.push("");
11123
+ sections.push("## Failed Attempts (DO NOT REPEAT THESE)");
11124
+ sections.push(failures);
11125
+ }
11126
+ const graph = this.state.attackGraph.toPrompt();
11127
+ if (graph) {
11128
+ sections.push("");
11129
+ sections.push("## Attack Graph");
11130
+ sections.push(graph);
11131
+ }
11132
+ const techniques = this.state.dynamicTechniques.toPrompt();
11133
+ if (techniques) {
11134
+ sections.push("");
11135
+ sections.push("## Learned Techniques");
11136
+ sections.push(techniques);
11137
+ }
11138
+ sections.push("");
11139
+ sections.push("## Time");
11140
+ sections.push(this.state.getTimeStatus());
11141
+ const analysis = this.state.getChallengeAnalysis();
11142
+ if (analysis && analysis.primaryType !== "unknown") {
11143
+ sections.push("");
11144
+ sections.push(`## Challenge Type: ${analysis.primaryType.toUpperCase()} (${(analysis.confidence * 100).toFixed(0)}%)`);
11145
+ sections.push(analysis.strategySuggestion);
11146
+ }
11147
+ let input = sections.join("\n");
11148
+ if (input.length > STRATEGIST_LIMITS.MAX_INPUT_CHARS) {
11149
+ input = input.slice(0, STRATEGIST_LIMITS.MAX_INPUT_CHARS) + "\n\n... [state truncated for Strategist context]";
11150
+ }
11151
+ return input;
11152
+ }
11153
+ // ─── LLM Call ───────────────────────────────────────────────
11154
+ async callLLM(input) {
11155
+ const messages = [{
11156
+ role: "user",
11157
+ content: `Analyze this penetration test situation and write a tactical directive for the attack agent.
11158
+
11159
+ ${input}`
11160
+ }];
11161
+ const response = await this.llm.generateResponse(
11162
+ messages,
11163
+ void 0,
11164
+ // No tools — strategy generation only
11165
+ this.systemPrompt
11166
+ );
11167
+ let content = response.content || "";
11168
+ if (content.length > STRATEGIST_LIMITS.MAX_OUTPUT_CHARS) {
11169
+ content = content.slice(0, STRATEGIST_LIMITS.MAX_OUTPUT_CHARS) + "\n... [directive truncated]";
11170
+ }
11171
+ const cost = response.usage ? response.usage.input_tokens + response.usage.output_tokens : 0;
11172
+ this.totalTokenCost += cost;
11173
+ return {
11174
+ content,
11175
+ generatedAt: Date.now(),
11176
+ tokenCost: cost
11177
+ };
11178
+ }
11179
+ // ─── Formatting ─────────────────────────────────────────────
11180
+ /**
11181
+ * Format directive for injection into the attack agent's system prompt.
11182
+ */
11183
+ formatForPrompt(directive, isStale = false) {
11184
+ if (!directive.content) return "";
11185
+ const age = Math.floor((Date.now() - directive.generatedAt) / 6e4);
11186
+ const staleWarning = isStale ? `
11187
+ NOTE: This directive is from ${age}min ago (Strategist call failed this turn). Verify assumptions are still valid.` : "";
11188
+ return [
11189
+ "<strategic-directive>",
11190
+ "TACTICAL DIRECTIVE (generated by Strategist LLM \u2014 follow these priorities):",
11191
+ "",
11192
+ directive.content,
11193
+ staleWarning,
11194
+ "</strategic-directive>"
11195
+ ].filter(Boolean).join("\n");
11196
+ }
11197
+ // ─── System Prompt Loading ──────────────────────────────────
11198
+ loadSystemPrompt() {
11199
+ try {
11200
+ if (existsSync9(STRATEGIST_PROMPT_PATH)) {
11201
+ return readFileSync6(STRATEGIST_PROMPT_PATH, "utf-8");
11202
+ }
11203
+ } catch {
11204
+ }
11205
+ return FALLBACK_SYSTEM_PROMPT;
11206
+ }
11207
+ // ─── Public API ─────────────────────────────────────────────
11208
+ /** Get total token cost of all Strategist calls this session. */
11209
+ getTotalTokenCost() {
11210
+ return this.totalTokenCost;
11211
+ }
11212
+ /** Get number of Strategist calls this session. */
11213
+ getTotalCalls() {
11214
+ return this.totalCalls;
11215
+ }
11216
+ /** Get the most recent directive (for TUI display). */
11217
+ getLastDirective() {
11218
+ return this.lastDirective;
11219
+ }
11220
+ /** Reset strategist state (for /clear command). */
11221
+ reset() {
11222
+ this.lastDirective = null;
11223
+ this.totalTokenCost = 0;
11224
+ this.totalCalls = 0;
11225
+ }
10927
11226
  };
11227
+ var FALLBACK_SYSTEM_PROMPT = `You are an elite autonomous penetration testing STRATEGIST \u2014 a red team tactical commander.
11228
+ Analyze the engagement state and issue precise attack orders for the execution agent.
11229
+ Format: SITUATION line, numbered PRIORITY items with ACTION/SEARCH/SUCCESS/FALLBACK/CHAIN fields, EXHAUSTED list, and SEARCH ORDERS.
11230
+ Be surgically specific: name exact tools, commands, parameters, and wordlists. The agent copy-pastes your commands.
11231
+ Include mandatory web_search directives for every unknown service/version.
11232
+ Detect stalls (repeated failures, no progress) and force completely different attack vectors.
11233
+ Chain every finding: "If X works \u2192 immediately do Y \u2192 which enables Z."
11234
+ Maximum 50 lines. Zero preamble. Direct imperatives only. Never repeat failed approaches.`;
10928
11235
 
10929
11236
  // src/agents/main-agent.ts
10930
11237
  var MainAgent = class extends CoreAgent {
10931
11238
  promptBuilder;
11239
+ strategist;
10932
11240
  approvalGate;
10933
11241
  scopeGuard;
10934
11242
  userInput = "";
@@ -10937,6 +11245,8 @@ var MainAgent = class extends CoreAgent {
10937
11245
  this.approvalGate = approvalGate;
10938
11246
  this.scopeGuard = scopeGuard;
10939
11247
  this.promptBuilder = new PromptBuilder(state);
11248
+ this.strategist = new Strategist(this.llm, state);
11249
+ this.promptBuilder.setStrategist(this.strategist);
10940
11250
  }
10941
11251
  /**
10942
11252
  * Public entry point for running the agent.
@@ -10947,7 +11257,8 @@ var MainAgent = class extends CoreAgent {
10947
11257
  this.emitStart(userInput);
10948
11258
  this.initializeTask();
10949
11259
  try {
10950
- const result2 = await this.run(userInput, this.getCurrentPrompt());
11260
+ const initialPrompt = await this.getCurrentPrompt();
11261
+ const result2 = await this.run(userInput, initialPrompt);
10951
11262
  return result2.output;
10952
11263
  } finally {
10953
11264
  try {
@@ -10963,9 +11274,10 @@ var MainAgent = class extends CoreAgent {
10963
11274
  /**
10964
11275
  * Override step to rebuild prompt dynamically each iteration.
10965
11276
  * This ensures the agent always sees the latest state, phase, and active processes.
11277
+ * The Strategist LLM generates a fresh tactical directive every turn.
10966
11278
  */
10967
11279
  async step(iteration, messages, _unusedPrompt, progress) {
10968
- const dynamicPrompt = this.getCurrentPrompt();
11280
+ const dynamicPrompt = await this.getCurrentPrompt();
10969
11281
  const result2 = await super.step(iteration, messages, dynamicPrompt, progress);
10970
11282
  this.emitStateChange();
10971
11283
  return result2;
@@ -10994,7 +11306,7 @@ var MainAgent = class extends CoreAgent {
10994
11306
  saveCurrentState() {
10995
11307
  return saveState(this.state);
10996
11308
  }
10997
- getCurrentPrompt() {
11309
+ async getCurrentPrompt() {
10998
11310
  const phase = this.state.getPhase() || PHASES.RECON;
10999
11311
  return this.promptBuilder.build(this.userInput, phase);
11000
11312
  }
@@ -11048,6 +11360,7 @@ var MainAgent = class extends CoreAgent {
11048
11360
  });
11049
11361
  this.state.reset();
11050
11362
  this.userInput = "";
11363
+ this.strategist.reset();
11051
11364
  return clearWorkspace();
11052
11365
  }
11053
11366
  setScope(allowed, exclusions = []) {
@@ -11069,6 +11382,10 @@ var MainAgent = class extends CoreAgent {
11069
11382
  });
11070
11383
  this.emitStateChange();
11071
11384
  }
11385
+ /** Get the Strategist instance (for TUI metrics display). */
11386
+ getStrategist() {
11387
+ return this.strategist;
11388
+ }
11072
11389
  };
11073
11390
 
11074
11391
  // src/agents/factory.ts
@@ -11095,7 +11412,7 @@ var AgentFactory = class {
11095
11412
  };
11096
11413
 
11097
11414
  // src/platform/tui/utils/format.ts
11098
- var formatDuration = (ms) => {
11415
+ var formatDuration2 = (ms) => {
11099
11416
  const totalSec = ms / 1e3;
11100
11417
  if (totalSec < 60) return `${totalSec.toFixed(1)}s`;
11101
11418
  const minutes = Math.floor(totalSec / 60);
@@ -11109,7 +11426,7 @@ var formatTokens = (count) => {
11109
11426
  };
11110
11427
  var formatMeta = (ms, tokens) => {
11111
11428
  const parts = [];
11112
- if (ms > 0) parts.push(formatDuration(ms));
11429
+ if (ms > 0) parts.push(formatDuration2(ms));
11113
11430
  if (tokens > 0) parts.push(`\u2191 ${formatTokens(tokens)} tokens`);
11114
11431
  return parts.length > 0 ? `(${parts.join(" \xB7 ")})` : "";
11115
11432
  };
@@ -11143,137 +11460,59 @@ var formatInlineStatus = () => {
11143
11460
  import { useState, useRef, useCallback } from "react";
11144
11461
 
11145
11462
  // src/shared/constants/theme.ts
11463
+ var COLORS = {
11464
+ primary: "#e11d48",
11465
+ // Rose 600 - red team primary
11466
+ accent: "#fb923c",
11467
+ // Orange 400 - fire accent
11468
+ secondary: "#94a3b8",
11469
+ // Slate - secondary/muted
11470
+ white: "#f8fafc",
11471
+ // Main text
11472
+ red: "#ff4500",
11473
+ // OrangeRed - Error/Critical (distinct from rose primary)
11474
+ yellow: "#facc15"
11475
+ // Yellow 400 - Warning/Pending (pure yellow)
11476
+ };
11146
11477
  var THEME = {
11147
- // Backgrounds (deep dark with blue tint)
11478
+ ...COLORS,
11148
11479
  bg: {
11149
11480
  primary: "#050505",
11150
- // Deepest black
11151
- secondary: "#0a0c10",
11152
- // Dark void
11153
- tertiary: "#0f172a",
11154
- // Slate dark
11155
- elevated: "#1e293b",
11156
- // Bright slate
11157
11481
  input: "#020617"
11158
- // Midnight
11159
11482
  },
11160
- // Text colors
11161
11483
  text: {
11162
- primary: "#f8fafc",
11163
- // Near white
11164
- secondary: "#cbd5e1",
11165
- // Brighter slate
11166
- muted: "#94a3b8",
11167
- // Lighter blue-gray (was too dark)
11168
- accent: "#38bdf8",
11169
- // Sky blue
11170
- highlight: "#ffffff"
11171
- // Pure white
11484
+ primary: COLORS.white,
11485
+ secondary: COLORS.secondary,
11486
+ muted: "#64748b",
11487
+ accent: COLORS.accent
11488
+ // Using Emerald for "accented" text
11172
11489
  },
11173
- // Status colors
11174
11490
  status: {
11175
- success: "#10b981",
11176
- // Emerald
11177
- warning: "#f59e0b",
11178
- // Amber
11179
- error: "#ef4444",
11180
- // Red
11181
- info: "#3b82f6",
11182
- // Blue 500 (distinct from user sky)
11183
- running: "#60a5fa",
11184
- // Blue 400 (AI activity — distinct from user sky)
11185
- pending: "#64748b"
11186
- // Slate
11491
+ success: COLORS.accent,
11492
+ // Success feels good in emerald
11493
+ warning: COLORS.yellow,
11494
+ error: COLORS.red,
11495
+ running: COLORS.primary
11496
+ // System operations in blue
11187
11497
  },
11188
- // Severity colors
11189
- semantic: {
11190
- critical: "#7f1d1d",
11191
- // Dark red
11192
- high: "#ef4444",
11193
- // Red
11194
- medium: "#f97316",
11195
- // Orange
11196
- low: "#eab308",
11197
- // Yellow
11198
- info: "#3b82f6"
11199
- // Blue
11200
- },
11201
- // Border colors
11202
11498
  border: {
11203
11499
  default: "#1e293b",
11204
- focus: "#7dd3fc",
11205
- // Sky 300 (softer, UI decorative)
11206
- error: "#ef4444",
11207
- success: "#22c55e"
11208
- },
11209
- // Phase colors
11210
- phase: {
11211
- recon: "#94a3b8",
11212
- enum: "#60a5fa",
11213
- // Blue 400 (phase indicator)
11214
- vuln: "#f59e0b",
11215
- exploit: "#ef4444",
11216
- privesc: "#8b5cf6",
11217
- persist: "#22c55e",
11218
- report: "#64748b"
11219
- },
11220
- // Accent colors
11221
- accent: {
11222
- pink: "#f472b6",
11223
- rose: "#fb7185",
11224
- fuchsia: "#e879f9",
11225
- purple: "#a78bfa",
11226
- violet: "#8b5cf6",
11227
- indigo: "#818cf8",
11228
- blue: "#60a5fa",
11229
- cyan: "#22d3ee",
11230
- teal: "#2dd4bf",
11231
- emerald: "#34d399",
11232
- green: "#4ade80",
11233
- lime: "#a3e635",
11234
- yellow: "#facc15",
11235
- amber: "#fbbf24",
11236
- orange: "#fb923c",
11237
- red: "#f87171"
11500
+ focus: COLORS.primary,
11501
+ error: COLORS.red
11238
11502
  },
11239
- // Gradients
11240
11503
  gradient: {
11241
- cyber: [
11242
- "#38bdf8",
11243
- // Sky 400
11244
- "#34c6f4",
11245
- "#30d0f0",
11246
- "#2cd9ec",
11247
- "#28e3e8",
11248
- "#22d3ee",
11249
- // Cyan 400
11250
- "#25dbd6",
11251
- "#28e4be",
11252
- "#2dd4bf",
11253
- // Teal 400
11254
- "#31dbac",
11255
- "#34d399"
11256
- // Emerald 400
11257
- ],
11258
- danger: ["#ef4444", "#7f1d1d"],
11259
- success: ["#22c55e", "#14532d"],
11260
- gold: ["#f59e0b", "#78350f"],
11261
- royal: ["#818cf8", "#312e81"]
11504
+ cyber: [COLORS.primary, "#f43f5e", "#fb923c"]
11505
+ // Red team fire gradient: rose → pink → amber
11262
11506
  },
11263
- // Spinner color (soft sky — UI feedback, not user input)
11264
- spinner: "#7dd3fc",
11265
- // Sky 300
11266
- // Identity color (branded accent)
11267
- identity: "#60a5fa"
11268
- // Blue 400
11507
+ spinner: COLORS.primary
11269
11508
  };
11270
11509
  var ASCII_BANNER = `
11271
- ____ __ __ _
11272
- / __ \\___ ____ / /____ _______/ /_(_)___ ____ _
11273
- / /_/ / _ \\/ __ \\/ __/ _ \\/ ___/ __/ / / __ \\/ __ \`/
11274
- / ____/ __/ / / / /_/ __(__ ) /_/ / / / / / /_/ /
11275
- /_/ \\___/_/ /_/\\__/\\___/____/\\__/_/_/_/ /_/\\__, /
11276
- /____/
11510
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557
11511
+ \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
11512
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557
11513
+ \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
11514
+ \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
11515
+ \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
11277
11516
  `;
11278
11517
  var ICONS = {
11279
11518
  // Status
@@ -11287,7 +11526,7 @@ var ICONS = {
11287
11526
  target: "\u25C8",
11288
11527
  // Diamond for target
11289
11528
  scan: "\u25CE",
11290
- exploit: "\u26A1",
11529
+ exploit: "\u2607",
11291
11530
  shell: "\u276F",
11292
11531
  // Progress
11293
11532
  pending: "\u25CB",
@@ -11299,9 +11538,9 @@ var ICONS = {
11299
11538
  medium: "\u25C7",
11300
11539
  low: "\u25E6",
11301
11540
  // Security
11302
- lock: "\u{1F512}",
11303
- unlock: "\u{1F513}",
11304
- key: "\u{1F511}",
11541
+ lock: "\u22A1",
11542
+ unlock: "\u2B1A",
11543
+ key: "\u26B7",
11305
11544
  flag: "\u2691",
11306
11545
  // Simple flag
11307
11546
  pwned: "\u25C8"
@@ -11311,39 +11550,39 @@ var ICONS = {
11311
11550
  // src/platform/tui/constants/display.ts
11312
11551
  var TUI_DISPLAY_LIMITS = {
11313
11552
  /** Reasoning buffer size for display */
11314
- reasoningBuffer: 1e3,
11553
+ reasoningBuffer: 2e3,
11315
11554
  /** Reasoning preview length */
11316
- reasoningPreview: 300,
11555
+ reasoningPreview: 500,
11317
11556
  /** Reasoning history slice for display */
11318
- reasoningHistorySlice: 500,
11557
+ reasoningHistorySlice: 1e3,
11319
11558
  /** Tool input preview length (for command display) */
11320
- toolInputPreview: 100,
11559
+ toolInputPreview: 200,
11321
11560
  /** Tool input truncated length */
11322
- toolInputTruncated: 97,
11323
- /** Tool output preview length */
11324
- toolOutputPreview: 500,
11561
+ toolInputTruncated: 197,
11562
+ /** Tool output preview length - increased to show full output */
11563
+ toolOutputPreview: 2e3,
11325
11564
  /** Error message preview length */
11326
- errorPreview: 300,
11565
+ errorPreview: 500,
11327
11566
  /** Status thought preview length */
11328
- statusThoughtPreview: 100,
11567
+ statusThoughtPreview: 150,
11329
11568
  /** Status thought truncated length */
11330
- statusThoughtTruncated: 97,
11569
+ statusThoughtTruncated: 147,
11331
11570
  /** Retry error preview length */
11332
- retryErrorPreview: 60,
11571
+ retryErrorPreview: 100,
11333
11572
  /** Retry error truncated length */
11334
- retryErrorTruncated: 57,
11573
+ retryErrorTruncated: 97,
11335
11574
  /** Timer update interval in ms */
11336
11575
  timerInterval: 1e3,
11337
11576
  /** Exit delay in ms */
11338
11577
  exitDelay: 100,
11339
11578
  /** Purpose text max length before truncation */
11340
- purposeMaxLength: 30,
11579
+ purposeMaxLength: 50,
11341
11580
  /** Purpose text truncated length */
11342
- purposeTruncated: 27,
11581
+ purposeTruncated: 47,
11343
11582
  /** Tool detail preview length in flow display */
11344
- toolDetailPreview: 100,
11583
+ toolDetailPreview: 200,
11345
11584
  /** Observe detail preview length in flow display */
11346
- observeDetailPreview: 80,
11585
+ observeDetailPreview: 150,
11347
11586
  /** Max flow nodes to display */
11348
11587
  maxFlowNodes: 50,
11349
11588
  /** Max stopped processes to show */
@@ -11358,7 +11597,7 @@ var MESSAGE_STYLES = {
11358
11597
  error: THEME.status.error,
11359
11598
  tool: THEME.status.running,
11360
11599
  result: THEME.text.muted,
11361
- status: THEME.accent.cyan
11600
+ status: THEME.primary
11362
11601
  },
11363
11602
  prefixes: {
11364
11603
  user: "\u276F",
@@ -11419,21 +11658,9 @@ var useAgentState = () => {
11419
11658
  const [isProcessing, setIsProcessing] = useState(false);
11420
11659
  const [currentStatus, setCurrentStatus] = useState("");
11421
11660
  const [elapsedTime, setElapsedTime] = useState(0);
11422
- const [retryState, setRetryState] = useState({
11423
- isRetrying: false,
11424
- attempt: 0,
11425
- maxRetries: 0,
11426
- delayMs: 0,
11427
- error: "",
11428
- countdown: 0
11429
- });
11661
+ const [retryState, setRetryState] = useState({ status: "idle" });
11430
11662
  const [currentTokens, setCurrentTokens] = useState(0);
11431
- const [inputRequest, setInputRequest] = useState({
11432
- isActive: false,
11433
- prompt: "",
11434
- isPassword: false,
11435
- resolve: null
11436
- });
11663
+ const [inputRequest, setInputRequest] = useState({ status: "inactive" });
11437
11664
  const [stats, setStats] = useState({ phase: DEFAULTS.INIT_PHASE, targets: 0, findings: 0, todo: 0 });
11438
11665
  const lastResponseMetaRef = useRef(null);
11439
11666
  const startTimeRef = useRef(0);
@@ -11557,10 +11784,10 @@ var useAgentEvents = (agent, eventsRef, state) => {
11557
11784
  setCurrentTokens(tokenAccumRef.current + stepTokens);
11558
11785
  };
11559
11786
  const onFlagFound = (e) => {
11560
- addMessage("system", `\u{1F3F4} FLAG FOUND: ${e.data.flag} (total: ${e.data.totalFlags})`);
11787
+ addMessage("system", `[FLAG] ${e.data.flag} (total: ${e.data.totalFlags})`);
11561
11788
  };
11562
11789
  const onPhaseChange = (e) => {
11563
- addMessage("system", `\u{1F504} Phase: ${e.data.fromPhase} \u2192 ${e.data.toPhase} (${e.data.reason})`);
11790
+ addMessage("system", `[Phase] ${e.data.fromPhase} -> ${e.data.toPhase} (${e.data.reason})`);
11564
11791
  const s = agent.getState();
11565
11792
  setStats({
11566
11793
  phase: e.data.toPhase,
@@ -11574,7 +11801,7 @@ var useAgentEvents = (agent, eventsRef, state) => {
11574
11801
  const isPassword = /password|passphrase/i.test(p);
11575
11802
  const inputType = /sudo/i.test(p) ? "sudo_password" : isPassword ? "password" : "text";
11576
11803
  setInputRequest({
11577
- isActive: true,
11804
+ status: "active",
11578
11805
  prompt: p.trim(),
11579
11806
  isPassword,
11580
11807
  inputType,
@@ -11588,7 +11815,7 @@ var useAgentEvents = (agent, eventsRef, state) => {
11588
11815
  const isPassword = hiddenTypes.includes(request.type);
11589
11816
  const displayPrompt = buildCredentialPrompt(request);
11590
11817
  setInputRequest({
11591
- isActive: true,
11818
+ status: "active",
11592
11819
  prompt: displayPrompt,
11593
11820
  isPassword,
11594
11821
  inputType: request.type,
@@ -11669,7 +11896,7 @@ function handleRetry(e, addMessage, setRetryState, retryCountdownRef, retryCount
11669
11896
  const retryNum = retryCountRef.current;
11670
11897
  addMessage("system", `\u27F3 Retry #${retryNum} \xB7 ${e.data.error} \xB7 waiting ${delaySec}s...`);
11671
11898
  setRetryState({
11672
- isRetrying: true,
11899
+ status: "retrying",
11673
11900
  attempt: retryNum,
11674
11901
  maxRetries: e.data.maxRetries,
11675
11902
  delayMs: e.data.delayMs,
@@ -11681,10 +11908,10 @@ function handleRetry(e, addMessage, setRetryState, retryCountdownRef, retryCount
11681
11908
  retryCountdownRef.current = setInterval(() => {
11682
11909
  remaining -= 1;
11683
11910
  if (remaining <= 0) {
11684
- setRetryState((prev) => ({ ...prev, isRetrying: false, countdown: 0 }));
11911
+ setRetryState({ status: "idle" });
11685
11912
  if (retryCountdownRef.current) clearInterval(retryCountdownRef.current);
11686
11913
  } else {
11687
- setRetryState((prev) => ({ ...prev, countdown: remaining }));
11914
+ setRetryState((prev) => prev.status === "retrying" ? { ...prev, countdown: remaining } : prev);
11688
11915
  }
11689
11916
  }, 1e3);
11690
11917
  }
@@ -11705,18 +11932,18 @@ Options: ${request.options.join(", ")}`;
11705
11932
  }
11706
11933
  function getCommandEventIcon(eventType) {
11707
11934
  const icons = {
11708
- [COMMAND_EVENT_TYPES.TOOL_MISSING]: "\u26A0\uFE0F",
11709
- [COMMAND_EVENT_TYPES.TOOL_INSTALL]: "\u{1F4E6}",
11710
- [COMMAND_EVENT_TYPES.TOOL_INSTALLED]: "\u2705",
11711
- [COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED]: "\u274C",
11712
- [COMMAND_EVENT_TYPES.TOOL_RETRY]: "\u{1F504}",
11713
- [COMMAND_EVENT_TYPES.COMMAND_START]: "\u25B6",
11714
- [COMMAND_EVENT_TYPES.COMMAND_SUCCESS]: "\u2713",
11715
- [COMMAND_EVENT_TYPES.COMMAND_FAILED]: "\u2717",
11716
- [COMMAND_EVENT_TYPES.COMMAND_ERROR]: "\u274C",
11717
- [COMMAND_EVENT_TYPES.INPUT_REQUIRED]: "\u{1F510}"
11935
+ [COMMAND_EVENT_TYPES.TOOL_MISSING]: "[!]",
11936
+ [COMMAND_EVENT_TYPES.TOOL_INSTALL]: "[install]",
11937
+ [COMMAND_EVENT_TYPES.TOOL_INSTALLED]: "[ok]",
11938
+ [COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED]: "[fail]",
11939
+ [COMMAND_EVENT_TYPES.TOOL_RETRY]: "[retry]",
11940
+ [COMMAND_EVENT_TYPES.COMMAND_START]: "[>]",
11941
+ [COMMAND_EVENT_TYPES.COMMAND_SUCCESS]: "[ok]",
11942
+ [COMMAND_EVENT_TYPES.COMMAND_FAILED]: "[x]",
11943
+ [COMMAND_EVENT_TYPES.COMMAND_ERROR]: "[err]",
11944
+ [COMMAND_EVENT_TYPES.INPUT_REQUIRED]: "[auth]"
11718
11945
  };
11719
- return icons[eventType] || "\u2022";
11946
+ return icons[eventType] || "[-]";
11720
11947
  }
11721
11948
 
11722
11949
  // src/platform/tui/hooks/useAgent.ts
@@ -11779,9 +12006,9 @@ var useAgent = (shouldAutoApprove, target) => {
11779
12006
  inputRequestRef.current = inputRequest;
11780
12007
  const cancelInputRequest = useCallback2(() => {
11781
12008
  const ir = inputRequestRef.current;
11782
- if (ir.isActive && ir.resolve) {
12009
+ if (ir.status === "active") {
11783
12010
  ir.resolve(null);
11784
- setInputRequest({ isActive: false, prompt: "", isPassword: false, resolve: null });
12011
+ setInputRequest({ status: "inactive" });
11785
12012
  addMessage("system", "Input cancelled");
11786
12013
  }
11787
12014
  }, [setInputRequest, addMessage]);
@@ -11817,12 +12044,13 @@ var useAgent = (shouldAutoApprove, target) => {
11817
12044
  };
11818
12045
 
11819
12046
  // src/platform/tui/components/MessageList.tsx
12047
+ import { memo } from "react";
11820
12048
  import { Box as Box2, Text as Text2, Static } from "ink";
11821
12049
 
11822
12050
  // src/platform/tui/components/inline-status.tsx
11823
12051
  import { Box, Text } from "ink";
11824
12052
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11825
- function formatDuration2(ms) {
12053
+ function formatDuration3(ms) {
11826
12054
  const seconds = Math.floor(ms / 1e3);
11827
12055
  if (seconds < 60) return `${seconds}s`;
11828
12056
  const minutes = Math.floor(seconds / 60);
@@ -11834,13 +12062,13 @@ function formatDuration2(ms) {
11834
12062
  }
11835
12063
  function getRoleColor(role) {
11836
12064
  const roleColors = {
11837
- listener: THEME.accent.cyan,
11838
- active_shell: THEME.accent.green,
11839
- server: THEME.accent.blue,
11840
- sniffer: THEME.accent.amber,
11841
- spoofer: THEME.accent.orange,
11842
- proxy: THEME.accent.purple,
11843
- callback: THEME.accent.pink,
12065
+ listener: THEME.primary,
12066
+ active_shell: THEME.primary,
12067
+ server: THEME.secondary,
12068
+ sniffer: THEME.yellow,
12069
+ spoofer: THEME.yellow,
12070
+ proxy: THEME.primary,
12071
+ callback: THEME.primary,
11844
12072
  background: THEME.text.muted
11845
12073
  };
11846
12074
  return roleColors[role] || THEME.text.secondary;
@@ -11864,7 +12092,7 @@ function StatusIndicator({ running, exitCode }) {
11864
12092
  ] });
11865
12093
  }
11866
12094
  function ProcessRow({ proc, compact }) {
11867
- const duration = formatDuration2(proc.durationMs);
12095
+ const duration = formatDuration3(proc.durationMs);
11868
12096
  const port = proc.listeningPort ? `:${proc.listeningPort}` : "";
11869
12097
  const purpose = proc.purpose || proc.description || "";
11870
12098
  const truncatedPurpose = compact && purpose.length > TUI_DISPLAY_LIMITS.purposeMaxLength ? purpose.slice(0, TUI_DISPLAY_LIMITS.purposeTruncated) + "..." : purpose;
@@ -11979,7 +12207,7 @@ function parseStatusContent(content) {
11979
12207
  }
11980
12208
  return null;
11981
12209
  }
11982
- var MessageList = ({ messages }) => {
12210
+ var MessageList = memo(({ messages }) => {
11983
12211
  return /* @__PURE__ */ jsx2(Static, { items: messages, children: (msg) => {
11984
12212
  if (msg.type === "status") {
11985
12213
  const statusData = parseStatusContent(msg.content);
@@ -12000,18 +12228,19 @@ var MessageList = ({ messages }) => {
12000
12228
  msg.content
12001
12229
  ] }) }, msg.id);
12002
12230
  } });
12003
- };
12231
+ });
12004
12232
 
12005
12233
  // src/platform/tui/components/StatusDisplay.tsx
12234
+ import { memo as memo3 } from "react";
12006
12235
  import { Box as Box3, Text as Text4 } from "ink";
12007
12236
 
12008
12237
  // src/platform/tui/components/MusicSpinner.tsx
12009
- import { useState as useState3, useEffect as useEffect3 } from "react";
12238
+ import { useState as useState3, useEffect as useEffect3, memo as memo2 } from "react";
12010
12239
  import { Text as Text3 } from "ink";
12011
12240
  import { jsx as jsx3 } from "react/jsx-runtime";
12012
12241
  var FRAMES = ["\u2669", "\u266A", "\u266B", "\u266C", "\u266B", "\u266A"];
12013
- var INTERVAL = 400;
12014
- var MusicSpinner = ({ color }) => {
12242
+ var INTERVAL = 600;
12243
+ var MusicSpinner = memo2(({ color }) => {
12015
12244
  const [index, setIndex] = useState3(0);
12016
12245
  useEffect3(() => {
12017
12246
  const timer = setInterval(() => {
@@ -12020,11 +12249,11 @@ var MusicSpinner = ({ color }) => {
12020
12249
  return () => clearInterval(timer);
12021
12250
  }, []);
12022
12251
  return /* @__PURE__ */ jsx3(Text3, { color, children: FRAMES[index] });
12023
- };
12252
+ });
12024
12253
 
12025
12254
  // src/platform/tui/components/StatusDisplay.tsx
12026
12255
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
12027
- var StatusDisplay = ({
12256
+ var StatusDisplay = memo3(({
12028
12257
  retryState,
12029
12258
  isProcessing,
12030
12259
  currentStatus,
@@ -12036,7 +12265,7 @@ var StatusDisplay = ({
12036
12265
  };
12037
12266
  const meta = formatMeta(elapsedTime * 1e3, currentTokens);
12038
12267
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 0, children: [
12039
- retryState.isRetrying && /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
12268
+ retryState.status === "retrying" && /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
12040
12269
  /* @__PURE__ */ jsx4(Text4, { color: THEME.status.warning, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: THEME.status.warning }) }),
12041
12270
  /* @__PURE__ */ jsxs3(Text4, { color: THEME.status.warning, children: [
12042
12271
  " \u27F3 Retry #",
@@ -12046,13 +12275,13 @@ var StatusDisplay = ({
12046
12275
  " \xB7 ",
12047
12276
  truncateError(retryState.error)
12048
12277
  ] }),
12049
- /* @__PURE__ */ jsxs3(Text4, { color: THEME.accent.cyan, bold: true, children: [
12278
+ /* @__PURE__ */ jsxs3(Text4, { color: THEME.primary, bold: true, children: [
12050
12279
  " \xB7 ",
12051
12280
  retryState.countdown,
12052
12281
  "s"
12053
12282
  ] })
12054
12283
  ] }),
12055
- isProcessing && !retryState.isRetrying && /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
12284
+ isProcessing && retryState.status !== "retrying" && /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
12056
12285
  /* @__PURE__ */ jsx4(Text4, { color: THEME.spinner, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: THEME.spinner }) }),
12057
12286
  /* @__PURE__ */ jsxs3(Text4, { color: THEME.text.muted, children: [
12058
12287
  " ",
@@ -12064,15 +12293,15 @@ var StatusDisplay = ({
12064
12293
  ] })
12065
12294
  ] })
12066
12295
  ] });
12067
- };
12296
+ });
12068
12297
 
12069
12298
  // src/platform/tui/components/ChatInput.tsx
12070
- import { useMemo, useCallback as useCallback3, useRef as useRef3 } from "react";
12299
+ import { useMemo, useCallback as useCallback3, useRef as useRef3, memo as memo4 } from "react";
12071
12300
  import { Box as Box4, Text as Text5, useInput } from "ink";
12072
12301
  import TextInput from "ink-text-input";
12073
12302
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
12074
12303
  var MAX_SUGGESTIONS = 6;
12075
- var ChatInput = ({
12304
+ var ChatInput = memo4(({
12076
12305
  value,
12077
12306
  onChange,
12078
12307
  onSubmit,
@@ -12101,11 +12330,13 @@ var ChatInput = ({
12101
12330
  const onChangeRef = useRef3(onChange);
12102
12331
  onChangeRef.current = onChange;
12103
12332
  useInput(useCallback3((_input, key) => {
12104
- if (inputRequestRef.current.isActive) return;
12333
+ if (inputRequestRef.current.status === "active") return;
12105
12334
  if (key.tab && isSlashModeRef.current && !hasArgsRef.current && suggestionsRef.current.length > 0) {
12106
12335
  const best = suggestionsRef.current[0];
12107
12336
  const argsHint = best.args ? " " : "";
12108
- onChangeRef.current(`/${best.name}${argsHint}`);
12337
+ const completed = `/${best.name}${argsHint}`;
12338
+ onChangeRef.current("");
12339
+ setTimeout(() => onChangeRef.current(completed), 0);
12109
12340
  }
12110
12341
  }, []));
12111
12342
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
@@ -12119,7 +12350,7 @@ var ChatInput = ({
12119
12350
  marginBottom: 0,
12120
12351
  children: suggestions.map((cmd, i) => {
12121
12352
  const isFirst = i === 0;
12122
- const nameColor = isFirst ? THEME.text.accent : THEME.text.secondary;
12353
+ const nameColor = isFirst ? THEME.primary : THEME.text.secondary;
12123
12354
  const aliasText = cmd.alias ? ` /${cmd.alias}` : "";
12124
12355
  const argsText = cmd.args ? ` ${cmd.args}` : "";
12125
12356
  return /* @__PURE__ */ jsxs4(Box4, { children: [
@@ -12133,7 +12364,7 @@ var ChatInput = ({
12133
12364
  " \u2014 ",
12134
12365
  cmd.description
12135
12366
  ] }),
12136
- isFirst && /* @__PURE__ */ jsx5(Text5, { color: THEME.accent.cyan, children: " \u21E5 Tab" })
12367
+ isFirst && /* @__PURE__ */ jsx5(Text5, { color: THEME.primary, children: " [Tab]" })
12137
12368
  ] }, cmd.name);
12138
12369
  })
12139
12370
  }
@@ -12142,10 +12373,10 @@ var ChatInput = ({
12142
12373
  Box4,
12143
12374
  {
12144
12375
  borderStyle: "single",
12145
- borderColor: inputRequest.isActive ? THEME.status.warning : THEME.border.default,
12376
+ borderColor: inputRequest.status === "active" ? THEME.status.warning : THEME.border.default,
12146
12377
  paddingX: 1,
12147
- children: inputRequest.isActive ? /* @__PURE__ */ jsxs4(Box4, { children: [
12148
- /* @__PURE__ */ jsx5(Text5, { color: THEME.status.warning, children: "\u{1F512}" }),
12378
+ children: inputRequest.status === "active" ? /* @__PURE__ */ jsxs4(Box4, { children: [
12379
+ /* @__PURE__ */ jsx5(Text5, { color: THEME.status.warning, children: "[auth]" }),
12149
12380
  /* @__PURE__ */ jsxs4(Text5, { color: THEME.text.muted, children: [
12150
12381
  " ",
12151
12382
  inputRequest.prompt
@@ -12176,9 +12407,10 @@ var ChatInput = ({
12176
12407
  }
12177
12408
  )
12178
12409
  ] });
12179
- };
12410
+ });
12180
12411
 
12181
12412
  // src/platform/tui/components/footer.tsx
12413
+ import { memo as memo5 } from "react";
12182
12414
  import { Box as Box5, Text as Text6 } from "ink";
12183
12415
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
12184
12416
  var formatElapsed = (totalSeconds) => {
@@ -12191,7 +12423,7 @@ var formatElapsed = (totalSeconds) => {
12191
12423
  }
12192
12424
  return `${minutes}:${pad(seconds)}`;
12193
12425
  };
12194
- var Footer = ({ phase, targets, findings, todo, elapsedTime, isProcessing }) => {
12426
+ var Footer = memo5(({ phase, targets, findings, todo, elapsedTime, isProcessing }) => {
12195
12427
  return /* @__PURE__ */ jsxs5(
12196
12428
  Box5,
12197
12429
  {
@@ -12202,7 +12434,7 @@ var Footer = ({ phase, targets, findings, todo, elapsedTime, isProcessing }) =>
12202
12434
  /* @__PURE__ */ jsxs5(Box5, { gap: 2, children: [
12203
12435
  /* @__PURE__ */ jsxs5(Text6, { color: THEME.text.muted, children: [
12204
12436
  "Phase: ",
12205
- /* @__PURE__ */ jsx6(Text6, { color: THEME.accent.cyan, children: phase })
12437
+ /* @__PURE__ */ jsx6(Text6, { color: THEME.primary, children: phase })
12206
12438
  ] }),
12207
12439
  /* @__PURE__ */ jsxs5(Text6, { color: THEME.text.muted, children: [
12208
12440
  "Targets: ",
@@ -12224,7 +12456,7 @@ var Footer = ({ phase, targets, findings, todo, elapsedTime, isProcessing }) =>
12224
12456
  ]
12225
12457
  }
12226
12458
  );
12227
- };
12459
+ });
12228
12460
  var footer_default = Footer;
12229
12461
 
12230
12462
  // src/platform/tui/app.tsx
@@ -12260,7 +12492,7 @@ var App = ({ autoApprove = false, target }) => {
12260
12492
  inputRequestRef.current = inputRequest;
12261
12493
  const handleExit = useCallback4(() => {
12262
12494
  const ir = inputRequestRef.current;
12263
- if (ir.isActive && ir.resolve) ir.resolve(null);
12495
+ if (ir.status === "active") ir.resolve(null);
12264
12496
  cleanupAllProcesses().catch(() => {
12265
12497
  });
12266
12498
  exit();
@@ -12281,9 +12513,9 @@ var App = ({ autoApprove = false, target }) => {
12281
12513
  setMessages([]);
12282
12514
  const result2 = await agent.resetSession();
12283
12515
  if (result2.cleared.length > 0) {
12284
- addMessage("system", `\u{1F9F9} Session reset \u2014 cleared: ${result2.cleared.join(", ")}`);
12516
+ addMessage("system", `[reset] Session cleared: ${result2.cleared.join(", ")}`);
12285
12517
  } else {
12286
- addMessage("system", "\u{1F9F9} Session reset \u2014 all clean");
12518
+ addMessage("system", "[reset] Session clean");
12287
12519
  }
12288
12520
  if (result2.errors.length > 0) {
12289
12521
  addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
@@ -12310,7 +12542,7 @@ var App = ({ autoApprove = false, target }) => {
12310
12542
  if (!autoApproveModeRef.current) {
12311
12543
  setAutoApproveMode(true);
12312
12544
  agent.setAutoApprove(true);
12313
- addMessage("system", "\u{1F680} Autonomous mode enabled (auto-approve ON)");
12545
+ addMessage("system", "[auto] Autonomous mode enabled");
12314
12546
  }
12315
12547
  addMessage("system", "Starting penetration test...");
12316
12548
  const targets = Array.from(agent.getState().getTargets().keys());
@@ -12324,8 +12556,29 @@ var App = ({ autoApprove = false, target }) => {
12324
12556
  addMessage("system", "No findings.");
12325
12557
  break;
12326
12558
  }
12327
- addMessage("system", `--- ${findings.length} Findings ---`);
12328
- findings.forEach((f) => addMessage("system", `[${f.severity}] ${f.title}${f.attackPattern ? ` (ATT&CK: ${f.attackPattern})` : ""}`));
12559
+ const findingLines = [`\u2500\u2500\u2500 ${findings.length} Findings \u2500\u2500\u2500`, ""];
12560
+ findings.forEach((f, i) => {
12561
+ const verified = f.isVerified ? `[${ICONS.success}] Verified` : `[${ICONS.warning}] Unverified`;
12562
+ const atk = f.attackPattern ? ` | ATT&CK: ${f.attackPattern}` : "";
12563
+ findingLines.push(`[${i + 1}] [${f.severity.toUpperCase()}] ${f.title}`);
12564
+ findingLines.push(` ${verified}${atk}`);
12565
+ if (f.affected.length > 0) {
12566
+ findingLines.push(` Affected: ${f.affected.join(", ")}`);
12567
+ }
12568
+ if (f.description) {
12569
+ findingLines.push(` ${f.description}`);
12570
+ }
12571
+ if (f.evidence.length > 0) {
12572
+ findingLines.push(` Evidence:`);
12573
+ f.evidence.slice(0, 3).forEach((e) => {
12574
+ const preview = e.length > 120 ? e.slice(0, 120) + "..." : e;
12575
+ findingLines.push(` \u25B8 ${preview}`);
12576
+ });
12577
+ if (f.evidence.length > 3) findingLines.push(` ... +${f.evidence.length - 3} more`);
12578
+ }
12579
+ findingLines.push("");
12580
+ });
12581
+ addMessage("system", findingLines.join("\n"));
12329
12582
  break;
12330
12583
  case UI_COMMANDS.ASSETS:
12331
12584
  case UI_COMMANDS.ASSETS_SHORT:
@@ -12348,7 +12601,7 @@ ${procData.stdout || "(no output)"}
12348
12601
  break;
12349
12602
  case UI_COMMANDS.CTF:
12350
12603
  const ctfEnabled = agent.toggleCtfMode();
12351
- addMessage("system", ctfEnabled ? "\u{1F3F4} CTF mode ON \u2014 flag detection active, CTF prompts loaded" : "\u{1F512} CTF mode OFF \u2014 standard pentest mode");
12604
+ addMessage("system", ctfEnabled ? "[CTF] Mode ON - flag detection active" : "[CTF] Mode OFF - standard pentest");
12352
12605
  break;
12353
12606
  case UI_COMMANDS.GRAPH:
12354
12607
  case UI_COMMANDS.GRAPH_SHORT:
@@ -12389,17 +12642,17 @@ ${procData.stdout || "(no output)"}
12389
12642
  }, [addMessage, executeTask, handleCommand]);
12390
12643
  const handleSecretSubmit = useCallback4((value) => {
12391
12644
  const ir = inputRequestRef.current;
12392
- if (!ir.isActive || !ir.resolve) return;
12645
+ if (ir.status !== "active") return;
12393
12646
  const displayText = ir.isPassword ? "\u2022".repeat(value.length) : value;
12394
12647
  const promptLabel = ir.prompt || "Input";
12395
12648
  addMessage("system", `\u21B3 ${promptLabel} ${displayText}`);
12396
12649
  ir.resolve(value);
12397
- setInputRequest({ isActive: false, prompt: "", isPassword: false, resolve: null });
12650
+ setInputRequest({ status: "inactive" });
12398
12651
  setSecretInput("");
12399
12652
  }, [addMessage, setInputRequest]);
12400
12653
  useInput2(useCallback4((ch, key) => {
12401
12654
  if (key.escape) {
12402
- if (inputRequestRef.current.isActive) cancelInputRequest();
12655
+ if (inputRequestRef.current.status === "active") cancelInputRequest();
12403
12656
  else if (isProcessingRef.current) abort();
12404
12657
  }
12405
12658
  if (key.ctrl && ch === "c") handleExit();
@@ -12486,9 +12739,9 @@ program.command("interactive", { isDefault: true }).alias("i").description("Star
12486
12739
  const opts = program.opts();
12487
12740
  const skipPermissions = opts.dangerouslySkipPermissions || false;
12488
12741
  console.clear();
12489
- console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
12742
+ console.log(chalk.hex(THEME.primary)(ASCII_BANNER));
12490
12743
  console.log(
12491
- " " + chalk.hex(THEME.text.secondary)(`v${APP_VERSION}`) + chalk.hex(THEME.text.muted)(" \u2502 ") + chalk.hex(THEME.accent.blue)("Type /help for commands") + "\n"
12744
+ " " + chalk.hex(THEME.text.secondary)(`v${APP_VERSION}`) + chalk.hex(THEME.text.muted)(" \u2502 ") + chalk.hex(THEME.primary)("Type /help for commands") + "\n"
12492
12745
  );
12493
12746
  if (skipPermissions) {
12494
12747
  console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions"));
@@ -12508,11 +12761,11 @@ program.command("interactive", { isDefault: true }).alias("i").description("Star
12508
12761
  program.command("run <objective>").alias("r").description("Run a single objective and exit").option("-o, --output <file>", "Output file for results").option("--max-steps <n>", "Maximum number of steps", String(CLI_DEFAULT.MAX_STEPS)).action(async (objective, options) => {
12509
12762
  const opts = program.opts();
12510
12763
  const skipPermissions = opts.dangerouslySkipPermissions || false;
12511
- console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
12764
+ console.log(chalk.hex(THEME.primary)(ASCII_BANNER));
12512
12765
  if (skipPermissions) {
12513
12766
  console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions\n"));
12514
12767
  }
12515
- console.log(chalk.hex(THEME.accent.blue)(`[target] Objective: ${objective}
12768
+ console.log(chalk.hex(THEME.primary)(`[target] Objective: ${objective}
12516
12769
  `));
12517
12770
  const agent = AgentFactory.createMainAgent(skipPermissions);
12518
12771
  if (skipPermissions) {
@@ -12534,7 +12787,7 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
12534
12787
  if (options.output) {
12535
12788
  const fs = await import("fs/promises");
12536
12789
  await fs.writeFile(options.output, JSON.stringify({ result: result2 }, null, 2));
12537
- console.log(chalk.hex(THEME.accent.blue)(`
12790
+ console.log(chalk.hex(THEME.primary)(`
12538
12791
  [+] Report saved to: ${options.output}`));
12539
12792
  }
12540
12793
  await shutdown(0);
@@ -12548,8 +12801,8 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
12548
12801
  program.command("scan <target>").description("Quick scan a target").option("-s, --scan-type <type>", `Scan type (${CLI_SCAN_TYPES.join("|")})`, CLI_DEFAULT.SCAN_TYPE).option("-p, --ports <ports>", "Specific ports to scan").action(async (target, options) => {
12549
12802
  const opts = program.opts();
12550
12803
  const skipPermissions = opts.dangerouslySkipPermissions || false;
12551
- console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
12552
- console.log(chalk.hex(THEME.accent.blue)(`
12804
+ console.log(chalk.hex(THEME.primary)(ASCII_BANNER));
12805
+ console.log(chalk.hex(THEME.primary)(`
12553
12806
  [scan] Target: ${target} (${options.scanType})
12554
12807
  `));
12555
12808
  const agent = AgentFactory.createMainAgent(skipPermissions);
@@ -12571,11 +12824,11 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
12571
12824
  }
12572
12825
  });
12573
12826
  program.command("help-extended").description("Show extended help with examples").action(() => {
12574
- console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
12827
+ console.log(chalk.hex(THEME.primary)(ASCII_BANNER));
12575
12828
  console.log(`
12576
- ${chalk.hex(THEME.accent.blue)(APP_NAME + " - Autonomous Penetration Testing AI")}
12829
+ ${chalk.hex(THEME.primary)(APP_NAME + " - Autonomous Penetration Testing AI")}
12577
12830
 
12578
- ${chalk.hex(THEME.status.warning)("Usage:")}
12831
+ ${chalk.hex(THEME.status.warning)("Usage:")}
12579
12832
 
12580
12833
  ${chalk.hex(THEME.status.success)("$ pentesting")} Start interactive mode
12581
12834
  ${chalk.hex(THEME.status.success)("$ pentesting -t 192.168.1.1")} Start with target
@@ -12583,24 +12836,24 @@ ${chalk.hex(THEME.status.warning)("Usage:")}
12583
12836
 
12584
12837
  ${chalk.hex(THEME.status.warning)("Commands:")}
12585
12838
 
12586
- ${chalk.hex(THEME.accent.blue)("pentesting")} Interactive TUI mode
12587
- ${chalk.hex(THEME.accent.blue)("pentesting run <objective>")} Run single objective
12588
- ${chalk.hex(THEME.accent.blue)("pentesting scan <target>")} Quick scan target
12839
+ ${chalk.hex(THEME.primary)("pentesting")} Interactive TUI mode
12840
+ ${chalk.hex(THEME.primary)("pentesting run <objective>")} Run single objective
12841
+ ${chalk.hex(THEME.primary)("pentesting scan <target>")} Quick scan target
12589
12842
 
12590
12843
  ${chalk.hex(THEME.status.warning)("Options:")}
12591
12844
 
12592
- ${chalk.hex(THEME.accent.blue)("--dangerously-skip-permissions")} Skip all permission prompts
12593
- ${chalk.hex(THEME.accent.blue)("-t, --target <ip>")} Set target
12594
- ${chalk.hex(THEME.accent.blue)("-o, --output <file>")} Save results to file
12845
+ ${chalk.hex(THEME.primary)("--dangerously-skip-permissions")} Skip all permission prompts
12846
+ ${chalk.hex(THEME.primary)("-t, --target <ip>")} Set target
12847
+ ${chalk.hex(THEME.primary)("-o, --output <file>")} Save results to file
12595
12848
 
12596
12849
  ${chalk.hex(THEME.status.warning)("Interactive Commands:")}
12597
12850
 
12598
- ${chalk.hex(THEME.accent.blue)("/target <ip>")} Set target
12599
- ${chalk.hex(THEME.accent.blue)("/start")} Start autonomous mode
12600
- ${chalk.hex(THEME.accent.blue)("/config")} Manage configuration
12601
- ${chalk.hex(THEME.accent.blue)("/hint <text>")} Provide hint
12602
- ${chalk.hex(THEME.accent.blue)("/findings")} Show findings
12603
- ${chalk.hex(THEME.accent.blue)("/reset")} Reset session
12851
+ ${chalk.hex(THEME.primary)("/target <ip>")} Set target
12852
+ ${chalk.hex(THEME.primary)("/start")} Start autonomous mode
12853
+ ${chalk.hex(THEME.primary)("/config")} Manage configuration
12854
+ ${chalk.hex(THEME.primary)("/hint <text>")} Provide hint
12855
+ ${chalk.hex(THEME.primary)("/findings")} Show findings
12856
+ ${chalk.hex(THEME.primary)("/reset")} Reset session
12604
12857
 
12605
12858
  ${chalk.hex(THEME.status.warning)("Examples:")}
12606
12859
 
@@ -12615,9 +12868,9 @@ ${chalk.hex(THEME.status.warning)("Examples:")}
12615
12868
 
12616
12869
  ${chalk.hex(THEME.status.warning)("Environment:")}
12617
12870
 
12618
- ${chalk.hex(THEME.accent.blue)("PENTEST_API_KEY")} Required - LLM API key
12619
- ${chalk.hex(THEME.accent.blue)("PENTEST_BASE_URL")} Optional - AI API base URL
12620
- ${chalk.hex(THEME.accent.blue)("PENTEST_MODEL")} Optional - Model override
12871
+ ${chalk.hex(THEME.primary)("PENTEST_API_KEY")} Required - LLM API key
12872
+ ${chalk.hex(THEME.primary)("PENTEST_BASE_URL")} Optional - AI API base URL
12873
+ ${chalk.hex(THEME.primary)("PENTEST_MODEL")} Optional - Model override
12621
12874
  `);
12622
12875
  });
12623
12876
  program.parse();