souleyez 2.43.29__py3-none-any.whl → 2.43.34__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.
Files changed (358) hide show
  1. souleyez/__init__.py +1 -2
  2. souleyez/ai/__init__.py +21 -15
  3. souleyez/ai/action_mapper.py +249 -150
  4. souleyez/ai/chain_advisor.py +116 -100
  5. souleyez/ai/claude_provider.py +29 -28
  6. souleyez/ai/context_builder.py +80 -62
  7. souleyez/ai/executor.py +158 -117
  8. souleyez/ai/feedback_handler.py +136 -121
  9. souleyez/ai/llm_factory.py +27 -20
  10. souleyez/ai/llm_provider.py +4 -2
  11. souleyez/ai/ollama_provider.py +6 -9
  12. souleyez/ai/ollama_service.py +44 -37
  13. souleyez/ai/path_scorer.py +91 -76
  14. souleyez/ai/recommender.py +176 -144
  15. souleyez/ai/report_context.py +74 -73
  16. souleyez/ai/report_service.py +84 -66
  17. souleyez/ai/result_parser.py +222 -229
  18. souleyez/ai/safety.py +67 -44
  19. souleyez/auth/__init__.py +23 -22
  20. souleyez/auth/audit.py +36 -26
  21. souleyez/auth/engagement_access.py +65 -48
  22. souleyez/auth/permissions.py +14 -3
  23. souleyez/auth/session_manager.py +54 -37
  24. souleyez/auth/user_manager.py +109 -64
  25. souleyez/commands/audit.py +40 -43
  26. souleyez/commands/auth.py +35 -15
  27. souleyez/commands/deliverables.py +55 -50
  28. souleyez/commands/engagement.py +47 -28
  29. souleyez/commands/license.py +32 -23
  30. souleyez/commands/screenshots.py +36 -32
  31. souleyez/commands/user.py +82 -36
  32. souleyez/config.py +52 -44
  33. souleyez/core/credential_tester.py +87 -81
  34. souleyez/core/cve_mappings.py +179 -192
  35. souleyez/core/cve_matcher.py +162 -148
  36. souleyez/core/msf_auto_mapper.py +100 -83
  37. souleyez/core/msf_chain_engine.py +294 -256
  38. souleyez/core/msf_database.py +153 -70
  39. souleyez/core/msf_integration.py +679 -673
  40. souleyez/core/msf_rpc_client.py +40 -42
  41. souleyez/core/msf_rpc_manager.py +77 -79
  42. souleyez/core/msf_sync_manager.py +241 -181
  43. souleyez/core/network_utils.py +22 -15
  44. souleyez/core/parser_handler.py +34 -25
  45. souleyez/core/pending_chains.py +114 -63
  46. souleyez/core/templates.py +158 -107
  47. souleyez/core/tool_chaining.py +9526 -2879
  48. souleyez/core/version_utils.py +79 -94
  49. souleyez/core/vuln_correlation.py +136 -89
  50. souleyez/core/web_utils.py +33 -32
  51. souleyez/data/wordlists/ad_users.txt +378 -0
  52. souleyez/data/wordlists/api_endpoints_large.txt +769 -0
  53. souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
  54. souleyez/data/wordlists/lfi_payloads.txt +82 -0
  55. souleyez/data/wordlists/passwords_brute.txt +1548 -0
  56. souleyez/data/wordlists/passwords_crack.txt +2479 -0
  57. souleyez/data/wordlists/passwords_spray.txt +386 -0
  58. souleyez/data/wordlists/subdomains_large.txt +5057 -0
  59. souleyez/data/wordlists/usernames_common.txt +694 -0
  60. souleyez/data/wordlists/web_dirs_large.txt +4769 -0
  61. souleyez/detection/__init__.py +1 -1
  62. souleyez/detection/attack_signatures.py +12 -17
  63. souleyez/detection/mitre_mappings.py +61 -55
  64. souleyez/detection/validator.py +97 -86
  65. souleyez/devtools.py +23 -10
  66. souleyez/docs/README.md +4 -4
  67. souleyez/docs/api-reference/cli-commands.md +2 -2
  68. souleyez/docs/developer-guide/adding-new-tools.md +562 -0
  69. souleyez/docs/user-guide/auto-chaining.md +30 -8
  70. souleyez/docs/user-guide/getting-started.md +1 -1
  71. souleyez/docs/user-guide/installation.md +26 -3
  72. souleyez/docs/user-guide/metasploit-integration.md +2 -2
  73. souleyez/docs/user-guide/rbac.md +1 -1
  74. souleyez/docs/user-guide/scope-management.md +1 -1
  75. souleyez/docs/user-guide/siem-integration.md +1 -1
  76. souleyez/docs/user-guide/tools-reference.md +1 -8
  77. souleyez/docs/user-guide/worker-management.md +1 -1
  78. souleyez/engine/background.py +1239 -535
  79. souleyez/engine/base.py +4 -1
  80. souleyez/engine/job_status.py +17 -49
  81. souleyez/engine/log_sanitizer.py +103 -77
  82. souleyez/engine/manager.py +38 -7
  83. souleyez/engine/result_handler.py +2200 -1550
  84. souleyez/engine/worker_manager.py +50 -41
  85. souleyez/export/evidence_bundle.py +72 -62
  86. souleyez/feature_flags/features.py +16 -20
  87. souleyez/feature_flags.py +5 -9
  88. souleyez/handlers/__init__.py +11 -0
  89. souleyez/handlers/base.py +188 -0
  90. souleyez/handlers/bash_handler.py +277 -0
  91. souleyez/handlers/bloodhound_handler.py +243 -0
  92. souleyez/handlers/certipy_handler.py +311 -0
  93. souleyez/handlers/crackmapexec_handler.py +486 -0
  94. souleyez/handlers/dnsrecon_handler.py +344 -0
  95. souleyez/handlers/enum4linux_handler.py +400 -0
  96. souleyez/handlers/evil_winrm_handler.py +493 -0
  97. souleyez/handlers/ffuf_handler.py +815 -0
  98. souleyez/handlers/gobuster_handler.py +1114 -0
  99. souleyez/handlers/gpp_extract_handler.py +334 -0
  100. souleyez/handlers/hashcat_handler.py +444 -0
  101. souleyez/handlers/hydra_handler.py +563 -0
  102. souleyez/handlers/impacket_getuserspns_handler.py +343 -0
  103. souleyez/handlers/impacket_psexec_handler.py +222 -0
  104. souleyez/handlers/impacket_secretsdump_handler.py +426 -0
  105. souleyez/handlers/john_handler.py +286 -0
  106. souleyez/handlers/katana_handler.py +425 -0
  107. souleyez/handlers/kerbrute_handler.py +298 -0
  108. souleyez/handlers/ldapsearch_handler.py +636 -0
  109. souleyez/handlers/lfi_extract_handler.py +464 -0
  110. souleyez/handlers/msf_auxiliary_handler.py +408 -0
  111. souleyez/handlers/msf_exploit_handler.py +380 -0
  112. souleyez/handlers/nikto_handler.py +413 -0
  113. souleyez/handlers/nmap_handler.py +821 -0
  114. souleyez/handlers/nuclei_handler.py +359 -0
  115. souleyez/handlers/nxc_handler.py +371 -0
  116. souleyez/handlers/rdp_sec_check_handler.py +353 -0
  117. souleyez/handlers/registry.py +292 -0
  118. souleyez/handlers/responder_handler.py +232 -0
  119. souleyez/handlers/service_explorer_handler.py +434 -0
  120. souleyez/handlers/smbclient_handler.py +344 -0
  121. souleyez/handlers/smbmap_handler.py +510 -0
  122. souleyez/handlers/smbpasswd_handler.py +296 -0
  123. souleyez/handlers/sqlmap_handler.py +1116 -0
  124. souleyez/handlers/theharvester_handler.py +601 -0
  125. souleyez/handlers/web_login_test_handler.py +327 -0
  126. souleyez/handlers/whois_handler.py +277 -0
  127. souleyez/handlers/wpscan_handler.py +554 -0
  128. souleyez/history.py +32 -16
  129. souleyez/importers/msf_importer.py +106 -75
  130. souleyez/importers/smart_importer.py +208 -147
  131. souleyez/integrations/siem/__init__.py +10 -10
  132. souleyez/integrations/siem/base.py +17 -18
  133. souleyez/integrations/siem/elastic.py +108 -122
  134. souleyez/integrations/siem/factory.py +207 -80
  135. souleyez/integrations/siem/googlesecops.py +146 -154
  136. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  137. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  138. souleyez/integrations/siem/sentinel.py +107 -109
  139. souleyez/integrations/siem/splunk.py +246 -212
  140. souleyez/integrations/siem/wazuh.py +65 -71
  141. souleyez/integrations/wazuh/__init__.py +5 -5
  142. souleyez/integrations/wazuh/client.py +70 -93
  143. souleyez/integrations/wazuh/config.py +85 -57
  144. souleyez/integrations/wazuh/host_mapper.py +28 -36
  145. souleyez/integrations/wazuh/sync.py +78 -68
  146. souleyez/intelligence/__init__.py +4 -5
  147. souleyez/intelligence/correlation_analyzer.py +309 -295
  148. souleyez/intelligence/exploit_knowledge.py +661 -623
  149. souleyez/intelligence/exploit_suggestions.py +159 -139
  150. souleyez/intelligence/gap_analyzer.py +132 -97
  151. souleyez/intelligence/gap_detector.py +251 -214
  152. souleyez/intelligence/sensitive_tables.py +266 -129
  153. souleyez/intelligence/service_parser.py +137 -123
  154. souleyez/intelligence/surface_analyzer.py +407 -268
  155. souleyez/intelligence/target_parser.py +159 -162
  156. souleyez/licensing/__init__.py +6 -6
  157. souleyez/licensing/validator.py +17 -19
  158. souleyez/log_config.py +79 -54
  159. souleyez/main.py +1505 -687
  160. souleyez/migrations/fix_job_counter.py +16 -14
  161. souleyez/parsers/bloodhound_parser.py +41 -39
  162. souleyez/parsers/crackmapexec_parser.py +178 -111
  163. souleyez/parsers/dalfox_parser.py +72 -77
  164. souleyez/parsers/dnsrecon_parser.py +103 -91
  165. souleyez/parsers/enum4linux_parser.py +183 -153
  166. souleyez/parsers/ffuf_parser.py +29 -25
  167. souleyez/parsers/gobuster_parser.py +301 -41
  168. souleyez/parsers/hashcat_parser.py +324 -79
  169. souleyez/parsers/http_fingerprint_parser.py +350 -103
  170. souleyez/parsers/hydra_parser.py +131 -111
  171. souleyez/parsers/impacket_parser.py +231 -178
  172. souleyez/parsers/john_parser.py +98 -86
  173. souleyez/parsers/katana_parser.py +316 -0
  174. souleyez/parsers/msf_parser.py +943 -498
  175. souleyez/parsers/nikto_parser.py +346 -65
  176. souleyez/parsers/nmap_parser.py +262 -174
  177. souleyez/parsers/nuclei_parser.py +40 -44
  178. souleyez/parsers/responder_parser.py +26 -26
  179. souleyez/parsers/searchsploit_parser.py +74 -74
  180. souleyez/parsers/service_explorer_parser.py +279 -0
  181. souleyez/parsers/smbmap_parser.py +180 -124
  182. souleyez/parsers/sqlmap_parser.py +434 -308
  183. souleyez/parsers/theharvester_parser.py +75 -57
  184. souleyez/parsers/whois_parser.py +135 -94
  185. souleyez/parsers/wpscan_parser.py +278 -190
  186. souleyez/plugins/afp.py +44 -36
  187. souleyez/plugins/afp_brute.py +114 -46
  188. souleyez/plugins/ard.py +48 -37
  189. souleyez/plugins/bloodhound.py +95 -61
  190. souleyez/plugins/certipy.py +303 -0
  191. souleyez/plugins/crackmapexec.py +186 -85
  192. souleyez/plugins/dalfox.py +120 -59
  193. souleyez/plugins/dns_hijack.py +146 -41
  194. souleyez/plugins/dnsrecon.py +97 -61
  195. souleyez/plugins/enum4linux.py +91 -66
  196. souleyez/plugins/evil_winrm.py +291 -0
  197. souleyez/plugins/ffuf.py +166 -90
  198. souleyez/plugins/firmware_extract.py +133 -29
  199. souleyez/plugins/gobuster.py +387 -190
  200. souleyez/plugins/gpp_extract.py +393 -0
  201. souleyez/plugins/hashcat.py +100 -73
  202. souleyez/plugins/http_fingerprint.py +854 -267
  203. souleyez/plugins/hydra.py +566 -200
  204. souleyez/plugins/impacket_getnpusers.py +117 -69
  205. souleyez/plugins/impacket_psexec.py +84 -64
  206. souleyez/plugins/impacket_secretsdump.py +103 -69
  207. souleyez/plugins/impacket_smbclient.py +89 -75
  208. souleyez/plugins/john.py +86 -69
  209. souleyez/plugins/katana.py +313 -0
  210. souleyez/plugins/kerbrute.py +237 -0
  211. souleyez/plugins/lfi_extract.py +541 -0
  212. souleyez/plugins/macos_ssh.py +117 -48
  213. souleyez/plugins/mdns.py +35 -30
  214. souleyez/plugins/msf_auxiliary.py +253 -130
  215. souleyez/plugins/msf_exploit.py +239 -161
  216. souleyez/plugins/nikto.py +134 -78
  217. souleyez/plugins/nmap.py +275 -91
  218. souleyez/plugins/nuclei.py +180 -89
  219. souleyez/plugins/nxc.py +285 -0
  220. souleyez/plugins/plugin_base.py +35 -36
  221. souleyez/plugins/plugin_template.py +13 -5
  222. souleyez/plugins/rdp_sec_check.py +130 -0
  223. souleyez/plugins/responder.py +112 -71
  224. souleyez/plugins/router_http_brute.py +76 -65
  225. souleyez/plugins/router_ssh_brute.py +118 -41
  226. souleyez/plugins/router_telnet_brute.py +124 -42
  227. souleyez/plugins/routersploit.py +91 -59
  228. souleyez/plugins/routersploit_exploit.py +77 -55
  229. souleyez/plugins/searchsploit.py +91 -77
  230. souleyez/plugins/service_explorer.py +1160 -0
  231. souleyez/plugins/smbmap.py +122 -72
  232. souleyez/plugins/smbpasswd.py +215 -0
  233. souleyez/plugins/sqlmap.py +301 -113
  234. souleyez/plugins/theharvester.py +127 -75
  235. souleyez/plugins/tr069.py +79 -57
  236. souleyez/plugins/upnp.py +65 -47
  237. souleyez/plugins/upnp_abuse.py +73 -55
  238. souleyez/plugins/vnc_access.py +129 -42
  239. souleyez/plugins/vnc_brute.py +109 -38
  240. souleyez/plugins/web_login_test.py +417 -0
  241. souleyez/plugins/whois.py +77 -58
  242. souleyez/plugins/wpscan.py +173 -69
  243. souleyez/reporting/__init__.py +2 -1
  244. souleyez/reporting/attack_chain.py +411 -346
  245. souleyez/reporting/charts.py +436 -501
  246. souleyez/reporting/compliance_mappings.py +334 -201
  247. souleyez/reporting/detection_report.py +126 -125
  248. souleyez/reporting/formatters.py +828 -591
  249. souleyez/reporting/generator.py +386 -302
  250. souleyez/reporting/metrics.py +72 -75
  251. souleyez/scanner.py +35 -29
  252. souleyez/security/__init__.py +37 -11
  253. souleyez/security/scope_validator.py +175 -106
  254. souleyez/security/validation.py +223 -149
  255. souleyez/security.py +22 -6
  256. souleyez/storage/credentials.py +247 -186
  257. souleyez/storage/crypto.py +296 -129
  258. souleyez/storage/database.py +73 -50
  259. souleyez/storage/db.py +58 -36
  260. souleyez/storage/deliverable_evidence.py +177 -128
  261. souleyez/storage/deliverable_exporter.py +282 -246
  262. souleyez/storage/deliverable_templates.py +134 -116
  263. souleyez/storage/deliverables.py +135 -130
  264. souleyez/storage/engagements.py +109 -56
  265. souleyez/storage/evidence.py +181 -152
  266. souleyez/storage/execution_log.py +31 -17
  267. souleyez/storage/exploit_attempts.py +93 -57
  268. souleyez/storage/exploits.py +67 -36
  269. souleyez/storage/findings.py +48 -61
  270. souleyez/storage/hosts.py +176 -144
  271. souleyez/storage/migrate_to_engagements.py +43 -19
  272. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  273. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  274. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  275. souleyez/storage/migrations/_005_screenshots.py +13 -5
  276. souleyez/storage/migrations/_006_deliverables.py +13 -5
  277. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  278. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  279. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  280. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  281. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  282. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  283. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  284. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  285. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  286. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  287. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  288. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  289. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  290. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  291. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  292. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  293. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  294. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  295. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  296. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  297. souleyez/storage/migrations/__init__.py +26 -26
  298. souleyez/storage/migrations/migration_manager.py +19 -19
  299. souleyez/storage/msf_sessions.py +100 -65
  300. souleyez/storage/osint.py +17 -24
  301. souleyez/storage/recommendation_engine.py +269 -235
  302. souleyez/storage/screenshots.py +33 -32
  303. souleyez/storage/smb_shares.py +136 -92
  304. souleyez/storage/sqlmap_data.py +183 -128
  305. souleyez/storage/team_collaboration.py +135 -141
  306. souleyez/storage/timeline_tracker.py +122 -94
  307. souleyez/storage/wazuh_vulns.py +64 -66
  308. souleyez/storage/web_paths.py +33 -37
  309. souleyez/testing/credential_tester.py +221 -205
  310. souleyez/ui/__init__.py +1 -1
  311. souleyez/ui/ai_quotes.py +12 -12
  312. souleyez/ui/attack_surface.py +2439 -1516
  313. souleyez/ui/chain_rules_view.py +914 -382
  314. souleyez/ui/correlation_view.py +312 -230
  315. souleyez/ui/dashboard.py +2382 -1130
  316. souleyez/ui/deliverables_view.py +148 -62
  317. souleyez/ui/design_system.py +13 -13
  318. souleyez/ui/errors.py +49 -49
  319. souleyez/ui/evidence_linking_view.py +284 -179
  320. souleyez/ui/evidence_vault.py +393 -285
  321. souleyez/ui/exploit_suggestions_view.py +555 -349
  322. souleyez/ui/export_view.py +100 -66
  323. souleyez/ui/gap_analysis_view.py +315 -171
  324. souleyez/ui/help_system.py +105 -97
  325. souleyez/ui/intelligence_view.py +436 -293
  326. souleyez/ui/interactive.py +22827 -10678
  327. souleyez/ui/interactive_selector.py +75 -68
  328. souleyez/ui/log_formatter.py +47 -39
  329. souleyez/ui/menu_components.py +22 -13
  330. souleyez/ui/msf_auxiliary_menu.py +184 -133
  331. souleyez/ui/pending_chains_view.py +336 -172
  332. souleyez/ui/progress_indicators.py +5 -3
  333. souleyez/ui/recommendations_view.py +195 -137
  334. souleyez/ui/rule_builder.py +343 -225
  335. souleyez/ui/setup_wizard.py +678 -284
  336. souleyez/ui/shortcuts.py +217 -165
  337. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  338. souleyez/ui/splunk_vulns_view.py +139 -86
  339. souleyez/ui/team_dashboard.py +498 -335
  340. souleyez/ui/template_selector.py +196 -105
  341. souleyez/ui/terminal.py +6 -6
  342. souleyez/ui/timeline_view.py +198 -127
  343. souleyez/ui/tool_setup.py +264 -164
  344. souleyez/ui/tutorial.py +202 -72
  345. souleyez/ui/tutorial_state.py +40 -40
  346. souleyez/ui/wazuh_vulns_view.py +235 -141
  347. souleyez/ui/wordlist_browser.py +260 -107
  348. souleyez/ui.py +464 -312
  349. souleyez/utils/tool_checker.py +427 -367
  350. souleyez/utils.py +33 -29
  351. souleyez/wordlists.py +134 -167
  352. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
  353. souleyez-2.43.34.dist-info/RECORD +443 -0
  354. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.29.dist-info/RECORD +0 -379
  356. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
souleyez/ui/tutorial.py CHANGED
@@ -18,6 +18,7 @@ from souleyez.ui.tutorial_state import get_tutorial_state, TutorialStep
18
18
  def clear_screen():
19
19
  """Clear the terminal screen."""
20
20
  from souleyez.ui.design_system import DesignSystem
21
+
21
22
  DesignSystem.clear_screen()
22
23
 
23
24
 
@@ -29,11 +30,11 @@ def wait_for_enter(prompt="Press Enter to continue..."):
29
30
  def show_header(title, subtitle=None):
30
31
  """Display a section header."""
31
32
  click.echo()
32
- click.echo(click.style("=" * 60, fg='cyan'))
33
- click.echo(click.style(f" {title}", fg='cyan', bold=True))
33
+ click.echo(click.style("=" * 60, fg="cyan"))
34
+ click.echo(click.style(f" {title}", fg="cyan", bold=True))
34
35
  if subtitle:
35
- click.echo(click.style(f" {subtitle}", fg='white'))
36
- click.echo(click.style("=" * 60, fg='cyan'))
36
+ click.echo(click.style(f" {subtitle}", fg="white"))
37
+ click.echo(click.style("=" * 60, fg="cyan"))
37
38
  click.echo()
38
39
 
39
40
 
@@ -58,7 +59,11 @@ def run_tutorial():
58
59
  click.echo(" 2. Creating your first engagement")
59
60
  click.echo(" 3. Enabling auto-chaining")
60
61
  click.echo(" 4. Running reconnaissance scans")
61
- click.echo(" 5. " + click.style("Hands-on exploration", fg='yellow') + " of the Command Center")
62
+ click.echo(
63
+ " 5. "
64
+ + click.style("Hands-on exploration", fg="yellow")
65
+ + " of the Command Center"
66
+ )
62
67
  click.echo()
63
68
 
64
69
  wait_for_enter()
@@ -67,17 +72,39 @@ def run_tutorial():
67
72
  clear_screen()
68
73
  show_header("Step 1: Understanding Engagements")
69
74
 
70
- click.echo("An " + click.style("engagement", fg='cyan', bold=True) + " is a container for all your pentest data.")
75
+ click.echo(
76
+ "An "
77
+ + click.style("engagement", fg="cyan", bold=True)
78
+ + " is a container for all your pentest data."
79
+ )
71
80
  click.echo()
72
81
  click.echo("Think of it like a project folder that keeps everything organized:")
73
82
  click.echo()
74
- click.echo(" • " + click.style("Hosts", fg='green') + " - Target systems you discover")
75
- click.echo(" • " + click.style("Services", fg='green') + " - Open ports and running software")
76
- click.echo(" • " + click.style("Findings", fg='green') + " - Vulnerabilities you find")
77
- click.echo(" • " + click.style("Credentials", fg='green') + " - Usernames, passwords, keys")
78
- click.echo(" • " + click.style("Evidence", fg='green') + " - Screenshots, logs, artifacts")
79
- click.echo()
80
- click.echo("Each engagement is " + click.style("isolated", fg='yellow') + " - data from one")
83
+ click.echo(
84
+ " • " + click.style("Hosts", fg="green") + " - Target systems you discover"
85
+ )
86
+ click.echo(
87
+ " • "
88
+ + click.style("Services", fg="green")
89
+ + " - Open ports and running software"
90
+ )
91
+ click.echo(
92
+ " • " + click.style("Findings", fg="green") + " - Vulnerabilities you find"
93
+ )
94
+ click.echo(
95
+ " • "
96
+ + click.style("Credentials", fg="green")
97
+ + " - Usernames, passwords, keys"
98
+ )
99
+ click.echo(
100
+ " • " + click.style("Evidence", fg="green") + " - Screenshots, logs, artifacts"
101
+ )
102
+ click.echo()
103
+ click.echo(
104
+ "Each engagement is "
105
+ + click.style("isolated", fg="yellow")
106
+ + " - data from one"
107
+ )
81
108
  click.echo("pentest never mixes with another.")
82
109
  click.echo()
83
110
 
@@ -89,9 +116,11 @@ def run_tutorial():
89
116
 
90
117
  click.echo("Let's create a test engagement to practice with.")
91
118
  click.echo()
92
- click.echo("We'll use a " + click.style("safe, legal target", fg='green', bold=True) + ":")
119
+ click.echo(
120
+ "We'll use a " + click.style("safe, legal target", fg="green", bold=True) + ":"
121
+ )
93
122
  click.echo()
94
- click.echo(" " + click.style("vulnweb.com", fg='cyan'))
123
+ click.echo(" " + click.style("vulnweb.com", fg="cyan"))
95
124
  click.echo(" (Acunetix's intentionally vulnerable test domain)")
96
125
  click.echo()
97
126
 
@@ -111,14 +140,16 @@ def run_tutorial():
111
140
  # Check if tutorial engagement already exists
112
141
  existing = em.get("Tutorial Engagement")
113
142
  if existing:
114
- engagement_id = existing['id']
115
- click.echo(click.style("✓ Using existing Tutorial Engagement", fg='green'))
143
+ engagement_id = existing["id"]
144
+ click.echo(click.style("✓ Using existing Tutorial Engagement", fg="green"))
116
145
  else:
117
146
  engagement_id = em.create(
118
147
  name="Tutorial Engagement",
119
- description="Created by SoulEyez tutorial. Target: vulnweb.com"
148
+ description="Created by SoulEyez tutorial. Target: vulnweb.com",
149
+ )
150
+ click.echo(
151
+ click.style("✓ Created engagement: Tutorial Engagement", fg="green")
120
152
  )
121
- click.echo(click.style("✓ Created engagement: Tutorial Engagement", fg='green'))
122
153
 
123
154
  # Set as current engagement
124
155
  em.set_current("Tutorial Engagement")
@@ -126,7 +157,7 @@ def run_tutorial():
126
157
  click.echo()
127
158
 
128
159
  except Exception as e:
129
- click.echo(click.style(f"✗ Error creating engagement: {e}", fg='red'))
160
+ click.echo(click.style(f"✗ Error creating engagement: {e}", fg="red"))
130
161
  click.echo("Please check 'souleyez doctor' for issues.")
131
162
  wait_for_enter()
132
163
  return
@@ -137,13 +168,24 @@ def run_tutorial():
137
168
  clear_screen()
138
169
  show_header("Step 3: Enable Auto-Chaining")
139
170
 
140
- click.echo("Before we scan, let's enable " + click.style("auto-chaining", fg='yellow', bold=True) + "!")
171
+ click.echo(
172
+ "Before we scan, let's enable "
173
+ + click.style("auto-chaining", fg="yellow", bold=True)
174
+ + "!"
175
+ )
141
176
  click.echo()
142
177
  click.echo("Auto-chaining is SoulEyez's killer feature:")
143
178
  click.echo()
144
- click.echo(" • When recon finds a host → " + click.style("auto-queue nmap", fg='cyan'))
145
- click.echo(" • When nmap finds HTTP → " + click.style("auto-queue gobuster, nikto", fg='cyan'))
146
- click.echo(" • When nmap finds SSH → " + click.style("auto-queue hydra", fg='cyan'))
179
+ click.echo(
180
+ " • When recon finds a host → " + click.style("auto-queue nmap", fg="cyan")
181
+ )
182
+ click.echo(
183
+ " • When nmap finds HTTP → "
184
+ + click.style("auto-queue gobuster, nikto", fg="cyan")
185
+ )
186
+ click.echo(
187
+ " • When nmap finds SSH → " + click.style("auto-queue hydra", fg="cyan")
188
+ )
147
189
  click.echo()
148
190
  click.echo("It thinks like a pentester and queues the logical next steps!")
149
191
  click.echo()
@@ -151,14 +193,19 @@ def run_tutorial():
151
193
  if click.confirm("Enable auto-chaining for this tutorial?", default=True):
152
194
  try:
153
195
  from souleyez.core.tool_chaining import ToolChaining
196
+
154
197
  chaining = ToolChaining()
155
198
  chaining.enable_chaining()
156
199
  click.echo()
157
- click.echo(click.style(" ✓ Auto-chaining ENABLED!", fg='green', bold=True))
158
- click.echo(" Mode: " + click.style("AUTO", fg='green') + " (chains execute immediately)")
200
+ click.echo(click.style(" ✓ Auto-chaining ENABLED!", fg="green", bold=True))
201
+ click.echo(
202
+ " Mode: "
203
+ + click.style("AUTO", fg="green")
204
+ + " (chains execute immediately)"
205
+ )
159
206
  click.echo()
160
207
  except Exception as e:
161
- click.echo(click.style(f" Could not enable: {e}", fg='yellow'))
208
+ click.echo(click.style(f" Could not enable: {e}", fg="yellow"))
162
209
  click.echo()
163
210
  else:
164
211
  click.echo()
@@ -171,23 +218,49 @@ def run_tutorial():
171
218
  clear_screen()
172
219
  show_header("Step 4: Running Your First Recon")
173
220
 
174
- click.echo("Now let's run " + click.style("passive reconnaissance", fg='cyan', bold=True) + "!")
175
- click.echo()
176
- click.echo("We'll use " + click.style("theHarvester", fg='green', bold=True) + " to search URLScan.io")
221
+ click.echo(
222
+ "Now let's run "
223
+ + click.style("passive reconnaissance", fg="cyan", bold=True)
224
+ + "!"
225
+ )
226
+ click.echo()
227
+ click.echo(
228
+ "We'll use "
229
+ + click.style("theHarvester", fg="green", bold=True)
230
+ + " to search URLScan.io"
231
+ )
177
232
  click.echo("for emails, subdomains, and IPs related to our target.")
178
233
  click.echo()
179
- click.echo("With " + click.style("auto-chaining ON", fg='yellow', bold=True) + ", when theHarvester finds hosts:")
180
- click.echo(" " + click.style("whois", fg='cyan') + " and " + click.style("dnsrecon", fg='cyan') + " will auto-queue")
181
- click.echo(" → Found web servers trigger " + click.style("nmap", fg='cyan') + " scans")
234
+ click.echo(
235
+ "With "
236
+ + click.style("auto-chaining ON", fg="yellow", bold=True)
237
+ + ", when theHarvester finds hosts:"
238
+ )
239
+ click.echo(
240
+ " → "
241
+ + click.style("whois", fg="cyan")
242
+ + " and "
243
+ + click.style("dnsrecon", fg="cyan")
244
+ + " will auto-queue"
245
+ )
246
+ click.echo(
247
+ " → Found web servers trigger " + click.style("nmap", fg="cyan") + " scans"
248
+ )
182
249
  click.echo(" → And so on... SoulEyez chains tools intelligently!")
183
250
  click.echo()
184
251
 
185
252
  target_domain = "vulnweb.com"
186
253
 
187
- queue_scan = click.confirm(f"Queue theHarvester against {target_domain}?", default=True)
254
+ queue_scan = click.confirm(
255
+ f"Queue theHarvester against {target_domain}?", default=True
256
+ )
188
257
  if queue_scan:
189
258
  click.echo()
190
- click.echo(click.style(" ✓ Scan will start when you enter the Command Center!", fg='green'))
259
+ click.echo(
260
+ click.style(
261
+ " ✓ Scan will start when you enter the Command Center!", fg="green"
262
+ )
263
+ )
191
264
  click.echo()
192
265
  else:
193
266
  click.echo()
@@ -200,21 +273,41 @@ def run_tutorial():
200
273
  clear_screen()
201
274
  show_header("Step 5: Entering the Command Center")
202
275
 
203
- click.echo("Now for the " + click.style("hands-on", fg='yellow', bold=True) + " part!")
276
+ click.echo(
277
+ "Now for the " + click.style("hands-on", fg="yellow", bold=True) + " part!"
278
+ )
204
279
  click.echo()
205
- click.echo("We're about to launch you into the " + click.style("Command Center", fg='cyan', bold=True) + " (dashboard).")
280
+ click.echo(
281
+ "We're about to launch you into the "
282
+ + click.style("Command Center", fg="cyan", bold=True)
283
+ + " (dashboard)."
284
+ )
206
285
  click.echo()
207
- click.echo("The tutorial will continue there with " + click.style("interactive hints", fg='green') + ":")
286
+ click.echo(
287
+ "The tutorial will continue there with "
288
+ + click.style("interactive hints", fg="green")
289
+ + ":"
290
+ )
208
291
  click.echo()
209
292
  click.echo(" • You'll see your queued scans running")
210
293
  click.echo(" • Hints will guide you to explore different views")
211
294
  click.echo(" • Press the highlighted keys to navigate")
212
295
  click.echo()
213
296
  click.echo(click.style("Quick reference for the Command Center:", bold=True))
214
- click.echo(" " + click.style("[j]", fg='yellow') + " Jobs queue - See running scans")
215
- click.echo(" " + click.style("[h]", fg='yellow') + " Hosts view - See discovered targets")
216
- click.echo(" " + click.style("[f]", fg='yellow') + " Findings - See vulnerabilities")
217
- click.echo(" " + click.style("[q]", fg='yellow') + " Quit - Exit dashboard")
297
+ click.echo(
298
+ " " + click.style("[j]", fg="yellow") + " Jobs queue - See running scans"
299
+ )
300
+ click.echo(
301
+ " "
302
+ + click.style("[h]", fg="yellow")
303
+ + " Hosts view - See discovered targets"
304
+ )
305
+ click.echo(
306
+ " " + click.style("[f]", fg="yellow") + " Findings - See vulnerabilities"
307
+ )
308
+ click.echo(
309
+ " " + click.style("[q]", fg="yellow") + " Quit - Exit dashboard"
310
+ )
218
311
  click.echo()
219
312
 
220
313
  if click.confirm("Ready to enter the Command Center?", default=True):
@@ -226,29 +319,35 @@ def run_tutorial():
226
319
  if queue_scan:
227
320
  try:
228
321
  from souleyez.engine.background import enqueue_job
322
+
229
323
  click.echo()
230
324
  click.echo("Starting scan...")
231
325
  job_id = enqueue_job(
232
- tool='theharvester',
326
+ tool="theharvester",
233
327
  target=target_domain,
234
- args=['-b', 'urlscan', '-l', '500'],
235
- label='tutorial-recon',
236
- engagement_id=engagement_id
328
+ args=["-b", "urlscan", "-l", "500"],
329
+ label="tutorial-recon",
330
+ engagement_id=engagement_id,
331
+ )
332
+ click.echo(
333
+ click.style(f" ✓ theHarvester queued (Job #{job_id})", fg="green")
237
334
  )
238
- click.echo(click.style(f" ✓ theHarvester queued (Job #{job_id})", fg='green'))
239
335
  except Exception as e:
240
- click.echo(click.style(f" Note: Could not queue scan: {e}", fg='yellow'))
336
+ click.echo(
337
+ click.style(f" Note: Could not queue scan: {e}", fg="yellow")
338
+ )
241
339
 
242
340
  click.echo()
243
- click.echo(click.style("Launching Command Center...", fg='cyan'))
341
+ click.echo(click.style("Launching Command Center...", fg="cyan"))
244
342
  time.sleep(1)
245
343
 
246
344
  # Launch the dashboard with tutorial mode active
247
345
  try:
248
346
  from souleyez.ui.dashboard import run_dashboard
347
+
249
348
  run_dashboard()
250
349
  except Exception as e:
251
- click.echo(click.style(f"Could not launch dashboard: {e}", fg='red'))
350
+ click.echo(click.style(f"Could not launch dashboard: {e}", fg="red"))
252
351
  click.echo("You can launch it manually with: souleyez dashboard")
253
352
 
254
353
  # After dashboard exits, show completion
@@ -256,7 +355,7 @@ def run_tutorial():
256
355
  else:
257
356
  click.echo()
258
357
  click.echo("No problem! You can explore the Command Center anytime with:")
259
- click.echo(click.style(" souleyez dashboard", fg='cyan'))
358
+ click.echo(click.style(" souleyez dashboard", fg="cyan"))
260
359
  click.echo()
261
360
  _show_tutorial_complete()
262
361
 
@@ -270,7 +369,11 @@ def _show_tutorial_complete():
270
369
  clear_screen()
271
370
  show_header("Tutorial Complete!")
272
371
 
273
- click.echo(click.style("You're ready to start pentesting with SoulEyez!", fg='green', bold=True))
372
+ click.echo(
373
+ click.style(
374
+ "You're ready to start pentesting with SoulEyez!", fg="green", bold=True
375
+ )
376
+ )
274
377
  click.echo()
275
378
  click.echo("What you learned:")
276
379
  click.echo(" ✓ Engagements organize your pentest data")
@@ -280,21 +383,38 @@ def _show_tutorial_complete():
280
383
  click.echo()
281
384
  click.echo(click.style("Quick reference:", bold=True))
282
385
  click.echo()
283
- click.echo(" " + click.style("souleyez dashboard", fg='cyan') + " - Real-time Command Center")
284
- click.echo(" " + click.style("souleyez interactive", fg='cyan') + " - Menu-driven interface")
285
- click.echo(" " + click.style("souleyez setup", fg='cyan') + " - Install pentest tools")
286
- click.echo(" " + click.style("souleyez doctor", fg='cyan') + " - Diagnose issues")
386
+ click.echo(
387
+ " "
388
+ + click.style("souleyez dashboard", fg="cyan")
389
+ + " - Real-time Command Center"
390
+ )
391
+ click.echo(
392
+ " "
393
+ + click.style("souleyez interactive", fg="cyan")
394
+ + " - Menu-driven interface"
395
+ )
396
+ click.echo(
397
+ " "
398
+ + click.style("souleyez setup", fg="cyan")
399
+ + " - Install pentest tools"
400
+ )
401
+ click.echo(
402
+ " " + click.style("souleyez doctor", fg="cyan") + " - Diagnose issues"
403
+ )
287
404
  click.echo()
288
405
  click.echo(click.style("Practice targets (safe & legal):", bold=True))
289
406
  click.echo(" • vulnweb.com subdomains (Acunetix)")
290
407
  click.echo(" • demo.owasp-juice.shop (OWASP)")
291
408
  click.echo()
292
- click.echo(click.style("⚠️ NEVER scan systems without permission!", fg='red', bold=True))
409
+ click.echo(
410
+ click.style("⚠️ NEVER scan systems without permission!", fg="red", bold=True)
411
+ )
293
412
  click.echo()
294
413
 
295
414
  # Always disable auto-chaining (it's a PRO feature, tutorial enabled for demo)
296
415
  try:
297
416
  from souleyez.core.tool_chaining import ToolChaining
417
+
298
418
  chaining = ToolChaining()
299
419
  chaining.disable_chaining()
300
420
  except Exception:
@@ -304,16 +424,27 @@ def _show_tutorial_complete():
304
424
  click.echo()
305
425
  if click.confirm("Clean up tutorial engagement and jobs?", default=True):
306
426
  _cleanup_tutorial_data()
307
- click.echo(click.style(" ✓ Tutorial data cleaned up!", fg='green'))
427
+ click.echo(click.style(" ✓ Tutorial data cleaned up!", fg="green"))
308
428
  else:
309
- click.echo("Tutorial engagement kept. Check results with: " + click.style("souleyez interactive", fg='cyan'))
429
+ click.echo(
430
+ "Tutorial engagement kept. Check results with: "
431
+ + click.style("souleyez interactive", fg="cyan")
432
+ )
310
433
 
311
434
  click.echo()
312
435
  click.echo("Need help?")
313
- click.echo(" • In-app: Press " + click.style("[?]", fg='yellow') + " for contextual help")
314
- click.echo(" • Full docs: " + click.style("souleyez interactive", fg='cyan') + " → Settings → " + click.style("[h]", fg='yellow') + " Help Center")
436
+ click.echo(
437
+ " • In-app: Press " + click.style("[?]", fg="yellow") + " for contextual help"
438
+ )
439
+ click.echo(
440
+ " • Full docs: "
441
+ + click.style("souleyez interactive", fg="cyan")
442
+ + " → Settings → "
443
+ + click.style("[h]", fg="yellow")
444
+ + " Help Center"
445
+ )
315
446
  click.echo()
316
- click.echo(click.style("Happy hacking! 🎯", fg='cyan', bold=True))
447
+ click.echo(click.style("Happy hacking! 🎯", fg="cyan", bold=True))
317
448
  click.echo()
318
449
 
319
450
  wait_for_enter("Press Enter to exit tutorial...")
@@ -337,21 +468,20 @@ def _cleanup_tutorial_data():
337
468
 
338
469
  # Find tutorial engagement
339
470
  tutorial_eng = em.get("Tutorial Engagement")
340
- engagement_id = tutorial_eng['id'] if tutorial_eng else None
471
+ engagement_id = tutorial_eng["id"] if tutorial_eng else None
341
472
 
342
473
  # Delete all tutorial jobs (by label OR by engagement_id)
343
474
  all_jobs = list_jobs(limit=500)
344
475
  for job in all_jobs:
345
- is_tutorial_job = (
346
- job.get('label') == 'tutorial-recon' or
347
- (engagement_id and job.get('engagement_id') == engagement_id)
476
+ is_tutorial_job = job.get("label") == "tutorial-recon" or (
477
+ engagement_id and job.get("engagement_id") == engagement_id
348
478
  )
349
479
  if is_tutorial_job:
350
480
  try:
351
481
  # Kill if running, then delete
352
- if job.get('status') in ('running', 'queued'):
353
- kill_job(job['id'])
354
- delete_job(job['id'])
482
+ if job.get("status") in ("running", "queued"):
483
+ kill_job(job["id"])
484
+ delete_job(job["id"])
355
485
  except Exception:
356
486
  pass
357
487
 
@@ -359,15 +489,15 @@ def _cleanup_tutorial_data():
359
489
  # (otherwise get_current() will fail trying to load deleted engagement)
360
490
  if tutorial_eng:
361
491
  current = em.get_current()
362
- if current and current.get('id') == tutorial_eng['id']:
492
+ if current and current.get("id") == tutorial_eng["id"]:
363
493
  em.set_current("default")
364
494
 
365
495
  # Now safe to delete the tutorial engagement
366
496
  em.delete("Tutorial Engagement")
367
497
 
368
498
  except Exception as e:
369
- click.echo(click.style(f" Note: Could not fully clean up: {e}", fg='yellow'))
499
+ click.echo(click.style(f" Note: Could not fully clean up: {e}", fg="yellow"))
370
500
 
371
501
 
372
- if __name__ == '__main__':
502
+ if __name__ == "__main__":
373
503
  run_tutorial()
@@ -13,22 +13,23 @@ import json
13
13
 
14
14
  class TutorialStep(Enum):
15
15
  """Tutorial steps in order."""
16
- INACTIVE = auto() # Tutorial not running
17
- WELCOME = auto() # Welcome screen
18
- ENGAGEMENT_EXPLAIN = auto() # Explaining engagements
16
+
17
+ INACTIVE = auto() # Tutorial not running
18
+ WELCOME = auto() # Welcome screen
19
+ ENGAGEMENT_EXPLAIN = auto() # Explaining engagements
19
20
  ENGAGEMENT_CREATE = auto() # Creating first engagement
20
- AUTOCHAIN_ENABLE = auto() # Enabling auto-chaining
21
- SCANS_QUEUE = auto() # Queuing recon scans
22
- DASHBOARD_INTRO = auto() # Entered dashboard for first time
23
- VIEW_JOBS = auto() # Hint: press [j] for jobs
24
- IN_JOB_QUEUE = auto() # User is in job queue
25
- VIEW_OSINT = auto() # Hint: press [o] for OSINT
26
- IN_OSINT_VIEW = auto() # User is viewing OSINT
27
- VIEW_HOSTS = auto() # Hint: press [h] for hosts
28
- IN_HOSTS_VIEW = auto() # User is viewing hosts
29
- VIEW_JOB_DETAILS = auto() # Hint: press [j] again for job details
30
- IN_JOB_DETAILS = auto() # User viewing job details (interactive mode)
31
- COMPLETE = auto() # Tutorial complete
21
+ AUTOCHAIN_ENABLE = auto() # Enabling auto-chaining
22
+ SCANS_QUEUE = auto() # Queuing recon scans
23
+ DASHBOARD_INTRO = auto() # Entered dashboard for first time
24
+ VIEW_JOBS = auto() # Hint: press [j] for jobs
25
+ IN_JOB_QUEUE = auto() # User is in job queue
26
+ VIEW_OSINT = auto() # Hint: press [o] for OSINT
27
+ IN_OSINT_VIEW = auto() # User is viewing OSINT
28
+ VIEW_HOSTS = auto() # Hint: press [h] for hosts
29
+ IN_HOSTS_VIEW = auto() # User is viewing hosts
30
+ VIEW_JOB_DETAILS = auto() # Hint: press [j] again for job details
31
+ IN_JOB_DETAILS = auto() # User viewing job details (interactive mode)
32
+ COMPLETE = auto() # Tutorial complete
32
33
 
33
34
 
34
35
  # Hints for each step when user is in the dashboard
@@ -36,27 +37,27 @@ DASHBOARD_HINTS: Dict[TutorialStep, Dict[str, str]] = {
36
37
  TutorialStep.DASHBOARD_INTRO: {
37
38
  "title": "Welcome to the Command Center!",
38
39
  "hint": "This is your mission control - active jobs, stats, and recommendations.",
39
- "action": "Press Enter to continue the tutorial..."
40
+ "action": "Press Enter to continue the tutorial...",
40
41
  },
41
42
  TutorialStep.VIEW_JOBS: {
42
43
  "title": "Check Your Queued Scans",
43
44
  "hint": "Your recon scans are now running! Let's check them out.",
44
- "action": "Press [j] to open the Job Queue"
45
+ "action": "Press [j] to open the Job Queue",
45
46
  },
46
47
  TutorialStep.VIEW_OSINT: {
47
48
  "title": "View OSINT Discoveries",
48
49
  "hint": "Passive recon found domains, emails, IPs and more. Let's explore!",
49
- "action": "Press [o] to open the OSINT view"
50
+ "action": "Press [o] to open the OSINT view",
50
51
  },
51
52
  TutorialStep.VIEW_HOSTS: {
52
53
  "title": "View Discovered Hosts",
53
54
  "hint": "As scans complete, hosts will appear here.",
54
- "action": "Press [h] to open the Hosts view"
55
+ "action": "Press [h] to open the Hosts view",
55
56
  },
56
57
  TutorialStep.VIEW_JOB_DETAILS: {
57
58
  "title": "View Job Details",
58
59
  "hint": "Now let's learn to inspect individual scan results!",
59
- "action": "Press [j] to go back to the Job Queue"
60
+ "action": "Press [j] to go back to the Job Queue",
60
61
  },
61
62
  }
62
63
 
@@ -65,15 +66,15 @@ JOB_QUEUE_HINTS: Dict[TutorialStep, Dict[str, str]] = {
65
66
  TutorialStep.IN_JOB_QUEUE: {
66
67
  "title": "This is the Job Queue",
67
68
  "hint": "Here you can see all queued, running, and completed scans.\n"
68
- " • White = queued • Yellow = running • Green = completed • Red = failed\n"
69
- " With auto-chaining ON, new jobs will auto-queue based on findings!",
70
- "action": "Press [q] to go back, then try [o] for OSINT"
69
+ " • White = queued • Yellow = running • Green = completed • Red = failed\n"
70
+ " With auto-chaining ON, new jobs will auto-queue based on findings!",
71
+ "action": "Press [q] to go back, then try [o] for OSINT",
71
72
  },
72
73
  TutorialStep.IN_JOB_DETAILS: {
73
74
  "title": "View Job Details",
74
75
  "hint": "Type a job number to see its full output and results.\n"
75
- " Or press [i] for interactive mode with more options.",
76
- "action": "Press [q] when done to complete the tutorial!"
76
+ " Or press [i] for interactive mode with more options.",
77
+ "action": "Press [q] when done to complete the tutorial!",
77
78
  },
78
79
  }
79
80
 
@@ -82,9 +83,9 @@ OSINT_HINTS: Dict[TutorialStep, Dict[str, str]] = {
82
83
  TutorialStep.IN_OSINT_VIEW: {
83
84
  "title": "This is the OSINT View",
84
85
  "hint": "Passive recon discovered domains, emails, IPs and more.\n"
85
- " This data came from theHarvester, whois, and dnsrecon.\n"
86
- " No packets were sent to the target - all public sources!",
87
- "action": "Press [q] to go back, then try [h] for Hosts"
86
+ " This data came from theHarvester, whois, and dnsrecon.\n"
87
+ " No packets were sent to the target - all public sources!",
88
+ "action": "Press [q] to go back, then try [h] for Hosts",
88
89
  },
89
90
  }
90
91
 
@@ -93,8 +94,8 @@ HOSTS_HINTS: Dict[TutorialStep, Dict[str, str]] = {
93
94
  TutorialStep.IN_HOSTS_VIEW: {
94
95
  "title": "This is the Hosts View",
95
96
  "hint": "As your scans discover targets, they appear here.\n"
96
- " Each host shows its IP, hostname, and discovered services.",
97
- "action": "Press [q] to go back, then [j] for Job Details"
97
+ " Each host shows its IP, hostname, and discovered services.",
98
+ "action": "Press [q] to go back, then [j] for Job Details",
98
99
  },
99
100
  }
100
101
 
@@ -113,10 +114,10 @@ class TutorialState:
113
114
  show_hint(hint)
114
115
  """
115
116
 
116
- _instance: Optional['TutorialState'] = None
117
- STATE_FILE = Path.home() / '.souleyez' / '.tutorial_state.json'
117
+ _instance: Optional["TutorialState"] = None
118
+ STATE_FILE = Path.home() / ".souleyez" / ".tutorial_state.json"
118
119
 
119
- def __new__(cls) -> 'TutorialState':
120
+ def __new__(cls) -> "TutorialState":
120
121
  if cls._instance is None:
121
122
  cls._instance = super().__new__(cls)
122
123
  cls._instance._initialized = False
@@ -136,20 +137,19 @@ class TutorialState:
136
137
  try:
137
138
  with open(self.STATE_FILE) as f:
138
139
  data = json.load(f)
139
- step_name = data.get('step', 'INACTIVE')
140
+ step_name = data.get("step", "INACTIVE")
140
141
  self.current_step = TutorialStep[step_name]
141
- self.engagement_id = data.get('engagement_id')
142
+ self.engagement_id = data.get("engagement_id")
142
143
  except (json.JSONDecodeError, KeyError, FileNotFoundError):
143
144
  self.current_step = TutorialStep.INACTIVE
144
145
 
145
146
  def _save_state(self):
146
147
  """Save tutorial state to file."""
147
148
  self.STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
148
- with open(self.STATE_FILE, 'w') as f:
149
- json.dump({
150
- 'step': self.current_step.name,
151
- 'engagement_id': self.engagement_id
152
- }, f)
149
+ with open(self.STATE_FILE, "w") as f:
150
+ json.dump(
151
+ {"step": self.current_step.name, "engagement_id": self.engagement_id}, f
152
+ )
153
153
 
154
154
  def start(self, engagement_id: Optional[int] = None):
155
155
  """Start the tutorial."""