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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- 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
|
+
]
|