code-puppy 0.0.214__py3-none-any.whl → 0.0.366__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. code_puppy/__init__.py +7 -1
  2. code_puppy/agents/__init__.py +2 -0
  3. code_puppy/agents/agent_c_reviewer.py +59 -6
  4. code_puppy/agents/agent_code_puppy.py +7 -1
  5. code_puppy/agents/agent_code_reviewer.py +12 -2
  6. code_puppy/agents/agent_cpp_reviewer.py +73 -6
  7. code_puppy/agents/agent_creator_agent.py +45 -4
  8. code_puppy/agents/agent_golang_reviewer.py +92 -3
  9. code_puppy/agents/agent_javascript_reviewer.py +101 -8
  10. code_puppy/agents/agent_manager.py +81 -4
  11. code_puppy/agents/agent_pack_leader.py +383 -0
  12. code_puppy/agents/agent_planning.py +163 -0
  13. code_puppy/agents/agent_python_programmer.py +165 -0
  14. code_puppy/agents/agent_python_reviewer.py +28 -6
  15. code_puppy/agents/agent_qa_expert.py +98 -6
  16. code_puppy/agents/agent_qa_kitten.py +12 -7
  17. code_puppy/agents/agent_security_auditor.py +113 -3
  18. code_puppy/agents/agent_terminal_qa.py +323 -0
  19. code_puppy/agents/agent_typescript_reviewer.py +106 -7
  20. code_puppy/agents/base_agent.py +802 -176
  21. code_puppy/agents/event_stream_handler.py +350 -0
  22. code_puppy/agents/pack/__init__.py +34 -0
  23. code_puppy/agents/pack/bloodhound.py +304 -0
  24. code_puppy/agents/pack/husky.py +321 -0
  25. code_puppy/agents/pack/retriever.py +393 -0
  26. code_puppy/agents/pack/shepherd.py +348 -0
  27. code_puppy/agents/pack/terrier.py +287 -0
  28. code_puppy/agents/pack/watchdog.py +367 -0
  29. code_puppy/agents/prompt_reviewer.py +145 -0
  30. code_puppy/agents/subagent_stream_handler.py +276 -0
  31. code_puppy/api/__init__.py +13 -0
  32. code_puppy/api/app.py +169 -0
  33. code_puppy/api/main.py +21 -0
  34. code_puppy/api/pty_manager.py +446 -0
  35. code_puppy/api/routers/__init__.py +12 -0
  36. code_puppy/api/routers/agents.py +36 -0
  37. code_puppy/api/routers/commands.py +217 -0
  38. code_puppy/api/routers/config.py +74 -0
  39. code_puppy/api/routers/sessions.py +232 -0
  40. code_puppy/api/templates/terminal.html +361 -0
  41. code_puppy/api/websocket.py +154 -0
  42. code_puppy/callbacks.py +142 -4
  43. code_puppy/chatgpt_codex_client.py +283 -0
  44. code_puppy/claude_cache_client.py +586 -0
  45. code_puppy/cli_runner.py +916 -0
  46. code_puppy/command_line/add_model_menu.py +1079 -0
  47. code_puppy/command_line/agent_menu.py +395 -0
  48. code_puppy/command_line/attachments.py +10 -5
  49. code_puppy/command_line/autosave_menu.py +605 -0
  50. code_puppy/command_line/clipboard.py +527 -0
  51. code_puppy/command_line/colors_menu.py +520 -0
  52. code_puppy/command_line/command_handler.py +176 -738
  53. code_puppy/command_line/command_registry.py +150 -0
  54. code_puppy/command_line/config_commands.py +715 -0
  55. code_puppy/command_line/core_commands.py +792 -0
  56. code_puppy/command_line/diff_menu.py +863 -0
  57. code_puppy/command_line/load_context_completion.py +15 -22
  58. code_puppy/command_line/mcp/base.py +0 -3
  59. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  60. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  61. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  62. code_puppy/command_line/mcp/edit_command.py +148 -0
  63. code_puppy/command_line/mcp/handler.py +9 -4
  64. code_puppy/command_line/mcp/help_command.py +6 -5
  65. code_puppy/command_line/mcp/install_command.py +15 -26
  66. code_puppy/command_line/mcp/install_menu.py +685 -0
  67. code_puppy/command_line/mcp/list_command.py +2 -2
  68. code_puppy/command_line/mcp/logs_command.py +174 -65
  69. code_puppy/command_line/mcp/remove_command.py +2 -2
  70. code_puppy/command_line/mcp/restart_command.py +12 -4
  71. code_puppy/command_line/mcp/search_command.py +16 -10
  72. code_puppy/command_line/mcp/start_all_command.py +18 -6
  73. code_puppy/command_line/mcp/start_command.py +47 -25
  74. code_puppy/command_line/mcp/status_command.py +4 -5
  75. code_puppy/command_line/mcp/stop_all_command.py +7 -1
  76. code_puppy/command_line/mcp/stop_command.py +8 -4
  77. code_puppy/command_line/mcp/test_command.py +2 -2
  78. code_puppy/command_line/mcp/wizard_utils.py +20 -16
  79. code_puppy/command_line/mcp_completion.py +174 -0
  80. code_puppy/command_line/model_picker_completion.py +75 -25
  81. code_puppy/command_line/model_settings_menu.py +884 -0
  82. code_puppy/command_line/motd.py +14 -8
  83. code_puppy/command_line/onboarding_slides.py +179 -0
  84. code_puppy/command_line/onboarding_wizard.py +340 -0
  85. code_puppy/command_line/pin_command_completion.py +329 -0
  86. code_puppy/command_line/prompt_toolkit_completion.py +463 -63
  87. code_puppy/command_line/session_commands.py +296 -0
  88. code_puppy/command_line/utils.py +54 -0
  89. code_puppy/config.py +898 -112
  90. code_puppy/error_logging.py +118 -0
  91. code_puppy/gemini_code_assist.py +385 -0
  92. code_puppy/gemini_model.py +602 -0
  93. code_puppy/http_utils.py +210 -148
  94. code_puppy/keymap.py +128 -0
  95. code_puppy/main.py +5 -698
  96. code_puppy/mcp_/__init__.py +17 -0
  97. code_puppy/mcp_/async_lifecycle.py +35 -4
  98. code_puppy/mcp_/blocking_startup.py +70 -43
  99. code_puppy/mcp_/captured_stdio_server.py +2 -2
  100. code_puppy/mcp_/config_wizard.py +4 -4
  101. code_puppy/mcp_/dashboard.py +15 -6
  102. code_puppy/mcp_/managed_server.py +65 -38
  103. code_puppy/mcp_/manager.py +146 -52
  104. code_puppy/mcp_/mcp_logs.py +224 -0
  105. code_puppy/mcp_/registry.py +6 -6
  106. code_puppy/mcp_/server_registry_catalog.py +24 -5
  107. code_puppy/messaging/__init__.py +199 -2
  108. code_puppy/messaging/bus.py +610 -0
  109. code_puppy/messaging/commands.py +167 -0
  110. code_puppy/messaging/markdown_patches.py +57 -0
  111. code_puppy/messaging/message_queue.py +17 -48
  112. code_puppy/messaging/messages.py +500 -0
  113. code_puppy/messaging/queue_console.py +1 -24
  114. code_puppy/messaging/renderers.py +43 -146
  115. code_puppy/messaging/rich_renderer.py +1027 -0
  116. code_puppy/messaging/spinner/__init__.py +21 -5
  117. code_puppy/messaging/spinner/console_spinner.py +86 -51
  118. code_puppy/messaging/subagent_console.py +461 -0
  119. code_puppy/model_factory.py +634 -83
  120. code_puppy/model_utils.py +167 -0
  121. code_puppy/models.json +66 -68
  122. code_puppy/models_dev_api.json +1 -0
  123. code_puppy/models_dev_parser.py +592 -0
  124. code_puppy/plugins/__init__.py +164 -10
  125. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  126. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  127. code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
  128. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  129. code_puppy/plugins/antigravity_oauth/constants.py +136 -0
  130. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  131. code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
  132. code_puppy/plugins/antigravity_oauth/storage.py +271 -0
  133. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  134. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  135. code_puppy/plugins/antigravity_oauth/transport.py +767 -0
  136. code_puppy/plugins/antigravity_oauth/utils.py +169 -0
  137. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  138. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  139. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
  140. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
  141. code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
  142. code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
  143. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  144. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  145. code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
  146. code_puppy/plugins/claude_code_oauth/config.py +50 -0
  147. code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
  148. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  149. code_puppy/plugins/claude_code_oauth/utils.py +518 -0
  150. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  151. code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
  152. code_puppy/plugins/example_custom_command/README.md +280 -0
  153. code_puppy/plugins/example_custom_command/register_callbacks.py +2 -2
  154. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  155. code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
  156. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  157. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  158. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  159. code_puppy/plugins/oauth_puppy_html.py +228 -0
  160. code_puppy/plugins/shell_safety/__init__.py +6 -0
  161. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  162. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  163. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  164. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  165. code_puppy/prompts/codex_system_prompt.md +310 -0
  166. code_puppy/pydantic_patches.py +131 -0
  167. code_puppy/reopenable_async_client.py +8 -8
  168. code_puppy/round_robin_model.py +9 -12
  169. code_puppy/session_storage.py +2 -1
  170. code_puppy/status_display.py +21 -4
  171. code_puppy/summarization_agent.py +41 -13
  172. code_puppy/terminal_utils.py +418 -0
  173. code_puppy/tools/__init__.py +37 -1
  174. code_puppy/tools/agent_tools.py +536 -52
  175. code_puppy/tools/browser/__init__.py +37 -0
  176. code_puppy/tools/browser/browser_control.py +19 -23
  177. code_puppy/tools/browser/browser_interactions.py +41 -48
  178. code_puppy/tools/browser/browser_locators.py +36 -38
  179. code_puppy/tools/browser/browser_manager.py +316 -0
  180. code_puppy/tools/browser/browser_navigation.py +16 -16
  181. code_puppy/tools/browser/browser_screenshot.py +79 -143
  182. code_puppy/tools/browser/browser_scripts.py +32 -42
  183. code_puppy/tools/browser/browser_workflows.py +44 -27
  184. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  185. code_puppy/tools/browser/terminal_command_tools.py +521 -0
  186. code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
  187. code_puppy/tools/browser/terminal_tools.py +525 -0
  188. code_puppy/tools/command_runner.py +930 -147
  189. code_puppy/tools/common.py +1113 -5
  190. code_puppy/tools/display.py +84 -0
  191. code_puppy/tools/file_modifications.py +288 -89
  192. code_puppy/tools/file_operations.py +226 -154
  193. code_puppy/tools/subagent_context.py +158 -0
  194. code_puppy/uvx_detection.py +242 -0
  195. code_puppy/version_checker.py +30 -11
  196. code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
  197. code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
  198. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/METADATA +149 -75
  199. code_puppy-0.0.366.dist-info/RECORD +217 -0
  200. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
  201. code_puppy/command_line/mcp/add_command.py +0 -183
  202. code_puppy/messaging/spinner/textual_spinner.py +0 -106
  203. code_puppy/tools/browser/camoufox_manager.py +0 -216
  204. code_puppy/tools/browser/vqa_agent.py +0 -70
  205. code_puppy/tui/__init__.py +0 -10
  206. code_puppy/tui/app.py +0 -1105
  207. code_puppy/tui/components/__init__.py +0 -21
  208. code_puppy/tui/components/chat_view.py +0 -551
  209. code_puppy/tui/components/command_history_modal.py +0 -218
  210. code_puppy/tui/components/copy_button.py +0 -139
  211. code_puppy/tui/components/custom_widgets.py +0 -63
  212. code_puppy/tui/components/human_input_modal.py +0 -175
  213. code_puppy/tui/components/input_area.py +0 -167
  214. code_puppy/tui/components/sidebar.py +0 -309
  215. code_puppy/tui/components/status_bar.py +0 -185
  216. code_puppy/tui/messages.py +0 -27
  217. code_puppy/tui/models/__init__.py +0 -8
  218. code_puppy/tui/models/chat_message.py +0 -25
  219. code_puppy/tui/models/command_history.py +0 -89
  220. code_puppy/tui/models/enums.py +0 -24
  221. code_puppy/tui/screens/__init__.py +0 -17
  222. code_puppy/tui/screens/autosave_picker.py +0 -175
  223. code_puppy/tui/screens/help.py +0 -130
  224. code_puppy/tui/screens/mcp_install_wizard.py +0 -803
  225. code_puppy/tui/screens/settings.py +0 -306
  226. code_puppy/tui/screens/tools.py +0 -74
  227. code_puppy/tui_state.py +0 -55
  228. code_puppy-0.0.214.data/data/code_puppy/models.json +0 -112
  229. code_puppy-0.0.214.dist-info/RECORD +0 -131
  230. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +0 -0
  231. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
@@ -4,8 +4,11 @@ from typing import List
4
4
 
5
5
  from pydantic_ai import Agent
6
6
 
7
- from code_puppy.config import get_use_dbos, get_global_model_name
8
- from code_puppy.model_factory import ModelFactory
7
+ from code_puppy.config import (
8
+ get_global_model_name,
9
+ get_use_dbos,
10
+ )
11
+ from code_puppy.model_factory import ModelFactory, make_model_settings
9
12
 
10
13
  # Keep a module-level agent reference to avoid rebuilding per call
11
14
  _summarization_agent = None
@@ -33,6 +36,16 @@ async def _run_agent_async(agent: Agent, prompt: str, message_history: List):
33
36
 
34
37
  def run_summarization_sync(prompt: str, message_history: List) -> List:
35
38
  agent = get_summarization_agent()
39
+
40
+ # Handle claude-code models: prepend system prompt to user prompt
41
+ from code_puppy.model_utils import prepare_prompt_for_model
42
+
43
+ model_name = get_global_model_name()
44
+ prepared = prepare_prompt_for_model(
45
+ model_name, _get_summarization_instructions(), prompt
46
+ )
47
+ prompt = prepared.user_prompt
48
+
36
49
  try:
37
50
  # Try to detect if we're already in an event loop
38
51
  asyncio.get_running_loop()
@@ -53,30 +66,45 @@ def run_summarization_sync(prompt: str, message_history: List) -> List:
53
66
  return result.new_messages()
54
67
 
55
68
 
69
+ def _get_summarization_instructions() -> str:
70
+ """Get the system instructions for the summarization agent."""
71
+ return """You are a message summarization expert. Your task is to summarize conversation messages
72
+ while preserving important context and information. The summaries should be concise but capture the essential content
73
+ and intent of the original messages. This is to help manage token usage in a conversation history
74
+ while maintaining context for the AI to continue the conversation effectively.
75
+
76
+ When summarizing:
77
+ 1. Keep summary concise but informative
78
+ 2. Preserve important context and key information and decisions
79
+ 3. Keep any important technical details
80
+ 4. Don't summarize the system message
81
+ 5. Make sure all tool calls and responses are summarized, as they are vital
82
+ 6. Focus on token usage efficiency and system message preservation"""
83
+
84
+
56
85
  def reload_summarization_agent():
57
86
  """Create a specialized agent for summarizing messages when context limit is reached."""
87
+ from code_puppy.model_utils import prepare_prompt_for_model
88
+
58
89
  models_config = ModelFactory.load_config()
59
90
  model_name = get_global_model_name()
60
91
  model = ModelFactory.get_model(model_name, models_config)
61
92
 
62
- # Specialized instructions for summarization
63
- instructions = """You are a message summarization expert. Your task is to summarize conversation messages
64
- while preserving important context and information. The summaries should be concise but capture the essential
65
- content and intent of the original messages. This is to help manage token usage in a conversation history
66
- while maintaining context for the AI to continue the conversation effectively.
93
+ # Handle claude-code models: swap instructions (prompt prepending happens in run_summarization_sync)
94
+ instructions = _get_summarization_instructions()
95
+ prepared = prepare_prompt_for_model(
96
+ model_name, instructions, "", prepend_system_to_user=False
97
+ )
98
+ instructions = prepared.instructions
67
99
 
68
- When summarizing:
69
- 1. Keep summary brief but informative
70
- 2. Preserve key information and decisions
71
- 3. Keep any important technical details
72
- 4. Don't summarize the system message
73
- 5. Make sure all tool calls and responses are summarized, as they are vital"""
100
+ model_settings = make_model_settings(model_name)
74
101
 
75
102
  agent = Agent(
76
103
  model=model,
77
104
  instructions=instructions,
78
105
  output_type=str,
79
106
  retries=1, # Fewer retries for summarization
107
+ model_settings=model_settings,
80
108
  )
81
109
  if get_use_dbos():
82
110
  from pydantic_ai.durable_exec.dbos import DBOSAgent
@@ -0,0 +1,418 @@
1
+ """Terminal utilities for cross-platform terminal state management.
2
+
3
+ Handles Windows console mode resets and Unix terminal sanity restoration.
4
+ """
5
+
6
+ import os
7
+ import platform
8
+ import subprocess
9
+ import sys
10
+ from typing import TYPE_CHECKING, Callable, Optional
11
+
12
+ if TYPE_CHECKING:
13
+ from rich.console import Console
14
+
15
+ # Store the original console ctrl handler so we can restore it if needed
16
+ _original_ctrl_handler: Optional[Callable] = None
17
+
18
+
19
+ def reset_windows_terminal_ansi() -> None:
20
+ """Reset ANSI formatting on Windows stdout/stderr.
21
+
22
+ This is a lightweight reset that just clears ANSI escape sequences.
23
+ Use this for quick resets after output operations.
24
+ """
25
+ if platform.system() != "Windows":
26
+ return
27
+
28
+ try:
29
+ sys.stdout.write("\x1b[0m") # Reset ANSI formatting
30
+ sys.stdout.flush()
31
+ sys.stderr.write("\x1b[0m")
32
+ sys.stderr.flush()
33
+ except Exception:
34
+ pass # Silently ignore errors - best effort reset
35
+
36
+
37
+ def reset_windows_console_mode() -> None:
38
+ """Full Windows console mode reset using ctypes.
39
+
40
+ This resets both stdout and stdin console modes to restore proper
41
+ terminal behavior after interrupts (Ctrl+C, Ctrl+D). Without this,
42
+ the terminal can become unresponsive (can't type characters).
43
+ """
44
+ if platform.system() != "Windows":
45
+ return
46
+
47
+ try:
48
+ import ctypes
49
+
50
+ kernel32 = ctypes.windll.kernel32
51
+
52
+ # Reset stdout
53
+ STD_OUTPUT_HANDLE = -11
54
+ handle = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
55
+
56
+ # Enable virtual terminal processing and line input
57
+ mode = ctypes.c_ulong()
58
+ kernel32.GetConsoleMode(handle, ctypes.byref(mode))
59
+
60
+ # Console mode flags for stdout
61
+ ENABLE_PROCESSED_OUTPUT = 0x0001
62
+ ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
63
+ ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
64
+
65
+ new_mode = (
66
+ mode.value
67
+ | ENABLE_PROCESSED_OUTPUT
68
+ | ENABLE_WRAP_AT_EOL_OUTPUT
69
+ | ENABLE_VIRTUAL_TERMINAL_PROCESSING
70
+ )
71
+ kernel32.SetConsoleMode(handle, new_mode)
72
+
73
+ # Reset stdin
74
+ STD_INPUT_HANDLE = -10
75
+ stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
76
+
77
+ # Console mode flags for stdin
78
+ ENABLE_LINE_INPUT = 0x0002
79
+ ENABLE_ECHO_INPUT = 0x0004
80
+ ENABLE_PROCESSED_INPUT = 0x0001
81
+
82
+ stdin_mode = ctypes.c_ulong()
83
+ kernel32.GetConsoleMode(stdin_handle, ctypes.byref(stdin_mode))
84
+
85
+ new_stdin_mode = (
86
+ stdin_mode.value
87
+ | ENABLE_LINE_INPUT
88
+ | ENABLE_ECHO_INPUT
89
+ | ENABLE_PROCESSED_INPUT
90
+ )
91
+ kernel32.SetConsoleMode(stdin_handle, new_stdin_mode)
92
+
93
+ except Exception:
94
+ pass # Silently ignore errors - best effort reset
95
+
96
+
97
+ def flush_windows_keyboard_buffer() -> None:
98
+ """Flush the Windows keyboard buffer.
99
+
100
+ Clears any pending keyboard input that could interfere with
101
+ subsequent input operations after an interrupt.
102
+ """
103
+ if platform.system() != "Windows":
104
+ return
105
+
106
+ try:
107
+ import msvcrt
108
+
109
+ while msvcrt.kbhit():
110
+ msvcrt.getch()
111
+ except Exception:
112
+ pass # Silently ignore errors - best effort flush
113
+
114
+
115
+ def reset_windows_terminal_full() -> None:
116
+ """Perform a full Windows terminal reset (ANSI + console mode + keyboard buffer).
117
+
118
+ Combines ANSI reset, console mode reset, and keyboard buffer flush
119
+ for complete terminal state restoration after interrupts.
120
+ """
121
+ if platform.system() != "Windows":
122
+ return
123
+
124
+ reset_windows_terminal_ansi()
125
+ reset_windows_console_mode()
126
+ flush_windows_keyboard_buffer()
127
+
128
+
129
+ def reset_unix_terminal() -> None:
130
+ """Reset Unix/Linux/macOS terminal to sane state.
131
+
132
+ Uses the `reset` command to restore terminal sanity.
133
+ Silently fails if the command isn't available.
134
+ """
135
+ if platform.system() == "Windows":
136
+ return
137
+
138
+ try:
139
+ subprocess.run(["reset"], check=True, capture_output=True)
140
+ except (subprocess.CalledProcessError, FileNotFoundError):
141
+ pass # Silently fail if reset command isn't available
142
+
143
+
144
+ def reset_terminal() -> None:
145
+ """Cross-platform terminal reset.
146
+
147
+ Automatically detects the platform and performs the appropriate
148
+ terminal reset operation.
149
+ """
150
+ if platform.system() == "Windows":
151
+ reset_windows_terminal_full()
152
+ else:
153
+ reset_unix_terminal()
154
+
155
+
156
+ def disable_windows_ctrl_c() -> bool:
157
+ """Disable Ctrl+C processing at the Windows console input level.
158
+
159
+ This removes ENABLE_PROCESSED_INPUT from stdin, which prevents
160
+ Ctrl+C from being interpreted as a signal at all. Instead, it
161
+ becomes just a regular character (^C) that gets ignored.
162
+
163
+ This is more reliable than SetConsoleCtrlHandler because it
164
+ prevents Ctrl+C from being processed before it reaches any handler.
165
+
166
+ Returns:
167
+ True if successfully disabled, False otherwise.
168
+ """
169
+ global _original_ctrl_handler
170
+
171
+ if platform.system() != "Windows":
172
+ return False
173
+
174
+ try:
175
+ import ctypes
176
+
177
+ kernel32 = ctypes.windll.kernel32
178
+
179
+ # Get stdin handle
180
+ STD_INPUT_HANDLE = -10
181
+ stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
182
+
183
+ # Get current console mode
184
+ mode = ctypes.c_ulong()
185
+ if not kernel32.GetConsoleMode(stdin_handle, ctypes.byref(mode)):
186
+ return False
187
+
188
+ # Save original mode for potential restoration
189
+ _original_ctrl_handler = mode.value
190
+
191
+ # Console mode flags
192
+ ENABLE_PROCESSED_INPUT = 0x0001 # This makes Ctrl+C generate signals
193
+
194
+ # Remove ENABLE_PROCESSED_INPUT to disable Ctrl+C signal generation
195
+ new_mode = mode.value & ~ENABLE_PROCESSED_INPUT
196
+
197
+ if kernel32.SetConsoleMode(stdin_handle, new_mode):
198
+ return True
199
+ return False
200
+
201
+ except Exception:
202
+ return False
203
+
204
+
205
+ def enable_windows_ctrl_c() -> bool:
206
+ """Re-enable Ctrl+C at the Windows console level.
207
+
208
+ Restores the original console mode saved by disable_windows_ctrl_c().
209
+
210
+ Returns:
211
+ True if successfully re-enabled, False otherwise.
212
+ """
213
+ global _original_ctrl_handler
214
+
215
+ if platform.system() != "Windows":
216
+ return False
217
+
218
+ if _original_ctrl_handler is None:
219
+ return True # Nothing to restore
220
+
221
+ try:
222
+ import ctypes
223
+
224
+ kernel32 = ctypes.windll.kernel32
225
+
226
+ # Get stdin handle
227
+ STD_INPUT_HANDLE = -10
228
+ stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
229
+
230
+ # Restore original mode
231
+ if kernel32.SetConsoleMode(stdin_handle, _original_ctrl_handler):
232
+ _original_ctrl_handler = None
233
+ return True
234
+ return False
235
+
236
+ except Exception:
237
+ return False
238
+
239
+
240
+ # Flag to track if we should keep Ctrl+C disabled
241
+ _keep_ctrl_c_disabled: bool = False
242
+
243
+
244
+ def set_keep_ctrl_c_disabled(value: bool) -> None:
245
+ """Set whether Ctrl+C should be kept disabled.
246
+
247
+ When True, ensure_ctrl_c_disabled() will re-disable Ctrl+C
248
+ even if something else (like prompt_toolkit) re-enables it.
249
+ """
250
+ global _keep_ctrl_c_disabled
251
+ _keep_ctrl_c_disabled = value
252
+
253
+
254
+ def ensure_ctrl_c_disabled() -> bool:
255
+ """Ensure Ctrl+C is disabled if it should be.
256
+
257
+ Call this after operations that might restore console mode
258
+ (like prompt_toolkit input).
259
+
260
+ Returns:
261
+ True if Ctrl+C is now disabled (or wasn't needed), False on error.
262
+ """
263
+ if not _keep_ctrl_c_disabled:
264
+ return True
265
+
266
+ if platform.system() != "Windows":
267
+ return True
268
+
269
+ try:
270
+ import ctypes
271
+
272
+ kernel32 = ctypes.windll.kernel32
273
+
274
+ # Get stdin handle
275
+ STD_INPUT_HANDLE = -10
276
+ stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
277
+
278
+ # Get current console mode
279
+ mode = ctypes.c_ulong()
280
+ if not kernel32.GetConsoleMode(stdin_handle, ctypes.byref(mode)):
281
+ return False
282
+
283
+ # Console mode flags
284
+ ENABLE_PROCESSED_INPUT = 0x0001
285
+
286
+ # Check if Ctrl+C processing is enabled
287
+ if mode.value & ENABLE_PROCESSED_INPUT:
288
+ # Disable it
289
+ new_mode = mode.value & ~ENABLE_PROCESSED_INPUT
290
+ return bool(kernel32.SetConsoleMode(stdin_handle, new_mode))
291
+
292
+ return True # Already disabled
293
+
294
+ except Exception:
295
+ return False
296
+
297
+
298
+ def detect_truecolor_support() -> bool:
299
+ """Detect if the terminal supports truecolor (24-bit color).
300
+
301
+ Checks multiple indicators:
302
+ 1. COLORTERM environment variable (most reliable)
303
+ 2. TERM environment variable patterns
304
+ 3. Rich's Console color_system detection as fallback
305
+
306
+ Returns:
307
+ True if truecolor is supported, False otherwise.
308
+ """
309
+ # Check COLORTERM - this is the most reliable indicator
310
+ colorterm = os.environ.get("COLORTERM", "").lower()
311
+ if colorterm in ("truecolor", "24bit"):
312
+ return True
313
+
314
+ # Check TERM for known truecolor-capable terminals
315
+ term = os.environ.get("TERM", "").lower()
316
+ truecolor_terms = (
317
+ "xterm-direct",
318
+ "xterm-truecolor",
319
+ "iterm2",
320
+ "vte-256color", # Many modern terminals set this
321
+ )
322
+ if any(t in term for t in truecolor_terms):
323
+ return True
324
+
325
+ # Some terminals like iTerm2, Kitty, Alacritty set specific env vars
326
+ if os.environ.get("ITERM_SESSION_ID"):
327
+ return True
328
+ if os.environ.get("KITTY_WINDOW_ID"):
329
+ return True
330
+ if os.environ.get("ALACRITTY_SOCKET"):
331
+ return True
332
+ if os.environ.get("WT_SESSION"): # Windows Terminal
333
+ return True
334
+
335
+ # Use Rich's detection as a fallback
336
+ try:
337
+ from rich.console import Console
338
+
339
+ console = Console(force_terminal=True)
340
+ color_system = console.color_system
341
+ return color_system == "truecolor"
342
+ except Exception:
343
+ pass
344
+
345
+ return False
346
+
347
+
348
+ def print_truecolor_warning(console: Optional["Console"] = None) -> None:
349
+ """Print a big fat red warning if truecolor is not supported.
350
+
351
+ Args:
352
+ console: Optional Rich Console instance. If None, creates a new one.
353
+ """
354
+ if detect_truecolor_support():
355
+ return # All good, no warning needed
356
+
357
+ if console is None:
358
+ try:
359
+ from rich.console import Console
360
+
361
+ console = Console()
362
+ except ImportError:
363
+ # Rich not available, fall back to plain print
364
+ print("\n" + "=" * 70)
365
+ print("⚠️ WARNING: TERMINAL DOES NOT SUPPORT TRUECOLOR (24-BIT COLOR)")
366
+ print("=" * 70)
367
+ print("Code Puppy looks best with truecolor support.")
368
+ print("Consider using a modern terminal like:")
369
+ print(" • iTerm2 (macOS)")
370
+ print(" • Windows Terminal (Windows)")
371
+ print(" • Kitty, Alacritty, or any modern terminal emulator")
372
+ print("")
373
+ print("You can also try setting: export COLORTERM=truecolor")
374
+ print("")
375
+ print("Note: The built-in macOS Terminal.app does not support truecolor")
376
+ print("(Sequoia and earlier). You'll need a different terminal app.")
377
+ print("=" * 70 + "\n")
378
+ return
379
+
380
+ # Get detected color system for diagnostic info
381
+ color_system = console.color_system or "unknown"
382
+
383
+ # Build the warning box
384
+ warning_lines = [
385
+ "",
386
+ "[bold bright_red on red]" + "━" * 72 + "[/]",
387
+ "[bold bright_red on red]┃[/][bold bright_white on red]"
388
+ + " " * 70
389
+ + "[/][bold bright_red on red]┃[/]",
390
+ "[bold bright_red on red]┃[/][bold bright_white on red] ⚠️ WARNING: TERMINAL DOES NOT SUPPORT TRUECOLOR (24-BIT COLOR) ⚠️ [/][bold bright_red on red]┃[/]",
391
+ "[bold bright_red on red]┃[/][bold bright_white on red]"
392
+ + " " * 70
393
+ + "[/][bold bright_red on red]┃[/]",
394
+ "[bold bright_red on red]" + "━" * 72 + "[/]",
395
+ "",
396
+ f"[yellow]Detected color system:[/] [bold]{color_system}[/]",
397
+ "",
398
+ "[bold white]Code Puppy uses rich colors and will look degraded without truecolor.[/]",
399
+ "",
400
+ "[cyan]Consider using a modern terminal emulator:[/]",
401
+ " [green]•[/] [bold]iTerm2[/] (macOS) - https://iterm2.com",
402
+ " [green]•[/] [bold]Windows Terminal[/] (Windows) - Built into Windows 11",
403
+ " [green]•[/] [bold]Kitty[/] - https://sw.kovidgoyal.net/kitty",
404
+ " [green]•[/] [bold]Alacritty[/] - https://alacritty.org",
405
+ " [green]•[/] [bold]Warp[/] (macOS) - https://warp.dev",
406
+ "",
407
+ "[cyan]Or try setting the COLORTERM environment variable:[/]",
408
+ " [dim]export COLORTERM=truecolor[/]",
409
+ "",
410
+ "[dim italic]Note: The built-in macOS Terminal.app does not support truecolor (Sequoia and earlier).[/]",
411
+ "[dim italic]Setting COLORTERM=truecolor won't help - you'll need a different terminal app.[/]",
412
+ "",
413
+ "[bold bright_red]" + "─" * 72 + "[/]",
414
+ "",
415
+ ]
416
+
417
+ for line in warning_lines:
418
+ console.print(line)
@@ -55,10 +55,32 @@ from code_puppy.tools.browser.browser_workflows import (
55
55
  register_read_workflow,
56
56
  register_save_workflow,
57
57
  )
58
+ from code_puppy.tools.browser.terminal_command_tools import (
59
+ register_run_terminal_command,
60
+ register_send_terminal_keys,
61
+ register_wait_terminal_output,
62
+ )
63
+ from code_puppy.tools.browser.terminal_screenshot_tools import (
64
+ register_load_image,
65
+ register_terminal_compare_mockup,
66
+ register_terminal_read_output,
67
+ register_terminal_screenshot,
68
+ )
69
+
70
+ # Terminal automation tools
71
+ from code_puppy.tools.browser.terminal_tools import (
72
+ register_check_terminal_server,
73
+ register_close_terminal,
74
+ register_open_terminal,
75
+ register_start_api_server,
76
+ )
58
77
  from code_puppy.tools.command_runner import (
59
78
  register_agent_run_shell_command,
60
79
  register_agent_share_your_reasoning,
61
80
  )
81
+ from code_puppy.tools.display import (
82
+ display_non_streamed_result as display_non_streamed_result,
83
+ )
62
84
  from code_puppy.tools.file_modifications import register_delete_file, register_edit_file
63
85
  from code_puppy.tools.file_operations import (
64
86
  register_grep,
@@ -121,12 +143,26 @@ TOOL_REGISTRY = {
121
143
  "browser_wait_for_element": register_wait_for_element,
122
144
  "browser_highlight_element": register_browser_highlight_element,
123
145
  "browser_clear_highlights": register_browser_clear_highlights,
124
- # Browser Screenshots and VQA
146
+ # Browser Screenshots
125
147
  "browser_screenshot_analyze": register_take_screenshot_and_analyze,
126
148
  # Browser Workflows
127
149
  "browser_save_workflow": register_save_workflow,
128
150
  "browser_list_workflows": register_list_workflows,
129
151
  "browser_read_workflow": register_read_workflow,
152
+ # Terminal Connection Tools
153
+ "terminal_check_server": register_check_terminal_server,
154
+ "terminal_open": register_open_terminal,
155
+ "terminal_close": register_close_terminal,
156
+ "start_api_server": register_start_api_server,
157
+ # Terminal Command Execution Tools
158
+ "terminal_run_command": register_run_terminal_command,
159
+ "terminal_send_keys": register_send_terminal_keys,
160
+ "terminal_wait_output": register_wait_terminal_output,
161
+ # Terminal Screenshot Tools
162
+ "terminal_screenshot_analyze": register_terminal_screenshot,
163
+ "terminal_read_output": register_terminal_read_output,
164
+ "terminal_compare_mockup": register_terminal_compare_mockup,
165
+ "load_image_for_analysis": register_load_image,
130
166
  }
131
167
 
132
168