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
@@ -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()