eva-exploit 3.3.7__tar.gz → 3.4__tar.gz
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.
- {eva_exploit-3.3.7 → eva_exploit-3.4}/PKG-INFO +2 -1
- {eva_exploit-3.3.7 → eva_exploit-3.4}/README.md +1 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/config.py +1 -1
- {eva_exploit-3.3.7 → eva_exploit-3.4}/eva_exploit.egg-info/PKG-INFO +2 -1
- {eva_exploit-3.3.7 → eva_exploit-3.4}/modules/llm.py +147 -10
- {eva_exploit-3.3.7 → eva_exploit-3.4}/modules/prompt_builder.py +1 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/pyproject.toml +1 -1
- {eva_exploit-3.3.7 → eva_exploit-3.4}/sessions/eva_session.py +59 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/utils/system.py +43 -22
- {eva_exploit-3.3.7 → eva_exploit-3.4}/eva.py +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/eva_exploit.egg-info/SOURCES.txt +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/eva_exploit.egg-info/dependency_links.txt +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/eva_exploit.egg-info/entry_points.txt +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/eva_exploit.egg-info/requires.txt +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/eva_exploit.egg-info/top_level.txt +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/modules/__init__.py +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/modules/attack_map.py +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/modules/exploit_search.py +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/modules/reporting.py +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/modules/tooling.py +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/modules/vuln_intel.py +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/modules/workflow.py +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/sessions/__init__.py +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/setup.cfg +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/utils/__init__.py +0 -0
- {eva_exploit-3.3.7 → eva_exploit-3.4}/utils/ui.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eva-exploit
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4
|
|
4
4
|
Summary: Exploit Vector Agent
|
|
5
5
|
Author: ARCANGEL0
|
|
6
6
|
License: MIT
|
|
@@ -275,6 +275,7 @@ eva
|
|
|
275
275
|
| `/exit` / `/quit` | Exit EVA and save session |
|
|
276
276
|
| `/model` | Change AI backend |
|
|
277
277
|
| `/rename` | Rename the current session |
|
|
278
|
+
| `/search <query>` or `search <query>` | Run exploit/vulnerability intel search inside current chat session and feed results into next analysis |
|
|
278
279
|
| `/report` | Generates a PDF/HTML report with latest findings on session |
|
|
279
280
|
| `/map` | Generates a html file with attack surface map of session |
|
|
280
281
|
| `/menu` | Return to session menu |
|
|
@@ -263,6 +263,7 @@ eva
|
|
|
263
263
|
| `/exit` / `/quit` | Exit EVA and save session |
|
|
264
264
|
| `/model` | Change AI backend |
|
|
265
265
|
| `/rename` | Rename the current session |
|
|
266
|
+
| `/search <query>` or `search <query>` | Run exploit/vulnerability intel search inside current chat session and feed results into next analysis |
|
|
266
267
|
| `/report` | Generates a PDF/HTML report with latest findings on session |
|
|
267
268
|
| `/map` | Generates a html file with attack surface map of session |
|
|
268
269
|
| `/menu` | Return to session menu |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eva-exploit
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4
|
|
4
4
|
Summary: Exploit Vector Agent
|
|
5
5
|
Author: ARCANGEL0
|
|
6
6
|
License: MIT
|
|
@@ -275,6 +275,7 @@ eva
|
|
|
275
275
|
| `/exit` / `/quit` | Exit EVA and save session |
|
|
276
276
|
| `/model` | Change AI backend |
|
|
277
277
|
| `/rename` | Rename the current session |
|
|
278
|
+
| `/search <query>` or `search <query>` | Run exploit/vulnerability intel search inside current chat session and feed results into next analysis |
|
|
278
279
|
| `/report` | Generates a PDF/HTML report with latest findings on session |
|
|
279
280
|
| `/map` | Generates a html file with attack surface map of session |
|
|
280
281
|
| `/menu` | Return to session menu |
|
|
@@ -49,6 +49,16 @@ STREAM_HIDE_MARKERS = [
|
|
|
49
49
|
"explanation of flags:",
|
|
50
50
|
"[authorized ctf/lab task]",
|
|
51
51
|
]
|
|
52
|
+
COMMAND_START_TOKENS = [
|
|
53
|
+
"nmap", "masscan", "rustscan", "nikto", "whatweb", "curl", "wget", "ffuf",
|
|
54
|
+
"gobuster", "dirsearch", "feroxbuster", "sqlmap", "hydra", "nc", "netcat",
|
|
55
|
+
"ssh", "ftp", "smbclient", "enum4linux", "rpcclient", "crackmapexec", "nuclei",
|
|
56
|
+
"msfconsole", "searchsploit", "command", "test", "find", "ls", "cat", "grep",
|
|
57
|
+
"awk", "sed", "cut", "sort", "uniq", "tr", "head", "tail", "id", "whoami",
|
|
58
|
+
"uname", "ip", "ifconfig", "ss", "netstat", "nslookup", "dig", "host", "ping",
|
|
59
|
+
"traceroute", "openssl", "sslscan", "ldapsearch", "snmpwalk", "python", "python3",
|
|
60
|
+
"bash", "sh",
|
|
61
|
+
]
|
|
52
62
|
## regex for the lame llm model excuses, to guarantee adherence to pentesting scope
|
|
53
63
|
REFUSAL_PATTERNS = [
|
|
54
64
|
r"\bi can(?:not|'t)\s+(?:assist|help|fulfill)\b",
|
|
@@ -87,6 +97,59 @@ def _build_no_output_analysis(last_output):
|
|
|
87
97
|
)
|
|
88
98
|
|
|
89
99
|
|
|
100
|
+
def _dedupe_keep_order(items):
|
|
101
|
+
out = []
|
|
102
|
+
seen = set()
|
|
103
|
+
for item in items:
|
|
104
|
+
key = str(item).strip()
|
|
105
|
+
if not key or key in seen:
|
|
106
|
+
continue
|
|
107
|
+
seen.add(key)
|
|
108
|
+
out.append(key)
|
|
109
|
+
return out
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _coerce_commands(value):
|
|
113
|
+
collected = []
|
|
114
|
+
token_group = "|".join(sorted((re.escape(t) for t in COMMAND_START_TOKENS), key=len, reverse=True))
|
|
115
|
+
loose_line_re = re.compile(
|
|
116
|
+
rf"^(?:sudo\s+)?(?:{token_group})\b",
|
|
117
|
+
flags=re.IGNORECASE,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _visit(node):
|
|
121
|
+
if node is None:
|
|
122
|
+
return
|
|
123
|
+
if isinstance(node, dict):
|
|
124
|
+
for key in ("cmd", "command", "value", "text", "content"):
|
|
125
|
+
if key in node:
|
|
126
|
+
_visit(node.get(key))
|
|
127
|
+
return
|
|
128
|
+
if isinstance(node, (list, tuple, set)):
|
|
129
|
+
for item in node:
|
|
130
|
+
_visit(item)
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
text = str(node).strip()
|
|
134
|
+
if not text:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
extracted = extract_commands_anywhere(text)
|
|
138
|
+
if extracted:
|
|
139
|
+
collected.extend(extracted)
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
for raw_line in text.splitlines():
|
|
143
|
+
line = re.sub(r"^\s*(?:[-*]|\d+[.)])\s*", "", raw_line).strip("` ").strip()
|
|
144
|
+
if not line:
|
|
145
|
+
continue
|
|
146
|
+
if loose_line_re.match(line):
|
|
147
|
+
collected.append(line)
|
|
148
|
+
|
|
149
|
+
_visit(value)
|
|
150
|
+
return _dedupe_keep_order(collected)
|
|
151
|
+
|
|
152
|
+
|
|
90
153
|
## extractjson
|
|
91
154
|
def _extract_code_fence_json(raw_str):
|
|
92
155
|
pattern = r"```(?:json)?\\s*(.*?)\\s*```"
|
|
@@ -167,15 +230,24 @@ def normalize_response(resp):
|
|
|
167
230
|
if not isinstance(resp, dict):
|
|
168
231
|
return {"analysis": "[::!] ⚠️ Error on LLM output.", "commands": []}
|
|
169
232
|
|
|
170
|
-
analysis = resp.get("analysis"
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
233
|
+
analysis = resp.get("analysis")
|
|
234
|
+
if analysis is None:
|
|
235
|
+
for key in ("response", "answer", "text", "content", "message"):
|
|
236
|
+
if key in resp and str(resp.get(key, "")).strip():
|
|
237
|
+
analysis = resp.get(key)
|
|
238
|
+
break
|
|
239
|
+
if analysis is None:
|
|
240
|
+
analysis = "[::!] ⚠️ Error with model response, please ask again."
|
|
241
|
+
|
|
242
|
+
commands = resp.get("commands")
|
|
243
|
+
if commands in (None, "", []):
|
|
244
|
+
for key in ("next_commands", "cmds", "command", "suggested_commands"):
|
|
245
|
+
if key in resp:
|
|
246
|
+
commands = resp.get(key)
|
|
247
|
+
break
|
|
248
|
+
commands = _coerce_commands(commands)
|
|
249
|
+
if not commands:
|
|
250
|
+
commands = extract_commands_anywhere(str(analysis))
|
|
179
251
|
|
|
180
252
|
return {
|
|
181
253
|
"analysis": str(analysis),
|
|
@@ -200,8 +272,9 @@ def extract_commands_anywhere(raw_str):
|
|
|
200
272
|
seen.add(candidate)
|
|
201
273
|
commands.append(candidate)
|
|
202
274
|
|
|
275
|
+
token_group = "|".join(sorted((re.escape(t) for t in COMMAND_START_TOKENS), key=len, reverse=True))
|
|
203
276
|
command_line_re = re.compile(
|
|
204
|
-
|
|
277
|
+
rf"^\s*(?:[-*]\s*)?(?:\d+[.)]\s*)?(?:`)?(?:\$+\s*)?((?:sudo\s+)?(?:{token_group})\b[^\n`]*)`?\s*$",
|
|
205
278
|
flags=re.IGNORECASE | re.MULTILINE,
|
|
206
279
|
)
|
|
207
280
|
for match in command_line_re.findall(raw_str):
|
|
@@ -210,6 +283,28 @@ def extract_commands_anywhere(raw_str):
|
|
|
210
283
|
seen.add(candidate)
|
|
211
284
|
commands.append(candidate)
|
|
212
285
|
|
|
286
|
+
inline_re = re.compile(
|
|
287
|
+
rf"`((?:sudo\s+)?(?:{token_group})\b[^`]+)`",
|
|
288
|
+
flags=re.IGNORECASE,
|
|
289
|
+
)
|
|
290
|
+
for match in inline_re.findall(raw_str):
|
|
291
|
+
candidate = match.strip()
|
|
292
|
+
if candidate and candidate not in seen:
|
|
293
|
+
seen.add(candidate)
|
|
294
|
+
commands.append(candidate)
|
|
295
|
+
|
|
296
|
+
phrase_re = re.compile(
|
|
297
|
+
rf"\b(?:use|run|execute)\s+((?:sudo\s+)?(?:{token_group})\b[^\n`]+)",
|
|
298
|
+
flags=re.IGNORECASE,
|
|
299
|
+
)
|
|
300
|
+
for line in raw_str.splitlines():
|
|
301
|
+
text_line = line.strip()
|
|
302
|
+
for match in phrase_re.findall(text_line):
|
|
303
|
+
candidate = match.strip().rstrip(" .;,")
|
|
304
|
+
if candidate and candidate not in seen:
|
|
305
|
+
seen.add(candidate)
|
|
306
|
+
commands.append(candidate)
|
|
307
|
+
|
|
213
308
|
return commands
|
|
214
309
|
|
|
215
310
|
|
|
@@ -387,6 +482,41 @@ def _ollama_run_fallback(prompt):
|
|
|
387
482
|
return ""
|
|
388
483
|
|
|
389
484
|
|
|
485
|
+
def _recover_commands_for_ollama(user_msg, last_output, workflow_context, analysis):
|
|
486
|
+
recovery_system = (
|
|
487
|
+
"You are EVA command planner for authorized CTF/lab activity.\n"
|
|
488
|
+
"Return strict JSON only: {\"commands\": [\"cmd1\", \"cmd2\", \"cmd3\"]}\n"
|
|
489
|
+
"Rules:\n"
|
|
490
|
+
"- Return 1-3 executable shell commands.\n"
|
|
491
|
+
"- No placeholders and no markdown.\n"
|
|
492
|
+
"- Use only targets/ports visible in provided evidence.\n"
|
|
493
|
+
"- If evidence is insufficient, return prerequisite evidence-gathering commands."
|
|
494
|
+
)
|
|
495
|
+
recovery_user = (
|
|
496
|
+
f"USER_MSG: {user_msg}\n\n"
|
|
497
|
+
f"ANALYSIS_TEXT:\n{analysis}\n\n"
|
|
498
|
+
f"CONTEXT_DATA:\n{_context_for_system_prompt(last_output)}\n\n"
|
|
499
|
+
f"WORKFLOW_STATE:\n{workflow_context or 'none'}"
|
|
500
|
+
)
|
|
501
|
+
raw, _ = _ollama_chat(
|
|
502
|
+
[
|
|
503
|
+
{"role": "system", "content": recovery_system},
|
|
504
|
+
{"role": "user", "content": recovery_user},
|
|
505
|
+
]
|
|
506
|
+
)
|
|
507
|
+
if not raw:
|
|
508
|
+
return []
|
|
509
|
+
|
|
510
|
+
parsed = extract_json_anywhere(raw) or {}
|
|
511
|
+
recovered = _coerce_commands(parsed.get("commands"))
|
|
512
|
+
if recovered:
|
|
513
|
+
return recovered[:3]
|
|
514
|
+
recovered = extract_commands_anywhere(raw)
|
|
515
|
+
if recovered:
|
|
516
|
+
return recovered[:3]
|
|
517
|
+
return []
|
|
518
|
+
|
|
519
|
+
|
|
390
520
|
def _query_g4f(history):
|
|
391
521
|
raw = ""
|
|
392
522
|
headers = {"Content-Type": "application/json"}
|
|
@@ -688,6 +818,13 @@ class LLM:
|
|
|
688
818
|
data["commands"] = inferred_commands
|
|
689
819
|
|
|
690
820
|
data = normalize_response(data)
|
|
821
|
+
if self.backend == "ollama" and not data.get("commands"):
|
|
822
|
+
data["commands"] = _recover_commands_for_ollama(
|
|
823
|
+
user_msg,
|
|
824
|
+
last_output,
|
|
825
|
+
workflow_context,
|
|
826
|
+
str(data.get("analysis", "")),
|
|
827
|
+
)
|
|
691
828
|
data["analysis"] = _clean_analysis_text(data.get("analysis", ""))
|
|
692
829
|
data["__streamed"] = streamed
|
|
693
830
|
|
|
@@ -51,6 +51,7 @@ COMMAND RULES:
|
|
|
51
51
|
- Evidence lock: never claim a command result happened unless it appears in CONTEXT_DATA/WORKFLOW_STATE.
|
|
52
52
|
- If latest output indicates missing evidence/no output, say that explicitly and avoid speculative findings.
|
|
53
53
|
- For any command requiring local files (wordlists/config/scripts), emit a file-existence check command first.
|
|
54
|
+
- `commands` MUST NOT be empty. If evidence is weak, still output safe prerequisite commands to collect missing evidence.
|
|
54
55
|
|
|
55
56
|
CONTEXT_DATA:
|
|
56
57
|
{last_output if last_output else "SYSTEM_BOOT: AWAITING_TARGET_PARAMETER"}
|
|
@@ -6,16 +6,20 @@
|
|
|
6
6
|
# ---------------------------------------------------------------------
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
|
+
import io
|
|
9
10
|
import getpass
|
|
10
11
|
import os
|
|
12
|
+
import re
|
|
11
13
|
import signal
|
|
12
14
|
import subprocess
|
|
15
|
+
import contextlib
|
|
13
16
|
from datetime import datetime, timezone
|
|
14
17
|
|
|
15
18
|
from colorama import Fore,Back,Style
|
|
16
19
|
|
|
17
20
|
from config import API_ENDPOINT, MAPS_DIR, REPORTS_DIR, SESSIONS_DIR, username
|
|
18
21
|
from modules.attack_map import generate_attack_map_files, open_attack_map
|
|
22
|
+
from modules.exploit_search import run_exploit_search
|
|
19
23
|
from modules.llm import LLM
|
|
20
24
|
from modules.reporting import build_html_report, open_report_file, try_generate_pdf
|
|
21
25
|
from modules.tooling import (
|
|
@@ -40,6 +44,8 @@ from utils.system import (
|
|
|
40
44
|
)
|
|
41
45
|
from utils.ui import cyber, menu, raw_input, spinner_start, spinner_stop
|
|
42
46
|
|
|
47
|
+
ANSI_ESCAPE_RE = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]")
|
|
48
|
+
|
|
43
49
|
|
|
44
50
|
# +-------------------------------------------+
|
|
45
51
|
# | Main core -██ |
|
|
@@ -345,6 +351,7 @@ class Eva:
|
|
|
345
351
|
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)
|
|
346
352
|
print(Fore.CYAN + "/// type /exit to quit the program anytime")
|
|
347
353
|
print(Fore.CYAN + "/// type /model to change current model")
|
|
354
|
+
print(Fore.CYAN + "/// type /search <query> to run exploit/vuln intel search in-session")
|
|
348
355
|
print(Fore.CYAN + "/// type /rename to change a session name")
|
|
349
356
|
print(Fore.CYAN + "/// type /viewmap to open attack map with last findings.")
|
|
350
357
|
print(Fore.CYAN + "/// type /report to generate a report in PDF and HTML.")
|
|
@@ -387,6 +394,7 @@ class Eva:
|
|
|
387
394
|
if user.lower() in ("help", "/help"):
|
|
388
395
|
print(Fore.CYAN + "/// type /exit to quit the program anytime")
|
|
389
396
|
print(Fore.CYAN + "/// type /model to change current model")
|
|
397
|
+
print(Fore.CYAN + "/// type /search <query> to run exploit/vuln intel search in-session")
|
|
390
398
|
print(Fore.CYAN + "/// type /rename to change a session name")
|
|
391
399
|
print(Fore.CYAN + "/// type /viewmap to open attack map with last findings.")
|
|
392
400
|
print(Fore.CYAN + "/// type /report to generate a report in PDF and HTML.")
|
|
@@ -405,6 +413,57 @@ class Eva:
|
|
|
405
413
|
if user.lower() in ("report", "/report"):
|
|
406
414
|
self.generate_report()
|
|
407
415
|
continue
|
|
416
|
+
low_user = user.strip().lower()
|
|
417
|
+
if (
|
|
418
|
+
low_user == "search"
|
|
419
|
+
or low_user.startswith("search ")
|
|
420
|
+
or low_user == "/search"
|
|
421
|
+
or low_user.startswith("/search ")
|
|
422
|
+
):
|
|
423
|
+
query = user.strip().split(maxsplit=1)[1].strip() if len(user.strip().split(maxsplit=1)) > 1 else ""
|
|
424
|
+
if not query:
|
|
425
|
+
query = raw_input("EVA search query > ").strip()
|
|
426
|
+
if not query:
|
|
427
|
+
cyber("Search query cannot be empty.", color=Fore.YELLOW)
|
|
428
|
+
continue
|
|
429
|
+
|
|
430
|
+
normalized_user = f"/search {query}"
|
|
431
|
+
self.memory["timeline"].append({
|
|
432
|
+
"type": "user",
|
|
433
|
+
"content": normalized_user
|
|
434
|
+
})
|
|
435
|
+
self.model.history.append({"role": "user", "content": normalized_user})
|
|
436
|
+
|
|
437
|
+
buf = io.StringIO()
|
|
438
|
+
rc = 1
|
|
439
|
+
try:
|
|
440
|
+
with contextlib.redirect_stdout(buf):
|
|
441
|
+
rc = run_exploit_search(query)
|
|
442
|
+
except KeyboardInterrupt:
|
|
443
|
+
if self.memory["timeline"] and self.memory["timeline"][-1].get("type") == "user":
|
|
444
|
+
self.memory["timeline"].pop()
|
|
445
|
+
if self.model.history and self.model.history[-1].get("role") == "user":
|
|
446
|
+
self.model.history.pop()
|
|
447
|
+
print(Fore.YELLOW + "\n// 🜂 Search cancelled")
|
|
448
|
+
continue
|
|
449
|
+
except Exception as exc:
|
|
450
|
+
print(Fore.RED + f"[!] Search failed: {exc}")
|
|
451
|
+
raw_search_output = buf.getvalue()
|
|
452
|
+
if raw_search_output:
|
|
453
|
+
print(raw_search_output, end="" if raw_search_output.endswith("\n") else "\n")
|
|
454
|
+
|
|
455
|
+
clean_output = ANSI_ESCAPE_RE.sub("", raw_search_output or "").strip()
|
|
456
|
+
if not clean_output:
|
|
457
|
+
clean_output = f"[EVA_NOTICE] /search returned no output. exit_code={rc}"
|
|
458
|
+
|
|
459
|
+
self.last_output = clean_output
|
|
460
|
+
self.memory["timeline"].append({
|
|
461
|
+
"type": "analysis",
|
|
462
|
+
"content": clean_output
|
|
463
|
+
})
|
|
464
|
+
self.model.history.append({"role": "assistant", "content": clean_output})
|
|
465
|
+
self.save()
|
|
466
|
+
continue
|
|
408
467
|
|
|
409
468
|
spinner_start()
|
|
410
469
|
spinner_stopped = False
|
|
@@ -370,35 +370,56 @@ def checkupdts():
|
|
|
370
370
|
|
|
371
371
|
def run_self_update():
|
|
372
372
|
print(Fore.CYAN + f"\nChecking updates for {APP_NAME}...\n")
|
|
373
|
-
|
|
373
|
+
status = get_update_status(force=True)
|
|
374
|
+
if not status.get("available"):
|
|
375
|
+
cyber("EVA is already up to date.", color=Fore.CYAN)
|
|
376
|
+
return 0
|
|
374
377
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
)
|
|
379
|
-
if pip_result.returncode == 0:
|
|
380
|
-
updated = True
|
|
378
|
+
latest = status.get("latest") or "latest"
|
|
379
|
+
source = status.get("source")
|
|
380
|
+
print(Style.BRIGHT + Fore.MAGENTA + f"Update {latest} found! Installing . . . . " + Style.RESET_ALL)
|
|
381
381
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
382
|
+
updated = False
|
|
383
|
+
with open(os.devnull, "w") as devnull:
|
|
384
|
+
if source == "github" and Path(".git").exists() and command_exists("git"):
|
|
385
|
+
branch = _git_branch()
|
|
386
|
+
pull_result = subprocess.run(
|
|
387
|
+
["git", "pull", "--tags", "origin", branch],
|
|
388
|
+
stdout=devnull,
|
|
389
|
+
stderr=devnull,
|
|
390
|
+
text=True
|
|
391
|
+
)
|
|
392
|
+
updated = pull_result.returncode == 0
|
|
393
|
+
else:
|
|
394
|
+
pip_result = subprocess.run(
|
|
395
|
+
[
|
|
396
|
+
sys.executable,
|
|
397
|
+
"-m",
|
|
398
|
+
"pip",
|
|
399
|
+
"install",
|
|
400
|
+
"--upgrade",
|
|
401
|
+
PYPI_PACKAGE,
|
|
402
|
+
"--break-system-packages",
|
|
403
|
+
"-q",
|
|
404
|
+
],
|
|
405
|
+
stdout=devnull,
|
|
406
|
+
stderr=devnull,
|
|
407
|
+
text=True
|
|
408
|
+
)
|
|
409
|
+
updated = pip_result.returncode == 0
|
|
395
410
|
|
|
411
|
+
print(Fore.CYAN + "Almost done . . . . ")
|
|
396
412
|
if updated:
|
|
397
|
-
|
|
413
|
+
global _UPDATE_STATUS_CACHE
|
|
414
|
+
_UPDATE_STATUS_CACHE = None
|
|
415
|
+
cyber("✔ Update process finished. Restart EVA to use the latest version.", color=Fore.GREEN)
|
|
398
416
|
return 0
|
|
399
417
|
|
|
400
418
|
print(Fore.RED + "\n[!] Could not auto-update EVA in this environment.")
|
|
401
|
-
|
|
419
|
+
if source == "github":
|
|
420
|
+
print(Fore.YELLOW + f"Try manually: git pull --tags origin {_git_branch()}")
|
|
421
|
+
else:
|
|
422
|
+
print(Fore.YELLOW + f"Try manually: {sys.executable} -m pip install --upgrade {PYPI_PACKAGE}")
|
|
402
423
|
return 1
|
|
403
424
|
|
|
404
425
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|