ripperdoc 0.3.1__py3-none-any.whl → 0.3.2__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.
- ripperdoc/__init__.py +1 -1
- ripperdoc/cli/cli.py +9 -1
- ripperdoc/cli/commands/agents_cmd.py +93 -53
- ripperdoc/cli/commands/mcp_cmd.py +3 -0
- ripperdoc/cli/commands/models_cmd.py +768 -283
- ripperdoc/cli/commands/permissions_cmd.py +107 -52
- ripperdoc/cli/commands/resume_cmd.py +61 -51
- ripperdoc/cli/commands/themes_cmd.py +31 -1
- ripperdoc/cli/ui/agents_tui/__init__.py +3 -0
- ripperdoc/cli/ui/agents_tui/textual_app.py +1138 -0
- ripperdoc/cli/ui/choice.py +376 -0
- ripperdoc/cli/ui/models_tui/__init__.py +5 -0
- ripperdoc/cli/ui/models_tui/textual_app.py +698 -0
- ripperdoc/cli/ui/panels.py +19 -4
- ripperdoc/cli/ui/permissions_tui/__init__.py +3 -0
- ripperdoc/cli/ui/permissions_tui/textual_app.py +526 -0
- ripperdoc/cli/ui/provider_options.py +220 -80
- ripperdoc/cli/ui/rich_ui.py +9 -11
- ripperdoc/cli/ui/tips.py +89 -0
- ripperdoc/cli/ui/wizard.py +98 -45
- ripperdoc/core/config.py +3 -0
- ripperdoc/core/permissions.py +25 -70
- ripperdoc/core/providers/anthropic.py +11 -0
- ripperdoc/protocol/stdio.py +3 -1
- ripperdoc/tools/bash_tool.py +2 -0
- ripperdoc/tools/file_edit_tool.py +100 -181
- ripperdoc/tools/file_read_tool.py +101 -25
- ripperdoc/tools/multi_edit_tool.py +239 -91
- ripperdoc/tools/notebook_edit_tool.py +11 -29
- ripperdoc/utils/file_editing.py +164 -0
- ripperdoc/utils/permissions/tool_permission_utils.py +11 -0
- {ripperdoc-0.3.1.dist-info → ripperdoc-0.3.2.dist-info}/METADATA +3 -2
- {ripperdoc-0.3.1.dist-info → ripperdoc-0.3.2.dist-info}/RECORD +37 -28
- {ripperdoc-0.3.1.dist-info → ripperdoc-0.3.2.dist-info}/WHEEL +0 -0
- {ripperdoc-0.3.1.dist-info → ripperdoc-0.3.2.dist-info}/entry_points.txt +0 -0
- {ripperdoc-0.3.1.dist-info → ripperdoc-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {ripperdoc-0.3.1.dist-info → ripperdoc-0.3.2.dist-info}/top_level.txt +0 -0
ripperdoc/cli/ui/wizard.py
CHANGED
|
@@ -8,6 +8,7 @@ from typing import List, Optional, Tuple
|
|
|
8
8
|
import click
|
|
9
9
|
from rich.console import Console
|
|
10
10
|
|
|
11
|
+
from ripperdoc.cli.ui.choice import ChoiceOption, onboarding_style, prompt_choice
|
|
11
12
|
from ripperdoc.cli.ui.provider_options import (
|
|
12
13
|
KNOWN_PROVIDERS,
|
|
13
14
|
ProviderOption,
|
|
@@ -66,38 +67,64 @@ def check_onboarding() -> bool:
|
|
|
66
67
|
|
|
67
68
|
def run_onboarding_wizard(config: GlobalConfig) -> bool:
|
|
68
69
|
"""Run interactive onboarding wizard."""
|
|
69
|
-
provider_keys = KNOWN_PROVIDERS.keys() + ["custom"]
|
|
70
|
+
provider_keys = list(KNOWN_PROVIDERS.keys()) + ["custom"]
|
|
70
71
|
default_choice_key = KNOWN_PROVIDERS.default_choice.key
|
|
71
72
|
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# Prompt for provider choice with validation
|
|
80
|
-
provider_choice: Optional[str] = None
|
|
81
|
-
while provider_choice is None:
|
|
82
|
-
raw_choice = click.prompt(
|
|
83
|
-
"Choose your model provider",
|
|
84
|
-
default=default_choice_key,
|
|
73
|
+
# Build provider choice options with rich styling
|
|
74
|
+
provider_options = [
|
|
75
|
+
ChoiceOption(
|
|
76
|
+
key,
|
|
77
|
+
key, # Plain text, not colored
|
|
78
|
+
is_default=(key == default_choice_key),
|
|
85
79
|
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
for key in provider_keys
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
# Prompt for provider choice using unified choice component
|
|
84
|
+
provider_choice = prompt_choice(
|
|
85
|
+
message="Choose your model provider",
|
|
86
|
+
options=provider_options,
|
|
87
|
+
title="Available providers",
|
|
88
|
+
allow_esc=True,
|
|
89
|
+
esc_value=default_choice_key,
|
|
90
|
+
style=onboarding_style(),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Validate the choice (in case user typed an invalid value)
|
|
94
|
+
validated_choice = resolve_provider_choice(provider_choice, provider_keys)
|
|
95
|
+
if validated_choice is None:
|
|
96
|
+
console.print(
|
|
97
|
+
f"[red]Invalid choice. Please enter a provider name or number (1-{len(provider_keys)}).[/red]"
|
|
98
|
+
)
|
|
99
|
+
return run_onboarding_wizard(config)
|
|
100
|
+
provider_choice = validated_choice
|
|
91
101
|
|
|
92
102
|
api_base_override: Optional[str] = None
|
|
93
103
|
if provider_choice == "custom":
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
104
|
+
# Build protocol choice options
|
|
105
|
+
protocol_options = [
|
|
106
|
+
ChoiceOption(
|
|
107
|
+
p.value,
|
|
108
|
+
p.value, # Plain text
|
|
109
|
+
is_default=(p == ProviderType.OPENAI_COMPATIBLE),
|
|
110
|
+
)
|
|
111
|
+
for p in ProviderType
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
protocol_input = prompt_choice(
|
|
115
|
+
message="Choose the protocol family (for API compatibility)",
|
|
116
|
+
options=protocol_options,
|
|
117
|
+
title="Custom Provider - Protocol Selection",
|
|
118
|
+
allow_esc=True,
|
|
119
|
+
esc_value=ProviderType.OPENAI_COMPATIBLE.value,
|
|
120
|
+
style=onboarding_style(),
|
|
98
121
|
)
|
|
99
122
|
protocol = ProviderType(protocol_input)
|
|
100
|
-
|
|
123
|
+
|
|
124
|
+
# Get API base URL - use click.prompt for free-form text input
|
|
125
|
+
console.print("\n[dim]Now we need your API Base URL.[/dim]")
|
|
126
|
+
api_base_override = click.prompt("API Base URL").strip()
|
|
127
|
+
|
|
101
128
|
provider_option = ProviderOption(
|
|
102
129
|
key="custom",
|
|
103
130
|
protocol=protocol,
|
|
@@ -160,29 +187,55 @@ def get_model_name_with_suggestions(
|
|
|
160
187
|
default_model = provider.default_model or default_model_for_protocol(provider.protocol)
|
|
161
188
|
suggestions = list(provider.model_suggestions)
|
|
162
189
|
|
|
163
|
-
#
|
|
190
|
+
# Prompt for model name using unified choice component if suggestions exist
|
|
164
191
|
if suggestions:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
192
|
+
# Create choice options for models
|
|
193
|
+
model_options = [
|
|
194
|
+
ChoiceOption(model, model) # Plain text
|
|
195
|
+
for model in suggestions[:5] # Show top 5
|
|
196
|
+
]
|
|
197
|
+
# Add custom option
|
|
198
|
+
model_options.append(ChoiceOption("custom", "<dim>Custom model...</dim>"))
|
|
199
|
+
|
|
200
|
+
model = prompt_choice(
|
|
201
|
+
message="Select a model or choose 'Custom' to enter manually",
|
|
202
|
+
options=model_options,
|
|
203
|
+
title="Available models for this provider",
|
|
204
|
+
description=f"Default: {default_model}",
|
|
205
|
+
allow_esc=True,
|
|
206
|
+
esc_value=default_model,
|
|
207
|
+
style=onboarding_style(),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# If user chose custom, prompt for manual input using click.prompt
|
|
211
|
+
if model == "custom":
|
|
212
|
+
console.print("\n[dim]Enter your custom model name:[/dim]")
|
|
213
|
+
model = click.prompt("Model name", default=default_model).strip()
|
|
184
214
|
else:
|
|
185
|
-
|
|
215
|
+
# No suggestions, use default
|
|
216
|
+
model = default_model
|
|
217
|
+
|
|
218
|
+
# Prompt for API base if still not set (for OpenAI-compatible providers)
|
|
219
|
+
if provider.protocol == ProviderType.OPENAI_COMPATIBLE and api_base is None:
|
|
220
|
+
api_base_input = prompt_choice(
|
|
221
|
+
message="Enter API base URL (or press Enter to skip)",
|
|
222
|
+
options=[ChoiceOption("", "<dim>Skip</dim>")],
|
|
223
|
+
title="API base URL (optional)",
|
|
224
|
+
allow_esc=True,
|
|
225
|
+
esc_value="",
|
|
226
|
+
style=onboarding_style(),
|
|
227
|
+
)
|
|
228
|
+
api_base = api_base_input or None
|
|
229
|
+
elif provider.protocol == ProviderType.GEMINI and api_base is None:
|
|
230
|
+
api_base_input = prompt_choice(
|
|
231
|
+
message="Enter API base URL (or press Enter to skip)",
|
|
232
|
+
options=[ChoiceOption("", "<dim>Skip</dim>")],
|
|
233
|
+
title="API base URL (optional)",
|
|
234
|
+
allow_esc=True,
|
|
235
|
+
esc_value="",
|
|
236
|
+
style=onboarding_style(),
|
|
237
|
+
)
|
|
238
|
+
api_base = api_base_input or None
|
|
186
239
|
|
|
187
240
|
return model, api_base
|
|
188
241
|
|
ripperdoc/core/config.py
CHANGED
|
@@ -230,6 +230,7 @@ class GlobalConfig(BaseModel):
|
|
|
230
230
|
# User-level permission rules (applied globally)
|
|
231
231
|
user_allow_rules: list[str] = Field(default_factory=list)
|
|
232
232
|
user_deny_rules: list[str] = Field(default_factory=list)
|
|
233
|
+
user_ask_rules: list[str] = Field(default_factory=list)
|
|
233
234
|
|
|
234
235
|
# Onboarding
|
|
235
236
|
has_completed_onboarding: bool = False
|
|
@@ -258,6 +259,7 @@ class ProjectConfig(BaseModel):
|
|
|
258
259
|
allowed_tools: list[str] = Field(default_factory=list)
|
|
259
260
|
bash_allow_rules: list[str] = Field(default_factory=list)
|
|
260
261
|
bash_deny_rules: list[str] = Field(default_factory=list)
|
|
262
|
+
bash_ask_rules: list[str] = Field(default_factory=list)
|
|
261
263
|
working_directories: list[str] = Field(default_factory=list)
|
|
262
264
|
|
|
263
265
|
# Path ignore patterns (gitignore-style)
|
|
@@ -291,6 +293,7 @@ class ProjectLocalConfig(BaseModel):
|
|
|
291
293
|
# Local permission rules (project-specific but not shared)
|
|
292
294
|
local_allow_rules: list[str] = Field(default_factory=list)
|
|
293
295
|
local_deny_rules: list[str] = Field(default_factory=list)
|
|
296
|
+
local_ask_rules: list[str] = Field(default_factory=list)
|
|
294
297
|
|
|
295
298
|
|
|
296
299
|
class ConfigManager:
|
ripperdoc/core/permissions.py
CHANGED
|
@@ -9,12 +9,7 @@ from dataclasses import dataclass
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any, Awaitable, Callable, Optional, Set, TYPE_CHECKING, TYPE_CHECKING as TYPE_CHECKING
|
|
11
11
|
|
|
12
|
-
from
|
|
13
|
-
from prompt_toolkit.formatted_text import HTML
|
|
14
|
-
from prompt_toolkit.key_binding import KeyBindings
|
|
15
|
-
from prompt_toolkit.shortcuts import choice
|
|
16
|
-
from prompt_toolkit.styles import Style
|
|
17
|
-
|
|
12
|
+
from ripperdoc.cli.ui.choice import prompt_choice
|
|
18
13
|
from ripperdoc.core.config import config_manager
|
|
19
14
|
from ripperdoc.core.hooks.manager import hook_manager
|
|
20
15
|
from ripperdoc.core.tool import Tool
|
|
@@ -101,25 +96,6 @@ def permission_key(tool: Tool[Any, Any], parsed_input: Any) -> str:
|
|
|
101
96
|
return tool.name
|
|
102
97
|
|
|
103
98
|
|
|
104
|
-
def _permission_style() -> Style:
|
|
105
|
-
"""Create the style for permission choice prompts."""
|
|
106
|
-
return Style.from_dict(
|
|
107
|
-
{
|
|
108
|
-
"frame.border": "#d4a017", # Golden/amber border
|
|
109
|
-
"selected-option": "bold",
|
|
110
|
-
"option": "#5fd7ff", # Cyan for unselected options
|
|
111
|
-
"title": "#ffaf00", # Orange/amber for tool name
|
|
112
|
-
"description": "#ffffff", # White for descriptions
|
|
113
|
-
"question": "#ffd700", # Gold for the question
|
|
114
|
-
"label": "#87afff", # Light blue for field labels (Command:, Sandbox:, etc.)
|
|
115
|
-
"warning": "#ff5555", # Red for warnings
|
|
116
|
-
"yes-option": "#ffffff", # Neutral for Yes options
|
|
117
|
-
"no-option": "#ffffff", # Neutral for No option
|
|
118
|
-
"value": "#f8f8f2", # Off-white for values
|
|
119
|
-
}
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
|
|
123
99
|
def _rule_strings(rule_suggestions: Optional[Any]) -> list[str]:
|
|
124
100
|
"""Normalize rule suggestions to simple strings."""
|
|
125
101
|
if not rule_suggestions:
|
|
@@ -137,8 +113,8 @@ def make_permission_checker(
|
|
|
137
113
|
project_path: Path,
|
|
138
114
|
yolo_mode: bool,
|
|
139
115
|
prompt_fn: Optional[Callable[[str], str]] = None,
|
|
140
|
-
console: Optional["Console"] = None,
|
|
141
|
-
prompt_session: Optional["PromptSession"] = None,
|
|
116
|
+
console: Optional["Console"] = None, # noqa: ARG001 (kept for backward compatibility)
|
|
117
|
+
prompt_session: Optional["PromptSession"] = None, # noqa: ARG001 (kept for backward compatibility)
|
|
142
118
|
) -> Callable[[Tool[Any, Any], Any], Awaitable[PermissionResult]]:
|
|
143
119
|
"""Create a permission checking function for the current project.
|
|
144
120
|
|
|
@@ -146,25 +122,25 @@ def make_permission_checker(
|
|
|
146
122
|
project_path: Path to the project directory
|
|
147
123
|
yolo_mode: If True, all tool calls are allowed without prompting
|
|
148
124
|
prompt_fn: Optional function to use for prompting (defaults to input())
|
|
149
|
-
console:
|
|
150
|
-
prompt_session:
|
|
125
|
+
console: (Deprecated) No longer used, kept for backward compatibility
|
|
126
|
+
prompt_session: (Deprecated) No longer used, kept for backward compatibility
|
|
151
127
|
|
|
152
128
|
In yolo mode, all tool calls are allowed without prompting.
|
|
153
129
|
"""
|
|
154
130
|
|
|
131
|
+
_ = console, prompt_session # Mark as intentionally unused
|
|
155
132
|
project_path = project_path.resolve()
|
|
156
133
|
config_manager.get_project_config(project_path)
|
|
157
134
|
|
|
158
135
|
session_allowed_tools: Set[str] = set()
|
|
159
136
|
session_tool_rules: dict[str, Set[str]] = defaultdict(set)
|
|
160
137
|
|
|
161
|
-
async def _prompt_user(prompt: str, options: list[tuple[str, str]]
|
|
162
|
-
"""Prompt the user with proper interrupt handling using choice
|
|
138
|
+
async def _prompt_user(prompt: str, options: list[tuple[str, str]]) -> str:
|
|
139
|
+
"""Prompt the user with proper interrupt handling using unified choice component.
|
|
163
140
|
|
|
164
141
|
Args:
|
|
165
|
-
prompt: The prompt text to display.
|
|
142
|
+
prompt: The prompt text to display (supports HTML formatting).
|
|
166
143
|
options: List of (value, label) tuples for choices.
|
|
167
|
-
is_html: If True, prompt is already formatted HTML and should not be escaped.
|
|
168
144
|
"""
|
|
169
145
|
loop = asyncio.get_running_loop()
|
|
170
146
|
|
|
@@ -179,42 +155,13 @@ def make_permission_checker(
|
|
|
179
155
|
input_prompt = f"Choice ({numeric_choices} or {shortcut_choices}): "
|
|
180
156
|
return responder(input_prompt)
|
|
181
157
|
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if is_html:
|
|
189
|
-
# Prompt is already HTML formatted
|
|
190
|
-
formatted_prompt = HTML(f"\n{prompt}\n")
|
|
191
|
-
else:
|
|
192
|
-
# Escape HTML special characters in plain text prompt
|
|
193
|
-
formatted_prompt = HTML(f"\n{html.escape(prompt)}\n")
|
|
194
|
-
|
|
195
|
-
esc_bindings = KeyBindings()
|
|
196
|
-
|
|
197
|
-
@esc_bindings.add("escape", eager=True)
|
|
198
|
-
def _esc_to_deny(event: Any) -> None:
|
|
199
|
-
event.app.exit(result="n", style="class:aborting")
|
|
200
|
-
|
|
201
|
-
result = choice(
|
|
202
|
-
message=formatted_prompt,
|
|
203
|
-
options=choice_options,
|
|
204
|
-
style=_permission_style(),
|
|
205
|
-
show_frame=~is_done, # Frame disappears after selection
|
|
206
|
-
key_bindings=esc_bindings,
|
|
158
|
+
# Use the unified choice component
|
|
159
|
+
return prompt_choice(
|
|
160
|
+
message=prompt,
|
|
161
|
+
options=options,
|
|
162
|
+
allow_esc=True,
|
|
163
|
+
esc_value="n", # ESC means no
|
|
207
164
|
)
|
|
208
|
-
|
|
209
|
-
# Clear the entire prompt after selection
|
|
210
|
-
# ANSI codes: ESC[F = move cursor to beginning of previous line
|
|
211
|
-
# ESC[2K = clear entire line
|
|
212
|
-
# We need to clear: blank + prompt + blank + options (each option takes 1 line)
|
|
213
|
-
# plus frame borders (top and bottom) = approximately 6-8 lines
|
|
214
|
-
for _ in range(12): # Clear enough lines to cover the prompt
|
|
215
|
-
print("\033[F\033[2K", end="", flush=True)
|
|
216
|
-
|
|
217
|
-
return result
|
|
218
165
|
except KeyboardInterrupt:
|
|
219
166
|
logger.debug("[permissions] KeyboardInterrupt in choice")
|
|
220
167
|
return "n"
|
|
@@ -270,6 +217,13 @@ def make_permission_checker(
|
|
|
270
217
|
| set(local_config.local_deny_rules or [])
|
|
271
218
|
)
|
|
272
219
|
}
|
|
220
|
+
ask_rules = {
|
|
221
|
+
"Bash": (
|
|
222
|
+
set(config.bash_ask_rules or [])
|
|
223
|
+
| set(global_config.user_ask_rules or [])
|
|
224
|
+
| set(local_config.local_ask_rules or [])
|
|
225
|
+
)
|
|
226
|
+
}
|
|
273
227
|
allowed_working_dirs = {
|
|
274
228
|
str(project_path.resolve()),
|
|
275
229
|
*[str(Path(p).resolve()) for p in config.working_directories or []],
|
|
@@ -287,6 +241,7 @@ def make_permission_checker(
|
|
|
287
241
|
{
|
|
288
242
|
"allowed_rules": allow_rules.get(tool.name, set()),
|
|
289
243
|
"denied_rules": deny_rules.get(tool.name, set()),
|
|
244
|
+
"ask_rules": ask_rules.get(tool.name, set()),
|
|
290
245
|
"allowed_working_directories": allowed_working_dirs,
|
|
291
246
|
},
|
|
292
247
|
)
|
|
@@ -321,7 +276,7 @@ def make_permission_checker(
|
|
|
321
276
|
|
|
322
277
|
# If tool doesn't normally require permission (e.g., read-only Bash),
|
|
323
278
|
# enforce deny rules but otherwise skip prompting.
|
|
324
|
-
if not needs_permission:
|
|
279
|
+
if not needs_permission and decision.behavior != "ask":
|
|
325
280
|
if decision.behavior == "deny":
|
|
326
281
|
return PermissionResult(
|
|
327
282
|
result=False,
|
|
@@ -410,7 +365,7 @@ def make_permission_checker(
|
|
|
410
365
|
("n", "<no-option>No</no-option>"),
|
|
411
366
|
]
|
|
412
367
|
|
|
413
|
-
answer = (await _prompt_user(prompt, options=options
|
|
368
|
+
answer = (await _prompt_user(prompt, options=options)).strip().lower()
|
|
414
369
|
logger.debug(
|
|
415
370
|
"[permissions] User answer for permission prompt",
|
|
416
371
|
extra={"answer": answer, "tool": getattr(tool, "name", None)},
|
|
@@ -689,6 +689,17 @@ class AnthropicClient(ProviderClient):
|
|
|
689
689
|
if usage:
|
|
690
690
|
# Update with final usage - output_tokens comes here
|
|
691
691
|
usage_tokens["output_tokens"] = getattr(usage, "output_tokens", 0)
|
|
692
|
+
# Some APIs (like zhipu) may also include input_tokens here
|
|
693
|
+
input_tokens = getattr(usage, "input_tokens", None)
|
|
694
|
+
if input_tokens is not None:
|
|
695
|
+
usage_tokens["input_tokens"] = input_tokens
|
|
696
|
+
# Also check for cache tokens
|
|
697
|
+
cache_read = getattr(usage, "cache_read_input_tokens", None)
|
|
698
|
+
if cache_read is not None:
|
|
699
|
+
usage_tokens["cache_read_input_tokens"] = cache_read
|
|
700
|
+
cache_creation = getattr(usage, "cache_creation_input_tokens", None)
|
|
701
|
+
if cache_creation is not None:
|
|
702
|
+
usage_tokens["cache_creation_input_tokens"] = cache_creation
|
|
692
703
|
|
|
693
704
|
elif event_type == "message_stop":
|
|
694
705
|
# Message complete
|
ripperdoc/protocol/stdio.py
CHANGED
|
@@ -23,7 +23,7 @@ from typing import Any, AsyncGenerator, Callable, TypeVar
|
|
|
23
23
|
import click
|
|
24
24
|
|
|
25
25
|
from ripperdoc.core.config import get_project_config, get_effective_model_profile
|
|
26
|
-
from ripperdoc.core.default_tools import get_default_tools
|
|
26
|
+
from ripperdoc.core.default_tools import filter_tools_by_names, get_default_tools
|
|
27
27
|
from ripperdoc.core.query import query, QueryContext
|
|
28
28
|
from ripperdoc.core.query_utils import resolve_model_profile
|
|
29
29
|
from ripperdoc.core.system_prompt import build_system_prompt
|
|
@@ -431,6 +431,8 @@ class StdioProtocolHandler:
|
|
|
431
431
|
dynamic_tools = await load_dynamic_mcp_tools_async(self._project_path)
|
|
432
432
|
if dynamic_tools:
|
|
433
433
|
tools = merge_tools_with_dynamic(tools, dynamic_tools)
|
|
434
|
+
if allowed_tools is not None:
|
|
435
|
+
tools = filter_tools_by_names(tools, allowed_tools)
|
|
434
436
|
self._query_context.tools = tools
|
|
435
437
|
|
|
436
438
|
mcp_instructions = format_mcp_instructions(servers)
|
ripperdoc/tools/bash_tool.py
CHANGED
|
@@ -316,6 +316,7 @@ build projects, run tests, and interact with the file system."""
|
|
|
316
316
|
|
|
317
317
|
allow_rules = permission_context.get("allowed_rules") or set()
|
|
318
318
|
deny_rules = permission_context.get("denied_rules") or set()
|
|
319
|
+
ask_rules = permission_context.get("ask_rules") or set()
|
|
319
320
|
allowed_dirs = permission_context.get("allowed_working_directories") or {safe_get_cwd()}
|
|
320
321
|
|
|
321
322
|
# Check for sensitive directory access with read-only commands (cd, find).
|
|
@@ -340,6 +341,7 @@ build projects, run tests, and interact with the file system."""
|
|
|
340
341
|
input_data,
|
|
341
342
|
allow_rules,
|
|
342
343
|
deny_rules,
|
|
344
|
+
ask_rules,
|
|
343
345
|
allowed_dirs,
|
|
344
346
|
# danger_detector uses default: validate_shell_command(cmd).behavior != "passthrough"
|
|
345
347
|
# read_only_detector uses default: _is_command_read_only
|