superqode 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,90 @@
1
+ """
2
+ Skill Registry - Central registry for QE skills.
3
+
4
+ Provides skill lookup, registration, and discovery.
5
+ """
6
+
7
+ from typing import Dict, List, Optional, Type
8
+
9
+ from .base import Skill, SkillConfig
10
+
11
+
12
+ class SkillRegistry:
13
+ """Registry for QE skills."""
14
+
15
+ _skills: Dict[str, Type[Skill]] = {}
16
+
17
+ @classmethod
18
+ def register(cls, skill_class: Type[Skill]) -> None:
19
+ """Register a skill."""
20
+ cls._skills[skill_class.NAME] = skill_class
21
+
22
+ @classmethod
23
+ def get(cls, name: str, config: Optional[SkillConfig] = None) -> Optional[Skill]:
24
+ """Get a skill by name."""
25
+ skill_class = cls._skills.get(name)
26
+ if skill_class:
27
+ return skill_class(config)
28
+ return None
29
+
30
+ @classmethod
31
+ def list_all(cls) -> List[Dict[str, str]]:
32
+ """List all registered skills."""
33
+ return [skill().get_info() for skill in cls._skills.values()]
34
+
35
+ @classmethod
36
+ def get_by_category(cls, category: str) -> List[Type[Skill]]:
37
+ """Get skills by category."""
38
+ return [skill for skill in cls._skills.values() if skill.CATEGORY == category]
39
+
40
+ @classmethod
41
+ def get_categories(cls) -> List[str]:
42
+ """Get all skill categories."""
43
+ return list(set(skill.CATEGORY for skill in cls._skills.values()))
44
+
45
+
46
+ def get_skill(name: str, config: Optional[SkillConfig] = None) -> Optional[Skill]:
47
+ """Get a skill by name."""
48
+ return SkillRegistry.get(name, config)
49
+
50
+
51
+ def list_skills() -> List[Dict[str, str]]:
52
+ """List all skills."""
53
+ return SkillRegistry.list_all()
54
+
55
+
56
+ def register_skill(skill_class: Type[Skill]) -> None:
57
+ """Register a skill."""
58
+ SkillRegistry.register(skill_class)
59
+
60
+
61
+ def get_skills_by_category(category: str) -> List[Type[Skill]]:
62
+ """Get skills by category."""
63
+ return SkillRegistry.get_by_category(category)
64
+
65
+
66
+ # Auto-register skills when module is imported
67
+ def _register_all_skills():
68
+ """Register all built-in skills."""
69
+ from .core_skills import (
70
+ TestabilityScoring,
71
+ TDDLondonChicago,
72
+ APITestingPatterns,
73
+ AccessibilityTesting,
74
+ ShiftLeftTesting,
75
+ ChaosEngineeringResilience,
76
+ VisualTestingAdvanced,
77
+ ComplianceTesting,
78
+ )
79
+
80
+ SkillRegistry.register(TestabilityScoring)
81
+ SkillRegistry.register(TDDLondonChicago)
82
+ SkillRegistry.register(APITestingPatterns)
83
+ SkillRegistry.register(AccessibilityTesting)
84
+ SkillRegistry.register(ShiftLeftTesting)
85
+ SkillRegistry.register(ChaosEngineeringResilience)
86
+ SkillRegistry.register(VisualTestingAdvanced)
87
+ SkillRegistry.register(ComplianceTesting)
88
+
89
+
90
+ _register_all_skills()
@@ -0,0 +1,101 @@
1
+ """Minimal fix verifier for OSS (no automation)."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List, Optional
7
+
8
+
9
+ class VerificationStatus(Enum):
10
+ """Status of fix verification."""
11
+
12
+ SKIPPED = "skipped"
13
+
14
+
15
+ @dataclass
16
+ class TestMetrics:
17
+ total_tests: int = 0
18
+ passed: int = 0
19
+ failed: int = 0
20
+ skipped: int = 0
21
+ errors: int = 0
22
+ duration_ms: int = 0
23
+ coverage_percent: Optional[float] = None
24
+
25
+ def to_dict(self) -> Dict[str, Any]:
26
+ return {
27
+ "total_tests": self.total_tests,
28
+ "passed": self.passed,
29
+ "failed": self.failed,
30
+ "skipped": self.skipped,
31
+ "errors": self.errors,
32
+ "duration_ms": self.duration_ms,
33
+ "coverage_percent": self.coverage_percent,
34
+ }
35
+
36
+
37
+ @dataclass
38
+ class VerificationResult:
39
+ status: VerificationStatus
40
+ finding_id: str
41
+ before_metrics: Optional[TestMetrics] = None
42
+ after_metrics: Optional[TestMetrics] = None
43
+ tests_fixed: int = 0
44
+ tests_broken: int = 0
45
+ coverage_delta: float = 0.0
46
+ evidence: List[str] = field(default_factory=list)
47
+ patch_file: Optional[str] = None
48
+ verification_duration_ms: int = 0
49
+ error_message: Optional[str] = None
50
+
51
+ @property
52
+ def is_improvement(self) -> bool:
53
+ return False
54
+
55
+ @property
56
+ def confidence_score(self) -> float:
57
+ return 0.0
58
+
59
+ def to_dict(self) -> Dict[str, Any]:
60
+ return {
61
+ "status": self.status.value,
62
+ "finding_id": self.finding_id,
63
+ "before_metrics": self.before_metrics.to_dict() if self.before_metrics else None,
64
+ "after_metrics": self.after_metrics.to_dict() if self.after_metrics else None,
65
+ "tests_fixed": self.tests_fixed,
66
+ "tests_broken": self.tests_broken,
67
+ "coverage_delta": self.coverage_delta,
68
+ "is_improvement": self.is_improvement,
69
+ "confidence_score": self.confidence_score,
70
+ "evidence": self.evidence,
71
+ "patch_file": self.patch_file,
72
+ "verification_duration_ms": self.verification_duration_ms,
73
+ "error_message": self.error_message,
74
+ }
75
+
76
+
77
+ @dataclass
78
+ class FixVerifierConfig:
79
+ test_command: str = "pytest"
80
+ test_args: List[str] = field(default_factory=list)
81
+ coverage_command: Optional[str] = None
82
+ timeout_seconds: int = 300
83
+ require_no_regressions: bool = True
84
+ min_improvement_threshold: float = 0.0
85
+
86
+
87
+ class FixVerifier:
88
+ """OSS placeholder verifier."""
89
+
90
+ def __init__(self, project_root: Path, config: Optional[FixVerifierConfig] = None):
91
+ self.project_root = Path(project_root)
92
+ self.config = config or FixVerifierConfig()
93
+
94
+ def verify_fix(
95
+ self,
96
+ finding_id: str,
97
+ patch_content: str,
98
+ target_file: Optional[Path] = None,
99
+ apply_patch_fn=None,
100
+ ) -> VerificationResult:
101
+ return VerificationResult(status=VerificationStatus.SKIPPED, finding_id=finding_id)
@@ -0,0 +1,76 @@
1
+ """SuperQE CLI entrypoint.
2
+
3
+ Exposes the QE automation commands as a dedicated CLI while keeping
4
+ SuperQode focused on the developer TUI experience.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import click
10
+
11
+ from superqode import __version__
12
+ import shutil
13
+ from pathlib import Path
14
+
15
+ from superqode.commands.qe import qe as qe_group
16
+ from superqode.commands.superqe import superqe as advanced_group
17
+
18
+
19
+ @click.group()
20
+ @click.version_option(version=__version__)
21
+ def superqe() -> None:
22
+ """SuperQE - Quality Engineering automation CLI.
23
+
24
+ Use `superqode` for the interactive developer TUI.
25
+ """
26
+
27
+
28
+ def _attach_commands(target: click.Group, source: click.Group) -> None:
29
+ for name, command in source.commands.items():
30
+ target.add_command(command, name=name)
31
+
32
+
33
+ _attach_commands(superqe, qe_group)
34
+
35
+
36
+ @superqe.command("init")
37
+ @click.argument("path", type=click.Path(), default=".")
38
+ @click.option("--force", "-f", is_flag=True, help="Overwrite existing configuration")
39
+ @click.option("--guided", "-g", is_flag=True, help="Run guided setup wizard")
40
+ def init_command(path: str, force: bool, guided: bool) -> None:
41
+ """Initialize superqode.yaml using the full template (non-interactive)."""
42
+ if guided:
43
+ from superqode.commands.init import init as guided_init
44
+
45
+ guided_init(path=path, force=force, minimal=False, guided=True)
46
+ return
47
+
48
+ project_root = Path(path).resolve()
49
+ config_path = project_root / "superqode.yaml"
50
+
51
+ if config_path.exists() and not force:
52
+ click.echo(f"Configuration already exists at {config_path}")
53
+ click.echo("Use --force to overwrite")
54
+ return
55
+
56
+ template_path = Path(__file__).resolve().parents[2] / "superqode-template.yaml"
57
+ if template_path.exists():
58
+ shutil.copy2(template_path, config_path)
59
+ click.echo(f"✓ Created {config_path} with all roles available")
60
+ else:
61
+ click.echo("Template not found; falling back to guided setup.")
62
+ from superqode.commands.init import init as guided_init
63
+
64
+ guided_init(path=path, force=force, minimal=False, guided=True)
65
+
66
+
67
+ superqe.add_command(advanced_group, name="advanced")
68
+
69
+
70
+ def main() -> None:
71
+ """Run the SuperQE CLI."""
72
+ superqe()
73
+
74
+
75
+ if __name__ == "__main__":
76
+ main()
superqode/tool_call.py ADDED
@@ -0,0 +1,358 @@
1
+ """
2
+ SuperQode Tool Call Display - Agent Tool Execution Visualization
3
+
4
+ Beautiful display for agent tool calls with:
5
+ - Expandable/collapsible content
6
+ - Status indicators
7
+ - Embedded diffs
8
+ - Syntax highlighting
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass, field
14
+ from enum import Enum
15
+ from typing import List, Optional, Any, Dict
16
+ from datetime import datetime
17
+
18
+ from rich.console import Console
19
+ from rich.panel import Panel
20
+ from rich.text import Text
21
+ from rich.syntax import Syntax
22
+ from rich.markdown import Markdown
23
+ from rich.box import ROUNDED, SIMPLE
24
+
25
+
26
+ class ToolStatus(Enum):
27
+ """Tool call status."""
28
+
29
+ PENDING = "pending"
30
+ IN_PROGRESS = "in_progress"
31
+ COMPLETED = "completed"
32
+ FAILED = "failed"
33
+
34
+
35
+ class ToolKind(Enum):
36
+ """Type of tool operation."""
37
+
38
+ READ = "read"
39
+ WRITE = "write"
40
+ EDIT = "edit"
41
+ SHELL = "shell"
42
+ SEARCH = "search"
43
+ OTHER = "other"
44
+
45
+
46
+ @dataclass
47
+ class ToolCallContent:
48
+ """Content from a tool call."""
49
+
50
+ content_type: str # "text", "diff", "code", "markdown"
51
+ data: Any
52
+ language: Optional[str] = None
53
+
54
+
55
+ @dataclass
56
+ class ToolCall:
57
+ """A tool call from the agent."""
58
+
59
+ id: str
60
+ name: str
61
+ title: str
62
+ kind: ToolKind = ToolKind.OTHER
63
+ status: ToolStatus = ToolStatus.PENDING
64
+ content: List[ToolCallContent] = field(default_factory=list)
65
+ arguments: Dict[str, Any] = field(default_factory=dict)
66
+ result: Optional[str] = None
67
+ error: Optional[str] = None
68
+ started_at: Optional[datetime] = None
69
+ completed_at: Optional[datetime] = None
70
+ expanded: bool = False
71
+
72
+
73
+ # SuperQode tool call colors
74
+ TOOL_COLORS = {
75
+ # Status colors
76
+ "pending": "#71717a",
77
+ "in_progress": "#06b6d4",
78
+ "completed": "#22c55e",
79
+ "failed": "#ef4444",
80
+ # Kind colors
81
+ "read": "#3b82f6",
82
+ "write": "#f97316",
83
+ "edit": "#eab308",
84
+ "shell": "#8b5cf6",
85
+ "search": "#06b6d4",
86
+ "other": "#71717a",
87
+ # UI colors
88
+ "header": "#a855f7",
89
+ "border": "#2a2a2a",
90
+ "content_bg": "#111111",
91
+ }
92
+
93
+ # Status icons
94
+ STATUS_ICONS = {
95
+ ToolStatus.PENDING: "⏳",
96
+ ToolStatus.IN_PROGRESS: "🔄",
97
+ ToolStatus.COMPLETED: "✅",
98
+ ToolStatus.FAILED: "❌",
99
+ }
100
+
101
+ # Kind icons
102
+ KIND_ICONS = {
103
+ ToolKind.READ: "📖",
104
+ ToolKind.WRITE: "✏️",
105
+ ToolKind.EDIT: "📝",
106
+ ToolKind.SHELL: "💻",
107
+ ToolKind.SEARCH: "🔍",
108
+ ToolKind.OTHER: "🔧",
109
+ }
110
+
111
+
112
+ class ToolCallManager:
113
+ """Manages tool call display and tracking."""
114
+
115
+ def __init__(self):
116
+ self.calls: List[ToolCall] = []
117
+ self.auto_expand: str = "both" # "always", "never", "success", "fail", "both"
118
+ self._call_counter = 0
119
+
120
+ def _generate_id(self) -> str:
121
+ """Generate a unique tool call ID."""
122
+ self._call_counter += 1
123
+ return f"tool_{self._call_counter}"
124
+
125
+ def add_call(
126
+ self,
127
+ name: str,
128
+ title: str,
129
+ kind: ToolKind = ToolKind.OTHER,
130
+ arguments: Optional[Dict[str, Any]] = None,
131
+ ) -> ToolCall:
132
+ """Add a new tool call."""
133
+ call = ToolCall(
134
+ id=self._generate_id(),
135
+ name=name,
136
+ title=title,
137
+ kind=kind,
138
+ arguments=arguments or {},
139
+ started_at=datetime.now(),
140
+ )
141
+ self.calls.append(call)
142
+ return call
143
+
144
+ def update_status(self, call_id: str, status: ToolStatus) -> bool:
145
+ """Update a tool call's status."""
146
+ for call in self.calls:
147
+ if call.id == call_id:
148
+ call.status = status
149
+ if status in (ToolStatus.COMPLETED, ToolStatus.FAILED):
150
+ call.completed_at = datetime.now()
151
+ # Auto-expand based on settings
152
+ call.expanded = self._should_expand(call)
153
+ return True
154
+ return False
155
+
156
+ def _should_expand(self, call: ToolCall) -> bool:
157
+ """Determine if a call should auto-expand."""
158
+ if self.auto_expand == "always":
159
+ return True
160
+ if self.auto_expand == "never":
161
+ return False
162
+ if self.auto_expand == "success":
163
+ return call.status == ToolStatus.COMPLETED
164
+ if self.auto_expand == "fail":
165
+ return call.status == ToolStatus.FAILED
166
+ if self.auto_expand == "both":
167
+ return call.status in (ToolStatus.COMPLETED, ToolStatus.FAILED)
168
+ return False
169
+
170
+ def add_content(
171
+ self, call_id: str, content_type: str, data: Any, language: Optional[str] = None
172
+ ) -> bool:
173
+ """Add content to a tool call."""
174
+ for call in self.calls:
175
+ if call.id == call_id:
176
+ call.content.append(
177
+ ToolCallContent(content_type=content_type, data=data, language=language)
178
+ )
179
+ return True
180
+ return False
181
+
182
+ def complete(self, call_id: str, result: Optional[str] = None) -> bool:
183
+ """Mark a tool call as completed."""
184
+ for call in self.calls:
185
+ if call.id == call_id:
186
+ call.status = ToolStatus.COMPLETED
187
+ call.result = result
188
+ call.completed_at = datetime.now()
189
+ call.expanded = self._should_expand(call)
190
+ return True
191
+ return False
192
+
193
+ def fail(self, call_id: str, error: str) -> bool:
194
+ """Mark a tool call as failed."""
195
+ for call in self.calls:
196
+ if call.id == call_id:
197
+ call.status = ToolStatus.FAILED
198
+ call.error = error
199
+ call.completed_at = datetime.now()
200
+ call.expanded = self._should_expand(call)
201
+ return True
202
+ return False
203
+
204
+ def toggle_expand(self, call_id: str) -> bool:
205
+ """Toggle expansion state of a tool call."""
206
+ for call in self.calls:
207
+ if call.id == call_id:
208
+ call.expanded = not call.expanded
209
+ return True
210
+ return False
211
+
212
+ def get_recent(self, count: int = 10) -> List[ToolCall]:
213
+ """Get the most recent tool calls."""
214
+ return self.calls[-count:]
215
+
216
+ def clear(self) -> None:
217
+ """Clear all tool calls."""
218
+ self.calls.clear()
219
+ self._call_counter = 0
220
+
221
+
222
+ def render_tool_call(call: ToolCall, console: Console, show_content: bool = True) -> None:
223
+ """Render a single tool call."""
224
+ status_icon = STATUS_ICONS.get(call.status, "🔧")
225
+ kind_icon = KIND_ICONS.get(call.kind, "🔧")
226
+ status_color = TOOL_COLORS.get(call.status.value, TOOL_COLORS["pending"])
227
+ kind_color = TOOL_COLORS.get(call.kind.value, TOOL_COLORS["other"])
228
+
229
+ # Header line
230
+ header = Text()
231
+
232
+ # Expand indicator
233
+ if call.content:
234
+ expand_icon = "▼" if call.expanded else "▶"
235
+ header.append(f"{expand_icon} ", style="dim")
236
+ else:
237
+ header.append(" ", style="")
238
+
239
+ # Kind icon and title
240
+ header.append(f"{kind_icon} ", style=kind_color)
241
+ header.append(call.title, style=f"bold {status_color}")
242
+
243
+ # Status indicator
244
+ header.append(" ", style="")
245
+ if call.status == ToolStatus.PENDING:
246
+ header.append("⏳", style=status_color)
247
+ elif call.status == ToolStatus.IN_PROGRESS:
248
+ header.append("🔄", style=status_color)
249
+ elif call.status == ToolStatus.COMPLETED:
250
+ header.append("✔", style=status_color)
251
+ elif call.status == ToolStatus.FAILED:
252
+ header.append("✗", style=status_color)
253
+
254
+ # Duration
255
+ if call.completed_at and call.started_at:
256
+ duration = (call.completed_at - call.started_at).total_seconds()
257
+ header.append(f" ({duration:.2f}s)", style="dim")
258
+
259
+ console.print(header)
260
+
261
+ # Content (if expanded)
262
+ if show_content and call.expanded and call.content:
263
+ render_tool_content(call, console)
264
+
265
+ # Error message
266
+ if call.error:
267
+ console.print(f" [red]Error: {call.error}[/red]")
268
+
269
+
270
+ def render_tool_content(call: ToolCall, console: Console) -> None:
271
+ """Render the content of a tool call."""
272
+ for content in call.content:
273
+ if content.content_type == "text":
274
+ # Plain text
275
+ text = str(content.data)
276
+ if len(text) > 500:
277
+ text = text[:500] + "..."
278
+ console.print(f" [dim]{text}[/dim]")
279
+
280
+ elif content.content_type == "code":
281
+ # Syntax highlighted code
282
+ lang = content.language or "text"
283
+ syntax = Syntax(
284
+ str(content.data),
285
+ lang,
286
+ theme="monokai",
287
+ line_numbers=True,
288
+ word_wrap=True,
289
+ background_color="#000000",
290
+ )
291
+ console.print(
292
+ Panel(syntax, border_style=TOOL_COLORS["border"], box=SIMPLE, padding=(0, 1))
293
+ )
294
+
295
+ elif content.content_type == "diff":
296
+ # Diff display
297
+ from superqode.diff_view import compute_diff, render_diff_unified
298
+
299
+ if isinstance(content.data, dict):
300
+ diff = compute_diff(
301
+ content.data.get("old", ""),
302
+ content.data.get("new", ""),
303
+ content.data.get("path", "file"),
304
+ )
305
+ render_diff_unified(diff, console)
306
+
307
+ elif content.content_type == "markdown":
308
+ # Markdown content
309
+ md = Markdown(str(content.data))
310
+ console.print(Panel(md, border_style=TOOL_COLORS["border"], box=SIMPLE, padding=(0, 1)))
311
+
312
+
313
+ def render_tool_calls(manager: ToolCallManager, console: Console, limit: int = 10) -> None:
314
+ """Render recent tool calls."""
315
+ calls = manager.get_recent(limit)
316
+
317
+ if not calls:
318
+ console.print(" [dim]No tool calls yet[/dim]")
319
+ return
320
+
321
+ # Header
322
+ header = Text()
323
+ header.append(" 🔧 ", style="bold")
324
+ header.append("Tool Calls", style="bold white")
325
+ header.append(f" ({len(calls)})", style="dim")
326
+
327
+ console.print(Panel(header, border_style=TOOL_COLORS["header"], box=ROUNDED, padding=(0, 1)))
328
+
329
+ # Render each call
330
+ for call in calls:
331
+ render_tool_call(call, console)
332
+
333
+
334
+ def render_tool_summary(manager: ToolCallManager, console: Console) -> None:
335
+ """Render a compact summary of tool calls."""
336
+ if not manager.calls:
337
+ return
338
+
339
+ completed = sum(1 for c in manager.calls if c.status == ToolStatus.COMPLETED)
340
+ failed = sum(1 for c in manager.calls if c.status == ToolStatus.FAILED)
341
+ pending = sum(
342
+ 1 for c in manager.calls if c.status in (ToolStatus.PENDING, ToolStatus.IN_PROGRESS)
343
+ )
344
+
345
+ line = Text()
346
+ line.append("🔧 ", style="")
347
+ line.append(f"{completed}", style=f"bold {TOOL_COLORS['completed']}")
348
+ line.append("✔ ", style=TOOL_COLORS["completed"])
349
+
350
+ if failed:
351
+ line.append(f"{failed}", style=f"bold {TOOL_COLORS['failed']}")
352
+ line.append("✗ ", style=TOOL_COLORS["failed"])
353
+
354
+ if pending:
355
+ line.append(f"{pending}", style=f"bold {TOOL_COLORS['pending']}")
356
+ line.append("⏳", style=TOOL_COLORS["pending"])
357
+
358
+ console.print(line)