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,340 @@
1
+ """
2
+ Issue Discovery Timeline Widget.
3
+
4
+ A SuperQode-original widget showing issues discovered during QE sessions
5
+ in a chronological timeline format with severity indicators.
6
+
7
+ Design: Clean, scannable timeline that emphasizes issue severity
8
+ and provides quick access to details.
9
+ """
10
+
11
+ from dataclasses import dataclass, field
12
+ from datetime import datetime
13
+ from enum import Enum
14
+ from typing import List, Optional
15
+
16
+ from rich.console import RenderableType
17
+ from rich.panel import Panel
18
+ from rich.text import Text
19
+ from textual.reactive import reactive
20
+ from textual.widgets import Static
21
+
22
+
23
+ class IssueSeverity(Enum):
24
+ """Severity level of an issue."""
25
+
26
+ CRITICAL = "critical" # Red - security, crash
27
+ HIGH = "high" # Orange - bugs, failures
28
+ MEDIUM = "medium" # Yellow - warnings
29
+ LOW = "low" # Green - suggestions
30
+ INFO = "info" # Blue - informational
31
+
32
+
33
+ class IssueCategory(Enum):
34
+ """Category of the issue."""
35
+
36
+ BUG = "bug"
37
+ SECURITY = "security"
38
+ PERFORMANCE = "performance"
39
+ COVERAGE = "coverage"
40
+ STYLE = "style"
41
+ COMPLEXITY = "complexity"
42
+ DEPENDENCY = "dependency"
43
+ TEST = "test"
44
+ OTHER = "other"
45
+
46
+
47
+ @dataclass
48
+ class DiscoveredIssue:
49
+ """An issue discovered during QE analysis."""
50
+
51
+ id: str
52
+ severity: IssueSeverity
53
+ category: IssueCategory
54
+ title: str
55
+ file_path: str = ""
56
+ line_number: Optional[int] = None
57
+ description: str = ""
58
+ discovered_at: datetime = field(default_factory=datetime.now)
59
+ discovered_by: str = "" # Agent name
60
+ verified: bool = False
61
+ fixed: bool = False
62
+
63
+ @property
64
+ def location(self) -> str:
65
+ """Get formatted location string."""
66
+ if self.line_number:
67
+ return f"{self.file_path}:{self.line_number}"
68
+ return self.file_path
69
+
70
+
71
+ # Severity styling
72
+ SEVERITY_STYLES = {
73
+ IssueSeverity.CRITICAL: {"color": "#ef4444", "icon": "🔴", "label": "CRITICAL"},
74
+ IssueSeverity.HIGH: {"color": "#f97316", "icon": "🟠", "label": "HIGH"},
75
+ IssueSeverity.MEDIUM: {"color": "#eab308", "icon": "🟡", "label": "MEDIUM"},
76
+ IssueSeverity.LOW: {"color": "#22c55e", "icon": "🟢", "label": "LOW"},
77
+ IssueSeverity.INFO: {"color": "#3b82f6", "icon": "🔵", "label": "INFO"},
78
+ }
79
+
80
+ CATEGORY_ICONS = {
81
+ IssueCategory.BUG: "🐛",
82
+ IssueCategory.SECURITY: "🔒",
83
+ IssueCategory.PERFORMANCE: "⚡",
84
+ IssueCategory.COVERAGE: "📊",
85
+ IssueCategory.STYLE: "🎨",
86
+ IssueCategory.COMPLEXITY: "🔄",
87
+ IssueCategory.DEPENDENCY: "📦",
88
+ IssueCategory.TEST: "🧪",
89
+ IssueCategory.OTHER: "📝",
90
+ }
91
+
92
+
93
+ class IssueTimeline(Static):
94
+ """Issue Discovery Timeline Widget.
95
+
96
+ Displays issues discovered during QE sessions in chronological order
97
+ with severity indicators and summary statistics.
98
+
99
+ Usage:
100
+ timeline = IssueTimeline()
101
+ timeline.add_issue(DiscoveredIssue(
102
+ id="issue-1",
103
+ severity=IssueSeverity.HIGH,
104
+ category=IssueCategory.BUG,
105
+ title="NullRef in UserService.get()",
106
+ file_path="src/api/user.py",
107
+ line_number=45,
108
+ ))
109
+ """
110
+
111
+ DEFAULT_CSS = """
112
+ IssueTimeline {
113
+ height: auto;
114
+ border: solid #3f3f46;
115
+ padding: 0 1;
116
+ margin: 0 0 1 0;
117
+ max-height: 20;
118
+ overflow-y: auto;
119
+ }
120
+ """
121
+
122
+ # Reactive state
123
+ show_verified_only: reactive[bool] = reactive(False)
124
+ show_category: reactive[Optional[IssueCategory]] = reactive(None)
125
+
126
+ def __init__(
127
+ self,
128
+ title: str = "Discovery Timeline",
129
+ max_visible: int = 10,
130
+ compact: bool = False,
131
+ **kwargs,
132
+ ):
133
+ super().__init__(**kwargs)
134
+ self.title = title
135
+ self.max_visible = max_visible
136
+ self.compact = compact
137
+ self._issues: List[DiscoveredIssue] = []
138
+
139
+ @property
140
+ def issues(self) -> List[DiscoveredIssue]:
141
+ """Get all issues."""
142
+ return self._issues.copy()
143
+
144
+ @property
145
+ def filtered_issues(self) -> List[DiscoveredIssue]:
146
+ """Get filtered issues based on current settings."""
147
+ result = self._issues
148
+
149
+ if self.show_verified_only:
150
+ result = [i for i in result if i.verified]
151
+
152
+ if self.show_category:
153
+ result = [i for i in result if i.category == self.show_category]
154
+
155
+ return result
156
+
157
+ def add_issue(self, issue: DiscoveredIssue) -> None:
158
+ """Add an issue to the timeline."""
159
+ self._issues.append(issue)
160
+ # Sort by time (newest first)
161
+ self._issues.sort(key=lambda i: i.discovered_at, reverse=True)
162
+ self.refresh()
163
+
164
+ def mark_verified(self, issue_id: str) -> None:
165
+ """Mark an issue as verified."""
166
+ for issue in self._issues:
167
+ if issue.id == issue_id:
168
+ issue.verified = True
169
+ break
170
+ self.refresh()
171
+
172
+ def mark_fixed(self, issue_id: str) -> None:
173
+ """Mark an issue as fixed."""
174
+ for issue in self._issues:
175
+ if issue.id == issue_id:
176
+ issue.fixed = True
177
+ break
178
+ self.refresh()
179
+
180
+ def remove_issue(self, issue_id: str) -> None:
181
+ """Remove an issue from the timeline."""
182
+ self._issues = [i for i in self._issues if i.id != issue_id]
183
+ self.refresh()
184
+
185
+ def clear(self) -> None:
186
+ """Clear all issues."""
187
+ self._issues.clear()
188
+ self.refresh()
189
+
190
+ def get_summary(self) -> dict:
191
+ """Get summary statistics."""
192
+ counts = {sev: 0 for sev in IssueSeverity}
193
+ for issue in self._issues:
194
+ counts[issue.severity] += 1
195
+
196
+ return {
197
+ "total": len(self._issues),
198
+ "verified": sum(1 for i in self._issues if i.verified),
199
+ "fixed": sum(1 for i in self._issues if i.fixed),
200
+ "by_severity": counts,
201
+ }
202
+
203
+ def _render_issue(self, issue: DiscoveredIssue) -> Text:
204
+ """Render a single issue entry."""
205
+ style = SEVERITY_STYLES[issue.severity]
206
+ cat_icon = CATEGORY_ICONS.get(issue.category, "📝")
207
+
208
+ result = Text()
209
+
210
+ # Timestamp
211
+ time_str = issue.discovered_at.strftime("%H:%M:%S")
212
+ result.append(f" {time_str} ", style="#6b7280")
213
+
214
+ # Severity indicator
215
+ result.append(f"{style['icon']} ", style=style["color"])
216
+ result.append(f"{style['label']:<8}", style=f"bold {style['color']}")
217
+
218
+ # Title (truncate if too long)
219
+ title = issue.title
220
+ if len(title) > 40 and self.compact:
221
+ title = title[:37] + "..."
222
+ result.append(f"{title}", style="#e2e8f0")
223
+
224
+ # Status badges
225
+ if issue.verified:
226
+ result.append(" ✓", style="bold #22c55e")
227
+ if issue.fixed:
228
+ result.append(" ✗", style="bold #3b82f6")
229
+
230
+ return result
231
+
232
+ def _render_summary_bar(self) -> Text:
233
+ """Render the summary statistics bar."""
234
+ summary = self.get_summary()
235
+ counts = summary["by_severity"]
236
+
237
+ result = Text()
238
+ result.append(" Total: ", style="#6b7280")
239
+ result.append(f"{summary['total']}", style="bold #e2e8f0")
240
+ result.append(" issues", style="#6b7280")
241
+
242
+ # Severity breakdown
243
+ parts = []
244
+ for sev in [
245
+ IssueSeverity.CRITICAL,
246
+ IssueSeverity.HIGH,
247
+ IssueSeverity.MEDIUM,
248
+ IssueSeverity.LOW,
249
+ ]:
250
+ if counts[sev] > 0:
251
+ style = SEVERITY_STYLES[sev]
252
+ parts.append(f"[{style['color']}]{counts[sev]} {style['label'].title()}[/]")
253
+
254
+ if parts:
255
+ result.append(" | ", style="#3f3f46")
256
+ result.append_markup(" | ".join(parts))
257
+
258
+ # Verified count
259
+ if summary["verified"] > 0:
260
+ result.append(" | ", style="#3f3f46")
261
+ result.append(f"{summary['verified']} verified", style="#22c55e")
262
+
263
+ return result
264
+
265
+ def render(self) -> RenderableType:
266
+ """Render the timeline."""
267
+ content = Text()
268
+
269
+ filtered = self.filtered_issues
270
+
271
+ if not filtered:
272
+ content.append("\n No issues discovered yet\n", style="#6b7280")
273
+ else:
274
+ # Show issues (limited by max_visible)
275
+ visible = filtered[: self.max_visible]
276
+
277
+ for issue in visible:
278
+ content.append(self._render_issue(issue))
279
+ content.append("\n")
280
+
281
+ # Show "more" indicator if truncated
282
+ remaining = len(filtered) - len(visible)
283
+ if remaining > 0:
284
+ content.append(f"\n ... and {remaining} more issues", style="#6b7280")
285
+ content.append("\n")
286
+
287
+ # Divider
288
+ content.append("\n")
289
+ content.append(" " + "═" * 50, style="#3f3f46")
290
+ content.append("\n")
291
+
292
+ # Summary bar
293
+ content.append(self._render_summary_bar())
294
+ content.append("\n")
295
+
296
+ return Panel(
297
+ content,
298
+ title=f"[bold #f59e0b]{self.title}[/]",
299
+ border_style="#3f3f46",
300
+ padding=(0, 0),
301
+ )
302
+
303
+
304
+ class CompactIssueTimeline(IssueTimeline):
305
+ """Compact version of IssueTimeline for smaller spaces."""
306
+
307
+ DEFAULT_CSS = """
308
+ CompactIssueTimeline {
309
+ height: auto;
310
+ max-height: 8;
311
+ border: solid #3f3f46;
312
+ padding: 0 1;
313
+ }
314
+ """
315
+
316
+ def __init__(self, **kwargs):
317
+ kwargs.setdefault("max_visible", 5)
318
+ kwargs.setdefault("compact", True)
319
+ super().__init__(**kwargs)
320
+
321
+ def _render_issue(self, issue: DiscoveredIssue) -> Text:
322
+ """Render a single issue in compact format."""
323
+ style = SEVERITY_STYLES[issue.severity]
324
+
325
+ result = Text()
326
+
327
+ # Time (short format)
328
+ time_str = issue.discovered_at.strftime("%H:%M")
329
+ result.append(f"{time_str} ", style="#6b7280")
330
+
331
+ # Severity dot
332
+ result.append(f"{style['icon']} ", style=style["color"])
333
+
334
+ # Title (shorter)
335
+ title = issue.title
336
+ if len(title) > 35:
337
+ title = title[:32] + "..."
338
+ result.append(title, style="#e2e8f0")
339
+
340
+ return result
@@ -0,0 +1,264 @@
1
+ """
2
+ SuperQode Leader Key Widget - Ctrl+X prefix shortcuts.
3
+
4
+ Implements leader key shortcuts where pressing Ctrl+X
5
+ shows available actions and waits for a second key.
6
+
7
+ Usage:
8
+ Ctrl+X → shows leader key popup
9
+ then press:
10
+ H - Help
11
+ E - Edit (open editor)
12
+ C - Copy response
13
+ S - Select text
14
+ T - Theme picker
15
+ D - Diagnostics
16
+ Q - Quit
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from typing import Callable, Dict, Optional, TYPE_CHECKING
22
+
23
+ from rich.text import Text
24
+
25
+ from textual.widgets import Static
26
+ from textual.containers import Container
27
+ from textual.reactive import reactive
28
+ from textual.message import Message
29
+
30
+ if TYPE_CHECKING:
31
+ from textual.app import App
32
+
33
+
34
+ # ============================================================================
35
+ # DESIGN
36
+ # ============================================================================
37
+
38
+ try:
39
+ from superqode.design_system import COLORS as SQ_COLORS
40
+ except ImportError:
41
+
42
+ class SQ_COLORS:
43
+ primary = "#7c3aed"
44
+ primary_light = "#a855f7"
45
+ text_primary = "#fafafa"
46
+ text_secondary = "#e4e4e7"
47
+ text_muted = "#a1a1aa"
48
+ text_dim = "#71717a"
49
+ bg_elevated = "#0a0a0a"
50
+ border_default = "#27272a"
51
+
52
+
53
+ # ============================================================================
54
+ # LEADER KEY DEFINITIONS
55
+ # ============================================================================
56
+
57
+ LEADER_KEYS = {
58
+ "h": {
59
+ "label": "Help",
60
+ "description": "Show help",
61
+ "action": "show_help",
62
+ },
63
+ "e": {
64
+ "label": "Edit",
65
+ "description": "Open external editor",
66
+ "action": "open_editor",
67
+ },
68
+ "c": {
69
+ "label": "Copy",
70
+ "description": "Copy last response",
71
+ "action": "copy_response",
72
+ },
73
+ "s": {
74
+ "label": "Select",
75
+ "description": "Open selectable view",
76
+ "action": "show_select",
77
+ },
78
+ "t": {
79
+ "label": "Theme",
80
+ "description": "Change theme",
81
+ "action": "show_theme",
82
+ },
83
+ "d": {
84
+ "label": "Diagnostics",
85
+ "description": "Show diagnostics",
86
+ "action": "show_diagnostics",
87
+ },
88
+ "b": {
89
+ "label": "Sidebar",
90
+ "description": "Toggle sidebar",
91
+ "action": "toggle_sidebar",
92
+ },
93
+ "q": {
94
+ "label": "Quit",
95
+ "description": "Exit application",
96
+ "action": "quit_app",
97
+ },
98
+ }
99
+
100
+
101
+ # ============================================================================
102
+ # LEADER KEY WIDGET
103
+ # ============================================================================
104
+
105
+
106
+ class LeaderKeyPopup(Static):
107
+ """
108
+ Popup showing available leader key commands.
109
+
110
+ Appears when user presses Ctrl+X and waits for second key.
111
+ """
112
+
113
+ DEFAULT_CSS = """
114
+ LeaderKeyPopup {
115
+ layer: overlay;
116
+ width: auto;
117
+ height: auto;
118
+ background: #0a0a0a;
119
+ border: round #7c3aed;
120
+ padding: 1 2;
121
+ display: none;
122
+ }
123
+
124
+ LeaderKeyPopup.visible {
125
+ display: block;
126
+ }
127
+ """
128
+
129
+ class KeyPressed(Message):
130
+ """Posted when a leader key is pressed."""
131
+
132
+ def __init__(self, key: str, action: str) -> None:
133
+ self.key = key
134
+ self.action = action
135
+ super().__init__()
136
+
137
+ class Cancelled(Message):
138
+ """Posted when leader mode is cancelled."""
139
+
140
+ pass
141
+
142
+ visible: reactive[bool] = reactive(False)
143
+
144
+ def __init__(self, **kwargs):
145
+ super().__init__("", **kwargs)
146
+
147
+ def watch_visible(self, visible: bool) -> None:
148
+ """Toggle visibility."""
149
+ if visible:
150
+ self.add_class("visible")
151
+ else:
152
+ self.remove_class("visible")
153
+
154
+ def show(self) -> None:
155
+ """Show the leader key popup."""
156
+ self.visible = True
157
+ self.focus()
158
+
159
+ def hide(self) -> None:
160
+ """Hide the popup."""
161
+ self.visible = False
162
+
163
+ def render(self) -> Text:
164
+ """Render the leader key options."""
165
+ t = Text()
166
+ t.append("◈ Leader: Ctrl+X + ...\n", style=f"bold {SQ_COLORS.primary}")
167
+ t.append("\n", style="")
168
+
169
+ for key, info in LEADER_KEYS.items():
170
+ t.append(f" [{key.upper()}]", style=f"bold {SQ_COLORS.primary_light}")
171
+ t.append(f" {info['label']:<12}", style=SQ_COLORS.text_secondary)
172
+ t.append(f" {info['description']}\n", style=SQ_COLORS.text_dim)
173
+
174
+ t.append("\n", style="")
175
+ t.append(" [Esc] Cancel", style=SQ_COLORS.text_muted)
176
+
177
+ return t
178
+
179
+ def on_key(self, event) -> None:
180
+ """Handle key press in leader mode."""
181
+ key = event.key.lower()
182
+
183
+ if key == "escape":
184
+ self.hide()
185
+ self.post_message(self.Cancelled())
186
+ event.stop()
187
+ return
188
+
189
+ if key in LEADER_KEYS:
190
+ action = LEADER_KEYS[key]["action"]
191
+ self.hide()
192
+ self.post_message(self.KeyPressed(key, action))
193
+ event.stop()
194
+
195
+
196
+ # ============================================================================
197
+ # LEADER KEY MIXIN
198
+ # ============================================================================
199
+
200
+
201
+ class LeaderKeyMixin:
202
+ """
203
+ Mixin to add leader key support to an App.
204
+
205
+ Usage:
206
+ class MyApp(App, LeaderKeyMixin):
207
+ def __init__(self):
208
+ super().__init__()
209
+ self._init_leader_key()
210
+ """
211
+
212
+ _leader_mode: bool = False
213
+ _leader_popup: Optional[LeaderKeyPopup] = None
214
+
215
+ def _init_leader_key(self) -> None:
216
+ """Initialize leader key support."""
217
+ self._leader_mode = False
218
+
219
+ def action_leader_key(self) -> None:
220
+ """Activate leader key mode (Ctrl+X)."""
221
+ if hasattr(self, "_leader_popup") and self._leader_popup:
222
+ self._leader_popup.show()
223
+ self._leader_mode = True
224
+
225
+ def _handle_leader_action(self, action: str) -> None:
226
+ """Handle a leader key action."""
227
+ self._leader_mode = False
228
+
229
+ # Map actions to app methods
230
+ action_map = {
231
+ "show_help": "action_show_help",
232
+ "open_editor": "action_open_editor",
233
+ "copy_response": "action_copy_response",
234
+ "show_select": "_show_select",
235
+ "show_theme": "_show_theme",
236
+ "show_diagnostics": "_show_diagnostics",
237
+ "toggle_sidebar": "action_toggle_sidebar",
238
+ "quit_app": "action_quit",
239
+ }
240
+
241
+ method_name = action_map.get(action)
242
+ if method_name and hasattr(self, method_name):
243
+ method = getattr(self, method_name)
244
+ if callable(method):
245
+ method()
246
+
247
+ def on_leader_key_popup_key_pressed(self, event: LeaderKeyPopup.KeyPressed) -> None:
248
+ """Handle leader key selection."""
249
+ self._handle_leader_action(event.action)
250
+
251
+ def on_leader_key_popup_cancelled(self, event: LeaderKeyPopup.Cancelled) -> None:
252
+ """Handle leader mode cancelled."""
253
+ self._leader_mode = False
254
+
255
+
256
+ # ============================================================================
257
+ # EXPORTS
258
+ # ============================================================================
259
+
260
+ __all__ = [
261
+ "LEADER_KEYS",
262
+ "LeaderKeyPopup",
263
+ "LeaderKeyMixin",
264
+ ]