superqode 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,812 @@
1
+ """Configuration loader for SuperQode."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Dict, Any, Optional, List
6
+ import yaml
7
+
8
+ from .schema import (
9
+ Config,
10
+ SuperQodeConfig,
11
+ TeamConfig,
12
+ ModeConfig,
13
+ RoleConfig,
14
+ ProviderConfig,
15
+ ResolvedMode,
16
+ ResolvedRole,
17
+ MCPServerConfigYAML,
18
+ HandoffConfig,
19
+ CrossValidationConfig,
20
+ AgentConfigBlock,
21
+ GatewayConfig,
22
+ CostTrackingConfig,
23
+ ErrorConfig,
24
+ )
25
+
26
+
27
+ class ConfigError(Exception):
28
+ """Configuration loading error."""
29
+
30
+ pass
31
+
32
+
33
+ def find_config_file() -> Optional[Path]:
34
+ """Find the superqode.yaml configuration file."""
35
+ # Check current directory first
36
+ config_path = Path.cwd() / "superqode.yaml"
37
+ if config_path.exists():
38
+ return config_path
39
+
40
+ # Check user home directory
41
+ home_config = Path.home() / ".superqode.yaml"
42
+ if home_config.exists():
43
+ return home_config
44
+
45
+ # Check system config directory
46
+ system_config = Path("/etc/superqode/superqode.yaml")
47
+ if system_config.exists():
48
+ return system_config
49
+
50
+ return None
51
+
52
+
53
+ def load_config_from_file(config_path: Optional[Path] = None) -> Dict[str, Any]:
54
+ """Load configuration from YAML file."""
55
+ if config_path is None:
56
+ config_path = find_config_file()
57
+
58
+ if config_path is None or not config_path.exists():
59
+ # Return default empty config
60
+ return {}
61
+
62
+ try:
63
+ with open(config_path, "r", encoding="utf-8") as f:
64
+ return yaml.safe_load(f) or {}
65
+ except Exception as e:
66
+ raise ConfigError(f"Failed to load config from {config_path}: {e}")
67
+
68
+
69
+ def parse_provider_config(data: Dict[str, Any]) -> ProviderConfig:
70
+ """Parse provider configuration."""
71
+ return ProviderConfig(
72
+ api_key_env=data.get("api_key_env", data.get("api_key", "")),
73
+ description=data.get("description", ""),
74
+ base_url=data.get("base_url", data.get("endpoint")),
75
+ recommended_models=data.get("recommended_models", data.get("models", [])),
76
+ custom_models_allowed=data.get("custom_models_allowed", True),
77
+ )
78
+
79
+
80
+ def parse_handoff_config(data: Dict[str, Any]) -> HandoffConfig:
81
+ """Parse handoff configuration."""
82
+ return HandoffConfig(
83
+ to=data.get("to", ""),
84
+ when=data.get("when", "task_complete"),
85
+ include=data.get("include", ["summary", "files_modified"]),
86
+ )
87
+
88
+
89
+ def parse_cross_validation_config(data: Dict[str, Any]) -> CrossValidationConfig:
90
+ """Parse cross-validation configuration."""
91
+ return CrossValidationConfig(
92
+ enabled=data.get("enabled", True),
93
+ exclude_same_model=data.get("exclude_same_model", True),
94
+ exclude_same_provider=data.get("exclude_same_provider", False),
95
+ )
96
+
97
+
98
+ def parse_agent_config_block(data: Dict[str, Any]) -> AgentConfigBlock:
99
+ """Parse agent config block for ACP mode."""
100
+ return AgentConfigBlock(
101
+ provider=data.get("provider"),
102
+ model=data.get("model"),
103
+ )
104
+
105
+
106
+ def parse_role_config(data: Dict[str, Any]) -> RoleConfig:
107
+ """Parse role configuration.
108
+
109
+ Supports three execution modes:
110
+ - BYOK (mode="byok"): Direct LLM API calls via gateway
111
+ - ACP (mode="acp"): Full coding agent via Agent Client Protocol
112
+ - LOCAL (mode="local"): Local/self-hosted models (no API keys)
113
+ """
114
+ handoff = None
115
+ if "handoff" in data:
116
+ handoff = parse_handoff_config(data["handoff"])
117
+
118
+ cross_validation = None
119
+ if "cross_validation" in data:
120
+ cross_validation = parse_cross_validation_config(data["cross_validation"])
121
+
122
+ # Parse agent_config if present
123
+ agent_config = None
124
+ if "agent_config" in data:
125
+ agent_config = parse_agent_config_block(data["agent_config"])
126
+
127
+ # Determine execution mode
128
+ # Explicit mode takes precedence
129
+ mode = data.get("mode", "").lower()
130
+
131
+ # If no explicit mode, infer from config
132
+ if not mode:
133
+ # New explicit 'agent' field = ACP
134
+ if data.get("agent"):
135
+ mode = "acp"
136
+ # Legacy: coding_agent that's a known ACP agent = ACP
137
+ elif data.get("coding_agent") and data.get("coding_agent") not in ("superqode", "byok"):
138
+ # If coding_agent is set to something like "opencode", "claude-code", etc.
139
+ # This is the OLD way of specifying ACP agents
140
+ mode = "acp"
141
+ # Has provider but no coding_agent = BYOK
142
+ elif data.get("provider") and not data.get("coding_agent"):
143
+ mode = "byok"
144
+ # Default to BYOK only if explicitly superqode
145
+ elif data.get("coding_agent") == "superqode":
146
+ mode = "byok"
147
+ else:
148
+ # Default: if coding_agent is set to an agent name, it's ACP
149
+ mode = "acp" if data.get("coding_agent") else "byok"
150
+
151
+ # Parse expert prompt configuration
152
+ expert_prompt_enabled = data.get("expert_prompt_enabled", False) # Default: OSS off
153
+ expert_prompt = data.get("expert_prompt", None) # Optional override
154
+
155
+ # Validate mode - accept "local" as well
156
+ valid_mode = mode if mode in ("byok", "acp", "local") else "byok"
157
+
158
+ # If mode is "local", validate provider is local
159
+ if valid_mode == "local":
160
+ from ..providers.registry import PROVIDERS, ProviderCategory
161
+
162
+ provider_id = data.get("provider")
163
+ if provider_id:
164
+ provider_def = PROVIDERS.get(provider_id)
165
+ if provider_def and provider_def.category != ProviderCategory.LOCAL:
166
+ # Provider is not local, fall back to byok
167
+ valid_mode = "byok"
168
+
169
+ return RoleConfig(
170
+ description=data.get("description", ""),
171
+ mode=valid_mode,
172
+ # BYOK/LOCAL mode settings
173
+ provider=data.get("provider"),
174
+ model=data.get("model"),
175
+ # ACP mode settings
176
+ agent=data.get("agent"),
177
+ agent_config=agent_config,
178
+ # Common settings
179
+ job_description=data.get("job_description", data.get("persona", "")),
180
+ enabled=data.get("enabled", True),
181
+ mcp_servers=data.get("mcp_servers", data.get("mcp", [])),
182
+ handoff=handoff,
183
+ cross_validation=cross_validation,
184
+ # Expert prompt configuration
185
+ expert_prompt_enabled=expert_prompt_enabled,
186
+ expert_prompt=expert_prompt,
187
+ # Legacy field (for backward compatibility) - use 'agent' if 'coding_agent' not set
188
+ coding_agent=data.get("coding_agent") or data.get("agent") or "superqode",
189
+ )
190
+
191
+
192
+ def parse_mode_config(data: Dict[str, Any]) -> ModeConfig:
193
+ """Parse mode configuration."""
194
+ mode = ModeConfig(
195
+ description=data.get("description", ""),
196
+ enabled=data.get("enabled", True),
197
+ mcp_servers=data.get("mcp_servers", []),
198
+ )
199
+
200
+ # Handle roles if present
201
+ if "roles" in data:
202
+ for role_name, role_data in data["roles"].items():
203
+ mode.roles[role_name] = parse_role_config(role_data)
204
+ else:
205
+ # Direct mode configuration
206
+ mode.coding_agent = data.get("coding_agent")
207
+ mode.provider = data.get("provider")
208
+ mode.model = data.get("model")
209
+ mode.job_description = data.get("job_description")
210
+
211
+ return mode
212
+
213
+
214
+ def parse_mcp_server_config(data: Dict[str, Any]) -> MCPServerConfigYAML:
215
+ """Parse MCP server configuration from YAML."""
216
+ return MCPServerConfigYAML(
217
+ transport=data.get("transport", "stdio"),
218
+ enabled=data.get("enabled", not data.get("disabled", False)),
219
+ auto_connect=data.get("auto_connect", data.get("autoConnect", True)),
220
+ command=data.get("command"),
221
+ args=data.get("args", []),
222
+ env=data.get("env", {}),
223
+ cwd=data.get("cwd"),
224
+ url=data.get("url"),
225
+ headers=data.get("headers", {}),
226
+ timeout=data.get("timeout", 30.0),
227
+ )
228
+
229
+
230
+ def parse_gateway_config(data: Dict[str, Any]) -> GatewayConfig:
231
+ """Parse gateway configuration."""
232
+ return GatewayConfig(
233
+ type=data.get("type", "litellm"),
234
+ )
235
+
236
+
237
+ def parse_cost_tracking_config(data: Dict[str, Any]) -> CostTrackingConfig:
238
+ """Parse cost tracking configuration."""
239
+ return CostTrackingConfig(
240
+ enabled=data.get("enabled", True),
241
+ show_after_task=data.get("show_after_task", True),
242
+ )
243
+
244
+
245
+ def parse_error_config(data: Dict[str, Any]) -> ErrorConfig:
246
+ """Parse error handling configuration."""
247
+ return ErrorConfig(
248
+ surface_rate_limits=data.get("surface_rate_limits", True),
249
+ surface_auth_errors=data.get("surface_auth_errors", True),
250
+ )
251
+
252
+
253
+ def parse_config(data: Dict[str, Any]) -> Config:
254
+ """Parse the complete configuration."""
255
+ config = Config()
256
+
257
+ # Parse superqode metadata
258
+ if "superqode" in data:
259
+ sq_data = data["superqode"]
260
+ config.superqode = SuperQodeConfig(
261
+ version=sq_data.get("version", "1.0"),
262
+ team_name=sq_data.get("team_name", sq_data.get("name", "My Development Team")),
263
+ description=sq_data.get("description", "Multi-agent software development team"),
264
+ )
265
+
266
+ # Parse gateway config
267
+ if "gateway" in sq_data:
268
+ config.superqode.gateway = parse_gateway_config(sq_data["gateway"])
269
+
270
+ # Parse cost tracking config
271
+ if "cost_tracking" in sq_data:
272
+ config.superqode.cost_tracking = parse_cost_tracking_config(sq_data["cost_tracking"])
273
+
274
+ # Parse error config
275
+ if "errors" in sq_data:
276
+ config.superqode.errors = parse_error_config(sq_data["errors"])
277
+
278
+ # Parse default configuration
279
+ if "default" in data:
280
+ config.default = parse_role_config(data["default"])
281
+
282
+ # Parse team configuration
283
+ if "team" in data:
284
+ team_data = data["team"]
285
+
286
+ # Handle both flat structure (mode_name: config) and nested structure (modes: {mode_name: config})
287
+ if "modes" in team_data and isinstance(team_data["modes"], dict):
288
+ # Nested structure: team.modes.{mode_name}
289
+ for mode_name, mode_data in team_data["modes"].items():
290
+ config.team.modes[mode_name] = parse_mode_config(mode_data)
291
+ else:
292
+ # Flat structure: team.{mode_name}
293
+ for mode_name, mode_data in team_data.items():
294
+ if mode_name != "modes": # Skip the nested modes key if it exists
295
+ config.team.modes[mode_name] = parse_mode_config(mode_data)
296
+
297
+ # Parse providers
298
+ if "providers" in data:
299
+ for provider_name, provider_data in data["providers"].items():
300
+ config.providers[provider_name] = parse_provider_config(provider_data)
301
+
302
+ # Parse MCP servers
303
+ if "mcp_servers" in data:
304
+ for server_id, server_data in data["mcp_servers"].items():
305
+ config.mcp_servers[server_id] = parse_mcp_server_config(server_data)
306
+ # Also support mcpServers format (for compatibility)
307
+ if "mcpServers" in data:
308
+ for server_id, server_data in data["mcpServers"].items():
309
+ config.mcp_servers[server_id] = parse_mcp_server_config(server_data)
310
+
311
+ # Parse other sections
312
+ config.agents = data.get("agents", {})
313
+ config.code_agents = data.get("code_agents", [])
314
+ config.custom_models = data.get("custom_models", {})
315
+ config.model_aliases = data.get("model_aliases", {})
316
+ config.legacy = data.get("legacy", {})
317
+
318
+ return config
319
+
320
+
321
+ def resolve_model_spec(model_spec: str, config: Config) -> tuple[str, str]:
322
+ """Resolve a model specification to (provider, model) tuple."""
323
+ # Check aliases first
324
+ if model_spec in config.model_aliases:
325
+ model_spec = config.model_aliases[model_spec]
326
+
327
+ # Check custom models
328
+ if model_spec in config.custom_models:
329
+ custom_def = config.custom_models[model_spec]
330
+ return custom_def["provider"], custom_def["model"]
331
+
332
+ # Auto-detect provider from model name patterns
333
+ provider_patterns = {
334
+ "claude-": "anthropic",
335
+ "gpt-": "openai",
336
+ "gemini-": "google",
337
+ "glm-": "zhipuai",
338
+ "DeepSeek-": "deepseek",
339
+ }
340
+
341
+ for pattern, provider in provider_patterns.items():
342
+ if model_spec.startswith(pattern):
343
+ return provider, model_spec
344
+
345
+ # Check for Ollama model patterns (name:tag format)
346
+ if ":" in model_spec and not model_spec.startswith(("http://", "https://")):
347
+ # Common Ollama model patterns
348
+ ollama_indicators = [
349
+ "llama",
350
+ "mistral",
351
+ "codellama",
352
+ "code",
353
+ "qwen",
354
+ "phi",
355
+ "gemma",
356
+ "orca",
357
+ "vicuna",
358
+ "wizard",
359
+ "openchat",
360
+ "zephyr",
361
+ "neural",
362
+ "dolphin",
363
+ ]
364
+ model_base = model_spec.split(":")[0].lower()
365
+
366
+ # Check if it matches known Ollama model patterns
367
+ if any(indicator in model_base for indicator in ollama_indicators):
368
+ return "ollama", model_spec
369
+
370
+ # If it has the name:tag format and no other provider matches, assume Ollama
371
+ # This covers custom or less common models that follow Ollama naming
372
+ return "ollama", model_spec
373
+
374
+ # Default to unknown provider
375
+ return "unknown", model_spec
376
+
377
+
378
+ def resolve_role(
379
+ mode_name: str, role_name: Optional[str], config: Config
380
+ ) -> Optional[ResolvedRole]:
381
+ """Resolve a mode/role combination to a ResolvedRole."""
382
+ if mode_name not in config.team.modes:
383
+ return None
384
+
385
+ mode_config = config.team.modes[mode_name]
386
+
387
+ if not mode_config.enabled:
388
+ return None
389
+
390
+ if role_name:
391
+ # Hierarchical mode with specific role
392
+ if role_name not in mode_config.roles:
393
+ return None
394
+
395
+ role_config = mode_config.roles[role_name]
396
+ if not role_config.enabled:
397
+ return None
398
+
399
+ # Resolve provider/model if needed
400
+ provider = role_config.provider
401
+ model = role_config.model
402
+
403
+ if role_config.coding_agent == "superqode" and (not provider or not model):
404
+ # Use default if not specified
405
+ if config.default:
406
+ provider = provider or config.default.provider
407
+ model = model or config.default.model
408
+
409
+ # Determine agent_type based on execution mode
410
+ execution_mode = role_config.mode
411
+ if execution_mode == "acp":
412
+ agent_type = "acp"
413
+ elif execution_mode == "local":
414
+ # Local mode uses BYOK execution but with local provider identification
415
+ agent_type = "byok"
416
+ elif execution_mode == "byok":
417
+ agent_type = "byok"
418
+ else:
419
+ # Legacy fallback
420
+ agent_type = "acp" if role_config.coding_agent != "superqode" else "superqode"
421
+
422
+ # Combine MCP servers from mode and role
423
+ mcp_servers = list(mode_config.mcp_servers) + list(role_config.mcp_servers)
424
+
425
+ return ResolvedRole(
426
+ mode=mode_name,
427
+ role=role_name,
428
+ description=role_config.description,
429
+ coding_agent=role_config.coding_agent,
430
+ agent_type=agent_type,
431
+ provider=provider,
432
+ model=model,
433
+ job_description=role_config.job_description,
434
+ enabled=role_config.enabled,
435
+ mcp_servers=mcp_servers,
436
+ # New execution mode fields
437
+ execution_mode=execution_mode if execution_mode in ("byok", "acp", "local") else "byok",
438
+ agent_id=role_config.agent,
439
+ agent_config=role_config.agent_config,
440
+ # Expert prompt configuration
441
+ expert_prompt_enabled=role_config.expert_prompt_enabled,
442
+ expert_prompt=role_config.expert_prompt,
443
+ )
444
+ else:
445
+ # Direct mode
446
+ if not mode_config.coding_agent:
447
+ return None
448
+
449
+ provider = mode_config.provider
450
+ model = mode_config.model
451
+
452
+ if mode_config.coding_agent == "superqode" and (not provider or not model):
453
+ # Use default if not specified
454
+ if config.default:
455
+ provider = provider or config.default.provider
456
+ model = model or config.default.model
457
+
458
+ agent_type = "acp" if mode_config.coding_agent != "superqode" else "superqode"
459
+
460
+ return ResolvedRole(
461
+ mode=mode_name,
462
+ role=None,
463
+ description=mode_config.description,
464
+ coding_agent=mode_config.coding_agent,
465
+ agent_type=agent_type,
466
+ provider=provider,
467
+ model=model,
468
+ job_description=mode_config.job_description or "",
469
+ enabled=mode_config.enabled,
470
+ mcp_servers=list(mode_config.mcp_servers),
471
+ # Default to byok for direct modes
472
+ execution_mode="byok",
473
+ # Expert prompts not applicable for direct modes (no role)
474
+ expert_prompt_enabled=False,
475
+ expert_prompt=None,
476
+ )
477
+
478
+
479
+ def load_enabled_modes(config: Optional[Config] = None) -> Dict[str, ResolvedMode]:
480
+ """Load only enabled modes and roles from configuration."""
481
+ if config is None:
482
+ config = load_config()
483
+
484
+ enabled_modes = {}
485
+
486
+ for mode_name, mode_config in config.team.modes.items():
487
+ if not mode_config.enabled:
488
+ continue
489
+
490
+ resolved_mode = ResolvedMode(
491
+ name=mode_name,
492
+ description=mode_config.description,
493
+ enabled=mode_config.enabled,
494
+ )
495
+
496
+ if mode_config.roles:
497
+ # Hierarchical mode - load enabled roles
498
+ for role_name, role_config in mode_config.roles.items():
499
+ resolved_role = resolve_role(mode_name, role_name, config)
500
+ if resolved_role:
501
+ resolved_mode.roles[role_name] = resolved_role
502
+ else:
503
+ # Direct mode
504
+ resolved_role = resolve_role(mode_name, None, config)
505
+ if resolved_role:
506
+ resolved_mode.direct_role = resolved_role
507
+
508
+ # Only include modes that have at least one enabled role
509
+ if resolved_mode.roles or resolved_mode.direct_role:
510
+ enabled_modes[mode_name] = resolved_mode
511
+
512
+ return enabled_modes
513
+
514
+
515
+ def load_config(config_path: Optional[Path] = None) -> Config:
516
+ """Load and parse the complete SuperQode configuration."""
517
+ try:
518
+ data = load_config_from_file(config_path)
519
+ return parse_config(data)
520
+ except Exception as e:
521
+ raise ConfigError(f"Failed to load configuration: {e}")
522
+
523
+
524
+ def save_config(config: Config, config_path: Optional[Path] = None) -> None:
525
+ """Save configuration to YAML file."""
526
+ if config_path is None:
527
+ config_path = find_config_file() or Path.cwd() / "superqode.yaml"
528
+
529
+ # Convert config back to dict for YAML serialization
530
+ config_dict = {
531
+ "superqode": {
532
+ "version": config.superqode.version,
533
+ "team_name": config.superqode.team_name,
534
+ "description": config.superqode.description,
535
+ }
536
+ }
537
+
538
+ if config.default:
539
+ config_dict["default"] = {
540
+ "description": config.default.description,
541
+ "coding_agent": config.default.coding_agent,
542
+ "provider": config.default.provider,
543
+ "model": config.default.model,
544
+ "job_description": config.default.job_description,
545
+ "enabled": config.default.enabled,
546
+ }
547
+
548
+ if config.team.modes:
549
+ config_dict["team"] = {}
550
+ for mode_name, mode_config in config.team.modes.items():
551
+ mode_dict = {
552
+ "description": mode_config.description,
553
+ "enabled": mode_config.enabled,
554
+ }
555
+
556
+ if mode_config.roles:
557
+ mode_dict["roles"] = {}
558
+ for role_name, role_config in mode_config.roles.items():
559
+ mode_dict["roles"][role_name] = {
560
+ "description": role_config.description,
561
+ "mode": role_config.mode,
562
+ "coding_agent": role_config.coding_agent,
563
+ "provider": role_config.provider,
564
+ "model": role_config.model,
565
+ "job_description": role_config.job_description,
566
+ "enabled": role_config.enabled,
567
+ }
568
+ elif mode_config.coding_agent:
569
+ mode_dict["coding_agent"] = mode_config.coding_agent
570
+ mode_dict["provider"] = mode_config.provider
571
+ mode_dict["model"] = mode_config.model
572
+ mode_dict["job_description"] = mode_config.job_description
573
+
574
+ config_dict["team"][mode_name] = mode_dict
575
+
576
+ if config.providers:
577
+ config_dict["providers"] = {}
578
+ for provider_name, provider_config in config.providers.items():
579
+ config_dict["providers"][provider_name] = {
580
+ "api_key_env": provider_config.api_key_env,
581
+ "description": provider_config.description,
582
+ "base_url": provider_config.base_url,
583
+ "recommended_models": provider_config.recommended_models,
584
+ "custom_models_allowed": provider_config.custom_models_allowed,
585
+ }
586
+
587
+ # Add other sections
588
+ if config.agents:
589
+ config_dict["agents"] = config.agents
590
+ if config.code_agents:
591
+ config_dict["code_agents"] = config.code_agents
592
+ if config.custom_models:
593
+ config_dict["custom_models"] = config.custom_models
594
+ if config.model_aliases:
595
+ config_dict["model_aliases"] = config.model_aliases
596
+ if config.legacy:
597
+ config_dict["legacy"] = config.legacy
598
+
599
+ try:
600
+ with open(config_path, "w", encoding="utf-8") as f:
601
+ yaml.safe_dump(config_dict, f, default_flow_style=False, sort_keys=False)
602
+ except Exception as e:
603
+ raise ConfigError(f"Failed to save configuration: {e}")
604
+
605
+
606
+ def create_default_config() -> Config:
607
+ """Create a default configuration with simplified ACP-only setup."""
608
+ config = Config()
609
+
610
+ # Simplified team configuration - only dev and qe modes
611
+ config.team.modes = {
612
+ "dev": ModeConfig(
613
+ description="Development and coding",
614
+ roles={
615
+ "fullstack": RoleConfig(
616
+ description="Full-stack development and implementation",
617
+ coding_agent="claude-code", # ACP Claude Code agent
618
+ job_description="""You are a full-stack developer using Claude Code.
619
+ Focus on clean, maintainable code across frontend and backend.
620
+ Build complete features and hand off to QA for review.""",
621
+ ),
622
+ },
623
+ ),
624
+ "qa": ModeConfig(
625
+ description="Quality assurance and testing",
626
+ roles={
627
+ "api_tester": RoleConfig(
628
+ description="API testing and validation",
629
+ coding_agent="gemini-cli", # ACP Gemini CLI agent
630
+ job_description="""You are a QA specialist using Gemini CLI.
631
+ Focus on testing, validation, and code review.
632
+ Critique and improve code quality from development.""",
633
+ ),
634
+ },
635
+ ),
636
+ }
637
+
638
+ # Available ACP agents for direct connection (14 Official ACP Agents)
639
+ config.code_agents = [
640
+ "gemini", # Google's reference ACP implementation
641
+ "claude-code", # Anthropic's Claude via Zed SDK adapter
642
+ "codex", # OpenAI's code generation agent
643
+ "junie", # JetBrains' AI agent for IDE ecosystem
644
+ "goose", # Square's open-source agent
645
+ "kimi", # CLI AI agent with ACP support
646
+ "opencode", # Open-source coding agent
647
+ "stakpak", # ACP-compatible code assistance
648
+ "vtcode", # Versatile coding agent
649
+ "auggie", # Agentic code capabilities
650
+ "code-assistant", # AI coding assistant in Rust
651
+ "cagent", # Multi-agent runtime
652
+ "fast-agent", # Sophisticated agent workflows
653
+ "llmling-agent", # LLM-powered agent framework
654
+ ]
655
+
656
+ # Provider configurations
657
+ config.providers = {
658
+ "anthropic": ProviderConfig(
659
+ api_key_env="ANTHROPIC_API_KEY",
660
+ description="Anthropic Claude models via API",
661
+ recommended_models=["claude-sonnet-4-5", "claude-opus-4-1", "claude-haiku-4-5"],
662
+ custom_models_allowed=True,
663
+ ),
664
+ "openai": ProviderConfig(
665
+ api_key_env="OPENAI_API_KEY",
666
+ description="OpenAI GPT models via API",
667
+ recommended_models=["gpt-4o", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano"],
668
+ custom_models_allowed=True,
669
+ ),
670
+ "google": ProviderConfig(
671
+ api_key_env="GOOGLE_API_KEY",
672
+ description="Google Gemini models via Vertex AI",
673
+ recommended_models=["gemini-3-pro-preview", "gemini-2.5-pro", "gemini-2.5-flash"],
674
+ custom_models_allowed=True,
675
+ ),
676
+ "zhipuai": ProviderConfig(
677
+ api_key_env="ZHIPUAI_API_KEY",
678
+ description="ZhipuAI GLM models (智谱AI)",
679
+ recommended_models=["glm-4.7", "glm-4.6", "glm-4.6v", "glm-4.6v-flash"],
680
+ custom_models_allowed=True,
681
+ ),
682
+ "deepseek": ProviderConfig(
683
+ api_key_env="DEEPSEEK_API_KEY",
684
+ description="DeepSeek models",
685
+ recommended_models=["DeepSeek-V3.2-Exp", "DeepSeek-V3.2-Exp-Think"],
686
+ custom_models_allowed=True,
687
+ ),
688
+ "groq": ProviderConfig(
689
+ api_key_env="GROQ_API_KEY",
690
+ description="Groq ultra-fast inference",
691
+ recommended_models=[
692
+ "llama-3.1-70b-versatile",
693
+ "llama-3.1-8b-instant",
694
+ "mixtral-8x7b-32768",
695
+ ],
696
+ custom_models_allowed=True,
697
+ ),
698
+ "ollama": ProviderConfig(
699
+ base_url="http://localhost:11434",
700
+ description="Local Ollama models",
701
+ recommended_models=["llama3.1:70b", "qwen2.5:72b", "codellama:34b", "mistral:7b"],
702
+ custom_models_allowed=True,
703
+ ),
704
+ "openrouter": ProviderConfig(
705
+ api_key_env="OPENROUTER_API_KEY",
706
+ description="OpenRouter - unified API for 100+ models",
707
+ recommended_models=[
708
+ "anthropic/claude-sonnet-4-5",
709
+ "openai/gpt-4o",
710
+ "google/gemini-3-pro-preview",
711
+ "zhipuai/glm-4.7",
712
+ ],
713
+ custom_models_allowed=True,
714
+ ),
715
+ }
716
+
717
+ # ACP Agents (14 Official ACP Agents)
718
+ config.agents = {
719
+ "acp": {
720
+ "gemini": {
721
+ "enabled": True,
722
+ "description": "Gemini CLI - Google's reference ACP implementation",
723
+ "install_command": "npm install -g @anthropic-ai/gemini-cli",
724
+ "api_key_env": "GEMINI_API_KEY",
725
+ },
726
+ "claude-code": {
727
+ "enabled": True,
728
+ "description": "Claude Code - Anthropic's Claude via Zed SDK adapter",
729
+ "install_command": "npm install -g @anthropic-ai/claude-code",
730
+ "api_key_env": "ANTHROPIC_API_KEY",
731
+ },
732
+ "codex": {
733
+ "enabled": True,
734
+ "description": "Codex - OpenAI's code generation agent",
735
+ "install_command": "npm install -g @openai/codex",
736
+ "api_key_env": "OPENAI_API_KEY",
737
+ },
738
+ "junie": {
739
+ "enabled": True,
740
+ "description": "JetBrains Junie - AI agent for IDE ecosystem",
741
+ "install_command": "npm install -g @jetbrains/junie",
742
+ },
743
+ "goose": {
744
+ "enabled": True,
745
+ "description": "Goose - Square's open-source agent",
746
+ "install_command": "curl -fsSL https://github.com/block/goose/releases/latest/download/install.sh | bash",
747
+ },
748
+ "kimi": {
749
+ "enabled": True,
750
+ "description": "Kimi CLI - CLI AI agent with ACP support",
751
+ "install_command": "npm install -g @anthropic-ai/kimi-cli",
752
+ "api_key_env": "MOONSHOT_API_KEY",
753
+ },
754
+ "opencode": {
755
+ "enabled": True,
756
+ "description": "OpenCode - Open-source coding agent",
757
+ "install_command": "go install github.com/opencode-ai/opencode@latest",
758
+ },
759
+ "stakpak": {
760
+ "enabled": True,
761
+ "description": "Stakpak - ACP-compatible code assistance",
762
+ "install_command": "npm install -g stakpak",
763
+ },
764
+ "vtcode": {
765
+ "enabled": True,
766
+ "description": "VT Code - Versatile coding agent",
767
+ "install_command": "npm install -g vtcode",
768
+ },
769
+ "auggie": {
770
+ "enabled": True,
771
+ "description": "Augment Code - Agentic code capabilities",
772
+ "install_command": "npm install -g @anthropic-ai/auggie",
773
+ "api_key_env": "AUGMENT_API_KEY",
774
+ },
775
+ "code-assistant": {
776
+ "enabled": True,
777
+ "description": "Code Assistant - AI coding assistant in Rust",
778
+ "install_command": "cargo install code-assistant",
779
+ },
780
+ "cagent": {
781
+ "enabled": True,
782
+ "description": "cagent - Multi-agent runtime orchestration",
783
+ "install_command": "npm install -g cagent",
784
+ },
785
+ "fast-agent": {
786
+ "enabled": True,
787
+ "description": "fast-agent - Sophisticated agent workflows",
788
+ "install_command": "pip install fast-agent",
789
+ },
790
+ "llmling-agent": {
791
+ "enabled": True,
792
+ "description": "LLMling-Agent - LLM-powered agent framework",
793
+ "install_command": "pip install llmling-agent",
794
+ },
795
+ }
796
+ }
797
+
798
+ # Model aliases
799
+ config.model_aliases = {
800
+ "latest-sonnet": "claude-sonnet-4-5",
801
+ "latest-gpt": "gpt-4o",
802
+ "latest-gemini": "gemini-3-pro-preview",
803
+ "latest-glm": "glm-4.7",
804
+ "fast": "claude-haiku-4-5",
805
+ "balanced": "claude-sonnet-4-5",
806
+ "powerful": "claude-opus-4-1",
807
+ "thinking": "DeepSeek-V3.2-Exp-Think",
808
+ "vision": "glm-4.6v",
809
+ "coding": "codellama:34b",
810
+ }
811
+
812
+ return config