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,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared spinner implementation for CLI mode.
|
|
3
|
+
|
|
4
|
+
This module provides consistent spinner animations across different UI modes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .console_spinner import ConsoleSpinner
|
|
8
|
+
from .spinner_base import SpinnerBase
|
|
9
|
+
|
|
10
|
+
# Keep track of all active spinners to manage them globally
|
|
11
|
+
_active_spinners = []
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def register_spinner(spinner):
|
|
15
|
+
"""Register an active spinner to be managed globally."""
|
|
16
|
+
if spinner not in _active_spinners:
|
|
17
|
+
_active_spinners.append(spinner)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def unregister_spinner(spinner):
|
|
21
|
+
"""Remove a spinner from global management."""
|
|
22
|
+
if spinner in _active_spinners:
|
|
23
|
+
_active_spinners.remove(spinner)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def pause_all_spinners():
|
|
27
|
+
"""Pause all active spinners.
|
|
28
|
+
|
|
29
|
+
No-op when called from a sub-agent context to prevent
|
|
30
|
+
parallel sub-agents from interfering with the main spinner.
|
|
31
|
+
"""
|
|
32
|
+
# Lazy import to avoid circular dependency
|
|
33
|
+
from code_puppy.tools.subagent_context import is_subagent
|
|
34
|
+
|
|
35
|
+
if is_subagent():
|
|
36
|
+
return # Sub-agents don't control the main spinner
|
|
37
|
+
for spinner in _active_spinners:
|
|
38
|
+
try:
|
|
39
|
+
spinner.pause()
|
|
40
|
+
except Exception:
|
|
41
|
+
# Ignore errors if a spinner can't be paused
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def resume_all_spinners():
|
|
46
|
+
"""Resume all active spinners.
|
|
47
|
+
|
|
48
|
+
No-op when called from a sub-agent context to prevent
|
|
49
|
+
parallel sub-agents from interfering with the main spinner.
|
|
50
|
+
"""
|
|
51
|
+
# Lazy import to avoid circular dependency
|
|
52
|
+
from code_puppy.tools.subagent_context import is_subagent
|
|
53
|
+
|
|
54
|
+
if is_subagent():
|
|
55
|
+
return # Sub-agents don't control the main spinner
|
|
56
|
+
for spinner in _active_spinners:
|
|
57
|
+
try:
|
|
58
|
+
spinner.resume()
|
|
59
|
+
except Exception:
|
|
60
|
+
# Ignore errors if a spinner can't be resumed
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def update_spinner_context(info: str) -> None:
|
|
65
|
+
"""Update the shared context information displayed beside active spinners."""
|
|
66
|
+
SpinnerBase.set_context_info(info)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def clear_spinner_context() -> None:
|
|
70
|
+
"""Clear any context information displayed beside active spinners."""
|
|
71
|
+
SpinnerBase.clear_context_info()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
__all__ = [
|
|
75
|
+
"SpinnerBase",
|
|
76
|
+
"ConsoleSpinner",
|
|
77
|
+
"register_spinner",
|
|
78
|
+
"unregister_spinner",
|
|
79
|
+
"pause_all_spinners",
|
|
80
|
+
"resume_all_spinners",
|
|
81
|
+
"update_spinner_context",
|
|
82
|
+
"clear_spinner_context",
|
|
83
|
+
]
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Console spinner implementation for CLI mode using Rich's Live Display.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import platform
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.live import Live
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
|
|
13
|
+
from .spinner_base import SpinnerBase
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConsoleSpinner(SpinnerBase):
|
|
17
|
+
"""A console-based spinner implementation using Rich's Live Display."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, console=None):
|
|
20
|
+
"""Initialize the console spinner.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
console: Optional Rich console instance to use for output.
|
|
24
|
+
If not provided, a new one will be created.
|
|
25
|
+
"""
|
|
26
|
+
super().__init__()
|
|
27
|
+
self.console = console or Console()
|
|
28
|
+
self._thread = None
|
|
29
|
+
self._stop_event = threading.Event()
|
|
30
|
+
self._paused = False
|
|
31
|
+
self._live = None
|
|
32
|
+
|
|
33
|
+
# Register this spinner for global management
|
|
34
|
+
from . import register_spinner
|
|
35
|
+
|
|
36
|
+
register_spinner(self)
|
|
37
|
+
|
|
38
|
+
def start(self):
|
|
39
|
+
"""Start the spinner animation."""
|
|
40
|
+
super().start()
|
|
41
|
+
self._stop_event.clear()
|
|
42
|
+
|
|
43
|
+
# Don't start a new thread if one is already running
|
|
44
|
+
if self._thread and self._thread.is_alive():
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# Print blank line before spinner for visual separation from content
|
|
48
|
+
self.console.print()
|
|
49
|
+
|
|
50
|
+
# Create a Live display for the spinner
|
|
51
|
+
self._live = Live(
|
|
52
|
+
self._generate_spinner_panel(),
|
|
53
|
+
console=self.console,
|
|
54
|
+
refresh_per_second=20,
|
|
55
|
+
transient=True, # Clear the spinner line when stopped (no puppy litter!)
|
|
56
|
+
auto_refresh=False, # Don't auto-refresh to avoid wiping out user input
|
|
57
|
+
)
|
|
58
|
+
self._live.start()
|
|
59
|
+
|
|
60
|
+
# Start a thread to update the spinner frames
|
|
61
|
+
self._thread = threading.Thread(target=self._update_spinner)
|
|
62
|
+
self._thread.daemon = True
|
|
63
|
+
self._thread.start()
|
|
64
|
+
|
|
65
|
+
def stop(self):
|
|
66
|
+
"""Stop the spinner animation."""
|
|
67
|
+
if not self._is_spinning:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
self._stop_event.set()
|
|
71
|
+
self._is_spinning = False
|
|
72
|
+
|
|
73
|
+
if self._live:
|
|
74
|
+
self._live.stop()
|
|
75
|
+
self._live = None
|
|
76
|
+
|
|
77
|
+
if self._thread and self._thread.is_alive():
|
|
78
|
+
self._thread.join(timeout=0.5)
|
|
79
|
+
|
|
80
|
+
self._thread = None
|
|
81
|
+
|
|
82
|
+
# Windows-specific cleanup: Rich's Live display can leave terminal in corrupted state
|
|
83
|
+
if platform.system() == "Windows":
|
|
84
|
+
import sys
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
# Reset ANSI formatting for both stdout and stderr
|
|
88
|
+
sys.stdout.write("\x1b[0m") # Reset all attributes
|
|
89
|
+
sys.stdout.flush()
|
|
90
|
+
sys.stderr.write("\x1b[0m")
|
|
91
|
+
sys.stderr.flush()
|
|
92
|
+
|
|
93
|
+
# Clear the line and reposition cursor
|
|
94
|
+
sys.stdout.write("\r") # Return to start of line
|
|
95
|
+
sys.stdout.write("\x1b[K") # Clear to end of line
|
|
96
|
+
sys.stdout.flush()
|
|
97
|
+
|
|
98
|
+
# Flush keyboard input buffer to clear any stuck keys
|
|
99
|
+
try:
|
|
100
|
+
import msvcrt
|
|
101
|
+
|
|
102
|
+
while msvcrt.kbhit():
|
|
103
|
+
msvcrt.getch()
|
|
104
|
+
except ImportError:
|
|
105
|
+
pass # msvcrt not available (not Windows or different Python impl)
|
|
106
|
+
except Exception:
|
|
107
|
+
pass # Fail silently if cleanup doesn't work
|
|
108
|
+
|
|
109
|
+
# Unregister this spinner from global management
|
|
110
|
+
from . import unregister_spinner
|
|
111
|
+
|
|
112
|
+
unregister_spinner(self)
|
|
113
|
+
|
|
114
|
+
def update_frame(self):
|
|
115
|
+
"""Update to the next frame."""
|
|
116
|
+
super().update_frame()
|
|
117
|
+
|
|
118
|
+
def _generate_spinner_panel(self):
|
|
119
|
+
"""Generate a Rich panel containing the spinner text."""
|
|
120
|
+
# Check if we're awaiting user input - show nothing during input prompts
|
|
121
|
+
from code_puppy.tools.command_runner import is_awaiting_user_input
|
|
122
|
+
|
|
123
|
+
if self._paused or is_awaiting_user_input():
|
|
124
|
+
return Text("")
|
|
125
|
+
|
|
126
|
+
text = Text()
|
|
127
|
+
|
|
128
|
+
# Show thinking message during normal processing
|
|
129
|
+
text.append(SpinnerBase.THINKING_MESSAGE, style="bold cyan")
|
|
130
|
+
text.append(self.current_frame, style="bold cyan")
|
|
131
|
+
|
|
132
|
+
context_info = SpinnerBase.get_context_info()
|
|
133
|
+
if context_info:
|
|
134
|
+
text.append(" ")
|
|
135
|
+
text.append(context_info, style="bold white")
|
|
136
|
+
|
|
137
|
+
# Return a simple Text object instead of a Panel for a cleaner look
|
|
138
|
+
return text
|
|
139
|
+
|
|
140
|
+
def _update_spinner(self):
|
|
141
|
+
"""Update the spinner in a background thread."""
|
|
142
|
+
try:
|
|
143
|
+
while not self._stop_event.is_set():
|
|
144
|
+
# Update the frame
|
|
145
|
+
self.update_frame()
|
|
146
|
+
|
|
147
|
+
# Check if we're awaiting user input before updating the display
|
|
148
|
+
from code_puppy.tools.command_runner import is_awaiting_user_input
|
|
149
|
+
|
|
150
|
+
awaiting_input = is_awaiting_user_input()
|
|
151
|
+
|
|
152
|
+
# Update the live display only if not paused and not awaiting input
|
|
153
|
+
if self._live and not self._paused and not awaiting_input:
|
|
154
|
+
# Manually refresh instead of auto-refresh to avoid wiping input
|
|
155
|
+
self._live.update(self._generate_spinner_panel())
|
|
156
|
+
self._live.refresh()
|
|
157
|
+
|
|
158
|
+
# Short sleep to control animation speed
|
|
159
|
+
time.sleep(0.05)
|
|
160
|
+
except Exception as e:
|
|
161
|
+
# Note: Using sys.stderr - can't use messaging during spinner
|
|
162
|
+
import sys
|
|
163
|
+
|
|
164
|
+
sys.stderr.write(f"\nSpinner error: {e}\n")
|
|
165
|
+
self._is_spinning = False
|
|
166
|
+
|
|
167
|
+
def pause(self):
|
|
168
|
+
"""Pause the spinner animation."""
|
|
169
|
+
if self._is_spinning:
|
|
170
|
+
self._paused = True
|
|
171
|
+
# Stop the live display completely to restore terminal echo during input
|
|
172
|
+
if self._live:
|
|
173
|
+
try:
|
|
174
|
+
self._live.stop()
|
|
175
|
+
self._live = None
|
|
176
|
+
# Clear the line to remove any artifacts
|
|
177
|
+
import sys
|
|
178
|
+
|
|
179
|
+
sys.stdout.write("\r") # Return to start of line
|
|
180
|
+
sys.stdout.write("\x1b[K") # Clear to end of line
|
|
181
|
+
sys.stdout.flush()
|
|
182
|
+
except Exception:
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
def resume(self):
|
|
186
|
+
"""Resume the spinner animation."""
|
|
187
|
+
# Check if we should show a spinner - don't resume if waiting for user input
|
|
188
|
+
from code_puppy.tools.command_runner import is_awaiting_user_input
|
|
189
|
+
|
|
190
|
+
if is_awaiting_user_input():
|
|
191
|
+
return # Don't resume if waiting for user input
|
|
192
|
+
|
|
193
|
+
if self._is_spinning and self._paused:
|
|
194
|
+
self._paused = False
|
|
195
|
+
# Restart the live display if it was stopped during pause
|
|
196
|
+
if not self._live:
|
|
197
|
+
try:
|
|
198
|
+
# Clear any leftover artifacts before starting
|
|
199
|
+
import sys
|
|
200
|
+
|
|
201
|
+
sys.stdout.write("\r") # Return to start of line
|
|
202
|
+
sys.stdout.write("\x1b[K") # Clear to end of line
|
|
203
|
+
sys.stdout.flush()
|
|
204
|
+
|
|
205
|
+
# Print blank line before spinner for visual separation
|
|
206
|
+
self.console.print()
|
|
207
|
+
|
|
208
|
+
self._live = Live(
|
|
209
|
+
self._generate_spinner_panel(),
|
|
210
|
+
console=self.console,
|
|
211
|
+
refresh_per_second=20,
|
|
212
|
+
transient=True, # Clear spinner line when stopped
|
|
213
|
+
auto_refresh=False,
|
|
214
|
+
)
|
|
215
|
+
self._live.start()
|
|
216
|
+
except Exception:
|
|
217
|
+
pass
|
|
218
|
+
else:
|
|
219
|
+
# If live display still exists, clear console state first
|
|
220
|
+
try:
|
|
221
|
+
# Force Rich to reset any cached console state
|
|
222
|
+
if hasattr(self.console, "_buffer"):
|
|
223
|
+
# Clear Rich's internal buffer to prevent artifacts
|
|
224
|
+
self.console.file.write("\r") # Return to start
|
|
225
|
+
self.console.file.write("\x1b[K") # Clear line
|
|
226
|
+
self.console.file.flush()
|
|
227
|
+
|
|
228
|
+
self._live.update(self._generate_spinner_panel())
|
|
229
|
+
self._live.refresh()
|
|
230
|
+
except Exception:
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
def __enter__(self):
|
|
234
|
+
"""Support for context manager."""
|
|
235
|
+
self.start()
|
|
236
|
+
return self
|
|
237
|
+
|
|
238
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
239
|
+
"""Clean up when exiting context manager."""
|
|
240
|
+
self.stop()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base spinner implementation to be extended for different UI modes.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from threading import Lock
|
|
7
|
+
|
|
8
|
+
from code_puppy.config import get_puppy_name
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SpinnerBase(ABC):
|
|
12
|
+
"""Abstract base class for spinner implementations."""
|
|
13
|
+
|
|
14
|
+
# Shared spinner frames across implementations
|
|
15
|
+
FRAMES = [
|
|
16
|
+
"(🐶 ) ",
|
|
17
|
+
"( 🐶 ) ",
|
|
18
|
+
"( 🐶 ) ",
|
|
19
|
+
"( 🐶 ) ",
|
|
20
|
+
"( 🐶) ",
|
|
21
|
+
"( 🐶 ) ",
|
|
22
|
+
"( 🐶 ) ",
|
|
23
|
+
"( 🐶 ) ",
|
|
24
|
+
"(🐶 ) ",
|
|
25
|
+
]
|
|
26
|
+
puppy_name = get_puppy_name().title()
|
|
27
|
+
|
|
28
|
+
# Default message when processing
|
|
29
|
+
THINKING_MESSAGE = f"{puppy_name} is thinking... "
|
|
30
|
+
|
|
31
|
+
# Message when waiting for user input
|
|
32
|
+
WAITING_MESSAGE = f"{puppy_name} is waiting... "
|
|
33
|
+
|
|
34
|
+
# Current message - starts with thinking by default
|
|
35
|
+
MESSAGE = THINKING_MESSAGE
|
|
36
|
+
|
|
37
|
+
_context_info: str = ""
|
|
38
|
+
_context_lock: Lock = Lock()
|
|
39
|
+
|
|
40
|
+
def __init__(self):
|
|
41
|
+
"""Initialize the spinner."""
|
|
42
|
+
self._is_spinning = False
|
|
43
|
+
self._frame_index = 0
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def start(self):
|
|
47
|
+
"""Start the spinner animation."""
|
|
48
|
+
self._is_spinning = True
|
|
49
|
+
self._frame_index = 0
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def stop(self):
|
|
53
|
+
"""Stop the spinner animation."""
|
|
54
|
+
self._is_spinning = False
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def update_frame(self):
|
|
58
|
+
"""Update to the next frame."""
|
|
59
|
+
if self._is_spinning:
|
|
60
|
+
self._frame_index = (self._frame_index + 1) % len(self.FRAMES)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def current_frame(self):
|
|
64
|
+
"""Get the current frame."""
|
|
65
|
+
return self.FRAMES[self._frame_index]
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def is_spinning(self):
|
|
69
|
+
"""Check if the spinner is currently spinning."""
|
|
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 f"Tokens: {total_tokens:,}/{capacity:,} ({proportion_pct:.1f}% used)"
|