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,457 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Permission Screen for ACP permission requests.
|
|
3
|
+
|
|
4
|
+
Shows a modal dialog when the agent requests permission to perform an action.
|
|
5
|
+
|
|
6
|
+
Enhanced Features:
|
|
7
|
+
- Support for multi-file permission requests
|
|
8
|
+
- j/k navigation between requests
|
|
9
|
+
- Multiple diff view modes
|
|
10
|
+
- Integration with enhanced permission preview
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
from typing import Callable, Awaitable, List, Optional
|
|
17
|
+
|
|
18
|
+
from textual.app import ComposeResult
|
|
19
|
+
from textual.binding import Binding
|
|
20
|
+
from textual.containers import Container, Vertical, Horizontal
|
|
21
|
+
from textual.screen import ModalScreen
|
|
22
|
+
from textual.widgets import Static, Button, Label
|
|
23
|
+
from textual.reactive import reactive
|
|
24
|
+
|
|
25
|
+
from rich.text import Text
|
|
26
|
+
from rich.panel import Panel
|
|
27
|
+
|
|
28
|
+
from superqode.acp.types import PermissionOption, ToolCall
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Theme colors
|
|
32
|
+
THEME = {
|
|
33
|
+
"purple": "#a855f7",
|
|
34
|
+
"pink": "#ec4899",
|
|
35
|
+
"success": "#22c55e",
|
|
36
|
+
"error": "#ef4444",
|
|
37
|
+
"warning": "#f59e0b",
|
|
38
|
+
"text": "#e4e4e7",
|
|
39
|
+
"muted": "#71717a",
|
|
40
|
+
"dim": "#52525b",
|
|
41
|
+
"bg": "#000000",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class PermissionScreen(ModalScreen[str]):
|
|
46
|
+
"""
|
|
47
|
+
Modal screen for handling ACP permission requests.
|
|
48
|
+
|
|
49
|
+
Returns the selected option ID.
|
|
50
|
+
|
|
51
|
+
Enhanced keyboard shortcuts:
|
|
52
|
+
- a: Allow once
|
|
53
|
+
- A: Allow always
|
|
54
|
+
- r: Reject once
|
|
55
|
+
- R: Reject always
|
|
56
|
+
- j: Next request (when multiple)
|
|
57
|
+
- k: Previous request (when multiple)
|
|
58
|
+
- v: Toggle diff view mode
|
|
59
|
+
- ?: Show help
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
BINDINGS = [
|
|
63
|
+
Binding("a", "allow_once", "Allow once", priority=True),
|
|
64
|
+
Binding("A", "allow_always", "Allow always", priority=True),
|
|
65
|
+
Binding("r", "reject_once", "Reject once", priority=True),
|
|
66
|
+
Binding("R", "reject_always", "Reject always", priority=True),
|
|
67
|
+
Binding("j", "next_request", "Next", priority=True, show=False),
|
|
68
|
+
Binding("k", "prev_request", "Previous", priority=True, show=False),
|
|
69
|
+
Binding("v", "toggle_view", "Toggle view", priority=True, show=False),
|
|
70
|
+
Binding("?", "show_help", "Help", priority=True, show=False),
|
|
71
|
+
Binding("escape", "cancel", "Cancel", priority=True),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
CSS = """
|
|
75
|
+
PermissionScreen {
|
|
76
|
+
align: center middle;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#permission-dialog {
|
|
80
|
+
width: 80;
|
|
81
|
+
height: auto;
|
|
82
|
+
max-height: 30;
|
|
83
|
+
background: #0a0a0a;
|
|
84
|
+
border: tall #a855f7;
|
|
85
|
+
padding: 1 2;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#permission-title {
|
|
89
|
+
text-align: center;
|
|
90
|
+
text-style: bold;
|
|
91
|
+
color: #f59e0b;
|
|
92
|
+
margin-bottom: 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#permission-tool {
|
|
96
|
+
margin-bottom: 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#permission-content {
|
|
100
|
+
height: auto;
|
|
101
|
+
max-height: 15;
|
|
102
|
+
overflow-y: auto;
|
|
103
|
+
margin-bottom: 1;
|
|
104
|
+
padding: 1;
|
|
105
|
+
background: #000000;
|
|
106
|
+
border: round #1a1a1a;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#permission-buttons {
|
|
110
|
+
height: auto;
|
|
111
|
+
align: center middle;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.permission-btn {
|
|
115
|
+
margin: 0 1;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.allow-btn {
|
|
119
|
+
background: #22c55e;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.reject-btn {
|
|
123
|
+
background: #ef4444;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#permission-hints {
|
|
127
|
+
text-align: center;
|
|
128
|
+
color: #52525b;
|
|
129
|
+
margin-top: 1;
|
|
130
|
+
}
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def __init__(
|
|
134
|
+
self,
|
|
135
|
+
options: list[PermissionOption],
|
|
136
|
+
tool_call: ToolCall,
|
|
137
|
+
name: str | None = None,
|
|
138
|
+
id: str | None = None,
|
|
139
|
+
classes: str | None = None,
|
|
140
|
+
):
|
|
141
|
+
super().__init__(name=name, id=id, classes=classes)
|
|
142
|
+
self.options = options
|
|
143
|
+
self.tool_call = tool_call
|
|
144
|
+
self._option_map: dict[str, str] = {} # kind -> optionId
|
|
145
|
+
|
|
146
|
+
for opt in options:
|
|
147
|
+
kind = opt.get("kind", "")
|
|
148
|
+
option_id = opt.get("optionId", "")
|
|
149
|
+
self._option_map[kind] = option_id
|
|
150
|
+
|
|
151
|
+
def compose(self) -> ComposeResult:
|
|
152
|
+
with Container(id="permission-dialog"):
|
|
153
|
+
yield Static("⚠️ Permission Request", id="permission-title")
|
|
154
|
+
yield Static(self._format_tool_info(), id="permission-tool")
|
|
155
|
+
yield Static(self._format_content(), id="permission-content")
|
|
156
|
+
|
|
157
|
+
with Horizontal(id="permission-buttons"):
|
|
158
|
+
yield Button("Allow [a]", id="btn-allow", classes="permission-btn allow-btn")
|
|
159
|
+
yield Button("Always [A]", id="btn-always", classes="permission-btn allow-btn")
|
|
160
|
+
yield Button("Reject [r]", id="btn-reject", classes="permission-btn reject-btn")
|
|
161
|
+
yield Button("Never [R]", id="btn-never", classes="permission-btn reject-btn")
|
|
162
|
+
|
|
163
|
+
yield Static(
|
|
164
|
+
"[a] Allow once [A] Allow always [r] Reject [R] Reject always [Esc] Cancel",
|
|
165
|
+
id="permission-hints",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def _format_tool_info(self) -> Text:
|
|
169
|
+
"""Format the tool call information."""
|
|
170
|
+
t = Text()
|
|
171
|
+
|
|
172
|
+
title = self.tool_call.get("title", "Unknown operation")
|
|
173
|
+
kind = self.tool_call.get("kind", "other")
|
|
174
|
+
|
|
175
|
+
# Icon based on kind
|
|
176
|
+
icons = {
|
|
177
|
+
"read": "📖",
|
|
178
|
+
"edit": "✏️",
|
|
179
|
+
"delete": "🗑️",
|
|
180
|
+
"move": "📦",
|
|
181
|
+
"search": "🔍",
|
|
182
|
+
"execute": "💻",
|
|
183
|
+
"think": "🧠",
|
|
184
|
+
"fetch": "🌐",
|
|
185
|
+
"other": "🔧",
|
|
186
|
+
}
|
|
187
|
+
icon = icons.get(kind, "🔧")
|
|
188
|
+
|
|
189
|
+
t.append(f"{icon} ", style=f"bold {THEME['warning']}")
|
|
190
|
+
t.append(f"{title}\n", style=f"bold {THEME['text']}")
|
|
191
|
+
t.append(f"Type: {kind}", style=THEME["muted"])
|
|
192
|
+
|
|
193
|
+
return t
|
|
194
|
+
|
|
195
|
+
def _format_content(self) -> Text:
|
|
196
|
+
"""Format the tool call content (diff, command, etc.)."""
|
|
197
|
+
t = Text()
|
|
198
|
+
|
|
199
|
+
content_list = self.tool_call.get("content", [])
|
|
200
|
+
raw_input = self.tool_call.get("rawInput", {})
|
|
201
|
+
|
|
202
|
+
for content in content_list:
|
|
203
|
+
content_type = content.get("type", "")
|
|
204
|
+
|
|
205
|
+
if content_type == "diff":
|
|
206
|
+
path = content.get("path", "")
|
|
207
|
+
old_text = content.get("oldText", "")
|
|
208
|
+
new_text = content.get("newText", "")
|
|
209
|
+
|
|
210
|
+
t.append(f"📄 File: {path}\n", style=f"bold {THEME['purple']}")
|
|
211
|
+
|
|
212
|
+
if old_text:
|
|
213
|
+
t.append("--- Old:\n", style=THEME["error"])
|
|
214
|
+
# Show first few lines
|
|
215
|
+
lines = old_text.split("\n")[:5]
|
|
216
|
+
for line in lines:
|
|
217
|
+
t.append(f" {line}\n", style=THEME["dim"])
|
|
218
|
+
if len(old_text.split("\n")) > 5:
|
|
219
|
+
t.append(" ...\n", style=THEME["dim"])
|
|
220
|
+
|
|
221
|
+
t.append("+++ New:\n", style=THEME["success"])
|
|
222
|
+
lines = new_text.split("\n")[:10]
|
|
223
|
+
for line in lines:
|
|
224
|
+
t.append(f" {line}\n", style=THEME["text"])
|
|
225
|
+
if len(new_text.split("\n")) > 10:
|
|
226
|
+
t.append(" ...\n", style=THEME["dim"])
|
|
227
|
+
|
|
228
|
+
elif content_type == "terminal":
|
|
229
|
+
terminal_id = content.get("terminalId", "")
|
|
230
|
+
t.append(f"💻 Terminal: {terminal_id}\n", style=f"bold {THEME['purple']}")
|
|
231
|
+
|
|
232
|
+
# Show raw input if no content
|
|
233
|
+
if not content_list and raw_input:
|
|
234
|
+
for key, value in raw_input.items():
|
|
235
|
+
if isinstance(value, str) and len(value) > 100:
|
|
236
|
+
value = value[:100] + "..."
|
|
237
|
+
t.append(f"{key}: ", style=THEME["muted"])
|
|
238
|
+
t.append(f"{value}\n", style=THEME["text"])
|
|
239
|
+
|
|
240
|
+
if not t.plain:
|
|
241
|
+
t.append("No details available", style=THEME["dim"])
|
|
242
|
+
|
|
243
|
+
return t
|
|
244
|
+
|
|
245
|
+
def action_allow_once(self) -> None:
|
|
246
|
+
"""Allow this operation once."""
|
|
247
|
+
option_id = self._option_map.get("allow_once", "")
|
|
248
|
+
if option_id:
|
|
249
|
+
self.dismiss(option_id)
|
|
250
|
+
else:
|
|
251
|
+
# Fallback to first allow option
|
|
252
|
+
for opt in self.options:
|
|
253
|
+
if "allow" in opt.get("kind", ""):
|
|
254
|
+
self.dismiss(opt.get("optionId", ""))
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
def action_allow_always(self) -> None:
|
|
258
|
+
"""Allow this operation always."""
|
|
259
|
+
option_id = self._option_map.get("allow_always", "")
|
|
260
|
+
if option_id:
|
|
261
|
+
self.dismiss(option_id)
|
|
262
|
+
else:
|
|
263
|
+
self.action_allow_once()
|
|
264
|
+
|
|
265
|
+
def action_reject_once(self) -> None:
|
|
266
|
+
"""Reject this operation once."""
|
|
267
|
+
option_id = self._option_map.get("reject_once", "")
|
|
268
|
+
if not option_id:
|
|
269
|
+
option_id = self._option_map.get("reject", "")
|
|
270
|
+
if option_id:
|
|
271
|
+
self.dismiss(option_id)
|
|
272
|
+
else:
|
|
273
|
+
# Fallback to first reject option
|
|
274
|
+
for opt in self.options:
|
|
275
|
+
if "reject" in opt.get("kind", ""):
|
|
276
|
+
self.dismiss(opt.get("optionId", ""))
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
def action_reject_always(self) -> None:
|
|
280
|
+
"""Reject this operation always."""
|
|
281
|
+
option_id = self._option_map.get("reject_always", "")
|
|
282
|
+
if option_id:
|
|
283
|
+
self.dismiss(option_id)
|
|
284
|
+
else:
|
|
285
|
+
self.action_reject_once()
|
|
286
|
+
|
|
287
|
+
def action_cancel(self) -> None:
|
|
288
|
+
"""Cancel the permission request."""
|
|
289
|
+
self.dismiss("")
|
|
290
|
+
|
|
291
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
292
|
+
"""Handle button presses."""
|
|
293
|
+
button_id = event.button.id
|
|
294
|
+
|
|
295
|
+
if button_id == "btn-allow":
|
|
296
|
+
self.action_allow_once()
|
|
297
|
+
elif button_id == "btn-always":
|
|
298
|
+
self.action_allow_always()
|
|
299
|
+
elif button_id == "btn-reject":
|
|
300
|
+
self.action_reject_once()
|
|
301
|
+
elif button_id == "btn-never":
|
|
302
|
+
self.action_reject_always()
|
|
303
|
+
|
|
304
|
+
def action_next_request(self) -> None:
|
|
305
|
+
"""Navigate to next request (for multi-file support)."""
|
|
306
|
+
# This is a placeholder for multi-request navigation
|
|
307
|
+
# Will be used when batch permissions are implemented
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
def action_prev_request(self) -> None:
|
|
311
|
+
"""Navigate to previous request (for multi-file support)."""
|
|
312
|
+
# This is a placeholder for multi-request navigation
|
|
313
|
+
# Will be used when batch permissions are implemented
|
|
314
|
+
pass
|
|
315
|
+
|
|
316
|
+
def action_toggle_view(self) -> None:
|
|
317
|
+
"""Toggle between unified and split diff view."""
|
|
318
|
+
# This is a placeholder for diff view mode toggle
|
|
319
|
+
# Will integrate with enhanced permission preview
|
|
320
|
+
pass
|
|
321
|
+
|
|
322
|
+
def action_show_help(self) -> None:
|
|
323
|
+
"""Show help for permission screen."""
|
|
324
|
+
# Could show a help overlay with keyboard shortcuts
|
|
325
|
+
pass
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class MultiPermissionScreen(ModalScreen[List[str]]):
|
|
329
|
+
"""
|
|
330
|
+
Modal screen for handling multiple ACP permission requests.
|
|
331
|
+
|
|
332
|
+
Returns a list of (request_id, action) tuples for each request.
|
|
333
|
+
Uses the enhanced permission preview with navigator.
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
BINDINGS = [
|
|
337
|
+
Binding("a", "allow_once", "Allow", priority=True),
|
|
338
|
+
Binding("A", "allow_always", "Allow always", priority=True),
|
|
339
|
+
Binding("r", "reject_once", "Reject", priority=True),
|
|
340
|
+
Binding("R", "reject_always", "Reject always", priority=True),
|
|
341
|
+
Binding("j", "next_request", "Next", priority=True),
|
|
342
|
+
Binding("k", "prev_request", "Previous", priority=True),
|
|
343
|
+
Binding("v", "toggle_view", "Toggle view", priority=True),
|
|
344
|
+
Binding("enter", "confirm", "Confirm all", priority=True),
|
|
345
|
+
Binding("escape", "cancel", "Cancel", priority=True),
|
|
346
|
+
]
|
|
347
|
+
|
|
348
|
+
CSS = """
|
|
349
|
+
MultiPermissionScreen {
|
|
350
|
+
align: center middle;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
#multi-permission-dialog {
|
|
354
|
+
width: 90%;
|
|
355
|
+
height: 80%;
|
|
356
|
+
max-width: 120;
|
|
357
|
+
background: #0a0a0a;
|
|
358
|
+
border: tall #a855f7;
|
|
359
|
+
}
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
def __init__(
|
|
363
|
+
self,
|
|
364
|
+
requests: List[tuple], # List of (options, tool_call) tuples
|
|
365
|
+
name: str | None = None,
|
|
366
|
+
id: str | None = None,
|
|
367
|
+
classes: str | None = None,
|
|
368
|
+
):
|
|
369
|
+
super().__init__(name=name, id=id, classes=classes)
|
|
370
|
+
self.requests = requests
|
|
371
|
+
self._decisions: dict[int, str] = {} # index -> action
|
|
372
|
+
self._current_index = 0
|
|
373
|
+
|
|
374
|
+
def compose(self) -> ComposeResult:
|
|
375
|
+
with Container(id="multi-permission-dialog"):
|
|
376
|
+
yield Static(
|
|
377
|
+
f" Multiple Permission Requests ({len(self.requests)} pending)",
|
|
378
|
+
id="permission-title",
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
# Will integrate with EnhancedPermissionPreviewScreen
|
|
382
|
+
yield Static("Permission requests will be shown here", id="request-content")
|
|
383
|
+
|
|
384
|
+
with Horizontal(id="permission-buttons"):
|
|
385
|
+
yield Button("Allow [a]", id="btn-allow", classes="permission-btn allow-btn")
|
|
386
|
+
yield Button("Always [A]", id="btn-always", classes="permission-btn allow-btn")
|
|
387
|
+
yield Button("Reject [r]", id="btn-reject", classes="permission-btn reject-btn")
|
|
388
|
+
yield Button("Never [R]", id="btn-never", classes="permission-btn reject-btn")
|
|
389
|
+
|
|
390
|
+
yield Static(
|
|
391
|
+
"[j/k] Navigate [a/A] Allow [r/R] Reject [Enter] Confirm all [Esc] Cancel",
|
|
392
|
+
id="permission-hints",
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
def action_allow_once(self) -> None:
|
|
396
|
+
"""Allow current request once."""
|
|
397
|
+
self._decisions[self._current_index] = "allow_once"
|
|
398
|
+
self._advance()
|
|
399
|
+
|
|
400
|
+
def action_allow_always(self) -> None:
|
|
401
|
+
"""Allow current request always."""
|
|
402
|
+
self._decisions[self._current_index] = "allow_always"
|
|
403
|
+
self._advance()
|
|
404
|
+
|
|
405
|
+
def action_reject_once(self) -> None:
|
|
406
|
+
"""Reject current request."""
|
|
407
|
+
self._decisions[self._current_index] = "reject_once"
|
|
408
|
+
self._advance()
|
|
409
|
+
|
|
410
|
+
def action_reject_always(self) -> None:
|
|
411
|
+
"""Reject current request always."""
|
|
412
|
+
self._decisions[self._current_index] = "reject_always"
|
|
413
|
+
self._advance()
|
|
414
|
+
|
|
415
|
+
def action_next_request(self) -> None:
|
|
416
|
+
"""Go to next request."""
|
|
417
|
+
if self._current_index < len(self.requests) - 1:
|
|
418
|
+
self._current_index += 1
|
|
419
|
+
self._update_display()
|
|
420
|
+
|
|
421
|
+
def action_prev_request(self) -> None:
|
|
422
|
+
"""Go to previous request."""
|
|
423
|
+
if self._current_index > 0:
|
|
424
|
+
self._current_index -= 1
|
|
425
|
+
self._update_display()
|
|
426
|
+
|
|
427
|
+
def action_toggle_view(self) -> None:
|
|
428
|
+
"""Toggle diff view mode."""
|
|
429
|
+
pass
|
|
430
|
+
|
|
431
|
+
def action_confirm(self) -> None:
|
|
432
|
+
"""Confirm all decisions."""
|
|
433
|
+
# Build result list
|
|
434
|
+
results = []
|
|
435
|
+
for i in range(len(self.requests)):
|
|
436
|
+
action = self._decisions.get(i, "reject_once") # Default to reject
|
|
437
|
+
results.append(action)
|
|
438
|
+
self.dismiss(results)
|
|
439
|
+
|
|
440
|
+
def action_cancel(self) -> None:
|
|
441
|
+
"""Cancel all requests."""
|
|
442
|
+
self.dismiss([])
|
|
443
|
+
|
|
444
|
+
def _advance(self) -> None:
|
|
445
|
+
"""Advance to next request or finish."""
|
|
446
|
+
if self._current_index < len(self.requests) - 1:
|
|
447
|
+
self._current_index += 1
|
|
448
|
+
self._update_display()
|
|
449
|
+
elif len(self._decisions) == len(self.requests):
|
|
450
|
+
# All decisions made
|
|
451
|
+
self.action_confirm()
|
|
452
|
+
|
|
453
|
+
def _update_display(self) -> None:
|
|
454
|
+
"""Update the display for current request."""
|
|
455
|
+
# Update title with current position
|
|
456
|
+
title = self.query_one("#permission-title", Static)
|
|
457
|
+
title.update(f" Request {self._current_index + 1} of {len(self.requests)}")
|