code-puppy 0.0.186__tar.gz → 0.0.188__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.
- {code_puppy-0.0.186 → code_puppy-0.0.188}/PKG-INFO +1 -3
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/base_agent.py +15 -14
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/callbacks.py +32 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/command_handler.py +78 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/messaging/spinner/__init__.py +12 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/messaging/spinner/console_spinner.py +5 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/messaging/spinner/spinner_base.py +31 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/messaging/spinner/textual_spinner.py +6 -1
- code_puppy-0.0.188/code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/pyproject.toml +1 -3
- {code_puppy-0.0.186 → code_puppy-0.0.188}/.gitignore +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/LICENSE +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/README.md +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/agent_c_reviewer.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/agent_code_reviewer.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/agent_golang_reviewer.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/agent_python_reviewer.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/agent_qa_expert.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/agent_security_auditor.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/add_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/config.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/main.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/blocking_startup.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/captured_stdio_server.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/managed_server.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/manager.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/models.json +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/browser/camoufox_manager.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/browser/vqa_agent.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/__init__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/app.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/components/__init__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/components/chat_view.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/components/command_history_modal.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/components/copy_button.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/components/custom_widgets.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/components/human_input_modal.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/components/input_area.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/components/sidebar.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/components/status_bar.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/messages.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/models/__init__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/models/chat_message.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/models/command_history.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/models/enums.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/screens/__init__.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/screens/help.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/screens/mcp_install_wizard.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/screens/settings.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/screens/tools.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui_state.py +0 -0
- {code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/version_checker.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: code-puppy
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.188
|
4
4
|
Summary: Code generation agent
|
5
5
|
Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
|
6
6
|
Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
|
@@ -39,8 +39,6 @@ Requires-Dist: tenacity>=8.2.0
|
|
39
39
|
Requires-Dist: termcolor>=3.1.0
|
40
40
|
Requires-Dist: textual-dev>=1.7.0
|
41
41
|
Requires-Dist: textual>=5.0.0
|
42
|
-
Requires-Dist: tree-sitter-language-pack>=0.8.0
|
43
|
-
Requires-Dist: tree-sitter-typescript>=0.23.2
|
44
42
|
Requires-Dist: uvicorn>=0.29.0
|
45
43
|
Description-Content-Type: text/markdown
|
46
44
|
|
@@ -42,6 +42,10 @@ from code_puppy.messaging import (
|
|
42
42
|
emit_system_message,
|
43
43
|
emit_warning,
|
44
44
|
)
|
45
|
+
from code_puppy.messaging.spinner import (
|
46
|
+
SpinnerBase,
|
47
|
+
update_spinner_context,
|
48
|
+
)
|
45
49
|
from code_puppy.model_factory import ModelFactory
|
46
50
|
from code_puppy.summarization_agent import run_summarization_sync
|
47
51
|
from code_puppy.tools.common import console
|
@@ -527,6 +531,11 @@ class BaseAgent(ABC):
|
|
527
531
|
# Check if we're in TUI mode and can update the status bar
|
528
532
|
from code_puppy.tui_state import get_tui_app_instance, is_tui_mode
|
529
533
|
|
534
|
+
context_summary = SpinnerBase.format_context_info(
|
535
|
+
total_current_tokens, model_max, proportion_used
|
536
|
+
)
|
537
|
+
update_spinner_context(context_summary)
|
538
|
+
|
530
539
|
if is_tui_mode():
|
531
540
|
tui_app = get_tui_app_instance()
|
532
541
|
if tui_app:
|
@@ -538,22 +547,11 @@ class BaseAgent(ABC):
|
|
538
547
|
)
|
539
548
|
except Exception as e:
|
540
549
|
emit_error(e)
|
541
|
-
# Fallback to chat message if status bar update fails
|
542
|
-
emit_info(
|
543
|
-
f"\n[bold white on blue] Tokens in context: {total_current_tokens}, total model capacity: {model_max}, proportion used: {proportion_used:.2f} [/bold white on blue] \n",
|
544
|
-
message_group="token_context_status",
|
545
|
-
)
|
546
550
|
else:
|
547
|
-
# Fallback if no TUI app instance
|
548
551
|
emit_info(
|
549
|
-
f"
|
552
|
+
f"Final token count after processing: {total_current_tokens}",
|
550
553
|
message_group="token_context_status",
|
551
554
|
)
|
552
|
-
else:
|
553
|
-
# Non-TUI mode - emit to console as before
|
554
|
-
emit_info(
|
555
|
-
f"\n[bold white on blue] Tokens in context: {total_current_tokens}, total model capacity: {model_max}, proportion used: {proportion_used:.2f} [/bold white on blue] \n"
|
556
|
-
)
|
557
555
|
# Get the configured compaction threshold
|
558
556
|
compaction_threshold = get_compaction_threshold()
|
559
557
|
|
@@ -578,6 +576,11 @@ class BaseAgent(ABC):
|
|
578
576
|
self.estimate_tokens_for_message(msg) for msg in result_messages
|
579
577
|
)
|
580
578
|
# Update status bar with final token count if in TUI mode
|
579
|
+
final_summary = SpinnerBase.format_context_info(
|
580
|
+
final_token_count, model_max, final_token_count / model_max
|
581
|
+
)
|
582
|
+
update_spinner_context(final_summary)
|
583
|
+
|
581
584
|
if is_tui_mode():
|
582
585
|
tui_app = get_tui_app_instance()
|
583
586
|
if tui_app:
|
@@ -596,8 +599,6 @@ class BaseAgent(ABC):
|
|
596
599
|
f"Final token count after processing: {final_token_count}",
|
597
600
|
message_group="token_context_status",
|
598
601
|
)
|
599
|
-
else:
|
600
|
-
emit_info(f"Final token count after processing: {final_token_count}")
|
601
602
|
self.set_message_history(result_messages)
|
602
603
|
for m in summarized_messages:
|
603
604
|
self.add_compacted_message_hash(self.hash_message(m))
|
@@ -15,6 +15,8 @@ PhaseType = Literal[
|
|
15
15
|
"load_model_config",
|
16
16
|
"load_prompt",
|
17
17
|
"agent_reload",
|
18
|
+
"custom_command",
|
19
|
+
"custom_command_help",
|
18
20
|
]
|
19
21
|
CallbackFunc = Callable[..., Any]
|
20
22
|
|
@@ -30,6 +32,8 @@ _callbacks: Dict[PhaseType, List[CallbackFunc]] = {
|
|
30
32
|
"load_model_config": [],
|
31
33
|
"load_prompt": [],
|
32
34
|
"agent_reload": [],
|
35
|
+
"custom_command": [],
|
36
|
+
"custom_command_help": [],
|
33
37
|
}
|
34
38
|
|
35
39
|
logger = logging.getLogger(__name__)
|
@@ -174,3 +178,31 @@ def on_agent_reload(*args, **kwargs) -> Any:
|
|
174
178
|
|
175
179
|
def on_load_prompt():
|
176
180
|
return _trigger_callbacks_sync("load_prompt")
|
181
|
+
|
182
|
+
|
183
|
+
def on_custom_command_help() -> List[Any]:
|
184
|
+
"""Collect custom command help entries from plugins.
|
185
|
+
|
186
|
+
Each callback should return a list of tuples [(name, description), ...]
|
187
|
+
or a single tuple, or None. We'll flatten and sanitize results.
|
188
|
+
"""
|
189
|
+
return _trigger_callbacks_sync("custom_command_help")
|
190
|
+
|
191
|
+
|
192
|
+
def on_custom_command(command: str, name: str) -> List[Any]:
|
193
|
+
"""Trigger custom command callbacks.
|
194
|
+
|
195
|
+
This allows plugins to register handlers for slash commands
|
196
|
+
that are not built into the core command handler.
|
197
|
+
|
198
|
+
Args:
|
199
|
+
command: The full command string (e.g., "/foo bar baz").
|
200
|
+
name: The primary command name without the leading slash (e.g., "foo").
|
201
|
+
|
202
|
+
Returns:
|
203
|
+
Implementations may return:
|
204
|
+
- True if the command was handled (and no further action is needed)
|
205
|
+
- A string to be processed as user input by the caller
|
206
|
+
- None to indicate not handled
|
207
|
+
"""
|
208
|
+
return _trigger_callbacks_sync("custom_command", command, name)
|
@@ -11,6 +11,9 @@ def get_commands_help():
|
|
11
11
|
"""Generate commands help using Rich Text objects to avoid markup conflicts."""
|
12
12
|
from rich.text import Text
|
13
13
|
|
14
|
+
# Ensure plugins are loaded so custom help can register
|
15
|
+
_ensure_plugins_loaded()
|
16
|
+
|
14
17
|
# Build help text programmatically
|
15
18
|
help_lines = []
|
16
19
|
|
@@ -90,6 +93,33 @@ def get_commands_help():
|
|
90
93
|
+ Text(" Show unknown command warning")
|
91
94
|
)
|
92
95
|
|
96
|
+
# Add custom commands from plugins (if any)
|
97
|
+
try:
|
98
|
+
from code_puppy import callbacks
|
99
|
+
|
100
|
+
custom_help_results = callbacks.on_custom_command_help()
|
101
|
+
# Flatten various returns into a list of (name, description)
|
102
|
+
custom_entries = []
|
103
|
+
for res in custom_help_results:
|
104
|
+
if not res:
|
105
|
+
continue
|
106
|
+
if isinstance(res, tuple) and len(res) == 2:
|
107
|
+
custom_entries.append(res)
|
108
|
+
elif isinstance(res, list):
|
109
|
+
for item in res:
|
110
|
+
if isinstance(item, tuple) and len(item) == 2:
|
111
|
+
custom_entries.append(item)
|
112
|
+
if custom_entries:
|
113
|
+
help_lines.append(Text("\n", style="dim"))
|
114
|
+
help_lines.append(Text("Custom Commands", style="bold magenta"))
|
115
|
+
for name, desc in custom_entries:
|
116
|
+
help_lines.append(
|
117
|
+
Text(f"/{name}", style="cyan") + Text(f" {desc}")
|
118
|
+
)
|
119
|
+
except Exception:
|
120
|
+
# If callbacks fail, skip custom help silently
|
121
|
+
pass
|
122
|
+
|
93
123
|
# Combine all lines
|
94
124
|
final_text = Text()
|
95
125
|
for i, line in enumerate(help_lines):
|
@@ -100,8 +130,32 @@ def get_commands_help():
|
|
100
130
|
return final_text
|
101
131
|
|
102
132
|
|
133
|
+
_PLUGINS_LOADED = False
|
134
|
+
|
135
|
+
|
136
|
+
def _ensure_plugins_loaded() -> None:
|
137
|
+
global _PLUGINS_LOADED
|
138
|
+
if _PLUGINS_LOADED:
|
139
|
+
return
|
140
|
+
try:
|
141
|
+
from code_puppy import plugins
|
142
|
+
|
143
|
+
plugins.load_plugin_callbacks()
|
144
|
+
_PLUGINS_LOADED = True
|
145
|
+
except Exception as e:
|
146
|
+
# If plugins fail to load, continue gracefully but note it
|
147
|
+
try:
|
148
|
+
from code_puppy.messaging import emit_warning
|
149
|
+
|
150
|
+
emit_warning(f"Plugin load error: {e}")
|
151
|
+
except Exception:
|
152
|
+
pass
|
153
|
+
_PLUGINS_LOADED = True
|
154
|
+
|
155
|
+
|
103
156
|
def handle_command(command: str):
|
104
157
|
from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
|
158
|
+
_ensure_plugins_loaded()
|
105
159
|
|
106
160
|
"""
|
107
161
|
Handle commands prefixed with '/'.
|
@@ -400,6 +454,8 @@ def handle_command(command: str):
|
|
400
454
|
|
401
455
|
handler = MCPCommandHandler()
|
402
456
|
return handler.handle_mcp_command(command)
|
457
|
+
|
458
|
+
# Built-in help
|
403
459
|
if command in ("/help", "/h"):
|
404
460
|
import uuid
|
405
461
|
|
@@ -705,8 +761,30 @@ def handle_command(command: str):
|
|
705
761
|
# Signal to the main app that we want to exit
|
706
762
|
# The actual exit handling is done in main.py
|
707
763
|
return True
|
764
|
+
|
765
|
+
# Try plugin-provided custom commands before unknown warning
|
708
766
|
if command.startswith("/"):
|
767
|
+
# Extract command name without leading slash and arguments intact
|
709
768
|
name = command[1:].split()[0] if len(command) > 1 else ""
|
769
|
+
try:
|
770
|
+
from code_puppy import callbacks
|
771
|
+
|
772
|
+
results = callbacks.on_custom_command(command=command, name=name)
|
773
|
+
# Iterate through callback results; treat str as handled (no model run)
|
774
|
+
for res in results:
|
775
|
+
if res is True:
|
776
|
+
return True
|
777
|
+
if isinstance(res, str):
|
778
|
+
# Display returned text to the user and treat as handled
|
779
|
+
try:
|
780
|
+
emit_info(res)
|
781
|
+
except Exception:
|
782
|
+
pass
|
783
|
+
return True
|
784
|
+
except Exception as e:
|
785
|
+
# Log via emit_error but do not block default handling
|
786
|
+
emit_warning(f"Custom command hook error: {e}")
|
787
|
+
|
710
788
|
if name:
|
711
789
|
emit_warning(
|
712
790
|
f"Unknown command: {command}\n[dim]Type /help for options.[/dim]"
|
@@ -44,6 +44,16 @@ def resume_all_spinners():
|
|
44
44
|
pass
|
45
45
|
|
46
46
|
|
47
|
+
def update_spinner_context(info: str) -> None:
|
48
|
+
"""Update the shared context information displayed beside active spinners."""
|
49
|
+
SpinnerBase.set_context_info(info)
|
50
|
+
|
51
|
+
|
52
|
+
def clear_spinner_context() -> None:
|
53
|
+
"""Clear any context information displayed beside active spinners."""
|
54
|
+
SpinnerBase.clear_context_info()
|
55
|
+
|
56
|
+
|
47
57
|
__all__ = [
|
48
58
|
"SpinnerBase",
|
49
59
|
"TextualSpinner",
|
@@ -52,4 +62,6 @@ __all__ = [
|
|
52
62
|
"unregister_spinner",
|
53
63
|
"pause_all_spinners",
|
54
64
|
"resume_all_spinners",
|
65
|
+
"update_spinner_context",
|
66
|
+
"clear_spinner_context",
|
55
67
|
]
|
@@ -103,6 +103,11 @@ class ConsoleSpinner(SpinnerBase):
|
|
103
103
|
|
104
104
|
text.append(self.current_frame, style="bold cyan")
|
105
105
|
|
106
|
+
context_info = SpinnerBase.get_context_info()
|
107
|
+
if context_info:
|
108
|
+
text.append(" ")
|
109
|
+
text.append(context_info, style="bold white")
|
110
|
+
|
106
111
|
# Return a simple Text object instead of a Panel for a cleaner look
|
107
112
|
return text
|
108
113
|
|
@@ -3,6 +3,7 @@ Base spinner implementation to be extended for different UI modes.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
from abc import ABC, abstractmethod
|
6
|
+
from threading import Lock
|
6
7
|
|
7
8
|
from code_puppy.config import get_puppy_name
|
8
9
|
|
@@ -33,6 +34,9 @@ class SpinnerBase(ABC):
|
|
33
34
|
# Current message - starts with thinking by default
|
34
35
|
MESSAGE = THINKING_MESSAGE
|
35
36
|
|
37
|
+
_context_info: str = ""
|
38
|
+
_context_lock: Lock = Lock()
|
39
|
+
|
36
40
|
def __init__(self):
|
37
41
|
"""Initialize the spinner."""
|
38
42
|
self._is_spinning = False
|
@@ -64,3 +68,30 @@ class SpinnerBase(ABC):
|
|
64
68
|
def is_spinning(self):
|
65
69
|
"""Check if the spinner is currently spinning."""
|
66
70
|
return self._is_spinning
|
71
|
+
|
72
|
+
@classmethod
|
73
|
+
def set_context_info(cls, info: str) -> None:
|
74
|
+
"""Set shared context information displayed beside the spinner."""
|
75
|
+
with cls._context_lock:
|
76
|
+
cls._context_info = info
|
77
|
+
|
78
|
+
@classmethod
|
79
|
+
def clear_context_info(cls) -> None:
|
80
|
+
"""Clear any context information displayed beside the spinner."""
|
81
|
+
cls.set_context_info("")
|
82
|
+
|
83
|
+
@classmethod
|
84
|
+
def get_context_info(cls) -> str:
|
85
|
+
"""Return the current spinner context information."""
|
86
|
+
with cls._context_lock:
|
87
|
+
return cls._context_info
|
88
|
+
|
89
|
+
@staticmethod
|
90
|
+
def format_context_info(total_tokens: int, capacity: int, proportion: float) -> str:
|
91
|
+
"""Create a concise context summary for spinner display."""
|
92
|
+
if capacity <= 0:
|
93
|
+
return ""
|
94
|
+
proportion_pct = proportion * 100
|
95
|
+
return (
|
96
|
+
f"Tokens: {total_tokens:,}/{capacity:,} ({proportion_pct:.1f}% used)"
|
97
|
+
)
|
@@ -70,8 +70,13 @@ class TextualSpinner(Static):
|
|
70
70
|
# Show thinking message during normal processing
|
71
71
|
message = SpinnerBase.THINKING_MESSAGE
|
72
72
|
|
73
|
+
context_info = SpinnerBase.get_context_info()
|
74
|
+
context_segment = (
|
75
|
+
f" [bold white]{context_info}[/bold white]" if context_info else ""
|
76
|
+
)
|
77
|
+
|
73
78
|
self.update(
|
74
|
-
f"[bold cyan]{message}[/bold cyan][bold cyan]{current_frame}[/bold cyan]"
|
79
|
+
f"[bold cyan]{message}[/bold cyan][bold cyan]{current_frame}[/bold cyan]{context_segment}"
|
75
80
|
)
|
76
81
|
|
77
82
|
def pause(self):
|
@@ -0,0 +1,51 @@
|
|
1
|
+
from code_puppy.callbacks import register_callback
|
2
|
+
from code_puppy.messaging import emit_info
|
3
|
+
|
4
|
+
|
5
|
+
def _custom_help():
|
6
|
+
return [
|
7
|
+
("woof", "Emit a playful woof message (no model)"),
|
8
|
+
("echo", "Echo back your text (display only)"),
|
9
|
+
]
|
10
|
+
|
11
|
+
|
12
|
+
def _handle_custom_command(command: str, name: str):
|
13
|
+
"""Handle a demo custom command.
|
14
|
+
|
15
|
+
Policy: custom commands must NOT invoke the model. They should emit
|
16
|
+
messages or return True to indicate handling. Returning a string is
|
17
|
+
treated as a display-only message by the command handler.
|
18
|
+
|
19
|
+
Supports:
|
20
|
+
- /woof → emits a fun message and returns True
|
21
|
+
- /echo <text> → emits the text (display-only)
|
22
|
+
"""
|
23
|
+
if not name:
|
24
|
+
return None
|
25
|
+
|
26
|
+
if name == "woof":
|
27
|
+
# If extra text is provided, pass it as a prompt; otherwise, send a fun default
|
28
|
+
parts = command.split(maxsplit=1)
|
29
|
+
if len(parts) == 2:
|
30
|
+
text = parts[1]
|
31
|
+
emit_info(f"🐶 Woof! sending prompt: {text}")
|
32
|
+
return text
|
33
|
+
emit_info("🐶 Woof! sending prompt: Tell me a dog fact")
|
34
|
+
return "Tell me a dog fact"
|
35
|
+
|
36
|
+
if name == "echo":
|
37
|
+
# Return the rest of the command (after the name) to be treated as input
|
38
|
+
# Example: "/echo Hello" → returns "Hello"
|
39
|
+
rest = command.split(maxsplit=1)
|
40
|
+
if len(rest) == 2:
|
41
|
+
text = rest[1]
|
42
|
+
emit_info(f"[dim]example plugin echo ->[/dim] {text}")
|
43
|
+
return text
|
44
|
+
emit_info("[dim]example plugin echo (empty)[/dim]")
|
45
|
+
return ""
|
46
|
+
|
47
|
+
return None
|
48
|
+
|
49
|
+
|
50
|
+
register_callback("custom_command_help", _custom_help)
|
51
|
+
register_callback("custom_command", _handle_custom_command)
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "code-puppy"
|
7
|
-
version = "0.0.
|
7
|
+
version = "0.0.188"
|
8
8
|
description = "Code generation agent"
|
9
9
|
readme = "README.md"
|
10
10
|
requires-python = ">=3.11"
|
@@ -23,8 +23,6 @@ dependencies = [
|
|
23
23
|
"pathspec>=0.11.0",
|
24
24
|
"rapidfuzz>=3.13.0",
|
25
25
|
"json-repair>=0.46.2",
|
26
|
-
"tree-sitter-language-pack>=0.8.0",
|
27
|
-
"tree-sitter-typescript>=0.23.2",
|
28
26
|
"fastapi>=0.110.0",
|
29
27
|
"uvicorn>=0.29.0",
|
30
28
|
"PyJWT>=2.8.0",
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/load_context_completion.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/model_picker_completion.py
RENAMED
File without changes
|
File without changes
|
{code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/command_line/prompt_toolkit_completion.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{code_puppy-0.0.186 → code_puppy-0.0.188}/code_puppy/tui/components/command_history_modal.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|