eva-exploit 2.5__py3-none-any.whl
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.
- config.py +29 -0
- eva.py +166 -0
- eva_exploit-2.5.dist-info/METADATA +410 -0
- eva_exploit-2.5.dist-info/RECORD +17 -0
- eva_exploit-2.5.dist-info/WHEEL +5 -0
- eva_exploit-2.5.dist-info/entry_points.txt +2 -0
- eva_exploit-2.5.dist-info/top_level.txt +5 -0
- modules/__init__.py +0 -0
- modules/attack_map.py +467 -0
- modules/llm.py +599 -0
- modules/prompt_builder.py +56 -0
- modules/reporting.py +254 -0
- sessions/__init__.py +0 -0
- sessions/eva_session.py +457 -0
- utils/__init__.py +0 -0
- utils/system.py +264 -0
- utils/ui.py +191 -0
sessions/eva_session.py
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import getpass
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import signal
|
|
6
|
+
import subprocess
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
|
|
9
|
+
from colorama import Fore,Back,Style
|
|
10
|
+
|
|
11
|
+
from config import API_ENDPOINT, MAPS_DIR, REPORTS_DIR, SESSIONS_DIR, username
|
|
12
|
+
from modules.attack_map import generate_attack_map_files, open_attack_map
|
|
13
|
+
from modules.llm import LLM
|
|
14
|
+
from modules.reporting import build_html_report, open_report_file, try_generate_pdf
|
|
15
|
+
from utils.system import (
|
|
16
|
+
checkAnthropicKey,
|
|
17
|
+
checkAPI,
|
|
18
|
+
checkGeminiKey,
|
|
19
|
+
checkOpenAIKey,
|
|
20
|
+
get_current_version,
|
|
21
|
+
graceful_exit,
|
|
22
|
+
)
|
|
23
|
+
from utils.ui import cyber, menu, raw_input, spinner_start, spinner_stop
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# +-------------------------------------------+
|
|
27
|
+
# | Main core -██ |
|
|
28
|
+
# | Handler for Eva utilities and |
|
|
29
|
+
# | Initialization and session manag |
|
|
30
|
+
# +-------------------------------------------+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Eva:
|
|
34
|
+
def __init__(self, session_path, backend, go_menu):
|
|
35
|
+
self.session_path = session_path
|
|
36
|
+
self.last_output = ""
|
|
37
|
+
self.backend = backend
|
|
38
|
+
self.sessionName = self.session_path.stem
|
|
39
|
+
self.memory = {
|
|
40
|
+
"backend": backend,
|
|
41
|
+
"timeline": [],
|
|
42
|
+
"attack_map_html": "",
|
|
43
|
+
"attack_map_json": "",
|
|
44
|
+
}
|
|
45
|
+
self.go_menu = go_menu
|
|
46
|
+
self.app_version = get_current_version()
|
|
47
|
+
if session_path.exists():
|
|
48
|
+
self.memory = json.loads(session_path.read_text())
|
|
49
|
+
self.backend = self.memory.get("backend", backend)
|
|
50
|
+
|
|
51
|
+
self.model = LLM(self.backend)
|
|
52
|
+
self._hydrate_model_context()
|
|
53
|
+
|
|
54
|
+
def _hydrate_model_context(self):
|
|
55
|
+
history = []
|
|
56
|
+
restored_last_output = ""
|
|
57
|
+
for item in self.memory.get("timeline", []):
|
|
58
|
+
item_type = item.get("type")
|
|
59
|
+
if item_type == "user":
|
|
60
|
+
content = str(item.get("content", "")).strip()
|
|
61
|
+
if content:
|
|
62
|
+
history.append({"role": "user", "content": content})
|
|
63
|
+
elif item_type == "analysis":
|
|
64
|
+
content = str(item.get("content", "")).strip()
|
|
65
|
+
if content:
|
|
66
|
+
history.append({"role": "assistant", "content": content})
|
|
67
|
+
elif item_type == "command":
|
|
68
|
+
output = item.get("output", "")
|
|
69
|
+
if isinstance(output, str):
|
|
70
|
+
restored_last_output = output
|
|
71
|
+
|
|
72
|
+
self.model.history = history
|
|
73
|
+
self.last_output = restored_last_output
|
|
74
|
+
|
|
75
|
+
def save(self):
|
|
76
|
+
self.session_path.write_text(json.dumps(self.memory, indent=2))
|
|
77
|
+
|
|
78
|
+
def change_model_menu(self):
|
|
79
|
+
"""
|
|
80
|
+
Model menu during session
|
|
81
|
+
"""
|
|
82
|
+
options = [
|
|
83
|
+
f"Use WhiteRabbit-Neo LLM locally {'::[SELECTED]' if self.backend=='ollama' else ''}",
|
|
84
|
+
f"Use OpenAI GPT-5 {'::[SELECTED]' if self.backend=='gpt' else ''}",
|
|
85
|
+
f"Use G4F.dev {'::[SELECTED]' if self.backend=='g4f' else ''}",
|
|
86
|
+
f"Use Anthropic Claude {'::[SELECTED]' if self.backend=='anthropic' else ''}",
|
|
87
|
+
f"Use Google Gemini {'::[SELECTED]' if self.backend=='gemini' else ''}",
|
|
88
|
+
|
|
89
|
+
f"Use Custom API endpoint [{API_ENDPOINT}] {'::[SELECTED]' if self.backend=='api' else ''}"
|
|
90
|
+
]
|
|
91
|
+
sel = menu("CHANGE BACKEND", options)
|
|
92
|
+
if sel == 0:
|
|
93
|
+
self.backend = "ollama"
|
|
94
|
+
self.memory["backend"] = self.backend
|
|
95
|
+
self.save()
|
|
96
|
+
elif sel == 1:
|
|
97
|
+
self.backend = "gpt"
|
|
98
|
+
checkOpenAIKey()
|
|
99
|
+
self.memory["backend"] = self.backend
|
|
100
|
+
self.save()
|
|
101
|
+
elif sel == 2:
|
|
102
|
+
self.backend = "g4f"
|
|
103
|
+
self.memory["backend"] = self.backend
|
|
104
|
+
self.save()
|
|
105
|
+
elif sel == 3:
|
|
106
|
+
self.backend = "anthropic"
|
|
107
|
+
checkAnthropicKey()
|
|
108
|
+
self.memory["backend"] = self.backend
|
|
109
|
+
self.save()
|
|
110
|
+
elif sel == 4:
|
|
111
|
+
self.backend = "gemini"
|
|
112
|
+
checkGeminiKey()
|
|
113
|
+
self.memory["backend"] = self.backend
|
|
114
|
+
self.save()
|
|
115
|
+
elif sel == 5:
|
|
116
|
+
self.backend = "api"
|
|
117
|
+
checkAPI()
|
|
118
|
+
self.memory["backend"] = self.backend
|
|
119
|
+
self.save()
|
|
120
|
+
self.model = LLM(self.backend)
|
|
121
|
+
self._hydrate_model_context()
|
|
122
|
+
|
|
123
|
+
def rename_session(self):
|
|
124
|
+
cyber("Type in the desired name for this session")
|
|
125
|
+
new_name = raw_input("⯁⮞ ").strip()
|
|
126
|
+
if not new_name:
|
|
127
|
+
cyber("[!] Session name cannot be empty.", color=Fore.RED)
|
|
128
|
+
return
|
|
129
|
+
if new_name == self.sessionName:
|
|
130
|
+
cyber("[!] New name is the same as current name.", color=Fore.YELLOW)
|
|
131
|
+
return
|
|
132
|
+
#
|
|
133
|
+
invalid_chars = '<>:"/\\|?*'
|
|
134
|
+
if any(char in new_name for char in invalid_chars):
|
|
135
|
+
cyber("[!] Invalid characters in name. Avoid < > : \" / \\ | ? *", color=Fore.RED)
|
|
136
|
+
return
|
|
137
|
+
new_path = SESSIONS_DIR / f"{new_name}.json"
|
|
138
|
+
if new_path.exists():
|
|
139
|
+
cyber("[!] A session with that name already exists.", color=Fore.YELLOW)
|
|
140
|
+
return
|
|
141
|
+
# Rename the session file
|
|
142
|
+
self.session_path.rename(new_path)
|
|
143
|
+
self.session_path = new_path
|
|
144
|
+
self.sessionName = new_name
|
|
145
|
+
self.save()
|
|
146
|
+
cyber(f"Session renamed to {new_name}", color=Fore.GREEN)
|
|
147
|
+
|
|
148
|
+
def run_command(self, cmd):
|
|
149
|
+
cyber(f"EXECUTING → {cmd}")
|
|
150
|
+
run_cmd = cmd
|
|
151
|
+
sudo_password = None
|
|
152
|
+
|
|
153
|
+
stripped = cmd.lstrip()
|
|
154
|
+
if stripped.startswith("sudo "):
|
|
155
|
+
if not re.search(r"(^|\s)-S(\s|$)", stripped):
|
|
156
|
+
run_cmd = re.sub(r"^\s*sudo(\s+)", r"sudo -S -p ''\1", cmd, count=1)
|
|
157
|
+
try:
|
|
158
|
+
sudo_password = getpass.getpass("Input sudo password: ")
|
|
159
|
+
except KeyboardInterrupt:
|
|
160
|
+
print(Fore.YELLOW + "\n// 🜂 Sudo prompt cancelled")
|
|
161
|
+
return
|
|
162
|
+
if not sudo_password:
|
|
163
|
+
print(Fore.YELLOW + "// 🜂 Empty password, command skipped")
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
proc = subprocess.Popen(
|
|
167
|
+
run_cmd, shell=True,
|
|
168
|
+
stdin=subprocess.PIPE,
|
|
169
|
+
stdout=subprocess.PIPE,
|
|
170
|
+
stderr=subprocess.STDOUT,
|
|
171
|
+
text=True,
|
|
172
|
+
preexec_fn=os.setsid
|
|
173
|
+
)
|
|
174
|
+
out = ""
|
|
175
|
+
try:
|
|
176
|
+
if sudo_password and proc.stdin:
|
|
177
|
+
proc.stdin.write(sudo_password + "\n")
|
|
178
|
+
proc.stdin.flush()
|
|
179
|
+
proc.stdin.close()
|
|
180
|
+
for line in proc.stdout:
|
|
181
|
+
print(line, end="")
|
|
182
|
+
out += line
|
|
183
|
+
except KeyboardInterrupt:
|
|
184
|
+
os.killpg(os.getpgid(proc.pid), signal.SIGINT)
|
|
185
|
+
print(Fore.RED + "\n/// 🜂 Command stopped by user.")
|
|
186
|
+
self.last_output = out
|
|
187
|
+
self.memory["timeline"].append({
|
|
188
|
+
"type": "command",
|
|
189
|
+
"cmd": cmd,
|
|
190
|
+
"output": out
|
|
191
|
+
})
|
|
192
|
+
self.save()
|
|
193
|
+
|
|
194
|
+
def generate_report(self):
|
|
195
|
+
if not self.memory.get("timeline"):
|
|
196
|
+
cyber("[!] No session data available to report.", color=Fore.YELLOW)
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
ts = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
|
200
|
+
base = f"{self.sessionName}_{ts}"
|
|
201
|
+
html_path = REPORTS_DIR / f"{base}.html"
|
|
202
|
+
pdf_path = REPORTS_DIR / f"{base}.pdf"
|
|
203
|
+
|
|
204
|
+
html_content = build_html_report(
|
|
205
|
+
self.sessionName,
|
|
206
|
+
self.backend,
|
|
207
|
+
self.memory["timeline"],
|
|
208
|
+
version=self.app_version,
|
|
209
|
+
)
|
|
210
|
+
html_path.write_text(html_content, encoding="utf-8")
|
|
211
|
+
|
|
212
|
+
pdf_ok = try_generate_pdf(html_path, pdf_path)
|
|
213
|
+
html_opened = open_report_file(html_path)
|
|
214
|
+
pdf_opened = open_report_file(pdf_path) if pdf_ok else False
|
|
215
|
+
|
|
216
|
+
if pdf_ok:
|
|
217
|
+
cyber(f"[ ✔ ] Report generated.", color=Fore.GREEN)
|
|
218
|
+
print(f"{Style.BRIGHT}{Fore.GREEN}{html_path}\n{pdf_path}\n")
|
|
219
|
+
if html_opened or pdf_opened:
|
|
220
|
+
cyber("[ ✔ ] Opened report in browser", color=Fore.GREEN)
|
|
221
|
+
print(f"{Fore.GREEN}{html_path}")
|
|
222
|
+
if pdf_opened:
|
|
223
|
+
print(f"{Fore.GREEN}{pdf_path}")
|
|
224
|
+
print()
|
|
225
|
+
else:
|
|
226
|
+
cyber("[!] Could not open browser automatically. Check report path in ~/.config/eva/reports", color=Fore.YELLOW)
|
|
227
|
+
|
|
228
|
+
else:
|
|
229
|
+
cyber(
|
|
230
|
+
f"[ ✔ ] HTML report generated.",
|
|
231
|
+
color=Fore.GREEN,
|
|
232
|
+
)
|
|
233
|
+
print(f"{Style.BRIGHT}{Fore.GREEN}{html_path}\n {Fore.RED} :: PDF was not created (wkhtmltopdf not found!) \n")
|
|
234
|
+
if html_opened:
|
|
235
|
+
cyber("[ ✔ ] Opened report in browser", color=Fore.GREEN)
|
|
236
|
+
print(f"{Fore.GREEN}{html_path}\n")
|
|
237
|
+
else:
|
|
238
|
+
cyber("[!] Could not open browser automatically. Check report path in ~/.config/eva/reports", color=Fore.YELLOW)
|
|
239
|
+
|
|
240
|
+
def generate_attack_map(self):
|
|
241
|
+
if not self.memory.get("timeline"):
|
|
242
|
+
cyber("[!] No session data available to map.", color=Fore.YELLOW)
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
html_path, json_path, graph = generate_attack_map_files(
|
|
246
|
+
self.sessionName,
|
|
247
|
+
self.memory["timeline"],
|
|
248
|
+
MAPS_DIR,
|
|
249
|
+
version=self.app_version,
|
|
250
|
+
)
|
|
251
|
+
self.memory["attack_map_html"] = str(html_path)
|
|
252
|
+
self.memory["attack_map_json"] = str(json_path)
|
|
253
|
+
self.save()
|
|
254
|
+
cyber(
|
|
255
|
+
f"[ ✔ ] Attack map generated.",
|
|
256
|
+
color=Fore.GREEN,
|
|
257
|
+
)
|
|
258
|
+
print(f"{Style.BRIGHT}{Fore.BLUE}{html_path} (nodes={graph['meta']['node_count']}, edges={graph['meta']['edge_count']}) \n")
|
|
259
|
+
return html_path
|
|
260
|
+
|
|
261
|
+
def view_attack_map(self):
|
|
262
|
+
map_path = self.memory.get("attack_map_html", "").strip()
|
|
263
|
+
if not map_path:
|
|
264
|
+
generated = self.generate_attack_map()
|
|
265
|
+
if not generated:
|
|
266
|
+
return
|
|
267
|
+
map_path = str(generated)
|
|
268
|
+
|
|
269
|
+
opened = open_attack_map(map_path)
|
|
270
|
+
if opened:
|
|
271
|
+
cyber(f"[ ✔ ] Opened session map in browser", color=Fore.GREEN)
|
|
272
|
+
print(f"{Fore.GREEN} {map_path} \n")
|
|
273
|
+
else:
|
|
274
|
+
cyber("[!] Could not open browser automatically. Check map path in ~/.config/eva/attack_maps", color=Fore.YELLOW)
|
|
275
|
+
|
|
276
|
+
def _render_session_header(self):
|
|
277
|
+
os.system("clear")
|
|
278
|
+
cyber(":: 🍎 EVA ONLINE :: ")
|
|
279
|
+
print(Fore.GREEN + "⯁⮞ ˹E˼xploit ˹V˼ector ˹A˼gent \n⬢ Current Model: " + Fore.CYAN + self.backend + f"\n{Fore.GREEN}𖨠 Session Name: " + Fore.YELLOW + self.sessionName)
|
|
280
|
+
print(Fore.CYAN + "/// type /exit to quit the program anytime")
|
|
281
|
+
print(Fore.CYAN + "/// type /model to change current model")
|
|
282
|
+
print(Fore.CYAN + "/// type /rename to change a session name")
|
|
283
|
+
print(Fore.CYAN + "/// type /viewmap to open attack map with last findings.")
|
|
284
|
+
print(Fore.CYAN + "/// type /report to generate a report in PDF and HTML.")
|
|
285
|
+
print(Fore.CYAN + "/// type /menu to go back to sessions menu\n\n")
|
|
286
|
+
|
|
287
|
+
def _render_timeline(self):
|
|
288
|
+
for item in self.memory["timeline"]:
|
|
289
|
+
if item["type"] == "user":
|
|
290
|
+
print(Fore.GREEN + f"{username.upper()} > {item['content']}\n")
|
|
291
|
+
|
|
292
|
+
elif item["type"] == "analysis":
|
|
293
|
+
cyber("ANALYSIS", color=Style.BRIGHT + Fore.CYAN)
|
|
294
|
+
print(Style.BRIGHT + Fore.CYAN + item["content"] + Style.RESET_ALL + "\n")
|
|
295
|
+
|
|
296
|
+
elif item["type"] == "command":
|
|
297
|
+
cyber(f"EXECUTED → {item['cmd']}", color=Fore.CYAN)
|
|
298
|
+
print(item["output"] + "\n")
|
|
299
|
+
|
|
300
|
+
def chat(self):
|
|
301
|
+
self._render_session_header()
|
|
302
|
+
self._render_timeline()
|
|
303
|
+
|
|
304
|
+
while True:
|
|
305
|
+
try:
|
|
306
|
+
user = raw_input(Fore.GREEN + f"\n{username.upper()} > ")
|
|
307
|
+
except KeyboardInterrupt:
|
|
308
|
+
print()
|
|
309
|
+
continue
|
|
310
|
+
if user.lower() in ("exit", "quit", "/exit","/quit","q"):
|
|
311
|
+
self.save()
|
|
312
|
+
graceful_exit()
|
|
313
|
+
if user.lower() in ("menu", "/menu"):
|
|
314
|
+
return self.go_menu()
|
|
315
|
+
if user.lower() in ("rename", "/rename"):
|
|
316
|
+
self.rename_session()
|
|
317
|
+
self._render_session_header()
|
|
318
|
+
self._render_timeline()
|
|
319
|
+
continue
|
|
320
|
+
|
|
321
|
+
if user.lower() in ("help", "/help"):
|
|
322
|
+
print(Fore.CYAN + "/// type /exit to quit the program anytime")
|
|
323
|
+
print(Fore.CYAN + "/// type /model to change current model")
|
|
324
|
+
print(Fore.CYAN + "/// type /rename to change a session name")
|
|
325
|
+
print(Fore.CYAN + "/// type /viewmap to open attack map with last findings.")
|
|
326
|
+
print(Fore.CYAN + "/// type /report to generate a report in PDF and HTML.")
|
|
327
|
+
print(Fore.CYAN + "/// type /menu to go back to sessions menu\n\n")
|
|
328
|
+
print(Style.BRIGHT + Fore.YELLOW + "-> Ask any question to EVA to request assistance, if no commands are generated to run, ask EVA explicitly for command generation.")
|
|
329
|
+
continue
|
|
330
|
+
|
|
331
|
+
if user.lower() in ("model", "/model"):
|
|
332
|
+
self.change_model_menu()
|
|
333
|
+
self._render_session_header()
|
|
334
|
+
self._render_timeline()
|
|
335
|
+
continue
|
|
336
|
+
if user.lower() in ("viewmap", "/viewmap","map","/map"):
|
|
337
|
+
self.view_attack_map()
|
|
338
|
+
continue
|
|
339
|
+
if user.lower() in ("report", "/report"):
|
|
340
|
+
self.generate_report()
|
|
341
|
+
continue
|
|
342
|
+
|
|
343
|
+
spinner_start()
|
|
344
|
+
spinner_stopped = False
|
|
345
|
+
analysis_header_shown = False
|
|
346
|
+
on_stream_start = None
|
|
347
|
+
if self.backend == "ollama":
|
|
348
|
+
def _on_stream_start():
|
|
349
|
+
nonlocal spinner_stopped, analysis_header_shown
|
|
350
|
+
if not spinner_stopped:
|
|
351
|
+
spinner_stop()
|
|
352
|
+
spinner_stopped = True
|
|
353
|
+
if not analysis_header_shown:
|
|
354
|
+
cyber("ANALYSIS", color=Style.BRIGHT + Fore.CYAN)
|
|
355
|
+
analysis_header_shown = True
|
|
356
|
+
|
|
357
|
+
on_stream_start = _on_stream_start
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
resp = self.model.query(user, self.last_output, on_stream_start=on_stream_start)
|
|
361
|
+
except KeyboardInterrupt:
|
|
362
|
+
if not spinner_stopped:
|
|
363
|
+
spinner_stop()
|
|
364
|
+
print(Fore.YELLOW + "\n// 🜂 Request cancelled")
|
|
365
|
+
continue
|
|
366
|
+
|
|
367
|
+
if not spinner_stopped:
|
|
368
|
+
spinner_stop()
|
|
369
|
+
spinner_stopped = True
|
|
370
|
+
self.memory["timeline"].append({
|
|
371
|
+
"type": "user",
|
|
372
|
+
"content": user
|
|
373
|
+
})
|
|
374
|
+
self.memory["timeline"].append({
|
|
375
|
+
"type": "analysis",
|
|
376
|
+
"content": resp["analysis"]
|
|
377
|
+
})
|
|
378
|
+
self.save()
|
|
379
|
+
if not analysis_header_shown:
|
|
380
|
+
cyber("ANALYSIS", color=Style.BRIGHT + Fore.CYAN)
|
|
381
|
+
analysis_header_shown = True
|
|
382
|
+
if not resp.get("__streamed"):
|
|
383
|
+
print(Style.BRIGHT + Fore.CYAN + resp["analysis"] + Style.RESET_ALL)
|
|
384
|
+
break_outer = False
|
|
385
|
+
for cmd in resp["commands"]:
|
|
386
|
+
|
|
387
|
+
while True:
|
|
388
|
+
try:
|
|
389
|
+
print("\n\n")
|
|
390
|
+
print(f" {Style.BRIGHT}{Back.RED}{Fore.CYAN}$ {cmd}{Style.RESET_ALL} \n")
|
|
391
|
+
print(f"{Style.BRIGHT}{Fore.CYAN} [| [R]un | [S]kip | [A]sk | [G]enerate HTML Report | [V]iew attack map | [Q]uit |]\n")
|
|
392
|
+
choice = raw_input("> ").strip().lower()
|
|
393
|
+
except KeyboardInterrupt:
|
|
394
|
+
print()
|
|
395
|
+
break_outer = True
|
|
396
|
+
break
|
|
397
|
+
|
|
398
|
+
if choice == "r":
|
|
399
|
+
self.run_command(cmd)
|
|
400
|
+
spinner_start()
|
|
401
|
+
spinner_stopped = False
|
|
402
|
+
analysis_header_shown = False
|
|
403
|
+
on_stream_start = None
|
|
404
|
+
if self.backend == "ollama":
|
|
405
|
+
def _on_stream_start():
|
|
406
|
+
nonlocal spinner_stopped, analysis_header_shown
|
|
407
|
+
if not spinner_stopped:
|
|
408
|
+
spinner_stop()
|
|
409
|
+
spinner_stopped = True
|
|
410
|
+
if not analysis_header_shown:
|
|
411
|
+
cyber("ANALYSIS", color=Style.BRIGHT + Fore.CYAN)
|
|
412
|
+
analysis_header_shown = True
|
|
413
|
+
|
|
414
|
+
on_stream_start = _on_stream_start
|
|
415
|
+
|
|
416
|
+
try:
|
|
417
|
+
resp = self.model.query(
|
|
418
|
+
"Analyze the previous command output and continue.",
|
|
419
|
+
self.last_output,
|
|
420
|
+
on_stream_start=on_stream_start
|
|
421
|
+
)
|
|
422
|
+
except KeyboardInterrupt:
|
|
423
|
+
if not spinner_stopped:
|
|
424
|
+
spinner_stop()
|
|
425
|
+
print(Fore.YELLOW + "\n// 🜂 Request cancelled")
|
|
426
|
+
break
|
|
427
|
+
|
|
428
|
+
if not spinner_stopped:
|
|
429
|
+
spinner_stop()
|
|
430
|
+
spinner_stopped = True
|
|
431
|
+
if not analysis_header_shown:
|
|
432
|
+
cyber("ANALYSIS", color=Style.BRIGHT + Fore.CYAN)
|
|
433
|
+
analysis_header_shown = True
|
|
434
|
+
if not resp.get("__streamed"):
|
|
435
|
+
print(Style.BRIGHT + Fore.CYAN + resp["analysis"] + Style.RESET_ALL)
|
|
436
|
+
break
|
|
437
|
+
|
|
438
|
+
elif choice == "a":
|
|
439
|
+
break_outer = True
|
|
440
|
+
break
|
|
441
|
+
|
|
442
|
+
elif choice == "s":
|
|
443
|
+
break
|
|
444
|
+
|
|
445
|
+
elif choice == "q":
|
|
446
|
+
self.save()
|
|
447
|
+
graceful_exit()
|
|
448
|
+
elif choice == "g":
|
|
449
|
+
self.generate_report()
|
|
450
|
+
elif choice == "v":
|
|
451
|
+
self.view_attack_map()
|
|
452
|
+
|
|
453
|
+
else:
|
|
454
|
+
print("// 🜂 Not a valid input, please type R, S, A, G, V or Q.")
|
|
455
|
+
if break_outer:
|
|
456
|
+
break
|
|
457
|
+
self.save()
|
utils/__init__.py
ADDED
|
File without changes
|