superqode 0.1.5__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 (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,27 @@
1
+ """
2
+ SuperQode TUI Widgets - Reusable UI components.
3
+
4
+ Provides enhanced widgets for the TUI:
5
+ - ToolDisplay: Shows tool calls with status
6
+ - ProgressPanel: Session progress tracking
7
+
8
+ Usage:
9
+ from superqode.tui_widgets import ToolDisplay, ProgressPanel
10
+ """
11
+
12
+ # Re-export widgets
13
+ from .widgets import (
14
+ ToolDisplay,
15
+ ToolCall,
16
+ ToolStatus,
17
+ ProgressPanel,
18
+ ProgressStep,
19
+ )
20
+
21
+ __all__ = [
22
+ "ToolDisplay",
23
+ "ToolCall",
24
+ "ToolStatus",
25
+ "ProgressPanel",
26
+ "ProgressStep",
27
+ ]
@@ -0,0 +1,18 @@
1
+ """
2
+ SuperQode TUI Widgets - Reusable UI components.
3
+
4
+ Provides enhanced widgets for the TUI:
5
+ - ToolDisplay: Shows tool calls with status
6
+ - ProgressPanel: Session progress tracking
7
+ """
8
+
9
+ from .tool_display import ToolDisplay, ToolCall, ToolStatus
10
+ from .progress import ProgressPanel, ProgressStep
11
+
12
+ __all__ = [
13
+ "ToolDisplay",
14
+ "ToolCall",
15
+ "ToolStatus",
16
+ "ProgressPanel",
17
+ "ProgressStep",
18
+ ]
@@ -0,0 +1,185 @@
1
+ """
2
+ Progress Panel Widget - Shows session progress.
3
+
4
+ Displays the current session progress with steps:
5
+ - Completed steps
6
+ - Current step (with spinner)
7
+ - Pending steps
8
+ """
9
+
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from typing import List, Optional
13
+
14
+ from rich.console import Console, Group
15
+ from rich.panel import Panel
16
+ from rich.text import Text
17
+ from rich.box import ROUNDED
18
+
19
+
20
+ class StepStatus(Enum):
21
+ """Status of a progress step."""
22
+
23
+ PENDING = "pending"
24
+ IN_PROGRESS = "in_progress"
25
+ COMPLETE = "complete"
26
+ SKIPPED = "skipped"
27
+ ERROR = "error"
28
+
29
+
30
+ @dataclass
31
+ class ProgressStep:
32
+ """Represents a single progress step."""
33
+
34
+ name: str
35
+ description: str = ""
36
+ status: StepStatus = StepStatus.PENDING
37
+ substeps: List[str] = field(default_factory=list)
38
+
39
+ @property
40
+ def status_icon(self) -> str:
41
+ """Get status icon."""
42
+ return {
43
+ StepStatus.PENDING: "[dim]○[/dim]",
44
+ StepStatus.IN_PROGRESS: "[yellow]⟳[/yellow]",
45
+ StepStatus.COMPLETE: "[green]✓[/green]",
46
+ StepStatus.SKIPPED: "[dim]⊘[/dim]",
47
+ StepStatus.ERROR: "[red]✗[/red]",
48
+ }.get(self.status, "?")
49
+
50
+ @property
51
+ def style(self) -> str:
52
+ """Get text style based on status."""
53
+ return {
54
+ StepStatus.PENDING: "dim",
55
+ StepStatus.IN_PROGRESS: "bold yellow",
56
+ StepStatus.COMPLETE: "green",
57
+ StepStatus.SKIPPED: "dim",
58
+ StepStatus.ERROR: "red",
59
+ }.get(self.status, "")
60
+
61
+
62
+ class ProgressPanel:
63
+ """
64
+ Widget for displaying session progress.
65
+
66
+ Shows a panel with progress steps and their status.
67
+ """
68
+
69
+ def __init__(
70
+ self,
71
+ title: str = "Session Progress",
72
+ role: Optional[str] = None,
73
+ mode: Optional[str] = None,
74
+ ):
75
+ """
76
+ Initialize the progress panel.
77
+
78
+ Args:
79
+ title: Panel title
80
+ role: Current role (e.g., "qe.security")
81
+ mode: Current mode (e.g., "Deep Scan")
82
+ """
83
+ self.title = title
84
+ self.role = role
85
+ self.mode = mode
86
+ self.steps: List[ProgressStep] = []
87
+ self.console = Console()
88
+
89
+ def add_step(self, name: str, description: str = "") -> ProgressStep:
90
+ """Add a new step."""
91
+ step = ProgressStep(name=name, description=description)
92
+ self.steps.append(step)
93
+ return step
94
+
95
+ def start_step(self, step: ProgressStep) -> None:
96
+ """Mark a step as in progress."""
97
+ step.status = StepStatus.IN_PROGRESS
98
+
99
+ def complete_step(self, step: ProgressStep) -> None:
100
+ """Mark a step as complete."""
101
+ step.status = StepStatus.COMPLETE
102
+
103
+ def error_step(self, step: ProgressStep) -> None:
104
+ """Mark a step as errored."""
105
+ step.status = StepStatus.ERROR
106
+
107
+ def skip_step(self, step: ProgressStep) -> None:
108
+ """Mark a step as skipped."""
109
+ step.status = StepStatus.SKIPPED
110
+
111
+ def get_current_step(self) -> Optional[ProgressStep]:
112
+ """Get the current in-progress step."""
113
+ for step in self.steps:
114
+ if step.status == StepStatus.IN_PROGRESS:
115
+ return step
116
+ return None
117
+
118
+ def render(self) -> Panel:
119
+ """Render the progress panel as a Rich Panel."""
120
+ lines = []
121
+
122
+ # Header with role and mode
123
+ if self.role or self.mode:
124
+ header_parts = []
125
+ if self.role:
126
+ header_parts.append(f"[cyan]Role:[/cyan] {self.role}")
127
+ if self.mode:
128
+ header_parts.append(f"[cyan]Mode:[/cyan] {self.mode}")
129
+ lines.append(Text.from_markup(" ".join(header_parts)))
130
+ lines.append(Text()) # Empty line
131
+
132
+ # Progress steps
133
+ if not self.steps:
134
+ lines.append(Text("No steps defined", style="dim"))
135
+ else:
136
+ for step in self.steps:
137
+ # Main step line
138
+ step_text = Text()
139
+ step_text.append(step.status_icon)
140
+ step_text.append(" ")
141
+ step_text.append(step.name, style=step.style)
142
+ if step.description and step.status == StepStatus.IN_PROGRESS:
143
+ step_text.append(f" - {step.description}", style="dim")
144
+ lines.append(step_text)
145
+
146
+ # Substeps (indented)
147
+ if step.substeps and step.status == StepStatus.IN_PROGRESS:
148
+ for substep in step.substeps[-3:]: # Show last 3 substeps
149
+ lines.append(Text(f" {substep}", style="dim"))
150
+
151
+ content = Group(*lines)
152
+
153
+ return Panel(
154
+ content,
155
+ title=f"[bold]{self.title}[/bold]",
156
+ border_style="cyan",
157
+ box=ROUNDED,
158
+ )
159
+
160
+ def print(self) -> None:
161
+ """Print the progress panel to console."""
162
+ self.console.print(self.render())
163
+
164
+
165
+ def create_qe_progress(role: str, mode: str) -> ProgressPanel:
166
+ """
167
+ Create a progress panel for a QE session.
168
+
169
+ Pre-populates with standard QE steps.
170
+ """
171
+ panel = ProgressPanel(
172
+ title="SuperQE Session",
173
+ role=role,
174
+ mode=mode,
175
+ )
176
+
177
+ panel.add_step("Initializing workspace", "Setting up ephemeral environment")
178
+ panel.add_step("Running test discovery", "Finding test files and suites")
179
+ panel.add_step("Executing tests", "Running smoke/sanity/regression")
180
+ panel.add_step("Analyzing results", "Processing test outcomes")
181
+ panel.add_step("Detecting issues", "Proactive vulnerability scanning")
182
+ panel.add_step("Generating QR", "Creating quality report")
183
+ panel.add_step("Cleaning up", "Reverting changes, preserving artifacts")
184
+
185
+ return panel
@@ -0,0 +1,188 @@
1
+ """
2
+ Tool Display Widget - Shows tool calls with status.
3
+
4
+ Displays tool invocations in a clear, structured format:
5
+ - Tool name and type
6
+ - Arguments (truncated for readability)
7
+ - Status (pending, running, complete, error)
8
+ - Duration and result summary
9
+ """
10
+
11
+ from dataclasses import dataclass, field
12
+ from datetime import datetime
13
+ from enum import Enum
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ from rich.console import Console, Group
17
+ from rich.panel import Panel
18
+ from rich.table import Table
19
+ from rich.text import Text
20
+ from rich.box import ROUNDED
21
+
22
+
23
+ class ToolStatus(Enum):
24
+ """Status of a tool call."""
25
+
26
+ PENDING = "pending"
27
+ RUNNING = "running"
28
+ COMPLETE = "complete"
29
+ ERROR = "error"
30
+
31
+
32
+ @dataclass
33
+ class ToolCall:
34
+ """Represents a single tool invocation."""
35
+
36
+ name: str
37
+ tool_type: str # e.g., "file_read", "shell_exec", "edit"
38
+ arguments: Dict[str, Any] = field(default_factory=dict)
39
+ status: ToolStatus = ToolStatus.PENDING
40
+ start_time: Optional[datetime] = None
41
+ end_time: Optional[datetime] = None
42
+ result_summary: str = ""
43
+ error_message: str = ""
44
+
45
+ @property
46
+ def duration_ms(self) -> Optional[float]:
47
+ """Get duration in milliseconds."""
48
+ if self.start_time and self.end_time:
49
+ return (self.end_time - self.start_time).total_seconds() * 1000
50
+ return None
51
+
52
+ @property
53
+ def duration_str(self) -> str:
54
+ """Get human-readable duration."""
55
+ if self.duration_ms is None:
56
+ return "-"
57
+ if self.duration_ms < 1000:
58
+ return f"{self.duration_ms:.0f}ms"
59
+ return f"{self.duration_ms / 1000:.1f}s"
60
+
61
+ @property
62
+ def status_icon(self) -> str:
63
+ """Get status icon."""
64
+ return {
65
+ ToolStatus.PENDING: "[dim]○[/dim]",
66
+ ToolStatus.RUNNING: "[yellow]⟳[/yellow]",
67
+ ToolStatus.COMPLETE: "[green]✓[/green]",
68
+ ToolStatus.ERROR: "[red]✗[/red]",
69
+ }.get(self.status, "?")
70
+
71
+
72
+ class ToolDisplay:
73
+ """
74
+ Widget for displaying tool calls in the TUI.
75
+
76
+ Shows a panel with recent tool invocations and their status.
77
+ """
78
+
79
+ def __init__(self, max_display: int = 5, show_arguments: bool = False):
80
+ """
81
+ Initialize the tool display.
82
+
83
+ Args:
84
+ max_display: Maximum number of tools to show
85
+ show_arguments: Whether to show tool arguments
86
+ """
87
+ self.max_display = max_display
88
+ self.show_arguments = show_arguments
89
+ self.calls: List[ToolCall] = []
90
+ self.console = Console()
91
+
92
+ def add_call(self, call: ToolCall) -> None:
93
+ """Add a new tool call."""
94
+ self.calls.append(call)
95
+ # Keep only the most recent calls
96
+ if len(self.calls) > self.max_display * 2:
97
+ self.calls = self.calls[-self.max_display :]
98
+
99
+ def start_call(self, name: str, tool_type: str, arguments: Dict[str, Any] = None) -> ToolCall:
100
+ """Create and start a new tool call."""
101
+ call = ToolCall(
102
+ name=name,
103
+ tool_type=tool_type,
104
+ arguments=arguments or {},
105
+ status=ToolStatus.RUNNING,
106
+ start_time=datetime.now(),
107
+ )
108
+ self.add_call(call)
109
+ return call
110
+
111
+ def complete_call(self, call: ToolCall, result_summary: str = "") -> None:
112
+ """Mark a tool call as complete."""
113
+ call.status = ToolStatus.COMPLETE
114
+ call.end_time = datetime.now()
115
+ call.result_summary = result_summary
116
+
117
+ def error_call(self, call: ToolCall, error_message: str) -> None:
118
+ """Mark a tool call as errored."""
119
+ call.status = ToolStatus.ERROR
120
+ call.end_time = datetime.now()
121
+ call.error_message = error_message
122
+
123
+ def _truncate(self, text: str, max_len: int = 50) -> str:
124
+ """Truncate text to max length."""
125
+ if len(text) <= max_len:
126
+ return text
127
+ return text[: max_len - 3] + "..."
128
+
129
+ def _format_arguments(self, args: Dict[str, Any]) -> str:
130
+ """Format arguments for display."""
131
+ if not args:
132
+ return ""
133
+ parts = []
134
+ for key, value in args.items():
135
+ if isinstance(value, str):
136
+ parts.append(f"{key}={self._truncate(repr(value), 30)}")
137
+ else:
138
+ parts.append(f"{key}={value}")
139
+ return ", ".join(parts[:3]) # Show at most 3 args
140
+
141
+ def render(self) -> Panel:
142
+ """Render the tool display as a Rich Panel."""
143
+ recent = self.calls[-self.max_display :]
144
+
145
+ if not recent:
146
+ content = Text("No tool calls yet", style="dim")
147
+ else:
148
+ table = Table(show_header=False, box=None, padding=(0, 1))
149
+ table.add_column("Status", width=3)
150
+ table.add_column("Tool", style="cyan")
151
+ table.add_column("Info", style="dim")
152
+ table.add_column("Time", width=8, justify="right")
153
+
154
+ for call in recent:
155
+ # Build info column
156
+ if call.status == ToolStatus.ERROR:
157
+ info = Text(self._truncate(call.error_message, 40), style="red")
158
+ elif call.result_summary:
159
+ info = Text(self._truncate(call.result_summary, 40))
160
+ elif self.show_arguments:
161
+ info = Text(self._truncate(self._format_arguments(call.arguments), 40))
162
+ else:
163
+ info = Text(call.tool_type, style="dim")
164
+
165
+ table.add_row(
166
+ call.status_icon,
167
+ call.name,
168
+ info,
169
+ call.duration_str,
170
+ )
171
+
172
+ content = table
173
+
174
+ return Panel(
175
+ content,
176
+ title="[bold]Tool Calls[/bold]",
177
+ border_style="dim",
178
+ box=ROUNDED,
179
+ )
180
+
181
+ def print(self) -> None:
182
+ """Print the tool display to console."""
183
+ self.console.print(self.render())
184
+
185
+
186
+ def create_tool_display(max_display: int = 5) -> ToolDisplay:
187
+ """Factory function to create a ToolDisplay."""
188
+ return ToolDisplay(max_display=max_display)