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,936 @@
1
+ """
2
+ Agent Store UI - Grid-Based Agent Browser and Installer.
3
+
4
+ Provides a visual interface for browsing, installing, and launching
5
+ agents from the agent registry.
6
+
7
+ Features:
8
+ - Grid layout with agent cards
9
+ - Category filtering
10
+ - Search functionality
11
+ - Install/uninstall actions
12
+ - Launch agents directly
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass, field
18
+ from enum import Enum
19
+ from typing import Any, Callable, List, Optional
20
+
21
+ from rich.text import Text
22
+ from textual.app import ComposeResult
23
+ from textual.binding import Binding
24
+ from textual.containers import Container, Grid, Horizontal, Vertical, ScrollableContainer
25
+ from textual.screen import Screen, ModalScreen
26
+ from textual.widgets import (
27
+ Button,
28
+ Header,
29
+ Footer,
30
+ Input,
31
+ Label,
32
+ OptionList,
33
+ Select,
34
+ Static,
35
+ )
36
+ from textual.widgets.option_list import Option
37
+ from textual.reactive import reactive
38
+ from textual.message import Message
39
+ from textual import events
40
+
41
+ # Optional clipboard support - gracefully handle if not available
42
+ try:
43
+ import pyperclip
44
+
45
+ HAS_CLIPBOARD = True
46
+ except ImportError:
47
+ HAS_CLIPBOARD = False
48
+
49
+
50
+ class AgentStatus(Enum):
51
+ """Status of an agent in the store."""
52
+
53
+ AVAILABLE = "available"
54
+ INSTALLED = "installed"
55
+ RUNNING = "running"
56
+ UPDATING = "updating"
57
+ ERROR = "error"
58
+
59
+
60
+ @dataclass
61
+ class AgentInfo:
62
+ """Information about an agent."""
63
+
64
+ id: str
65
+ name: str
66
+ description: str
67
+ author: str
68
+ version: str
69
+ category: str = "general"
70
+ status: AgentStatus = AgentStatus.AVAILABLE
71
+ icon: str = ""
72
+ tags: List[str] = field(default_factory=list)
73
+ repository: str = ""
74
+ homepage: str = ""
75
+ downloads: int = 0
76
+ rating: float = 0.0
77
+ # Installation info (populated from TOML/registry)
78
+ install_command: str = ""
79
+ install_description: str = ""
80
+ run_command: str = ""
81
+ requirements: List[str] = field(default_factory=list)
82
+ documentation_url: str = ""
83
+
84
+
85
+ @dataclass
86
+ class InstallStep:
87
+ """A single installation step."""
88
+
89
+ order: int
90
+ description: str
91
+ command: str
92
+ is_optional: bool = False
93
+
94
+
95
+ def copy_to_clipboard(text: str) -> bool:
96
+ """
97
+ Copy text to clipboard.
98
+
99
+ Returns True if successful, False otherwise.
100
+ """
101
+ if not HAS_CLIPBOARD:
102
+ return False
103
+ try:
104
+ pyperclip.copy(text)
105
+ return True
106
+ except Exception:
107
+ return False
108
+
109
+
110
+ class InstallGuidancePanel(ModalScreen):
111
+ """
112
+ Modal panel showing installation guidance for an agent.
113
+
114
+ Shows installation commands, requirements, and documentation links
115
+ with copy buttons. Does NOT auto-install - users run commands manually.
116
+ """
117
+
118
+ BINDINGS = [
119
+ Binding("escape", "dismiss", "Close"),
120
+ Binding("c", "copy_main", "Copy Command"),
121
+ Binding("enter", "dismiss", "Close"),
122
+ ]
123
+
124
+ CSS = """
125
+ InstallGuidancePanel {
126
+ align: center middle;
127
+ }
128
+
129
+ #install-dialog {
130
+ width: 80%;
131
+ max-width: 100;
132
+ height: auto;
133
+ max-height: 80%;
134
+ background: #0a0a0a;
135
+ border: double #a855f7;
136
+ padding: 2;
137
+ }
138
+
139
+ #install-header {
140
+ height: 3;
141
+ margin-bottom: 1;
142
+ }
143
+
144
+ #install-title {
145
+ text-style: bold;
146
+ color: #a855f7;
147
+ }
148
+
149
+ #install-subtitle {
150
+ color: #71717a;
151
+ }
152
+
153
+ #requirements-section {
154
+ height: auto;
155
+ margin-bottom: 1;
156
+ padding: 1;
157
+ background: #18181b;
158
+ border: round #27272a;
159
+ }
160
+
161
+ #requirements-title {
162
+ text-style: bold;
163
+ color: #f59e0b;
164
+ margin-bottom: 1;
165
+ }
166
+
167
+ #commands-section {
168
+ height: auto;
169
+ margin-bottom: 1;
170
+ }
171
+
172
+ .command-box {
173
+ height: auto;
174
+ margin-bottom: 1;
175
+ padding: 1;
176
+ background: #1a1a1a;
177
+ border: round #3f3f46;
178
+ }
179
+
180
+ .command-label {
181
+ color: #22c55e;
182
+ margin-bottom: 0;
183
+ }
184
+
185
+ .command-text {
186
+ color: #e4e4e7;
187
+ background: #0d0d0d;
188
+ padding: 0 1;
189
+ margin: 0;
190
+ }
191
+
192
+ .copy-hint {
193
+ color: #52525b;
194
+ text-align: right;
195
+ }
196
+
197
+ #docs-section {
198
+ height: auto;
199
+ margin-bottom: 1;
200
+ }
201
+
202
+ #docs-link {
203
+ color: #3b82f6;
204
+ }
205
+
206
+ #action-buttons {
207
+ height: 3;
208
+ margin-top: 1;
209
+ }
210
+
211
+ #action-buttons Button {
212
+ margin-right: 1;
213
+ }
214
+
215
+ .copy-btn {
216
+ background: #3b82f6;
217
+ }
218
+
219
+ .close-btn {
220
+ background: #3f3f46;
221
+ }
222
+ """
223
+
224
+ def __init__(self, agent: AgentInfo, **kwargs):
225
+ super().__init__(**kwargs)
226
+ self.agent = agent
227
+ self._copied = False
228
+
229
+ def compose(self) -> ComposeResult:
230
+ """Compose the install guidance panel."""
231
+ with Container(id="install-dialog"):
232
+ # Header
233
+ with Container(id="install-header"):
234
+ yield Static(f" Install {self.agent.name}", id="install-title")
235
+ yield Static(
236
+ "Run these commands in your terminal to install the agent",
237
+ id="install-subtitle",
238
+ )
239
+
240
+ # Requirements section
241
+ if self.agent.requirements:
242
+ with Container(id="requirements-section"):
243
+ yield Static(" Requirements", id="requirements-title")
244
+ reqs_text = Text()
245
+ for req in self.agent.requirements:
246
+ reqs_text.append(f" • {req}\n", style="#a1a1aa")
247
+ yield Static(reqs_text)
248
+
249
+ # Commands section
250
+ with Container(id="commands-section"):
251
+ # Main install command
252
+ if self.agent.install_command:
253
+ with Container(classes="command-box"):
254
+ yield Static(
255
+ f" {self.agent.install_description or 'Install Command'}",
256
+ classes="command-label",
257
+ )
258
+ yield Static(
259
+ Text(f"$ {self.agent.install_command}", style="bold"),
260
+ classes="command-text",
261
+ id="main-command",
262
+ )
263
+ yield Static(
264
+ "[Press C to copy]" if HAS_CLIPBOARD else "",
265
+ classes="copy-hint",
266
+ )
267
+
268
+ # Run command info
269
+ if self.agent.run_command:
270
+ with Container(classes="command-box"):
271
+ yield Static(" Run Command (after install)", classes="command-label")
272
+ yield Static(
273
+ Text(f"$ {self.agent.run_command}", style="bold"),
274
+ classes="command-text",
275
+ id="run-command",
276
+ )
277
+
278
+ # Documentation section
279
+ if self.agent.documentation_url or self.agent.homepage or self.agent.repository:
280
+ with Container(id="docs-section"):
281
+ doc_url = (
282
+ self.agent.documentation_url or self.agent.homepage or self.agent.repository
283
+ )
284
+ yield Static(
285
+ Text.assemble(
286
+ (" Documentation: ", "#6b7280"),
287
+ (doc_url, "underline #3b82f6"),
288
+ ),
289
+ id="docs-link",
290
+ )
291
+
292
+ # Action buttons
293
+ with Horizontal(id="action-buttons"):
294
+ if HAS_CLIPBOARD:
295
+ yield Button(
296
+ " Copy Install Command",
297
+ id="btn-copy",
298
+ classes="copy-btn",
299
+ variant="primary",
300
+ )
301
+ yield Button("Close", id="btn-close", classes="close-btn")
302
+
303
+ # Status message
304
+ yield Static("", id="status-message")
305
+
306
+ def on_button_pressed(self, event: Button.Pressed) -> None:
307
+ """Handle button presses."""
308
+ if event.button.id == "btn-copy":
309
+ self.action_copy_main()
310
+ elif event.button.id == "btn-close":
311
+ self.dismiss()
312
+
313
+ def action_copy_main(self) -> None:
314
+ """Copy the main install command to clipboard."""
315
+ if self.agent.install_command and copy_to_clipboard(self.agent.install_command):
316
+ self._copied = True
317
+ try:
318
+ status = self.query_one("#status-message", Static)
319
+ status.update(Text(" Copied to clipboard!", style="bold #22c55e"))
320
+ except Exception:
321
+ pass
322
+ elif not HAS_CLIPBOARD:
323
+ try:
324
+ status = self.query_one("#status-message", Static)
325
+ status.update(
326
+ Text(
327
+ " Copy manually: Select the command above",
328
+ style="#f59e0b",
329
+ )
330
+ )
331
+ except Exception:
332
+ pass
333
+
334
+ def action_dismiss(self) -> None:
335
+ """Close the panel."""
336
+ self.dismiss()
337
+
338
+
339
+ # Theme colors matching SuperQode style
340
+ THEME = {
341
+ "purple": "#a855f7",
342
+ "pink": "#ec4899",
343
+ "success": "#22c55e",
344
+ "error": "#ef4444",
345
+ "warning": "#f59e0b",
346
+ "info": "#3b82f6",
347
+ "text": "#e4e4e7",
348
+ "muted": "#71717a",
349
+ "dim": "#52525b",
350
+ "bg": "#0a0a0a",
351
+ "card_bg": "#18181b",
352
+ "border": "#3f3f46",
353
+ }
354
+
355
+ STATUS_STYLES = {
356
+ AgentStatus.AVAILABLE: {"color": THEME["muted"], "icon": "", "label": "Available"},
357
+ AgentStatus.INSTALLED: {"color": THEME["success"], "icon": "", "label": "Installed"},
358
+ AgentStatus.RUNNING: {"color": THEME["purple"], "icon": "", "label": "Running"},
359
+ AgentStatus.UPDATING: {"color": THEME["warning"], "icon": "", "label": "Updating"},
360
+ AgentStatus.ERROR: {"color": THEME["error"], "icon": "", "label": "Error"},
361
+ }
362
+
363
+ CATEGORY_ICONS = {
364
+ "general": "",
365
+ "code": "",
366
+ "docs": "",
367
+ "test": "",
368
+ "devops": "",
369
+ "data": "",
370
+ "web": "",
371
+ "ai": "",
372
+ }
373
+
374
+
375
+ class AgentCard(Static):
376
+ """Display card for a single agent."""
377
+
378
+ DEFAULT_CSS = """
379
+ AgentCard {
380
+ width: 100%;
381
+ height: auto;
382
+ min-height: 8;
383
+ background: #18181b;
384
+ border: round #3f3f46;
385
+ padding: 1;
386
+ margin: 0 0 1 0;
387
+ }
388
+
389
+ AgentCard:hover {
390
+ border: round #a855f7;
391
+ }
392
+
393
+ AgentCard:focus {
394
+ border: round #ec4899;
395
+ }
396
+
397
+ AgentCard.selected {
398
+ border: round #a855f7;
399
+ background: #1f1f23;
400
+ }
401
+
402
+ AgentCard .agent-name {
403
+ text-style: bold;
404
+ color: #e4e4e7;
405
+ }
406
+
407
+ AgentCard .agent-author {
408
+ color: #71717a;
409
+ }
410
+
411
+ AgentCard .agent-desc {
412
+ color: #a1a1aa;
413
+ margin-top: 1;
414
+ }
415
+
416
+ AgentCard .agent-meta {
417
+ color: #52525b;
418
+ margin-top: 1;
419
+ }
420
+ """
421
+
422
+ can_focus = True
423
+ selected: reactive[bool] = reactive(False)
424
+
425
+ def __init__(
426
+ self,
427
+ agent: AgentInfo,
428
+ on_select: Optional[Callable[[AgentInfo], None]] = None,
429
+ **kwargs,
430
+ ):
431
+ super().__init__(**kwargs)
432
+ self.agent = agent
433
+ self._on_select = on_select
434
+
435
+ def render(self) -> Text:
436
+ """Render the agent card."""
437
+ t = Text()
438
+
439
+ # Status indicator and name
440
+ status_style = STATUS_STYLES.get(self.agent.status, STATUS_STYLES[AgentStatus.AVAILABLE])
441
+ t.append(f"{status_style['icon']} ", style=status_style["color"])
442
+ t.append(f"{self.agent.name}", style=f"bold {THEME['text']}")
443
+ t.append(f" v{self.agent.version}\n", style=THEME["muted"])
444
+
445
+ # Author
446
+ t.append(f"by {self.agent.author}\n", style=THEME["muted"])
447
+
448
+ # Description (truncated)
449
+ desc = self.agent.description
450
+ if len(desc) > 100:
451
+ desc = desc[:97] + "..."
452
+ t.append(f"{desc}\n", style=THEME["dim"])
453
+
454
+ # Category and tags
455
+ category_icon = CATEGORY_ICONS.get(self.agent.category, "")
456
+ t.append(f"\n{category_icon} {self.agent.category}", style=THEME["muted"])
457
+
458
+ if self.agent.tags:
459
+ tags = " ".join(f"#{tag}" for tag in self.agent.tags[:3])
460
+ t.append(f" {tags}", style=THEME["dim"])
461
+
462
+ return t
463
+
464
+ def watch_selected(self, selected: bool) -> None:
465
+ """Handle selection state change."""
466
+ if selected:
467
+ self.add_class("selected")
468
+ else:
469
+ self.remove_class("selected")
470
+
471
+ def on_click(self, event: events.Click) -> None:
472
+ """Handle click event."""
473
+ event.stop()
474
+ if self._on_select:
475
+ self._on_select(self.agent)
476
+
477
+
478
+ class AgentGrid(ScrollableContainer):
479
+ """Grid container for agent cards."""
480
+
481
+ DEFAULT_CSS = """
482
+ AgentGrid {
483
+ height: 100%;
484
+ padding: 1;
485
+ }
486
+ """
487
+
488
+ def __init__(
489
+ self,
490
+ agents: List[AgentInfo],
491
+ on_select: Optional[Callable[[AgentInfo], None]] = None,
492
+ **kwargs,
493
+ ):
494
+ super().__init__(**kwargs)
495
+ self.agents = agents
496
+ self._on_select = on_select
497
+ self._selected_index = 0
498
+
499
+ def compose(self) -> ComposeResult:
500
+ """Compose the agent grid."""
501
+ for i, agent in enumerate(self.agents):
502
+ yield AgentCard(
503
+ agent,
504
+ on_select=self._on_select,
505
+ id=f"agent-card-{i}",
506
+ )
507
+
508
+ def select_agent(self, index: int) -> None:
509
+ """Select an agent by index."""
510
+ if not self.agents:
511
+ return
512
+
513
+ # Clamp index
514
+ index = max(0, min(index, len(self.agents) - 1))
515
+
516
+ # Update selection
517
+ old_card = self.query_one(f"#agent-card-{self._selected_index}", AgentCard)
518
+ old_card.selected = False
519
+
520
+ self._selected_index = index
521
+ new_card = self.query_one(f"#agent-card-{self._selected_index}", AgentCard)
522
+ new_card.selected = True
523
+ new_card.focus()
524
+
525
+ def get_selected_agent(self) -> Optional[AgentInfo]:
526
+ """Get the currently selected agent."""
527
+ if 0 <= self._selected_index < len(self.agents):
528
+ return self.agents[self._selected_index]
529
+ return None
530
+
531
+
532
+ class CategoryFilter(OptionList):
533
+ """Category filter sidebar."""
534
+
535
+ DEFAULT_CSS = """
536
+ CategoryFilter {
537
+ width: 20;
538
+ height: 100%;
539
+ border-right: solid #3f3f46;
540
+ padding: 1;
541
+ }
542
+ """
543
+
544
+ def __init__(self, categories: List[str], **kwargs):
545
+ super().__init__(**kwargs)
546
+ self.categories = ["all"] + categories
547
+
548
+ def on_mount(self) -> None:
549
+ """Mount the category list."""
550
+ for category in self.categories:
551
+ icon = CATEGORY_ICONS.get(category, "") if category != "all" else ""
552
+ label = category.title()
553
+ self.add_option(Option(f"{icon} {label}", id=category))
554
+
555
+
556
+ class AgentStoreScreen(Screen):
557
+ """
558
+ Full-screen agent store browser.
559
+
560
+ Shows a grid of available agents with filtering,
561
+ search, and installation capabilities.
562
+ """
563
+
564
+ BINDINGS = [
565
+ Binding("j", "next_agent", "Next", show=True),
566
+ Binding("k", "prev_agent", "Previous", show=True),
567
+ Binding("enter", "launch_agent", "Launch", show=True),
568
+ Binding("i", "install_agent", "Setup Guide", show=True),
569
+ Binding("u", "uninstall_agent", "Uninstall", show=False),
570
+ Binding("/", "focus_search", "Search", show=True),
571
+ Binding("escape", "dismiss", "Back", show=True),
572
+ Binding("r", "refresh", "Refresh", show=True),
573
+ ]
574
+
575
+ CSS = """
576
+ AgentStoreScreen {
577
+ background: #0a0a0a;
578
+ }
579
+
580
+ #store-header {
581
+ height: 3;
582
+ background: #18181b;
583
+ padding: 0 1;
584
+ }
585
+
586
+ #store-title {
587
+ color: #a855f7;
588
+ text-style: bold;
589
+ }
590
+
591
+ #search-input {
592
+ width: 40;
593
+ margin-left: 2;
594
+ }
595
+
596
+ #store-body {
597
+ height: 1fr;
598
+ }
599
+
600
+ #agent-detail {
601
+ width: 30%;
602
+ border-left: solid #3f3f46;
603
+ padding: 1;
604
+ }
605
+
606
+ #detail-title {
607
+ text-style: bold;
608
+ color: #e4e4e7;
609
+ }
610
+
611
+ #detail-actions {
612
+ margin-top: 2;
613
+ }
614
+
615
+ #detail-actions Button {
616
+ margin-right: 1;
617
+ }
618
+
619
+ .action-button {
620
+ min-width: 12;
621
+ }
622
+
623
+ .install-btn {
624
+ background: #22c55e;
625
+ }
626
+
627
+ .launch-btn {
628
+ background: #a855f7;
629
+ }
630
+
631
+ .uninstall-btn {
632
+ background: #ef4444;
633
+ }
634
+ """
635
+
636
+ def __init__(
637
+ self,
638
+ agents: Optional[List[AgentInfo]] = None,
639
+ on_launch: Optional[Callable[[AgentInfo], None]] = None,
640
+ on_install: Optional[Callable[[AgentInfo], None]] = None,
641
+ on_uninstall: Optional[Callable[[AgentInfo], None]] = None,
642
+ **kwargs,
643
+ ):
644
+ super().__init__(**kwargs)
645
+ self.agents = agents or []
646
+ self._on_launch = on_launch
647
+ self._on_install = on_install
648
+ self._on_uninstall = on_uninstall
649
+ self._selected_agent: Optional[AgentInfo] = None
650
+ self._filter_category = "all"
651
+ self._search_query = ""
652
+
653
+ def compose(self) -> ComposeResult:
654
+ """Compose the store screen."""
655
+ yield Header()
656
+
657
+ # Header with title and search
658
+ with Horizontal(id="store-header"):
659
+ yield Static(" Agent Store", id="store-title")
660
+ yield Input(placeholder="Search agents...", id="search-input")
661
+
662
+ # Main body
663
+ with Horizontal(id="store-body"):
664
+ # Category filter
665
+ categories = list(set(a.category for a in self.agents))
666
+ yield CategoryFilter(categories, id="category-filter")
667
+
668
+ # Agent grid
669
+ yield AgentGrid(
670
+ self._get_filtered_agents(),
671
+ on_select=self._on_agent_select,
672
+ id="agent-grid",
673
+ )
674
+
675
+ # Detail panel
676
+ with Vertical(id="agent-detail"):
677
+ yield Static("Select an agent", id="detail-content")
678
+ with Horizontal(id="detail-actions"):
679
+ yield Button("Launch", id="btn-launch", classes="action-button launch-btn")
680
+ yield Button(
681
+ " Setup Guide", id="btn-install", classes="action-button install-btn"
682
+ )
683
+
684
+ yield Footer()
685
+
686
+ def _get_filtered_agents(self) -> List[AgentInfo]:
687
+ """Get agents filtered by category and search."""
688
+ agents = self.agents
689
+
690
+ # Filter by category
691
+ if self._filter_category != "all":
692
+ agents = [a for a in agents if a.category == self._filter_category]
693
+
694
+ # Filter by search
695
+ if self._search_query:
696
+ query = self._search_query.lower()
697
+ agents = [
698
+ a
699
+ for a in agents
700
+ if query in a.name.lower()
701
+ or query in a.description.lower()
702
+ or any(query in tag.lower() for tag in a.tags)
703
+ ]
704
+
705
+ return agents
706
+
707
+ def _on_agent_select(self, agent: AgentInfo) -> None:
708
+ """Handle agent selection."""
709
+ self._selected_agent = agent
710
+ self._update_detail_panel()
711
+
712
+ def _update_detail_panel(self) -> None:
713
+ """Update the detail panel with selected agent."""
714
+ detail = self.query_one("#detail-content", Static)
715
+
716
+ if not self._selected_agent:
717
+ detail.update("Select an agent")
718
+ return
719
+
720
+ agent = self._selected_agent
721
+ status_style = STATUS_STYLES.get(agent.status, STATUS_STYLES[AgentStatus.AVAILABLE])
722
+
723
+ content = Text()
724
+ content.append(f"{agent.name}\n", style=f"bold {THEME['text']}")
725
+ content.append(f"v{agent.version} by {agent.author}\n\n", style=THEME["muted"])
726
+ content.append(f"{agent.description}\n\n", style=THEME["text"])
727
+ content.append(
728
+ f"Status: {status_style['icon']} {status_style['label']}\n", style=status_style["color"]
729
+ )
730
+ content.append(f"Category: {agent.category}\n", style=THEME["muted"])
731
+
732
+ if agent.tags:
733
+ content.append(f"Tags: {', '.join(agent.tags)}\n", style=THEME["dim"])
734
+
735
+ if agent.downloads:
736
+ content.append(f"Downloads: {agent.downloads:,}\n", style=THEME["muted"])
737
+
738
+ if agent.rating:
739
+ stars = "" * int(agent.rating) + "" * (5 - int(agent.rating))
740
+ content.append(f"Rating: {stars}\n", style=THEME["warning"])
741
+
742
+ detail.update(content)
743
+
744
+ # Update buttons
745
+ install_btn = self.query_one("#btn-install", Button)
746
+ if agent.status == AgentStatus.INSTALLED:
747
+ # Agent already installed - show uninstall option
748
+ install_btn.label = " Uninstall"
749
+ install_btn.remove_class("install-btn")
750
+ install_btn.add_class("uninstall-btn")
751
+ else:
752
+ # Show setup guide (not auto-install)
753
+ install_btn.label = " Setup Guide"
754
+ install_btn.remove_class("uninstall-btn")
755
+ install_btn.add_class("install-btn")
756
+
757
+ def on_input_changed(self, event: Input.Changed) -> None:
758
+ """Handle search input change."""
759
+ if event.input.id == "search-input":
760
+ self._search_query = event.value
761
+ self._refresh_grid()
762
+
763
+ def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
764
+ """Handle category selection."""
765
+ if event.option_list.id == "category-filter":
766
+ self._filter_category = str(event.option.id)
767
+ self._refresh_grid()
768
+
769
+ def _refresh_grid(self) -> None:
770
+ """Refresh the agent grid with current filters."""
771
+ grid = self.query_one("#agent-grid", AgentGrid)
772
+ grid.remove()
773
+
774
+ new_grid = AgentGrid(
775
+ self._get_filtered_agents(),
776
+ on_select=self._on_agent_select,
777
+ id="agent-grid",
778
+ )
779
+
780
+ body = self.query_one("#store-body", Horizontal)
781
+ body.mount(new_grid, before=self.query_one("#agent-detail"))
782
+
783
+ def on_button_pressed(self, event: Button.Pressed) -> None:
784
+ """Handle button presses."""
785
+ if not self._selected_agent:
786
+ return
787
+
788
+ if event.button.id == "btn-launch":
789
+ self.action_launch_agent()
790
+ elif event.button.id == "btn-install":
791
+ if self._selected_agent.status == AgentStatus.INSTALLED:
792
+ self.action_uninstall_agent()
793
+ else:
794
+ # Show setup guide (not auto-install)
795
+ self.action_install_agent()
796
+
797
+ def action_next_agent(self) -> None:
798
+ """Select next agent."""
799
+ grid = self.query_one("#agent-grid", AgentGrid)
800
+ grid.select_agent(grid._selected_index + 1)
801
+ agent = grid.get_selected_agent()
802
+ if agent:
803
+ self._on_agent_select(agent)
804
+
805
+ def action_prev_agent(self) -> None:
806
+ """Select previous agent."""
807
+ grid = self.query_one("#agent-grid", AgentGrid)
808
+ grid.select_agent(grid._selected_index - 1)
809
+ agent = grid.get_selected_agent()
810
+ if agent:
811
+ self._on_agent_select(agent)
812
+
813
+ def action_launch_agent(self) -> None:
814
+ """Launch the selected agent."""
815
+ if self._selected_agent and self._on_launch:
816
+ self._on_launch(self._selected_agent)
817
+ self.dismiss()
818
+
819
+ def action_install_agent(self) -> None:
820
+ """Show install guidance for the selected agent."""
821
+ if self._selected_agent:
822
+ # Show the install guidance panel (does NOT auto-install)
823
+ self.app.push_screen(InstallGuidancePanel(self._selected_agent))
824
+
825
+ def action_uninstall_agent(self) -> None:
826
+ """Uninstall the selected agent."""
827
+ if self._selected_agent and self._on_uninstall:
828
+ self._on_uninstall(self._selected_agent)
829
+ self._selected_agent.status = AgentStatus.AVAILABLE
830
+ self._update_detail_panel()
831
+
832
+ def action_focus_search(self) -> None:
833
+ """Focus the search input."""
834
+ self.query_one("#search-input", Input).focus()
835
+
836
+ def action_refresh(self) -> None:
837
+ """Refresh the agent list."""
838
+ self._refresh_grid()
839
+
840
+ def action_dismiss(self) -> None:
841
+ """Close the store screen."""
842
+ self.app.pop_screen()
843
+
844
+
845
+ # Helper function to create sample agents for testing
846
+ def create_sample_agents() -> List[AgentInfo]:
847
+ """Create sample agents for testing."""
848
+ return [
849
+ AgentInfo(
850
+ id="claude-code",
851
+ name="Claude Code",
852
+ description="Full-featured AI coding assistant with file editing, shell access, and code intelligence.",
853
+ author="Anthropic",
854
+ version="1.0.0",
855
+ category="code",
856
+ status=AgentStatus.INSTALLED,
857
+ tags=["ai", "coding", "assistant"],
858
+ downloads=50000,
859
+ rating=4.8,
860
+ install_command="curl -fsSL https://claude.ai/install.sh | bash && npm install -g @zed-industries/claude-code-acp",
861
+ install_description="Install Claude Code + ACP adapter",
862
+ run_command="claude-code-acp",
863
+ requirements=["Node.js 18+", "npm"],
864
+ documentation_url="https://claude.ai/code",
865
+ ),
866
+ AgentInfo(
867
+ id="opencode",
868
+ name="OpenCode",
869
+ description="Open-source AI coding agent with ACP protocol support.",
870
+ author="SST",
871
+ version="0.5.0",
872
+ category="code",
873
+ status=AgentStatus.AVAILABLE,
874
+ tags=["ai", "coding", "open-source"],
875
+ downloads=10000,
876
+ rating=4.5,
877
+ install_command="npm i -g opencode-ai",
878
+ install_description="Install OpenCode",
879
+ run_command="opencode acp",
880
+ requirements=["Node.js 18+", "npm"],
881
+ documentation_url="https://opencode.ai/",
882
+ repository="https://github.com/sst/opencode",
883
+ ),
884
+ AgentInfo(
885
+ id="gemini-cli",
886
+ name="Gemini CLI",
887
+ description="Google's Gemini AI in your terminal with ACP support.",
888
+ author="Google",
889
+ version="1.0.0",
890
+ category="code",
891
+ status=AgentStatus.AVAILABLE,
892
+ tags=["ai", "google", "gemini"],
893
+ downloads=20000,
894
+ rating=4.6,
895
+ install_command="npm install -g @anthropic-ai/gemini-cli",
896
+ install_description="Install Gemini CLI",
897
+ run_command="gemini --experimental-acp",
898
+ requirements=["Node.js 18+", "npm", "Google Cloud account"],
899
+ documentation_url="https://ai.google.dev/gemini-api/docs",
900
+ ),
901
+ AgentInfo(
902
+ id="codex",
903
+ name="Codex",
904
+ description="OpenAI's code generation agent with streaming terminal output.",
905
+ author="OpenAI",
906
+ version="1.0.0",
907
+ category="code",
908
+ status=AgentStatus.AVAILABLE,
909
+ tags=["ai", "openai", "coding"],
910
+ downloads=30000,
911
+ rating=4.4,
912
+ install_command="npm install -g @zed-industries/codex-acp",
913
+ install_description="Install Codex ACP adapter",
914
+ run_command="codex-acp",
915
+ requirements=["Node.js 18+", "npm", "OpenAI API key"],
916
+ documentation_url="https://openai.com/codex",
917
+ ),
918
+ AgentInfo(
919
+ id="goose",
920
+ name="Goose",
921
+ description="Block's developer agent that automates engineering tasks.",
922
+ author="Block",
923
+ version="1.0.0",
924
+ category="code",
925
+ status=AgentStatus.AVAILABLE,
926
+ tags=["ai", "automation", "coding"],
927
+ downloads=15000,
928
+ rating=4.3,
929
+ install_command="pipx install goose-ai",
930
+ install_description="Install Goose",
931
+ run_command="goose",
932
+ requirements=["Python 3.10+", "pipx"],
933
+ documentation_url="https://github.com/block/goose",
934
+ repository="https://github.com/block/goose",
935
+ ),
936
+ ]