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
superqode/main.py ADDED
@@ -0,0 +1,1069 @@
1
+ # Fix CWD before any imports that might resolve it (e.g., logfire via acp, litellm)
2
+ # This prevents FileNotFoundError when current directory doesn't exist
3
+ import os
4
+ import sys
5
+ import pathlib
6
+
7
+ try:
8
+ cwd = os.getcwd()
9
+ if not pathlib.Path(cwd).exists():
10
+ # Change to home directory if CWD doesn't exist
11
+ os.chdir(os.path.expanduser("~"))
12
+ except (OSError, FileNotFoundError):
13
+ # If getcwd() fails, change to home directory
14
+ try:
15
+ os.chdir(os.path.expanduser("~"))
16
+ except Exception:
17
+ pass # Last resort - let it fail naturally
18
+
19
+ import json
20
+ import time
21
+ from datetime import datetime
22
+ from pathlib import Path
23
+ from typing import Optional, Sequence, Iterable, List, Dict, Any
24
+
25
+ import click
26
+
27
+ # Global variables for interactive mode
28
+ current_mode: str = "home" # Start in neutral home state
29
+ interactive_modes: dict[str, dict[str, object]] = {}
30
+
31
+
32
+ # Session state management
33
+ class SessionContext:
34
+ """Tracks work context for handoff between agents."""
35
+
36
+ def __init__(self):
37
+ self.session_id = f"session_{int(time.time())}"
38
+ self.created_at = datetime.now()
39
+ self.updated_at = datetime.now()
40
+ self.current_role = None
41
+ self.previous_role = None
42
+ self.work_description = ""
43
+ self.files_modified = []
44
+ self.files_created = []
45
+ self.tasks_completed = []
46
+ self.tasks_pending = []
47
+ self.quality_issues = []
48
+ self.handoff_history = []
49
+ self.metadata = {}
50
+
51
+ def update_work_context(
52
+ self,
53
+ description: str,
54
+ files_modified: List[str] = None,
55
+ files_created: List[str] = None,
56
+ tasks_completed: List[str] = None,
57
+ tasks_pending: List[str] = None,
58
+ ):
59
+ """Update the current work context."""
60
+ self.work_description = description
61
+ self.updated_at = datetime.now()
62
+
63
+ if files_modified:
64
+ self.files_modified.extend(files_modified)
65
+ if files_created:
66
+ self.files_created.extend(files_created)
67
+ if tasks_completed:
68
+ self.tasks_completed.extend(tasks_completed)
69
+ if tasks_pending:
70
+ self.tasks_pending.extend(tasks_pending)
71
+
72
+ def add_quality_issue(self, issue: str, severity: str = "medium"):
73
+ """Add a quality issue found during review."""
74
+ self.quality_issues.append(
75
+ {
76
+ "issue": issue,
77
+ "severity": severity,
78
+ "timestamp": datetime.now().isoformat(),
79
+ "resolved": False,
80
+ }
81
+ )
82
+
83
+ def resolve_quality_issue(self, index: int):
84
+ """Mark a quality issue as resolved."""
85
+ if 0 <= index < len(self.quality_issues):
86
+ self.quality_issues[index]["resolved"] = True
87
+ self.quality_issues[index]["resolved_at"] = datetime.now().isoformat()
88
+
89
+ def record_handoff(self, from_role: str, to_role: str, reason: str = ""):
90
+ """Record a handoff event in history."""
91
+ self.handoff_history.append(
92
+ {
93
+ "timestamp": datetime.now().isoformat(),
94
+ "from_role": from_role,
95
+ "to_role": to_role,
96
+ "reason": reason,
97
+ "work_description": self.work_description,
98
+ "quality_issues_count": len([i for i in self.quality_issues if not i["resolved"]]),
99
+ }
100
+ )
101
+ self.previous_role = from_role
102
+ self.current_role = to_role
103
+
104
+ def to_dict(self) -> Dict[str, Any]:
105
+ """Serialize to dictionary for storage."""
106
+ return {
107
+ "session_id": self.session_id,
108
+ "created_at": self.created_at.isoformat(),
109
+ "updated_at": self.updated_at.isoformat(),
110
+ "current_role": self.current_role,
111
+ "previous_role": self.previous_role,
112
+ "work_description": self.work_description,
113
+ "files_modified": self.files_modified,
114
+ "files_created": self.files_created,
115
+ "tasks_completed": self.tasks_completed,
116
+ "tasks_pending": self.tasks_pending,
117
+ "quality_issues": self.quality_issues,
118
+ "handoff_history": self.handoff_history,
119
+ "metadata": self.metadata,
120
+ }
121
+
122
+ @classmethod
123
+ def from_dict(cls, data: Dict[str, Any]) -> "SessionContext":
124
+ """Deserialize from dictionary."""
125
+ context = cls()
126
+ context.session_id = data.get("session_id", f"session_{int(time.time())}")
127
+ context.created_at = (
128
+ datetime.fromisoformat(data["created_at"]) if "created_at" in data else datetime.now()
129
+ )
130
+ context.updated_at = (
131
+ datetime.fromisoformat(data["updated_at"]) if "updated_at" in data else datetime.now()
132
+ )
133
+ context.current_role = data.get("current_role")
134
+ context.previous_role = data.get("previous_role")
135
+ context.work_description = data.get("work_description", "")
136
+ context.files_modified = data.get("files_modified", [])
137
+ context.files_created = data.get("files_created", [])
138
+ context.tasks_completed = data.get("tasks_completed", [])
139
+ context.tasks_pending = data.get("tasks_pending", [])
140
+ context.quality_issues = data.get("quality_issues", [])
141
+ context.handoff_history = data.get("handoff_history", [])
142
+ context.metadata = data.get("metadata", {})
143
+ return context
144
+
145
+ def save_to_file(self, filepath: Path):
146
+ """Save context to JSON file."""
147
+ with open(filepath, "w") as f:
148
+ json.dump(self.to_dict(), f, indent=2, default=str)
149
+
150
+ @classmethod
151
+ def load_from_file(cls, filepath: Path) -> Optional["SessionContext"]:
152
+ """Load context from JSON file."""
153
+ try:
154
+ with open(filepath, "r") as f:
155
+ data = json.load(f)
156
+ return cls.from_dict(data)
157
+ except (FileNotFoundError, json.JSONDecodeError):
158
+ return None
159
+
160
+
161
+ class HandoffWorkflow:
162
+ """Manages workflow transitions between development and QA roles."""
163
+
164
+ def __init__(self):
165
+ self.context_dir = Path.home() / ".superqode" / "sessions"
166
+ self.context_dir.mkdir(parents=True, exist_ok=True)
167
+
168
+ def initiate_handoff(
169
+ self,
170
+ from_role: str,
171
+ to_role: str,
172
+ context: SessionContext,
173
+ reason: str = "",
174
+ additional_context: str = "",
175
+ ) -> str:
176
+ """Initiate a handoff between roles with context preservation."""
177
+ # Record the handoff
178
+ context.record_handoff(from_role, to_role, reason)
179
+
180
+ # Save current context
181
+ context_file = self.context_dir / f"{context.session_id}.json"
182
+ context.save_to_file(context_file)
183
+
184
+ # Generate handoff message
185
+ handoff_message = self._generate_handoff_message(
186
+ from_role, to_role, context, reason, additional_context
187
+ )
188
+
189
+ return handoff_message
190
+
191
+ def _generate_handoff_message(
192
+ self,
193
+ from_role: str,
194
+ to_role: str,
195
+ context: SessionContext,
196
+ reason: str,
197
+ additional_context: str,
198
+ ) -> str:
199
+ """Generate a comprehensive handoff message."""
200
+ message_parts = []
201
+
202
+ # Header
203
+ message_parts.append(f"🤝 **Handoff from {from_role} to {to_role}**")
204
+ message_parts.append(f"📅 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
205
+ if reason:
206
+ message_parts.append(f"📝 Reason: {reason}")
207
+ message_parts.append("")
208
+
209
+ # Work description
210
+ if context.work_description:
211
+ message_parts.append("📋 **Work Completed:**")
212
+ message_parts.append(f"{context.work_description}")
213
+ message_parts.append("")
214
+
215
+ # Files changed
216
+ if context.files_modified or context.files_created:
217
+ message_parts.append("📁 **Files Involved:**")
218
+ for file in context.files_created:
219
+ message_parts.append(f" 🆕 {file}")
220
+ for file in context.files_modified:
221
+ message_parts.append(f" ✏️ {file}")
222
+ message_parts.append("")
223
+
224
+ # Tasks
225
+ if context.tasks_completed:
226
+ message_parts.append("✅ **Tasks Completed:**")
227
+ for task in context.tasks_completed:
228
+ message_parts.append(f" • {task}")
229
+ message_parts.append("")
230
+
231
+ if context.tasks_pending:
232
+ message_parts.append("⏳ **Tasks Pending:**")
233
+ for task in context.tasks_pending:
234
+ message_parts.append(f" • {task}")
235
+ message_parts.append("")
236
+
237
+ # Quality issues
238
+ unresolved_issues = [i for i in context.quality_issues if not i["resolved"]]
239
+ if unresolved_issues:
240
+ message_parts.append("⚠️ **Quality Issues Found:**")
241
+ severity_emojis = {"low": "🟢", "medium": "🟡", "high": "🔴", "critical": "💥"}
242
+ for i, issue in enumerate(unresolved_issues):
243
+ emoji = severity_emojis.get(issue["severity"], "🟡")
244
+ message_parts.append(f" {emoji} {issue['issue']}")
245
+ message_parts.append("")
246
+
247
+ # Context for recipient
248
+ role_contexts = {
249
+ "dev.fullstack": "Please review the implementation for code quality, security, and best practices.",
250
+ "qa.api_tester": "Please test the functionality, validate requirements, and identify any issues.",
251
+ }
252
+
253
+ if to_role in role_contexts:
254
+ message_parts.append(f"🎯 **Your Role:** {role_contexts[to_role]}")
255
+
256
+ # Additional context
257
+ if additional_context:
258
+ message_parts.append("")
259
+ message_parts.append("📎 **Additional Context:**")
260
+ message_parts.append(additional_context)
261
+
262
+ return "\n".join(message_parts)
263
+
264
+ def get_pending_handoffs(self) -> List[Dict[str, Any]]:
265
+ """Get list of pending handoffs that need attention."""
266
+ pending = []
267
+ for context_file in self.context_dir.glob("*.json"):
268
+ context = SessionContext.load_from_file(context_file)
269
+ if context:
270
+ # Show handoffs that are not yet approved
271
+ if not context.metadata.get("approved", False):
272
+ pending.append(
273
+ {
274
+ "session_id": context.session_id,
275
+ "current_role": context.current_role,
276
+ "work_description": context.work_description,
277
+ "pending_tasks": len(context.tasks_pending),
278
+ "quality_issues": len(
279
+ [i for i in context.quality_issues if not i["resolved"]]
280
+ ),
281
+ "last_updated": context.updated_at,
282
+ }
283
+ )
284
+ return sorted(pending, key=lambda x: x["last_updated"], reverse=True)
285
+
286
+ def approve_work(self, session_id: str, approval_notes: str = "") -> bool:
287
+ """Approve work for deployment."""
288
+ context_file = self.context_dir / f"{session_id}.json"
289
+ context = SessionContext.load_from_file(context_file)
290
+
291
+ if not context:
292
+ return False
293
+
294
+ # Mark all quality issues as resolved
295
+ for issue in context.quality_issues:
296
+ if not issue["resolved"]:
297
+ issue["resolved"] = True
298
+ issue["resolved_at"] = datetime.now().isoformat()
299
+ issue["approved_by"] = context.current_role
300
+
301
+ # Clear pending tasks
302
+ context.tasks_pending.clear()
303
+
304
+ # Add approval metadata
305
+ context.metadata["approved"] = True
306
+ context.metadata["approved_at"] = datetime.now().isoformat()
307
+ context.metadata["approved_by"] = context.current_role
308
+ context.metadata["approval_notes"] = approval_notes
309
+
310
+ # Save updated context
311
+ context.save_to_file(context_file)
312
+ return True
313
+
314
+
315
+ class SessionState:
316
+ def __init__(self):
317
+ self.state = "superqode" # "superqode" | "agent_connected" | "role_mode"
318
+ self.connected_agent = None # Agent data when in agent_connected state
319
+ self.agent_role_info = None # Role info when connected via role
320
+ self.current_context = SessionContext() # Current work context
321
+ self.handoff_workflow = HandoffWorkflow() # Handoff management
322
+ self.acp_manager = None # ACP agent manager for real connections
323
+ self.execution_mode = "acp" # "acp" or "byok"
324
+
325
+ def connect_to_agent(self, agent_data, role_info=None, model=None, execution_mode="acp"):
326
+ """Connect to an agent directly (bypassing roles)
327
+
328
+ Args:
329
+ agent_data: Agent information dict
330
+ role_info: Optional role information
331
+ model: Optional model override
332
+ execution_mode: "acp" for coding agent, "byok" for direct LLM
333
+ """
334
+ self.state = "agent_connected"
335
+ self.connected_agent = agent_data
336
+ self.agent_role_info = role_info
337
+ self.selected_model = model # Store selected model for direct connections
338
+ self.execution_mode = execution_mode # Track execution mode
339
+
340
+ def set_acp_manager(self, manager):
341
+ """Set the active ACP manager for real-time communication"""
342
+ self.acp_manager = manager
343
+
344
+ def disconnect_acp_manager(self):
345
+ """Disconnect the ACP manager"""
346
+ if self.acp_manager:
347
+ import asyncio
348
+
349
+ asyncio.run(self.acp_manager.disconnect())
350
+ self.acp_manager = None
351
+
352
+ def disconnect_agent(self):
353
+ """Disconnect from agent and return to superqode mode"""
354
+ self.state = "superqode"
355
+ self.connected_agent = None
356
+ self.agent_role_info = None
357
+ self.selected_model = None
358
+ self.execution_mode = "acp" # Reset to default
359
+
360
+ def switch_to_role_mode(self, mode):
361
+ """Switch to role-based mode"""
362
+ self.state = "role_mode"
363
+ global current_mode
364
+ current_mode = mode
365
+
366
+ # Check for pending handoffs for this role
367
+ pending = self.get_pending_handoffs()
368
+ role_handoffs = [h for h in pending if h["current_role"] == mode]
369
+
370
+ if role_handoffs:
371
+ # Automatically resume the most recent handoff for this role
372
+ latest_handoff = role_handoffs[0] # Already sorted by updated_at desc
373
+ if self.load_context_from_session(latest_handoff["session_id"]):
374
+ print(f"🤝 Resumed pending handoff: {latest_handoff['work_description'][:50]}...")
375
+ return True
376
+ return False
377
+
378
+ def is_connected_to_agent(self):
379
+ """Check if currently connected to an agent"""
380
+ return self.state == "agent_connected" and self.connected_agent is not None
381
+
382
+ def get_prompt_suffix(self):
383
+ """Get the prompt suffix based on current state"""
384
+ if self.state == "agent_connected":
385
+ agent_name = (
386
+ self.connected_agent.get("short_name", "Unknown")
387
+ if self.connected_agent
388
+ else "Unknown"
389
+ )
390
+ # Show execution mode in prompt
391
+ if self.execution_mode == "acp":
392
+ return f"🔗 ACP • {agent_name.upper()}"
393
+ elif self.execution_mode == "byok":
394
+ return f"⚡ BYOK • {agent_name.upper()}"
395
+ else:
396
+ return f"🔗 {agent_name.upper()}"
397
+ elif self.state == "role_mode":
398
+ return current_mode.replace(".", "/").upper()
399
+ else: # superqode
400
+ if current_mode == "home":
401
+ return "🏠 HOME"
402
+ else:
403
+ return current_mode.replace(".", "/").upper()
404
+
405
+ def get_connection_info(self):
406
+ """Get detailed connection information for display"""
407
+ if not self.is_connected_to_agent():
408
+ return None
409
+
410
+ info = {
411
+ "agent": self.connected_agent.get("name", "Unknown")
412
+ if self.connected_agent
413
+ else "Unknown",
414
+ "short_name": self.connected_agent.get("short_name", "unknown")
415
+ if self.connected_agent
416
+ else "unknown",
417
+ "type": self.connected_agent.get("type", "unknown")
418
+ if self.connected_agent
419
+ else "unknown",
420
+ "description": self.connected_agent.get("description", "")
421
+ if self.connected_agent
422
+ else "",
423
+ "execution_mode": self.execution_mode, # Include execution mode
424
+ }
425
+
426
+ # Add role info if connected via role
427
+ if self.agent_role_info:
428
+ info.update(
429
+ {
430
+ "role": self.agent_role_info.get("role", ""),
431
+ "provider": self.agent_role_info.get("provider", ""),
432
+ "model": self.agent_role_info.get("model", ""),
433
+ "job_description": self.agent_role_info.get("job_description", ""),
434
+ }
435
+ )
436
+
437
+ return info
438
+
439
+ def update_context(
440
+ self,
441
+ description: str = None,
442
+ files_modified: List[str] = None,
443
+ files_created: List[str] = None,
444
+ tasks_completed: List[str] = None,
445
+ tasks_pending: List[str] = None,
446
+ ):
447
+ """Update the current work context."""
448
+ if description or files_modified or files_created or tasks_completed or tasks_pending:
449
+ self.current_context.update_work_context(
450
+ description or self.current_context.work_description,
451
+ files_modified,
452
+ files_created,
453
+ tasks_completed,
454
+ tasks_pending,
455
+ )
456
+
457
+ def add_quality_issue(self, issue: str, severity: str = "medium"):
458
+ """Add a quality issue to the current context."""
459
+ self.current_context.add_quality_issue(issue, severity)
460
+
461
+ def resolve_quality_issue(self, index: int):
462
+ """Resolve a quality issue by index."""
463
+ self.current_context.resolve_quality_issue(index)
464
+
465
+ def initiate_handoff(self, to_role: str, reason: str = "", additional_context: str = "") -> str:
466
+ """Initiate a handoff to another role."""
467
+ from_role = self.get_current_role_name()
468
+
469
+ if not from_role:
470
+ return "Error: Not currently in a role mode for handoff"
471
+
472
+ handoff_message = self.handoff_workflow.initiate_handoff(
473
+ from_role, to_role, self.current_context, reason, additional_context
474
+ )
475
+
476
+ # Reset context for new role (but keep session ID)
477
+ old_session_id = self.current_context.session_id
478
+ self.current_context = SessionContext()
479
+ self.current_context.session_id = old_session_id
480
+ self.current_context.previous_role = from_role
481
+ self.current_context.current_role = to_role
482
+
483
+ return handoff_message
484
+
485
+ def approve_work(self, approval_notes: str = "") -> bool:
486
+ """Approve current work for deployment."""
487
+ return self.handoff_workflow.approve_work(self.current_context.session_id, approval_notes)
488
+
489
+ def get_pending_handoffs(self) -> List[Dict[str, Any]]:
490
+ """Get list of pending handoffs."""
491
+ return self.handoff_workflow.get_pending_handoffs()
492
+
493
+ def get_current_role_name(self) -> Optional[str]:
494
+ """Get the current role name for handoffs."""
495
+ if self.state == "role_mode":
496
+ return current_mode
497
+ elif self.agent_role_info:
498
+ role = self.agent_role_info.get("role", "")
499
+ mode = self.agent_role_info.get("mode", "")
500
+ if mode and role:
501
+ return f"{mode}.{role}"
502
+ return None
503
+
504
+ def load_context_from_session(self, session_id: str) -> bool:
505
+ """Load a previous session context."""
506
+ context_file = self.handoff_workflow.context_dir / f"{session_id}.json"
507
+ context = SessionContext.load_from_file(context_file)
508
+ if context:
509
+ self.current_context = context
510
+ return True
511
+ return False
512
+
513
+
514
+ # Global session state instance
515
+ session = SessionState()
516
+
517
+ # Main CLI group
518
+ import click
519
+
520
+
521
+ @click.group(invoke_without_command=True)
522
+ @click.version_option(version="0.1.5")
523
+ @click.option("--tui", is_flag=True, help="Launch the Textual TUI interface")
524
+ @click.pass_context
525
+ def cli_main(ctx, tui):
526
+ """SuperQode - Developer TUI for multi-agent coding and exploration.
527
+
528
+ Interactive interface for orchestrating coding agents across dev, QE, and DevOps.
529
+ For automation and CI, use the `superqe` CLI.
530
+ """
531
+
532
+ # If no command is provided, launch Textual app (default behavior)
533
+ if ctx.invoked_subcommand is None or tui:
534
+ import time
535
+
536
+ # Show simple loading message before TUI starts
537
+ print("🚀 Starting SuperQode...", end="", flush=True)
538
+ time.sleep(0.5)
539
+
540
+ # Clear the loading message before TUI takes over
541
+ print("\r" + " " * 50 + "\r", end="", flush=True)
542
+
543
+ # Import and run the TUI
544
+ from superqode.app import run_textual_app
545
+
546
+ run_textual_app()
547
+ return
548
+
549
+
550
+ # Configuration management commands - defined before main() for proper registration
551
+ @cli_main.group()
552
+ def config():
553
+ """Manage SuperQode configuration."""
554
+ pass
555
+
556
+
557
+ @config.command("list-modes")
558
+ def config_list_modes():
559
+ """List all configured modes and roles."""
560
+ from superqode.config import load_enabled_modes
561
+ from rich.console import Console
562
+ from rich.table import Table
563
+
564
+ console = Console()
565
+ enabled_modes = load_enabled_modes()
566
+
567
+ if not enabled_modes:
568
+ console.print(
569
+ "[yellow]No modes configured. Run 'superqode init' to create a repo configuration.[/yellow]"
570
+ )
571
+ return
572
+
573
+ table = Table(title="Configured Modes and Roles")
574
+ table.add_column("Mode", style="cyan", no_wrap=True)
575
+ table.add_column("Role", style="magenta", no_wrap=True)
576
+ table.add_column("Agent", style="green")
577
+ table.add_column("Description", style="white")
578
+
579
+ for mode_name, mode_config in enabled_modes.items():
580
+ if mode_config.direct_role:
581
+ table.add_row(
582
+ mode_name,
583
+ "(direct)",
584
+ f"{mode_config.direct_role.coding_agent} ({mode_config.direct_role.agent_type})",
585
+ mode_config.direct_role.description,
586
+ )
587
+ elif mode_config.roles:
588
+ for role_name, role_config in mode_config.roles.items():
589
+ table.add_row(
590
+ mode_name,
591
+ role_name,
592
+ f"{role_config.coding_agent} ({role_config.agent_type})",
593
+ role_config.description,
594
+ )
595
+
596
+ console.print(table)
597
+
598
+
599
+ @config.command("init")
600
+ @click.option("--force", "-f", is_flag=True, help="Overwrite existing configuration")
601
+ def config_init(force):
602
+ """Initialize default SuperQode configuration."""
603
+ from superqode.config import create_default_config, save_config, find_config_file
604
+ from pathlib import Path
605
+ import os
606
+
607
+ config_path = find_config_file()
608
+ if config_path and config_path.exists() and not force:
609
+ click.echo(f"Configuration already exists at {config_path}")
610
+ click.echo("Use --force to overwrite")
611
+ return
612
+
613
+ if not config_path:
614
+ config_path = Path.cwd() / "superqode.yaml"
615
+
616
+ # Create default config
617
+ config = create_default_config()
618
+ save_config(config, config_path)
619
+
620
+ click.echo(f"Created default configuration at {config_path}")
621
+ click.echo("Edit the file to customize your development team!")
622
+
623
+
624
+ @config.command("set-model")
625
+ @click.argument("mode_role", metavar="MODE.ROLE")
626
+ @click.argument("model", metavar="MODEL")
627
+ def config_set_model(mode_role, model):
628
+ """Set the model for a specific mode/role."""
629
+ from superqode.config import load_config, save_config, resolve_role
630
+
631
+ parts = mode_role.split(".", 1)
632
+ if len(parts) != 2:
633
+ click.echo("Error: MODE.ROLE must be in format 'mode.role' (e.g., 'dev.backend')")
634
+ return
635
+
636
+ mode_name, role_name = parts
637
+ config = load_config()
638
+
639
+ resolved_role = resolve_role(mode_name, role_name, config)
640
+ if not resolved_role:
641
+ click.echo(f"Error: Role '{mode_role}' not found in configuration")
642
+ return
643
+
644
+ if resolved_role.agent_type == "acp":
645
+ click.echo("Error: Cannot set model for ACP agents. ACP agents use their own models.")
646
+ return
647
+
648
+ # Update the configuration
649
+ if role_name:
650
+ config.team.modes[mode_name].roles[role_name].model = model
651
+ else:
652
+ config.team.modes[mode_name].model = model
653
+
654
+ save_config(config)
655
+ click.echo(f"Updated {mode_role} to use model '{model}'")
656
+
657
+
658
+ @config.command("set-agent")
659
+ @click.argument("mode_role", metavar="MODE.ROLE")
660
+ @click.argument("agent", metavar="AGENT")
661
+ @click.option("--provider", "-p", help="Provider for SuperQode agents")
662
+ def config_set_agent(mode_role, agent, provider):
663
+ """Set the agent for a specific mode/role."""
664
+ from superqode.config import load_config, save_config, resolve_role
665
+
666
+ parts = mode_role.split(".", 1)
667
+ if len(parts) != 2:
668
+ click.echo("Error: MODE.ROLE must be in format 'mode.role' (e.g., 'dev.backend')")
669
+ return
670
+
671
+ mode_name, role_name = parts
672
+ config = load_config()
673
+
674
+ resolved_role = resolve_role(mode_name, role_name, config)
675
+ if not resolved_role:
676
+ click.echo(f"Error: Role '{mode_role}' not found in configuration")
677
+ return
678
+
679
+ # Update the configuration
680
+ if role_name:
681
+ config.team.modes[mode_name].roles[role_name].coding_agent = agent
682
+ if provider:
683
+ config.team.modes[mode_name].roles[role_name].provider = provider
684
+ else:
685
+ config.team.modes[mode_name].coding_agent = agent
686
+ if provider:
687
+ config.team.modes[mode_name].provider = provider
688
+
689
+ save_config(config)
690
+ click.echo(
691
+ f"Updated {mode_role} to use agent '{agent}'{' with provider ' + provider if provider else ''}"
692
+ )
693
+
694
+
695
+ @config.command("enable-role")
696
+ @click.argument("mode_role", metavar="MODE.ROLE")
697
+ def config_enable_role(mode_role):
698
+ """Enable a disabled role."""
699
+ from superqode.config import load_config, save_config
700
+
701
+ parts = mode_role.split(".", 1)
702
+ if len(parts) != 2:
703
+ click.echo("Error: MODE.ROLE must be in format 'mode.role' (e.g., 'dev.mobile')")
704
+ return
705
+
706
+ mode_name, role_name = parts
707
+ config = load_config()
708
+
709
+ if mode_name not in config.team.modes:
710
+ click.echo(f"Error: Mode '{mode_name}' not found")
711
+ return
712
+
713
+ mode_config = config.team.modes[mode_name]
714
+ if role_name not in mode_config.roles:
715
+ click.echo(f"Error: Role '{role_name}' not found in mode '{mode_name}'")
716
+ return
717
+
718
+ mode_config.roles[role_name].enabled = True
719
+ save_config(config)
720
+ click.echo(f"Enabled role '{mode_role}'")
721
+
722
+
723
+ @config.command("disable-role")
724
+ @click.argument("mode_role", metavar="MODE.ROLE")
725
+ def config_disable_role(mode_role):
726
+ """Disable an enabled role."""
727
+ from superqode.config import load_config, save_config
728
+
729
+ parts = mode_role.split(".", 1)
730
+ if len(parts) != 2:
731
+ click.echo("Error: MODE.ROLE must be in format 'mode.role' (e.g., 'dev.mobile')")
732
+ return
733
+
734
+ mode_name, role_name = parts
735
+ config = load_config()
736
+
737
+ if mode_name not in config.team.modes:
738
+ click.echo(f"Error: Mode '{mode_name}' not found")
739
+ return
740
+
741
+ mode_config = config.team.modes[mode_name]
742
+ if role_name not in mode_config.roles:
743
+ click.echo(f"Error: Role '{role_name}' not found in mode '{mode_name}'")
744
+ return
745
+
746
+ mode_config.roles[role_name].enabled = False
747
+ save_config(config)
748
+ click.echo(f"Disabled role '{mode_role}'")
749
+
750
+
751
+ # TUI command
752
+ @cli_main.command("tui")
753
+ def tui_command():
754
+ """Launch the Textual TUI interface."""
755
+ from superqode.app import run_textual_app
756
+
757
+ run_textual_app()
758
+
759
+
760
+ # Init command (top-level for convenience)
761
+ @cli_main.command("init")
762
+ @click.option("--force", "-f", is_flag=True, help="Overwrite existing configuration")
763
+ def init_command(force):
764
+ """Initialize SuperQode in current directory.
765
+
766
+ Creates a superqode.yaml with all team roles enabled
767
+ configured to use OpenCode with free models.
768
+ """
769
+ from superqode.config import find_config_file
770
+ from pathlib import Path
771
+ import os
772
+
773
+ config_path = find_config_file()
774
+ if config_path and config_path.exists() and not force:
775
+ click.echo(f"✓ Configuration already exists at {config_path}")
776
+ click.echo(" Use --force to overwrite")
777
+ return
778
+
779
+ config_path = Path.cwd() / "superqode.yaml"
780
+
781
+ # Copy the full configuration from the template
782
+ template_path = Path(__file__).parent.parent.parent / "superqode-template.yaml"
783
+ if template_path.exists():
784
+ import shutil
785
+
786
+ shutil.copy2(template_path, config_path)
787
+ click.echo(f"✓ Created {config_path} with all roles available")
788
+ else:
789
+ # Fallback: create basic config if template not found
790
+ default_config = """# =============================================================================
791
+ # SuperQode - Team Configuration
792
+ # =============================================================================
793
+ # Multi-agent software development team
794
+ # Run: superqode (TUI) or superqode --help (CLI)
795
+ # =============================================================================
796
+
797
+ superqode:
798
+ version: "1.0"
799
+ team_name: "Full Stack Development Team"
800
+ description: "AI-powered software development team"
801
+
802
+ # Default configuration for all roles
803
+ default:
804
+ mode: "acp"
805
+ agent: "opencode"
806
+ agent_config:
807
+ provider: "opencode"
808
+ model: "glm-4.7-free"
809
+
810
+ # =============================================================================
811
+ # TEAM ROLES - All enabled by default
812
+ # =============================================================================
813
+ team:
814
+ # Development roles
815
+ dev:
816
+ description: "Software Development"
817
+ roles:
818
+ fullstack:
819
+ description: "Full-stack development"
820
+ mode: "acp"
821
+ agent: "opencode"
822
+ agent_config:
823
+ provider: "opencode"
824
+ model: "glm-4.7-free"
825
+ enabled: false
826
+ job_description: |
827
+ You are a Senior Full-Stack Developer.
828
+ Write clean, maintainable code. Follow best practices.
829
+ Implement features end-to-end across frontend and backend.
830
+
831
+ # QE roles
832
+ qe:
833
+ description: "Quality Engineering"
834
+ roles:
835
+ fullstack:
836
+ description: "Full-stack QE engineer"
837
+ mode: "acp"
838
+ agent: "opencode"
839
+ agent_config:
840
+ provider: "opencode"
841
+ model: "grok-code"
842
+ enabled: false
843
+ job_description: |
844
+ You are a Senior QE Engineer.
845
+ Review code for bugs, security issues, and best practices.
846
+ Write and run tests. Validate requirements are met.
847
+
848
+ # DevOps roles
849
+ devops:
850
+ description: "DevOps & Infrastructure"
851
+ roles:
852
+ fullstack:
853
+ description: "Full-stack DevOps engineer"
854
+ mode: "acp"
855
+ agent: "opencode"
856
+ agent_config:
857
+ provider: "opencode"
858
+ model: "gpt-5-nano"
859
+ enabled: false
860
+ job_description: |
861
+ You are a Senior DevOps Engineer.
862
+ Design CI/CD pipelines, containerize apps, manage infrastructure.
863
+ Ensure security, monitoring, and deployment best practices.
864
+
865
+ # =============================================================================
866
+ # Available free models: glm-4.7-free, grok-code, gpt-5-nano,
867
+ # minimax-m2.1-free, big-pickle
868
+ # =============================================================================
869
+ """
870
+
871
+ with open(config_path, "w") as f:
872
+ f.write(default_config)
873
+ click.echo(f"✓ Created {config_path} with basic roles available")
874
+
875
+ click.echo("")
876
+ click.echo(" Quick start:")
877
+ click.echo(" superqode # Launch TUI")
878
+ click.echo(" superqe roles # List configured QE roles")
879
+ click.echo(" superqe run . # Run QE using your superqode.yaml")
880
+ click.echo("")
881
+ click.echo(" Edit superqode.yaml to add or enable roles as needed.")
882
+
883
+
884
+ # ACP Agent commands
885
+ @cli_main.group()
886
+ def agents():
887
+ """Manage ACP (Agent-Client Protocol) coding agents."""
888
+ pass
889
+
890
+
891
+ @agents.command("list")
892
+ @click.option("--store", is_flag=True, help="Show agent store interface")
893
+ def agents_list(store):
894
+ """List all available ACP coding agents."""
895
+ from superqode.commands.acp import show_agents_list, show_agents_store
896
+
897
+ if store:
898
+ show_agents_store()
899
+ else:
900
+ show_agents_list()
901
+
902
+
903
+ @agents.command("store")
904
+ def agents_store():
905
+ """Show the beautiful agent store interface."""
906
+ from superqode.commands.acp import show_agents_store
907
+
908
+ show_agents_store()
909
+
910
+
911
+ @agents.command("show")
912
+ @click.argument("agent", metavar="AGENT")
913
+ def agents_show(agent):
914
+ """Show detailed information about a specific agent."""
915
+ from superqode.commands.acp import show_agent
916
+
917
+ show_agent(agent)
918
+
919
+
920
+ @agents.command("connect")
921
+ @click.argument("agent", metavar="AGENT")
922
+ @click.option("--project-dir", "-d", metavar="DIR", help="Project directory to work in")
923
+ def agents_connect(agent, project_dir):
924
+ """Connect to an ACP coding agent. (Deprecated: use 'superqode connect acp' instead)"""
925
+ import warnings
926
+
927
+ warnings.warn(
928
+ "'superqode agents connect' is deprecated. Use 'superqode connect acp' instead.",
929
+ DeprecationWarning,
930
+ stacklevel=2,
931
+ )
932
+ from superqode.commands.acp import connect_agent
933
+
934
+ exit(connect_agent(agent, project_dir))
935
+
936
+
937
+ @agents.command("install")
938
+ @click.argument("agent", metavar="AGENT")
939
+ def agents_install(agent):
940
+ """Install an ACP coding agent."""
941
+ from superqode.commands.acp import install_agent_cmd
942
+
943
+ exit(install_agent_cmd(agent))
944
+
945
+
946
+ @cli_main.group()
947
+ def connect():
948
+ """Connect to models via ACP agents, BYOK providers, or LOCAL providers."""
949
+ pass
950
+
951
+
952
+ @connect.command("acp")
953
+ @click.argument("agent", metavar="AGENT")
954
+ @click.option("--project-dir", "-d", metavar="DIR", help="Project directory to work in")
955
+ def connect_acp(agent, project_dir):
956
+ """Connect to an ACP coding agent."""
957
+ from superqode.commands.acp import connect_agent
958
+
959
+ exit(connect_agent(agent, project_dir))
960
+
961
+
962
+ @connect.command("byok")
963
+ @click.argument("provider", metavar="PROVIDER", required=False)
964
+ @click.argument("model", metavar="MODEL", required=False)
965
+ def connect_byok(provider, model):
966
+ """Connect to a BYOK provider/model."""
967
+ from superqode.commands.providers import connect_provider
968
+
969
+ exit(connect_provider(provider, model))
970
+
971
+
972
+ @connect.command("local")
973
+ @click.argument("provider", metavar="PROVIDER", required=False)
974
+ @click.argument("model", metavar="MODEL", required=False)
975
+ def connect_local(provider, model):
976
+ """Connect to a local/self-hosted provider/model."""
977
+ from superqode.commands.providers import connect_local_provider
978
+
979
+ exit(connect_local_provider(provider, model))
980
+
981
+
982
+ # Alias for backward compatibility
983
+ main = cli_main
984
+
985
+
986
+ # Simple toast replacement since UI components were removed
987
+ class ToastType:
988
+ SUCCESS = "success"
989
+ ERROR = "error"
990
+ INFO = "info"
991
+ WARNING = "warning"
992
+
993
+
994
+ def show_toast(message: str, toast_type: str) -> None:
995
+ """Simple toast replacement - just print the message."""
996
+ if toast_type == ToastType.SUCCESS:
997
+ _console.print(f"[green]✓ {message}[/green]")
998
+ elif toast_type == ToastType.ERROR:
999
+ _console.print(f"[red]✗ {message}[/red]")
1000
+ elif toast_type == ToastType.WARNING:
1001
+ _console.print(f"[yellow]⚠️ {message}[/yellow]")
1002
+ else:
1003
+ _console.print(f"[blue]ℹ️ {message}[/blue]")
1004
+
1005
+
1006
+ from rich.text import Text
1007
+ from rich.panel import Panel
1008
+ from rich.live import Live
1009
+ from rich.spinner import Spinner
1010
+ from rich.markdown import Markdown
1011
+ from rich.syntax import Syntax
1012
+ from rich.align import Align
1013
+ from rich.box import DOUBLE, ROUNDED
1014
+ from rich.columns import Columns
1015
+ from rich.table import Table
1016
+ from rich.markup import escape
1017
+ import rich.box
1018
+
1019
+ from superqode import __version__
1020
+ from superqode.providers import ProviderManager
1021
+ from superqode.dialogs import ProviderDialog, ModelDialog, ConnectDialog
1022
+ from superqode.tui import (
1023
+ SuperQodeUI,
1024
+ ThinkingSpinner,
1025
+ ResponsePanel,
1026
+ print_disconnect_message,
1027
+ print_exit_message,
1028
+ )
1029
+
1030
+ # Alias for backward compatibility
1031
+ SuperQodeTUI = SuperQodeUI
1032
+
1033
+ # LLM provider management
1034
+ from superqode.providers.manager import ProviderManager
1035
+
1036
+ # Register new BYOK provider and agent commands
1037
+ from superqode.commands.providers import providers as providers_cmd
1038
+ from superqode.commands.agents import agents as agents_cmd_new
1039
+ from superqode.commands.auth import auth as auth_cmd
1040
+ from superqode.commands.qe import qe as qe_cmd
1041
+ from superqode.commands.roles import roles as roles_cmd
1042
+ from superqode.commands.suggestions import suggestions as suggestions_cmd
1043
+ from superqode.commands.serve import serve as serve_cmd
1044
+
1045
+ # Add provider commands (superqode providers list, superqode providers show, etc.)
1046
+ cli_main.add_command(providers_cmd, name="providers")
1047
+
1048
+ # Add auth commands (superqode auth info, superqode auth check, etc.)
1049
+ cli_main.add_command(auth_cmd, name="auth")
1050
+
1051
+ # Add QE commands (superqode qe ...)
1052
+ cli_main.add_command(qe_cmd, name="qe")
1053
+
1054
+ # Add roles commands (superqode roles list, superqode roles info, etc.)
1055
+ cli_main.add_command(roles_cmd, name="roles")
1056
+
1057
+ # Add suggestions commands (superqode suggestions list, superqode suggestions apply, etc.)
1058
+ cli_main.add_command(suggestions_cmd, name="suggestions")
1059
+
1060
+ # Add Server commands (superqode serve lsp, superqode serve web, etc.)
1061
+ cli_main.add_command(serve_cmd, name="serve")
1062
+
1063
+ # Note: agents command already exists, so we add the new one with a different approach
1064
+ # The existing agents command handles ACP agents, we'll enhance it
1065
+
1066
+
1067
+ if __name__ == "__main__":
1068
+ """Entry point for the SuperQode CLI."""
1069
+ cli_main()