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/pure_mode.py ADDED
@@ -0,0 +1,384 @@
1
+ """
2
+ Pure Mode - Minimal Harness for Fair Model Testing.
3
+
4
+ Integrates with both TUI and CLI for testing model coding capabilities
5
+ without the bias of heavy harnesses.
6
+ """
7
+
8
+ import asyncio
9
+ from dataclasses import dataclass, field
10
+ from pathlib import Path
11
+ from typing import Any, Awaitable, Callable, Dict, List, Optional
12
+
13
+ from rich.console import Console
14
+ from rich.panel import Panel
15
+ from rich.table import Table
16
+ from rich.text import Text
17
+
18
+ from .agent.loop import AgentLoop, AgentConfig, AgentResponse
19
+ from .agent.system_prompts import SystemPromptLevel
20
+ from .tools.base import ToolRegistry, ToolResult
21
+ from .providers.gateway.litellm_gateway import LiteLLMGateway
22
+ from .providers.registry import PROVIDERS, ProviderTier, ProviderCategory
23
+
24
+
25
+ @dataclass
26
+ class PureSession:
27
+ """Session state for Pure Mode."""
28
+
29
+ provider: str = ""
30
+ model: str = ""
31
+ system_level: SystemPromptLevel = SystemPromptLevel.MINIMAL
32
+ working_directory: Path = field(default_factory=Path.cwd)
33
+ connected: bool = False
34
+
35
+ # Stats
36
+ total_tool_calls: int = 0
37
+ total_iterations: int = 0
38
+ total_requests: int = 0
39
+
40
+
41
+ class PureMode:
42
+ """Pure Mode manager for TUI and CLI integration."""
43
+
44
+ def __init__(self):
45
+ self.session = PureSession()
46
+ self.gateway = LiteLLMGateway()
47
+ self.tools = ToolRegistry.default()
48
+ self._agent: Optional[AgentLoop] = None
49
+
50
+ # Callbacks for UI updates
51
+ self.on_tool_call: Optional[Callable[[str, Dict], None]] = None
52
+ self.on_tool_result: Optional[Callable[[str, ToolResult], None]] = None
53
+ self.on_thinking: Optional[Callable[[str], Awaitable[None]]] = None
54
+ self.on_stream_chunk: Optional[Callable[[str], None]] = None
55
+
56
+ def get_providers_for_picker(self) -> List[Dict[str, Any]]:
57
+ """Get providers formatted for the TUI picker."""
58
+ providers = []
59
+
60
+ # Group by tier
61
+ tier_order = [ProviderTier.TIER1, ProviderTier.TIER2, ProviderTier.FREE, ProviderTier.LOCAL]
62
+
63
+ for tier in tier_order:
64
+ tier_providers = [p for p in PROVIDERS.values() if p.tier == tier]
65
+ for p in sorted(tier_providers, key=lambda x: x.name):
66
+ # Check if configured (has env var set)
67
+ import os
68
+
69
+ configured = any(os.environ.get(env) for env in p.env_vars) if p.env_vars else True
70
+
71
+ providers.append(
72
+ {
73
+ "id": p.id,
74
+ "name": p.name,
75
+ "tier": tier.name,
76
+ "category": p.category.value,
77
+ "configured": configured,
78
+ "example_models": p.example_models[:3],
79
+ "notes": p.notes,
80
+ }
81
+ )
82
+
83
+ return providers
84
+
85
+ def get_models_for_provider(self, provider_id: str) -> List[str]:
86
+ """Get example models for a provider."""
87
+ provider = PROVIDERS.get(provider_id)
88
+ if provider:
89
+ return provider.example_models
90
+ return []
91
+
92
+ def connect(
93
+ self,
94
+ provider: str,
95
+ model: str,
96
+ system_level: SystemPromptLevel = SystemPromptLevel.MINIMAL,
97
+ working_directory: Optional[Path] = None,
98
+ job_description: Optional[str] = None,
99
+ role_config: Optional[Any] = None,
100
+ ) -> bool:
101
+ """Connect to a provider in Pure Mode.
102
+
103
+ Args:
104
+ provider: Provider ID (e.g., "ollama", "anthropic")
105
+ model: Model name (e.g., "llama3.2:3b")
106
+ system_level: System prompt verbosity level
107
+ working_directory: Optional working directory
108
+ job_description: Optional job description for role-based connections
109
+ role_config: Optional ResolvedRole config for role context
110
+ """
111
+ self.session.provider = provider
112
+ self.session.model = model
113
+ self.session.system_level = system_level
114
+ self.session.working_directory = working_directory or Path.cwd()
115
+ self.session.connected = True
116
+
117
+ # Create agent loop with job description if provided
118
+ config = AgentConfig(
119
+ provider=provider,
120
+ model=model,
121
+ system_prompt_level=system_level,
122
+ working_directory=self.session.working_directory,
123
+ job_description=job_description,
124
+ )
125
+
126
+ self._agent = AgentLoop(
127
+ gateway=self.gateway,
128
+ tools=self.tools,
129
+ config=config,
130
+ on_tool_call=self.on_tool_call,
131
+ on_tool_result=self.on_tool_result,
132
+ on_thinking=self.on_thinking,
133
+ )
134
+
135
+ # Ensure callbacks are set on the agent (in case they were set after agent creation)
136
+ if self._agent:
137
+ self._agent.on_tool_call = self.on_tool_call
138
+ self._agent.on_tool_result = self.on_tool_result
139
+ self._agent.on_thinking = self.on_thinking
140
+
141
+ return True
142
+
143
+ def disconnect(self):
144
+ """Disconnect from Pure Mode."""
145
+ self.session = PureSession()
146
+ self._agent = None
147
+
148
+ def set_system_level(self, level: SystemPromptLevel):
149
+ """Change the system prompt level."""
150
+ self.session.system_level = level
151
+ if self._agent:
152
+ self._agent.config.system_prompt_level = level
153
+ self._agent.system_prompt = self._agent._build_system_prompt()
154
+
155
+ async def run(self, prompt: str) -> AgentResponse:
156
+ """Run a task in Pure Mode."""
157
+ if not self._agent:
158
+ raise RuntimeError("Not connected. Call connect() first.")
159
+
160
+ response = await self._agent.run(prompt)
161
+
162
+ # Update stats
163
+ self.session.total_tool_calls += response.tool_calls_made
164
+ self.session.total_iterations += response.iterations
165
+ self.session.total_requests += 1
166
+
167
+ return response
168
+
169
+ async def run_streaming(self, prompt: str):
170
+ """Run a task with streaming output."""
171
+ if not self._agent:
172
+ raise RuntimeError("Not connected. Call connect() first.")
173
+
174
+ # Reset cancellation flag for new operation
175
+ self._agent.reset_cancellation()
176
+
177
+ async for chunk in self._agent.run_streaming(prompt):
178
+ if self.on_stream_chunk:
179
+ self.on_stream_chunk(chunk)
180
+ yield chunk
181
+
182
+ self.session.total_requests += 1
183
+
184
+ def cancel(self):
185
+ """Cancel the current agent operation."""
186
+ if self._agent:
187
+ self._agent.cancel()
188
+
189
+ def get_status(self) -> Dict[str, Any]:
190
+ """Get current Pure Mode status."""
191
+ return {
192
+ "connected": self.session.connected,
193
+ "provider": self.session.provider,
194
+ "model": self.session.model,
195
+ "system_level": self.session.system_level.value,
196
+ "working_directory": str(self.session.working_directory),
197
+ "stats": {
198
+ "total_requests": self.session.total_requests,
199
+ "total_tool_calls": self.session.total_tool_calls,
200
+ "total_iterations": self.session.total_iterations,
201
+ },
202
+ "tools": [t.name for t in self.tools.list()],
203
+ }
204
+
205
+
206
+ def render_provider_picker(console: Console) -> tuple[str, str]:
207
+ """Interactive provider picker for TUI.
208
+
209
+ Returns:
210
+ Tuple of (provider_id, model)
211
+ """
212
+ pure = PureMode()
213
+ providers = pure.get_providers_for_picker()
214
+
215
+ console.print()
216
+ console.print(
217
+ Panel.fit(
218
+ "[bold magenta]🧪 Pure Mode[/bold magenta]\n"
219
+ "Select a provider to test model coding capabilities",
220
+ border_style="magenta",
221
+ )
222
+ )
223
+ console.print()
224
+
225
+ # Group by tier
226
+ current_tier = None
227
+ tier_names = {
228
+ "TIER1": "⭐ Tier 1 (First-Class Support)",
229
+ "TIER2": "🔷 Tier 2 (Supported)",
230
+ "FREE": "🆓 Free Providers",
231
+ "LOCAL": "🏠 Local Providers",
232
+ }
233
+
234
+ provider_list = []
235
+ idx = 1
236
+
237
+ for p in providers:
238
+ if p["tier"] != current_tier:
239
+ current_tier = p["tier"]
240
+ console.print(f"\n[bold]{tier_names.get(current_tier, current_tier)}[/bold]")
241
+
242
+ status = "✅" if p["configured"] else "○"
243
+ models_hint = ", ".join(p["example_models"][:2]) if p["example_models"] else ""
244
+
245
+ console.print(f" [{idx}] {status} [bold]{p['name']:<15}[/bold] [dim]{models_hint}[/dim]")
246
+ provider_list.append(p)
247
+ idx += 1
248
+
249
+ console.print()
250
+
251
+ # Get provider selection
252
+ while True:
253
+ try:
254
+ choice = console.input("[bold cyan]Select provider (number): [/bold cyan]")
255
+ provider_idx = int(choice) - 1
256
+ if 0 <= provider_idx < len(provider_list):
257
+ selected_provider = provider_list[provider_idx]
258
+ break
259
+ console.print("[red]Invalid selection[/red]")
260
+ except ValueError:
261
+ console.print("[red]Please enter a number[/red]")
262
+
263
+ provider_id = selected_provider["id"]
264
+
265
+ # Get model selection
266
+ models = pure.get_models_for_provider(provider_id)
267
+
268
+ if models:
269
+ console.print(f"\n[bold]Available models for {selected_provider['name']}:[/bold]")
270
+ for i, model in enumerate(models, 1):
271
+ console.print(f" [{i}] {model}")
272
+ console.print(f" [0] Enter custom model")
273
+ console.print()
274
+
275
+ while True:
276
+ try:
277
+ choice = console.input("[bold cyan]Select model (number or name): [/bold cyan]")
278
+ if choice == "0":
279
+ model = console.input("[bold cyan]Enter model name: [/bold cyan]")
280
+ break
281
+ elif choice.isdigit():
282
+ model_idx = int(choice) - 1
283
+ if 0 <= model_idx < len(models):
284
+ model = models[model_idx]
285
+ break
286
+ else:
287
+ # Assume it's a model name
288
+ model = choice
289
+ break
290
+ console.print("[red]Invalid selection[/red]")
291
+ except ValueError:
292
+ console.print("[red]Please enter a number or model name[/red]")
293
+ else:
294
+ model = console.input("[bold cyan]Enter model name: [/bold cyan]")
295
+
296
+ return provider_id, model
297
+
298
+
299
+ def render_system_level_picker(console: Console) -> SystemPromptLevel:
300
+ """Interactive system prompt level picker."""
301
+ console.print()
302
+ console.print("[bold]System Prompt Level:[/bold]")
303
+ console.print(" [1] [yellow]none[/yellow] - No system prompt (pure model behavior)")
304
+ console.print(" [2] [green]minimal[/green] - Just 'You are a coding assistant' [default]")
305
+ console.print(" [3] [cyan]standard[/cyan] - Basic tool usage guidance")
306
+ console.print(" [4] [magenta]full[/magenta] - Detailed instructions (like other agents)")
307
+ console.print()
308
+
309
+ choice = console.input("[bold cyan]Select level (1-4, default=2): [/bold cyan]").strip()
310
+
311
+ level_map = {
312
+ "1": SystemPromptLevel.NONE,
313
+ "2": SystemPromptLevel.MINIMAL,
314
+ "3": SystemPromptLevel.STANDARD,
315
+ "4": SystemPromptLevel.FULL,
316
+ "": SystemPromptLevel.MINIMAL, # Default
317
+ }
318
+
319
+ return level_map.get(choice, SystemPromptLevel.MINIMAL)
320
+
321
+
322
+ def render_pure_status(pure: PureMode, console: Console):
323
+ """Render Pure Mode status panel."""
324
+ status = pure.get_status()
325
+
326
+ if not status["connected"]:
327
+ console.print("[dim]Pure Mode not connected[/dim]")
328
+ return
329
+
330
+ t = Text()
331
+ t.append("🧪 ", style="bold magenta")
332
+ t.append("PURE MODE", style="bold magenta reverse")
333
+ t.append("\n\n")
334
+
335
+ t.append("Provider: ", style="bold")
336
+ t.append(f"{status['provider']}\n", style="cyan")
337
+
338
+ t.append("Model: ", style="bold")
339
+ t.append(f"{status['model']}\n", style="cyan")
340
+
341
+ t.append("System Prompt: ", style="bold")
342
+ t.append(f"{status['system_level']}\n", style="yellow")
343
+
344
+ t.append("\nStats:\n", style="bold")
345
+ t.append(f" Requests: {status['stats']['total_requests']}\n", style="dim")
346
+ t.append(f" Tool Calls: {status['stats']['total_tool_calls']}\n", style="dim")
347
+ t.append(f" Iterations: {status['stats']['total_iterations']}\n", style="dim")
348
+
349
+ t.append(f"\nTools: {len(status['tools'])}\n", style="dim")
350
+
351
+ console.print(Panel(t, border_style="magenta"))
352
+
353
+
354
+ def render_tool_call_inline(name: str, args: Dict, console: Console):
355
+ """Render a tool call inline."""
356
+ console.print(f" [dim]→[/dim] [yellow]{name}[/yellow]", end="")
357
+ if args:
358
+ # Show key args
359
+ key_args = []
360
+ if "path" in args:
361
+ key_args.append(f"path={args['path']}")
362
+ if "command" in args:
363
+ cmd = (
364
+ args["command"][:30] + "..."
365
+ if len(args.get("command", "")) > 30
366
+ else args.get("command", "")
367
+ )
368
+ key_args.append(f"cmd={cmd}")
369
+ if "pattern" in args:
370
+ key_args.append(f"pattern={args['pattern']}")
371
+ if key_args:
372
+ console.print(f" [dim]({', '.join(key_args)})[/dim]")
373
+ else:
374
+ console.print()
375
+ else:
376
+ console.print()
377
+
378
+
379
+ def render_tool_result_inline(name: str, result: ToolResult, console: Console):
380
+ """Render a tool result inline."""
381
+ if result.success:
382
+ console.print(f" [green]✓[/green] [dim]{name}[/dim]")
383
+ else:
384
+ console.print(f" [red]✗[/red] [dim]{name}: {result.error}[/dim]")
@@ -0,0 +1,23 @@
1
+ """
2
+ Quality Report (QR) Module.
3
+
4
+ Generates research-grade QA reports that document:
5
+ - Investigation objective and scope
6
+ - Attack and exploration methodology
7
+ - Reproduction steps and evidence
8
+ - Root cause and failure analysis
9
+ - Suggested fixes and alternatives
10
+ - Benchmark and validation results
11
+ - Production-readiness assessment
12
+ """
13
+
14
+ from .generator import QRGenerator, QRSection, QRVerdict
15
+ from .templates import QRTemplate, get_template
16
+
17
+ __all__ = [
18
+ "QRGenerator",
19
+ "QRSection",
20
+ "QRVerdict",
21
+ "QRTemplate",
22
+ "get_template",
23
+ ]