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.
- ripperdoc/__init__.py +1 -1
- ripperdoc/cli/cli.py +379 -51
- ripperdoc/cli/commands/__init__.py +6 -0
- ripperdoc/cli/commands/agents_cmd.py +128 -5
- ripperdoc/cli/commands/clear_cmd.py +8 -0
- ripperdoc/cli/commands/doctor_cmd.py +29 -0
- ripperdoc/cli/commands/exit_cmd.py +1 -0
- ripperdoc/cli/commands/memory_cmd.py +2 -1
- ripperdoc/cli/commands/models_cmd.py +63 -7
- ripperdoc/cli/commands/resume_cmd.py +5 -0
- ripperdoc/cli/commands/skills_cmd.py +103 -0
- ripperdoc/cli/commands/stats_cmd.py +244 -0
- ripperdoc/cli/commands/status_cmd.py +10 -0
- ripperdoc/cli/commands/tasks_cmd.py +6 -3
- ripperdoc/cli/commands/themes_cmd.py +139 -0
- ripperdoc/cli/ui/file_mention_completer.py +63 -13
- ripperdoc/cli/ui/helpers.py +6 -3
- ripperdoc/cli/ui/interrupt_handler.py +34 -0
- ripperdoc/cli/ui/panels.py +14 -8
- ripperdoc/cli/ui/rich_ui.py +737 -47
- ripperdoc/cli/ui/spinner.py +93 -18
- ripperdoc/cli/ui/thinking_spinner.py +1 -2
- ripperdoc/cli/ui/tool_renderers.py +10 -9
- ripperdoc/cli/ui/wizard.py +24 -19
- ripperdoc/core/agents.py +14 -3
- ripperdoc/core/config.py +238 -6
- ripperdoc/core/default_tools.py +91 -10
- ripperdoc/core/hooks/events.py +4 -0
- ripperdoc/core/hooks/llm_callback.py +58 -0
- ripperdoc/core/hooks/manager.py +6 -0
- ripperdoc/core/permissions.py +160 -9
- ripperdoc/core/providers/openai.py +84 -28
- ripperdoc/core/query.py +489 -87
- ripperdoc/core/query_utils.py +17 -14
- ripperdoc/core/skills.py +1 -0
- ripperdoc/core/theme.py +298 -0
- ripperdoc/core/tool.py +15 -5
- ripperdoc/protocol/__init__.py +14 -0
- ripperdoc/protocol/models.py +300 -0
- ripperdoc/protocol/stdio.py +1453 -0
- ripperdoc/tools/background_shell.py +354 -139
- ripperdoc/tools/bash_tool.py +117 -22
- ripperdoc/tools/file_edit_tool.py +228 -50
- ripperdoc/tools/file_read_tool.py +154 -3
- ripperdoc/tools/file_write_tool.py +53 -11
- ripperdoc/tools/grep_tool.py +98 -8
- ripperdoc/tools/lsp_tool.py +609 -0
- ripperdoc/tools/multi_edit_tool.py +26 -3
- ripperdoc/tools/skill_tool.py +52 -1
- ripperdoc/tools/task_tool.py +539 -65
- ripperdoc/utils/conversation_compaction.py +1 -1
- ripperdoc/utils/file_watch.py +216 -7
- ripperdoc/utils/image_utils.py +125 -0
- ripperdoc/utils/log.py +30 -3
- ripperdoc/utils/lsp.py +812 -0
- ripperdoc/utils/mcp.py +80 -18
- ripperdoc/utils/message_formatting.py +7 -4
- ripperdoc/utils/messages.py +198 -33
- ripperdoc/utils/pending_messages.py +50 -0
- ripperdoc/utils/permissions/shell_command_validation.py +3 -3
- ripperdoc/utils/permissions/tool_permission_utils.py +180 -15
- ripperdoc/utils/platform.py +198 -0
- ripperdoc/utils/session_heatmap.py +242 -0
- ripperdoc/utils/session_history.py +2 -2
- ripperdoc/utils/session_stats.py +294 -0
- ripperdoc/utils/shell_utils.py +8 -5
- ripperdoc/utils/todo.py +0 -6
- {ripperdoc-0.2.9.dist-info → ripperdoc-0.3.0.dist-info}/METADATA +55 -17
- ripperdoc-0.3.0.dist-info/RECORD +136 -0
- {ripperdoc-0.2.9.dist-info → ripperdoc-0.3.0.dist-info}/WHEEL +1 -1
- ripperdoc/sdk/__init__.py +0 -9
- ripperdoc/sdk/client.py +0 -333
- ripperdoc-0.2.9.dist-info/RECORD +0 -123
- {ripperdoc-0.2.9.dist-info → ripperdoc-0.3.0.dist-info}/entry_points.txt +0 -0
- {ripperdoc-0.2.9.dist-info → ripperdoc-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {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.
|
|
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.
|
|
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
|
|
97
|
-
cd
|
|
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
|
|
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
|
|
147
|
-
- Project skills
|
|
148
|
-
- Each `SKILL.md` starts with YAML frontmatter
|
|
149
|
-
-
|
|
150
|
-
-
|
|
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,,
|
ripperdoc/sdk/__init__.py
DELETED
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()
|