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,369 @@
1
+ """
2
+ Simple Desktop Notifications for SuperQode.
3
+
4
+ Provides optional desktop notifications for long-running tasks and QE runs.
5
+ Uses OS built-in tools without external dependencies:
6
+ - macOS: osascript (AppleScript)
7
+ - Linux: notify-send (libnotify)
8
+ - Windows: PowerShell toast notifications
9
+
10
+ All notifications are optional and fail silently if the system doesn't support them.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import subprocess
17
+ import sys
18
+ from dataclasses import dataclass
19
+ from enum import Enum
20
+ from typing import Optional
21
+
22
+
23
+ class NotificationLevel(Enum):
24
+ """Notification urgency level."""
25
+
26
+ INFO = "info"
27
+ SUCCESS = "success"
28
+ WARNING = "warning"
29
+ ERROR = "error"
30
+
31
+
32
+ @dataclass
33
+ class NotificationConfig:
34
+ """Configuration for notifications."""
35
+
36
+ enabled: bool = True
37
+ sound: bool = True # Play sound with notification (macOS only)
38
+ timeout_seconds: int = 5 # Auto-dismiss timeout (Linux only)
39
+
40
+
41
+ # Default configuration
42
+ _config = NotificationConfig()
43
+
44
+
45
+ def configure_notifications(
46
+ enabled: bool = True,
47
+ sound: bool = True,
48
+ timeout_seconds: int = 5,
49
+ ) -> None:
50
+ """
51
+ Configure notification behavior.
52
+
53
+ Args:
54
+ enabled: Whether notifications are enabled.
55
+ sound: Whether to play sound with notifications (macOS).
56
+ timeout_seconds: Auto-dismiss timeout in seconds (Linux).
57
+ """
58
+ global _config
59
+ _config = NotificationConfig(
60
+ enabled=enabled,
61
+ sound=sound,
62
+ timeout_seconds=timeout_seconds,
63
+ )
64
+
65
+
66
+ def is_notifications_supported() -> bool:
67
+ """
68
+ Check if desktop notifications are supported on this system.
69
+
70
+ Returns:
71
+ True if notifications are likely to work, False otherwise.
72
+ """
73
+ if sys.platform == "darwin":
74
+ return True # macOS always has osascript
75
+ elif sys.platform == "linux":
76
+ try:
77
+ result = subprocess.run(
78
+ ["which", "notify-send"],
79
+ capture_output=True,
80
+ timeout=2,
81
+ )
82
+ return result.returncode == 0
83
+ except Exception:
84
+ return False
85
+ elif sys.platform == "win32":
86
+ return True # PowerShell is always available on modern Windows
87
+ return False
88
+
89
+
90
+ def notify(
91
+ title: str,
92
+ message: str,
93
+ level: NotificationLevel = NotificationLevel.INFO,
94
+ subtitle: Optional[str] = None,
95
+ ) -> bool:
96
+ """
97
+ Send a desktop notification.
98
+
99
+ This function is synchronous and blocks briefly. For async contexts,
100
+ use notify_async() instead.
101
+
102
+ Args:
103
+ title: Notification title (short, ~50 chars max).
104
+ message: Notification body text.
105
+ level: Notification urgency level (affects icon/sound on some systems).
106
+ subtitle: Optional subtitle (macOS only).
107
+
108
+ Returns:
109
+ True if notification was sent successfully, False otherwise.
110
+ """
111
+ if not _config.enabled:
112
+ return False
113
+
114
+ try:
115
+ if sys.platform == "darwin":
116
+ return _notify_macos(title, message, subtitle, level)
117
+ elif sys.platform == "linux":
118
+ return _notify_linux(title, message, level)
119
+ elif sys.platform == "win32":
120
+ return _notify_windows(title, message, level)
121
+ else:
122
+ return False
123
+ except Exception:
124
+ # Notifications are optional - fail silently
125
+ return False
126
+
127
+
128
+ async def notify_async(
129
+ title: str,
130
+ message: str,
131
+ level: NotificationLevel = NotificationLevel.INFO,
132
+ subtitle: Optional[str] = None,
133
+ ) -> bool:
134
+ """
135
+ Send a desktop notification asynchronously.
136
+
137
+ Non-blocking version of notify() for use in async contexts.
138
+
139
+ Args:
140
+ title: Notification title (short, ~50 chars max).
141
+ message: Notification body text.
142
+ level: Notification urgency level.
143
+ subtitle: Optional subtitle (macOS only).
144
+
145
+ Returns:
146
+ True if notification was sent successfully, False otherwise.
147
+ """
148
+ loop = asyncio.get_event_loop()
149
+ return await loop.run_in_executor(None, notify, title, message, level, subtitle)
150
+
151
+
152
+ def _notify_macos(
153
+ title: str,
154
+ message: str,
155
+ subtitle: Optional[str],
156
+ level: NotificationLevel,
157
+ ) -> bool:
158
+ """Send notification on macOS using osascript."""
159
+ # Build AppleScript command
160
+ script_parts = [f'display notification "{_escape_applescript(message)}"']
161
+ script_parts.append(f'with title "{_escape_applescript(title)}"')
162
+
163
+ if subtitle:
164
+ script_parts.append(f'subtitle "{_escape_applescript(subtitle)}"')
165
+
166
+ # Add sound based on level and config
167
+ if _config.sound:
168
+ sound_map = {
169
+ NotificationLevel.INFO: "default",
170
+ NotificationLevel.SUCCESS: "Glass",
171
+ NotificationLevel.WARNING: "Basso",
172
+ NotificationLevel.ERROR: "Sosumi",
173
+ }
174
+ sound = sound_map.get(level, "default")
175
+ script_parts.append(f'sound name "{sound}"')
176
+
177
+ script = " ".join(script_parts)
178
+
179
+ result = subprocess.run(
180
+ ["osascript", "-e", script],
181
+ capture_output=True,
182
+ timeout=5,
183
+ )
184
+ return result.returncode == 0
185
+
186
+
187
+ def _notify_linux(title: str, message: str, level: NotificationLevel) -> bool:
188
+ """Send notification on Linux using notify-send."""
189
+ # Map level to urgency
190
+ urgency_map = {
191
+ NotificationLevel.INFO: "normal",
192
+ NotificationLevel.SUCCESS: "low",
193
+ NotificationLevel.WARNING: "normal",
194
+ NotificationLevel.ERROR: "critical",
195
+ }
196
+ urgency = urgency_map.get(level, "normal")
197
+
198
+ # Map level to icon
199
+ icon_map = {
200
+ NotificationLevel.INFO: "dialog-information",
201
+ NotificationLevel.SUCCESS: "emblem-ok-symbolic",
202
+ NotificationLevel.WARNING: "dialog-warning",
203
+ NotificationLevel.ERROR: "dialog-error",
204
+ }
205
+ icon = icon_map.get(level, "dialog-information")
206
+
207
+ cmd = [
208
+ "notify-send",
209
+ "--urgency",
210
+ urgency,
211
+ "--icon",
212
+ icon,
213
+ "--expire-time",
214
+ str(_config.timeout_seconds * 1000), # Convert to ms
215
+ "--app-name",
216
+ "SuperQode",
217
+ title,
218
+ message,
219
+ ]
220
+
221
+ result = subprocess.run(cmd, capture_output=True, timeout=5)
222
+ return result.returncode == 0
223
+
224
+
225
+ def _notify_windows(title: str, message: str, level: NotificationLevel) -> bool:
226
+ """Send notification on Windows using PowerShell toast."""
227
+ # PowerShell toast notification
228
+ ps_script = f"""
229
+ [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
230
+ [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType = WindowsRuntime] | Out-Null
231
+
232
+ $template = @"
233
+ <toast>
234
+ <visual>
235
+ <binding template="ToastText02">
236
+ <text id="1">{_escape_xml(title)}</text>
237
+ <text id="2">{_escape_xml(message)}</text>
238
+ </binding>
239
+ </visual>
240
+ </toast>
241
+ "@
242
+
243
+ $xml = New-Object Windows.Data.Xml.Dom.XmlDocument
244
+ $xml.LoadXml($template)
245
+ $toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
246
+ [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("SuperQode").Show($toast)
247
+ """
248
+
249
+ result = subprocess.run(
250
+ ["powershell", "-Command", ps_script],
251
+ capture_output=True,
252
+ timeout=10,
253
+ )
254
+ return result.returncode == 0
255
+
256
+
257
+ def _escape_applescript(text: str) -> str:
258
+ """Escape text for AppleScript string."""
259
+ return text.replace("\\", "\\\\").replace('"', '\\"')
260
+
261
+
262
+ def _escape_xml(text: str) -> str:
263
+ """Escape text for XML."""
264
+ return (
265
+ text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
266
+ )
267
+
268
+
269
+ # =============================================================================
270
+ # CONVENIENCE FUNCTIONS FOR COMMON NOTIFICATIONS
271
+ # =============================================================================
272
+
273
+
274
+ def notify_qe_complete(
275
+ findings_count: int,
276
+ duration_seconds: float,
277
+ success: bool = True,
278
+ ) -> bool:
279
+ """
280
+ Notify when a QE run completes.
281
+
282
+ Args:
283
+ findings_count: Number of findings discovered.
284
+ duration_seconds: How long the QE run took.
285
+ success: Whether the run completed successfully.
286
+
287
+ Returns:
288
+ True if notification was sent, False otherwise.
289
+ """
290
+ duration_str = f"{duration_seconds:.1f}s"
291
+ if duration_seconds > 60:
292
+ minutes = int(duration_seconds // 60)
293
+ seconds = int(duration_seconds % 60)
294
+ duration_str = f"{minutes}m {seconds}s"
295
+
296
+ if success:
297
+ if findings_count == 0:
298
+ title = "QE Complete - No Issues!"
299
+ message = f"All checks passed in {duration_str}"
300
+ level = NotificationLevel.SUCCESS
301
+ else:
302
+ title = f"QE Complete - {findings_count} Finding{'s' if findings_count != 1 else ''}"
303
+ message = f"Completed in {duration_str}. Review findings in SuperQode."
304
+ level = NotificationLevel.WARNING
305
+ else:
306
+ title = "QE Run Failed"
307
+ message = f"Run failed after {duration_str}. Check logs for details."
308
+ level = NotificationLevel.ERROR
309
+
310
+ return notify(title, message, level, subtitle="SuperQE")
311
+
312
+
313
+ def notify_task_complete(task_name: str, success: bool = True) -> bool:
314
+ """
315
+ Notify when a long-running task completes.
316
+
317
+ Args:
318
+ task_name: Name of the task that completed.
319
+ success: Whether the task succeeded.
320
+
321
+ Returns:
322
+ True if notification was sent, False otherwise.
323
+ """
324
+ if success:
325
+ return notify(
326
+ "Task Complete",
327
+ f"{task_name} finished successfully.",
328
+ NotificationLevel.SUCCESS,
329
+ )
330
+ else:
331
+ return notify(
332
+ "Task Failed",
333
+ f"{task_name} encountered an error.",
334
+ NotificationLevel.ERROR,
335
+ )
336
+
337
+
338
+ def notify_agent_ready(agent_name: str) -> bool:
339
+ """
340
+ Notify when an agent is ready for interaction.
341
+
342
+ Args:
343
+ agent_name: Name of the agent that's ready.
344
+
345
+ Returns:
346
+ True if notification was sent, False otherwise.
347
+ """
348
+ return notify(
349
+ "Agent Ready",
350
+ f"{agent_name} is connected and ready.",
351
+ NotificationLevel.INFO,
352
+ )
353
+
354
+
355
+ def notify_permission_required(action: str) -> bool:
356
+ """
357
+ Notify when user permission is required.
358
+
359
+ Args:
360
+ action: Description of the action requiring permission.
361
+
362
+ Returns:
363
+ True if notification was sent, False otherwise.
364
+ """
365
+ return notify(
366
+ "Permission Required",
367
+ f"SuperQode needs approval for: {action}",
368
+ NotificationLevel.WARNING,
369
+ )
@@ -0,0 +1,5 @@
1
+ """Optimization helpers (SuperOpt integration)."""
2
+
3
+ from .config import load_optimize_config
4
+
5
+ __all__ = ["load_optimize_config"]
@@ -0,0 +1,33 @@
1
+ """SuperOpt configuration loader (OSS command-based)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any, Dict
7
+
8
+ import yaml
9
+
10
+ from superqode.config.schema import OptimizeConfig
11
+
12
+
13
+ def load_optimize_config(project_root: Path) -> OptimizeConfig:
14
+ """Load SuperOpt config from superqode.yaml."""
15
+ config_path = project_root / "superqode.yaml"
16
+ if not config_path.exists():
17
+ return OptimizeConfig()
18
+
19
+ try:
20
+ data: Dict[str, Any] = yaml.safe_load(config_path.read_text()) or {}
21
+ except Exception:
22
+ return OptimizeConfig()
23
+
24
+ optimize_data = data.get("superqode", {}).get("qe", {}).get("optimize", {})
25
+
26
+ if not isinstance(optimize_data, dict):
27
+ return OptimizeConfig()
28
+
29
+ return OptimizeConfig(
30
+ enabled=optimize_data.get("enabled", False),
31
+ command=optimize_data.get("command", optimize_data.get("cmd", "")),
32
+ timeout_seconds=optimize_data.get("timeout_seconds", optimize_data.get("timeout", 300)),
33
+ )
@@ -0,0 +1,25 @@
1
+ """
2
+ SuperQode Permission System.
3
+
4
+ Rule-based access control for agent operations.
5
+ """
6
+
7
+ from .rules import (
8
+ PermissionAction,
9
+ PermissionScope,
10
+ PermissionRule,
11
+ PermissionRequest,
12
+ PermissionDecision,
13
+ PermissionManager,
14
+ create_permission_request,
15
+ )
16
+
17
+ __all__ = [
18
+ "PermissionAction",
19
+ "PermissionScope",
20
+ "PermissionRule",
21
+ "PermissionRequest",
22
+ "PermissionDecision",
23
+ "PermissionManager",
24
+ "create_permission_request",
25
+ ]