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.
Files changed (288) hide show
  1. code_puppy/__init__.py +10 -0
  2. code_puppy/__main__.py +10 -0
  3. code_puppy/agents/__init__.py +31 -0
  4. code_puppy/agents/agent_c_reviewer.py +155 -0
  5. code_puppy/agents/agent_code_puppy.py +117 -0
  6. code_puppy/agents/agent_code_reviewer.py +90 -0
  7. code_puppy/agents/agent_cpp_reviewer.py +132 -0
  8. code_puppy/agents/agent_creator_agent.py +638 -0
  9. code_puppy/agents/agent_golang_reviewer.py +151 -0
  10. code_puppy/agents/agent_helios.py +124 -0
  11. code_puppy/agents/agent_javascript_reviewer.py +160 -0
  12. code_puppy/agents/agent_manager.py +742 -0
  13. code_puppy/agents/agent_pack_leader.py +385 -0
  14. code_puppy/agents/agent_planning.py +165 -0
  15. code_puppy/agents/agent_python_programmer.py +169 -0
  16. code_puppy/agents/agent_python_reviewer.py +90 -0
  17. code_puppy/agents/agent_qa_expert.py +163 -0
  18. code_puppy/agents/agent_qa_kitten.py +208 -0
  19. code_puppy/agents/agent_scheduler.py +121 -0
  20. code_puppy/agents/agent_security_auditor.py +181 -0
  21. code_puppy/agents/agent_terminal_qa.py +323 -0
  22. code_puppy/agents/agent_typescript_reviewer.py +166 -0
  23. code_puppy/agents/base_agent.py +2156 -0
  24. code_puppy/agents/event_stream_handler.py +348 -0
  25. code_puppy/agents/json_agent.py +202 -0
  26. code_puppy/agents/pack/__init__.py +34 -0
  27. code_puppy/agents/pack/bloodhound.py +304 -0
  28. code_puppy/agents/pack/husky.py +327 -0
  29. code_puppy/agents/pack/retriever.py +393 -0
  30. code_puppy/agents/pack/shepherd.py +348 -0
  31. code_puppy/agents/pack/terrier.py +287 -0
  32. code_puppy/agents/pack/watchdog.py +367 -0
  33. code_puppy/agents/prompt_reviewer.py +145 -0
  34. code_puppy/agents/subagent_stream_handler.py +276 -0
  35. code_puppy/api/__init__.py +13 -0
  36. code_puppy/api/app.py +169 -0
  37. code_puppy/api/main.py +21 -0
  38. code_puppy/api/pty_manager.py +453 -0
  39. code_puppy/api/routers/__init__.py +12 -0
  40. code_puppy/api/routers/agents.py +36 -0
  41. code_puppy/api/routers/commands.py +217 -0
  42. code_puppy/api/routers/config.py +75 -0
  43. code_puppy/api/routers/sessions.py +234 -0
  44. code_puppy/api/templates/terminal.html +361 -0
  45. code_puppy/api/websocket.py +154 -0
  46. code_puppy/callbacks.py +692 -0
  47. code_puppy/chatgpt_codex_client.py +338 -0
  48. code_puppy/claude_cache_client.py +672 -0
  49. code_puppy/cli_runner.py +1073 -0
  50. code_puppy/command_line/__init__.py +1 -0
  51. code_puppy/command_line/add_model_menu.py +1092 -0
  52. code_puppy/command_line/agent_menu.py +662 -0
  53. code_puppy/command_line/attachments.py +395 -0
  54. code_puppy/command_line/autosave_menu.py +704 -0
  55. code_puppy/command_line/clipboard.py +527 -0
  56. code_puppy/command_line/colors_menu.py +532 -0
  57. code_puppy/command_line/command_handler.py +293 -0
  58. code_puppy/command_line/command_registry.py +150 -0
  59. code_puppy/command_line/config_commands.py +719 -0
  60. code_puppy/command_line/core_commands.py +867 -0
  61. code_puppy/command_line/diff_menu.py +865 -0
  62. code_puppy/command_line/file_path_completion.py +73 -0
  63. code_puppy/command_line/load_context_completion.py +52 -0
  64. code_puppy/command_line/mcp/__init__.py +10 -0
  65. code_puppy/command_line/mcp/base.py +32 -0
  66. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  67. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  68. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  69. code_puppy/command_line/mcp/edit_command.py +148 -0
  70. code_puppy/command_line/mcp/handler.py +138 -0
  71. code_puppy/command_line/mcp/help_command.py +147 -0
  72. code_puppy/command_line/mcp/install_command.py +214 -0
  73. code_puppy/command_line/mcp/install_menu.py +705 -0
  74. code_puppy/command_line/mcp/list_command.py +94 -0
  75. code_puppy/command_line/mcp/logs_command.py +235 -0
  76. code_puppy/command_line/mcp/remove_command.py +82 -0
  77. code_puppy/command_line/mcp/restart_command.py +100 -0
  78. code_puppy/command_line/mcp/search_command.py +123 -0
  79. code_puppy/command_line/mcp/start_all_command.py +135 -0
  80. code_puppy/command_line/mcp/start_command.py +117 -0
  81. code_puppy/command_line/mcp/status_command.py +184 -0
  82. code_puppy/command_line/mcp/stop_all_command.py +112 -0
  83. code_puppy/command_line/mcp/stop_command.py +80 -0
  84. code_puppy/command_line/mcp/test_command.py +107 -0
  85. code_puppy/command_line/mcp/utils.py +129 -0
  86. code_puppy/command_line/mcp/wizard_utils.py +334 -0
  87. code_puppy/command_line/mcp_completion.py +174 -0
  88. code_puppy/command_line/model_picker_completion.py +197 -0
  89. code_puppy/command_line/model_settings_menu.py +932 -0
  90. code_puppy/command_line/motd.py +96 -0
  91. code_puppy/command_line/onboarding_slides.py +179 -0
  92. code_puppy/command_line/onboarding_wizard.py +342 -0
  93. code_puppy/command_line/pin_command_completion.py +329 -0
  94. code_puppy/command_line/prompt_toolkit_completion.py +846 -0
  95. code_puppy/command_line/session_commands.py +302 -0
  96. code_puppy/command_line/shell_passthrough.py +145 -0
  97. code_puppy/command_line/skills_completion.py +160 -0
  98. code_puppy/command_line/uc_menu.py +893 -0
  99. code_puppy/command_line/utils.py +93 -0
  100. code_puppy/command_line/wiggum_state.py +78 -0
  101. code_puppy/config.py +1770 -0
  102. code_puppy/error_logging.py +134 -0
  103. code_puppy/gemini_code_assist.py +385 -0
  104. code_puppy/gemini_model.py +754 -0
  105. code_puppy/hook_engine/README.md +105 -0
  106. code_puppy/hook_engine/__init__.py +21 -0
  107. code_puppy/hook_engine/aliases.py +155 -0
  108. code_puppy/hook_engine/engine.py +221 -0
  109. code_puppy/hook_engine/executor.py +296 -0
  110. code_puppy/hook_engine/matcher.py +156 -0
  111. code_puppy/hook_engine/models.py +240 -0
  112. code_puppy/hook_engine/registry.py +106 -0
  113. code_puppy/hook_engine/validator.py +144 -0
  114. code_puppy/http_utils.py +361 -0
  115. code_puppy/keymap.py +128 -0
  116. code_puppy/main.py +10 -0
  117. code_puppy/mcp_/__init__.py +66 -0
  118. code_puppy/mcp_/async_lifecycle.py +286 -0
  119. code_puppy/mcp_/blocking_startup.py +469 -0
  120. code_puppy/mcp_/captured_stdio_server.py +275 -0
  121. code_puppy/mcp_/circuit_breaker.py +290 -0
  122. code_puppy/mcp_/config_wizard.py +507 -0
  123. code_puppy/mcp_/dashboard.py +308 -0
  124. code_puppy/mcp_/error_isolation.py +407 -0
  125. code_puppy/mcp_/examples/retry_example.py +226 -0
  126. code_puppy/mcp_/health_monitor.py +589 -0
  127. code_puppy/mcp_/managed_server.py +428 -0
  128. code_puppy/mcp_/manager.py +807 -0
  129. code_puppy/mcp_/mcp_logs.py +224 -0
  130. code_puppy/mcp_/registry.py +451 -0
  131. code_puppy/mcp_/retry_manager.py +337 -0
  132. code_puppy/mcp_/server_registry_catalog.py +1126 -0
  133. code_puppy/mcp_/status_tracker.py +355 -0
  134. code_puppy/mcp_/system_tools.py +209 -0
  135. code_puppy/mcp_prompts/__init__.py +1 -0
  136. code_puppy/mcp_prompts/hook_creator.py +103 -0
  137. code_puppy/messaging/__init__.py +255 -0
  138. code_puppy/messaging/bus.py +613 -0
  139. code_puppy/messaging/commands.py +167 -0
  140. code_puppy/messaging/markdown_patches.py +57 -0
  141. code_puppy/messaging/message_queue.py +361 -0
  142. code_puppy/messaging/messages.py +569 -0
  143. code_puppy/messaging/queue_console.py +271 -0
  144. code_puppy/messaging/renderers.py +311 -0
  145. code_puppy/messaging/rich_renderer.py +1158 -0
  146. code_puppy/messaging/spinner/__init__.py +83 -0
  147. code_puppy/messaging/spinner/console_spinner.py +240 -0
  148. code_puppy/messaging/spinner/spinner_base.py +95 -0
  149. code_puppy/messaging/subagent_console.py +460 -0
  150. code_puppy/model_factory.py +848 -0
  151. code_puppy/model_switching.py +63 -0
  152. code_puppy/model_utils.py +168 -0
  153. code_puppy/models.json +174 -0
  154. code_puppy/models_dev_api.json +1 -0
  155. code_puppy/models_dev_parser.py +592 -0
  156. code_puppy/plugins/__init__.py +186 -0
  157. code_puppy/plugins/agent_skills/__init__.py +22 -0
  158. code_puppy/plugins/agent_skills/config.py +175 -0
  159. code_puppy/plugins/agent_skills/discovery.py +136 -0
  160. code_puppy/plugins/agent_skills/downloader.py +392 -0
  161. code_puppy/plugins/agent_skills/installer.py +22 -0
  162. code_puppy/plugins/agent_skills/metadata.py +219 -0
  163. code_puppy/plugins/agent_skills/prompt_builder.py +60 -0
  164. code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
  165. code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
  166. code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
  167. code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
  168. code_puppy/plugins/agent_skills/skills_menu.py +781 -0
  169. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  170. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  171. code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
  172. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  173. code_puppy/plugins/antigravity_oauth/constants.py +133 -0
  174. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  175. code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
  176. code_puppy/plugins/antigravity_oauth/storage.py +288 -0
  177. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  178. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  179. code_puppy/plugins/antigravity_oauth/transport.py +863 -0
  180. code_puppy/plugins/antigravity_oauth/utils.py +168 -0
  181. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  182. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  183. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +329 -0
  184. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
  185. code_puppy/plugins/chatgpt_oauth/test_plugin.py +301 -0
  186. code_puppy/plugins/chatgpt_oauth/utils.py +523 -0
  187. code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
  188. code_puppy/plugins/claude_code_hooks/config.py +137 -0
  189. code_puppy/plugins/claude_code_hooks/register_callbacks.py +175 -0
  190. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  191. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  192. code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  193. code_puppy/plugins/claude_code_oauth/config.py +52 -0
  194. code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
  195. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  196. code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
  197. code_puppy/plugins/claude_code_oauth/utils.py +640 -0
  198. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  199. code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
  200. code_puppy/plugins/example_custom_command/README.md +280 -0
  201. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  202. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  203. code_puppy/plugins/file_permission_handler/register_callbacks.py +470 -0
  204. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  205. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  206. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  207. code_puppy/plugins/hook_creator/__init__.py +1 -0
  208. code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
  209. code_puppy/plugins/hook_manager/__init__.py +1 -0
  210. code_puppy/plugins/hook_manager/config.py +290 -0
  211. code_puppy/plugins/hook_manager/hooks_menu.py +564 -0
  212. code_puppy/plugins/hook_manager/register_callbacks.py +227 -0
  213. code_puppy/plugins/oauth_puppy_html.py +228 -0
  214. code_puppy/plugins/scheduler/__init__.py +1 -0
  215. code_puppy/plugins/scheduler/register_callbacks.py +88 -0
  216. code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
  217. code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
  218. code_puppy/plugins/shell_safety/__init__.py +6 -0
  219. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  220. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  221. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  222. code_puppy/plugins/synthetic_status/__init__.py +1 -0
  223. code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
  224. code_puppy/plugins/synthetic_status/status_api.py +147 -0
  225. code_puppy/plugins/universal_constructor/__init__.py +13 -0
  226. code_puppy/plugins/universal_constructor/models.py +138 -0
  227. code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  228. code_puppy/plugins/universal_constructor/registry.py +302 -0
  229. code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  230. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  231. code_puppy/pydantic_patches.py +356 -0
  232. code_puppy/reopenable_async_client.py +232 -0
  233. code_puppy/round_robin_model.py +150 -0
  234. code_puppy/scheduler/__init__.py +41 -0
  235. code_puppy/scheduler/__main__.py +9 -0
  236. code_puppy/scheduler/cli.py +118 -0
  237. code_puppy/scheduler/config.py +126 -0
  238. code_puppy/scheduler/daemon.py +280 -0
  239. code_puppy/scheduler/executor.py +155 -0
  240. code_puppy/scheduler/platform.py +19 -0
  241. code_puppy/scheduler/platform_unix.py +22 -0
  242. code_puppy/scheduler/platform_win.py +32 -0
  243. code_puppy/session_storage.py +338 -0
  244. code_puppy/status_display.py +257 -0
  245. code_puppy/summarization_agent.py +176 -0
  246. code_puppy/terminal_utils.py +418 -0
  247. code_puppy/tools/__init__.py +501 -0
  248. code_puppy/tools/agent_tools.py +603 -0
  249. code_puppy/tools/ask_user_question/__init__.py +26 -0
  250. code_puppy/tools/ask_user_question/constants.py +73 -0
  251. code_puppy/tools/ask_user_question/demo_tui.py +55 -0
  252. code_puppy/tools/ask_user_question/handler.py +232 -0
  253. code_puppy/tools/ask_user_question/models.py +304 -0
  254. code_puppy/tools/ask_user_question/registration.py +26 -0
  255. code_puppy/tools/ask_user_question/renderers.py +309 -0
  256. code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
  257. code_puppy/tools/ask_user_question/theme.py +155 -0
  258. code_puppy/tools/ask_user_question/tui_loop.py +423 -0
  259. code_puppy/tools/browser/__init__.py +37 -0
  260. code_puppy/tools/browser/browser_control.py +289 -0
  261. code_puppy/tools/browser/browser_interactions.py +545 -0
  262. code_puppy/tools/browser/browser_locators.py +640 -0
  263. code_puppy/tools/browser/browser_manager.py +378 -0
  264. code_puppy/tools/browser/browser_navigation.py +251 -0
  265. code_puppy/tools/browser/browser_screenshot.py +179 -0
  266. code_puppy/tools/browser/browser_scripts.py +462 -0
  267. code_puppy/tools/browser/browser_workflows.py +221 -0
  268. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  269. code_puppy/tools/browser/terminal_command_tools.py +534 -0
  270. code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
  271. code_puppy/tools/browser/terminal_tools.py +525 -0
  272. code_puppy/tools/command_runner.py +1346 -0
  273. code_puppy/tools/common.py +1409 -0
  274. code_puppy/tools/display.py +84 -0
  275. code_puppy/tools/file_modifications.py +886 -0
  276. code_puppy/tools/file_operations.py +802 -0
  277. code_puppy/tools/scheduler_tools.py +412 -0
  278. code_puppy/tools/skills_tools.py +244 -0
  279. code_puppy/tools/subagent_context.py +158 -0
  280. code_puppy/tools/tools_content.py +51 -0
  281. code_puppy/tools/universal_constructor.py +889 -0
  282. code_puppy/uvx_detection.py +242 -0
  283. code_puppy/version_checker.py +82 -0
  284. codepp-0.0.437.dist-info/METADATA +766 -0
  285. codepp-0.0.437.dist-info/RECORD +288 -0
  286. codepp-0.0.437.dist-info/WHEEL +4 -0
  287. codepp-0.0.437.dist-info/entry_points.txt +3 -0
  288. 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)"