ripperdoc 0.2.9__py3-none-any.whl → 0.3.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 (76) hide show
  1. ripperdoc/__init__.py +1 -1
  2. ripperdoc/cli/cli.py +379 -51
  3. ripperdoc/cli/commands/__init__.py +6 -0
  4. ripperdoc/cli/commands/agents_cmd.py +128 -5
  5. ripperdoc/cli/commands/clear_cmd.py +8 -0
  6. ripperdoc/cli/commands/doctor_cmd.py +29 -0
  7. ripperdoc/cli/commands/exit_cmd.py +1 -0
  8. ripperdoc/cli/commands/memory_cmd.py +2 -1
  9. ripperdoc/cli/commands/models_cmd.py +63 -7
  10. ripperdoc/cli/commands/resume_cmd.py +5 -0
  11. ripperdoc/cli/commands/skills_cmd.py +103 -0
  12. ripperdoc/cli/commands/stats_cmd.py +244 -0
  13. ripperdoc/cli/commands/status_cmd.py +10 -0
  14. ripperdoc/cli/commands/tasks_cmd.py +6 -3
  15. ripperdoc/cli/commands/themes_cmd.py +139 -0
  16. ripperdoc/cli/ui/file_mention_completer.py +63 -13
  17. ripperdoc/cli/ui/helpers.py +6 -3
  18. ripperdoc/cli/ui/interrupt_handler.py +34 -0
  19. ripperdoc/cli/ui/panels.py +14 -8
  20. ripperdoc/cli/ui/rich_ui.py +737 -47
  21. ripperdoc/cli/ui/spinner.py +93 -18
  22. ripperdoc/cli/ui/thinking_spinner.py +1 -2
  23. ripperdoc/cli/ui/tool_renderers.py +10 -9
  24. ripperdoc/cli/ui/wizard.py +24 -19
  25. ripperdoc/core/agents.py +14 -3
  26. ripperdoc/core/config.py +238 -6
  27. ripperdoc/core/default_tools.py +91 -10
  28. ripperdoc/core/hooks/events.py +4 -0
  29. ripperdoc/core/hooks/llm_callback.py +58 -0
  30. ripperdoc/core/hooks/manager.py +6 -0
  31. ripperdoc/core/permissions.py +160 -9
  32. ripperdoc/core/providers/openai.py +84 -28
  33. ripperdoc/core/query.py +489 -87
  34. ripperdoc/core/query_utils.py +17 -14
  35. ripperdoc/core/skills.py +1 -0
  36. ripperdoc/core/theme.py +298 -0
  37. ripperdoc/core/tool.py +15 -5
  38. ripperdoc/protocol/__init__.py +14 -0
  39. ripperdoc/protocol/models.py +300 -0
  40. ripperdoc/protocol/stdio.py +1453 -0
  41. ripperdoc/tools/background_shell.py +354 -139
  42. ripperdoc/tools/bash_tool.py +117 -22
  43. ripperdoc/tools/file_edit_tool.py +228 -50
  44. ripperdoc/tools/file_read_tool.py +154 -3
  45. ripperdoc/tools/file_write_tool.py +53 -11
  46. ripperdoc/tools/grep_tool.py +98 -8
  47. ripperdoc/tools/lsp_tool.py +609 -0
  48. ripperdoc/tools/multi_edit_tool.py +26 -3
  49. ripperdoc/tools/skill_tool.py +52 -1
  50. ripperdoc/tools/task_tool.py +539 -65
  51. ripperdoc/utils/conversation_compaction.py +1 -1
  52. ripperdoc/utils/file_watch.py +216 -7
  53. ripperdoc/utils/image_utils.py +125 -0
  54. ripperdoc/utils/log.py +30 -3
  55. ripperdoc/utils/lsp.py +812 -0
  56. ripperdoc/utils/mcp.py +80 -18
  57. ripperdoc/utils/message_formatting.py +7 -4
  58. ripperdoc/utils/messages.py +198 -33
  59. ripperdoc/utils/pending_messages.py +50 -0
  60. ripperdoc/utils/permissions/shell_command_validation.py +3 -3
  61. ripperdoc/utils/permissions/tool_permission_utils.py +180 -15
  62. ripperdoc/utils/platform.py +198 -0
  63. ripperdoc/utils/session_heatmap.py +242 -0
  64. ripperdoc/utils/session_history.py +2 -2
  65. ripperdoc/utils/session_stats.py +294 -0
  66. ripperdoc/utils/shell_utils.py +8 -5
  67. ripperdoc/utils/todo.py +0 -6
  68. {ripperdoc-0.2.9.dist-info → ripperdoc-0.3.0.dist-info}/METADATA +55 -17
  69. ripperdoc-0.3.0.dist-info/RECORD +136 -0
  70. {ripperdoc-0.2.9.dist-info → ripperdoc-0.3.0.dist-info}/WHEEL +1 -1
  71. ripperdoc/sdk/__init__.py +0 -9
  72. ripperdoc/sdk/client.py +0 -333
  73. ripperdoc-0.2.9.dist-info/RECORD +0 -123
  74. {ripperdoc-0.2.9.dist-info → ripperdoc-0.3.0.dist-info}/entry_points.txt +0 -0
  75. {ripperdoc-0.2.9.dist-info → ripperdoc-0.3.0.dist-info}/licenses/LICENSE +0 -0
  76. {ripperdoc-0.2.9.dist-info → ripperdoc-0.3.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripperdoc
3
- Version: 0.2.9
3
+ Version: 0.3.0
4
4
  Summary: AI-powered terminal assistant for coding tasks
5
5
  Author: Ripperdoc Team
6
6
  License: Apache-2.0
@@ -23,16 +23,23 @@ Requires-Dist: python-dotenv>=1.0.0
23
23
  Requires-Dist: aiofiles>=23.0.0
24
24
  Requires-Dist: prompt-toolkit>=3.0.0
25
25
  Requires-Dist: PyYAML>=6.0.0
26
- Requires-Dist: mcp[cli]>=1.22.0
26
+ Requires-Dist: mcp[cli]>=1.25.0
27
27
  Requires-Dist: json_repair>=0.54.2
28
28
  Requires-Dist: tiktoken>=0.7.0
29
29
  Requires-Dist: google-genai>=0.3.0
30
+ Requires-Dist: charset-normalizer>=3.0.0
30
31
  Provides-Extra: dev
31
32
  Requires-Dist: pytest>=7.0.0; extra == "dev"
32
33
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
33
34
  Requires-Dist: mypy>=1.0.0; extra == "dev"
34
35
  Requires-Dist: black>=23.0.0; extra == "dev"
35
36
  Requires-Dist: ruff>=0.1.0; extra == "dev"
37
+ Provides-Extra: anthropic
38
+ Requires-Dist: anthropic>=0.39.0; extra == "anthropic"
39
+ Provides-Extra: openai
40
+ Requires-Dist: openai>=1.0.0; extra == "openai"
41
+ Provides-Extra: gemini
42
+ Requires-Dist: google-genai>=0.3.0; extra == "gemini"
36
43
  Dynamic: license-file
37
44
 
38
45
  <div align="center">
@@ -93,27 +100,22 @@ pip install git+https://github.com/quantmew/ripperdoc.git
93
100
  Or install from source:
94
101
  ```bash
95
102
  # Clone the repository
96
- git clone <repository-url>
97
- cd Ripperdoc
103
+ git clone https://github.com/quantmew/ripperdoc.git
104
+ cd ripperdoc
98
105
 
99
106
  # Install from source
100
107
  pip install -e .
101
108
  ```
102
109
 
103
- ### Configuration
104
110
 
105
- Set your API key as an environment variable:
106
- ```bash
107
- export OPENAI_API_KEY="your-api-key-here"
108
- # or for Anthropic Claude
109
- export ANTHROPIC_API_KEY="your-api-key-here"
110
- ```
111
111
 
112
112
  ## Usage
113
113
 
114
114
  ### Interactive Mode (Recommended)
115
115
  ```bash
116
116
  ripperdoc
117
+ # or use the short alias
118
+ rd
117
119
  ```
118
120
 
119
121
  This launches an interactive session where you can:
@@ -122,6 +124,13 @@ This launches an interactive session where you can:
122
124
  - Execute commands
123
125
  - Navigate and explore files
124
126
 
127
+ **Options:**
128
+ - `--yolo` - Skip permission prompts (safe mode is on by default)
129
+ - `--model <model_name>` - Specify a model (e.g., `claude-sonnet-4-20250514`, `gpt-4o`)
130
+ - `--tools <tool_list>` - Filter available tools (comma-separated, or "" for none)
131
+ - `--no-mcp` - Disable MCP server integration
132
+ - `--verbose` - Enable verbose logging
133
+
125
134
  ### Python SDK (headless)
126
135
 
127
136
  Use Ripperdoc without the terminal UI via the new Python SDK. See [docs/SDK_USAGE.md](docs/SDK_USAGE.md) for examples of the one-shot `query` helper and the session-based `RipperdocClient`. 中文指南见 [docs/SDK_USAGE_CN.md](docs/SDK_USAGE_CN.md)。
@@ -137,38 +146,67 @@ See the [examples/](examples/) directory for complete SDK usage examples.
137
146
 
138
147
  ### Safe Mode Permissions
139
148
 
140
- Safe mode is the default. Use `--unsafe` to skip permission prompts. Choose `a`/`always` to allow a tool for the current session (not persisted across sessions).
149
+ Safe mode is enabled by default. When prompted:
150
+ - Type `y` or `yes` to allow a single operation
151
+ - Type `a` or `always` to allow all operations of that type for the session
152
+ - Type `n` or `no` to deny the operation
153
+
154
+ Use `--yolo` flag to skip all permission prompts:
155
+ ```bash
156
+ ripperdoc --yolo
157
+ ```
141
158
 
142
159
  ### Agent Skills
143
160
 
144
161
  Extend Ripperdoc with reusable Skill bundles:
145
162
 
146
- - Personal skills live in `~/.ripperdoc/skills/<skill-name>/SKILL.md`
147
- - Project skills live in `.ripperdoc/skills/<skill-name>/SKILL.md` and can be checked into git
148
- - Each `SKILL.md` starts with YAML frontmatter (`name`, `description`, optional `allowed-tools`, `model`, `max-thinking-tokens`, `disable-model-invocation`) followed by the instructions; add supporting files alongside it
149
- - Model and max-thinking-token hints from skills are applied automatically for the rest of the session after you load them with the `Skill` tool
150
- - Ripperdoc exposes skill names/descriptions in the system prompt and loads full content on demand via the `Skill` tool
163
+ - **Personal skills**: `~/.ripperdoc/skills/<skill-name>/SKILL.md`
164
+ - **Project skills**: `.ripperdoc/skills/<skill-name>/SKILL.md` (can be checked into git)
165
+ - Each `SKILL.md` starts with YAML frontmatter:
166
+ - `name` - Skill identifier
167
+ - `description` - What the skill does
168
+ - `allowed-tools` (optional) - Restrict which tools the skill can use
169
+ - `model` (optional) - Suggest a specific model for this skill
170
+ - `max-thinking-tokens` (optional) - Control thinking budget
171
+ - `disable-model-invocation` (optional) - Use skill without calling the model
172
+ - Add supporting files alongside `SKILL.md` as needed
173
+ - Skills are auto-discovered and loaded on demand via the `Skill` tool
174
+
175
+ **Built-in skills:** PDF manipulation (`pdf`), PowerPoint (`pptx`), Excel (`xlsx`)
151
176
 
152
177
  ## Examples
153
178
 
154
179
  ### Code Analysis
155
180
  ```
156
181
  > Can you explain what this function does?
182
+ > Find all references to the `parse_config` function
157
183
  ```
158
184
 
159
185
  ### File Operations
160
186
  ```
161
187
  > Read the main.py file and suggest improvements
188
+ > Create a new component called UserProfile.tsx
189
+ > Update all imports to use the new package structure
162
190
  ```
163
191
 
164
192
  ### Code Generation
165
193
  ```
166
194
  > Create a new Python script that implements a REST API client
195
+ > Generate unit tests for the auth module
196
+ > Add error handling to the database connection code
167
197
  ```
168
198
 
169
199
  ### Project Navigation
170
200
  ```
171
201
  > Show me all the Python files in the project
202
+ > Find where the user authentication logic is implemented
203
+ > List all API endpoints in the project
204
+ ```
205
+
206
+ ### MCP Integration
207
+ ```
208
+ > What MCP servers are available?
209
+ > Query the context7 documentation for React hooks
172
210
  ```
173
211
 
174
212
  ## Development
@@ -0,0 +1,136 @@
1
+ ripperdoc/__init__.py,sha256=r9wkvo_s7lhTfuQG7WVFeE3Eg4HgMGHovnyZR1OVnPA,66
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=OZgN803jYmzdKxynHWG7VNwd7dQhM1KdYakXDyMlZT8,21826
5
+ ripperdoc/cli/commands/__init__.py,sha256=Up-tTlq3lhxDt-YEdiOoHwfRyIdb8nlL5waECzRg-xY,4858
6
+ ripperdoc/cli/commands/agents_cmd.py,sha256=agDTXwZWSr-fmtp_afuD1kMTA1NalMHtoKQ3eVtJMFs,15699
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=w-AVph1fvmlnbuTeUdAL19zikj4MmNRxmXlhcvR2SXQ,8188
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=BsYYUosmnIs92mwgHH1e3DdzOj83iJ5_AJ8kBeSkRCw,6594
19
+ ripperdoc/cli/commands/models_cmd.py,sha256=LYebi7s9kv6xxlSqX3Pb1AsWY9S-xp0ikdtPVECinvo,18635
20
+ ripperdoc/cli/commands/permissions_cmd.py,sha256=aIMIvmt78nB2Q-Qa5ojqClUOsPRcoyeDIuhOJX8xg2k,11559
21
+ ripperdoc/cli/commands/resume_cmd.py,sha256=Pil2gJIkjf9fIFrxTyVXTwS7kzsg14CVg1SNQPZYAmw,4295
22
+ ripperdoc/cli/commands/skills_cmd.py,sha256=OFDLy2g_o_GDzdatk18E-Xv4bhVG80MQUr_PQLmTveQ,3496
23
+ ripperdoc/cli/commands/stats_cmd.py,sha256=I6nFZndBy_VRcUAxXAjplSjIzA-5UqRK4HlQJbpHydg,7857
24
+ ripperdoc/cli/commands/status_cmd.py,sha256=9pI_GF4mxR9cQXF-HEURZb0qO6GWp2hcy3rzaVW98II,5962
25
+ ripperdoc/cli/commands/tasks_cmd.py,sha256=tghFKy0ENeJ9Mf0h5Waceq8l9JXP5xTLsJHP4ZkibbQ,9044
26
+ ripperdoc/cli/commands/themes_cmd.py,sha256=b3Ti-KmRF9XVk1jtS7bkz5F8317yu1fyp1I89SSpfqI,4019
27
+ ripperdoc/cli/commands/todos_cmd.py,sha256=7Q0B1NVqGtB3R29ndbn4m0VQQm-YQ7d4Wlk7vJ7dLQI,1848
28
+ ripperdoc/cli/commands/tools_cmd.py,sha256=3cMi0vN4mAUhpKqJtRgNvZfcKzRPaMs_pkYYXlyvSSU,384
29
+ ripperdoc/cli/ui/__init__.py,sha256=TxSzTYdITlrYmYVfins_w_jzPqqWRpqky5u1ikwvmtM,43
30
+ ripperdoc/cli/ui/context_display.py,sha256=3ezdtHVwltkPQ5etYwfqUh-fjnpPu8B3P81UzrdHxZs,10020
31
+ ripperdoc/cli/ui/file_mention_completer.py,sha256=ysNqZieQVlUb7DC4CP_FMKNeTG8AIqKLmJgl6HJBURo,13649
32
+ ripperdoc/cli/ui/helpers.py,sha256=iM7kMb-fMTO6n4_MDVrESE1P-Y7w8PXiIhCCLp5yyA4,2618
33
+ ripperdoc/cli/ui/interrupt_handler.py,sha256=6MwZ2wo1ZCdd4JGV5dWOzHkNDDce51z9Rhf0LdFjWTM,7107
34
+ ripperdoc/cli/ui/message_display.py,sha256=W9VlRCpMykkamxphej5LHgmU-P4YaBfm9HPdIKlFKpg,10573
35
+ ripperdoc/cli/ui/panels.py,sha256=kaf3sDG2AIcrdrqbASnqGYHySfSeMNozGFhqvZ4bTJs,2207
36
+ ripperdoc/cli/ui/provider_options.py,sha256=Ic30K1ev8w2oEcha4IjDYSoxw1tyCVB4hLoQpS_Y_5E,8369
37
+ ripperdoc/cli/ui/rich_ui.py,sha256=LZjmaCSfXf8Le5zq03e4PH2S0nlsSXW566q7mW-xWaA,76968
38
+ ripperdoc/cli/ui/spinner.py,sha256=IlmMgyk-eA6Bk4fXDjbWs3AFL2iFPeWmnS0q28tWOgU,5175
39
+ ripperdoc/cli/ui/thinking_spinner.py,sha256=3zmxj3vd-1njdiHF2Afsm7RGiRl7645AEc7fTLKgAbU,2805
40
+ ripperdoc/cli/ui/tool_renderers.py,sha256=7ACcZoKU91kvFsk3YdP2TzibfI-W_F1dv-Ksr68rrYE,11280
41
+ ripperdoc/cli/ui/wizard.py,sha256=bFKJeQStJ2Opq_ih9tgchkJP14bMvI-PX-zHVwzMiy8,7423
42
+ ripperdoc/core/__init__.py,sha256=UemJCA-Y8df1466AX-YbRFj071zKajmqO1mi40YVW2g,40
43
+ ripperdoc/core/agents.py,sha256=Xl1HEJz8v-xIEhzgObeysgrz-SQBYbxYVRI4CvRl-w8,20539
44
+ ripperdoc/core/commands.py,sha256=NXCkljYbAP4dDoRy-_3semFNWxG4YAk9q82u8FTKH60,835
45
+ ripperdoc/core/config.py,sha256=4KvaGKG4bf9wX6haPvxJllokdpPbJVvfTIMfsC0maag,29936
46
+ ripperdoc/core/custom_commands.py,sha256=2BMYiBq15FDjl3aOa3styN6nARyfU7xFAb4ly2Vsp-w,14254
47
+ ripperdoc/core/default_tools.py,sha256=mpb9mqmrAjMqgcmpRU_uQF1G6vpYbp8nkiwrh71KQyM,5250
48
+ ripperdoc/core/permissions.py,sha256=yp7OK36_I_UceDnLbGmxPh6nSbIDfns4IgTVGH5Tkqs,16986
49
+ ripperdoc/core/query.py,sha256=dC_sW2D2bPLI6nPM3VxFpTBA4fYYHHRNmE6zvy93pus,62057
50
+ ripperdoc/core/query_utils.py,sha256=2OQYeobFD-24NG0CZnnm3D5HCJ7r8AOB08I-wpQhH1A,24888
51
+ ripperdoc/core/skills.py,sha256=NGP_QFzL2SBkz8wZBGxNA_6Gt8qkRIZtJDnPs_DayxE,10511
52
+ ripperdoc/core/system_prompt.py,sha256=d3GNCsJ_mdIojpWg1Wc0gRDC_gzRDI5G4tQN_gxhRdo,26752
53
+ ripperdoc/core/theme.py,sha256=cQ0LTDxP2whFEsTRi421p1Rs7Epe9rlv-HgFirvIxzM,8427
54
+ ripperdoc/core/tool.py,sha256=0ihPcsgr0Dz5kl9-8hCU-xaRui5_WCjIOnVTpshc8k0,8529
55
+ ripperdoc/core/hooks/__init__.py,sha256=xw7VJQu1ZB0ENHVqL5xtruBnP3d0FNgrBH6NTL2xYgg,2735
56
+ ripperdoc/core/hooks/config.py,sha256=wE_eMHDZu9-yHGyJ45ySL1_l2yx7B8i4jiThs78W6Zc,10085
57
+ ripperdoc/core/hooks/events.py,sha256=UGBDdec52abi2e6ox8ncsLeDo3t9ZJesymKaPwxNDAE,18403
58
+ ripperdoc/core/hooks/executor.py,sha256=3MdiCdc4Bn2xHcTlRT72BajrgAZ5roKck0NBnO4Re0c,16781
59
+ ripperdoc/core/hooks/integration.py,sha256=IzkOSpaMjC397zKdKO1jTR0uyzOet-eCwPLuXwTYOts,11082
60
+ ripperdoc/core/hooks/llm_callback.py,sha256=TsttZ8Q7c8-36ebbI8nTyYcKbA7Kr9rmY5UO2gzuL-c,1733
61
+ ripperdoc/core/hooks/manager.py,sha256=dfHhB8f5US8ZN2a0JbzD5bQZHsnBS6Zs8xVZ2IcSsrg,25092
62
+ ripperdoc/core/providers/__init__.py,sha256=yevsHF0AUI4b6Wiq_401NXewJ3dqe8LUUtQm0TLPPNQ,1911
63
+ ripperdoc/core/providers/anthropic.py,sha256=B967szN1Thc0K1Iv8TvKWcVKktCevBmShftupVag8pk,28551
64
+ ripperdoc/core/providers/base.py,sha256=HNOa3_XWszu6DbI8BYixxV0hnZb9qZ_FU4uinFVRHjU,9271
65
+ ripperdoc/core/providers/gemini.py,sha256=Fs-dShsmIVBFfz-jg4fBjvQyrxVnZ5yx4ALcES-l5Sg,27089
66
+ ripperdoc/core/providers/openai.py,sha256=BhvDvkDjeYJMbvI4mbswQXbjdab53oJduku-Yja1zmQ,25561
67
+ ripperdoc/protocol/__init__.py,sha256=rSYu_Y2acRfsAE8gUTG82VnyrK3AFrIfwuJBf2cjHjs,325
68
+ ripperdoc/protocol/models.py,sha256=goNurNKI0eGZeCGmr7KqMPkZMvMOABXWsb-8un0LDlc,7468
69
+ ripperdoc/protocol/stdio.py,sha256=jia8cca3offyOc8kego-MnJfXsZkFwQw0cZZzfeFQqs,58933
70
+ ripperdoc/tools/__init__.py,sha256=RBFz0DDnztDXMqv_zRxFHVY-ez2HYcncx8zh_y-BX6w,42
71
+ ripperdoc/tools/ask_user_question_tool.py,sha256=ZWg5xAdeaRoR98KvvPuKmJH4L2dgzH87VXM4dxKcqBE,15478
72
+ ripperdoc/tools/background_shell.py,sha256=VMJd0G7s1l7qSRvlkRaIbgWmj4N0U_sr0kLzBzVLO2A,21263
73
+ ripperdoc/tools/bash_output_tool.py,sha256=cC5dDmKYmkOTsLCXCcTYgc0loVWtmRobPn0C-I6qO-o,3379
74
+ ripperdoc/tools/bash_tool.py,sha256=m3jfukBBOSQUYJbntekTGGk3FIn1GKaZVvcqMWwLBYE,47598
75
+ ripperdoc/tools/dynamic_mcp_tool.py,sha256=kxxWhp6pP9N8fK3ubu5fHQYQv7aSwxcnaa3C9ZsBbOU,15879
76
+ ripperdoc/tools/enter_plan_mode_tool.py,sha256=LNQv43uWkohiQTYQdsrKbpAfQSJNE_FJ9Y6AM_ckjng,7976
77
+ ripperdoc/tools/exit_plan_mode_tool.py,sha256=XxhEih5EUrcscvkRA9lel54XoVIhl_cCL2xcvMJjtVA,5756
78
+ ripperdoc/tools/file_edit_tool.py,sha256=IHgaWbzR7NKpLJ5XOjxU24c5-RyseVJw-p3QFGtcKJQ,22190
79
+ ripperdoc/tools/file_read_tool.py,sha256=TXEt_Q7RSaTx4qdx3_mOYP704UU6qRccL5_AqPlNFrY,13735
80
+ ripperdoc/tools/file_write_tool.py,sha256=HqOkFYedacNsPatKxVhth0kI5fGh83tm2VxdD_pO-V0,8570
81
+ ripperdoc/tools/glob_tool.py,sha256=eZG4fzahjJsSM8NdmTiVl5nBfDQK7egPg6P7cqOM_1Y,5948
82
+ ripperdoc/tools/grep_tool.py,sha256=bqrliI6e-9cNKIOfxdCv6mBwHr_1BSLwAPSTbOTc4B4,18239
83
+ ripperdoc/tools/kill_bash_tool.py,sha256=_jwnJVCPe8uXZTJd7myh4hWCD-eBuB2XDaYwRCmNSsI,4625
84
+ ripperdoc/tools/ls_tool.py,sha256=JWQucgNOLjrGZwM7imu3GWe5YFwXxdXGoaBr50wDZCQ,15367
85
+ ripperdoc/tools/lsp_tool.py,sha256=atpaaMqf3jld7SyNXtfdM6NkxYrp3KbYZ91oC4EqQlU,21315
86
+ ripperdoc/tools/mcp_tools.py,sha256=BVz8MJhijNnHq1J2LLkZS89wp_pwwnOytH1HYcfkFqQ,23085
87
+ ripperdoc/tools/multi_edit_tool.py,sha256=s9gN3oRPqeYjQxdsfnkfHSIMTrBs09ihV_TYuVqaey8,18446
88
+ ripperdoc/tools/notebook_edit_tool.py,sha256=Y2XkQB4WDbSWeCsO3Ybnsrbcxki99aOv3o23jZ5D1Hw,14445
89
+ ripperdoc/tools/skill_tool.py,sha256=8TcmNxpSGiBXd-ipgoD9BIopKkR13-3U56rkzozazhY,9763
90
+ ripperdoc/tools/task_tool.py,sha256=SQW2yLM0yectI0dVBoaDF0Tp5T4yOYqvHpsFWapOE44,33979
91
+ ripperdoc/tools/todo_tool.py,sha256=eIwF-s1117DrXJ4yXUMwkNs1gfKYNF2OlWzkJAXDzmk,20001
92
+ ripperdoc/tools/tool_search_tool.py,sha256=AeY-tFtWr2IAHTCEnG9kvsRRBqrBd-PJ96oUnFKa3Vs,13890
93
+ ripperdoc/utils/__init__.py,sha256=gdso60znB2hsYZ_YZBKVcuOY3QVfoqD2wHQ4pvr5lSw,37
94
+ ripperdoc/utils/bash_constants.py,sha256=KNn8bzB6nVU5jid9jvjiH4FAu8pP3DZONJ-OknJypAQ,1641
95
+ ripperdoc/utils/bash_output_utils.py,sha256=3Cf5wKJzRbUesmCNy5HtXIBtv0Z2BxklTfFHJ9q1T3w,1210
96
+ ripperdoc/utils/coerce.py,sha256=KOPb4KR4p32nwHWG_6GsGHeVZunJyYc2YhC5DLmEZO8,1015
97
+ ripperdoc/utils/context_length_errors.py,sha256=oyDVr_ME_6j97TLwVZ8bDMb6ISGQx6wEHrY7ckc0GuA,7714
98
+ ripperdoc/utils/conversation_compaction.py,sha256=mbOYwQXXsAqiJ7mBQ25195bXtH3tb5GSU0HUNsvnl1g,18345
99
+ ripperdoc/utils/exit_code_handlers.py,sha256=QtO1iDxVAb8Xp03D6_QixPoJC-RQlcp3ssIo_rm4two,7973
100
+ ripperdoc/utils/file_watch.py,sha256=Oqx5elgE3_z6CSAKdTbIPna_E4SePBqDujpqqANcw9U,11823
101
+ ripperdoc/utils/git_utils.py,sha256=Hq-Zx-KPyX4lp_i8ozhic15LyYdX_IfCRm-EyoFu59A,9047
102
+ ripperdoc/utils/image_utils.py,sha256=52YRLN0NwPEHnL3-smYktx2T8Oh002W0nvZsxI-WFdo,3380
103
+ ripperdoc/utils/json_utils.py,sha256=Bx1pHHu5r7GtvCqFHM3K9EoknFAtYOaCqTn9RN-5qBA,757
104
+ ripperdoc/utils/log.py,sha256=niCsP__DSbgSAJfitmrsSkh3UjPw7NdYHURrx7DLRuU,7197
105
+ ripperdoc/utils/lsp.py,sha256=4_zw1ZzbvMe16kn6U5EHyMyACX-m0Hr0AjoFXi1xsEQ,26844
106
+ ripperdoc/utils/mcp.py,sha256=-8yZD6gc43BqiFmG3intZcyfVhAYusaXgoI8rJUSdGA,22621
107
+ ripperdoc/utils/memory.py,sha256=J_kucw1BBnHQ1qG2_ZzdNysKvS1lrpuMtB5wxJDmXZU,8033
108
+ ripperdoc/utils/message_compaction.py,sha256=FwYxjWc0B7IlzT1VPEyfhwEl8rXbDi37utbEbS5qGWw,22577
109
+ ripperdoc/utils/message_formatting.py,sha256=9uA7c3MMuWTIp0SQAXkvuRsofKlQ_r0xRHD7ln5lR8s,7820
110
+ ripperdoc/utils/messages.py,sha256=qX9vh81hZXQc3vHLTLfuiLrLwMkLc4XKpX7ZnTRzI7I,30987
111
+ ripperdoc/utils/output_utils.py,sha256=R3wqFh9Dko_GK00Exx7XI0DnnldRWMsxZypYX5y6SJo,7448
112
+ ripperdoc/utils/path_ignore.py,sha256=5VOk075Ef9Wz9LhAWjFQuXJfnypxTD0w9cZVoTGko2M,17882
113
+ ripperdoc/utils/path_utils.py,sha256=C45Q3OeXnj-0FVEtvf_tdG5922XB6HthUzlUCvfc17Y,1626
114
+ ripperdoc/utils/pending_messages.py,sha256=75DKO1SgVGMlzC_ycfKNJ_cktj_MFVFy60j4EXeRsM0,1683
115
+ ripperdoc/utils/platform.py,sha256=Q-2hp0ywegEiuxK3TzE5Pg9wuHJiY8tPp9eoEMCtx4Y,5024
116
+ ripperdoc/utils/prompt.py,sha256=zICNEsA_OtKx8t3zo9tHLXXu6G5K8rPO3jFLKz4j5tg,560
117
+ ripperdoc/utils/safe_get_cwd.py,sha256=lYxFJAN7lomoLwTAfMZtyOueotuvhC8TN84NtrPKj1E,827
118
+ ripperdoc/utils/sandbox_utils.py,sha256=G91P8dw2VFcCiCpjXZ4LvzbAPiO8REqMhw39eI5Z4dU,1123
119
+ ripperdoc/utils/session_heatmap.py,sha256=o88ZlL9tFizOEbo92r6RKcfe2hscghVv7wS_REltZIw,8436
120
+ ripperdoc/utils/session_history.py,sha256=S-z5TneMvyLvoITJG4bRKvr4bvh5LWp8rKIuekLaFA4,9716
121
+ ripperdoc/utils/session_stats.py,sha256=xf5KKhNlzocPxMAS5fRUF6YVDH9fpqObfx_ah_iAMqA,9101
122
+ ripperdoc/utils/session_usage.py,sha256=p8_s46zDTzV1qzP4HR4PuZmLeJvSfq9mG_Y5rCnRYyA,3213
123
+ ripperdoc/utils/shell_token_utils.py,sha256=SduoSU-RERJdM_7gBn0urr5UXtl4XOpPgydBd2fwzWg,2500
124
+ ripperdoc/utils/shell_utils.py,sha256=RQ0iMdvv0SQOs9vqDAaklLLOo0inC935DOjmy0iZKYc,5541
125
+ ripperdoc/utils/todo.py,sha256=t0DJMHtwRBGf_uEx9zzDwp2K0TOoOT0NEhFXWm_YoR0,6807
126
+ ripperdoc/utils/token_estimation.py,sha256=qPQbeUwVlafEjzsXw6qMo0hd4Vjb1gCUMAPBouYUASI,1066
127
+ ripperdoc/utils/permissions/__init__.py,sha256=33FfOaDLepxJSkp0RLvTdVu7qBXuEcnOoTHFbEtFOt0,653
128
+ ripperdoc/utils/permissions/path_validation_utils.py,sha256=KOegjWaph8tXU7aqwQXRAxFEzrmRuPvdLb36J1QIPDQ,5772
129
+ ripperdoc/utils/permissions/shell_command_validation.py,sha256=BRi-1OGzr-Wiuxa00Ye6oqPctoJUZBOoRoi6dUiAsfM,33256
130
+ ripperdoc/utils/permissions/tool_permission_utils.py,sha256=YqbYSXyR0RBbLibBtigYb9O9143mDRulsQCh_WwArvY,14653
131
+ ripperdoc-0.3.0.dist-info/licenses/LICENSE,sha256=bRv9UhBor6GhnQDj12RciDcRfu0R7sB-lqCy1sWF75c,9242
132
+ ripperdoc-0.3.0.dist-info/METADATA,sha256=fZifh_w_rlHCJHbkadgF5_g3oDi7bH_7M_qekoC4g1Y,8651
133
+ ripperdoc-0.3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
134
+ ripperdoc-0.3.0.dist-info/entry_points.txt,sha256=79aohFxFPJmrQ3-Mhain04vb3EWpuc0EyzvDDUnwAu4,81
135
+ ripperdoc-0.3.0.dist-info/top_level.txt,sha256=u8LbdTr1a-laHgCO0Utl_R3QGFUhLxWelCDnP2ZgpCU,10
136
+ ripperdoc-0.3.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
ripperdoc/sdk/__init__.py DELETED
@@ -1,9 +0,0 @@
1
- """Lightweight Python SDK for using Ripperdoc headlessly."""
2
-
3
- from ripperdoc.sdk.client import (
4
- RipperdocClient,
5
- RipperdocOptions,
6
- query,
7
- )
8
-
9
- __all__ = ["RipperdocClient", "RipperdocOptions", "query"]
ripperdoc/sdk/client.py DELETED
@@ -1,333 +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
- from dataclasses import dataclass, field
12
- from pathlib import Path
13
- from typing import (
14
- Any,
15
- AsyncIterator,
16
- Awaitable,
17
- Callable,
18
- Dict,
19
- List,
20
- Optional,
21
- Sequence,
22
- Tuple,
23
- Union,
24
- )
25
-
26
- from ripperdoc.core.default_tools import get_default_tools
27
- from ripperdoc.core.query import QueryContext, query as _core_query
28
- from ripperdoc.core.permissions import PermissionResult
29
- from ripperdoc.core.system_prompt import build_system_prompt
30
- from ripperdoc.core.skills import build_skill_summary, load_all_skills
31
- from ripperdoc.core.tool import Tool
32
- from ripperdoc.tools.task_tool import TaskTool
33
- from ripperdoc.tools.mcp_tools import load_dynamic_mcp_tools_async, merge_tools_with_dynamic
34
- from ripperdoc.utils.memory import build_memory_instructions
35
- from ripperdoc.utils.messages import (
36
- AssistantMessage,
37
- ProgressMessage,
38
- UserMessage,
39
- create_user_message,
40
- )
41
- from ripperdoc.utils.mcp import (
42
- format_mcp_instructions,
43
- load_mcp_servers_async,
44
- shutdown_mcp_runtime,
45
- )
46
- from ripperdoc.utils.log import get_logger
47
-
48
- MessageType = Union[UserMessage, AssistantMessage, ProgressMessage]
49
- PermissionChecker = Callable[
50
- [Tool[Any, Any], Any],
51
- Union[
52
- PermissionResult,
53
- Dict[str, Any],
54
- Tuple[bool, Optional[str]],
55
- bool,
56
- Awaitable[Union[PermissionResult, Dict[str, Any], Tuple[bool, Optional[str]], bool]],
57
- ],
58
- ]
59
- QueryRunner = Callable[
60
- [
61
- List[MessageType],
62
- str,
63
- Dict[str, str],
64
- QueryContext,
65
- Optional[PermissionChecker],
66
- ],
67
- AsyncIterator[MessageType],
68
- ]
69
-
70
- _END_OF_STREAM = object()
71
-
72
- logger = get_logger()
73
-
74
-
75
- def _coerce_to_path(path: Union[str, Path]) -> Path:
76
- return path if isinstance(path, Path) else Path(path)
77
-
78
-
79
- @dataclass
80
- class RipperdocOptions:
81
- """Configuration for SDK usage."""
82
-
83
- tools: Optional[Sequence[Tool[Any, Any]]] = None
84
- allowed_tools: Optional[Sequence[str]] = None
85
- disallowed_tools: Optional[Sequence[str]] = None
86
- yolo_mode: bool = False
87
- verbose: bool = False
88
- model: str = "main"
89
- max_thinking_tokens: int = 0
90
- context: Dict[str, str] = field(default_factory=dict)
91
- system_prompt: Optional[str] = None
92
- additional_instructions: Optional[Union[str, Sequence[str]]] = None
93
- permission_checker: Optional[PermissionChecker] = None
94
- cwd: Optional[Union[str, Path]] = None
95
-
96
- def build_tools(self) -> List[Tool[Any, Any]]:
97
- """Create the tool set with allow/deny filters applied."""
98
- base_tools = list(self.tools) if self.tools is not None else get_default_tools()
99
- allowed = set(self.allowed_tools) if self.allowed_tools is not None else None
100
- disallowed = set(self.disallowed_tools or [])
101
-
102
- filtered: List[Tool[Any, Any]] = []
103
- for tool in base_tools:
104
- name = getattr(tool, "name", tool.__class__.__name__)
105
- if allowed is not None and name not in allowed:
106
- continue
107
- if name in disallowed:
108
- continue
109
- filtered.append(tool)
110
-
111
- if allowed is not None and not filtered:
112
- raise ValueError("No tools remain after applying allowed_tools/disallowed_tools.")
113
-
114
- # The default Task tool captures the original base tools. If filters are
115
- # applied, recreate it so the subagent only sees the filtered set.
116
- if (self.allowed_tools or self.disallowed_tools) and self.tools is None:
117
- has_task = any(getattr(tool, "name", None) == "Task" for tool in filtered)
118
- if has_task:
119
- filtered_base = [tool for tool in filtered if getattr(tool, "name", None) != "Task"]
120
-
121
- def _filtered_base_provider() -> List[Tool[Any, Any]]:
122
- return filtered_base
123
-
124
- filtered = [
125
- (
126
- TaskTool(_filtered_base_provider)
127
- if getattr(tool, "name", None) == "Task"
128
- else tool
129
- )
130
- for tool in filtered
131
- ]
132
-
133
- return filtered
134
-
135
- def extra_instructions(self) -> List[str]:
136
- """Normalize additional instructions to a list."""
137
- if self.additional_instructions is None:
138
- return []
139
- if isinstance(self.additional_instructions, str):
140
- return [self.additional_instructions]
141
- return [text for text in self.additional_instructions if text]
142
-
143
-
144
- class RipperdocClient:
145
- """Persistent Ripperdoc session with conversation history."""
146
-
147
- def __init__(
148
- self,
149
- options: Optional[RipperdocOptions] = None,
150
- query_runner: Optional[QueryRunner] = None,
151
- ) -> None:
152
- self.options = options or RipperdocOptions()
153
- self._tools = self.options.build_tools()
154
- self._query_runner = query_runner or _core_query
155
-
156
- self._history: List[MessageType] = []
157
- self._queue: asyncio.Queue = asyncio.Queue()
158
- self._current_task: Optional[asyncio.Task] = None
159
- self._current_context: Optional[QueryContext] = None
160
- self._connected = False
161
- self._previous_cwd: Optional[Path] = None
162
-
163
- @property
164
- def tools(self) -> List[Tool[Any, Any]]:
165
- return self._tools
166
-
167
- @property
168
- def history(self) -> List[MessageType]:
169
- return list(self._history)
170
-
171
- async def __aenter__(self) -> "RipperdocClient":
172
- await self.connect()
173
- return self
174
-
175
- async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: # type: ignore[override]
176
- await self.disconnect()
177
-
178
- async def connect(self, prompt: Optional[str] = None) -> None:
179
- """Prepare the session and optionally send an initial prompt."""
180
- if not self._connected:
181
- if self.options.cwd is not None:
182
- self._previous_cwd = Path.cwd()
183
- os.chdir(_coerce_to_path(self.options.cwd))
184
- self._connected = True
185
-
186
- if prompt:
187
- await self.query(prompt)
188
-
189
- async def disconnect(self) -> None:
190
- """Tear down the session and restore the working directory."""
191
- if self._current_context:
192
- self._current_context.abort_controller.set()
193
-
194
- if self._current_task and not self._current_task.done():
195
- self._current_task.cancel()
196
- try:
197
- await self._current_task
198
- except asyncio.CancelledError:
199
- pass
200
-
201
- if self._previous_cwd:
202
- os.chdir(self._previous_cwd)
203
- self._previous_cwd = None
204
-
205
- self._connected = False
206
- await shutdown_mcp_runtime()
207
-
208
- async def query(self, prompt: str) -> None:
209
- """Send a prompt and start streaming the response."""
210
- if self._current_task and not self._current_task.done():
211
- raise RuntimeError(
212
- "A query is already in progress; wait for it to finish or interrupt it."
213
- )
214
-
215
- if not self._connected:
216
- await self.connect()
217
-
218
- self._queue = asyncio.Queue()
219
-
220
- user_message = create_user_message(prompt)
221
- history = list(self._history) + [user_message]
222
- self._history.append(user_message)
223
-
224
- system_prompt = await self._build_system_prompt(prompt)
225
- context = dict(self.options.context)
226
-
227
- query_context = QueryContext(
228
- tools=self._tools,
229
- max_thinking_tokens=self.options.max_thinking_tokens,
230
- yolo_mode=self.options.yolo_mode,
231
- model=self.options.model,
232
- verbose=self.options.verbose,
233
- )
234
- self._current_context = query_context
235
-
236
- async def _runner() -> None:
237
- try:
238
- async for message in self._query_runner(
239
- history,
240
- system_prompt,
241
- context,
242
- query_context,
243
- self.options.permission_checker,
244
- ):
245
- if getattr(message, "type", None) in ("user", "assistant"):
246
- self._history.append(message) # type: ignore[arg-type]
247
- await self._queue.put(message)
248
- finally:
249
- await self._queue.put(_END_OF_STREAM)
250
-
251
- self._current_task = asyncio.create_task(_runner())
252
-
253
- async def receive_messages(self) -> AsyncIterator[MessageType]:
254
- """Yield messages for the active query."""
255
- if self._current_task is None:
256
- raise RuntimeError("No active query to receive messages from.")
257
-
258
- while True:
259
- message = await self._queue.get()
260
- if message is _END_OF_STREAM:
261
- break
262
- yield message # type: ignore[misc]
263
-
264
- async def receive_response(self) -> AsyncIterator[MessageType]:
265
- """Alias for receive_messages."""
266
- async for message in self.receive_messages():
267
- yield message
268
-
269
- async def interrupt(self) -> None:
270
- """Request cancellation of the active query."""
271
- if self._current_context:
272
- self._current_context.abort_controller.set()
273
-
274
- if self._current_task and not self._current_task.done():
275
- self._current_task.cancel()
276
- try:
277
- await self._current_task
278
- except asyncio.CancelledError:
279
- pass
280
-
281
- await self._queue.put(_END_OF_STREAM)
282
-
283
- async def _build_system_prompt(self, user_prompt: str) -> str:
284
- if self.options.system_prompt:
285
- return self.options.system_prompt
286
-
287
- instructions: List[str] = []
288
- project_path = _coerce_to_path(self.options.cwd or Path.cwd())
289
- skill_result = load_all_skills(project_path)
290
- for err in skill_result.errors:
291
- logger.warning(
292
- "[skills] Failed to load skill",
293
- extra={"path": str(err.path), "reason": err.reason},
294
- )
295
- skill_instructions = build_skill_summary(skill_result.skills)
296
- if skill_instructions:
297
- instructions.append(skill_instructions)
298
- instructions.extend(self.options.extra_instructions())
299
- memory = build_memory_instructions()
300
- if memory:
301
- instructions.append(memory)
302
-
303
- dynamic_tools = await load_dynamic_mcp_tools_async(project_path)
304
- if dynamic_tools:
305
- self._tools = merge_tools_with_dynamic(self._tools, dynamic_tools)
306
-
307
- servers = await load_mcp_servers_async(project_path)
308
- mcp_instructions = format_mcp_instructions(servers)
309
-
310
- return build_system_prompt(
311
- self._tools,
312
- user_prompt,
313
- dict(self.options.context),
314
- instructions or None,
315
- mcp_instructions=mcp_instructions,
316
- )
317
-
318
-
319
- async def query(
320
- prompt: str,
321
- options: Optional[RipperdocOptions] = None,
322
- query_runner: Optional[QueryRunner] = None,
323
- ) -> AsyncIterator[MessageType]:
324
- """One-shot helper: run a prompt in a fresh session."""
325
- client = RipperdocClient(options=options, query_runner=query_runner)
326
- await client.connect()
327
- await client.query(prompt)
328
-
329
- try:
330
- async for message in client.receive_messages():
331
- yield message
332
- finally:
333
- await client.disconnect()