codepp 0.0.437__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 +10 -0
- code_puppy/__main__.py +10 -0
- code_puppy/agents/__init__.py +31 -0
- code_puppy/agents/agent_c_reviewer.py +155 -0
- code_puppy/agents/agent_code_puppy.py +117 -0
- 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 +638 -0
- code_puppy/agents/agent_golang_reviewer.py +151 -0
- code_puppy/agents/agent_helios.py +124 -0
- code_puppy/agents/agent_javascript_reviewer.py +160 -0
- code_puppy/agents/agent_manager.py +742 -0
- code_puppy/agents/agent_pack_leader.py +385 -0
- code_puppy/agents/agent_planning.py +165 -0
- code_puppy/agents/agent_python_programmer.py +169 -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_scheduler.py +121 -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 +2156 -0
- code_puppy/agents/event_stream_handler.py +348 -0
- code_puppy/agents/json_agent.py +202 -0
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +304 -0
- code_puppy/agents/pack/husky.py +327 -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 +453 -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 +75 -0
- code_puppy/api/routers/sessions.py +234 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +692 -0
- code_puppy/chatgpt_codex_client.py +338 -0
- code_puppy/claude_cache_client.py +672 -0
- code_puppy/cli_runner.py +1073 -0
- code_puppy/command_line/__init__.py +1 -0
- code_puppy/command_line/add_model_menu.py +1092 -0
- code_puppy/command_line/agent_menu.py +662 -0
- code_puppy/command_line/attachments.py +395 -0
- code_puppy/command_line/autosave_menu.py +704 -0
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +532 -0
- code_puppy/command_line/command_handler.py +293 -0
- code_puppy/command_line/command_registry.py +150 -0
- code_puppy/command_line/config_commands.py +719 -0
- code_puppy/command_line/core_commands.py +867 -0
- code_puppy/command_line/diff_menu.py +865 -0
- code_puppy/command_line/file_path_completion.py +73 -0
- code_puppy/command_line/load_context_completion.py +52 -0
- code_puppy/command_line/mcp/__init__.py +10 -0
- code_puppy/command_line/mcp/base.py +32 -0
- 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 +138 -0
- code_puppy/command_line/mcp/help_command.py +147 -0
- code_puppy/command_line/mcp/install_command.py +214 -0
- code_puppy/command_line/mcp/install_menu.py +705 -0
- code_puppy/command_line/mcp/list_command.py +94 -0
- code_puppy/command_line/mcp/logs_command.py +235 -0
- code_puppy/command_line/mcp/remove_command.py +82 -0
- code_puppy/command_line/mcp/restart_command.py +100 -0
- code_puppy/command_line/mcp/search_command.py +123 -0
- code_puppy/command_line/mcp/start_all_command.py +135 -0
- code_puppy/command_line/mcp/start_command.py +117 -0
- code_puppy/command_line/mcp/status_command.py +184 -0
- code_puppy/command_line/mcp/stop_all_command.py +112 -0
- code_puppy/command_line/mcp/stop_command.py +80 -0
- code_puppy/command_line/mcp/test_command.py +107 -0
- code_puppy/command_line/mcp/utils.py +129 -0
- code_puppy/command_line/mcp/wizard_utils.py +334 -0
- code_puppy/command_line/mcp_completion.py +174 -0
- code_puppy/command_line/model_picker_completion.py +197 -0
- code_puppy/command_line/model_settings_menu.py +932 -0
- code_puppy/command_line/motd.py +96 -0
- code_puppy/command_line/onboarding_slides.py +179 -0
- code_puppy/command_line/onboarding_wizard.py +342 -0
- code_puppy/command_line/pin_command_completion.py +329 -0
- code_puppy/command_line/prompt_toolkit_completion.py +846 -0
- code_puppy/command_line/session_commands.py +302 -0
- code_puppy/command_line/shell_passthrough.py +145 -0
- code_puppy/command_line/skills_completion.py +160 -0
- code_puppy/command_line/uc_menu.py +893 -0
- code_puppy/command_line/utils.py +93 -0
- code_puppy/command_line/wiggum_state.py +78 -0
- code_puppy/config.py +1770 -0
- code_puppy/error_logging.py +134 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/gemini_model.py +754 -0
- code_puppy/hook_engine/README.md +105 -0
- code_puppy/hook_engine/__init__.py +21 -0
- code_puppy/hook_engine/aliases.py +155 -0
- code_puppy/hook_engine/engine.py +221 -0
- code_puppy/hook_engine/executor.py +296 -0
- code_puppy/hook_engine/matcher.py +156 -0
- code_puppy/hook_engine/models.py +240 -0
- code_puppy/hook_engine/registry.py +106 -0
- code_puppy/hook_engine/validator.py +144 -0
- code_puppy/http_utils.py +361 -0
- code_puppy/keymap.py +128 -0
- code_puppy/main.py +10 -0
- code_puppy/mcp_/__init__.py +66 -0
- code_puppy/mcp_/async_lifecycle.py +286 -0
- code_puppy/mcp_/blocking_startup.py +469 -0
- code_puppy/mcp_/captured_stdio_server.py +275 -0
- code_puppy/mcp_/circuit_breaker.py +290 -0
- code_puppy/mcp_/config_wizard.py +507 -0
- code_puppy/mcp_/dashboard.py +308 -0
- code_puppy/mcp_/error_isolation.py +407 -0
- code_puppy/mcp_/examples/retry_example.py +226 -0
- code_puppy/mcp_/health_monitor.py +589 -0
- code_puppy/mcp_/managed_server.py +428 -0
- code_puppy/mcp_/manager.py +807 -0
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/mcp_/registry.py +451 -0
- code_puppy/mcp_/retry_manager.py +337 -0
- code_puppy/mcp_/server_registry_catalog.py +1126 -0
- code_puppy/mcp_/status_tracker.py +355 -0
- code_puppy/mcp_/system_tools.py +209 -0
- code_puppy/mcp_prompts/__init__.py +1 -0
- code_puppy/mcp_prompts/hook_creator.py +103 -0
- code_puppy/messaging/__init__.py +255 -0
- code_puppy/messaging/bus.py +613 -0
- code_puppy/messaging/commands.py +167 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/message_queue.py +361 -0
- code_puppy/messaging/messages.py +569 -0
- code_puppy/messaging/queue_console.py +271 -0
- code_puppy/messaging/renderers.py +311 -0
- code_puppy/messaging/rich_renderer.py +1158 -0
- code_puppy/messaging/spinner/__init__.py +83 -0
- code_puppy/messaging/spinner/console_spinner.py +240 -0
- code_puppy/messaging/spinner/spinner_base.py +95 -0
- code_puppy/messaging/subagent_console.py +460 -0
- code_puppy/model_factory.py +848 -0
- code_puppy/model_switching.py +63 -0
- code_puppy/model_utils.py +168 -0
- code_puppy/models.json +174 -0
- code_puppy/models_dev_api.json +1 -0
- code_puppy/models_dev_parser.py +592 -0
- code_puppy/plugins/__init__.py +186 -0
- code_puppy/plugins/agent_skills/__init__.py +22 -0
- code_puppy/plugins/agent_skills/config.py +175 -0
- code_puppy/plugins/agent_skills/discovery.py +136 -0
- code_puppy/plugins/agent_skills/downloader.py +392 -0
- code_puppy/plugins/agent_skills/installer.py +22 -0
- code_puppy/plugins/agent_skills/metadata.py +219 -0
- code_puppy/plugins/agent_skills/prompt_builder.py +60 -0
- code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
- code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
- code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
- code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
- code_puppy/plugins/agent_skills/skills_menu.py +781 -0
- 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 +706 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +133 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
- code_puppy/plugins/antigravity_oauth/storage.py +288 -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 +863 -0
- code_puppy/plugins/antigravity_oauth/utils.py +168 -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 +329 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +301 -0
- code_puppy/plugins/chatgpt_oauth/utils.py +523 -0
- code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
- code_puppy/plugins/claude_code_hooks/config.py +137 -0
- code_puppy/plugins/claude_code_hooks/register_callbacks.py +175 -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 +25 -0
- code_puppy/plugins/claude_code_oauth/config.py +52 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
- code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
- code_puppy/plugins/claude_code_oauth/utils.py +640 -0
- code_puppy/plugins/customizable_commands/__init__.py +0 -0
- code_puppy/plugins/customizable_commands/register_callbacks.py +152 -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 +470 -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/hook_creator/__init__.py +1 -0
- code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
- code_puppy/plugins/hook_manager/__init__.py +1 -0
- code_puppy/plugins/hook_manager/config.py +290 -0
- code_puppy/plugins/hook_manager/hooks_menu.py +564 -0
- code_puppy/plugins/hook_manager/register_callbacks.py +227 -0
- code_puppy/plugins/oauth_puppy_html.py +228 -0
- code_puppy/plugins/scheduler/__init__.py +1 -0
- code_puppy/plugins/scheduler/register_callbacks.py +88 -0
- code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
- code_puppy/plugins/scheduler/scheduler_wizard.py +341 -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/plugins/synthetic_status/__init__.py +1 -0
- code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
- code_puppy/plugins/synthetic_status/status_api.py +147 -0
- code_puppy/plugins/universal_constructor/__init__.py +13 -0
- code_puppy/plugins/universal_constructor/models.py +138 -0
- code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
- code_puppy/plugins/universal_constructor/registry.py +302 -0
- code_puppy/plugins/universal_constructor/sandbox.py +584 -0
- code_puppy/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/pydantic_patches.py +356 -0
- code_puppy/reopenable_async_client.py +232 -0
- code_puppy/round_robin_model.py +150 -0
- code_puppy/scheduler/__init__.py +41 -0
- code_puppy/scheduler/__main__.py +9 -0
- code_puppy/scheduler/cli.py +118 -0
- code_puppy/scheduler/config.py +126 -0
- code_puppy/scheduler/daemon.py +280 -0
- code_puppy/scheduler/executor.py +155 -0
- code_puppy/scheduler/platform.py +19 -0
- code_puppy/scheduler/platform_unix.py +22 -0
- code_puppy/scheduler/platform_win.py +32 -0
- code_puppy/session_storage.py +338 -0
- code_puppy/status_display.py +257 -0
- code_puppy/summarization_agent.py +176 -0
- code_puppy/terminal_utils.py +418 -0
- code_puppy/tools/__init__.py +501 -0
- code_puppy/tools/agent_tools.py +603 -0
- code_puppy/tools/ask_user_question/__init__.py +26 -0
- code_puppy/tools/ask_user_question/constants.py +73 -0
- code_puppy/tools/ask_user_question/demo_tui.py +55 -0
- code_puppy/tools/ask_user_question/handler.py +232 -0
- code_puppy/tools/ask_user_question/models.py +304 -0
- code_puppy/tools/ask_user_question/registration.py +26 -0
- code_puppy/tools/ask_user_question/renderers.py +309 -0
- code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
- code_puppy/tools/ask_user_question/theme.py +155 -0
- code_puppy/tools/ask_user_question/tui_loop.py +423 -0
- 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 +378 -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 +534 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +1346 -0
- code_puppy/tools/common.py +1409 -0
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/file_modifications.py +886 -0
- code_puppy/tools/file_operations.py +802 -0
- code_puppy/tools/scheduler_tools.py +412 -0
- code_puppy/tools/skills_tools.py +244 -0
- code_puppy/tools/subagent_context.py +158 -0
- code_puppy/tools/tools_content.py +51 -0
- code_puppy/tools/universal_constructor.py +889 -0
- code_puppy/uvx_detection.py +242 -0
- code_puppy/version_checker.py +82 -0
- codepp-0.0.437.dist-info/METADATA +766 -0
- codepp-0.0.437.dist-info/RECORD +288 -0
- codepp-0.0.437.dist-info/WHEEL +4 -0
- codepp-0.0.437.dist-info/entry_points.txt +3 -0
- codepp-0.0.437.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Import to trigger command registration
|
|
2
|
+
import code_puppy.command_line.config_commands # noqa: F401
|
|
3
|
+
import code_puppy.command_line.core_commands # noqa: F401
|
|
4
|
+
import code_puppy.command_line.session_commands # noqa: F401
|
|
5
|
+
import code_puppy.command_line.uc_menu # noqa: F401
|
|
6
|
+
|
|
7
|
+
# Global flag to track if plugins have been loaded
|
|
8
|
+
_PLUGINS_LOADED = False
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_commands_help():
|
|
12
|
+
"""Generate aligned commands help using Rich Text for safe markup.
|
|
13
|
+
|
|
14
|
+
Now dynamically generates help from the command registry!
|
|
15
|
+
Only shows two sections: Built-in Commands and Custom Commands.
|
|
16
|
+
"""
|
|
17
|
+
from rich.text import Text
|
|
18
|
+
|
|
19
|
+
from code_puppy.command_line.command_registry import get_unique_commands
|
|
20
|
+
|
|
21
|
+
# Ensure plugins are loaded so custom help can register
|
|
22
|
+
_ensure_plugins_loaded()
|
|
23
|
+
|
|
24
|
+
lines: list[Text] = []
|
|
25
|
+
# No global header needed - user already knows they're viewing help
|
|
26
|
+
|
|
27
|
+
# Collect all built-in commands (registered + legacy)
|
|
28
|
+
builtin_cmds: list[tuple[str, str]] = []
|
|
29
|
+
|
|
30
|
+
# Get registered commands (all categories are built-in)
|
|
31
|
+
registered_commands = get_unique_commands()
|
|
32
|
+
for cmd_info in sorted(registered_commands, key=lambda c: c.name):
|
|
33
|
+
builtin_cmds.append((cmd_info.usage, cmd_info.description))
|
|
34
|
+
|
|
35
|
+
# Get custom commands from plugins
|
|
36
|
+
custom_entries: list[tuple[str, str]] = []
|
|
37
|
+
try:
|
|
38
|
+
from code_puppy import callbacks
|
|
39
|
+
|
|
40
|
+
custom_help_results = callbacks.on_custom_command_help()
|
|
41
|
+
for res in custom_help_results:
|
|
42
|
+
if not res:
|
|
43
|
+
continue
|
|
44
|
+
# Format 1: Tuple with (command_name, description)
|
|
45
|
+
if isinstance(res, tuple) and len(res) == 2:
|
|
46
|
+
cmd_name = str(res[0])
|
|
47
|
+
custom_entries.append((f"/{cmd_name}", str(res[1])))
|
|
48
|
+
# Format 2: List of tuples or strings
|
|
49
|
+
elif isinstance(res, list):
|
|
50
|
+
# Check if it's a list of tuples (preferred format)
|
|
51
|
+
if res and isinstance(res[0], tuple) and len(res[0]) == 2:
|
|
52
|
+
for item in res:
|
|
53
|
+
if isinstance(item, tuple) and len(item) == 2:
|
|
54
|
+
cmd_name = str(item[0])
|
|
55
|
+
custom_entries.append((f"/{cmd_name}", str(item[1])))
|
|
56
|
+
# Format 3: List of strings (legacy format)
|
|
57
|
+
# Extract command from first line like "/command_name - Description"
|
|
58
|
+
elif res and isinstance(res[0], str) and res[0].startswith("/"):
|
|
59
|
+
first_line = res[0]
|
|
60
|
+
if " - " in first_line:
|
|
61
|
+
parts = first_line.split(" - ", 1)
|
|
62
|
+
cmd_name = parts[0].lstrip("/").strip()
|
|
63
|
+
description = parts[1].strip()
|
|
64
|
+
custom_entries.append((f"/{cmd_name}", description))
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
# Calculate global column width (longest command across ALL sections + padding)
|
|
69
|
+
all_commands = builtin_cmds + custom_entries
|
|
70
|
+
if all_commands:
|
|
71
|
+
max_cmd_width = max(len(cmd) for cmd, _ in all_commands)
|
|
72
|
+
column_width = max_cmd_width + 4 # Add 4 spaces padding
|
|
73
|
+
else:
|
|
74
|
+
column_width = 30
|
|
75
|
+
|
|
76
|
+
# Maximum description width before truncation (to prevent line wrapping)
|
|
77
|
+
max_desc_width = 80
|
|
78
|
+
|
|
79
|
+
def truncate_desc(desc: str, max_width: int) -> str:
|
|
80
|
+
"""Truncate description if too long, add ellipsis."""
|
|
81
|
+
if len(desc) <= max_width:
|
|
82
|
+
return desc
|
|
83
|
+
return desc[: max_width - 3] + "..."
|
|
84
|
+
|
|
85
|
+
# Display Built-in Commands section (starts immediately, no blank line)
|
|
86
|
+
lines.append(Text("Built-in Commands", style="bold magenta"))
|
|
87
|
+
for cmd, desc in sorted(builtin_cmds, key=lambda x: x[0]):
|
|
88
|
+
truncated_desc = truncate_desc(desc, max_desc_width)
|
|
89
|
+
left = Text(cmd.ljust(column_width), style="cyan")
|
|
90
|
+
right = Text(truncated_desc)
|
|
91
|
+
line = Text()
|
|
92
|
+
line.append_text(left)
|
|
93
|
+
line.append_text(right)
|
|
94
|
+
lines.append(line)
|
|
95
|
+
|
|
96
|
+
# Display Custom Commands section (if any)
|
|
97
|
+
if custom_entries:
|
|
98
|
+
lines.append(Text(""))
|
|
99
|
+
lines.append(Text("Custom Commands", style="bold magenta"))
|
|
100
|
+
for cmd, desc in sorted(custom_entries, key=lambda x: x[0]):
|
|
101
|
+
truncated_desc = truncate_desc(desc, max_desc_width)
|
|
102
|
+
left = Text(cmd.ljust(column_width), style="cyan")
|
|
103
|
+
right = Text(truncated_desc)
|
|
104
|
+
line = Text()
|
|
105
|
+
line.append_text(left)
|
|
106
|
+
line.append_text(right)
|
|
107
|
+
lines.append(line)
|
|
108
|
+
|
|
109
|
+
# Display Shell Pass-through section
|
|
110
|
+
lines.append(Text(""))
|
|
111
|
+
lines.append(Text("Shell Pass-through", style="bold magenta"))
|
|
112
|
+
shell_left = Text("!<command>".ljust(column_width), style="cyan")
|
|
113
|
+
shell_right = Text("Run a shell command directly (e.g., !git status)")
|
|
114
|
+
shell_line = Text()
|
|
115
|
+
shell_line.append_text(shell_left)
|
|
116
|
+
shell_line.append_text(shell_right)
|
|
117
|
+
lines.append(shell_line)
|
|
118
|
+
|
|
119
|
+
final_text = Text()
|
|
120
|
+
for i, line in enumerate(lines):
|
|
121
|
+
if i > 0:
|
|
122
|
+
final_text.append("\n")
|
|
123
|
+
final_text.append_text(line)
|
|
124
|
+
|
|
125
|
+
# Add trailing newline for spacing before next prompt
|
|
126
|
+
final_text.append("\n")
|
|
127
|
+
|
|
128
|
+
return final_text
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ============================================================================
|
|
132
|
+
# IMPORT BUILT-IN COMMAND HANDLERS
|
|
133
|
+
# ============================================================================
|
|
134
|
+
# All built-in command handlers have been split into category-specific files.
|
|
135
|
+
# These imports trigger their registration via @register_command decorators.
|
|
136
|
+
|
|
137
|
+
# ============================================================================
|
|
138
|
+
# UTILITY FUNCTIONS
|
|
139
|
+
# ============================================================================
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _ensure_plugins_loaded() -> None:
|
|
143
|
+
global _PLUGINS_LOADED
|
|
144
|
+
if _PLUGINS_LOADED:
|
|
145
|
+
return
|
|
146
|
+
try:
|
|
147
|
+
from code_puppy import plugins
|
|
148
|
+
|
|
149
|
+
plugins.load_plugin_callbacks()
|
|
150
|
+
_PLUGINS_LOADED = True
|
|
151
|
+
except Exception as e:
|
|
152
|
+
# If plugins fail to load, continue gracefully but note it
|
|
153
|
+
try:
|
|
154
|
+
from code_puppy.messaging import emit_warning
|
|
155
|
+
|
|
156
|
+
emit_warning(f"Plugin load error: {e}")
|
|
157
|
+
except Exception:
|
|
158
|
+
pass
|
|
159
|
+
_PLUGINS_LOADED = True
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# All command handlers moved to builtin_commands.py
|
|
163
|
+
# The import above triggers their registration
|
|
164
|
+
|
|
165
|
+
# ============================================================================
|
|
166
|
+
# MAIN COMMAND DISPATCHER
|
|
167
|
+
# ============================================================================
|
|
168
|
+
|
|
169
|
+
# _show_color_options has been moved to builtin_commands.py
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def handle_command(command: str):
|
|
173
|
+
"""
|
|
174
|
+
Handle commands prefixed with '/'.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
command: The command string to handle
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
True if the command was handled, False if not, or a string to be processed as user input
|
|
181
|
+
"""
|
|
182
|
+
from rich.text import Text
|
|
183
|
+
|
|
184
|
+
from code_puppy.command_line.command_registry import get_command
|
|
185
|
+
from code_puppy.messaging import emit_info, emit_warning
|
|
186
|
+
|
|
187
|
+
_ensure_plugins_loaded()
|
|
188
|
+
|
|
189
|
+
command = command.strip()
|
|
190
|
+
|
|
191
|
+
# Check if this is a registered command
|
|
192
|
+
if command.startswith("/"):
|
|
193
|
+
# Extract command name (first word after /)
|
|
194
|
+
cmd_name = command[1:].split()[0] if len(command) > 1 else ""
|
|
195
|
+
|
|
196
|
+
# Try to find in registry
|
|
197
|
+
cmd_info = get_command(cmd_name)
|
|
198
|
+
if cmd_info:
|
|
199
|
+
# Execute the registered handler
|
|
200
|
+
return cmd_info.handler(command)
|
|
201
|
+
|
|
202
|
+
# ========================================================================
|
|
203
|
+
# LEGACY COMMAND FALLBACK
|
|
204
|
+
# ========================================================================
|
|
205
|
+
# This section is kept as a fallback mechanism for commands added in other
|
|
206
|
+
# branches that haven't been migrated to the registry system yet.
|
|
207
|
+
#
|
|
208
|
+
# All current commands are registered above using @register_command, so
|
|
209
|
+
# they won't fall through to this section.
|
|
210
|
+
#
|
|
211
|
+
# If you're rebasing and your branch adds a new command using the old
|
|
212
|
+
# if/elif style, it will still work! Just add your if block below.
|
|
213
|
+
#
|
|
214
|
+
# EXAMPLE: How to add a legacy command:
|
|
215
|
+
#
|
|
216
|
+
# if command.startswith("/mycommand"):
|
|
217
|
+
# from code_puppy.messaging import emit_info
|
|
218
|
+
# emit_info("My command executed!")
|
|
219
|
+
# return True
|
|
220
|
+
#
|
|
221
|
+
# NOTE: For new commands, please use @register_command instead (see above).
|
|
222
|
+
# ========================================================================
|
|
223
|
+
|
|
224
|
+
# Legacy commands from other branches/rebases go here:
|
|
225
|
+
# (All current commands are in the registry above)
|
|
226
|
+
|
|
227
|
+
# Example placeholder (remove this and add your command if needed):
|
|
228
|
+
# if command.startswith("/my_new_command"):
|
|
229
|
+
# from code_puppy.messaging import emit_info
|
|
230
|
+
# emit_info("Command executed!")
|
|
231
|
+
# return True
|
|
232
|
+
|
|
233
|
+
# End of legacy fallback section
|
|
234
|
+
# ========================================================================
|
|
235
|
+
|
|
236
|
+
# All legacy command implementations have been moved to @register_command handlers above.
|
|
237
|
+
# If you're adding a new command via rebase, add your if block here.
|
|
238
|
+
|
|
239
|
+
# Try plugin-provided custom commands before unknown warning
|
|
240
|
+
if command.startswith("/"):
|
|
241
|
+
# Extract command name without leading slash and arguments intact
|
|
242
|
+
name = command[1:].split()[0] if len(command) > 1 else ""
|
|
243
|
+
try:
|
|
244
|
+
from code_puppy import callbacks
|
|
245
|
+
|
|
246
|
+
# Import the special result class for markdown commands
|
|
247
|
+
try:
|
|
248
|
+
from code_puppy.plugins.customizable_commands.register_callbacks import (
|
|
249
|
+
MarkdownCommandResult,
|
|
250
|
+
)
|
|
251
|
+
except ImportError:
|
|
252
|
+
MarkdownCommandResult = None
|
|
253
|
+
|
|
254
|
+
results = callbacks.on_custom_command(command=command, name=name)
|
|
255
|
+
# Iterate through callback results; treat str as handled (no model run)
|
|
256
|
+
for res in results:
|
|
257
|
+
if res is True:
|
|
258
|
+
return True
|
|
259
|
+
if MarkdownCommandResult and isinstance(res, MarkdownCommandResult):
|
|
260
|
+
# Special case: markdown command that should be processed as input
|
|
261
|
+
# Replace the command with the markdown content and let it be processed
|
|
262
|
+
# This is handled by the caller, so return the content as string
|
|
263
|
+
return res.content
|
|
264
|
+
if isinstance(res, str):
|
|
265
|
+
# Display returned text to the user and treat as handled
|
|
266
|
+
try:
|
|
267
|
+
emit_info(res)
|
|
268
|
+
except Exception:
|
|
269
|
+
pass
|
|
270
|
+
return True
|
|
271
|
+
except Exception as e:
|
|
272
|
+
# Log via emit_error but do not block default handling
|
|
273
|
+
emit_warning(f"Custom command hook error: {e}")
|
|
274
|
+
|
|
275
|
+
if name:
|
|
276
|
+
emit_warning(
|
|
277
|
+
Text.from_markup(
|
|
278
|
+
f"Unknown command: {command}\n[dim]Type /help for options.[/dim]"
|
|
279
|
+
)
|
|
280
|
+
)
|
|
281
|
+
else:
|
|
282
|
+
# Show current model ONLY here
|
|
283
|
+
from code_puppy.command_line.model_picker_completion import get_active_model
|
|
284
|
+
|
|
285
|
+
current_model = get_active_model()
|
|
286
|
+
emit_info(
|
|
287
|
+
Text.from_markup(
|
|
288
|
+
f"[bold green]Current Model:[/bold green] [cyan]{current_model}[/cyan]"
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
return True
|
|
292
|
+
|
|
293
|
+
return False
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Command registry for dynamic command discovery.
|
|
2
|
+
|
|
3
|
+
This module provides a decorator-based registration system for commands,
|
|
4
|
+
enabling automatic help generation and eliminating static command lists.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Callable, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class CommandInfo:
|
|
13
|
+
"""Metadata for a registered command."""
|
|
14
|
+
|
|
15
|
+
name: str
|
|
16
|
+
description: str
|
|
17
|
+
handler: Callable[[str], bool]
|
|
18
|
+
usage: str = ""
|
|
19
|
+
aliases: List[str] = field(default_factory=list)
|
|
20
|
+
category: str = "core"
|
|
21
|
+
detailed_help: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
def __post_init__(self):
|
|
24
|
+
"""Set default usage if not provided."""
|
|
25
|
+
if not self.usage:
|
|
26
|
+
self.usage = f"/{self.name}"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Global registry: maps command name/alias -> CommandInfo
|
|
30
|
+
_COMMAND_REGISTRY: Dict[str, CommandInfo] = {}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def register_command(
|
|
34
|
+
name: str,
|
|
35
|
+
description: str,
|
|
36
|
+
usage: str = "",
|
|
37
|
+
aliases: Optional[List[str]] = None,
|
|
38
|
+
category: str = "core",
|
|
39
|
+
detailed_help: Optional[str] = None,
|
|
40
|
+
):
|
|
41
|
+
"""Decorator to register a command handler.
|
|
42
|
+
|
|
43
|
+
This decorator registers a command function so it can be:
|
|
44
|
+
- Auto-discovered by the help system
|
|
45
|
+
- Invoked by handle_command() dynamically
|
|
46
|
+
- Grouped by category
|
|
47
|
+
- Documented with aliases and detailed help
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
name: Primary command name (without leading /)
|
|
51
|
+
description: Short one-line description for help text
|
|
52
|
+
usage: Full usage string (e.g., "/cd <dir>"). Defaults to "/{name}"
|
|
53
|
+
aliases: List of alternative names (without leading /)
|
|
54
|
+
category: Grouping category ("core", "session", "config", etc.)
|
|
55
|
+
detailed_help: Optional detailed help text for /help <command>
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
>>> @register_command(
|
|
59
|
+
... name="session",
|
|
60
|
+
... description="Show or rotate autosave session ID",
|
|
61
|
+
... usage="/session [id|new]",
|
|
62
|
+
... aliases=["s"],
|
|
63
|
+
... category="session",
|
|
64
|
+
... )
|
|
65
|
+
... def handle_session(command: str) -> bool:
|
|
66
|
+
... return True
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
The decorated function, unchanged
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def decorator(func: Callable[[str], bool]) -> Callable[[str], bool]:
|
|
73
|
+
# Create CommandInfo instance
|
|
74
|
+
cmd_info = CommandInfo(
|
|
75
|
+
name=name,
|
|
76
|
+
description=description,
|
|
77
|
+
handler=func,
|
|
78
|
+
usage=usage,
|
|
79
|
+
aliases=aliases or [],
|
|
80
|
+
category=category,
|
|
81
|
+
detailed_help=detailed_help,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Register primary name
|
|
85
|
+
_COMMAND_REGISTRY[name] = cmd_info
|
|
86
|
+
|
|
87
|
+
# Register all aliases pointing to the same CommandInfo
|
|
88
|
+
for alias in aliases or []:
|
|
89
|
+
_COMMAND_REGISTRY[alias] = cmd_info
|
|
90
|
+
|
|
91
|
+
return func
|
|
92
|
+
|
|
93
|
+
return decorator
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_all_commands() -> Dict[str, CommandInfo]:
|
|
97
|
+
"""Get all registered commands.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Dictionary mapping command names/aliases to CommandInfo objects.
|
|
101
|
+
Note: Aliases point to the same CommandInfo as their primary command.
|
|
102
|
+
"""
|
|
103
|
+
return _COMMAND_REGISTRY.copy()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_unique_commands() -> List[CommandInfo]:
|
|
107
|
+
"""Get unique registered commands (no duplicates from aliases).
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
List of unique CommandInfo objects (one per primary command).
|
|
111
|
+
"""
|
|
112
|
+
seen = set()
|
|
113
|
+
unique = []
|
|
114
|
+
for cmd_info in _COMMAND_REGISTRY.values():
|
|
115
|
+
# Use object id to avoid duplicates from aliases
|
|
116
|
+
if id(cmd_info) not in seen:
|
|
117
|
+
seen.add(id(cmd_info))
|
|
118
|
+
unique.append(cmd_info)
|
|
119
|
+
return unique
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_command(name: str) -> Optional[CommandInfo]:
|
|
123
|
+
"""Get command info by name or alias (case-insensitive).
|
|
124
|
+
|
|
125
|
+
First tries exact match for backward compatibility, then falls back to
|
|
126
|
+
case-insensitive matching.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
name: Command name or alias (without leading /)
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
CommandInfo if found, None otherwise
|
|
133
|
+
"""
|
|
134
|
+
# First try exact match (for backward compatibility)
|
|
135
|
+
exact_match = _COMMAND_REGISTRY.get(name)
|
|
136
|
+
if exact_match is not None:
|
|
137
|
+
return exact_match
|
|
138
|
+
|
|
139
|
+
# If no exact match, try case-insensitive matching
|
|
140
|
+
name_lower = name.lower()
|
|
141
|
+
for registered_name, cmd_info in _COMMAND_REGISTRY.items():
|
|
142
|
+
if registered_name.lower() == name_lower:
|
|
143
|
+
return cmd_info
|
|
144
|
+
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def clear_registry():
|
|
149
|
+
"""Clear all registered commands. Useful for testing."""
|
|
150
|
+
_COMMAND_REGISTRY.clear()
|