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.
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/PKG-INFO +17 -2
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/README.md +13 -1
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/pyproject.toml +9 -1
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/__init__.py +1 -1
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/__main__.py +0 -5
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/cli.py +108 -22
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/__init__.py +6 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/agents_cmd.py +36 -7
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/compact_cmd.py +7 -3
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/context_cmd.py +44 -14
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/cost_cmd.py +5 -0
- ripperdoc-0.2.5/ripperdoc/cli/commands/doctor_cmd.py +221 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/exit_cmd.py +1 -1
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/mcp_cmd.py +13 -8
- ripperdoc-0.2.5/ripperdoc/cli/commands/memory_cmd.py +202 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/models_cmd.py +99 -13
- ripperdoc-0.2.5/ripperdoc/cli/commands/permissions_cmd.py +302 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/resume_cmd.py +2 -1
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/status_cmd.py +1 -1
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/tasks_cmd.py +43 -5
- ripperdoc-0.2.5/ripperdoc/cli/ui/rich_ui.py +1399 -0
- ripperdoc-0.2.5/ripperdoc/cli/ui/thinking_spinner.py +128 -0
- ripperdoc-0.2.5/ripperdoc/cli/ui/tool_renderers.py +298 -0
- ripperdoc-0.2.5/ripperdoc/core/agents.py +486 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/config.py +183 -6
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/default_tools.py +28 -3
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/permissions.py +31 -6
- ripperdoc-0.2.5/ripperdoc/core/providers/__init__.py +47 -0
- ripperdoc-0.2.5/ripperdoc/core/providers/anthropic.py +250 -0
- ripperdoc-0.2.5/ripperdoc/core/providers/base.py +265 -0
- ripperdoc-0.2.5/ripperdoc/core/providers/gemini.py +615 -0
- ripperdoc-0.2.5/ripperdoc/core/providers/openai.py +487 -0
- ripperdoc-0.2.5/ripperdoc/core/query.py +1047 -0
- ripperdoc-0.2.5/ripperdoc/core/query_utils.py +622 -0
- ripperdoc-0.2.5/ripperdoc/core/skills.py +295 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/system_prompt.py +79 -66
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/tool.py +27 -3
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/sdk/client.py +26 -2
- ripperdoc-0.2.5/ripperdoc/tools/ask_user_question_tool.py +431 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/background_shell.py +120 -22
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/bash_tool.py +377 -195
- ripperdoc-0.2.5/ripperdoc/tools/dynamic_mcp_tool.py +428 -0
- ripperdoc-0.2.5/ripperdoc/tools/enter_plan_mode_tool.py +226 -0
- ripperdoc-0.2.5/ripperdoc/tools/exit_plan_mode_tool.py +153 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/file_edit_tool.py +67 -4
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/file_read_tool.py +34 -1
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/file_write_tool.py +63 -7
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/glob_tool.py +66 -21
- ripperdoc-0.2.5/ripperdoc/tools/grep_tool.py +370 -0
- ripperdoc-0.2.5/ripperdoc/tools/ls_tool.py +471 -0
- ripperdoc-0.2.5/ripperdoc/tools/mcp_tools.py +591 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/multi_edit_tool.py +65 -2
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/notebook_edit_tool.py +67 -8
- ripperdoc-0.2.5/ripperdoc/tools/skill_tool.py +205 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/task_tool.py +95 -6
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/todo_tool.py +168 -36
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/tool_search_tool.py +16 -4
- ripperdoc-0.2.5/ripperdoc/utils/coerce.py +34 -0
- ripperdoc-0.2.5/ripperdoc/utils/context_length_errors.py +252 -0
- ripperdoc-0.2.5/ripperdoc/utils/file_watch.py +135 -0
- ripperdoc-0.2.5/ripperdoc/utils/git_utils.py +274 -0
- ripperdoc-0.2.5/ripperdoc/utils/json_utils.py +27 -0
- ripperdoc-0.2.5/ripperdoc/utils/log.py +176 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/mcp.py +146 -21
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/memory.py +17 -3
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/message_compaction.py +34 -14
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/messages.py +142 -22
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/output_utils.py +34 -9
- ripperdoc-0.2.5/ripperdoc/utils/path_ignore.py +677 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/permissions/__init__.py +7 -1
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/permissions/path_validation_utils.py +9 -1
- ripperdoc-0.2.5/ripperdoc/utils/permissions/shell_command_validation.py +552 -0
- ripperdoc-0.2.5/ripperdoc/utils/prompt.py +17 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/safe_get_cwd.py +8 -1
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/session_history.py +49 -12
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/session_usage.py +7 -0
- ripperdoc-0.2.5/ripperdoc/utils/shell_utils.py +159 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/todo.py +6 -2
- ripperdoc-0.2.5/ripperdoc/utils/token_estimation.py +34 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc.egg-info/PKG-INFO +17 -2
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc.egg-info/SOURCES.txt +33 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc.egg-info/requires.txt +3 -0
- ripperdoc-0.2.5/tests/test_background_shell_shutdown.py +90 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_config.py +10 -1
- ripperdoc-0.2.5/tests/test_context_length_errors.py +74 -0
- ripperdoc-0.2.5/tests/test_mcp_config.py +18 -0
- ripperdoc-0.2.5/tests/test_messages.py +162 -0
- ripperdoc-0.2.5/tests/test_output_utils.py +23 -0
- ripperdoc-0.2.5/tests/test_path_ignore.py +492 -0
- ripperdoc-0.2.5/tests/test_query_abort.py +155 -0
- ripperdoc-0.2.5/tests/test_shell_permissions.py +710 -0
- ripperdoc-0.2.5/tests/test_skills.py +196 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_tools.py +7 -7
- ripperdoc-0.2.0/ripperdoc/cli/ui/rich_ui.py +0 -1025
- ripperdoc-0.2.0/ripperdoc/core/agents.py +0 -308
- ripperdoc-0.2.0/ripperdoc/core/query.py +0 -684
- ripperdoc-0.2.0/ripperdoc/tools/grep_tool.py +0 -232
- ripperdoc-0.2.0/ripperdoc/tools/ls_tool.py +0 -298
- ripperdoc-0.2.0/ripperdoc/tools/mcp_tools.py +0 -810
- ripperdoc-0.2.0/ripperdoc/utils/log.py +0 -76
- ripperdoc-0.2.0/ripperdoc/utils/permissions/shell_command_validation.py +0 -74
- ripperdoc-0.2.0/tests/test_messages.py +0 -70
- ripperdoc-0.2.0/tests/test_shell_permissions.py +0 -124
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/LICENSE +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/__init__.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/base.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/clear_cmd.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/config_cmd.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/help_cmd.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/todos_cmd.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/commands/tools_cmd.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/ui/__init__.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/ui/context_display.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/ui/helpers.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/cli/ui/spinner.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/__init__.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/core/commands.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/sdk/__init__.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/__init__.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/bash_output_tool.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/tools/kill_bash_tool.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/__init__.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/bash_constants.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/bash_output_utils.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/exit_code_handlers.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/path_utils.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/permissions/tool_permission_utils.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/sandbox_utils.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc/utils/shell_token_utils.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc.egg-info/dependency_links.txt +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc.egg-info/entry_points.txt +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/ripperdoc.egg-info/top_level.txt +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/setup.cfg +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/setup.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_cli_commands.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_context_limits.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_permissions.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_sdk.py +0 -0
- {ripperdoc-0.2.0 → ripperdoc-0.2.5}/tests/test_todo.py +0 -0
- {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.
|
|
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
|
-
|
|
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
|
|
@@ -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,
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
"--
|
|
286
|
+
"--yolo",
|
|
236
287
|
is_flag=True,
|
|
237
|
-
help="
|
|
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],
|
|
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
|
|
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(
|
|
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
|
|
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] —
|
|
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] —
|
|
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}
|
|
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;
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
95
|
-
except
|
|
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
|
|
101
|
-
|
|
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
|