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,819 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provider CLI commands for SuperQode.
|
|
3
|
+
|
|
4
|
+
Commands for listing, showing, and testing BYOK providers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import os
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
from rich.text import Text
|
|
16
|
+
|
|
17
|
+
from ..providers.registry import (
|
|
18
|
+
PROVIDERS,
|
|
19
|
+
ProviderCategory,
|
|
20
|
+
ProviderTier,
|
|
21
|
+
get_providers_by_category,
|
|
22
|
+
get_providers_by_tier,
|
|
23
|
+
get_free_providers,
|
|
24
|
+
get_local_providers,
|
|
25
|
+
)
|
|
26
|
+
from ..providers.gateway import LiteLLMGateway
|
|
27
|
+
from ..providers.local.mlx import get_mlx_client
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
console = Console()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@click.group()
|
|
34
|
+
def providers():
|
|
35
|
+
"""Manage BYOK (Bring Your Own Key) providers."""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@providers.command("list")
|
|
40
|
+
@click.option(
|
|
41
|
+
"--category",
|
|
42
|
+
type=click.Choice(["us", "china", "other-labs", "model-hosts", "local", "free"]),
|
|
43
|
+
help="Filter by category",
|
|
44
|
+
)
|
|
45
|
+
@click.option(
|
|
46
|
+
"--tier",
|
|
47
|
+
type=click.Choice(["1", "2", "local"]),
|
|
48
|
+
help="Filter by tier",
|
|
49
|
+
)
|
|
50
|
+
@click.option(
|
|
51
|
+
"--configured",
|
|
52
|
+
is_flag=True,
|
|
53
|
+
help="Show only configured providers",
|
|
54
|
+
)
|
|
55
|
+
def list_providers(category: Optional[str], tier: Optional[str], configured: bool):
|
|
56
|
+
"""List available BYOK providers."""
|
|
57
|
+
|
|
58
|
+
# Filter providers
|
|
59
|
+
filtered = dict(PROVIDERS)
|
|
60
|
+
|
|
61
|
+
if category:
|
|
62
|
+
category_map = {
|
|
63
|
+
"us": ProviderCategory.US_LABS,
|
|
64
|
+
"china": ProviderCategory.CHINA_LABS,
|
|
65
|
+
"other-labs": ProviderCategory.OTHER_LABS,
|
|
66
|
+
"model-hosts": ProviderCategory.MODEL_HOSTS,
|
|
67
|
+
"local": ProviderCategory.LOCAL,
|
|
68
|
+
}
|
|
69
|
+
if category == "free":
|
|
70
|
+
# Special case: show providers that have free models configured
|
|
71
|
+
filtered = {k: v for k, v in filtered.items() if v.free_models}
|
|
72
|
+
else:
|
|
73
|
+
cat = category_map.get(category)
|
|
74
|
+
if cat:
|
|
75
|
+
filtered = {k: v for k, v in filtered.items() if v.category == cat}
|
|
76
|
+
|
|
77
|
+
if tier:
|
|
78
|
+
tier_map = {
|
|
79
|
+
"1": ProviderTier.TIER1,
|
|
80
|
+
"2": ProviderTier.TIER2,
|
|
81
|
+
"free": ProviderTier.FREE,
|
|
82
|
+
"local": ProviderTier.LOCAL,
|
|
83
|
+
}
|
|
84
|
+
t = tier_map.get(tier)
|
|
85
|
+
if t:
|
|
86
|
+
filtered = {k: v for k, v in filtered.items() if v.tier == t}
|
|
87
|
+
|
|
88
|
+
# Check configuration status
|
|
89
|
+
provider_status = {}
|
|
90
|
+
for provider_id, provider_def in filtered.items():
|
|
91
|
+
is_configured = False
|
|
92
|
+
|
|
93
|
+
if not provider_def.env_vars:
|
|
94
|
+
# Local provider - check if base URL is accessible
|
|
95
|
+
is_configured = True # Assume local is available
|
|
96
|
+
else:
|
|
97
|
+
# Check if any env var is set
|
|
98
|
+
for env_var in provider_def.env_vars:
|
|
99
|
+
if os.environ.get(env_var):
|
|
100
|
+
is_configured = True
|
|
101
|
+
break
|
|
102
|
+
|
|
103
|
+
provider_status[provider_id] = is_configured
|
|
104
|
+
|
|
105
|
+
if configured:
|
|
106
|
+
filtered = {k: v for k, v in filtered.items() if provider_status.get(k)}
|
|
107
|
+
|
|
108
|
+
# Build table
|
|
109
|
+
table = Table(title="BYOK Providers", show_header=True, header_style="bold cyan")
|
|
110
|
+
table.add_column("Provider", style="white")
|
|
111
|
+
table.add_column("Name", style="white")
|
|
112
|
+
table.add_column("Tier", style="dim")
|
|
113
|
+
table.add_column("Category", style="dim")
|
|
114
|
+
table.add_column("Status", style="white")
|
|
115
|
+
table.add_column("Env Var", style="dim")
|
|
116
|
+
|
|
117
|
+
# Sort by category then tier
|
|
118
|
+
sorted_providers = sorted(
|
|
119
|
+
filtered.items(), key=lambda x: (x[1].category.value, x[1].tier.value, x[0])
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
for provider_id, provider_def in sorted_providers:
|
|
123
|
+
is_configured = provider_status.get(provider_id, False)
|
|
124
|
+
|
|
125
|
+
status = "[green]✅ Configured[/green]" if is_configured else "[red]❌ Not configured[/red]"
|
|
126
|
+
if provider_def.category == ProviderCategory.LOCAL and not provider_def.env_vars:
|
|
127
|
+
status = "[blue]🏠 Local[/blue]"
|
|
128
|
+
|
|
129
|
+
env_var = provider_def.env_vars[0] if provider_def.env_vars else "(none)"
|
|
130
|
+
|
|
131
|
+
tier_str = {
|
|
132
|
+
ProviderTier.TIER1: "Tier 1",
|
|
133
|
+
ProviderTier.TIER2: "Tier 2",
|
|
134
|
+
ProviderTier.FREE: "Free",
|
|
135
|
+
ProviderTier.LOCAL: "Local",
|
|
136
|
+
}.get(provider_def.tier, "")
|
|
137
|
+
|
|
138
|
+
table.add_row(
|
|
139
|
+
provider_id,
|
|
140
|
+
provider_def.name,
|
|
141
|
+
tier_str,
|
|
142
|
+
provider_def.category.value,
|
|
143
|
+
status,
|
|
144
|
+
env_var,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
console.print(table)
|
|
148
|
+
|
|
149
|
+
# Summary
|
|
150
|
+
configured_count = sum(1 for v in provider_status.values() if v)
|
|
151
|
+
console.print(f"\n[dim]Total: {len(filtered)} providers, {configured_count} configured[/dim]")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@providers.command("show")
|
|
155
|
+
@click.argument("provider_id")
|
|
156
|
+
def show_provider(provider_id: str):
|
|
157
|
+
"""Show details for a specific provider."""
|
|
158
|
+
|
|
159
|
+
provider_def = PROVIDERS.get(provider_id)
|
|
160
|
+
|
|
161
|
+
if not provider_def:
|
|
162
|
+
console.print(f"[red]Error: Provider '{provider_id}' not found[/red]")
|
|
163
|
+
console.print("\nAvailable providers:")
|
|
164
|
+
for pid in sorted(PROVIDERS.keys()):
|
|
165
|
+
console.print(f" • {pid}")
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
# Check configuration status
|
|
169
|
+
is_configured = False
|
|
170
|
+
configured_env = None
|
|
171
|
+
|
|
172
|
+
for env_var in provider_def.env_vars:
|
|
173
|
+
if os.environ.get(env_var):
|
|
174
|
+
is_configured = True
|
|
175
|
+
configured_env = env_var
|
|
176
|
+
break
|
|
177
|
+
|
|
178
|
+
# Build info panel
|
|
179
|
+
tier_str = {
|
|
180
|
+
ProviderTier.TIER1: "Tier 1 (First-class support)",
|
|
181
|
+
ProviderTier.TIER2: "Tier 2 (Supported)",
|
|
182
|
+
ProviderTier.FREE: "Free Tier",
|
|
183
|
+
ProviderTier.LOCAL: "Local (Self-hosted)",
|
|
184
|
+
}.get(provider_def.tier, "")
|
|
185
|
+
|
|
186
|
+
status = "[green]✅ Configured[/green]" if is_configured else "[red]❌ Not configured[/red]"
|
|
187
|
+
if provider_def.category == ProviderCategory.LOCAL and not provider_def.env_vars:
|
|
188
|
+
status = "[blue]🏠 Local (no API key needed)[/blue]"
|
|
189
|
+
|
|
190
|
+
info_lines = [
|
|
191
|
+
f"[bold]Provider:[/bold] {provider_def.name}",
|
|
192
|
+
f"[bold]ID:[/bold] {provider_id}",
|
|
193
|
+
f"[bold]Tier:[/bold] {tier_str}",
|
|
194
|
+
f"[bold]Category:[/bold] {provider_def.category.value}",
|
|
195
|
+
f"[bold]Status:[/bold] {status}",
|
|
196
|
+
"",
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
# Environment variables
|
|
200
|
+
if provider_def.env_vars:
|
|
201
|
+
info_lines.append("[bold]Environment Variables:[/bold]")
|
|
202
|
+
for env_var in provider_def.env_vars:
|
|
203
|
+
is_set = bool(os.environ.get(env_var))
|
|
204
|
+
status_icon = "[green]✓[/green]" if is_set else "[red]✗[/red]"
|
|
205
|
+
info_lines.append(f" {status_icon} {env_var}")
|
|
206
|
+
info_lines.append("")
|
|
207
|
+
|
|
208
|
+
if provider_def.optional_env:
|
|
209
|
+
info_lines.append("[bold]Optional Environment Variables:[/bold]")
|
|
210
|
+
for env_var in provider_def.optional_env:
|
|
211
|
+
is_set = bool(os.environ.get(env_var))
|
|
212
|
+
status_icon = "[green]✓[/green]" if is_set else "[dim]○[/dim]"
|
|
213
|
+
info_lines.append(f" {status_icon} {env_var}")
|
|
214
|
+
info_lines.append("")
|
|
215
|
+
|
|
216
|
+
# Base URL
|
|
217
|
+
if provider_def.base_url_env:
|
|
218
|
+
base_url = os.environ.get(
|
|
219
|
+
provider_def.base_url_env, provider_def.default_base_url or "(not set)"
|
|
220
|
+
)
|
|
221
|
+
info_lines.append(f"[bold]Base URL:[/bold] {base_url}")
|
|
222
|
+
info_lines.append(f"[bold]Base URL Env:[/bold] {provider_def.base_url_env}")
|
|
223
|
+
info_lines.append("")
|
|
224
|
+
|
|
225
|
+
# Example models
|
|
226
|
+
if provider_def.example_models:
|
|
227
|
+
info_lines.append("[bold]Example Models:[/bold]")
|
|
228
|
+
for model in provider_def.example_models[:8]:
|
|
229
|
+
info_lines.append(f" • {model}")
|
|
230
|
+
if len(provider_def.example_models) > 8:
|
|
231
|
+
info_lines.append(f" [dim]... and {len(provider_def.example_models) - 8} more[/dim]")
|
|
232
|
+
info_lines.append("")
|
|
233
|
+
|
|
234
|
+
# Free models
|
|
235
|
+
if provider_def.free_models:
|
|
236
|
+
info_lines.append("[bold]Free Models:[/bold]")
|
|
237
|
+
for model in provider_def.free_models:
|
|
238
|
+
info_lines.append(f" • {model}")
|
|
239
|
+
info_lines.append("")
|
|
240
|
+
|
|
241
|
+
# Notes
|
|
242
|
+
if provider_def.notes:
|
|
243
|
+
info_lines.append(f"[bold]Notes:[/bold] {provider_def.notes}")
|
|
244
|
+
info_lines.append("")
|
|
245
|
+
|
|
246
|
+
# Docs
|
|
247
|
+
info_lines.append(f"[bold]Documentation:[/bold] {provider_def.docs_url}")
|
|
248
|
+
|
|
249
|
+
panel = Panel(
|
|
250
|
+
"\n".join(info_lines),
|
|
251
|
+
title=f"Provider: {provider_def.name}",
|
|
252
|
+
border_style="cyan",
|
|
253
|
+
)
|
|
254
|
+
console.print(panel)
|
|
255
|
+
|
|
256
|
+
# Setup instructions if not configured
|
|
257
|
+
if not is_configured and provider_def.env_vars:
|
|
258
|
+
console.print("\n[yellow]To configure this provider:[/yellow]")
|
|
259
|
+
env_var = provider_def.env_vars[0]
|
|
260
|
+
console.print(f' export {env_var}="your-api-key"')
|
|
261
|
+
console.print(f"\n Get your API key at: {provider_def.docs_url}")
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@providers.command("test")
|
|
265
|
+
@click.argument("provider_id")
|
|
266
|
+
@click.option("--model", "-m", help="Model to test with")
|
|
267
|
+
def test_provider(provider_id: str, model: Optional[str]):
|
|
268
|
+
"""Test connection to a provider."""
|
|
269
|
+
|
|
270
|
+
provider_def = PROVIDERS.get(provider_id)
|
|
271
|
+
|
|
272
|
+
if not provider_def:
|
|
273
|
+
console.print(f"[red]Error: Provider '{provider_id}' not found[/red]")
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
# Check if configured
|
|
277
|
+
is_configured = False
|
|
278
|
+
for env_var in provider_def.env_vars:
|
|
279
|
+
if os.environ.get(env_var):
|
|
280
|
+
is_configured = True
|
|
281
|
+
break
|
|
282
|
+
|
|
283
|
+
if not is_configured and provider_def.env_vars:
|
|
284
|
+
console.print(f"[red]Error: Provider '{provider_id}' is not configured[/red]")
|
|
285
|
+
console.print(f"\nSet one of: {', '.join(provider_def.env_vars)}")
|
|
286
|
+
console.print(f"Get your API key at: {provider_def.docs_url}")
|
|
287
|
+
return
|
|
288
|
+
|
|
289
|
+
# Use first example model if not specified
|
|
290
|
+
test_model = model or (provider_def.example_models[0] if provider_def.example_models else None)
|
|
291
|
+
|
|
292
|
+
if not test_model:
|
|
293
|
+
console.print("[red]Error: No model specified and no example models available[/red]")
|
|
294
|
+
return
|
|
295
|
+
|
|
296
|
+
console.print(f"Testing {provider_def.name} with model {test_model}...")
|
|
297
|
+
|
|
298
|
+
async def run_test():
|
|
299
|
+
gateway = LiteLLMGateway()
|
|
300
|
+
return await gateway.test_connection(provider_id, test_model)
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
result = asyncio.run(run_test())
|
|
304
|
+
|
|
305
|
+
if result["success"]:
|
|
306
|
+
console.print(f"\n[green]✅ Success![/green]")
|
|
307
|
+
console.print(f" Provider: {result['provider']}")
|
|
308
|
+
console.print(f" Model: {result.get('response_model', test_model)}")
|
|
309
|
+
if result.get("usage"):
|
|
310
|
+
console.print(f" Tokens used: {result['usage'].get('total_tokens', 'N/A')}")
|
|
311
|
+
else:
|
|
312
|
+
console.print(f"\n[red]❌ Failed[/red]")
|
|
313
|
+
console.print(f" Error: {result.get('error', 'Unknown error')}")
|
|
314
|
+
if result.get("error_type"):
|
|
315
|
+
console.print(f" Type: {result['error_type']}")
|
|
316
|
+
|
|
317
|
+
except Exception as e:
|
|
318
|
+
console.print(f"\n[red]❌ Error: {e}[/red]")
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@providers.command("mlx")
|
|
322
|
+
@click.argument("action", type=click.Choice(["list", "server", "models", "check", "setup"]))
|
|
323
|
+
@click.option("--model", "-m", help="Model for server command")
|
|
324
|
+
@click.option("--host", default="localhost", help="Server host")
|
|
325
|
+
@click.option("--port", default=8080, type=int, help="Server port")
|
|
326
|
+
def mlx_command(action: str, model: Optional[str], host: str, port: int):
|
|
327
|
+
"""Manage MLX (Apple Silicon) models and servers.
|
|
328
|
+
|
|
329
|
+
Actions:
|
|
330
|
+
- list: List available MLX models (cached and server)
|
|
331
|
+
- server: Show command to start MLX server
|
|
332
|
+
- models: Show suggested MLX models
|
|
333
|
+
- check: Check if mlx_lm is installed
|
|
334
|
+
- setup: Complete setup guide for MLX
|
|
335
|
+
"""
|
|
336
|
+
from ..providers.local.mlx import MLXClient
|
|
337
|
+
|
|
338
|
+
if action == "list":
|
|
339
|
+
console.print("[bold]🔍 Discovering MLX models...[/bold]")
|
|
340
|
+
|
|
341
|
+
server_running = False
|
|
342
|
+
server_models = []
|
|
343
|
+
|
|
344
|
+
# Show server models if available
|
|
345
|
+
async def check_server():
|
|
346
|
+
nonlocal server_running, server_models
|
|
347
|
+
try:
|
|
348
|
+
client = await get_mlx_client()
|
|
349
|
+
if client:
|
|
350
|
+
console.print("\n[green]🟢 MLX server running:[/green]")
|
|
351
|
+
models = await client.list_models()
|
|
352
|
+
server_running = True
|
|
353
|
+
server_models = models
|
|
354
|
+
if models:
|
|
355
|
+
for model in models:
|
|
356
|
+
console.print(f" • {model.id} ({model.name})")
|
|
357
|
+
else:
|
|
358
|
+
console.print(" No models loaded")
|
|
359
|
+
else:
|
|
360
|
+
console.print("\n[yellow]🟡 MLX server not running[/yellow]")
|
|
361
|
+
except Exception as e:
|
|
362
|
+
console.print(f"\n[red]❌ Error checking server: {e}[/red]")
|
|
363
|
+
|
|
364
|
+
asyncio.run(check_server())
|
|
365
|
+
|
|
366
|
+
# Show cached models (only supported ones)
|
|
367
|
+
console.print("\n[blue]📦 Supported models in HuggingFace cache:[/blue]")
|
|
368
|
+
cache_models = MLXClient.discover_huggingface_models()
|
|
369
|
+
supported_cache_models = [m for m in cache_models if MLXClient.is_model_supported(m["id"])]
|
|
370
|
+
|
|
371
|
+
if supported_cache_models:
|
|
372
|
+
for model in supported_cache_models:
|
|
373
|
+
size_mb = model["size_bytes"] / (1024 * 1024)
|
|
374
|
+
format_note = ""
|
|
375
|
+
if "mlx" in model["id"].lower():
|
|
376
|
+
format_note = " (MLX format)"
|
|
377
|
+
elif "4bit" in model["id"].lower() or "8bit" in model["id"].lower():
|
|
378
|
+
format_note = " (quantized)"
|
|
379
|
+
console.print(f" • {model['id']} ({size_mb:.1f} MB){format_note}")
|
|
380
|
+
else:
|
|
381
|
+
console.print(" No supported MLX models found in cache")
|
|
382
|
+
|
|
383
|
+
# Show guidance if no server running
|
|
384
|
+
if not server_running:
|
|
385
|
+
console.print(
|
|
386
|
+
"\n[green]✅ Supported formats:[/green] MLX (.npz), safetensors (auto-converted)"
|
|
387
|
+
)
|
|
388
|
+
console.print(
|
|
389
|
+
" [green]✅ Working architectures:[/green] Standard transformers, QWen, Llama, Mistral, Phi"
|
|
390
|
+
)
|
|
391
|
+
console.print(
|
|
392
|
+
" [red]❌ Known issues:[/red] MoE models (Mixtral, some gpt-oss) not supported"
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
if supported_cache_models:
|
|
396
|
+
console.print("\n [green]📦 You have supported models available![/green]")
|
|
397
|
+
console.print(" To start MLX server:")
|
|
398
|
+
console.print(
|
|
399
|
+
" 1. [cyan]superqode providers mlx models[/cyan] - See your cached models"
|
|
400
|
+
)
|
|
401
|
+
console.print(
|
|
402
|
+
" 2. [cyan]superqode providers mlx server --model <model-id>[/cyan] - Start server"
|
|
403
|
+
)
|
|
404
|
+
console.print(" 3. [cyan]superqode connect byok mlx <model-id>[/cyan] - Connect")
|
|
405
|
+
else:
|
|
406
|
+
console.print("\n [yellow]📥 No supported models found in cache[/yellow]")
|
|
407
|
+
console.print(" To get started with MLX:")
|
|
408
|
+
console.print(
|
|
409
|
+
" 1. [cyan]superqode providers mlx setup[/cyan] - Complete setup guide"
|
|
410
|
+
)
|
|
411
|
+
console.print(
|
|
412
|
+
" 2. [cyan]mlx_lm.download mlx-community/Llama-3.2-1B-Instruct-4bit[/cyan] - Download model"
|
|
413
|
+
)
|
|
414
|
+
console.print(
|
|
415
|
+
" 3. [cyan]mlx_lm.server --model mlx-community/Llama-3.2-1B-Instruct-4bit[/cyan] - Start server"
|
|
416
|
+
)
|
|
417
|
+
console.print(
|
|
418
|
+
" 4. [cyan]superqode connect byok mlx mlx-community/Llama-3.2-1B-Instruct-4bit[/cyan] - Connect"
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
elif action == "server":
|
|
422
|
+
if not model:
|
|
423
|
+
console.print("[red]❌ Model required for server command[/red]")
|
|
424
|
+
console.print("Usage: superqode providers mlx server --model <model-id>")
|
|
425
|
+
console.print()
|
|
426
|
+
console.print("[yellow]💡 Get model IDs with:[/yellow]")
|
|
427
|
+
console.print(" [cyan]superqode providers mlx models[/cyan]")
|
|
428
|
+
console.print(" [cyan]superqode providers mlx list[/cyan]")
|
|
429
|
+
return
|
|
430
|
+
|
|
431
|
+
console.print(f"[bold]🚀 MLX Server Setup for {model}:[/bold]")
|
|
432
|
+
console.print()
|
|
433
|
+
|
|
434
|
+
from ..providers.local.mlx import MLXClient
|
|
435
|
+
|
|
436
|
+
cmd_parts = MLXClient.get_server_command(model, host, port)
|
|
437
|
+
cmd_str = " ".join(cmd_parts)
|
|
438
|
+
|
|
439
|
+
console.print("[bold]1. Start the MLX server:[/bold]")
|
|
440
|
+
console.print(f" [cyan]{cmd_str}[/cyan]")
|
|
441
|
+
console.print()
|
|
442
|
+
console.print("[bold]2. In another terminal, verify server is running:[/bold]")
|
|
443
|
+
console.print(f" [cyan]curl http://localhost:{port}/v1/models[/cyan]")
|
|
444
|
+
console.print()
|
|
445
|
+
console.print("[bold]3. Connect in SuperQode:[/bold]")
|
|
446
|
+
console.print(f" [cyan]superqode connect byok mlx {model}[/cyan]")
|
|
447
|
+
console.print()
|
|
448
|
+
console.print("[yellow]💡 Pro tips:[/yellow]")
|
|
449
|
+
console.print(" • Large models (20B+) may take 1-2 minutes to load")
|
|
450
|
+
console.print(" • Keep the server terminal open while using MLX models")
|
|
451
|
+
console.print(" • Use Ctrl+C to stop the server when done")
|
|
452
|
+
|
|
453
|
+
elif action == "models":
|
|
454
|
+
console.print("[bold]💡 Suggested MLX Models:[/bold]")
|
|
455
|
+
console.print(" (Optimized for Apple Silicon - fast inference, low memory usage)")
|
|
456
|
+
console.print()
|
|
457
|
+
|
|
458
|
+
models = MLXClient.suggest_models()
|
|
459
|
+
for i, model_id in enumerate(models, 1):
|
|
460
|
+
console.print(f" {i}. [cyan]{model_id}[/cyan]")
|
|
461
|
+
|
|
462
|
+
console.print()
|
|
463
|
+
console.print("[bold]Quick Start Commands:[/bold]")
|
|
464
|
+
console.print(f" [cyan]mlx_lm.download {models[0]}[/cyan] # Download first model")
|
|
465
|
+
console.print(f" [cyan]mlx_lm.server --model {models[0]}[/cyan] # Start server")
|
|
466
|
+
console.print(f" [cyan]superqode connect byok mlx {models[0]}[/cyan] # Connect")
|
|
467
|
+
console.print()
|
|
468
|
+
console.print("[yellow]💡 Model Recommendations:[/yellow]")
|
|
469
|
+
console.print(" • Start with smaller models for testing (1B-3B parameters)")
|
|
470
|
+
console.print(" • Use 4bit/8bit quantized models for best performance")
|
|
471
|
+
console.print(" • Larger models need more RAM but provide better quality")
|
|
472
|
+
console.print(
|
|
473
|
+
" • [red]⚠️ MLX Limitation:[/red] Only one active request per server instance"
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
elif action == "check":
|
|
477
|
+
|
|
478
|
+
async def check_install():
|
|
479
|
+
installed = await MLXClient.check_mlx_lm_installed()
|
|
480
|
+
if installed:
|
|
481
|
+
console.print("[green]✅ mlx_lm is installed[/green]")
|
|
482
|
+
console.print("Version info:")
|
|
483
|
+
# Try to get version
|
|
484
|
+
import subprocess
|
|
485
|
+
|
|
486
|
+
try:
|
|
487
|
+
result = subprocess.run(
|
|
488
|
+
["mlx_lm.server", "--version"], capture_output=True, text=True, timeout=5
|
|
489
|
+
)
|
|
490
|
+
if result.returncode == 0:
|
|
491
|
+
console.print(f" {result.stdout.strip()}")
|
|
492
|
+
else:
|
|
493
|
+
console.print(" Version check failed")
|
|
494
|
+
except Exception:
|
|
495
|
+
console.print(" Version check failed")
|
|
496
|
+
else:
|
|
497
|
+
console.print("[red]❌ mlx_lm is not installed[/red]")
|
|
498
|
+
console.print()
|
|
499
|
+
console.print("Install with:")
|
|
500
|
+
console.print(" pip install mlx-lm")
|
|
501
|
+
console.print()
|
|
502
|
+
console.print("Or for optional dependency:")
|
|
503
|
+
console.print(" pip install superqode[mlx]")
|
|
504
|
+
|
|
505
|
+
asyncio.run(check_install())
|
|
506
|
+
|
|
507
|
+
elif action == "setup":
|
|
508
|
+
console.print("[bold]🚀 Complete MLX Setup Guide[/bold]")
|
|
509
|
+
console.print()
|
|
510
|
+
|
|
511
|
+
# Supported formats info
|
|
512
|
+
console.print("[green]✅ Supported Formats & Architectures:[/green]")
|
|
513
|
+
console.print(" • [cyan]Formats:[/cyan] MLX (.npz), safetensors (auto-converted)")
|
|
514
|
+
console.print(
|
|
515
|
+
" • [cyan]Architectures:[/cyan] Standard transformers, QWen, Llama, Mistral, Phi"
|
|
516
|
+
)
|
|
517
|
+
console.print(" • [red]Not supported:[/red] MoE models (Mixtral, some gpt-oss variants)")
|
|
518
|
+
console.print()
|
|
519
|
+
|
|
520
|
+
# LM Studio section
|
|
521
|
+
console.print("[bold]🖥️ Alternative: LM Studio (GUI Interface)[/bold]")
|
|
522
|
+
console.print("LM Studio provides a user-friendly GUI for running models locally.")
|
|
523
|
+
console.print()
|
|
524
|
+
console.print("[bold]LM Studio Setup:[/bold]")
|
|
525
|
+
console.print(" 1. [cyan]Download LM Studio:[/cyan] https://lmstudio.ai/")
|
|
526
|
+
console.print(" 2. [cyan]Install and open LM Studio[/cyan]")
|
|
527
|
+
console.print(
|
|
528
|
+
" 3. [cyan]Download a model:[/cyan] Search for models like 'qwen3-30b' or 'llama3.2-3b'"
|
|
529
|
+
)
|
|
530
|
+
console.print(" 4. [cyan]Load the model:[/cyan] Click 'Load Model' in LM Studio")
|
|
531
|
+
console.print(
|
|
532
|
+
" 5. [cyan]Start local server:[/cyan] Go to 'Local Server' tab and click 'Start Server'"
|
|
533
|
+
)
|
|
534
|
+
console.print(" • Default port: 1234")
|
|
535
|
+
console.print(" • Keep LM Studio running in background")
|
|
536
|
+
console.print(" 6. [cyan]Connect in SuperQode:[/cyan] superqode connect byok lmstudio")
|
|
537
|
+
console.print()
|
|
538
|
+
console.print("[yellow]💡 LM Studio Tips:[/yellow]")
|
|
539
|
+
console.print(" • No command-line installation needed")
|
|
540
|
+
console.print(" • GUI shows model loading progress")
|
|
541
|
+
console.print(" • Can test models directly in LM Studio first")
|
|
542
|
+
console.print(" • Server runs on http://localhost:1234/v1/chat/completions")
|
|
543
|
+
console.print()
|
|
544
|
+
|
|
545
|
+
# Check installation
|
|
546
|
+
console.print("[bold]1. Install MLX:[/bold]")
|
|
547
|
+
|
|
548
|
+
async def check_and_guide():
|
|
549
|
+
installed = await MLXClient.check_mlx_lm_installed()
|
|
550
|
+
if installed:
|
|
551
|
+
console.print(" [green]✅ mlx_lm is already installed[/green]")
|
|
552
|
+
else:
|
|
553
|
+
console.print(" [yellow]Install MLX framework:[/yellow]")
|
|
554
|
+
console.print(" [cyan]pip install mlx-lm[/cyan]")
|
|
555
|
+
console.print(" [cyan]# or: pip install superqode[mlx][/cyan]")
|
|
556
|
+
console.print()
|
|
557
|
+
|
|
558
|
+
asyncio.run(check_and_guide())
|
|
559
|
+
|
|
560
|
+
# Show models
|
|
561
|
+
console.print("[bold]2. Choose and Download a Model:[/bold]")
|
|
562
|
+
models = MLXClient.suggest_models()
|
|
563
|
+
console.print(" [yellow]✅ Recommended working models (smallest to largest):[/yellow]")
|
|
564
|
+
for i, model_id in enumerate(models[:6], 1): # Show first 6
|
|
565
|
+
size_indicator = ""
|
|
566
|
+
if "0.6b" in model_id or "1B" in model_id:
|
|
567
|
+
size_indicator = " [green](fast)[/green]"
|
|
568
|
+
elif "3B" in model_id or "7B" in model_id:
|
|
569
|
+
size_indicator = " [yellow](medium)[/yellow]"
|
|
570
|
+
elif "30B" in model_id:
|
|
571
|
+
size_indicator = " [red](large)[/red]"
|
|
572
|
+
console.print(f" {i}. [cyan]{model_id}[/cyan]{size_indicator}")
|
|
573
|
+
console.print()
|
|
574
|
+
console.print(" [yellow]Download a model:[/yellow]")
|
|
575
|
+
console.print(f" [cyan]mlx_lm.download {models[0]}[/cyan] # ~1-2 minutes (small model)")
|
|
576
|
+
console.print(f" [cyan]mlx_lm.download {models[3]}[/cyan] # ~3-5 minutes (medium model)")
|
|
577
|
+
console.print()
|
|
578
|
+
console.print(" [yellow]💡 MLX Limitations:[/yellow]")
|
|
579
|
+
console.print(" • Each model needs its own server instance")
|
|
580
|
+
console.print(" • One server per model for concurrent use")
|
|
581
|
+
console.print(" • Different ports if running multiple servers")
|
|
582
|
+
console.print(" • Only one active request per server instance")
|
|
583
|
+
console.print()
|
|
584
|
+
|
|
585
|
+
# Start server
|
|
586
|
+
console.print("[bold]3. Start the MLX Server:[/bold]")
|
|
587
|
+
console.print(" [yellow]In a separate terminal, run:[/yellow]")
|
|
588
|
+
console.print(f" [cyan]mlx_lm.server --model {models[0]}[/cyan]")
|
|
589
|
+
console.print()
|
|
590
|
+
console.print(" [yellow]Verify server is running:[/yellow]")
|
|
591
|
+
console.print(" [cyan]curl http://localhost:8080/v1/models[/cyan]")
|
|
592
|
+
console.print()
|
|
593
|
+
console.print(
|
|
594
|
+
" [yellow]⚠️ Important:[/yellow] MLX servers handle only ONE request at a time"
|
|
595
|
+
)
|
|
596
|
+
console.print(" • Keep this terminal open while using the model")
|
|
597
|
+
console.print(" • Start separate servers on different ports for concurrent use")
|
|
598
|
+
console.print()
|
|
599
|
+
|
|
600
|
+
# Connect
|
|
601
|
+
console.print("[bold]4. Connect in SuperQode:[/bold]")
|
|
602
|
+
console.print(" [yellow]Open SuperQode and connect:[/yellow]")
|
|
603
|
+
console.print(f" [cyan]superqode connect byok mlx {models[0]}[/cyan]")
|
|
604
|
+
console.print()
|
|
605
|
+
|
|
606
|
+
# Troubleshooting
|
|
607
|
+
console.print("[bold]5. Troubleshooting:[/bold]")
|
|
608
|
+
console.print(" [yellow]If connection fails:[/yellow]")
|
|
609
|
+
console.print(" • Check server is still running in the terminal")
|
|
610
|
+
console.print(" • Large models may take 1-2 minutes to load")
|
|
611
|
+
console.print(" • Try a smaller model first for testing")
|
|
612
|
+
console.print(" • Check RAM usage - MLX needs available memory")
|
|
613
|
+
console.print()
|
|
614
|
+
console.print(" [yellow]Useful commands:[/yellow]")
|
|
615
|
+
console.print(" • [cyan]superqode providers mlx list[/cyan] - See available models")
|
|
616
|
+
console.print(" • [cyan]superqode providers mlx check[/cyan] - Verify installation")
|
|
617
|
+
console.print(" • [cyan]superqode providers mlx models[/cyan] - See all suggestions")
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def connect_provider(provider: Optional[str] = None, model: Optional[str] = None) -> int:
|
|
621
|
+
"""Connect to a BYOK provider/model via CLI.
|
|
622
|
+
|
|
623
|
+
Args:
|
|
624
|
+
provider: Optional provider ID (e.g., 'ollama', 'anthropic')
|
|
625
|
+
model: Optional model ID (e.g., 'llama3.2', 'claude-3-5-sonnet')
|
|
626
|
+
|
|
627
|
+
Returns:
|
|
628
|
+
Exit code (0 for success, 1 for failure)
|
|
629
|
+
"""
|
|
630
|
+
from ..dialogs.provider import ConnectDialog
|
|
631
|
+
from ..providers.manager import ProviderManager
|
|
632
|
+
|
|
633
|
+
manager = ProviderManager()
|
|
634
|
+
|
|
635
|
+
# If both provider and model are provided, try direct connection
|
|
636
|
+
if provider and model:
|
|
637
|
+
# Validate provider exists
|
|
638
|
+
if provider not in PROVIDERS:
|
|
639
|
+
console.print(f"[red]❌ Provider '{provider}' not found.[/red]")
|
|
640
|
+
console.print(f"[dim]Use 'superqode providers list' to see available providers.[/dim]")
|
|
641
|
+
return 1
|
|
642
|
+
|
|
643
|
+
# Test connection
|
|
644
|
+
console.print(f"[cyan]🔍 Testing connection to {provider}/{model}...[/cyan]")
|
|
645
|
+
success, error = manager.test_connection(provider)
|
|
646
|
+
|
|
647
|
+
if not success:
|
|
648
|
+
console.print(f"[red]❌ Connection failed: {error}[/red]")
|
|
649
|
+
provider_def = PROVIDERS[provider]
|
|
650
|
+
if provider_def.env_vars:
|
|
651
|
+
env_var = provider_def.env_vars[0]
|
|
652
|
+
console.print(f"\n[yellow]💡 Set API key:[/yellow]")
|
|
653
|
+
console.print(f"[dim] export {env_var}=your-key[/dim]")
|
|
654
|
+
return 1
|
|
655
|
+
|
|
656
|
+
console.print(f"[green]✓ Connected to {provider}/{model}[/green]")
|
|
657
|
+
console.print(
|
|
658
|
+
"[dim]Note: This is a CLI connection test. For interactive use, run 'superqode' (TUI).[/dim]"
|
|
659
|
+
)
|
|
660
|
+
return 0
|
|
661
|
+
|
|
662
|
+
# If only provider is provided, show model selection dialog
|
|
663
|
+
if provider:
|
|
664
|
+
if provider not in PROVIDERS:
|
|
665
|
+
console.print(f"[red]❌ Provider '{provider}' not found.[/red]")
|
|
666
|
+
console.print(f"[dim]Use 'superqode providers list' to see available providers.[/dim]")
|
|
667
|
+
return 1
|
|
668
|
+
|
|
669
|
+
from ..dialogs.model import ModelDialog
|
|
670
|
+
|
|
671
|
+
dialog = ModelDialog(provider, manager)
|
|
672
|
+
model_id = dialog.show()
|
|
673
|
+
|
|
674
|
+
if model_id:
|
|
675
|
+
console.print(f"[green]✓ Selected: {provider}/{model_id}[/green]")
|
|
676
|
+
console.print(
|
|
677
|
+
"[dim]Note: This is a CLI selection. For interactive use, run 'superqode' (TUI).[/dim]"
|
|
678
|
+
)
|
|
679
|
+
return 0
|
|
680
|
+
else:
|
|
681
|
+
console.print("[yellow]Connection cancelled.[/yellow]")
|
|
682
|
+
return 1
|
|
683
|
+
|
|
684
|
+
# If no provider specified, show full connect dialog
|
|
685
|
+
dialog = ConnectDialog(manager)
|
|
686
|
+
result = dialog.show()
|
|
687
|
+
|
|
688
|
+
if result:
|
|
689
|
+
provider_id, model_id = result
|
|
690
|
+
console.print(f"[green]✓ Connected to {provider_id}/{model_id}[/green]")
|
|
691
|
+
console.print(
|
|
692
|
+
"[dim]Note: This is a CLI selection. For interactive use, run 'superqode' (TUI).[/dim]"
|
|
693
|
+
)
|
|
694
|
+
return 0
|
|
695
|
+
else:
|
|
696
|
+
console.print("[yellow]Connection cancelled.[/yellow]")
|
|
697
|
+
return 1
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def connect_local_provider(provider: Optional[str] = None, model: Optional[str] = None) -> int:
|
|
701
|
+
"""Connect to a local/self-hosted provider/model via CLI.
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
provider: Optional provider ID (e.g., 'ollama', 'lmstudio', 'mlx')
|
|
705
|
+
model: Optional model ID (e.g., 'llama3.2', 'qwen3-30b')
|
|
706
|
+
|
|
707
|
+
Returns:
|
|
708
|
+
Exit code (0 for success, 1 for failure)
|
|
709
|
+
"""
|
|
710
|
+
from ..providers.manager import ProviderManager
|
|
711
|
+
from ..providers.registry import get_local_providers, ProviderCategory
|
|
712
|
+
|
|
713
|
+
manager = ProviderManager()
|
|
714
|
+
local_providers = get_local_providers()
|
|
715
|
+
|
|
716
|
+
# If both provider and model are provided, try direct connection
|
|
717
|
+
if provider and model:
|
|
718
|
+
# Validate provider exists and is local
|
|
719
|
+
if provider not in PROVIDERS:
|
|
720
|
+
console.print(f"[red]❌ Provider '{provider}' not found.[/red]")
|
|
721
|
+
console.print(f"[dim]Use 'superqode providers list' to see available providers.[/dim]")
|
|
722
|
+
return 1
|
|
723
|
+
|
|
724
|
+
provider_def = PROVIDERS[provider]
|
|
725
|
+
if provider_def.category != ProviderCategory.LOCAL:
|
|
726
|
+
console.print(f"[red]❌ Provider '{provider}' is not a local provider.[/red]")
|
|
727
|
+
console.print(
|
|
728
|
+
f"[dim]Use 'superqode connect byok {provider}/{model}' for cloud providers.[/dim]"
|
|
729
|
+
)
|
|
730
|
+
console.print(
|
|
731
|
+
f"[dim]Use 'superqode connect local' to see available local providers.[/dim]"
|
|
732
|
+
)
|
|
733
|
+
return 1
|
|
734
|
+
|
|
735
|
+
# Test connection (local providers don't need API keys)
|
|
736
|
+
console.print(f"[cyan]🔍 Testing connection to local {provider}/{model}...[/cyan]")
|
|
737
|
+
success, error = manager.test_connection(provider)
|
|
738
|
+
|
|
739
|
+
if not success:
|
|
740
|
+
console.print(f"[red]❌ Connection failed: {error}[/red]")
|
|
741
|
+
if provider == "ollama":
|
|
742
|
+
console.print(f"\n[yellow]💡 Make sure Ollama is running:[/yellow]")
|
|
743
|
+
console.print(f"[dim] 1. Start Ollama: ollama serve[/dim]")
|
|
744
|
+
console.print(f"[dim] 2. Verify model: ollama list | grep {model}[/dim]")
|
|
745
|
+
console.print(f"[dim] 3. Pull if needed: ollama pull {model}[/dim]")
|
|
746
|
+
elif provider == "mlx":
|
|
747
|
+
console.print(f"\n[yellow]💡 MLX requires a running server:[/yellow]")
|
|
748
|
+
console.print(f"[dim] Run: superqode providers mlx server --model {model}[/dim]")
|
|
749
|
+
elif provider == "lmstudio":
|
|
750
|
+
console.print(f"\n[yellow]💡 LM Studio requires the GUI application:[/yellow]")
|
|
751
|
+
console.print(f"[dim] 1. Download: https://lmstudio.ai/[/dim]")
|
|
752
|
+
console.print(f"[dim] 2. Load model in LM Studio[/dim]")
|
|
753
|
+
console.print(f"[dim] 3. Start Local Server[/dim]")
|
|
754
|
+
return 1
|
|
755
|
+
|
|
756
|
+
console.print(f"[green]✓ Connected to local {provider}/{model}[/green]")
|
|
757
|
+
console.print(
|
|
758
|
+
"[dim]Note: This is a CLI connection test. For interactive use, run 'superqode' (TUI).[/dim]"
|
|
759
|
+
)
|
|
760
|
+
return 0
|
|
761
|
+
|
|
762
|
+
# If only provider is provided, show model selection
|
|
763
|
+
if provider:
|
|
764
|
+
if provider not in PROVIDERS:
|
|
765
|
+
console.print(f"[red]❌ Provider '{provider}' not found.[/red]")
|
|
766
|
+
console.print(f"[dim]Use 'superqode providers list' to see available providers.[/dim]")
|
|
767
|
+
return 1
|
|
768
|
+
|
|
769
|
+
provider_def = PROVIDERS[provider]
|
|
770
|
+
if provider_def.category != ProviderCategory.LOCAL:
|
|
771
|
+
console.print(f"[red]❌ Provider '{provider}' is not a local provider.[/red]")
|
|
772
|
+
console.print(
|
|
773
|
+
f"[dim]Use 'superqode connect byok {provider}' for cloud providers.[/dim]"
|
|
774
|
+
)
|
|
775
|
+
return 1
|
|
776
|
+
|
|
777
|
+
from ..dialogs.model import ModelDialog
|
|
778
|
+
|
|
779
|
+
dialog = ModelDialog(provider, manager)
|
|
780
|
+
model_id = dialog.show()
|
|
781
|
+
|
|
782
|
+
if model_id:
|
|
783
|
+
console.print(f"[green]✓ Selected: {provider}/{model_id}[/green]")
|
|
784
|
+
console.print(
|
|
785
|
+
"[dim]Note: This is a CLI selection. For interactive use, run 'superqode' (TUI).[/dim]"
|
|
786
|
+
)
|
|
787
|
+
return 0
|
|
788
|
+
else:
|
|
789
|
+
console.print("[yellow]Connection cancelled.[/yellow]")
|
|
790
|
+
return 1
|
|
791
|
+
|
|
792
|
+
# If no provider specified, show local providers
|
|
793
|
+
if not local_providers:
|
|
794
|
+
console.print("[yellow]⚠️ No local providers configured.[/yellow]")
|
|
795
|
+
console.print("[dim]Local providers include: ollama, lmstudio, mlx, vllm, etc.[/dim]")
|
|
796
|
+
return 1
|
|
797
|
+
|
|
798
|
+
console.print("\n[bold cyan]💻 Local Providers[/bold cyan]\n")
|
|
799
|
+
console.print("[dim]No API key required - these run on your machine[/dim]\n")
|
|
800
|
+
|
|
801
|
+
for idx, (provider_id, provider_def) in enumerate(local_providers.items(), 1):
|
|
802
|
+
console.print(f" [{idx}] [cyan]{provider_def.name}[/cyan] ({provider_id})")
|
|
803
|
+
console.print(f" {provider_def.description}")
|
|
804
|
+
if provider_def.example_models:
|
|
805
|
+
example = provider_def.example_models[0]
|
|
806
|
+
console.print(
|
|
807
|
+
f" Example: [dim]superqode connect local {provider_id}/{example}[/dim]"
|
|
808
|
+
)
|
|
809
|
+
console.print()
|
|
810
|
+
|
|
811
|
+
console.print("[dim]💡 Use: superqode connect local <provider>[/<model>][/dim]")
|
|
812
|
+
console.print("[dim] Example: superqode connect local ollama/llama3.2[/dim]")
|
|
813
|
+
return 0
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
# Register with main CLI
|
|
817
|
+
def register_commands(cli):
|
|
818
|
+
"""Register provider commands with the main CLI."""
|
|
819
|
+
cli.add_command(providers)
|