ripperdoc 0.2.10__py3-none-any.whl → 0.3.1__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 (73) hide show
  1. ripperdoc/__init__.py +1 -1
  2. ripperdoc/cli/cli.py +164 -57
  3. ripperdoc/cli/commands/__init__.py +4 -0
  4. ripperdoc/cli/commands/agents_cmd.py +3 -7
  5. ripperdoc/cli/commands/doctor_cmd.py +29 -0
  6. ripperdoc/cli/commands/memory_cmd.py +2 -1
  7. ripperdoc/cli/commands/models_cmd.py +61 -5
  8. ripperdoc/cli/commands/resume_cmd.py +1 -0
  9. ripperdoc/cli/commands/skills_cmd.py +103 -0
  10. ripperdoc/cli/commands/stats_cmd.py +4 -4
  11. ripperdoc/cli/commands/status_cmd.py +10 -0
  12. ripperdoc/cli/commands/tasks_cmd.py +6 -3
  13. ripperdoc/cli/commands/themes_cmd.py +139 -0
  14. ripperdoc/cli/ui/file_mention_completer.py +63 -13
  15. ripperdoc/cli/ui/helpers.py +6 -3
  16. ripperdoc/cli/ui/interrupt_listener.py +233 -0
  17. ripperdoc/cli/ui/message_display.py +7 -0
  18. ripperdoc/cli/ui/panels.py +13 -8
  19. ripperdoc/cli/ui/rich_ui.py +513 -84
  20. ripperdoc/cli/ui/spinner.py +68 -5
  21. ripperdoc/cli/ui/tool_renderers.py +10 -9
  22. ripperdoc/cli/ui/wizard.py +18 -11
  23. ripperdoc/core/agents.py +4 -0
  24. ripperdoc/core/config.py +235 -0
  25. ripperdoc/core/default_tools.py +1 -0
  26. ripperdoc/core/hooks/llm_callback.py +0 -1
  27. ripperdoc/core/hooks/manager.py +6 -0
  28. ripperdoc/core/permissions.py +123 -39
  29. ripperdoc/core/providers/openai.py +55 -9
  30. ripperdoc/core/query.py +349 -108
  31. ripperdoc/core/query_utils.py +17 -14
  32. ripperdoc/core/skills.py +1 -0
  33. ripperdoc/core/theme.py +298 -0
  34. ripperdoc/core/tool.py +8 -3
  35. ripperdoc/protocol/__init__.py +14 -0
  36. ripperdoc/protocol/models.py +300 -0
  37. ripperdoc/protocol/stdio.py +1453 -0
  38. ripperdoc/tools/background_shell.py +49 -5
  39. ripperdoc/tools/bash_tool.py +75 -9
  40. ripperdoc/tools/file_edit_tool.py +98 -29
  41. ripperdoc/tools/file_read_tool.py +139 -8
  42. ripperdoc/tools/file_write_tool.py +46 -3
  43. ripperdoc/tools/grep_tool.py +98 -8
  44. ripperdoc/tools/lsp_tool.py +9 -15
  45. ripperdoc/tools/multi_edit_tool.py +26 -3
  46. ripperdoc/tools/skill_tool.py +52 -1
  47. ripperdoc/tools/task_tool.py +33 -8
  48. ripperdoc/utils/file_watch.py +12 -6
  49. ripperdoc/utils/image_utils.py +125 -0
  50. ripperdoc/utils/log.py +30 -3
  51. ripperdoc/utils/lsp.py +9 -3
  52. ripperdoc/utils/mcp.py +80 -18
  53. ripperdoc/utils/message_formatting.py +2 -2
  54. ripperdoc/utils/messages.py +177 -32
  55. ripperdoc/utils/pending_messages.py +50 -0
  56. ripperdoc/utils/permissions/shell_command_validation.py +3 -3
  57. ripperdoc/utils/permissions/tool_permission_utils.py +9 -3
  58. ripperdoc/utils/platform.py +198 -0
  59. ripperdoc/utils/session_heatmap.py +1 -3
  60. ripperdoc/utils/session_history.py +2 -2
  61. ripperdoc/utils/session_stats.py +1 -0
  62. ripperdoc/utils/shell_utils.py +8 -5
  63. ripperdoc/utils/todo.py +0 -6
  64. {ripperdoc-0.2.10.dist-info → ripperdoc-0.3.1.dist-info}/METADATA +49 -17
  65. ripperdoc-0.3.1.dist-info/RECORD +136 -0
  66. {ripperdoc-0.2.10.dist-info → ripperdoc-0.3.1.dist-info}/WHEEL +1 -1
  67. ripperdoc/cli/ui/interrupt_handler.py +0 -174
  68. ripperdoc/sdk/__init__.py +0 -9
  69. ripperdoc/sdk/client.py +0 -408
  70. ripperdoc-0.2.10.dist-info/RECORD +0 -129
  71. {ripperdoc-0.2.10.dist-info → ripperdoc-0.3.1.dist-info}/entry_points.txt +0 -0
  72. {ripperdoc-0.2.10.dist-info → ripperdoc-0.3.1.dist-info}/licenses/LICENSE +0 -0
  73. {ripperdoc-0.2.10.dist-info → ripperdoc-0.3.1.dist-info}/top_level.txt +0 -0
ripperdoc/sdk/client.py DELETED
@@ -1,408 +0,0 @@
1
- """Headless Python SDK for Ripperdoc.
2
-
3
- `query` helper for simple calls and a `RipperdocClient` for long-lived
4
- sessions that keep conversation history.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import asyncio
10
- import os
11
- import time
12
- import uuid
13
- from dataclasses import dataclass, field
14
- from pathlib import Path
15
- from typing import (
16
- Any,
17
- AsyncIterator,
18
- Awaitable,
19
- Callable,
20
- Dict,
21
- List,
22
- Optional,
23
- Sequence,
24
- Tuple,
25
- Union,
26
- )
27
-
28
- from ripperdoc.core.default_tools import get_default_tools
29
- from ripperdoc.core.hooks.llm_callback import build_hook_llm_callback
30
- from ripperdoc.core.hooks.manager import hook_manager
31
- from ripperdoc.core.query import QueryContext, query as _core_query
32
- from ripperdoc.core.permissions import PermissionResult
33
- from ripperdoc.core.system_prompt import build_system_prompt
34
- from ripperdoc.core.skills import build_skill_summary, load_all_skills
35
- from ripperdoc.core.tool import Tool
36
- from ripperdoc.tools.task_tool import TaskTool
37
- from ripperdoc.tools.mcp_tools import load_dynamic_mcp_tools_async, merge_tools_with_dynamic
38
- from ripperdoc.utils.memory import build_memory_instructions
39
- from ripperdoc.utils.messages import (
40
- AssistantMessage,
41
- ProgressMessage,
42
- UserMessage,
43
- create_assistant_message,
44
- create_user_message,
45
- )
46
- from ripperdoc.utils.mcp import (
47
- format_mcp_instructions,
48
- load_mcp_servers_async,
49
- shutdown_mcp_runtime,
50
- )
51
- from ripperdoc.utils.log import get_logger
52
-
53
- MessageType = Union[UserMessage, AssistantMessage, ProgressMessage]
54
- PermissionChecker = Callable[
55
- [Tool[Any, Any], Any],
56
- Union[
57
- PermissionResult,
58
- Dict[str, Any],
59
- Tuple[bool, Optional[str]],
60
- bool,
61
- Awaitable[Union[PermissionResult, Dict[str, Any], Tuple[bool, Optional[str]], bool]],
62
- ],
63
- ]
64
- QueryRunner = Callable[
65
- [
66
- List[MessageType],
67
- str,
68
- Dict[str, str],
69
- QueryContext,
70
- Optional[PermissionChecker],
71
- ],
72
- AsyncIterator[MessageType],
73
- ]
74
-
75
- _END_OF_STREAM = object()
76
-
77
- logger = get_logger()
78
-
79
-
80
- def _coerce_to_path(path: Union[str, Path]) -> Path:
81
- return path if isinstance(path, Path) else Path(path)
82
-
83
-
84
- @dataclass
85
- class RipperdocOptions:
86
- """Configuration for SDK usage."""
87
-
88
- tools: Optional[Sequence[Tool[Any, Any]]] = None
89
- allowed_tools: Optional[Sequence[str]] = None
90
- disallowed_tools: Optional[Sequence[str]] = None
91
- yolo_mode: bool = False
92
- verbose: bool = False
93
- model: str = "main"
94
- max_thinking_tokens: int = 0
95
- context: Dict[str, str] = field(default_factory=dict)
96
- system_prompt: Optional[str] = None
97
- additional_instructions: Optional[Union[str, Sequence[str]]] = None
98
- permission_checker: Optional[PermissionChecker] = None
99
- cwd: Optional[Union[str, Path]] = None
100
-
101
- def build_tools(self) -> List[Tool[Any, Any]]:
102
- """Create the tool set with allow/deny filters applied."""
103
- base_tools = list(self.tools) if self.tools is not None else get_default_tools()
104
- allowed = set(self.allowed_tools) if self.allowed_tools is not None else None
105
- disallowed = set(self.disallowed_tools or [])
106
-
107
- filtered: List[Tool[Any, Any]] = []
108
- for tool in base_tools:
109
- name = getattr(tool, "name", tool.__class__.__name__)
110
- if allowed is not None and name not in allowed:
111
- continue
112
- if name in disallowed:
113
- continue
114
- filtered.append(tool)
115
-
116
- if allowed is not None and not filtered:
117
- raise ValueError("No tools remain after applying allowed_tools/disallowed_tools.")
118
-
119
- # The default Task tool captures the original base tools. If filters are
120
- # applied, recreate it so the subagent only sees the filtered set.
121
- if (self.allowed_tools or self.disallowed_tools) and self.tools is None:
122
- has_task = any(getattr(tool, "name", None) == "Task" for tool in filtered)
123
- if has_task:
124
- filtered_base = [tool for tool in filtered if getattr(tool, "name", None) != "Task"]
125
-
126
- def _filtered_base_provider() -> List[Tool[Any, Any]]:
127
- return filtered_base
128
-
129
- filtered = [
130
- (
131
- TaskTool(_filtered_base_provider)
132
- if getattr(tool, "name", None) == "Task"
133
- else tool
134
- )
135
- for tool in filtered
136
- ]
137
-
138
- return filtered
139
-
140
- def extra_instructions(self) -> List[str]:
141
- """Normalize additional instructions to a list."""
142
- if self.additional_instructions is None:
143
- return []
144
- if isinstance(self.additional_instructions, str):
145
- return [self.additional_instructions]
146
- return [text for text in self.additional_instructions if text]
147
-
148
-
149
- class RipperdocClient:
150
- """Persistent Ripperdoc session with conversation history."""
151
-
152
- def __init__(
153
- self,
154
- options: Optional[RipperdocOptions] = None,
155
- query_runner: Optional[QueryRunner] = None,
156
- ) -> None:
157
- self.options = options or RipperdocOptions()
158
- self._tools = self.options.build_tools()
159
- self._query_runner = query_runner or _core_query
160
-
161
- self._history: List[MessageType] = []
162
- self._queue: asyncio.Queue = asyncio.Queue()
163
- self._current_task: Optional[asyncio.Task] = None
164
- self._current_context: Optional[QueryContext] = None
165
- self._connected = False
166
- self._previous_cwd: Optional[Path] = None
167
- self._session_hook_contexts: List[str] = []
168
- self._session_id: Optional[str] = None
169
- self._session_start_time: Optional[float] = None
170
- self._session_end_sent: bool = False
171
-
172
- @property
173
- def tools(self) -> List[Tool[Any, Any]]:
174
- return self._tools
175
-
176
- @property
177
- def history(self) -> List[MessageType]:
178
- return list(self._history)
179
-
180
- async def __aenter__(self) -> "RipperdocClient":
181
- await self.connect()
182
- return self
183
-
184
- async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: # type: ignore[override]
185
- await self.disconnect()
186
-
187
- async def connect(self, prompt: Optional[str] = None) -> None:
188
- """Prepare the session and optionally send an initial prompt."""
189
- if not self._connected:
190
- if self.options.cwd is not None:
191
- self._previous_cwd = Path.cwd()
192
- os.chdir(_coerce_to_path(self.options.cwd))
193
- self._connected = True
194
- project_path = _coerce_to_path(self.options.cwd or Path.cwd())
195
- hook_manager.set_project_dir(project_path)
196
- self._session_id = self._session_id or str(uuid.uuid4())
197
- hook_manager.set_session_id(self._session_id)
198
- hook_manager.set_llm_callback(build_hook_llm_callback())
199
- try:
200
- result = await hook_manager.run_session_start_async("startup")
201
- self._session_hook_contexts = self._collect_hook_contexts(result)
202
- self._session_start_time = time.time()
203
- self._session_end_sent = False
204
- except (OSError, RuntimeError, ConnectionError, ValueError, TypeError) as exc:
205
- logger.warning(
206
- "[sdk] SessionStart hook failed: %s: %s",
207
- type(exc).__name__,
208
- exc,
209
- )
210
-
211
- if prompt:
212
- await self.query(prompt)
213
-
214
- async def disconnect(self) -> None:
215
- """Tear down the session and restore the working directory."""
216
- if self._current_context:
217
- self._current_context.abort_controller.set()
218
-
219
- if self._current_task and not self._current_task.done():
220
- self._current_task.cancel()
221
- try:
222
- await self._current_task
223
- except asyncio.CancelledError:
224
- pass
225
-
226
- if self._previous_cwd:
227
- os.chdir(self._previous_cwd)
228
- self._previous_cwd = None
229
-
230
- self._connected = False
231
- if not self._session_end_sent:
232
- duration = (
233
- max(time.time() - self._session_start_time, 0.0)
234
- if self._session_start_time is not None
235
- else None
236
- )
237
- try:
238
- await hook_manager.run_session_end_async(
239
- "other",
240
- duration_seconds=duration,
241
- message_count=len(self._history),
242
- )
243
- except (OSError, RuntimeError, ConnectionError, ValueError, TypeError) as exc:
244
- logger.warning(
245
- "[sdk] SessionEnd hook failed: %s: %s",
246
- type(exc).__name__,
247
- exc,
248
- )
249
- self._session_end_sent = True
250
- await shutdown_mcp_runtime()
251
-
252
- async def query(self, prompt: str) -> None:
253
- """Send a prompt and start streaming the response."""
254
- if self._current_task and not self._current_task.done():
255
- raise RuntimeError(
256
- "A query is already in progress; wait for it to finish or interrupt it."
257
- )
258
-
259
- if not self._connected:
260
- await self.connect()
261
-
262
- self._queue = asyncio.Queue()
263
-
264
- hook_result = await hook_manager.run_user_prompt_submit_async(prompt)
265
- if hook_result.should_block or not hook_result.should_continue:
266
- reason = (
267
- hook_result.block_reason
268
- or hook_result.stop_reason
269
- or "Prompt blocked by hook."
270
- )
271
- blocked_message = create_assistant_message(str(reason))
272
- self._history.append(blocked_message)
273
- await self._queue.put(blocked_message)
274
- await self._queue.put(_END_OF_STREAM)
275
- self._current_task = asyncio.create_task(asyncio.sleep(0))
276
- return
277
- hook_instructions = self._collect_hook_contexts(hook_result)
278
-
279
- user_message = create_user_message(prompt)
280
- history = list(self._history) + [user_message]
281
- self._history.append(user_message)
282
-
283
- system_prompt = await self._build_system_prompt(prompt, hook_instructions)
284
- context = dict(self.options.context)
285
-
286
- query_context = QueryContext(
287
- tools=self._tools,
288
- max_thinking_tokens=self.options.max_thinking_tokens,
289
- yolo_mode=self.options.yolo_mode,
290
- model=self.options.model,
291
- verbose=self.options.verbose,
292
- )
293
- self._current_context = query_context
294
-
295
- async def _runner() -> None:
296
- try:
297
- async for message in self._query_runner(
298
- history,
299
- system_prompt,
300
- context,
301
- query_context,
302
- self.options.permission_checker,
303
- ):
304
- if getattr(message, "type", None) in ("user", "assistant"):
305
- self._history.append(message) # type: ignore[arg-type]
306
- await self._queue.put(message)
307
- finally:
308
- await self._queue.put(_END_OF_STREAM)
309
-
310
- self._current_task = asyncio.create_task(_runner())
311
-
312
- async def receive_messages(self) -> AsyncIterator[MessageType]:
313
- """Yield messages for the active query."""
314
- if self._current_task is None:
315
- raise RuntimeError("No active query to receive messages from.")
316
-
317
- while True:
318
- message = await self._queue.get()
319
- if message is _END_OF_STREAM:
320
- break
321
- yield message # type: ignore[misc]
322
-
323
- async def receive_response(self) -> AsyncIterator[MessageType]:
324
- """Alias for receive_messages."""
325
- async for message in self.receive_messages():
326
- yield message
327
-
328
- async def interrupt(self) -> None:
329
- """Request cancellation of the active query."""
330
- if self._current_context:
331
- self._current_context.abort_controller.set()
332
-
333
- if self._current_task and not self._current_task.done():
334
- self._current_task.cancel()
335
- try:
336
- await self._current_task
337
- except asyncio.CancelledError:
338
- pass
339
-
340
- await self._queue.put(_END_OF_STREAM)
341
-
342
- async def _build_system_prompt(
343
- self, user_prompt: str, hook_instructions: Optional[List[str]] = None
344
- ) -> str:
345
- if self.options.system_prompt:
346
- return self.options.system_prompt
347
-
348
- instructions: List[str] = []
349
- project_path = _coerce_to_path(self.options.cwd or Path.cwd())
350
- skill_result = load_all_skills(project_path)
351
- for err in skill_result.errors:
352
- logger.warning(
353
- "[skills] Failed to load skill",
354
- extra={"path": str(err.path), "reason": err.reason},
355
- )
356
- skill_instructions = build_skill_summary(skill_result.skills)
357
- if skill_instructions:
358
- instructions.append(skill_instructions)
359
- instructions.extend(self.options.extra_instructions())
360
- memory = build_memory_instructions()
361
- if memory:
362
- instructions.append(memory)
363
- if self._session_hook_contexts:
364
- instructions.extend(self._session_hook_contexts)
365
- if hook_instructions:
366
- instructions.extend([text for text in hook_instructions if text])
367
-
368
- dynamic_tools = await load_dynamic_mcp_tools_async(project_path)
369
- if dynamic_tools:
370
- self._tools = merge_tools_with_dynamic(self._tools, dynamic_tools)
371
-
372
- servers = await load_mcp_servers_async(project_path)
373
- mcp_instructions = format_mcp_instructions(servers)
374
-
375
- return build_system_prompt(
376
- self._tools,
377
- user_prompt,
378
- dict(self.options.context),
379
- instructions or None,
380
- mcp_instructions=mcp_instructions,
381
- )
382
-
383
- def _collect_hook_contexts(self, hook_result: Any) -> List[str]:
384
- contexts: List[str] = []
385
- system_message = getattr(hook_result, "system_message", None)
386
- additional_context = getattr(hook_result, "additional_context", None)
387
- if system_message:
388
- contexts.append(str(system_message))
389
- if additional_context:
390
- contexts.append(str(additional_context))
391
- return contexts
392
-
393
-
394
- async def query(
395
- prompt: str,
396
- options: Optional[RipperdocOptions] = None,
397
- query_runner: Optional[QueryRunner] = None,
398
- ) -> AsyncIterator[MessageType]:
399
- """One-shot helper: run a prompt in a fresh session."""
400
- client = RipperdocClient(options=options, query_runner=query_runner)
401
- await client.connect()
402
- await client.query(prompt)
403
-
404
- try:
405
- async for message in client.receive_messages():
406
- yield message
407
- finally:
408
- await client.disconnect()
@@ -1,129 +0,0 @@
1
- ripperdoc/__init__.py,sha256=oGsHv3v5Rn-xgN3ZJ5m1ExqzAZleylUN7J1e5kmk5bA,67
2
- ripperdoc/__main__.py,sha256=1Avq2MceBfwUlNsfasC8n4dqVL_V56Bl3DRsnY4_Nxk,370
3
- ripperdoc/cli/__init__.py,sha256=03wf6gXBcEgXJrDJS-W_5BEG_DdJ_ep7CxQFPML-73g,35
4
- ripperdoc/cli/cli.py,sha256=BHZTyn5AfVOUE5-dmj6hEdkXgMDJ0YB65wYDY3eAlBM,19207
5
- ripperdoc/cli/commands/__init__.py,sha256=Z8REiBCaBjGT8Ce4gD3h5MH3YyhTM9mVqhaaU85E7WQ,4718
6
- ripperdoc/cli/commands/agents_cmd.py,sha256=AlkuSxdIsAC53BhGz5kztfgNmjoaOHs4kHrVwEzI22I,15763
7
- ripperdoc/cli/commands/base.py,sha256=4KUjxCM04MwbSMUKVNEBph_jeAKPI8b5MHsUFoz7l5g,386
8
- ripperdoc/cli/commands/clear_cmd.py,sha256=iyOLWtgYwJKNjG-el2mwFRA3VWGIXrNNLl32Xdwpq9o,584
9
- ripperdoc/cli/commands/compact_cmd.py,sha256=uR_nB1OX7cUL1TOJoefwdO31Qfyjd0nZSSttErqUxbA,473
10
- ripperdoc/cli/commands/config_cmd.py,sha256=QcFYOOmNFSHmw6K2vY_wfKrYXi8PSzz6koJFREJoF_c,884
11
- ripperdoc/cli/commands/context_cmd.py,sha256=tM8o2ZfX-axFYaFLsWOTSER_Yevk3ANr2numHfuh2UE,5232
12
- ripperdoc/cli/commands/cost_cmd.py,sha256=yD9LSqgxVvYNTDPnEHxugjyLWcmbtH5dXim7DIW9zXc,2822
13
- ripperdoc/cli/commands/doctor_cmd.py,sha256=K3bea4DHb34twx_lr_iyPpxdTGvmx4OewGGfKpXSM-Y,7170
14
- ripperdoc/cli/commands/exit_cmd.py,sha256=lEGLMVozoOM2Ea_Yw-sbaIvAbfb_Nx3UTpC49ga-MZ8,376
15
- ripperdoc/cli/commands/help_cmd.py,sha256=jyK6U2bsGEIwFpu08slVHKfxRyS3oblnRXdqSgU_W4w,978
16
- ripperdoc/cli/commands/hooks_cmd.py,sha256=-ixQhKb1CX2c7_2zDdAqXV9ThnMf31UH3a9UBERD9mw,21026
17
- ripperdoc/cli/commands/mcp_cmd.py,sha256=ZCnswx0TIiaiUUsIX7NpHaLZLZtvlUhBnN12s_ZtPCA,2424
18
- ripperdoc/cli/commands/memory_cmd.py,sha256=gDvRr_-U1gMrOdC3OvujYLL5_CUgyZpwaJdytRP5CBM,6549
19
- ripperdoc/cli/commands/models_cmd.py,sha256=Jjj7iw4p5ydBZ8RBl4ozdgBozlKWFyXwF7FWzSKjHOo,16373
20
- ripperdoc/cli/commands/permissions_cmd.py,sha256=aIMIvmt78nB2Q-Qa5ojqClUOsPRcoyeDIuhOJX8xg2k,11559
21
- ripperdoc/cli/commands/resume_cmd.py,sha256=zcZ-pimjn-XPm5s3dw-SCkw1-ALVFlFUzT1zmkTH9Cg,4268
22
- ripperdoc/cli/commands/stats_cmd.py,sha256=W0KKp4qTyo9olDS_AiJi3tri7m0W37pjX16Nd4g0p0c,7855
23
- ripperdoc/cli/commands/status_cmd.py,sha256=Sqm8-5Q-3gui2relPHA99z2R50ft1mkf2QnqvKlDtVM,5535
24
- ripperdoc/cli/commands/tasks_cmd.py,sha256=M-LuowiRt4xSzvIv_m5-h9KTi5o3KvdV9k2VQBqyuYM,8867
25
- ripperdoc/cli/commands/todos_cmd.py,sha256=7Q0B1NVqGtB3R29ndbn4m0VQQm-YQ7d4Wlk7vJ7dLQI,1848
26
- ripperdoc/cli/commands/tools_cmd.py,sha256=3cMi0vN4mAUhpKqJtRgNvZfcKzRPaMs_pkYYXlyvSSU,384
27
- ripperdoc/cli/ui/__init__.py,sha256=TxSzTYdITlrYmYVfins_w_jzPqqWRpqky5u1ikwvmtM,43
28
- ripperdoc/cli/ui/context_display.py,sha256=3ezdtHVwltkPQ5etYwfqUh-fjnpPu8B3P81UzrdHxZs,10020
29
- ripperdoc/cli/ui/file_mention_completer.py,sha256=PYfcauxhU4NkLsNP-fGeBhZQFzUeKoDC5FO4S4pC1LU,11650
30
- ripperdoc/cli/ui/helpers.py,sha256=DmgMMouyQdesjQ5RsErwsRCKVdWiDJnpqJjv90a3neE,2545
31
- ripperdoc/cli/ui/interrupt_handler.py,sha256=mZAZhBXID6_NR03K8fSjPDDjtTScy182VFBbVHamcoM,5914
32
- ripperdoc/cli/ui/message_display.py,sha256=W9VlRCpMykkamxphej5LHgmU-P4YaBfm9HPdIKlFKpg,10573
33
- ripperdoc/cli/ui/panels.py,sha256=A0xX0Lxk7WTSz5y8sJDAYnpFFAX5WWql0199teuTipA,1929
34
- ripperdoc/cli/ui/provider_options.py,sha256=Ic30K1ev8w2oEcha4IjDYSoxw1tyCVB4hLoQpS_Y_5E,8369
35
- ripperdoc/cli/ui/rich_ui.py,sha256=PHEl0epQyeOGc9qomjjUsSmfrXPHPEroGDgLnljB5Tw,60789
36
- ripperdoc/cli/ui/spinner.py,sha256=4T47qbT_YZlFkzWMGew_NP7fg_xf81AkWbWo8gJskZM,2663
37
- ripperdoc/cli/ui/thinking_spinner.py,sha256=3zmxj3vd-1njdiHF2Afsm7RGiRl7645AEc7fTLKgAbU,2805
38
- ripperdoc/cli/ui/tool_renderers.py,sha256=tWXFyHOHz-kryoidra5RL-h9K8nbjrTBZ9o_thIgRho,11220
39
- ripperdoc/cli/ui/wizard.py,sha256=vc_uGuuf6F73bDB4DSgfTki8kVzwMywQ_u9YyGPuWPY,6972
40
- ripperdoc/core/__init__.py,sha256=UemJCA-Y8df1466AX-YbRFj071zKajmqO1mi40YVW2g,40
41
- ripperdoc/core/agents.py,sha256=t7Bc5BqUlSp8Xzqe2lMGhM9oupEJIsUBNsUaJGkwt9Y,20343
42
- ripperdoc/core/commands.py,sha256=NXCkljYbAP4dDoRy-_3semFNWxG4YAk9q82u8FTKH60,835
43
- ripperdoc/core/config.py,sha256=hjC6c02HGSzwJ0pEu2Gp0w0X_6yiZ_UwxFG2H-u1FC4,21947
44
- ripperdoc/core/custom_commands.py,sha256=2BMYiBq15FDjl3aOa3styN6nARyfU7xFAb4ly2Vsp-w,14254
45
- ripperdoc/core/default_tools.py,sha256=TWNo537SqI4ViWqINpbR-SOiBe40Chl2l0A1ddaUVqY,5249
46
- ripperdoc/core/permissions.py,sha256=v8pFdLw3fYr73ynW2pkANXH34M6Djf3iDcM4KfLOS7I,13901
47
- ripperdoc/core/query.py,sha256=WXAgwQbozeeRi7kZGGQYoZc1peBWW3kAQpzGoJqBoe0,50298
48
- ripperdoc/core/query_utils.py,sha256=xF9_vcqVSKANd9gwbk7aIG0t4a_f6R0m78-p3j5vkyI,24923
49
- ripperdoc/core/skills.py,sha256=cco9vpQxg_5HCKp64k6TtN01NaWbvWef2A9olbpTZaU,10366
50
- ripperdoc/core/system_prompt.py,sha256=d3GNCsJ_mdIojpWg1Wc0gRDC_gzRDI5G4tQN_gxhRdo,26752
51
- ripperdoc/core/tool.py,sha256=m9O8MvzCS_8q1QvwO7-ZlYepyly7egjHjMIzgmz6eeA,8124
52
- ripperdoc/core/hooks/__init__.py,sha256=xw7VJQu1ZB0ENHVqL5xtruBnP3d0FNgrBH6NTL2xYgg,2735
53
- ripperdoc/core/hooks/config.py,sha256=wE_eMHDZu9-yHGyJ45ySL1_l2yx7B8i4jiThs78W6Zc,10085
54
- ripperdoc/core/hooks/events.py,sha256=UGBDdec52abi2e6ox8ncsLeDo3t9ZJesymKaPwxNDAE,18403
55
- ripperdoc/core/hooks/executor.py,sha256=3MdiCdc4Bn2xHcTlRT72BajrgAZ5roKck0NBnO4Re0c,16781
56
- ripperdoc/core/hooks/integration.py,sha256=IzkOSpaMjC397zKdKO1jTR0uyzOet-eCwPLuXwTYOts,11082
57
- ripperdoc/core/hooks/llm_callback.py,sha256=umUB0iG9BcHv8PQvBflrM-LIkqxoc9e1pl5OfkyXi80,1734
58
- ripperdoc/core/hooks/manager.py,sha256=11CVV3GHbVETNCw5hYG_Fn-CgSHP-PncBdmuqlvKso8,24607
59
- ripperdoc/core/providers/__init__.py,sha256=yevsHF0AUI4b6Wiq_401NXewJ3dqe8LUUtQm0TLPPNQ,1911
60
- ripperdoc/core/providers/anthropic.py,sha256=B967szN1Thc0K1Iv8TvKWcVKktCevBmShftupVag8pk,28551
61
- ripperdoc/core/providers/base.py,sha256=HNOa3_XWszu6DbI8BYixxV0hnZb9qZ_FU4uinFVRHjU,9271
62
- ripperdoc/core/providers/gemini.py,sha256=Fs-dShsmIVBFfz-jg4fBjvQyrxVnZ5yx4ALcES-l5Sg,27089
63
- ripperdoc/core/providers/openai.py,sha256=DukxAXZJUVIZPDEuFwG748y3iucSIJnPQUqGjX_qUgQ,23675
64
- ripperdoc/sdk/__init__.py,sha256=aDSgI4lcCDs9cV3bxNmEEV3SuYx2aCd4VnUjs6H-R7E,213
65
- ripperdoc/sdk/client.py,sha256=ioDp1xwTUPmdTzBemKjZroO9bu6RC8geJk5Io9AkkHE,14785
66
- ripperdoc/tools/__init__.py,sha256=RBFz0DDnztDXMqv_zRxFHVY-ez2HYcncx8zh_y-BX6w,42
67
- ripperdoc/tools/ask_user_question_tool.py,sha256=ZWg5xAdeaRoR98KvvPuKmJH4L2dgzH87VXM4dxKcqBE,15478
68
- ripperdoc/tools/background_shell.py,sha256=qrLxxwK85KsZTrQpw_bdAfz2EE4tSdut8u0xXbEXXQg,19525
69
- ripperdoc/tools/bash_output_tool.py,sha256=cC5dDmKYmkOTsLCXCcTYgc0loVWtmRobPn0C-I6qO-o,3379
70
- ripperdoc/tools/bash_tool.py,sha256=jKPx_dTcN7npULsNCDfP50xsgxus4yDo88JKnOvEbtU,44644
71
- ripperdoc/tools/dynamic_mcp_tool.py,sha256=kxxWhp6pP9N8fK3ubu5fHQYQv7aSwxcnaa3C9ZsBbOU,15879
72
- ripperdoc/tools/enter_plan_mode_tool.py,sha256=LNQv43uWkohiQTYQdsrKbpAfQSJNE_FJ9Y6AM_ckjng,7976
73
- ripperdoc/tools/exit_plan_mode_tool.py,sha256=XxhEih5EUrcscvkRA9lel54XoVIhl_cCL2xcvMJjtVA,5756
74
- ripperdoc/tools/file_edit_tool.py,sha256=lk54kJo7KbwPMRLwcLnLpptt7gx3Y2LXj-r5bKnD1pQ,19172
75
- ripperdoc/tools/file_read_tool.py,sha256=jB4g0jF2Bl5WCzZNrLLTfzM_FmLDGTxK2q3eetrBkhw,8553
76
- ripperdoc/tools/file_write_tool.py,sha256=J6SCapkBGN0Wotfb8ce0sKhAtyC7SloKNSDqrBlkdcU,7040
77
- ripperdoc/tools/glob_tool.py,sha256=eZG4fzahjJsSM8NdmTiVl5nBfDQK7egPg6P7cqOM_1Y,5948
78
- ripperdoc/tools/grep_tool.py,sha256=ng2vJjMnkhY9ZukKrKTYLAHI0ZqzmooEuaNe6gdocr0,14141
79
- ripperdoc/tools/kill_bash_tool.py,sha256=_jwnJVCPe8uXZTJd7myh4hWCD-eBuB2XDaYwRCmNSsI,4625
80
- ripperdoc/tools/ls_tool.py,sha256=JWQucgNOLjrGZwM7imu3GWe5YFwXxdXGoaBr50wDZCQ,15367
81
- ripperdoc/tools/lsp_tool.py,sha256=dW7i0b0ziPy0W-Hnaj-T96EKovzx9guGZDoZY4dEeD0,21363
82
- ripperdoc/tools/mcp_tools.py,sha256=BVz8MJhijNnHq1J2LLkZS89wp_pwwnOytH1HYcfkFqQ,23085
83
- ripperdoc/tools/multi_edit_tool.py,sha256=Kq8kQ-j-kUgJWpx2OMSUjbjiFoiSaBLaYdg0avoZM0k,17607
84
- ripperdoc/tools/notebook_edit_tool.py,sha256=Y2XkQB4WDbSWeCsO3Ybnsrbcxki99aOv3o23jZ5D1Hw,14445
85
- ripperdoc/tools/skill_tool.py,sha256=TIUiUzL69mrKXFTais2YOssAwWnS5dwfK2_JFMG4xsY,7701
86
- ripperdoc/tools/task_tool.py,sha256=buQrpTeK-za9QBxePwa1tnZDTdKKRSwW2tyL7y4hCG8,32492
87
- ripperdoc/tools/todo_tool.py,sha256=eIwF-s1117DrXJ4yXUMwkNs1gfKYNF2OlWzkJAXDzmk,20001
88
- ripperdoc/tools/tool_search_tool.py,sha256=AeY-tFtWr2IAHTCEnG9kvsRRBqrBd-PJ96oUnFKa3Vs,13890
89
- ripperdoc/utils/__init__.py,sha256=gdso60znB2hsYZ_YZBKVcuOY3QVfoqD2wHQ4pvr5lSw,37
90
- ripperdoc/utils/bash_constants.py,sha256=KNn8bzB6nVU5jid9jvjiH4FAu8pP3DZONJ-OknJypAQ,1641
91
- ripperdoc/utils/bash_output_utils.py,sha256=3Cf5wKJzRbUesmCNy5HtXIBtv0Z2BxklTfFHJ9q1T3w,1210
92
- ripperdoc/utils/coerce.py,sha256=KOPb4KR4p32nwHWG_6GsGHeVZunJyYc2YhC5DLmEZO8,1015
93
- ripperdoc/utils/context_length_errors.py,sha256=oyDVr_ME_6j97TLwVZ8bDMb6ISGQx6wEHrY7ckc0GuA,7714
94
- ripperdoc/utils/conversation_compaction.py,sha256=mbOYwQXXsAqiJ7mBQ25195bXtH3tb5GSU0HUNsvnl1g,18345
95
- ripperdoc/utils/exit_code_handlers.py,sha256=QtO1iDxVAb8Xp03D6_QixPoJC-RQlcp3ssIo_rm4two,7973
96
- ripperdoc/utils/file_watch.py,sha256=8uOPZNqiJffEPRnbBDJ-JwyVIMEPletBZIHfEHOmDuc,11653
97
- ripperdoc/utils/git_utils.py,sha256=Hq-Zx-KPyX4lp_i8ozhic15LyYdX_IfCRm-EyoFu59A,9047
98
- ripperdoc/utils/json_utils.py,sha256=Bx1pHHu5r7GtvCqFHM3K9EoknFAtYOaCqTn9RN-5qBA,757
99
- ripperdoc/utils/log.py,sha256=mieFMPxiv-M87bB-dgiY8D5WMxQbjVKJdsrW8QvCui8,6138
100
- ripperdoc/utils/lsp.py,sha256=L0y9laUbj4gxR1rHgFOcloVGsH1QTPbv7eAmPsr2V64,26712
101
- ripperdoc/utils/mcp.py,sha256=92PMrGOzAWoqKGIiWBX2nNO-wkyQ-lgchmdlQ8Oo3zs,19548
102
- ripperdoc/utils/memory.py,sha256=J_kucw1BBnHQ1qG2_ZzdNysKvS1lrpuMtB5wxJDmXZU,8033
103
- ripperdoc/utils/message_compaction.py,sha256=FwYxjWc0B7IlzT1VPEyfhwEl8rXbDi37utbEbS5qGWw,22577
104
- ripperdoc/utils/message_formatting.py,sha256=0d0hDAUy-9G2Kxl16z63sPtTGAKLqtEZAhCeQCtmHBc,7828
105
- ripperdoc/utils/messages.py,sha256=RP5jvZejiMCPhq6hyuyq1igBtYKRf658ZhbtGacTFfU,24340
106
- ripperdoc/utils/output_utils.py,sha256=R3wqFh9Dko_GK00Exx7XI0DnnldRWMsxZypYX5y6SJo,7448
107
- ripperdoc/utils/path_ignore.py,sha256=5VOk075Ef9Wz9LhAWjFQuXJfnypxTD0w9cZVoTGko2M,17882
108
- ripperdoc/utils/path_utils.py,sha256=C45Q3OeXnj-0FVEtvf_tdG5922XB6HthUzlUCvfc17Y,1626
109
- ripperdoc/utils/prompt.py,sha256=zICNEsA_OtKx8t3zo9tHLXXu6G5K8rPO3jFLKz4j5tg,560
110
- ripperdoc/utils/safe_get_cwd.py,sha256=lYxFJAN7lomoLwTAfMZtyOueotuvhC8TN84NtrPKj1E,827
111
- ripperdoc/utils/sandbox_utils.py,sha256=G91P8dw2VFcCiCpjXZ4LvzbAPiO8REqMhw39eI5Z4dU,1123
112
- ripperdoc/utils/session_heatmap.py,sha256=5zEJCfAo6eP1byuvBjho0ojbhXP-u63bTcIfvHgbgKY,8442
113
- ripperdoc/utils/session_history.py,sha256=67RKV6eiX16FYYRR851MBFjgezkyP95LnllNZazWwcU,9704
114
- ripperdoc/utils/session_stats.py,sha256=jWqkTGH5qplVW8aHOBy3JCWiKl70SvXroyr_fT7khb4,9100
115
- ripperdoc/utils/session_usage.py,sha256=p8_s46zDTzV1qzP4HR4PuZmLeJvSfq9mG_Y5rCnRYyA,3213
116
- ripperdoc/utils/shell_token_utils.py,sha256=SduoSU-RERJdM_7gBn0urr5UXtl4XOpPgydBd2fwzWg,2500
117
- ripperdoc/utils/shell_utils.py,sha256=t-neFPy_VhEmWZ79J7hh1ULBEdX116Rb9_pnXvir1Jw,5235
118
- ripperdoc/utils/todo.py,sha256=6siiBz3R5iC7arONolPVxwmM68K5o_N-qIdv2bFeTZo,7051
119
- ripperdoc/utils/token_estimation.py,sha256=qPQbeUwVlafEjzsXw6qMo0hd4Vjb1gCUMAPBouYUASI,1066
120
- ripperdoc/utils/permissions/__init__.py,sha256=33FfOaDLepxJSkp0RLvTdVu7qBXuEcnOoTHFbEtFOt0,653
121
- ripperdoc/utils/permissions/path_validation_utils.py,sha256=KOegjWaph8tXU7aqwQXRAxFEzrmRuPvdLb36J1QIPDQ,5772
122
- ripperdoc/utils/permissions/shell_command_validation.py,sha256=tEp0sJoijYdy0stLdYxekCxGlGhPTTd46lRrWaY7w-I,33280
123
- ripperdoc/utils/permissions/tool_permission_utils.py,sha256=auKylTR73EVmkeQ5TTRRhCDMGl-cEEt882Qt6Fa5VSI,14579
124
- ripperdoc-0.2.10.dist-info/licenses/LICENSE,sha256=bRv9UhBor6GhnQDj12RciDcRfu0R7sB-lqCy1sWF75c,9242
125
- ripperdoc-0.2.10.dist-info/METADATA,sha256=GQ1s_0BaysD_QxGX9konIIMUKFY6T4-WstY_2fbHdjw,7702
126
- ripperdoc-0.2.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
127
- ripperdoc-0.2.10.dist-info/entry_points.txt,sha256=79aohFxFPJmrQ3-Mhain04vb3EWpuc0EyzvDDUnwAu4,81
128
- ripperdoc-0.2.10.dist-info/top_level.txt,sha256=u8LbdTr1a-laHgCO0Utl_R3QGFUhLxWelCDnP2ZgpCU,10
129
- ripperdoc-0.2.10.dist-info/RECORD,,