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
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Chat message data model.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from datetime import datetime
|
|
7
|
-
from typing import Any, Dict
|
|
8
|
-
|
|
9
|
-
from .enums import MessageType
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@dataclass
|
|
13
|
-
class ChatMessage:
|
|
14
|
-
"""Represents a message in the chat interface."""
|
|
15
|
-
|
|
16
|
-
id: str
|
|
17
|
-
type: MessageType
|
|
18
|
-
content: str
|
|
19
|
-
timestamp: datetime
|
|
20
|
-
metadata: Dict[str, Any] = None
|
|
21
|
-
group_id: str = None
|
|
22
|
-
|
|
23
|
-
def __post_init__(self):
|
|
24
|
-
if self.metadata is None:
|
|
25
|
-
self.metadata = {}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Command history reader for TUI history tab.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import re
|
|
7
|
-
from datetime import datetime
|
|
8
|
-
from typing import Dict, List
|
|
9
|
-
|
|
10
|
-
from code_puppy.config import COMMAND_HISTORY_FILE
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class HistoryFileReader:
|
|
14
|
-
"""Reads and parses the command history file for display in the TUI history tab."""
|
|
15
|
-
|
|
16
|
-
def __init__(self, history_file_path: str = COMMAND_HISTORY_FILE):
|
|
17
|
-
"""Initialize the history file reader.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
history_file_path: Path to the command history file. Defaults to the standard location.
|
|
21
|
-
"""
|
|
22
|
-
self.history_file_path = history_file_path
|
|
23
|
-
self._timestamp_pattern = re.compile(
|
|
24
|
-
r"^# (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})"
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
def read_history(self, max_entries: int = 100) -> List[Dict[str, str]]:
|
|
28
|
-
"""Read command history from the history file.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
max_entries: Maximum number of entries to read. Defaults to 100.
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
List of history entries with timestamp and command, most recent first.
|
|
35
|
-
"""
|
|
36
|
-
if not os.path.exists(self.history_file_path):
|
|
37
|
-
return []
|
|
38
|
-
|
|
39
|
-
try:
|
|
40
|
-
with open(self.history_file_path, "r") as f:
|
|
41
|
-
content = f.read()
|
|
42
|
-
|
|
43
|
-
# Split content by timestamp marker
|
|
44
|
-
raw_chunks = re.split(r"(# \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})", content)
|
|
45
|
-
|
|
46
|
-
# Filter out empty chunks
|
|
47
|
-
chunks = [chunk for chunk in raw_chunks if chunk.strip()]
|
|
48
|
-
|
|
49
|
-
entries = []
|
|
50
|
-
|
|
51
|
-
# Process chunks in pairs (timestamp and command)
|
|
52
|
-
i = 0
|
|
53
|
-
while i < len(chunks) - 1:
|
|
54
|
-
if self._timestamp_pattern.match(chunks[i]):
|
|
55
|
-
timestamp = self._timestamp_pattern.match(chunks[i]).group(1)
|
|
56
|
-
command_text = chunks[i + 1].strip()
|
|
57
|
-
|
|
58
|
-
if command_text: # Skip empty commands
|
|
59
|
-
entries.append(
|
|
60
|
-
{"timestamp": timestamp, "command": command_text}
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
i += 2
|
|
64
|
-
else:
|
|
65
|
-
# Skip invalid chunks
|
|
66
|
-
i += 1
|
|
67
|
-
|
|
68
|
-
# Limit the number of entries and reverse to get most recent first
|
|
69
|
-
return entries[-max_entries:][::-1]
|
|
70
|
-
|
|
71
|
-
except Exception:
|
|
72
|
-
# Return empty list on any error
|
|
73
|
-
return []
|
|
74
|
-
|
|
75
|
-
def format_timestamp(self, timestamp: str, format_str: str = "%H:%M:%S") -> str:
|
|
76
|
-
"""Format a timestamp string for display.
|
|
77
|
-
|
|
78
|
-
Args:
|
|
79
|
-
timestamp: ISO format timestamp string (YYYY-MM-DDThh:mm:ss)
|
|
80
|
-
format_str: Format string for datetime.strftime
|
|
81
|
-
|
|
82
|
-
Returns:
|
|
83
|
-
Formatted timestamp string
|
|
84
|
-
"""
|
|
85
|
-
try:
|
|
86
|
-
dt = datetime.fromisoformat(timestamp)
|
|
87
|
-
return dt.strftime(format_str)
|
|
88
|
-
except (ValueError, TypeError):
|
|
89
|
-
return timestamp
|
code_puppy/tui/models/enums.py
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Enums for the TUI module.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from enum import Enum
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class MessageType(Enum):
|
|
9
|
-
"""Types of messages in the chat interface."""
|
|
10
|
-
|
|
11
|
-
USER = "user"
|
|
12
|
-
AGENT = "agent"
|
|
13
|
-
SYSTEM = "system"
|
|
14
|
-
ERROR = "error"
|
|
15
|
-
DIVIDER = "divider"
|
|
16
|
-
INFO = "info"
|
|
17
|
-
SUCCESS = "success"
|
|
18
|
-
WARNING = "warning"
|
|
19
|
-
TOOL_OUTPUT = "tool_output"
|
|
20
|
-
COMMAND_OUTPUT = "command_output"
|
|
21
|
-
|
|
22
|
-
AGENT_REASONING = "agent_reasoning"
|
|
23
|
-
PLANNED_NEXT_STEPS = "planned_next_steps"
|
|
24
|
-
AGENT_RESPONSE = "agent_response"
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
TUI screens package.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from .help import HelpScreen
|
|
6
|
-
from .mcp_install_wizard import MCPInstallWizardScreen
|
|
7
|
-
from .settings import SettingsScreen
|
|
8
|
-
from .tools import ToolsScreen
|
|
9
|
-
from .autosave_picker import AutosavePicker
|
|
10
|
-
|
|
11
|
-
__all__ = [
|
|
12
|
-
"HelpScreen",
|
|
13
|
-
"SettingsScreen",
|
|
14
|
-
"ToolsScreen",
|
|
15
|
-
"MCPInstallWizardScreen",
|
|
16
|
-
"AutosavePicker",
|
|
17
|
-
]
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Autosave Picker modal for TUI.
|
|
3
|
-
Lists recent autosave sessions and lets the user load one.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
import json
|
|
9
|
-
from dataclasses import dataclass
|
|
10
|
-
from datetime import datetime
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import List, Optional, Tuple
|
|
13
|
-
|
|
14
|
-
from textual import on
|
|
15
|
-
from textual.app import ComposeResult
|
|
16
|
-
from textual.containers import Container, Horizontal
|
|
17
|
-
from textual.screen import ModalScreen
|
|
18
|
-
from textual.widgets import Button, Label, ListItem, ListView, Static
|
|
19
|
-
|
|
20
|
-
from code_puppy.session_storage import list_sessions
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@dataclass(slots=True)
|
|
24
|
-
class AutosaveEntry:
|
|
25
|
-
name: str
|
|
26
|
-
timestamp: Optional[str]
|
|
27
|
-
message_count: Optional[int]
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _load_metadata(base_dir: Path, name: str) -> Tuple[Optional[str], Optional[int]]:
|
|
31
|
-
meta_path = base_dir / f"{name}_meta.json"
|
|
32
|
-
try:
|
|
33
|
-
with meta_path.open("r", encoding="utf-8") as meta_file:
|
|
34
|
-
data = json.load(meta_file)
|
|
35
|
-
return data.get("timestamp"), data.get("message_count")
|
|
36
|
-
except Exception:
|
|
37
|
-
return None, None
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class AutosavePicker(ModalScreen):
|
|
41
|
-
"""Modal to present available autosave sessions for selection."""
|
|
42
|
-
|
|
43
|
-
DEFAULT_CSS = """
|
|
44
|
-
AutosavePicker {
|
|
45
|
-
align: center middle;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
#modal-container {
|
|
49
|
-
width: 80%;
|
|
50
|
-
max-width: 100;
|
|
51
|
-
height: 24;
|
|
52
|
-
min-height: 18;
|
|
53
|
-
background: $surface;
|
|
54
|
-
border: solid $primary;
|
|
55
|
-
padding: 1 2;
|
|
56
|
-
layout: vertical;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
#list-label {
|
|
60
|
-
width: 100%;
|
|
61
|
-
height: 1;
|
|
62
|
-
color: $text;
|
|
63
|
-
text-align: left;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
#autosave-list {
|
|
67
|
-
height: 1fr;
|
|
68
|
-
overflow: auto;
|
|
69
|
-
border: solid $primary-darken-2;
|
|
70
|
-
background: $surface-darken-1;
|
|
71
|
-
margin: 1 0;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.button-row {
|
|
75
|
-
height: 3;
|
|
76
|
-
align-horizontal: right;
|
|
77
|
-
margin-top: 1;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
#cancel-button { background: $primary-darken-1; }
|
|
81
|
-
#load-button { background: $success; }
|
|
82
|
-
"""
|
|
83
|
-
|
|
84
|
-
def __init__(self, autosave_dir: Path, **kwargs):
|
|
85
|
-
super().__init__(**kwargs)
|
|
86
|
-
self.autosave_dir = autosave_dir
|
|
87
|
-
self.entries: List[AutosaveEntry] = []
|
|
88
|
-
self.list_view: Optional[ListView] = None
|
|
89
|
-
|
|
90
|
-
def on_mount(self) -> None:
|
|
91
|
-
names = list_sessions(self.autosave_dir)
|
|
92
|
-
raw_entries: List[Tuple[str, Optional[str], Optional[int]]] = []
|
|
93
|
-
for name in names:
|
|
94
|
-
ts, count = _load_metadata(self.autosave_dir, name)
|
|
95
|
-
raw_entries.append((name, ts, count))
|
|
96
|
-
|
|
97
|
-
def sort_key(entry):
|
|
98
|
-
_, ts, _ = entry
|
|
99
|
-
if ts:
|
|
100
|
-
try:
|
|
101
|
-
return datetime.fromisoformat(ts)
|
|
102
|
-
except ValueError:
|
|
103
|
-
return datetime.min
|
|
104
|
-
return datetime.min
|
|
105
|
-
|
|
106
|
-
raw_entries.sort(key=sort_key, reverse=True)
|
|
107
|
-
self.entries = [AutosaveEntry(*e) for e in raw_entries]
|
|
108
|
-
|
|
109
|
-
# Populate the ListView now that entries are ready
|
|
110
|
-
if self.list_view is None:
|
|
111
|
-
try:
|
|
112
|
-
self.list_view = self.query_one("#autosave-list", ListView)
|
|
113
|
-
except Exception:
|
|
114
|
-
self.list_view = None
|
|
115
|
-
|
|
116
|
-
if self.list_view is not None:
|
|
117
|
-
# Clear existing items if any
|
|
118
|
-
try:
|
|
119
|
-
self.list_view.clear()
|
|
120
|
-
except Exception:
|
|
121
|
-
# Fallback: remove children manually
|
|
122
|
-
self.list_view.children.clear() # type: ignore
|
|
123
|
-
|
|
124
|
-
for entry in self.entries[:50]:
|
|
125
|
-
ts = entry.timestamp or "unknown time"
|
|
126
|
-
count = (
|
|
127
|
-
f"{entry.message_count} msgs"
|
|
128
|
-
if entry.message_count is not None
|
|
129
|
-
else "unknown size"
|
|
130
|
-
)
|
|
131
|
-
label = f"{entry.name} — {count}, saved at {ts}"
|
|
132
|
-
self.list_view.append(ListItem(Static(label)))
|
|
133
|
-
|
|
134
|
-
# Focus and select first item for better UX
|
|
135
|
-
if len(self.entries) > 0:
|
|
136
|
-
self.list_view.index = 0
|
|
137
|
-
self.list_view.focus()
|
|
138
|
-
|
|
139
|
-
def compose(self) -> ComposeResult:
|
|
140
|
-
with Container(id="modal-container"):
|
|
141
|
-
yield Label("Select an autosave to load (Esc to cancel)", id="list-label")
|
|
142
|
-
self.list_view = ListView(id="autosave-list")
|
|
143
|
-
# populate items
|
|
144
|
-
for entry in self.entries[:50]: # cap to avoid long lists
|
|
145
|
-
ts = entry.timestamp or "unknown time"
|
|
146
|
-
count = (
|
|
147
|
-
f"{entry.message_count} msgs"
|
|
148
|
-
if entry.message_count is not None
|
|
149
|
-
else "unknown size"
|
|
150
|
-
)
|
|
151
|
-
label = f"{entry.name} — {count}, saved at {ts}"
|
|
152
|
-
self.list_view.append(ListItem(Static(label)))
|
|
153
|
-
yield self.list_view
|
|
154
|
-
with Horizontal(classes="button-row"):
|
|
155
|
-
yield Button("Cancel", id="cancel-button")
|
|
156
|
-
yield Button("Load", id="load-button", variant="primary")
|
|
157
|
-
|
|
158
|
-
@on(Button.Pressed, "#cancel-button")
|
|
159
|
-
def cancel(self) -> None:
|
|
160
|
-
self.dismiss(None)
|
|
161
|
-
|
|
162
|
-
@on(Button.Pressed, "#load-button")
|
|
163
|
-
def load_selected(self) -> None:
|
|
164
|
-
if not self.list_view or not self.entries:
|
|
165
|
-
self.dismiss(None)
|
|
166
|
-
return
|
|
167
|
-
idx = self.list_view.index if self.list_view.index is not None else 0
|
|
168
|
-
if 0 <= idx < len(self.entries):
|
|
169
|
-
self.dismiss(self.entries[idx].name)
|
|
170
|
-
else:
|
|
171
|
-
self.dismiss(None)
|
|
172
|
-
|
|
173
|
-
def on_list_view_selected(self, event: ListView.Selected) -> None: # type: ignore
|
|
174
|
-
# Double-enter may select; we just map to load button
|
|
175
|
-
self.load_selected()
|
code_puppy/tui/screens/help.py
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Help modal screen.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from textual import on
|
|
6
|
-
from textual.app import ComposeResult
|
|
7
|
-
from textual.containers import Container, VerticalScroll
|
|
8
|
-
from textual.screen import ModalScreen
|
|
9
|
-
from textual.widgets import Button, Static
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class HelpScreen(ModalScreen):
|
|
13
|
-
"""Help modal screen."""
|
|
14
|
-
|
|
15
|
-
DEFAULT_CSS = """
|
|
16
|
-
HelpScreen {
|
|
17
|
-
align: center middle;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
#help-dialog {
|
|
21
|
-
width: 80;
|
|
22
|
-
height: 30;
|
|
23
|
-
border: thick $primary;
|
|
24
|
-
background: $surface;
|
|
25
|
-
padding: 1;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
#help-content {
|
|
29
|
-
height: 1fr;
|
|
30
|
-
margin: 0 0 1 0;
|
|
31
|
-
overflow-y: auto;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
#help-buttons {
|
|
35
|
-
layout: horizontal;
|
|
36
|
-
height: 3;
|
|
37
|
-
align: center middle;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
#dismiss-button {
|
|
41
|
-
margin: 0 1;
|
|
42
|
-
}
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
def compose(self) -> ComposeResult:
|
|
46
|
-
with Container(id="help-dialog"):
|
|
47
|
-
yield Static("📚 Code Puppy TUI Help", id="help-title")
|
|
48
|
-
with VerticalScroll(id="help-content"):
|
|
49
|
-
yield Static(self.get_help_content(), id="help-text")
|
|
50
|
-
with Container(id="help-buttons"):
|
|
51
|
-
yield Button("Dismiss", id="dismiss-button", variant="primary")
|
|
52
|
-
|
|
53
|
-
def get_help_content(self) -> str:
|
|
54
|
-
"""Get the help content text."""
|
|
55
|
-
try:
|
|
56
|
-
# Get terminal width for responsive help
|
|
57
|
-
terminal_width = self.app.size.width if hasattr(self.app, "size") else 80
|
|
58
|
-
except Exception:
|
|
59
|
-
terminal_width = 80
|
|
60
|
-
|
|
61
|
-
if terminal_width < 60:
|
|
62
|
-
# Compact help for narrow terminals
|
|
63
|
-
return """
|
|
64
|
-
Code Puppy TUI (Compact Mode):
|
|
65
|
-
|
|
66
|
-
Controls:
|
|
67
|
-
- Enter: Send message
|
|
68
|
-
- Ctrl+Enter: New line
|
|
69
|
-
- Ctrl+Q: Quit
|
|
70
|
-
- Ctrl+2: Toggle History
|
|
71
|
-
- Ctrl+3: Settings
|
|
72
|
-
- Ctrl+4: Tools
|
|
73
|
-
- Ctrl+5: Focus prompt
|
|
74
|
-
- Ctrl+6: Focus response
|
|
75
|
-
|
|
76
|
-
Use this help for full details.
|
|
77
|
-
"""
|
|
78
|
-
else:
|
|
79
|
-
# Full help text
|
|
80
|
-
return """
|
|
81
|
-
Code Puppy TUI Help:
|
|
82
|
-
|
|
83
|
-
Input Controls:
|
|
84
|
-
- Enter: Send message
|
|
85
|
-
- ALT+Enter: New line (multi-line input)
|
|
86
|
-
- Standard text editing shortcuts supported
|
|
87
|
-
|
|
88
|
-
Keyboard Shortcuts:
|
|
89
|
-
- Ctrl+Q/Ctrl+C: Quit application
|
|
90
|
-
- Ctrl+L: Clear chat history
|
|
91
|
-
- Ctrl+1: Show this help
|
|
92
|
-
- Ctrl+2: Toggle History
|
|
93
|
-
- Ctrl+3: Open settings
|
|
94
|
-
- Ctrl+4: Tools
|
|
95
|
-
- Ctrl+5: Focus prompt (input field)
|
|
96
|
-
- Ctrl+6: Focus response (chat area)
|
|
97
|
-
|
|
98
|
-
Chat Navigation:
|
|
99
|
-
- Ctrl+Up/Down: Scroll chat up/down
|
|
100
|
-
- Ctrl+Home: Scroll to top
|
|
101
|
-
- Ctrl+End: Scroll to bottom
|
|
102
|
-
|
|
103
|
-
Commands:
|
|
104
|
-
- /clear: Clear chat history
|
|
105
|
-
- /m <model>: Switch model
|
|
106
|
-
- /cd <dir>: Change directory
|
|
107
|
-
- /help: Show help
|
|
108
|
-
- /status: Show current status
|
|
109
|
-
|
|
110
|
-
Use the input area at the bottom to type messages.
|
|
111
|
-
Press Ctrl+2 to view History when needed.
|
|
112
|
-
Agent responses support syntax highlighting for code blocks.
|
|
113
|
-
Press Ctrl+3 to access all configuration settings.
|
|
114
|
-
|
|
115
|
-
Copy Feature:
|
|
116
|
-
- 📋 Copy buttons appear after agent responses
|
|
117
|
-
- Click or press Enter/Space on copy button to copy content
|
|
118
|
-
- Raw markdown content is copied to clipboard
|
|
119
|
-
- Visual feedback shows copy success/failure
|
|
120
|
-
"""
|
|
121
|
-
|
|
122
|
-
@on(Button.Pressed, "#dismiss-button")
|
|
123
|
-
def dismiss_help(self) -> None:
|
|
124
|
-
"""Dismiss the help modal."""
|
|
125
|
-
self.dismiss()
|
|
126
|
-
|
|
127
|
-
def on_key(self, event) -> None:
|
|
128
|
-
"""Handle key events."""
|
|
129
|
-
if event.key == "escape":
|
|
130
|
-
self.dismiss()
|