strix-agent 0.1.1__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/__init__.py +0 -0
- strix/agents/StrixAgent/__init__.py +4 -0
- strix/agents/StrixAgent/strix_agent.py +60 -0
- strix/agents/StrixAgent/system_prompt.jinja +504 -0
- strix/agents/__init__.py +10 -0
- strix/agents/base_agent.py +394 -0
- strix/agents/state.py +139 -0
- strix/cli/__init__.py +4 -0
- strix/cli/app.py +1124 -0
- strix/cli/assets/cli.tcss +680 -0
- strix/cli/main.py +542 -0
- strix/cli/tool_components/__init__.py +39 -0
- strix/cli/tool_components/agents_graph_renderer.py +129 -0
- strix/cli/tool_components/base_renderer.py +61 -0
- strix/cli/tool_components/browser_renderer.py +107 -0
- strix/cli/tool_components/file_edit_renderer.py +95 -0
- strix/cli/tool_components/finish_renderer.py +32 -0
- strix/cli/tool_components/notes_renderer.py +108 -0
- strix/cli/tool_components/proxy_renderer.py +255 -0
- strix/cli/tool_components/python_renderer.py +34 -0
- strix/cli/tool_components/registry.py +72 -0
- strix/cli/tool_components/reporting_renderer.py +53 -0
- strix/cli/tool_components/scan_info_renderer.py +58 -0
- strix/cli/tool_components/terminal_renderer.py +99 -0
- strix/cli/tool_components/thinking_renderer.py +29 -0
- strix/cli/tool_components/user_message_renderer.py +43 -0
- strix/cli/tool_components/web_search_renderer.py +28 -0
- strix/cli/tracer.py +308 -0
- strix/llm/__init__.py +14 -0
- strix/llm/config.py +19 -0
- strix/llm/llm.py +310 -0
- strix/llm/memory_compressor.py +206 -0
- strix/llm/request_queue.py +63 -0
- strix/llm/utils.py +84 -0
- strix/prompts/__init__.py +113 -0
- strix/prompts/coordination/root_agent.jinja +41 -0
- strix/prompts/vulnerabilities/authentication_jwt.jinja +129 -0
- strix/prompts/vulnerabilities/business_logic.jinja +143 -0
- strix/prompts/vulnerabilities/csrf.jinja +168 -0
- strix/prompts/vulnerabilities/idor.jinja +164 -0
- strix/prompts/vulnerabilities/race_conditions.jinja +194 -0
- strix/prompts/vulnerabilities/rce.jinja +222 -0
- strix/prompts/vulnerabilities/sql_injection.jinja +216 -0
- strix/prompts/vulnerabilities/ssrf.jinja +168 -0
- strix/prompts/vulnerabilities/xss.jinja +221 -0
- strix/prompts/vulnerabilities/xxe.jinja +276 -0
- strix/runtime/__init__.py +19 -0
- strix/runtime/docker_runtime.py +298 -0
- strix/runtime/runtime.py +25 -0
- strix/runtime/tool_server.py +97 -0
- strix/tools/__init__.py +64 -0
- strix/tools/agents_graph/__init__.py +16 -0
- strix/tools/agents_graph/agents_graph_actions.py +610 -0
- strix/tools/agents_graph/agents_graph_actions_schema.xml +223 -0
- strix/tools/argument_parser.py +120 -0
- strix/tools/browser/__init__.py +4 -0
- strix/tools/browser/browser_actions.py +236 -0
- strix/tools/browser/browser_actions_schema.xml +183 -0
- strix/tools/browser/browser_instance.py +533 -0
- strix/tools/browser/tab_manager.py +342 -0
- strix/tools/executor.py +302 -0
- strix/tools/file_edit/__init__.py +4 -0
- strix/tools/file_edit/file_edit_actions.py +141 -0
- strix/tools/file_edit/file_edit_actions_schema.xml +128 -0
- strix/tools/finish/__init__.py +4 -0
- strix/tools/finish/finish_actions.py +167 -0
- strix/tools/finish/finish_actions_schema.xml +45 -0
- strix/tools/notes/__init__.py +14 -0
- strix/tools/notes/notes_actions.py +191 -0
- strix/tools/notes/notes_actions_schema.xml +150 -0
- strix/tools/proxy/__init__.py +20 -0
- strix/tools/proxy/proxy_actions.py +101 -0
- strix/tools/proxy/proxy_actions_schema.xml +267 -0
- strix/tools/proxy/proxy_manager.py +785 -0
- strix/tools/python/__init__.py +4 -0
- strix/tools/python/python_actions.py +47 -0
- strix/tools/python/python_actions_schema.xml +131 -0
- strix/tools/python/python_instance.py +172 -0
- strix/tools/python/python_manager.py +131 -0
- strix/tools/registry.py +196 -0
- strix/tools/reporting/__init__.py +6 -0
- strix/tools/reporting/reporting_actions.py +63 -0
- strix/tools/reporting/reporting_actions_schema.xml +30 -0
- strix/tools/terminal/__init__.py +4 -0
- strix/tools/terminal/terminal_actions.py +53 -0
- strix/tools/terminal/terminal_actions_schema.xml +114 -0
- strix/tools/terminal/terminal_instance.py +231 -0
- strix/tools/terminal/terminal_manager.py +191 -0
- strix/tools/thinking/__init__.py +4 -0
- strix/tools/thinking/thinking_actions.py +18 -0
- strix/tools/thinking/thinking_actions_schema.xml +52 -0
- strix/tools/web_search/__init__.py +4 -0
- strix/tools/web_search/web_search_actions.py +80 -0
- strix/tools/web_search/web_search_actions_schema.xml +83 -0
- strix_agent-0.1.1.dist-info/LICENSE +201 -0
- strix_agent-0.1.1.dist-info/METADATA +200 -0
- strix_agent-0.1.1.dist-info/RECORD +99 -0
- strix_agent-0.1.1.dist-info/WHEEL +4 -0
- strix_agent-0.1.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
from typing import Any, ClassVar
|
2
|
+
|
3
|
+
from textual.widgets import Static
|
4
|
+
|
5
|
+
from .base_renderer import BaseToolRenderer
|
6
|
+
from .registry import register_tool_renderer
|
7
|
+
|
8
|
+
|
9
|
+
@register_tool_renderer
|
10
|
+
class ViewAgentGraphRenderer(BaseToolRenderer):
|
11
|
+
tool_name: ClassVar[str] = "view_agent_graph"
|
12
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: ARG003
|
16
|
+
content_text = "🕸️ [bold #fbbf24]Viewing agents graph[/]"
|
17
|
+
|
18
|
+
css_classes = cls.get_css_classes("completed")
|
19
|
+
return Static(content_text, classes=css_classes)
|
20
|
+
|
21
|
+
|
22
|
+
@register_tool_renderer
|
23
|
+
class CreateAgentRenderer(BaseToolRenderer):
|
24
|
+
tool_name: ClassVar[str] = "create_agent"
|
25
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
26
|
+
|
27
|
+
@classmethod
|
28
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
29
|
+
args = tool_data.get("args", {})
|
30
|
+
|
31
|
+
task = args.get("task", "")
|
32
|
+
name = args.get("name", "Agent")
|
33
|
+
|
34
|
+
header = f"🤖 [bold #fbbf24]Creating {name}[/]"
|
35
|
+
|
36
|
+
if task:
|
37
|
+
task_display = task[:400] + "..." if len(task) > 400 else task
|
38
|
+
content_text = f"{header}\n [dim]{cls.escape_markup(task_display)}[/]"
|
39
|
+
else:
|
40
|
+
content_text = f"{header}\n [dim]Spawning agent...[/]"
|
41
|
+
|
42
|
+
css_classes = cls.get_css_classes("completed")
|
43
|
+
return Static(content_text, classes=css_classes)
|
44
|
+
|
45
|
+
|
46
|
+
@register_tool_renderer
|
47
|
+
class SendMessageToAgentRenderer(BaseToolRenderer):
|
48
|
+
tool_name: ClassVar[str] = "send_message_to_agent"
|
49
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
50
|
+
|
51
|
+
@classmethod
|
52
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
53
|
+
args = tool_data.get("args", {})
|
54
|
+
|
55
|
+
message = args.get("message", "")
|
56
|
+
|
57
|
+
header = "💬 [bold #fbbf24]Sending message[/]"
|
58
|
+
|
59
|
+
if message:
|
60
|
+
message_display = message[:400] + "..." if len(message) > 400 else message
|
61
|
+
content_text = f"{header}\n [dim]{cls.escape_markup(message_display)}[/]"
|
62
|
+
else:
|
63
|
+
content_text = f"{header}\n [dim]Sending...[/]"
|
64
|
+
|
65
|
+
css_classes = cls.get_css_classes("completed")
|
66
|
+
return Static(content_text, classes=css_classes)
|
67
|
+
|
68
|
+
|
69
|
+
@register_tool_renderer
|
70
|
+
class AgentFinishRenderer(BaseToolRenderer):
|
71
|
+
tool_name: ClassVar[str] = "agent_finish"
|
72
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
73
|
+
|
74
|
+
@classmethod
|
75
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
76
|
+
args = tool_data.get("args", {})
|
77
|
+
|
78
|
+
result_summary = args.get("result_summary", "")
|
79
|
+
findings = args.get("findings", [])
|
80
|
+
success = args.get("success", True)
|
81
|
+
|
82
|
+
header = (
|
83
|
+
"🏁 [bold #fbbf24]Agent completed[/]" if success else "🏁 [bold #fbbf24]Agent failed[/]"
|
84
|
+
)
|
85
|
+
|
86
|
+
if result_summary:
|
87
|
+
summary_display = (
|
88
|
+
result_summary[:400] + "..." if len(result_summary) > 400 else result_summary
|
89
|
+
)
|
90
|
+
content_parts = [f"{header}\n [bold]{cls.escape_markup(summary_display)}[/]"]
|
91
|
+
|
92
|
+
if findings and isinstance(findings, list):
|
93
|
+
finding_lines = [f"• {finding}" for finding in findings[:3]]
|
94
|
+
if len(findings) > 3:
|
95
|
+
finding_lines.append(f"• ... +{len(findings) - 3} more findings")
|
96
|
+
|
97
|
+
content_parts.append(
|
98
|
+
f" [dim]{chr(10).join([cls.escape_markup(line) for line in finding_lines])}[/]"
|
99
|
+
)
|
100
|
+
|
101
|
+
content_text = "\n".join(content_parts)
|
102
|
+
else:
|
103
|
+
content_text = f"{header}\n [dim]Completing task...[/]"
|
104
|
+
|
105
|
+
css_classes = cls.get_css_classes("completed")
|
106
|
+
return Static(content_text, classes=css_classes)
|
107
|
+
|
108
|
+
|
109
|
+
@register_tool_renderer
|
110
|
+
class WaitForMessageRenderer(BaseToolRenderer):
|
111
|
+
tool_name: ClassVar[str] = "wait_for_message"
|
112
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
113
|
+
|
114
|
+
@classmethod
|
115
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
116
|
+
args = tool_data.get("args", {})
|
117
|
+
|
118
|
+
reason = args.get("reason", "Waiting for messages from other agents or user input")
|
119
|
+
|
120
|
+
header = "⏸️ [bold #fbbf24]Waiting for messages[/]"
|
121
|
+
|
122
|
+
if reason:
|
123
|
+
reason_display = reason[:400] + "..." if len(reason) > 400 else reason
|
124
|
+
content_text = f"{header}\n [dim]{cls.escape_markup(reason_display)}[/]"
|
125
|
+
else:
|
126
|
+
content_text = f"{header}\n [dim]Agent paused until message received...[/]"
|
127
|
+
|
128
|
+
css_classes = cls.get_css_classes("completed")
|
129
|
+
return Static(content_text, classes=css_classes)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import Any, ClassVar
|
3
|
+
|
4
|
+
from textual.widgets import Static
|
5
|
+
|
6
|
+
|
7
|
+
class BaseToolRenderer(ABC):
|
8
|
+
tool_name: ClassVar[str] = ""
|
9
|
+
|
10
|
+
css_classes: ClassVar[list[str]] = ["tool-call"]
|
11
|
+
|
12
|
+
@classmethod
|
13
|
+
@abstractmethod
|
14
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
15
|
+
pass
|
16
|
+
|
17
|
+
@classmethod
|
18
|
+
def escape_markup(cls, text: str) -> str:
|
19
|
+
return text.replace("[", "\\[").replace("]", "\\]")
|
20
|
+
|
21
|
+
@classmethod
|
22
|
+
def format_args(cls, args: dict[str, Any], max_length: int = 500) -> str:
|
23
|
+
if not args:
|
24
|
+
return ""
|
25
|
+
|
26
|
+
args_parts = []
|
27
|
+
for k, v in args.items():
|
28
|
+
str_v = str(v)
|
29
|
+
if len(str_v) > max_length:
|
30
|
+
str_v = str_v[: max_length - 3] + "..."
|
31
|
+
args_parts.append(f" [dim]{k}:[/] {cls.escape_markup(str_v)}")
|
32
|
+
return "\n".join(args_parts)
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def format_result(cls, result: Any, max_length: int = 1000) -> str:
|
36
|
+
if result is None:
|
37
|
+
return ""
|
38
|
+
|
39
|
+
str_result = str(result).strip()
|
40
|
+
if not str_result:
|
41
|
+
return ""
|
42
|
+
|
43
|
+
if len(str_result) > max_length:
|
44
|
+
str_result = str_result[: max_length - 3] + "..."
|
45
|
+
return cls.escape_markup(str_result)
|
46
|
+
|
47
|
+
@classmethod
|
48
|
+
def get_status_icon(cls, status: str) -> str:
|
49
|
+
status_icons = {
|
50
|
+
"running": "[#f59e0b]●[/#f59e0b] In progress...",
|
51
|
+
"completed": "[#22c55e]✓[/#22c55e] Done",
|
52
|
+
"failed": "[#dc2626]✗[/#dc2626] Failed",
|
53
|
+
"error": "[#dc2626]✗[/#dc2626] Error",
|
54
|
+
}
|
55
|
+
return status_icons.get(status, "[dim]○[/dim] Unknown")
|
56
|
+
|
57
|
+
@classmethod
|
58
|
+
def get_css_classes(cls, status: str) -> str:
|
59
|
+
base_classes = cls.css_classes.copy()
|
60
|
+
base_classes.append(f"status-{status}")
|
61
|
+
return " ".join(base_classes)
|
@@ -0,0 +1,107 @@
|
|
1
|
+
from typing import Any, ClassVar
|
2
|
+
|
3
|
+
from textual.widgets import Static
|
4
|
+
|
5
|
+
from .base_renderer import BaseToolRenderer
|
6
|
+
from .registry import register_tool_renderer
|
7
|
+
|
8
|
+
|
9
|
+
@register_tool_renderer
|
10
|
+
class BrowserRenderer(BaseToolRenderer):
|
11
|
+
tool_name: ClassVar[str] = "browser_action"
|
12
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "browser-tool"]
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
16
|
+
args = tool_data.get("args", {})
|
17
|
+
status = tool_data.get("status", "unknown")
|
18
|
+
|
19
|
+
action = args.get("action", "unknown")
|
20
|
+
|
21
|
+
content = cls._build_sleek_content(action, args)
|
22
|
+
|
23
|
+
css_classes = cls.get_css_classes(status)
|
24
|
+
return Static(content, classes=css_classes)
|
25
|
+
|
26
|
+
@classmethod
|
27
|
+
def _build_sleek_content(cls, action: str, args: dict[str, Any]) -> str:
|
28
|
+
browser_icon = "🌐"
|
29
|
+
|
30
|
+
url = args.get("url")
|
31
|
+
text = args.get("text")
|
32
|
+
js_code = args.get("js_code")
|
33
|
+
|
34
|
+
if action in [
|
35
|
+
"launch",
|
36
|
+
"goto",
|
37
|
+
"new_tab",
|
38
|
+
"type",
|
39
|
+
"execute_js",
|
40
|
+
"click",
|
41
|
+
"double_click",
|
42
|
+
"hover",
|
43
|
+
]:
|
44
|
+
if action == "launch":
|
45
|
+
display_url = cls._format_url(url) if url else None
|
46
|
+
message = (
|
47
|
+
f"launching {display_url} on browser" if display_url else "launching browser"
|
48
|
+
)
|
49
|
+
elif action == "goto":
|
50
|
+
display_url = cls._format_url(url) if url else None
|
51
|
+
message = f"navigating to {display_url}" if display_url else "navigating"
|
52
|
+
elif action == "new_tab":
|
53
|
+
display_url = cls._format_url(url) if url else None
|
54
|
+
message = f"opening tab {display_url}" if display_url else "opening tab"
|
55
|
+
elif action == "type":
|
56
|
+
display_text = cls._format_text(text) if text else None
|
57
|
+
message = f"typing {display_text}" if display_text else "typing"
|
58
|
+
elif action == "execute_js":
|
59
|
+
display_js = cls._format_js(js_code) if js_code else None
|
60
|
+
message = (
|
61
|
+
f"executing javascript\n{display_js}" if display_js else "executing javascript"
|
62
|
+
)
|
63
|
+
else:
|
64
|
+
action_words = {
|
65
|
+
"click": "clicking",
|
66
|
+
"double_click": "double clicking",
|
67
|
+
"hover": "hovering",
|
68
|
+
}
|
69
|
+
message = action_words[action]
|
70
|
+
|
71
|
+
return f"{browser_icon} [#06b6d4]{message}[/]"
|
72
|
+
|
73
|
+
simple_actions = {
|
74
|
+
"back": "going back in browser history",
|
75
|
+
"forward": "going forward in browser history",
|
76
|
+
"refresh": "refreshing browser tab",
|
77
|
+
"close_tab": "closing browser tab",
|
78
|
+
"switch_tab": "switching browser tab",
|
79
|
+
"list_tabs": "listing browser tabs",
|
80
|
+
"view_source": "viewing page source",
|
81
|
+
"screenshot": "taking screenshot of browser tab",
|
82
|
+
"wait": "waiting...",
|
83
|
+
"close": "closing browser",
|
84
|
+
}
|
85
|
+
|
86
|
+
if action in simple_actions:
|
87
|
+
return f"{browser_icon} [#06b6d4]{simple_actions[action]}[/]"
|
88
|
+
|
89
|
+
return f"{browser_icon} [#06b6d4]{action}[/]"
|
90
|
+
|
91
|
+
@classmethod
|
92
|
+
def _format_url(cls, url: str) -> str:
|
93
|
+
if len(url) > 300:
|
94
|
+
url = url[:297] + "..."
|
95
|
+
return cls.escape_markup(url)
|
96
|
+
|
97
|
+
@classmethod
|
98
|
+
def _format_text(cls, text: str) -> str:
|
99
|
+
if len(text) > 200:
|
100
|
+
text = text[:197] + "..."
|
101
|
+
return cls.escape_markup(text)
|
102
|
+
|
103
|
+
@classmethod
|
104
|
+
def _format_js(cls, js_code: str) -> str:
|
105
|
+
if len(js_code) > 200:
|
106
|
+
js_code = js_code[:197] + "..."
|
107
|
+
return f"[white]{cls.escape_markup(js_code)}[/white]"
|
@@ -0,0 +1,95 @@
|
|
1
|
+
from typing import Any, ClassVar
|
2
|
+
|
3
|
+
from textual.widgets import Static
|
4
|
+
|
5
|
+
from .base_renderer import BaseToolRenderer
|
6
|
+
from .registry import register_tool_renderer
|
7
|
+
|
8
|
+
|
9
|
+
@register_tool_renderer
|
10
|
+
class StrReplaceEditorRenderer(BaseToolRenderer):
|
11
|
+
tool_name: ClassVar[str] = "str_replace_editor"
|
12
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "file-edit-tool"]
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
16
|
+
args = tool_data.get("args", {})
|
17
|
+
result = tool_data.get("result")
|
18
|
+
|
19
|
+
command = args.get("command", "")
|
20
|
+
path = args.get("path", "")
|
21
|
+
|
22
|
+
if command == "view":
|
23
|
+
header = "📖 [bold #10b981]Reading file[/]"
|
24
|
+
elif command == "str_replace":
|
25
|
+
header = "✏️ [bold #10b981]Editing file[/]"
|
26
|
+
elif command == "create":
|
27
|
+
header = "📝 [bold #10b981]Creating file[/]"
|
28
|
+
else:
|
29
|
+
header = "📄 [bold #10b981]File operation[/]"
|
30
|
+
|
31
|
+
if (result and isinstance(result, dict) and "content" in result) or path:
|
32
|
+
path_display = path[-60:] if len(path) > 60 else path
|
33
|
+
content_text = f"{header} [dim]{cls.escape_markup(path_display)}[/]"
|
34
|
+
else:
|
35
|
+
content_text = f"{header} [dim]Processing...[/]"
|
36
|
+
|
37
|
+
css_classes = cls.get_css_classes("completed")
|
38
|
+
return Static(content_text, classes=css_classes)
|
39
|
+
|
40
|
+
|
41
|
+
@register_tool_renderer
|
42
|
+
class ListFilesRenderer(BaseToolRenderer):
|
43
|
+
tool_name: ClassVar[str] = "list_files"
|
44
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "file-edit-tool"]
|
45
|
+
|
46
|
+
@classmethod
|
47
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
48
|
+
args = tool_data.get("args", {})
|
49
|
+
|
50
|
+
path = args.get("path", "")
|
51
|
+
|
52
|
+
header = "📂 [bold #10b981]Listing files[/]"
|
53
|
+
|
54
|
+
if path:
|
55
|
+
path_display = path[-60:] if len(path) > 60 else path
|
56
|
+
content_text = f"{header} [dim]{cls.escape_markup(path_display)}[/]"
|
57
|
+
else:
|
58
|
+
content_text = f"{header} [dim]Current directory[/]"
|
59
|
+
|
60
|
+
css_classes = cls.get_css_classes("completed")
|
61
|
+
return Static(content_text, classes=css_classes)
|
62
|
+
|
63
|
+
|
64
|
+
@register_tool_renderer
|
65
|
+
class SearchFilesRenderer(BaseToolRenderer):
|
66
|
+
tool_name: ClassVar[str] = "search_files"
|
67
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "file-edit-tool"]
|
68
|
+
|
69
|
+
@classmethod
|
70
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
71
|
+
args = tool_data.get("args", {})
|
72
|
+
|
73
|
+
path = args.get("path", "")
|
74
|
+
regex = args.get("regex", "")
|
75
|
+
|
76
|
+
header = "🔍 [bold purple]Searching files[/]"
|
77
|
+
|
78
|
+
if path and regex:
|
79
|
+
path_display = path[-30:] if len(path) > 30 else path
|
80
|
+
regex_display = regex[:30] if len(regex) > 30 else regex
|
81
|
+
content_text = (
|
82
|
+
f"{header} [dim]{cls.escape_markup(path_display)} for "
|
83
|
+
f"'{cls.escape_markup(regex_display)}'[/]"
|
84
|
+
)
|
85
|
+
elif path:
|
86
|
+
path_display = path[-60:] if len(path) > 60 else path
|
87
|
+
content_text = f"{header} [dim]{cls.escape_markup(path_display)}[/]"
|
88
|
+
elif regex:
|
89
|
+
regex_display = regex[:60] if len(regex) > 60 else regex
|
90
|
+
content_text = f"{header} [dim]'{cls.escape_markup(regex_display)}'[/]"
|
91
|
+
else:
|
92
|
+
content_text = f"{header} [dim]Searching...[/]"
|
93
|
+
|
94
|
+
css_classes = cls.get_css_classes("completed")
|
95
|
+
return Static(content_text, classes=css_classes)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from typing import Any, ClassVar
|
2
|
+
|
3
|
+
from textual.widgets import Static
|
4
|
+
|
5
|
+
from .base_renderer import BaseToolRenderer
|
6
|
+
from .registry import register_tool_renderer
|
7
|
+
|
8
|
+
|
9
|
+
@register_tool_renderer
|
10
|
+
class FinishScanRenderer(BaseToolRenderer):
|
11
|
+
tool_name: ClassVar[str] = "finish_scan"
|
12
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "finish-tool"]
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
16
|
+
args = tool_data.get("args", {})
|
17
|
+
|
18
|
+
content = args.get("content", "")
|
19
|
+
success = args.get("success", True)
|
20
|
+
|
21
|
+
header = (
|
22
|
+
"🏁 [bold #dc2626]Finishing Scan[/]" if success else "🏁 [bold #dc2626]Scan Failed[/]"
|
23
|
+
)
|
24
|
+
|
25
|
+
if content:
|
26
|
+
content_display = content[:600] + "..." if len(content) > 600 else content
|
27
|
+
content_text = f"{header}\n [bold]{cls.escape_markup(content_display)}[/]"
|
28
|
+
else:
|
29
|
+
content_text = f"{header}\n [dim]Generating final report...[/]"
|
30
|
+
|
31
|
+
css_classes = cls.get_css_classes("completed")
|
32
|
+
return Static(content_text, classes=css_classes)
|
@@ -0,0 +1,108 @@
|
|
1
|
+
from typing import Any, ClassVar
|
2
|
+
|
3
|
+
from textual.widgets import Static
|
4
|
+
|
5
|
+
from .base_renderer import BaseToolRenderer
|
6
|
+
from .registry import register_tool_renderer
|
7
|
+
|
8
|
+
|
9
|
+
@register_tool_renderer
|
10
|
+
class CreateNoteRenderer(BaseToolRenderer):
|
11
|
+
tool_name: ClassVar[str] = "create_note"
|
12
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "notes-tool"]
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
16
|
+
args = tool_data.get("args", {})
|
17
|
+
|
18
|
+
title = args.get("title", "")
|
19
|
+
content = args.get("content", "")
|
20
|
+
|
21
|
+
header = "📝 [bold #fbbf24]Note[/]"
|
22
|
+
|
23
|
+
if title:
|
24
|
+
title_display = title[:100] + "..." if len(title) > 100 else title
|
25
|
+
note_parts = [f"{header}\n [bold]{cls.escape_markup(title_display)}[/]"]
|
26
|
+
|
27
|
+
if content:
|
28
|
+
content_display = content[:200] + "..." if len(content) > 200 else content
|
29
|
+
note_parts.append(f" [dim]{cls.escape_markup(content_display)}[/]")
|
30
|
+
|
31
|
+
content_text = "\n".join(note_parts)
|
32
|
+
else:
|
33
|
+
content_text = f"{header}\n [dim]Creating note...[/]"
|
34
|
+
|
35
|
+
css_classes = cls.get_css_classes("completed")
|
36
|
+
return Static(content_text, classes=css_classes)
|
37
|
+
|
38
|
+
|
39
|
+
@register_tool_renderer
|
40
|
+
class DeleteNoteRenderer(BaseToolRenderer):
|
41
|
+
tool_name: ClassVar[str] = "delete_note"
|
42
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "notes-tool"]
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: ARG003
|
46
|
+
header = "🗑️ [bold #fbbf24]Delete Note[/]"
|
47
|
+
content_text = f"{header}\n [dim]Deleting...[/]"
|
48
|
+
|
49
|
+
css_classes = cls.get_css_classes("completed")
|
50
|
+
return Static(content_text, classes=css_classes)
|
51
|
+
|
52
|
+
|
53
|
+
@register_tool_renderer
|
54
|
+
class UpdateNoteRenderer(BaseToolRenderer):
|
55
|
+
tool_name: ClassVar[str] = "update_note"
|
56
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "notes-tool"]
|
57
|
+
|
58
|
+
@classmethod
|
59
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
60
|
+
args = tool_data.get("args", {})
|
61
|
+
|
62
|
+
title = args.get("title", "")
|
63
|
+
content = args.get("content", "")
|
64
|
+
|
65
|
+
header = "✏️ [bold #fbbf24]Update Note[/]"
|
66
|
+
|
67
|
+
if title or content:
|
68
|
+
note_parts = [header]
|
69
|
+
|
70
|
+
if title:
|
71
|
+
title_display = title[:100] + "..." if len(title) > 100 else title
|
72
|
+
note_parts.append(f" [bold]{cls.escape_markup(title_display)}[/]")
|
73
|
+
|
74
|
+
if content:
|
75
|
+
content_display = content[:200] + "..." if len(content) > 200 else content
|
76
|
+
note_parts.append(f" [dim]{cls.escape_markup(content_display)}[/]")
|
77
|
+
|
78
|
+
content_text = "\n".join(note_parts)
|
79
|
+
else:
|
80
|
+
content_text = f"{header}\n [dim]Updating...[/]"
|
81
|
+
|
82
|
+
css_classes = cls.get_css_classes("completed")
|
83
|
+
return Static(content_text, classes=css_classes)
|
84
|
+
|
85
|
+
|
86
|
+
@register_tool_renderer
|
87
|
+
class ListNotesRenderer(BaseToolRenderer):
|
88
|
+
tool_name: ClassVar[str] = "list_notes"
|
89
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "notes-tool"]
|
90
|
+
|
91
|
+
@classmethod
|
92
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
93
|
+
result = tool_data.get("result")
|
94
|
+
|
95
|
+
header = "📋 [bold #fbbf24]Listing notes[/]"
|
96
|
+
|
97
|
+
if result and isinstance(result, dict) and "notes" in result:
|
98
|
+
notes = result["notes"]
|
99
|
+
if isinstance(notes, list):
|
100
|
+
count = len(notes)
|
101
|
+
content_text = f"{header}\n [dim]{count} notes found[/]"
|
102
|
+
else:
|
103
|
+
content_text = f"{header}\n [dim]No notes found[/]"
|
104
|
+
else:
|
105
|
+
content_text = f"{header}\n [dim]Listing notes...[/]"
|
106
|
+
|
107
|
+
css_classes = cls.get_css_classes("completed")
|
108
|
+
return Static(content_text, classes=css_classes)
|