code-puppy 0.0.169__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 +8 -8
- code_puppy/agents/agent_c_reviewer.py +155 -0
- code_puppy/agents/agent_code_puppy.py +9 -2
- code_puppy/agents/agent_code_reviewer.py +90 -0
- code_puppy/agents/agent_cpp_reviewer.py +132 -0
- code_puppy/agents/agent_creator_agent.py +48 -9
- code_puppy/agents/agent_golang_reviewer.py +151 -0
- code_puppy/agents/agent_javascript_reviewer.py +160 -0
- code_puppy/agents/agent_manager.py +146 -199
- 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 +90 -0
- code_puppy/agents/agent_qa_expert.py +163 -0
- code_puppy/agents/agent_qa_kitten.py +208 -0
- code_puppy/agents/agent_security_auditor.py +181 -0
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/agent_typescript_reviewer.py +166 -0
- code_puppy/agents/base_agent.py +1713 -1
- code_puppy/agents/event_stream_handler.py +350 -0
- code_puppy/agents/json_agent.py +12 -1
- 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 +174 -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 +395 -0
- 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 +233 -627
- 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 +1 -4
- 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 +16 -27
- code_puppy/command_line/mcp/install_menu.py +685 -0
- code_puppy/command_line/mcp/list_command.py +3 -3
- 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 +17 -11
- code_puppy/command_line/mcp/start_all_command.py +22 -13
- code_puppy/command_line/mcp/start_command.py +50 -31
- code_puppy/command_line/mcp/status_command.py +6 -7
- code_puppy/command_line/mcp/stop_all_command.py +11 -8
- code_puppy/command_line/mcp/stop_command.py +11 -10
- code_puppy/command_line/mcp/test_command.py +2 -2
- code_puppy/command_line/mcp/utils.py +1 -1
- code_puppy/command_line/mcp/wizard_utils.py +22 -18
- code_puppy/command_line/mcp_completion.py +174 -0
- code_puppy/command_line/model_picker_completion.py +89 -30
- 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 +626 -75
- code_puppy/command_line/session_commands.py +296 -0
- code_puppy/command_line/utils.py +54 -0
- code_puppy/config.py +1181 -51
- 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 +220 -104
- code_puppy/keymap.py +128 -0
- code_puppy/main.py +5 -594
- code_puppy/{mcp → mcp_}/__init__.py +17 -0
- code_puppy/{mcp → mcp_}/async_lifecycle.py +35 -4
- code_puppy/{mcp → mcp_}/blocking_startup.py +70 -43
- code_puppy/{mcp → mcp_}/captured_stdio_server.py +2 -2
- code_puppy/{mcp → mcp_}/config_wizard.py +5 -5
- code_puppy/{mcp → mcp_}/dashboard.py +15 -6
- code_puppy/{mcp → mcp_}/examples/retry_example.py +4 -1
- code_puppy/{mcp → mcp_}/managed_server.py +66 -39
- code_puppy/{mcp → mcp_}/manager.py +146 -52
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/{mcp → mcp_}/registry.py +6 -6
- code_puppy/{mcp → mcp_}/server_registry_catalog.py +25 -8
- 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 +33 -5
- code_puppy/messaging/spinner/console_spinner.py +92 -52
- code_puppy/messaging/spinner/spinner_base.py +29 -0
- code_puppy/messaging/subagent_console.py +461 -0
- code_puppy/model_factory.py +686 -80
- code_puppy/model_utils.py +167 -0
- code_puppy/models.json +86 -104
- 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 +51 -0
- 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 +10 -15
- code_puppy/session_storage.py +294 -0
- code_puppy/status_display.py +21 -4
- code_puppy/summarization_agent.py +52 -14
- code_puppy/terminal_utils.py +418 -0
- code_puppy/tools/__init__.py +139 -6
- code_puppy/tools/agent_tools.py +548 -49
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +289 -0
- code_puppy/tools/browser/browser_interactions.py +545 -0
- code_puppy/tools/browser/browser_locators.py +640 -0
- code_puppy/tools/browser/browser_manager.py +316 -0
- code_puppy/tools/browser/browser_navigation.py +251 -0
- code_puppy/tools/browser/browser_screenshot.py +179 -0
- code_puppy/tools/browser/browser_scripts.py +462 -0
- code_puppy/tools/browser/browser_workflows.py +221 -0
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +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 +941 -153
- code_puppy/tools/common.py +1146 -6
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/file_modifications.py +288 -89
- code_puppy/tools/file_operations.py +352 -266
- 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.169.dist-info → code_puppy-0.0.366.dist-info}/METADATA +184 -67
- code_puppy-0.0.366.dist-info/RECORD +217 -0
- {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
- {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +1 -0
- code_puppy/agent.py +0 -231
- code_puppy/agents/agent_orchestrator.json +0 -26
- code_puppy/agents/runtime_manager.py +0 -272
- code_puppy/command_line/mcp/add_command.py +0 -183
- code_puppy/command_line/meta_command_handler.py +0 -153
- code_puppy/message_history_processor.py +0 -490
- code_puppy/messaging/spinner/textual_spinner.py +0 -101
- code_puppy/state_management.py +0 -200
- code_puppy/tui/__init__.py +0 -10
- code_puppy/tui/app.py +0 -986
- code_puppy/tui/components/__init__.py +0 -21
- code_puppy/tui/components/chat_view.py +0 -550
- 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 -182
- 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 -15
- 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 -290
- code_puppy/tui/screens/tools.py +0 -74
- code_puppy-0.0.169.data/data/code_puppy/models.json +0 -128
- code_puppy-0.0.169.dist-info/RECORD +0 -112
- /code_puppy/{mcp → mcp_}/circuit_breaker.py +0 -0
- /code_puppy/{mcp → mcp_}/error_isolation.py +0 -0
- /code_puppy/{mcp → mcp_}/health_monitor.py +0 -0
- /code_puppy/{mcp → mcp_}/retry_manager.py +0 -0
- /code_puppy/{mcp → mcp_}/status_tracker.py +0 -0
- /code_puppy/{mcp → mcp_}/system_tools.py +0 -0
- {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Modal component for displaying command history entries.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from textual import on
|
|
6
|
-
from textual.app import ComposeResult
|
|
7
|
-
from textual.containers import Container, Horizontal
|
|
8
|
-
from textual.events import Key
|
|
9
|
-
from textual.screen import ModalScreen
|
|
10
|
-
from textual.widgets import Button, Label, Static
|
|
11
|
-
|
|
12
|
-
from ..messages import CommandSelected
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class CommandHistoryModal(ModalScreen):
|
|
16
|
-
"""Modal for displaying a command history entry."""
|
|
17
|
-
|
|
18
|
-
def __init__(self, **kwargs):
|
|
19
|
-
"""Initialize the modal with command history data.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
**kwargs: Additional arguments to pass to the parent class
|
|
23
|
-
"""
|
|
24
|
-
super().__init__(**kwargs)
|
|
25
|
-
|
|
26
|
-
# Get the current command from the sidebar
|
|
27
|
-
try:
|
|
28
|
-
# We'll get everything from the sidebar on demand
|
|
29
|
-
self.sidebar = None
|
|
30
|
-
self.command = ""
|
|
31
|
-
self.timestamp = ""
|
|
32
|
-
except Exception:
|
|
33
|
-
self.command = ""
|
|
34
|
-
self.timestamp = ""
|
|
35
|
-
|
|
36
|
-
# UI components to update
|
|
37
|
-
self.command_display = None
|
|
38
|
-
self.timestamp_display = None
|
|
39
|
-
|
|
40
|
-
def on_mount(self) -> None:
|
|
41
|
-
"""Setup when the modal is mounted."""
|
|
42
|
-
# Get the sidebar and current command entry
|
|
43
|
-
try:
|
|
44
|
-
self.sidebar = self.app.query_one("Sidebar")
|
|
45
|
-
current_entry = self.sidebar.get_current_command_entry()
|
|
46
|
-
self.command = current_entry["command"]
|
|
47
|
-
self.timestamp = current_entry["timestamp"]
|
|
48
|
-
self.update_display()
|
|
49
|
-
except Exception as e:
|
|
50
|
-
import logging
|
|
51
|
-
|
|
52
|
-
logging.debug(f"Error initializing modal: {str(e)}")
|
|
53
|
-
|
|
54
|
-
DEFAULT_CSS = """
|
|
55
|
-
CommandHistoryModal {
|
|
56
|
-
align: center middle;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
#modal-container {
|
|
60
|
-
width: 80%;
|
|
61
|
-
max-width: 100;
|
|
62
|
-
/* Set a definite height that's large enough but fits on screen */
|
|
63
|
-
height: 22; /* Increased height to make room for navigation hint */
|
|
64
|
-
min-height: 18;
|
|
65
|
-
background: $surface;
|
|
66
|
-
border: solid $primary;
|
|
67
|
-
/* Increase vertical padding to add more space between elements */
|
|
68
|
-
padding: 1 2;
|
|
69
|
-
/* Use vertical layout to ensure proper element sizing */
|
|
70
|
-
layout: vertical;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
#timestamp-display {
|
|
74
|
-
width: 100%;
|
|
75
|
-
margin-bottom: 1;
|
|
76
|
-
color: $text-muted;
|
|
77
|
-
text-align: right;
|
|
78
|
-
/* Fix the height */
|
|
79
|
-
height: 1;
|
|
80
|
-
margin-top: 0;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
#command-display {
|
|
84
|
-
width: 100%;
|
|
85
|
-
/* Allow this container to grow/shrink as needed but keep buttons visible */
|
|
86
|
-
min-height: 3;
|
|
87
|
-
height: 1fr;
|
|
88
|
-
max-height: 12;
|
|
89
|
-
padding: 0 1;
|
|
90
|
-
margin-bottom: 1;
|
|
91
|
-
margin-top: 1;
|
|
92
|
-
background: $surface-darken-1;
|
|
93
|
-
border: solid $primary-darken-2;
|
|
94
|
-
overflow: auto;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
#nav-hint {
|
|
98
|
-
width: 100%;
|
|
99
|
-
color: $text;
|
|
100
|
-
text-align: center;
|
|
101
|
-
margin: 1 0;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.button-container {
|
|
105
|
-
width: 100%;
|
|
106
|
-
/* Fix the height to ensure buttons are always visible */
|
|
107
|
-
height: 3;
|
|
108
|
-
align-horizontal: right;
|
|
109
|
-
margin-top: 1;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
Button {
|
|
113
|
-
margin-right: 1;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
#use-button {
|
|
117
|
-
background: $success;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
#cancel-button {
|
|
121
|
-
background: $primary-darken-1;
|
|
122
|
-
}
|
|
123
|
-
"""
|
|
124
|
-
|
|
125
|
-
def compose(self) -> ComposeResult:
|
|
126
|
-
"""Create the modal layout."""
|
|
127
|
-
with Container(id="modal-container"):
|
|
128
|
-
# Header with timestamp
|
|
129
|
-
self.timestamp_display = Label(
|
|
130
|
-
f"Timestamp: {self.timestamp}", id="timestamp-display"
|
|
131
|
-
)
|
|
132
|
-
yield self.timestamp_display
|
|
133
|
-
|
|
134
|
-
# Scrollable content area that can expand/contract as needed
|
|
135
|
-
# The content will scroll if it's too long, ensuring buttons remain visible
|
|
136
|
-
with Container(id="command-display"):
|
|
137
|
-
self.command_display = Static(self.command)
|
|
138
|
-
yield self.command_display
|
|
139
|
-
|
|
140
|
-
# Super simple navigation hint
|
|
141
|
-
yield Label("Press Up/Down arrows to navigate history", id="nav-hint")
|
|
142
|
-
|
|
143
|
-
# Fixed button container at the bottom
|
|
144
|
-
with Horizontal(classes="button-container"):
|
|
145
|
-
yield Button("Cancel", id="cancel-button", variant="default")
|
|
146
|
-
yield Button("Use Command", id="use-button", variant="primary")
|
|
147
|
-
|
|
148
|
-
def on_key(self, event: Key) -> None:
|
|
149
|
-
"""Handle key events for navigation."""
|
|
150
|
-
# Handle arrow keys for navigation
|
|
151
|
-
if event.key == "down":
|
|
152
|
-
self.navigate_to_next_command()
|
|
153
|
-
event.prevent_default()
|
|
154
|
-
elif event.key == "up":
|
|
155
|
-
self.navigate_to_previous_command()
|
|
156
|
-
event.prevent_default()
|
|
157
|
-
elif event.key == "escape":
|
|
158
|
-
self.app.pop_screen()
|
|
159
|
-
event.prevent_default()
|
|
160
|
-
|
|
161
|
-
def navigate_to_next_command(self) -> None:
|
|
162
|
-
"""Navigate to the next command in history."""
|
|
163
|
-
try:
|
|
164
|
-
# Get the sidebar
|
|
165
|
-
if not self.sidebar:
|
|
166
|
-
self.sidebar = self.app.query_one("Sidebar")
|
|
167
|
-
|
|
168
|
-
# Use sidebar's method to navigate
|
|
169
|
-
if self.sidebar.navigate_to_next_command():
|
|
170
|
-
# Get updated command entry
|
|
171
|
-
current_entry = self.sidebar.get_current_command_entry()
|
|
172
|
-
self.command = current_entry["command"]
|
|
173
|
-
self.timestamp = current_entry["timestamp"]
|
|
174
|
-
self.update_display()
|
|
175
|
-
except Exception as e:
|
|
176
|
-
# Log the error but don't crash
|
|
177
|
-
import logging
|
|
178
|
-
|
|
179
|
-
logging.debug(f"Error navigating to next command: {str(e)}")
|
|
180
|
-
|
|
181
|
-
def navigate_to_previous_command(self) -> None:
|
|
182
|
-
"""Navigate to the previous command in history."""
|
|
183
|
-
try:
|
|
184
|
-
# Get the sidebar
|
|
185
|
-
if not self.sidebar:
|
|
186
|
-
self.sidebar = self.app.query_one("Sidebar")
|
|
187
|
-
|
|
188
|
-
# Use sidebar's method to navigate
|
|
189
|
-
if self.sidebar.navigate_to_previous_command():
|
|
190
|
-
# Get updated command entry
|
|
191
|
-
current_entry = self.sidebar.get_current_command_entry()
|
|
192
|
-
self.command = current_entry["command"]
|
|
193
|
-
self.timestamp = current_entry["timestamp"]
|
|
194
|
-
self.update_display()
|
|
195
|
-
except Exception as e:
|
|
196
|
-
# Log the error but don't crash
|
|
197
|
-
import logging
|
|
198
|
-
|
|
199
|
-
logging.debug(f"Error navigating to previous command: {str(e)}")
|
|
200
|
-
|
|
201
|
-
def update_display(self) -> None:
|
|
202
|
-
"""Update the display with the current command and timestamp."""
|
|
203
|
-
if self.command_display:
|
|
204
|
-
self.command_display.update(self.command)
|
|
205
|
-
if self.timestamp_display:
|
|
206
|
-
self.timestamp_display.update(f"Timestamp: {self.timestamp}")
|
|
207
|
-
|
|
208
|
-
@on(Button.Pressed, "#use-button")
|
|
209
|
-
def use_command(self) -> None:
|
|
210
|
-
"""Handle use button press."""
|
|
211
|
-
# Post a message to the app with the selected command
|
|
212
|
-
self.post_message(CommandSelected(self.command))
|
|
213
|
-
self.app.pop_screen()
|
|
214
|
-
|
|
215
|
-
@on(Button.Pressed, "#cancel-button")
|
|
216
|
-
def cancel(self) -> None:
|
|
217
|
-
"""Handle cancel button press."""
|
|
218
|
-
self.app.pop_screen()
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Copy button component for copying agent responses to clipboard.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import subprocess
|
|
6
|
-
import sys
|
|
7
|
-
from typing import Optional
|
|
8
|
-
|
|
9
|
-
from textual.binding import Binding
|
|
10
|
-
from textual.events import Click
|
|
11
|
-
from textual.message import Message
|
|
12
|
-
from textual.widgets import Button
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class CopyButton(Button):
|
|
16
|
-
"""A button that copies associated text to the clipboard."""
|
|
17
|
-
|
|
18
|
-
DEFAULT_CSS = """
|
|
19
|
-
CopyButton {
|
|
20
|
-
width: auto;
|
|
21
|
-
height: 3;
|
|
22
|
-
min-width: 8;
|
|
23
|
-
margin: 0 1 1 1;
|
|
24
|
-
padding: 0 1;
|
|
25
|
-
background: $primary;
|
|
26
|
-
color: $text;
|
|
27
|
-
border: none;
|
|
28
|
-
text-align: center;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
CopyButton:hover {
|
|
32
|
-
background: $accent;
|
|
33
|
-
color: $text;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
CopyButton:focus {
|
|
37
|
-
background: $accent;
|
|
38
|
-
color: $text;
|
|
39
|
-
text-style: bold;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
CopyButton.-pressed {
|
|
43
|
-
background: $success;
|
|
44
|
-
color: $text;
|
|
45
|
-
}
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
BINDINGS = [
|
|
49
|
-
Binding("enter", "press", "Copy", show=False),
|
|
50
|
-
Binding("space", "press", "Copy", show=False),
|
|
51
|
-
]
|
|
52
|
-
|
|
53
|
-
def __init__(self, text_to_copy: str, **kwargs):
|
|
54
|
-
super().__init__("📋 Copy", **kwargs)
|
|
55
|
-
self.text_to_copy = text_to_copy
|
|
56
|
-
self._original_label = "📋 Copy"
|
|
57
|
-
self._copied_label = "✅ Copied!"
|
|
58
|
-
|
|
59
|
-
class CopyCompleted(Message):
|
|
60
|
-
"""Message sent when text is successfully copied."""
|
|
61
|
-
|
|
62
|
-
def __init__(self, success: bool, error: Optional[str] = None):
|
|
63
|
-
super().__init__()
|
|
64
|
-
self.success = success
|
|
65
|
-
self.error = error
|
|
66
|
-
|
|
67
|
-
def copy_to_clipboard(self, text: str) -> tuple[bool, Optional[str]]:
|
|
68
|
-
"""
|
|
69
|
-
Copy text to clipboard using platform-appropriate method.
|
|
70
|
-
|
|
71
|
-
Returns:
|
|
72
|
-
tuple: (success: bool, error_message: Optional[str])
|
|
73
|
-
"""
|
|
74
|
-
try:
|
|
75
|
-
if sys.platform == "darwin": # macOS
|
|
76
|
-
subprocess.run(
|
|
77
|
-
["pbcopy"], input=text, text=True, check=True, capture_output=True
|
|
78
|
-
)
|
|
79
|
-
elif sys.platform == "win32": # Windows
|
|
80
|
-
subprocess.run(
|
|
81
|
-
["clip"], input=text, text=True, check=True, capture_output=True
|
|
82
|
-
)
|
|
83
|
-
else: # Linux and other Unix-like systems
|
|
84
|
-
# Try xclip first, then xsel as fallback
|
|
85
|
-
try:
|
|
86
|
-
subprocess.run(
|
|
87
|
-
["xclip", "-selection", "clipboard"],
|
|
88
|
-
input=text,
|
|
89
|
-
text=True,
|
|
90
|
-
check=True,
|
|
91
|
-
capture_output=True,
|
|
92
|
-
)
|
|
93
|
-
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
94
|
-
# Fallback to xsel
|
|
95
|
-
subprocess.run(
|
|
96
|
-
["xsel", "--clipboard", "--input"],
|
|
97
|
-
input=text,
|
|
98
|
-
text=True,
|
|
99
|
-
check=True,
|
|
100
|
-
capture_output=True,
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
return True, None
|
|
104
|
-
|
|
105
|
-
except subprocess.CalledProcessError as e:
|
|
106
|
-
return False, f"Clipboard command failed: {e}"
|
|
107
|
-
except FileNotFoundError:
|
|
108
|
-
if sys.platform not in ["darwin", "win32"]:
|
|
109
|
-
return (
|
|
110
|
-
False,
|
|
111
|
-
"Clipboard utilities not found. Please install xclip or xsel.",
|
|
112
|
-
)
|
|
113
|
-
else:
|
|
114
|
-
return False, "System clipboard command not found."
|
|
115
|
-
except Exception as e:
|
|
116
|
-
return False, f"Unexpected error: {e}"
|
|
117
|
-
|
|
118
|
-
def on_click(self, event: Click) -> None:
|
|
119
|
-
"""Handle button click to copy text."""
|
|
120
|
-
self.action_press()
|
|
121
|
-
|
|
122
|
-
def action_press(self) -> None:
|
|
123
|
-
"""Copy the text to clipboard and provide visual feedback."""
|
|
124
|
-
success, error = self.copy_to_clipboard(self.text_to_copy)
|
|
125
|
-
|
|
126
|
-
if success:
|
|
127
|
-
# Visual feedback - change button text temporarily
|
|
128
|
-
self.label = self._copied_label
|
|
129
|
-
self.add_class("-pressed")
|
|
130
|
-
|
|
131
|
-
# Reset button appearance after a short delay
|
|
132
|
-
# self.set_timer(1.5, self._reset_button_appearance)
|
|
133
|
-
|
|
134
|
-
# Send message about copy operation
|
|
135
|
-
self.post_message(self.CopyCompleted(success, error))
|
|
136
|
-
|
|
137
|
-
def update_text_to_copy(self, new_text: str) -> None:
|
|
138
|
-
"""Update the text that will be copied when button is pressed."""
|
|
139
|
-
self.text_to_copy = new_text
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Custom widget components for the TUI.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from textual.binding import Binding
|
|
6
|
-
from textual.events import Key
|
|
7
|
-
from textual.message import Message
|
|
8
|
-
from textual.widgets import TextArea
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class CustomTextArea(TextArea):
|
|
12
|
-
"""Custom TextArea that sends a message with Enter and allows new lines with Shift+Enter."""
|
|
13
|
-
|
|
14
|
-
# Define key bindings
|
|
15
|
-
BINDINGS = [
|
|
16
|
-
Binding("alt+enter", "insert_newline", ""),
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
def __init__(self, *args, **kwargs):
|
|
20
|
-
super().__init__(*args, **kwargs)
|
|
21
|
-
|
|
22
|
-
def on_key(self, event):
|
|
23
|
-
"""Handle key events before they reach the internal _on_key handler."""
|
|
24
|
-
# Let the binding system handle alt+enter
|
|
25
|
-
if event.key == "alt+enter":
|
|
26
|
-
# Don't prevent default - let the binding system handle it
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
# Handle escape+enter manually
|
|
30
|
-
if event.key == "escape+enter":
|
|
31
|
-
self.action_insert_newline()
|
|
32
|
-
event.prevent_default()
|
|
33
|
-
event.stop()
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
def _on_key(self, event: Key) -> None:
|
|
37
|
-
"""Override internal key handler to intercept Enter keys."""
|
|
38
|
-
# Handle Enter key specifically
|
|
39
|
-
if event.key == "enter":
|
|
40
|
-
# Check if this key is part of an escape sequence (Alt+Enter)
|
|
41
|
-
if hasattr(event, "is_cursor_sequence") or (
|
|
42
|
-
hasattr(event, "meta") and event.meta
|
|
43
|
-
):
|
|
44
|
-
# If it's part of an escape sequence, let the parent handle it
|
|
45
|
-
# so that bindings can process it
|
|
46
|
-
super()._on_key(event)
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
# This handles plain Enter only, not escape+enter
|
|
50
|
-
self.post_message(self.MessageSent())
|
|
51
|
-
return # Don't call super() to prevent default newline behavior
|
|
52
|
-
|
|
53
|
-
# Let TextArea handle other keys
|
|
54
|
-
super()._on_key(event)
|
|
55
|
-
|
|
56
|
-
def action_insert_newline(self) -> None:
|
|
57
|
-
"""Action to insert a new line - called by shift+enter and escape+enter bindings."""
|
|
58
|
-
self.insert("\n")
|
|
59
|
-
|
|
60
|
-
class MessageSent(Message):
|
|
61
|
-
"""Message sent when Enter key is pressed (without Shift)."""
|
|
62
|
-
|
|
63
|
-
pass
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Modal component for human input requests.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from textual import on
|
|
6
|
-
from textual.app import ComposeResult
|
|
7
|
-
from textual.containers import Container, Horizontal
|
|
8
|
-
from textual.events import Key
|
|
9
|
-
from textual.screen import ModalScreen
|
|
10
|
-
from textual.widgets import Button, Static, TextArea
|
|
11
|
-
|
|
12
|
-
try:
|
|
13
|
-
from .custom_widgets import CustomTextArea
|
|
14
|
-
except ImportError:
|
|
15
|
-
# Fallback to regular TextArea if CustomTextArea isn't available
|
|
16
|
-
CustomTextArea = TextArea
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class HumanInputModal(ModalScreen):
|
|
20
|
-
"""Modal for requesting human input."""
|
|
21
|
-
|
|
22
|
-
def __init__(self, prompt_text: str, prompt_id: str, **kwargs):
|
|
23
|
-
"""Initialize the modal with prompt information.
|
|
24
|
-
|
|
25
|
-
Args:
|
|
26
|
-
prompt_text: The prompt to display to the user
|
|
27
|
-
prompt_id: Unique identifier for this prompt request
|
|
28
|
-
**kwargs: Additional arguments to pass to the parent class
|
|
29
|
-
"""
|
|
30
|
-
super().__init__(**kwargs)
|
|
31
|
-
self.prompt_text = prompt_text
|
|
32
|
-
self.prompt_id = prompt_id
|
|
33
|
-
self.response = ""
|
|
34
|
-
print(f"[DEBUG] Created HumanInputModal for prompt_id: {prompt_id}")
|
|
35
|
-
|
|
36
|
-
DEFAULT_CSS = """
|
|
37
|
-
HumanInputModal {
|
|
38
|
-
align: center middle;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
#modal-container {
|
|
42
|
-
width: 80%;
|
|
43
|
-
max-width: 80;
|
|
44
|
-
height: 16;
|
|
45
|
-
min-height: 12;
|
|
46
|
-
background: $surface;
|
|
47
|
-
border: solid $primary;
|
|
48
|
-
padding: 1 2;
|
|
49
|
-
layout: vertical;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
#prompt-display {
|
|
53
|
-
width: 100%;
|
|
54
|
-
margin-bottom: 1;
|
|
55
|
-
color: $text;
|
|
56
|
-
text-align: left;
|
|
57
|
-
height: auto;
|
|
58
|
-
max-height: 6;
|
|
59
|
-
overflow: auto;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
#input-container {
|
|
63
|
-
width: 100%;
|
|
64
|
-
height: 4;
|
|
65
|
-
margin-bottom: 1;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
#response-input {
|
|
69
|
-
width: 100%;
|
|
70
|
-
height: 4;
|
|
71
|
-
border: solid $primary;
|
|
72
|
-
background: $surface-darken-1;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
#button-container {
|
|
76
|
-
width: 100%;
|
|
77
|
-
height: 3;
|
|
78
|
-
align: center bottom;
|
|
79
|
-
layout: horizontal;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
#submit-button, #cancel-button {
|
|
83
|
-
width: auto;
|
|
84
|
-
height: 3;
|
|
85
|
-
margin: 0 1;
|
|
86
|
-
min-width: 10;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
#hint-text {
|
|
90
|
-
width: 100%;
|
|
91
|
-
color: $text-muted;
|
|
92
|
-
text-align: center;
|
|
93
|
-
height: 1;
|
|
94
|
-
margin-top: 1;
|
|
95
|
-
}
|
|
96
|
-
"""
|
|
97
|
-
|
|
98
|
-
def compose(self) -> ComposeResult:
|
|
99
|
-
"""Create the modal layout."""
|
|
100
|
-
with Container(id="modal-container"):
|
|
101
|
-
yield Static(self.prompt_text, id="prompt-display")
|
|
102
|
-
with Container(id="input-container"):
|
|
103
|
-
yield CustomTextArea("", id="response-input")
|
|
104
|
-
with Horizontal(id="button-container"):
|
|
105
|
-
yield Button("Submit", id="submit-button", variant="primary")
|
|
106
|
-
yield Button("Cancel", id="cancel-button", variant="default")
|
|
107
|
-
yield Static("Enter to submit • Escape to cancel", id="hint-text")
|
|
108
|
-
|
|
109
|
-
def on_mount(self) -> None:
|
|
110
|
-
"""Focus the input field when modal opens."""
|
|
111
|
-
try:
|
|
112
|
-
print("[DEBUG] Modal on_mount called")
|
|
113
|
-
input_field = self.query_one("#response-input", CustomTextArea)
|
|
114
|
-
input_field.focus()
|
|
115
|
-
print("[DEBUG] Modal input field focused")
|
|
116
|
-
except Exception as e:
|
|
117
|
-
print(f"[DEBUG] Modal on_mount exception: {e}")
|
|
118
|
-
import traceback
|
|
119
|
-
|
|
120
|
-
traceback.print_exc()
|
|
121
|
-
|
|
122
|
-
@on(Button.Pressed, "#submit-button")
|
|
123
|
-
def on_submit_clicked(self) -> None:
|
|
124
|
-
"""Handle submit button click."""
|
|
125
|
-
self._submit_response()
|
|
126
|
-
|
|
127
|
-
@on(Button.Pressed, "#cancel-button")
|
|
128
|
-
def on_cancel_clicked(self) -> None:
|
|
129
|
-
"""Handle cancel button click."""
|
|
130
|
-
self._cancel_response()
|
|
131
|
-
|
|
132
|
-
def on_key(self, event: Key) -> None:
|
|
133
|
-
"""Handle key events."""
|
|
134
|
-
if event.key == "escape":
|
|
135
|
-
self._cancel_response()
|
|
136
|
-
event.prevent_default()
|
|
137
|
-
elif event.key == "enter":
|
|
138
|
-
# Check if we're in the text area and it's not multi-line
|
|
139
|
-
try:
|
|
140
|
-
input_field = self.query_one("#response-input", CustomTextArea)
|
|
141
|
-
if input_field.has_focus and "\n" not in input_field.text:
|
|
142
|
-
self._submit_response()
|
|
143
|
-
event.prevent_default()
|
|
144
|
-
except Exception:
|
|
145
|
-
pass
|
|
146
|
-
|
|
147
|
-
def _submit_response(self) -> None:
|
|
148
|
-
"""Submit the user's response."""
|
|
149
|
-
try:
|
|
150
|
-
input_field = self.query_one("#response-input", CustomTextArea)
|
|
151
|
-
self.response = input_field.text.strip()
|
|
152
|
-
print(f"[DEBUG] Modal submitting response: {self.response[:20]}...")
|
|
153
|
-
|
|
154
|
-
# Provide the response back to the message queue
|
|
155
|
-
from code_puppy.messaging import provide_prompt_response
|
|
156
|
-
|
|
157
|
-
provide_prompt_response(self.prompt_id, self.response)
|
|
158
|
-
|
|
159
|
-
# Close the modal using the same method as other modals
|
|
160
|
-
self.app.pop_screen()
|
|
161
|
-
except Exception as e:
|
|
162
|
-
print(f"[DEBUG] Modal error during submit: {e}")
|
|
163
|
-
# If something goes wrong, provide empty response
|
|
164
|
-
from code_puppy.messaging import provide_prompt_response
|
|
165
|
-
|
|
166
|
-
provide_prompt_response(self.prompt_id, "")
|
|
167
|
-
self.app.pop_screen()
|
|
168
|
-
|
|
169
|
-
def _cancel_response(self) -> None:
|
|
170
|
-
"""Cancel the input request."""
|
|
171
|
-
print("[DEBUG] Modal cancelling response")
|
|
172
|
-
from code_puppy.messaging import provide_prompt_response
|
|
173
|
-
|
|
174
|
-
provide_prompt_response(self.prompt_id, "")
|
|
175
|
-
self.app.pop_screen()
|