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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. code_puppy/__init__.py +7 -1
  2. code_puppy/agents/__init__.py +2 -0
  3. code_puppy/agents/agent_c_reviewer.py +59 -6
  4. code_puppy/agents/agent_code_puppy.py +7 -1
  5. code_puppy/agents/agent_code_reviewer.py +12 -2
  6. code_puppy/agents/agent_cpp_reviewer.py +73 -6
  7. code_puppy/agents/agent_creator_agent.py +45 -4
  8. code_puppy/agents/agent_golang_reviewer.py +92 -3
  9. code_puppy/agents/agent_javascript_reviewer.py +101 -8
  10. code_puppy/agents/agent_manager.py +81 -4
  11. code_puppy/agents/agent_pack_leader.py +383 -0
  12. code_puppy/agents/agent_planning.py +163 -0
  13. code_puppy/agents/agent_python_programmer.py +165 -0
  14. code_puppy/agents/agent_python_reviewer.py +28 -6
  15. code_puppy/agents/agent_qa_expert.py +98 -6
  16. code_puppy/agents/agent_qa_kitten.py +12 -7
  17. code_puppy/agents/agent_security_auditor.py +113 -3
  18. code_puppy/agents/agent_terminal_qa.py +323 -0
  19. code_puppy/agents/agent_typescript_reviewer.py +106 -7
  20. code_puppy/agents/base_agent.py +802 -176
  21. code_puppy/agents/event_stream_handler.py +350 -0
  22. code_puppy/agents/pack/__init__.py +34 -0
  23. code_puppy/agents/pack/bloodhound.py +304 -0
  24. code_puppy/agents/pack/husky.py +321 -0
  25. code_puppy/agents/pack/retriever.py +393 -0
  26. code_puppy/agents/pack/shepherd.py +348 -0
  27. code_puppy/agents/pack/terrier.py +287 -0
  28. code_puppy/agents/pack/watchdog.py +367 -0
  29. code_puppy/agents/prompt_reviewer.py +145 -0
  30. code_puppy/agents/subagent_stream_handler.py +276 -0
  31. code_puppy/api/__init__.py +13 -0
  32. code_puppy/api/app.py +169 -0
  33. code_puppy/api/main.py +21 -0
  34. code_puppy/api/pty_manager.py +446 -0
  35. code_puppy/api/routers/__init__.py +12 -0
  36. code_puppy/api/routers/agents.py +36 -0
  37. code_puppy/api/routers/commands.py +217 -0
  38. code_puppy/api/routers/config.py +74 -0
  39. code_puppy/api/routers/sessions.py +232 -0
  40. code_puppy/api/templates/terminal.html +361 -0
  41. code_puppy/api/websocket.py +154 -0
  42. code_puppy/callbacks.py +142 -4
  43. code_puppy/chatgpt_codex_client.py +283 -0
  44. code_puppy/claude_cache_client.py +586 -0
  45. code_puppy/cli_runner.py +916 -0
  46. code_puppy/command_line/add_model_menu.py +1079 -0
  47. code_puppy/command_line/agent_menu.py +395 -0
  48. code_puppy/command_line/attachments.py +10 -5
  49. code_puppy/command_line/autosave_menu.py +605 -0
  50. code_puppy/command_line/clipboard.py +527 -0
  51. code_puppy/command_line/colors_menu.py +520 -0
  52. code_puppy/command_line/command_handler.py +176 -738
  53. code_puppy/command_line/command_registry.py +150 -0
  54. code_puppy/command_line/config_commands.py +715 -0
  55. code_puppy/command_line/core_commands.py +792 -0
  56. code_puppy/command_line/diff_menu.py +863 -0
  57. code_puppy/command_line/load_context_completion.py +15 -22
  58. code_puppy/command_line/mcp/base.py +0 -3
  59. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  60. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  61. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  62. code_puppy/command_line/mcp/edit_command.py +148 -0
  63. code_puppy/command_line/mcp/handler.py +9 -4
  64. code_puppy/command_line/mcp/help_command.py +6 -5
  65. code_puppy/command_line/mcp/install_command.py +15 -26
  66. code_puppy/command_line/mcp/install_menu.py +685 -0
  67. code_puppy/command_line/mcp/list_command.py +2 -2
  68. code_puppy/command_line/mcp/logs_command.py +174 -65
  69. code_puppy/command_line/mcp/remove_command.py +2 -2
  70. code_puppy/command_line/mcp/restart_command.py +12 -4
  71. code_puppy/command_line/mcp/search_command.py +16 -10
  72. code_puppy/command_line/mcp/start_all_command.py +18 -6
  73. code_puppy/command_line/mcp/start_command.py +47 -25
  74. code_puppy/command_line/mcp/status_command.py +4 -5
  75. code_puppy/command_line/mcp/stop_all_command.py +7 -1
  76. code_puppy/command_line/mcp/stop_command.py +8 -4
  77. code_puppy/command_line/mcp/test_command.py +2 -2
  78. code_puppy/command_line/mcp/wizard_utils.py +20 -16
  79. code_puppy/command_line/mcp_completion.py +174 -0
  80. code_puppy/command_line/model_picker_completion.py +75 -25
  81. code_puppy/command_line/model_settings_menu.py +884 -0
  82. code_puppy/command_line/motd.py +14 -8
  83. code_puppy/command_line/onboarding_slides.py +179 -0
  84. code_puppy/command_line/onboarding_wizard.py +340 -0
  85. code_puppy/command_line/pin_command_completion.py +329 -0
  86. code_puppy/command_line/prompt_toolkit_completion.py +463 -63
  87. code_puppy/command_line/session_commands.py +296 -0
  88. code_puppy/command_line/utils.py +54 -0
  89. code_puppy/config.py +898 -112
  90. code_puppy/error_logging.py +118 -0
  91. code_puppy/gemini_code_assist.py +385 -0
  92. code_puppy/gemini_model.py +602 -0
  93. code_puppy/http_utils.py +210 -148
  94. code_puppy/keymap.py +128 -0
  95. code_puppy/main.py +5 -698
  96. code_puppy/mcp_/__init__.py +17 -0
  97. code_puppy/mcp_/async_lifecycle.py +35 -4
  98. code_puppy/mcp_/blocking_startup.py +70 -43
  99. code_puppy/mcp_/captured_stdio_server.py +2 -2
  100. code_puppy/mcp_/config_wizard.py +4 -4
  101. code_puppy/mcp_/dashboard.py +15 -6
  102. code_puppy/mcp_/managed_server.py +65 -38
  103. code_puppy/mcp_/manager.py +146 -52
  104. code_puppy/mcp_/mcp_logs.py +224 -0
  105. code_puppy/mcp_/registry.py +6 -6
  106. code_puppy/mcp_/server_registry_catalog.py +24 -5
  107. code_puppy/messaging/__init__.py +199 -2
  108. code_puppy/messaging/bus.py +610 -0
  109. code_puppy/messaging/commands.py +167 -0
  110. code_puppy/messaging/markdown_patches.py +57 -0
  111. code_puppy/messaging/message_queue.py +17 -48
  112. code_puppy/messaging/messages.py +500 -0
  113. code_puppy/messaging/queue_console.py +1 -24
  114. code_puppy/messaging/renderers.py +43 -146
  115. code_puppy/messaging/rich_renderer.py +1027 -0
  116. code_puppy/messaging/spinner/__init__.py +21 -5
  117. code_puppy/messaging/spinner/console_spinner.py +86 -51
  118. code_puppy/messaging/subagent_console.py +461 -0
  119. code_puppy/model_factory.py +634 -83
  120. code_puppy/model_utils.py +167 -0
  121. code_puppy/models.json +66 -68
  122. code_puppy/models_dev_api.json +1 -0
  123. code_puppy/models_dev_parser.py +592 -0
  124. code_puppy/plugins/__init__.py +164 -10
  125. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  126. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  127. code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
  128. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  129. code_puppy/plugins/antigravity_oauth/constants.py +136 -0
  130. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  131. code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
  132. code_puppy/plugins/antigravity_oauth/storage.py +271 -0
  133. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  134. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  135. code_puppy/plugins/antigravity_oauth/transport.py +767 -0
  136. code_puppy/plugins/antigravity_oauth/utils.py +169 -0
  137. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  138. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  139. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
  140. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
  141. code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
  142. code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
  143. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  144. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  145. code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
  146. code_puppy/plugins/claude_code_oauth/config.py +50 -0
  147. code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
  148. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  149. code_puppy/plugins/claude_code_oauth/utils.py +518 -0
  150. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  151. code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
  152. code_puppy/plugins/example_custom_command/README.md +280 -0
  153. code_puppy/plugins/example_custom_command/register_callbacks.py +2 -2
  154. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  155. code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
  156. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  157. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  158. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  159. code_puppy/plugins/oauth_puppy_html.py +228 -0
  160. code_puppy/plugins/shell_safety/__init__.py +6 -0
  161. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  162. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  163. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  164. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  165. code_puppy/prompts/codex_system_prompt.md +310 -0
  166. code_puppy/pydantic_patches.py +131 -0
  167. code_puppy/reopenable_async_client.py +8 -8
  168. code_puppy/round_robin_model.py +9 -12
  169. code_puppy/session_storage.py +2 -1
  170. code_puppy/status_display.py +21 -4
  171. code_puppy/summarization_agent.py +41 -13
  172. code_puppy/terminal_utils.py +418 -0
  173. code_puppy/tools/__init__.py +37 -1
  174. code_puppy/tools/agent_tools.py +536 -52
  175. code_puppy/tools/browser/__init__.py +37 -0
  176. code_puppy/tools/browser/browser_control.py +19 -23
  177. code_puppy/tools/browser/browser_interactions.py +41 -48
  178. code_puppy/tools/browser/browser_locators.py +36 -38
  179. code_puppy/tools/browser/browser_manager.py +316 -0
  180. code_puppy/tools/browser/browser_navigation.py +16 -16
  181. code_puppy/tools/browser/browser_screenshot.py +79 -143
  182. code_puppy/tools/browser/browser_scripts.py +32 -42
  183. code_puppy/tools/browser/browser_workflows.py +44 -27
  184. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  185. code_puppy/tools/browser/terminal_command_tools.py +521 -0
  186. code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
  187. code_puppy/tools/browser/terminal_tools.py +525 -0
  188. code_puppy/tools/command_runner.py +930 -147
  189. code_puppy/tools/common.py +1113 -5
  190. code_puppy/tools/display.py +84 -0
  191. code_puppy/tools/file_modifications.py +288 -89
  192. code_puppy/tools/file_operations.py +226 -154
  193. code_puppy/tools/subagent_context.py +158 -0
  194. code_puppy/uvx_detection.py +242 -0
  195. code_puppy/version_checker.py +30 -11
  196. code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
  197. code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
  198. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/METADATA +149 -75
  199. code_puppy-0.0.366.dist-info/RECORD +217 -0
  200. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
  201. code_puppy/command_line/mcp/add_command.py +0 -183
  202. code_puppy/messaging/spinner/textual_spinner.py +0 -106
  203. code_puppy/tools/browser/camoufox_manager.py +0 -216
  204. code_puppy/tools/browser/vqa_agent.py +0 -70
  205. code_puppy/tui/__init__.py +0 -10
  206. code_puppy/tui/app.py +0 -1105
  207. code_puppy/tui/components/__init__.py +0 -21
  208. code_puppy/tui/components/chat_view.py +0 -551
  209. code_puppy/tui/components/command_history_modal.py +0 -218
  210. code_puppy/tui/components/copy_button.py +0 -139
  211. code_puppy/tui/components/custom_widgets.py +0 -63
  212. code_puppy/tui/components/human_input_modal.py +0 -175
  213. code_puppy/tui/components/input_area.py +0 -167
  214. code_puppy/tui/components/sidebar.py +0 -309
  215. code_puppy/tui/components/status_bar.py +0 -185
  216. code_puppy/tui/messages.py +0 -27
  217. code_puppy/tui/models/__init__.py +0 -8
  218. code_puppy/tui/models/chat_message.py +0 -25
  219. code_puppy/tui/models/command_history.py +0 -89
  220. code_puppy/tui/models/enums.py +0 -24
  221. code_puppy/tui/screens/__init__.py +0 -17
  222. code_puppy/tui/screens/autosave_picker.py +0 -175
  223. code_puppy/tui/screens/help.py +0 -130
  224. code_puppy/tui/screens/mcp_install_wizard.py +0 -803
  225. code_puppy/tui/screens/settings.py +0 -306
  226. code_puppy/tui/screens/tools.py +0 -74
  227. code_puppy/tui_state.py +0 -55
  228. code_puppy-0.0.214.data/data/code_puppy/models.json +0 -112
  229. code_puppy-0.0.214.dist-info/RECORD +0 -131
  230. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +0 -0
  231. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
@@ -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
@@ -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()
@@ -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()