ripperdoc 0.2.6__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 +3 -0
- ripperdoc/__main__.py +20 -0
- ripperdoc/cli/__init__.py +1 -0
- ripperdoc/cli/cli.py +405 -0
- ripperdoc/cli/commands/__init__.py +82 -0
- ripperdoc/cli/commands/agents_cmd.py +263 -0
- ripperdoc/cli/commands/base.py +19 -0
- ripperdoc/cli/commands/clear_cmd.py +18 -0
- ripperdoc/cli/commands/compact_cmd.py +23 -0
- ripperdoc/cli/commands/config_cmd.py +31 -0
- ripperdoc/cli/commands/context_cmd.py +144 -0
- ripperdoc/cli/commands/cost_cmd.py +82 -0
- ripperdoc/cli/commands/doctor_cmd.py +221 -0
- ripperdoc/cli/commands/exit_cmd.py +19 -0
- ripperdoc/cli/commands/help_cmd.py +20 -0
- ripperdoc/cli/commands/mcp_cmd.py +70 -0
- ripperdoc/cli/commands/memory_cmd.py +202 -0
- ripperdoc/cli/commands/models_cmd.py +413 -0
- ripperdoc/cli/commands/permissions_cmd.py +302 -0
- ripperdoc/cli/commands/resume_cmd.py +98 -0
- ripperdoc/cli/commands/status_cmd.py +167 -0
- ripperdoc/cli/commands/tasks_cmd.py +278 -0
- ripperdoc/cli/commands/todos_cmd.py +69 -0
- ripperdoc/cli/commands/tools_cmd.py +19 -0
- ripperdoc/cli/ui/__init__.py +1 -0
- ripperdoc/cli/ui/context_display.py +298 -0
- ripperdoc/cli/ui/helpers.py +22 -0
- ripperdoc/cli/ui/rich_ui.py +1557 -0
- ripperdoc/cli/ui/spinner.py +49 -0
- ripperdoc/cli/ui/thinking_spinner.py +128 -0
- ripperdoc/cli/ui/tool_renderers.py +298 -0
- ripperdoc/core/__init__.py +1 -0
- ripperdoc/core/agents.py +486 -0
- ripperdoc/core/commands.py +33 -0
- ripperdoc/core/config.py +559 -0
- ripperdoc/core/default_tools.py +88 -0
- ripperdoc/core/permissions.py +252 -0
- ripperdoc/core/providers/__init__.py +47 -0
- ripperdoc/core/providers/anthropic.py +250 -0
- ripperdoc/core/providers/base.py +265 -0
- ripperdoc/core/providers/gemini.py +615 -0
- ripperdoc/core/providers/openai.py +487 -0
- ripperdoc/core/query.py +1058 -0
- ripperdoc/core/query_utils.py +622 -0
- ripperdoc/core/skills.py +295 -0
- ripperdoc/core/system_prompt.py +431 -0
- ripperdoc/core/tool.py +240 -0
- ripperdoc/sdk/__init__.py +9 -0
- ripperdoc/sdk/client.py +333 -0
- ripperdoc/tools/__init__.py +1 -0
- ripperdoc/tools/ask_user_question_tool.py +431 -0
- ripperdoc/tools/background_shell.py +389 -0
- ripperdoc/tools/bash_output_tool.py +98 -0
- ripperdoc/tools/bash_tool.py +1016 -0
- ripperdoc/tools/dynamic_mcp_tool.py +428 -0
- ripperdoc/tools/enter_plan_mode_tool.py +226 -0
- ripperdoc/tools/exit_plan_mode_tool.py +153 -0
- ripperdoc/tools/file_edit_tool.py +346 -0
- ripperdoc/tools/file_read_tool.py +203 -0
- ripperdoc/tools/file_write_tool.py +205 -0
- ripperdoc/tools/glob_tool.py +179 -0
- ripperdoc/tools/grep_tool.py +370 -0
- ripperdoc/tools/kill_bash_tool.py +136 -0
- ripperdoc/tools/ls_tool.py +471 -0
- ripperdoc/tools/mcp_tools.py +591 -0
- ripperdoc/tools/multi_edit_tool.py +456 -0
- ripperdoc/tools/notebook_edit_tool.py +386 -0
- ripperdoc/tools/skill_tool.py +205 -0
- ripperdoc/tools/task_tool.py +379 -0
- ripperdoc/tools/todo_tool.py +494 -0
- ripperdoc/tools/tool_search_tool.py +380 -0
- ripperdoc/utils/__init__.py +1 -0
- ripperdoc/utils/bash_constants.py +51 -0
- ripperdoc/utils/bash_output_utils.py +43 -0
- ripperdoc/utils/coerce.py +34 -0
- ripperdoc/utils/context_length_errors.py +252 -0
- ripperdoc/utils/exit_code_handlers.py +241 -0
- ripperdoc/utils/file_watch.py +135 -0
- ripperdoc/utils/git_utils.py +274 -0
- ripperdoc/utils/json_utils.py +27 -0
- ripperdoc/utils/log.py +176 -0
- ripperdoc/utils/mcp.py +560 -0
- ripperdoc/utils/memory.py +253 -0
- ripperdoc/utils/message_compaction.py +676 -0
- ripperdoc/utils/messages.py +519 -0
- ripperdoc/utils/output_utils.py +258 -0
- ripperdoc/utils/path_ignore.py +677 -0
- ripperdoc/utils/path_utils.py +46 -0
- ripperdoc/utils/permissions/__init__.py +27 -0
- ripperdoc/utils/permissions/path_validation_utils.py +174 -0
- ripperdoc/utils/permissions/shell_command_validation.py +552 -0
- ripperdoc/utils/permissions/tool_permission_utils.py +279 -0
- ripperdoc/utils/prompt.py +17 -0
- ripperdoc/utils/safe_get_cwd.py +31 -0
- ripperdoc/utils/sandbox_utils.py +38 -0
- ripperdoc/utils/session_history.py +260 -0
- ripperdoc/utils/session_usage.py +117 -0
- ripperdoc/utils/shell_token_utils.py +95 -0
- ripperdoc/utils/shell_utils.py +159 -0
- ripperdoc/utils/todo.py +203 -0
- ripperdoc/utils/token_estimation.py +34 -0
- ripperdoc-0.2.6.dist-info/METADATA +193 -0
- ripperdoc-0.2.6.dist-info/RECORD +107 -0
- ripperdoc-0.2.6.dist-info/WHEEL +5 -0
- ripperdoc-0.2.6.dist-info/entry_points.txt +3 -0
- ripperdoc-0.2.6.dist-info/licenses/LICENSE +53 -0
- ripperdoc-0.2.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Permission utilities."""
|
|
2
|
+
|
|
3
|
+
from .path_validation_utils import validate_shell_command_paths
|
|
4
|
+
from .shell_command_validation import (
|
|
5
|
+
validate_shell_command,
|
|
6
|
+
is_complex_unsafe_shell_command,
|
|
7
|
+
ValidationResult,
|
|
8
|
+
)
|
|
9
|
+
from .tool_permission_utils import (
|
|
10
|
+
PermissionDecision,
|
|
11
|
+
ToolRule,
|
|
12
|
+
evaluate_shell_command_permissions,
|
|
13
|
+
extract_rule_prefix,
|
|
14
|
+
match_rule,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"PermissionDecision",
|
|
19
|
+
"ToolRule",
|
|
20
|
+
"ValidationResult",
|
|
21
|
+
"evaluate_shell_command_permissions",
|
|
22
|
+
"extract_rule_prefix",
|
|
23
|
+
"is_complex_unsafe_shell_command",
|
|
24
|
+
"match_rule",
|
|
25
|
+
"validate_shell_command_paths",
|
|
26
|
+
"validate_shell_command",
|
|
27
|
+
]
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Path validation utilities for shell commands (cd/ls/find)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Iterable, List, Set
|
|
10
|
+
|
|
11
|
+
from ripperdoc.utils.safe_get_cwd import safe_get_cwd
|
|
12
|
+
from ripperdoc.utils.shell_token_utils import parse_and_clean_shell_tokens
|
|
13
|
+
from ripperdoc.utils.log import get_logger
|
|
14
|
+
|
|
15
|
+
logger = get_logger()
|
|
16
|
+
|
|
17
|
+
_GLOB_PATTERN = re.compile(r"[*?\[\]{}]")
|
|
18
|
+
_MAX_VISIBLE_ITEMS = 5
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ValidationResponse:
|
|
23
|
+
behavior: str # 'passthrough' | 'ask' | 'deny'
|
|
24
|
+
message: str
|
|
25
|
+
rule_suggestions: None | list[str] = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _format_allowed_dirs_preview(allowed_dirs: Iterable[str]) -> str:
|
|
29
|
+
dirs = list(allowed_dirs)
|
|
30
|
+
if len(dirs) <= _MAX_VISIBLE_ITEMS:
|
|
31
|
+
return ", ".join(f"'{item}'" for item in dirs)
|
|
32
|
+
return (
|
|
33
|
+
", ".join(f"'{item}'" for item in dirs[:_MAX_VISIBLE_ITEMS])
|
|
34
|
+
+ f", and {len(dirs) - _MAX_VISIBLE_ITEMS} more"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _expand_tilde(path_str: str) -> str:
|
|
39
|
+
if path_str == "~" or path_str.startswith("~/"):
|
|
40
|
+
return os.path.expanduser(path_str)
|
|
41
|
+
return path_str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _resolve_path(raw_path: str, cwd: str) -> Path:
|
|
45
|
+
expanded = _expand_tilde(raw_path.strip("'\""))
|
|
46
|
+
candidate = Path(expanded)
|
|
47
|
+
if not candidate.is_absolute():
|
|
48
|
+
candidate = Path(cwd) / candidate
|
|
49
|
+
try:
|
|
50
|
+
return candidate.resolve()
|
|
51
|
+
except (OSError, ValueError) as exc:
|
|
52
|
+
logger.warning(
|
|
53
|
+
"[path_validation] Failed to resolve path: %s: %s",
|
|
54
|
+
type(exc).__name__, exc,
|
|
55
|
+
extra={"raw_path": raw_path, "cwd": cwd},
|
|
56
|
+
)
|
|
57
|
+
return candidate
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _extract_directory_from_glob(glob_pattern: str) -> str:
|
|
61
|
+
match = _GLOB_PATTERN.search(glob_pattern)
|
|
62
|
+
if not match or match.start() == 0:
|
|
63
|
+
return glob_pattern
|
|
64
|
+
prefix = glob_pattern[: match.start()]
|
|
65
|
+
if "/" not in prefix:
|
|
66
|
+
return "."
|
|
67
|
+
return prefix.rsplit("/", 1)[0] or "/"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _is_path_allowed(resolved_path: Path, allowed_dirs: Set[str]) -> bool:
|
|
71
|
+
resolved_str = str(resolved_path)
|
|
72
|
+
for allowed in allowed_dirs:
|
|
73
|
+
normalized_allowed = os.path.abspath(allowed)
|
|
74
|
+
normalized = os.path.abspath(resolved_str)
|
|
75
|
+
if normalized == normalized_allowed:
|
|
76
|
+
return True
|
|
77
|
+
if normalized.startswith(normalized_allowed.rstrip(os.sep) + os.sep):
|
|
78
|
+
return True
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _validate_path(raw_path: str, cwd: str, allowed_dirs: Set[str]) -> tuple[bool, str]:
|
|
83
|
+
expanded = _expand_tilde(raw_path.strip() or ".")
|
|
84
|
+
if _GLOB_PATTERN.search(expanded):
|
|
85
|
+
directory = _extract_directory_from_glob(expanded)
|
|
86
|
+
resolved_dir = _resolve_path(directory, cwd)
|
|
87
|
+
return _is_path_allowed(resolved_dir, allowed_dirs), str(resolved_dir)
|
|
88
|
+
|
|
89
|
+
resolved = _resolve_path(expanded, cwd)
|
|
90
|
+
return _is_path_allowed(resolved, allowed_dirs), str(resolved)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _check_command_paths(
|
|
94
|
+
command: str, args: List[str], cwd: str, allowed_dirs: Set[str]
|
|
95
|
+
) -> ValidationResponse:
|
|
96
|
+
if command == "cd":
|
|
97
|
+
target = args[0] if args else os.path.expanduser("~")
|
|
98
|
+
allowed, resolved = _validate_path(target, cwd, allowed_dirs)
|
|
99
|
+
elif command == "ls":
|
|
100
|
+
# ls is a read-only command, allow it to run on any path
|
|
101
|
+
# This enables viewing system directories like /usr, /etc, etc.
|
|
102
|
+
return ValidationResponse(
|
|
103
|
+
behavior="passthrough",
|
|
104
|
+
message="ls is a read-only command, no path restrictions applied",
|
|
105
|
+
rule_suggestions=None,
|
|
106
|
+
)
|
|
107
|
+
elif command == "find":
|
|
108
|
+
paths: list[str] = []
|
|
109
|
+
for arg in args:
|
|
110
|
+
if arg.startswith("-"):
|
|
111
|
+
continue
|
|
112
|
+
paths.append(arg)
|
|
113
|
+
if not paths:
|
|
114
|
+
paths = ["."]
|
|
115
|
+
allowed = True
|
|
116
|
+
resolved = ""
|
|
117
|
+
for candidate in paths:
|
|
118
|
+
allowed, resolved = _validate_path(candidate, cwd, allowed_dirs)
|
|
119
|
+
if not allowed:
|
|
120
|
+
break
|
|
121
|
+
else:
|
|
122
|
+
return ValidationResponse(
|
|
123
|
+
behavior="passthrough",
|
|
124
|
+
message=f"Command '{command}' is not path-restricted",
|
|
125
|
+
rule_suggestions=None,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if allowed:
|
|
129
|
+
return ValidationResponse(
|
|
130
|
+
behavior="passthrough",
|
|
131
|
+
message="Path validation passed",
|
|
132
|
+
rule_suggestions=None,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
preview = _format_allowed_dirs_preview(sorted(allowed_dirs))
|
|
136
|
+
action = {
|
|
137
|
+
"cd": "change directory to",
|
|
138
|
+
"find": "search files in",
|
|
139
|
+
}.get(command, "access")
|
|
140
|
+
return ValidationResponse(
|
|
141
|
+
behavior="ask",
|
|
142
|
+
message=f"Requesting permission to {action} '{resolved}' (outside allowed directories: {preview})",
|
|
143
|
+
rule_suggestions=None,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def validate_shell_command_paths(
|
|
148
|
+
shell_command: str | object, cwd: str | None = None, allowed_dirs: Set[str] | None = None
|
|
149
|
+
) -> ValidationResponse:
|
|
150
|
+
"""Validate path-oriented shell commands against allowed working directories."""
|
|
151
|
+
command_str = shell_command.command if hasattr(shell_command, "command") else str(shell_command)
|
|
152
|
+
cwd = cwd or safe_get_cwd()
|
|
153
|
+
allowed_dirs = allowed_dirs or {cwd}
|
|
154
|
+
|
|
155
|
+
tokens = parse_and_clean_shell_tokens(command_str)
|
|
156
|
+
if not tokens:
|
|
157
|
+
return ValidationResponse(
|
|
158
|
+
behavior="passthrough",
|
|
159
|
+
message="Empty command - no paths to validate",
|
|
160
|
+
rule_suggestions=None,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
first, *rest = tokens
|
|
164
|
+
if first not in {"cd", "ls", "find"}:
|
|
165
|
+
return ValidationResponse(
|
|
166
|
+
behavior="passthrough",
|
|
167
|
+
message=f"Command '{first}' is not a path-restricted command",
|
|
168
|
+
rule_suggestions=None,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return _check_command_paths(first, rest, cwd, allowed_dirs)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
__all__ = ["ValidationResponse", "validate_shell_command_paths"]
|