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,648 @@
1
+ """
2
+ SuperQode Stream View - Real-time Agent Output Display
3
+
4
+ Colorful widgets for displaying streaming agent output including:
5
+ - Message chunks (agent responses)
6
+ - Thinking/reasoning
7
+ - Tool calls with status
8
+ - Plans with task tracking
9
+ - Permission requests
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import asyncio
15
+ from dataclasses import dataclass
16
+ from typing import Any, Callable, Dict, List, Optional
17
+ from time import monotonic
18
+
19
+ from textual.app import ComposeResult
20
+ from textual.containers import Container, Vertical, Horizontal
21
+ from textual.widgets import Static, RichLog, Button
22
+ from textual.reactive import reactive
23
+ from textual.message import Message
24
+
25
+ from rich.text import Text
26
+ from rich.panel import Panel
27
+ from rich.box import ROUNDED
28
+ from rich.syntax import Syntax
29
+
30
+ from superqode.agent_stream import (
31
+ StreamEvent,
32
+ StreamEventType,
33
+ StreamMessage,
34
+ StreamThought,
35
+ StreamToolCall,
36
+ StreamPlan,
37
+ StreamPermission,
38
+ PlanTask,
39
+ ToolKind,
40
+ ToolStatus,
41
+ TaskStatus,
42
+ TaskPriority,
43
+ get_tool_icon,
44
+ get_status_icon,
45
+ get_status_color,
46
+ get_task_icon,
47
+ get_task_color,
48
+ STREAM_COLORS,
49
+ STREAM_ICONS,
50
+ )
51
+
52
+
53
+ # ============================================================================
54
+ # THEME
55
+ # ============================================================================
56
+
57
+ THEME = {
58
+ "bg": "#0a0a0a",
59
+ "surface": "#111111",
60
+ "border": "#2a2a2a",
61
+ "purple": "#a855f7",
62
+ "pink": "#ec4899",
63
+ "orange": "#f97316",
64
+ "cyan": "#06b6d4",
65
+ "green": "#22c55e",
66
+ "red": "#ef4444",
67
+ "yellow": "#fbbf24",
68
+ "text": "#e4e4e7",
69
+ "muted": "#71717a",
70
+ "dim": "#52525b",
71
+ }
72
+
73
+
74
+ # ============================================================================
75
+ # STREAMING MESSAGE WIDGET
76
+ # ============================================================================
77
+
78
+
79
+ class StreamingMessage(Static):
80
+ """Widget that displays streaming text with typing effect."""
81
+
82
+ text_content = reactive("")
83
+ is_complete = reactive(False)
84
+
85
+ def __init__(self, agent_name: str = "Agent", **kwargs):
86
+ super().__init__(**kwargs)
87
+ self.agent_name = agent_name
88
+ self._buffer = ""
89
+
90
+ def append_text(self, text: str):
91
+ """Append text to the message."""
92
+ self._buffer += text
93
+ self.text_content = self._buffer
94
+
95
+ def mark_complete(self):
96
+ """Mark the message as complete."""
97
+ self.is_complete = True
98
+
99
+ def render(self) -> Text:
100
+ result = Text()
101
+
102
+ # Header with agent name
103
+ color = STREAM_COLORS["message"]
104
+ result.append(f"💬 {self.agent_name}", style=f"bold {color}")
105
+
106
+ if not self.is_complete:
107
+ result.append(" ●", style=f"bold {STREAM_COLORS['progress']}")
108
+
109
+ result.append("\n", style="")
110
+
111
+ # Message content
112
+ if self.text_content:
113
+ result.append(self.text_content, style=THEME["text"])
114
+ else:
115
+ result.append("...", style=f"italic {THEME['muted']}")
116
+
117
+ return result
118
+
119
+
120
+ class ThinkingBubble(Static):
121
+ """Widget that displays agent's thinking process."""
122
+
123
+ thought = reactive("")
124
+
125
+ def __init__(self, **kwargs):
126
+ super().__init__(**kwargs)
127
+ self._thoughts: List[str] = []
128
+
129
+ def add_thought(self, text: str):
130
+ """Add a thought."""
131
+ self._thoughts.append(text)
132
+ self.thought = text
133
+
134
+ def render(self) -> Text:
135
+ result = Text()
136
+
137
+ color = STREAM_COLORS["thought"]
138
+ result.append(f"💭 ", style=f"bold {color}")
139
+ result.append("Thinking", style=f"italic {color}")
140
+ result.append("\n", style="")
141
+
142
+ # Show last few thoughts
143
+ for thought in self._thoughts[-3:]:
144
+ thought_short = thought[:100] + "..." if len(thought) > 100 else thought
145
+ result.append(f" • {thought_short}\n", style=f"italic {THEME['muted']}")
146
+
147
+ return result
148
+
149
+
150
+ # ============================================================================
151
+ # TOOL CALL WIDGET
152
+ # ============================================================================
153
+
154
+
155
+ class ToolCallWidget(Static):
156
+ """Widget displaying a tool call with status and content."""
157
+
158
+ status = reactive(ToolStatus.PENDING)
159
+
160
+ def __init__(self, tool_call: StreamToolCall, **kwargs):
161
+ super().__init__(**kwargs)
162
+ self.tool_call = tool_call
163
+
164
+ def update_tool(self, tool_call: StreamToolCall):
165
+ """Update the tool call data."""
166
+ self.tool_call = tool_call
167
+ self.status = tool_call.status
168
+
169
+ def render(self) -> Text:
170
+ result = Text()
171
+ tc = self.tool_call
172
+
173
+ # Status icon and color
174
+ status_icon = get_status_icon(tc.status)
175
+ status_color = get_status_color(tc.status)
176
+ tool_icon = get_tool_icon(tc.kind)
177
+
178
+ # Header line
179
+ result.append(f"{status_icon} ", style=status_color)
180
+ result.append(f"{tool_icon} ", style=STREAM_COLORS["tool"])
181
+ result.append(tc.title, style=f"bold {status_color}")
182
+
183
+ # Location info
184
+ if tc.locations:
185
+ loc = tc.locations[0]
186
+ path = loc.get("path", "")
187
+ line = loc.get("line")
188
+ if path:
189
+ result.append(f" 📍 {path}", style=THEME["muted"])
190
+ if line:
191
+ result.append(f":{line}", style=THEME["dim"])
192
+
193
+ result.append("\n", style="")
194
+
195
+ # Content preview
196
+ for content in tc.content[:2]: # Show first 2 content items
197
+ if content.type == "diff":
198
+ data = content.data
199
+ path = data.get("path", "")
200
+ old_text = data.get("oldText", "")
201
+ new_text = data.get("newText", "")
202
+
203
+ result.append(f" 📄 {path}\n", style=THEME["cyan"])
204
+
205
+ # Show diff preview
206
+ if old_text:
207
+ old_lines = old_text.split("\n")[:3]
208
+ for line in old_lines:
209
+ result.append(f" - {line[:60]}\n", style=f"on #2d1f1f {THEME['red']}")
210
+ if new_text:
211
+ new_lines = new_text.split("\n")[:3]
212
+ for line in new_lines:
213
+ result.append(f" + {line[:60]}\n", style=f"on #1f2d1f {THEME['green']}")
214
+
215
+ elif content.type == "terminal":
216
+ terminal_id = content.data.get("terminalId", "")
217
+ result.append(f" 🖥️ Terminal: {terminal_id}\n", style=THEME["muted"])
218
+
219
+ elif content.type == "content":
220
+ text = content.data.get("text", "")
221
+ if text:
222
+ preview = text[:100] + "..." if len(text) > 100 else text
223
+ result.append(f" {preview}\n", style=THEME["dim"])
224
+
225
+ return result
226
+
227
+
228
+ # ============================================================================
229
+ # PLAN WIDGET
230
+ # ============================================================================
231
+
232
+
233
+ class PlanWidget(Static):
234
+ """Widget displaying agent's plan with task status."""
235
+
236
+ def __init__(self, plan: StreamPlan, **kwargs):
237
+ super().__init__(**kwargs)
238
+ self.plan = plan
239
+
240
+ def update_plan(self, plan: StreamPlan):
241
+ """Update the plan."""
242
+ self.plan = plan
243
+ self.refresh()
244
+
245
+ def render(self) -> Text:
246
+ result = Text()
247
+
248
+ # Header
249
+ result.append(f"📋 ", style=f"bold {STREAM_COLORS['plan']}")
250
+ result.append("Plan", style=f"bold {STREAM_COLORS['plan']}")
251
+
252
+ # Progress
253
+ completed = sum(1 for t in self.plan.tasks if t.status == TaskStatus.COMPLETED)
254
+ total = len(self.plan.tasks)
255
+ if total > 0:
256
+ result.append(f" ({completed}/{total})", style=THEME["muted"])
257
+
258
+ result.append("\n", style="")
259
+
260
+ # Tasks
261
+ for i, task in enumerate(self.plan.tasks, 1):
262
+ icon = get_task_icon(task.status)
263
+ color = get_task_color(task.status)
264
+
265
+ # Priority indicator
266
+ priority_icon = ""
267
+ if task.priority == TaskPriority.HIGH:
268
+ priority_icon = "🔴 "
269
+ elif task.priority == TaskPriority.LOW:
270
+ priority_icon = "🔵 "
271
+
272
+ result.append(f" {icon} ", style=color)
273
+ result.append(f"{priority_icon}", style="")
274
+ result.append(f"{i}. ", style=THEME["muted"])
275
+
276
+ # Strike through completed tasks
277
+ if task.status == TaskStatus.COMPLETED:
278
+ result.append(task.content, style=f"strike {THEME['dim']}")
279
+ else:
280
+ result.append(
281
+ task.content,
282
+ style=color if task.status == TaskStatus.IN_PROGRESS else THEME["text"],
283
+ )
284
+
285
+ result.append("\n", style="")
286
+
287
+ return result
288
+
289
+
290
+ # ============================================================================
291
+ # PERMISSION REQUEST WIDGET
292
+ # ============================================================================
293
+
294
+
295
+ class PermissionRequest(Message):
296
+ """Message sent when user responds to permission request."""
297
+
298
+ def __init__(self, option_id: str):
299
+ self.option_id = option_id
300
+ super().__init__()
301
+
302
+
303
+ class PermissionWidget(Static):
304
+ """Widget for permission requests with action buttons."""
305
+
306
+ DEFAULT_CSS = """
307
+ PermissionWidget {
308
+ height: auto;
309
+ padding: 1;
310
+ background: #1a1a1a;
311
+ border: round #f97316;
312
+ }
313
+
314
+ PermissionWidget .permission-buttons {
315
+ height: auto;
316
+ margin-top: 1;
317
+ }
318
+
319
+ PermissionWidget Button {
320
+ margin-right: 1;
321
+ }
322
+
323
+ PermissionWidget Button.allow {
324
+ background: #22c55e;
325
+ }
326
+
327
+ PermissionWidget Button.reject {
328
+ background: #ef4444;
329
+ }
330
+ """
331
+
332
+ def __init__(self, permission: StreamPermission, **kwargs):
333
+ super().__init__(**kwargs)
334
+ self.permission = permission
335
+
336
+ def compose(self) -> ComposeResult:
337
+ yield Static(self._render_header(), id="permission-header")
338
+ yield Static(self._render_tool_info(), id="permission-tool")
339
+
340
+ with Horizontal(classes="permission-buttons"):
341
+ for option in self.permission.options:
342
+ btn_class = "allow" if "allow" in option.kind else "reject"
343
+ yield Button(
344
+ option.name,
345
+ id=f"perm-{option.option_id}",
346
+ classes=btn_class,
347
+ )
348
+
349
+ def _render_header(self) -> Text:
350
+ result = Text()
351
+ result.append("🔐 ", style=f"bold {STREAM_COLORS['warning']}")
352
+ result.append("Permission Required", style=f"bold {STREAM_COLORS['warning']}")
353
+ return result
354
+
355
+ def _render_tool_info(self) -> Text:
356
+ result = Text()
357
+ tc = self.permission.tool_call
358
+
359
+ tool_icon = get_tool_icon(tc.kind)
360
+ result.append(f"\n{tool_icon} ", style=STREAM_COLORS["tool"])
361
+ result.append(tc.title, style=f"bold {THEME['text']}")
362
+
363
+ if tc.locations:
364
+ loc = tc.locations[0]
365
+ path = loc.get("path", "")
366
+ if path:
367
+ result.append(f"\n📍 {path}", style=THEME["muted"])
368
+
369
+ return result
370
+
371
+ def on_button_pressed(self, event: Button.Pressed) -> None:
372
+ """Handle button press."""
373
+ button_id = event.button.id or ""
374
+ if button_id.startswith("perm-"):
375
+ option_id = button_id[5:] # Remove "perm-" prefix
376
+
377
+ # Resolve the future
378
+ if self.permission.result_future and not self.permission.result_future.done():
379
+ self.permission.result_future.set_result(option_id)
380
+
381
+ # Post message
382
+ self.post_message(PermissionRequest(option_id))
383
+
384
+ # Remove widget
385
+ self.remove()
386
+
387
+
388
+ # ============================================================================
389
+ # STREAM VIEW CONTAINER
390
+ # ============================================================================
391
+
392
+
393
+ class StreamView(Container):
394
+ """
395
+ Container for displaying streaming agent output.
396
+
397
+ Manages multiple streaming widgets and updates them in real-time.
398
+ """
399
+
400
+ DEFAULT_CSS = """
401
+ StreamView {
402
+ height: auto;
403
+ padding: 0 1;
404
+ }
405
+
406
+ StreamView .stream-message {
407
+ margin-bottom: 1;
408
+ }
409
+
410
+ StreamView .stream-thinking {
411
+ margin-bottom: 1;
412
+ padding: 0 1;
413
+ background: #1a1a1a;
414
+ border-left: tall #ec4899;
415
+ }
416
+
417
+ StreamView .stream-tool {
418
+ margin-bottom: 1;
419
+ padding: 0 1;
420
+ background: #1a1a1a;
421
+ border-left: tall #f97316;
422
+ }
423
+
424
+ StreamView .stream-plan {
425
+ margin-bottom: 1;
426
+ padding: 0 1;
427
+ background: #1a1a1a;
428
+ border-left: tall #06b6d4;
429
+ }
430
+ """
431
+
432
+ def __init__(self, agent_name: str = "Agent", **kwargs):
433
+ super().__init__(**kwargs)
434
+ self.agent_name = agent_name
435
+ self._current_message: Optional[StreamingMessage] = None
436
+ self._thinking: Optional[ThinkingBubble] = None
437
+ self._plan: Optional[PlanWidget] = None
438
+ self._tool_widgets: Dict[str, ToolCallWidget] = {}
439
+
440
+ def handle_event(self, event: StreamEvent):
441
+ """Handle a streaming event."""
442
+ if event.event_type == StreamEventType.MESSAGE_CHUNK:
443
+ self._handle_message(event.data)
444
+ elif event.event_type == StreamEventType.THOUGHT_CHUNK:
445
+ self._handle_thought(event.data)
446
+ elif event.event_type == StreamEventType.TOOL_CALL:
447
+ self._handle_tool_call(event.data)
448
+ elif event.event_type == StreamEventType.TOOL_UPDATE:
449
+ self._handle_tool_update(event.data)
450
+ elif event.event_type == StreamEventType.PLAN:
451
+ self._handle_plan(event.data)
452
+ elif event.event_type == StreamEventType.PERMISSION:
453
+ self._handle_permission(event.data)
454
+ elif event.event_type == StreamEventType.ERROR:
455
+ self._handle_error(event.data)
456
+ elif event.event_type == StreamEventType.COMPLETE:
457
+ self._handle_complete()
458
+
459
+ def _handle_message(self, msg: StreamMessage):
460
+ """Handle message chunk."""
461
+ if msg.is_complete:
462
+ if self._current_message:
463
+ self._current_message.mark_complete()
464
+ self._current_message = None
465
+ return
466
+
467
+ if not self._current_message:
468
+ self._current_message = StreamingMessage(
469
+ agent_name=self.agent_name, classes="stream-message"
470
+ )
471
+ self.mount(self._current_message)
472
+
473
+ self._current_message.append_text(msg.text)
474
+
475
+ def _handle_thought(self, thought: StreamThought):
476
+ """Handle thought chunk."""
477
+ if not self._thinking:
478
+ self._thinking = ThinkingBubble(classes="stream-thinking")
479
+ self.mount(self._thinking)
480
+
481
+ self._thinking.add_thought(thought.text)
482
+
483
+ def _handle_tool_call(self, tool_call: StreamToolCall):
484
+ """Handle new tool call."""
485
+ widget = ToolCallWidget(tool_call, classes="stream-tool")
486
+ self._tool_widgets[tool_call.tool_id] = widget
487
+ self.mount(widget)
488
+
489
+ def _handle_tool_update(self, tool_call: StreamToolCall):
490
+ """Handle tool call update."""
491
+ if tool_call.tool_id in self._tool_widgets:
492
+ self._tool_widgets[tool_call.tool_id].update_tool(tool_call)
493
+
494
+ def _handle_plan(self, plan: StreamPlan):
495
+ """Handle plan update."""
496
+ if not self._plan:
497
+ self._plan = PlanWidget(plan, classes="stream-plan")
498
+ self.mount(self._plan)
499
+ else:
500
+ self._plan.update_plan(plan)
501
+
502
+ def _handle_permission(self, permission: StreamPermission):
503
+ """Handle permission request."""
504
+ widget = PermissionWidget(permission)
505
+ self.mount(widget)
506
+
507
+ def _handle_error(self, error: str):
508
+ """Handle error."""
509
+ error_widget = Static(
510
+ Text(f"❌ {error}", style=STREAM_COLORS["error"]), classes="stream-error"
511
+ )
512
+ self.mount(error_widget)
513
+
514
+ def _handle_complete(self):
515
+ """Handle completion."""
516
+ # Clear thinking bubble
517
+ if self._thinking:
518
+ self._thinking.remove()
519
+ self._thinking = None
520
+
521
+ # Mark message complete
522
+ if self._current_message:
523
+ self._current_message.mark_complete()
524
+ self._current_message = None
525
+
526
+ def clear(self):
527
+ """Clear all streaming content."""
528
+ self._current_message = None
529
+ self._thinking = None
530
+ self._plan = None
531
+ self._tool_widgets.clear()
532
+ self.remove_children()
533
+
534
+
535
+ # ============================================================================
536
+ # STREAMING STATUS BAR
537
+ # ============================================================================
538
+
539
+
540
+ class StreamStatusBar(Static):
541
+ """Status bar showing streaming progress with colorful indicators."""
542
+
543
+ DEFAULT_CSS = """
544
+ StreamStatusBar {
545
+ height: 1;
546
+ background: #111111;
547
+ padding: 0 1;
548
+ }
549
+ """
550
+
551
+ status = reactive("idle")
552
+ agent_name = reactive("Agent")
553
+ tool_count = reactive(0)
554
+ message_length = reactive(0)
555
+
556
+ def render(self) -> Text:
557
+ result = Text()
558
+
559
+ # Agent indicator
560
+ result.append(f"🤖 {self.agent_name}", style=f"bold {STREAM_COLORS['message']}")
561
+ result.append(" │ ", style=THEME["dim"])
562
+
563
+ # Status with color
564
+ status_styles = {
565
+ "idle": ("○", THEME["muted"]),
566
+ "connecting": ("◌", STREAM_COLORS["warning"]),
567
+ "streaming": ("●", STREAM_COLORS["success"]),
568
+ "thinking": ("◐", STREAM_COLORS["thought"]),
569
+ "tool": ("◑", STREAM_COLORS["tool"]),
570
+ "complete": ("✓", STREAM_COLORS["success"]),
571
+ "error": ("✗", STREAM_COLORS["error"]),
572
+ }
573
+ icon, color = status_styles.get(self.status, ("○", THEME["muted"]))
574
+ result.append(f"{icon} ", style=color)
575
+ result.append(self.status.title(), style=color)
576
+
577
+ # Stats
578
+ if self.tool_count > 0:
579
+ result.append(" │ ", style=THEME["dim"])
580
+ result.append(f"🔧 {self.tool_count}", style=STREAM_COLORS["tool"])
581
+
582
+ if self.message_length > 0:
583
+ result.append(" │ ", style=THEME["dim"])
584
+ result.append(f"💬 {self.message_length} chars", style=THEME["muted"])
585
+
586
+ return result
587
+
588
+
589
+ # ============================================================================
590
+ # COLORFUL DIFF PREVIEW
591
+ # ============================================================================
592
+
593
+
594
+ class StreamDiffPreview(Static):
595
+ """Colorful diff preview for file changes."""
596
+
597
+ DEFAULT_CSS = """
598
+ StreamDiffPreview {
599
+ height: auto;
600
+ padding: 1;
601
+ background: #0d0d0d;
602
+ border: round #2a2a2a;
603
+ margin: 1 0;
604
+ }
605
+ """
606
+
607
+ def __init__(self, path: str, old_text: str, new_text: str, **kwargs):
608
+ super().__init__(**kwargs)
609
+ self.path = path
610
+ self.old_text = old_text
611
+ self.new_text = new_text
612
+
613
+ def render(self) -> Text:
614
+ result = Text()
615
+
616
+ # Header
617
+ result.append("📄 ", style=f"bold {THEME['cyan']}")
618
+ result.append(self.path, style=f"bold {THEME['cyan']}")
619
+ result.append("\n", style="")
620
+
621
+ # Calculate stats
622
+ old_lines = self.old_text.split("\n") if self.old_text else []
623
+ new_lines = self.new_text.split("\n") if self.new_text else []
624
+
625
+ result.append(f" ", style="")
626
+ result.append(f"+{len(new_lines)}", style=f"bold {THEME['green']}")
627
+ result.append(" / ", style=THEME["muted"])
628
+ result.append(f"-{len(old_lines)}", style=f"bold {THEME['red']}")
629
+ result.append(" lines\n\n", style=THEME["muted"])
630
+
631
+ # Show diff preview (first few lines)
632
+ for line in old_lines[:3]:
633
+ line_preview = line[:60] + "..." if len(line) > 60 else line
634
+ result.append(f" - {line_preview}\n", style=f"on #2d1f1f {THEME['red']}")
635
+
636
+ if len(old_lines) > 3:
637
+ result.append(f" ... ({len(old_lines) - 3} more lines)\n", style=THEME["dim"])
638
+
639
+ result.append("\n", style="")
640
+
641
+ for line in new_lines[:3]:
642
+ line_preview = line[:60] + "..." if len(line) > 60 else line
643
+ result.append(f" + {line_preview}\n", style=f"on #1f2d1f {THEME['green']}")
644
+
645
+ if len(new_lines) > 3:
646
+ result.append(f" ... ({len(new_lines) - 3} more lines)\n", style=THEME["dim"])
647
+
648
+ return result
@@ -0,0 +1,3 @@
1
+ """SuperQode styles module."""
2
+
3
+ # This module is reserved for future styling functionality.