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,400 @@
1
+ """
2
+ Tool Permission System - Control tool execution.
3
+
4
+ Provides fine-grained control over which tools can execute and how:
5
+ - ALLOW: Execute without confirmation
6
+ - DENY: Block execution
7
+ - ASK: Prompt user for confirmation
8
+
9
+ Permissions can be configured:
10
+ - Per-tool (e.g., "bash": "ask")
11
+ - Per-group (e.g., "write": "allow" covers write_file, edit_file, etc.)
12
+ - Globally (e.g., default: "ask")
13
+
14
+ Configuration via superqode.yaml:
15
+ ```yaml
16
+ superqode:
17
+ permissions:
18
+ default: ask
19
+ groups:
20
+ read: allow
21
+ write: ask
22
+ shell: ask
23
+ network: deny
24
+ tools:
25
+ bash: deny
26
+ diagnostics: allow
27
+ ```
28
+ """
29
+
30
+ from dataclasses import dataclass, field
31
+ from enum import Enum
32
+ from pathlib import Path
33
+ from typing import Any, Callable, Dict, List, Optional, Set
34
+ import asyncio
35
+
36
+
37
+ class Permission(Enum):
38
+ """Permission levels for tool execution."""
39
+
40
+ ALLOW = "allow" # Execute without asking
41
+ DENY = "deny" # Block execution
42
+ ASK = "ask" # Prompt user for confirmation
43
+
44
+
45
+ class ToolGroup(Enum):
46
+ """Groups of related tools."""
47
+
48
+ READ = "read" # read_file, list_directory, grep, glob
49
+ WRITE = "write" # write_file, edit_file, insert_text, patch, multi_edit
50
+ SHELL = "shell" # bash
51
+ NETWORK = "network" # fetch, download
52
+ DIAGNOSTICS = "diagnostics" # diagnostics
53
+ SEARCH = "search" # code_search
54
+ AGENT = "agent" # sub-agent spawning
55
+
56
+
57
+ # Mapping of tools to their groups
58
+ TOOL_GROUPS: Dict[str, ToolGroup] = {
59
+ # Read operations
60
+ "read_file": ToolGroup.READ,
61
+ "list_directory": ToolGroup.READ,
62
+ "grep": ToolGroup.READ,
63
+ "glob": ToolGroup.READ,
64
+ # Write operations
65
+ "write_file": ToolGroup.WRITE,
66
+ "edit_file": ToolGroup.WRITE,
67
+ "insert_text": ToolGroup.WRITE,
68
+ "patch": ToolGroup.WRITE,
69
+ "multi_edit": ToolGroup.WRITE,
70
+ # Shell
71
+ "bash": ToolGroup.SHELL,
72
+ # Network
73
+ "fetch": ToolGroup.NETWORK,
74
+ "download": ToolGroup.NETWORK,
75
+ # Diagnostics
76
+ "diagnostics": ToolGroup.DIAGNOSTICS,
77
+ # Search
78
+ "code_search": ToolGroup.SEARCH,
79
+ # Agent
80
+ "agent": ToolGroup.AGENT,
81
+ "sub_agent": ToolGroup.AGENT,
82
+ }
83
+
84
+
85
+ @dataclass
86
+ class PermissionConfig:
87
+ """Configuration for tool permissions."""
88
+
89
+ # Default permission for unconfigured tools
90
+ default: Permission = Permission.ASK
91
+
92
+ # Group-level permissions
93
+ groups: Dict[ToolGroup, Permission] = field(default_factory=dict)
94
+
95
+ # Tool-specific permissions (override groups)
96
+ tools: Dict[str, Permission] = field(default_factory=dict)
97
+
98
+ # Patterns to always allow/deny
99
+ allow_patterns: List[str] = field(default_factory=list)
100
+ deny_patterns: List[str] = field(default_factory=list)
101
+
102
+ @classmethod
103
+ def from_yaml_dict(cls, data: Dict[str, Any]) -> "PermissionConfig":
104
+ """Create config from YAML dict."""
105
+ config = cls()
106
+
107
+ if not data:
108
+ return config
109
+
110
+ # Parse default
111
+ default_str = data.get("default", "ask")
112
+ try:
113
+ config.default = Permission(default_str)
114
+ except ValueError:
115
+ pass
116
+
117
+ # Parse groups
118
+ groups_data = data.get("groups", {})
119
+ for group_name, perm_str in groups_data.items():
120
+ try:
121
+ group = ToolGroup(group_name)
122
+ perm = Permission(perm_str)
123
+ config.groups[group] = perm
124
+ except ValueError:
125
+ pass
126
+
127
+ # Parse tool-specific
128
+ tools_data = data.get("tools", {})
129
+ for tool_name, perm_str in tools_data.items():
130
+ try:
131
+ perm = Permission(perm_str)
132
+ config.tools[tool_name] = perm
133
+ except ValueError:
134
+ pass
135
+
136
+ # Parse patterns
137
+ config.allow_patterns = data.get("allow_patterns", [])
138
+ config.deny_patterns = data.get("deny_patterns", [])
139
+
140
+ return config
141
+
142
+ def get_permission(self, tool_name: str) -> Permission:
143
+ """Get the effective permission for a tool."""
144
+ # Check tool-specific first
145
+ if tool_name in self.tools:
146
+ return self.tools[tool_name]
147
+
148
+ # Check group
149
+ group = TOOL_GROUPS.get(tool_name)
150
+ if group and group in self.groups:
151
+ return self.groups[group]
152
+
153
+ # Return default
154
+ return self.default
155
+
156
+
157
+ @dataclass
158
+ class PermissionRequest:
159
+ """A request for tool execution permission."""
160
+
161
+ tool_name: str
162
+ arguments: Dict[str, Any]
163
+ description: str
164
+ risk_level: str = "medium" # low, medium, high
165
+
166
+ def format_for_user(self) -> str:
167
+ """Format the request for display to user."""
168
+ lines = [
169
+ f"Tool: {self.tool_name}",
170
+ f"Risk: {self.risk_level}",
171
+ "",
172
+ "Arguments:",
173
+ ]
174
+
175
+ for key, value in self.arguments.items():
176
+ value_str = str(value)
177
+ if len(value_str) > 100:
178
+ value_str = value_str[:100] + "..."
179
+ lines.append(f" {key}: {value_str}")
180
+
181
+ return "\n".join(lines)
182
+
183
+
184
+ class PermissionManager:
185
+ """
186
+ Manages tool execution permissions.
187
+
188
+ Usage:
189
+ manager = PermissionManager(config)
190
+
191
+ # Check permission
192
+ perm = manager.check_permission("bash", {"command": "rm -rf /"})
193
+
194
+ if perm == Permission.DENY:
195
+ return error
196
+ elif perm == Permission.ASK:
197
+ approved = await manager.request_permission(...)
198
+ if not approved:
199
+ return error
200
+
201
+ # Execute tool
202
+ """
203
+
204
+ def __init__(
205
+ self,
206
+ config: Optional[PermissionConfig] = None,
207
+ on_permission_request: Optional[Callable[["PermissionRequest"], bool]] = None,
208
+ ):
209
+ self.config = config or PermissionConfig()
210
+ self._on_permission_request = on_permission_request
211
+
212
+ # Cache of approved commands (for session)
213
+ self._session_approvals: Set[str] = set()
214
+
215
+ # Dangerous command patterns
216
+ self._dangerous_patterns = [
217
+ r"rm\s+(-rf?|--recursive)",
218
+ r"rm\s+-[^-]*r",
219
+ r"sudo\s+",
220
+ r"chmod\s+777",
221
+ r">\s*/dev/",
222
+ r"mkfs\.",
223
+ r"dd\s+if=",
224
+ r":(){ :|:& };:", # Fork bomb
225
+ ]
226
+
227
+ def check_permission(self, tool_name: str, arguments: Dict[str, Any]) -> Permission:
228
+ """
229
+ Check the permission level for a tool call.
230
+
231
+ Returns the permission level (ALLOW, DENY, or ASK).
232
+ """
233
+ # Check deny patterns first
234
+ if self._matches_deny_pattern(tool_name, arguments):
235
+ return Permission.DENY
236
+
237
+ # Check allow patterns
238
+ if self._matches_allow_pattern(tool_name, arguments):
239
+ return Permission.ALLOW
240
+
241
+ # Check for dangerous commands in shell
242
+ if tool_name == "bash":
243
+ command = arguments.get("command", "")
244
+ if self._is_dangerous_command(command):
245
+ return Permission.DENY
246
+
247
+ # Get configured permission
248
+ return self.config.get_permission(tool_name)
249
+
250
+ def _matches_deny_pattern(self, tool_name: str, arguments: Dict[str, Any]) -> bool:
251
+ """Check if the call matches a deny pattern."""
252
+ import re
253
+
254
+ for pattern in self.config.deny_patterns:
255
+ # Pattern can be tool:arg_pattern or just arg_pattern
256
+ if ":" in pattern:
257
+ tool_pat, arg_pat = pattern.split(":", 1)
258
+ if tool_name != tool_pat:
259
+ continue
260
+ for value in arguments.values():
261
+ if re.search(arg_pat, str(value)):
262
+ return True
263
+ else:
264
+ for value in arguments.values():
265
+ if re.search(pattern, str(value)):
266
+ return True
267
+
268
+ return False
269
+
270
+ def _matches_allow_pattern(self, tool_name: str, arguments: Dict[str, Any]) -> bool:
271
+ """Check if the call matches an allow pattern."""
272
+ import re
273
+
274
+ for pattern in self.config.allow_patterns:
275
+ if ":" in pattern:
276
+ tool_pat, arg_pat = pattern.split(":", 1)
277
+ if tool_name != tool_pat:
278
+ continue
279
+ for value in arguments.values():
280
+ if re.search(arg_pat, str(value)):
281
+ return True
282
+ else:
283
+ if tool_name == pattern:
284
+ return True
285
+
286
+ return False
287
+
288
+ def _is_dangerous_command(self, command: str) -> bool:
289
+ """Check if a shell command is dangerous."""
290
+ import re
291
+
292
+ for pattern in self._dangerous_patterns:
293
+ if re.search(pattern, command, re.IGNORECASE):
294
+ return True
295
+
296
+ return False
297
+
298
+ def get_risk_level(self, tool_name: str, arguments: Dict[str, Any]) -> str:
299
+ """Assess the risk level of a tool call."""
300
+ # Shell commands
301
+ if tool_name == "bash":
302
+ command = arguments.get("command", "")
303
+ if any(kw in command.lower() for kw in ["rm", "delete", "drop", "truncate"]):
304
+ return "high"
305
+ if any(kw in command.lower() for kw in ["mv", "cp", "chmod", "chown"]):
306
+ return "medium"
307
+ return "low"
308
+
309
+ # Write operations
310
+ if tool_name in ["write_file", "edit_file", "patch", "multi_edit"]:
311
+ path = arguments.get("path", "")
312
+ if any(p in path for p in ["/etc", "/usr", "/bin", "/var"]):
313
+ return "high"
314
+ return "medium"
315
+
316
+ # Network
317
+ if tool_name in ["fetch", "download"]:
318
+ return "medium"
319
+
320
+ # Read operations
321
+ if tool_name in ["read_file", "list_directory", "grep", "glob"]:
322
+ return "low"
323
+
324
+ return "medium"
325
+
326
+ async def request_permission(
327
+ self, tool_name: str, arguments: Dict[str, Any], description: str = ""
328
+ ) -> bool:
329
+ """
330
+ Request permission from user for a tool call.
331
+
332
+ Returns True if approved, False if denied.
333
+ """
334
+ # Check session cache
335
+ cache_key = f"{tool_name}:{hash(str(sorted(arguments.items())))}"
336
+ if cache_key in self._session_approvals:
337
+ return True
338
+
339
+ # Create request
340
+ request = PermissionRequest(
341
+ tool_name=tool_name,
342
+ arguments=arguments,
343
+ description=description,
344
+ risk_level=self.get_risk_level(tool_name, arguments),
345
+ )
346
+
347
+ # Call handler
348
+ if self._on_permission_request:
349
+ approved = self._on_permission_request(request)
350
+ else:
351
+ # Default: deny if no handler
352
+ approved = False
353
+
354
+ # Cache approval
355
+ if approved:
356
+ self._session_approvals.add(cache_key)
357
+
358
+ return approved
359
+
360
+ def clear_session_approvals(self) -> None:
361
+ """Clear session approval cache."""
362
+ self._session_approvals.clear()
363
+
364
+
365
+ # Singleton instance
366
+ _permission_manager: Optional[PermissionManager] = None
367
+
368
+
369
+ def get_permission_manager() -> PermissionManager:
370
+ """Get the global permission manager."""
371
+ global _permission_manager
372
+ if _permission_manager is None:
373
+ _permission_manager = PermissionManager()
374
+ return _permission_manager
375
+
376
+
377
+ def set_permission_manager(manager: PermissionManager) -> None:
378
+ """Set the global permission manager."""
379
+ global _permission_manager
380
+ _permission_manager = manager
381
+
382
+
383
+ def load_permission_config(project_root: Path) -> PermissionConfig:
384
+ """Load permission config from superqode.yaml."""
385
+ import yaml
386
+
387
+ yaml_path = project_root / "superqode.yaml"
388
+ if not yaml_path.exists():
389
+ return PermissionConfig()
390
+
391
+ try:
392
+ with open(yaml_path) as f:
393
+ data = yaml.safe_load(f)
394
+
395
+ perm_data = data.get("superqode", {}).get("permissions", {})
396
+
397
+ return PermissionConfig.from_yaml_dict(perm_data)
398
+
399
+ except Exception:
400
+ return PermissionConfig()