ripperdoc 0.2.6__py3-none-any.whl → 0.2.8__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 (44) hide show
  1. ripperdoc/__init__.py +1 -1
  2. ripperdoc/cli/cli.py +5 -0
  3. ripperdoc/cli/commands/__init__.py +71 -6
  4. ripperdoc/cli/commands/clear_cmd.py +1 -0
  5. ripperdoc/cli/commands/exit_cmd.py +1 -1
  6. ripperdoc/cli/commands/help_cmd.py +11 -1
  7. ripperdoc/cli/commands/hooks_cmd.py +636 -0
  8. ripperdoc/cli/commands/permissions_cmd.py +36 -34
  9. ripperdoc/cli/commands/resume_cmd.py +71 -37
  10. ripperdoc/cli/ui/file_mention_completer.py +276 -0
  11. ripperdoc/cli/ui/helpers.py +100 -3
  12. ripperdoc/cli/ui/interrupt_handler.py +175 -0
  13. ripperdoc/cli/ui/message_display.py +249 -0
  14. ripperdoc/cli/ui/panels.py +63 -0
  15. ripperdoc/cli/ui/rich_ui.py +233 -648
  16. ripperdoc/cli/ui/tool_renderers.py +2 -2
  17. ripperdoc/core/agents.py +4 -4
  18. ripperdoc/core/custom_commands.py +411 -0
  19. ripperdoc/core/hooks/__init__.py +99 -0
  20. ripperdoc/core/hooks/config.py +303 -0
  21. ripperdoc/core/hooks/events.py +540 -0
  22. ripperdoc/core/hooks/executor.py +498 -0
  23. ripperdoc/core/hooks/integration.py +353 -0
  24. ripperdoc/core/hooks/manager.py +720 -0
  25. ripperdoc/core/providers/anthropic.py +476 -69
  26. ripperdoc/core/query.py +61 -4
  27. ripperdoc/core/query_utils.py +1 -1
  28. ripperdoc/core/tool.py +1 -1
  29. ripperdoc/tools/bash_tool.py +5 -5
  30. ripperdoc/tools/file_edit_tool.py +2 -2
  31. ripperdoc/tools/file_read_tool.py +2 -2
  32. ripperdoc/tools/multi_edit_tool.py +1 -1
  33. ripperdoc/utils/conversation_compaction.py +476 -0
  34. ripperdoc/utils/message_compaction.py +109 -154
  35. ripperdoc/utils/message_formatting.py +216 -0
  36. ripperdoc/utils/messages.py +31 -9
  37. ripperdoc/utils/path_ignore.py +3 -4
  38. ripperdoc/utils/session_history.py +19 -7
  39. {ripperdoc-0.2.6.dist-info → ripperdoc-0.2.8.dist-info}/METADATA +24 -3
  40. {ripperdoc-0.2.6.dist-info → ripperdoc-0.2.8.dist-info}/RECORD +44 -30
  41. {ripperdoc-0.2.6.dist-info → ripperdoc-0.2.8.dist-info}/WHEEL +0 -0
  42. {ripperdoc-0.2.6.dist-info → ripperdoc-0.2.8.dist-info}/entry_points.txt +0 -0
  43. {ripperdoc-0.2.6.dist-info → ripperdoc-0.2.8.dist-info}/licenses/LICENSE +0 -0
  44. {ripperdoc-0.2.6.dist-info → ripperdoc-0.2.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,216 @@
1
+ """Message content formatting utilities.
2
+
3
+ This module provides functions for converting message content to plain text,
4
+ with support for detailed tool information extraction. Used primarily for
5
+ conversation summarization and compaction.
6
+ """
7
+
8
+ from typing import Any, List, Union
9
+
10
+ from ripperdoc.utils.messages import UserMessage, AssistantMessage, ProgressMessage
11
+
12
+ ConversationMessage = Union[UserMessage, AssistantMessage, ProgressMessage]
13
+
14
+
15
+ def stringify_message_content(
16
+ content: Any, *, include_tool_details: bool = False
17
+ ) -> str:
18
+ """Convert message content to plain string.
19
+
20
+ Args:
21
+ content: The message content to stringify.
22
+ include_tool_details: If True, include tool input/output details
23
+ instead of just placeholders. Useful for summarization.
24
+
25
+ Returns:
26
+ Plain text representation of the message content.
27
+ """
28
+ if content is None:
29
+ return ""
30
+ if isinstance(content, str):
31
+ return content
32
+ if isinstance(content, list):
33
+ parts: List[str] = []
34
+ for block in content:
35
+ block_type = getattr(block, "type", None)
36
+ if block_type == "text":
37
+ text = getattr(block, "text", None)
38
+ if text:
39
+ parts.append(str(text))
40
+ elif block_type == "tool_use":
41
+ name = getattr(block, "name", "tool")
42
+ if include_tool_details:
43
+ tool_input = getattr(block, "input", None)
44
+ parts.append(format_tool_use_detail(name, tool_input))
45
+ else:
46
+ parts.append(f"[Called {name}]")
47
+ elif block_type == "tool_result":
48
+ if include_tool_details:
49
+ result_text = getattr(block, "text", None) or ""
50
+ is_error = getattr(block, "is_error", False)
51
+ parts.append(format_tool_result_detail(result_text, is_error))
52
+ else:
53
+ parts.append("[Tool result]")
54
+ return "\n".join(parts)
55
+ return str(content)
56
+
57
+
58
+ def format_tool_use_detail(name: str, tool_input: Any) -> str:
59
+ """Format tool_use block with input details for summarization.
60
+
61
+ Args:
62
+ name: The tool name.
63
+ tool_input: The tool input dictionary.
64
+
65
+ Returns:
66
+ Formatted string like "[Called Bash(command=ls -la)]"
67
+ """
68
+ if not tool_input:
69
+ return f"[Called {name}]"
70
+
71
+ summary_parts: List[str] = []
72
+ if isinstance(tool_input, dict):
73
+ # Common patterns for different tools
74
+ if name == "Bash":
75
+ cmd = tool_input.get("command", "")
76
+ if cmd:
77
+ cmd_preview = cmd[:200] + "..." if len(cmd) > 200 else cmd
78
+ summary_parts.append(f"command={cmd_preview}")
79
+ elif name in ("Read", "Write", "Edit", "MultiEdit"):
80
+ path = tool_input.get("file_path", "")
81
+ if path:
82
+ summary_parts.append(f"file={path}")
83
+ elif name in ("Glob", "Grep"):
84
+ pattern = tool_input.get("pattern", "")
85
+ if pattern:
86
+ summary_parts.append(f"pattern={pattern}")
87
+ elif name == "Task":
88
+ desc = tool_input.get("description", "")
89
+ subagent = tool_input.get("subagent_type", "")
90
+ if subagent:
91
+ summary_parts.append(f"subagent={subagent}")
92
+ if desc:
93
+ summary_parts.append(f"desc={desc}")
94
+ else:
95
+ # Generic: show first few key-value pairs
96
+ for key, value in list(tool_input.items())[:3]:
97
+ val_str = str(value)
98
+ if len(val_str) > 50:
99
+ val_str = val_str[:47] + "..."
100
+ summary_parts.append(f"{key}={val_str}")
101
+
102
+ if summary_parts:
103
+ return f"[Called {name}({', '.join(summary_parts)})]"
104
+ return f"[Called {name}]"
105
+
106
+
107
+ def format_tool_result_detail(result_text: str, is_error: bool = False) -> str:
108
+ """Format tool_result block with output details for summarization.
109
+
110
+ Args:
111
+ result_text: The tool result text.
112
+ is_error: Whether this is an error result.
113
+
114
+ Returns:
115
+ Formatted string like "[Tool result]: file contents..."
116
+ """
117
+ prefix = "[Tool error]" if is_error else "[Tool result]"
118
+ if not result_text:
119
+ return prefix
120
+
121
+ # Truncate very long results but keep enough for context
122
+ max_len = 500
123
+ if len(result_text) > max_len:
124
+ result_preview = result_text[:max_len] + f"... (truncated, {len(result_text)} chars total)"
125
+ else:
126
+ result_preview = result_text
127
+
128
+ return f"{prefix}: {result_preview}"
129
+
130
+
131
+ def format_reasoning_preview(reasoning: Any) -> str:
132
+ """Return a short preview of reasoning/thinking content.
133
+
134
+ Args:
135
+ reasoning: The reasoning content (string, list, or other).
136
+
137
+ Returns:
138
+ A short preview string (max ~80 chars with ellipsis).
139
+ """
140
+ if reasoning is None:
141
+ return ""
142
+ if isinstance(reasoning, str):
143
+ text = reasoning
144
+ elif isinstance(reasoning, list):
145
+ parts = []
146
+ for block in reasoning:
147
+ if isinstance(block, dict):
148
+ parts.append(block.get("thinking") or block.get("summary") or "")
149
+ elif hasattr(block, "thinking"):
150
+ parts.append(getattr(block, "thinking", "") or "")
151
+ text = "\n".join(p for p in parts if p)
152
+ else:
153
+ text = str(reasoning)
154
+ lines = text.strip().splitlines()
155
+ if not lines:
156
+ return ""
157
+ preview = lines[0][:80]
158
+ if len(lines) > 1 or len(lines[0]) > 80:
159
+ preview += "..."
160
+ return preview
161
+
162
+
163
+ def render_transcript(
164
+ messages: List[ConversationMessage], *, include_tool_details: bool = True
165
+ ) -> str:
166
+ """Render conversation messages into a plain-text transcript.
167
+
168
+ Args:
169
+ messages: List of conversation messages to render.
170
+ include_tool_details: If True (default), include tool input/output
171
+ details for better summarization context.
172
+
173
+ Returns:
174
+ Plain text transcript of the conversation.
175
+ """
176
+ lines: List[str] = []
177
+ for msg in messages:
178
+ msg_type = getattr(msg, "type", "")
179
+ if msg_type == "progress":
180
+ continue
181
+ role = "User" if msg_type == "user" else "Assistant"
182
+ content = getattr(getattr(msg, "message", None), "content", None)
183
+ text = stringify_message_content(content, include_tool_details=include_tool_details)
184
+ if text.strip():
185
+ lines.append(f"{role}: {text}")
186
+ return "\n\n".join(lines)
187
+
188
+
189
+ def extract_assistant_text(assistant_message: Any) -> str:
190
+ """Extract plain text from an assistant response object.
191
+
192
+ Args:
193
+ assistant_message: An AssistantMessage or similar object.
194
+
195
+ Returns:
196
+ Plain text content from the message.
197
+ """
198
+ # AssistantMessage has .message.content structure
199
+ message = getattr(assistant_message, "message", None)
200
+ if message is not None:
201
+ content = getattr(message, "content", None)
202
+ else:
203
+ # Fallback: maybe it's a raw object with .content directly
204
+ content = getattr(assistant_message, "content", None)
205
+
206
+ if isinstance(content, str):
207
+ return content
208
+ if isinstance(content, list):
209
+ parts: List[str] = []
210
+ for block in content:
211
+ if getattr(block, "type", None) == "text":
212
+ text = getattr(block, "text", None)
213
+ if text:
214
+ parts.append(str(text))
215
+ return "\n".join(parts)
216
+ return ""
@@ -381,20 +381,30 @@ def normalize_messages_for_api(
381
381
  # Precompute tool_result positions so we can drop dangling tool_calls that
382
382
  # lack a following tool response (which OpenAI rejects).
383
383
  tool_result_positions: Dict[str, int] = {}
384
+ # Precompute tool_use positions so we can drop dangling tool_results that
385
+ # lack a preceding tool_call (which OpenAI also rejects).
386
+ tool_use_positions: Dict[str, int] = {}
384
387
  skipped_tool_uses_no_result = 0
385
388
  skipped_tool_uses_no_id = 0
389
+ skipped_tool_results_no_call = 0
386
390
  if protocol == "openai":
387
391
  for idx, msg in enumerate(messages):
388
- if _msg_type(msg) != "user":
389
- continue
392
+ msg_type = _msg_type(msg)
390
393
  content = _msg_content(msg)
391
394
  if not isinstance(content, list):
392
395
  continue
393
- for block in content:
394
- if getattr(block, "type", None) == "tool_result":
395
- tool_id = getattr(block, "tool_use_id", None) or getattr(block, "id", None)
396
- if tool_id and tool_id not in tool_result_positions:
397
- tool_result_positions[tool_id] = idx
396
+ if msg_type == "user":
397
+ for block in content:
398
+ if getattr(block, "type", None) == "tool_result":
399
+ tool_id = getattr(block, "tool_use_id", None) or getattr(block, "id", None)
400
+ if tool_id and tool_id not in tool_result_positions:
401
+ tool_result_positions[tool_id] = idx
402
+ elif msg_type == "assistant":
403
+ for block in content:
404
+ if getattr(block, "type", None) == "tool_use":
405
+ tool_id = getattr(block, "id", None) or getattr(block, "tool_use_id", None)
406
+ if tool_id and tool_id not in tool_use_positions:
407
+ tool_use_positions[tool_id] = idx
398
408
 
399
409
  for msg_index, msg in enumerate(messages):
400
410
  msg_type = _msg_type(msg)
@@ -412,8 +422,18 @@ def normalize_messages_for_api(
412
422
  # Map each block to an OpenAI-style message
413
423
  openai_msgs: List[Dict[str, Any]] = []
414
424
  for block in user_content:
415
- if getattr(block, "type", None) == "tool_result":
425
+ block_type = getattr(block, "type", None)
426
+ if block_type == "tool_result":
416
427
  tool_results_seen += 1
428
+ # Skip tool_result blocks that lack a preceding tool_use
429
+ tool_id = getattr(block, "tool_use_id", None) or getattr(block, "id", None)
430
+ if not tool_id:
431
+ skipped_tool_results_no_call += 1
432
+ continue
433
+ call_pos = tool_use_positions.get(tool_id)
434
+ if call_pos is None or call_pos >= msg_index:
435
+ skipped_tool_results_no_call += 1
436
+ continue
417
437
  mapped = _content_block_to_openai(block)
418
438
  if mapped:
419
439
  openai_msgs.append(mapped)
@@ -498,8 +518,10 @@ def normalize_messages_for_api(
498
518
  f"input_msgs={len(messages)} normalized={len(normalized)} "
499
519
  f"tool_results_seen={tool_results_seen} tool_uses_seen={tool_uses_seen} "
500
520
  f"tool_result_positions={len(tool_result_positions)} "
521
+ f"tool_use_positions={len(tool_use_positions)} "
501
522
  f"skipped_tool_uses_no_result={skipped_tool_uses_no_result} "
502
- f"skipped_tool_uses_no_id={skipped_tool_uses_no_id}"
523
+ f"skipped_tool_uses_no_id={skipped_tool_uses_no_id} "
524
+ f"skipped_tool_results_no_call={skipped_tool_results_no_call}"
503
525
  )
504
526
  return normalized
505
527
 
@@ -11,8 +11,7 @@ from __future__ import annotations
11
11
 
12
12
  import re
13
13
  from pathlib import Path
14
- from typing import Dict, List, Optional, Set, Tuple
15
- from functools import lru_cache
14
+ from typing import Any, Dict, List, Optional, Set, Tuple
16
15
 
17
16
  from ripperdoc.utils.git_utils import (
18
17
  get_git_root,
@@ -361,7 +360,7 @@ class IgnoreFilter:
361
360
 
362
361
  return result
363
362
 
364
- def test(self, path: str) -> Dict[str, any]:
363
+ def test(self, path: str) -> Dict[str, Any]:
365
364
  """Check if a path should be ignored and return details.
366
365
 
367
366
  Returns:
@@ -369,7 +368,7 @@ class IgnoreFilter:
369
368
  """
370
369
  path = path.replace("\\", "/").strip("/")
371
370
 
372
- result = {"ignored": False, "rule": None}
371
+ result: Dict[str, Any] = {"ignored": False, "rule": None}
373
372
 
374
373
  for pattern, is_negation in self._patterns:
375
374
  if pattern.search(path):
@@ -29,7 +29,7 @@ class SessionSummary:
29
29
  message_count: int
30
30
  created_at: datetime
31
31
  updated_at: datetime
32
- first_prompt: str
32
+ last_prompt: str
33
33
 
34
34
 
35
35
  def _sessions_root() -> Path:
@@ -132,7 +132,9 @@ class SessionHistory:
132
132
  }
133
133
  try:
134
134
  with self.path.open("a", encoding="utf-8") as fh:
135
- json.dump(entry, fh)
135
+ # ensure_ascii=False 避免中文等字符被转义为 \uXXXX
136
+ # separators 去掉多余空格,减小体积
137
+ json.dump(entry, fh, ensure_ascii=False, separators=(",", ":"))
136
138
  fh.write("\n")
137
139
  if isinstance(msg_uuid, str):
138
140
  self._seen_ids.add(msg_uuid)
@@ -198,10 +200,20 @@ def list_session_summaries(project_path: Path) -> List[SessionSummary]:
198
200
  if isinstance(updated_raw, str)
199
201
  else datetime.fromtimestamp(jsonl_path.stat().st_mtime)
200
202
  )
201
- first_prompt = ""
202
- for payload in conversation_payloads:
203
- first_prompt = _extract_prompt(payload)
204
- if first_prompt:
203
+ # Extract last user prompt with more than 10 characters
204
+ # If not found, fall back to any user prompt
205
+ last_prompt = ""
206
+ fallback_prompt = ""
207
+ for payload in reversed(conversation_payloads):
208
+ if payload.get("type") != "user":
209
+ continue
210
+ prompt = _extract_prompt(payload)
211
+ if not prompt:
212
+ continue
213
+ if not fallback_prompt:
214
+ fallback_prompt = prompt
215
+ if len(prompt) > 10:
216
+ last_prompt = prompt
205
217
  break
206
218
  summaries.append(
207
219
  SessionSummary(
@@ -210,7 +222,7 @@ def list_session_summaries(project_path: Path) -> List[SessionSummary]:
210
222
  message_count=len(conversation_payloads),
211
223
  created_at=created_at,
212
224
  updated_at=updated_at,
213
- first_prompt=first_prompt or "(no prompt)",
225
+ last_prompt=last_prompt or fallback_prompt or "(no prompt)",
214
226
  )
215
227
  )
216
228
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripperdoc
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: AI-powered terminal assistant for coding tasks
5
5
  Author: Ripperdoc Team
6
6
  License: Apache-2.0
@@ -35,9 +35,28 @@ Requires-Dist: black>=23.0.0; extra == "dev"
35
35
  Requires-Dist: ruff>=0.1.0; extra == "dev"
36
36
  Dynamic: license-file
37
37
 
38
- # Ripperdoc - AI-Powered Terminal Assistant
38
+ <div align="center">
39
39
 
40
- Ripperdoc is an AI-powered terminal assistant for coding tasks, providing an interactive interface for AI-assisted development, file management, and command execution.
40
+ # Ripperdoc
41
+
42
+ _an open-source, extensible AI coding agent that runs in your terminal_
43
+
44
+ <p align="center">
45
+ <a href="https://opensource.org/licenses/Apache-2.0">
46
+ <img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg">
47
+ </a>
48
+ <a href="https://www.python.org/downloads/">
49
+ <img src="https://img.shields.io/badge/python-3.10+-blue.svg">
50
+ </a>
51
+ <a href="https://github.com/quantmew/ripperdoc/stargazers">
52
+ <img src="https://img.shields.io/github/stars/quantmew/ripperdoc.svg" alt="GitHub stars">
53
+ </a>
54
+ </p>
55
+ </div>
56
+
57
+ Ripperdoc is your on-machine AI coding assistant, similar to [Claude Code](https://claude.com/claude-code), [Codex](https://github.com/openai/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Aider](https://github.com/paul-gauthier/aider), and [Goose](https://github.com/block/goose). It can write code, refactor projects, execute shell commands, and manage files - all through natural language conversations in your terminal.
58
+
59
+ Designed for maximum flexibility, Ripperdoc works with **any LLM** (Anthropic Claude, OpenAI, DeepSeek, local models via OpenAI-compatible APIs), supports **custom hooks** to intercept and control tool execution, and offers both an interactive CLI and a **Python SDK** for headless automation.
41
60
 
42
61
  [中文文档](README_CN.md) | [Contributing](CONTRIBUTING.md) | [Documentation](docs/)
43
62
 
@@ -60,6 +79,8 @@ Ripperdoc is an AI-powered terminal assistant for coding tasks, providing an int
60
79
  - **MCP Server Support** - Integration with Model Context Protocol servers
61
80
  - **Session Management** - Persistent session history and usage tracking
62
81
  - **Jupyter Notebook Support** - Edit .ipynb files directly
82
+ - **Hooks System** - Execute custom scripts at lifecycle events with decision control
83
+ - **Custom Commands** - Define reusable slash commands with parameter substitution
63
84
 
64
85
  ## Installation
65
86
 
@@ -1,47 +1,59 @@
1
- ripperdoc/__init__.py,sha256=9pRQSHIr5pIFaG1rbNYM4nNgdBNpy2bj8TXd_M7GItI,66
1
+ ripperdoc/__init__.py,sha256=lrY1tU8qp_EIUO-H5GAvKzGaBf8Z5xcFbY1i2_NBgjE,66
2
2
  ripperdoc/__main__.py,sha256=1Avq2MceBfwUlNsfasC8n4dqVL_V56Bl3DRsnY4_Nxk,370
3
3
  ripperdoc/cli/__init__.py,sha256=03wf6gXBcEgXJrDJS-W_5BEG_DdJ_ep7CxQFPML-73g,35
4
- ripperdoc/cli/cli.py,sha256=ECbi5Rm8prKkNtiv4P191ftSMEMSwYIG2LKC5IJXyIk,14140
5
- ripperdoc/cli/commands/__init__.py,sha256=rSB7hmuMRBfqYCxuUMsdb3-8mloKQyfOTinXU3wm3Uo,2414
4
+ ripperdoc/cli/cli.py,sha256=5H_kIa-kPVyF_KyQA2QROLr6JrHg99RORyHKPrS82qk,14316
5
+ ripperdoc/cli/commands/__init__.py,sha256=Xs69l9O8VglF1z1JkxbmAy_9ylrfXZxFW9G5GiPQBZk,4676
6
6
  ripperdoc/cli/commands/agents_cmd.py,sha256=YDE9oIXPmsPyvkHhq95aXxnneN3PqZ1ZOtcn26cXeO8,10438
7
7
  ripperdoc/cli/commands/base.py,sha256=4KUjxCM04MwbSMUKVNEBph_jeAKPI8b5MHsUFoz7l5g,386
8
- ripperdoc/cli/commands/clear_cmd.py,sha256=zSYT0Nn_htZzLWTTQ4E5KWHfRg0Q5CYvRO4e--7thBY,345
8
+ ripperdoc/cli/commands/clear_cmd.py,sha256=FDZ0W34VxGyLhLiU4TzukHCyElqsnLwkCmfKJqLfFAQ,366
9
9
  ripperdoc/cli/commands/compact_cmd.py,sha256=uR_nB1OX7cUL1TOJoefwdO31Qfyjd0nZSSttErqUxbA,473
10
10
  ripperdoc/cli/commands/config_cmd.py,sha256=ebIQk7zUFv353liWfbBSSfPiOaaCR7rQsd_eTw7nsvY,884
11
11
  ripperdoc/cli/commands/context_cmd.py,sha256=6Yrz3_Oa2NwEsZo4tLK_PFFYP0Vq-amQCMBomSVFmBo,5220
12
12
  ripperdoc/cli/commands/cost_cmd.py,sha256=yD9LSqgxVvYNTDPnEHxugjyLWcmbtH5dXim7DIW9zXc,2822
13
13
  ripperdoc/cli/commands/doctor_cmd.py,sha256=q_PO1mnknRysVG7uopiqDWvkIIcvRX2i-JWgfKN-0gQ,7052
14
- ripperdoc/cli/commands/exit_cmd.py,sha256=ASOoLeXarbWK2vdC0223s98qmAMMRh1lcJPX0Qe5o7c,327
15
- ripperdoc/cli/commands/help_cmd.py,sha256=iz1vR-rmWsvvfzdebLiIWEWrcMZo5_Eb55_wLr4Ufno,508
14
+ ripperdoc/cli/commands/exit_cmd.py,sha256=B0CNKQos2eRC4LSjizLdKsFYzFfwRkrUur6Afu3Fh9M,334
15
+ ripperdoc/cli/commands/help_cmd.py,sha256=jyK6U2bsGEIwFpu08slVHKfxRyS3oblnRXdqSgU_W4w,978
16
+ ripperdoc/cli/commands/hooks_cmd.py,sha256=9kTMXl-7vR9Y63Dm8iieuRK5jYnnlsqWKG5NvDsWUxU,21228
16
17
  ripperdoc/cli/commands/mcp_cmd.py,sha256=ZCnswx0TIiaiUUsIX7NpHaLZLZtvlUhBnN12s_ZtPCA,2424
17
18
  ripperdoc/cli/commands/memory_cmd.py,sha256=gDvRr_-U1gMrOdC3OvujYLL5_CUgyZpwaJdytRP5CBM,6549
18
19
  ripperdoc/cli/commands/models_cmd.py,sha256=p6IeV_K9BjOahmtqmI2Gu7xsqRagVsIPYy7FEeuKQWQ,16135
19
- ripperdoc/cli/commands/permissions_cmd.py,sha256=3ERLLYTLUFW7jV7uo29i6SmLxrizpK4LXayLrPZbB1U,10827
20
- ripperdoc/cli/commands/resume_cmd.py,sha256=to-904VX5TVT60-YF92WTUKF-IBRf30UN4dZnLvHKME,2997
20
+ ripperdoc/cli/commands/permissions_cmd.py,sha256=k2n82VxlESxM7u5TUrUh85NM-n0JHjqnJxzeAaHpDL0,11325
21
+ ripperdoc/cli/commands/resume_cmd.py,sha256=pFuo3_S_6l3F8usQ9NfyJvc-nBliKW9ct8Rma8YXPlA,4121
21
22
  ripperdoc/cli/commands/status_cmd.py,sha256=yM_c_GgoAL7CMH_ucGSwUhlbHggxYuvCEb4AXtpN-8s,5534
22
23
  ripperdoc/cli/commands/tasks_cmd.py,sha256=QrRF9MKg6LIH9BQz5E39KKdrwMiI3HTvI-c14aM7BU0,8815
23
24
  ripperdoc/cli/commands/todos_cmd.py,sha256=7Q0B1NVqGtB3R29ndbn4m0VQQm-YQ7d4Wlk7vJ7dLQI,1848
24
25
  ripperdoc/cli/commands/tools_cmd.py,sha256=3cMi0vN4mAUhpKqJtRgNvZfcKzRPaMs_pkYYXlyvSSU,384
25
26
  ripperdoc/cli/ui/__init__.py,sha256=TxSzTYdITlrYmYVfins_w_jzPqqWRpqky5u1ikwvmtM,43
26
27
  ripperdoc/cli/ui/context_display.py,sha256=3ezdtHVwltkPQ5etYwfqUh-fjnpPu8B3P81UzrdHxZs,10020
27
- ripperdoc/cli/ui/helpers.py,sha256=TJCipP0neh-96ETQfGhusCJ4aWt5gLw1HZbI-3bWDpw,739
28
- ripperdoc/cli/ui/rich_ui.py,sha256=BXQAT1TAWd4p4uGiaNtYaV_qsrcbsoYIM1UcDd2KEvY,61380
28
+ ripperdoc/cli/ui/file_mention_completer.py,sha256=U6uZbhCamC-cJTVCbblcXvPqmkaevNgKotMC_ftssug,11648
29
+ ripperdoc/cli/ui/helpers.py,sha256=DmgMMouyQdesjQ5RsErwsRCKVdWiDJnpqJjv90a3neE,2545
30
+ ripperdoc/cli/ui/interrupt_handler.py,sha256=dg15njl4NsFQvwAtxKPETeubuYl5OwiV2VlPECW2aNI,5930
31
+ ripperdoc/cli/ui/message_display.py,sha256=FHvfBY9hvf-lroB6SDzHDMS6bxuh57BfK5iBIifs2Ks,10361
32
+ ripperdoc/cli/ui/panels.py,sha256=lzgg7kP8nzJKqGFjE-0UVbr9a1YZ0i2XlkUy6-LcLnk,1875
33
+ ripperdoc/cli/ui/rich_ui.py,sha256=_VOx1zdt_QV23cwwiV6P8FiAA_H-JLIbC6WqYBLgUwM,47066
29
34
  ripperdoc/cli/ui/spinner.py,sha256=XsPRwJ-70InLX9Qw50CEgSHn5oKA5PFIue8Un4edhUk,1449
30
35
  ripperdoc/cli/ui/thinking_spinner.py,sha256=9Et5EqPChfkmkiOO8w1OPs8t-sHaisgjn9A__kEYLyg,2824
31
- ripperdoc/cli/ui/tool_renderers.py,sha256=cG0aFa8alIYJryG9tV6a7arslMN-iN8bAF5Q2XNL5Dc,11195
36
+ ripperdoc/cli/ui/tool_renderers.py,sha256=gVuZM083Nys9KWYAFTdmr1vpJm7ardqNhyUZx7KkL6s,11170
32
37
  ripperdoc/core/__init__.py,sha256=UemJCA-Y8df1466AX-YbRFj071zKajmqO1mi40YVW2g,40
33
- ripperdoc/core/agents.py,sha256=2aOvGEdj-P7MVMPtcFT4mAFIWeaFLSd6aVTqXtNjew0,19930
38
+ ripperdoc/core/agents.py,sha256=3glBiVM8e9NruCQGYl4inQ6Lt7jWb7C4S44YQXRWWl8,19930
34
39
  ripperdoc/core/commands.py,sha256=NXCkljYbAP4dDoRy-_3semFNWxG4YAk9q82u8FTKH60,835
35
40
  ripperdoc/core/config.py,sha256=fuzXTSSpPFIkzgZJW-tOf18cNeemrot64ihO4cdM79g,20979
41
+ ripperdoc/core/custom_commands.py,sha256=2Voc1u6WSZ-nJYRvGKmUcpNvzLCotacgGupvXUv6RT8,14289
36
42
  ripperdoc/core/default_tools.py,sha256=fHmqIlPIE9qGwmgeYYw-QepKRoQLMhclnCv6Qahews0,3090
37
43
  ripperdoc/core/permissions.py,sha256=_WLWE7Kq-Z5j3zEDAPr8JqdT0fz2oFqNs18NL0qoeWQ,9768
38
- ripperdoc/core/query.py,sha256=wpeQEJGTNwqp5Vvu9KoF-y1sZ-TOx_-xGaNwcDJYhfc,38729
39
- ripperdoc/core/query_utils.py,sha256=PA2rmHBwT5dczF8YYfbrL3QWrfNxQOhCA57G1Z-XBXE,24776
44
+ ripperdoc/core/query.py,sha256=We15XbLl_rAdAgJ5NAufYLNes6kNNeQ7CMj8rcGgBsE,40939
45
+ ripperdoc/core/query_utils.py,sha256=-lBRL5oAV0p6p6LukpFfBZKEcRdztbzCNOt51pEsKBM,24776
40
46
  ripperdoc/core/skills.py,sha256=XkMt3WPT2_0xfx2qQhEnBbwJ0121aRFmuXLckw3MtVU,10251
41
47
  ripperdoc/core/system_prompt.py,sha256=smhuRfzbvhfzNsQ3D89Mucll16u_40VWpOzKS0kJPFQ,26724
42
- ripperdoc/core/tool.py,sha256=Kmc3D--fpaJuK2bSsNwRpoMoQEI3PDgKSKwyLYPOkyc,7818
48
+ ripperdoc/core/tool.py,sha256=Hnnt7FYBGlD6lyAr9XhDgp_LfcP7o3yapzd7t6FPlBE,7813
49
+ ripperdoc/core/hooks/__init__.py,sha256=xw7VJQu1ZB0ENHVqL5xtruBnP3d0FNgrBH6NTL2xYgg,2735
50
+ ripperdoc/core/hooks/config.py,sha256=2KZXpkCGWecg3loQkYZbr5Xh68dDZv-XlTvjwQbl29o,10123
51
+ ripperdoc/core/hooks/events.py,sha256=ZDyP_YaRPKeDz23d6wKbfwlb64poRZDpiuPJaz8ZN9w,17957
52
+ ripperdoc/core/hooks/executor.py,sha256=inOAT2-xB_lXiz0io6c3QV4Wnw9ntl0unf-w0nXPMak,16818
53
+ ripperdoc/core/hooks/integration.py,sha256=Gb3ADGoTTYWDrCmkcgjlyLyldu3wRcji77_2VAyuYJw,11213
54
+ ripperdoc/core/hooks/manager.py,sha256=8YqvGPfOL3ianxFz_tnd7c_HbhHeLDHr5cLk1ayKicU,23706
43
55
  ripperdoc/core/providers/__init__.py,sha256=yevsHF0AUI4b6Wiq_401NXewJ3dqe8LUUtQm0TLPPNQ,1911
44
- ripperdoc/core/providers/anthropic.py,sha256=XNv_LhQTBrsFN5dB5rdYrNikBQ6Qq4sEu_PxukJXuks,10257
56
+ ripperdoc/core/providers/anthropic.py,sha256=V5sofBIU1w7bo1FmrrZ_lqVvGRXt9ZkykDa_J0b1xtc,26309
45
57
  ripperdoc/core/providers/base.py,sha256=HNOa3_XWszu6DbI8BYixxV0hnZb9qZ_FU4uinFVRHjU,9271
46
58
  ripperdoc/core/providers/gemini.py,sha256=3U69Gh6hiL8QWsf-nawZJTfh5oeZP5DAmXYDfWtrEQE,24786
47
59
  ripperdoc/core/providers/openai.py,sha256=FiNTCBtFpq0i0K6S0OyC-F-0HssSWJE6jrH3xRsPzD4,20981
@@ -51,19 +63,19 @@ ripperdoc/tools/__init__.py,sha256=RBFz0DDnztDXMqv_zRxFHVY-ez2HYcncx8zh_y-BX6w,4
51
63
  ripperdoc/tools/ask_user_question_tool.py,sha256=QgzmIDVR-wdlLf9fSiVPbRm_8tSaIlGJhuuRYOCGiUU,15446
52
64
  ripperdoc/tools/background_shell.py,sha256=HangGLwN4iy-udo02zUZF3QRPIqOa7sVDesbv2wL9Js,12854
53
65
  ripperdoc/tools/bash_output_tool.py,sha256=ljIOzTOnkbQfe3jExlhpUlMiLT6HpeD-1QI-D1CwHh8,3379
54
- ripperdoc/tools/bash_tool.py,sha256=qxIZyxygZVl1an0W9Tx_8lEfeKQ_6vg0V33yOVA458U,42669
66
+ ripperdoc/tools/bash_tool.py,sha256=Wua9_R7S0fMfV8SmmI6_m4mpJ7gYdyku34dyTPIRA4o,42757
55
67
  ripperdoc/tools/dynamic_mcp_tool.py,sha256=GERh7qT1mPVivFUIhlFxPNRUwOGNw5CmCnymEwQ-7vk,15662
56
68
  ripperdoc/tools/enter_plan_mode_tool.py,sha256=FYjm_TmBL55pY4GdP7t0ISlqg-Qe3DwpIt-2weL1S4s,7976
57
69
  ripperdoc/tools/exit_plan_mode_tool.py,sha256=3smkwGLTITem5fgA8catSSRay_a1OGQrjs8JF1zDdUQ,5756
58
- ripperdoc/tools/file_edit_tool.py,sha256=RcaEowUeyHknQ9R8w7C7_BrxROa8--zO-iUc2KA8RG0,13768
59
- ripperdoc/tools/file_read_tool.py,sha256=smkqGtqE1ss6NYV6kjemcbBUS4vYVq4CKq32tGVyTDA,7413
70
+ ripperdoc/tools/file_edit_tool.py,sha256=pF4ZCBFq3vy2DukxGZjBGoyVJIRH-7UY4A-TpFegzdA,13768
71
+ ripperdoc/tools/file_read_tool.py,sha256=CvjnYusjolTH-PoQ8CPUVVR37GMLAntT16ffRwqKBto,7396
60
72
  ripperdoc/tools/file_write_tool.py,sha256=SUsFvLVvCwegxEDhL8xpppNRlSl_Hcc1xwNR35FbMqU,7044
61
73
  ripperdoc/tools/glob_tool.py,sha256=oy1S-MrQl57X_wpNXcqXyE4oHI3kmpOQoTYavx3mzEg,5932
62
74
  ripperdoc/tools/grep_tool.py,sha256=n_YNKg8w63JgVJVpg7Qakj75JrymeKByNoapl8IS05U,14125
63
75
  ripperdoc/tools/kill_bash_tool.py,sha256=36F8w2Rm1IVQitwOAwS-D8NTnyQdWfKWIam44qlXErk,4625
64
76
  ripperdoc/tools/ls_tool.py,sha256=x0nw7F5cWjKK7Vb66nqdg9djgupPpCTwPAqd5rZlMUs,15367
65
77
  ripperdoc/tools/mcp_tools.py,sha256=P0wbk_WRUD_MVkLDpMjttjQ13nje8Zc-mR3ud94QqZ0,23018
66
- ripperdoc/tools/multi_edit_tool.py,sha256=da9WUvQRp8ZOm7G6OUUJhKIL4h0unjHSjg-oNco_NuE,17579
78
+ ripperdoc/tools/multi_edit_tool.py,sha256=ndCb-OlHQRqlrGElZ_q5QOTibZqW9uDp6e4ctOPNLbU,17579
67
79
  ripperdoc/tools/notebook_edit_tool.py,sha256=FOgx0RtZnD1zZCUv5vsXS531ep26ABXaNsRhh0ZU4Uc,14393
68
80
  ripperdoc/tools/skill_tool.py,sha256=vquTDL8ZihGvgP6U6EswOm5IjQYFHwxIiLzlzCEWrVw,7701
69
81
  ripperdoc/tools/task_tool.py,sha256=EcUAaWY3aTTZawE84wvk2LyHSNU7nXAbpeDoOjU4_K0,18325
@@ -74,6 +86,7 @@ ripperdoc/utils/bash_constants.py,sha256=KNn8bzB6nVU5jid9jvjiH4FAu8pP3DZONJ-OknJ
74
86
  ripperdoc/utils/bash_output_utils.py,sha256=3Cf5wKJzRbUesmCNy5HtXIBtv0Z2BxklTfFHJ9q1T3w,1210
75
87
  ripperdoc/utils/coerce.py,sha256=KOPb4KR4p32nwHWG_6GsGHeVZunJyYc2YhC5DLmEZO8,1015
76
88
  ripperdoc/utils/context_length_errors.py,sha256=oyDVr_ME_6j97TLwVZ8bDMb6ISGQx6wEHrY7ckc0GuA,7714
89
+ ripperdoc/utils/conversation_compaction.py,sha256=m9AHZcRJwbZUHrNYl5Q1esjKQSBKFkARBkO9YNUjm5Q,18364
77
90
  ripperdoc/utils/exit_code_handlers.py,sha256=QtO1iDxVAb8Xp03D6_QixPoJC-RQlcp3ssIo_rm4two,7973
78
91
  ripperdoc/utils/file_watch.py,sha256=CoUIcLuS-VcfxotuxFkel5KpNluMmLGJKDzx26MG3yY,4039
79
92
  ripperdoc/utils/git_utils.py,sha256=Hq-Zx-KPyX4lp_i8ozhic15LyYdX_IfCRm-EyoFu59A,9047
@@ -81,15 +94,16 @@ ripperdoc/utils/json_utils.py,sha256=e12eMpWlLDniHZVg7zdOkXw5wBZhnjhVtDm8tpBEOjk
81
94
  ripperdoc/utils/log.py,sha256=mieFMPxiv-M87bB-dgiY8D5WMxQbjVKJdsrW8QvCui8,6138
82
95
  ripperdoc/utils/mcp.py,sha256=xszG3kDrlctVVZvXOHr7wgndthpu-AwMySSwpnaHFGc,19445
83
96
  ripperdoc/utils/memory.py,sha256=KNB8Eoobl0vgIEh6phXKtGmDUct7_DNQH-F6Il4KRDQ,8009
84
- ripperdoc/utils/message_compaction.py,sha256=9OX7eDYaUEbolGzqWk_4Uz-S1wrxahI2b6EfrOcS98Y,25063
85
- ripperdoc/utils/messages.py,sha256=b34bl7pvX3RSPKO0EbDQwve_ZEy88VkiSww5s3kB-Wk,20265
97
+ ripperdoc/utils/message_compaction.py,sha256=ydTtMqo09ds1qTneJrHaFroZ4UtmzzXlXHErPm7ZY5E,22370
98
+ ripperdoc/utils/message_formatting.py,sha256=M-2zEkjNEiLyda9x4S0-xyIBSsVOQS3eTzbIE3K7G_Q,7492
99
+ ripperdoc/utils/messages.py,sha256=OrUK758mmUx2_u_hE5RFLJMp8VeDy-crLGVADwgFO3c,21665
86
100
  ripperdoc/utils/output_utils.py,sha256=R3wqFh9Dko_GK00Exx7XI0DnnldRWMsxZypYX5y6SJo,7448
87
- ripperdoc/utils/path_ignore.py,sha256=2THb4h__cnFz_mZoR6gchDFiIirxSvsbcEKONJhiAQQ,17607
101
+ ripperdoc/utils/path_ignore.py,sha256=m-cbf5I_1HeV7mjwzpb4O9BXGMTFZ4EnFmmguzD7WeY,17596
88
102
  ripperdoc/utils/path_utils.py,sha256=C45Q3OeXnj-0FVEtvf_tdG5922XB6HthUzlUCvfc17Y,1626
89
103
  ripperdoc/utils/prompt.py,sha256=zICNEsA_OtKx8t3zo9tHLXXu6G5K8rPO3jFLKz4j5tg,560
90
104
  ripperdoc/utils/safe_get_cwd.py,sha256=IvG8dIJd2tC5_glUsfeWXkpcF1EHzdkjFtuUGJd669w,815
91
105
  ripperdoc/utils/sandbox_utils.py,sha256=G91P8dw2VFcCiCpjXZ4LvzbAPiO8REqMhw39eI5Z4dU,1123
92
- ripperdoc/utils/session_history.py,sha256=M7TzmXibbWwh1xjHi_v3RfogaN6ZlKvfBYGRrsJp2hE,8978
106
+ ripperdoc/utils/session_history.py,sha256=SQ3HusavKtbVyx6V-KPCXbQtTm_rcVuL-qjwv_iNS7Y,9568
93
107
  ripperdoc/utils/session_usage.py,sha256=p8_s46zDTzV1qzP4HR4PuZmLeJvSfq9mG_Y5rCnRYyA,3213
94
108
  ripperdoc/utils/shell_token_utils.py,sha256=SduoSU-RERJdM_7gBn0urr5UXtl4XOpPgydBd2fwzWg,2500
95
109
  ripperdoc/utils/shell_utils.py,sha256=t-neFPy_VhEmWZ79J7hh1ULBEdX116Rb9_pnXvir1Jw,5235
@@ -99,9 +113,9 @@ ripperdoc/utils/permissions/__init__.py,sha256=33FfOaDLepxJSkp0RLvTdVu7qBXuEcnOo
99
113
  ripperdoc/utils/permissions/path_validation_utils.py,sha256=FWbb21Hwgb9gUPu_WtA14w99UUojVQLVz5HByormoUs,5760
100
114
  ripperdoc/utils/permissions/shell_command_validation.py,sha256=94ylqoDUiTF4v4wEiVS35jFJakyaSxdIFqYKumPTrGk,21205
101
115
  ripperdoc/utils/permissions/tool_permission_utils.py,sha256=6Fdu9-dMKhLsUExjEjoS0EUeRpEVN5UkqyseIC05YmM,9207
102
- ripperdoc-0.2.6.dist-info/licenses/LICENSE,sha256=bRv9UhBor6GhnQDj12RciDcRfu0R7sB-lqCy1sWF75c,9242
103
- ripperdoc-0.2.6.dist-info/METADATA,sha256=_G-UKolwTzfnVWiAc5Wf_5Zw7_Fss1XM7w71teW1z9o,6218
104
- ripperdoc-0.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
105
- ripperdoc-0.2.6.dist-info/entry_points.txt,sha256=79aohFxFPJmrQ3-Mhain04vb3EWpuc0EyzvDDUnwAu4,81
106
- ripperdoc-0.2.6.dist-info/top_level.txt,sha256=u8LbdTr1a-laHgCO0Utl_R3QGFUhLxWelCDnP2ZgpCU,10
107
- ripperdoc-0.2.6.dist-info/RECORD,,
116
+ ripperdoc-0.2.8.dist-info/licenses/LICENSE,sha256=bRv9UhBor6GhnQDj12RciDcRfu0R7sB-lqCy1sWF75c,9242
117
+ ripperdoc-0.2.8.dist-info/METADATA,sha256=aoAeFiQqkUDQpJEt4buP6G1wKlkCMXI88slI-tGSDGg,7474
118
+ ripperdoc-0.2.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
119
+ ripperdoc-0.2.8.dist-info/entry_points.txt,sha256=79aohFxFPJmrQ3-Mhain04vb3EWpuc0EyzvDDUnwAu4,81
120
+ ripperdoc-0.2.8.dist-info/top_level.txt,sha256=u8LbdTr1a-laHgCO0Utl_R3QGFUhLxWelCDnP2ZgpCU,10
121
+ ripperdoc-0.2.8.dist-info/RECORD,,