ripperdoc 0.2.4__tar.gz → 0.2.6__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.4 → ripperdoc-0.2.6}/PKG-INFO +12 -1
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/README.md +11 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/__init__.py +1 -1
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/__main__.py +0 -5
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/cli.py +37 -16
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/__init__.py +2 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/agents_cmd.py +12 -9
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/compact_cmd.py +7 -3
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/context_cmd.py +33 -13
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/doctor_cmd.py +27 -14
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/exit_cmd.py +1 -1
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/mcp_cmd.py +13 -8
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/memory_cmd.py +5 -5
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/models_cmd.py +47 -16
- ripperdoc-0.2.6/ripperdoc/cli/commands/permissions_cmd.py +302 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/resume_cmd.py +1 -2
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/tasks_cmd.py +24 -13
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/ui/rich_ui.py +659 -407
- ripperdoc-0.2.6/ripperdoc/cli/ui/tool_renderers.py +298 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/agents.py +17 -9
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/config.py +130 -6
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/default_tools.py +7 -2
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/permissions.py +20 -14
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/providers/anthropic.py +107 -4
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/providers/base.py +33 -4
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/providers/gemini.py +169 -50
- ripperdoc-0.2.6/ripperdoc/core/providers/openai.py +487 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/query.py +306 -62
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/query_utils.py +50 -6
- ripperdoc-0.2.6/ripperdoc/core/skills.py +295 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/system_prompt.py +13 -7
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/tool.py +13 -9
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/sdk/client.py +14 -1
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/ask_user_question_tool.py +20 -22
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/background_shell.py +19 -13
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/bash_tool.py +376 -217
- ripperdoc-0.2.6/ripperdoc/tools/dynamic_mcp_tool.py +428 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/enter_plan_mode_tool.py +5 -2
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/exit_plan_mode_tool.py +6 -3
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/file_edit_tool.py +57 -12
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/file_read_tool.py +20 -8
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/file_write_tool.py +53 -15
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/glob_tool.py +10 -9
- ripperdoc-0.2.6/ripperdoc/tools/grep_tool.py +370 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/ls_tool.py +6 -6
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/mcp_tools.py +106 -456
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/multi_edit_tool.py +49 -9
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/notebook_edit_tool.py +61 -15
- ripperdoc-0.2.6/ripperdoc/tools/skill_tool.py +205 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/task_tool.py +7 -8
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/todo_tool.py +12 -12
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/tool_search_tool.py +5 -6
- ripperdoc-0.2.6/ripperdoc/utils/coerce.py +34 -0
- ripperdoc-0.2.6/ripperdoc/utils/context_length_errors.py +252 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/file_watch.py +5 -4
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/json_utils.py +4 -4
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/log.py +3 -3
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/mcp.py +36 -15
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/memory.py +9 -6
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/message_compaction.py +16 -11
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/messages.py +73 -8
- ripperdoc-0.2.6/ripperdoc/utils/path_ignore.py +677 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/permissions/__init__.py +7 -1
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/permissions/path_validation_utils.py +16 -13
- ripperdoc-0.2.6/ripperdoc/utils/permissions/shell_command_validation.py +552 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/prompt.py +1 -1
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/safe_get_cwd.py +5 -2
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/session_history.py +38 -19
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/todo.py +6 -2
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/token_estimation.py +4 -3
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc.egg-info/PKG-INFO +12 -1
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc.egg-info/SOURCES.txt +11 -0
- ripperdoc-0.2.6/tests/test_context_length_errors.py +74 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/tests/test_messages.py +39 -0
- ripperdoc-0.2.6/tests/test_path_ignore.py +492 -0
- ripperdoc-0.2.6/tests/test_shell_permissions.py +727 -0
- ripperdoc-0.2.6/tests/test_skills.py +196 -0
- ripperdoc-0.2.4/ripperdoc/core/providers/openai.py +0 -253
- ripperdoc-0.2.4/ripperdoc/tools/grep_tool.py +0 -239
- ripperdoc-0.2.4/ripperdoc/utils/permissions/shell_command_validation.py +0 -74
- ripperdoc-0.2.4/tests/test_shell_permissions.py +0 -124
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/LICENSE +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/pyproject.toml +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/__init__.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/base.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/clear_cmd.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/config_cmd.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/cost_cmd.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/help_cmd.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/status_cmd.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/todos_cmd.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/commands/tools_cmd.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/ui/__init__.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/ui/context_display.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/ui/helpers.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/ui/spinner.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/cli/ui/thinking_spinner.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/__init__.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/commands.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/core/providers/__init__.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/sdk/__init__.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/__init__.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/bash_output_tool.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/tools/kill_bash_tool.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/__init__.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/bash_constants.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/bash_output_utils.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/exit_code_handlers.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/git_utils.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/output_utils.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/path_utils.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/permissions/tool_permission_utils.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/sandbox_utils.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/session_usage.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/shell_token_utils.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc/utils/shell_utils.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc.egg-info/dependency_links.txt +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc.egg-info/entry_points.txt +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc.egg-info/requires.txt +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/ripperdoc.egg-info/top_level.txt +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/setup.cfg +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/setup.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/tests/test_background_shell_shutdown.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/tests/test_cli_commands.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/tests/test_config.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/tests/test_context_limits.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/tests/test_mcp_config.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/tests/test_output_utils.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/tests/test_permissions.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/tests/test_query_abort.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/tests/test_sdk.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/tests/test_todo.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/tests/test_tool_search.py +0 -0
- {ripperdoc-0.2.4 → ripperdoc-0.2.6}/tests/test_tools.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.6
|
|
4
4
|
Summary: AI-powered terminal assistant for coding tasks
|
|
5
5
|
Author: Ripperdoc Team
|
|
6
6
|
License: Apache-2.0
|
|
@@ -50,6 +50,7 @@ Ripperdoc is an AI-powered terminal assistant for coding tasks, providing an int
|
|
|
50
50
|
- **Codebase Understanding** - Analyzes project structure and code relationships
|
|
51
51
|
- **Command Execution** - Run shell commands with real-time feedback
|
|
52
52
|
- **Tool System** - Extensible architecture with specialized tools
|
|
53
|
+
- **Agent Skills** - Load SKILL.md bundles to extend the agent on demand
|
|
53
54
|
- **Subagents** - Delegate tasks to specialized agents with their own tool scopes
|
|
54
55
|
- **File Operations** - Read, write, edit, search, and manage files
|
|
55
56
|
- **Todo Tracking** - Plan, read, and update persistent todo lists per project
|
|
@@ -117,6 +118,16 @@ See the [examples/](examples/) directory for complete SDK usage examples.
|
|
|
117
118
|
|
|
118
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).
|
|
119
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
|
+
|
|
120
131
|
## Examples
|
|
121
132
|
|
|
122
133
|
### Code Analysis
|
|
@@ -13,6 +13,7 @@ Ripperdoc is an AI-powered terminal assistant for coding tasks, providing an int
|
|
|
13
13
|
- **Codebase Understanding** - Analyzes project structure and code relationships
|
|
14
14
|
- **Command Execution** - Run shell commands with real-time feedback
|
|
15
15
|
- **Tool System** - Extensible architecture with specialized tools
|
|
16
|
+
- **Agent Skills** - Load SKILL.md bundles to extend the agent on demand
|
|
16
17
|
- **Subagents** - Delegate tasks to specialized agents with their own tool scopes
|
|
17
18
|
- **File Operations** - Read, write, edit, search, and manage files
|
|
18
19
|
- **Todo Tracking** - Plan, read, and update persistent todo lists per project
|
|
@@ -80,6 +81,16 @@ See the [examples/](examples/) directory for complete SDK usage examples.
|
|
|
80
81
|
|
|
81
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).
|
|
82
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
|
+
|
|
83
94
|
## Examples
|
|
84
95
|
|
|
85
96
|
### Code Analysis
|
|
@@ -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__
|
|
@@ -21,6 +21,7 @@ from ripperdoc.core.config import (
|
|
|
21
21
|
from ripperdoc.core.default_tools import get_default_tools
|
|
22
22
|
from ripperdoc.core.query import query, QueryContext
|
|
23
23
|
from ripperdoc.core.system_prompt import build_system_prompt
|
|
24
|
+
from ripperdoc.core.skills import build_skill_summary, load_all_skills
|
|
24
25
|
from ripperdoc.utils.messages import create_user_message
|
|
25
26
|
from ripperdoc.utils.memory import build_memory_instructions
|
|
26
27
|
from ripperdoc.core.permissions import make_permission_checker
|
|
@@ -86,18 +87,26 @@ async def run_query(
|
|
|
86
87
|
tools = merge_tools_with_dynamic(tools, dynamic_tools)
|
|
87
88
|
query_context.tools = tools
|
|
88
89
|
mcp_instructions = format_mcp_instructions(servers)
|
|
89
|
-
|
|
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(
|
|
90
104
|
tools,
|
|
91
105
|
prompt,
|
|
92
106
|
context,
|
|
107
|
+
additional_instructions=additional_instructions or None,
|
|
93
108
|
mcp_instructions=mcp_instructions,
|
|
94
109
|
)
|
|
95
|
-
memory_instructions = build_memory_instructions()
|
|
96
|
-
system_prompt = (
|
|
97
|
-
f"{base_system_prompt}\n\n{memory_instructions}"
|
|
98
|
-
if memory_instructions
|
|
99
|
-
else base_system_prompt
|
|
100
|
-
)
|
|
101
110
|
|
|
102
111
|
# Run the query
|
|
103
112
|
try:
|
|
@@ -112,6 +121,7 @@ async def run_query(
|
|
|
112
121
|
Markdown(message.message.content),
|
|
113
122
|
title="Ripperdoc",
|
|
114
123
|
border_style="cyan",
|
|
124
|
+
padding=(0, 1),
|
|
115
125
|
)
|
|
116
126
|
)
|
|
117
127
|
else:
|
|
@@ -124,6 +134,7 @@ async def run_query(
|
|
|
124
134
|
Markdown(block["text"]),
|
|
125
135
|
title="Ripperdoc",
|
|
126
136
|
border_style="cyan",
|
|
137
|
+
padding=(0, 1),
|
|
127
138
|
)
|
|
128
139
|
)
|
|
129
140
|
else:
|
|
@@ -133,6 +144,7 @@ async def run_query(
|
|
|
133
144
|
Markdown(block.text or ""),
|
|
134
145
|
title="Ripperdoc",
|
|
135
146
|
border_style="cyan",
|
|
147
|
+
padding=(0, 1),
|
|
136
148
|
)
|
|
137
149
|
)
|
|
138
150
|
|
|
@@ -146,10 +158,14 @@ async def run_query(
|
|
|
146
158
|
|
|
147
159
|
except KeyboardInterrupt:
|
|
148
160
|
console.print("\n[yellow]Interrupted by user[/yellow]")
|
|
149
|
-
except
|
|
161
|
+
except asyncio.CancelledError:
|
|
162
|
+
console.print("\n[yellow]Operation cancelled[/yellow]")
|
|
163
|
+
except (RuntimeError, ValueError, TypeError, OSError, IOError, ConnectionError) as e:
|
|
150
164
|
console.print(f"[red]Error: {escape(str(e))}[/red]")
|
|
151
|
-
logger.
|
|
152
|
-
"[cli] Unhandled error while running prompt
|
|
165
|
+
logger.warning(
|
|
166
|
+
"[cli] Unhandled error while running prompt: %s: %s",
|
|
167
|
+
type(e).__name__, e,
|
|
168
|
+
extra={"session_id": session_id},
|
|
153
169
|
)
|
|
154
170
|
if verbose:
|
|
155
171
|
import traceback
|
|
@@ -267,15 +283,15 @@ def check_onboarding() -> bool:
|
|
|
267
283
|
@click.version_option(version=__version__)
|
|
268
284
|
@click.option("--cwd", type=click.Path(exists=True), help="Working directory")
|
|
269
285
|
@click.option(
|
|
270
|
-
"--
|
|
286
|
+
"--yolo",
|
|
271
287
|
is_flag=True,
|
|
272
|
-
help="
|
|
288
|
+
help="YOLO mode: skip all permission prompts for tools",
|
|
273
289
|
)
|
|
274
290
|
@click.option("--verbose", is_flag=True, help="Verbose output")
|
|
275
291
|
@click.option("-p", "--prompt", type=str, help="Direct prompt (non-interactive)")
|
|
276
292
|
@click.pass_context
|
|
277
293
|
def cli(
|
|
278
|
-
ctx: click.Context, cwd: Optional[str],
|
|
294
|
+
ctx: click.Context, cwd: Optional[str], yolo: bool, verbose: bool, prompt: Optional[str]
|
|
279
295
|
) -> None:
|
|
280
296
|
"""Ripperdoc - AI-powered coding agent"""
|
|
281
297
|
session_id = str(uuid.uuid4())
|
|
@@ -313,7 +329,7 @@ def cli(
|
|
|
313
329
|
# Initialize project configuration for the current working directory
|
|
314
330
|
get_project_config(project_path)
|
|
315
331
|
|
|
316
|
-
safe_mode = not
|
|
332
|
+
safe_mode = not yolo
|
|
317
333
|
logger.debug(
|
|
318
334
|
"[cli] Configuration initialized",
|
|
319
335
|
extra={"session_id": session_id, "safe_mode": safe_mode, "verbose": verbose},
|
|
@@ -374,9 +390,14 @@ def main() -> None:
|
|
|
374
390
|
except KeyboardInterrupt:
|
|
375
391
|
console.print("\n[yellow]Interrupted[/yellow]")
|
|
376
392
|
sys.exit(130)
|
|
377
|
-
except
|
|
393
|
+
except SystemExit:
|
|
394
|
+
raise
|
|
395
|
+
except (RuntimeError, ValueError, TypeError, OSError, IOError, ConnectionError, click.ClickException) as e:
|
|
378
396
|
console.print(f"[red]Fatal error: {escape(str(e))}[/red]")
|
|
379
|
-
logger.
|
|
397
|
+
logger.warning(
|
|
398
|
+
"[cli] Fatal error in main CLI entrypoint: %s: %s",
|
|
399
|
+
type(e).__name__, e,
|
|
400
|
+
)
|
|
380
401
|
sys.exit(1)
|
|
381
402
|
|
|
382
403
|
|
|
@@ -17,6 +17,7 @@ from .help_cmd import command as help_command
|
|
|
17
17
|
from .memory_cmd import command as memory_command
|
|
18
18
|
from .mcp_cmd import command as mcp_command
|
|
19
19
|
from .models_cmd import command as models_command
|
|
20
|
+
from .permissions_cmd import command as permissions_command
|
|
20
21
|
from .resume_cmd import command as resume_command
|
|
21
22
|
from .tasks_cmd import command as tasks_command
|
|
22
23
|
from .status_cmd import command as status_command
|
|
@@ -44,6 +45,7 @@ ALL_COMMANDS: List[SlashCommand] = [
|
|
|
44
45
|
status_command,
|
|
45
46
|
doctor_command,
|
|
46
47
|
memory_command,
|
|
48
|
+
permissions_command,
|
|
47
49
|
tasks_command,
|
|
48
50
|
todos_command,
|
|
49
51
|
mcp_command,
|
|
@@ -111,11 +111,12 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
111
111
|
console.print(
|
|
112
112
|
f"[green]✓ Agent '{escape(agent_name)}' created at {escape(str(path))}[/green]"
|
|
113
113
|
)
|
|
114
|
-
except
|
|
114
|
+
except (OSError, IOError, PermissionError, ValueError) as exc:
|
|
115
115
|
console.print(f"[red]Failed to create agent: {escape(str(exc))}[/red]")
|
|
116
116
|
print_agents_usage()
|
|
117
|
-
logger.
|
|
118
|
-
"[agents_cmd] Failed to create agent",
|
|
117
|
+
logger.warning(
|
|
118
|
+
"[agents_cmd] Failed to create agent: %s: %s",
|
|
119
|
+
type(exc).__name__, exc,
|
|
119
120
|
extra={"agent": agent_name, "session_id": getattr(ui, "session_id", None)},
|
|
120
121
|
)
|
|
121
122
|
return True
|
|
@@ -142,11 +143,12 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
142
143
|
)
|
|
143
144
|
except FileNotFoundError as exc:
|
|
144
145
|
console.print(f"[yellow]{escape(str(exc))}[/yellow]")
|
|
145
|
-
except
|
|
146
|
+
except (OSError, IOError, PermissionError, ValueError) as exc:
|
|
146
147
|
console.print(f"[red]Failed to delete agent: {escape(str(exc))}[/red]")
|
|
147
148
|
print_agents_usage()
|
|
148
|
-
logger.
|
|
149
|
-
"[agents_cmd] Failed to delete agent",
|
|
149
|
+
logger.warning(
|
|
150
|
+
"[agents_cmd] Failed to delete agent: %s: %s",
|
|
151
|
+
type(exc).__name__, exc,
|
|
150
152
|
extra={"agent": agent_name, "session_id": getattr(ui, "session_id", None)},
|
|
151
153
|
)
|
|
152
154
|
return True
|
|
@@ -219,11 +221,12 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
219
221
|
console.print(
|
|
220
222
|
f"[green]✓ Agent '{escape(agent_name)}' updated at {escape(str(path))}[/green]"
|
|
221
223
|
)
|
|
222
|
-
except
|
|
224
|
+
except (OSError, IOError, PermissionError, ValueError) as exc:
|
|
223
225
|
console.print(f"[red]Failed to update agent: {escape(str(exc))}[/red]")
|
|
224
226
|
print_agents_usage()
|
|
225
|
-
logger.
|
|
226
|
-
"[agents_cmd] Failed to update agent",
|
|
227
|
+
logger.warning(
|
|
228
|
+
"[agents_cmd] Failed to update agent: %s: %s",
|
|
229
|
+
type(exc).__name__, exc,
|
|
227
230
|
extra={"agent": agent_name, "session_id": getattr(ui, "session_id", None)},
|
|
228
231
|
)
|
|
229
232
|
return True
|
|
@@ -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,6 +6,7 @@ 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
12
|
get_remaining_context_tokens,
|
|
@@ -18,7 +18,6 @@ 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
|
)
|
|
23
22
|
from ripperdoc.utils.log import get_logger
|
|
24
23
|
|
|
@@ -46,21 +45,41 @@ def _handle(ui: Any, _: str) -> bool:
|
|
|
46
45
|
)
|
|
47
46
|
|
|
48
47
|
async def _load_servers() -> List[Any]:
|
|
49
|
-
|
|
50
|
-
return await load_mcp_servers_async(ui.project_path)
|
|
51
|
-
finally:
|
|
52
|
-
await shutdown_mcp_runtime()
|
|
48
|
+
return await load_mcp_servers_async(ui.project_path)
|
|
53
49
|
|
|
54
|
-
|
|
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())
|
|
55
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)
|
|
56
75
|
base_system_prompt = build_system_prompt(
|
|
57
76
|
ui.query_context.tools,
|
|
58
77
|
"",
|
|
59
78
|
{},
|
|
79
|
+
additional_instructions=additional_instructions or None,
|
|
60
80
|
mcp_instructions=mcp_instructions,
|
|
61
81
|
)
|
|
62
|
-
|
|
63
|
-
memory_tokens = estimate_tokens(memory_instructions) if memory_instructions else 0
|
|
82
|
+
memory_tokens = 0
|
|
64
83
|
mcp_tokens = estimate_mcp_tokens(servers) if mcp_instructions else 0
|
|
65
84
|
|
|
66
85
|
breakdown = summarize_context_usage(
|
|
@@ -99,14 +118,15 @@ def _handle(ui: Any, _: str) -> bool:
|
|
|
99
118
|
try:
|
|
100
119
|
schema = tool.input_schema.model_json_schema()
|
|
101
120
|
token_est = estimate_tokens(json.dumps(schema, sort_keys=True))
|
|
102
|
-
except
|
|
121
|
+
except (AttributeError, TypeError, ValueError):
|
|
103
122
|
token_est = 0
|
|
104
123
|
lines.append(f" └ {display}: {format_tokens(token_est)} tokens")
|
|
105
124
|
if len(mcp_tools) > 20:
|
|
106
125
|
lines.append(f" └ ... (+{len(mcp_tools) - 20} more)")
|
|
107
|
-
except
|
|
108
|
-
logger.
|
|
109
|
-
"[context_cmd] Failed to summarize MCP tools",
|
|
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,
|
|
110
130
|
extra={"session_id": getattr(ui, "session_id", None)},
|
|
111
131
|
)
|
|
112
132
|
for line in lines:
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import json
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any, List, Optional, Tuple
|
|
7
|
+
from typing import Any, Callable, List, Optional, Tuple
|
|
8
8
|
|
|
9
9
|
from rich.markup import escape
|
|
10
10
|
from rich.panel import Panel
|
|
@@ -18,7 +18,7 @@ from ripperdoc.core.config import (
|
|
|
18
18
|
)
|
|
19
19
|
from ripperdoc.cli.ui.helpers import get_profile_for_pointer
|
|
20
20
|
from ripperdoc.utils.log import get_logger
|
|
21
|
-
from ripperdoc.utils.mcp import load_mcp_servers_async
|
|
21
|
+
from ripperdoc.utils.mcp import load_mcp_servers_async
|
|
22
22
|
from ripperdoc.utils.sandbox_utils import is_sandbox_available
|
|
23
23
|
|
|
24
24
|
from .base import SlashCommand
|
|
@@ -108,21 +108,29 @@ def _sandbox_status() -> Tuple[str, str, str]:
|
|
|
108
108
|
return _status_row("Sandbox", "warn", "Sandbox runtime not detected; commands run normally")
|
|
109
109
|
|
|
110
110
|
|
|
111
|
-
def _mcp_status(
|
|
111
|
+
def _mcp_status(
|
|
112
|
+
project_path: Path, runner: Optional[Callable[[Any], Any]] = None
|
|
113
|
+
) -> Tuple[List[Tuple[str, str, str]], List[str]]:
|
|
112
114
|
"""Return MCP status rows and errors."""
|
|
113
115
|
rows: List[Tuple[str, str, str]] = []
|
|
114
116
|
errors: List[str] = []
|
|
115
117
|
|
|
116
118
|
async def _load() -> List[Any]:
|
|
117
|
-
|
|
118
|
-
return await load_mcp_servers_async(project_path)
|
|
119
|
-
finally:
|
|
120
|
-
await shutdown_mcp_runtime()
|
|
119
|
+
return await load_mcp_servers_async(project_path)
|
|
121
120
|
|
|
122
121
|
try:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
if runner is None:
|
|
123
|
+
import asyncio
|
|
124
|
+
|
|
125
|
+
servers = asyncio.run(_load())
|
|
126
|
+
else:
|
|
127
|
+
servers = runner(_load())
|
|
128
|
+
except (OSError, RuntimeError, ConnectionError, ValueError, TypeError) as exc: # pragma: no cover - defensive
|
|
129
|
+
logger.warning(
|
|
130
|
+
"[doctor] Failed to load MCP servers: %s: %s",
|
|
131
|
+
type(exc).__name__, exc,
|
|
132
|
+
exc_info=exc,
|
|
133
|
+
)
|
|
126
134
|
rows.append(_status_row("MCP", "error", f"Failed to load MCP config: {exc}"))
|
|
127
135
|
return rows, errors
|
|
128
136
|
|
|
@@ -153,8 +161,12 @@ def _project_status(project_path: Path) -> Tuple[str, str, str]:
|
|
|
153
161
|
return _status_row(
|
|
154
162
|
"Project config", "ok", f".ripperdoc/config.json loaded for {project_path}"
|
|
155
163
|
)
|
|
156
|
-
except
|
|
157
|
-
logger.
|
|
164
|
+
except (OSError, IOError, json.JSONDecodeError, ValueError, TypeError) as exc: # pragma: no cover - defensive
|
|
165
|
+
logger.warning(
|
|
166
|
+
"[doctor] Failed to load project config: %s: %s",
|
|
167
|
+
type(exc).__name__, exc,
|
|
168
|
+
exc_info=exc,
|
|
169
|
+
)
|
|
158
170
|
return _status_row(
|
|
159
171
|
"Project config", "warn", f"Could not read .ripperdoc/config.json: {exc}"
|
|
160
172
|
)
|
|
@@ -179,7 +191,8 @@ def _handle(ui: Any, _: str) -> bool:
|
|
|
179
191
|
project_row = _project_status(project_path)
|
|
180
192
|
results.append(project_row)
|
|
181
193
|
|
|
182
|
-
|
|
194
|
+
runner = getattr(ui, "run_async", None)
|
|
195
|
+
mcp_rows, mcp_errors = _mcp_status(project_path, runner=runner)
|
|
183
196
|
results.extend(mcp_rows)
|
|
184
197
|
results.append(_sandbox_status())
|
|
185
198
|
|
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
|
|
3
1
|
from rich.markup import escape
|
|
4
2
|
|
|
5
|
-
from ripperdoc.utils.mcp import load_mcp_servers_async
|
|
3
|
+
from ripperdoc.utils.mcp import load_mcp_servers_async
|
|
6
4
|
|
|
7
5
|
from typing import Any
|
|
8
6
|
from .base import SlashCommand
|
|
9
7
|
|
|
10
8
|
|
|
9
|
+
def _run_in_ui(ui: Any, coro: Any) -> Any:
|
|
10
|
+
runner = getattr(ui, "run_async", None)
|
|
11
|
+
if callable(runner):
|
|
12
|
+
return runner(coro)
|
|
13
|
+
# Fallback for non-UI contexts.
|
|
14
|
+
import asyncio
|
|
15
|
+
|
|
16
|
+
return asyncio.run(coro)
|
|
17
|
+
|
|
18
|
+
|
|
11
19
|
def _handle(ui: Any, _: str) -> bool:
|
|
12
20
|
async def _load() -> list:
|
|
13
|
-
|
|
14
|
-
return await load_mcp_servers_async(ui.project_path)
|
|
15
|
-
finally:
|
|
16
|
-
await shutdown_mcp_runtime()
|
|
21
|
+
return await load_mcp_servers_async(ui.project_path)
|
|
17
22
|
|
|
18
|
-
servers =
|
|
23
|
+
servers = _run_in_ui(ui, _load())
|
|
19
24
|
if not servers:
|
|
20
25
|
ui.console.print(
|
|
21
26
|
"[yellow]No MCP servers configured. Add servers to ~/.ripperdoc/mcp.json, ~/.mcp.json, or a project .mcp.json file.[/yellow]"
|
|
@@ -26,14 +26,14 @@ def _shorten_path(path: Path, project_path: Path) -> str:
|
|
|
26
26
|
"""Return a short, user-friendly path."""
|
|
27
27
|
try:
|
|
28
28
|
return str(path.resolve().relative_to(project_path.resolve()))
|
|
29
|
-
except
|
|
29
|
+
except (ValueError, OSError):
|
|
30
30
|
pass
|
|
31
31
|
|
|
32
32
|
home = Path.home()
|
|
33
33
|
try:
|
|
34
34
|
rel_home = path.resolve().relative_to(home)
|
|
35
35
|
return f"~/{rel_home}"
|
|
36
|
-
except
|
|
36
|
+
except (ValueError, OSError):
|
|
37
37
|
return str(path)
|
|
38
38
|
|
|
39
39
|
|
|
@@ -77,7 +77,7 @@ def _ensure_gitignore_entry(project_path: Path, entry: str) -> bool:
|
|
|
77
77
|
f.write("\n")
|
|
78
78
|
f.write(f"{entry}\n")
|
|
79
79
|
return True
|
|
80
|
-
except
|
|
80
|
+
except (OSError, IOError):
|
|
81
81
|
return False
|
|
82
82
|
|
|
83
83
|
|
|
@@ -116,7 +116,7 @@ def _open_in_editor(path: Path, console: Any) -> bool:
|
|
|
116
116
|
except FileNotFoundError:
|
|
117
117
|
console.print(f"[red]Editor command not found: {escape(editor_cmd[0])}[/red]")
|
|
118
118
|
return False
|
|
119
|
-
except
|
|
119
|
+
except (OSError, subprocess.SubprocessError) as exc: # pragma: no cover - best-effort logging
|
|
120
120
|
console.print(f"[red]Failed to launch editor: {escape(str(exc))}[/red]")
|
|
121
121
|
return False
|
|
122
122
|
|
|
@@ -195,7 +195,7 @@ command = SlashCommand(
|
|
|
195
195
|
name="memory",
|
|
196
196
|
description="List and edit AGENTS memory files",
|
|
197
197
|
handler=_handle,
|
|
198
|
-
aliases=(
|
|
198
|
+
aliases=(),
|
|
199
199
|
)
|
|
200
200
|
|
|
201
201
|
|
|
@@ -35,6 +35,7 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
35
35
|
console.print("[bold]/models edit <name>[/bold] — edit an existing model profile")
|
|
36
36
|
console.print("[bold]/models delete <name>[/bold] — delete a model profile")
|
|
37
37
|
console.print("[bold]/models use <name>[/bold] — set the main model pointer")
|
|
38
|
+
console.print("[bold]/models use <pointer> <name>[/bold] — set a specific pointer (main/task/reasoning/quick)")
|
|
38
39
|
|
|
39
40
|
def parse_int(prompt_text: str, default_value: Optional[int]) -> Optional[int]:
|
|
40
41
|
raw = console.input(prompt_text).strip()
|
|
@@ -181,10 +182,11 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
181
182
|
overwrite=overwrite,
|
|
182
183
|
set_as_main=set_as_main,
|
|
183
184
|
)
|
|
184
|
-
except
|
|
185
|
+
except (OSError, IOError, ValueError, TypeError, PermissionError) as exc:
|
|
185
186
|
console.print(f"[red]Failed to save model: {escape(str(exc))}[/red]")
|
|
186
|
-
logger.
|
|
187
|
-
"[models_cmd] Failed to save model profile",
|
|
187
|
+
logger.warning(
|
|
188
|
+
"[models_cmd] Failed to save model profile: %s: %s",
|
|
189
|
+
type(exc).__name__, exc,
|
|
188
190
|
extra={"profile": profile_name, "session_id": getattr(ui, "session_id", None)},
|
|
189
191
|
)
|
|
190
192
|
return True
|
|
@@ -289,10 +291,11 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
289
291
|
overwrite=True,
|
|
290
292
|
set_as_main=False,
|
|
291
293
|
)
|
|
292
|
-
except
|
|
294
|
+
except (OSError, IOError, ValueError, TypeError, PermissionError) as exc:
|
|
293
295
|
console.print(f"[red]Failed to update model: {escape(str(exc))}[/red]")
|
|
294
|
-
logger.
|
|
295
|
-
"[models_cmd] Failed to update model profile",
|
|
296
|
+
logger.warning(
|
|
297
|
+
"[models_cmd] Failed to update model profile: %s: %s",
|
|
298
|
+
type(exc).__name__, exc,
|
|
296
299
|
extra={"profile": profile_name, "session_id": getattr(ui, "session_id", None)},
|
|
297
300
|
)
|
|
298
301
|
return True
|
|
@@ -311,30 +314,58 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
311
314
|
console.print(f"[green]✓ Deleted model '{escape(target)}'[/green]")
|
|
312
315
|
except KeyError as exc:
|
|
313
316
|
console.print(f"[yellow]{escape(str(exc))}[/yellow]")
|
|
314
|
-
except
|
|
317
|
+
except (OSError, IOError, PermissionError) as exc:
|
|
315
318
|
console.print(f"[red]Failed to delete model: {escape(str(exc))}[/red]")
|
|
316
319
|
print_models_usage()
|
|
317
|
-
logger.
|
|
318
|
-
"[models_cmd] Failed to delete model profile",
|
|
320
|
+
logger.warning(
|
|
321
|
+
"[models_cmd] Failed to delete model profile: %s: %s",
|
|
322
|
+
type(exc).__name__, exc,
|
|
319
323
|
extra={"profile": target, "session_id": getattr(ui, "session_id", None)},
|
|
320
324
|
)
|
|
321
325
|
return True
|
|
322
326
|
|
|
323
327
|
if subcmd in ("use", "main", "set-main"):
|
|
324
|
-
|
|
328
|
+
# Support both "/models use <profile>" and "/models use <pointer> <profile>"
|
|
329
|
+
valid_pointers = {"main", "task", "reasoning", "quick"}
|
|
330
|
+
|
|
331
|
+
if len(tokens) >= 3:
|
|
332
|
+
# /models use <pointer> <profile>
|
|
333
|
+
pointer = tokens[1].lower()
|
|
334
|
+
target = tokens[2]
|
|
335
|
+
if pointer not in valid_pointers:
|
|
336
|
+
console.print(f"[red]Invalid pointer '{escape(pointer)}'. Valid pointers: {', '.join(valid_pointers)}[/red]")
|
|
337
|
+
print_models_usage()
|
|
338
|
+
return True
|
|
339
|
+
elif len(tokens) >= 2:
|
|
340
|
+
# Check if second token is a pointer or a profile
|
|
341
|
+
if tokens[1].lower() in valid_pointers:
|
|
342
|
+
pointer = tokens[1].lower()
|
|
343
|
+
target = console.input(f"Model to use for '{pointer}': ").strip()
|
|
344
|
+
else:
|
|
345
|
+
# /models use <profile> (defaults to main)
|
|
346
|
+
pointer = "main"
|
|
347
|
+
target = tokens[1]
|
|
348
|
+
else:
|
|
349
|
+
pointer = console.input("Pointer (main/task/reasoning/quick) [main]: ").strip().lower() or "main"
|
|
350
|
+
if pointer not in valid_pointers:
|
|
351
|
+
console.print(f"[red]Invalid pointer '{escape(pointer)}'. Valid pointers: {', '.join(valid_pointers)}[/red]")
|
|
352
|
+
return True
|
|
353
|
+
target = console.input(f"Model to use for '{pointer}': ").strip()
|
|
354
|
+
|
|
325
355
|
if not target:
|
|
326
356
|
console.print("[red]Model name is required.[/red]")
|
|
327
357
|
print_models_usage()
|
|
328
358
|
return True
|
|
329
359
|
try:
|
|
330
|
-
set_model_pointer(
|
|
331
|
-
console.print(f"[green]✓
|
|
332
|
-
except
|
|
360
|
+
set_model_pointer(pointer, target)
|
|
361
|
+
console.print(f"[green]✓ Pointer '{escape(pointer)}' set to '{escape(target)}'[/green]")
|
|
362
|
+
except (ValueError, KeyError, OSError, IOError, PermissionError) as exc:
|
|
333
363
|
console.print(f"[red]{escape(str(exc))}[/red]")
|
|
334
364
|
print_models_usage()
|
|
335
|
-
logger.
|
|
336
|
-
"[models_cmd] Failed to set
|
|
337
|
-
|
|
365
|
+
logger.warning(
|
|
366
|
+
"[models_cmd] Failed to set model pointer: %s: %s",
|
|
367
|
+
type(exc).__name__, exc,
|
|
368
|
+
extra={"pointer": pointer, "profile": target, "session_id": getattr(ui, "session_id", None)},
|
|
338
369
|
)
|
|
339
370
|
return True
|
|
340
371
|
|