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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- 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()
|