ripperdoc 0.1.0__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 +25 -0
- ripperdoc/cli/__init__.py +1 -0
- ripperdoc/cli/cli.py +317 -0
- ripperdoc/cli/commands/__init__.py +76 -0
- ripperdoc/cli/commands/agents_cmd.py +234 -0
- ripperdoc/cli/commands/base.py +19 -0
- ripperdoc/cli/commands/clear_cmd.py +18 -0
- ripperdoc/cli/commands/compact_cmd.py +19 -0
- ripperdoc/cli/commands/config_cmd.py +31 -0
- ripperdoc/cli/commands/context_cmd.py +114 -0
- ripperdoc/cli/commands/cost_cmd.py +77 -0
- ripperdoc/cli/commands/exit_cmd.py +19 -0
- ripperdoc/cli/commands/help_cmd.py +20 -0
- ripperdoc/cli/commands/mcp_cmd.py +65 -0
- ripperdoc/cli/commands/models_cmd.py +327 -0
- ripperdoc/cli/commands/resume_cmd.py +97 -0
- ripperdoc/cli/commands/status_cmd.py +167 -0
- ripperdoc/cli/commands/tasks_cmd.py +240 -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 +297 -0
- ripperdoc/cli/ui/helpers.py +22 -0
- ripperdoc/cli/ui/rich_ui.py +1010 -0
- ripperdoc/cli/ui/spinner.py +50 -0
- ripperdoc/core/__init__.py +1 -0
- ripperdoc/core/agents.py +306 -0
- ripperdoc/core/commands.py +33 -0
- ripperdoc/core/config.py +382 -0
- ripperdoc/core/default_tools.py +57 -0
- ripperdoc/core/permissions.py +227 -0
- ripperdoc/core/query.py +682 -0
- ripperdoc/core/system_prompt.py +418 -0
- ripperdoc/core/tool.py +214 -0
- ripperdoc/sdk/__init__.py +9 -0
- ripperdoc/sdk/client.py +309 -0
- ripperdoc/tools/__init__.py +1 -0
- ripperdoc/tools/background_shell.py +291 -0
- ripperdoc/tools/bash_output_tool.py +98 -0
- ripperdoc/tools/bash_tool.py +822 -0
- ripperdoc/tools/file_edit_tool.py +281 -0
- ripperdoc/tools/file_read_tool.py +168 -0
- ripperdoc/tools/file_write_tool.py +141 -0
- ripperdoc/tools/glob_tool.py +134 -0
- ripperdoc/tools/grep_tool.py +232 -0
- ripperdoc/tools/kill_bash_tool.py +136 -0
- ripperdoc/tools/ls_tool.py +298 -0
- ripperdoc/tools/mcp_tools.py +804 -0
- ripperdoc/tools/multi_edit_tool.py +393 -0
- ripperdoc/tools/notebook_edit_tool.py +325 -0
- ripperdoc/tools/task_tool.py +282 -0
- ripperdoc/tools/todo_tool.py +362 -0
- ripperdoc/tools/tool_search_tool.py +366 -0
- ripperdoc/utils/__init__.py +1 -0
- ripperdoc/utils/bash_constants.py +51 -0
- ripperdoc/utils/bash_output_utils.py +43 -0
- ripperdoc/utils/exit_code_handlers.py +241 -0
- ripperdoc/utils/log.py +76 -0
- ripperdoc/utils/mcp.py +427 -0
- ripperdoc/utils/memory.py +239 -0
- ripperdoc/utils/message_compaction.py +640 -0
- ripperdoc/utils/messages.py +399 -0
- ripperdoc/utils/output_utils.py +233 -0
- ripperdoc/utils/path_utils.py +46 -0
- ripperdoc/utils/permissions/__init__.py +21 -0
- ripperdoc/utils/permissions/path_validation_utils.py +165 -0
- ripperdoc/utils/permissions/shell_command_validation.py +74 -0
- ripperdoc/utils/permissions/tool_permission_utils.py +279 -0
- ripperdoc/utils/safe_get_cwd.py +24 -0
- ripperdoc/utils/sandbox_utils.py +38 -0
- ripperdoc/utils/session_history.py +223 -0
- ripperdoc/utils/session_usage.py +110 -0
- ripperdoc/utils/shell_token_utils.py +95 -0
- ripperdoc/utils/todo.py +199 -0
- ripperdoc-0.1.0.dist-info/METADATA +178 -0
- ripperdoc-0.1.0.dist-info/RECORD +81 -0
- ripperdoc-0.1.0.dist-info/WHEEL +5 -0
- ripperdoc-0.1.0.dist-info/entry_points.txt +3 -0
- ripperdoc-0.1.0.dist-info/licenses/LICENSE +53 -0
- ripperdoc-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Smart exit code handlers for common shell commands and related helpers.
|
|
2
|
+
|
|
3
|
+
Provides intelligent interpretation of exit codes for commands like grep, diff, test, etc.
|
|
4
|
+
where non-zero exit codes don't necessarily indicate errors. Also includes small utilities
|
|
5
|
+
shared by bash tooling such as command classification, preview sizing, and lightweight
|
|
6
|
+
command/result schemas.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import shlex
|
|
10
|
+
from typing import Callable, Optional
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ExitCodeResult:
|
|
17
|
+
"""Result of exit code interpretation."""
|
|
18
|
+
|
|
19
|
+
is_error: bool
|
|
20
|
+
message: Optional[str] = None
|
|
21
|
+
semantic_meaning: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
ExitCodeHandler = Callable[[int, str, str], ExitCodeResult]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Default/max timeouts exposed for bash tooling (keep aligned with BashTool).
|
|
28
|
+
DEFAULT_BASH_TIMEOUT_MS = 120000
|
|
29
|
+
MAX_BASH_TIMEOUT_MS = 600000
|
|
30
|
+
|
|
31
|
+
# Commands we intentionally ignore in certain contexts (e.g., background-safety checks).
|
|
32
|
+
IGNORED_COMMANDS: tuple[str, ...] = ("sleep",)
|
|
33
|
+
|
|
34
|
+
# Preview limits for rendering long commands compactly.
|
|
35
|
+
MAX_PREVIEW_LINES = 2
|
|
36
|
+
MAX_PREVIEW_CHARS = 160
|
|
37
|
+
|
|
38
|
+
# Heuristic command classification list (mirrors the reference set).
|
|
39
|
+
COMMON_COMMANDS: tuple[str, ...] = (
|
|
40
|
+
"npm",
|
|
41
|
+
"yarn",
|
|
42
|
+
"pnpm",
|
|
43
|
+
"node",
|
|
44
|
+
"python",
|
|
45
|
+
"python3",
|
|
46
|
+
"go",
|
|
47
|
+
"cargo",
|
|
48
|
+
"make",
|
|
49
|
+
"docker",
|
|
50
|
+
"terraform",
|
|
51
|
+
"webpack",
|
|
52
|
+
"vite",
|
|
53
|
+
"jest",
|
|
54
|
+
"pytest",
|
|
55
|
+
"curl",
|
|
56
|
+
"wget",
|
|
57
|
+
"build",
|
|
58
|
+
"test",
|
|
59
|
+
"serve",
|
|
60
|
+
"watch",
|
|
61
|
+
"dev",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class BashCommandSchema(BaseModel):
|
|
66
|
+
"""Schema describing a bash command request."""
|
|
67
|
+
|
|
68
|
+
command: str = Field(description="The command to execute")
|
|
69
|
+
timeout: Optional[int] = Field(
|
|
70
|
+
default=None, description=f"Optional timeout in milliseconds (max {MAX_BASH_TIMEOUT_MS})"
|
|
71
|
+
)
|
|
72
|
+
description: Optional[str] = Field(
|
|
73
|
+
default=None,
|
|
74
|
+
description="Clear, concise description of what this command does in 5-10 words.",
|
|
75
|
+
)
|
|
76
|
+
run_in_background: bool = Field(
|
|
77
|
+
default=False, description="Set to true to run this command in the background."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ExtendedBashCommandSchema(BashCommandSchema):
|
|
82
|
+
"""Schema describing an extended bash command request."""
|
|
83
|
+
|
|
84
|
+
sandbox: Optional[bool] = Field(
|
|
85
|
+
default=None, description="Whether to request sandboxed execution (read-only)."
|
|
86
|
+
)
|
|
87
|
+
shell_executable: Optional[str] = Field(
|
|
88
|
+
default=None, description="Optional shell path to use instead of the default shell."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class CommandResultSchema(BaseModel):
|
|
93
|
+
"""Schema describing the shape of a command result."""
|
|
94
|
+
|
|
95
|
+
stdout: str = Field(description="The standard output of the command")
|
|
96
|
+
stderr: str = Field(description="The standard error output of the command")
|
|
97
|
+
summary: Optional[str] = Field(default=None, description="Summarized output when available")
|
|
98
|
+
interrupted: bool = Field(default=False, description="Whether the command was interrupted")
|
|
99
|
+
is_image: Optional[bool] = Field(
|
|
100
|
+
default=None, description="Flag to indicate if stdout contains image data"
|
|
101
|
+
)
|
|
102
|
+
background_task_id: Optional[str] = Field(
|
|
103
|
+
default=None, description="ID of the background task if command is running in background"
|
|
104
|
+
)
|
|
105
|
+
sandbox: Optional[bool] = Field(
|
|
106
|
+
default=None, description="Flag to indicate if the command was run in sandbox mode"
|
|
107
|
+
)
|
|
108
|
+
return_code_interpretation: Optional[str] = Field(
|
|
109
|
+
default=None,
|
|
110
|
+
description="Semantic interpretation for non-error exit codes with special meaning",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def default_handler(exit_code: int, stdout: str, stderr: str) -> ExitCodeResult:
|
|
115
|
+
"""Default exit code handler - non-zero is error."""
|
|
116
|
+
return ExitCodeResult(
|
|
117
|
+
is_error=exit_code != 0,
|
|
118
|
+
message=f"Command failed with exit code {exit_code}" if exit_code != 0 else None,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def grep_handler(exit_code: int, stdout: str, stderr: str) -> ExitCodeResult:
|
|
123
|
+
"""Handle grep/rg exit codes: 0=found, 1=not found, 2+=error."""
|
|
124
|
+
if exit_code == 0:
|
|
125
|
+
return ExitCodeResult(is_error=False)
|
|
126
|
+
elif exit_code == 1:
|
|
127
|
+
return ExitCodeResult(is_error=False, semantic_meaning="No matches found")
|
|
128
|
+
else:
|
|
129
|
+
return ExitCodeResult(is_error=True, message=f"grep failed with exit code {exit_code}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def diff_handler(exit_code: int, stdout: str, stderr: str) -> ExitCodeResult:
|
|
133
|
+
"""Handle diff exit codes: 0=same, 1=different, 2+=error."""
|
|
134
|
+
if exit_code == 0:
|
|
135
|
+
return ExitCodeResult(is_error=False, semantic_meaning="Files are identical")
|
|
136
|
+
elif exit_code == 1:
|
|
137
|
+
return ExitCodeResult(is_error=False, semantic_meaning="Files differ")
|
|
138
|
+
else:
|
|
139
|
+
return ExitCodeResult(is_error=True, message=f"diff failed with exit code {exit_code}")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_handler(exit_code: int, stdout: str, stderr: str) -> ExitCodeResult:
|
|
143
|
+
"""Handle test/[ exit codes: 0=true, 1=false, 2+=error."""
|
|
144
|
+
if exit_code == 0:
|
|
145
|
+
return ExitCodeResult(is_error=False, semantic_meaning="Condition is true")
|
|
146
|
+
elif exit_code == 1:
|
|
147
|
+
return ExitCodeResult(is_error=False, semantic_meaning="Condition is false")
|
|
148
|
+
else:
|
|
149
|
+
return ExitCodeResult(
|
|
150
|
+
is_error=True, message=f"test command failed with exit code {exit_code}"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def find_handler(exit_code: int, stdout: str, stderr: str) -> ExitCodeResult:
|
|
155
|
+
"""Handle find exit codes: 0=ok, 1=partial, 2+=error."""
|
|
156
|
+
if exit_code == 0:
|
|
157
|
+
return ExitCodeResult(is_error=False)
|
|
158
|
+
elif exit_code == 1:
|
|
159
|
+
return ExitCodeResult(is_error=False, semantic_meaning="Some directories were inaccessible")
|
|
160
|
+
else:
|
|
161
|
+
return ExitCodeResult(is_error=True, message=f"find failed with exit code {exit_code}")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# Command-specific handlers
|
|
165
|
+
COMMAND_HANDLERS: dict[str, ExitCodeHandler] = {
|
|
166
|
+
"grep": grep_handler,
|
|
167
|
+
"rg": grep_handler,
|
|
168
|
+
"ripgrep": grep_handler,
|
|
169
|
+
"diff": diff_handler,
|
|
170
|
+
"test": test_handler,
|
|
171
|
+
"[": test_handler,
|
|
172
|
+
"find": find_handler,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def normalize_command(command: str) -> str:
|
|
177
|
+
"""Extract the base command from a command string.
|
|
178
|
+
|
|
179
|
+
Handles pipes, command chains, and extracts the final command.
|
|
180
|
+
Examples:
|
|
181
|
+
'git status' -> 'git'
|
|
182
|
+
'cat file | grep pattern' -> 'grep'
|
|
183
|
+
'ls -la' -> 'ls'
|
|
184
|
+
"""
|
|
185
|
+
# Get the last command in a pipe chain
|
|
186
|
+
if "|" in command:
|
|
187
|
+
command = command.split("|")[-1].strip()
|
|
188
|
+
|
|
189
|
+
# Get the first word (the actual command)
|
|
190
|
+
command = command.strip().split()[0] if command.strip() else ""
|
|
191
|
+
|
|
192
|
+
return command
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def classify_command(command: str) -> str:
|
|
196
|
+
"""Classify a shell command into a known category or 'other'."""
|
|
197
|
+
try:
|
|
198
|
+
tokens = shlex.split(command)
|
|
199
|
+
except ValueError:
|
|
200
|
+
tokens = command.split()
|
|
201
|
+
|
|
202
|
+
if not tokens:
|
|
203
|
+
return "other"
|
|
204
|
+
|
|
205
|
+
for token in tokens:
|
|
206
|
+
cleaned = token.strip()
|
|
207
|
+
if not cleaned or cleaned in {"&&", "||", ";", "|"}:
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
first_word = cleaned.split()[0].lower()
|
|
211
|
+
if first_word in COMMON_COMMANDS:
|
|
212
|
+
return first_word
|
|
213
|
+
|
|
214
|
+
return "other"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def get_exit_code_handler(command: str) -> ExitCodeHandler:
|
|
218
|
+
"""Get the appropriate exit code handler for a command."""
|
|
219
|
+
normalized = normalize_command(command)
|
|
220
|
+
return COMMAND_HANDLERS.get(normalized, default_handler)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def interpret_exit_code(command: str, exit_code: int, stdout: str, stderr: str) -> ExitCodeResult:
|
|
224
|
+
"""Interpret an exit code in the context of the command.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
command: The shell command that was executed
|
|
228
|
+
exit_code: The exit code returned
|
|
229
|
+
stdout: Standard output from the command
|
|
230
|
+
stderr: Standard error from the command
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
ExitCodeResult with interpretation
|
|
234
|
+
"""
|
|
235
|
+
handler = get_exit_code_handler(command)
|
|
236
|
+
return handler(exit_code, stdout, stderr)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def create_exit_result(command: str, exit_code: int, stdout: str, stderr: str) -> ExitCodeResult:
|
|
240
|
+
"""Convenience wrapper to mirror reference API naming."""
|
|
241
|
+
return interpret_exit_code(command, exit_code, stdout, stderr)
|
ripperdoc/utils/log.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Logging utilities for Ripperdoc."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RipperdocLogger:
|
|
12
|
+
"""Logger for Ripperdoc."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, name: str = "ripperdoc", log_dir: Optional[Path] = None):
|
|
15
|
+
self.logger = logging.getLogger(name)
|
|
16
|
+
level_name = os.getenv("RIPPERDOC_LOG_LEVEL", "WARNING").upper()
|
|
17
|
+
level = getattr(logging, level_name, logging.WARNING)
|
|
18
|
+
self.logger.setLevel(level)
|
|
19
|
+
|
|
20
|
+
# Console handler
|
|
21
|
+
console_handler = logging.StreamHandler(sys.stderr)
|
|
22
|
+
console_handler.setLevel(level)
|
|
23
|
+
console_formatter = logging.Formatter("%(levelname)s: %(message)s")
|
|
24
|
+
console_handler.setFormatter(console_formatter)
|
|
25
|
+
self.logger.addHandler(console_handler)
|
|
26
|
+
|
|
27
|
+
# File handler (optional)
|
|
28
|
+
if log_dir:
|
|
29
|
+
log_dir.mkdir(exist_ok=True)
|
|
30
|
+
log_file = log_dir / f"ripperdoc_{datetime.now().strftime('%Y%m%d')}.log"
|
|
31
|
+
file_handler = logging.FileHandler(log_file)
|
|
32
|
+
file_handler.setLevel(logging.DEBUG)
|
|
33
|
+
file_formatter = logging.Formatter(
|
|
34
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
35
|
+
)
|
|
36
|
+
file_handler.setFormatter(file_formatter)
|
|
37
|
+
self.logger.addHandler(file_handler)
|
|
38
|
+
|
|
39
|
+
def debug(self, message: str) -> None:
|
|
40
|
+
"""Log debug message."""
|
|
41
|
+
self.logger.debug(message)
|
|
42
|
+
|
|
43
|
+
def info(self, message: str) -> None:
|
|
44
|
+
"""Log info message."""
|
|
45
|
+
self.logger.info(message)
|
|
46
|
+
|
|
47
|
+
def warning(self, message: str) -> None:
|
|
48
|
+
"""Log warning message."""
|
|
49
|
+
self.logger.warning(message)
|
|
50
|
+
|
|
51
|
+
def error(self, message: str) -> None:
|
|
52
|
+
"""Log error message."""
|
|
53
|
+
self.logger.error(message)
|
|
54
|
+
|
|
55
|
+
def critical(self, message: str) -> None:
|
|
56
|
+
"""Log critical message."""
|
|
57
|
+
self.logger.critical(message)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Global logger instance
|
|
61
|
+
_logger: Optional[RipperdocLogger] = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_logger() -> RipperdocLogger:
|
|
65
|
+
"""Get the global logger instance."""
|
|
66
|
+
global _logger
|
|
67
|
+
if _logger is None:
|
|
68
|
+
_logger = RipperdocLogger()
|
|
69
|
+
return _logger
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def init_logger(log_dir: Optional[Path] = None) -> RipperdocLogger:
|
|
73
|
+
"""Initialize the global logger."""
|
|
74
|
+
global _logger
|
|
75
|
+
_logger = RipperdocLogger(log_dir=log_dir)
|
|
76
|
+
return _logger
|