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,167 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Input area component for message input.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from textual.app import ComposeResult
|
|
6
|
-
from textual.containers import Container, Horizontal
|
|
7
|
-
from textual.message import Message
|
|
8
|
-
from textual.reactive import reactive
|
|
9
|
-
from textual.widgets import Button, Static
|
|
10
|
-
|
|
11
|
-
from code_puppy.messaging.spinner import TextualSpinner
|
|
12
|
-
|
|
13
|
-
from .custom_widgets import CustomTextArea
|
|
14
|
-
|
|
15
|
-
# Alias SimpleSpinnerWidget to TextualSpinner for backward compatibility
|
|
16
|
-
SimpleSpinnerWidget = TextualSpinner
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class SubmitCancelButton(Button):
|
|
20
|
-
"""A button that toggles between submit and cancel states."""
|
|
21
|
-
|
|
22
|
-
is_cancel_mode = reactive(False)
|
|
23
|
-
|
|
24
|
-
DEFAULT_CSS = """
|
|
25
|
-
SubmitCancelButton {
|
|
26
|
-
width: 3;
|
|
27
|
-
min-width: 3;
|
|
28
|
-
height: 3;
|
|
29
|
-
content-align: center middle;
|
|
30
|
-
border: none;
|
|
31
|
-
background: $surface;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
SubmitCancelButton:focus {
|
|
35
|
-
border: none;
|
|
36
|
-
color: $surface;
|
|
37
|
-
background: $surface;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
SubmitCancelButton:hover {
|
|
41
|
-
border: none;
|
|
42
|
-
background: $surface;
|
|
43
|
-
}
|
|
44
|
-
"""
|
|
45
|
-
|
|
46
|
-
def __init__(self, **kwargs):
|
|
47
|
-
super().__init__("▶️", **kwargs)
|
|
48
|
-
self.id = "submit-cancel-button"
|
|
49
|
-
|
|
50
|
-
def watch_is_cancel_mode(self, is_cancel: bool) -> None:
|
|
51
|
-
"""Update the button label when cancel mode changes."""
|
|
52
|
-
self.label = "⏹️" if is_cancel else "▶️"
|
|
53
|
-
|
|
54
|
-
def on_click(self) -> None:
|
|
55
|
-
"""Handle click event and bubble it up to parent."""
|
|
56
|
-
# When clicked, send a ButtonClicked message that will be handled by the parent
|
|
57
|
-
self.post_message(self.Clicked(self))
|
|
58
|
-
|
|
59
|
-
class Clicked(Message):
|
|
60
|
-
"""Button was clicked."""
|
|
61
|
-
|
|
62
|
-
def __init__(self, button: "SubmitCancelButton") -> None:
|
|
63
|
-
self.is_cancel_mode = button.is_cancel_mode
|
|
64
|
-
super().__init__()
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class InputArea(Container):
|
|
68
|
-
"""Input area with text input, spinner, help text, and send button."""
|
|
69
|
-
|
|
70
|
-
DEFAULT_CSS = """
|
|
71
|
-
InputArea {
|
|
72
|
-
dock: bottom;
|
|
73
|
-
height: 9;
|
|
74
|
-
margin: 1;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
#spinner {
|
|
78
|
-
height: 1;
|
|
79
|
-
width: 1fr;
|
|
80
|
-
margin: 0 3 0 1;
|
|
81
|
-
content-align: left middle;
|
|
82
|
-
text-align: left;
|
|
83
|
-
display: none;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
#spinner.visible {
|
|
87
|
-
display: block;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
#input-container {
|
|
91
|
-
height: 5;
|
|
92
|
-
width: 1fr;
|
|
93
|
-
margin: 1 3 0 1;
|
|
94
|
-
align: center middle;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
#input-field {
|
|
98
|
-
height: 5;
|
|
99
|
-
width: 1fr;
|
|
100
|
-
border: round $primary;
|
|
101
|
-
background: $surface;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
#submit-cancel-button {
|
|
105
|
-
height: 3;
|
|
106
|
-
width: 3;
|
|
107
|
-
min-width: 3;
|
|
108
|
-
margin: 1 0 1 1;
|
|
109
|
-
content-align: center middle;
|
|
110
|
-
border: none;
|
|
111
|
-
background: $surface;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
#input-help {
|
|
115
|
-
height: 1;
|
|
116
|
-
width: 1fr;
|
|
117
|
-
margin: 0 3 1 1;
|
|
118
|
-
color: $text-muted;
|
|
119
|
-
text-align: center;
|
|
120
|
-
}
|
|
121
|
-
"""
|
|
122
|
-
|
|
123
|
-
def on_mount(self) -> None:
|
|
124
|
-
"""Initialize the button state based on the app's agent_busy state."""
|
|
125
|
-
app = self.app
|
|
126
|
-
if hasattr(app, "agent_busy"):
|
|
127
|
-
button = self.query_one(SubmitCancelButton)
|
|
128
|
-
button.is_cancel_mode = app.agent_busy
|
|
129
|
-
|
|
130
|
-
def compose(self) -> ComposeResult:
|
|
131
|
-
yield SimpleSpinnerWidget(id="spinner")
|
|
132
|
-
with Horizontal(id="input-container"):
|
|
133
|
-
yield CustomTextArea(id="input-field", show_line_numbers=False)
|
|
134
|
-
yield SubmitCancelButton()
|
|
135
|
-
yield Static(
|
|
136
|
-
"Enter to send • Alt+Enter for new line • Ctrl+1 for help",
|
|
137
|
-
id="input-help",
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
def on_submit_cancel_button_clicked(
|
|
141
|
-
self, event: SubmitCancelButton.Clicked
|
|
142
|
-
) -> None:
|
|
143
|
-
"""Handle button clicks based on current mode."""
|
|
144
|
-
if event.is_cancel_mode:
|
|
145
|
-
# Cancel mode - stop the current process
|
|
146
|
-
self.post_message(self.CancelRequested())
|
|
147
|
-
else:
|
|
148
|
-
# Submit mode - send the message
|
|
149
|
-
self.post_message(self.SubmitRequested())
|
|
150
|
-
|
|
151
|
-
# Return focus to the input field
|
|
152
|
-
self.app.call_after_refresh(self.focus_input_field)
|
|
153
|
-
|
|
154
|
-
def focus_input_field(self) -> None:
|
|
155
|
-
"""Focus the input field after button click."""
|
|
156
|
-
input_field = self.query_one("#input-field")
|
|
157
|
-
input_field.focus()
|
|
158
|
-
|
|
159
|
-
class SubmitRequested(Message):
|
|
160
|
-
"""Request to submit the current input."""
|
|
161
|
-
|
|
162
|
-
pass
|
|
163
|
-
|
|
164
|
-
class CancelRequested(Message):
|
|
165
|
-
"""Request to cancel the current process."""
|
|
166
|
-
|
|
167
|
-
pass
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Sidebar component with history tab.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import time
|
|
6
|
-
|
|
7
|
-
from textual import on
|
|
8
|
-
from textual.app import ComposeResult
|
|
9
|
-
from textual.containers import Container
|
|
10
|
-
from textual.events import Key
|
|
11
|
-
from textual.widgets import Label, ListItem, ListView, TabbedContent, TabPane
|
|
12
|
-
|
|
13
|
-
from ..components.command_history_modal import CommandHistoryModal
|
|
14
|
-
|
|
15
|
-
# Import the shared message class and history reader
|
|
16
|
-
from ..models.command_history import HistoryFileReader
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class Sidebar(Container):
|
|
20
|
-
"""Sidebar with session history."""
|
|
21
|
-
|
|
22
|
-
def __init__(self, **kwargs):
|
|
23
|
-
super().__init__(**kwargs)
|
|
24
|
-
# Double-click detection variables
|
|
25
|
-
self._last_click_time = 0
|
|
26
|
-
self._last_clicked_item = None
|
|
27
|
-
self._double_click_threshold = 0.5 # 500ms for double-click
|
|
28
|
-
|
|
29
|
-
# Initialize history reader
|
|
30
|
-
self.history_reader = HistoryFileReader()
|
|
31
|
-
|
|
32
|
-
# Current index for history navigation - centralized reference
|
|
33
|
-
self.current_history_index = 0
|
|
34
|
-
self.history_entries = []
|
|
35
|
-
|
|
36
|
-
DEFAULT_CSS = """
|
|
37
|
-
Sidebar {
|
|
38
|
-
dock: left;
|
|
39
|
-
width: 30;
|
|
40
|
-
min-width: 20;
|
|
41
|
-
max-width: 50;
|
|
42
|
-
background: $surface;
|
|
43
|
-
border-right: solid $primary;
|
|
44
|
-
display: none;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
#sidebar-tabs {
|
|
48
|
-
height: 1fr;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
#history-list {
|
|
52
|
-
height: 1fr;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.history-interactive {
|
|
56
|
-
color: #34d399;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.history-tui {
|
|
60
|
-
color: #60a5fa;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.history-system {
|
|
64
|
-
color: #fbbf24;
|
|
65
|
-
text-style: italic;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.history-command {
|
|
69
|
-
/* Use default text color from theme */
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.history-generic {
|
|
73
|
-
color: #d1d5db;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.history-empty {
|
|
77
|
-
color: #6b7280;
|
|
78
|
-
text-style: italic;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.history-error {
|
|
82
|
-
color: #ef4444;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.file-item {
|
|
86
|
-
color: #d1d5db;
|
|
87
|
-
}
|
|
88
|
-
"""
|
|
89
|
-
|
|
90
|
-
def compose(self) -> ComposeResult:
|
|
91
|
-
"""Create the sidebar layout with tabs."""
|
|
92
|
-
with TabbedContent(id="sidebar-tabs"):
|
|
93
|
-
with TabPane("📜 History", id="history-tab"):
|
|
94
|
-
yield ListView(id="history-list")
|
|
95
|
-
|
|
96
|
-
def on_mount(self) -> None:
|
|
97
|
-
"""Initialize the sidebar when mounted."""
|
|
98
|
-
# Set up event handlers for keyboard interaction
|
|
99
|
-
history_list = self.query_one("#history-list", ListView)
|
|
100
|
-
|
|
101
|
-
# Add a class to make it focusable
|
|
102
|
-
history_list.can_focus = True
|
|
103
|
-
|
|
104
|
-
# Load command history
|
|
105
|
-
self.load_command_history()
|
|
106
|
-
|
|
107
|
-
@on(ListView.Highlighted)
|
|
108
|
-
def on_list_highlighted(self, event: ListView.Highlighted) -> None:
|
|
109
|
-
"""Handle highlighting of list items to ensure they can be selected."""
|
|
110
|
-
# This ensures the item gets focus when highlighted by arrow keys
|
|
111
|
-
if event.list_view.id == "history-list":
|
|
112
|
-
event.list_view.focus()
|
|
113
|
-
# Sync the current_history_index with the ListView index to fix modal sync issue
|
|
114
|
-
self.current_history_index = event.list_view.index
|
|
115
|
-
|
|
116
|
-
@on(ListView.Selected)
|
|
117
|
-
def on_list_selected(self, event: ListView.Selected) -> None:
|
|
118
|
-
"""Handle selection of list items (including mouse clicks).
|
|
119
|
-
|
|
120
|
-
Implements double-click detection to allow users to retrieve history items
|
|
121
|
-
by either pressing ENTER or double-clicking with the mouse.
|
|
122
|
-
"""
|
|
123
|
-
if event.list_view.id == "history-list":
|
|
124
|
-
current_time = time.time()
|
|
125
|
-
selected_item = event.item
|
|
126
|
-
|
|
127
|
-
# Check if this is a double-click
|
|
128
|
-
if (
|
|
129
|
-
selected_item == self._last_clicked_item
|
|
130
|
-
and current_time - self._last_click_time <= self._double_click_threshold
|
|
131
|
-
and hasattr(selected_item, "command_entry")
|
|
132
|
-
):
|
|
133
|
-
# Double-click detected! Show command in modal
|
|
134
|
-
# Find the index of this item
|
|
135
|
-
history_list = self.query_one("#history-list", ListView)
|
|
136
|
-
self.current_history_index = history_list.index
|
|
137
|
-
|
|
138
|
-
# Push the modal screen - it will get data from the sidebar
|
|
139
|
-
self.app.push_screen(CommandHistoryModal())
|
|
140
|
-
|
|
141
|
-
# Reset click tracking to prevent triple-click issues
|
|
142
|
-
self._last_click_time = 0
|
|
143
|
-
self._last_clicked_item = None
|
|
144
|
-
else:
|
|
145
|
-
# Single click - just update tracking
|
|
146
|
-
self._last_click_time = current_time
|
|
147
|
-
self._last_clicked_item = selected_item
|
|
148
|
-
|
|
149
|
-
@on(Key)
|
|
150
|
-
def on_key(self, event: Key) -> None:
|
|
151
|
-
"""Handle key events for the sidebar."""
|
|
152
|
-
# Handle Enter key on the history list
|
|
153
|
-
if event.key == "enter":
|
|
154
|
-
history_list = self.query_one("#history-list", ListView)
|
|
155
|
-
if (
|
|
156
|
-
history_list.has_focus
|
|
157
|
-
and history_list.highlighted_child
|
|
158
|
-
and hasattr(history_list.highlighted_child, "command_entry")
|
|
159
|
-
):
|
|
160
|
-
# Show command details in modal
|
|
161
|
-
# Update the current history index to match this item
|
|
162
|
-
self.current_history_index = history_list.index
|
|
163
|
-
|
|
164
|
-
# Push the modal screen - it will get data from the sidebar
|
|
165
|
-
self.app.push_screen(CommandHistoryModal())
|
|
166
|
-
|
|
167
|
-
# Stop propagation
|
|
168
|
-
event.stop()
|
|
169
|
-
event.prevent_default()
|
|
170
|
-
|
|
171
|
-
def load_command_history(self) -> None:
|
|
172
|
-
"""Load command history from file into the history list."""
|
|
173
|
-
try:
|
|
174
|
-
# Clear existing items
|
|
175
|
-
history_list = self.query_one("#history-list", ListView)
|
|
176
|
-
history_list.clear()
|
|
177
|
-
|
|
178
|
-
# Get command history entries (limit to last 50)
|
|
179
|
-
entries = self.history_reader.read_history(max_entries=50)
|
|
180
|
-
|
|
181
|
-
# Filter out CLI-specific commands that aren't relevant for TUI
|
|
182
|
-
cli_commands = {
|
|
183
|
-
"/help",
|
|
184
|
-
"/exit",
|
|
185
|
-
"/m",
|
|
186
|
-
"/motd",
|
|
187
|
-
"/show",
|
|
188
|
-
"/set",
|
|
189
|
-
"/tools",
|
|
190
|
-
}
|
|
191
|
-
filtered_entries = []
|
|
192
|
-
for entry in entries:
|
|
193
|
-
command = entry.get("command", "").strip()
|
|
194
|
-
# Skip CLI commands but keep everything else
|
|
195
|
-
if not any(command.startswith(cli_cmd) for cli_cmd in cli_commands):
|
|
196
|
-
filtered_entries.append(entry)
|
|
197
|
-
|
|
198
|
-
# Store filtered entries centrally
|
|
199
|
-
self.history_entries = filtered_entries
|
|
200
|
-
|
|
201
|
-
# Reset history index
|
|
202
|
-
self.current_history_index = 0
|
|
203
|
-
|
|
204
|
-
if not filtered_entries:
|
|
205
|
-
# No history available (after filtering)
|
|
206
|
-
history_list.append(
|
|
207
|
-
ListItem(Label("No command history", classes="history-empty"))
|
|
208
|
-
)
|
|
209
|
-
return
|
|
210
|
-
|
|
211
|
-
# Add filtered entries to the list (most recent first)
|
|
212
|
-
for entry in filtered_entries:
|
|
213
|
-
timestamp = entry["timestamp"]
|
|
214
|
-
command = entry["command"]
|
|
215
|
-
|
|
216
|
-
# Format timestamp for display
|
|
217
|
-
time_display = self.history_reader.format_timestamp(timestamp)
|
|
218
|
-
|
|
219
|
-
# Truncate command for display if needed
|
|
220
|
-
display_text = command
|
|
221
|
-
if len(display_text) > 60:
|
|
222
|
-
display_text = display_text[:57] + "..."
|
|
223
|
-
|
|
224
|
-
# Create list item
|
|
225
|
-
label = Label(
|
|
226
|
-
f"[{time_display}] {display_text}", classes="history-command"
|
|
227
|
-
)
|
|
228
|
-
list_item = ListItem(label)
|
|
229
|
-
list_item.command_entry = entry
|
|
230
|
-
history_list.append(list_item)
|
|
231
|
-
|
|
232
|
-
# Focus on the most recent command (first in the list)
|
|
233
|
-
if len(history_list.children) > 0:
|
|
234
|
-
history_list.index = 0
|
|
235
|
-
# Sync the current_history_index to match the ListView index
|
|
236
|
-
self.current_history_index = 0
|
|
237
|
-
|
|
238
|
-
# Note: We don't automatically show the modal here when just loading the history
|
|
239
|
-
# That will be handled by the app's action_toggle_sidebar method
|
|
240
|
-
# This ensures the modal only appears when explicitly opening the sidebar, not during refresh
|
|
241
|
-
|
|
242
|
-
except Exception as e:
|
|
243
|
-
# Add error item
|
|
244
|
-
history_list = self.query_one("#history-list", ListView)
|
|
245
|
-
history_list.clear()
|
|
246
|
-
history_list.append(
|
|
247
|
-
ListItem(
|
|
248
|
-
Label(f"Error loading history: {str(e)}", classes="history-error")
|
|
249
|
-
)
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
def navigate_to_next_command(self) -> bool:
|
|
253
|
-
"""Navigate to the next command in history.
|
|
254
|
-
|
|
255
|
-
Returns:
|
|
256
|
-
bool: True if navigation succeeded, False otherwise
|
|
257
|
-
"""
|
|
258
|
-
if (
|
|
259
|
-
not self.history_entries
|
|
260
|
-
or self.current_history_index >= len(self.history_entries) - 1
|
|
261
|
-
):
|
|
262
|
-
return False
|
|
263
|
-
|
|
264
|
-
# Increment the index
|
|
265
|
-
self.current_history_index += 1
|
|
266
|
-
|
|
267
|
-
# Update the listview selection
|
|
268
|
-
try:
|
|
269
|
-
history_list = self.query_one("#history-list", ListView)
|
|
270
|
-
if history_list and self.current_history_index < len(history_list.children):
|
|
271
|
-
history_list.index = self.current_history_index
|
|
272
|
-
except Exception:
|
|
273
|
-
pass
|
|
274
|
-
|
|
275
|
-
return True
|
|
276
|
-
|
|
277
|
-
def navigate_to_previous_command(self) -> bool:
|
|
278
|
-
"""Navigate to the previous command in history.
|
|
279
|
-
|
|
280
|
-
Returns:
|
|
281
|
-
bool: True if navigation succeeded, False otherwise
|
|
282
|
-
"""
|
|
283
|
-
if not self.history_entries or self.current_history_index <= 0:
|
|
284
|
-
return False
|
|
285
|
-
|
|
286
|
-
# Decrement the index
|
|
287
|
-
self.current_history_index -= 1
|
|
288
|
-
|
|
289
|
-
# Update the listview selection
|
|
290
|
-
try:
|
|
291
|
-
history_list = self.query_one("#history-list", ListView)
|
|
292
|
-
if history_list and self.current_history_index >= 0:
|
|
293
|
-
history_list.index = self.current_history_index
|
|
294
|
-
except Exception:
|
|
295
|
-
pass
|
|
296
|
-
|
|
297
|
-
return True
|
|
298
|
-
|
|
299
|
-
def get_current_command_entry(self) -> dict:
|
|
300
|
-
"""Get the current command entry based on the current index.
|
|
301
|
-
|
|
302
|
-
Returns:
|
|
303
|
-
dict: The current command entry or empty dict if not available
|
|
304
|
-
"""
|
|
305
|
-
if self.history_entries and 0 <= self.current_history_index < len(
|
|
306
|
-
self.history_entries
|
|
307
|
-
):
|
|
308
|
-
return self.history_entries[self.current_history_index]
|
|
309
|
-
return {"command": "", "timestamp": ""}
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Status bar component for the TUI.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
|
|
7
|
-
from rich.text import Text
|
|
8
|
-
from textual.app import ComposeResult
|
|
9
|
-
from textual.reactive import reactive
|
|
10
|
-
from textual.widgets import Static
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class StatusBar(Static):
|
|
14
|
-
"""Status bar showing current model, puppy name, and connection status."""
|
|
15
|
-
|
|
16
|
-
DEFAULT_CSS = """
|
|
17
|
-
StatusBar {
|
|
18
|
-
dock: top;
|
|
19
|
-
height: 1;
|
|
20
|
-
background: $primary;
|
|
21
|
-
color: $text;
|
|
22
|
-
text-align: right;
|
|
23
|
-
padding: 0 1;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
#status-content {
|
|
27
|
-
text-align: right;
|
|
28
|
-
width: 100%;
|
|
29
|
-
}
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
current_model = reactive("")
|
|
33
|
-
puppy_name = reactive("")
|
|
34
|
-
connection_status = reactive("Connected")
|
|
35
|
-
agent_status = reactive("Ready")
|
|
36
|
-
progress_visible = reactive(False)
|
|
37
|
-
token_count = reactive(0)
|
|
38
|
-
token_capacity = reactive(0)
|
|
39
|
-
token_proportion = reactive(0.0)
|
|
40
|
-
|
|
41
|
-
def compose(self) -> ComposeResult:
|
|
42
|
-
yield Static(id="status-content")
|
|
43
|
-
|
|
44
|
-
def watch_current_model(self) -> None:
|
|
45
|
-
self.update_status()
|
|
46
|
-
|
|
47
|
-
def watch_puppy_name(self) -> None:
|
|
48
|
-
self.update_status()
|
|
49
|
-
|
|
50
|
-
def watch_connection_status(self) -> None:
|
|
51
|
-
self.update_status()
|
|
52
|
-
|
|
53
|
-
def watch_agent_status(self) -> None:
|
|
54
|
-
self.update_status()
|
|
55
|
-
|
|
56
|
-
def watch_token_count(self) -> None:
|
|
57
|
-
self.update_status()
|
|
58
|
-
|
|
59
|
-
def watch_token_capacity(self) -> None:
|
|
60
|
-
self.update_status()
|
|
61
|
-
|
|
62
|
-
def watch_token_proportion(self) -> None:
|
|
63
|
-
self.update_status()
|
|
64
|
-
|
|
65
|
-
def watch_progress_visible(self) -> None:
|
|
66
|
-
self.update_status()
|
|
67
|
-
|
|
68
|
-
def update_status(self) -> None:
|
|
69
|
-
"""Update the status bar content with responsive design."""
|
|
70
|
-
status_widget = self.query_one("#status-content", Static)
|
|
71
|
-
|
|
72
|
-
# Get current working directory
|
|
73
|
-
cwd = os.getcwd()
|
|
74
|
-
cwd_short = os.path.basename(cwd) if cwd != "/" else "/"
|
|
75
|
-
|
|
76
|
-
# Add agent status indicator with different colors
|
|
77
|
-
if self.agent_status == "Thinking":
|
|
78
|
-
status_indicator = "🤔"
|
|
79
|
-
status_color = "yellow"
|
|
80
|
-
elif self.agent_status == "Processing":
|
|
81
|
-
status_indicator = "⚡"
|
|
82
|
-
status_color = "blue"
|
|
83
|
-
elif self.agent_status == "Busy":
|
|
84
|
-
status_indicator = "🔄"
|
|
85
|
-
status_color = "orange"
|
|
86
|
-
else: # Ready
|
|
87
|
-
status_indicator = "✅"
|
|
88
|
-
status_color = "green"
|
|
89
|
-
|
|
90
|
-
# Get terminal width for responsive content
|
|
91
|
-
try:
|
|
92
|
-
terminal_width = self.app.size.width if hasattr(self.app, "size") else 80
|
|
93
|
-
except Exception:
|
|
94
|
-
terminal_width = 80
|
|
95
|
-
|
|
96
|
-
# Create responsive status text based on terminal width
|
|
97
|
-
rich_text = Text()
|
|
98
|
-
|
|
99
|
-
# Token status with color coding
|
|
100
|
-
token_status = ""
|
|
101
|
-
token_color = "green"
|
|
102
|
-
if self.token_count > 0 and self.token_capacity > 0:
|
|
103
|
-
# Import here to avoid circular import
|
|
104
|
-
from code_puppy.config import get_compaction_threshold
|
|
105
|
-
|
|
106
|
-
get_compaction_threshold = get_compaction_threshold()
|
|
107
|
-
|
|
108
|
-
if self.token_proportion > get_compaction_threshold:
|
|
109
|
-
token_color = "red"
|
|
110
|
-
token_status = f"🔴 {self.token_count}/{self.token_capacity} ({self.token_proportion:.1%})"
|
|
111
|
-
elif self.token_proportion > (
|
|
112
|
-
get_compaction_threshold - 0.15
|
|
113
|
-
): # 15% before summarization threshold
|
|
114
|
-
token_color = "yellow"
|
|
115
|
-
token_status = f"🟡 {self.token_count}/{self.token_capacity} ({self.token_proportion:.1%})"
|
|
116
|
-
else:
|
|
117
|
-
token_color = "green"
|
|
118
|
-
token_status = f"🟢 {self.token_count}/{self.token_capacity} ({self.token_proportion:.1%})"
|
|
119
|
-
|
|
120
|
-
if terminal_width >= 140:
|
|
121
|
-
# Extra wide - show full path and all info including tokens
|
|
122
|
-
rich_text.append(
|
|
123
|
-
f"📁 {cwd} | 🐶 {self.puppy_name} | Model: {self.current_model} | "
|
|
124
|
-
)
|
|
125
|
-
if token_status:
|
|
126
|
-
rich_text.append(f"{token_status} | ", style=token_color)
|
|
127
|
-
rich_text.append(
|
|
128
|
-
f"{status_indicator} {self.agent_status}", style=status_color
|
|
129
|
-
)
|
|
130
|
-
elif terminal_width >= 100:
|
|
131
|
-
# Full status display for wide terminals
|
|
132
|
-
rich_text.append(
|
|
133
|
-
f"📁 {cwd_short} | 🐶 {self.puppy_name} | Model: {self.current_model} | "
|
|
134
|
-
)
|
|
135
|
-
rich_text.append(
|
|
136
|
-
f"{status_indicator} {self.agent_status}", style=status_color
|
|
137
|
-
)
|
|
138
|
-
elif terminal_width >= 120:
|
|
139
|
-
# Medium display - shorten model name if needed
|
|
140
|
-
model_display = (
|
|
141
|
-
self.current_model[:15] + "..."
|
|
142
|
-
if len(self.current_model) > 18
|
|
143
|
-
else self.current_model
|
|
144
|
-
)
|
|
145
|
-
rich_text.append(
|
|
146
|
-
f"📁 {cwd_short} | 🐶 {self.puppy_name} | {model_display} | "
|
|
147
|
-
)
|
|
148
|
-
if token_status:
|
|
149
|
-
rich_text.append(f"{token_status} | ", style=token_color)
|
|
150
|
-
rich_text.append(
|
|
151
|
-
f"{status_indicator} {self.agent_status}", style=status_color
|
|
152
|
-
)
|
|
153
|
-
elif terminal_width >= 60:
|
|
154
|
-
# Compact display - use abbreviations
|
|
155
|
-
puppy_short = (
|
|
156
|
-
self.puppy_name[:8] + "..."
|
|
157
|
-
if len(self.puppy_name) > 10
|
|
158
|
-
else self.puppy_name
|
|
159
|
-
)
|
|
160
|
-
model_short = (
|
|
161
|
-
self.current_model[:12] + "..."
|
|
162
|
-
if len(self.current_model) > 15
|
|
163
|
-
else self.current_model
|
|
164
|
-
)
|
|
165
|
-
rich_text.append(f"📁 {cwd_short} | 🐶 {puppy_short} | {model_short} | ")
|
|
166
|
-
rich_text.append(f"{status_indicator}", style=status_color)
|
|
167
|
-
else:
|
|
168
|
-
# Minimal display for very narrow terminals
|
|
169
|
-
cwd_mini = cwd_short[:8] + "..." if len(cwd_short) > 10 else cwd_short
|
|
170
|
-
rich_text.append(f"📁 {cwd_mini} | ")
|
|
171
|
-
rich_text.append(f"{status_indicator}", style=status_color)
|
|
172
|
-
|
|
173
|
-
rich_text.justify = "right"
|
|
174
|
-
status_widget.update(rich_text)
|
|
175
|
-
|
|
176
|
-
def update_token_info(
|
|
177
|
-
self, current_tokens: int, max_tokens: int, proportion: float
|
|
178
|
-
) -> None:
|
|
179
|
-
"""Update token information in the status bar."""
|
|
180
|
-
self.token_count = current_tokens
|
|
181
|
-
self.token_capacity = max_tokens
|
|
182
|
-
self.token_proportion = proportion
|
code_puppy/tui/messages.py
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Custom message classes for TUI components.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from textual.message import Message
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class HistoryEntrySelected(Message):
|
|
9
|
-
"""Message sent when a history entry is selected from the sidebar."""
|
|
10
|
-
|
|
11
|
-
def __init__(self, history_entry: dict) -> None:
|
|
12
|
-
"""Initialize with the history entry data."""
|
|
13
|
-
self.history_entry = history_entry
|
|
14
|
-
super().__init__()
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class CommandSelected(Message):
|
|
18
|
-
"""Message sent when a command is selected from the history modal."""
|
|
19
|
-
|
|
20
|
-
def __init__(self, command: str) -> None:
|
|
21
|
-
"""Initialize with the command text.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
command: The command text that was selected
|
|
25
|
-
"""
|
|
26
|
-
self.command = command
|
|
27
|
-
super().__init__()
|