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.
Files changed (40) hide show
  1. ripperdoc/__init__.py +1 -1
  2. ripperdoc/cli/cli.py +9 -1
  3. ripperdoc/cli/commands/agents_cmd.py +93 -53
  4. ripperdoc/cli/commands/mcp_cmd.py +3 -0
  5. ripperdoc/cli/commands/models_cmd.py +768 -283
  6. ripperdoc/cli/commands/permissions_cmd.py +107 -52
  7. ripperdoc/cli/commands/resume_cmd.py +61 -51
  8. ripperdoc/cli/commands/themes_cmd.py +31 -1
  9. ripperdoc/cli/ui/agents_tui/__init__.py +3 -0
  10. ripperdoc/cli/ui/agents_tui/textual_app.py +1138 -0
  11. ripperdoc/cli/ui/choice.py +376 -0
  12. ripperdoc/cli/ui/interrupt_listener.py +233 -0
  13. ripperdoc/cli/ui/message_display.py +7 -0
  14. ripperdoc/cli/ui/models_tui/__init__.py +5 -0
  15. ripperdoc/cli/ui/models_tui/textual_app.py +698 -0
  16. ripperdoc/cli/ui/panels.py +19 -4
  17. ripperdoc/cli/ui/permissions_tui/__init__.py +3 -0
  18. ripperdoc/cli/ui/permissions_tui/textual_app.py +526 -0
  19. ripperdoc/cli/ui/provider_options.py +220 -80
  20. ripperdoc/cli/ui/rich_ui.py +91 -83
  21. ripperdoc/cli/ui/tips.py +89 -0
  22. ripperdoc/cli/ui/wizard.py +98 -45
  23. ripperdoc/core/config.py +3 -0
  24. ripperdoc/core/permissions.py +66 -104
  25. ripperdoc/core/providers/anthropic.py +11 -0
  26. ripperdoc/protocol/stdio.py +3 -1
  27. ripperdoc/tools/bash_tool.py +2 -0
  28. ripperdoc/tools/file_edit_tool.py +100 -181
  29. ripperdoc/tools/file_read_tool.py +101 -25
  30. ripperdoc/tools/multi_edit_tool.py +239 -91
  31. ripperdoc/tools/notebook_edit_tool.py +11 -29
  32. ripperdoc/utils/file_editing.py +164 -0
  33. ripperdoc/utils/permissions/tool_permission_utils.py +11 -0
  34. {ripperdoc-0.3.0.dist-info → ripperdoc-0.3.2.dist-info}/METADATA +3 -2
  35. {ripperdoc-0.3.0.dist-info → ripperdoc-0.3.2.dist-info}/RECORD +39 -30
  36. ripperdoc/cli/ui/interrupt_handler.py +0 -208
  37. {ripperdoc-0.3.0.dist-info → ripperdoc-0.3.2.dist-info}/WHEEL +0 -0
  38. {ripperdoc-0.3.0.dist-info → ripperdoc-0.3.2.dist-info}/entry_points.txt +0 -0
  39. {ripperdoc-0.3.0.dist-info → ripperdoc-0.3.2.dist-info}/licenses/LICENSE +0 -0
  40. {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.0
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 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
+ 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=r9wkvo_s7lhTfuQG7WVFeE3Eg4HgMGHovnyZR1OVnPA,66
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=OZgN803jYmzdKxynHWG7VNwd7dQhM1KdYakXDyMlZT8,21826
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=agDTXwZWSr-fmtp_afuD1kMTA1NalMHtoKQ3eVtJMFs,15699
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=ZCnswx0TIiaiUUsIX7NpHaLZLZtvlUhBnN12s_ZtPCA,2424
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=LYebi7s9kv6xxlSqX3Pb1AsWY9S-xp0ikdtPVECinvo,18635
20
- ripperdoc/cli/commands/permissions_cmd.py,sha256=aIMIvmt78nB2Q-Qa5ojqClUOsPRcoyeDIuhOJX8xg2k,11559
21
- ripperdoc/cli/commands/resume_cmd.py,sha256=Pil2gJIkjf9fIFrxTyVXTwS7kzsg14CVg1SNQPZYAmw,4295
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=b3Ti-KmRF9XVk1jtS7bkz5F8317yu1fyp1I89SSpfqI,4019
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/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
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=bFKJeQStJ2Opq_ih9tgchkJP14bMvI-PX-zHVwzMiy8,7423
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=4KvaGKG4bf9wX6haPvxJllokdpPbJVvfTIMfsC0maag,29936
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=yp7OK36_I_UceDnLbGmxPh6nSbIDfns4IgTVGH5Tkqs,16986
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=B967szN1Thc0K1Iv8TvKWcVKktCevBmShftupVag8pk,28551
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=jia8cca3offyOc8kego-MnJfXsZkFwQw0cZZzfeFQqs,58933
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=m3jfukBBOSQUYJbntekTGGk3FIn1GKaZVvcqMWwLBYE,47598
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=IHgaWbzR7NKpLJ5XOjxU24c5-RyseVJw-p3QFGtcKJQ,22190
79
- ripperdoc/tools/file_read_tool.py,sha256=TXEt_Q7RSaTx4qdx3_mOYP704UU6qRccL5_AqPlNFrY,13735
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=s9gN3oRPqeYjQxdsfnkfHSIMTrBs09ihV_TYuVqaey8,18446
88
- ripperdoc/tools/notebook_edit_tool.py,sha256=Y2XkQB4WDbSWeCsO3Ybnsrbcxki99aOv3o23jZ5D1Hw,14445
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=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,,
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)