souleyez 2.43.29__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 +22783 -10678
  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.29.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.29.dist-info → souleyez-2.43.32.dist-info}/WHEEL +1 -1
  353. souleyez-2.43.29.dist-info/RECORD +0 -379
  354. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/entry_points.txt +0 -0
  355. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/licenses/LICENSE +0 -0
  356. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Team collaboration features for deliverables.
3
3
  """
4
+
4
5
  from typing import Dict, List, Optional
5
6
  from datetime import datetime
6
7
  from .database import get_db
@@ -9,66 +10,69 @@ import os
9
10
 
10
11
  class TeamCollaboration:
11
12
  """Manage team collaboration on deliverables."""
12
-
13
+
13
14
  def __init__(self):
14
15
  self.db = get_db()
15
-
16
+
16
17
  def _get_current_user(self) -> str:
17
18
  """Get current username from environment or system."""
18
19
  # Try environment variable first
19
- user = os.environ.get('USER') or os.environ.get('USERNAME') or 'unknown'
20
+ user = os.environ.get("USER") or os.environ.get("USERNAME") or "unknown"
20
21
  return user
21
-
22
+
22
23
  def log_activity(
23
24
  self,
24
25
  deliverable_id: int,
25
26
  engagement_id: int,
26
27
  action: str,
27
28
  details: str = None,
28
- user: str = None
29
+ user: str = None,
29
30
  ) -> int:
30
31
  """
31
32
  Log activity on a deliverable.
32
-
33
+
33
34
  Args:
34
35
  deliverable_id: Deliverable ID
35
36
  engagement_id: Engagement ID
36
37
  action: Action type (started, completed, updated, assigned, etc.)
37
38
  details: Additional details
38
39
  user: Username (defaults to current user)
39
-
40
+
40
41
  Returns:
41
42
  Activity log ID
42
43
  """
43
44
  if user is None:
44
45
  user = self._get_current_user()
45
-
46
- activity_id = self.db.insert('deliverable_activity', {
47
- 'deliverable_id': deliverable_id,
48
- 'engagement_id': engagement_id,
49
- 'user': user,
50
- 'action': action,
51
- 'details': details
52
- })
53
-
46
+
47
+ activity_id = self.db.insert(
48
+ "deliverable_activity",
49
+ {
50
+ "deliverable_id": deliverable_id,
51
+ "engagement_id": engagement_id,
52
+ "user": user,
53
+ "action": action,
54
+ "details": details,
55
+ },
56
+ )
57
+
54
58
  return activity_id
55
-
59
+
56
60
  def get_activity(
57
61
  self,
58
62
  deliverable_id: int = None,
59
63
  engagement_id: int = None,
60
64
  user: str = None,
61
- limit: int = 50
65
+ limit: int = 50,
62
66
  ) -> List[Dict]:
63
67
  """
64
68
  Get activity log.
65
-
69
+
66
70
  Args:
67
71
  deliverable_id: Filter by deliverable
68
72
  engagement_id: Filter by engagement
69
73
  user: Filter by user
70
74
  limit: Max results
71
-
75
+
72
76
  Returns:
73
77
  List of activity records
74
78
  """
@@ -82,59 +86,53 @@ class TeamCollaboration:
82
86
  WHERE 1=1
83
87
  """
84
88
  params = []
85
-
89
+
86
90
  if deliverable_id:
87
91
  query += " AND da.deliverable_id = ?"
88
92
  params.append(deliverable_id)
89
-
93
+
90
94
  if engagement_id:
91
95
  query += " AND da.engagement_id = ?"
92
96
  params.append(engagement_id)
93
-
97
+
94
98
  if user:
95
99
  query += " AND da.user = ?"
96
100
  params.append(user)
97
-
101
+
98
102
  query += " ORDER BY da.created_at DESC LIMIT ?"
99
103
  params.append(limit)
100
-
104
+
101
105
  return self.db.execute(query, tuple(params))
102
-
103
- def add_comment(
104
- self,
105
- deliverable_id: int,
106
- comment: str,
107
- user: str = None
108
- ) -> int:
106
+
107
+ def add_comment(self, deliverable_id: int, comment: str, user: str = None) -> int:
109
108
  """
110
109
  Add comment to deliverable.
111
-
110
+
112
111
  Args:
113
112
  deliverable_id: Deliverable ID
114
113
  comment: Comment text
115
114
  user: Username (defaults to current user)
116
-
115
+
117
116
  Returns:
118
117
  Comment ID
119
118
  """
120
119
  if user is None:
121
120
  user = self._get_current_user()
122
-
123
- comment_id = self.db.insert('deliverable_comments', {
124
- 'deliverable_id': deliverable_id,
125
- 'user': user,
126
- 'comment': comment
127
- })
128
-
121
+
122
+ comment_id = self.db.insert(
123
+ "deliverable_comments",
124
+ {"deliverable_id": deliverable_id, "user": user, "comment": comment},
125
+ )
126
+
129
127
  return comment_id
130
-
128
+
131
129
  def get_comments(self, deliverable_id: int) -> List[Dict]:
132
130
  """
133
131
  Get comments for deliverable.
134
-
132
+
135
133
  Args:
136
134
  deliverable_id: Deliverable ID
137
-
135
+
138
136
  Returns:
139
137
  List of comments
140
138
  """
@@ -144,120 +142,116 @@ class TeamCollaboration:
144
142
  WHERE deliverable_id = ?
145
143
  ORDER BY created_at ASC
146
144
  """,
147
- (deliverable_id,)
145
+ (deliverable_id,),
148
146
  )
149
-
147
+
150
148
  def delete_comment(self, comment_id: int, user: str = None) -> bool:
151
149
  """
152
150
  Delete comment (only by original author).
153
-
151
+
154
152
  Args:
155
153
  comment_id: Comment ID
156
154
  user: Username (defaults to current user)
157
-
155
+
158
156
  Returns:
159
157
  True if deleted
160
158
  """
161
159
  if user is None:
162
160
  user = self._get_current_user()
163
-
161
+
164
162
  # Verify ownership
165
163
  comment = self.db.execute_one(
166
- "SELECT user FROM deliverable_comments WHERE id = ?",
167
- (comment_id,)
164
+ "SELECT user FROM deliverable_comments WHERE id = ?", (comment_id,)
168
165
  )
169
-
170
- if not comment or comment['user'] != user:
166
+
167
+ if not comment or comment["user"] != user:
171
168
  return False
172
-
173
- self.db.execute(
174
- "DELETE FROM deliverable_comments WHERE id = ?",
175
- (comment_id,)
176
- )
177
-
169
+
170
+ self.db.execute("DELETE FROM deliverable_comments WHERE id = ?", (comment_id,))
171
+
178
172
  return True
179
-
173
+
180
174
  def assign_deliverable(
181
175
  self,
182
176
  deliverable_id: int,
183
177
  engagement_id: int,
184
178
  assigned_to: str,
185
- assigned_by: str = None
179
+ assigned_by: str = None,
186
180
  ) -> bool:
187
181
  """
188
182
  Assign deliverable to user.
189
-
183
+
190
184
  Args:
191
185
  deliverable_id: Deliverable ID
192
186
  engagement_id: Engagement ID
193
187
  assigned_to: Username to assign to
194
188
  assigned_by: Username doing assignment
195
-
189
+
196
190
  Returns:
197
191
  True if successful
198
192
  """
199
193
  if assigned_by is None:
200
194
  assigned_by = self._get_current_user()
201
-
195
+
202
196
  # Update deliverable
203
197
  self.db.execute(
204
198
  "UPDATE deliverables SET assigned_to = ? WHERE id = ?",
205
- (assigned_to, deliverable_id)
199
+ (assigned_to, deliverable_id),
206
200
  )
207
-
201
+
208
202
  # Log activity
209
- details = f"Assigned to {assigned_to}" if assigned_by != assigned_to else "Self-assigned"
203
+ details = (
204
+ f"Assigned to {assigned_to}"
205
+ if assigned_by != assigned_to
206
+ else "Self-assigned"
207
+ )
210
208
  self.log_activity(
211
209
  deliverable_id=deliverable_id,
212
210
  engagement_id=engagement_id,
213
- action='assigned',
211
+ action="assigned",
214
212
  details=details,
215
- user=assigned_by
213
+ user=assigned_by,
216
214
  )
217
-
215
+
218
216
  return True
219
-
217
+
220
218
  def unassign_deliverable(
221
- self,
222
- deliverable_id: int,
223
- engagement_id: int,
224
- user: str = None
219
+ self, deliverable_id: int, engagement_id: int, user: str = None
225
220
  ) -> bool:
226
221
  """
227
222
  Unassign deliverable.
228
-
223
+
229
224
  Args:
230
225
  deliverable_id: Deliverable ID
231
226
  engagement_id: Engagement ID
232
227
  user: Username doing unassignment
233
-
228
+
234
229
  Returns:
235
230
  True if successful
236
231
  """
237
232
  if user is None:
238
233
  user = self._get_current_user()
239
-
234
+
240
235
  # Update deliverable
241
236
  self.db.execute(
242
- "UPDATE deliverables SET assigned_to = NULL WHERE id = ?",
243
- (deliverable_id,)
237
+ "UPDATE deliverables SET assigned_to = NULL WHERE id = ?", (deliverable_id,)
244
238
  )
245
-
239
+
246
240
  # Log activity
247
241
  self.log_activity(
248
242
  deliverable_id=deliverable_id,
249
243
  engagement_id=engagement_id,
250
- action='unassigned',
244
+ action="unassigned",
251
245
  details="Unassigned",
252
- user=user
246
+ user=user,
253
247
  )
254
-
248
+
255
249
  return True
256
-
250
+
257
251
  def get_team_summary(self, engagement_id: int) -> Dict:
258
252
  """
259
253
  Get team activity summary for engagement.
260
-
254
+
261
255
  Returns:
262
256
  Dict with team statistics
263
257
  """
@@ -268,16 +262,16 @@ class TeamCollaboration:
268
262
  FROM deliverable_activity
269
263
  WHERE engagement_id = ?
270
264
  """,
271
- (engagement_id,)
265
+ (engagement_id,),
272
266
  )
273
-
274
- user_list = [u['user'] for u in users]
275
-
267
+
268
+ user_list = [u["user"] for u in users]
269
+
276
270
  # Get activity count per user
277
271
  user_activity = {}
278
272
  for user_row in users:
279
- user = user_row['user']
280
-
273
+ user = user_row["user"]
274
+
281
275
  # Count activities
282
276
  activity_count = self.db.execute_one(
283
277
  """
@@ -285,9 +279,9 @@ class TeamCollaboration:
285
279
  FROM deliverable_activity
286
280
  WHERE engagement_id = ? AND user = ?
287
281
  """,
288
- (engagement_id, user)
282
+ (engagement_id, user),
289
283
  )
290
-
284
+
291
285
  # Count assignments
292
286
  assigned_count = self.db.execute_one(
293
287
  """
@@ -295,9 +289,9 @@ class TeamCollaboration:
295
289
  FROM deliverables
296
290
  WHERE engagement_id = ? AND assigned_to = ?
297
291
  """,
298
- (engagement_id, user)
292
+ (engagement_id, user),
299
293
  )
300
-
294
+
301
295
  # Count completed
302
296
  completed_count = self.db.execute_one(
303
297
  """
@@ -305,25 +299,25 @@ class TeamCollaboration:
305
299
  FROM deliverables
306
300
  WHERE engagement_id = ? AND assigned_to = ? AND status = 'completed'
307
301
  """,
308
- (engagement_id, user)
302
+ (engagement_id, user),
309
303
  )
310
-
304
+
311
305
  user_activity[user] = {
312
- 'activity_count': activity_count['count'] if activity_count else 0,
313
- 'assigned_count': assigned_count['count'] if assigned_count else 0,
314
- 'completed_count': completed_count['count'] if completed_count else 0
306
+ "activity_count": activity_count["count"] if activity_count else 0,
307
+ "assigned_count": assigned_count["count"] if assigned_count else 0,
308
+ "completed_count": completed_count["count"] if completed_count else 0,
315
309
  }
316
-
310
+
317
311
  return {
318
- 'users': user_list,
319
- 'user_activity': user_activity,
320
- 'total_users': len(user_list)
312
+ "users": user_list,
313
+ "user_activity": user_activity,
314
+ "total_users": len(user_list),
321
315
  }
322
-
316
+
323
317
  def get_user_workload(self, engagement_id: int) -> List[Dict]:
324
318
  """
325
319
  Get workload per user.
326
-
320
+
327
321
  Returns:
328
322
  List of users with workload stats
329
323
  """
@@ -341,66 +335,66 @@ class TeamCollaboration:
341
335
  GROUP BY assigned_to
342
336
  ORDER BY total_assigned DESC
343
337
  """,
344
- (engagement_id,)
338
+ (engagement_id,),
345
339
  )
346
-
340
+
347
341
  return workload
348
-
342
+
349
343
  def get_recent_activity_feed(
350
- self,
351
- engagement_id: int,
352
- limit: int = 20
344
+ self, engagement_id: int, limit: int = 20
353
345
  ) -> List[Dict]:
354
346
  """
355
347
  Get recent activity feed with formatted messages.
356
-
348
+
357
349
  Returns:
358
350
  List of activity items with human-readable messages
359
351
  """
360
352
  activities = self.get_activity(engagement_id=engagement_id, limit=limit)
361
-
353
+
362
354
  feed = []
363
355
  for activity in activities:
364
356
  # Format message based on action
365
- action = activity['action']
366
- user = activity['user']
367
- title = activity.get('deliverable_title', 'Unknown')
368
- details = activity.get('details', '')
369
- created_at = activity['created_at']
370
-
371
- if action == 'started':
357
+ action = activity["action"]
358
+ user = activity["user"]
359
+ title = activity.get("deliverable_title", "Unknown")
360
+ details = activity.get("details", "")
361
+ created_at = activity["created_at"]
362
+
363
+ if action == "started":
372
364
  message = f"{user} started '{title}'"
373
- elif action == 'completed':
365
+ elif action == "completed":
374
366
  message = f"{user} completed '{title}'"
375
- elif action == 'updated':
367
+ elif action == "updated":
376
368
  message = f"{user} updated '{title}'"
377
369
  if details:
378
370
  message += f" ({details})"
379
- elif action == 'assigned':
371
+ elif action == "assigned":
380
372
  message = f"{user} {details} for '{title}'"
381
- elif action == 'unassigned':
373
+ elif action == "unassigned":
382
374
  message = f"{user} unassigned '{title}'"
383
- elif action == 'blocker_set':
375
+ elif action == "blocker_set":
384
376
  message = f"{user} set blocker on '{title}': {details}"
385
- elif action == 'blocker_cleared':
377
+ elif action == "blocker_cleared":
386
378
  message = f"{user} cleared blocker on '{title}'"
387
- elif action == 'evidence_linked':
379
+ elif action == "evidence_linked":
388
380
  message = f"{user} linked evidence to '{title}'"
389
381
  if details:
390
382
  message += f" ({details})"
391
- elif action == 'commented':
383
+ elif action == "commented":
392
384
  message = f"{user} commented on '{title}'"
393
385
  else:
394
386
  message = f"{user} {action} '{title}'"
395
-
396
- feed.append({
397
- 'id': activity['id'],
398
- 'message': message,
399
- 'action': action,
400
- 'user': user,
401
- 'deliverable_id': activity['deliverable_id'],
402
- 'created_at': created_at,
403
- 'details': details
404
- })
405
-
387
+
388
+ feed.append(
389
+ {
390
+ "id": activity["id"],
391
+ "message": message,
392
+ "action": action,
393
+ "user": user,
394
+ "deliverable_id": activity["deliverable_id"],
395
+ "created_at": created_at,
396
+ "details": details,
397
+ }
398
+ )
399
+
406
400
  return feed