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.
Files changed (107) hide show
  1. ripperdoc/__init__.py +3 -0
  2. ripperdoc/__main__.py +20 -0
  3. ripperdoc/cli/__init__.py +1 -0
  4. ripperdoc/cli/cli.py +405 -0
  5. ripperdoc/cli/commands/__init__.py +82 -0
  6. ripperdoc/cli/commands/agents_cmd.py +263 -0
  7. ripperdoc/cli/commands/base.py +19 -0
  8. ripperdoc/cli/commands/clear_cmd.py +18 -0
  9. ripperdoc/cli/commands/compact_cmd.py +23 -0
  10. ripperdoc/cli/commands/config_cmd.py +31 -0
  11. ripperdoc/cli/commands/context_cmd.py +144 -0
  12. ripperdoc/cli/commands/cost_cmd.py +82 -0
  13. ripperdoc/cli/commands/doctor_cmd.py +221 -0
  14. ripperdoc/cli/commands/exit_cmd.py +19 -0
  15. ripperdoc/cli/commands/help_cmd.py +20 -0
  16. ripperdoc/cli/commands/mcp_cmd.py +70 -0
  17. ripperdoc/cli/commands/memory_cmd.py +202 -0
  18. ripperdoc/cli/commands/models_cmd.py +413 -0
  19. ripperdoc/cli/commands/permissions_cmd.py +302 -0
  20. ripperdoc/cli/commands/resume_cmd.py +98 -0
  21. ripperdoc/cli/commands/status_cmd.py +167 -0
  22. ripperdoc/cli/commands/tasks_cmd.py +278 -0
  23. ripperdoc/cli/commands/todos_cmd.py +69 -0
  24. ripperdoc/cli/commands/tools_cmd.py +19 -0
  25. ripperdoc/cli/ui/__init__.py +1 -0
  26. ripperdoc/cli/ui/context_display.py +298 -0
  27. ripperdoc/cli/ui/helpers.py +22 -0
  28. ripperdoc/cli/ui/rich_ui.py +1557 -0
  29. ripperdoc/cli/ui/spinner.py +49 -0
  30. ripperdoc/cli/ui/thinking_spinner.py +128 -0
  31. ripperdoc/cli/ui/tool_renderers.py +298 -0
  32. ripperdoc/core/__init__.py +1 -0
  33. ripperdoc/core/agents.py +486 -0
  34. ripperdoc/core/commands.py +33 -0
  35. ripperdoc/core/config.py +559 -0
  36. ripperdoc/core/default_tools.py +88 -0
  37. ripperdoc/core/permissions.py +252 -0
  38. ripperdoc/core/providers/__init__.py +47 -0
  39. ripperdoc/core/providers/anthropic.py +250 -0
  40. ripperdoc/core/providers/base.py +265 -0
  41. ripperdoc/core/providers/gemini.py +615 -0
  42. ripperdoc/core/providers/openai.py +487 -0
  43. ripperdoc/core/query.py +1058 -0
  44. ripperdoc/core/query_utils.py +622 -0
  45. ripperdoc/core/skills.py +295 -0
  46. ripperdoc/core/system_prompt.py +431 -0
  47. ripperdoc/core/tool.py +240 -0
  48. ripperdoc/sdk/__init__.py +9 -0
  49. ripperdoc/sdk/client.py +333 -0
  50. ripperdoc/tools/__init__.py +1 -0
  51. ripperdoc/tools/ask_user_question_tool.py +431 -0
  52. ripperdoc/tools/background_shell.py +389 -0
  53. ripperdoc/tools/bash_output_tool.py +98 -0
  54. ripperdoc/tools/bash_tool.py +1016 -0
  55. ripperdoc/tools/dynamic_mcp_tool.py +428 -0
  56. ripperdoc/tools/enter_plan_mode_tool.py +226 -0
  57. ripperdoc/tools/exit_plan_mode_tool.py +153 -0
  58. ripperdoc/tools/file_edit_tool.py +346 -0
  59. ripperdoc/tools/file_read_tool.py +203 -0
  60. ripperdoc/tools/file_write_tool.py +205 -0
  61. ripperdoc/tools/glob_tool.py +179 -0
  62. ripperdoc/tools/grep_tool.py +370 -0
  63. ripperdoc/tools/kill_bash_tool.py +136 -0
  64. ripperdoc/tools/ls_tool.py +471 -0
  65. ripperdoc/tools/mcp_tools.py +591 -0
  66. ripperdoc/tools/multi_edit_tool.py +456 -0
  67. ripperdoc/tools/notebook_edit_tool.py +386 -0
  68. ripperdoc/tools/skill_tool.py +205 -0
  69. ripperdoc/tools/task_tool.py +379 -0
  70. ripperdoc/tools/todo_tool.py +494 -0
  71. ripperdoc/tools/tool_search_tool.py +380 -0
  72. ripperdoc/utils/__init__.py +1 -0
  73. ripperdoc/utils/bash_constants.py +51 -0
  74. ripperdoc/utils/bash_output_utils.py +43 -0
  75. ripperdoc/utils/coerce.py +34 -0
  76. ripperdoc/utils/context_length_errors.py +252 -0
  77. ripperdoc/utils/exit_code_handlers.py +241 -0
  78. ripperdoc/utils/file_watch.py +135 -0
  79. ripperdoc/utils/git_utils.py +274 -0
  80. ripperdoc/utils/json_utils.py +27 -0
  81. ripperdoc/utils/log.py +176 -0
  82. ripperdoc/utils/mcp.py +560 -0
  83. ripperdoc/utils/memory.py +253 -0
  84. ripperdoc/utils/message_compaction.py +676 -0
  85. ripperdoc/utils/messages.py +519 -0
  86. ripperdoc/utils/output_utils.py +258 -0
  87. ripperdoc/utils/path_ignore.py +677 -0
  88. ripperdoc/utils/path_utils.py +46 -0
  89. ripperdoc/utils/permissions/__init__.py +27 -0
  90. ripperdoc/utils/permissions/path_validation_utils.py +174 -0
  91. ripperdoc/utils/permissions/shell_command_validation.py +552 -0
  92. ripperdoc/utils/permissions/tool_permission_utils.py +279 -0
  93. ripperdoc/utils/prompt.py +17 -0
  94. ripperdoc/utils/safe_get_cwd.py +31 -0
  95. ripperdoc/utils/sandbox_utils.py +38 -0
  96. ripperdoc/utils/session_history.py +260 -0
  97. ripperdoc/utils/session_usage.py +117 -0
  98. ripperdoc/utils/shell_token_utils.py +95 -0
  99. ripperdoc/utils/shell_utils.py +159 -0
  100. ripperdoc/utils/todo.py +203 -0
  101. ripperdoc/utils/token_estimation.py +34 -0
  102. ripperdoc-0.2.6.dist-info/METADATA +193 -0
  103. ripperdoc-0.2.6.dist-info/RECORD +107 -0
  104. ripperdoc-0.2.6.dist-info/WHEEL +5 -0
  105. ripperdoc-0.2.6.dist-info/entry_points.txt +3 -0
  106. ripperdoc-0.2.6.dist-info/licenses/LICENSE +53 -0
  107. 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"]