souleyez 2.43.29__py3-none-any.whl → 3.0.0__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.
- souleyez/__init__.py +1 -2
- souleyez/ai/__init__.py +21 -15
- souleyez/ai/action_mapper.py +249 -150
- souleyez/ai/chain_advisor.py +116 -100
- souleyez/ai/claude_provider.py +29 -28
- souleyez/ai/context_builder.py +80 -62
- souleyez/ai/executor.py +158 -117
- souleyez/ai/feedback_handler.py +136 -121
- souleyez/ai/llm_factory.py +27 -20
- souleyez/ai/llm_provider.py +4 -2
- souleyez/ai/ollama_provider.py +6 -9
- souleyez/ai/ollama_service.py +44 -37
- souleyez/ai/path_scorer.py +91 -76
- souleyez/ai/recommender.py +176 -144
- souleyez/ai/report_context.py +74 -73
- souleyez/ai/report_service.py +84 -66
- souleyez/ai/result_parser.py +222 -229
- souleyez/ai/safety.py +67 -44
- souleyez/auth/__init__.py +23 -22
- souleyez/auth/audit.py +36 -26
- souleyez/auth/engagement_access.py +65 -48
- souleyez/auth/permissions.py +14 -3
- souleyez/auth/session_manager.py +54 -37
- souleyez/auth/user_manager.py +109 -64
- souleyez/commands/audit.py +40 -43
- souleyez/commands/auth.py +35 -15
- souleyez/commands/deliverables.py +55 -50
- souleyez/commands/engagement.py +47 -28
- souleyez/commands/license.py +32 -23
- souleyez/commands/screenshots.py +36 -32
- souleyez/commands/user.py +82 -36
- souleyez/config.py +52 -44
- souleyez/core/credential_tester.py +87 -81
- souleyez/core/cve_mappings.py +179 -192
- souleyez/core/cve_matcher.py +162 -148
- souleyez/core/msf_auto_mapper.py +100 -83
- souleyez/core/msf_chain_engine.py +294 -256
- souleyez/core/msf_database.py +153 -70
- souleyez/core/msf_integration.py +679 -673
- souleyez/core/msf_rpc_client.py +40 -42
- souleyez/core/msf_rpc_manager.py +77 -79
- souleyez/core/msf_sync_manager.py +241 -181
- souleyez/core/network_utils.py +22 -15
- souleyez/core/parser_handler.py +34 -25
- souleyez/core/pending_chains.py +114 -63
- souleyez/core/templates.py +158 -107
- souleyez/core/tool_chaining.py +9564 -2881
- souleyez/core/version_utils.py +79 -94
- souleyez/core/vuln_correlation.py +136 -89
- souleyez/core/web_utils.py +33 -32
- souleyez/data/wordlists/ad_users.txt +378 -0
- souleyez/data/wordlists/api_endpoints_large.txt +769 -0
- souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
- souleyez/data/wordlists/lfi_payloads.txt +82 -0
- souleyez/data/wordlists/passwords_brute.txt +1548 -0
- souleyez/data/wordlists/passwords_crack.txt +2479 -0
- souleyez/data/wordlists/passwords_spray.txt +386 -0
- souleyez/data/wordlists/subdomains_large.txt +5057 -0
- souleyez/data/wordlists/usernames_common.txt +694 -0
- souleyez/data/wordlists/web_dirs_large.txt +4769 -0
- souleyez/detection/__init__.py +1 -1
- souleyez/detection/attack_signatures.py +12 -17
- souleyez/detection/mitre_mappings.py +61 -55
- souleyez/detection/validator.py +97 -86
- souleyez/devtools.py +23 -10
- souleyez/docs/README.md +4 -4
- souleyez/docs/api-reference/cli-commands.md +2 -2
- souleyez/docs/developer-guide/adding-new-tools.md +562 -0
- souleyez/docs/user-guide/auto-chaining.md +30 -8
- souleyez/docs/user-guide/getting-started.md +1 -1
- souleyez/docs/user-guide/installation.md +26 -3
- souleyez/docs/user-guide/metasploit-integration.md +2 -2
- souleyez/docs/user-guide/rbac.md +1 -1
- souleyez/docs/user-guide/scope-management.md +1 -1
- souleyez/docs/user-guide/siem-integration.md +1 -1
- souleyez/docs/user-guide/tools-reference.md +1 -8
- souleyez/docs/user-guide/worker-management.md +1 -1
- souleyez/engine/background.py +1239 -535
- souleyez/engine/base.py +4 -1
- souleyez/engine/job_status.py +17 -49
- souleyez/engine/log_sanitizer.py +103 -77
- souleyez/engine/manager.py +38 -7
- souleyez/engine/result_handler.py +2200 -1550
- souleyez/engine/worker_manager.py +50 -41
- souleyez/export/evidence_bundle.py +72 -62
- souleyez/feature_flags/features.py +16 -20
- souleyez/feature_flags.py +5 -9
- souleyez/handlers/__init__.py +11 -0
- souleyez/handlers/base.py +188 -0
- souleyez/handlers/bash_handler.py +277 -0
- souleyez/handlers/bloodhound_handler.py +243 -0
- souleyez/handlers/certipy_handler.py +311 -0
- souleyez/handlers/crackmapexec_handler.py +486 -0
- souleyez/handlers/dnsrecon_handler.py +344 -0
- souleyez/handlers/enum4linux_handler.py +400 -0
- souleyez/handlers/evil_winrm_handler.py +493 -0
- souleyez/handlers/ffuf_handler.py +815 -0
- souleyez/handlers/gobuster_handler.py +1114 -0
- souleyez/handlers/gpp_extract_handler.py +334 -0
- souleyez/handlers/hashcat_handler.py +444 -0
- souleyez/handlers/hydra_handler.py +564 -0
- souleyez/handlers/impacket_getuserspns_handler.py +343 -0
- souleyez/handlers/impacket_psexec_handler.py +222 -0
- souleyez/handlers/impacket_secretsdump_handler.py +426 -0
- souleyez/handlers/john_handler.py +286 -0
- souleyez/handlers/katana_handler.py +425 -0
- souleyez/handlers/kerbrute_handler.py +298 -0
- souleyez/handlers/ldapsearch_handler.py +636 -0
- souleyez/handlers/lfi_extract_handler.py +464 -0
- souleyez/handlers/msf_auxiliary_handler.py +409 -0
- souleyez/handlers/msf_exploit_handler.py +380 -0
- souleyez/handlers/nikto_handler.py +413 -0
- souleyez/handlers/nmap_handler.py +821 -0
- souleyez/handlers/nuclei_handler.py +359 -0
- souleyez/handlers/nxc_handler.py +417 -0
- souleyez/handlers/rdp_sec_check_handler.py +353 -0
- souleyez/handlers/registry.py +292 -0
- souleyez/handlers/responder_handler.py +232 -0
- souleyez/handlers/service_explorer_handler.py +434 -0
- souleyez/handlers/smbclient_handler.py +344 -0
- souleyez/handlers/smbmap_handler.py +510 -0
- souleyez/handlers/smbpasswd_handler.py +296 -0
- souleyez/handlers/sqlmap_handler.py +1116 -0
- souleyez/handlers/theharvester_handler.py +601 -0
- souleyez/handlers/web_login_test_handler.py +327 -0
- souleyez/handlers/whois_handler.py +277 -0
- souleyez/handlers/wpscan_handler.py +554 -0
- souleyez/history.py +32 -16
- souleyez/importers/msf_importer.py +106 -75
- souleyez/importers/smart_importer.py +208 -147
- souleyez/integrations/siem/__init__.py +10 -10
- souleyez/integrations/siem/base.py +17 -18
- souleyez/integrations/siem/elastic.py +108 -122
- souleyez/integrations/siem/factory.py +207 -80
- souleyez/integrations/siem/googlesecops.py +146 -154
- souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
- souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
- souleyez/integrations/siem/sentinel.py +107 -109
- souleyez/integrations/siem/splunk.py +246 -212
- souleyez/integrations/siem/wazuh.py +65 -71
- souleyez/integrations/wazuh/__init__.py +5 -5
- souleyez/integrations/wazuh/client.py +70 -93
- souleyez/integrations/wazuh/config.py +85 -57
- souleyez/integrations/wazuh/host_mapper.py +28 -36
- souleyez/integrations/wazuh/sync.py +78 -68
- souleyez/intelligence/__init__.py +4 -5
- souleyez/intelligence/correlation_analyzer.py +309 -295
- souleyez/intelligence/exploit_knowledge.py +661 -623
- souleyez/intelligence/exploit_suggestions.py +159 -139
- souleyez/intelligence/gap_analyzer.py +132 -97
- souleyez/intelligence/gap_detector.py +251 -214
- souleyez/intelligence/sensitive_tables.py +266 -129
- souleyez/intelligence/service_parser.py +137 -123
- souleyez/intelligence/surface_analyzer.py +407 -268
- souleyez/intelligence/target_parser.py +159 -162
- souleyez/licensing/__init__.py +6 -6
- souleyez/licensing/validator.py +17 -19
- souleyez/log_config.py +79 -54
- souleyez/main.py +1505 -687
- souleyez/migrations/fix_job_counter.py +16 -14
- souleyez/parsers/bloodhound_parser.py +41 -39
- souleyez/parsers/crackmapexec_parser.py +178 -111
- souleyez/parsers/dalfox_parser.py +72 -77
- souleyez/parsers/dnsrecon_parser.py +103 -91
- souleyez/parsers/enum4linux_parser.py +183 -153
- souleyez/parsers/ffuf_parser.py +29 -25
- souleyez/parsers/gobuster_parser.py +301 -41
- souleyez/parsers/hashcat_parser.py +324 -79
- souleyez/parsers/http_fingerprint_parser.py +350 -103
- souleyez/parsers/hydra_parser.py +131 -111
- souleyez/parsers/impacket_parser.py +231 -178
- souleyez/parsers/john_parser.py +98 -86
- souleyez/parsers/katana_parser.py +316 -0
- souleyez/parsers/msf_parser.py +943 -498
- souleyez/parsers/nikto_parser.py +346 -65
- souleyez/parsers/nmap_parser.py +262 -174
- souleyez/parsers/nuclei_parser.py +40 -44
- souleyez/parsers/responder_parser.py +26 -26
- souleyez/parsers/searchsploit_parser.py +74 -74
- souleyez/parsers/service_explorer_parser.py +279 -0
- souleyez/parsers/smbmap_parser.py +180 -124
- souleyez/parsers/sqlmap_parser.py +434 -308
- souleyez/parsers/theharvester_parser.py +75 -57
- souleyez/parsers/whois_parser.py +135 -94
- souleyez/parsers/wpscan_parser.py +278 -190
- souleyez/plugins/afp.py +44 -36
- souleyez/plugins/afp_brute.py +114 -46
- souleyez/plugins/ard.py +48 -37
- souleyez/plugins/bloodhound.py +95 -61
- souleyez/plugins/certipy.py +303 -0
- souleyez/plugins/crackmapexec.py +186 -85
- souleyez/plugins/dalfox.py +120 -59
- souleyez/plugins/dns_hijack.py +146 -41
- souleyez/plugins/dnsrecon.py +97 -61
- souleyez/plugins/enum4linux.py +91 -66
- souleyez/plugins/evil_winrm.py +291 -0
- souleyez/plugins/ffuf.py +166 -90
- souleyez/plugins/firmware_extract.py +133 -29
- souleyez/plugins/gobuster.py +387 -190
- souleyez/plugins/gpp_extract.py +393 -0
- souleyez/plugins/hashcat.py +100 -73
- souleyez/plugins/http_fingerprint.py +913 -267
- souleyez/plugins/hydra.py +566 -200
- souleyez/plugins/impacket_getnpusers.py +117 -69
- souleyez/plugins/impacket_psexec.py +84 -64
- souleyez/plugins/impacket_secretsdump.py +103 -69
- souleyez/plugins/impacket_smbclient.py +89 -75
- souleyez/plugins/john.py +86 -69
- souleyez/plugins/katana.py +313 -0
- souleyez/plugins/kerbrute.py +237 -0
- souleyez/plugins/lfi_extract.py +541 -0
- souleyez/plugins/macos_ssh.py +117 -48
- souleyez/plugins/mdns.py +35 -30
- souleyez/plugins/msf_auxiliary.py +253 -130
- souleyez/plugins/msf_exploit.py +239 -161
- souleyez/plugins/nikto.py +134 -78
- souleyez/plugins/nmap.py +275 -91
- souleyez/plugins/nuclei.py +180 -89
- souleyez/plugins/nxc.py +285 -0
- souleyez/plugins/plugin_base.py +35 -36
- souleyez/plugins/plugin_template.py +13 -5
- souleyez/plugins/rdp_sec_check.py +130 -0
- souleyez/plugins/responder.py +112 -71
- souleyez/plugins/router_http_brute.py +76 -65
- souleyez/plugins/router_ssh_brute.py +118 -41
- souleyez/plugins/router_telnet_brute.py +124 -42
- souleyez/plugins/routersploit.py +91 -59
- souleyez/plugins/routersploit_exploit.py +77 -55
- souleyez/plugins/searchsploit.py +91 -77
- souleyez/plugins/service_explorer.py +1160 -0
- souleyez/plugins/smbmap.py +122 -72
- souleyez/plugins/smbpasswd.py +215 -0
- souleyez/plugins/sqlmap.py +301 -113
- souleyez/plugins/theharvester.py +127 -75
- souleyez/plugins/tr069.py +79 -57
- souleyez/plugins/upnp.py +65 -47
- souleyez/plugins/upnp_abuse.py +73 -55
- souleyez/plugins/vnc_access.py +129 -42
- souleyez/plugins/vnc_brute.py +109 -38
- souleyez/plugins/web_login_test.py +417 -0
- souleyez/plugins/whois.py +77 -58
- souleyez/plugins/wpscan.py +219 -69
- souleyez/reporting/__init__.py +2 -1
- souleyez/reporting/attack_chain.py +411 -346
- souleyez/reporting/charts.py +436 -501
- souleyez/reporting/compliance_mappings.py +334 -201
- souleyez/reporting/detection_report.py +126 -125
- souleyez/reporting/formatters.py +828 -591
- souleyez/reporting/generator.py +386 -302
- souleyez/reporting/metrics.py +72 -75
- souleyez/scanner.py +35 -29
- souleyez/security/__init__.py +37 -11
- souleyez/security/scope_validator.py +175 -106
- souleyez/security/validation.py +237 -149
- souleyez/security.py +22 -6
- souleyez/storage/credentials.py +247 -186
- souleyez/storage/crypto.py +296 -129
- souleyez/storage/database.py +73 -50
- souleyez/storage/db.py +58 -36
- souleyez/storage/deliverable_evidence.py +177 -128
- souleyez/storage/deliverable_exporter.py +282 -246
- souleyez/storage/deliverable_templates.py +134 -116
- souleyez/storage/deliverables.py +135 -130
- souleyez/storage/engagements.py +109 -56
- souleyez/storage/evidence.py +181 -152
- souleyez/storage/execution_log.py +31 -17
- souleyez/storage/exploit_attempts.py +93 -57
- souleyez/storage/exploits.py +67 -36
- souleyez/storage/findings.py +48 -61
- souleyez/storage/hosts.py +176 -144
- souleyez/storage/migrate_to_engagements.py +43 -19
- souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
- souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
- souleyez/storage/migrations/_003_add_execution_log.py +14 -8
- souleyez/storage/migrations/_005_screenshots.py +13 -5
- souleyez/storage/migrations/_006_deliverables.py +13 -5
- souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
- souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
- souleyez/storage/migrations/_010_evidence_linking.py +17 -10
- souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
- souleyez/storage/migrations/_012_team_collaboration.py +34 -21
- souleyez/storage/migrations/_013_add_host_tags.py +12 -6
- souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
- souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
- souleyez/storage/migrations/_016_add_domain_field.py +10 -4
- souleyez/storage/migrations/_017_msf_sessions.py +16 -8
- souleyez/storage/migrations/_018_add_osint_target.py +10 -6
- souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
- souleyez/storage/migrations/_020_add_rbac.py +36 -15
- souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
- souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
- souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
- souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
- souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
- souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
- souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
- souleyez/storage/migrations/__init__.py +26 -26
- souleyez/storage/migrations/migration_manager.py +19 -19
- souleyez/storage/msf_sessions.py +100 -65
- souleyez/storage/osint.py +17 -24
- souleyez/storage/recommendation_engine.py +269 -235
- souleyez/storage/screenshots.py +33 -32
- souleyez/storage/smb_shares.py +136 -92
- souleyez/storage/sqlmap_data.py +183 -128
- souleyez/storage/team_collaboration.py +135 -141
- souleyez/storage/timeline_tracker.py +122 -94
- souleyez/storage/wazuh_vulns.py +64 -66
- souleyez/storage/web_paths.py +33 -37
- souleyez/testing/credential_tester.py +221 -205
- souleyez/ui/__init__.py +1 -1
- souleyez/ui/ai_quotes.py +12 -12
- souleyez/ui/attack_surface.py +2439 -1516
- souleyez/ui/chain_rules_view.py +914 -382
- souleyez/ui/correlation_view.py +312 -230
- souleyez/ui/dashboard.py +2382 -1130
- souleyez/ui/deliverables_view.py +148 -62
- souleyez/ui/design_system.py +13 -13
- souleyez/ui/errors.py +49 -49
- souleyez/ui/evidence_linking_view.py +284 -179
- souleyez/ui/evidence_vault.py +393 -285
- souleyez/ui/exploit_suggestions_view.py +555 -349
- souleyez/ui/export_view.py +100 -66
- souleyez/ui/gap_analysis_view.py +315 -171
- souleyez/ui/help_system.py +105 -97
- souleyez/ui/intelligence_view.py +436 -293
- souleyez/ui/interactive.py +23034 -10679
- souleyez/ui/interactive_selector.py +75 -68
- souleyez/ui/log_formatter.py +47 -39
- souleyez/ui/menu_components.py +22 -13
- souleyez/ui/msf_auxiliary_menu.py +184 -133
- souleyez/ui/pending_chains_view.py +336 -172
- souleyez/ui/progress_indicators.py +5 -3
- souleyez/ui/recommendations_view.py +195 -137
- souleyez/ui/rule_builder.py +343 -225
- souleyez/ui/setup_wizard.py +678 -284
- souleyez/ui/shortcuts.py +217 -165
- souleyez/ui/splunk_gap_analysis_view.py +452 -270
- souleyez/ui/splunk_vulns_view.py +139 -86
- souleyez/ui/team_dashboard.py +498 -335
- souleyez/ui/template_selector.py +196 -105
- souleyez/ui/terminal.py +6 -6
- souleyez/ui/timeline_view.py +198 -127
- souleyez/ui/tool_setup.py +264 -164
- souleyez/ui/tutorial.py +202 -72
- souleyez/ui/tutorial_state.py +40 -40
- souleyez/ui/wazuh_vulns_view.py +235 -141
- souleyez/ui/wordlist_browser.py +260 -107
- souleyez/ui.py +464 -312
- souleyez/utils/tool_checker.py +427 -367
- souleyez/utils.py +33 -29
- souleyez/wordlists.py +134 -167
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/METADATA +2 -2
- souleyez-3.0.0.dist-info/RECORD +443 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/WHEEL +1 -1
- souleyez-2.43.29.dist-info/RECORD +0 -379
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/top_level.txt +0 -0
souleyez/ui/setup_wizard.py
CHANGED
|
@@ -14,7 +14,7 @@ from souleyez.ui.design_system import DesignSystem
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
# Wizard state file
|
|
17
|
-
WIZARD_STATE_FILE = Path.home() /
|
|
17
|
+
WIZARD_STATE_FILE = Path.home() / ".souleyez" / ".wizard_completed"
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def is_wizard_completed() -> bool:
|
|
@@ -31,7 +31,7 @@ def mark_wizard_completed():
|
|
|
31
31
|
def _check_disk_space(min_mb: int = 500) -> bool:
|
|
32
32
|
"""Check if there's enough disk space for installs."""
|
|
33
33
|
try:
|
|
34
|
-
usage = shutil.disk_usage(
|
|
34
|
+
usage = shutil.disk_usage("/")
|
|
35
35
|
free_mb = usage.free // (1024 * 1024)
|
|
36
36
|
return free_mb >= min_mb
|
|
37
37
|
except Exception:
|
|
@@ -60,32 +60,36 @@ def _configure_sudoers(tool_name: str, tool_path: str) -> bool:
|
|
|
60
60
|
|
|
61
61
|
try:
|
|
62
62
|
# Write to temp file first
|
|
63
|
-
with open(tmp_file,
|
|
64
|
-
f.write(sudoers_line +
|
|
63
|
+
with open(tmp_file, "w") as f:
|
|
64
|
+
f.write(sudoers_line + "\n")
|
|
65
65
|
|
|
66
66
|
# Validate syntax with visudo before applying
|
|
67
67
|
result = subprocess.run(
|
|
68
|
-
[
|
|
69
|
-
capture_output=True,
|
|
70
|
-
timeout=30
|
|
68
|
+
["sudo", "visudo", "-c", "-f", tmp_file], capture_output=True, timeout=30
|
|
71
69
|
)
|
|
72
70
|
|
|
73
71
|
if result.returncode != 0:
|
|
74
|
-
click.echo(
|
|
72
|
+
click.echo(
|
|
73
|
+
f" {click.style('!', fg='yellow')} Invalid sudoers syntax for {tool_name}, skipping"
|
|
74
|
+
)
|
|
75
75
|
os.unlink(tmp_file)
|
|
76
76
|
return False
|
|
77
77
|
|
|
78
78
|
# Safe to move to sudoers.d
|
|
79
|
-
subprocess.run([
|
|
80
|
-
subprocess.run([
|
|
79
|
+
subprocess.run(["sudo", "mv", tmp_file, sudoers_file], check=True, timeout=30)
|
|
80
|
+
subprocess.run(["sudo", "chmod", "0440", sudoers_file], check=True, timeout=30)
|
|
81
81
|
|
|
82
82
|
return True
|
|
83
83
|
|
|
84
84
|
except subprocess.TimeoutExpired:
|
|
85
|
-
click.echo(
|
|
85
|
+
click.echo(
|
|
86
|
+
f" {click.style('!', fg='yellow')} Timeout configuring sudoers for {tool_name}"
|
|
87
|
+
)
|
|
86
88
|
return False
|
|
87
89
|
except Exception as e:
|
|
88
|
-
click.echo(
|
|
90
|
+
click.echo(
|
|
91
|
+
f" {click.style('!', fg='yellow')} Error configuring sudoers for {tool_name}: {e}"
|
|
92
|
+
)
|
|
89
93
|
# Clean up temp file if it exists
|
|
90
94
|
if os.path.exists(tmp_file):
|
|
91
95
|
try:
|
|
@@ -103,10 +107,10 @@ def _install_desktop_shortcut():
|
|
|
103
107
|
disrupt the setup flow.
|
|
104
108
|
"""
|
|
105
109
|
try:
|
|
106
|
-
applications_dir = Path.home() /
|
|
107
|
-
icons_dir = Path.home() /
|
|
108
|
-
desktop_file = applications_dir /
|
|
109
|
-
icon_dest = icons_dir /
|
|
110
|
+
applications_dir = Path.home() / ".local" / "share" / "applications"
|
|
111
|
+
icons_dir = Path.home() / ".local" / "share" / "icons"
|
|
112
|
+
desktop_file = applications_dir / "souleyez.desktop"
|
|
113
|
+
icon_dest = icons_dir / "souleyez.png"
|
|
110
114
|
|
|
111
115
|
# Skip if already installed
|
|
112
116
|
if desktop_file.exists():
|
|
@@ -121,16 +125,19 @@ def _install_desktop_shortcut():
|
|
|
121
125
|
# Try importlib.resources first (Python 3.9+)
|
|
122
126
|
try:
|
|
123
127
|
from importlib.resources import files
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
|
|
129
|
+
icon_source = files("souleyez.assets").joinpath("souleyez-icon.png")
|
|
130
|
+
with open(icon_source, "rb") as src:
|
|
126
131
|
icon_data = src.read()
|
|
127
132
|
except (ImportError, TypeError, FileNotFoundError):
|
|
128
133
|
# Fallback: find icon relative to this file
|
|
129
|
-
icon_source =
|
|
130
|
-
|
|
134
|
+
icon_source = (
|
|
135
|
+
Path(__file__).parent.parent / "assets" / "souleyez-icon.png"
|
|
136
|
+
)
|
|
137
|
+
with open(icon_source, "rb") as src:
|
|
131
138
|
icon_data = src.read()
|
|
132
139
|
|
|
133
|
-
with open(icon_dest,
|
|
140
|
+
with open(icon_dest, "wb") as dst:
|
|
134
141
|
dst.write(icon_data)
|
|
135
142
|
except Exception:
|
|
136
143
|
icon_dest = "utilities-terminal" # Fallback to system icon
|
|
@@ -151,12 +158,18 @@ Keywords=pentest;security;hacking;nmap;metasploit;
|
|
|
151
158
|
|
|
152
159
|
# Update desktop database (optional)
|
|
153
160
|
try:
|
|
154
|
-
subprocess.run(
|
|
155
|
-
|
|
161
|
+
subprocess.run(
|
|
162
|
+
["update-desktop-database", str(applications_dir)],
|
|
163
|
+
capture_output=True,
|
|
164
|
+
check=False,
|
|
165
|
+
timeout=5,
|
|
166
|
+
)
|
|
156
167
|
except Exception:
|
|
157
168
|
pass
|
|
158
169
|
|
|
159
|
-
click.echo(
|
|
170
|
+
click.echo(
|
|
171
|
+
f" {click.style('✓', fg='green')} Desktop shortcut: Added to Applications menu"
|
|
172
|
+
)
|
|
160
173
|
|
|
161
174
|
except Exception:
|
|
162
175
|
# Silently ignore errors - desktop shortcut is nice-to-have
|
|
@@ -164,9 +177,7 @@ Keywords=pentest;security;hacking;nmap;metasploit;
|
|
|
164
177
|
|
|
165
178
|
|
|
166
179
|
def _run_tool_installs(
|
|
167
|
-
missing_tools: List[Dict],
|
|
168
|
-
wrong_version_tools: List[Dict],
|
|
169
|
-
distro: str
|
|
180
|
+
missing_tools: List[Dict], wrong_version_tools: List[Dict], distro: str
|
|
170
181
|
) -> bool:
|
|
171
182
|
"""
|
|
172
183
|
Prompt user and run install/upgrade commands.
|
|
@@ -186,25 +197,29 @@ def _run_tool_installs(
|
|
|
186
197
|
# Add wrong version tools (upgrades)
|
|
187
198
|
for tool in wrong_version_tools:
|
|
188
199
|
# Try to get upgrade command, fall back to install command
|
|
189
|
-
tool_info = tool.get(
|
|
200
|
+
tool_info = tool.get("tool_info", {})
|
|
190
201
|
upgrade_cmd = get_upgrade_command(tool_info, distro) if tool_info else None
|
|
191
|
-
install_cmd = upgrade_cmd or tool.get(
|
|
192
|
-
|
|
193
|
-
all_installs.append(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
202
|
+
install_cmd = upgrade_cmd or tool.get("install", "")
|
|
203
|
+
|
|
204
|
+
all_installs.append(
|
|
205
|
+
{
|
|
206
|
+
"name": tool["name"],
|
|
207
|
+
"cmd": install_cmd,
|
|
208
|
+
"action": "upgrade",
|
|
209
|
+
"tool_info": tool_info,
|
|
210
|
+
}
|
|
211
|
+
)
|
|
199
212
|
|
|
200
213
|
# Add missing tools (installs)
|
|
201
214
|
for tool in missing_tools:
|
|
202
|
-
all_installs.append(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
215
|
+
all_installs.append(
|
|
216
|
+
{
|
|
217
|
+
"name": tool["name"],
|
|
218
|
+
"cmd": tool["install"],
|
|
219
|
+
"action": "install",
|
|
220
|
+
"tool_info": tool.get("tool_info", {}),
|
|
221
|
+
}
|
|
222
|
+
)
|
|
208
223
|
|
|
209
224
|
if not all_installs:
|
|
210
225
|
return False
|
|
@@ -212,7 +227,9 @@ def _run_tool_installs(
|
|
|
212
227
|
# Check disk space
|
|
213
228
|
if not _check_disk_space(500):
|
|
214
229
|
click.echo()
|
|
215
|
-
click.echo(
|
|
230
|
+
click.echo(
|
|
231
|
+
f" {click.style('!', fg='yellow')} Low disk space (<500MB free). Installs may fail."
|
|
232
|
+
)
|
|
216
233
|
if not click.confirm(" Continue anyway?", default=False):
|
|
217
234
|
return False
|
|
218
235
|
|
|
@@ -220,8 +237,10 @@ def _run_tool_installs(
|
|
|
220
237
|
click.echo()
|
|
221
238
|
click.echo(f" {len(all_installs)} tool(s) to install/upgrade:")
|
|
222
239
|
for item in all_installs:
|
|
223
|
-
action_color =
|
|
224
|
-
click.echo(
|
|
240
|
+
action_color = "cyan" if item["action"] == "upgrade" else "green"
|
|
241
|
+
click.echo(
|
|
242
|
+
f" - {item['name']} ({click.style(item['action'], fg=action_color)})"
|
|
243
|
+
)
|
|
225
244
|
|
|
226
245
|
click.echo()
|
|
227
246
|
if not click.confirm(" Install/upgrade now?", default=False):
|
|
@@ -231,19 +250,23 @@ def _run_tool_installs(
|
|
|
231
250
|
# Request sudo upfront
|
|
232
251
|
click.echo()
|
|
233
252
|
click.echo(" Requesting sudo access...")
|
|
234
|
-
result = subprocess.run([
|
|
253
|
+
result = subprocess.run(["sudo", "-v"], check=False)
|
|
235
254
|
if result.returncode != 0:
|
|
236
|
-
click.echo(
|
|
255
|
+
click.echo(
|
|
256
|
+
f" {click.style('x', fg='red')} Could not obtain sudo. Aborting installs."
|
|
257
|
+
)
|
|
237
258
|
return False
|
|
238
259
|
|
|
239
260
|
# Update apt cache first (for apt-based installs)
|
|
240
|
-
has_apt_installs = any(
|
|
261
|
+
has_apt_installs = any("apt" in item["cmd"] for item in all_installs)
|
|
241
262
|
if has_apt_installs:
|
|
242
263
|
click.echo()
|
|
243
264
|
click.echo(" Updating package lists...")
|
|
244
|
-
result = subprocess.run([
|
|
265
|
+
result = subprocess.run(["sudo", "apt", "update"], capture_output=True)
|
|
245
266
|
if result.returncode != 0:
|
|
246
|
-
click.echo(
|
|
267
|
+
click.echo(
|
|
268
|
+
f" {click.style('!', fg='yellow')} apt update had issues, continuing anyway..."
|
|
269
|
+
)
|
|
247
270
|
|
|
248
271
|
# Track results for summary
|
|
249
272
|
results = []
|
|
@@ -251,29 +274,51 @@ def _run_tool_installs(
|
|
|
251
274
|
# Run each install command
|
|
252
275
|
for item in all_installs:
|
|
253
276
|
click.echo()
|
|
254
|
-
click.echo(
|
|
277
|
+
click.echo(
|
|
278
|
+
f" {click.style(item['action'].capitalize() + 'ing', fg='cyan')} {item['name']}..."
|
|
279
|
+
)
|
|
255
280
|
|
|
256
281
|
try:
|
|
257
282
|
# Run install command
|
|
258
283
|
proc = subprocess.run(
|
|
259
|
-
item[
|
|
284
|
+
item["cmd"],
|
|
260
285
|
shell=True, # nosec B602 - commands from trusted EXTERNAL_TOOLS
|
|
261
|
-
timeout=600 # 10 minute timeout per tool
|
|
286
|
+
timeout=600, # 10 minute timeout per tool
|
|
262
287
|
)
|
|
263
288
|
|
|
264
289
|
if proc.returncode == 0:
|
|
265
|
-
click.echo(
|
|
266
|
-
|
|
290
|
+
click.echo(
|
|
291
|
+
f" {click.style('✓', fg='green')} {item['name']} {item['action']} complete"
|
|
292
|
+
)
|
|
293
|
+
results.append(
|
|
294
|
+
{
|
|
295
|
+
"name": item["name"],
|
|
296
|
+
"success": True,
|
|
297
|
+
"tool_info": item["tool_info"],
|
|
298
|
+
}
|
|
299
|
+
)
|
|
267
300
|
else:
|
|
268
|
-
click.echo(
|
|
269
|
-
|
|
301
|
+
click.echo(
|
|
302
|
+
f" {click.style('✗', fg='red')} {item['name']} failed (exit {proc.returncode})"
|
|
303
|
+
)
|
|
304
|
+
results.append(
|
|
305
|
+
{
|
|
306
|
+
"name": item["name"],
|
|
307
|
+
"success": False,
|
|
308
|
+
"tool_info": item["tool_info"],
|
|
309
|
+
}
|
|
310
|
+
)
|
|
270
311
|
|
|
271
312
|
except subprocess.TimeoutExpired:
|
|
272
313
|
click.echo(f" {click.style('✗', fg='red')} {item['name']} timed out")
|
|
273
|
-
results.append(
|
|
314
|
+
results.append(
|
|
315
|
+
{"name": item["name"], "success": False, "tool_info": item["tool_info"]}
|
|
316
|
+
)
|
|
274
317
|
except Exception as e:
|
|
275
318
|
click.echo(f" {click.style('✗', fg='red')} {item['name']} error: {e}")
|
|
276
|
-
results.append(
|
|
319
|
+
results.append(
|
|
320
|
+
{"name": item["name"], "success": False, "tool_info": item["tool_info"]}
|
|
321
|
+
)
|
|
277
322
|
|
|
278
323
|
# Configure sudoers for privileged tools that installed successfully
|
|
279
324
|
click.echo()
|
|
@@ -281,32 +326,38 @@ def _run_tool_installs(
|
|
|
281
326
|
|
|
282
327
|
sudoers_configured = False
|
|
283
328
|
for res in results:
|
|
284
|
-
if not res[
|
|
329
|
+
if not res["success"]:
|
|
285
330
|
continue
|
|
286
331
|
|
|
287
|
-
tool_info = res[
|
|
288
|
-
if not tool_info.get(
|
|
332
|
+
tool_info = res["tool_info"]
|
|
333
|
+
if not tool_info.get("needs_sudo"):
|
|
289
334
|
continue
|
|
290
335
|
|
|
291
336
|
sudoers_configured = True
|
|
292
337
|
# Find the actual binary path
|
|
293
|
-
command = tool_info.get(
|
|
338
|
+
command = tool_info.get("command", res["name"])
|
|
294
339
|
tool_path = shutil.which(command)
|
|
295
340
|
|
|
296
341
|
# Check alt_commands if primary not found
|
|
297
|
-
if not tool_path and tool_info.get(
|
|
298
|
-
for alt in tool_info[
|
|
342
|
+
if not tool_path and tool_info.get("alt_commands"):
|
|
343
|
+
for alt in tool_info["alt_commands"]:
|
|
299
344
|
tool_path = shutil.which(alt)
|
|
300
345
|
if tool_path:
|
|
301
346
|
break
|
|
302
347
|
|
|
303
348
|
if tool_path:
|
|
304
|
-
if _configure_sudoers(res[
|
|
305
|
-
click.echo(
|
|
349
|
+
if _configure_sudoers(res["name"], tool_path):
|
|
350
|
+
click.echo(
|
|
351
|
+
f" {click.style('✓', fg='green')} {res['name']} configured for passwordless sudo"
|
|
352
|
+
)
|
|
306
353
|
else:
|
|
307
|
-
click.echo(
|
|
354
|
+
click.echo(
|
|
355
|
+
f" {click.style('!', fg='yellow')} {res['name']} sudoers config failed"
|
|
356
|
+
)
|
|
308
357
|
else:
|
|
309
|
-
click.echo(
|
|
358
|
+
click.echo(
|
|
359
|
+
f" {click.style('!', fg='yellow')} {res['name']} binary not found, skipping sudoers"
|
|
360
|
+
)
|
|
310
361
|
|
|
311
362
|
if not sudoers_configured:
|
|
312
363
|
click.echo(" No privileged tools needed configuration.")
|
|
@@ -319,9 +370,9 @@ def _run_tool_installs(
|
|
|
319
370
|
fail_count = 0
|
|
320
371
|
|
|
321
372
|
for res in results:
|
|
322
|
-
tool_info = res[
|
|
323
|
-
command = tool_info.get(
|
|
324
|
-
alt_commands = tool_info.get(
|
|
373
|
+
tool_info = res["tool_info"]
|
|
374
|
+
command = tool_info.get("command", res["name"])
|
|
375
|
+
alt_commands = tool_info.get("alt_commands")
|
|
325
376
|
|
|
326
377
|
# Check if tool is now available
|
|
327
378
|
found = shutil.which(command) is not None
|
|
@@ -335,15 +386,21 @@ def _run_tool_installs(
|
|
|
335
386
|
click.echo(f" {click.style('✓', fg='green')} {res['name']} verified")
|
|
336
387
|
success_count += 1
|
|
337
388
|
else:
|
|
338
|
-
click.echo(
|
|
389
|
+
click.echo(
|
|
390
|
+
f" {click.style('✗', fg='red')} {res['name']} not found after install"
|
|
391
|
+
)
|
|
339
392
|
fail_count += 1
|
|
340
393
|
|
|
341
394
|
# Summary
|
|
342
395
|
click.echo()
|
|
343
396
|
if fail_count == 0:
|
|
344
|
-
click.echo(
|
|
397
|
+
click.echo(
|
|
398
|
+
f" {click.style('✓', fg='green')} All {success_count} tool(s) installed successfully!"
|
|
399
|
+
)
|
|
345
400
|
else:
|
|
346
|
-
click.echo(
|
|
401
|
+
click.echo(
|
|
402
|
+
f" {click.style('!', fg='yellow')} {success_count} succeeded, {fail_count} failed"
|
|
403
|
+
)
|
|
347
404
|
click.echo(" Failed tools may need manual installation.")
|
|
348
405
|
|
|
349
406
|
return True
|
|
@@ -352,14 +409,56 @@ def _run_tool_installs(
|
|
|
352
409
|
def _show_wizard_banner():
|
|
353
410
|
"""Display the SoulEyez ASCII banner for wizard steps."""
|
|
354
411
|
click.echo()
|
|
355
|
-
click.echo(
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
412
|
+
click.echo(
|
|
413
|
+
click.style(
|
|
414
|
+
" ███████╗ ██████╗ ██╗ ██╗██╗ ███████╗██╗ ██╗███████╗███████╗",
|
|
415
|
+
fg="bright_cyan",
|
|
416
|
+
bold=True,
|
|
417
|
+
)
|
|
418
|
+
+ click.style(" ▄██▄", fg="bright_blue", bold=True)
|
|
419
|
+
)
|
|
420
|
+
click.echo(
|
|
421
|
+
click.style(
|
|
422
|
+
" ██╔════╝██╔═══██╗██║ ██║██║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══███╔╝",
|
|
423
|
+
fg="bright_cyan",
|
|
424
|
+
bold=True,
|
|
425
|
+
)
|
|
426
|
+
+ click.style(" ▄█▀ ▀█▄", fg="bright_blue", bold=True)
|
|
427
|
+
)
|
|
428
|
+
click.echo(
|
|
429
|
+
click.style(
|
|
430
|
+
" ███████╗██║ ██║██║ ██║██║ █████╗ ╚████╔╝ █████╗ ███╔╝ ",
|
|
431
|
+
fg="bright_cyan",
|
|
432
|
+
bold=True,
|
|
433
|
+
)
|
|
434
|
+
+ click.style(" █ ◉ █", fg="bright_blue", bold=True)
|
|
435
|
+
)
|
|
436
|
+
click.echo(
|
|
437
|
+
click.style(
|
|
438
|
+
" ╚════██║██║ ██║██║ ██║██║ ██╔══╝ ╚██╔╝ ██╔══╝ ███╔╝ ",
|
|
439
|
+
fg="bright_cyan",
|
|
440
|
+
bold=True,
|
|
441
|
+
)
|
|
442
|
+
+ click.style(" █ ═══ █", fg="bright_blue", bold=True)
|
|
443
|
+
)
|
|
444
|
+
click.echo(
|
|
445
|
+
click.style(
|
|
446
|
+
" ███████║╚██████╔╝╚██████╔╝███████╗███████╗ ██║ ███████╗███████╗",
|
|
447
|
+
fg="bright_cyan",
|
|
448
|
+
bold=True,
|
|
449
|
+
)
|
|
450
|
+
+ click.style(" ▀█▄ ▄█▀", fg="bright_blue", bold=True)
|
|
451
|
+
)
|
|
452
|
+
click.echo(
|
|
453
|
+
click.style(
|
|
454
|
+
" ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚══════╝╚══════╝",
|
|
455
|
+
fg="bright_cyan",
|
|
456
|
+
bold=True,
|
|
457
|
+
)
|
|
458
|
+
+ click.style(" ▀██▀", fg="bright_blue", bold=True)
|
|
459
|
+
)
|
|
361
460
|
click.echo()
|
|
362
|
-
click.echo(click.style(" Created by CyberSoul SecurITy", fg=
|
|
461
|
+
click.echo(click.style(" Created by CyberSoul SecurITy", fg="bright_blue"))
|
|
363
462
|
click.echo()
|
|
364
463
|
|
|
365
464
|
|
|
@@ -373,6 +472,7 @@ def run_setup_wizard() -> bool:
|
|
|
373
472
|
try:
|
|
374
473
|
# Check if user has Pro tier
|
|
375
474
|
from souleyez.auth import get_current_user, Tier
|
|
475
|
+
|
|
376
476
|
user = get_current_user()
|
|
377
477
|
is_pro = user and user.tier == Tier.PRO
|
|
378
478
|
|
|
@@ -399,11 +499,11 @@ def run_setup_wizard() -> bool:
|
|
|
399
499
|
if is_pro:
|
|
400
500
|
automation_prefs = _wizard_automation_prefs()
|
|
401
501
|
else:
|
|
402
|
-
automation_prefs = {
|
|
502
|
+
automation_prefs = {"enabled": False, "mode": None}
|
|
403
503
|
|
|
404
504
|
# Step 7: Deliverable Templates (Pro only)
|
|
405
505
|
if is_pro:
|
|
406
|
-
templates = _wizard_deliverables(engagement_info.get(
|
|
506
|
+
templates = _wizard_deliverables(engagement_info.get("type"))
|
|
407
507
|
else:
|
|
408
508
|
templates = []
|
|
409
509
|
|
|
@@ -415,14 +515,14 @@ def run_setup_wizard() -> bool:
|
|
|
415
515
|
ai_enabled,
|
|
416
516
|
automation_prefs,
|
|
417
517
|
templates,
|
|
418
|
-
is_pro
|
|
518
|
+
is_pro,
|
|
419
519
|
)
|
|
420
520
|
|
|
421
521
|
mark_wizard_completed()
|
|
422
522
|
return True
|
|
423
523
|
|
|
424
524
|
except (KeyboardInterrupt, click.Abort):
|
|
425
|
-
click.echo(click.style("\n\n Setup wizard cancelled.", fg=
|
|
525
|
+
click.echo(click.style("\n\n Setup wizard cancelled.", fg="yellow"))
|
|
426
526
|
mark_wizard_completed() # Don't show wizard again
|
|
427
527
|
click.pause()
|
|
428
528
|
return False
|
|
@@ -435,40 +535,100 @@ def _wizard_welcome(is_pro: bool = False) -> bool:
|
|
|
435
535
|
|
|
436
536
|
# Header
|
|
437
537
|
click.echo("\n┌" + "─" * (width - 2) + "┐")
|
|
438
|
-
click.echo(
|
|
538
|
+
click.echo(
|
|
539
|
+
"│"
|
|
540
|
+
+ click.style(
|
|
541
|
+
" WELCOME TO SOULEYEZ - SETUP WIZARD ".center(width - 2),
|
|
542
|
+
bold=True,
|
|
543
|
+
fg="cyan",
|
|
544
|
+
)
|
|
545
|
+
+ "│"
|
|
546
|
+
)
|
|
439
547
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
440
548
|
click.echo()
|
|
441
549
|
|
|
442
550
|
# ASCII Art Banner - SOULEYEZ with all-seeing eye on the right
|
|
443
|
-
click.echo(
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
551
|
+
click.echo(
|
|
552
|
+
click.style(
|
|
553
|
+
" ███████╗ ██████╗ ██╗ ██╗██╗ ███████╗██╗ ██╗███████╗███████╗",
|
|
554
|
+
fg="bright_cyan",
|
|
555
|
+
bold=True,
|
|
556
|
+
)
|
|
557
|
+
+ click.style(" ▄██▄", fg="bright_blue", bold=True)
|
|
558
|
+
)
|
|
559
|
+
click.echo(
|
|
560
|
+
click.style(
|
|
561
|
+
" ██╔════╝██╔═══██╗██║ ██║██║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══███╔╝",
|
|
562
|
+
fg="bright_cyan",
|
|
563
|
+
bold=True,
|
|
564
|
+
)
|
|
565
|
+
+ click.style(" ▄█▀ ▀█▄", fg="bright_blue", bold=True)
|
|
566
|
+
)
|
|
567
|
+
click.echo(
|
|
568
|
+
click.style(
|
|
569
|
+
" ███████╗██║ ██║██║ ██║██║ █████╗ ╚████╔╝ █████╗ ███╔╝ ",
|
|
570
|
+
fg="bright_cyan",
|
|
571
|
+
bold=True,
|
|
572
|
+
)
|
|
573
|
+
+ click.style(" █ ◉ █", fg="bright_blue", bold=True)
|
|
574
|
+
)
|
|
575
|
+
click.echo(
|
|
576
|
+
click.style(
|
|
577
|
+
" ╚════██║██║ ██║██║ ██║██║ ██╔══╝ ╚██╔╝ ██╔══╝ ███╔╝ ",
|
|
578
|
+
fg="bright_cyan",
|
|
579
|
+
bold=True,
|
|
580
|
+
)
|
|
581
|
+
+ click.style(" █ ═══ █", fg="bright_blue", bold=True)
|
|
582
|
+
)
|
|
583
|
+
click.echo(
|
|
584
|
+
click.style(
|
|
585
|
+
" ███████║╚██████╔╝╚██████╔╝███████╗███████╗ ██║ ███████╗███████╗",
|
|
586
|
+
fg="bright_cyan",
|
|
587
|
+
bold=True,
|
|
588
|
+
)
|
|
589
|
+
+ click.style(" ▀█▄ ▄█▀", fg="bright_blue", bold=True)
|
|
590
|
+
)
|
|
591
|
+
click.echo(
|
|
592
|
+
click.style(
|
|
593
|
+
" ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚══════╝╚══════╝",
|
|
594
|
+
fg="bright_cyan",
|
|
595
|
+
bold=True,
|
|
596
|
+
)
|
|
597
|
+
+ click.style(" ▀██▀", fg="bright_blue", bold=True)
|
|
598
|
+
)
|
|
449
599
|
click.echo()
|
|
450
600
|
|
|
451
601
|
# Tagline and description
|
|
452
|
-
click.echo(click.style(" Created by CyberSoul SecurITy", fg=
|
|
602
|
+
click.echo(click.style(" Created by CyberSoul SecurITy", fg="bright_blue"))
|
|
453
603
|
click.echo()
|
|
454
|
-
click.echo(
|
|
455
|
-
|
|
456
|
-
|
|
604
|
+
click.echo(
|
|
605
|
+
" SoulEyez brings your hacking tools together so you can spend less time switching windows"
|
|
606
|
+
)
|
|
607
|
+
click.echo(
|
|
608
|
+
" and more time breaking things (ethically, of course). Launch scans with Nmap, Metasploit,"
|
|
609
|
+
)
|
|
610
|
+
click.echo(
|
|
611
|
+
" Gobuster, theHarvester, and many more. Manage engagements, review findings, generate reports,"
|
|
612
|
+
)
|
|
457
613
|
click.echo(" and let AI recommend your next moves — all in one place.")
|
|
458
614
|
click.echo()
|
|
459
|
-
click.echo(click.style(" SETUP WIZARD", bold=True, fg=
|
|
615
|
+
click.echo(click.style(" SETUP WIZARD", bold=True, fg="cyan"))
|
|
460
616
|
click.echo(" ─────────────")
|
|
461
617
|
click.echo()
|
|
462
618
|
click.echo(" This wizard will help you get started:")
|
|
463
|
-
click.echo(
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
click.echo(" " + click.style("✓", fg=
|
|
619
|
+
click.echo(
|
|
620
|
+
" " + click.style("✓", fg="green") + " Set up encryption for credentials"
|
|
621
|
+
)
|
|
622
|
+
click.echo(" " + click.style("✓", fg="green") + " Create your first engagement")
|
|
623
|
+
click.echo(" " + click.style("✓", fg="green") + " Check installed tools")
|
|
624
|
+
click.echo(" " + click.style("✓", fg="green") + " Set up AI features (optional)")
|
|
467
625
|
|
|
468
626
|
# Only show Pro steps if user has Pro tier
|
|
469
627
|
if is_pro:
|
|
470
|
-
click.echo(
|
|
471
|
-
|
|
628
|
+
click.echo(
|
|
629
|
+
" " + click.style("✓", fg="green") + " Configure automation preferences"
|
|
630
|
+
)
|
|
631
|
+
click.echo(" " + click.style("✓", fg="green") + " Select report templates")
|
|
472
632
|
|
|
473
633
|
click.echo()
|
|
474
634
|
click.pause(" Press ENTER to continue...")
|
|
@@ -485,38 +645,54 @@ def _wizard_encryption_setup() -> bool:
|
|
|
485
645
|
width = 60
|
|
486
646
|
|
|
487
647
|
click.echo("┌" + "─" * (width - 2) + "┐")
|
|
488
|
-
click.echo(
|
|
648
|
+
click.echo(
|
|
649
|
+
"│"
|
|
650
|
+
+ click.style(
|
|
651
|
+
" STEP 2: ENCRYPTION SETUP ".center(width - 2), bold=True, fg="cyan"
|
|
652
|
+
)
|
|
653
|
+
+ "│"
|
|
654
|
+
)
|
|
489
655
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
490
656
|
click.echo()
|
|
491
657
|
|
|
492
658
|
crypto = CryptoManager()
|
|
493
659
|
|
|
494
660
|
if crypto.is_encryption_enabled():
|
|
495
|
-
click.echo(" " + click.style("✓ Encryption already configured", fg=
|
|
661
|
+
click.echo(" " + click.style("✓ Encryption already configured", fg="green"))
|
|
496
662
|
click.echo()
|
|
497
663
|
click.pause(" Press any key to continue...")
|
|
498
664
|
return True
|
|
499
665
|
|
|
500
666
|
click.echo(" SoulEyez encrypts all credentials (passwords, API keys, tokens)")
|
|
501
|
-
click.echo(
|
|
667
|
+
click.echo(
|
|
668
|
+
" with a master password. This is "
|
|
669
|
+
+ click.style("required", fg="green", bold=True)
|
|
670
|
+
+ " for security."
|
|
671
|
+
)
|
|
502
672
|
click.echo()
|
|
503
|
-
click.echo(
|
|
504
|
-
|
|
673
|
+
click.echo(
|
|
674
|
+
" " + click.style("[y]", fg="green") + " Enable encryption (recommended)"
|
|
675
|
+
)
|
|
676
|
+
click.echo(" " + click.style("[n]", fg="bright_black") + " Skip encryption")
|
|
505
677
|
click.echo()
|
|
506
678
|
|
|
507
|
-
choice = click.prompt(" Enable encryption? [y/n]", default=
|
|
679
|
+
choice = click.prompt(" Enable encryption? [y/n]", default="y").strip().lower()
|
|
508
680
|
|
|
509
|
-
if choice in (
|
|
681
|
+
if choice in ("n", "s", "no", "skip"):
|
|
510
682
|
click.echo()
|
|
511
683
|
time.sleep(0.5)
|
|
512
|
-
click.echo(" " + click.style("SIKE! 😏", fg=
|
|
684
|
+
click.echo(" " + click.style("SIKE! 😏", fg="magenta", bold=True))
|
|
513
685
|
time.sleep(0.8)
|
|
514
686
|
click.echo()
|
|
515
|
-
click.echo(
|
|
687
|
+
click.echo(
|
|
688
|
+
" " + click.style("Security is not optional.", fg="cyan", bold=True)
|
|
689
|
+
)
|
|
516
690
|
click.echo(" " + "We're a security company. Encryption is mandatory.")
|
|
517
691
|
click.echo()
|
|
518
692
|
time.sleep(1)
|
|
519
|
-
click.echo(
|
|
693
|
+
click.echo(
|
|
694
|
+
" " + click.style("Let's set up your vault password...", fg="green")
|
|
695
|
+
)
|
|
520
696
|
click.pause(" Press any key to continue...")
|
|
521
697
|
|
|
522
698
|
click.echo()
|
|
@@ -526,39 +702,63 @@ def _wizard_encryption_setup() -> bool:
|
|
|
526
702
|
click.echo(" • At least one number")
|
|
527
703
|
click.echo(" • At least one special character (!@#$%^&*)")
|
|
528
704
|
click.echo()
|
|
529
|
-
click.echo(
|
|
530
|
-
|
|
705
|
+
click.echo(
|
|
706
|
+
" "
|
|
707
|
+
+ click.style(
|
|
708
|
+
"⚠️ If you lose this password, encrypted credentials cannot be recovered!",
|
|
709
|
+
fg="yellow",
|
|
710
|
+
bold=True,
|
|
711
|
+
)
|
|
712
|
+
)
|
|
531
713
|
click.echo()
|
|
532
714
|
|
|
533
715
|
import re
|
|
716
|
+
|
|
534
717
|
password_set = False
|
|
535
718
|
while not password_set:
|
|
536
719
|
password = getpass.getpass(" Enter master password: ")
|
|
537
720
|
|
|
538
721
|
# Validate password strength
|
|
539
722
|
if len(password) < 12:
|
|
540
|
-
click.echo(
|
|
723
|
+
click.echo(
|
|
724
|
+
click.style(" ✗ Password must be at least 12 characters.", fg="red")
|
|
725
|
+
)
|
|
541
726
|
continue
|
|
542
727
|
|
|
543
|
-
if not re.search(r
|
|
544
|
-
click.echo(
|
|
728
|
+
if not re.search(r"[a-z]", password):
|
|
729
|
+
click.echo(
|
|
730
|
+
click.style(
|
|
731
|
+
" ✗ Password must contain at least one lowercase letter.", fg="red"
|
|
732
|
+
)
|
|
733
|
+
)
|
|
545
734
|
continue
|
|
546
735
|
|
|
547
|
-
if not re.search(r
|
|
548
|
-
click.echo(
|
|
736
|
+
if not re.search(r"[A-Z]", password):
|
|
737
|
+
click.echo(
|
|
738
|
+
click.style(
|
|
739
|
+
" ✗ Password must contain at least one uppercase letter.", fg="red"
|
|
740
|
+
)
|
|
741
|
+
)
|
|
549
742
|
continue
|
|
550
743
|
|
|
551
|
-
if not re.search(r
|
|
552
|
-
click.echo(
|
|
744
|
+
if not re.search(r"\d", password):
|
|
745
|
+
click.echo(
|
|
746
|
+
click.style(" ✗ Password must contain at least one number.", fg="red")
|
|
747
|
+
)
|
|
553
748
|
continue
|
|
554
749
|
|
|
555
750
|
if not re.search(r'[!@#$%^&*()_+\-=\[\]{};:\'",.<>?/\\|`~]', password):
|
|
556
|
-
click.echo(
|
|
751
|
+
click.echo(
|
|
752
|
+
click.style(
|
|
753
|
+
" ✗ Password must contain at least one special character.",
|
|
754
|
+
fg="red",
|
|
755
|
+
)
|
|
756
|
+
)
|
|
557
757
|
continue
|
|
558
758
|
|
|
559
759
|
password_confirm = getpass.getpass(" Confirm master password: ")
|
|
560
760
|
if password != password_confirm:
|
|
561
|
-
click.echo(click.style(" ✗ Passwords don't match!", fg=
|
|
761
|
+
click.echo(click.style(" ✗ Passwords don't match!", fg="red"))
|
|
562
762
|
continue
|
|
563
763
|
|
|
564
764
|
password_set = True
|
|
@@ -568,13 +768,13 @@ def _wizard_encryption_setup() -> bool:
|
|
|
568
768
|
|
|
569
769
|
try:
|
|
570
770
|
if crypto.enable_encryption(password):
|
|
571
|
-
click.echo(" " + click.style("✓ Encryption enabled!", fg=
|
|
771
|
+
click.echo(" " + click.style("✓ Encryption enabled!", fg="green"))
|
|
572
772
|
else:
|
|
573
|
-
click.echo(" " + click.style("✗ Failed to enable encryption!", fg=
|
|
773
|
+
click.echo(" " + click.style("✗ Failed to enable encryption!", fg="red"))
|
|
574
774
|
click.pause(" Press any key to continue...")
|
|
575
775
|
return False
|
|
576
776
|
except Exception as e:
|
|
577
|
-
click.echo(" " + click.style(f"✗ Error: {e}", fg=
|
|
777
|
+
click.echo(" " + click.style(f"✗ Error: {e}", fg="red"))
|
|
578
778
|
click.pause(" Press any key to continue...")
|
|
579
779
|
return False
|
|
580
780
|
|
|
@@ -591,39 +791,55 @@ def _wizard_create_engagement() -> dict:
|
|
|
591
791
|
width = 60
|
|
592
792
|
|
|
593
793
|
click.echo("┌" + "─" * (width - 2) + "┐")
|
|
594
|
-
click.echo(
|
|
794
|
+
click.echo(
|
|
795
|
+
"│"
|
|
796
|
+
+ click.style(
|
|
797
|
+
" STEP 3: CREATE YOUR ENGAGEMENT ".center(width - 2), bold=True, fg="cyan"
|
|
798
|
+
)
|
|
799
|
+
+ "│"
|
|
800
|
+
)
|
|
595
801
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
596
802
|
click.echo()
|
|
597
803
|
click.echo(" An engagement organizes all your work for a specific target.")
|
|
598
|
-
click.echo(
|
|
804
|
+
click.echo(
|
|
805
|
+
" Give it a descriptive name like 'Acme Corp Pentest' or 'HackTheBox Lab'."
|
|
806
|
+
)
|
|
599
807
|
click.echo()
|
|
600
808
|
|
|
601
809
|
name = click.prompt(" Engagement Name").strip()
|
|
602
810
|
|
|
603
811
|
# Require a name
|
|
604
812
|
while not name:
|
|
605
|
-
click.echo(click.style(" ✗ Name is required!", fg=
|
|
813
|
+
click.echo(click.style(" ✗ Name is required!", fg="red"))
|
|
606
814
|
name = click.prompt(" Engagement Name").strip()
|
|
607
815
|
|
|
608
816
|
click.echo()
|
|
609
817
|
click.echo(" " + click.style("Engagement Type:", bold=True))
|
|
610
818
|
click.echo(" [1] Penetration Test - Full-scope security assessment")
|
|
611
819
|
click.echo(" [2] Bug Bounty - Vulnerability hunting with defined scope")
|
|
612
|
-
click.echo(
|
|
820
|
+
click.echo(
|
|
821
|
+
" [3] CTF / Lab - Practice environment, aggressive scanning OK"
|
|
822
|
+
)
|
|
613
823
|
click.echo(" [4] Red Team - Adversary simulation, stealth preferred")
|
|
614
824
|
click.echo(" [5] Custom - Define your own")
|
|
615
825
|
click.echo()
|
|
616
|
-
click.echo(
|
|
826
|
+
click.echo(
|
|
827
|
+
" "
|
|
828
|
+
+ click.style("NOTE:", fg="yellow")
|
|
829
|
+
+ " Type affects default automation and scan aggressiveness"
|
|
830
|
+
)
|
|
617
831
|
click.echo()
|
|
618
832
|
|
|
619
|
-
type_choice = click.prompt(
|
|
833
|
+
type_choice = click.prompt(
|
|
834
|
+
" Select option", type=click.IntRange(1, 5), default=1, show_default=False
|
|
835
|
+
)
|
|
620
836
|
|
|
621
837
|
engagement_types = {
|
|
622
|
-
1:
|
|
623
|
-
2:
|
|
624
|
-
3:
|
|
625
|
-
4:
|
|
626
|
-
5:
|
|
838
|
+
1: "penetration_test",
|
|
839
|
+
2: "bug_bounty",
|
|
840
|
+
3: "ctf",
|
|
841
|
+
4: "red_team",
|
|
842
|
+
5: "custom",
|
|
627
843
|
}
|
|
628
844
|
|
|
629
845
|
engagement_type = engagement_types[type_choice]
|
|
@@ -633,35 +849,44 @@ def _wizard_create_engagement() -> dict:
|
|
|
633
849
|
|
|
634
850
|
# Handle duplicate engagement names
|
|
635
851
|
try:
|
|
636
|
-
engagement_id = em.create_engagement(
|
|
852
|
+
engagement_id = em.create_engagement(
|
|
853
|
+
name, f"Created via Setup Wizard - Type: {engagement_type}"
|
|
854
|
+
)
|
|
637
855
|
click.echo()
|
|
638
|
-
click.echo(
|
|
856
|
+
click.echo(
|
|
857
|
+
" "
|
|
858
|
+
+ click.style(
|
|
859
|
+
f"✓ Engagement '{name}' created and set as active!", fg="green"
|
|
860
|
+
)
|
|
861
|
+
)
|
|
639
862
|
except ValueError as e:
|
|
640
863
|
if "already exists" in str(e):
|
|
641
864
|
click.echo()
|
|
642
|
-
click.echo(
|
|
643
|
-
|
|
865
|
+
click.echo(
|
|
866
|
+
" " + click.style(f"Engagement '{name}' already exists.", fg="yellow")
|
|
867
|
+
)
|
|
868
|
+
click.echo(" " + click.style("✓ Using existing engagement.", fg="green"))
|
|
644
869
|
# Get existing engagement ID
|
|
645
870
|
existing = em.get_engagement(name)
|
|
646
|
-
engagement_id = existing[
|
|
871
|
+
engagement_id = existing["id"] if existing else None
|
|
647
872
|
else:
|
|
648
873
|
raise
|
|
649
874
|
|
|
650
875
|
em.set_current(name) # Set as active so user drops into this engagement
|
|
651
876
|
click.pause(" Press any key to continue...")
|
|
652
877
|
|
|
653
|
-
return {
|
|
654
|
-
'id': engagement_id,
|
|
655
|
-
'name': name,
|
|
656
|
-
'type': engagement_type
|
|
657
|
-
}
|
|
878
|
+
return {"id": engagement_id, "name": name, "type": engagement_type}
|
|
658
879
|
|
|
659
880
|
|
|
660
881
|
def _wizard_tool_check() -> dict:
|
|
661
882
|
"""Check installed tools using the centralized tool_checker module."""
|
|
662
883
|
from souleyez.utils.tool_checker import (
|
|
663
|
-
check_tool_version,
|
|
664
|
-
|
|
884
|
+
check_tool_version,
|
|
885
|
+
EXTERNAL_TOOLS,
|
|
886
|
+
get_install_command,
|
|
887
|
+
detect_distro,
|
|
888
|
+
get_tool_version,
|
|
889
|
+
get_upgrade_command,
|
|
665
890
|
)
|
|
666
891
|
|
|
667
892
|
DesignSystem.clear_screen()
|
|
@@ -670,7 +895,13 @@ def _wizard_tool_check() -> dict:
|
|
|
670
895
|
distro = detect_distro()
|
|
671
896
|
|
|
672
897
|
click.echo("┌" + "─" * (width - 2) + "┐")
|
|
673
|
-
click.echo(
|
|
898
|
+
click.echo(
|
|
899
|
+
"│"
|
|
900
|
+
+ click.style(
|
|
901
|
+
" STEP 4: TOOL AVAILABILITY ".center(width - 2), bold=True, fg="cyan"
|
|
902
|
+
)
|
|
903
|
+
+ "│"
|
|
904
|
+
)
|
|
674
905
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
675
906
|
click.echo()
|
|
676
907
|
click.echo(" Checking installed tools...")
|
|
@@ -679,17 +910,17 @@ def _wizard_tool_check() -> dict:
|
|
|
679
910
|
# Core tools to check in the wizard (subset of full tool list)
|
|
680
911
|
# Maps display name -> tool_checker key (category, tool_name)
|
|
681
912
|
wizard_tools = {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
913
|
+
"nmap": ("reconnaissance", "nmap"),
|
|
914
|
+
"gobuster": ("web_scanning", "gobuster"),
|
|
915
|
+
"sqlmap": ("exploitation", "sqlmap"),
|
|
916
|
+
"nuclei": ("web_scanning", "nuclei"),
|
|
917
|
+
"hydra": ("credential_attacks", "hydra"),
|
|
918
|
+
"theHarvester": ("reconnaissance", "theharvester"),
|
|
919
|
+
"wpscan": ("web_scanning", "wpscan"),
|
|
920
|
+
"netexec": ("windows_ad", "netexec"),
|
|
921
|
+
"enum4linux": ("windows_ad", "enum4linux"),
|
|
922
|
+
"dnsrecon": ("reconnaissance", "dnsrecon"),
|
|
923
|
+
"whois": ("reconnaissance", "whois"),
|
|
693
924
|
}
|
|
694
925
|
|
|
695
926
|
found = 0
|
|
@@ -700,58 +931,71 @@ def _wizard_tool_check() -> dict:
|
|
|
700
931
|
|
|
701
932
|
for display_name, (category, tool_key) in wizard_tools.items():
|
|
702
933
|
tool_info = EXTERNAL_TOOLS[category][tool_key]
|
|
703
|
-
command = tool_info[
|
|
934
|
+
command = tool_info["command"]
|
|
704
935
|
|
|
705
936
|
# Use version-aware check
|
|
706
937
|
version_status = check_tool_version(tool_info)
|
|
707
938
|
|
|
708
|
-
if version_status[
|
|
939
|
+
if version_status["installed"]:
|
|
709
940
|
# Use the actual command that was found (may be primary or alt)
|
|
710
|
-
actual_cmd = version_status.get(
|
|
941
|
+
actual_cmd = version_status.get("actual_command") or command
|
|
711
942
|
path = shutil.which(actual_cmd)
|
|
712
943
|
|
|
713
944
|
# Try to get version for display (even if no min_version requirement)
|
|
714
|
-
version_str = version_status[
|
|
945
|
+
version_str = version_status["version"]
|
|
715
946
|
if not version_str:
|
|
716
947
|
# Try to get version using the actual found command
|
|
717
948
|
version_str = get_tool_version(actual_cmd)
|
|
718
949
|
|
|
719
950
|
# Check if version is OK
|
|
720
|
-
if version_status[
|
|
951
|
+
if version_status["needs_upgrade"]:
|
|
721
952
|
# Tool installed but wrong version
|
|
722
|
-
ver_display = version_str or
|
|
723
|
-
min_ver = version_status[
|
|
724
|
-
click.echo(
|
|
725
|
-
|
|
953
|
+
ver_display = version_str or "unknown"
|
|
954
|
+
min_ver = version_status["min_version"]
|
|
955
|
+
click.echo(
|
|
956
|
+
f" {click.style('!', fg='yellow')} {display_name:<15} v{ver_display} "
|
|
957
|
+
+ click.style(f"(needs v{min_ver}+)", fg="yellow")
|
|
958
|
+
)
|
|
726
959
|
tool_status[display_name] = {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
960
|
+
"found": True,
|
|
961
|
+
"path": path,
|
|
962
|
+
"version": ver_display,
|
|
963
|
+
"needs_upgrade": True,
|
|
731
964
|
}
|
|
732
|
-
wrong_version_tools.append(
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
965
|
+
wrong_version_tools.append(
|
|
966
|
+
{
|
|
967
|
+
"name": display_name,
|
|
968
|
+
"installed": ver_display,
|
|
969
|
+
"required": min_ver,
|
|
970
|
+
"install": get_upgrade_command(tool_info, distro)
|
|
971
|
+
or get_install_command(tool_info, distro),
|
|
972
|
+
"note": version_status.get("version_note"),
|
|
973
|
+
"tool_info": tool_info,
|
|
974
|
+
}
|
|
975
|
+
)
|
|
740
976
|
found += 1 # Still counts as found, just needs upgrade
|
|
741
977
|
else:
|
|
742
978
|
# Tool installed with correct version
|
|
743
979
|
ver_display = f"v{version_str}" if version_str else ""
|
|
744
|
-
click.echo(
|
|
745
|
-
|
|
980
|
+
click.echo(
|
|
981
|
+
f" {click.style('✓', fg='green')} {display_name:<15} Found {ver_display}"
|
|
982
|
+
)
|
|
983
|
+
tool_status[display_name] = {
|
|
984
|
+
"found": True,
|
|
985
|
+
"path": path,
|
|
986
|
+
"version": version_str,
|
|
987
|
+
}
|
|
746
988
|
found += 1
|
|
747
989
|
else:
|
|
748
990
|
click.echo(f" {click.style('✗', fg='red')} {display_name:<15} NOT FOUND")
|
|
749
|
-
tool_status[display_name] = {
|
|
750
|
-
missing_tools.append(
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
991
|
+
tool_status[display_name] = {"found": False, "path": None}
|
|
992
|
+
missing_tools.append(
|
|
993
|
+
{
|
|
994
|
+
"name": display_name,
|
|
995
|
+
"install": get_install_command(tool_info, distro),
|
|
996
|
+
"tool_info": tool_info,
|
|
997
|
+
}
|
|
998
|
+
)
|
|
755
999
|
|
|
756
1000
|
click.echo()
|
|
757
1001
|
click.echo(f" Found {found}/{total} recommended tools")
|
|
@@ -759,10 +1003,12 @@ def _wizard_tool_check() -> dict:
|
|
|
759
1003
|
# Show version warnings
|
|
760
1004
|
if wrong_version_tools:
|
|
761
1005
|
click.echo()
|
|
762
|
-
click.echo(" " + click.style("VERSION ISSUES:", fg=
|
|
1006
|
+
click.echo(" " + click.style("VERSION ISSUES:", fg="yellow", bold=True))
|
|
763
1007
|
for tool in wrong_version_tools:
|
|
764
|
-
click.echo(
|
|
765
|
-
|
|
1008
|
+
click.echo(
|
|
1009
|
+
f" - {tool['name']}: installed v{tool['installed']}, needs v{tool['required']}+"
|
|
1010
|
+
)
|
|
1011
|
+
if tool.get("note"):
|
|
766
1012
|
click.echo(f" {click.style(tool['note'], fg='bright_black')}")
|
|
767
1013
|
|
|
768
1014
|
# Offer to install/upgrade tools
|
|
@@ -781,22 +1027,30 @@ def _wizard_tool_check() -> dict:
|
|
|
781
1027
|
tool_info = EXTERNAL_TOOLS[category][tool_key]
|
|
782
1028
|
version_status = check_tool_version(tool_info)
|
|
783
1029
|
|
|
784
|
-
if version_status[
|
|
785
|
-
tool_status[display_name] = {
|
|
1030
|
+
if version_status["installed"] and not version_status["needs_upgrade"]:
|
|
1031
|
+
tool_status[display_name] = {
|
|
1032
|
+
"found": True,
|
|
1033
|
+
"path": shutil.which(tool_info["command"]),
|
|
1034
|
+
}
|
|
786
1035
|
found += 1
|
|
787
|
-
elif version_status[
|
|
788
|
-
tool_status[display_name] = {
|
|
789
|
-
wrong_version_tools.append({
|
|
1036
|
+
elif version_status["installed"] and version_status["needs_upgrade"]:
|
|
1037
|
+
tool_status[display_name] = {"found": True, "needs_upgrade": True}
|
|
1038
|
+
wrong_version_tools.append({"name": display_name})
|
|
790
1039
|
found += 1
|
|
791
1040
|
else:
|
|
792
|
-
tool_status[display_name] = {
|
|
1041
|
+
tool_status[display_name] = {"found": False, "path": None}
|
|
793
1042
|
|
|
794
1043
|
click.echo(f" Now have {found}/{total} tools available")
|
|
795
1044
|
|
|
796
1045
|
click.echo()
|
|
797
1046
|
click.pause(" Press any key to continue...")
|
|
798
1047
|
|
|
799
|
-
return {
|
|
1048
|
+
return {
|
|
1049
|
+
"found": found,
|
|
1050
|
+
"total": total,
|
|
1051
|
+
"tools": tool_status,
|
|
1052
|
+
"wrong_version": wrong_version_tools,
|
|
1053
|
+
}
|
|
800
1054
|
|
|
801
1055
|
|
|
802
1056
|
def _wizard_ai_setup() -> bool:
|
|
@@ -809,54 +1063,83 @@ def _wizard_ai_setup() -> bool:
|
|
|
809
1063
|
width = 60
|
|
810
1064
|
|
|
811
1065
|
click.echo("┌" + "─" * (width - 2) + "┐")
|
|
812
|
-
click.echo(
|
|
1066
|
+
click.echo(
|
|
1067
|
+
"│"
|
|
1068
|
+
+ click.style(
|
|
1069
|
+
" STEP 5: AI FEATURES (OPTIONAL) ".center(width - 2), bold=True, fg="cyan"
|
|
1070
|
+
)
|
|
1071
|
+
+ "│"
|
|
1072
|
+
)
|
|
813
1073
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
814
1074
|
click.echo()
|
|
815
1075
|
click.echo(" SoulEyez can use AI to suggest scanning strategies and")
|
|
816
|
-
click.echo(
|
|
1076
|
+
click.echo(
|
|
1077
|
+
" prioritize targets. This requires "
|
|
1078
|
+
+ click.style("Ollama", bold=True)
|
|
1079
|
+
+ " (local AI runtime)."
|
|
1080
|
+
)
|
|
817
1081
|
click.echo()
|
|
818
1082
|
|
|
819
1083
|
# Check if Ollama is installed
|
|
820
|
-
ollama_path = shutil.which(
|
|
1084
|
+
ollama_path = shutil.which("ollama")
|
|
821
1085
|
|
|
822
1086
|
if ollama_path:
|
|
823
|
-
click.echo(" " + click.style("✓", fg=
|
|
1087
|
+
click.echo(" " + click.style("✓", fg="green") + " Ollama is installed")
|
|
824
1088
|
|
|
825
1089
|
# Check if it's running
|
|
826
1090
|
try:
|
|
827
1091
|
result = subprocess.run(
|
|
828
|
-
[
|
|
829
|
-
capture_output=True,
|
|
1092
|
+
["curl", "-s", "http://localhost:11434/api/tags"],
|
|
1093
|
+
capture_output=True,
|
|
1094
|
+
timeout=5,
|
|
830
1095
|
)
|
|
831
1096
|
if result.returncode == 0:
|
|
832
|
-
click.echo(" " + click.style("✓", fg=
|
|
1097
|
+
click.echo(" " + click.style("✓", fg="green") + " Ollama is running")
|
|
833
1098
|
|
|
834
1099
|
# Check for models
|
|
835
1100
|
import json
|
|
1101
|
+
|
|
836
1102
|
try:
|
|
837
1103
|
data = json.loads(result.stdout)
|
|
838
|
-
models = data.get(
|
|
1104
|
+
models = data.get("models", [])
|
|
839
1105
|
if models:
|
|
840
|
-
click.echo(
|
|
1106
|
+
click.echo(
|
|
1107
|
+
f" "
|
|
1108
|
+
+ click.style("✓", fg="green")
|
|
1109
|
+
+ f" {len(models)} model(s) available"
|
|
1110
|
+
)
|
|
841
1111
|
click.echo()
|
|
842
|
-
click.echo(
|
|
1112
|
+
click.echo(
|
|
1113
|
+
" "
|
|
1114
|
+
+ click.style(
|
|
1115
|
+
"AI features are ready!", fg="green", bold=True
|
|
1116
|
+
)
|
|
1117
|
+
)
|
|
843
1118
|
click.echo()
|
|
844
1119
|
click.pause(" Press any key to continue...")
|
|
845
1120
|
return True
|
|
846
1121
|
else:
|
|
847
|
-
click.echo(
|
|
1122
|
+
click.echo(
|
|
1123
|
+
" "
|
|
1124
|
+
+ click.style("!", fg="yellow")
|
|
1125
|
+
+ " No models installed"
|
|
1126
|
+
)
|
|
848
1127
|
except:
|
|
849
1128
|
pass
|
|
850
1129
|
|
|
851
1130
|
# Offer to pull a model
|
|
852
1131
|
click.echo()
|
|
853
1132
|
click.echo(" Would you like to download a model now?")
|
|
854
|
-
click.echo(
|
|
1133
|
+
click.echo(
|
|
1134
|
+
" Recommended: "
|
|
1135
|
+
+ click.style("llama3.1:8b", fg="cyan")
|
|
1136
|
+
+ " (~4.7GB)"
|
|
1137
|
+
)
|
|
855
1138
|
click.echo()
|
|
856
1139
|
|
|
857
1140
|
try:
|
|
858
1141
|
response = input(" Download model? (y/n): ").strip().lower()
|
|
859
|
-
if response ==
|
|
1142
|
+
if response == "y":
|
|
860
1143
|
return _pull_ollama_model()
|
|
861
1144
|
except (KeyboardInterrupt, EOFError):
|
|
862
1145
|
click.echo("\n Skipped.")
|
|
@@ -865,11 +1148,15 @@ def _wizard_ai_setup() -> bool:
|
|
|
865
1148
|
return False
|
|
866
1149
|
else:
|
|
867
1150
|
# Ollama installed but not running
|
|
868
|
-
click.echo(
|
|
1151
|
+
click.echo(
|
|
1152
|
+
" " + click.style("!", fg="yellow") + " Ollama is not running"
|
|
1153
|
+
)
|
|
869
1154
|
return _start_ollama_and_setup()
|
|
870
1155
|
|
|
871
1156
|
except subprocess.TimeoutExpired:
|
|
872
|
-
click.echo(
|
|
1157
|
+
click.echo(
|
|
1158
|
+
" " + click.style("!", fg="yellow") + " Ollama is not responding"
|
|
1159
|
+
)
|
|
873
1160
|
return _start_ollama_and_setup()
|
|
874
1161
|
except FileNotFoundError:
|
|
875
1162
|
# curl not found, try another method
|
|
@@ -877,7 +1164,7 @@ def _wizard_ai_setup() -> bool:
|
|
|
877
1164
|
|
|
878
1165
|
else:
|
|
879
1166
|
# Ollama not installed - offer to install
|
|
880
|
-
click.echo(" " + click.style("!", fg=
|
|
1167
|
+
click.echo(" " + click.style("!", fg="yellow") + " Ollama is not installed")
|
|
881
1168
|
click.echo()
|
|
882
1169
|
click.echo(" Would you like to install Ollama now?")
|
|
883
1170
|
click.echo(" This will download and install the Ollama runtime (~100MB).")
|
|
@@ -885,13 +1172,17 @@ def _wizard_ai_setup() -> bool:
|
|
|
885
1172
|
|
|
886
1173
|
try:
|
|
887
1174
|
response = input(" Install Ollama? (y/n): ").strip().lower()
|
|
888
|
-
if response ==
|
|
1175
|
+
if response == "y":
|
|
889
1176
|
return _install_ollama()
|
|
890
1177
|
except (KeyboardInterrupt, EOFError):
|
|
891
1178
|
click.echo("\n Skipped.")
|
|
892
1179
|
|
|
893
1180
|
click.echo()
|
|
894
|
-
click.echo(
|
|
1181
|
+
click.echo(
|
|
1182
|
+
" "
|
|
1183
|
+
+ click.style("Skipping AI setup.", fg="bright_black")
|
|
1184
|
+
+ " You can enable it later in Settings."
|
|
1185
|
+
)
|
|
895
1186
|
click.pause(" Press any key to continue...")
|
|
896
1187
|
return False
|
|
897
1188
|
|
|
@@ -902,28 +1193,45 @@ def _install_ollama() -> bool:
|
|
|
902
1193
|
|
|
903
1194
|
click.echo()
|
|
904
1195
|
click.echo(" Installing Ollama...")
|
|
905
|
-
click.echo(
|
|
1196
|
+
click.echo(
|
|
1197
|
+
" "
|
|
1198
|
+
+ click.style(
|
|
1199
|
+
"(This may take a minute - downloading ~100MB)", fg="bright_black"
|
|
1200
|
+
)
|
|
1201
|
+
)
|
|
906
1202
|
click.echo()
|
|
907
1203
|
|
|
908
1204
|
try:
|
|
909
1205
|
# Run the official Ollama install script with timeout
|
|
910
1206
|
# Add curl timeouts to fail faster on network issues
|
|
911
1207
|
result = subprocess.run(
|
|
912
|
-
[
|
|
1208
|
+
[
|
|
1209
|
+
"bash",
|
|
1210
|
+
"-c",
|
|
1211
|
+
"curl --connect-timeout 30 --max-time 300 -fsSL https://ollama.ai/install.sh | sh",
|
|
1212
|
+
],
|
|
913
1213
|
check=False,
|
|
914
|
-
timeout=360 # 6 minute total timeout
|
|
1214
|
+
timeout=360, # 6 minute total timeout
|
|
915
1215
|
)
|
|
916
1216
|
|
|
917
1217
|
if result.returncode == 0:
|
|
918
1218
|
click.echo()
|
|
919
|
-
click.echo(
|
|
1219
|
+
click.echo(
|
|
1220
|
+
" " + click.style("✓ Ollama installed successfully!", fg="green")
|
|
1221
|
+
)
|
|
920
1222
|
|
|
921
1223
|
# Start Ollama and set up model
|
|
922
1224
|
return _start_ollama_and_setup()
|
|
923
1225
|
else:
|
|
924
1226
|
click.echo()
|
|
925
|
-
click.echo(" " + click.style("✗ Installation failed", fg=
|
|
926
|
-
click.echo(
|
|
1227
|
+
click.echo(" " + click.style("✗ Installation failed", fg="red"))
|
|
1228
|
+
click.echo(
|
|
1229
|
+
" "
|
|
1230
|
+
+ click.style(
|
|
1231
|
+
"This is usually a network issue (slow connection or timeout).",
|
|
1232
|
+
fg="yellow",
|
|
1233
|
+
)
|
|
1234
|
+
)
|
|
927
1235
|
click.echo()
|
|
928
1236
|
click.echo(" To install manually:")
|
|
929
1237
|
click.echo(" curl -fsSL https://ollama.ai/install.sh | sh")
|
|
@@ -934,8 +1242,14 @@ def _install_ollama() -> bool:
|
|
|
934
1242
|
|
|
935
1243
|
except subprocess.TimeoutExpired:
|
|
936
1244
|
click.echo()
|
|
937
|
-
click.echo(" " + click.style("✗ Installation timed out", fg=
|
|
938
|
-
click.echo(
|
|
1245
|
+
click.echo(" " + click.style("✗ Installation timed out", fg="red"))
|
|
1246
|
+
click.echo(
|
|
1247
|
+
" "
|
|
1248
|
+
+ click.style(
|
|
1249
|
+
"The download took too long. Check your internet connection.",
|
|
1250
|
+
fg="yellow",
|
|
1251
|
+
)
|
|
1252
|
+
)
|
|
939
1253
|
click.echo()
|
|
940
1254
|
click.echo(" To install manually:")
|
|
941
1255
|
click.echo(" curl -fsSL https://ollama.ai/install.sh | sh")
|
|
@@ -944,7 +1258,7 @@ def _install_ollama() -> bool:
|
|
|
944
1258
|
|
|
945
1259
|
except Exception as e:
|
|
946
1260
|
click.echo()
|
|
947
|
-
click.echo(click.style(f" ✗ Error: {e}", fg=
|
|
1261
|
+
click.echo(click.style(f" ✗ Error: {e}", fg="red"))
|
|
948
1262
|
click.echo(" You can install manually from https://ollama.ai")
|
|
949
1263
|
click.pause(" Press any key to continue...")
|
|
950
1264
|
return False
|
|
@@ -961,10 +1275,10 @@ def _start_ollama_and_setup() -> bool:
|
|
|
961
1275
|
try:
|
|
962
1276
|
# Start ollama serve in background
|
|
963
1277
|
subprocess.Popen(
|
|
964
|
-
[
|
|
1278
|
+
["ollama", "serve"],
|
|
965
1279
|
stdout=subprocess.DEVNULL,
|
|
966
1280
|
stderr=subprocess.DEVNULL,
|
|
967
|
-
start_new_session=True
|
|
1281
|
+
start_new_session=True,
|
|
968
1282
|
)
|
|
969
1283
|
|
|
970
1284
|
# Wait for it to start
|
|
@@ -973,22 +1287,25 @@ def _start_ollama_and_setup() -> bool:
|
|
|
973
1287
|
|
|
974
1288
|
# Verify it's running
|
|
975
1289
|
result = subprocess.run(
|
|
976
|
-
[
|
|
977
|
-
capture_output=True,
|
|
1290
|
+
["curl", "-s", "http://localhost:11434/api/tags"],
|
|
1291
|
+
capture_output=True,
|
|
1292
|
+
timeout=5,
|
|
978
1293
|
)
|
|
979
1294
|
|
|
980
1295
|
if result.returncode == 0:
|
|
981
|
-
click.echo(" " + click.style("✓ Ollama started!", fg=
|
|
1296
|
+
click.echo(" " + click.style("✓ Ollama started!", fg="green"))
|
|
982
1297
|
|
|
983
1298
|
# Offer to pull model
|
|
984
1299
|
click.echo()
|
|
985
1300
|
click.echo(" Would you like to download a model now?")
|
|
986
|
-
click.echo(
|
|
1301
|
+
click.echo(
|
|
1302
|
+
" Recommended: " + click.style("llama3.1:8b", fg="cyan") + " (~4.7GB)"
|
|
1303
|
+
)
|
|
987
1304
|
click.echo()
|
|
988
1305
|
|
|
989
1306
|
try:
|
|
990
1307
|
response = input(" Download model? (y/n): ").strip().lower()
|
|
991
|
-
if response ==
|
|
1308
|
+
if response == "y":
|
|
992
1309
|
return _pull_ollama_model()
|
|
993
1310
|
except (KeyboardInterrupt, EOFError):
|
|
994
1311
|
click.echo("\n Skipped.")
|
|
@@ -996,12 +1313,12 @@ def _start_ollama_and_setup() -> bool:
|
|
|
996
1313
|
click.pause(" Press any key to continue...")
|
|
997
1314
|
return True
|
|
998
1315
|
else:
|
|
999
|
-
click.echo(" " + click.style("✗ Failed to start Ollama", fg=
|
|
1316
|
+
click.echo(" " + click.style("✗ Failed to start Ollama", fg="red"))
|
|
1000
1317
|
click.pause(" Press any key to continue...")
|
|
1001
1318
|
return False
|
|
1002
1319
|
|
|
1003
1320
|
except Exception as e:
|
|
1004
|
-
click.echo(click.style(f" ✗ Error: {e}", fg=
|
|
1321
|
+
click.echo(click.style(f" ✗ Error: {e}", fg="red"))
|
|
1005
1322
|
click.pause(" Press any key to continue...")
|
|
1006
1323
|
return False
|
|
1007
1324
|
|
|
@@ -1014,32 +1331,38 @@ def _pull_ollama_model() -> bool:
|
|
|
1014
1331
|
|
|
1015
1332
|
click.echo()
|
|
1016
1333
|
click.echo(f" Downloading {model}...")
|
|
1017
|
-
click.echo(
|
|
1334
|
+
click.echo(
|
|
1335
|
+
" "
|
|
1336
|
+
+ click.style(
|
|
1337
|
+
"(This may take several minutes depending on your connection)",
|
|
1338
|
+
fg="bright_black",
|
|
1339
|
+
)
|
|
1340
|
+
)
|
|
1018
1341
|
click.echo()
|
|
1019
1342
|
|
|
1020
1343
|
try:
|
|
1021
1344
|
# Run ollama pull and let it output directly to terminal for proper progress bar
|
|
1022
|
-
result = subprocess.run(
|
|
1023
|
-
['ollama', 'pull', model],
|
|
1024
|
-
check=False
|
|
1025
|
-
)
|
|
1345
|
+
result = subprocess.run(["ollama", "pull", model], check=False)
|
|
1026
1346
|
|
|
1027
1347
|
if result.returncode == 0:
|
|
1028
1348
|
click.echo()
|
|
1029
|
-
click.echo(" " + click.style(f"✓ Model {model} ready!", fg=
|
|
1349
|
+
click.echo(" " + click.style(f"✓ Model {model} ready!", fg="green"))
|
|
1030
1350
|
click.echo()
|
|
1031
|
-
click.echo(
|
|
1351
|
+
click.echo(
|
|
1352
|
+
" "
|
|
1353
|
+
+ click.style("AI features are now enabled!", fg="green", bold=True)
|
|
1354
|
+
)
|
|
1032
1355
|
click.pause(" Press any key to continue...")
|
|
1033
1356
|
return True
|
|
1034
1357
|
else:
|
|
1035
1358
|
click.echo()
|
|
1036
|
-
click.echo(" " + click.style("✗ Failed to download model", fg=
|
|
1359
|
+
click.echo(" " + click.style("✗ Failed to download model", fg="red"))
|
|
1037
1360
|
click.echo(" You can try later with: ollama pull " + model)
|
|
1038
1361
|
click.pause(" Press any key to continue...")
|
|
1039
1362
|
return False
|
|
1040
1363
|
|
|
1041
1364
|
except Exception as e:
|
|
1042
|
-
click.echo(click.style(f" ✗ Error: {e}", fg=
|
|
1365
|
+
click.echo(click.style(f" ✗ Error: {e}", fg="red"))
|
|
1043
1366
|
click.pause(" Press any key to continue...")
|
|
1044
1367
|
return False
|
|
1045
1368
|
|
|
@@ -1051,7 +1374,13 @@ def _wizard_automation_prefs() -> dict:
|
|
|
1051
1374
|
width = 60
|
|
1052
1375
|
|
|
1053
1376
|
click.echo("┌" + "─" * (width - 2) + "┐")
|
|
1054
|
-
click.echo(
|
|
1377
|
+
click.echo(
|
|
1378
|
+
"│"
|
|
1379
|
+
+ click.style(
|
|
1380
|
+
" STEP 6: AUTOMATION PREFERENCES ".center(width - 2), bold=True, fg="cyan"
|
|
1381
|
+
)
|
|
1382
|
+
+ "│"
|
|
1383
|
+
)
|
|
1055
1384
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
1056
1385
|
click.echo()
|
|
1057
1386
|
click.echo(" SoulEyez can automatically chain tools based on discoveries.")
|
|
@@ -1069,15 +1398,17 @@ def _wizard_automation_prefs() -> dict:
|
|
|
1069
1398
|
click.echo()
|
|
1070
1399
|
click.echo(f" {click.style('✓', fg='green')} Auto-chaining disabled")
|
|
1071
1400
|
click.pause(" Press any key to continue...")
|
|
1072
|
-
return {
|
|
1401
|
+
return {"enabled": False, "mode": None}
|
|
1073
1402
|
|
|
1074
|
-
mode =
|
|
1403
|
+
mode = "auto" if mode_choice == 1 else "approval"
|
|
1075
1404
|
|
|
1076
1405
|
click.echo()
|
|
1077
|
-
click.echo(
|
|
1406
|
+
click.echo(
|
|
1407
|
+
f" {click.style('✓', fg='green')} Auto-chaining enabled in {mode.upper()} mode"
|
|
1408
|
+
)
|
|
1078
1409
|
click.pause(" Press any key to continue...")
|
|
1079
1410
|
|
|
1080
|
-
return {
|
|
1411
|
+
return {"enabled": True, "mode": mode}
|
|
1081
1412
|
|
|
1082
1413
|
|
|
1083
1414
|
def _wizard_deliverables(engagement_type: str) -> list:
|
|
@@ -1087,25 +1418,39 @@ def _wizard_deliverables(engagement_type: str) -> list:
|
|
|
1087
1418
|
width = 60
|
|
1088
1419
|
|
|
1089
1420
|
click.echo("┌" + "─" * (width - 2) + "┐")
|
|
1090
|
-
click.echo(
|
|
1421
|
+
click.echo(
|
|
1422
|
+
"│"
|
|
1423
|
+
+ click.style(
|
|
1424
|
+
" STEP 7: REPORT TEMPLATES ".center(width - 2), bold=True, fg="cyan"
|
|
1425
|
+
)
|
|
1426
|
+
+ "│"
|
|
1427
|
+
)
|
|
1091
1428
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
1092
1429
|
click.echo()
|
|
1093
1430
|
click.echo(" Pre-select report templates that will be available for export.")
|
|
1094
1431
|
click.echo(" You can change these later in Reports & Export menu.")
|
|
1095
1432
|
click.echo()
|
|
1096
|
-
click.echo(
|
|
1433
|
+
click.echo(
|
|
1434
|
+
" "
|
|
1435
|
+
+ click.style("NOTE:", fg="yellow")
|
|
1436
|
+
+ " Templates help generate professional reports from your findings"
|
|
1437
|
+
)
|
|
1097
1438
|
click.echo()
|
|
1098
1439
|
|
|
1099
1440
|
# Default templates based on engagement type
|
|
1100
1441
|
type_templates = {
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1442
|
+
"penetration_test": [
|
|
1443
|
+
"Executive Summary",
|
|
1444
|
+
"Technical Findings Report",
|
|
1445
|
+
"Vulnerability Details",
|
|
1446
|
+
],
|
|
1447
|
+
"bug_bounty": ["Vulnerability Details", "Proof of Concept"],
|
|
1448
|
+
"ctf": ["Technical Findings Report", "Attack Narrative"],
|
|
1449
|
+
"red_team": ["Attack Narrative", "Remediation Roadmap"],
|
|
1450
|
+
"custom": ["Technical Findings Report"],
|
|
1106
1451
|
}
|
|
1107
1452
|
|
|
1108
|
-
recommended = type_templates.get(engagement_type, [
|
|
1453
|
+
recommended = type_templates.get(engagement_type, ["Technical Findings Report"])
|
|
1109
1454
|
|
|
1110
1455
|
click.echo(f" Recommended for {engagement_type.replace('_', ' ').title()}:")
|
|
1111
1456
|
for template in recommended:
|
|
@@ -1118,7 +1463,15 @@ def _wizard_deliverables(engagement_type: str) -> list:
|
|
|
1118
1463
|
return recommended
|
|
1119
1464
|
|
|
1120
1465
|
|
|
1121
|
-
def _wizard_summary(
|
|
1466
|
+
def _wizard_summary(
|
|
1467
|
+
encryption_enabled,
|
|
1468
|
+
engagement_info,
|
|
1469
|
+
tool_status,
|
|
1470
|
+
ai_enabled,
|
|
1471
|
+
automation_prefs,
|
|
1472
|
+
templates,
|
|
1473
|
+
is_pro=False,
|
|
1474
|
+
):
|
|
1122
1475
|
"""Show wizard summary."""
|
|
1123
1476
|
DesignSystem.clear_screen()
|
|
1124
1477
|
_show_wizard_banner()
|
|
@@ -1127,43 +1480,65 @@ def _wizard_summary(encryption_enabled, engagement_info, tool_status, ai_enabled
|
|
|
1127
1480
|
# Adjust step number based on tier
|
|
1128
1481
|
step_num = "8" if is_pro else "6"
|
|
1129
1482
|
click.echo("┌" + "─" * (width - 2) + "┐")
|
|
1130
|
-
click.echo(
|
|
1483
|
+
click.echo(
|
|
1484
|
+
"│"
|
|
1485
|
+
+ click.style(
|
|
1486
|
+
f" STEP {step_num}: SETUP COMPLETE! ".center(width - 2),
|
|
1487
|
+
bold=True,
|
|
1488
|
+
fg="green",
|
|
1489
|
+
)
|
|
1490
|
+
+ "│"
|
|
1491
|
+
)
|
|
1131
1492
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
1132
1493
|
click.echo()
|
|
1133
1494
|
|
|
1134
1495
|
# Summary
|
|
1135
|
-
enc_status =
|
|
1496
|
+
enc_status = (
|
|
1497
|
+
click.style("Enabled", fg="green")
|
|
1498
|
+
if encryption_enabled
|
|
1499
|
+
else click.style("Disabled", fg="yellow")
|
|
1500
|
+
)
|
|
1136
1501
|
click.echo(f" {click.style('✓', fg='green')} Encryption: {enc_status}")
|
|
1137
1502
|
|
|
1138
|
-
eng_name = engagement_info[
|
|
1139
|
-
eng_type = engagement_info[
|
|
1140
|
-
click.echo(
|
|
1503
|
+
eng_name = engagement_info["name"]
|
|
1504
|
+
eng_type = engagement_info["type"].replace("_", " ").title()
|
|
1505
|
+
click.echo(
|
|
1506
|
+
f" {click.style('✓', fg='green')} Engagement: \"{eng_name}\" ({eng_type})"
|
|
1507
|
+
)
|
|
1141
1508
|
|
|
1142
|
-
tools_found = tool_status[
|
|
1143
|
-
tools_total = tool_status[
|
|
1144
|
-
click.echo(
|
|
1509
|
+
tools_found = tool_status["found"]
|
|
1510
|
+
tools_total = tool_status["total"]
|
|
1511
|
+
click.echo(
|
|
1512
|
+
f" {click.style('✓', fg='green')} Tools: {tools_found}/{tools_total} available"
|
|
1513
|
+
)
|
|
1145
1514
|
|
|
1146
1515
|
# AI status
|
|
1147
1516
|
if ai_enabled:
|
|
1148
1517
|
click.echo(f" {click.style('✓', fg='green')} AI Features: Enabled (Ollama)")
|
|
1149
1518
|
else:
|
|
1150
|
-
click.echo(
|
|
1519
|
+
click.echo(
|
|
1520
|
+
f" {click.style('○', fg='bright_black')} AI Features: Not configured"
|
|
1521
|
+
)
|
|
1151
1522
|
|
|
1152
1523
|
# Show tier status
|
|
1153
1524
|
if is_pro:
|
|
1154
1525
|
click.echo(f" {click.style('💎', fg='magenta')} License: PRO")
|
|
1155
|
-
if automation_prefs[
|
|
1156
|
-
auto_mode = automation_prefs[
|
|
1157
|
-
click.echo(
|
|
1526
|
+
if automation_prefs["enabled"]:
|
|
1527
|
+
auto_mode = automation_prefs["mode"].upper()
|
|
1528
|
+
click.echo(
|
|
1529
|
+
f" {click.style('✓', fg='green')} Auto-Chain: ON ({auto_mode} mode)"
|
|
1530
|
+
)
|
|
1158
1531
|
else:
|
|
1159
1532
|
click.echo(f" {click.style('✓', fg='green')} Auto-Chain: OFF")
|
|
1160
1533
|
|
|
1161
1534
|
if templates:
|
|
1162
|
-
click.echo(
|
|
1535
|
+
click.echo(
|
|
1536
|
+
f" {click.style('✓', fg='green')} Templates: {len(templates)} selected"
|
|
1537
|
+
)
|
|
1163
1538
|
else:
|
|
1164
1539
|
click.echo(f" {click.style('○', fg='bright_black')} License: FREE")
|
|
1165
1540
|
click.echo()
|
|
1166
|
-
click.echo(click.style(" Upgrade to Pro for:", fg=
|
|
1541
|
+
click.echo(click.style(" Upgrade to Pro for:", fg="yellow"))
|
|
1167
1542
|
click.echo(" • AI Execute - Autonomous exploitation")
|
|
1168
1543
|
click.echo(" • Automation - Smart tool chaining")
|
|
1169
1544
|
click.echo(" • MSF Integration - Advanced attack chains")
|
|
@@ -1171,7 +1546,7 @@ def _wizard_summary(encryption_enabled, engagement_info, tool_status, ai_enabled
|
|
|
1171
1546
|
click.echo(f" {click.style('→ cybersoulsecurity.com/upgrade', fg='cyan')}")
|
|
1172
1547
|
|
|
1173
1548
|
click.echo()
|
|
1174
|
-
click.echo(" " + click.style("You're ready to start!", bold=True, fg=
|
|
1549
|
+
click.echo(" " + click.style("You're ready to start!", bold=True, fg="cyan"))
|
|
1175
1550
|
click.echo()
|
|
1176
1551
|
|
|
1177
1552
|
# Install desktop shortcut automatically
|
|
@@ -1179,22 +1554,41 @@ def _wizard_summary(encryption_enabled, engagement_info, tool_status, ai_enabled
|
|
|
1179
1554
|
|
|
1180
1555
|
# Prompt for interactive tutorial
|
|
1181
1556
|
click.echo(" ┌" + "─" * 56 + "┐")
|
|
1182
|
-
click.echo(
|
|
1183
|
-
|
|
1557
|
+
click.echo(
|
|
1558
|
+
" │"
|
|
1559
|
+
+ click.style(
|
|
1560
|
+
" Would you like to run the interactive tutorial?", fg="cyan"
|
|
1561
|
+
).center(65)
|
|
1562
|
+
+ "│"
|
|
1563
|
+
)
|
|
1564
|
+
click.echo(
|
|
1565
|
+
" │" + " Recommended for new users - takes about 5 minutes".center(56) + "│"
|
|
1566
|
+
)
|
|
1184
1567
|
click.echo(" └" + "─" * 56 + "┘")
|
|
1185
1568
|
click.echo()
|
|
1186
|
-
click.echo(
|
|
1187
|
-
|
|
1569
|
+
click.echo(
|
|
1570
|
+
" " + click.style("[Y]", fg="green", bold=True) + " Yes, show me around"
|
|
1571
|
+
)
|
|
1572
|
+
click.echo(" " + click.style("[n]", fg="bright_black") + " No, go to main menu")
|
|
1188
1573
|
click.echo()
|
|
1189
1574
|
|
|
1190
|
-
choice =
|
|
1575
|
+
choice = (
|
|
1576
|
+
click.prompt(" Run tutorial?", default="y", show_default=False).strip().lower()
|
|
1577
|
+
)
|
|
1191
1578
|
|
|
1192
|
-
if choice in (
|
|
1579
|
+
if choice in ("y", "yes", ""):
|
|
1193
1580
|
from souleyez.ui.tutorial import run_tutorial
|
|
1581
|
+
|
|
1194
1582
|
run_tutorial()
|
|
1195
1583
|
else:
|
|
1196
1584
|
click.echo()
|
|
1197
|
-
click.echo(
|
|
1198
|
-
|
|
1585
|
+
click.echo(
|
|
1586
|
+
" "
|
|
1587
|
+
+ click.style("No problem!", fg="cyan")
|
|
1588
|
+
+ " You can run the tutorial anytime from:"
|
|
1589
|
+
)
|
|
1590
|
+
click.echo(
|
|
1591
|
+
" " + click.style("Settings & Security → [t] Tutorial", fg="yellow")
|
|
1592
|
+
)
|
|
1199
1593
|
click.echo()
|
|
1200
1594
|
click.pause(" Press any key to continue to main menu...")
|