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.
- strix/agents/StrixAgent/strix_agent.py +3 -3
- strix/agents/StrixAgent/system_prompt.jinja +30 -26
- strix/agents/base_agent.py +159 -75
- strix/agents/state.py +5 -2
- strix/config/__init__.py +12 -0
- strix/config/config.py +172 -0
- strix/interface/assets/tui_styles.tcss +195 -230
- strix/interface/cli.py +16 -41
- strix/interface/main.py +151 -74
- strix/interface/streaming_parser.py +119 -0
- strix/interface/tool_components/__init__.py +4 -0
- strix/interface/tool_components/agent_message_renderer.py +190 -0
- strix/interface/tool_components/agents_graph_renderer.py +54 -38
- strix/interface/tool_components/base_renderer.py +68 -36
- strix/interface/tool_components/browser_renderer.py +106 -91
- strix/interface/tool_components/file_edit_renderer.py +117 -36
- strix/interface/tool_components/finish_renderer.py +43 -10
- strix/interface/tool_components/notes_renderer.py +63 -38
- strix/interface/tool_components/proxy_renderer.py +133 -92
- strix/interface/tool_components/python_renderer.py +121 -8
- strix/interface/tool_components/registry.py +19 -12
- strix/interface/tool_components/reporting_renderer.py +196 -28
- strix/interface/tool_components/scan_info_renderer.py +22 -19
- strix/interface/tool_components/terminal_renderer.py +270 -90
- strix/interface/tool_components/thinking_renderer.py +8 -6
- strix/interface/tool_components/todo_renderer.py +225 -0
- strix/interface/tool_components/user_message_renderer.py +26 -19
- strix/interface/tool_components/web_search_renderer.py +7 -6
- strix/interface/tui.py +907 -262
- strix/interface/utils.py +236 -4
- strix/llm/__init__.py +6 -2
- strix/llm/config.py +8 -5
- strix/llm/dedupe.py +217 -0
- strix/llm/llm.py +209 -356
- strix/llm/memory_compressor.py +6 -5
- strix/llm/utils.py +17 -8
- strix/runtime/__init__.py +12 -3
- strix/runtime/docker_runtime.py +121 -202
- strix/runtime/tool_server.py +55 -95
- strix/skills/README.md +64 -0
- strix/skills/__init__.py +110 -0
- strix/{prompts → skills}/frameworks/nextjs.jinja +26 -0
- strix/skills/scan_modes/deep.jinja +145 -0
- strix/skills/scan_modes/quick.jinja +63 -0
- strix/skills/scan_modes/standard.jinja +91 -0
- strix/telemetry/README.md +38 -0
- strix/telemetry/__init__.py +7 -1
- strix/telemetry/posthog.py +137 -0
- strix/telemetry/tracer.py +194 -54
- strix/tools/__init__.py +11 -4
- strix/tools/agents_graph/agents_graph_actions.py +20 -21
- strix/tools/agents_graph/agents_graph_actions_schema.xml +8 -8
- strix/tools/browser/browser_actions.py +10 -6
- strix/tools/browser/browser_actions_schema.xml +6 -1
- strix/tools/browser/browser_instance.py +96 -48
- strix/tools/browser/tab_manager.py +121 -102
- strix/tools/context.py +12 -0
- strix/tools/executor.py +63 -4
- strix/tools/file_edit/file_edit_actions.py +6 -3
- strix/tools/file_edit/file_edit_actions_schema.xml +45 -3
- strix/tools/finish/finish_actions.py +80 -105
- strix/tools/finish/finish_actions_schema.xml +121 -14
- strix/tools/notes/notes_actions.py +6 -33
- strix/tools/notes/notes_actions_schema.xml +50 -46
- strix/tools/proxy/proxy_actions.py +14 -2
- strix/tools/proxy/proxy_actions_schema.xml +0 -1
- strix/tools/proxy/proxy_manager.py +28 -16
- strix/tools/python/python_actions.py +2 -2
- strix/tools/python/python_actions_schema.xml +9 -1
- strix/tools/python/python_instance.py +39 -37
- strix/tools/python/python_manager.py +43 -31
- strix/tools/registry.py +73 -12
- strix/tools/reporting/reporting_actions.py +218 -31
- strix/tools/reporting/reporting_actions_schema.xml +256 -8
- strix/tools/terminal/terminal_actions.py +2 -2
- strix/tools/terminal/terminal_actions_schema.xml +6 -0
- strix/tools/terminal/terminal_manager.py +41 -30
- strix/tools/thinking/thinking_actions_schema.xml +27 -25
- strix/tools/todo/__init__.py +18 -0
- strix/tools/todo/todo_actions.py +568 -0
- strix/tools/todo/todo_actions_schema.xml +225 -0
- strix/utils/__init__.py +0 -0
- strix/utils/resource_paths.py +13 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/METADATA +90 -65
- strix_agent-0.6.2.dist-info/RECORD +134 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/WHEEL +1 -1
- strix/llm/request_queue.py +0 -87
- strix/prompts/README.md +0 -64
- strix/prompts/__init__.py +0 -109
- strix_agent-0.4.0.dist-info/RECORD +0 -118
- /strix/{prompts → skills}/cloud/.gitkeep +0 -0
- /strix/{prompts → skills}/coordination/root_agent.jinja +0 -0
- /strix/{prompts → skills}/custom/.gitkeep +0 -0
- /strix/{prompts → skills}/frameworks/fastapi.jinja +0 -0
- /strix/{prompts → skills}/protocols/graphql.jinja +0 -0
- /strix/{prompts → skills}/reconnaissance/.gitkeep +0 -0
- /strix/{prompts → skills}/technologies/firebase_firestore.jinja +0 -0
- /strix/{prompts → skills}/technologies/supabase.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/authentication_jwt.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/broken_function_level_authorization.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/business_logic.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/csrf.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/idor.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/information_disclosure.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/insecure_file_uploads.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/mass_assignment.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/open_redirect.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/path_traversal_lfi_rfi.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/race_conditions.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/rce.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/sql_injection.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/ssrf.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/subdomain_takeover.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/xss.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/xxe.jinja +0 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/entry_points.txt +0 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,120 +1,135 @@
|
|
|
1
|
+
from functools import cache
|
|
1
2
|
from typing import Any, ClassVar
|
|
2
3
|
|
|
4
|
+
from pygments.lexers import get_lexer_by_name
|
|
5
|
+
from pygments.styles import get_style_by_name
|
|
6
|
+
from rich.text import Text
|
|
3
7
|
from textual.widgets import Static
|
|
4
8
|
|
|
5
9
|
from .base_renderer import BaseToolRenderer
|
|
6
10
|
from .registry import register_tool_renderer
|
|
7
11
|
|
|
8
12
|
|
|
13
|
+
@cache
|
|
14
|
+
def _get_style_colors() -> dict[Any, str]:
|
|
15
|
+
style = get_style_by_name("native")
|
|
16
|
+
return {token: f"#{style_def['color']}" for token, style_def in style if style_def["color"]}
|
|
17
|
+
|
|
18
|
+
|
|
9
19
|
@register_tool_renderer
|
|
10
20
|
class BrowserRenderer(BaseToolRenderer):
|
|
11
21
|
tool_name: ClassVar[str] = "browser_action"
|
|
12
22
|
css_classes: ClassVar[list[str]] = ["tool-call", "browser-tool"]
|
|
13
23
|
|
|
24
|
+
SIMPLE_ACTIONS: ClassVar[dict[str, str]] = {
|
|
25
|
+
"back": "going back in browser history",
|
|
26
|
+
"forward": "going forward in browser history",
|
|
27
|
+
"scroll_down": "scrolling down",
|
|
28
|
+
"scroll_up": "scrolling up",
|
|
29
|
+
"refresh": "refreshing browser tab",
|
|
30
|
+
"close_tab": "closing browser tab",
|
|
31
|
+
"switch_tab": "switching browser tab",
|
|
32
|
+
"list_tabs": "listing browser tabs",
|
|
33
|
+
"view_source": "viewing page source",
|
|
34
|
+
"get_console_logs": "getting console logs",
|
|
35
|
+
"screenshot": "taking screenshot of browser tab",
|
|
36
|
+
"wait": "waiting...",
|
|
37
|
+
"close": "closing browser",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def _get_token_color(cls, token_type: Any) -> str | None:
|
|
42
|
+
colors = _get_style_colors()
|
|
43
|
+
while token_type:
|
|
44
|
+
if token_type in colors:
|
|
45
|
+
return colors[token_type]
|
|
46
|
+
token_type = token_type.parent
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def _highlight_js(cls, code: str) -> Text:
|
|
51
|
+
lexer = get_lexer_by_name("javascript")
|
|
52
|
+
text = Text()
|
|
53
|
+
|
|
54
|
+
for token_type, token_value in lexer.get_tokens(code):
|
|
55
|
+
if not token_value:
|
|
56
|
+
continue
|
|
57
|
+
color = cls._get_token_color(token_type)
|
|
58
|
+
text.append(token_value, style=color)
|
|
59
|
+
|
|
60
|
+
return text
|
|
61
|
+
|
|
14
62
|
@classmethod
|
|
15
63
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
16
64
|
args = tool_data.get("args", {})
|
|
17
65
|
status = tool_data.get("status", "unknown")
|
|
18
66
|
|
|
19
67
|
action = args.get("action", "unknown")
|
|
20
|
-
|
|
21
|
-
content = cls._build_sleek_content(action, args)
|
|
68
|
+
content = cls._build_content(action, args)
|
|
22
69
|
|
|
23
70
|
css_classes = cls.get_css_classes(status)
|
|
24
71
|
return Static(content, classes=css_classes)
|
|
25
72
|
|
|
26
73
|
@classmethod
|
|
27
|
-
def
|
|
28
|
-
|
|
74
|
+
def _build_url_action(cls, text: Text, label: str, url: str | None, suffix: str = "") -> None:
|
|
75
|
+
text.append(label, style="#06b6d4")
|
|
76
|
+
if url:
|
|
77
|
+
text.append(url, style="#06b6d4")
|
|
78
|
+
if suffix:
|
|
79
|
+
text.append(suffix, style="#06b6d4")
|
|
29
80
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
file_path = args.get("file_path")
|
|
35
|
-
|
|
36
|
-
if action in [
|
|
37
|
-
"launch",
|
|
38
|
-
"goto",
|
|
39
|
-
"new_tab",
|
|
40
|
-
"type",
|
|
41
|
-
"execute_js",
|
|
42
|
-
"click",
|
|
43
|
-
"double_click",
|
|
44
|
-
"hover",
|
|
45
|
-
"press_key",
|
|
46
|
-
"save_pdf",
|
|
47
|
-
]:
|
|
48
|
-
if action == "launch":
|
|
49
|
-
display_url = cls._format_url(url) if url else None
|
|
50
|
-
message = (
|
|
51
|
-
f"launching {display_url} on browser" if display_url else "launching browser"
|
|
52
|
-
)
|
|
53
|
-
elif action == "goto":
|
|
54
|
-
display_url = cls._format_url(url) if url else None
|
|
55
|
-
message = f"navigating to {display_url}" if display_url else "navigating"
|
|
56
|
-
elif action == "new_tab":
|
|
57
|
-
display_url = cls._format_url(url) if url else None
|
|
58
|
-
message = f"opening tab {display_url}" if display_url else "opening tab"
|
|
59
|
-
elif action == "type":
|
|
60
|
-
display_text = cls._format_text(text) if text else None
|
|
61
|
-
message = f"typing {display_text}" if display_text else "typing"
|
|
62
|
-
elif action == "execute_js":
|
|
63
|
-
display_js = cls._format_js(js_code) if js_code else None
|
|
64
|
-
message = (
|
|
65
|
-
f"executing javascript\n{display_js}" if display_js else "executing javascript"
|
|
66
|
-
)
|
|
67
|
-
elif action == "press_key":
|
|
68
|
-
display_key = cls.escape_markup(key) if key else None
|
|
69
|
-
message = f"pressing key {display_key}" if display_key else "pressing key"
|
|
70
|
-
elif action == "save_pdf":
|
|
71
|
-
display_path = cls.escape_markup(file_path) if file_path else None
|
|
72
|
-
message = f"saving PDF to {display_path}" if display_path else "saving PDF"
|
|
73
|
-
else:
|
|
74
|
-
action_words = {
|
|
75
|
-
"click": "clicking",
|
|
76
|
-
"double_click": "double clicking",
|
|
77
|
-
"hover": "hovering",
|
|
78
|
-
}
|
|
79
|
-
message = cls.escape_markup(action_words[action])
|
|
80
|
-
|
|
81
|
-
return f"{browser_icon} [#06b6d4]{message}[/]"
|
|
82
|
-
|
|
83
|
-
simple_actions = {
|
|
84
|
-
"back": "going back in browser history",
|
|
85
|
-
"forward": "going forward in browser history",
|
|
86
|
-
"scroll_down": "scrolling down",
|
|
87
|
-
"scroll_up": "scrolling up",
|
|
88
|
-
"refresh": "refreshing browser tab",
|
|
89
|
-
"close_tab": "closing browser tab",
|
|
90
|
-
"switch_tab": "switching browser tab",
|
|
91
|
-
"list_tabs": "listing browser tabs",
|
|
92
|
-
"view_source": "viewing page source",
|
|
93
|
-
"get_console_logs": "getting console logs",
|
|
94
|
-
"screenshot": "taking screenshot of browser tab",
|
|
95
|
-
"wait": "waiting...",
|
|
96
|
-
"close": "closing browser",
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if action in simple_actions:
|
|
100
|
-
return f"{browser_icon} [#06b6d4]{cls.escape_markup(simple_actions[action])}[/]"
|
|
81
|
+
@classmethod
|
|
82
|
+
def _build_content(cls, action: str, args: dict[str, Any]) -> Text:
|
|
83
|
+
text = Text()
|
|
84
|
+
text.append("🌐 ")
|
|
101
85
|
|
|
102
|
-
|
|
86
|
+
if action in cls.SIMPLE_ACTIONS:
|
|
87
|
+
text.append(cls.SIMPLE_ACTIONS[action], style="#06b6d4")
|
|
88
|
+
return text
|
|
103
89
|
|
|
104
|
-
|
|
105
|
-
def _format_url(cls, url: str) -> str:
|
|
106
|
-
if len(url) > 300:
|
|
107
|
-
url = url[:297] + "..."
|
|
108
|
-
return cls.escape_markup(url)
|
|
90
|
+
url = args.get("url")
|
|
109
91
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
92
|
+
url_actions = {
|
|
93
|
+
"launch": ("launching ", " on browser" if url else "browser"),
|
|
94
|
+
"goto": ("navigating to ", ""),
|
|
95
|
+
"new_tab": ("opening tab ", ""),
|
|
96
|
+
}
|
|
97
|
+
if action in url_actions:
|
|
98
|
+
label, suffix = url_actions[action]
|
|
99
|
+
if action == "launch" and not url:
|
|
100
|
+
text.append("launching browser", style="#06b6d4")
|
|
101
|
+
else:
|
|
102
|
+
cls._build_url_action(text, label, url, suffix)
|
|
103
|
+
return text
|
|
115
104
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
105
|
+
click_actions = {
|
|
106
|
+
"click": "clicking",
|
|
107
|
+
"double_click": "double clicking",
|
|
108
|
+
"hover": "hovering",
|
|
109
|
+
}
|
|
110
|
+
if action in click_actions:
|
|
111
|
+
text.append(click_actions[action], style="#06b6d4")
|
|
112
|
+
return text
|
|
113
|
+
|
|
114
|
+
handlers: dict[str, tuple[str, str | None]] = {
|
|
115
|
+
"type": ("typing ", args.get("text")),
|
|
116
|
+
"press_key": ("pressing key ", args.get("key")),
|
|
117
|
+
"save_pdf": ("saving PDF to ", args.get("file_path")),
|
|
118
|
+
}
|
|
119
|
+
if action in handlers:
|
|
120
|
+
label, value = handlers[action]
|
|
121
|
+
text.append(label, style="#06b6d4")
|
|
122
|
+
if value:
|
|
123
|
+
text.append(str(value), style="#06b6d4")
|
|
124
|
+
return text
|
|
125
|
+
|
|
126
|
+
if action == "execute_js":
|
|
127
|
+
text.append("executing javascript", style="#06b6d4")
|
|
128
|
+
js_code = args.get("js_code")
|
|
129
|
+
if js_code:
|
|
130
|
+
text.append("\n")
|
|
131
|
+
text.append_text(cls._highlight_js(js_code))
|
|
132
|
+
return text
|
|
133
|
+
|
|
134
|
+
text.append(action, style="#06b6d4")
|
|
135
|
+
return text
|
|
@@ -1,16 +1,56 @@
|
|
|
1
|
+
from functools import cache
|
|
1
2
|
from typing import Any, ClassVar
|
|
2
3
|
|
|
4
|
+
from pygments.lexers import get_lexer_by_name, get_lexer_for_filename
|
|
5
|
+
from pygments.styles import get_style_by_name
|
|
6
|
+
from pygments.util import ClassNotFound
|
|
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
|
+
def _get_lexer_for_file(path: str) -> Any:
|
|
21
|
+
try:
|
|
22
|
+
return get_lexer_for_filename(path)
|
|
23
|
+
except ClassNotFound:
|
|
24
|
+
return get_lexer_by_name("text")
|
|
25
|
+
|
|
26
|
+
|
|
9
27
|
@register_tool_renderer
|
|
10
28
|
class StrReplaceEditorRenderer(BaseToolRenderer):
|
|
11
29
|
tool_name: ClassVar[str] = "str_replace_editor"
|
|
12
30
|
css_classes: ClassVar[list[str]] = ["tool-call", "file-edit-tool"]
|
|
13
31
|
|
|
32
|
+
@classmethod
|
|
33
|
+
def _get_token_color(cls, token_type: Any) -> str | None:
|
|
34
|
+
colors = _get_style_colors()
|
|
35
|
+
while token_type:
|
|
36
|
+
if token_type in colors:
|
|
37
|
+
return colors[token_type]
|
|
38
|
+
token_type = token_type.parent
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def _highlight_code(cls, code: str, path: str) -> Text:
|
|
43
|
+
lexer = _get_lexer_for_file(path)
|
|
44
|
+
text = Text()
|
|
45
|
+
|
|
46
|
+
for token_type, token_value in lexer.get_tokens(code):
|
|
47
|
+
if not token_value:
|
|
48
|
+
continue
|
|
49
|
+
color = cls._get_token_color(token_type)
|
|
50
|
+
text.append(token_value, style=color)
|
|
51
|
+
|
|
52
|
+
return text
|
|
53
|
+
|
|
14
54
|
@classmethod
|
|
15
55
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
16
56
|
args = tool_data.get("args", {})
|
|
@@ -18,28 +58,67 @@ class StrReplaceEditorRenderer(BaseToolRenderer):
|
|
|
18
58
|
|
|
19
59
|
command = args.get("command", "")
|
|
20
60
|
path = args.get("path", "")
|
|
61
|
+
old_str = args.get("old_str", "")
|
|
62
|
+
new_str = args.get("new_str", "")
|
|
63
|
+
file_text = args.get("file_text", "")
|
|
21
64
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
header = "↩️ [bold #10b981]Undoing edit[/]"
|
|
32
|
-
else:
|
|
33
|
-
header = "📄 [bold #10b981]File operation[/]"
|
|
65
|
+
text = Text()
|
|
66
|
+
|
|
67
|
+
icons_and_labels = {
|
|
68
|
+
"view": ("📖 ", "Reading file", "#10b981"),
|
|
69
|
+
"str_replace": ("✏️ ", "Editing file", "#10b981"),
|
|
70
|
+
"create": ("📝 ", "Creating file", "#10b981"),
|
|
71
|
+
"insert": ("✏️ ", "Inserting text", "#10b981"),
|
|
72
|
+
"undo_edit": ("↩️ ", "Undoing edit", "#10b981"),
|
|
73
|
+
}
|
|
34
74
|
|
|
35
|
-
|
|
75
|
+
icon, label, color = icons_and_labels.get(command, ("📄 ", "File operation", "#10b981"))
|
|
76
|
+
text.append(icon)
|
|
77
|
+
text.append(label, style=f"bold {color}")
|
|
78
|
+
|
|
79
|
+
if path:
|
|
36
80
|
path_display = path[-60:] if len(path) > 60 else path
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
81
|
+
text.append(" ")
|
|
82
|
+
text.append(path_display, style="dim")
|
|
83
|
+
|
|
84
|
+
if command == "str_replace" and (old_str or new_str):
|
|
85
|
+
if old_str:
|
|
86
|
+
highlighted_old = cls._highlight_code(old_str, path)
|
|
87
|
+
for line in highlighted_old.plain.split("\n"):
|
|
88
|
+
text.append("\n")
|
|
89
|
+
text.append("-", style="#ef4444")
|
|
90
|
+
text.append(" ")
|
|
91
|
+
text.append(line)
|
|
92
|
+
|
|
93
|
+
if new_str:
|
|
94
|
+
highlighted_new = cls._highlight_code(new_str, path)
|
|
95
|
+
for line in highlighted_new.plain.split("\n"):
|
|
96
|
+
text.append("\n")
|
|
97
|
+
text.append("+", style="#22c55e")
|
|
98
|
+
text.append(" ")
|
|
99
|
+
text.append(line)
|
|
100
|
+
|
|
101
|
+
elif command == "create" and file_text:
|
|
102
|
+
text.append("\n")
|
|
103
|
+
text.append_text(cls._highlight_code(file_text, path))
|
|
104
|
+
|
|
105
|
+
elif command == "insert" and new_str:
|
|
106
|
+
highlighted_new = cls._highlight_code(new_str, path)
|
|
107
|
+
for line in highlighted_new.plain.split("\n"):
|
|
108
|
+
text.append("\n")
|
|
109
|
+
text.append("+", style="#22c55e")
|
|
110
|
+
text.append(" ")
|
|
111
|
+
text.append(line)
|
|
112
|
+
|
|
113
|
+
elif isinstance(result, str) and result.strip():
|
|
114
|
+
text.append("\n ")
|
|
115
|
+
text.append(result.strip(), style="dim")
|
|
116
|
+
elif not (result and isinstance(result, dict) and "content" in result) and not path:
|
|
117
|
+
text.append(" ")
|
|
118
|
+
text.append("Processing...", style="dim")
|
|
40
119
|
|
|
41
120
|
css_classes = cls.get_css_classes("completed")
|
|
42
|
-
return Static(
|
|
121
|
+
return Static(text, classes=css_classes)
|
|
43
122
|
|
|
44
123
|
|
|
45
124
|
@register_tool_renderer
|
|
@@ -50,19 +129,21 @@ class ListFilesRenderer(BaseToolRenderer):
|
|
|
50
129
|
@classmethod
|
|
51
130
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
52
131
|
args = tool_data.get("args", {})
|
|
53
|
-
|
|
54
132
|
path = args.get("path", "")
|
|
55
133
|
|
|
56
|
-
|
|
134
|
+
text = Text()
|
|
135
|
+
text.append("📂 ")
|
|
136
|
+
text.append("Listing files", style="bold #10b981")
|
|
137
|
+
text.append(" ")
|
|
57
138
|
|
|
58
139
|
if path:
|
|
59
140
|
path_display = path[-60:] if len(path) > 60 else path
|
|
60
|
-
|
|
141
|
+
text.append(path_display, style="dim")
|
|
61
142
|
else:
|
|
62
|
-
|
|
143
|
+
text.append("Current directory", style="dim")
|
|
63
144
|
|
|
64
145
|
css_classes = cls.get_css_classes("completed")
|
|
65
|
-
return Static(
|
|
146
|
+
return Static(text, classes=css_classes)
|
|
66
147
|
|
|
67
148
|
|
|
68
149
|
@register_tool_renderer
|
|
@@ -73,27 +154,27 @@ class SearchFilesRenderer(BaseToolRenderer):
|
|
|
73
154
|
@classmethod
|
|
74
155
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
75
156
|
args = tool_data.get("args", {})
|
|
76
|
-
|
|
77
157
|
path = args.get("path", "")
|
|
78
158
|
regex = args.get("regex", "")
|
|
79
159
|
|
|
80
|
-
|
|
160
|
+
text = Text()
|
|
161
|
+
text.append("🔍 ")
|
|
162
|
+
text.append("Searching files", style="bold purple")
|
|
163
|
+
text.append(" ")
|
|
81
164
|
|
|
82
165
|
if path and regex:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
f"'{cls.escape_markup(regex_display)}'[/]"
|
|
88
|
-
)
|
|
166
|
+
text.append(path, style="dim")
|
|
167
|
+
text.append(" for '", style="dim")
|
|
168
|
+
text.append(regex, style="dim")
|
|
169
|
+
text.append("'", style="dim")
|
|
89
170
|
elif path:
|
|
90
|
-
|
|
91
|
-
content_text = f"{header} [dim]{cls.escape_markup(path_display)}[/]"
|
|
171
|
+
text.append(path, style="dim")
|
|
92
172
|
elif regex:
|
|
93
|
-
|
|
94
|
-
|
|
173
|
+
text.append("'", style="dim")
|
|
174
|
+
text.append(regex, style="dim")
|
|
175
|
+
text.append("'", style="dim")
|
|
95
176
|
else:
|
|
96
|
-
|
|
177
|
+
text.append("Searching...", style="dim")
|
|
97
178
|
|
|
98
179
|
css_classes = cls.get_css_classes("completed")
|
|
99
|
-
return Static(
|
|
180
|
+
return Static(text, classes=css_classes)
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
from typing import Any, ClassVar
|
|
2
2
|
|
|
3
|
+
from rich.padding import Padding
|
|
4
|
+
from rich.text import Text
|
|
3
5
|
from textual.widgets import Static
|
|
4
6
|
|
|
5
7
|
from .base_renderer import BaseToolRenderer
|
|
6
8
|
from .registry import register_tool_renderer
|
|
7
9
|
|
|
8
10
|
|
|
11
|
+
FIELD_STYLE = "bold #4ade80"
|
|
12
|
+
BG_COLOR = "#141414"
|
|
13
|
+
|
|
14
|
+
|
|
9
15
|
@register_tool_renderer
|
|
10
16
|
class FinishScanRenderer(BaseToolRenderer):
|
|
11
17
|
tool_name: ClassVar[str] = "finish_scan"
|
|
@@ -15,17 +21,44 @@ class FinishScanRenderer(BaseToolRenderer):
|
|
|
15
21
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
16
22
|
args = tool_data.get("args", {})
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
executive_summary = args.get("executive_summary", "")
|
|
25
|
+
methodology = args.get("methodology", "")
|
|
26
|
+
technical_analysis = args.get("technical_analysis", "")
|
|
27
|
+
recommendations = args.get("recommendations", "")
|
|
28
|
+
|
|
29
|
+
text = Text()
|
|
30
|
+
text.append("🏁 ")
|
|
31
|
+
text.append("Finishing Scan", style="bold #dc2626")
|
|
32
|
+
|
|
33
|
+
if executive_summary:
|
|
34
|
+
text.append("\n\n")
|
|
35
|
+
text.append("Executive Summary", style=FIELD_STYLE)
|
|
36
|
+
text.append("\n")
|
|
37
|
+
text.append(executive_summary)
|
|
38
|
+
|
|
39
|
+
if methodology:
|
|
40
|
+
text.append("\n\n")
|
|
41
|
+
text.append("Methodology", style=FIELD_STYLE)
|
|
42
|
+
text.append("\n")
|
|
43
|
+
text.append(methodology)
|
|
44
|
+
|
|
45
|
+
if technical_analysis:
|
|
46
|
+
text.append("\n\n")
|
|
47
|
+
text.append("Technical Analysis", style=FIELD_STYLE)
|
|
48
|
+
text.append("\n")
|
|
49
|
+
text.append(technical_analysis)
|
|
50
|
+
|
|
51
|
+
if recommendations:
|
|
52
|
+
text.append("\n\n")
|
|
53
|
+
text.append("Recommendations", style=FIELD_STYLE)
|
|
54
|
+
text.append("\n")
|
|
55
|
+
text.append(recommendations)
|
|
20
56
|
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
|
|
57
|
+
if not (executive_summary or methodology or technical_analysis or recommendations):
|
|
58
|
+
text.append("\n ")
|
|
59
|
+
text.append("Generating final report...", style="dim")
|
|
24
60
|
|
|
25
|
-
|
|
26
|
-
content_text = f"{header}\n [bold]{cls.escape_markup(content)}[/]"
|
|
27
|
-
else:
|
|
28
|
-
content_text = f"{header}\n [dim]Generating final report...[/]"
|
|
61
|
+
padded = Padding(text, 2, style=f"on {BG_COLOR}")
|
|
29
62
|
|
|
30
63
|
css_classes = cls.get_css_classes("completed")
|
|
31
|
-
return Static(
|
|
64
|
+
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
|
|
@@ -17,23 +18,28 @@ class CreateNoteRenderer(BaseToolRenderer):
|
|
|
17
18
|
|
|
18
19
|
title = args.get("title", "")
|
|
19
20
|
content = args.get("content", "")
|
|
21
|
+
category = args.get("category", "general")
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
text = Text()
|
|
24
|
+
text.append("📝 ")
|
|
25
|
+
text.append("Note", style="bold #fbbf24")
|
|
26
|
+
text.append(" ")
|
|
27
|
+
text.append(f"({category})", style="dim")
|
|
22
28
|
|
|
23
29
|
if title:
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
text.append("\n ")
|
|
31
|
+
text.append(title.strip())
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
if content:
|
|
34
|
+
text.append("\n ")
|
|
35
|
+
text.append(content.strip(), style="dim")
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
if not title and not content:
|
|
38
|
+
text.append("\n ")
|
|
39
|
+
text.append("Capturing...", style="dim")
|
|
34
40
|
|
|
35
41
|
css_classes = cls.get_css_classes("completed")
|
|
36
|
-
return Static(
|
|
42
|
+
return Static(text, classes=css_classes)
|
|
37
43
|
|
|
38
44
|
|
|
39
45
|
@register_tool_renderer
|
|
@@ -43,11 +49,12 @@ class DeleteNoteRenderer(BaseToolRenderer):
|
|
|
43
49
|
|
|
44
50
|
@classmethod
|
|
45
51
|
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: ARG003
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
text = Text()
|
|
53
|
+
text.append("📝 ")
|
|
54
|
+
text.append("Note Removed", style="bold #94a3b8")
|
|
48
55
|
|
|
49
56
|
css_classes = cls.get_css_classes("completed")
|
|
50
|
-
return Static(
|
|
57
|
+
return Static(text, classes=css_classes)
|
|
51
58
|
|
|
52
59
|
|
|
53
60
|
@register_tool_renderer
|
|
@@ -59,28 +66,27 @@ class UpdateNoteRenderer(BaseToolRenderer):
|
|
|
59
66
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
60
67
|
args = tool_data.get("args", {})
|
|
61
68
|
|
|
62
|
-
title = args.get("title"
|
|
63
|
-
content = args.get("content"
|
|
64
|
-
|
|
65
|
-
header = "✏️ [bold #fbbf24]Update Note[/]"
|
|
69
|
+
title = args.get("title")
|
|
70
|
+
content = args.get("content")
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
text = Text()
|
|
73
|
+
text.append("📝 ")
|
|
74
|
+
text.append("Note Updated", style="bold #fbbf24")
|
|
69
75
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
if title:
|
|
77
|
+
text.append("\n ")
|
|
78
|
+
text.append(title)
|
|
73
79
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
if content:
|
|
81
|
+
text.append("\n ")
|
|
82
|
+
text.append(content.strip(), style="dim")
|
|
77
83
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
if not title and not content:
|
|
85
|
+
text.append("\n ")
|
|
86
|
+
text.append("Updating...", style="dim")
|
|
81
87
|
|
|
82
88
|
css_classes = cls.get_css_classes("completed")
|
|
83
|
-
return Static(
|
|
89
|
+
return Static(text, classes=css_classes)
|
|
84
90
|
|
|
85
91
|
|
|
86
92
|
@register_tool_renderer
|
|
@@ -92,17 +98,36 @@ class ListNotesRenderer(BaseToolRenderer):
|
|
|
92
98
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
93
99
|
result = tool_data.get("result")
|
|
94
100
|
|
|
95
|
-
|
|
101
|
+
text = Text()
|
|
102
|
+
text.append("📝 ")
|
|
103
|
+
text.append("Notes", style="bold #fbbf24")
|
|
104
|
+
|
|
105
|
+
if isinstance(result, str) and result.strip():
|
|
106
|
+
text.append("\n ")
|
|
107
|
+
text.append(result.strip(), style="dim")
|
|
108
|
+
elif result and isinstance(result, dict) and result.get("success"):
|
|
109
|
+
count = result.get("total_count", 0)
|
|
110
|
+
notes = result.get("notes", []) or []
|
|
96
111
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
count = len(notes)
|
|
101
|
-
content_text = f"{header}\n [dim]{count} notes found[/]"
|
|
112
|
+
if count == 0:
|
|
113
|
+
text.append("\n ")
|
|
114
|
+
text.append("No notes", style="dim")
|
|
102
115
|
else:
|
|
103
|
-
|
|
116
|
+
for note in notes:
|
|
117
|
+
title = note.get("title", "").strip() or "(untitled)"
|
|
118
|
+
category = note.get("category", "general")
|
|
119
|
+
note_content = note.get("content", "").strip()
|
|
120
|
+
|
|
121
|
+
text.append("\n - ")
|
|
122
|
+
text.append(title)
|
|
123
|
+
text.append(f" ({category})", style="dim")
|
|
124
|
+
|
|
125
|
+
if note_content:
|
|
126
|
+
text.append("\n ")
|
|
127
|
+
text.append(note_content, style="dim")
|
|
104
128
|
else:
|
|
105
|
-
|
|
129
|
+
text.append("\n ")
|
|
130
|
+
text.append("Loading...", style="dim")
|
|
106
131
|
|
|
107
132
|
css_classes = cls.get_css_classes("completed")
|
|
108
|
-
return Static(
|
|
133
|
+
return Static(text, classes=css_classes)
|