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,965 @@
|
|
|
1
|
+
"""ACP (Agent-Client Protocol) commands for SuperQode."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
|
|
14
|
+
from superqode.agents.client import ACPAgentManager
|
|
15
|
+
from superqode.agents.discovery import get_agent_by_identity, get_agent_by_short_name, read_agents
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from superqode.agents.schema import Agent
|
|
19
|
+
|
|
20
|
+
_console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def check_agent_installed(agent: "Agent") -> bool:
|
|
24
|
+
"""Check if an agent is installed on the system."""
|
|
25
|
+
import shutil
|
|
26
|
+
|
|
27
|
+
run_command = agent.get("run_command", {}).get("*", "")
|
|
28
|
+
if not run_command:
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
# Extract the command name (first part before any spaces or arguments)
|
|
32
|
+
cmd_name = run_command.split()[0]
|
|
33
|
+
|
|
34
|
+
# Check if command exists in PATH
|
|
35
|
+
return shutil.which(cmd_name) is not None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def create_agent_card(
|
|
39
|
+
agent: "Agent", is_enabled: bool = False, is_installed: bool | None = None
|
|
40
|
+
) -> Panel:
|
|
41
|
+
"""Create a beautiful agent card."""
|
|
42
|
+
# Check installation status if not provided
|
|
43
|
+
if is_installed is None:
|
|
44
|
+
is_installed = check_agent_installed(agent)
|
|
45
|
+
|
|
46
|
+
# Status indicators
|
|
47
|
+
status_icon = "✅" if is_enabled else "⏳" if is_installed else "📦"
|
|
48
|
+
status_text = "Enabled" if is_enabled else "Ready" if is_installed else "Available"
|
|
49
|
+
status_color = "green" if is_enabled else "yellow" if is_installed else "dim"
|
|
50
|
+
|
|
51
|
+
# Agent info
|
|
52
|
+
name = agent["name"]
|
|
53
|
+
short_name = agent["short_name"]
|
|
54
|
+
description = agent["description"]
|
|
55
|
+
author = agent["author_name"]
|
|
56
|
+
agent_type = agent["type"]
|
|
57
|
+
|
|
58
|
+
# Type badge
|
|
59
|
+
type_badge = f"[bold white on blue] {agent_type.upper()} [/bold white on blue]"
|
|
60
|
+
|
|
61
|
+
content = f"""[bold cyan]{name}[/bold cyan] [dim]({short_name})[/dim]
|
|
62
|
+
{type_badge}
|
|
63
|
+
|
|
64
|
+
[white]{description}[/white]
|
|
65
|
+
|
|
66
|
+
[dim]By {author}[/dim]
|
|
67
|
+
[{status_color}]{status_icon} {status_text}[/{status_color}]"""
|
|
68
|
+
|
|
69
|
+
border_style = "bright_green" if is_enabled else "cyan" if is_installed else "dim"
|
|
70
|
+
|
|
71
|
+
return Panel.fit(
|
|
72
|
+
content,
|
|
73
|
+
border_style=border_style,
|
|
74
|
+
padding=(1, 2),
|
|
75
|
+
title=f"[bold]{short_name}[/bold]",
|
|
76
|
+
title_align="center",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def show_agents_store() -> None:
|
|
81
|
+
"""Show the beautiful Agent Store interface."""
|
|
82
|
+
import asyncio
|
|
83
|
+
from superqode.config import load_config
|
|
84
|
+
from superqode.agents.registry import get_all_acp_agents
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
agents = asyncio.run(get_all_acp_agents())
|
|
88
|
+
except Exception as e:
|
|
89
|
+
_console.print(f"[red]Error loading agents: {e}[/red]")
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
if not agents:
|
|
93
|
+
_console.print("[yellow]No ACP agents found. Agent configurations may be missing.[/yellow]")
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
# Get current configuration
|
|
97
|
+
config = load_config()
|
|
98
|
+
config_agents = (
|
|
99
|
+
getattr(config, "agents", {}).get("acp", {}) if hasattr(config, "agents") else {}
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Count installed vs not installed
|
|
103
|
+
installed_count = sum(1 for agent_data in agents.values() if check_agent_installed(agent_data))
|
|
104
|
+
not_installed_count = len(agents) - installed_count
|
|
105
|
+
|
|
106
|
+
# Header
|
|
107
|
+
header = Panel.fit(
|
|
108
|
+
"[bold bright_blue]🛍️ SuperQode Agent Store[/bold bright_blue]\n"
|
|
109
|
+
"[dim]Discover and install AI coding agents for your development team[/dim]\n\n"
|
|
110
|
+
f"[cyan]📊 {len(agents)} agents available[/cyan] | "
|
|
111
|
+
f"[green]✓ {installed_count} installed[/green] | "
|
|
112
|
+
f"[yellow]○ {not_installed_count} not installed[/yellow]",
|
|
113
|
+
border_style="bright_blue",
|
|
114
|
+
padding=(1, 2),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
_console.print(header)
|
|
118
|
+
_console.print()
|
|
119
|
+
|
|
120
|
+
# Create agent cards in a grid layout (3 per row)
|
|
121
|
+
agent_cards = []
|
|
122
|
+
for agent_id, agent_data in agents.items():
|
|
123
|
+
is_enabled = config_agents.get(agent_id, {}).get("enabled", False)
|
|
124
|
+
is_installed = check_agent_installed(agent_data)
|
|
125
|
+
card = create_agent_card(agent_data, is_enabled, is_installed)
|
|
126
|
+
agent_cards.append(card)
|
|
127
|
+
|
|
128
|
+
# Display in rows of 3
|
|
129
|
+
from rich.columns import Columns
|
|
130
|
+
|
|
131
|
+
for i in range(0, len(agent_cards), 3):
|
|
132
|
+
row_cards = agent_cards[i : i + 3]
|
|
133
|
+
if len(row_cards) == 1:
|
|
134
|
+
_console.print(row_cards[0])
|
|
135
|
+
else:
|
|
136
|
+
_console.print(Columns(row_cards, equal=True, expand=True))
|
|
137
|
+
_console.print()
|
|
138
|
+
|
|
139
|
+
# Footer with commands
|
|
140
|
+
footer = Panel.fit(
|
|
141
|
+
"[bold cyan]🚀 Quick Commands:[/bold cyan]\n\n"
|
|
142
|
+
"[green]superqode agents show <agent>[/green] View detailed agent info\n"
|
|
143
|
+
"[green]superqode agents install <agent>[/green] Install agent on your system\n"
|
|
144
|
+
"[green]superqode agents connect <agent> [model][/green] Connect with specific model\n\n"
|
|
145
|
+
"[bold cyan]💻 Interactive Commands:[/bold cyan]\n"
|
|
146
|
+
"[yellow]:agents[/yellow] Browse this marketplace\n"
|
|
147
|
+
"[yellow]:agents install <agent>[/yellow] Install directly from here\n"
|
|
148
|
+
"[yellow]:agents connect <agent> [model][/yellow] Connect with model\n"
|
|
149
|
+
"[yellow]:agent <command>[/yellow] Same as :agents (singular)\n\n"
|
|
150
|
+
"[dim]💡 Configure agents in your superqode.yaml to enable them in your team[/dim]",
|
|
151
|
+
border_style="cyan",
|
|
152
|
+
padding=(1, 2),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
_console.print(footer)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def show_agents_list() -> None:
|
|
159
|
+
"""Show a list of available ACP agents with installation status."""
|
|
160
|
+
import asyncio
|
|
161
|
+
from superqode.config import load_config
|
|
162
|
+
from superqode.agents.registry import get_all_acp_agents, get_agent_installation_info
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
agents = asyncio.run(get_all_acp_agents())
|
|
166
|
+
except Exception as e:
|
|
167
|
+
_console.print(f"[red]Error loading agents: {e}[/red]")
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
if not agents:
|
|
171
|
+
_console.print("[yellow]No ACP agents found. Agent configurations may be missing.[/yellow]")
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
# Get current configuration
|
|
175
|
+
config = load_config()
|
|
176
|
+
config_agents = (
|
|
177
|
+
getattr(config, "agents", {}).get("acp", {}) if hasattr(config, "agents") else {}
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
_console.print()
|
|
181
|
+
_console.print("[bold bright_blue]🤖 Available ACP Coding Agents[/bold bright_blue]")
|
|
182
|
+
_console.print()
|
|
183
|
+
|
|
184
|
+
# Separate agents by installation status
|
|
185
|
+
installed_agents = []
|
|
186
|
+
not_installed_agents = []
|
|
187
|
+
|
|
188
|
+
for agent_id, agent_data in agents.items():
|
|
189
|
+
is_installed = check_agent_installed(agent_data)
|
|
190
|
+
if is_installed:
|
|
191
|
+
installed_agents.append((agent_id, agent_data))
|
|
192
|
+
else:
|
|
193
|
+
not_installed_agents.append((agent_id, agent_data))
|
|
194
|
+
|
|
195
|
+
# Show installed agents first
|
|
196
|
+
if installed_agents:
|
|
197
|
+
_console.print("[bold green]✓ Installed Agents[/bold green]")
|
|
198
|
+
_console.print()
|
|
199
|
+
|
|
200
|
+
table = Table(show_header=True, header_style="bold green")
|
|
201
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
|
202
|
+
table.add_column("Short Name", style="green")
|
|
203
|
+
table.add_column("Description", style="white", max_width=50)
|
|
204
|
+
table.add_column("Author", style="yellow")
|
|
205
|
+
table.add_column("Status", style="cyan", no_wrap=True)
|
|
206
|
+
|
|
207
|
+
for agent_id, agent_data in sorted(installed_agents, key=lambda x: x[1]["name"]):
|
|
208
|
+
is_enabled = config_agents.get(agent_id, {}).get("enabled", False)
|
|
209
|
+
status = "[green]✓ Enabled[/green]" if is_enabled else "[dim]Not configured[/dim]"
|
|
210
|
+
|
|
211
|
+
table.add_row(
|
|
212
|
+
agent_data["name"],
|
|
213
|
+
agent_data["short_name"],
|
|
214
|
+
agent_data["description"][:50] + "..."
|
|
215
|
+
if len(agent_data["description"]) > 50
|
|
216
|
+
else agent_data["description"],
|
|
217
|
+
agent_data["author_name"],
|
|
218
|
+
status,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
_console.print(table)
|
|
222
|
+
_console.print()
|
|
223
|
+
|
|
224
|
+
# Show not installed agents with installation commands
|
|
225
|
+
if not_installed_agents:
|
|
226
|
+
_console.print("[bold yellow]○ Not Installed Agents[/bold yellow]")
|
|
227
|
+
_console.print()
|
|
228
|
+
|
|
229
|
+
table = Table(show_header=True, header_style="bold yellow")
|
|
230
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
|
231
|
+
table.add_column("Short Name", style="green")
|
|
232
|
+
table.add_column("Description", style="white", max_width=40)
|
|
233
|
+
table.add_column("Install Command", style="magenta", no_wrap=False)
|
|
234
|
+
|
|
235
|
+
for agent_id, agent_data in sorted(not_installed_agents, key=lambda x: x[1]["name"]):
|
|
236
|
+
install_info = get_agent_installation_info(agent_data)
|
|
237
|
+
install_cmd = install_info.get("command", "N/A")
|
|
238
|
+
|
|
239
|
+
# Truncate long commands
|
|
240
|
+
if len(install_cmd) > 40:
|
|
241
|
+
install_cmd = install_cmd[:37] + "..."
|
|
242
|
+
|
|
243
|
+
table.add_row(
|
|
244
|
+
agent_data["name"],
|
|
245
|
+
agent_data["short_name"],
|
|
246
|
+
agent_data["description"][:40] + "..."
|
|
247
|
+
if len(agent_data["description"]) > 40
|
|
248
|
+
else agent_data["description"],
|
|
249
|
+
f"[dim]{install_cmd}[/dim]"
|
|
250
|
+
if install_cmd == "N/A"
|
|
251
|
+
else f"[cyan]{install_cmd}[/cyan]",
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
_console.print(table)
|
|
255
|
+
_console.print()
|
|
256
|
+
_console.print("[dim]💡 To install an agent, run:[/dim]")
|
|
257
|
+
_console.print("[cyan] superqode agents install <short-name>[/cyan]")
|
|
258
|
+
_console.print("[dim] or[/dim]")
|
|
259
|
+
_console.print("[cyan] :acp install <short-name>[/cyan]")
|
|
260
|
+
_console.print()
|
|
261
|
+
|
|
262
|
+
_console.print("[dim]Use 'superqode agents store' to see the beautiful store interface[/dim]")
|
|
263
|
+
_console.print("[dim]Use 'superqode connect acp <short-name>' to connect to an agent[/dim]")
|
|
264
|
+
_console.print()
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def format_installation_instructions(agent: "Agent") -> Text:
|
|
268
|
+
"""Format installation instructions for an agent.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
agent: Agent dict.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Formatted Text object with installation instructions.
|
|
275
|
+
"""
|
|
276
|
+
from superqode.agents.registry import get_agent_installation_info
|
|
277
|
+
import asyncio
|
|
278
|
+
|
|
279
|
+
install_info = get_agent_installation_info(agent)
|
|
280
|
+
command = install_info.get("command", "")
|
|
281
|
+
description = install_info.get("description", "Install agent")
|
|
282
|
+
instructions = install_info.get("instructions", "")
|
|
283
|
+
|
|
284
|
+
text = Text()
|
|
285
|
+
text.append(f"\n 📦 ", style="bold cyan")
|
|
286
|
+
text.append(f"Installation Instructions for {agent['name']}\n\n", style="bold")
|
|
287
|
+
|
|
288
|
+
if command:
|
|
289
|
+
text.append(" Installation Command:\n", style="bold yellow")
|
|
290
|
+
text.append(f" {command}\n\n", style="cyan")
|
|
291
|
+
|
|
292
|
+
if instructions:
|
|
293
|
+
text.append(" Instructions:\n", style="bold yellow")
|
|
294
|
+
# Split instructions by lines and format
|
|
295
|
+
for line in instructions.split("\n"):
|
|
296
|
+
if line.strip():
|
|
297
|
+
text.append(f" {line}\n", style="white")
|
|
298
|
+
text.append("\n", style="")
|
|
299
|
+
|
|
300
|
+
# Show requirements if available
|
|
301
|
+
run_command = agent.get("run_command", {}).get("*", "")
|
|
302
|
+
if run_command:
|
|
303
|
+
text.append(" Verification:\n", style="bold yellow")
|
|
304
|
+
text.append(f" After installation, verify with: ", style="dim")
|
|
305
|
+
text.append(f"which {run_command.split()[0]}\n", style="cyan")
|
|
306
|
+
text.append("\n", style="")
|
|
307
|
+
|
|
308
|
+
return text
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def show_agent_installation_steps(agent_identifier: str) -> None:
|
|
312
|
+
"""Display detailed installation instructions for an agent.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
agent_identifier: Agent short name or identity.
|
|
316
|
+
"""
|
|
317
|
+
import asyncio
|
|
318
|
+
from superqode.agents.discovery import (
|
|
319
|
+
get_agent_by_short_name_async,
|
|
320
|
+
get_agent_by_identity_async,
|
|
321
|
+
)
|
|
322
|
+
from superqode.agents.registry import get_all_acp_agents
|
|
323
|
+
|
|
324
|
+
async def show_steps_async():
|
|
325
|
+
# Try to find agent
|
|
326
|
+
agent = await get_agent_by_short_name_async(agent_identifier, include_registry=True)
|
|
327
|
+
if not agent:
|
|
328
|
+
agent = await get_agent_by_identity_async(agent_identifier, include_registry=True)
|
|
329
|
+
|
|
330
|
+
if not agent:
|
|
331
|
+
_console.print(f"[red]Agent '{agent_identifier}' not found.[/red]")
|
|
332
|
+
_console.print("[dim]Use 'superqode agents list' to see available agents.[/dim]")
|
|
333
|
+
return
|
|
334
|
+
|
|
335
|
+
# Check if already installed
|
|
336
|
+
is_installed = check_agent_installed(agent)
|
|
337
|
+
if is_installed:
|
|
338
|
+
_console.print(f"[green]✓ {agent['name']} is already installed![/green]")
|
|
339
|
+
_console.print(
|
|
340
|
+
f"[dim]Run command: {agent.get('run_command', {}).get('*', 'N/A')}[/dim]"
|
|
341
|
+
)
|
|
342
|
+
return
|
|
343
|
+
|
|
344
|
+
# Show installation instructions
|
|
345
|
+
instructions = format_installation_instructions(agent)
|
|
346
|
+
_console.print(instructions)
|
|
347
|
+
|
|
348
|
+
# Show prerequisites
|
|
349
|
+
install_info = get_agent_installation_info(agent)
|
|
350
|
+
command = install_info.get("command", "")
|
|
351
|
+
|
|
352
|
+
if command:
|
|
353
|
+
if "npm" in command:
|
|
354
|
+
_console.print(" Prerequisites:\n", style="bold yellow")
|
|
355
|
+
_console.print(" - Node.js (v16 or higher)\n", style="white")
|
|
356
|
+
_console.print(" - npm (comes with Node.js)\n", style="white")
|
|
357
|
+
elif "pip" in command:
|
|
358
|
+
_console.print(" Prerequisites:\n", style="bold yellow")
|
|
359
|
+
_console.print(" - Python 3.8 or higher\n", style="white")
|
|
360
|
+
_console.print(" - pip (Python package manager)\n", style="white")
|
|
361
|
+
elif "cargo" in command:
|
|
362
|
+
_console.print(" Prerequisites:\n", style="bold yellow")
|
|
363
|
+
_console.print(" - Rust toolchain (rustc and cargo)\n", style="white")
|
|
364
|
+
_console.print(" - Cargo package manager\n", style="white")
|
|
365
|
+
|
|
366
|
+
_console.print("\n Quick Install:\n", style="bold green")
|
|
367
|
+
_console.print(f" {command}\n", style="cyan")
|
|
368
|
+
_console.print("\n Or use SuperQode:\n", style="bold green")
|
|
369
|
+
_console.print(f" superqode agents install {agent['short_name']}\n", style="cyan")
|
|
370
|
+
|
|
371
|
+
asyncio.run(show_steps_async())
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def show_agent_details(agent: "Agent") -> None:
|
|
375
|
+
"""Show detailed information about a specific agent."""
|
|
376
|
+
_console.print()
|
|
377
|
+
_console.print(f"[bold bright_blue]🤖 {agent['name']}[/bold bright_blue]")
|
|
378
|
+
_console.print(f"[dim]{agent['url']}[/dim]")
|
|
379
|
+
_console.print()
|
|
380
|
+
|
|
381
|
+
# Basic info
|
|
382
|
+
_console.print("[bold cyan]Basic Information:[/bold cyan]")
|
|
383
|
+
_console.print(f" Author: {agent['author_name']} ({agent['author_url']})")
|
|
384
|
+
_console.print(f" Type: {agent['type']}")
|
|
385
|
+
_console.print(f" Protocol: {agent['protocol']}")
|
|
386
|
+
_console.print()
|
|
387
|
+
|
|
388
|
+
# Description
|
|
389
|
+
_console.print("[bold cyan]Description:[/bold cyan]")
|
|
390
|
+
_console.print(f" {agent['description']}")
|
|
391
|
+
_console.print()
|
|
392
|
+
|
|
393
|
+
# Help
|
|
394
|
+
if agent.get("help"):
|
|
395
|
+
_console.print("[bold cyan]Help:[/bold cyan]")
|
|
396
|
+
_console.print(agent["help"])
|
|
397
|
+
_console.print()
|
|
398
|
+
|
|
399
|
+
# Actions
|
|
400
|
+
if agent.get("actions", {}).get("*"):
|
|
401
|
+
_console.print("[bold cyan]Available Actions:[/bold cyan]")
|
|
402
|
+
for action_name, action_data in agent["actions"]["*"].items():
|
|
403
|
+
_console.print(f" [green]{action_name}[/green]: {action_data['description']}")
|
|
404
|
+
_console.print()
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def validate_agent_environment(agent: "Agent") -> list[str]:
|
|
408
|
+
"""Validate environment variables required for an agent.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
agent: Agent configuration
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
List of missing environment variables
|
|
415
|
+
"""
|
|
416
|
+
missing_vars = []
|
|
417
|
+
|
|
418
|
+
# Agent-specific environment variable requirements
|
|
419
|
+
# This could be extended to read from agent config
|
|
420
|
+
agent_name = agent.get("short_name", "").lower()
|
|
421
|
+
|
|
422
|
+
# Define known environment variable requirements for agents
|
|
423
|
+
env_requirements = {
|
|
424
|
+
"claude": ["ANTHROPIC_API_KEY"],
|
|
425
|
+
"opencode": ["ZHIPUAI_API_KEY"], # GLM-4.7 uses ZHIPUAI
|
|
426
|
+
"gemini": ["GOOGLE_API_KEY"],
|
|
427
|
+
"openai": ["OPENAI_API_KEY"],
|
|
428
|
+
"kimi": ["MOONSHOT_API_KEY"],
|
|
429
|
+
"grok": ["XAI_API_KEY"],
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
# Check if agent requires specific environment variables
|
|
433
|
+
if agent_name in env_requirements:
|
|
434
|
+
required_vars = env_requirements[agent_name]
|
|
435
|
+
for var in required_vars:
|
|
436
|
+
if not os.getenv(var):
|
|
437
|
+
missing_vars.append(var)
|
|
438
|
+
|
|
439
|
+
return missing_vars
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def perform_agent_health_check(agent: "Agent") -> tuple[bool, list[str]]:
|
|
443
|
+
"""Perform comprehensive health checks before launching an agent.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
agent: Agent configuration
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
Tuple of (is_healthy, list_of_issues)
|
|
450
|
+
"""
|
|
451
|
+
issues = []
|
|
452
|
+
|
|
453
|
+
# Check 1: Installation status
|
|
454
|
+
if not check_agent_installed(agent):
|
|
455
|
+
issues.append(f"Agent '{agent['short_name']}' is not installed")
|
|
456
|
+
return False, issues
|
|
457
|
+
|
|
458
|
+
# Check 2: Environment variables
|
|
459
|
+
missing_env_vars = validate_agent_environment(agent)
|
|
460
|
+
if missing_env_vars:
|
|
461
|
+
issues.extend([f"Missing environment variable: {var}" for var in missing_env_vars])
|
|
462
|
+
|
|
463
|
+
# Check 3: Run command validity
|
|
464
|
+
run_command = agent.get("run_command", {}).get("*")
|
|
465
|
+
if not run_command:
|
|
466
|
+
issues.append("No run command configured")
|
|
467
|
+
else:
|
|
468
|
+
import shutil
|
|
469
|
+
|
|
470
|
+
cmd_parts = run_command.split()
|
|
471
|
+
if not shutil.which(cmd_parts[0]):
|
|
472
|
+
issues.append(f"Command '{cmd_parts[0]}' not found in PATH")
|
|
473
|
+
|
|
474
|
+
# Check 4: Protocol support
|
|
475
|
+
protocol = agent.get("protocol")
|
|
476
|
+
if protocol != "acp":
|
|
477
|
+
issues.append(f"Unsupported protocol: {protocol} (only ACP is supported)")
|
|
478
|
+
|
|
479
|
+
# Check 5: Required fields
|
|
480
|
+
required_fields = ["identity", "name", "short_name"]
|
|
481
|
+
for field in required_fields:
|
|
482
|
+
if field not in agent:
|
|
483
|
+
issues.append(f"Missing required field: {field}")
|
|
484
|
+
|
|
485
|
+
is_healthy = len(issues) == 0
|
|
486
|
+
return is_healthy, issues
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def diagnose_agent_issues(agent: "Agent", issues: list[str]) -> None:
|
|
490
|
+
"""Provide helpful diagnostics and solutions for agent issues.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
agent: Agent configuration
|
|
494
|
+
issues: List of identified issues
|
|
495
|
+
"""
|
|
496
|
+
if not issues:
|
|
497
|
+
return
|
|
498
|
+
|
|
499
|
+
_console.print(f"[yellow]🔍 Diagnostics for {agent['name']}:[/yellow]")
|
|
500
|
+
|
|
501
|
+
for issue in issues:
|
|
502
|
+
if "not installed" in issue:
|
|
503
|
+
_console.print(f" [red]• {issue}[/red]")
|
|
504
|
+
_console.print(
|
|
505
|
+
f" [cyan]Solution: superqode agents install {agent['short_name']}[/cyan]"
|
|
506
|
+
)
|
|
507
|
+
elif "Missing environment variable" in issue:
|
|
508
|
+
var_name = issue.split(": ")[1]
|
|
509
|
+
_console.print(f" [red]• {issue}[/red]")
|
|
510
|
+
_console.print(f" [cyan]Solution: export {var_name}=your_api_key_here[/cyan]")
|
|
511
|
+
_console.print(
|
|
512
|
+
f" [dim]Get your API key from: {agent.get('author_url', 'the provider website')}[/dim]"
|
|
513
|
+
)
|
|
514
|
+
elif "not found in PATH" in issue:
|
|
515
|
+
cmd_name = issue.split("'")[1]
|
|
516
|
+
_console.print(f" [red]• {issue}[/red]")
|
|
517
|
+
_console.print(
|
|
518
|
+
f" [cyan]Solution: Install {cmd_name} and ensure it's in your PATH[/cyan]"
|
|
519
|
+
)
|
|
520
|
+
elif "Unsupported protocol" in issue:
|
|
521
|
+
_console.print(f" [red]• {issue}[/red]")
|
|
522
|
+
_console.print(f" [dim]This agent uses an unsupported protocol[/dim]")
|
|
523
|
+
else:
|
|
524
|
+
_console.print(f" [red]• {issue}[/red]")
|
|
525
|
+
|
|
526
|
+
_console.print()
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
async def connect_to_agent(agent_identifier: str, project_dir: str | None = None) -> int:
|
|
530
|
+
"""Connect to an ACP coding agent.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
agent_identifier: Short name or identity of the agent
|
|
534
|
+
project_dir: Project directory to work in
|
|
535
|
+
|
|
536
|
+
Returns:
|
|
537
|
+
Exit code
|
|
538
|
+
"""
|
|
539
|
+
# Find the agent
|
|
540
|
+
from superqode.agents.discovery import (
|
|
541
|
+
get_agent_by_short_name_async,
|
|
542
|
+
get_agent_by_identity_async,
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
agent = await get_agent_by_short_name_async(agent_identifier)
|
|
546
|
+
if not agent:
|
|
547
|
+
agent = await get_agent_by_identity_async(agent_identifier)
|
|
548
|
+
|
|
549
|
+
if not agent:
|
|
550
|
+
_console.print(f"[red]Agent '{agent_identifier}' not found.[/red]")
|
|
551
|
+
_console.print("[dim]Use 'superqode agents list' to see available agents.[/dim]")
|
|
552
|
+
return 1
|
|
553
|
+
|
|
554
|
+
# Perform comprehensive health check
|
|
555
|
+
_console.print(f"[cyan]🔍 Checking {agent['name']}...[/cyan]")
|
|
556
|
+
is_healthy, issues = perform_agent_health_check(agent)
|
|
557
|
+
|
|
558
|
+
if not is_healthy:
|
|
559
|
+
_console.print(f"[red]❌ Health check failed for {agent['name']}[/red]")
|
|
560
|
+
diagnose_agent_issues(agent, issues)
|
|
561
|
+
return 1
|
|
562
|
+
|
|
563
|
+
_console.print(f"[green]✓ Health check passed[/green]")
|
|
564
|
+
_console.print(f"[green]Connecting to {agent['name']}...[/green]")
|
|
565
|
+
|
|
566
|
+
# Get the run command
|
|
567
|
+
run_command = agent.get("run_command", {}).get("*")
|
|
568
|
+
if not run_command:
|
|
569
|
+
_console.print(f"[red]No run command configured for agent '{agent['name']}'.[/red]")
|
|
570
|
+
return 1
|
|
571
|
+
|
|
572
|
+
# Check if the command exists
|
|
573
|
+
import shutil
|
|
574
|
+
|
|
575
|
+
cmd_parts = run_command.split()
|
|
576
|
+
if not shutil.which(cmd_parts[0]):
|
|
577
|
+
_console.print(
|
|
578
|
+
f"[red]Command '{cmd_parts[0]}' not found. Please install the agent first.[/red]"
|
|
579
|
+
)
|
|
580
|
+
_console.print(f"[dim]Run: superqode agents install {agent['short_name']}[/dim]")
|
|
581
|
+
return 1
|
|
582
|
+
|
|
583
|
+
# Create agent manager
|
|
584
|
+
manager = ACPAgentManager()
|
|
585
|
+
|
|
586
|
+
try:
|
|
587
|
+
# Connect to the agent
|
|
588
|
+
cwd = project_dir or os.getcwd()
|
|
589
|
+
success = await manager.connect_to_agent(run_command, cwd)
|
|
590
|
+
|
|
591
|
+
if not success:
|
|
592
|
+
_console.print(f"[red]Failed to connect to {agent['name']}.[/red]")
|
|
593
|
+
return 1
|
|
594
|
+
|
|
595
|
+
_console.print(f"[green]✓ Connected to {agent['name']}![/green]")
|
|
596
|
+
_console.print(
|
|
597
|
+
"[dim]Type your messages and press Enter. Type 'exit' or 'quit' to disconnect.[/dim]"
|
|
598
|
+
)
|
|
599
|
+
_console.print()
|
|
600
|
+
|
|
601
|
+
# Interactive loop
|
|
602
|
+
while True:
|
|
603
|
+
try:
|
|
604
|
+
# Get user input
|
|
605
|
+
user_input = await asyncio.get_event_loop().run_in_executor(
|
|
606
|
+
None, lambda: input("> ").strip()
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
if user_input.lower() in ("exit", "quit", "q"):
|
|
610
|
+
break
|
|
611
|
+
|
|
612
|
+
if not user_input:
|
|
613
|
+
continue
|
|
614
|
+
|
|
615
|
+
# Send message to agent
|
|
616
|
+
await manager.send_message(user_input)
|
|
617
|
+
|
|
618
|
+
# Receive and display responses
|
|
619
|
+
messages = await manager.receive_messages()
|
|
620
|
+
for message in messages:
|
|
621
|
+
if hasattr(message, "content"):
|
|
622
|
+
print(f"Agent: {message.content}")
|
|
623
|
+
else:
|
|
624
|
+
print(f"Agent: {message}")
|
|
625
|
+
|
|
626
|
+
except KeyboardInterrupt:
|
|
627
|
+
break
|
|
628
|
+
except EOFError:
|
|
629
|
+
break
|
|
630
|
+
|
|
631
|
+
except Exception as e:
|
|
632
|
+
_console.print(f"[red]Connection error: {e}[/red]")
|
|
633
|
+
return 1
|
|
634
|
+
finally:
|
|
635
|
+
await manager.disconnect()
|
|
636
|
+
|
|
637
|
+
_console.print("[green]Disconnected from agent.[/green]")
|
|
638
|
+
return 0
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def check_system_dependencies() -> dict[str, bool]:
|
|
642
|
+
"""Check for common system dependencies required by agents.
|
|
643
|
+
|
|
644
|
+
Returns:
|
|
645
|
+
Dict mapping dependency names to availability status
|
|
646
|
+
"""
|
|
647
|
+
import shutil
|
|
648
|
+
|
|
649
|
+
dependencies = {
|
|
650
|
+
"npm": shutil.which("npm") is not None,
|
|
651
|
+
"node": shutil.which("node") is not None,
|
|
652
|
+
"python": shutil.which("python") is not None or shutil.which("python3") is not None,
|
|
653
|
+
"uv": shutil.which("uv") is not None,
|
|
654
|
+
"pip": shutil.which("pip") is not None or shutil.which("pip3") is not None,
|
|
655
|
+
"curl": shutil.which("curl") is not None,
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return dependencies
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def get_os_command(actions: dict, action_name: str) -> str | None:
|
|
662
|
+
"""Get the appropriate command for the current OS."""
|
|
663
|
+
import platform
|
|
664
|
+
|
|
665
|
+
system = platform.system().lower()
|
|
666
|
+
|
|
667
|
+
# Try OS-specific command first
|
|
668
|
+
os_command = actions.get(system, {}).get("command")
|
|
669
|
+
if os_command:
|
|
670
|
+
return os_command
|
|
671
|
+
|
|
672
|
+
# Fall back to wildcard
|
|
673
|
+
wildcard_command = actions.get("*", {}).get("command")
|
|
674
|
+
if wildcard_command:
|
|
675
|
+
return wildcard_command
|
|
676
|
+
|
|
677
|
+
return None
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def install_system_dependency(dep_name: str) -> bool:
|
|
681
|
+
"""Attempt to install a system dependency.
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
dep_name: Name of the dependency to install
|
|
685
|
+
|
|
686
|
+
Returns:
|
|
687
|
+
True if installation succeeded or was skipped
|
|
688
|
+
"""
|
|
689
|
+
import subprocess
|
|
690
|
+
import platform
|
|
691
|
+
|
|
692
|
+
system = platform.system().lower()
|
|
693
|
+
|
|
694
|
+
install_commands = {
|
|
695
|
+
"uv": {
|
|
696
|
+
"darwin": "curl -LsSf https://astral.sh/uv/install.sh | sh",
|
|
697
|
+
"linux": "curl -LsSf https://astral.sh/uv/install.sh | sh",
|
|
698
|
+
"windows": 'powershell -c "irm https://astral.sh/uv/install.sh | iex"',
|
|
699
|
+
},
|
|
700
|
+
"npm": {
|
|
701
|
+
"darwin": "brew install node",
|
|
702
|
+
"linux": "curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - && sudo apt-get install -y nodejs",
|
|
703
|
+
"windows": "choco install nodejs",
|
|
704
|
+
},
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if dep_name in install_commands and system in install_commands[dep_name]:
|
|
708
|
+
cmd = install_commands[dep_name][system]
|
|
709
|
+
_console.print(f"[cyan]Installing {dep_name}...[/cyan]")
|
|
710
|
+
_console.print(f"[dim]{cmd}[/dim]")
|
|
711
|
+
|
|
712
|
+
try:
|
|
713
|
+
result = subprocess.run(cmd, shell=True, check=True, capture_output=True, text=True)
|
|
714
|
+
_console.print(f"[green]✓ {dep_name} installed successfully[/green]")
|
|
715
|
+
return True
|
|
716
|
+
except subprocess.CalledProcessError as e:
|
|
717
|
+
_console.print(f"[yellow]⚠️ Could not auto-install {dep_name}[/yellow]")
|
|
718
|
+
_console.print(f"[dim]Please install {dep_name} manually[/dim]")
|
|
719
|
+
return False
|
|
720
|
+
|
|
721
|
+
return False
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
def install_agent(agent_identifier: str) -> int:
|
|
725
|
+
"""Install an ACP agent with smart dependency detection.
|
|
726
|
+
|
|
727
|
+
Args:
|
|
728
|
+
agent_identifier: Short name or identity of the agent
|
|
729
|
+
|
|
730
|
+
Returns:
|
|
731
|
+
Exit code
|
|
732
|
+
"""
|
|
733
|
+
import asyncio
|
|
734
|
+
import subprocess
|
|
735
|
+
|
|
736
|
+
async def install_agent_async():
|
|
737
|
+
# Find the agent (include registry)
|
|
738
|
+
from superqode.agents.discovery import (
|
|
739
|
+
get_agent_by_short_name_async,
|
|
740
|
+
get_agent_by_identity_async,
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
agent = await get_agent_by_short_name_async(agent_identifier, include_registry=True)
|
|
744
|
+
if not agent:
|
|
745
|
+
agent = await get_agent_by_identity_async(agent_identifier, include_registry=True)
|
|
746
|
+
|
|
747
|
+
if not agent:
|
|
748
|
+
_console.print(f"[red]Agent '{agent_identifier}' not found.[/red]")
|
|
749
|
+
_console.print("[dim]Use 'superqode agents list' to see available agents.[/dim]")
|
|
750
|
+
return 1
|
|
751
|
+
|
|
752
|
+
# Get available actions
|
|
753
|
+
actions = agent.get("actions", {})
|
|
754
|
+
|
|
755
|
+
# If no actions in agent, try to get from registry
|
|
756
|
+
if not actions:
|
|
757
|
+
from superqode.agents.registry import get_agent_installation_info
|
|
758
|
+
|
|
759
|
+
install_info = get_agent_installation_info(agent)
|
|
760
|
+
command = install_info.get("command", "")
|
|
761
|
+
|
|
762
|
+
if command:
|
|
763
|
+
# Create temporary actions structure
|
|
764
|
+
actions = {
|
|
765
|
+
"*": {
|
|
766
|
+
"install": {
|
|
767
|
+
"command": command,
|
|
768
|
+
"description": install_info.get(
|
|
769
|
+
"description", f"Install {agent['name']}"
|
|
770
|
+
),
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
else:
|
|
775
|
+
_console.print(
|
|
776
|
+
f"[red]No installation actions available for '{agent['name']}'.[/red]"
|
|
777
|
+
)
|
|
778
|
+
_console.print(
|
|
779
|
+
"[dim]Please check the agent's documentation for installation instructions.[/dim]"
|
|
780
|
+
)
|
|
781
|
+
return 1
|
|
782
|
+
|
|
783
|
+
# Pre-flight checks
|
|
784
|
+
_console.print(f"[bold cyan]🚀 Installing {agent['name']}[/bold cyan]")
|
|
785
|
+
_console.print(f"[dim]{agent.get('description', '')}[/dim]")
|
|
786
|
+
_console.print()
|
|
787
|
+
|
|
788
|
+
# Check system dependencies
|
|
789
|
+
deps = check_system_dependencies()
|
|
790
|
+
missing_deps = [dep for dep, available in deps.items() if not available]
|
|
791
|
+
|
|
792
|
+
if missing_deps:
|
|
793
|
+
_console.print("[yellow]⚠️ Missing system dependencies detected:[/yellow]")
|
|
794
|
+
for dep in missing_deps:
|
|
795
|
+
_console.print(f" [red]• {dep}[/red]")
|
|
796
|
+
|
|
797
|
+
# Try to auto-install critical dependencies
|
|
798
|
+
critical_deps = ["uv"] # Add more as needed
|
|
799
|
+
for dep in critical_deps:
|
|
800
|
+
if dep in missing_deps:
|
|
801
|
+
if not install_system_dependency(dep):
|
|
802
|
+
_console.print(
|
|
803
|
+
f"[red]Cannot proceed without {dep}. Please install it manually.[/red]"
|
|
804
|
+
)
|
|
805
|
+
return 1
|
|
806
|
+
|
|
807
|
+
_console.print()
|
|
808
|
+
|
|
809
|
+
# Get the install command for current OS
|
|
810
|
+
install_command = get_os_command(actions, "install")
|
|
811
|
+
if not install_command:
|
|
812
|
+
_console.print("[red]No installation command found for this agent on your OS.[/red]")
|
|
813
|
+
return 1
|
|
814
|
+
|
|
815
|
+
# Check for bootstrap_uv flag
|
|
816
|
+
needs_uv_bootstrap = False
|
|
817
|
+
for os_actions in actions.values():
|
|
818
|
+
if isinstance(os_actions, dict) and os_actions.get("bootstrap_uv", False):
|
|
819
|
+
needs_uv_bootstrap = True
|
|
820
|
+
break
|
|
821
|
+
|
|
822
|
+
# Bootstrap UV if needed
|
|
823
|
+
if needs_uv_bootstrap and not deps.get("uv", False):
|
|
824
|
+
_console.print("[cyan]Bootstrapping UV package manager...[/cyan]")
|
|
825
|
+
if not install_system_dependency("uv"):
|
|
826
|
+
_console.print("[red]UV bootstrap failed. Cannot proceed.[/red]")
|
|
827
|
+
return 1
|
|
828
|
+
|
|
829
|
+
_console.print(f"[green]Installing {agent['name']}...[/green]")
|
|
830
|
+
_console.print(f"[dim]Command: {install_command}[/dim]")
|
|
831
|
+
_console.print()
|
|
832
|
+
|
|
833
|
+
# Run the installation command with progress feedback
|
|
834
|
+
try:
|
|
835
|
+
# Use a more interactive approach for long-running installs
|
|
836
|
+
process = subprocess.Popen(
|
|
837
|
+
install_command,
|
|
838
|
+
shell=True,
|
|
839
|
+
stdout=subprocess.PIPE,
|
|
840
|
+
stderr=subprocess.PIPE,
|
|
841
|
+
text=True,
|
|
842
|
+
bufsize=1,
|
|
843
|
+
universal_newlines=True,
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
# Show progress
|
|
847
|
+
_console.print("[cyan]Installation in progress...[/cyan]")
|
|
848
|
+
|
|
849
|
+
# Wait for completion
|
|
850
|
+
stdout, stderr = process.communicate()
|
|
851
|
+
|
|
852
|
+
if process.returncode == 0:
|
|
853
|
+
_console.print("[green]✓ Installation completed successfully![/green]")
|
|
854
|
+
|
|
855
|
+
# Verify installation
|
|
856
|
+
if check_agent_installed(agent):
|
|
857
|
+
_console.print(f"[green]✓ Agent '{agent['short_name']}' is ready to use![/green]")
|
|
858
|
+
_console.print(f"[dim]Try: superqode agents connect {agent['short_name']}[/dim]")
|
|
859
|
+
else:
|
|
860
|
+
_console.print("[yellow]⚠️ Agent installed but verification failed[/yellow]")
|
|
861
|
+
_console.print(
|
|
862
|
+
f"[dim]You may need to restart your shell or check the installation manually[/dim]"
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
return 0
|
|
866
|
+
else:
|
|
867
|
+
_console.print(f"[red]✗ Installation failed (exit code {process.returncode})[/red]")
|
|
868
|
+
|
|
869
|
+
# Enhanced error analysis
|
|
870
|
+
error_msg = stderr.lower() if stderr else ""
|
|
871
|
+
|
|
872
|
+
if "eacces" in error_msg or "permission denied" in error_msg:
|
|
873
|
+
_console.print("[yellow]💡 Permission Error:[/yellow]")
|
|
874
|
+
_console.print(" This command requires administrator privileges.")
|
|
875
|
+
_console.print(f" Try: [bold]sudo {install_command}[/bold]")
|
|
876
|
+
_console.print(
|
|
877
|
+
" Or use a Node version manager like nvm/fnm for user-space installation"
|
|
878
|
+
)
|
|
879
|
+
elif "command not found" in error_msg or "npm: command not found" in error_msg:
|
|
880
|
+
_console.print("[yellow]💡 Missing npm:[/yellow]")
|
|
881
|
+
_console.print(" Node.js/npm is not installed. Install from:")
|
|
882
|
+
_console.print(" https://nodejs.org/ or use your system package manager")
|
|
883
|
+
elif "uv: command not found" in error_msg:
|
|
884
|
+
_console.print("[yellow]💡 Missing UV:[/yellow]")
|
|
885
|
+
_console.print(" UV package manager is not installed. Install with:")
|
|
886
|
+
_console.print(" curl -LsSf https://astral.sh/uv/install.sh | sh")
|
|
887
|
+
elif "certificate" in error_msg or "ssl" in error_msg:
|
|
888
|
+
_console.print("[yellow]💡 SSL/Certificate Error:[/yellow]")
|
|
889
|
+
_console.print(" There may be network or certificate issues.")
|
|
890
|
+
_console.print(" Try updating your system's CA certificates.")
|
|
891
|
+
else:
|
|
892
|
+
# Show the actual error output
|
|
893
|
+
if stdout.strip():
|
|
894
|
+
_console.print("[dim]Output:[/dim]")
|
|
895
|
+
_console.print(stdout.strip())
|
|
896
|
+
if stderr.strip():
|
|
897
|
+
_console.print("[dim]Error:[/dim]")
|
|
898
|
+
_console.print(stderr.strip())
|
|
899
|
+
|
|
900
|
+
_console.print(
|
|
901
|
+
f"\n[dim]💡 Alternative: Try installing manually with: {install_command}[/dim]"
|
|
902
|
+
)
|
|
903
|
+
return 1
|
|
904
|
+
|
|
905
|
+
except FileNotFoundError:
|
|
906
|
+
_console.print(
|
|
907
|
+
f"[red]Command not found. Please ensure required dependencies are installed.[/red]"
|
|
908
|
+
)
|
|
909
|
+
return 1
|
|
910
|
+
except Exception as e:
|
|
911
|
+
_console.print(f"[red]Installation error: {e}[/red]")
|
|
912
|
+
return 1
|
|
913
|
+
|
|
914
|
+
try:
|
|
915
|
+
return asyncio.run(install_agent_async())
|
|
916
|
+
except RuntimeError as e:
|
|
917
|
+
if "asyncio.run() cannot be called from a running event loop" in str(e):
|
|
918
|
+
# We're already in an async context
|
|
919
|
+
import nest_asyncio
|
|
920
|
+
|
|
921
|
+
nest_asyncio.apply()
|
|
922
|
+
return asyncio.run(install_agent_async())
|
|
923
|
+
else:
|
|
924
|
+
raise
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
# Main command functions for CLI integration
|
|
928
|
+
def list_agents() -> None:
|
|
929
|
+
"""List all available ACP agents."""
|
|
930
|
+
show_agents_list()
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
def show_agent(agent_identifier: str) -> None:
|
|
934
|
+
"""Show details about a specific agent."""
|
|
935
|
+
import asyncio
|
|
936
|
+
|
|
937
|
+
agent = get_agent_by_short_name(agent_identifier)
|
|
938
|
+
if not agent:
|
|
939
|
+
agent = get_agent_by_identity(agent_identifier)
|
|
940
|
+
|
|
941
|
+
if not agent:
|
|
942
|
+
_console.print(f"[red]Agent '{agent_identifier}' not found.[/red]")
|
|
943
|
+
return
|
|
944
|
+
|
|
945
|
+
show_agent_details(agent)
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
def connect_agent(agent_identifier: str, project_dir: str | None = None) -> int:
|
|
949
|
+
"""Connect to an ACP agent (synchronous wrapper)."""
|
|
950
|
+
try:
|
|
951
|
+
return asyncio.run(connect_to_agent(agent_identifier, project_dir))
|
|
952
|
+
except RuntimeError as e:
|
|
953
|
+
if "asyncio.run() cannot be called from a running event loop" in str(e):
|
|
954
|
+
# We're already in an async context, create a new task
|
|
955
|
+
import nest_asyncio
|
|
956
|
+
|
|
957
|
+
nest_asyncio.apply()
|
|
958
|
+
return asyncio.run(connect_to_agent(agent_identifier, project_dir))
|
|
959
|
+
else:
|
|
960
|
+
raise
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def install_agent_cmd(agent_identifier: str) -> int:
|
|
964
|
+
"""Install an ACP agent (synchronous wrapper)."""
|
|
965
|
+
return install_agent(agent_identifier)
|