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/history.py ADDED
@@ -0,0 +1,279 @@
1
+ """
2
+ SuperQode History Manager - Command & Session History
3
+
4
+ Manages command history with:
5
+ - JSON-based persistent storage
6
+ - Async file operations
7
+ - Search and filtering
8
+ - Session tracking
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import asyncio
15
+ from dataclasses import dataclass, field, asdict
16
+ from datetime import datetime
17
+ from pathlib import Path
18
+ from typing import List, Optional, Dict, Any
19
+
20
+
21
+ @dataclass
22
+ class HistoryEntry:
23
+ """A single history entry."""
24
+
25
+ input: str
26
+ timestamp: float
27
+ session_id: Optional[str] = None
28
+ mode: Optional[str] = None
29
+ agent: Optional[str] = None
30
+ success: bool = True
31
+ metadata: Dict[str, Any] = field(default_factory=dict)
32
+
33
+
34
+ @dataclass
35
+ class SessionInfo:
36
+ """Information about a session."""
37
+
38
+ session_id: str
39
+ started_at: datetime
40
+ ended_at: Optional[datetime] = None
41
+ mode: Optional[str] = None
42
+ agent: Optional[str] = None
43
+ command_count: int = 0
44
+
45
+
46
+ class HistoryManager:
47
+ """Manages command history with persistence."""
48
+
49
+ def __init__(self, history_file: Optional[Path] = None):
50
+ self.history_file = history_file or Path.home() / ".superqode" / "history.jsonl"
51
+ self._entries: List[HistoryEntry] = []
52
+ self._loaded = False
53
+ self._current_session: Optional[str] = None
54
+ self._position = 0 # For navigation
55
+
56
+ async def load(self) -> bool:
57
+ """Load history from file."""
58
+ if self._loaded:
59
+ return True
60
+
61
+ def _read_history() -> List[HistoryEntry]:
62
+ entries = []
63
+ try:
64
+ self.history_file.parent.mkdir(parents=True, exist_ok=True)
65
+ self.history_file.touch(exist_ok=True)
66
+
67
+ with self.history_file.open("r") as f:
68
+ for line in f:
69
+ line = line.strip()
70
+ if line:
71
+ try:
72
+ data = json.loads(line)
73
+ entries.append(HistoryEntry(**data))
74
+ except (json.JSONDecodeError, TypeError):
75
+ continue
76
+ except Exception:
77
+ pass
78
+ return entries
79
+
80
+ self._entries = await asyncio.to_thread(_read_history)
81
+ self._loaded = True
82
+ self._position = len(self._entries)
83
+ return True
84
+
85
+ async def append(
86
+ self,
87
+ input_text: str,
88
+ mode: Optional[str] = None,
89
+ agent: Optional[str] = None,
90
+ success: bool = True,
91
+ metadata: Optional[Dict[str, Any]] = None,
92
+ ) -> HistoryEntry:
93
+ """Append a new entry to history."""
94
+ if not input_text.strip():
95
+ return None
96
+
97
+ entry = HistoryEntry(
98
+ input=input_text,
99
+ timestamp=datetime.now().timestamp(),
100
+ session_id=self._current_session,
101
+ mode=mode,
102
+ agent=agent,
103
+ success=success,
104
+ metadata=metadata or {},
105
+ )
106
+
107
+ self._entries.append(entry)
108
+ self._position = len(self._entries)
109
+
110
+ # Write to file
111
+ def _write_entry():
112
+ try:
113
+ with self.history_file.open("a") as f:
114
+ f.write(json.dumps(asdict(entry)) + "\n")
115
+ except Exception:
116
+ pass
117
+
118
+ await asyncio.to_thread(_write_entry)
119
+ return entry
120
+
121
+ def append_sync(
122
+ self,
123
+ input_text: str,
124
+ mode: Optional[str] = None,
125
+ agent: Optional[str] = None,
126
+ success: bool = True,
127
+ ) -> Optional[HistoryEntry]:
128
+ """Synchronous version of append."""
129
+ if not input_text.strip():
130
+ return None
131
+
132
+ entry = HistoryEntry(
133
+ input=input_text,
134
+ timestamp=datetime.now().timestamp(),
135
+ session_id=self._current_session,
136
+ mode=mode,
137
+ agent=agent,
138
+ success=success,
139
+ )
140
+
141
+ self._entries.append(entry)
142
+ self._position = len(self._entries)
143
+
144
+ try:
145
+ self.history_file.parent.mkdir(parents=True, exist_ok=True)
146
+ with self.history_file.open("a") as f:
147
+ f.write(json.dumps(asdict(entry)) + "\n")
148
+ except Exception:
149
+ pass
150
+
151
+ return entry
152
+
153
+ def get_previous(self) -> Optional[str]:
154
+ """Get previous history entry (for up arrow)."""
155
+ if not self._entries or self._position <= 0:
156
+ return None
157
+
158
+ self._position -= 1
159
+ return self._entries[self._position].input
160
+
161
+ def get_next(self) -> Optional[str]:
162
+ """Get next history entry (for down arrow)."""
163
+ if self._position >= len(self._entries) - 1:
164
+ self._position = len(self._entries)
165
+ return ""
166
+
167
+ self._position += 1
168
+ return self._entries[self._position].input
169
+
170
+ def reset_position(self) -> None:
171
+ """Reset navigation position to end."""
172
+ self._position = len(self._entries)
173
+
174
+ def search(self, query: str, limit: int = 20) -> List[HistoryEntry]:
175
+ """Search history entries."""
176
+ query_lower = query.lower()
177
+ results = []
178
+
179
+ for entry in reversed(self._entries):
180
+ if query_lower in entry.input.lower():
181
+ results.append(entry)
182
+ if len(results) >= limit:
183
+ break
184
+
185
+ return results
186
+
187
+ def get_recent(self, count: int = 20) -> List[HistoryEntry]:
188
+ """Get most recent entries."""
189
+ return list(reversed(self._entries[-count:]))
190
+
191
+ def get_by_mode(self, mode: str, limit: int = 20) -> List[HistoryEntry]:
192
+ """Get entries for a specific mode."""
193
+ results = []
194
+ for entry in reversed(self._entries):
195
+ if entry.mode == mode:
196
+ results.append(entry)
197
+ if len(results) >= limit:
198
+ break
199
+ return results
200
+
201
+ def get_by_agent(self, agent: str, limit: int = 20) -> List[HistoryEntry]:
202
+ """Get entries for a specific agent."""
203
+ results = []
204
+ for entry in reversed(self._entries):
205
+ if entry.agent == agent:
206
+ results.append(entry)
207
+ if len(results) >= limit:
208
+ break
209
+ return results
210
+
211
+ def clear(self) -> None:
212
+ """Clear all history."""
213
+ self._entries.clear()
214
+ self._position = 0
215
+ try:
216
+ self.history_file.unlink(missing_ok=True)
217
+ except Exception:
218
+ pass
219
+
220
+ def set_session(self, session_id: str) -> None:
221
+ """Set the current session ID."""
222
+ self._current_session = session_id
223
+
224
+ @property
225
+ def size(self) -> int:
226
+ """Get the number of history entries."""
227
+ return len(self._entries)
228
+
229
+ @property
230
+ def entries(self) -> List[HistoryEntry]:
231
+ """Get all entries."""
232
+ return self._entries.copy()
233
+
234
+
235
+ def render_history(
236
+ entries: List[HistoryEntry], console, limit: int = 20, show_metadata: bool = False
237
+ ) -> None:
238
+ """Render history entries."""
239
+ from rich.text import Text
240
+ from rich.panel import Panel
241
+ from rich.box import ROUNDED
242
+
243
+ if not entries:
244
+ console.print(" [dim]No history[/dim]")
245
+ return
246
+
247
+ header = Text()
248
+ header.append(" 📜 ", style="bold")
249
+ header.append("Command History", style="bold white")
250
+ header.append(f" ({len(entries)} entries)", style="dim")
251
+
252
+ console.print(Panel(header, border_style="#a855f7", box=ROUNDED, padding=(0, 1)))
253
+
254
+ for i, entry in enumerate(entries[:limit]):
255
+ line = Text()
256
+
257
+ # Timestamp
258
+ dt = datetime.fromtimestamp(entry.timestamp)
259
+ time_str = dt.strftime("%H:%M:%S")
260
+ line.append(f" {time_str} ", style="dim")
261
+
262
+ # Mode/Agent indicator
263
+ if entry.agent:
264
+ line.append(f"[{entry.agent}] ", style="bold cyan")
265
+ elif entry.mode:
266
+ line.append(f"[{entry.mode}] ", style="bold green")
267
+
268
+ # Success indicator
269
+ if not entry.success:
270
+ line.append("✗ ", style="red")
271
+
272
+ # Command
273
+ cmd = entry.input[:60] + "..." if len(entry.input) > 60 else entry.input
274
+ line.append(cmd, style="white")
275
+
276
+ console.print(line)
277
+
278
+ if len(entries) > limit:
279
+ console.print(f" [dim]... and {len(entries) - limit} more[/dim]")
@@ -0,0 +1,124 @@
1
+ """SuperOpt command-based runner for OSS."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ from pathlib import Path
8
+ from typing import Any, Dict, Optional
9
+
10
+ from superopt import AgenticEnvironment, ExecutionTrace, SuperOpt
11
+ from superopt.core.environment import PromptConfig, RetrievalConfig, ToolSchema
12
+
13
+ from superqode.agent.system_prompts import SystemPromptLevel, get_system_prompt
14
+ from superqode.config import load_config
15
+ from superqode.tools.base import ToolRegistry
16
+
17
+
18
+ def _pick_job_description(project_root: Path) -> str:
19
+ try:
20
+ config = load_config(project_root)
21
+ except Exception:
22
+ return "You are a QE agent."
23
+
24
+ qe_mode = config.team.modes.get("qe") if config.team else None
25
+ if qe_mode and qe_mode.roles:
26
+ for role in qe_mode.roles.values():
27
+ if role.enabled and role.job_description:
28
+ return role.job_description.strip()
29
+
30
+ if config.default and config.default.job_description:
31
+ return config.default.job_description.strip()
32
+
33
+ return "You are a QE agent."
34
+
35
+
36
+ def _build_environment(project_root: Path) -> AgenticEnvironment:
37
+ system_prompt = get_system_prompt(
38
+ level=SystemPromptLevel.STANDARD,
39
+ working_directory=project_root,
40
+ )
41
+ job_description = _pick_job_description(project_root)
42
+
43
+ prompt_config = PromptConfig(
44
+ system_prompt=system_prompt,
45
+ instruction_policy=job_description,
46
+ )
47
+
48
+ registry = ToolRegistry.default()
49
+ tools: Dict[str, ToolSchema] = {}
50
+ for tool in registry.list():
51
+ tools[tool.name] = ToolSchema(
52
+ name=tool.name,
53
+ description=tool.description,
54
+ arguments=tool.parameters,
55
+ )
56
+
57
+ retrieval_config = RetrievalConfig()
58
+
59
+ return AgenticEnvironment(
60
+ prompts=prompt_config,
61
+ tools=tools,
62
+ retrieval=retrieval_config,
63
+ memory=[],
64
+ )
65
+
66
+
67
+ def _build_trace(result: Dict[str, Any]) -> ExecutionTrace:
68
+ findings = result.get("findings", [])
69
+ critical = [f for f in findings if f.get("severity") == "critical"]
70
+ tests_failed = result.get("tests_failed", 0)
71
+ status = result.get("status", "")
72
+
73
+ success = status == "completed" and tests_failed == 0 and not critical
74
+ failure_message = result.get("verdict") if not success else None
75
+
76
+ trace = ExecutionTrace(
77
+ task_description=f"QE session {result.get('session_id', 'unknown')}",
78
+ success=success,
79
+ failure_message=failure_message,
80
+ )
81
+
82
+ if tests_failed:
83
+ trace.test_failures = [f"{tests_failed} test(s) failed"]
84
+
85
+ errors = result.get("errors", [])
86
+ if errors:
87
+ trace.runtime_exceptions = list(errors)
88
+
89
+ return trace
90
+
91
+
92
+ def run_superopt(trace_path: Path, output_path: Path, project_root: Path) -> int:
93
+ try:
94
+ result = json.loads(trace_path.read_text())
95
+ except Exception as exc:
96
+ raise SystemExit(f"Failed to read trace: {exc}") from exc
97
+
98
+ environment = _build_environment(project_root)
99
+ trace = _build_trace(result)
100
+
101
+ optimizer = SuperOpt(environment=environment)
102
+ optimizer.step(trace)
103
+
104
+ output_path.parent.mkdir(parents=True, exist_ok=True)
105
+ output_path.write_text(json.dumps(optimizer.environment.to_dict(), indent=2))
106
+ return 0
107
+
108
+
109
+ def main() -> None:
110
+ parser = argparse.ArgumentParser(description="Run SuperOpt optimization.")
111
+ parser.add_argument("--trace", required=True, help="Path to QE result JSON")
112
+ parser.add_argument("--out", required=True, help="Output path for environment JSON")
113
+ parser.add_argument("--project-root", default=".", help="Project root")
114
+ args = parser.parse_args()
115
+
116
+ trace_path = Path(args.trace).resolve()
117
+ output_path = Path(args.out).resolve()
118
+ project_root = Path(args.project_root).resolve()
119
+
120
+ raise SystemExit(run_superopt(trace_path, output_path, project_root))
121
+
122
+
123
+ if __name__ == "__main__":
124
+ main()
@@ -0,0 +1,49 @@
1
+ """
2
+ SuperQode Unified Logging System.
3
+
4
+ Provides consistent logging across all provider modes (ACP, BYOK, Local).
5
+ """
6
+
7
+ from superqode.logging.unified_log import (
8
+ LogVerbosity,
9
+ LogConfig,
10
+ LogEntry,
11
+ LogKind,
12
+ LogSource,
13
+ UnifiedLogger,
14
+ LogSink,
15
+ )
16
+ from superqode.logging.formatter import UnifiedLogFormatter
17
+ from superqode.logging.sinks import ConversationLogSink, BufferSink, CallbackSink
18
+ from superqode.logging.adapters import (
19
+ BYOKAdapter,
20
+ LocalAdapter,
21
+ ACPAdapter,
22
+ create_adapter,
23
+ )
24
+ from superqode.logging.integration import TUILoggerManager, create_tui_logger
25
+
26
+ __all__ = [
27
+ # Core
28
+ "LogVerbosity",
29
+ "LogConfig",
30
+ "LogEntry",
31
+ "LogKind",
32
+ "LogSource",
33
+ "UnifiedLogger",
34
+ "LogSink",
35
+ # Formatting
36
+ "UnifiedLogFormatter",
37
+ # Sinks
38
+ "ConversationLogSink",
39
+ "BufferSink",
40
+ "CallbackSink",
41
+ # Adapters
42
+ "BYOKAdapter",
43
+ "LocalAdapter",
44
+ "ACPAdapter",
45
+ "create_adapter",
46
+ # TUI Integration
47
+ "TUILoggerManager",
48
+ "create_tui_logger",
49
+ ]
@@ -0,0 +1,219 @@
1
+ """
2
+ Provider Adapters for SuperQode Unified Logging.
3
+
4
+ These adapters bridge existing callback interfaces to the unified logging system.
5
+ Each adapter converts provider-specific events into LogEntry objects.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Awaitable, Callable, Optional, TYPE_CHECKING
11
+ import asyncio
12
+
13
+ from superqode.logging.unified_log import LogEntry, LogSource, UnifiedLogger
14
+
15
+ if TYPE_CHECKING:
16
+ from superqode.tools.base import ToolResult
17
+
18
+
19
+ class BYOKAdapter:
20
+ """
21
+ Adapter for BYOK (LiteLLM Gateway) mode.
22
+
23
+ Bridges on_tool_call, on_tool_result, and on_thinking callbacks
24
+ to the unified logging system.
25
+ """
26
+
27
+ def __init__(self, logger: UnifiedLogger):
28
+ self.logger = logger
29
+ self._span_ids: dict[str, str] = {} # tool_name -> span_id
30
+
31
+ def on_tool_call(self, name: str, args: dict) -> None:
32
+ """Handle tool call - emit to unified logger."""
33
+ span_id = self.logger.tool_call(name, args, source="byok")
34
+ self._span_ids[name] = span_id
35
+
36
+ def on_tool_result(self, name: str, result: Any) -> None:
37
+ """Handle tool result - emit to unified logger."""
38
+ from superqode.tools.base import ToolResult
39
+
40
+ span_id = self._span_ids.pop(name, None)
41
+
42
+ if isinstance(result, ToolResult):
43
+ success = result.success
44
+ output = str(result.output) if result.output else ""
45
+ if not success and result.error:
46
+ output = str(result.error)
47
+ else:
48
+ success = True
49
+ output = str(result) if result else ""
50
+
51
+ self.logger.tool_result(name, output, success, source="byok", span_id=span_id)
52
+
53
+ async def on_thinking_async(self, text: str) -> None:
54
+ """Handle thinking text - emit to unified logger."""
55
+ if text and text.strip():
56
+ self.logger.thinking(text, source="byok")
57
+
58
+ def on_thinking_sync(self, text: str) -> None:
59
+ """Synchronous thinking handler."""
60
+ if text and text.strip():
61
+ self.logger.thinking(text, source="byok")
62
+
63
+ def get_callbacks(self) -> dict[str, Callable]:
64
+ """Get callback functions for use with pure_mode."""
65
+ return {
66
+ "on_tool_call": self.on_tool_call,
67
+ "on_tool_result": self.on_tool_result,
68
+ "on_thinking": self.on_thinking_async,
69
+ }
70
+
71
+
72
+ class LocalAdapter:
73
+ """
74
+ Adapter for Local models (Ollama, etc.).
75
+
76
+ Handles streaming responses with code block detection and formatting.
77
+ """
78
+
79
+ def __init__(self, logger: UnifiedLogger):
80
+ self.logger = logger
81
+ self._response_buffer = ""
82
+ self._in_code_block = False
83
+ self._code_language = ""
84
+ self._code_buffer = ""
85
+
86
+ def on_tool_call(self, name: str, args: dict) -> None:
87
+ """Handle tool call."""
88
+ self.logger.tool_call(name, args, source="local")
89
+
90
+ def on_tool_result(self, name: str, result: Any) -> None:
91
+ """Handle tool result."""
92
+ from superqode.tools.base import ToolResult
93
+
94
+ if isinstance(result, ToolResult):
95
+ self.logger.tool_result(
96
+ name,
97
+ str(result.output) if result.output else "",
98
+ result.success,
99
+ source="local",
100
+ )
101
+ else:
102
+ self.logger.tool_result(name, str(result), True, source="local")
103
+
104
+ async def on_thinking_async(self, text: str) -> None:
105
+ """Handle thinking text."""
106
+ if text and text.strip():
107
+ self.logger.thinking(text, source="local")
108
+
109
+ def on_response_chunk(self, text: str) -> None:
110
+ """Handle streaming response chunk with code detection."""
111
+ if not text:
112
+ return
113
+
114
+ self._response_buffer += text
115
+ self.logger.response_chunk(text, source="local")
116
+
117
+ def on_response_complete(self) -> str:
118
+ """Complete response and return full text."""
119
+ response = self._response_buffer
120
+ self._response_buffer = ""
121
+ self.logger.response_complete(source="local")
122
+ return response
123
+
124
+ def get_callbacks(self) -> dict[str, Callable]:
125
+ """Get callback functions."""
126
+ return {
127
+ "on_tool_call": self.on_tool_call,
128
+ "on_tool_result": self.on_tool_result,
129
+ "on_thinking": self.on_thinking_async,
130
+ }
131
+
132
+
133
+ class ACPAdapter:
134
+ """
135
+ Adapter for ACP (Agent Client Protocol) mode.
136
+
137
+ Bridges ACP session updates to the unified logging system.
138
+ """
139
+
140
+ def __init__(self, logger: UnifiedLogger):
141
+ self.logger = logger
142
+ self._span_ids: dict[str, str] = {} # tool_call_id -> span_id
143
+ self._message_buffer = ""
144
+
145
+ async def on_message(self, text: str) -> None:
146
+ """Handle agent message chunks."""
147
+ if text:
148
+ self._message_buffer += text
149
+ self.logger.response_chunk(text, source="acp", agent="Agent")
150
+
151
+ async def on_thinking(self, text: str) -> None:
152
+ """Handle agent thinking."""
153
+ if text and text.strip():
154
+ # ACP thinking often comes with prefixes like [agent], strip them
155
+ clean_text = text
156
+ if clean_text.startswith("[agent] "):
157
+ clean_text = clean_text[8:]
158
+ elif clean_text.startswith("["):
159
+ # Remove other prefixes like [startup error], [model switch error], etc.
160
+ bracket_end = clean_text.find("] ")
161
+ if bracket_end > 0:
162
+ clean_text = clean_text[bracket_end + 2 :]
163
+
164
+ self.logger.thinking(clean_text, source="acp")
165
+
166
+ async def on_tool_call(self, tool_call: dict) -> None:
167
+ """Handle ACP tool call."""
168
+ title = tool_call.get("title", "tool")
169
+ raw_input = tool_call.get("rawInput", {})
170
+ tool_call_id = tool_call.get("toolCallId", "")
171
+
172
+ span_id = self.logger.tool_call(title, raw_input, source="acp")
173
+ if tool_call_id:
174
+ self._span_ids[tool_call_id] = span_id
175
+
176
+ async def on_tool_update(self, update: dict) -> None:
177
+ """Handle ACP tool update."""
178
+ status = update.get("status", "")
179
+ tool_call_id = update.get("toolCallId", "")
180
+ output = update.get("rawOutput") or update.get("output") or update.get("result")
181
+
182
+ span_id = self._span_ids.get(tool_call_id)
183
+ title = update.get("title", "tool")
184
+
185
+ if status in ("completed", "done", "success"):
186
+ self.logger.tool_result(
187
+ title, str(output) if output else "", True, source="acp", span_id=span_id
188
+ )
189
+ elif status in ("error", "failed"):
190
+ self.logger.tool_result(
191
+ title, str(output) if output else "failed", False, source="acp", span_id=span_id
192
+ )
193
+
194
+ def on_session_complete(self) -> str:
195
+ """Complete session and return full message."""
196
+ message = self._message_buffer
197
+ self._message_buffer = ""
198
+ self.logger.response_complete(source="acp", agent="Agent")
199
+ return message
200
+
201
+ def get_callbacks(self) -> dict[str, Callable]:
202
+ """Get callback functions for use with ACPClient."""
203
+ return {
204
+ "on_message": self.on_message,
205
+ "on_thinking": self.on_thinking,
206
+ }
207
+
208
+
209
+ def create_adapter(
210
+ logger: UnifiedLogger,
211
+ source: LogSource,
212
+ ) -> BYOKAdapter | LocalAdapter | ACPAdapter:
213
+ """Create an adapter for the given source."""
214
+ if source == "acp":
215
+ return ACPAdapter(logger)
216
+ elif source == "local":
217
+ return LocalAdapter(logger)
218
+ else:
219
+ return BYOKAdapter(logger)