ripperdoc 0.3.0__py3-none-any.whl → 0.3.2__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 +9 -1
- ripperdoc/cli/commands/agents_cmd.py +93 -53
- ripperdoc/cli/commands/mcp_cmd.py +3 -0
- ripperdoc/cli/commands/models_cmd.py +768 -283
- ripperdoc/cli/commands/permissions_cmd.py +107 -52
- ripperdoc/cli/commands/resume_cmd.py +61 -51
- ripperdoc/cli/commands/themes_cmd.py +31 -1
- ripperdoc/cli/ui/agents_tui/__init__.py +3 -0
- ripperdoc/cli/ui/agents_tui/textual_app.py +1138 -0
- ripperdoc/cli/ui/choice.py +376 -0
- ripperdoc/cli/ui/interrupt_listener.py +233 -0
- ripperdoc/cli/ui/message_display.py +7 -0
- ripperdoc/cli/ui/models_tui/__init__.py +5 -0
- ripperdoc/cli/ui/models_tui/textual_app.py +698 -0
- ripperdoc/cli/ui/panels.py +19 -4
- ripperdoc/cli/ui/permissions_tui/__init__.py +3 -0
- ripperdoc/cli/ui/permissions_tui/textual_app.py +526 -0
- ripperdoc/cli/ui/provider_options.py +220 -80
- ripperdoc/cli/ui/rich_ui.py +91 -83
- ripperdoc/cli/ui/tips.py +89 -0
- ripperdoc/cli/ui/wizard.py +98 -45
- ripperdoc/core/config.py +3 -0
- ripperdoc/core/permissions.py +66 -104
- ripperdoc/core/providers/anthropic.py +11 -0
- ripperdoc/protocol/stdio.py +3 -1
- ripperdoc/tools/bash_tool.py +2 -0
- ripperdoc/tools/file_edit_tool.py +100 -181
- ripperdoc/tools/file_read_tool.py +101 -25
- ripperdoc/tools/multi_edit_tool.py +239 -91
- ripperdoc/tools/notebook_edit_tool.py +11 -29
- ripperdoc/utils/file_editing.py +164 -0
- ripperdoc/utils/permissions/tool_permission_utils.py +11 -0
- {ripperdoc-0.3.0.dist-info → ripperdoc-0.3.2.dist-info}/METADATA +3 -2
- {ripperdoc-0.3.0.dist-info → ripperdoc-0.3.2.dist-info}/RECORD +39 -30
- ripperdoc/cli/ui/interrupt_handler.py +0 -208
- {ripperdoc-0.3.0.dist-info → ripperdoc-0.3.2.dist-info}/WHEEL +0 -0
- {ripperdoc-0.3.0.dist-info → ripperdoc-0.3.2.dist-info}/entry_points.txt +0 -0
- {ripperdoc-0.3.0.dist-info → ripperdoc-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {ripperdoc-0.3.0.dist-info → ripperdoc-0.3.2.dist-info}/top_level.txt +0 -0
|
@@ -293,6 +293,7 @@ def evaluate_shell_command_permissions(
|
|
|
293
293
|
tool_request: object,
|
|
294
294
|
allowed_rules: Iterable[str],
|
|
295
295
|
denied_rules: Iterable[str],
|
|
296
|
+
ask_rules: Iterable[str],
|
|
296
297
|
allowed_working_dirs: Set[str] | None = None,
|
|
297
298
|
*,
|
|
298
299
|
danger_detector: Callable[[str], bool] | None = None,
|
|
@@ -325,6 +326,7 @@ def evaluate_shell_command_permissions(
|
|
|
325
326
|
|
|
326
327
|
merged_denied = _merge_rules(denied_rules)
|
|
327
328
|
merged_allowed = _merge_rules(allowed_rules)
|
|
329
|
+
merged_ask = _merge_rules(ask_rules)
|
|
328
330
|
|
|
329
331
|
if any(match_rule(trimmed_command, rule) for rule in merged_denied):
|
|
330
332
|
return PermissionDecision(
|
|
@@ -334,6 +336,14 @@ def evaluate_shell_command_permissions(
|
|
|
334
336
|
rule_suggestions=None,
|
|
335
337
|
)
|
|
336
338
|
|
|
339
|
+
if any(match_rule(trimmed_command, rule) for rule in merged_ask):
|
|
340
|
+
return PermissionDecision(
|
|
341
|
+
behavior="ask",
|
|
342
|
+
message="Command requires confirmation by rule.",
|
|
343
|
+
decision_reason={"type": "rule"},
|
|
344
|
+
rule_suggestions=None,
|
|
345
|
+
)
|
|
346
|
+
|
|
337
347
|
if any(match_rule(trimmed_command, rule) for rule in merged_allowed):
|
|
338
348
|
return PermissionDecision(
|
|
339
349
|
behavior="allow",
|
|
@@ -382,6 +392,7 @@ def evaluate_shell_command_permissions(
|
|
|
382
392
|
type("Cmd", (), {"command": left_command}),
|
|
383
393
|
merged_allowed,
|
|
384
394
|
merged_denied,
|
|
395
|
+
merged_ask,
|
|
385
396
|
allowed_working_dirs,
|
|
386
397
|
danger_detector=danger_detector,
|
|
387
398
|
read_only_detector=read_only_detector,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ripperdoc
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: AI-powered terminal assistant for coding tasks
|
|
5
5
|
Author: Ripperdoc Team
|
|
6
6
|
License: Apache-2.0
|
|
@@ -18,6 +18,7 @@ Requires-Dist: anthropic>=0.39.0
|
|
|
18
18
|
Requires-Dist: openai>=1.0.0
|
|
19
19
|
Requires-Dist: click>=8.1.0
|
|
20
20
|
Requires-Dist: rich>=13.0.0
|
|
21
|
+
Requires-Dist: textual>=0.58.0
|
|
21
22
|
Requires-Dist: pydantic>=2.0.0
|
|
22
23
|
Requires-Dist: python-dotenv>=1.0.0
|
|
23
24
|
Requires-Dist: aiofiles>=23.0.0
|
|
@@ -133,7 +134,7 @@ This launches an interactive session where you can:
|
|
|
133
134
|
|
|
134
135
|
### Python SDK (headless)
|
|
135
136
|
|
|
136
|
-
Use Ripperdoc without the terminal UI via the
|
|
137
|
+
Use Ripperdoc without the terminal UI via the Python SDK. See [SDK Documentation](https://ripperdoc-docs.pages.dev/docs/sdk-overview/) for examples of the one-shot `query` helper and the session-based `RipperdocClient`.
|
|
137
138
|
|
|
138
139
|
#### SDK Examples
|
|
139
140
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
ripperdoc/__init__.py,sha256=
|
|
1
|
+
ripperdoc/__init__.py,sha256=CqfRbU6Rs09BsqBC1kVaVGeyPEQk301AZHU5mjNeT_8,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=
|
|
4
|
+
ripperdoc/cli/cli.py,sha256=IIvUs_cFECrKabLfw2aREKdfNN-MvffNo6SkjooDX4c,22068
|
|
5
5
|
ripperdoc/cli/commands/__init__.py,sha256=Up-tTlq3lhxDt-YEdiOoHwfRyIdb8nlL5waECzRg-xY,4858
|
|
6
|
-
ripperdoc/cli/commands/agents_cmd.py,sha256=
|
|
6
|
+
ripperdoc/cli/commands/agents_cmd.py,sha256=aoSb4Qd765zeo3S4JpcruozUc9x7IvX-nIZF8hK2tII,16965
|
|
7
7
|
ripperdoc/cli/commands/base.py,sha256=4KUjxCM04MwbSMUKVNEBph_jeAKPI8b5MHsUFoz7l5g,386
|
|
8
8
|
ripperdoc/cli/commands/clear_cmd.py,sha256=iyOLWtgYwJKNjG-el2mwFRA3VWGIXrNNLl32Xdwpq9o,584
|
|
9
9
|
ripperdoc/cli/commands/compact_cmd.py,sha256=uR_nB1OX7cUL1TOJoefwdO31Qfyjd0nZSSttErqUxbA,473
|
|
@@ -14,38 +14,46 @@ ripperdoc/cli/commands/doctor_cmd.py,sha256=w-AVph1fvmlnbuTeUdAL19zikj4MmNRxmXlh
|
|
|
14
14
|
ripperdoc/cli/commands/exit_cmd.py,sha256=lEGLMVozoOM2Ea_Yw-sbaIvAbfb_Nx3UTpC49ga-MZ8,376
|
|
15
15
|
ripperdoc/cli/commands/help_cmd.py,sha256=jyK6U2bsGEIwFpu08slVHKfxRyS3oblnRXdqSgU_W4w,978
|
|
16
16
|
ripperdoc/cli/commands/hooks_cmd.py,sha256=-ixQhKb1CX2c7_2zDdAqXV9ThnMf31UH3a9UBERD9mw,21026
|
|
17
|
-
ripperdoc/cli/commands/mcp_cmd.py,sha256=
|
|
17
|
+
ripperdoc/cli/commands/mcp_cmd.py,sha256=asOlZTCrMzAI03x_Qvc6JCk9fw1vPaXGpCCSckSyMqI,2599
|
|
18
18
|
ripperdoc/cli/commands/memory_cmd.py,sha256=BsYYUosmnIs92mwgHH1e3DdzOj83iJ5_AJ8kBeSkRCw,6594
|
|
19
|
-
ripperdoc/cli/commands/models_cmd.py,sha256=
|
|
20
|
-
ripperdoc/cli/commands/permissions_cmd.py,sha256=
|
|
21
|
-
ripperdoc/cli/commands/resume_cmd.py,sha256=
|
|
19
|
+
ripperdoc/cli/commands/models_cmd.py,sha256=6NJJgCohLMlhC7svRrObiT_Dvf-Vm4q3Oqf3Y5UNm64,37462
|
|
20
|
+
ripperdoc/cli/commands/permissions_cmd.py,sha256=DODGrlX4UuyYvUwQaCw9EAGZM6cjDlWN9Wjwt2Ooc_0,13176
|
|
21
|
+
ripperdoc/cli/commands/resume_cmd.py,sha256=GljVqNPgdyGA2wTLDNQLlT3vlWaafxk6ZpGTl1DZMnU,4485
|
|
22
22
|
ripperdoc/cli/commands/skills_cmd.py,sha256=OFDLy2g_o_GDzdatk18E-Xv4bhVG80MQUr_PQLmTveQ,3496
|
|
23
23
|
ripperdoc/cli/commands/stats_cmd.py,sha256=I6nFZndBy_VRcUAxXAjplSjIzA-5UqRK4HlQJbpHydg,7857
|
|
24
24
|
ripperdoc/cli/commands/status_cmd.py,sha256=9pI_GF4mxR9cQXF-HEURZb0qO6GWp2hcy3rzaVW98II,5962
|
|
25
25
|
ripperdoc/cli/commands/tasks_cmd.py,sha256=tghFKy0ENeJ9Mf0h5Waceq8l9JXP5xTLsJHP4ZkibbQ,9044
|
|
26
|
-
ripperdoc/cli/commands/themes_cmd.py,sha256=
|
|
26
|
+
ripperdoc/cli/commands/themes_cmd.py,sha256=MoNFQlWlj3KcNp7UAVSwr_5rGvuLSBbRKdrfBrUQR1U,5033
|
|
27
27
|
ripperdoc/cli/commands/todos_cmd.py,sha256=7Q0B1NVqGtB3R29ndbn4m0VQQm-YQ7d4Wlk7vJ7dLQI,1848
|
|
28
28
|
ripperdoc/cli/commands/tools_cmd.py,sha256=3cMi0vN4mAUhpKqJtRgNvZfcKzRPaMs_pkYYXlyvSSU,384
|
|
29
29
|
ripperdoc/cli/ui/__init__.py,sha256=TxSzTYdITlrYmYVfins_w_jzPqqWRpqky5u1ikwvmtM,43
|
|
30
|
+
ripperdoc/cli/ui/choice.py,sha256=_tsU4hGlW5EhteELvzGxyxscFn2WvIh4RpbTd2Sh8zg,11984
|
|
30
31
|
ripperdoc/cli/ui/context_display.py,sha256=3ezdtHVwltkPQ5etYwfqUh-fjnpPu8B3P81UzrdHxZs,10020
|
|
31
32
|
ripperdoc/cli/ui/file_mention_completer.py,sha256=ysNqZieQVlUb7DC4CP_FMKNeTG8AIqKLmJgl6HJBURo,13649
|
|
32
33
|
ripperdoc/cli/ui/helpers.py,sha256=iM7kMb-fMTO6n4_MDVrESE1P-Y7w8PXiIhCCLp5yyA4,2618
|
|
33
|
-
ripperdoc/cli/ui/
|
|
34
|
-
ripperdoc/cli/ui/message_display.py,sha256=
|
|
35
|
-
ripperdoc/cli/ui/panels.py,sha256=
|
|
36
|
-
ripperdoc/cli/ui/provider_options.py,sha256=
|
|
37
|
-
ripperdoc/cli/ui/rich_ui.py,sha256=
|
|
34
|
+
ripperdoc/cli/ui/interrupt_listener.py,sha256=m9NeyOSJ3f-zuLE4YzUUEoKuuxo7oBKuvY5Z1WabjoE,7223
|
|
35
|
+
ripperdoc/cli/ui/message_display.py,sha256=eaS80PBo9QfgWT-MX4jfQOjbt0vwQ4w1MDGKBWPrQOs,10856
|
|
36
|
+
ripperdoc/cli/ui/panels.py,sha256=hP_kviv1FvwsXB27VLbvU0O6IahCdrdTHWHCF8Qz7Ow,2523
|
|
37
|
+
ripperdoc/cli/ui/provider_options.py,sha256=GwbeywCyIY_TwzdhZVeEfw_PVv1i1ugfSBpxYfAeyE0,13143
|
|
38
|
+
ripperdoc/cli/ui/rich_ui.py,sha256=VZnki6UpWH3-Rz1VsCSJQUAGEn1igjEhO8SLr7x4hBI,76910
|
|
38
39
|
ripperdoc/cli/ui/spinner.py,sha256=IlmMgyk-eA6Bk4fXDjbWs3AFL2iFPeWmnS0q28tWOgU,5175
|
|
39
40
|
ripperdoc/cli/ui/thinking_spinner.py,sha256=3zmxj3vd-1njdiHF2Afsm7RGiRl7645AEc7fTLKgAbU,2805
|
|
41
|
+
ripperdoc/cli/ui/tips.py,sha256=wJYIBZr1wKyzPZpnkUHiv3fzfi9YExMZnZs9K83AlD8,3713
|
|
40
42
|
ripperdoc/cli/ui/tool_renderers.py,sha256=7ACcZoKU91kvFsk3YdP2TzibfI-W_F1dv-Ksr68rrYE,11280
|
|
41
|
-
ripperdoc/cli/ui/wizard.py,sha256=
|
|
43
|
+
ripperdoc/cli/ui/wizard.py,sha256=lBZcsJNuHo6Lc2OLTWoakzII3f_Z6NoESVJcnGqi91w,9094
|
|
44
|
+
ripperdoc/cli/ui/agents_tui/__init__.py,sha256=5MvUBb9KhvoMxe2Rxma0Y39zIcAuAr3HdQLcsyWVMnI,70
|
|
45
|
+
ripperdoc/cli/ui/agents_tui/textual_app.py,sha256=c3e7gW8Pe0WS2eb9LpE6B548ZCRXhpKUQNSaeeQBefw,40585
|
|
46
|
+
ripperdoc/cli/ui/models_tui/__init__.py,sha256=1zQW3IJZLq_yzNayZ27SO71H45wGRNojts-_gsfY8YI,103
|
|
47
|
+
ripperdoc/cli/ui/models_tui/textual_app.py,sha256=VfXNokxGRfVidnXPphNRqZRKXH2RYMzQT8pY_b90KbU,24793
|
|
48
|
+
ripperdoc/cli/ui/permissions_tui/__init__.py,sha256=EDwWp9t07M1zGvxsT57XDWlU1Ijg7-4Vq-YhYoS04CU,80
|
|
49
|
+
ripperdoc/cli/ui/permissions_tui/textual_app.py,sha256=Eh8MBKosaPWV-AGkKbwuzMQazBY5iqSzjQe3SM9lr1E,17690
|
|
42
50
|
ripperdoc/core/__init__.py,sha256=UemJCA-Y8df1466AX-YbRFj071zKajmqO1mi40YVW2g,40
|
|
43
51
|
ripperdoc/core/agents.py,sha256=Xl1HEJz8v-xIEhzgObeysgrz-SQBYbxYVRI4CvRl-w8,20539
|
|
44
52
|
ripperdoc/core/commands.py,sha256=NXCkljYbAP4dDoRy-_3semFNWxG4YAk9q82u8FTKH60,835
|
|
45
|
-
ripperdoc/core/config.py,sha256=
|
|
53
|
+
ripperdoc/core/config.py,sha256=8Bu9NurOE3QKooURfB3LwccR1kCB_gD2DUQ-TDDZH5w,30117
|
|
46
54
|
ripperdoc/core/custom_commands.py,sha256=2BMYiBq15FDjl3aOa3styN6nARyfU7xFAb4ly2Vsp-w,14254
|
|
47
55
|
ripperdoc/core/default_tools.py,sha256=mpb9mqmrAjMqgcmpRU_uQF1G6vpYbp8nkiwrh71KQyM,5250
|
|
48
|
-
ripperdoc/core/permissions.py,sha256=
|
|
56
|
+
ripperdoc/core/permissions.py,sha256=IWd8j1g9xYLGx0-Xj_sUBuJynm2lrdHaVHPVjyPFwNY,16379
|
|
49
57
|
ripperdoc/core/query.py,sha256=dC_sW2D2bPLI6nPM3VxFpTBA4fYYHHRNmE6zvy93pus,62057
|
|
50
58
|
ripperdoc/core/query_utils.py,sha256=2OQYeobFD-24NG0CZnnm3D5HCJ7r8AOB08I-wpQhH1A,24888
|
|
51
59
|
ripperdoc/core/skills.py,sha256=NGP_QFzL2SBkz8wZBGxNA_6Gt8qkRIZtJDnPs_DayxE,10511
|
|
@@ -60,23 +68,23 @@ ripperdoc/core/hooks/integration.py,sha256=IzkOSpaMjC397zKdKO1jTR0uyzOet-eCwPLuX
|
|
|
60
68
|
ripperdoc/core/hooks/llm_callback.py,sha256=TsttZ8Q7c8-36ebbI8nTyYcKbA7Kr9rmY5UO2gzuL-c,1733
|
|
61
69
|
ripperdoc/core/hooks/manager.py,sha256=dfHhB8f5US8ZN2a0JbzD5bQZHsnBS6Zs8xVZ2IcSsrg,25092
|
|
62
70
|
ripperdoc/core/providers/__init__.py,sha256=yevsHF0AUI4b6Wiq_401NXewJ3dqe8LUUtQm0TLPPNQ,1911
|
|
63
|
-
ripperdoc/core/providers/anthropic.py,sha256=
|
|
71
|
+
ripperdoc/core/providers/anthropic.py,sha256=idVSfx63ohMNSZqtIG7W589RL_8x_LbhdaRR_7e9Z4w,29256
|
|
64
72
|
ripperdoc/core/providers/base.py,sha256=HNOa3_XWszu6DbI8BYixxV0hnZb9qZ_FU4uinFVRHjU,9271
|
|
65
73
|
ripperdoc/core/providers/gemini.py,sha256=Fs-dShsmIVBFfz-jg4fBjvQyrxVnZ5yx4ALcES-l5Sg,27089
|
|
66
74
|
ripperdoc/core/providers/openai.py,sha256=BhvDvkDjeYJMbvI4mbswQXbjdab53oJduku-Yja1zmQ,25561
|
|
67
75
|
ripperdoc/protocol/__init__.py,sha256=rSYu_Y2acRfsAE8gUTG82VnyrK3AFrIfwuJBf2cjHjs,325
|
|
68
76
|
ripperdoc/protocol/models.py,sha256=goNurNKI0eGZeCGmr7KqMPkZMvMOABXWsb-8un0LDlc,7468
|
|
69
|
-
ripperdoc/protocol/stdio.py,sha256=
|
|
77
|
+
ripperdoc/protocol/stdio.py,sha256=6zlWq6HfHVnftPYmFIyfrkWCeHaub2Dguv1vn0dhiPk,59074
|
|
70
78
|
ripperdoc/tools/__init__.py,sha256=RBFz0DDnztDXMqv_zRxFHVY-ez2HYcncx8zh_y-BX6w,42
|
|
71
79
|
ripperdoc/tools/ask_user_question_tool.py,sha256=ZWg5xAdeaRoR98KvvPuKmJH4L2dgzH87VXM4dxKcqBE,15478
|
|
72
80
|
ripperdoc/tools/background_shell.py,sha256=VMJd0G7s1l7qSRvlkRaIbgWmj4N0U_sr0kLzBzVLO2A,21263
|
|
73
81
|
ripperdoc/tools/bash_output_tool.py,sha256=cC5dDmKYmkOTsLCXCcTYgc0loVWtmRobPn0C-I6qO-o,3379
|
|
74
|
-
ripperdoc/tools/bash_tool.py,sha256=
|
|
82
|
+
ripperdoc/tools/bash_tool.py,sha256=lDDdD4c8pcTXs8uH3PLUkLwnzJQ8gZyW5ubKPi3BrZA,47686
|
|
75
83
|
ripperdoc/tools/dynamic_mcp_tool.py,sha256=kxxWhp6pP9N8fK3ubu5fHQYQv7aSwxcnaa3C9ZsBbOU,15879
|
|
76
84
|
ripperdoc/tools/enter_plan_mode_tool.py,sha256=LNQv43uWkohiQTYQdsrKbpAfQSJNE_FJ9Y6AM_ckjng,7976
|
|
77
85
|
ripperdoc/tools/exit_plan_mode_tool.py,sha256=XxhEih5EUrcscvkRA9lel54XoVIhl_cCL2xcvMJjtVA,5756
|
|
78
|
-
ripperdoc/tools/file_edit_tool.py,sha256=
|
|
79
|
-
ripperdoc/tools/file_read_tool.py,sha256=
|
|
86
|
+
ripperdoc/tools/file_edit_tool.py,sha256=NZ2X07jd33H2Lv7sXVtl9YxFBg6NsJoCrwhorK1sMqU,17637
|
|
87
|
+
ripperdoc/tools/file_read_tool.py,sha256=KbKlfMaWU3KSp-2FSY43sIGPoRo9HGJBLq0P31Mc--Q,16504
|
|
80
88
|
ripperdoc/tools/file_write_tool.py,sha256=HqOkFYedacNsPatKxVhth0kI5fGh83tm2VxdD_pO-V0,8570
|
|
81
89
|
ripperdoc/tools/glob_tool.py,sha256=eZG4fzahjJsSM8NdmTiVl5nBfDQK7egPg6P7cqOM_1Y,5948
|
|
82
90
|
ripperdoc/tools/grep_tool.py,sha256=bqrliI6e-9cNKIOfxdCv6mBwHr_1BSLwAPSTbOTc4B4,18239
|
|
@@ -84,8 +92,8 @@ ripperdoc/tools/kill_bash_tool.py,sha256=_jwnJVCPe8uXZTJd7myh4hWCD-eBuB2XDaYwRCm
|
|
|
84
92
|
ripperdoc/tools/ls_tool.py,sha256=JWQucgNOLjrGZwM7imu3GWe5YFwXxdXGoaBr50wDZCQ,15367
|
|
85
93
|
ripperdoc/tools/lsp_tool.py,sha256=atpaaMqf3jld7SyNXtfdM6NkxYrp3KbYZ91oC4EqQlU,21315
|
|
86
94
|
ripperdoc/tools/mcp_tools.py,sha256=BVz8MJhijNnHq1J2LLkZS89wp_pwwnOytH1HYcfkFqQ,23085
|
|
87
|
-
ripperdoc/tools/multi_edit_tool.py,sha256=
|
|
88
|
-
ripperdoc/tools/notebook_edit_tool.py,sha256=
|
|
95
|
+
ripperdoc/tools/multi_edit_tool.py,sha256=47s98DFQJENQxoWQf435_OgKJXwzAEWE5tQeH5drgBk,24430
|
|
96
|
+
ripperdoc/tools/notebook_edit_tool.py,sha256=viW4g-5EUfRiabKI0-c3rKz1tHDrvOVd7tLrX8Y0uck,13755
|
|
89
97
|
ripperdoc/tools/skill_tool.py,sha256=8TcmNxpSGiBXd-ipgoD9BIopKkR13-3U56rkzozazhY,9763
|
|
90
98
|
ripperdoc/tools/task_tool.py,sha256=SQW2yLM0yectI0dVBoaDF0Tp5T4yOYqvHpsFWapOE44,33979
|
|
91
99
|
ripperdoc/tools/todo_tool.py,sha256=eIwF-s1117DrXJ4yXUMwkNs1gfKYNF2OlWzkJAXDzmk,20001
|
|
@@ -97,6 +105,7 @@ ripperdoc/utils/coerce.py,sha256=KOPb4KR4p32nwHWG_6GsGHeVZunJyYc2YhC5DLmEZO8,101
|
|
|
97
105
|
ripperdoc/utils/context_length_errors.py,sha256=oyDVr_ME_6j97TLwVZ8bDMb6ISGQx6wEHrY7ckc0GuA,7714
|
|
98
106
|
ripperdoc/utils/conversation_compaction.py,sha256=mbOYwQXXsAqiJ7mBQ25195bXtH3tb5GSU0HUNsvnl1g,18345
|
|
99
107
|
ripperdoc/utils/exit_code_handlers.py,sha256=QtO1iDxVAb8Xp03D6_QixPoJC-RQlcp3ssIo_rm4two,7973
|
|
108
|
+
ripperdoc/utils/file_editing.py,sha256=9wxPVTZ42cNQEImOIlZPL0dDWLl1ps3zvTyL-G6opBA,5049
|
|
100
109
|
ripperdoc/utils/file_watch.py,sha256=Oqx5elgE3_z6CSAKdTbIPna_E4SePBqDujpqqANcw9U,11823
|
|
101
110
|
ripperdoc/utils/git_utils.py,sha256=Hq-Zx-KPyX4lp_i8ozhic15LyYdX_IfCRm-EyoFu59A,9047
|
|
102
111
|
ripperdoc/utils/image_utils.py,sha256=52YRLN0NwPEHnL3-smYktx2T8Oh002W0nvZsxI-WFdo,3380
|
|
@@ -127,10 +136,10 @@ ripperdoc/utils/token_estimation.py,sha256=qPQbeUwVlafEjzsXw6qMo0hd4Vjb1gCUMAPBo
|
|
|
127
136
|
ripperdoc/utils/permissions/__init__.py,sha256=33FfOaDLepxJSkp0RLvTdVu7qBXuEcnOoTHFbEtFOt0,653
|
|
128
137
|
ripperdoc/utils/permissions/path_validation_utils.py,sha256=KOegjWaph8tXU7aqwQXRAxFEzrmRuPvdLb36J1QIPDQ,5772
|
|
129
138
|
ripperdoc/utils/permissions/shell_command_validation.py,sha256=BRi-1OGzr-Wiuxa00Ye6oqPctoJUZBOoRoi6dUiAsfM,33256
|
|
130
|
-
ripperdoc/utils/permissions/tool_permission_utils.py,sha256=
|
|
131
|
-
ripperdoc-0.3.
|
|
132
|
-
ripperdoc-0.3.
|
|
133
|
-
ripperdoc-0.3.
|
|
134
|
-
ripperdoc-0.3.
|
|
135
|
-
ripperdoc-0.3.
|
|
136
|
-
ripperdoc-0.3.
|
|
139
|
+
ripperdoc/utils/permissions/tool_permission_utils.py,sha256=nTDi9BPVJbreJQN5q8PNDB_Sn9eEAtMdWpl-2moJFXU,15035
|
|
140
|
+
ripperdoc-0.3.2.dist-info/licenses/LICENSE,sha256=bRv9UhBor6GhnQDj12RciDcRfu0R7sB-lqCy1sWF75c,9242
|
|
141
|
+
ripperdoc-0.3.2.dist-info/METADATA,sha256=8Qc_5GFKNORVSSpAS0I44jDUCtGKEyr8pO9fMLULlJQ,8648
|
|
142
|
+
ripperdoc-0.3.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
143
|
+
ripperdoc-0.3.2.dist-info/entry_points.txt,sha256=79aohFxFPJmrQ3-Mhain04vb3EWpuc0EyzvDDUnwAu4,81
|
|
144
|
+
ripperdoc-0.3.2.dist-info/top_level.txt,sha256=u8LbdTr1a-laHgCO0Utl_R3QGFUhLxWelCDnP2ZgpCU,10
|
|
145
|
+
ripperdoc-0.3.2.dist-info/RECORD,,
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
"""Interrupt handling for RichUI.
|
|
2
|
-
|
|
3
|
-
This module handles ESC/Ctrl+C key detection during query execution,
|
|
4
|
-
including terminal raw mode management.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import asyncio
|
|
8
|
-
import contextlib
|
|
9
|
-
import sys
|
|
10
|
-
from typing import Any, Optional, Set
|
|
11
|
-
|
|
12
|
-
from ripperdoc.utils.log import get_logger
|
|
13
|
-
from ripperdoc.utils.platform import is_windows
|
|
14
|
-
|
|
15
|
-
logger = get_logger()
|
|
16
|
-
|
|
17
|
-
# Keys that trigger interrupt
|
|
18
|
-
INTERRUPT_KEYS: Set[str] = {"\x1b", "\x03"} # ESC, Ctrl+C
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class InterruptHandler:
|
|
22
|
-
"""Handles keyboard interrupt detection during async operations."""
|
|
23
|
-
|
|
24
|
-
def __init__(self) -> None:
|
|
25
|
-
"""Initialize the interrupt handler."""
|
|
26
|
-
self._query_interrupted: bool = False
|
|
27
|
-
self._esc_listener_active: bool = False
|
|
28
|
-
self._esc_listener_paused: bool = False
|
|
29
|
-
self._stdin_fd: Optional[int] = None
|
|
30
|
-
self._stdin_old_settings: Optional[list] = None
|
|
31
|
-
self._stdin_in_raw_mode: bool = False
|
|
32
|
-
self._abort_callback: Optional[Any] = None
|
|
33
|
-
|
|
34
|
-
def set_abort_callback(self, callback: Any) -> None:
|
|
35
|
-
"""Set the callback to trigger when interrupt is detected."""
|
|
36
|
-
self._abort_callback = callback
|
|
37
|
-
|
|
38
|
-
@property
|
|
39
|
-
def was_interrupted(self) -> bool:
|
|
40
|
-
"""Check if the last query was interrupted."""
|
|
41
|
-
return self._query_interrupted
|
|
42
|
-
|
|
43
|
-
def pause_listener(self) -> bool:
|
|
44
|
-
"""Pause ESC listener and restore cooked terminal mode if we own raw mode.
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
Previous paused state for later restoration.
|
|
48
|
-
"""
|
|
49
|
-
prev = self._esc_listener_paused
|
|
50
|
-
self._esc_listener_paused = True
|
|
51
|
-
|
|
52
|
-
# Windows doesn't support termios
|
|
53
|
-
if is_windows():
|
|
54
|
-
return prev
|
|
55
|
-
|
|
56
|
-
try:
|
|
57
|
-
import termios
|
|
58
|
-
except ImportError:
|
|
59
|
-
return prev
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
self._stdin_fd is not None
|
|
63
|
-
and self._stdin_old_settings is not None
|
|
64
|
-
and self._stdin_in_raw_mode
|
|
65
|
-
):
|
|
66
|
-
with contextlib.suppress(OSError, termios.error, ValueError):
|
|
67
|
-
termios.tcsetattr(self._stdin_fd, termios.TCSADRAIN, self._stdin_old_settings)
|
|
68
|
-
self._stdin_in_raw_mode = False
|
|
69
|
-
return prev
|
|
70
|
-
|
|
71
|
-
def resume_listener(self, previous_state: bool) -> None:
|
|
72
|
-
"""Restore paused state to what it was before a blocking prompt."""
|
|
73
|
-
self._esc_listener_paused = previous_state
|
|
74
|
-
|
|
75
|
-
async def _listen_for_interrupt_key(self) -> bool:
|
|
76
|
-
"""Listen for interrupt keys (ESC/Ctrl+C) during query execution.
|
|
77
|
-
|
|
78
|
-
Uses raw terminal mode for immediate key detection without waiting
|
|
79
|
-
for escape sequences to complete.
|
|
80
|
-
|
|
81
|
-
Returns:
|
|
82
|
-
True if an interrupt key was pressed.
|
|
83
|
-
"""
|
|
84
|
-
if is_windows():
|
|
85
|
-
# Windows: use msvcrt for non-blocking key detection
|
|
86
|
-
try:
|
|
87
|
-
import msvcrt
|
|
88
|
-
except ImportError:
|
|
89
|
-
# Fallback: just wait - Ctrl+C is handled by OS
|
|
90
|
-
while self._esc_listener_active:
|
|
91
|
-
await asyncio.sleep(0.1)
|
|
92
|
-
return False
|
|
93
|
-
|
|
94
|
-
while self._esc_listener_active:
|
|
95
|
-
if self._esc_listener_paused:
|
|
96
|
-
await asyncio.sleep(0.05)
|
|
97
|
-
continue
|
|
98
|
-
|
|
99
|
-
# Check for key press in a thread to avoid blocking
|
|
100
|
-
def check_key() -> Optional[str]:
|
|
101
|
-
if msvcrt.kbhit(): # type: ignore[attr-defined]
|
|
102
|
-
return msvcrt.getch().decode("latin-1") # type: ignore[attr-defined,no-any-return]
|
|
103
|
-
return None
|
|
104
|
-
|
|
105
|
-
key = await asyncio.to_thread(check_key)
|
|
106
|
-
if key in INTERRUPT_KEYS:
|
|
107
|
-
return True
|
|
108
|
-
|
|
109
|
-
await asyncio.sleep(0.02)
|
|
110
|
-
return False
|
|
111
|
-
|
|
112
|
-
import select
|
|
113
|
-
import termios
|
|
114
|
-
import tty
|
|
115
|
-
|
|
116
|
-
try:
|
|
117
|
-
fd = sys.stdin.fileno()
|
|
118
|
-
old_settings = termios.tcgetattr(fd)
|
|
119
|
-
except (OSError, termios.error, ValueError):
|
|
120
|
-
return False
|
|
121
|
-
|
|
122
|
-
self._stdin_fd = fd
|
|
123
|
-
self._stdin_old_settings = old_settings
|
|
124
|
-
raw_enabled = False
|
|
125
|
-
try:
|
|
126
|
-
while self._esc_listener_active:
|
|
127
|
-
if self._esc_listener_paused:
|
|
128
|
-
if raw_enabled:
|
|
129
|
-
with contextlib.suppress(OSError, termios.error, ValueError):
|
|
130
|
-
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
131
|
-
raw_enabled = False
|
|
132
|
-
self._stdin_in_raw_mode = False
|
|
133
|
-
await asyncio.sleep(0.05)
|
|
134
|
-
continue
|
|
135
|
-
|
|
136
|
-
if not raw_enabled:
|
|
137
|
-
tty.setraw(fd)
|
|
138
|
-
raw_enabled = True
|
|
139
|
-
self._stdin_in_raw_mode = True
|
|
140
|
-
|
|
141
|
-
await asyncio.sleep(0.02)
|
|
142
|
-
if select.select([sys.stdin], [], [], 0)[0]:
|
|
143
|
-
if sys.stdin.read(1) in INTERRUPT_KEYS:
|
|
144
|
-
return True
|
|
145
|
-
except (OSError, ValueError):
|
|
146
|
-
pass
|
|
147
|
-
finally:
|
|
148
|
-
self._stdin_in_raw_mode = False
|
|
149
|
-
with contextlib.suppress(OSError, termios.error, ValueError):
|
|
150
|
-
if raw_enabled:
|
|
151
|
-
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
152
|
-
self._stdin_fd = None
|
|
153
|
-
self._stdin_old_settings = None
|
|
154
|
-
|
|
155
|
-
return False
|
|
156
|
-
|
|
157
|
-
async def _cancel_task(self, task: asyncio.Task) -> None:
|
|
158
|
-
"""Cancel a task and wait for it to finish."""
|
|
159
|
-
if not task.done():
|
|
160
|
-
task.cancel()
|
|
161
|
-
with contextlib.suppress(asyncio.CancelledError):
|
|
162
|
-
await task
|
|
163
|
-
|
|
164
|
-
def _trigger_abort(self) -> None:
|
|
165
|
-
"""Signal the query to abort via callback."""
|
|
166
|
-
if self._abort_callback is not None:
|
|
167
|
-
self._abort_callback()
|
|
168
|
-
|
|
169
|
-
async def run_with_interrupt(self, query_coro: Any) -> bool:
|
|
170
|
-
"""Run a coroutine with ESC key interrupt support.
|
|
171
|
-
|
|
172
|
-
Args:
|
|
173
|
-
query_coro: The coroutine to run with interrupt support.
|
|
174
|
-
|
|
175
|
-
Returns:
|
|
176
|
-
True if interrupted, False if completed normally.
|
|
177
|
-
"""
|
|
178
|
-
self._query_interrupted = False
|
|
179
|
-
self._esc_listener_active = True
|
|
180
|
-
|
|
181
|
-
query_task = asyncio.create_task(query_coro)
|
|
182
|
-
interrupt_task = asyncio.create_task(self._listen_for_interrupt_key())
|
|
183
|
-
|
|
184
|
-
try:
|
|
185
|
-
done, _ = await asyncio.wait(
|
|
186
|
-
{query_task, interrupt_task}, return_when=asyncio.FIRST_COMPLETED
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
# Check if interrupted
|
|
190
|
-
if interrupt_task in done and interrupt_task.result():
|
|
191
|
-
self._query_interrupted = True
|
|
192
|
-
self._trigger_abort()
|
|
193
|
-
await self._cancel_task(query_task)
|
|
194
|
-
return True
|
|
195
|
-
|
|
196
|
-
# Query completed normally
|
|
197
|
-
if query_task in done:
|
|
198
|
-
await self._cancel_task(interrupt_task)
|
|
199
|
-
with contextlib.suppress(Exception):
|
|
200
|
-
query_task.result()
|
|
201
|
-
return False
|
|
202
|
-
|
|
203
|
-
return False
|
|
204
|
-
|
|
205
|
-
finally:
|
|
206
|
-
self._esc_listener_active = False
|
|
207
|
-
await self._cancel_task(query_task)
|
|
208
|
-
await self._cancel_task(interrupt_task)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|