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,412 @@
1
+ """Command palette widget (Ctrl+K) for quick command access - Redesigned for accessibility."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Callable
7
+
8
+ from textual import on
9
+ from textual.app import ComposeResult
10
+ from textual.containers import Vertical, VerticalScroll, Horizontal
11
+ from textual.message import Message
12
+ from textual.reactive import reactive
13
+ from textual.widget import Widget
14
+ from textual.widgets import Input, Static
15
+
16
+ from superqode.utils.fuzzy import FuzzySearch
17
+
18
+
19
+ @dataclass
20
+ class PaletteCommand:
21
+ """A command palette item."""
22
+
23
+ id: str
24
+ label: str
25
+ description: str
26
+ icon: str = ""
27
+ shortcut: str = ""
28
+ category: str = "general"
29
+ action: Callable | None = None
30
+
31
+
32
+ # Default palette commands
33
+ DEFAULT_PALETTE_COMMANDS: list[PaletteCommand] = [
34
+ # Agent commands
35
+ PaletteCommand(
36
+ "connect_agent",
37
+ "Connect to Agent",
38
+ "Connect to an AI coding agent",
39
+ "🤖",
40
+ "Ctrl+A",
41
+ "agents",
42
+ ),
43
+ PaletteCommand(
44
+ "agent_store", "Agent Store", "Browse available agents", "🛍️", "Ctrl+S", "agents"
45
+ ),
46
+ PaletteCommand(
47
+ "disconnect", "Disconnect", "Disconnect from current agent", "🔌", "Ctrl+D", "agents"
48
+ ),
49
+ # Role commands
50
+ PaletteCommand(
51
+ "dev_mode", "Development Mode", "Switch to dev.fullstack role", "💻", "", "roles"
52
+ ),
53
+ # File commands
54
+ PaletteCommand("open_file", "Open File", "Open a file from project", "📁", "Ctrl+O", "files"),
55
+ PaletteCommand("find_file", "Find File", "Fuzzy search for files", "🔍", "Ctrl+F", "files"),
56
+ PaletteCommand("recent_files", "Recent Files", "Show recently opened files", "📋", "", "files"),
57
+ PaletteCommand("bookmarks", "Bookmarks", "Manage file bookmarks", "🔖", "", "files"),
58
+ # Workflow commands
59
+ PaletteCommand(
60
+ "handoff", "Handoff Work", "Hand off work to another role", "🤝", "Ctrl+H", "workflow"
61
+ ),
62
+ PaletteCommand(
63
+ "context", "View Context", "Show current work context", "📋", "Ctrl+I", "workflow"
64
+ ),
65
+ PaletteCommand("approve", "Approve Work", "Approve work for deployment", "✅", "", "workflow"),
66
+ PaletteCommand("sessions", "View Sessions", "Show pending handoffs", "📂", "", "workflow"),
67
+ # System commands
68
+ PaletteCommand("settings", "Settings", "Open settings", "⚙️", "Ctrl+,", "system"),
69
+ PaletteCommand("help", "Help", "Show help documentation", "❓", "?", "system"),
70
+ PaletteCommand("exit", "Exit", "Exit SuperQode", "🚪", "Ctrl+C", "system"),
71
+ ]
72
+
73
+
74
+ class PaletteItem(Widget):
75
+ """A single command palette item - high contrast design."""
76
+
77
+ DEFAULT_CSS = """
78
+ PaletteItem {
79
+ height: 3;
80
+ padding: 0 1;
81
+ layout: horizontal;
82
+ background: #0a0a0a;
83
+ border-bottom: solid #222222;
84
+ }
85
+
86
+ PaletteItem:hover {
87
+ background: #1a3a5a;
88
+ }
89
+
90
+ PaletteItem.selected {
91
+ background: #00aaff;
92
+ }
93
+
94
+ PaletteItem .icon {
95
+ width: 4;
96
+ height: 3;
97
+ content-align: center middle;
98
+ color: #ffffff;
99
+ }
100
+
101
+ PaletteItem .content {
102
+ height: 3;
103
+ padding-left: 1;
104
+ }
105
+
106
+ PaletteItem .label {
107
+ text-style: bold;
108
+ color: #ffffff;
109
+ }
110
+
111
+ PaletteItem.selected .label {
112
+ color: #000000;
113
+ }
114
+
115
+ PaletteItem .description {
116
+ color: #aaaaaa;
117
+ }
118
+
119
+ PaletteItem.selected .description {
120
+ color: #000000;
121
+ }
122
+
123
+ PaletteItem .shortcut {
124
+ dock: right;
125
+ color: #00ff00;
126
+ text-style: bold;
127
+ width: auto;
128
+ padding-right: 1;
129
+ content-align: center middle;
130
+ }
131
+
132
+ PaletteItem.selected .shortcut {
133
+ color: #004400;
134
+ }
135
+ """
136
+
137
+ class Selected(Message):
138
+ """Message sent when item is selected."""
139
+
140
+ def __init__(self, command: PaletteCommand) -> None:
141
+ self.command = command
142
+ super().__init__()
143
+
144
+ selected: reactive[bool] = reactive(False)
145
+
146
+ def __init__(self, command: PaletteCommand, **kwargs) -> None:
147
+ super().__init__(**kwargs)
148
+ self.command = command
149
+
150
+ def compose(self) -> ComposeResult:
151
+ yield Static(self.command.icon, classes="icon")
152
+ with Vertical(classes="content"):
153
+ yield Static(self.command.label, classes="label")
154
+ yield Static(self.command.description, classes="description")
155
+ if self.command.shortcut:
156
+ yield Static(self.command.shortcut, classes="shortcut")
157
+
158
+ def watch_selected(self, selected: bool) -> None:
159
+ self.set_class(selected, "selected")
160
+
161
+ def on_click(self) -> None:
162
+ self.post_message(self.Selected(self.command))
163
+
164
+
165
+ class CommandPalette(Widget):
166
+ """
167
+ Command palette overlay (Ctrl+K) - High contrast, accessible design.
168
+
169
+ Provides fuzzy-searchable access to all commands.
170
+ """
171
+
172
+ DEFAULT_CSS = """
173
+ CommandPalette {
174
+ layer: overlay;
175
+ align: center top;
176
+ margin-top: 3;
177
+ width: 70;
178
+ height: auto;
179
+ max-height: 25;
180
+ background: #000000;
181
+ border: double #00ffff;
182
+ display: none;
183
+ }
184
+
185
+ CommandPalette.show-palette {
186
+ display: block;
187
+ }
188
+
189
+ CommandPalette #palette-title-bar {
190
+ height: 3;
191
+ background: #001a33;
192
+ padding: 1;
193
+ }
194
+
195
+ CommandPalette #palette-title {
196
+ text-style: bold;
197
+ color: #00ffff;
198
+ text-align: center;
199
+ }
200
+
201
+ CommandPalette #palette-subtitle {
202
+ color: #888888;
203
+ text-align: center;
204
+ }
205
+
206
+ CommandPalette #palette-search-container {
207
+ height: 3;
208
+ padding: 0 1;
209
+ background: #0a0a0a;
210
+ border-bottom: solid #333333;
211
+ }
212
+
213
+ CommandPalette #palette-search {
214
+ width: 100%;
215
+ background: #1a1a1a;
216
+ border: tall #00ffff;
217
+ color: #ffffff;
218
+ }
219
+
220
+ CommandPalette #palette-search:focus {
221
+ border: tall #00ff00;
222
+ background: #0a1a0a;
223
+ }
224
+
225
+ CommandPalette #palette-results {
226
+ height: auto;
227
+ max-height: 16;
228
+ background: #0a0a0a;
229
+ }
230
+
231
+ CommandPalette .no-results {
232
+ padding: 2;
233
+ color: #ffff00;
234
+ text-style: bold;
235
+ text-align: center;
236
+ background: #1a1a00;
237
+ }
238
+
239
+ CommandPalette #palette-footer {
240
+ height: 2;
241
+ background: #1a1a1a;
242
+ color: #00ff00;
243
+ padding: 0 1;
244
+ border-top: solid #333333;
245
+ }
246
+
247
+ CommandPalette #footer-hints {
248
+ text-align: center;
249
+ color: #00ff00;
250
+ }
251
+ """
252
+
253
+ class CommandSelected(Message):
254
+ """Message sent when a command is selected."""
255
+
256
+ def __init__(self, command: PaletteCommand) -> None:
257
+ self.command = command
258
+ super().__init__()
259
+
260
+ class Dismissed(Message):
261
+ """Message sent when palette is dismissed."""
262
+
263
+ pass
264
+
265
+ # State
266
+ is_visible: reactive[bool] = reactive(False)
267
+ search_text: reactive[str] = reactive("")
268
+ selected_index: reactive[int] = reactive(0)
269
+
270
+ def __init__(
271
+ self,
272
+ commands: list[PaletteCommand] | None = None,
273
+ **kwargs,
274
+ ) -> None:
275
+ super().__init__(**kwargs)
276
+ self.commands = commands or DEFAULT_PALETTE_COMMANDS
277
+ self.filtered_commands: list[PaletteCommand] = []
278
+ self.fuzzy = FuzzySearch()
279
+
280
+ def compose(self) -> ComposeResult:
281
+ with Vertical(id="palette-title-bar"):
282
+ yield Static("🔍 COMMAND PALETTE", id="palette-title")
283
+ yield Static("Type to search commands", id="palette-subtitle")
284
+ with Vertical(id="palette-search-container"):
285
+ yield Input(placeholder="Search commands...", id="palette-search")
286
+ yield VerticalScroll(id="palette-results")
287
+ with Vertical(id="palette-footer"):
288
+ yield Static("↑↓ Navigate │ Enter Select │ Esc Close", id="footer-hints")
289
+
290
+ def on_mount(self) -> None:
291
+ """Initialize on mount."""
292
+ self._update_filtered_commands()
293
+
294
+ def show(self) -> None:
295
+ """Show command palette."""
296
+ self.search_text = ""
297
+ self.selected_index = 0
298
+ self.is_visible = True
299
+ self.add_class("show-palette")
300
+ self._update_filtered_commands()
301
+
302
+ # Focus search input
303
+ search_input = self.query_one("#palette-search", Input)
304
+ search_input.value = ""
305
+ search_input.focus()
306
+
307
+ def hide(self) -> None:
308
+ """Hide command palette."""
309
+ self.is_visible = False
310
+ self.remove_class("show-palette")
311
+ self.post_message(self.Dismissed())
312
+
313
+ def toggle(self) -> None:
314
+ """Toggle palette visibility."""
315
+ if self.is_visible:
316
+ self.hide()
317
+ else:
318
+ self.show()
319
+
320
+ @on(Input.Changed, "#palette-search")
321
+ def on_search_changed(self, event: Input.Changed) -> None:
322
+ """Handle search input changes."""
323
+ self.search_text = event.value
324
+ self.selected_index = 0
325
+ self._update_filtered_commands()
326
+
327
+ @on(Input.Submitted, "#palette-search")
328
+ def on_search_submitted(self, event: Input.Submitted) -> None:
329
+ """Handle search submission."""
330
+ self.select_current()
331
+
332
+ def _update_filtered_commands(self) -> None:
333
+ """Update filtered commands based on search text."""
334
+ if self.search_text:
335
+ # Build searchable items (search both label and description)
336
+ items = [(f"{cmd.label} {cmd.description}", cmd) for cmd in self.commands]
337
+ results = self.fuzzy.search_with_data(self.search_text, items, max_results=10)
338
+ self.filtered_commands = [cmd for _, cmd in results]
339
+ else:
340
+ self.filtered_commands = self.commands[:10]
341
+
342
+ self._render_commands()
343
+
344
+ def _render_commands(self) -> None:
345
+ """Render filtered commands."""
346
+ container = self.query_one("#palette-results", VerticalScroll)
347
+ container.remove_children()
348
+
349
+ if not self.filtered_commands:
350
+ container.mount(Static("No matching commands found", classes="no-results"))
351
+ return
352
+
353
+ for i, cmd in enumerate(self.filtered_commands):
354
+ item = PaletteItem(cmd, id=f"palette-item-{i}")
355
+ item.selected = i == self.selected_index
356
+ container.mount(item)
357
+
358
+ def _update_selection(self) -> None:
359
+ """Update visual selection state."""
360
+ for i, item in enumerate(self.query("#palette-results PaletteItem")):
361
+ if isinstance(item, PaletteItem):
362
+ item.selected = i == self.selected_index
363
+
364
+ def move_selection(self, delta: int) -> None:
365
+ """Move selection up or down."""
366
+ if not self.filtered_commands:
367
+ return
368
+ new_index = (self.selected_index + delta) % len(self.filtered_commands)
369
+ self.selected_index = new_index
370
+ self._update_selection()
371
+
372
+ # Scroll to make selection visible
373
+ try:
374
+ container = self.query_one("#palette-results", VerticalScroll)
375
+ selected_item = self.query_one(f"#palette-item-{self.selected_index}")
376
+ if selected_item:
377
+ container.scroll_visible(selected_item)
378
+ except Exception:
379
+ pass
380
+
381
+ def select_current(self) -> PaletteCommand | None:
382
+ """Select current command."""
383
+ if self.filtered_commands and 0 <= self.selected_index < len(self.filtered_commands):
384
+ cmd = self.filtered_commands[self.selected_index]
385
+ self.post_message(self.CommandSelected(cmd))
386
+ self.hide()
387
+ return cmd
388
+ return None
389
+
390
+ def on_key(self, event) -> None:
391
+ """Handle key events."""
392
+ if not self.is_visible:
393
+ return
394
+
395
+ if event.key == "escape":
396
+ self.hide()
397
+ event.stop()
398
+ elif event.key == "up":
399
+ self.move_selection(-1)
400
+ event.stop()
401
+ elif event.key == "down":
402
+ self.move_selection(1)
403
+ event.stop()
404
+ elif event.key == "enter":
405
+ self.select_current()
406
+ event.stop()
407
+
408
+ @on(PaletteItem.Selected)
409
+ def on_item_selected(self, event: PaletteItem.Selected) -> None:
410
+ """Handle item selection via click."""
411
+ self.post_message(self.CommandSelected(event.command))
412
+ self.hide()