souleyez 2.43.28__py3-none-any.whl → 2.43.32__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 (356) 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 +9592 -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 +1238 -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 +2198 -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 +288 -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/whois_handler.py +277 -0
  126. souleyez/handlers/wpscan_handler.py +554 -0
  127. souleyez/history.py +32 -16
  128. souleyez/importers/msf_importer.py +106 -75
  129. souleyez/importers/smart_importer.py +208 -147
  130. souleyez/integrations/siem/__init__.py +10 -10
  131. souleyez/integrations/siem/base.py +17 -18
  132. souleyez/integrations/siem/elastic.py +108 -122
  133. souleyez/integrations/siem/factory.py +207 -80
  134. souleyez/integrations/siem/googlesecops.py +146 -154
  135. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  136. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  137. souleyez/integrations/siem/sentinel.py +107 -109
  138. souleyez/integrations/siem/splunk.py +246 -212
  139. souleyez/integrations/siem/wazuh.py +65 -71
  140. souleyez/integrations/wazuh/__init__.py +5 -5
  141. souleyez/integrations/wazuh/client.py +70 -93
  142. souleyez/integrations/wazuh/config.py +85 -57
  143. souleyez/integrations/wazuh/host_mapper.py +28 -36
  144. souleyez/integrations/wazuh/sync.py +78 -68
  145. souleyez/intelligence/__init__.py +4 -5
  146. souleyez/intelligence/correlation_analyzer.py +309 -295
  147. souleyez/intelligence/exploit_knowledge.py +661 -623
  148. souleyez/intelligence/exploit_suggestions.py +159 -139
  149. souleyez/intelligence/gap_analyzer.py +132 -97
  150. souleyez/intelligence/gap_detector.py +251 -214
  151. souleyez/intelligence/sensitive_tables.py +266 -129
  152. souleyez/intelligence/service_parser.py +137 -123
  153. souleyez/intelligence/surface_analyzer.py +407 -268
  154. souleyez/intelligence/target_parser.py +159 -162
  155. souleyez/licensing/__init__.py +6 -6
  156. souleyez/licensing/validator.py +17 -19
  157. souleyez/log_config.py +79 -54
  158. souleyez/main.py +1505 -687
  159. souleyez/migrations/fix_job_counter.py +16 -14
  160. souleyez/parsers/bloodhound_parser.py +41 -39
  161. souleyez/parsers/crackmapexec_parser.py +178 -111
  162. souleyez/parsers/dalfox_parser.py +72 -77
  163. souleyez/parsers/dnsrecon_parser.py +103 -91
  164. souleyez/parsers/enum4linux_parser.py +183 -153
  165. souleyez/parsers/ffuf_parser.py +29 -25
  166. souleyez/parsers/gobuster_parser.py +301 -41
  167. souleyez/parsers/hashcat_parser.py +324 -79
  168. souleyez/parsers/http_fingerprint_parser.py +350 -103
  169. souleyez/parsers/hydra_parser.py +131 -111
  170. souleyez/parsers/impacket_parser.py +231 -178
  171. souleyez/parsers/john_parser.py +98 -86
  172. souleyez/parsers/katana_parser.py +316 -0
  173. souleyez/parsers/msf_parser.py +943 -498
  174. souleyez/parsers/nikto_parser.py +346 -65
  175. souleyez/parsers/nmap_parser.py +262 -174
  176. souleyez/parsers/nuclei_parser.py +40 -44
  177. souleyez/parsers/responder_parser.py +26 -26
  178. souleyez/parsers/searchsploit_parser.py +74 -74
  179. souleyez/parsers/service_explorer_parser.py +279 -0
  180. souleyez/parsers/smbmap_parser.py +180 -124
  181. souleyez/parsers/sqlmap_parser.py +434 -308
  182. souleyez/parsers/theharvester_parser.py +75 -57
  183. souleyez/parsers/whois_parser.py +135 -94
  184. souleyez/parsers/wpscan_parser.py +278 -190
  185. souleyez/plugins/afp.py +44 -36
  186. souleyez/plugins/afp_brute.py +114 -46
  187. souleyez/plugins/ard.py +48 -37
  188. souleyez/plugins/bloodhound.py +95 -61
  189. souleyez/plugins/certipy.py +303 -0
  190. souleyez/plugins/crackmapexec.py +186 -85
  191. souleyez/plugins/dalfox.py +120 -59
  192. souleyez/plugins/dns_hijack.py +146 -41
  193. souleyez/plugins/dnsrecon.py +97 -61
  194. souleyez/plugins/enum4linux.py +91 -66
  195. souleyez/plugins/evil_winrm.py +291 -0
  196. souleyez/plugins/ffuf.py +166 -90
  197. souleyez/plugins/firmware_extract.py +133 -29
  198. souleyez/plugins/gobuster.py +387 -190
  199. souleyez/plugins/gpp_extract.py +393 -0
  200. souleyez/plugins/hashcat.py +100 -73
  201. souleyez/plugins/http_fingerprint.py +854 -267
  202. souleyez/plugins/hydra.py +566 -200
  203. souleyez/plugins/impacket_getnpusers.py +117 -69
  204. souleyez/plugins/impacket_psexec.py +84 -64
  205. souleyez/plugins/impacket_secretsdump.py +103 -69
  206. souleyez/plugins/impacket_smbclient.py +89 -75
  207. souleyez/plugins/john.py +86 -69
  208. souleyez/plugins/katana.py +313 -0
  209. souleyez/plugins/kerbrute.py +237 -0
  210. souleyez/plugins/lfi_extract.py +541 -0
  211. souleyez/plugins/macos_ssh.py +117 -48
  212. souleyez/plugins/mdns.py +35 -30
  213. souleyez/plugins/msf_auxiliary.py +253 -130
  214. souleyez/plugins/msf_exploit.py +239 -161
  215. souleyez/plugins/nikto.py +134 -78
  216. souleyez/plugins/nmap.py +275 -91
  217. souleyez/plugins/nuclei.py +180 -89
  218. souleyez/plugins/nxc.py +285 -0
  219. souleyez/plugins/plugin_base.py +35 -36
  220. souleyez/plugins/plugin_template.py +13 -5
  221. souleyez/plugins/rdp_sec_check.py +130 -0
  222. souleyez/plugins/responder.py +112 -71
  223. souleyez/plugins/router_http_brute.py +76 -65
  224. souleyez/plugins/router_ssh_brute.py +118 -41
  225. souleyez/plugins/router_telnet_brute.py +124 -42
  226. souleyez/plugins/routersploit.py +91 -59
  227. souleyez/plugins/routersploit_exploit.py +77 -55
  228. souleyez/plugins/searchsploit.py +91 -77
  229. souleyez/plugins/service_explorer.py +1160 -0
  230. souleyez/plugins/smbmap.py +122 -72
  231. souleyez/plugins/smbpasswd.py +215 -0
  232. souleyez/plugins/sqlmap.py +301 -113
  233. souleyez/plugins/theharvester.py +127 -75
  234. souleyez/plugins/tr069.py +79 -57
  235. souleyez/plugins/upnp.py +65 -47
  236. souleyez/plugins/upnp_abuse.py +73 -55
  237. souleyez/plugins/vnc_access.py +129 -42
  238. souleyez/plugins/vnc_brute.py +109 -38
  239. souleyez/plugins/whois.py +77 -58
  240. souleyez/plugins/wpscan.py +173 -69
  241. souleyez/reporting/__init__.py +2 -1
  242. souleyez/reporting/attack_chain.py +411 -346
  243. souleyez/reporting/charts.py +436 -501
  244. souleyez/reporting/compliance_mappings.py +334 -201
  245. souleyez/reporting/detection_report.py +126 -125
  246. souleyez/reporting/formatters.py +828 -591
  247. souleyez/reporting/generator.py +386 -302
  248. souleyez/reporting/metrics.py +72 -75
  249. souleyez/scanner.py +35 -29
  250. souleyez/security/__init__.py +37 -11
  251. souleyez/security/scope_validator.py +175 -106
  252. souleyez/security/validation.py +223 -149
  253. souleyez/security.py +22 -6
  254. souleyez/storage/credentials.py +247 -186
  255. souleyez/storage/crypto.py +296 -129
  256. souleyez/storage/database.py +73 -50
  257. souleyez/storage/db.py +58 -36
  258. souleyez/storage/deliverable_evidence.py +177 -128
  259. souleyez/storage/deliverable_exporter.py +282 -246
  260. souleyez/storage/deliverable_templates.py +134 -116
  261. souleyez/storage/deliverables.py +135 -130
  262. souleyez/storage/engagements.py +109 -56
  263. souleyez/storage/evidence.py +181 -152
  264. souleyez/storage/execution_log.py +31 -17
  265. souleyez/storage/exploit_attempts.py +93 -57
  266. souleyez/storage/exploits.py +67 -36
  267. souleyez/storage/findings.py +48 -61
  268. souleyez/storage/hosts.py +176 -144
  269. souleyez/storage/migrate_to_engagements.py +43 -19
  270. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  271. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  272. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  273. souleyez/storage/migrations/_005_screenshots.py +13 -5
  274. souleyez/storage/migrations/_006_deliverables.py +13 -5
  275. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  276. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  277. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  278. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  279. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  280. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  281. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  282. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  283. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  284. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  285. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  286. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  287. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  288. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  289. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  290. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  291. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  292. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  293. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  294. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  295. souleyez/storage/migrations/__init__.py +26 -26
  296. souleyez/storage/migrations/migration_manager.py +19 -19
  297. souleyez/storage/msf_sessions.py +100 -65
  298. souleyez/storage/osint.py +17 -24
  299. souleyez/storage/recommendation_engine.py +269 -235
  300. souleyez/storage/screenshots.py +33 -32
  301. souleyez/storage/smb_shares.py +136 -92
  302. souleyez/storage/sqlmap_data.py +183 -128
  303. souleyez/storage/team_collaboration.py +135 -141
  304. souleyez/storage/timeline_tracker.py +122 -94
  305. souleyez/storage/wazuh_vulns.py +64 -66
  306. souleyez/storage/web_paths.py +33 -37
  307. souleyez/testing/credential_tester.py +221 -205
  308. souleyez/ui/__init__.py +1 -1
  309. souleyez/ui/ai_quotes.py +12 -12
  310. souleyez/ui/attack_surface.py +2439 -1516
  311. souleyez/ui/chain_rules_view.py +914 -382
  312. souleyez/ui/correlation_view.py +312 -230
  313. souleyez/ui/dashboard.py +2382 -1130
  314. souleyez/ui/deliverables_view.py +148 -62
  315. souleyez/ui/design_system.py +13 -13
  316. souleyez/ui/errors.py +49 -49
  317. souleyez/ui/evidence_linking_view.py +284 -179
  318. souleyez/ui/evidence_vault.py +393 -285
  319. souleyez/ui/exploit_suggestions_view.py +555 -349
  320. souleyez/ui/export_view.py +100 -66
  321. souleyez/ui/gap_analysis_view.py +315 -171
  322. souleyez/ui/help_system.py +105 -97
  323. souleyez/ui/intelligence_view.py +436 -293
  324. souleyez/ui/interactive.py +23142 -10430
  325. souleyez/ui/interactive_selector.py +75 -68
  326. souleyez/ui/log_formatter.py +47 -39
  327. souleyez/ui/menu_components.py +22 -13
  328. souleyez/ui/msf_auxiliary_menu.py +184 -133
  329. souleyez/ui/pending_chains_view.py +336 -172
  330. souleyez/ui/progress_indicators.py +5 -3
  331. souleyez/ui/recommendations_view.py +195 -137
  332. souleyez/ui/rule_builder.py +343 -225
  333. souleyez/ui/setup_wizard.py +678 -284
  334. souleyez/ui/shortcuts.py +217 -165
  335. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  336. souleyez/ui/splunk_vulns_view.py +139 -86
  337. souleyez/ui/team_dashboard.py +498 -335
  338. souleyez/ui/template_selector.py +196 -105
  339. souleyez/ui/terminal.py +6 -6
  340. souleyez/ui/timeline_view.py +198 -127
  341. souleyez/ui/tool_setup.py +264 -164
  342. souleyez/ui/tutorial.py +202 -72
  343. souleyez/ui/tutorial_state.py +40 -40
  344. souleyez/ui/wazuh_vulns_view.py +235 -141
  345. souleyez/ui/wordlist_browser.py +260 -107
  346. souleyez/ui.py +464 -312
  347. souleyez/utils/tool_checker.py +427 -367
  348. souleyez/utils.py +33 -29
  349. souleyez/wordlists.py +134 -167
  350. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/METADATA +1 -1
  351. souleyez-2.43.32.dist-info/RECORD +441 -0
  352. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/WHEEL +1 -1
  353. souleyez-2.43.28.dist-info/RECORD +0 -379
  354. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/entry_points.txt +0 -0
  355. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/licenses/LICENSE +0 -0
  356. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  """Timeline and velocity tracking UI for deliverables."""
2
+
2
3
  import click
3
4
  from souleyez.storage.timeline_tracker import TimelineTracker
4
5
  from souleyez.storage.deliverables import DeliverableManager
@@ -9,7 +10,7 @@ from souleyez.ui.design_system import DesignSystem
9
10
  def show_timeline_view(engagement_id: int):
10
11
  """
11
12
  Display timeline and velocity metrics for an engagement.
12
-
13
+
13
14
  Shows:
14
15
  - Phase breakdown (time per PTES phase)
15
16
  - Velocity (deliverables per hour)
@@ -20,155 +21,213 @@ def show_timeline_view(engagement_id: int):
20
21
  tt = TimelineTracker()
21
22
  dm = DeliverableManager()
22
23
  em = EngagementManager()
23
-
24
+
24
25
  engagement = em.get_by_id(engagement_id)
25
26
  if not engagement:
26
- click.echo(click.style(" Error: Engagement not found", fg='red'))
27
+ click.echo(click.style(" Error: Engagement not found", fg="red"))
27
28
  click.pause()
28
29
  return
29
-
30
+
30
31
  while True:
31
32
  DesignSystem.clear_screen()
32
-
33
+
33
34
  width = DesignSystem.get_terminal_width()
34
-
35
+
35
36
  # Header
36
37
  click.echo("\n┌" + "─" * (width - 2) + "┐")
37
- click.echo("│" + click.style(" ⏱️ TIMELINE & VELOCITY TRACKING ".center(width - 2), bold=True, fg='cyan') + "│")
38
+ click.echo(
39
+ "│"
40
+ + click.style(
41
+ " ⏱️ TIMELINE & VELOCITY TRACKING ".center(width - 2),
42
+ bold=True,
43
+ fg="cyan",
44
+ )
45
+ + "│"
46
+ )
38
47
  click.echo("└" + "─" * (width - 2) + "┘")
39
48
  click.echo()
40
-
41
- click.echo(f" Engagement: {click.style(engagement['name'], bold=True, fg='cyan')}")
49
+
50
+ click.echo(
51
+ f" Engagement: {click.style(engagement['name'], bold=True, fg='cyan')}"
52
+ )
42
53
  click.echo()
43
-
54
+
44
55
  # Get timeline summary
45
56
  summary = tt.get_timeline_summary(engagement_id)
46
-
57
+
47
58
  # Phase Breakdown
48
- click.echo(click.style(" 📊 PHASE BREAKDOWN", bold=True, fg='cyan'))
59
+ click.echo(click.style(" 📊 PHASE BREAKDOWN", bold=True, fg="cyan"))
49
60
  click.echo(" " + "─" * (width - 4))
50
61
  click.echo()
51
-
62
+
52
63
  phase_names = {
53
- 'reconnaissance': '🔭 Reconnaissance',
54
- 'enumeration': '🔍 Enumeration',
55
- 'exploitation': '💥 Exploitation',
56
- 'post_exploitation': '🎯 Post-Exploitation',
57
- 'techniques': '🛠️ Techniques'
64
+ "reconnaissance": "🔭 Reconnaissance",
65
+ "enumeration": "🔍 Enumeration",
66
+ "exploitation": "💥 Exploitation",
67
+ "post_exploitation": "🎯 Post-Exploitation",
68
+ "techniques": "🛠️ Techniques",
58
69
  }
59
-
60
- for phase, stats in summary['phase_breakdown'].items():
61
- if stats['total'] == 0:
70
+
71
+ for phase, stats in summary["phase_breakdown"].items():
72
+ if stats["total"] == 0:
62
73
  continue
63
-
74
+
64
75
  phase_name = phase_names.get(phase, phase)
65
- completion = stats['completion_rate']
66
-
76
+ completion = stats["completion_rate"]
77
+
67
78
  # Progress bar
68
79
  bar_width = 20
69
80
  filled = int((completion / 100) * bar_width)
70
81
  bar = "█" * filled + "░" * (bar_width - filled)
71
-
82
+
72
83
  click.echo(f" {phase_name}")
73
84
  click.echo(f" Progress: [{bar}] {completion:.0f}%")
74
85
  click.echo(f" Deliverables: {stats['completed']}/{stats['total']}")
75
-
76
- if stats['actual_hours'] > 0:
86
+
87
+ if stats["actual_hours"] > 0:
77
88
  click.echo(f" Time Spent: {stats['actual_hours']:.1f}h")
78
- if stats['estimated_hours'] > 0:
79
- time_var = stats['actual_hours'] - stats['estimated_hours']
89
+ if stats["estimated_hours"] > 0:
90
+ time_var = stats["actual_hours"] - stats["estimated_hours"]
80
91
  if time_var > 0:
81
- click.echo(click.style(f" Over estimate by: {time_var:.1f}h", fg='yellow'))
92
+ click.echo(
93
+ click.style(
94
+ f" Over estimate by: {time_var:.1f}h", fg="yellow"
95
+ )
96
+ )
82
97
  else:
83
- click.echo(click.style(f" Under estimate by: {abs(time_var):.1f}h", fg='green'))
84
-
98
+ click.echo(
99
+ click.style(
100
+ f" Under estimate by: {abs(time_var):.1f}h",
101
+ fg="green",
102
+ )
103
+ )
104
+
85
105
  click.echo()
86
-
106
+
87
107
  # Velocity Metrics
88
- click.echo(click.style(" 🚀 VELOCITY METRICS", bold=True, fg='cyan'))
108
+ click.echo(click.style(" 🚀 VELOCITY METRICS", bold=True, fg="cyan"))
89
109
  click.echo(" " + "─" * (width - 4))
90
110
  click.echo()
91
-
92
- velocity = summary['velocity']
93
-
94
- if velocity['completed_deliverables'] > 0:
95
- click.echo(f" Completed: {velocity['completed_deliverables']} deliverables")
111
+
112
+ velocity = summary["velocity"]
113
+
114
+ if velocity["completed_deliverables"] > 0:
115
+ click.echo(
116
+ f" Completed: {velocity['completed_deliverables']} deliverables"
117
+ )
96
118
  click.echo(f" Total Time: {velocity['total_hours']:.1f} hours")
97
- click.echo(f" Average: {velocity['avg_hours_per_deliverable']:.1f}h per deliverable")
119
+ click.echo(
120
+ f" Average: {velocity['avg_hours_per_deliverable']:.1f}h per deliverable"
121
+ )
98
122
  click.echo(f" Velocity: {velocity['velocity']:.2f} deliverables/hour")
99
123
  else:
100
- click.echo(click.style(" No completed deliverables yet (velocity unknown)", fg='yellow'))
101
-
124
+ click.echo(
125
+ click.style(
126
+ " No completed deliverables yet (velocity unknown)", fg="yellow"
127
+ )
128
+ )
129
+
102
130
  click.echo()
103
-
131
+
104
132
  # Completion Projection
105
- click.echo(click.style(" 🎯 COMPLETION PROJECTION", bold=True, fg='cyan'))
133
+ click.echo(click.style(" 🎯 COMPLETION PROJECTION", bold=True, fg="cyan"))
106
134
  click.echo(" " + "─" * (width - 4))
107
135
  click.echo()
108
-
109
- projection = summary['projection']
110
-
111
- if projection['status'] == 'complete':
112
- click.echo(click.style(" ✅ All deliverables completed!", fg='green'))
136
+
137
+ projection = summary["projection"]
138
+
139
+ if projection["status"] == "complete":
140
+ click.echo(click.style(" ✅ All deliverables completed!", fg="green"))
113
141
  else:
114
- click.echo(f" Remaining: {projection['remaining_deliverables']} deliverables")
115
- click.echo(f" Estimated Time: {projection['projected_hours']}h ({projection['projected_days']} work days)")
116
- click.echo(f" Projected Completion: {click.style(projection['projected_date'], bold=True, fg='yellow')}")
117
-
118
- if projection.get('velocity', 0) > 0:
119
- click.echo(click.style(f" (Based on current velocity: {projection['velocity']} deliverables/h)", fg='bright_black'))
142
+ click.echo(
143
+ f" Remaining: {projection['remaining_deliverables']} deliverables"
144
+ )
145
+ click.echo(
146
+ f" Estimated Time: {projection['projected_hours']}h ({projection['projected_days']} work days)"
147
+ )
148
+ click.echo(
149
+ f" Projected Completion: {click.style(projection['projected_date'], bold=True, fg='yellow')}"
150
+ )
151
+
152
+ if projection.get("velocity", 0) > 0:
153
+ click.echo(
154
+ click.style(
155
+ f" (Based on current velocity: {projection['velocity']} deliverables/h)",
156
+ fg="bright_black",
157
+ )
158
+ )
120
159
  else:
121
- click.echo(click.style(" (Based on 2h/deliverable estimate)", fg='bright_black'))
122
-
160
+ click.echo(
161
+ click.style(
162
+ " (Based on 2h/deliverable estimate)", fg="bright_black"
163
+ )
164
+ )
165
+
123
166
  click.echo()
124
-
167
+
125
168
  # Blockers
126
- blockers = summary['blockers']
169
+ blockers = summary["blockers"]
127
170
  if blockers:
128
- click.echo(click.style(f" ⚠️ BLOCKERS ({len(blockers)})", bold=True, fg='red'))
171
+ click.echo(
172
+ click.style(f" ⚠️ BLOCKERS ({len(blockers)})", bold=True, fg="red")
173
+ )
129
174
  click.echo(" " + "─" * (width - 4))
130
175
  click.echo()
131
-
176
+
132
177
  for b in blockers[:5]:
133
178
  priority_color = {
134
- 'critical': 'red',
135
- 'high': 'yellow',
136
- 'medium': 'white',
137
- 'low': 'bright_black'
138
- }.get(b.get('priority', 'medium'), 'white')
139
-
140
- click.echo(f" • [{click.style(b.get('priority', 'N/A').upper(), fg=priority_color)}] "
141
- f"#{b['id']} {b['title'][:50]}")
142
- click.echo(click.style(f" Blocker: {b['blocker']}", fg='yellow'))
143
-
179
+ "critical": "red",
180
+ "high": "yellow",
181
+ "medium": "white",
182
+ "low": "bright_black",
183
+ }.get(b.get("priority", "medium"), "white")
184
+
185
+ click.echo(
186
+ f" • [{click.style(b.get('priority', 'N/A').upper(), fg=priority_color)}] "
187
+ f"#{b['id']} {b['title'][:50]}"
188
+ )
189
+ click.echo(click.style(f" Blocker: {b['blocker']}", fg="yellow"))
190
+
144
191
  if len(blockers) > 5:
145
- click.echo(click.style(f" ... and {len(blockers) - 5} more", fg='bright_black'))
146
-
192
+ click.echo(
193
+ click.style(
194
+ f" ... and {len(blockers) - 5} more", fg="bright_black"
195
+ )
196
+ )
197
+
147
198
  click.echo()
148
-
199
+
149
200
  # In Progress
150
- in_progress = summary['in_progress']
201
+ in_progress = summary["in_progress"]
151
202
  if in_progress:
152
- click.echo(click.style(f" 🔄 IN PROGRESS ({len(in_progress)})", bold=True, fg='cyan'))
203
+ click.echo(
204
+ click.style(
205
+ f" 🔄 IN PROGRESS ({len(in_progress)})", bold=True, fg="cyan"
206
+ )
207
+ )
153
208
  click.echo(" " + "─" * (width - 4))
154
209
  click.echo()
155
-
210
+
156
211
  for item in in_progress[:5]:
157
212
  click.echo(f" • #{item['id']} {item['title'][:50]}")
158
-
159
- if item['started_at']:
213
+
214
+ if item["started_at"]:
160
215
  click.echo(f" Started: {item['started_at']}")
161
-
162
- if item['estimated_hours']:
216
+
217
+ if item["estimated_hours"]:
163
218
  click.echo(f" Estimated: {item['estimated_hours']:.1f}h")
164
-
219
+
165
220
  if len(in_progress) > 5:
166
- click.echo(click.style(f" ... and {len(in_progress) - 5} more", fg='bright_black'))
167
-
221
+ click.echo(
222
+ click.style(
223
+ f" ... and {len(in_progress) - 5} more", fg="bright_black"
224
+ )
225
+ )
226
+
168
227
  click.echo()
169
-
228
+
170
229
  # Menu
171
- click.echo(click.style(" ⚙️ ACTIONS", bold=True, fg='cyan'))
230
+ click.echo(click.style(" ⚙️ ACTIONS", bold=True, fg="cyan"))
172
231
  click.echo(" " + "─" * (width - 4))
173
232
  click.echo()
174
233
  click.echo(" [S] Start Deliverable")
@@ -178,111 +237,123 @@ def show_timeline_view(engagement_id: int):
178
237
  click.echo()
179
238
  click.echo(" [q] ← Back")
180
239
  click.echo()
181
-
182
- choice = click.prompt("Select option", type=str, default='q', show_default=False).strip().lower()
183
-
184
- if choice == 'q':
240
+
241
+ choice = (
242
+ click.prompt("Select option", type=str, default="q", show_default=False)
243
+ .strip()
244
+ .lower()
245
+ )
246
+
247
+ if choice == "q":
185
248
  break
186
- elif choice == 's':
249
+ elif choice == "s":
187
250
  _start_deliverable(engagement_id, tt)
188
- elif choice == 'c':
251
+ elif choice == "c":
189
252
  _complete_deliverable(engagement_id, tt)
190
- elif choice == 'b':
253
+ elif choice == "b":
191
254
  _manage_blocker(engagement_id, tt)
192
- elif choice == 'r':
255
+ elif choice == "r":
193
256
  continue
194
257
 
195
258
 
196
259
  def _start_deliverable(engagement_id: int, tt: TimelineTracker):
197
260
  """Start a deliverable (sets started_at timestamp)."""
198
261
  dm = DeliverableManager()
199
-
262
+
200
263
  click.echo()
201
264
  deliverable_id = click.prompt(" Enter deliverable ID to start", type=int)
202
-
265
+
203
266
  deliverable = dm.get_deliverable(deliverable_id)
204
267
  if not deliverable:
205
- click.echo(click.style(" Deliverable not found", fg='red'))
268
+ click.echo(click.style(" Deliverable not found", fg="red"))
206
269
  click.pause()
207
270
  return
208
-
209
- if deliverable['engagement_id'] != engagement_id:
210
- click.echo(click.style(" Deliverable belongs to different engagement", fg='red'))
271
+
272
+ if deliverable["engagement_id"] != engagement_id:
273
+ click.echo(
274
+ click.style(" Deliverable belongs to different engagement", fg="red")
275
+ )
211
276
  click.pause()
212
277
  return
213
-
278
+
214
279
  tt.start_deliverable(deliverable_id)
215
- click.echo(click.style(f" ✅ Started: {deliverable['title']}", fg='green'))
280
+ click.echo(click.style(f" ✅ Started: {deliverable['title']}", fg="green"))
216
281
  click.pause()
217
282
 
218
283
 
219
284
  def _complete_deliverable(engagement_id: int, tt: TimelineTracker):
220
285
  """Complete a deliverable with optional time entry."""
221
286
  dm = DeliverableManager()
222
-
287
+
223
288
  click.echo()
224
289
  deliverable_id = click.prompt(" Enter deliverable ID to complete", type=int)
225
-
290
+
226
291
  deliverable = dm.get_deliverable(deliverable_id)
227
292
  if not deliverable:
228
- click.echo(click.style(" Deliverable not found", fg='red'))
293
+ click.echo(click.style(" Deliverable not found", fg="red"))
229
294
  click.pause()
230
295
  return
231
-
232
- if deliverable['engagement_id'] != engagement_id:
233
- click.echo(click.style(" Deliverable belongs to different engagement", fg='red'))
296
+
297
+ if deliverable["engagement_id"] != engagement_id:
298
+ click.echo(
299
+ click.style(" Deliverable belongs to different engagement", fg="red")
300
+ )
234
301
  click.pause()
235
302
  return
236
-
303
+
237
304
  click.echo()
238
305
  manual_time = click.confirm(" Enter time manually?", default=False)
239
-
306
+
240
307
  if manual_time:
241
308
  hours = click.prompt(" Hours spent", type=float)
242
309
  tt.complete_deliverable(deliverable_id, actual_hours=hours)
243
310
  else:
244
311
  tt.complete_deliverable(deliverable_id)
245
-
312
+
246
313
  # Update engagement total hours
247
314
  tt.update_engagement_hours(engagement_id)
248
-
249
- click.echo(click.style(f" ✅ Completed: {deliverable['title']}", fg='green'))
315
+
316
+ click.echo(click.style(f" ✅ Completed: {deliverable['title']}", fg="green"))
250
317
  click.pause()
251
318
 
252
319
 
253
320
  def _manage_blocker(engagement_id: int, tt: TimelineTracker):
254
321
  """Set or clear a blocker."""
255
322
  dm = DeliverableManager()
256
-
323
+
257
324
  click.echo()
258
325
  deliverable_id = click.prompt(" Enter deliverable ID", type=int)
259
-
326
+
260
327
  deliverable = dm.get_deliverable(deliverable_id)
261
328
  if not deliverable:
262
- click.echo(click.style(" Deliverable not found", fg='red'))
329
+ click.echo(click.style(" Deliverable not found", fg="red"))
263
330
  click.pause()
264
331
  return
265
-
266
- if deliverable['engagement_id'] != engagement_id:
267
- click.echo(click.style(" Deliverable belongs to different engagement", fg='red'))
332
+
333
+ if deliverable["engagement_id"] != engagement_id:
334
+ click.echo(
335
+ click.style(" Deliverable belongs to different engagement", fg="red")
336
+ )
268
337
  click.pause()
269
338
  return
270
-
339
+
271
340
  click.echo()
272
-
273
- if deliverable.get('blocker'):
274
- click.echo(f" Current blocker: {click.style(deliverable['blocker'], fg='yellow')}")
341
+
342
+ if deliverable.get("blocker"):
343
+ click.echo(
344
+ f" Current blocker: {click.style(deliverable['blocker'], fg='yellow')}"
345
+ )
275
346
  click.echo()
276
347
  if click.confirm(" Clear blocker?", default=True):
277
348
  tt.clear_blocker(deliverable_id)
278
- click.echo(click.style(" ✅ Blocker cleared", fg='green'))
349
+ click.echo(click.style(" ✅ Blocker cleared", fg="green"))
279
350
  else:
280
351
  blocker = click.prompt(" New blocker description", type=str)
281
352
  tt.set_blocker(deliverable_id, blocker)
282
- click.echo(click.style(" ✅ Blocker updated", fg='yellow'))
353
+ click.echo(click.style(" ✅ Blocker updated", fg="yellow"))
283
354
  else:
284
355
  blocker = click.prompt(" Blocker description", type=str)
285
356
  tt.set_blocker(deliverable_id, blocker)
286
- click.echo(click.style(" ⚠️ Blocker set", fg='yellow'))
287
-
357
+ click.echo(click.style(" ⚠️ Blocker set", fg="yellow"))
358
+
288
359
  click.pause()