code-puppy 0.0.169__py3-none-any.whl → 0.0.366__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_puppy/__init__.py +7 -1
- code_puppy/agents/__init__.py +8 -8
- code_puppy/agents/agent_c_reviewer.py +155 -0
- code_puppy/agents/agent_code_puppy.py +9 -2
- code_puppy/agents/agent_code_reviewer.py +90 -0
- code_puppy/agents/agent_cpp_reviewer.py +132 -0
- code_puppy/agents/agent_creator_agent.py +48 -9
- code_puppy/agents/agent_golang_reviewer.py +151 -0
- code_puppy/agents/agent_javascript_reviewer.py +160 -0
- code_puppy/agents/agent_manager.py +146 -199
- code_puppy/agents/agent_pack_leader.py +383 -0
- code_puppy/agents/agent_planning.py +163 -0
- code_puppy/agents/agent_python_programmer.py +165 -0
- code_puppy/agents/agent_python_reviewer.py +90 -0
- code_puppy/agents/agent_qa_expert.py +163 -0
- code_puppy/agents/agent_qa_kitten.py +208 -0
- code_puppy/agents/agent_security_auditor.py +181 -0
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/agent_typescript_reviewer.py +166 -0
- code_puppy/agents/base_agent.py +1713 -1
- code_puppy/agents/event_stream_handler.py +350 -0
- code_puppy/agents/json_agent.py +12 -1
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +304 -0
- code_puppy/agents/pack/husky.py +321 -0
- code_puppy/agents/pack/retriever.py +393 -0
- code_puppy/agents/pack/shepherd.py +348 -0
- code_puppy/agents/pack/terrier.py +287 -0
- code_puppy/agents/pack/watchdog.py +367 -0
- code_puppy/agents/prompt_reviewer.py +145 -0
- code_puppy/agents/subagent_stream_handler.py +276 -0
- code_puppy/api/__init__.py +13 -0
- code_puppy/api/app.py +169 -0
- code_puppy/api/main.py +21 -0
- code_puppy/api/pty_manager.py +446 -0
- code_puppy/api/routers/__init__.py +12 -0
- code_puppy/api/routers/agents.py +36 -0
- code_puppy/api/routers/commands.py +217 -0
- code_puppy/api/routers/config.py +74 -0
- code_puppy/api/routers/sessions.py +232 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +174 -4
- code_puppy/chatgpt_codex_client.py +283 -0
- code_puppy/claude_cache_client.py +586 -0
- code_puppy/cli_runner.py +916 -0
- code_puppy/command_line/add_model_menu.py +1079 -0
- code_puppy/command_line/agent_menu.py +395 -0
- code_puppy/command_line/attachments.py +395 -0
- code_puppy/command_line/autosave_menu.py +605 -0
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +520 -0
- code_puppy/command_line/command_handler.py +233 -627
- code_puppy/command_line/command_registry.py +150 -0
- code_puppy/command_line/config_commands.py +715 -0
- code_puppy/command_line/core_commands.py +792 -0
- code_puppy/command_line/diff_menu.py +863 -0
- code_puppy/command_line/load_context_completion.py +15 -22
- code_puppy/command_line/mcp/base.py +1 -4
- code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
- code_puppy/command_line/mcp/custom_server_form.py +688 -0
- code_puppy/command_line/mcp/custom_server_installer.py +195 -0
- code_puppy/command_line/mcp/edit_command.py +148 -0
- code_puppy/command_line/mcp/handler.py +9 -4
- code_puppy/command_line/mcp/help_command.py +6 -5
- code_puppy/command_line/mcp/install_command.py +16 -27
- code_puppy/command_line/mcp/install_menu.py +685 -0
- code_puppy/command_line/mcp/list_command.py +3 -3
- code_puppy/command_line/mcp/logs_command.py +174 -65
- code_puppy/command_line/mcp/remove_command.py +2 -2
- code_puppy/command_line/mcp/restart_command.py +12 -4
- code_puppy/command_line/mcp/search_command.py +17 -11
- code_puppy/command_line/mcp/start_all_command.py +22 -13
- code_puppy/command_line/mcp/start_command.py +50 -31
- code_puppy/command_line/mcp/status_command.py +6 -7
- code_puppy/command_line/mcp/stop_all_command.py +11 -8
- code_puppy/command_line/mcp/stop_command.py +11 -10
- code_puppy/command_line/mcp/test_command.py +2 -2
- code_puppy/command_line/mcp/utils.py +1 -1
- code_puppy/command_line/mcp/wizard_utils.py +22 -18
- code_puppy/command_line/mcp_completion.py +174 -0
- code_puppy/command_line/model_picker_completion.py +89 -30
- code_puppy/command_line/model_settings_menu.py +884 -0
- code_puppy/command_line/motd.py +14 -8
- code_puppy/command_line/onboarding_slides.py +179 -0
- code_puppy/command_line/onboarding_wizard.py +340 -0
- code_puppy/command_line/pin_command_completion.py +329 -0
- code_puppy/command_line/prompt_toolkit_completion.py +626 -75
- code_puppy/command_line/session_commands.py +296 -0
- code_puppy/command_line/utils.py +54 -0
- code_puppy/config.py +1181 -51
- code_puppy/error_logging.py +118 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/gemini_model.py +602 -0
- code_puppy/http_utils.py +220 -104
- code_puppy/keymap.py +128 -0
- code_puppy/main.py +5 -594
- code_puppy/{mcp → mcp_}/__init__.py +17 -0
- code_puppy/{mcp → mcp_}/async_lifecycle.py +35 -4
- code_puppy/{mcp → mcp_}/blocking_startup.py +70 -43
- code_puppy/{mcp → mcp_}/captured_stdio_server.py +2 -2
- code_puppy/{mcp → mcp_}/config_wizard.py +5 -5
- code_puppy/{mcp → mcp_}/dashboard.py +15 -6
- code_puppy/{mcp → mcp_}/examples/retry_example.py +4 -1
- code_puppy/{mcp → mcp_}/managed_server.py +66 -39
- code_puppy/{mcp → mcp_}/manager.py +146 -52
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/{mcp → mcp_}/registry.py +6 -6
- code_puppy/{mcp → mcp_}/server_registry_catalog.py +25 -8
- code_puppy/messaging/__init__.py +199 -2
- code_puppy/messaging/bus.py +610 -0
- code_puppy/messaging/commands.py +167 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/message_queue.py +17 -48
- code_puppy/messaging/messages.py +500 -0
- code_puppy/messaging/queue_console.py +1 -24
- code_puppy/messaging/renderers.py +43 -146
- code_puppy/messaging/rich_renderer.py +1027 -0
- code_puppy/messaging/spinner/__init__.py +33 -5
- code_puppy/messaging/spinner/console_spinner.py +92 -52
- code_puppy/messaging/spinner/spinner_base.py +29 -0
- code_puppy/messaging/subagent_console.py +461 -0
- code_puppy/model_factory.py +686 -80
- code_puppy/model_utils.py +167 -0
- code_puppy/models.json +86 -104
- code_puppy/models_dev_api.json +1 -0
- code_puppy/models_dev_parser.py +592 -0
- code_puppy/plugins/__init__.py +164 -10
- code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +136 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
- code_puppy/plugins/antigravity_oauth/storage.py +271 -0
- code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy/plugins/antigravity_oauth/transport.py +767 -0
- code_puppy/plugins/antigravity_oauth/utils.py +169 -0
- code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
- code_puppy/plugins/chatgpt_oauth/config.py +52 -0
- code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
- code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
- code_puppy/plugins/claude_code_oauth/README.md +167 -0
- code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
- code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
- code_puppy/plugins/claude_code_oauth/config.py +50 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
- code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_puppy/plugins/claude_code_oauth/utils.py +518 -0
- code_puppy/plugins/customizable_commands/__init__.py +0 -0
- code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
- code_puppy/plugins/example_custom_command/README.md +280 -0
- code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
- code_puppy/plugins/file_permission_handler/__init__.py +4 -0
- code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
- code_puppy/plugins/frontend_emitter/__init__.py +25 -0
- code_puppy/plugins/frontend_emitter/emitter.py +121 -0
- code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
- code_puppy/plugins/oauth_puppy_html.py +228 -0
- code_puppy/plugins/shell_safety/__init__.py +6 -0
- code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_puppy/plugins/shell_safety/command_cache.py +156 -0
- code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
- code_puppy/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/prompts/codex_system_prompt.md +310 -0
- code_puppy/pydantic_patches.py +131 -0
- code_puppy/reopenable_async_client.py +8 -8
- code_puppy/round_robin_model.py +10 -15
- code_puppy/session_storage.py +294 -0
- code_puppy/status_display.py +21 -4
- code_puppy/summarization_agent.py +52 -14
- code_puppy/terminal_utils.py +418 -0
- code_puppy/tools/__init__.py +139 -6
- code_puppy/tools/agent_tools.py +548 -49
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +289 -0
- code_puppy/tools/browser/browser_interactions.py +545 -0
- code_puppy/tools/browser/browser_locators.py +640 -0
- code_puppy/tools/browser/browser_manager.py +316 -0
- code_puppy/tools/browser/browser_navigation.py +251 -0
- code_puppy/tools/browser/browser_screenshot.py +179 -0
- code_puppy/tools/browser/browser_scripts.py +462 -0
- code_puppy/tools/browser/browser_workflows.py +221 -0
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +521 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +941 -153
- code_puppy/tools/common.py +1146 -6
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/file_modifications.py +288 -89
- code_puppy/tools/file_operations.py +352 -266
- code_puppy/tools/subagent_context.py +158 -0
- code_puppy/uvx_detection.py +242 -0
- code_puppy/version_checker.py +30 -11
- code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
- code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
- {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/METADATA +184 -67
- code_puppy-0.0.366.dist-info/RECORD +217 -0
- {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
- {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +1 -0
- code_puppy/agent.py +0 -231
- code_puppy/agents/agent_orchestrator.json +0 -26
- code_puppy/agents/runtime_manager.py +0 -272
- code_puppy/command_line/mcp/add_command.py +0 -183
- code_puppy/command_line/meta_command_handler.py +0 -153
- code_puppy/message_history_processor.py +0 -490
- code_puppy/messaging/spinner/textual_spinner.py +0 -101
- code_puppy/state_management.py +0 -200
- code_puppy/tui/__init__.py +0 -10
- code_puppy/tui/app.py +0 -986
- code_puppy/tui/components/__init__.py +0 -21
- code_puppy/tui/components/chat_view.py +0 -550
- code_puppy/tui/components/command_history_modal.py +0 -218
- code_puppy/tui/components/copy_button.py +0 -139
- code_puppy/tui/components/custom_widgets.py +0 -63
- code_puppy/tui/components/human_input_modal.py +0 -175
- code_puppy/tui/components/input_area.py +0 -167
- code_puppy/tui/components/sidebar.py +0 -309
- code_puppy/tui/components/status_bar.py +0 -182
- code_puppy/tui/messages.py +0 -27
- code_puppy/tui/models/__init__.py +0 -8
- code_puppy/tui/models/chat_message.py +0 -25
- code_puppy/tui/models/command_history.py +0 -89
- code_puppy/tui/models/enums.py +0 -24
- code_puppy/tui/screens/__init__.py +0 -15
- code_puppy/tui/screens/help.py +0 -130
- code_puppy/tui/screens/mcp_install_wizard.py +0 -803
- code_puppy/tui/screens/settings.py +0 -290
- code_puppy/tui/screens/tools.py +0 -74
- code_puppy-0.0.169.data/data/code_puppy/models.json +0 -128
- code_puppy-0.0.169.dist-info/RECORD +0 -112
- /code_puppy/{mcp → mcp_}/circuit_breaker.py +0 -0
- /code_puppy/{mcp → mcp_}/error_isolation.py +0 -0
- /code_puppy/{mcp → mcp_}/health_monitor.py +0 -0
- /code_puppy/{mcp → mcp_}/retry_manager.py +0 -0
- /code_puppy/{mcp → mcp_}/status_tracker.py +0 -0
- /code_puppy/{mcp → mcp_}/system_tools.py +0 -0
- {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,9 +7,10 @@ from datetime import datetime
|
|
|
7
7
|
from typing import List, Optional
|
|
8
8
|
|
|
9
9
|
from rich.panel import Panel
|
|
10
|
+
from rich.text import Text
|
|
10
11
|
|
|
11
|
-
from code_puppy.
|
|
12
|
-
from code_puppy.messaging import emit_info
|
|
12
|
+
from code_puppy.mcp_.managed_server import ServerState
|
|
13
|
+
from code_puppy.messaging import emit_error, emit_info
|
|
13
14
|
|
|
14
15
|
from .base import MCPCommandBase
|
|
15
16
|
from .list_command import ListCommand
|
|
@@ -117,7 +118,7 @@ class StatusCommand(MCPCommandBase):
|
|
|
117
118
|
|
|
118
119
|
# Check async lifecycle manager status if available
|
|
119
120
|
try:
|
|
120
|
-
from code_puppy.
|
|
121
|
+
from code_puppy.mcp_.async_lifecycle import get_lifecycle_manager
|
|
121
122
|
|
|
122
123
|
lifecycle_mgr = get_lifecycle_manager()
|
|
123
124
|
if lifecycle_mgr.is_running(server_id):
|
|
@@ -158,7 +159,7 @@ class StatusCommand(MCPCommandBase):
|
|
|
158
159
|
status_lines.append(f"[bold]Metadata:[/bold] {len(metadata)} keys")
|
|
159
160
|
|
|
160
161
|
# Create and show the panel
|
|
161
|
-
panel_content = "\n".join(status_lines)
|
|
162
|
+
panel_content = Text.from_markup("\n".join(status_lines))
|
|
162
163
|
panel = Panel(
|
|
163
164
|
panel_content, title=f"🔌 {server_name} Status", border_style="cyan"
|
|
164
165
|
)
|
|
@@ -180,6 +181,4 @@ class StatusCommand(MCPCommandBase):
|
|
|
180
181
|
logger.error(
|
|
181
182
|
f"Error getting detailed status for server '{server_name}': {e}"
|
|
182
183
|
)
|
|
183
|
-
|
|
184
|
-
f"[red]Error getting server status: {e}[/red]", message_group=group_id
|
|
185
|
-
)
|
|
184
|
+
emit_error(f"Error getting server status: {e}", message_group=group_id)
|
|
@@ -6,9 +6,12 @@ import logging
|
|
|
6
6
|
import time
|
|
7
7
|
from typing import List, Optional
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
|
|
11
|
+
from code_puppy.mcp_.managed_server import ServerState
|
|
10
12
|
from code_puppy.messaging import emit_info
|
|
11
13
|
|
|
14
|
+
from ...agents import get_current_agent
|
|
12
15
|
from .base import MCPCommandBase
|
|
13
16
|
|
|
14
17
|
# Configure logging
|
|
@@ -91,14 +94,14 @@ class StopAllCommand(MCPCommandBase):
|
|
|
91
94
|
pass # No async loop, servers will stop when needed
|
|
92
95
|
|
|
93
96
|
try:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
manager = get_runtime_agent_manager()
|
|
99
|
-
manager.reload_agent()
|
|
97
|
+
agent = get_current_agent()
|
|
98
|
+
agent.reload_code_generation_agent()
|
|
99
|
+
# Update MCP tool cache immediately so token counts reflect the change
|
|
100
|
+
agent.update_mcp_tool_cache_sync()
|
|
100
101
|
emit_info(
|
|
101
|
-
|
|
102
|
+
Text.from_markup(
|
|
103
|
+
"[dim]Agent reloaded with updated servers[/dim]"
|
|
104
|
+
),
|
|
102
105
|
message_group=group_id,
|
|
103
106
|
)
|
|
104
107
|
except Exception as e:
|
|
@@ -5,8 +5,11 @@ MCP Stop Command - Stops a specific MCP server.
|
|
|
5
5
|
import logging
|
|
6
6
|
from typing import List, Optional
|
|
7
7
|
|
|
8
|
-
from
|
|
8
|
+
from rich.text import Text
|
|
9
9
|
|
|
10
|
+
from code_puppy.messaging import emit_error, emit_info
|
|
11
|
+
|
|
12
|
+
from ...agents import get_current_agent
|
|
10
13
|
from .base import MCPCommandBase
|
|
11
14
|
from .utils import find_server_id_by_name, suggest_similar_servers
|
|
12
15
|
|
|
@@ -34,7 +37,7 @@ class StopCommand(MCPCommandBase):
|
|
|
34
37
|
|
|
35
38
|
if not args:
|
|
36
39
|
emit_info(
|
|
37
|
-
"[yellow]Usage: /mcp stop <server_name>[/yellow]",
|
|
40
|
+
Text.from_markup("[yellow]Usage: /mcp stop <server_name>[/yellow]"),
|
|
38
41
|
message_group=group_id,
|
|
39
42
|
)
|
|
40
43
|
return
|
|
@@ -57,14 +60,12 @@ class StopCommand(MCPCommandBase):
|
|
|
57
60
|
|
|
58
61
|
# Reload the agent to remove the disabled server
|
|
59
62
|
try:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
manager = get_runtime_agent_manager()
|
|
65
|
-
manager.reload_agent()
|
|
63
|
+
agent = get_current_agent()
|
|
64
|
+
agent.reload_code_generation_agent()
|
|
65
|
+
# Update MCP tool cache immediately so token counts reflect the change
|
|
66
|
+
agent.update_mcp_tool_cache_sync()
|
|
66
67
|
emit_info(
|
|
67
|
-
"
|
|
68
|
+
"Agent reloaded with updated servers",
|
|
68
69
|
message_group=group_id,
|
|
69
70
|
)
|
|
70
71
|
except Exception as e:
|
|
@@ -76,4 +77,4 @@ class StopCommand(MCPCommandBase):
|
|
|
76
77
|
|
|
77
78
|
except Exception as e:
|
|
78
79
|
logger.error(f"Error stopping server '{server_name}': {e}")
|
|
79
|
-
|
|
80
|
+
emit_error(f"Failed to stop server: {e}", message_group=group_id)
|
|
@@ -5,7 +5,7 @@ MCP Test Command - Tests connectivity to a specific MCP server.
|
|
|
5
5
|
import logging
|
|
6
6
|
from typing import List, Optional
|
|
7
7
|
|
|
8
|
-
from code_puppy.messaging import emit_info
|
|
8
|
+
from code_puppy.messaging import emit_error, emit_info
|
|
9
9
|
|
|
10
10
|
from .base import MCPCommandBase
|
|
11
11
|
from .utils import find_server_id_by_name, suggest_similar_servers
|
|
@@ -104,4 +104,4 @@ class TestCommand(MCPCommandBase):
|
|
|
104
104
|
|
|
105
105
|
except Exception as e:
|
|
106
106
|
logger.error(f"Error testing server '{server_name}': {e}")
|
|
107
|
-
|
|
107
|
+
emit_error(f"Error testing server: {e}", message_group=group_id)
|
|
@@ -7,7 +7,9 @@ Provides interactive functionality for installing and configuring MCP servers.
|
|
|
7
7
|
import logging
|
|
8
8
|
from typing import Any, Dict, Optional
|
|
9
9
|
|
|
10
|
-
from
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
|
|
12
|
+
from code_puppy.messaging import emit_error, emit_info, emit_prompt
|
|
11
13
|
|
|
12
14
|
# Configure logging
|
|
13
15
|
logger = logging.getLogger(__name__)
|
|
@@ -51,7 +53,7 @@ def run_interactive_install_wizard(manager, group_id: str) -> bool:
|
|
|
51
53
|
required_env_vars = selected_server.get_environment_vars()
|
|
52
54
|
if required_env_vars:
|
|
53
55
|
emit_info(
|
|
54
|
-
"\n[yellow]Required Environment Variables:[/yellow]",
|
|
56
|
+
Text.from_markup("\n[yellow]Required Environment Variables:[/yellow]"),
|
|
55
57
|
message_group=group_id,
|
|
56
58
|
)
|
|
57
59
|
for var in required_env_vars:
|
|
@@ -61,7 +63,8 @@ def run_interactive_install_wizard(manager, group_id: str) -> bool:
|
|
|
61
63
|
current_value = os.environ.get(var, "")
|
|
62
64
|
if current_value:
|
|
63
65
|
emit_info(
|
|
64
|
-
f" {var}: [green]Already set[/green]",
|
|
66
|
+
Text.from_markup(f" {var}: [green]Already set[/green]"),
|
|
67
|
+
message_group=group_id,
|
|
65
68
|
)
|
|
66
69
|
env_vars[var] = current_value
|
|
67
70
|
else:
|
|
@@ -73,7 +76,8 @@ def run_interactive_install_wizard(manager, group_id: str) -> bool:
|
|
|
73
76
|
required_cmd_args = selected_server.get_command_line_args()
|
|
74
77
|
if required_cmd_args:
|
|
75
78
|
emit_info(
|
|
76
|
-
"\n[yellow]Command Line Arguments:[/yellow]",
|
|
79
|
+
Text.from_markup("\n[yellow]Command Line Arguments:[/yellow]"),
|
|
80
|
+
message_group=group_id,
|
|
77
81
|
)
|
|
78
82
|
for arg_config in required_cmd_args:
|
|
79
83
|
name = arg_config.get("name", "")
|
|
@@ -101,11 +105,11 @@ def run_interactive_install_wizard(manager, group_id: str) -> bool:
|
|
|
101
105
|
)
|
|
102
106
|
|
|
103
107
|
except ImportError:
|
|
104
|
-
|
|
108
|
+
emit_error("Server catalog not available", message_group=group_id)
|
|
105
109
|
return False
|
|
106
110
|
except Exception as e:
|
|
107
111
|
logger.error(f"Error in interactive wizard: {e}")
|
|
108
|
-
|
|
112
|
+
emit_error(f"Wizard error: {e}", message_group=group_id)
|
|
109
113
|
return False
|
|
110
114
|
|
|
111
115
|
|
|
@@ -118,13 +122,11 @@ def interactive_server_selection(group_id: str):
|
|
|
118
122
|
# This is a simplified version - the full implementation would have
|
|
119
123
|
# category browsing, search, etc. For now, we'll just show popular servers
|
|
120
124
|
try:
|
|
121
|
-
from code_puppy.
|
|
125
|
+
from code_puppy.mcp_.server_registry_catalog import catalog
|
|
122
126
|
|
|
123
127
|
servers = catalog.get_popular(10)
|
|
124
128
|
if not servers:
|
|
125
|
-
emit_info(
|
|
126
|
-
"[red]No servers available in catalog[/red]", message_group=group_id
|
|
127
|
-
)
|
|
129
|
+
emit_info("No servers available in catalog", message_group=group_id)
|
|
128
130
|
return None
|
|
129
131
|
|
|
130
132
|
emit_info("Popular MCP Servers:", message_group=group_id)
|
|
@@ -156,10 +158,10 @@ def interactive_server_selection(group_id: str):
|
|
|
156
158
|
if 0 <= index < len(servers):
|
|
157
159
|
return servers[index]
|
|
158
160
|
else:
|
|
159
|
-
|
|
161
|
+
emit_error("Invalid selection", message_group=group_id)
|
|
160
162
|
return None
|
|
161
163
|
except ValueError:
|
|
162
|
-
|
|
164
|
+
emit_error("Invalid input", message_group=group_id)
|
|
163
165
|
return None
|
|
164
166
|
|
|
165
167
|
except Exception as e:
|
|
@@ -215,7 +217,7 @@ def interactive_configure_server(
|
|
|
215
217
|
if env_vars:
|
|
216
218
|
emit_info("Environment Variables:", message_group=group_id)
|
|
217
219
|
for var, value in env_vars.items():
|
|
218
|
-
emit_info(f" {var}:
|
|
220
|
+
emit_info(f" {var}: ***", message_group=group_id)
|
|
219
221
|
|
|
220
222
|
if cmd_args:
|
|
221
223
|
emit_info("Command Line Arguments:", message_group=group_id)
|
|
@@ -234,7 +236,7 @@ def interactive_configure_server(
|
|
|
234
236
|
|
|
235
237
|
except Exception as e:
|
|
236
238
|
logger.error(f"Error configuring server: {e}")
|
|
237
|
-
|
|
239
|
+
emit_error(f"Configuration error: {e}", message_group=group_id)
|
|
238
240
|
return False
|
|
239
241
|
|
|
240
242
|
|
|
@@ -256,7 +258,7 @@ def install_server_from_catalog(
|
|
|
256
258
|
import os
|
|
257
259
|
|
|
258
260
|
from code_puppy.config import MCP_SERVERS_FILE
|
|
259
|
-
from code_puppy.
|
|
261
|
+
from code_puppy.mcp_.managed_server import ServerConfig
|
|
260
262
|
|
|
261
263
|
# Set environment variables in the current environment
|
|
262
264
|
for var, value in env_vars.items():
|
|
@@ -288,7 +290,7 @@ def install_server_from_catalog(
|
|
|
288
290
|
|
|
289
291
|
if not server_id:
|
|
290
292
|
emit_info(
|
|
291
|
-
"
|
|
293
|
+
"Failed to register server with manager",
|
|
292
294
|
message_group=group_id,
|
|
293
295
|
)
|
|
294
296
|
return False
|
|
@@ -314,7 +316,9 @@ def install_server_from_catalog(
|
|
|
314
316
|
json.dump(data, f, indent=2)
|
|
315
317
|
|
|
316
318
|
emit_info(
|
|
317
|
-
|
|
319
|
+
Text.from_markup(
|
|
320
|
+
f"[green]✓ Successfully installed server: {server_name}[/green]"
|
|
321
|
+
),
|
|
318
322
|
message_group=group_id,
|
|
319
323
|
)
|
|
320
324
|
emit_info(
|
|
@@ -326,5 +330,5 @@ def install_server_from_catalog(
|
|
|
326
330
|
|
|
327
331
|
except Exception as e:
|
|
328
332
|
logger.error(f"Error installing server: {e}")
|
|
329
|
-
|
|
333
|
+
emit_error(f"Installation failed: {e}", message_group=group_id)
|
|
330
334
|
return False
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Iterable
|
|
3
|
+
|
|
4
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
5
|
+
from prompt_toolkit.document import Document
|
|
6
|
+
|
|
7
|
+
# Configure logging
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_server_names():
|
|
12
|
+
"""Load server names from the MCP manager."""
|
|
13
|
+
try:
|
|
14
|
+
from code_puppy.mcp_.manager import MCPManager
|
|
15
|
+
|
|
16
|
+
manager = MCPManager()
|
|
17
|
+
servers = manager.list_servers()
|
|
18
|
+
return [server.name for server in servers]
|
|
19
|
+
except Exception as e:
|
|
20
|
+
logger.debug(f"Could not load server names: {e}")
|
|
21
|
+
return []
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MCPCompleter(Completer):
|
|
25
|
+
"""
|
|
26
|
+
A completer that triggers on '/mcp' to show available MCP subcommands
|
|
27
|
+
and server names where appropriate.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, trigger: str = "/mcp"):
|
|
31
|
+
self.trigger = trigger
|
|
32
|
+
|
|
33
|
+
# Define all available MCP subcommands
|
|
34
|
+
# Subcommands that take server names as arguments
|
|
35
|
+
self.server_subcommands = {
|
|
36
|
+
"start": "Start a specific MCP server",
|
|
37
|
+
"stop": "Stop a specific MCP server",
|
|
38
|
+
"restart": "Restart a specific MCP server",
|
|
39
|
+
"status": "Show status of a specific MCP server",
|
|
40
|
+
"logs": "Show logs for a specific MCP server",
|
|
41
|
+
"edit": "Edit an existing MCP server config",
|
|
42
|
+
"remove": "Remove an MCP server",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Subcommands that don't take server names
|
|
46
|
+
self.general_subcommands = {
|
|
47
|
+
"list": "List all registered MCP servers",
|
|
48
|
+
"start-all": "Start all MCP servers",
|
|
49
|
+
"stop-all": "Stop all MCP servers",
|
|
50
|
+
"test": "Test MCP server connection",
|
|
51
|
+
"add": "Add a new MCP server",
|
|
52
|
+
"install": "Install MCP servers from a list",
|
|
53
|
+
"search": "Search for available MCP servers",
|
|
54
|
+
"help": "Show help for MCP commands",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# All subcommands combined for completion when no subcommand is typed yet
|
|
58
|
+
self.all_subcommands = {**self.server_subcommands, **self.general_subcommands}
|
|
59
|
+
|
|
60
|
+
# Cache server names to avoid repeated lookups
|
|
61
|
+
self._server_names_cache = None
|
|
62
|
+
self._cache_timestamp = None
|
|
63
|
+
|
|
64
|
+
def _get_server_names(self):
|
|
65
|
+
"""Get server names with caching."""
|
|
66
|
+
import time
|
|
67
|
+
|
|
68
|
+
# Cache for 30 seconds to avoid repeated manager calls
|
|
69
|
+
current_time = time.time()
|
|
70
|
+
if (
|
|
71
|
+
self._server_names_cache is None
|
|
72
|
+
or self._cache_timestamp is None
|
|
73
|
+
or current_time - self._cache_timestamp > 30
|
|
74
|
+
):
|
|
75
|
+
self._server_names_cache = load_server_names()
|
|
76
|
+
self._cache_timestamp = current_time
|
|
77
|
+
|
|
78
|
+
return self._server_names_cache or []
|
|
79
|
+
|
|
80
|
+
def get_completions(
|
|
81
|
+
self, document: Document, complete_event
|
|
82
|
+
) -> Iterable[Completion]:
|
|
83
|
+
text = document.text
|
|
84
|
+
cursor_position = document.cursor_position
|
|
85
|
+
text_before_cursor = text[:cursor_position]
|
|
86
|
+
|
|
87
|
+
# Only trigger if /mcp is at the very beginning of the line
|
|
88
|
+
stripped_text = text_before_cursor.lstrip()
|
|
89
|
+
if not stripped_text.startswith(self.trigger):
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
# Find where /mcp actually starts (after any leading whitespace)
|
|
93
|
+
mcp_pos = text_before_cursor.find(self.trigger)
|
|
94
|
+
mcp_end = mcp_pos + len(self.trigger)
|
|
95
|
+
|
|
96
|
+
# Require a space after /mcp before showing completions
|
|
97
|
+
if mcp_end >= len(text_before_cursor) or text_before_cursor[mcp_end] != " ":
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
# Extract everything after /mcp (and after the space)
|
|
101
|
+
after_mcp = text_before_cursor[mcp_end + 1 :].strip()
|
|
102
|
+
|
|
103
|
+
# If nothing after /mcp, show all available subcommands
|
|
104
|
+
if not after_mcp:
|
|
105
|
+
for subcommand, description in sorted(self.all_subcommands.items()):
|
|
106
|
+
yield Completion(
|
|
107
|
+
subcommand,
|
|
108
|
+
start_position=0,
|
|
109
|
+
display=subcommand,
|
|
110
|
+
display_meta=description,
|
|
111
|
+
)
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
# Parse what's been typed after /mcp
|
|
115
|
+
# Split by space but be careful with what we're currently typing
|
|
116
|
+
parts = after_mcp.split()
|
|
117
|
+
|
|
118
|
+
# Priority: Check for server name completion first when appropriate
|
|
119
|
+
# This handles cases like '/mcp start ' where the space indicates ready for server name
|
|
120
|
+
if len(parts) >= 1:
|
|
121
|
+
subcommand = parts[0].lower()
|
|
122
|
+
|
|
123
|
+
# Only complete server names for specific subcommands
|
|
124
|
+
if subcommand in self.server_subcommands:
|
|
125
|
+
# Case 1: Exactly the subcommand followed by a space (ready for server name)
|
|
126
|
+
if len(parts) == 1 and text.endswith(" "):
|
|
127
|
+
partial_server = ""
|
|
128
|
+
start_position = 0
|
|
129
|
+
|
|
130
|
+
server_names = self._get_server_names()
|
|
131
|
+
for server_name in sorted(server_names):
|
|
132
|
+
yield Completion(
|
|
133
|
+
server_name,
|
|
134
|
+
start_position=start_position,
|
|
135
|
+
display=server_name,
|
|
136
|
+
display_meta="MCP Server",
|
|
137
|
+
)
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
# Case 2: Subcommand + partial server name (require space after subcommand)
|
|
141
|
+
elif len(parts) == 2 and cursor_position > (
|
|
142
|
+
mcp_end + 1 + len(subcommand) + 1
|
|
143
|
+
):
|
|
144
|
+
partial_server = parts[1]
|
|
145
|
+
start_position = -(len(partial_server))
|
|
146
|
+
|
|
147
|
+
server_names = self._get_server_names()
|
|
148
|
+
for server_name in sorted(server_names):
|
|
149
|
+
if server_name.lower().startswith(partial_server.lower()):
|
|
150
|
+
yield Completion(
|
|
151
|
+
server_name,
|
|
152
|
+
start_position=start_position,
|
|
153
|
+
display=server_name,
|
|
154
|
+
display_meta="MCP Server",
|
|
155
|
+
)
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
# If we only have one part and haven't returned above, show subcommand completions
|
|
159
|
+
# This includes cases like '/mcp start' where they might want 'start-all'
|
|
160
|
+
# But NOT when there's a space after the subcommand (which indicates they want arguments)
|
|
161
|
+
if len(parts) == 1 and not text.endswith(" "):
|
|
162
|
+
partial_subcommand = parts[0]
|
|
163
|
+
for subcommand, description in sorted(self.all_subcommands.items()):
|
|
164
|
+
if subcommand.startswith(partial_subcommand):
|
|
165
|
+
yield Completion(
|
|
166
|
+
subcommand,
|
|
167
|
+
start_position=-(len(partial_subcommand)),
|
|
168
|
+
display=subcommand,
|
|
169
|
+
display_meta=description,
|
|
170
|
+
)
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
# For general subcommands, we don't provide argument completion
|
|
174
|
+
# They may have their own specific completions in the future
|
|
@@ -6,7 +6,7 @@ from prompt_toolkit.completion import Completer, Completion
|
|
|
6
6
|
from prompt_toolkit.document import Document
|
|
7
7
|
from prompt_toolkit.history import FileHistory
|
|
8
8
|
|
|
9
|
-
from code_puppy.config import
|
|
9
|
+
from code_puppy.config import get_global_model_name, set_model_name
|
|
10
10
|
from code_puppy.model_factory import ModelFactory
|
|
11
11
|
|
|
12
12
|
|
|
@@ -21,21 +21,32 @@ def get_active_model():
|
|
|
21
21
|
Returns the active model from the config using get_model_name().
|
|
22
22
|
This ensures consistency across the codebase by always using the config value.
|
|
23
23
|
"""
|
|
24
|
-
return
|
|
24
|
+
return get_global_model_name()
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def set_active_model(model_name: str):
|
|
28
28
|
"""
|
|
29
29
|
Sets the active model name by updating the config (for persistence).
|
|
30
30
|
"""
|
|
31
|
+
from code_puppy.messaging import emit_info, emit_warning
|
|
32
|
+
|
|
31
33
|
set_model_name(model_name)
|
|
32
|
-
# Reload agent
|
|
34
|
+
# Reload the currently active agent so the new model takes effect immediately
|
|
33
35
|
try:
|
|
34
|
-
from code_puppy.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
from code_puppy.agents import get_current_agent
|
|
37
|
+
|
|
38
|
+
current_agent = get_current_agent()
|
|
39
|
+
# JSON agents may need to refresh their config before reload
|
|
40
|
+
if hasattr(current_agent, "refresh_config"):
|
|
41
|
+
try:
|
|
42
|
+
current_agent.refresh_config()
|
|
43
|
+
except Exception:
|
|
44
|
+
# Non-fatal, continue to reload
|
|
45
|
+
...
|
|
46
|
+
current_agent.reload_code_generation_agent()
|
|
47
|
+
emit_info("Active agent reloaded")
|
|
48
|
+
except Exception as e:
|
|
49
|
+
emit_warning(f"Model changed but agent reload failed: {e}")
|
|
39
50
|
|
|
40
51
|
|
|
41
52
|
class ModelNameCompleter(Completer):
|
|
@@ -54,13 +65,31 @@ class ModelNameCompleter(Completer):
|
|
|
54
65
|
text = document.text
|
|
55
66
|
cursor_position = document.cursor_position
|
|
56
67
|
text_before_cursor = text[:cursor_position]
|
|
57
|
-
|
|
68
|
+
|
|
69
|
+
# Only trigger if /model is at the very beginning of the line and has a space after it
|
|
70
|
+
stripped_text = text_before_cursor.lstrip()
|
|
71
|
+
if not stripped_text.startswith(self.trigger + " "):
|
|
58
72
|
return
|
|
59
|
-
|
|
60
|
-
|
|
73
|
+
|
|
74
|
+
# Find where /model actually starts (after any leading whitespace)
|
|
75
|
+
symbol_pos = text_before_cursor.find(self.trigger)
|
|
76
|
+
text_after_trigger = text_before_cursor[
|
|
77
|
+
symbol_pos + len(self.trigger) + 1 :
|
|
78
|
+
].lstrip()
|
|
61
79
|
start_position = -(len(text_after_trigger))
|
|
80
|
+
|
|
81
|
+
# Filter model names based on what's typed after /model (case-insensitive)
|
|
62
82
|
for model_name in self.model_names:
|
|
63
|
-
|
|
83
|
+
if text_after_trigger and not model_name.lower().startswith(
|
|
84
|
+
text_after_trigger.lower()
|
|
85
|
+
):
|
|
86
|
+
continue # Skip models that don't match the typed text
|
|
87
|
+
|
|
88
|
+
meta = (
|
|
89
|
+
"Model (selected)"
|
|
90
|
+
if model_name.lower() == get_active_model().lower()
|
|
91
|
+
else "Model"
|
|
92
|
+
)
|
|
64
93
|
yield Completion(
|
|
65
94
|
model_name,
|
|
66
95
|
start_position=start_position,
|
|
@@ -72,32 +101,62 @@ class ModelNameCompleter(Completer):
|
|
|
72
101
|
def update_model_in_input(text: str) -> Optional[str]:
|
|
73
102
|
# If input starts with /model or /m and a model name, set model and strip it out
|
|
74
103
|
content = text.strip()
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
104
|
+
model_names = load_model_names()
|
|
105
|
+
|
|
106
|
+
# Check for /model command (require space after /model, case-insensitive)
|
|
107
|
+
if content.lower().startswith("/model "):
|
|
108
|
+
# Find the actual /model command (case-insensitive)
|
|
109
|
+
model_cmd = content.split(" ", 1)[0] # Get the command part
|
|
110
|
+
rest = content[len(model_cmd) :].strip() # Remove the actual command
|
|
111
|
+
|
|
112
|
+
# Look for a model name at the start of rest (case-insensitive)
|
|
113
|
+
for model in model_names:
|
|
114
|
+
if rest.lower().startswith(model.lower()):
|
|
115
|
+
# Found a matching model - now extract it properly
|
|
81
116
|
set_active_model(model)
|
|
82
|
-
|
|
83
|
-
|
|
117
|
+
|
|
118
|
+
# Find the actual model name in the original text (preserving case)
|
|
119
|
+
# We need to find where the model ends in the original rest string
|
|
120
|
+
model_end_idx = len(model)
|
|
121
|
+
|
|
122
|
+
# Build the full command+model part to remove
|
|
123
|
+
cmd_and_model_pattern = model_cmd + " " + rest[:model_end_idx]
|
|
124
|
+
idx = text.find(cmd_and_model_pattern)
|
|
84
125
|
if idx != -1:
|
|
85
126
|
new_text = (
|
|
86
|
-
text[:idx] + text[idx + len(
|
|
127
|
+
text[:idx] + text[idx + len(cmd_and_model_pattern) :]
|
|
87
128
|
).strip()
|
|
88
129
|
return new_text
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
# Check for /m command (case-insensitive)
|
|
133
|
+
elif content.lower().startswith("/m ") and not content.lower().startswith(
|
|
134
|
+
"/model "
|
|
135
|
+
):
|
|
136
|
+
# Find the actual /m command (case-insensitive)
|
|
137
|
+
m_cmd = content.split(" ", 1)[0] # Get the command part
|
|
138
|
+
rest = content[len(m_cmd) :].strip() # Remove the actual command
|
|
139
|
+
|
|
140
|
+
# Look for a model name at the start of rest (case-insensitive)
|
|
141
|
+
for model in model_names:
|
|
142
|
+
if rest.lower().startswith(model.lower()):
|
|
143
|
+
# Found a matching model - now extract it properly
|
|
95
144
|
set_active_model(model)
|
|
96
|
-
|
|
97
|
-
|
|
145
|
+
|
|
146
|
+
# Find the actual model name in the original text (preserving case)
|
|
147
|
+
# We need to find where the model ends in the original rest string
|
|
148
|
+
model_end_idx = len(model)
|
|
149
|
+
|
|
150
|
+
# Build the full command+model part to remove
|
|
151
|
+
# Handle space variations in the original text
|
|
152
|
+
cmd_and_model_pattern = m_cmd + " " + rest[:model_end_idx]
|
|
153
|
+
idx = text.find(cmd_and_model_pattern)
|
|
98
154
|
if idx != -1:
|
|
99
|
-
new_text = (
|
|
155
|
+
new_text = (
|
|
156
|
+
text[:idx] + text[idx + len(cmd_and_model_pattern) :]
|
|
157
|
+
).strip()
|
|
100
158
|
return new_text
|
|
159
|
+
return None
|
|
101
160
|
|
|
102
161
|
return None
|
|
103
162
|
|