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.
Files changed (81) hide show
  1. ripperdoc/__init__.py +3 -0
  2. ripperdoc/__main__.py +25 -0
  3. ripperdoc/cli/__init__.py +1 -0
  4. ripperdoc/cli/cli.py +317 -0
  5. ripperdoc/cli/commands/__init__.py +76 -0
  6. ripperdoc/cli/commands/agents_cmd.py +234 -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 +19 -0
  10. ripperdoc/cli/commands/config_cmd.py +31 -0
  11. ripperdoc/cli/commands/context_cmd.py +114 -0
  12. ripperdoc/cli/commands/cost_cmd.py +77 -0
  13. ripperdoc/cli/commands/exit_cmd.py +19 -0
  14. ripperdoc/cli/commands/help_cmd.py +20 -0
  15. ripperdoc/cli/commands/mcp_cmd.py +65 -0
  16. ripperdoc/cli/commands/models_cmd.py +327 -0
  17. ripperdoc/cli/commands/resume_cmd.py +97 -0
  18. ripperdoc/cli/commands/status_cmd.py +167 -0
  19. ripperdoc/cli/commands/tasks_cmd.py +240 -0
  20. ripperdoc/cli/commands/todos_cmd.py +69 -0
  21. ripperdoc/cli/commands/tools_cmd.py +19 -0
  22. ripperdoc/cli/ui/__init__.py +1 -0
  23. ripperdoc/cli/ui/context_display.py +297 -0
  24. ripperdoc/cli/ui/helpers.py +22 -0
  25. ripperdoc/cli/ui/rich_ui.py +1010 -0
  26. ripperdoc/cli/ui/spinner.py +50 -0
  27. ripperdoc/core/__init__.py +1 -0
  28. ripperdoc/core/agents.py +306 -0
  29. ripperdoc/core/commands.py +33 -0
  30. ripperdoc/core/config.py +382 -0
  31. ripperdoc/core/default_tools.py +57 -0
  32. ripperdoc/core/permissions.py +227 -0
  33. ripperdoc/core/query.py +682 -0
  34. ripperdoc/core/system_prompt.py +418 -0
  35. ripperdoc/core/tool.py +214 -0
  36. ripperdoc/sdk/__init__.py +9 -0
  37. ripperdoc/sdk/client.py +309 -0
  38. ripperdoc/tools/__init__.py +1 -0
  39. ripperdoc/tools/background_shell.py +291 -0
  40. ripperdoc/tools/bash_output_tool.py +98 -0
  41. ripperdoc/tools/bash_tool.py +822 -0
  42. ripperdoc/tools/file_edit_tool.py +281 -0
  43. ripperdoc/tools/file_read_tool.py +168 -0
  44. ripperdoc/tools/file_write_tool.py +141 -0
  45. ripperdoc/tools/glob_tool.py +134 -0
  46. ripperdoc/tools/grep_tool.py +232 -0
  47. ripperdoc/tools/kill_bash_tool.py +136 -0
  48. ripperdoc/tools/ls_tool.py +298 -0
  49. ripperdoc/tools/mcp_tools.py +804 -0
  50. ripperdoc/tools/multi_edit_tool.py +393 -0
  51. ripperdoc/tools/notebook_edit_tool.py +325 -0
  52. ripperdoc/tools/task_tool.py +282 -0
  53. ripperdoc/tools/todo_tool.py +362 -0
  54. ripperdoc/tools/tool_search_tool.py +366 -0
  55. ripperdoc/utils/__init__.py +1 -0
  56. ripperdoc/utils/bash_constants.py +51 -0
  57. ripperdoc/utils/bash_output_utils.py +43 -0
  58. ripperdoc/utils/exit_code_handlers.py +241 -0
  59. ripperdoc/utils/log.py +76 -0
  60. ripperdoc/utils/mcp.py +427 -0
  61. ripperdoc/utils/memory.py +239 -0
  62. ripperdoc/utils/message_compaction.py +640 -0
  63. ripperdoc/utils/messages.py +399 -0
  64. ripperdoc/utils/output_utils.py +233 -0
  65. ripperdoc/utils/path_utils.py +46 -0
  66. ripperdoc/utils/permissions/__init__.py +21 -0
  67. ripperdoc/utils/permissions/path_validation_utils.py +165 -0
  68. ripperdoc/utils/permissions/shell_command_validation.py +74 -0
  69. ripperdoc/utils/permissions/tool_permission_utils.py +279 -0
  70. ripperdoc/utils/safe_get_cwd.py +24 -0
  71. ripperdoc/utils/sandbox_utils.py +38 -0
  72. ripperdoc/utils/session_history.py +223 -0
  73. ripperdoc/utils/session_usage.py +110 -0
  74. ripperdoc/utils/shell_token_utils.py +95 -0
  75. ripperdoc/utils/todo.py +199 -0
  76. ripperdoc-0.1.0.dist-info/METADATA +178 -0
  77. ripperdoc-0.1.0.dist-info/RECORD +81 -0
  78. ripperdoc-0.1.0.dist-info/WHEEL +5 -0
  79. ripperdoc-0.1.0.dist-info/entry_points.txt +3 -0
  80. ripperdoc-0.1.0.dist-info/licenses/LICENSE +53 -0
  81. 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