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,721 @@
1
+ """
2
+ SuperQode Agent Output Display - World's Most Beautiful CLI Agent Output
3
+
4
+ Features:
5
+ - Collapsible thinking/logs section with toggle (Ctrl+T)
6
+ - Beautiful final response with rich formatting
7
+ - Syntax highlighted code blocks
8
+ - Colorful emojis and gradients
9
+ - Clear visual separation
10
+ - Animated elements
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import re
16
+ from dataclasses import dataclass, field
17
+ from typing import List, Optional, Dict, Any
18
+ from time import monotonic
19
+
20
+ from textual.app import ComposeResult
21
+ from textual.containers import Container, Vertical, Horizontal, ScrollableContainer
22
+ from textual.widgets import Static, Collapsible, RichLog
23
+ from textual.reactive import reactive
24
+ from textual.binding import Binding
25
+
26
+ from rich.text import Text
27
+ from rich.panel import Panel
28
+ from rich.syntax import Syntax
29
+ from rich.markdown import Markdown
30
+ from rich.console import Group
31
+ from rich.box import ROUNDED, HEAVY, DOUBLE
32
+ from rich.table import Table
33
+
34
+
35
+ # ============================================================================
36
+ # THEME - Vibrant SuperQode Colors
37
+ # ============================================================================
38
+
39
+ COLORS = {
40
+ # Primary gradient
41
+ "purple": "#a855f7",
42
+ "magenta": "#d946ef",
43
+ "pink": "#ec4899",
44
+ "rose": "#fb7185",
45
+ "orange": "#f97316",
46
+ "gold": "#fbbf24",
47
+ # Accent colors
48
+ "cyan": "#06b6d4",
49
+ "teal": "#14b8a6",
50
+ "green": "#22c55e",
51
+ "blue": "#3b82f6",
52
+ # Status
53
+ "success": "#22c55e",
54
+ "error": "#ef4444",
55
+ "warning": "#f59e0b",
56
+ "info": "#06b6d4",
57
+ # Backgrounds
58
+ "bg_dark": "#0a0a0a",
59
+ "bg_surface": "#111111",
60
+ "bg_elevated": "#1a1a1a",
61
+ "bg_thinking": "#0d1117",
62
+ "bg_response": "#0f0a1a",
63
+ # Text
64
+ "text": "#e4e4e7",
65
+ "text_muted": "#71717a",
66
+ "text_dim": "#52525b",
67
+ # Borders
68
+ "border": "#2a2a2a",
69
+ "border_active": "#a855f7",
70
+ }
71
+
72
+ # Rainbow gradient for special effects
73
+ RAINBOW = ["#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4", "#3b82f6", "#8b5cf6", "#ec4899"]
74
+
75
+ # Gradient for response header
76
+ RESPONSE_GRADIENT = ["#a855f7", "#c026d3", "#d946ef", "#ec4899", "#f43f5e"]
77
+
78
+
79
+ # ============================================================================
80
+ # ICONS & EMOJIS
81
+ # ============================================================================
82
+
83
+ THINKING_ICONS = {
84
+ "read": "📖",
85
+ "write": "✏️",
86
+ "search": "🔍",
87
+ "run": "⚡",
88
+ "think": "🧠",
89
+ "analyze": "🔬",
90
+ "create": "🎨",
91
+ "delete": "🗑️",
92
+ "move": "📦",
93
+ "fetch": "🌐",
94
+ "test": "🧪",
95
+ "build": "🔨",
96
+ "deploy": "🚀",
97
+ "debug": "🐛",
98
+ "config": "⚙️",
99
+ "git": "📦",
100
+ "install": "📥",
101
+ "update": "🔄",
102
+ }
103
+
104
+ RESPONSE_DECORATIONS = {
105
+ "sparkle": "✨",
106
+ "star": "⭐",
107
+ "rocket": "🚀",
108
+ "magic": "🪄",
109
+ "gem": "💎",
110
+ "crown": "👑",
111
+ "fire": "🔥",
112
+ "lightning": "⚡",
113
+ "rainbow": "🌈",
114
+ "heart": "💜",
115
+ }
116
+
117
+
118
+ def get_thinking_icon(line: str) -> str:
119
+ """Get appropriate icon for a thinking/log line."""
120
+ line_lower = line.lower()
121
+
122
+ if any(x in line_lower for x in ["reading", "read", "loading", "fetching"]):
123
+ return THINKING_ICONS["read"]
124
+ elif any(x in line_lower for x in ["writing", "write", "creating", "saving"]):
125
+ return THINKING_ICONS["write"]
126
+ elif any(x in line_lower for x in ["searching", "search", "finding", "looking"]):
127
+ return THINKING_ICONS["search"]
128
+ elif any(x in line_lower for x in ["running", "executing", "command"]):
129
+ return THINKING_ICONS["run"]
130
+ elif any(x in line_lower for x in ["thinking", "analyzing", "processing"]):
131
+ return THINKING_ICONS["think"]
132
+ elif any(x in line_lower for x in ["test", "testing"]):
133
+ return THINKING_ICONS["test"]
134
+ elif any(x in line_lower for x in ["build", "compiling"]):
135
+ return THINKING_ICONS["build"]
136
+ elif any(x in line_lower for x in ["git", "commit", "push", "pull"]):
137
+ return THINKING_ICONS["git"]
138
+ elif any(x in line_lower for x in ["install", "npm", "pip", "yarn"]):
139
+ return THINKING_ICONS["install"]
140
+ elif any(x in line_lower for x in ["error", "fail", "bug"]):
141
+ return THINKING_ICONS["debug"]
142
+ else:
143
+ return "▸"
144
+
145
+
146
+ def strip_markdown(text: str) -> str:
147
+ """Strip markdown formatting from text for clean display."""
148
+ # Strip bold: **text** or __text__
149
+ text = re.sub(r"\*\*(.+?)\*\*", r"\1", text)
150
+ text = re.sub(r"__(.+?)__", r"\1", text)
151
+
152
+ # Strip italic: *text* or _text_
153
+ text = re.sub(r"(?<![*\w])\*([^*]+?)\*(?![*\w])", r"\1", text)
154
+ text = re.sub(r"(?<![_\w])_([^_]+?)_(?![_\w])", r"\1", text)
155
+
156
+ # Strip strikethrough: ~~text~~
157
+ text = re.sub(r"~~(.+?)~~", r"\1", text)
158
+
159
+ # Strip inline code: `code`
160
+ text = re.sub(r"`([^`]+?)`", r"\1", text)
161
+
162
+ # Strip links: [text](url) -> text
163
+ text = re.sub(r"\[([^\]]+?)\]\([^)]+?\)", r"\1", text)
164
+
165
+ # Strip images: ![alt](url) -> alt
166
+ text = re.sub(r"!\[([^\]]*?)\]\([^)]+?\)", r"\1", text)
167
+
168
+ # Strip blockquotes: > text -> text
169
+ text = re.sub(r"^>\s*", "", text, flags=re.MULTILINE)
170
+
171
+ # Strip headers: # text -> text (keep the text)
172
+ text = re.sub(r"^#{1,6}\s+", "", text, flags=re.MULTILINE)
173
+
174
+ return text
175
+
176
+
177
+ # ============================================================================
178
+ # DATA CLASSES
179
+ # ============================================================================
180
+
181
+
182
+ @dataclass
183
+ class ThinkingLine:
184
+ """A single line of thinking/log output."""
185
+
186
+ text: str
187
+ icon: str = ""
188
+ timestamp: float = field(default_factory=monotonic)
189
+
190
+ def __post_init__(self):
191
+ if not self.icon:
192
+ self.icon = get_thinking_icon(self.text)
193
+
194
+
195
+ @dataclass
196
+ class CodeBlock:
197
+ """A code block in the response."""
198
+
199
+ code: str
200
+ language: str = "text"
201
+ filename: str = ""
202
+
203
+
204
+ @dataclass
205
+ class AgentResponse:
206
+ """Parsed agent response with structured content."""
207
+
208
+ raw_text: str
209
+ paragraphs: List[str] = field(default_factory=list)
210
+ code_blocks: List[CodeBlock] = field(default_factory=list)
211
+ bullet_points: List[str] = field(default_factory=list)
212
+
213
+ def __post_init__(self):
214
+ self._parse_response()
215
+
216
+ def _parse_response(self):
217
+ """Parse the raw response into structured parts."""
218
+ text = self.raw_text
219
+
220
+ # Extract code blocks
221
+ code_pattern = r"```(\w*)\n(.*?)```"
222
+ for match in re.finditer(code_pattern, text, re.DOTALL):
223
+ lang = match.group(1) or "text"
224
+ code = match.group(2).strip()
225
+ self.code_blocks.append(CodeBlock(code=code, language=lang))
226
+
227
+ # Remove code blocks from text for paragraph parsing
228
+ text_without_code = re.sub(code_pattern, "", text, flags=re.DOTALL)
229
+
230
+ # Extract bullet points
231
+ bullet_pattern = r"^[\s]*[-*•]\s+(.+)$"
232
+ for match in re.finditer(bullet_pattern, text_without_code, re.MULTILINE):
233
+ self.bullet_points.append(match.group(1).strip())
234
+
235
+ # Extract paragraphs (non-empty lines that aren't bullets)
236
+ text_without_bullets = re.sub(bullet_pattern, "", text_without_code, flags=re.MULTILINE)
237
+ for para in text_without_bullets.split("\n\n"):
238
+ para = para.strip()
239
+ if para and len(para) > 10:
240
+ self.paragraphs.append(para)
241
+
242
+
243
+ # ============================================================================
244
+ # RENDERING FUNCTIONS
245
+ # ============================================================================
246
+
247
+
248
+ def render_thinking_line(line: ThinkingLine, index: int) -> Text:
249
+ """Render a single thinking line with icon and color."""
250
+ result = Text()
251
+
252
+ # Alternating subtle background effect via color
253
+ color = COLORS["text_dim"] if index % 2 == 0 else COLORS["text_muted"]
254
+
255
+ # Icon
256
+ result.append(f" {line.icon} ", style=COLORS["cyan"])
257
+
258
+ # Text (truncate if too long)
259
+ text = line.text[:100] + "..." if len(line.text) > 100 else line.text
260
+ result.append(text, style=color)
261
+
262
+ return result
263
+
264
+
265
+ def render_thinking_section(lines: List[ThinkingLine], collapsed: bool = False) -> Panel:
266
+ """Render the collapsible thinking section."""
267
+ if not lines:
268
+ return Panel(
269
+ Text("No thinking logs", style=COLORS["text_dim"]),
270
+ title="[bold]💭 Thinking[/]",
271
+ border_style=COLORS["border"],
272
+ box=ROUNDED,
273
+ )
274
+
275
+ content = Text()
276
+
277
+ # Show summary when collapsed
278
+ if collapsed:
279
+ content.append(f" 📊 {len(lines)} operations performed\n", style=COLORS["text_muted"])
280
+ content.append(f" ⏱️ Click to expand details", style=COLORS["text_dim"])
281
+ else:
282
+ # Show all lines
283
+ for i, line in enumerate(lines[-50:]): # Last 50 lines
284
+ content.append_text(render_thinking_line(line, i))
285
+ content.append("\n")
286
+
287
+ return Panel(
288
+ content,
289
+ title=f"[bold {COLORS['cyan']}]💭 Thinking ({len(lines)} steps)[/]",
290
+ subtitle="[dim]Ctrl+T to toggle[/]",
291
+ border_style=COLORS["border"],
292
+ box=ROUNDED,
293
+ padding=(0, 1),
294
+ )
295
+
296
+
297
+ def render_code_block(block: CodeBlock) -> Panel:
298
+ """Render a beautiful code block with syntax highlighting."""
299
+ # Create syntax highlighted code
300
+ syntax = Syntax(
301
+ block.code,
302
+ block.language,
303
+ theme="monokai",
304
+ line_numbers=True,
305
+ word_wrap=True,
306
+ background_color="#000000",
307
+ )
308
+
309
+ # Language badge
310
+ lang_icons = {
311
+ "python": "🐍",
312
+ "javascript": "📜",
313
+ "typescript": "💠",
314
+ "rust": "🦀",
315
+ "go": "🐹",
316
+ "java": "☕",
317
+ "ruby": "💎",
318
+ "bash": "🖥️",
319
+ "shell": "🖥️",
320
+ "sql": "🗄️",
321
+ "html": "🌐",
322
+ "css": "🎨",
323
+ "json": "📋",
324
+ "yaml": "📝",
325
+ "markdown": "📄",
326
+ }
327
+ icon = lang_icons.get(block.language.lower(), "📄")
328
+
329
+ title = f"[bold {COLORS['green']}]{icon} {block.language.upper()}[/]"
330
+ if block.filename:
331
+ title += f" [dim]({block.filename})[/]"
332
+
333
+ return Panel(
334
+ syntax,
335
+ title=title,
336
+ border_style=COLORS["green"],
337
+ box=ROUNDED,
338
+ padding=(0, 1),
339
+ )
340
+
341
+
342
+ def render_response_header(agent_name: str = "Agent") -> Text:
343
+ """Render a beautiful gradient header for the response."""
344
+ result = Text()
345
+
346
+ # Decorative line with gradient
347
+ line_chars = "━" * 50
348
+ for i, char in enumerate(line_chars):
349
+ color_idx = i % len(RESPONSE_GRADIENT)
350
+ result.append(char, style=RESPONSE_GRADIENT[color_idx])
351
+
352
+ result.append("\n")
353
+
354
+ # Agent name with sparkles
355
+ result.append(" 🤖 ", style=COLORS["gold"])
356
+ result.append(agent_name.upper(), style=f"bold {COLORS['purple']}")
357
+ result.append(" Response ", style=f"bold {COLORS['magenta']}")
358
+ result.append("🤖\n", style=COLORS["gold"])
359
+
360
+ # Another gradient line
361
+ for i, char in enumerate(line_chars):
362
+ color_idx = (i + 3) % len(RESPONSE_GRADIENT)
363
+ result.append(char, style=RESPONSE_GRADIENT[color_idx])
364
+
365
+ return result
366
+
367
+
368
+ def render_paragraph(text: str, width: int = 80) -> Text:
369
+ """Render a paragraph with nice formatting and word wrapping."""
370
+ import textwrap
371
+ import shutil
372
+
373
+ # Get terminal width if not specified
374
+ try:
375
+ term_width = shutil.get_terminal_size().columns
376
+ wrap_width = min(term_width - 10, width)
377
+ except Exception:
378
+ wrap_width = width
379
+
380
+ result = Text()
381
+ # Strip markdown and wrap text
382
+ clean_text = strip_markdown(text)
383
+ wrapped = textwrap.fill(clean_text, width=wrap_width - 4)
384
+ for line in wrapped.split("\n"):
385
+ result.append(" ", style="")
386
+ result.append(line, style=COLORS["text"])
387
+ result.append("\n", style="")
388
+ return result
389
+
390
+
391
+ def render_bullet_point(text: str, index: int, width: int = 80) -> Text:
392
+ """Render a bullet point with colorful bullet and word wrapping."""
393
+ import textwrap
394
+ import shutil
395
+
396
+ # Get terminal width if not specified
397
+ try:
398
+ term_width = shutil.get_terminal_size().columns
399
+ wrap_width = min(term_width - 10, width)
400
+ except Exception:
401
+ wrap_width = width
402
+
403
+ result = Text()
404
+
405
+ # Rotating colors for bullets
406
+ bullet_colors = [
407
+ COLORS["purple"],
408
+ COLORS["pink"],
409
+ COLORS["cyan"],
410
+ COLORS["green"],
411
+ COLORS["orange"],
412
+ ]
413
+ color = bullet_colors[index % len(bullet_colors)]
414
+
415
+ # Strip markdown and wrap the text
416
+ clean_text = strip_markdown(text)
417
+ wrapped = textwrap.fill(clean_text, width=wrap_width - 6)
418
+ lines = wrapped.split("\n")
419
+
420
+ for i, line in enumerate(lines):
421
+ if i == 0:
422
+ result.append(" ", style="")
423
+ result.append("◆ ", style=f"bold {color}")
424
+ result.append(line, style=COLORS["text"])
425
+ else:
426
+ result.append(" ", style="") # Indent continuation
427
+ result.append(line, style=COLORS["text"])
428
+ result.append("\n", style="")
429
+
430
+ return result
431
+
432
+
433
+ def render_response_footer(duration: float = 0) -> Text:
434
+ """Render a beautiful footer for the response."""
435
+ result = Text()
436
+
437
+ # Gradient line
438
+ line_chars = "─" * 50
439
+ for i, char in enumerate(line_chars):
440
+ color_idx = i % len(RAINBOW)
441
+ result.append(char, style=RAINBOW[color_idx])
442
+
443
+ result.append("\n")
444
+
445
+ # Stats
446
+ result.append(" 🎯 ", style=COLORS["green"])
447
+ result.append("Response complete", style=COLORS["text_muted"])
448
+
449
+ if duration > 0:
450
+ result.append(f" ⏱️ {duration:.1f}s", style=COLORS["text_dim"])
451
+
452
+ result.append(" 💜", style=COLORS["purple"])
453
+
454
+ return result
455
+
456
+
457
+ def render_full_response(
458
+ response: AgentResponse, agent_name: str = "Agent", duration: float = 0
459
+ ) -> Group:
460
+ """Render the complete beautiful response."""
461
+ elements = []
462
+
463
+ # Header
464
+ elements.append(render_response_header(agent_name))
465
+ elements.append(Text("\n"))
466
+
467
+ # Main content - paragraphs
468
+ for para in response.paragraphs:
469
+ elements.append(render_paragraph(para))
470
+ elements.append(Text("\n\n"))
471
+
472
+ # Bullet points
473
+ if response.bullet_points:
474
+ elements.append(Text("\n"))
475
+ for i, bullet in enumerate(response.bullet_points):
476
+ elements.append(render_bullet_point(bullet, i))
477
+ elements.append(Text("\n"))
478
+
479
+ # Code blocks
480
+ for block in response.code_blocks:
481
+ elements.append(Text("\n"))
482
+ elements.append(render_code_block(block))
483
+ elements.append(Text("\n"))
484
+
485
+ # Footer
486
+ elements.append(Text("\n"))
487
+ elements.append(render_response_footer(duration))
488
+
489
+ return Group(*elements)
490
+
491
+
492
+ # ============================================================================
493
+ # TEXTUAL WIDGETS
494
+ # ============================================================================
495
+
496
+
497
+ class ThinkingLog(Static):
498
+ """Collapsible thinking/log display widget."""
499
+
500
+ DEFAULT_CSS = """
501
+ ThinkingLog {
502
+ height: auto;
503
+ max-height: 15;
504
+ background: #0d1117;
505
+ border: round #2a2a2a;
506
+ margin: 0 0 1 0;
507
+ overflow-y: auto;
508
+ }
509
+
510
+ ThinkingLog.collapsed {
511
+ max-height: 3;
512
+ }
513
+ """
514
+
515
+ collapsed = reactive(False)
516
+
517
+ def __init__(self, **kwargs):
518
+ super().__init__(**kwargs)
519
+ self._lines: List[ThinkingLine] = []
520
+
521
+ def add_line(self, text: str):
522
+ """Add a thinking line."""
523
+ self._lines.append(ThinkingLine(text=text))
524
+ self.refresh()
525
+
526
+ def toggle(self):
527
+ """Toggle collapsed state."""
528
+ self.collapsed = not self.collapsed
529
+ self.set_class(self.collapsed, "collapsed")
530
+
531
+ def clear(self):
532
+ """Clear all lines."""
533
+ self._lines.clear()
534
+ self.refresh()
535
+
536
+ def render(self) -> Panel:
537
+ return render_thinking_section(self._lines, self.collapsed)
538
+
539
+
540
+ class AgentResponseDisplay(Static):
541
+ """Beautiful agent response display widget."""
542
+
543
+ DEFAULT_CSS = """
544
+ AgentResponseDisplay {
545
+ height: auto;
546
+ background: #0f0a1a;
547
+ border: round #a855f7;
548
+ padding: 1;
549
+ margin: 1 0;
550
+ }
551
+ """
552
+
553
+ def __init__(
554
+ self, response_text: str = "", agent_name: str = "Agent", duration: float = 0, **kwargs
555
+ ):
556
+ super().__init__(**kwargs)
557
+ self.response_text = response_text
558
+ self.agent_name = agent_name
559
+ self.duration = duration
560
+ self._response: Optional[AgentResponse] = None
561
+
562
+ def set_response(self, text: str, agent_name: str = "Agent", duration: float = 0):
563
+ """Set the response content."""
564
+ self.response_text = text
565
+ self.agent_name = agent_name
566
+ self.duration = duration
567
+ self._response = AgentResponse(raw_text=text)
568
+ self.refresh()
569
+
570
+ def render(self) -> Group:
571
+ if not self.response_text:
572
+ return Group(Text("Waiting for response...", style=COLORS["text_dim"]))
573
+
574
+ if self._response is None:
575
+ self._response = AgentResponse(raw_text=self.response_text)
576
+
577
+ return render_full_response(self._response, self.agent_name, self.duration)
578
+
579
+
580
+ class AgentOutputContainer(Container):
581
+ """
582
+ Complete agent output container with thinking logs and response.
583
+
584
+ Features:
585
+ - Collapsible thinking section (Ctrl+T)
586
+ - Beautiful response display
587
+ - Clear visual separation
588
+ """
589
+
590
+ DEFAULT_CSS = """
591
+ AgentOutputContainer {
592
+ height: auto;
593
+ padding: 0 1;
594
+ }
595
+
596
+ AgentOutputContainer .output-header {
597
+ height: 1;
598
+ text-align: center;
599
+ margin-bottom: 1;
600
+ }
601
+ """
602
+
603
+ BINDINGS = [
604
+ Binding("ctrl+t", "toggle_thinking", "Toggle Thinking", show=True),
605
+ ]
606
+
607
+ show_thinking = reactive(True)
608
+
609
+ def __init__(self, agent_name: str = "Agent", **kwargs):
610
+ super().__init__(**kwargs)
611
+ self.agent_name = agent_name
612
+ self._thinking_log: Optional[ThinkingLog] = None
613
+ self._response_display: Optional[AgentResponseDisplay] = None
614
+ self._start_time: float = 0
615
+
616
+ def compose(self) -> ComposeResult:
617
+ yield Static(self._render_header(), classes="output-header")
618
+ self._thinking_log = ThinkingLog()
619
+ yield self._thinking_log
620
+ self._response_display = AgentResponseDisplay(agent_name=self.agent_name)
621
+ yield self._response_display
622
+
623
+ def _render_header(self) -> Text:
624
+ result = Text()
625
+ result.append("🤖 ", style=COLORS["purple"])
626
+ result.append(self.agent_name.upper(), style=f"bold {COLORS['purple']}")
627
+ result.append(" OUTPUT", style=f"bold {COLORS['magenta']}")
628
+ return result
629
+
630
+ def start_session(self):
631
+ """Start a new output session."""
632
+ self._start_time = monotonic()
633
+ if self._thinking_log:
634
+ self._thinking_log.clear()
635
+
636
+ def add_thinking(self, text: str):
637
+ """Add a thinking/log line."""
638
+ if self._thinking_log:
639
+ self._thinking_log.add_line(text)
640
+
641
+ def set_response(self, text: str):
642
+ """Set the final response."""
643
+ duration = monotonic() - self._start_time if self._start_time else 0
644
+ if self._response_display:
645
+ self._response_display.set_response(text, self.agent_name, duration)
646
+
647
+ def action_toggle_thinking(self):
648
+ """Toggle thinking section visibility."""
649
+ if self._thinking_log:
650
+ self._thinking_log.toggle()
651
+ self.show_thinking = not self._thinking_log.collapsed
652
+
653
+
654
+ # ============================================================================
655
+ # HELPER FUNCTIONS FOR APP INTEGRATION
656
+ # ============================================================================
657
+
658
+
659
+ def format_agent_output_for_log(
660
+ thinking_lines: List[str],
661
+ response_text: str,
662
+ agent_name: str = "Agent",
663
+ duration: float = 0,
664
+ show_thinking: bool = True,
665
+ ) -> Group:
666
+ """
667
+ Format complete agent output for display in conversation log.
668
+
669
+ Args:
670
+ thinking_lines: List of thinking/log lines
671
+ response_text: Final response text
672
+ agent_name: Name of the agent
673
+ duration: Time taken in seconds
674
+ show_thinking: Whether to show thinking section
675
+
676
+ Returns:
677
+ Rich Group ready for display
678
+ """
679
+ elements = []
680
+
681
+ # Thinking section (collapsible)
682
+ if thinking_lines and show_thinking:
683
+ lines = [ThinkingLine(text=t) for t in thinking_lines]
684
+ elements.append(render_thinking_section(lines, collapsed=False))
685
+ elements.append(Text("\n"))
686
+ elif thinking_lines:
687
+ # Show collapsed summary
688
+ elements.append(
689
+ Text(f" 💭 {len(thinking_lines)} thinking steps (hidden)\n", style=COLORS["text_dim"])
690
+ )
691
+
692
+ # Response
693
+ if response_text:
694
+ response = AgentResponse(raw_text=response_text)
695
+ elements.append(render_full_response(response, agent_name, duration))
696
+
697
+ return Group(*elements)
698
+
699
+
700
+ def create_simple_response_panel(text: str, agent_name: str = "Agent") -> Panel:
701
+ """Create a simple but beautiful response panel."""
702
+ content = Text()
703
+
704
+ # Add sparkle decoration
705
+ content.append("✨ ", style=COLORS["gold"])
706
+
707
+ # Format the text nicely
708
+ lines = text.strip().split("\n")
709
+ for i, line in enumerate(lines):
710
+ if line.strip():
711
+ content.append(line, style=COLORS["text"])
712
+ if i < len(lines) - 1:
713
+ content.append("\n")
714
+
715
+ return Panel(
716
+ content,
717
+ title=f"[bold {COLORS['purple']}]🤖 {agent_name}[/]",
718
+ border_style=COLORS["purple"],
719
+ box=ROUNDED,
720
+ padding=(1, 2),
721
+ )