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.
- code_puppy/__init__.py +7 -1
- code_puppy/agents/__init__.py +2 -0
- code_puppy/agents/agent_c_reviewer.py +59 -6
- code_puppy/agents/agent_code_puppy.py +7 -1
- code_puppy/agents/agent_code_reviewer.py +12 -2
- code_puppy/agents/agent_cpp_reviewer.py +73 -6
- code_puppy/agents/agent_creator_agent.py +45 -4
- code_puppy/agents/agent_golang_reviewer.py +92 -3
- code_puppy/agents/agent_javascript_reviewer.py +101 -8
- code_puppy/agents/agent_manager.py +81 -4
- code_puppy/agents/agent_pack_leader.py +383 -0
- code_puppy/agents/agent_planning.py +163 -0
- code_puppy/agents/agent_python_programmer.py +165 -0
- code_puppy/agents/agent_python_reviewer.py +28 -6
- code_puppy/agents/agent_qa_expert.py +98 -6
- code_puppy/agents/agent_qa_kitten.py +12 -7
- code_puppy/agents/agent_security_auditor.py +113 -3
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/agent_typescript_reviewer.py +106 -7
- code_puppy/agents/base_agent.py +802 -176
- code_puppy/agents/event_stream_handler.py +350 -0
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +304 -0
- code_puppy/agents/pack/husky.py +321 -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 +446 -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 +74 -0
- code_puppy/api/routers/sessions.py +232 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +142 -4
- code_puppy/chatgpt_codex_client.py +283 -0
- code_puppy/claude_cache_client.py +586 -0
- code_puppy/cli_runner.py +916 -0
- code_puppy/command_line/add_model_menu.py +1079 -0
- code_puppy/command_line/agent_menu.py +395 -0
- code_puppy/command_line/attachments.py +10 -5
- code_puppy/command_line/autosave_menu.py +605 -0
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +520 -0
- code_puppy/command_line/command_handler.py +176 -738
- code_puppy/command_line/command_registry.py +150 -0
- code_puppy/command_line/config_commands.py +715 -0
- code_puppy/command_line/core_commands.py +792 -0
- code_puppy/command_line/diff_menu.py +863 -0
- code_puppy/command_line/load_context_completion.py +15 -22
- code_puppy/command_line/mcp/base.py +0 -3
- 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 +9 -4
- code_puppy/command_line/mcp/help_command.py +6 -5
- code_puppy/command_line/mcp/install_command.py +15 -26
- code_puppy/command_line/mcp/install_menu.py +685 -0
- code_puppy/command_line/mcp/list_command.py +2 -2
- code_puppy/command_line/mcp/logs_command.py +174 -65
- code_puppy/command_line/mcp/remove_command.py +2 -2
- code_puppy/command_line/mcp/restart_command.py +12 -4
- code_puppy/command_line/mcp/search_command.py +16 -10
- code_puppy/command_line/mcp/start_all_command.py +18 -6
- code_puppy/command_line/mcp/start_command.py +47 -25
- code_puppy/command_line/mcp/status_command.py +4 -5
- code_puppy/command_line/mcp/stop_all_command.py +7 -1
- code_puppy/command_line/mcp/stop_command.py +8 -4
- code_puppy/command_line/mcp/test_command.py +2 -2
- code_puppy/command_line/mcp/wizard_utils.py +20 -16
- code_puppy/command_line/mcp_completion.py +174 -0
- code_puppy/command_line/model_picker_completion.py +75 -25
- code_puppy/command_line/model_settings_menu.py +884 -0
- code_puppy/command_line/motd.py +14 -8
- code_puppy/command_line/onboarding_slides.py +179 -0
- code_puppy/command_line/onboarding_wizard.py +340 -0
- code_puppy/command_line/pin_command_completion.py +329 -0
- code_puppy/command_line/prompt_toolkit_completion.py +463 -63
- code_puppy/command_line/session_commands.py +296 -0
- code_puppy/command_line/utils.py +54 -0
- code_puppy/config.py +898 -112
- code_puppy/error_logging.py +118 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/gemini_model.py +602 -0
- code_puppy/http_utils.py +210 -148
- code_puppy/keymap.py +128 -0
- code_puppy/main.py +5 -698
- code_puppy/mcp_/__init__.py +17 -0
- code_puppy/mcp_/async_lifecycle.py +35 -4
- code_puppy/mcp_/blocking_startup.py +70 -43
- code_puppy/mcp_/captured_stdio_server.py +2 -2
- code_puppy/mcp_/config_wizard.py +4 -4
- code_puppy/mcp_/dashboard.py +15 -6
- code_puppy/mcp_/managed_server.py +65 -38
- code_puppy/mcp_/manager.py +146 -52
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/mcp_/registry.py +6 -6
- code_puppy/mcp_/server_registry_catalog.py +24 -5
- code_puppy/messaging/__init__.py +199 -2
- code_puppy/messaging/bus.py +610 -0
- code_puppy/messaging/commands.py +167 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/message_queue.py +17 -48
- code_puppy/messaging/messages.py +500 -0
- code_puppy/messaging/queue_console.py +1 -24
- code_puppy/messaging/renderers.py +43 -146
- code_puppy/messaging/rich_renderer.py +1027 -0
- code_puppy/messaging/spinner/__init__.py +21 -5
- code_puppy/messaging/spinner/console_spinner.py +86 -51
- code_puppy/messaging/subagent_console.py +461 -0
- code_puppy/model_factory.py +634 -83
- code_puppy/model_utils.py +167 -0
- code_puppy/models.json +66 -68
- code_puppy/models_dev_api.json +1 -0
- code_puppy/models_dev_parser.py +592 -0
- code_puppy/plugins/__init__.py +164 -10
- 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 +704 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +136 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
- code_puppy/plugins/antigravity_oauth/storage.py +271 -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 +767 -0
- code_puppy/plugins/antigravity_oauth/utils.py +169 -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 +328 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
- code_puppy/plugins/chatgpt_oauth/utils.py +489 -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 +6 -0
- code_puppy/plugins/claude_code_oauth/config.py +50 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
- code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_puppy/plugins/claude_code_oauth/utils.py +518 -0
- code_puppy/plugins/customizable_commands/__init__.py +0 -0
- code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
- code_puppy/plugins/example_custom_command/README.md +280 -0
- code_puppy/plugins/example_custom_command/register_callbacks.py +2 -2
- code_puppy/plugins/file_permission_handler/__init__.py +4 -0
- code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -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/oauth_puppy_html.py +228 -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/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/prompts/codex_system_prompt.md +310 -0
- code_puppy/pydantic_patches.py +131 -0
- code_puppy/reopenable_async_client.py +8 -8
- code_puppy/round_robin_model.py +9 -12
- code_puppy/session_storage.py +2 -1
- code_puppy/status_display.py +21 -4
- code_puppy/summarization_agent.py +41 -13
- code_puppy/terminal_utils.py +418 -0
- code_puppy/tools/__init__.py +37 -1
- code_puppy/tools/agent_tools.py +536 -52
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +19 -23
- code_puppy/tools/browser/browser_interactions.py +41 -48
- code_puppy/tools/browser/browser_locators.py +36 -38
- code_puppy/tools/browser/browser_manager.py +316 -0
- code_puppy/tools/browser/browser_navigation.py +16 -16
- code_puppy/tools/browser/browser_screenshot.py +79 -143
- code_puppy/tools/browser/browser_scripts.py +32 -42
- code_puppy/tools/browser/browser_workflows.py +44 -27
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +521 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +930 -147
- code_puppy/tools/common.py +1113 -5
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/file_modifications.py +288 -89
- code_puppy/tools/file_operations.py +226 -154
- code_puppy/tools/subagent_context.py +158 -0
- code_puppy/uvx_detection.py +242 -0
- code_puppy/version_checker.py +30 -11
- code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
- code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/METADATA +149 -75
- code_puppy-0.0.366.dist-info/RECORD +217 -0
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
- code_puppy/command_line/mcp/add_command.py +0 -183
- code_puppy/messaging/spinner/textual_spinner.py +0 -106
- code_puppy/tools/browser/camoufox_manager.py +0 -216
- code_puppy/tools/browser/vqa_agent.py +0 -70
- code_puppy/tui/__init__.py +0 -10
- code_puppy/tui/app.py +0 -1105
- code_puppy/tui/components/__init__.py +0 -21
- code_puppy/tui/components/chat_view.py +0 -551
- code_puppy/tui/components/command_history_modal.py +0 -218
- code_puppy/tui/components/copy_button.py +0 -139
- code_puppy/tui/components/custom_widgets.py +0 -63
- code_puppy/tui/components/human_input_modal.py +0 -175
- code_puppy/tui/components/input_area.py +0 -167
- code_puppy/tui/components/sidebar.py +0 -309
- code_puppy/tui/components/status_bar.py +0 -185
- code_puppy/tui/messages.py +0 -27
- code_puppy/tui/models/__init__.py +0 -8
- code_puppy/tui/models/chat_message.py +0 -25
- code_puppy/tui/models/command_history.py +0 -89
- code_puppy/tui/models/enums.py +0 -24
- code_puppy/tui/screens/__init__.py +0 -17
- code_puppy/tui/screens/autosave_picker.py +0 -175
- code_puppy/tui/screens/help.py +0 -130
- code_puppy/tui/screens/mcp_install_wizard.py +0 -803
- code_puppy/tui/screens/settings.py +0 -306
- code_puppy/tui/screens/tools.py +0 -74
- code_puppy/tui_state.py +0 -55
- code_puppy-0.0.214.data/data/code_puppy/models.json +0 -112
- code_puppy-0.0.214.dist-info/RECORD +0 -131
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +0 -0
- {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
|
|
8
|
-
|
|
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
|
-
#
|
|
63
|
-
instructions =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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)
|
code_puppy/tools/__init__.py
CHANGED
|
@@ -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
|
|
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
|
|