strix-agent 0.4.0__py3-none-any.whl → 0.6.2__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 (117) hide show
  1. strix/agents/StrixAgent/strix_agent.py +3 -3
  2. strix/agents/StrixAgent/system_prompt.jinja +30 -26
  3. strix/agents/base_agent.py +159 -75
  4. strix/agents/state.py +5 -2
  5. strix/config/__init__.py +12 -0
  6. strix/config/config.py +172 -0
  7. strix/interface/assets/tui_styles.tcss +195 -230
  8. strix/interface/cli.py +16 -41
  9. strix/interface/main.py +151 -74
  10. strix/interface/streaming_parser.py +119 -0
  11. strix/interface/tool_components/__init__.py +4 -0
  12. strix/interface/tool_components/agent_message_renderer.py +190 -0
  13. strix/interface/tool_components/agents_graph_renderer.py +54 -38
  14. strix/interface/tool_components/base_renderer.py +68 -36
  15. strix/interface/tool_components/browser_renderer.py +106 -91
  16. strix/interface/tool_components/file_edit_renderer.py +117 -36
  17. strix/interface/tool_components/finish_renderer.py +43 -10
  18. strix/interface/tool_components/notes_renderer.py +63 -38
  19. strix/interface/tool_components/proxy_renderer.py +133 -92
  20. strix/interface/tool_components/python_renderer.py +121 -8
  21. strix/interface/tool_components/registry.py +19 -12
  22. strix/interface/tool_components/reporting_renderer.py +196 -28
  23. strix/interface/tool_components/scan_info_renderer.py +22 -19
  24. strix/interface/tool_components/terminal_renderer.py +270 -90
  25. strix/interface/tool_components/thinking_renderer.py +8 -6
  26. strix/interface/tool_components/todo_renderer.py +225 -0
  27. strix/interface/tool_components/user_message_renderer.py +26 -19
  28. strix/interface/tool_components/web_search_renderer.py +7 -6
  29. strix/interface/tui.py +907 -262
  30. strix/interface/utils.py +236 -4
  31. strix/llm/__init__.py +6 -2
  32. strix/llm/config.py +8 -5
  33. strix/llm/dedupe.py +217 -0
  34. strix/llm/llm.py +209 -356
  35. strix/llm/memory_compressor.py +6 -5
  36. strix/llm/utils.py +17 -8
  37. strix/runtime/__init__.py +12 -3
  38. strix/runtime/docker_runtime.py +121 -202
  39. strix/runtime/tool_server.py +55 -95
  40. strix/skills/README.md +64 -0
  41. strix/skills/__init__.py +110 -0
  42. strix/{prompts → skills}/frameworks/nextjs.jinja +26 -0
  43. strix/skills/scan_modes/deep.jinja +145 -0
  44. strix/skills/scan_modes/quick.jinja +63 -0
  45. strix/skills/scan_modes/standard.jinja +91 -0
  46. strix/telemetry/README.md +38 -0
  47. strix/telemetry/__init__.py +7 -1
  48. strix/telemetry/posthog.py +137 -0
  49. strix/telemetry/tracer.py +194 -54
  50. strix/tools/__init__.py +11 -4
  51. strix/tools/agents_graph/agents_graph_actions.py +20 -21
  52. strix/tools/agents_graph/agents_graph_actions_schema.xml +8 -8
  53. strix/tools/browser/browser_actions.py +10 -6
  54. strix/tools/browser/browser_actions_schema.xml +6 -1
  55. strix/tools/browser/browser_instance.py +96 -48
  56. strix/tools/browser/tab_manager.py +121 -102
  57. strix/tools/context.py +12 -0
  58. strix/tools/executor.py +63 -4
  59. strix/tools/file_edit/file_edit_actions.py +6 -3
  60. strix/tools/file_edit/file_edit_actions_schema.xml +45 -3
  61. strix/tools/finish/finish_actions.py +80 -105
  62. strix/tools/finish/finish_actions_schema.xml +121 -14
  63. strix/tools/notes/notes_actions.py +6 -33
  64. strix/tools/notes/notes_actions_schema.xml +50 -46
  65. strix/tools/proxy/proxy_actions.py +14 -2
  66. strix/tools/proxy/proxy_actions_schema.xml +0 -1
  67. strix/tools/proxy/proxy_manager.py +28 -16
  68. strix/tools/python/python_actions.py +2 -2
  69. strix/tools/python/python_actions_schema.xml +9 -1
  70. strix/tools/python/python_instance.py +39 -37
  71. strix/tools/python/python_manager.py +43 -31
  72. strix/tools/registry.py +73 -12
  73. strix/tools/reporting/reporting_actions.py +218 -31
  74. strix/tools/reporting/reporting_actions_schema.xml +256 -8
  75. strix/tools/terminal/terminal_actions.py +2 -2
  76. strix/tools/terminal/terminal_actions_schema.xml +6 -0
  77. strix/tools/terminal/terminal_manager.py +41 -30
  78. strix/tools/thinking/thinking_actions_schema.xml +27 -25
  79. strix/tools/todo/__init__.py +18 -0
  80. strix/tools/todo/todo_actions.py +568 -0
  81. strix/tools/todo/todo_actions_schema.xml +225 -0
  82. strix/utils/__init__.py +0 -0
  83. strix/utils/resource_paths.py +13 -0
  84. {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/METADATA +90 -65
  85. strix_agent-0.6.2.dist-info/RECORD +134 -0
  86. {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/WHEEL +1 -1
  87. strix/llm/request_queue.py +0 -87
  88. strix/prompts/README.md +0 -64
  89. strix/prompts/__init__.py +0 -109
  90. strix_agent-0.4.0.dist-info/RECORD +0 -118
  91. /strix/{prompts → skills}/cloud/.gitkeep +0 -0
  92. /strix/{prompts → skills}/coordination/root_agent.jinja +0 -0
  93. /strix/{prompts → skills}/custom/.gitkeep +0 -0
  94. /strix/{prompts → skills}/frameworks/fastapi.jinja +0 -0
  95. /strix/{prompts → skills}/protocols/graphql.jinja +0 -0
  96. /strix/{prompts → skills}/reconnaissance/.gitkeep +0 -0
  97. /strix/{prompts → skills}/technologies/firebase_firestore.jinja +0 -0
  98. /strix/{prompts → skills}/technologies/supabase.jinja +0 -0
  99. /strix/{prompts → skills}/vulnerabilities/authentication_jwt.jinja +0 -0
  100. /strix/{prompts → skills}/vulnerabilities/broken_function_level_authorization.jinja +0 -0
  101. /strix/{prompts → skills}/vulnerabilities/business_logic.jinja +0 -0
  102. /strix/{prompts → skills}/vulnerabilities/csrf.jinja +0 -0
  103. /strix/{prompts → skills}/vulnerabilities/idor.jinja +0 -0
  104. /strix/{prompts → skills}/vulnerabilities/information_disclosure.jinja +0 -0
  105. /strix/{prompts → skills}/vulnerabilities/insecure_file_uploads.jinja +0 -0
  106. /strix/{prompts → skills}/vulnerabilities/mass_assignment.jinja +0 -0
  107. /strix/{prompts → skills}/vulnerabilities/open_redirect.jinja +0 -0
  108. /strix/{prompts → skills}/vulnerabilities/path_traversal_lfi_rfi.jinja +0 -0
  109. /strix/{prompts → skills}/vulnerabilities/race_conditions.jinja +0 -0
  110. /strix/{prompts → skills}/vulnerabilities/rce.jinja +0 -0
  111. /strix/{prompts → skills}/vulnerabilities/sql_injection.jinja +0 -0
  112. /strix/{prompts → skills}/vulnerabilities/ssrf.jinja +0 -0
  113. /strix/{prompts → skills}/vulnerabilities/subdomain_takeover.jinja +0 -0
  114. /strix/{prompts → skills}/vulnerabilities/xss.jinja +0 -0
  115. /strix/{prompts → skills}/vulnerabilities/xxe.jinja +0 -0
  116. {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/entry_points.txt +0 -0
  117. {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info/licenses}/LICENSE +0 -0
@@ -1,53 +1,221 @@
1
+ from functools import cache
1
2
  from typing import Any, ClassVar
2
3
 
4
+ from pygments.lexers import PythonLexer
5
+ from pygments.styles import get_style_by_name
6
+ from rich.padding import Padding
7
+ from rich.text import Text
3
8
  from textual.widgets import Static
4
9
 
5
10
  from .base_renderer import BaseToolRenderer
6
11
  from .registry import register_tool_renderer
7
12
 
8
13
 
14
+ @cache
15
+ def _get_style_colors() -> dict[Any, str]:
16
+ style = get_style_by_name("native")
17
+ return {token: f"#{style_def['color']}" for token, style_def in style if style_def["color"]}
18
+
19
+
20
+ FIELD_STYLE = "bold #4ade80"
21
+ BG_COLOR = "#141414"
22
+
23
+
9
24
  @register_tool_renderer
10
25
  class CreateVulnerabilityReportRenderer(BaseToolRenderer):
11
26
  tool_name: ClassVar[str] = "create_vulnerability_report"
12
27
  css_classes: ClassVar[list[str]] = ["tool-call", "reporting-tool"]
13
28
 
29
+ SEVERITY_COLORS: ClassVar[dict[str, str]] = {
30
+ "critical": "#dc2626",
31
+ "high": "#ea580c",
32
+ "medium": "#d97706",
33
+ "low": "#65a30d",
34
+ "info": "#0284c7",
35
+ }
36
+
37
+ @classmethod
38
+ def _get_token_color(cls, token_type: Any) -> str | None:
39
+ colors = _get_style_colors()
40
+ while token_type:
41
+ if token_type in colors:
42
+ return colors[token_type]
43
+ token_type = token_type.parent
44
+ return None
45
+
14
46
  @classmethod
15
- def render(cls, tool_data: dict[str, Any]) -> Static:
47
+ def _highlight_python(cls, code: str) -> Text:
48
+ lexer = PythonLexer()
49
+ text = Text()
50
+
51
+ for token_type, token_value in lexer.get_tokens(code):
52
+ if not token_value:
53
+ continue
54
+ color = cls._get_token_color(token_type)
55
+ text.append(token_value, style=color)
56
+
57
+ return text
58
+
59
+ @classmethod
60
+ def _get_cvss_color(cls, cvss_score: float) -> str:
61
+ if cvss_score >= 9.0:
62
+ return "#dc2626"
63
+ if cvss_score >= 7.0:
64
+ return "#ea580c"
65
+ if cvss_score >= 4.0:
66
+ return "#d97706"
67
+ if cvss_score >= 0.1:
68
+ return "#65a30d"
69
+ return "#6b7280"
70
+
71
+ @classmethod
72
+ def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: PLR0912, PLR0915
16
73
  args = tool_data.get("args", {})
74
+ result = tool_data.get("result", {})
17
75
 
18
76
  title = args.get("title", "")
19
- severity = args.get("severity", "")
20
- content = args.get("content", "")
77
+ description = args.get("description", "")
78
+ impact = args.get("impact", "")
79
+ target = args.get("target", "")
80
+ technical_analysis = args.get("technical_analysis", "")
81
+ poc_description = args.get("poc_description", "")
82
+ poc_script_code = args.get("poc_script_code", "")
83
+ remediation_steps = args.get("remediation_steps", "")
84
+
85
+ attack_vector = args.get("attack_vector", "")
86
+ attack_complexity = args.get("attack_complexity", "")
87
+ privileges_required = args.get("privileges_required", "")
88
+ user_interaction = args.get("user_interaction", "")
89
+ scope = args.get("scope", "")
90
+ confidentiality = args.get("confidentiality", "")
91
+ integrity = args.get("integrity", "")
92
+ availability = args.get("availability", "")
93
+
94
+ endpoint = args.get("endpoint", "")
95
+ method = args.get("method", "")
96
+ cve = args.get("cve", "")
21
97
 
22
- header = "🐞 [bold #ea580c]Vulnerability Report[/]"
98
+ severity = ""
99
+ cvss_score = None
100
+ if isinstance(result, dict):
101
+ severity = result.get("severity", "")
102
+ cvss_score = result.get("cvss_score")
103
+
104
+ text = Text()
105
+ text.append("🐞 ")
106
+ text.append("Vulnerability Report", style="bold #ea580c")
23
107
 
24
108
  if title:
25
- content_parts = [f"{header}\n [bold]{cls.escape_markup(title)}[/]"]
109
+ text.append("\n\n")
110
+ text.append("Title: ", style=FIELD_STYLE)
111
+ text.append(title)
26
112
 
27
- if severity:
28
- severity_color = cls._get_severity_color(severity.lower())
29
- content_parts.append(
30
- f" [dim]Severity: [{severity_color}]"
31
- f"{cls.escape_markup(severity.upper())}[/{severity_color}][/]"
32
- )
113
+ if severity:
114
+ text.append("\n\n")
115
+ text.append("Severity: ", style=FIELD_STYLE)
116
+ severity_color = cls.SEVERITY_COLORS.get(severity.lower(), "#6b7280")
117
+ text.append(severity.upper(), style=f"bold {severity_color}")
33
118
 
34
- if content:
35
- content_parts.append(f" [dim]{cls.escape_markup(content)}[/]")
119
+ if cvss_score is not None:
120
+ text.append("\n\n")
121
+ text.append("CVSS Score: ", style=FIELD_STYLE)
122
+ cvss_color = cls._get_cvss_color(cvss_score)
123
+ text.append(str(cvss_score), style=f"bold {cvss_color}")
36
124
 
37
- content_text = "\n".join(content_parts)
38
- else:
39
- content_text = f"{header}\n [dim]Creating report...[/]"
125
+ if target:
126
+ text.append("\n\n")
127
+ text.append("Target: ", style=FIELD_STYLE)
128
+ text.append(target)
40
129
 
41
- css_classes = cls.get_css_classes("completed")
42
- return Static(content_text, classes=css_classes)
130
+ if endpoint:
131
+ text.append("\n\n")
132
+ text.append("Endpoint: ", style=FIELD_STYLE)
133
+ text.append(endpoint)
43
134
 
44
- @classmethod
45
- def _get_severity_color(cls, severity: str) -> str:
46
- severity_colors = {
47
- "critical": "#dc2626",
48
- "high": "#ea580c",
49
- "medium": "#d97706",
50
- "low": "#65a30d",
51
- "info": "#0284c7",
52
- }
53
- return severity_colors.get(severity, "#6b7280")
135
+ if method:
136
+ text.append("\n\n")
137
+ text.append("Method: ", style=FIELD_STYLE)
138
+ text.append(method)
139
+
140
+ if cve:
141
+ text.append("\n\n")
142
+ text.append("CVE: ", style=FIELD_STYLE)
143
+ text.append(cve)
144
+
145
+ if any(
146
+ [
147
+ attack_vector,
148
+ attack_complexity,
149
+ privileges_required,
150
+ user_interaction,
151
+ scope,
152
+ confidentiality,
153
+ integrity,
154
+ availability,
155
+ ]
156
+ ):
157
+ text.append("\n\n")
158
+ cvss_parts = []
159
+ if attack_vector:
160
+ cvss_parts.append(f"AV:{attack_vector}")
161
+ if attack_complexity:
162
+ cvss_parts.append(f"AC:{attack_complexity}")
163
+ if privileges_required:
164
+ cvss_parts.append(f"PR:{privileges_required}")
165
+ if user_interaction:
166
+ cvss_parts.append(f"UI:{user_interaction}")
167
+ if scope:
168
+ cvss_parts.append(f"S:{scope}")
169
+ if confidentiality:
170
+ cvss_parts.append(f"C:{confidentiality}")
171
+ if integrity:
172
+ cvss_parts.append(f"I:{integrity}")
173
+ if availability:
174
+ cvss_parts.append(f"A:{availability}")
175
+ text.append("CVSS Vector: ", style=FIELD_STYLE)
176
+ text.append("/".join(cvss_parts), style="dim")
177
+
178
+ if description:
179
+ text.append("\n\n")
180
+ text.append("Description", style=FIELD_STYLE)
181
+ text.append("\n")
182
+ text.append(description)
183
+
184
+ if impact:
185
+ text.append("\n\n")
186
+ text.append("Impact", style=FIELD_STYLE)
187
+ text.append("\n")
188
+ text.append(impact)
189
+
190
+ if technical_analysis:
191
+ text.append("\n\n")
192
+ text.append("Technical Analysis", style=FIELD_STYLE)
193
+ text.append("\n")
194
+ text.append(technical_analysis)
195
+
196
+ if poc_description:
197
+ text.append("\n\n")
198
+ text.append("PoC Description", style=FIELD_STYLE)
199
+ text.append("\n")
200
+ text.append(poc_description)
201
+
202
+ if poc_script_code:
203
+ text.append("\n\n")
204
+ text.append("PoC Code", style=FIELD_STYLE)
205
+ text.append("\n")
206
+ text.append_text(cls._highlight_python(poc_script_code))
207
+
208
+ if remediation_steps:
209
+ text.append("\n\n")
210
+ text.append("Remediation", style=FIELD_STYLE)
211
+ text.append("\n")
212
+ text.append(remediation_steps)
213
+
214
+ if not title:
215
+ text.append("\n ")
216
+ text.append("Creating report...", style="dim")
217
+
218
+ padded = Padding(text, 2, style=f"on {BG_COLOR}")
219
+
220
+ css_classes = cls.get_css_classes("completed")
221
+ return Static(padded, classes=css_classes)
@@ -1,5 +1,6 @@
1
1
  from typing import Any, ClassVar
2
2
 
3
+ from rich.text import Text
3
4
  from textual.widgets import Static
4
5
 
5
6
  from .base_renderer import BaseToolRenderer
@@ -15,29 +16,28 @@ class ScanStartInfoRenderer(BaseToolRenderer):
15
16
  def render(cls, tool_data: dict[str, Any]) -> Static:
16
17
  args = tool_data.get("args", {})
17
18
  status = tool_data.get("status", "unknown")
18
-
19
19
  targets = args.get("targets", [])
20
20
 
21
+ text = Text()
22
+ text.append("🚀 Starting penetration test")
23
+
21
24
  if len(targets) == 1:
22
- target_display = cls._build_single_target_display(targets[0])
23
- content = f"🚀 Starting penetration test on {target_display}"
25
+ text.append(" on ")
26
+ text.append(cls._get_target_display(targets[0]))
24
27
  elif len(targets) > 1:
25
- content = f"🚀 Starting penetration test on {len(targets)} targets"
28
+ text.append(f" on {len(targets)} targets")
26
29
  for target_info in targets:
27
- target_display = cls._build_single_target_display(target_info)
28
- content += f"\n • {target_display}"
29
- else:
30
- content = "🚀 Starting penetration test"
30
+ text.append("\n • ")
31
+ text.append(cls._get_target_display(target_info))
31
32
 
32
33
  css_classes = cls.get_css_classes(status)
33
- return Static(content, classes=css_classes)
34
+ return Static(text, classes=css_classes)
34
35
 
35
36
  @classmethod
36
- def _build_single_target_display(cls, target_info: dict[str, Any]) -> str:
37
+ def _get_target_display(cls, target_info: dict[str, Any]) -> str:
37
38
  original = target_info.get("original")
38
39
  if original:
39
- return cls.escape_markup(str(original))
40
-
40
+ return str(original)
41
41
  return "unknown target"
42
42
 
43
43
 
@@ -51,14 +51,17 @@ class SubagentStartInfoRenderer(BaseToolRenderer):
51
51
  args = tool_data.get("args", {})
52
52
  status = tool_data.get("status", "unknown")
53
53
 
54
- name = args.get("name", "Unknown Agent")
55
- task = args.get("task", "")
54
+ name = str(args.get("name", "Unknown Agent"))
55
+ task = str(args.get("task", ""))
56
+
57
+ text = Text()
58
+ text.append("◈ ", style="#a78bfa")
59
+ text.append("subagent ", style="dim")
60
+ text.append(name, style="bold #a78bfa")
56
61
 
57
- name = cls.escape_markup(str(name))
58
- content = f"🤖 Spawned subagent {name}"
59
62
  if task:
60
- task = cls.escape_markup(str(task))
61
- content += f"\n Task: {task}"
63
+ text.append("\n ")
64
+ text.append(task, style="dim")
62
65
 
63
66
  css_classes = cls.get_css_classes(status)
64
- return Static(content, classes=css_classes)
67
+ return Static(text, classes=css_classes)