ripperdoc 0.2.0__tar.gz → 0.2.5__tar.gz

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 (140) hide show
  1. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/PKG-INFO +17 -2
  2. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/README.md +13 -1
  3. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/pyproject.toml +9 -1
  4. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/__init__.py +1 -1
  5. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/__main__.py +0 -5
  6. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/cli.py +108 -22
  7. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/__init__.py +6 -0
  8. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/agents_cmd.py +36 -7
  9. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/compact_cmd.py +7 -3
  10. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/context_cmd.py +44 -14
  11. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/cost_cmd.py +5 -0
  12. ripperdoc-0.2.5/ripperdoc/cli/commands/doctor_cmd.py +221 -0
  13. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/exit_cmd.py +1 -1
  14. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/mcp_cmd.py +13 -8
  15. ripperdoc-0.2.5/ripperdoc/cli/commands/memory_cmd.py +202 -0
  16. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/models_cmd.py +99 -13
  17. ripperdoc-0.2.5/ripperdoc/cli/commands/permissions_cmd.py +302 -0
  18. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/resume_cmd.py +2 -1
  19. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/status_cmd.py +1 -1
  20. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/tasks_cmd.py +43 -5
  21. ripperdoc-0.2.5/ripperdoc/cli/ui/rich_ui.py +1399 -0
  22. ripperdoc-0.2.5/ripperdoc/cli/ui/thinking_spinner.py +128 -0
  23. ripperdoc-0.2.5/ripperdoc/cli/ui/tool_renderers.py +298 -0
  24. ripperdoc-0.2.5/ripperdoc/core/agents.py +486 -0
  25. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/config.py +183 -6
  26. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/default_tools.py +28 -3
  27. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/permissions.py +31 -6
  28. ripperdoc-0.2.5/ripperdoc/core/providers/__init__.py +47 -0
  29. ripperdoc-0.2.5/ripperdoc/core/providers/anthropic.py +250 -0
  30. ripperdoc-0.2.5/ripperdoc/core/providers/base.py +265 -0
  31. ripperdoc-0.2.5/ripperdoc/core/providers/gemini.py +615 -0
  32. ripperdoc-0.2.5/ripperdoc/core/providers/openai.py +487 -0
  33. ripperdoc-0.2.5/ripperdoc/core/query.py +1047 -0
  34. ripperdoc-0.2.5/ripperdoc/core/query_utils.py +622 -0
  35. ripperdoc-0.2.5/ripperdoc/core/skills.py +295 -0
  36. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/system_prompt.py +79 -66
  37. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/tool.py +27 -3
  38. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/sdk/client.py +26 -2
  39. ripperdoc-0.2.5/ripperdoc/tools/ask_user_question_tool.py +431 -0
  40. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/background_shell.py +120 -22
  41. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/bash_tool.py +377 -195
  42. ripperdoc-0.2.5/ripperdoc/tools/dynamic_mcp_tool.py +428 -0
  43. ripperdoc-0.2.5/ripperdoc/tools/enter_plan_mode_tool.py +226 -0
  44. ripperdoc-0.2.5/ripperdoc/tools/exit_plan_mode_tool.py +153 -0
  45. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/file_edit_tool.py +67 -4
  46. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/file_read_tool.py +34 -1
  47. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/file_write_tool.py +63 -7
  48. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/glob_tool.py +66 -21
  49. ripperdoc-0.2.5/ripperdoc/tools/grep_tool.py +370 -0
  50. ripperdoc-0.2.5/ripperdoc/tools/ls_tool.py +471 -0
  51. ripperdoc-0.2.5/ripperdoc/tools/mcp_tools.py +591 -0
  52. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/multi_edit_tool.py +65 -2
  53. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/notebook_edit_tool.py +67 -8
  54. ripperdoc-0.2.5/ripperdoc/tools/skill_tool.py +205 -0
  55. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/task_tool.py +95 -6
  56. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/todo_tool.py +168 -36
  57. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/tool_search_tool.py +16 -4
  58. ripperdoc-0.2.5/ripperdoc/utils/coerce.py +34 -0
  59. ripperdoc-0.2.5/ripperdoc/utils/context_length_errors.py +252 -0
  60. ripperdoc-0.2.5/ripperdoc/utils/file_watch.py +135 -0
  61. ripperdoc-0.2.5/ripperdoc/utils/git_utils.py +274 -0
  62. ripperdoc-0.2.5/ripperdoc/utils/json_utils.py +27 -0
  63. ripperdoc-0.2.5/ripperdoc/utils/log.py +176 -0
  64. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/mcp.py +146 -21
  65. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/memory.py +17 -3
  66. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/message_compaction.py +34 -14
  67. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/messages.py +142 -22
  68. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/output_utils.py +34 -9
  69. ripperdoc-0.2.5/ripperdoc/utils/path_ignore.py +677 -0
  70. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/permissions/__init__.py +7 -1
  71. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/permissions/path_validation_utils.py +9 -1
  72. ripperdoc-0.2.5/ripperdoc/utils/permissions/shell_command_validation.py +552 -0
  73. ripperdoc-0.2.5/ripperdoc/utils/prompt.py +17 -0
  74. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/safe_get_cwd.py +8 -1
  75. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/session_history.py +49 -12
  76. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/session_usage.py +7 -0
  77. ripperdoc-0.2.5/ripperdoc/utils/shell_utils.py +159 -0
  78. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/todo.py +6 -2
  79. ripperdoc-0.2.5/ripperdoc/utils/token_estimation.py +34 -0
  80. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc.egg-info/PKG-INFO +17 -2
  81. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc.egg-info/SOURCES.txt +33 -0
  82. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc.egg-info/requires.txt +3 -0
  83. ripperdoc-0.2.5/tests/test_background_shell_shutdown.py +90 -0
  84. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_config.py +10 -1
  85. ripperdoc-0.2.5/tests/test_context_length_errors.py +74 -0
  86. ripperdoc-0.2.5/tests/test_mcp_config.py +18 -0
  87. ripperdoc-0.2.5/tests/test_messages.py +162 -0
  88. ripperdoc-0.2.5/tests/test_output_utils.py +23 -0
  89. ripperdoc-0.2.5/tests/test_path_ignore.py +492 -0
  90. ripperdoc-0.2.5/tests/test_query_abort.py +155 -0
  91. ripperdoc-0.2.5/tests/test_shell_permissions.py +710 -0
  92. ripperdoc-0.2.5/tests/test_skills.py +196 -0
  93. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_tools.py +7 -7
  94. ripperdoc-0.2.0/ripperdoc/cli/ui/rich_ui.py +0 -1025
  95. ripperdoc-0.2.0/ripperdoc/core/agents.py +0 -308
  96. ripperdoc-0.2.0/ripperdoc/core/query.py +0 -684
  97. ripperdoc-0.2.0/ripperdoc/tools/grep_tool.py +0 -232
  98. ripperdoc-0.2.0/ripperdoc/tools/ls_tool.py +0 -298
  99. ripperdoc-0.2.0/ripperdoc/tools/mcp_tools.py +0 -810
  100. ripperdoc-0.2.0/ripperdoc/utils/log.py +0 -76
  101. ripperdoc-0.2.0/ripperdoc/utils/permissions/shell_command_validation.py +0 -74
  102. ripperdoc-0.2.0/tests/test_messages.py +0 -70
  103. ripperdoc-0.2.0/tests/test_shell_permissions.py +0 -124
  104. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/LICENSE +0 -0
  105. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/__init__.py +0 -0
  106. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/base.py +0 -0
  107. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/clear_cmd.py +0 -0
  108. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/config_cmd.py +0 -0
  109. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/help_cmd.py +0 -0
  110. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/todos_cmd.py +0 -0
  111. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/tools_cmd.py +0 -0
  112. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/ui/__init__.py +0 -0
  113. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/ui/context_display.py +0 -0
  114. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/ui/helpers.py +0 -0
  115. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/ui/spinner.py +0 -0
  116. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/__init__.py +0 -0
  117. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/commands.py +0 -0
  118. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/sdk/__init__.py +0 -0
  119. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/__init__.py +0 -0
  120. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/bash_output_tool.py +0 -0
  121. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/kill_bash_tool.py +0 -0
  122. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/__init__.py +0 -0
  123. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/bash_constants.py +0 -0
  124. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/bash_output_utils.py +0 -0
  125. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/exit_code_handlers.py +0 -0
  126. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/path_utils.py +0 -0
  127. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/permissions/tool_permission_utils.py +0 -0
  128. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/sandbox_utils.py +0 -0
  129. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/shell_token_utils.py +0 -0
  130. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc.egg-info/dependency_links.txt +0 -0
  131. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc.egg-info/entry_points.txt +0 -0
  132. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc.egg-info/top_level.txt +0 -0
  133. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/setup.cfg +0 -0
  134. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/setup.py +0 -0
  135. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_cli_commands.py +0 -0
  136. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_context_limits.py +0 -0
  137. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_permissions.py +0 -0
  138. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_sdk.py +0 -0
  139. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_todo.py +0 -0
  140. {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_tool_search.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripperdoc
3
- Version: 0.2.0
3
+ Version: 0.2.5
4
4
  Summary: AI-powered terminal assistant for coding tasks
5
5
  Author: Ripperdoc Team
6
6
  License: Apache-2.0
@@ -24,6 +24,9 @@ Requires-Dist: aiofiles>=23.0.0
24
24
  Requires-Dist: prompt-toolkit>=3.0.0
25
25
  Requires-Dist: PyYAML>=6.0.0
26
26
  Requires-Dist: mcp[cli]>=1.22.0
27
+ Requires-Dist: json_repair>=0.54.2
28
+ Requires-Dist: tiktoken>=0.7.0
29
+ Requires-Dist: google-genai>=0.3.0
27
30
  Provides-Extra: dev
28
31
  Requires-Dist: pytest>=7.0.0; extra == "dev"
29
32
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -37,14 +40,17 @@ Dynamic: license-file
37
40
  Ripperdoc is an AI-powered terminal assistant for coding tasks, providing an interactive interface for AI-assisted development, file management, and command execution.
38
41
 
39
42
  [中文文档](README_CN.md) | [Contributing](CONTRIBUTING.md) | [Documentation](docs/)
43
+
40
44
  ## Features
41
45
 
42
46
  - **AI-Powered Assistance** - Uses AI models to understand and respond to coding requests
43
47
  - **Multi-Model Support** - Support for Anthropic Claude and OpenAI models
48
+ - **Rich UI** - Beautiful terminal interface with syntax highlighting
44
49
  - **Code Editing** - Directly edit files with intelligent suggestions
45
50
  - **Codebase Understanding** - Analyzes project structure and code relationships
46
51
  - **Command Execution** - Run shell commands with real-time feedback
47
52
  - **Tool System** - Extensible architecture with specialized tools
53
+ - **Agent Skills** - Load SKILL.md bundles to extend the agent on demand
48
54
  - **Subagents** - Delegate tasks to specialized agents with their own tool scopes
49
55
  - **File Operations** - Read, write, edit, search, and manage files
50
56
  - **Todo Tracking** - Plan, read, and update persistent todo lists per project
@@ -52,7 +58,6 @@ Ripperdoc is an AI-powered terminal assistant for coding tasks, providing an int
52
58
  - **Permission System** - Safe mode with permission prompts for operations
53
59
  - **Multi-Edit Support** - Batch edit operations on files
54
60
  - **MCP Server Support** - Integration with Model Context Protocol servers
55
- - **Subagent System** - Delegate tasks to specialized agents
56
61
  - **Session Management** - Persistent session history and usage tracking
57
62
  - **Jupyter Notebook Support** - Edit .ipynb files directly
58
63
 
@@ -113,6 +118,16 @@ See the [examples/](examples/) directory for complete SDK usage examples.
113
118
 
114
119
  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).
115
120
 
121
+ ### Agent Skills
122
+
123
+ Extend Ripperdoc with reusable Skill bundles:
124
+
125
+ - Personal skills live in `~/.ripperdoc/skills/<skill-name>/SKILL.md`
126
+ - Project skills live in `.ripperdoc/skills/<skill-name>/SKILL.md` and can be checked into git
127
+ - 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
128
+ - 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
129
+ - Ripperdoc exposes skill names/descriptions in the system prompt and loads full content on demand via the `Skill` tool
130
+
116
131
  ## Examples
117
132
 
118
133
  ### Code Analysis
@@ -3,14 +3,17 @@
3
3
  Ripperdoc is an AI-powered terminal assistant for coding tasks, providing an interactive interface for AI-assisted development, file management, and command execution.
4
4
 
5
5
  [中文文档](README_CN.md) | [Contributing](CONTRIBUTING.md) | [Documentation](docs/)
6
+
6
7
  ## Features
7
8
 
8
9
  - **AI-Powered Assistance** - Uses AI models to understand and respond to coding requests
9
10
  - **Multi-Model Support** - Support for Anthropic Claude and OpenAI models
11
+ - **Rich UI** - Beautiful terminal interface with syntax highlighting
10
12
  - **Code Editing** - Directly edit files with intelligent suggestions
11
13
  - **Codebase Understanding** - Analyzes project structure and code relationships
12
14
  - **Command Execution** - Run shell commands with real-time feedback
13
15
  - **Tool System** - Extensible architecture with specialized tools
16
+ - **Agent Skills** - Load SKILL.md bundles to extend the agent on demand
14
17
  - **Subagents** - Delegate tasks to specialized agents with their own tool scopes
15
18
  - **File Operations** - Read, write, edit, search, and manage files
16
19
  - **Todo Tracking** - Plan, read, and update persistent todo lists per project
@@ -18,7 +21,6 @@ Ripperdoc is an AI-powered terminal assistant for coding tasks, providing an int
18
21
  - **Permission System** - Safe mode with permission prompts for operations
19
22
  - **Multi-Edit Support** - Batch edit operations on files
20
23
  - **MCP Server Support** - Integration with Model Context Protocol servers
21
- - **Subagent System** - Delegate tasks to specialized agents
22
24
  - **Session Management** - Persistent session history and usage tracking
23
25
  - **Jupyter Notebook Support** - Edit .ipynb files directly
24
26
 
@@ -79,6 +81,16 @@ See the [examples/](examples/) directory for complete SDK usage examples.
79
81
 
80
82
  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).
81
83
 
84
+ ### Agent Skills
85
+
86
+ Extend Ripperdoc with reusable Skill bundles:
87
+
88
+ - Personal skills live in `~/.ripperdoc/skills/<skill-name>/SKILL.md`
89
+ - Project skills live in `.ripperdoc/skills/<skill-name>/SKILL.md` and can be checked into git
90
+ - 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
91
+ - 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
92
+ - Ripperdoc exposes skill names/descriptions in the system prompt and loads full content on demand via the `Skill` tool
93
+
82
94
  ## Examples
83
95
 
84
96
  ### Code Analysis
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ripperdoc"
7
- version = "0.2.0"
7
+ dynamic = ["version"]
8
8
  description = "AI-powered terminal assistant for coding tasks"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -33,6 +33,9 @@ dependencies = [
33
33
  "prompt-toolkit>=3.0.0",
34
34
  "PyYAML>=6.0.0",
35
35
  "mcp[cli]>=1.22.0",
36
+ "json_repair>=0.54.2",
37
+ "tiktoken>=0.7.0",
38
+ "google-genai>=0.3.0",
36
39
  ]
37
40
 
38
41
  [project.optional-dependencies]
@@ -48,6 +51,9 @@ dev = [
48
51
  ripperdoc = "ripperdoc.cli.cli:main"
49
52
  rd = "ripperdoc.cli.cli:main"
50
53
 
54
+ [tool.setuptools.dynamic]
55
+ version = {attr = "ripperdoc.__version__"}
56
+
51
57
  [tool.setuptools.packages.find]
52
58
  where = ["."]
53
59
  include = ["ripperdoc*"]
@@ -61,6 +67,8 @@ python_version = "3.10"
61
67
  warn_return_any = true
62
68
  warn_unused_configs = true
63
69
  disallow_untyped_defs = true
70
+ files = ["ripperdoc"]
71
+ exclude = ["^tests/"]
64
72
 
65
73
  [tool.ruff]
66
74
  line-length = 100
@@ -1,3 +1,3 @@
1
1
  """Ripperdoc - AI-powered coding agent."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.2.5"
@@ -13,11 +13,6 @@ Features:
13
13
  Quick Start:
14
14
  pip install -e .
15
15
  ripperdoc -p "your prompt here"
16
-
17
- For more information:
18
- - README.md: Project overview
19
- - QUICKSTART.md: Quick start guide
20
- - DEVELOPMENT.md: Development guide
21
16
  """
22
17
 
23
18
  from ripperdoc import __version__
@@ -6,6 +6,7 @@ This module provides the command-line interface for the Ripperdoc agent.
6
6
  import asyncio
7
7
  import click
8
8
  import sys
9
+ import uuid
9
10
  from pathlib import Path
10
11
  from typing import Any, Dict, List, Optional
11
12
 
@@ -20,6 +21,7 @@ from ripperdoc.core.config import (
20
21
  from ripperdoc.core.default_tools import get_default_tools
21
22
  from ripperdoc.core.query import query, QueryContext
22
23
  from ripperdoc.core.system_prompt import build_system_prompt
24
+ from ripperdoc.core.skills import build_skill_summary, load_all_skills
23
25
  from ripperdoc.utils.messages import create_user_message
24
26
  from ripperdoc.utils.memory import build_memory_instructions
25
27
  from ripperdoc.core.permissions import make_permission_checker
@@ -29,6 +31,8 @@ from ripperdoc.utils.mcp import (
29
31
  shutdown_mcp_runtime,
30
32
  )
31
33
  from ripperdoc.tools.mcp_tools import load_dynamic_mcp_tools_async, merge_tools_with_dynamic
34
+ from ripperdoc.utils.log import enable_session_file_logging, get_logger
35
+ from ripperdoc.utils.prompt import prompt_secret
32
36
 
33
37
  from rich.console import Console
34
38
  from rich.markdown import Markdown
@@ -36,13 +40,33 @@ from rich.panel import Panel
36
40
  from rich.markup import escape
37
41
 
38
42
  console = Console()
43
+ logger = get_logger()
39
44
 
40
45
 
41
46
  async def run_query(
42
- prompt: str, tools: list, safe_mode: bool = False, verbose: bool = False
47
+ prompt: str,
48
+ tools: list,
49
+ safe_mode: bool = False,
50
+ verbose: bool = False,
51
+ session_id: Optional[str] = None,
43
52
  ) -> None:
44
53
  """Run a single query and print the response."""
45
54
 
55
+ logger.info(
56
+ "[cli] Running single prompt session",
57
+ extra={
58
+ "safe_mode": safe_mode,
59
+ "verbose": verbose,
60
+ "session_id": session_id,
61
+ "prompt_length": len(prompt),
62
+ },
63
+ )
64
+ if prompt:
65
+ logger.debug(
66
+ "[cli] Prompt preview",
67
+ extra={"session_id": session_id, "prompt_preview": prompt[:200]},
68
+ )
69
+
46
70
  project_path = Path.cwd()
47
71
  can_use_tool = make_permission_checker(project_path, safe_mode) if safe_mode else None
48
72
 
@@ -63,18 +87,26 @@ async def run_query(
63
87
  tools = merge_tools_with_dynamic(tools, dynamic_tools)
64
88
  query_context.tools = tools
65
89
  mcp_instructions = format_mcp_instructions(servers)
66
- base_system_prompt = build_system_prompt(
90
+ skill_result = load_all_skills(Path.cwd())
91
+ for err in skill_result.errors:
92
+ logger.warning(
93
+ "[skills] Failed to load skill",
94
+ extra={"path": str(err.path), "reason": err.reason},
95
+ )
96
+ skill_instructions = build_skill_summary(skill_result.skills)
97
+ additional_instructions: List[str] = []
98
+ if skill_instructions:
99
+ additional_instructions.append(skill_instructions)
100
+ memory_instructions = build_memory_instructions()
101
+ if memory_instructions:
102
+ additional_instructions.append(memory_instructions)
103
+ system_prompt = build_system_prompt(
67
104
  tools,
68
105
  prompt,
69
106
  context,
107
+ additional_instructions=additional_instructions or None,
70
108
  mcp_instructions=mcp_instructions,
71
109
  )
72
- memory_instructions = build_memory_instructions()
73
- system_prompt = (
74
- f"{base_system_prompt}\n\n{memory_instructions}"
75
- if memory_instructions
76
- else base_system_prompt
77
- )
78
110
 
79
111
  # Run the query
80
112
  try:
@@ -89,6 +121,7 @@ async def run_query(
89
121
  Markdown(message.message.content),
90
122
  title="Ripperdoc",
91
123
  border_style="cyan",
124
+ padding=(0, 1),
92
125
  )
93
126
  )
94
127
  else:
@@ -101,6 +134,7 @@ async def run_query(
101
134
  Markdown(block["text"]),
102
135
  title="Ripperdoc",
103
136
  border_style="cyan",
137
+ padding=(0, 1),
104
138
  )
105
139
  )
106
140
  else:
@@ -110,6 +144,7 @@ async def run_query(
110
144
  Markdown(block.text or ""),
111
145
  title="Ripperdoc",
112
146
  border_style="cyan",
147
+ padding=(0, 1),
113
148
  )
114
149
  )
115
150
 
@@ -123,14 +158,26 @@ async def run_query(
123
158
 
124
159
  except KeyboardInterrupt:
125
160
  console.print("\n[yellow]Interrupted by user[/yellow]")
126
- except Exception as e:
161
+ except asyncio.CancelledError:
162
+ console.print("\n[yellow]Operation cancelled[/yellow]")
163
+ except (RuntimeError, ValueError, TypeError, OSError, IOError, ConnectionError) as e:
127
164
  console.print(f"[red]Error: {escape(str(e))}[/red]")
165
+ logger.warning(
166
+ "[cli] Unhandled error while running prompt: %s: %s",
167
+ type(e).__name__, e,
168
+ extra={"session_id": session_id},
169
+ )
128
170
  if verbose:
129
171
  import traceback
130
172
 
131
173
  console.print(traceback.format_exc(), markup=False)
174
+ logger.info(
175
+ "[cli] Prompt session completed",
176
+ extra={"session_id": session_id, "message_count": len(messages)},
177
+ )
132
178
  finally:
133
179
  await shutdown_mcp_runtime()
180
+ logger.debug("[cli] Shutdown MCP runtime", extra={"session_id": session_id})
134
181
 
135
182
 
136
183
  def check_onboarding() -> bool:
@@ -169,7 +216,11 @@ def check_onboarding() -> bool:
169
216
  )
170
217
  api_base = click.prompt("API Base URL")
171
218
 
172
- api_key = click.prompt("Enter your API key", hide_input=True)
219
+ api_key = ""
220
+ while not api_key:
221
+ api_key = prompt_secret("Enter your API key").strip()
222
+ if not api_key:
223
+ console.print("[red]API key is required.[/red]")
173
224
 
174
225
  provider = ProviderType(provider_choice)
175
226
 
@@ -232,38 +283,62 @@ def check_onboarding() -> bool:
232
283
  @click.version_option(version=__version__)
233
284
  @click.option("--cwd", type=click.Path(exists=True), help="Working directory")
234
285
  @click.option(
235
- "--unsafe",
286
+ "--yolo",
236
287
  is_flag=True,
237
- help="Disable safe mode (skip permission prompts for tools)",
288
+ help="YOLO mode: skip all permission prompts for tools",
238
289
  )
239
290
  @click.option("--verbose", is_flag=True, help="Verbose output")
240
291
  @click.option("-p", "--prompt", type=str, help="Direct prompt (non-interactive)")
241
292
  @click.pass_context
242
293
  def cli(
243
- ctx: click.Context, cwd: Optional[str], unsafe: bool, verbose: bool, prompt: Optional[str]
294
+ ctx: click.Context, cwd: Optional[str], yolo: bool, verbose: bool, prompt: Optional[str]
244
295
  ) -> None:
245
296
  """Ripperdoc - AI-powered coding agent"""
246
-
247
- # Ensure onboarding is complete
248
- if not check_onboarding():
249
- sys.exit(1)
297
+ session_id = str(uuid.uuid4())
250
298
 
251
299
  # Set working directory
252
300
  if cwd:
253
301
  import os
254
302
 
255
303
  os.chdir(cwd)
304
+ logger.debug(
305
+ "[cli] Changed working directory via --cwd",
306
+ extra={"cwd": cwd, "session_id": session_id},
307
+ )
256
308
 
257
- # Initialize project configuration for the current working directory
258
309
  project_path = Path.cwd()
310
+ log_file = enable_session_file_logging(project_path, session_id)
311
+ logger.info(
312
+ "[cli] Starting CLI invocation",
313
+ extra={
314
+ "session_id": session_id,
315
+ "project_path": str(project_path),
316
+ "log_file": str(log_file),
317
+ "prompt_mode": bool(prompt),
318
+ },
319
+ )
320
+
321
+ # Ensure onboarding is complete
322
+ if not check_onboarding():
323
+ logger.info(
324
+ "[cli] Onboarding check failed or aborted; exiting.",
325
+ extra={"session_id": session_id},
326
+ )
327
+ sys.exit(1)
328
+
329
+ # Initialize project configuration for the current working directory
259
330
  get_project_config(project_path)
260
331
 
261
- safe_mode = not unsafe
332
+ safe_mode = not yolo
333
+ logger.debug(
334
+ "[cli] Configuration initialized",
335
+ extra={"session_id": session_id, "safe_mode": safe_mode, "verbose": verbose},
336
+ )
262
337
 
263
338
  # If prompt is provided, run directly
264
339
  if prompt:
265
340
  tools = get_default_tools()
266
- asyncio.run(run_query(prompt, tools, safe_mode, verbose))
341
+ asyncio.run(run_query(prompt, tools, safe_mode, verbose, session_id=session_id))
267
342
  return
268
343
 
269
344
  # If no command specified, start interactive REPL with Rich interface
@@ -271,7 +346,12 @@ def cli(
271
346
  # Use Rich interface by default
272
347
  from ripperdoc.cli.ui.rich_ui import main_rich
273
348
 
274
- main_rich(safe_mode=safe_mode, verbose=verbose)
349
+ main_rich(
350
+ safe_mode=safe_mode,
351
+ verbose=verbose,
352
+ session_id=session_id,
353
+ log_file_path=log_file,
354
+ )
275
355
  return
276
356
 
277
357
 
@@ -310,8 +390,14 @@ def main() -> None:
310
390
  except KeyboardInterrupt:
311
391
  console.print("\n[yellow]Interrupted[/yellow]")
312
392
  sys.exit(130)
313
- except Exception as e:
393
+ except SystemExit:
394
+ raise
395
+ except (RuntimeError, ValueError, TypeError, OSError, IOError, ConnectionError, click.ClickException) as e:
314
396
  console.print(f"[red]Fatal error: {escape(str(e))}[/red]")
397
+ logger.warning(
398
+ "[cli] Fatal error in main CLI entrypoint: %s: %s",
399
+ type(e).__name__, e,
400
+ )
315
401
  sys.exit(1)
316
402
 
317
403
 
@@ -11,10 +11,13 @@ from .compact_cmd import command as compact_command
11
11
  from .config_cmd import command as config_command
12
12
  from .cost_cmd import command as cost_command
13
13
  from .context_cmd import command as context_command
14
+ from .doctor_cmd import command as doctor_command
14
15
  from .exit_cmd import command as exit_command
15
16
  from .help_cmd import command as help_command
17
+ from .memory_cmd import command as memory_command
16
18
  from .mcp_cmd import command as mcp_command
17
19
  from .models_cmd import command as models_command
20
+ from .permissions_cmd import command as permissions_command
18
21
  from .resume_cmd import command as resume_command
19
22
  from .tasks_cmd import command as tasks_command
20
23
  from .status_cmd import command as status_command
@@ -40,6 +43,9 @@ ALL_COMMANDS: List[SlashCommand] = [
40
43
  models_command,
41
44
  exit_command,
42
45
  status_command,
46
+ doctor_command,
47
+ memory_command,
48
+ permissions_command,
43
49
  tasks_command,
44
50
  todos_command,
45
51
  mcp_command,
@@ -8,27 +8,40 @@ from ripperdoc.core.agents import (
8
8
  save_agent_definition,
9
9
  )
10
10
  from ripperdoc.core.config import get_global_config
11
+ from ripperdoc.utils.log import get_logger
11
12
 
12
13
  from typing import Any
13
14
  from .base import SlashCommand
14
15
 
16
+ logger = get_logger()
17
+
15
18
 
16
19
  def _handle(ui: Any, trimmed_arg: str) -> bool:
17
20
  console = ui.console
18
21
  tokens = trimmed_arg.split()
19
22
  subcmd = tokens[0].lower() if tokens else ""
23
+ logger.info(
24
+ "[agents_cmd] Handling /agents command",
25
+ extra={
26
+ "subcommand": subcmd or "list",
27
+ "session_id": getattr(ui, "session_id", None),
28
+ },
29
+ )
20
30
 
21
31
  def print_agents_usage() -> None:
22
32
  console.print("[bold]/agents[/bold] — list configured agents")
23
33
  console.print(
24
- "[bold]/agents create <name> [location] [model][/bold] — create agent (location: user|project, default user)"
34
+ "[bold]/agents create <name> [location] [model][/bold] — "
35
+ "create agent (location: user|project, default user)"
25
36
  )
26
37
  console.print("[bold]/agents edit <name> [location][/bold] — edit an existing agent")
27
38
  console.print(
28
- "[bold]/agents delete <name> [location][/bold] — delete agent (location: user|project, default user)"
39
+ "[bold]/agents delete <name> [location][/bold] — "
40
+ "delete agent (location: user|project, default user)"
29
41
  )
30
42
  console.print(
31
- f"[dim]Agent files live in ~/.ripperdoc/{AGENT_DIR_NAME} or ./.ripperdoc/{AGENT_DIR_NAME}[/dim]"
43
+ f"[dim]Agent files live in ~/.ripperdoc/{AGENT_DIR_NAME} "
44
+ f"or ./.ripperdoc/{AGENT_DIR_NAME}[/dim]"
32
45
  )
33
46
  console.print(
34
47
  "[dim]Model can be a profile name or pointer (task/main/etc). Defaults to 'task'.[/dim]"
@@ -82,7 +95,8 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
82
95
  and model_input not in pointer_map
83
96
  ):
84
97
  console.print(
85
- "[yellow]Model not found in profiles or pointers; will fall back to main if unavailable.[/yellow]"
98
+ "[yellow]Model not found in profiles or pointers; "
99
+ "will fall back to main if unavailable.[/yellow]"
86
100
  )
87
101
 
88
102
  try:
@@ -97,9 +111,14 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
97
111
  console.print(
98
112
  f"[green]✓ Agent '{escape(agent_name)}' created at {escape(str(path))}[/green]"
99
113
  )
100
- except Exception as exc:
114
+ except (OSError, IOError, PermissionError, ValueError) as exc:
101
115
  console.print(f"[red]Failed to create agent: {escape(str(exc))}[/red]")
102
116
  print_agents_usage()
117
+ logger.warning(
118
+ "[agents_cmd] Failed to create agent: %s: %s",
119
+ type(exc).__name__, exc,
120
+ extra={"agent": agent_name, "session_id": getattr(ui, "session_id", None)},
121
+ )
103
122
  return True
104
123
 
105
124
  if subcmd in ("delete", "del", "remove"):
@@ -124,9 +143,14 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
124
143
  )
125
144
  except FileNotFoundError as exc:
126
145
  console.print(f"[yellow]{escape(str(exc))}[/yellow]")
127
- except Exception as exc:
146
+ except (OSError, IOError, PermissionError, ValueError) as exc:
128
147
  console.print(f"[red]Failed to delete agent: {escape(str(exc))}[/red]")
129
148
  print_agents_usage()
149
+ logger.warning(
150
+ "[agents_cmd] Failed to delete agent: %s: %s",
151
+ type(exc).__name__, exc,
152
+ extra={"agent": agent_name, "session_id": getattr(ui, "session_id", None)},
153
+ )
130
154
  return True
131
155
 
132
156
  if subcmd in ("edit", "update"):
@@ -197,9 +221,14 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
197
221
  console.print(
198
222
  f"[green]✓ Agent '{escape(agent_name)}' updated at {escape(str(path))}[/green]"
199
223
  )
200
- except Exception as exc:
224
+ except (OSError, IOError, PermissionError, ValueError) as exc:
201
225
  console.print(f"[red]Failed to update agent: {escape(str(exc))}[/red]")
202
226
  print_agents_usage()
227
+ logger.warning(
228
+ "[agents_cmd] Failed to update agent: %s: %s",
229
+ type(exc).__name__, exc,
230
+ extra={"agent": agent_name, "session_id": getattr(ui, "session_id", None)},
231
+ )
203
232
  return True
204
233
 
205
234
  agents = load_agent_definitions()
@@ -1,11 +1,15 @@
1
- import asyncio
2
-
3
1
  from typing import Any
4
2
  from .base import SlashCommand
5
3
 
6
4
 
7
5
  def _handle(ui: Any, trimmed_arg: str) -> bool:
8
- asyncio.run(ui._run_manual_compact(trimmed_arg))
6
+ runner = getattr(ui, "run_async", None)
7
+ if callable(runner):
8
+ runner(ui._run_manual_compact(trimmed_arg))
9
+ else:
10
+ import asyncio
11
+
12
+ asyncio.run(ui._run_manual_compact(trimmed_arg))
9
13
  return True
10
14
 
11
15
 
@@ -1,4 +1,3 @@
1
- import asyncio
2
1
  import json
3
2
  from typing import List, Any
4
3
 
@@ -7,24 +6,31 @@ from ripperdoc.cli.ui.context_display import format_tokens
7
6
  from ripperdoc.core.config import get_global_config, provider_protocol
8
7
  from ripperdoc.core.query import QueryContext
9
8
  from ripperdoc.core.system_prompt import build_system_prompt
9
+ from ripperdoc.core.skills import build_skill_summary, load_all_skills
10
10
  from ripperdoc.utils.memory import build_memory_instructions
11
11
  from ripperdoc.utils.message_compaction import (
12
- estimate_tokens_from_text,
13
12
  get_remaining_context_tokens,
14
13
  resolve_auto_compact_enabled,
15
14
  summarize_context_usage,
16
15
  )
16
+ from ripperdoc.utils.token_estimation import estimate_tokens
17
17
  from ripperdoc.utils.mcp import (
18
18
  estimate_mcp_tokens,
19
19
  format_mcp_instructions,
20
20
  load_mcp_servers_async,
21
- shutdown_mcp_runtime,
22
21
  )
22
+ from ripperdoc.utils.log import get_logger
23
23
 
24
24
  from .base import SlashCommand
25
25
 
26
+ logger = get_logger()
27
+
26
28
 
27
29
  def _handle(ui: Any, _: str) -> bool:
30
+ logger.info(
31
+ "[context_cmd] Rendering context summary",
32
+ extra={"session_id": getattr(ui, "session_id", None)},
33
+ )
28
34
  config = get_global_config()
29
35
  model_profile = get_profile_for_pointer("main")
30
36
  max_context_tokens = get_remaining_context_tokens(model_profile, config.context_token_limit)
@@ -39,21 +45,41 @@ def _handle(ui: Any, _: str) -> bool:
39
45
  )
40
46
 
41
47
  async def _load_servers() -> List[Any]:
42
- try:
43
- return await load_mcp_servers_async(ui.project_path)
44
- finally:
45
- await shutdown_mcp_runtime()
48
+ return await load_mcp_servers_async(ui.project_path)
46
49
 
47
- servers = asyncio.run(_load_servers())
50
+ runner = getattr(ui, "run_async", None)
51
+ if callable(runner):
52
+ servers = runner(_load_servers())
53
+ else:
54
+ import asyncio
55
+
56
+ servers = asyncio.run(_load_servers())
48
57
  mcp_instructions = format_mcp_instructions(servers)
58
+ skill_result = load_all_skills(ui.project_path)
59
+ for err in skill_result.errors:
60
+ logger.warning(
61
+ "[skills] Failed to load skill",
62
+ extra={
63
+ "path": str(err.path),
64
+ "reason": err.reason,
65
+ "session_id": getattr(ui, "session_id", None),
66
+ },
67
+ )
68
+ skill_instructions = build_skill_summary(skill_result.skills)
69
+ additional_instructions: List[str] = []
70
+ if skill_instructions:
71
+ additional_instructions.append(skill_instructions)
72
+ memory_instructions = build_memory_instructions()
73
+ if memory_instructions:
74
+ additional_instructions.append(memory_instructions)
49
75
  base_system_prompt = build_system_prompt(
50
76
  ui.query_context.tools,
51
77
  "",
52
78
  {},
79
+ additional_instructions=additional_instructions or None,
53
80
  mcp_instructions=mcp_instructions,
54
81
  )
55
- memory_instructions = build_memory_instructions()
56
- memory_tokens = estimate_tokens_from_text(memory_instructions) if memory_instructions else 0
82
+ memory_tokens = 0
57
83
  mcp_tokens = estimate_mcp_tokens(servers) if mcp_instructions else 0
58
84
 
59
85
  breakdown = summarize_context_usage(
@@ -91,14 +117,18 @@ def _handle(ui: Any, _: str) -> bool:
91
117
  display = f"{display} ({server})"
92
118
  try:
93
119
  schema = tool.input_schema.model_json_schema()
94
- token_est = estimate_tokens_from_text(json.dumps(schema, sort_keys=True))
95
- except Exception:
120
+ token_est = estimate_tokens(json.dumps(schema, sort_keys=True))
121
+ except (AttributeError, TypeError, ValueError):
96
122
  token_est = 0
97
123
  lines.append(f" └ {display}: {format_tokens(token_est)} tokens")
98
124
  if len(mcp_tools) > 20:
99
125
  lines.append(f" └ ... (+{len(mcp_tools) - 20} more)")
100
- except Exception:
101
- pass
126
+ except (OSError, RuntimeError, AttributeError, TypeError) as exc:
127
+ logger.warning(
128
+ "[context_cmd] Failed to summarize MCP tools: %s: %s",
129
+ type(exc).__name__, exc,
130
+ extra={"session_id": getattr(ui, "session_id", None)},
131
+ )
102
132
  for line in lines:
103
133
  ui.console.print(line)
104
134
  return True