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,356 @@
1
+ """Slash command completion overlay widget - 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 Static
15
+
16
+ from superqode.utils.fuzzy import FuzzySearch
17
+
18
+
19
+ @dataclass
20
+ class SlashCommand:
21
+ """A slash command definition."""
22
+
23
+ command: str # e.g., "/handoff"
24
+ description: str # e.g., "Hand off work to another role"
25
+ shortcut: str = "" # e.g., "Ctrl+H"
26
+ category: str = "general" # For grouping
27
+ action: Callable | None = None # Optional action callback
28
+
29
+
30
+ # Default slash commands
31
+ DEFAULT_COMMANDS: list[SlashCommand] = [
32
+ # Role commands
33
+ SlashCommand("/dev", "Switch to development mode", category="roles"),
34
+ SlashCommand("/dev fullstack", "Start full-stack development", category="roles"),
35
+ # Agent commands
36
+ SlashCommand("/agents", "List available agents", "Ctrl+A", category="agents"),
37
+ SlashCommand("/store", "Open agent marketplace", "Ctrl+S", category="agents"),
38
+ SlashCommand("/agents store", "Browse agent marketplace", category="agents"),
39
+ SlashCommand("/agents connect", "Connect to an agent", category="agents"),
40
+ SlashCommand("/agents install", "Install an agent", category="agents"),
41
+ # File commands
42
+ SlashCommand("/files", "Show project files", "Ctrl+O", category="files"),
43
+ SlashCommand("/find", "Fuzzy search files", "Ctrl+F", category="files"),
44
+ SlashCommand("/recent", "Show recent files", category="files"),
45
+ SlashCommand("/open", "Open a file or bookmark", category="files"),
46
+ SlashCommand("/bookmark", "Manage bookmarks", category="files"),
47
+ # Workflow commands
48
+ SlashCommand("/handoff", "Hand off work to another role", "Ctrl+H", category="workflow"),
49
+ SlashCommand("/context", "View/update work context", "Ctrl+I", category="workflow"),
50
+ SlashCommand("/approve", "Approve work for deployment", category="workflow"),
51
+ # System commands
52
+ SlashCommand("/settings", "Open settings", "Ctrl+,", category="system"),
53
+ SlashCommand("/help", "Show help", "?", category="system"),
54
+ SlashCommand("/disconnect", "Disconnect from agent", "Ctrl+D", category="system"),
55
+ SlashCommand("/exit", "Exit SuperQode", "Ctrl+C", category="system"),
56
+ ]
57
+
58
+
59
+ class SlashCompleteItem(Widget):
60
+ """A single slash command completion item - high contrast design."""
61
+
62
+ DEFAULT_CSS = """
63
+ SlashCompleteItem {
64
+ height: 2;
65
+ padding: 0 1;
66
+ layout: horizontal;
67
+ background: #0a0a0a;
68
+ border-bottom: solid #1a1a1a;
69
+ }
70
+
71
+ SlashCompleteItem:hover {
72
+ background: #1a3a5a;
73
+ }
74
+
75
+ SlashCompleteItem.selected {
76
+ background: #00ffff;
77
+ }
78
+
79
+ SlashCompleteItem .command {
80
+ color: #ffff00;
81
+ text-style: bold;
82
+ min-width: 24;
83
+ width: 24;
84
+ }
85
+
86
+ SlashCompleteItem.selected .command {
87
+ color: #000000;
88
+ text-style: bold;
89
+ }
90
+
91
+ SlashCompleteItem .description {
92
+ color: #ffffff;
93
+ }
94
+
95
+ SlashCompleteItem.selected .description {
96
+ color: #000000;
97
+ text-style: bold;
98
+ }
99
+
100
+ SlashCompleteItem .shortcut {
101
+ dock: right;
102
+ color: #00ff00;
103
+ text-style: bold;
104
+ min-width: 10;
105
+ }
106
+
107
+ SlashCompleteItem.selected .shortcut {
108
+ color: #004400;
109
+ }
110
+ """
111
+
112
+ class Click(Message):
113
+ """Message sent when item is clicked."""
114
+
115
+ def __init__(self, widget: "SlashCompleteItem") -> None:
116
+ self.widget = widget
117
+ super().__init__()
118
+
119
+ selected: reactive[bool] = reactive(False)
120
+
121
+ def __init__(self, command: SlashCommand, **kwargs) -> None:
122
+ super().__init__(**kwargs)
123
+ self.command = command
124
+
125
+ def compose(self) -> ComposeResult:
126
+ yield Static(self.command.command, classes="command")
127
+ yield Static(self.command.description, classes="description")
128
+ if self.command.shortcut:
129
+ yield Static(self.command.shortcut, classes="shortcut")
130
+
131
+ def watch_selected(self, selected: bool) -> None:
132
+ self.set_class(selected, "selected")
133
+
134
+ def on_click(self) -> None:
135
+ self.post_message(self.Click(self))
136
+
137
+
138
+ class SlashComplete(Widget):
139
+ """
140
+ Slash command completion overlay - High contrast, accessible design.
141
+
142
+ Shows when user types "/" and provides fuzzy-filtered command suggestions.
143
+ """
144
+
145
+ DEFAULT_CSS = """
146
+ SlashComplete {
147
+ layer: overlay;
148
+ dock: bottom;
149
+ height: auto;
150
+ max-height: 16;
151
+ margin: 0 2 4 2;
152
+ background: #000000;
153
+ border: double #00ffff;
154
+ display: none;
155
+ }
156
+
157
+ SlashComplete.visible {
158
+ display: block;
159
+ }
160
+
161
+ SlashComplete #slash-header {
162
+ height: 2;
163
+ background: #001a33;
164
+ color: #00ffff;
165
+ padding: 0 1;
166
+ text-style: bold;
167
+ }
168
+
169
+ SlashComplete #slash-title {
170
+ color: #00ffff;
171
+ text-style: bold;
172
+ }
173
+
174
+ SlashComplete #slash-hint {
175
+ color: #888888;
176
+ }
177
+
178
+ SlashComplete #slash-list {
179
+ height: auto;
180
+ max-height: 12;
181
+ background: #0a0a0a;
182
+ }
183
+
184
+ SlashComplete .no-results {
185
+ padding: 1;
186
+ color: #ffff00;
187
+ text-style: bold;
188
+ text-align: center;
189
+ background: #1a1a00;
190
+ }
191
+
192
+ SlashComplete #slash-footer {
193
+ height: 1;
194
+ background: #1a1a1a;
195
+ color: #00ff00;
196
+ padding: 0 1;
197
+ text-align: center;
198
+ border-top: solid #333333;
199
+ }
200
+ """
201
+
202
+ class CommandSelected(Message):
203
+ """Message sent when a command is selected."""
204
+
205
+ def __init__(self, command: SlashCommand) -> None:
206
+ self.command = command
207
+ super().__init__()
208
+
209
+ class Dismissed(Message):
210
+ """Message sent when the overlay is dismissed."""
211
+
212
+ pass
213
+
214
+ # State
215
+ is_visible: reactive[bool] = reactive(False)
216
+ search_query: reactive[str] = reactive("")
217
+ selected_index: reactive[int] = reactive(0)
218
+
219
+ def __init__(
220
+ self,
221
+ commands: list[SlashCommand] | None = None,
222
+ **kwargs,
223
+ ) -> None:
224
+ super().__init__(**kwargs)
225
+ self.commands = commands or DEFAULT_COMMANDS
226
+ self.filtered_commands: list[SlashCommand] = []
227
+ self.fuzzy = FuzzySearch()
228
+ self._render_counter = 0 # Unique ID counter to prevent duplicates
229
+
230
+ def compose(self) -> ComposeResult:
231
+ with Vertical(id="slash-header"):
232
+ yield Static("⚡ SLASH COMMANDS", id="slash-title")
233
+ yield Static("Type to filter commands", id="slash-hint")
234
+ yield VerticalScroll(id="slash-list")
235
+ yield Static("↑↓ Navigate │ Enter Select │ Esc Close", id="slash-footer")
236
+
237
+ def on_mount(self) -> None:
238
+ """Initialize on mount."""
239
+ self._update_filtered_commands()
240
+
241
+ def show(self, initial_query: str = "/") -> None:
242
+ """Show slash completion overlay."""
243
+ self.selected_index = 0
244
+ self.is_visible = True
245
+ self.add_class("visible")
246
+ # Force update if query is the same (watcher won't trigger on same value)
247
+ if self.search_query == initial_query:
248
+ self._update_filtered_commands()
249
+ else:
250
+ # Setting search_query triggers watch_search_query which calls _update_filtered_commands
251
+ self.search_query = initial_query
252
+ self.focus()
253
+
254
+ def hide(self) -> None:
255
+ """Hide slash completion overlay."""
256
+ self.is_visible = False
257
+ self.remove_class("visible")
258
+ self.post_message(self.Dismissed())
259
+
260
+ def watch_search_query(self, search_query: str) -> None:
261
+ """React to search query changes."""
262
+ if not self.is_mounted:
263
+ return
264
+ self.selected_index = 0
265
+ self._update_filtered_commands()
266
+
267
+ def watch_selected_index(self, index: int) -> None:
268
+ """React to selection changes."""
269
+ if not self.is_mounted:
270
+ return
271
+ self._update_selection()
272
+
273
+ def _update_filtered_commands(self) -> None:
274
+ """Update the filtered command list based on query."""
275
+ # Remove leading "/" for search
276
+ search_text = self.search_query.lstrip("/")
277
+
278
+ # Build searchable items
279
+ items = [(cmd.command.lstrip("/"), cmd) for cmd in self.commands]
280
+
281
+ if search_text:
282
+ # Fuzzy search
283
+ results = self.fuzzy.search_with_data(
284
+ search_text,
285
+ items,
286
+ max_results=10,
287
+ )
288
+ self.filtered_commands = [cmd for _, cmd in results]
289
+ else:
290
+ # Show all commands (limited)
291
+ self.filtered_commands = self.commands[:10]
292
+
293
+ self._render_commands()
294
+
295
+ def _render_commands(self) -> None:
296
+ """Render the filtered commands in the list."""
297
+ self._render_counter += 1
298
+ render_id = self._render_counter
299
+
300
+ container = self.query_one("#slash-list", VerticalScroll)
301
+ container.remove_children()
302
+
303
+ if not self.filtered_commands:
304
+ container.mount(Static("No matching commands found", classes="no-results"))
305
+ return
306
+
307
+ for i, cmd in enumerate(self.filtered_commands):
308
+ # Use render counter in ID to ensure uniqueness across renders
309
+ item = SlashCompleteItem(cmd, id=f"slash-item-{render_id}-{i}")
310
+ item.selected = i == self.selected_index
311
+ container.mount(item)
312
+
313
+ def _update_selection(self) -> None:
314
+ """Update the visual selection state."""
315
+ for i, item in enumerate(self.query("#slash-list SlashCompleteItem")):
316
+ if isinstance(item, SlashCompleteItem):
317
+ item.selected = i == self.selected_index
318
+
319
+ def move_selection(self, delta: int) -> None:
320
+ """Move selection up or down."""
321
+ if not self.filtered_commands:
322
+ return
323
+ new_index = (self.selected_index + delta) % len(self.filtered_commands)
324
+ self.selected_index = new_index
325
+
326
+ # Scroll to make selection visible
327
+ try:
328
+ container = self.query_one("#slash-list", VerticalScroll)
329
+ items = list(self.query("#slash-list SlashCompleteItem"))
330
+ if 0 <= self.selected_index < len(items):
331
+ container.scroll_visible(items[self.selected_index])
332
+ except Exception:
333
+ pass
334
+
335
+ def select_current(self) -> SlashCommand | None:
336
+ """Select the currently highlighted command."""
337
+ if self.filtered_commands and 0 <= self.selected_index < len(self.filtered_commands):
338
+ cmd = self.filtered_commands[self.selected_index]
339
+ self.post_message(self.CommandSelected(cmd))
340
+ self.hide()
341
+ return cmd
342
+ return None
343
+
344
+ def update_query(self, query: str) -> None:
345
+ """Update the search query."""
346
+ self.search_query = query
347
+
348
+ @on(SlashCompleteItem.Click)
349
+ def on_item_click(self, event: SlashCompleteItem.Click) -> None:
350
+ """Handle item click."""
351
+ # Find the clicked item's index
352
+ for i, item in enumerate(self.query("#slash-list SlashCompleteItem")):
353
+ if item is event.widget:
354
+ self.selected_index = i
355
+ self.select_current()
356
+ break