ripperdoc 0.2.7__tar.gz → 0.2.9__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.7 → ripperdoc-0.2.9}/PKG-INFO +24 -3
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/README.md +23 -2
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/__init__.py +1 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/cli.py +33 -115
- ripperdoc-0.2.9/ripperdoc/cli/commands/__init__.py +146 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/agents_cmd.py +6 -3
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/clear_cmd.py +1 -4
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/config_cmd.py +1 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/context_cmd.py +3 -2
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/doctor_cmd.py +18 -4
- ripperdoc-0.2.9/ripperdoc/cli/commands/help_cmd.py +30 -0
- ripperdoc-0.2.9/ripperdoc/cli/commands/hooks_cmd.py +610 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/models_cmd.py +26 -9
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/permissions_cmd.py +57 -37
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/resume_cmd.py +6 -4
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/status_cmd.py +4 -4
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/tasks_cmd.py +8 -4
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/ui/file_mention_completer.py +64 -8
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/ui/interrupt_handler.py +3 -4
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/ui/message_display.py +5 -3
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/ui/panels.py +13 -10
- ripperdoc-0.2.9/ripperdoc/cli/ui/provider_options.py +247 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/ui/rich_ui.py +196 -77
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/ui/spinner.py +25 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/ui/tool_renderers.py +8 -2
- ripperdoc-0.2.9/ripperdoc/cli/ui/wizard.py +215 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/agents.py +9 -3
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/config.py +49 -12
- ripperdoc-0.2.9/ripperdoc/core/custom_commands.py +412 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/default_tools.py +11 -2
- ripperdoc-0.2.9/ripperdoc/core/hooks/__init__.py +99 -0
- ripperdoc-0.2.9/ripperdoc/core/hooks/config.py +301 -0
- ripperdoc-0.2.9/ripperdoc/core/hooks/events.py +535 -0
- ripperdoc-0.2.9/ripperdoc/core/hooks/executor.py +496 -0
- ripperdoc-0.2.9/ripperdoc/core/hooks/integration.py +344 -0
- ripperdoc-0.2.9/ripperdoc/core/hooks/manager.py +745 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/permissions.py +40 -8
- ripperdoc-0.2.9/ripperdoc/core/providers/anthropic.py +730 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/providers/gemini.py +70 -5
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/providers/openai.py +60 -5
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/query.py +140 -39
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/query_utils.py +2 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/skills.py +9 -3
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/system_prompt.py +4 -2
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/tool.py +9 -5
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/sdk/client.py +2 -2
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/ask_user_question_tool.py +5 -3
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/background_shell.py +2 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/bash_output_tool.py +1 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/bash_tool.py +30 -20
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/dynamic_mcp_tool.py +29 -8
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/enter_plan_mode_tool.py +1 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/exit_plan_mode_tool.py +1 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/file_edit_tool.py +8 -4
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/file_read_tool.py +9 -5
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/file_write_tool.py +9 -5
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/glob_tool.py +3 -2
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/grep_tool.py +3 -2
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/kill_bash_tool.py +1 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/ls_tool.py +1 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/mcp_tools.py +13 -10
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/multi_edit_tool.py +8 -7
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/notebook_edit_tool.py +7 -4
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/skill_tool.py +1 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/task_tool.py +5 -4
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/todo_tool.py +2 -2
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/tool_search_tool.py +3 -2
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/conversation_compaction.py +11 -7
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/file_watch.py +8 -2
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/json_utils.py +2 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/mcp.py +11 -3
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/memory.py +4 -2
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/message_compaction.py +21 -7
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/message_formatting.py +11 -7
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/messages.py +105 -66
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/path_ignore.py +38 -12
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/permissions/path_validation_utils.py +2 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/permissions/shell_command_validation.py +427 -91
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/safe_get_cwd.py +2 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/session_history.py +13 -6
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/todo.py +2 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/token_estimation.py +6 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc.egg-info/PKG-INFO +24 -3
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc.egg-info/SOURCES.txt +13 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_compact.py +2 -6
- ripperdoc-0.2.9/tests/test_custom_commands.py +260 -0
- ripperdoc-0.2.9/tests/test_file_mention_completer.py +72 -0
- ripperdoc-0.2.9/tests/test_hooks_cmd.py +139 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_path_ignore.py +3 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_permissions.py +12 -12
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_query_abort.py +3 -3
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_sdk.py +2 -2
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_shell_permissions.py +18 -12
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_tool_search.py +1 -1
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_tools.py +1 -1
- ripperdoc-0.2.7/ripperdoc/cli/commands/__init__.py +0 -82
- ripperdoc-0.2.7/ripperdoc/cli/commands/help_cmd.py +0 -20
- ripperdoc-0.2.7/ripperdoc/core/providers/anthropic.py +0 -250
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/LICENSE +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/pyproject.toml +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/__main__.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/__init__.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/base.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/compact_cmd.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/cost_cmd.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/exit_cmd.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/mcp_cmd.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/memory_cmd.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/todos_cmd.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/commands/tools_cmd.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/ui/__init__.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/ui/context_display.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/ui/helpers.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/cli/ui/thinking_spinner.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/__init__.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/commands.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/providers/__init__.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/core/providers/base.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/sdk/__init__.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/tools/__init__.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/__init__.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/bash_constants.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/bash_output_utils.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/coerce.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/context_length_errors.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/exit_code_handlers.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/git_utils.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/log.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/output_utils.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/path_utils.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/permissions/__init__.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/permissions/tool_permission_utils.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/prompt.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/sandbox_utils.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/session_usage.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/shell_token_utils.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc/utils/shell_utils.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc.egg-info/dependency_links.txt +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc.egg-info/entry_points.txt +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc.egg-info/requires.txt +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/ripperdoc.egg-info/top_level.txt +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/setup.cfg +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/setup.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_background_shell_shutdown.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_cli_commands.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_config.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_context_length_errors.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_context_limits.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_mcp_config.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_messages.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_output_utils.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_skills.py +0 -0
- {ripperdoc-0.2.7 → ripperdoc-0.2.9}/tests/test_todo.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.9
|
|
4
4
|
Summary: AI-powered terminal assistant for coding tasks
|
|
5
5
|
Author: Ripperdoc Team
|
|
6
6
|
License: Apache-2.0
|
|
@@ -35,9 +35,28 @@ Requires-Dist: black>=23.0.0; extra == "dev"
|
|
|
35
35
|
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
36
36
|
Dynamic: license-file
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
<div align="center">
|
|
39
39
|
|
|
40
|
-
Ripperdoc
|
|
40
|
+
# Ripperdoc
|
|
41
|
+
|
|
42
|
+
_an open-source, extensible AI coding agent that runs in your terminal_
|
|
43
|
+
|
|
44
|
+
<p align="center">
|
|
45
|
+
<a href="https://opensource.org/licenses/Apache-2.0">
|
|
46
|
+
<img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg">
|
|
47
|
+
</a>
|
|
48
|
+
<a href="https://www.python.org/downloads/">
|
|
49
|
+
<img src="https://img.shields.io/badge/python-3.10+-blue.svg">
|
|
50
|
+
</a>
|
|
51
|
+
<a href="https://github.com/quantmew/ripperdoc/stargazers">
|
|
52
|
+
<img src="https://img.shields.io/github/stars/quantmew/ripperdoc.svg" alt="GitHub stars">
|
|
53
|
+
</a>
|
|
54
|
+
</p>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
Ripperdoc is your on-machine AI coding assistant, similar to [Claude Code](https://claude.com/claude-code), [Codex](https://github.com/openai/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Aider](https://github.com/paul-gauthier/aider), and [Goose](https://github.com/block/goose). It can write code, refactor projects, execute shell commands, and manage files - all through natural language conversations in your terminal.
|
|
58
|
+
|
|
59
|
+
Designed for maximum flexibility, Ripperdoc works with **any LLM** (Anthropic Claude, OpenAI, DeepSeek, local models via OpenAI-compatible APIs), supports **custom hooks** to intercept and control tool execution, and offers both an interactive CLI and a **Python SDK** for headless automation.
|
|
41
60
|
|
|
42
61
|
[中文文档](README_CN.md) | [Contributing](CONTRIBUTING.md) | [Documentation](docs/)
|
|
43
62
|
|
|
@@ -60,6 +79,8 @@ Ripperdoc is an AI-powered terminal assistant for coding tasks, providing an int
|
|
|
60
79
|
- **MCP Server Support** - Integration with Model Context Protocol servers
|
|
61
80
|
- **Session Management** - Persistent session history and usage tracking
|
|
62
81
|
- **Jupyter Notebook Support** - Edit .ipynb files directly
|
|
82
|
+
- **Hooks System** - Execute custom scripts at lifecycle events with decision control
|
|
83
|
+
- **Custom Commands** - Define reusable slash commands with parameter substitution
|
|
63
84
|
|
|
64
85
|
## Installation
|
|
65
86
|
|
|
@@ -1,6 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
Ripperdoc
|
|
3
|
+
# Ripperdoc
|
|
4
|
+
|
|
5
|
+
_an open-source, extensible AI coding agent that runs in your terminal_
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://opensource.org/licenses/Apache-2.0">
|
|
9
|
+
<img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg">
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://www.python.org/downloads/">
|
|
12
|
+
<img src="https://img.shields.io/badge/python-3.10+-blue.svg">
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://github.com/quantmew/ripperdoc/stargazers">
|
|
15
|
+
<img src="https://img.shields.io/github/stars/quantmew/ripperdoc.svg" alt="GitHub stars">
|
|
16
|
+
</a>
|
|
17
|
+
</p>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
Ripperdoc is your on-machine AI coding assistant, similar to [Claude Code](https://claude.com/claude-code), [Codex](https://github.com/openai/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Aider](https://github.com/paul-gauthier/aider), and [Goose](https://github.com/block/goose). It can write code, refactor projects, execute shell commands, and manage files - all through natural language conversations in your terminal.
|
|
21
|
+
|
|
22
|
+
Designed for maximum flexibility, Ripperdoc works with **any LLM** (Anthropic Claude, OpenAI, DeepSeek, local models via OpenAI-compatible APIs), supports **custom hooks** to intercept and control tool execution, and offers both an interactive CLI and a **Python SDK** for headless automation.
|
|
4
23
|
|
|
5
24
|
[中文文档](README_CN.md) | [Contributing](CONTRIBUTING.md) | [Documentation](docs/)
|
|
6
25
|
|
|
@@ -23,6 +42,8 @@ Ripperdoc is an AI-powered terminal assistant for coding tasks, providing an int
|
|
|
23
42
|
- **MCP Server Support** - Integration with Model Context Protocol servers
|
|
24
43
|
- **Session Management** - Persistent session history and usage tracking
|
|
25
44
|
- **Jupyter Notebook Support** - Edit .ipynb files directly
|
|
45
|
+
- **Hooks System** - Execute custom scripts at lifecycle events with decision control
|
|
46
|
+
- **Custom Commands** - Define reusable slash commands with parameter substitution
|
|
26
47
|
|
|
27
48
|
## Installation
|
|
28
49
|
|
|
@@ -13,15 +13,14 @@ from typing import Any, Dict, List, Optional
|
|
|
13
13
|
from ripperdoc import __version__
|
|
14
14
|
from ripperdoc.core.config import (
|
|
15
15
|
get_global_config,
|
|
16
|
-
save_global_config,
|
|
17
16
|
get_project_config,
|
|
18
|
-
ModelProfile,
|
|
19
|
-
ProviderType,
|
|
20
17
|
)
|
|
18
|
+
from ripperdoc.cli.ui.wizard import check_onboarding
|
|
21
19
|
from ripperdoc.core.default_tools import get_default_tools
|
|
22
20
|
from ripperdoc.core.query import query, QueryContext
|
|
23
21
|
from ripperdoc.core.system_prompt import build_system_prompt
|
|
24
22
|
from ripperdoc.core.skills import build_skill_summary, load_all_skills
|
|
23
|
+
from ripperdoc.core.hooks.manager import hook_manager
|
|
25
24
|
from ripperdoc.utils.messages import create_user_message
|
|
26
25
|
from ripperdoc.utils.memory import build_memory_instructions
|
|
27
26
|
from ripperdoc.core.permissions import make_permission_checker
|
|
@@ -32,7 +31,7 @@ from ripperdoc.utils.mcp import (
|
|
|
32
31
|
)
|
|
33
32
|
from ripperdoc.tools.mcp_tools import load_dynamic_mcp_tools_async, merge_tools_with_dynamic
|
|
34
33
|
from ripperdoc.utils.log import enable_session_file_logging, get_logger
|
|
35
|
-
|
|
34
|
+
|
|
36
35
|
|
|
37
36
|
from rich.console import Console
|
|
38
37
|
from rich.markdown import Markdown
|
|
@@ -46,7 +45,7 @@ logger = get_logger()
|
|
|
46
45
|
async def run_query(
|
|
47
46
|
prompt: str,
|
|
48
47
|
tools: list,
|
|
49
|
-
|
|
48
|
+
yolo_mode: bool = False,
|
|
50
49
|
verbose: bool = False,
|
|
51
50
|
session_id: Optional[str] = None,
|
|
52
51
|
) -> None:
|
|
@@ -55,7 +54,7 @@ async def run_query(
|
|
|
55
54
|
logger.info(
|
|
56
55
|
"[cli] Running single prompt session",
|
|
57
56
|
extra={
|
|
58
|
-
"
|
|
57
|
+
"yolo_mode": yolo_mode,
|
|
59
58
|
"verbose": verbose,
|
|
60
59
|
"session_id": session_id,
|
|
61
60
|
"prompt_length": len(prompt),
|
|
@@ -68,7 +67,11 @@ async def run_query(
|
|
|
68
67
|
)
|
|
69
68
|
|
|
70
69
|
project_path = Path.cwd()
|
|
71
|
-
can_use_tool = make_permission_checker(project_path,
|
|
70
|
+
can_use_tool = None if yolo_mode else make_permission_checker(project_path, yolo_mode=False)
|
|
71
|
+
|
|
72
|
+
# Initialize hook manager
|
|
73
|
+
hook_manager.set_project_dir(project_path)
|
|
74
|
+
hook_manager.set_session_id(session_id)
|
|
72
75
|
|
|
73
76
|
# Create initial user message
|
|
74
77
|
from ripperdoc.utils.messages import UserMessage, AssistantMessage, ProgressMessage
|
|
@@ -76,7 +79,7 @@ async def run_query(
|
|
|
76
79
|
messages: List[UserMessage | AssistantMessage | ProgressMessage] = [create_user_message(prompt)]
|
|
77
80
|
|
|
78
81
|
# Create query context
|
|
79
|
-
query_context = QueryContext(tools=tools,
|
|
82
|
+
query_context = QueryContext(tools=tools, yolo_mode=yolo_mode, verbose=verbose)
|
|
80
83
|
|
|
81
84
|
try:
|
|
82
85
|
context: Dict[str, Any] = {}
|
|
@@ -164,7 +167,8 @@ async def run_query(
|
|
|
164
167
|
console.print(f"[red]Error: {escape(str(e))}[/red]")
|
|
165
168
|
logger.warning(
|
|
166
169
|
"[cli] Unhandled error while running prompt: %s: %s",
|
|
167
|
-
type(e).__name__,
|
|
170
|
+
type(e).__name__,
|
|
171
|
+
e,
|
|
168
172
|
extra={"session_id": session_id},
|
|
169
173
|
)
|
|
170
174
|
if verbose:
|
|
@@ -180,104 +184,6 @@ async def run_query(
|
|
|
180
184
|
logger.debug("[cli] Shutdown MCP runtime", extra={"session_id": session_id})
|
|
181
185
|
|
|
182
186
|
|
|
183
|
-
def check_onboarding() -> bool:
|
|
184
|
-
"""Check if onboarding is complete and run if needed."""
|
|
185
|
-
config = get_global_config()
|
|
186
|
-
|
|
187
|
-
if config.has_completed_onboarding:
|
|
188
|
-
return True
|
|
189
|
-
|
|
190
|
-
console.print("[bold cyan]Welcome to Ripperdoc![/bold cyan]\n")
|
|
191
|
-
console.print("Let's set up your AI model configuration.\n")
|
|
192
|
-
|
|
193
|
-
# Simple onboarding
|
|
194
|
-
provider_choices = [
|
|
195
|
-
*[p.value for p in ProviderType],
|
|
196
|
-
"openai",
|
|
197
|
-
"deepseek",
|
|
198
|
-
"mistral",
|
|
199
|
-
"kimi",
|
|
200
|
-
"qwen",
|
|
201
|
-
"glm",
|
|
202
|
-
"custom",
|
|
203
|
-
]
|
|
204
|
-
provider_choice = click.prompt(
|
|
205
|
-
"Choose your model protocol",
|
|
206
|
-
type=click.Choice(provider_choices),
|
|
207
|
-
default=ProviderType.ANTHROPIC.value,
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
api_base = None
|
|
211
|
-
if provider_choice == "custom":
|
|
212
|
-
provider_choice = click.prompt(
|
|
213
|
-
"Protocol family (for API compatibility)",
|
|
214
|
-
type=click.Choice([p.value for p in ProviderType]),
|
|
215
|
-
default=ProviderType.OPENAI_COMPATIBLE.value,
|
|
216
|
-
)
|
|
217
|
-
api_base = click.prompt("API Base URL")
|
|
218
|
-
|
|
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]")
|
|
224
|
-
|
|
225
|
-
provider = ProviderType(provider_choice)
|
|
226
|
-
|
|
227
|
-
# Get model name
|
|
228
|
-
if provider == ProviderType.ANTHROPIC:
|
|
229
|
-
model = click.prompt("Model name", default="claude-3-5-sonnet-20241022")
|
|
230
|
-
elif provider == ProviderType.OPENAI_COMPATIBLE:
|
|
231
|
-
default_model = "gpt-4o-mini"
|
|
232
|
-
if provider_choice == "deepseek":
|
|
233
|
-
default_model = "deepseek-chat"
|
|
234
|
-
api_base = api_base or "https://api.deepseek.com"
|
|
235
|
-
model = click.prompt("Model name", default=default_model)
|
|
236
|
-
if api_base is None:
|
|
237
|
-
api_base = (
|
|
238
|
-
click.prompt("API base URL (optional)", default="", show_default=False) or None
|
|
239
|
-
)
|
|
240
|
-
elif provider == ProviderType.GEMINI:
|
|
241
|
-
console.print(
|
|
242
|
-
"[yellow]Gemini protocol support is not yet available; configuration is saved for "
|
|
243
|
-
"future support.[/yellow]"
|
|
244
|
-
)
|
|
245
|
-
model = click.prompt("Model name", default="gemini-1.5-pro")
|
|
246
|
-
if api_base is None:
|
|
247
|
-
api_base = (
|
|
248
|
-
click.prompt("API base URL (optional)", default="", show_default=False) or None
|
|
249
|
-
)
|
|
250
|
-
else:
|
|
251
|
-
model = click.prompt("Model name")
|
|
252
|
-
|
|
253
|
-
context_window_input = click.prompt(
|
|
254
|
-
"Context window in tokens (optional, press Enter to skip)", default="", show_default=False
|
|
255
|
-
)
|
|
256
|
-
context_window = None
|
|
257
|
-
if context_window_input.strip():
|
|
258
|
-
try:
|
|
259
|
-
context_window = int(context_window_input.strip())
|
|
260
|
-
except ValueError:
|
|
261
|
-
console.print("[yellow]Invalid context window, using auto-detected defaults.[/yellow]")
|
|
262
|
-
|
|
263
|
-
# Create model profile
|
|
264
|
-
config.model_profiles["default"] = ModelProfile(
|
|
265
|
-
provider=provider,
|
|
266
|
-
model=model,
|
|
267
|
-
api_key=api_key,
|
|
268
|
-
api_base=api_base,
|
|
269
|
-
context_window=context_window,
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
config.has_completed_onboarding = True
|
|
273
|
-
config.last_onboarding_version = __version__
|
|
274
|
-
|
|
275
|
-
save_global_config(config)
|
|
276
|
-
|
|
277
|
-
console.print("\n[green]✓ Configuration saved![/green]\n")
|
|
278
|
-
|
|
279
|
-
return True
|
|
280
|
-
|
|
281
187
|
|
|
282
188
|
@click.group(invoke_without_command=True)
|
|
283
189
|
@click.version_option(version=__version__)
|
|
@@ -288,10 +194,11 @@ def check_onboarding() -> bool:
|
|
|
288
194
|
help="YOLO mode: skip all permission prompts for tools",
|
|
289
195
|
)
|
|
290
196
|
@click.option("--verbose", is_flag=True, help="Verbose output")
|
|
197
|
+
@click.option("--show-full-thinking", is_flag=True, help="Show full reasoning content instead of truncated preview")
|
|
291
198
|
@click.option("-p", "--prompt", type=str, help="Direct prompt (non-interactive)")
|
|
292
199
|
@click.pass_context
|
|
293
200
|
def cli(
|
|
294
|
-
ctx: click.Context, cwd: Optional[str], yolo: bool, verbose: bool, prompt: Optional[str]
|
|
201
|
+
ctx: click.Context, cwd: Optional[str], yolo: bool, verbose: bool, show_full_thinking: bool, prompt: Optional[str]
|
|
295
202
|
) -> None:
|
|
296
203
|
"""Ripperdoc - AI-powered coding agent"""
|
|
297
204
|
session_id = str(uuid.uuid4())
|
|
@@ -329,16 +236,16 @@ def cli(
|
|
|
329
236
|
# Initialize project configuration for the current working directory
|
|
330
237
|
get_project_config(project_path)
|
|
331
238
|
|
|
332
|
-
|
|
239
|
+
yolo_mode = yolo
|
|
333
240
|
logger.debug(
|
|
334
241
|
"[cli] Configuration initialized",
|
|
335
|
-
extra={"session_id": session_id, "
|
|
242
|
+
extra={"session_id": session_id, "yolo_mode": yolo_mode, "verbose": verbose},
|
|
336
243
|
)
|
|
337
244
|
|
|
338
245
|
# If prompt is provided, run directly
|
|
339
246
|
if prompt:
|
|
340
247
|
tools = get_default_tools()
|
|
341
|
-
asyncio.run(run_query(prompt, tools,
|
|
248
|
+
asyncio.run(run_query(prompt, tools, yolo_mode, verbose, session_id=session_id))
|
|
342
249
|
return
|
|
343
250
|
|
|
344
251
|
# If no command specified, start interactive REPL with Rich interface
|
|
@@ -347,8 +254,9 @@ def cli(
|
|
|
347
254
|
from ripperdoc.cli.ui.rich_ui import main_rich
|
|
348
255
|
|
|
349
256
|
main_rich(
|
|
350
|
-
|
|
257
|
+
yolo_mode=yolo_mode,
|
|
351
258
|
verbose=verbose,
|
|
259
|
+
show_full_thinking=show_full_thinking,
|
|
352
260
|
session_id=session_id,
|
|
353
261
|
log_file_path=log_file,
|
|
354
262
|
)
|
|
@@ -365,7 +273,8 @@ def config_cmd() -> None:
|
|
|
365
273
|
console.print(f"Onboarding Complete: {config.has_completed_onboarding}")
|
|
366
274
|
console.print(f"Theme: {config.theme}")
|
|
367
275
|
console.print(f"Verbose: {config.verbose}")
|
|
368
|
-
console.print(f"
|
|
276
|
+
console.print(f"Yolo Mode: {config.yolo_mode}")
|
|
277
|
+
console.print(f"Show Full Thinking: {config.show_full_thinking}\n")
|
|
369
278
|
|
|
370
279
|
if config.model_profiles:
|
|
371
280
|
console.print("[bold]Model Profiles:[/bold]")
|
|
@@ -392,11 +301,20 @@ def main() -> None:
|
|
|
392
301
|
sys.exit(130)
|
|
393
302
|
except SystemExit:
|
|
394
303
|
raise
|
|
395
|
-
except (
|
|
304
|
+
except (
|
|
305
|
+
RuntimeError,
|
|
306
|
+
ValueError,
|
|
307
|
+
TypeError,
|
|
308
|
+
OSError,
|
|
309
|
+
IOError,
|
|
310
|
+
ConnectionError,
|
|
311
|
+
click.ClickException,
|
|
312
|
+
) as e:
|
|
396
313
|
console.print(f"[red]Fatal error: {escape(str(e))}[/red]")
|
|
397
314
|
logger.warning(
|
|
398
315
|
"[cli] Fatal error in main CLI entrypoint: %s: %s",
|
|
399
|
-
type(e).__name__,
|
|
316
|
+
type(e).__name__,
|
|
317
|
+
e,
|
|
400
318
|
)
|
|
401
319
|
sys.exit(1)
|
|
402
320
|
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Slash command registry with custom command support."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, List, Optional, Tuple, Union
|
|
7
|
+
|
|
8
|
+
from .base import SlashCommand
|
|
9
|
+
from .agents_cmd import command as agents_command
|
|
10
|
+
from .clear_cmd import command as clear_command
|
|
11
|
+
from .compact_cmd import command as compact_command
|
|
12
|
+
from .config_cmd import command as config_command
|
|
13
|
+
from .cost_cmd import command as cost_command
|
|
14
|
+
from .context_cmd import command as context_command
|
|
15
|
+
from .doctor_cmd import command as doctor_command
|
|
16
|
+
from .exit_cmd import command as exit_command
|
|
17
|
+
from .help_cmd import command as help_command
|
|
18
|
+
from .hooks_cmd import command as hooks_command
|
|
19
|
+
from .memory_cmd import command as memory_command
|
|
20
|
+
from .mcp_cmd import command as mcp_command
|
|
21
|
+
from .models_cmd import command as models_command
|
|
22
|
+
from .permissions_cmd import command as permissions_command
|
|
23
|
+
from .resume_cmd import command as resume_command
|
|
24
|
+
from .tasks_cmd import command as tasks_command
|
|
25
|
+
from .status_cmd import command as status_command
|
|
26
|
+
from .todos_cmd import command as todos_command
|
|
27
|
+
from .tools_cmd import command as tools_command
|
|
28
|
+
|
|
29
|
+
from ripperdoc.core.custom_commands import (
|
|
30
|
+
CustomCommandDefinition,
|
|
31
|
+
load_all_custom_commands,
|
|
32
|
+
expand_command_content,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _build_registry(commands: List[SlashCommand]) -> Dict[str, SlashCommand]:
|
|
37
|
+
"""Map command names and aliases to SlashCommand entries."""
|
|
38
|
+
registry: Dict[str, SlashCommand] = {}
|
|
39
|
+
for cmd in commands:
|
|
40
|
+
registry[cmd.name] = cmd
|
|
41
|
+
for alias in cmd.aliases:
|
|
42
|
+
registry[alias] = cmd
|
|
43
|
+
return registry
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
ALL_COMMANDS: List[SlashCommand] = [
|
|
47
|
+
help_command,
|
|
48
|
+
clear_command,
|
|
49
|
+
config_command,
|
|
50
|
+
tools_command,
|
|
51
|
+
models_command,
|
|
52
|
+
exit_command,
|
|
53
|
+
status_command,
|
|
54
|
+
doctor_command,
|
|
55
|
+
memory_command,
|
|
56
|
+
permissions_command,
|
|
57
|
+
tasks_command,
|
|
58
|
+
todos_command,
|
|
59
|
+
mcp_command,
|
|
60
|
+
hooks_command,
|
|
61
|
+
cost_command,
|
|
62
|
+
context_command,
|
|
63
|
+
compact_command,
|
|
64
|
+
resume_command,
|
|
65
|
+
agents_command,
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
COMMAND_REGISTRY: Dict[str, SlashCommand] = _build_registry(ALL_COMMANDS)
|
|
69
|
+
|
|
70
|
+
# Cache for custom commands
|
|
71
|
+
_custom_commands_cache: Optional[Tuple[Path, List[CustomCommandDefinition]]] = None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _get_custom_commands(project_path: Optional[Path] = None) -> List[CustomCommandDefinition]:
|
|
75
|
+
"""Get custom commands with caching."""
|
|
76
|
+
global _custom_commands_cache
|
|
77
|
+
current_path = (project_path or Path.cwd()).resolve()
|
|
78
|
+
|
|
79
|
+
# Return cached commands if same project
|
|
80
|
+
if _custom_commands_cache and _custom_commands_cache[0] == current_path:
|
|
81
|
+
return _custom_commands_cache[1]
|
|
82
|
+
|
|
83
|
+
# Load and cache
|
|
84
|
+
result = load_all_custom_commands(project_path=current_path)
|
|
85
|
+
_custom_commands_cache = (current_path, result.commands)
|
|
86
|
+
return result.commands
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def refresh_custom_commands(project_path: Optional[Path] = None) -> List[CustomCommandDefinition]:
|
|
90
|
+
"""Force reload custom commands."""
|
|
91
|
+
global _custom_commands_cache
|
|
92
|
+
_custom_commands_cache = None
|
|
93
|
+
return _get_custom_commands(project_path)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def list_slash_commands() -> List[SlashCommand]:
|
|
97
|
+
"""Return the ordered list of base slash commands (no aliases)."""
|
|
98
|
+
return ALL_COMMANDS
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def list_custom_commands(project_path: Optional[Path] = None) -> List[CustomCommandDefinition]:
|
|
102
|
+
"""Return all loaded custom commands."""
|
|
103
|
+
return _get_custom_commands(project_path)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_slash_command(name: str) -> SlashCommand | None:
|
|
107
|
+
"""Return a built-in command by name or alias."""
|
|
108
|
+
return COMMAND_REGISTRY.get(name)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_custom_command(
|
|
112
|
+
name: str, project_path: Optional[Path] = None
|
|
113
|
+
) -> CustomCommandDefinition | None:
|
|
114
|
+
"""Return a custom command by name."""
|
|
115
|
+
commands = _get_custom_commands(project_path)
|
|
116
|
+
return next((cmd for cmd in commands if cmd.name == name), None)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def slash_command_completions(
|
|
120
|
+
project_path: Optional[Path] = None,
|
|
121
|
+
) -> List[Tuple[str, Union[SlashCommand, CustomCommandDefinition]]]:
|
|
122
|
+
"""Return (name, command) pairs for completion including aliases and custom commands."""
|
|
123
|
+
completions: List[Tuple[str, Union[SlashCommand, CustomCommandDefinition]]] = []
|
|
124
|
+
|
|
125
|
+
# Add built-in commands
|
|
126
|
+
completions.extend(list(COMMAND_REGISTRY.items()))
|
|
127
|
+
|
|
128
|
+
# Add custom commands
|
|
129
|
+
custom_cmds = _get_custom_commands(project_path)
|
|
130
|
+
for cmd in custom_cmds:
|
|
131
|
+
completions.append((cmd.name, cmd))
|
|
132
|
+
|
|
133
|
+
return completions
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
__all__ = [
|
|
137
|
+
"SlashCommand",
|
|
138
|
+
"CustomCommandDefinition",
|
|
139
|
+
"list_slash_commands",
|
|
140
|
+
"list_custom_commands",
|
|
141
|
+
"get_slash_command",
|
|
142
|
+
"get_custom_command",
|
|
143
|
+
"slash_command_completions",
|
|
144
|
+
"refresh_custom_commands",
|
|
145
|
+
"expand_command_content",
|
|
146
|
+
]
|
|
@@ -116,7 +116,8 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
116
116
|
print_agents_usage()
|
|
117
117
|
logger.warning(
|
|
118
118
|
"[agents_cmd] Failed to create agent: %s: %s",
|
|
119
|
-
type(exc).__name__,
|
|
119
|
+
type(exc).__name__,
|
|
120
|
+
exc,
|
|
120
121
|
extra={"agent": agent_name, "session_id": getattr(ui, "session_id", None)},
|
|
121
122
|
)
|
|
122
123
|
return True
|
|
@@ -148,7 +149,8 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
148
149
|
print_agents_usage()
|
|
149
150
|
logger.warning(
|
|
150
151
|
"[agents_cmd] Failed to delete agent: %s: %s",
|
|
151
|
-
type(exc).__name__,
|
|
152
|
+
type(exc).__name__,
|
|
153
|
+
exc,
|
|
152
154
|
extra={"agent": agent_name, "session_id": getattr(ui, "session_id", None)},
|
|
153
155
|
)
|
|
154
156
|
return True
|
|
@@ -226,7 +228,8 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
226
228
|
print_agents_usage()
|
|
227
229
|
logger.warning(
|
|
228
230
|
"[agents_cmd] Failed to update agent: %s: %s",
|
|
229
|
-
type(exc).__name__,
|
|
231
|
+
type(exc).__name__,
|
|
232
|
+
exc,
|
|
230
233
|
extra={"agent": agent_name, "session_id": getattr(ui, "session_id", None)},
|
|
231
234
|
)
|
|
232
235
|
return True
|
|
@@ -9,10 +9,7 @@ def _handle(ui: Any, _: str) -> bool:
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
command = SlashCommand(
|
|
12
|
-
name="clear",
|
|
13
|
-
description="Clear conversation history",
|
|
14
|
-
handler=_handle,
|
|
15
|
-
aliases=("new",)
|
|
12
|
+
name="clear", description="Clear conversation history", handler=_handle, aliases=("new",)
|
|
16
13
|
)
|
|
17
14
|
|
|
18
15
|
|
|
@@ -16,7 +16,7 @@ def _handle(ui: Any, _: str) -> bool:
|
|
|
16
16
|
ui.console.print(
|
|
17
17
|
f"\n[bold]Model (main -> {escape(str(main_pointer))}):[/bold] {escape(str(model_label))}"
|
|
18
18
|
)
|
|
19
|
-
ui.console.print(f"[bold]
|
|
19
|
+
ui.console.print(f"[bold]Yolo Mode:[/bold] {escape(str(ui.yolo_mode))}")
|
|
20
20
|
ui.console.print(f"[bold]Verbose:[/bold] {escape(str(ui.verbose))}")
|
|
21
21
|
return True
|
|
22
22
|
|
|
@@ -40,7 +40,7 @@ def _handle(ui: Any, _: str) -> bool:
|
|
|
40
40
|
if not ui.query_context:
|
|
41
41
|
ui.query_context = QueryContext(
|
|
42
42
|
tools=ui.get_default_tools(),
|
|
43
|
-
|
|
43
|
+
yolo_mode=ui.yolo_mode,
|
|
44
44
|
verbose=ui.verbose,
|
|
45
45
|
)
|
|
46
46
|
|
|
@@ -126,7 +126,8 @@ def _handle(ui: Any, _: str) -> bool:
|
|
|
126
126
|
except (OSError, RuntimeError, AttributeError, TypeError) as exc:
|
|
127
127
|
logger.warning(
|
|
128
128
|
"[context_cmd] Failed to summarize MCP tools: %s: %s",
|
|
129
|
-
type(exc).__name__,
|
|
129
|
+
type(exc).__name__,
|
|
130
|
+
exc,
|
|
130
131
|
extra={"session_id": getattr(ui, "session_id", None)},
|
|
131
132
|
)
|
|
132
133
|
for line in lines:
|
|
@@ -125,10 +125,17 @@ def _mcp_status(
|
|
|
125
125
|
servers = asyncio.run(_load())
|
|
126
126
|
else:
|
|
127
127
|
servers = runner(_load())
|
|
128
|
-
except (
|
|
128
|
+
except (
|
|
129
|
+
OSError,
|
|
130
|
+
RuntimeError,
|
|
131
|
+
ConnectionError,
|
|
132
|
+
ValueError,
|
|
133
|
+
TypeError,
|
|
134
|
+
) as exc: # pragma: no cover - defensive
|
|
129
135
|
logger.warning(
|
|
130
136
|
"[doctor] Failed to load MCP servers: %s: %s",
|
|
131
|
-
type(exc).__name__,
|
|
137
|
+
type(exc).__name__,
|
|
138
|
+
exc,
|
|
132
139
|
exc_info=exc,
|
|
133
140
|
)
|
|
134
141
|
rows.append(_status_row("MCP", "error", f"Failed to load MCP config: {exc}"))
|
|
@@ -161,10 +168,17 @@ def _project_status(project_path: Path) -> Tuple[str, str, str]:
|
|
|
161
168
|
return _status_row(
|
|
162
169
|
"Project config", "ok", f".ripperdoc/config.json loaded for {project_path}"
|
|
163
170
|
)
|
|
164
|
-
except (
|
|
171
|
+
except (
|
|
172
|
+
OSError,
|
|
173
|
+
IOError,
|
|
174
|
+
json.JSONDecodeError,
|
|
175
|
+
ValueError,
|
|
176
|
+
TypeError,
|
|
177
|
+
) as exc: # pragma: no cover - defensive
|
|
165
178
|
logger.warning(
|
|
166
179
|
"[doctor] Failed to load project config: %s: %s",
|
|
167
|
-
type(exc).__name__,
|
|
180
|
+
type(exc).__name__,
|
|
181
|
+
exc,
|
|
168
182
|
exc_info=exc,
|
|
169
183
|
)
|
|
170
184
|
return _status_row(
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from .base import SlashCommand
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _handle(ui: Any, _: str) -> bool:
|
|
6
|
+
ui.console.print("\n[bold]Available Slash Commands:[/bold]")
|
|
7
|
+
for cmd in ui.command_list:
|
|
8
|
+
alias_text = f" (aliases: {', '.join(cmd.aliases)})" if cmd.aliases else ""
|
|
9
|
+
ui.console.print(f" /{cmd.name:<12} - {cmd.description}{alias_text}")
|
|
10
|
+
|
|
11
|
+
# Show custom commands if any
|
|
12
|
+
custom_cmds = getattr(ui, "_custom_command_list", [])
|
|
13
|
+
if custom_cmds:
|
|
14
|
+
ui.console.print("\n[bold]Custom Commands:[/bold]")
|
|
15
|
+
for cmd in sorted(custom_cmds, key=lambda c: c.name):
|
|
16
|
+
hint = f" {cmd.argument_hint}" if cmd.argument_hint else ""
|
|
17
|
+
location = f" ({cmd.location.value})" if cmd.location else ""
|
|
18
|
+
ui.console.print(f" /{cmd.name:<12}{hint} - {cmd.description}{location}")
|
|
19
|
+
|
|
20
|
+
return True
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
command = SlashCommand(
|
|
24
|
+
name="help",
|
|
25
|
+
description="Show available slash commands",
|
|
26
|
+
handler=_handle,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
__all__ = ["command"]
|