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
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"""Interactive terminal UI for selecting agents.
|
|
2
|
+
|
|
3
|
+
Provides a split-panel interface for browsing and selecting agents
|
|
4
|
+
with live preview of agent details.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
import unicodedata
|
|
10
|
+
from typing import List, Optional, Tuple
|
|
11
|
+
|
|
12
|
+
from prompt_toolkit.application import Application
|
|
13
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
14
|
+
from prompt_toolkit.layout import Dimension, Layout, VSplit, Window
|
|
15
|
+
from prompt_toolkit.layout.controls import FormattedTextControl
|
|
16
|
+
from prompt_toolkit.widgets import Frame
|
|
17
|
+
|
|
18
|
+
from code_puppy.agents import (
|
|
19
|
+
get_agent_descriptions,
|
|
20
|
+
get_available_agents,
|
|
21
|
+
get_current_agent,
|
|
22
|
+
)
|
|
23
|
+
from code_puppy.tools.command_runner import set_awaiting_user_input
|
|
24
|
+
|
|
25
|
+
PAGE_SIZE = 10 # Agents per page
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _sanitize_display_text(text: str) -> str:
|
|
29
|
+
"""Remove or replace characters that cause terminal rendering issues.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
text: Text that may contain emojis or wide characters
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Sanitized text safe for prompt_toolkit rendering
|
|
36
|
+
"""
|
|
37
|
+
# Keep only characters that render cleanly in terminals
|
|
38
|
+
# Be aggressive about stripping anything that could cause width issues
|
|
39
|
+
result = []
|
|
40
|
+
for char in text:
|
|
41
|
+
# Get unicode category
|
|
42
|
+
cat = unicodedata.category(char)
|
|
43
|
+
# Categories to KEEP:
|
|
44
|
+
# - L* (Letters): Lu, Ll, Lt, Lm, Lo
|
|
45
|
+
# - N* (Numbers): Nd, Nl, No
|
|
46
|
+
# - P* (Punctuation): Pc, Pd, Ps, Pe, Pi, Pf, Po
|
|
47
|
+
# - Zs (Space separator)
|
|
48
|
+
# - Sm (Math symbols like +, -, =)
|
|
49
|
+
# - Sc (Currency symbols like $, €)
|
|
50
|
+
# - Sk (Modifier symbols)
|
|
51
|
+
#
|
|
52
|
+
# Categories to SKIP (cause rendering issues):
|
|
53
|
+
# - So (Symbol, other) - emojis
|
|
54
|
+
# - Cf (Format) - ZWJ, etc.
|
|
55
|
+
# - Mn (Mark, nonspacing) - combining characters
|
|
56
|
+
# - Mc (Mark, spacing combining)
|
|
57
|
+
# - Me (Mark, enclosing)
|
|
58
|
+
# - Cn (Not assigned)
|
|
59
|
+
# - Co (Private use)
|
|
60
|
+
# - Cs (Surrogate)
|
|
61
|
+
safe_categories = (
|
|
62
|
+
"Lu",
|
|
63
|
+
"Ll",
|
|
64
|
+
"Lt",
|
|
65
|
+
"Lm",
|
|
66
|
+
"Lo", # Letters
|
|
67
|
+
"Nd",
|
|
68
|
+
"Nl",
|
|
69
|
+
"No", # Numbers
|
|
70
|
+
"Pc",
|
|
71
|
+
"Pd",
|
|
72
|
+
"Ps",
|
|
73
|
+
"Pe",
|
|
74
|
+
"Pi",
|
|
75
|
+
"Pf",
|
|
76
|
+
"Po", # Punctuation
|
|
77
|
+
"Zs", # Space
|
|
78
|
+
"Sm",
|
|
79
|
+
"Sc",
|
|
80
|
+
"Sk", # Safe symbols (math, currency, modifier)
|
|
81
|
+
)
|
|
82
|
+
if cat in safe_categories:
|
|
83
|
+
result.append(char)
|
|
84
|
+
|
|
85
|
+
# Clean up any double spaces left behind and strip
|
|
86
|
+
cleaned = " ".join("".join(result).split())
|
|
87
|
+
return cleaned
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _get_agent_entries() -> List[Tuple[str, str, str]]:
|
|
91
|
+
"""Get all agents with their display names and descriptions.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
List of tuples (agent_name, display_name, description) sorted by name.
|
|
95
|
+
"""
|
|
96
|
+
available = get_available_agents()
|
|
97
|
+
descriptions = get_agent_descriptions()
|
|
98
|
+
|
|
99
|
+
entries = []
|
|
100
|
+
for name, display_name in available.items():
|
|
101
|
+
description = descriptions.get(name, "No description available")
|
|
102
|
+
entries.append((name, display_name, description))
|
|
103
|
+
|
|
104
|
+
# Sort alphabetically by agent name
|
|
105
|
+
entries.sort(key=lambda x: x[0].lower())
|
|
106
|
+
return entries
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _render_menu_panel(
|
|
110
|
+
entries: List[Tuple[str, str, str]],
|
|
111
|
+
page: int,
|
|
112
|
+
selected_idx: int,
|
|
113
|
+
current_agent_name: str,
|
|
114
|
+
) -> List:
|
|
115
|
+
"""Render the left menu panel with pagination.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
entries: List of (name, display_name, description) tuples
|
|
119
|
+
page: Current page number (0-indexed)
|
|
120
|
+
selected_idx: Currently selected index (global)
|
|
121
|
+
current_agent_name: Name of the current active agent
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
List of (style, text) tuples for FormattedTextControl
|
|
125
|
+
"""
|
|
126
|
+
lines = []
|
|
127
|
+
total_pages = (len(entries) + PAGE_SIZE - 1) // PAGE_SIZE if entries else 1
|
|
128
|
+
start_idx = page * PAGE_SIZE
|
|
129
|
+
end_idx = min(start_idx + PAGE_SIZE, len(entries))
|
|
130
|
+
|
|
131
|
+
lines.append(("bold", "Agents"))
|
|
132
|
+
lines.append(("fg:ansibrightblack", f" (Page {page + 1}/{total_pages})"))
|
|
133
|
+
lines.append(("", "\n\n"))
|
|
134
|
+
|
|
135
|
+
if not entries:
|
|
136
|
+
lines.append(("fg:yellow", " No agents found."))
|
|
137
|
+
lines.append(("", "\n\n"))
|
|
138
|
+
else:
|
|
139
|
+
# Show agents for current page
|
|
140
|
+
for i in range(start_idx, end_idx):
|
|
141
|
+
name, display_name, _ = entries[i]
|
|
142
|
+
is_selected = i == selected_idx
|
|
143
|
+
is_current = name == current_agent_name
|
|
144
|
+
|
|
145
|
+
# Sanitize display name to avoid emoji rendering issues
|
|
146
|
+
safe_display_name = _sanitize_display_text(display_name)
|
|
147
|
+
|
|
148
|
+
# Build the line
|
|
149
|
+
if is_selected:
|
|
150
|
+
lines.append(("fg:ansigreen", "▶ "))
|
|
151
|
+
lines.append(("fg:ansigreen bold", safe_display_name))
|
|
152
|
+
else:
|
|
153
|
+
lines.append(("", " "))
|
|
154
|
+
lines.append(("", safe_display_name))
|
|
155
|
+
|
|
156
|
+
# Add current marker
|
|
157
|
+
if is_current:
|
|
158
|
+
lines.append(("fg:ansicyan", " ← current"))
|
|
159
|
+
|
|
160
|
+
lines.append(("", "\n"))
|
|
161
|
+
|
|
162
|
+
# Navigation hints
|
|
163
|
+
lines.append(("", "\n"))
|
|
164
|
+
lines.append(("fg:ansibrightblack", " ↑↓ "))
|
|
165
|
+
lines.append(("", "Navigate\n"))
|
|
166
|
+
lines.append(("fg:ansibrightblack", " ←→ "))
|
|
167
|
+
lines.append(("", "Page\n"))
|
|
168
|
+
lines.append(("fg:green", " Enter "))
|
|
169
|
+
lines.append(("", "Select\n"))
|
|
170
|
+
lines.append(("fg:ansibrightred", " Ctrl+C "))
|
|
171
|
+
lines.append(("", "Cancel"))
|
|
172
|
+
|
|
173
|
+
return lines
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _render_preview_panel(
|
|
177
|
+
entry: Optional[Tuple[str, str, str]],
|
|
178
|
+
current_agent_name: str,
|
|
179
|
+
) -> List:
|
|
180
|
+
"""Render the right preview panel with agent details.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
entry: Tuple of (name, display_name, description) or None
|
|
184
|
+
current_agent_name: Name of the current active agent
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
List of (style, text) tuples for FormattedTextControl
|
|
188
|
+
"""
|
|
189
|
+
lines = []
|
|
190
|
+
|
|
191
|
+
lines.append(("dim cyan", " AGENT DETAILS"))
|
|
192
|
+
lines.append(("", "\n\n"))
|
|
193
|
+
|
|
194
|
+
if not entry:
|
|
195
|
+
lines.append(("fg:yellow", " No agent selected."))
|
|
196
|
+
lines.append(("", "\n"))
|
|
197
|
+
return lines
|
|
198
|
+
|
|
199
|
+
name, display_name, description = entry
|
|
200
|
+
is_current = name == current_agent_name
|
|
201
|
+
|
|
202
|
+
# Sanitize text to avoid emoji rendering issues
|
|
203
|
+
safe_display_name = _sanitize_display_text(display_name)
|
|
204
|
+
safe_description = _sanitize_display_text(description)
|
|
205
|
+
|
|
206
|
+
# Agent name (identifier)
|
|
207
|
+
lines.append(("bold", "Name: "))
|
|
208
|
+
lines.append(("", name))
|
|
209
|
+
lines.append(("", "\n\n"))
|
|
210
|
+
|
|
211
|
+
# Display name
|
|
212
|
+
lines.append(("bold", "Display Name: "))
|
|
213
|
+
lines.append(("fg:ansicyan", safe_display_name))
|
|
214
|
+
lines.append(("", "\n\n"))
|
|
215
|
+
|
|
216
|
+
# Description
|
|
217
|
+
lines.append(("bold", "Description:"))
|
|
218
|
+
lines.append(("", "\n"))
|
|
219
|
+
|
|
220
|
+
# Wrap description to fit panel
|
|
221
|
+
desc_lines = safe_description.split("\n")
|
|
222
|
+
for desc_line in desc_lines:
|
|
223
|
+
# Word wrap long lines
|
|
224
|
+
words = desc_line.split()
|
|
225
|
+
current_line = ""
|
|
226
|
+
for word in words:
|
|
227
|
+
if len(current_line) + len(word) + 1 > 55:
|
|
228
|
+
lines.append(("fg:ansibrightblack", current_line))
|
|
229
|
+
lines.append(("", "\n"))
|
|
230
|
+
current_line = word
|
|
231
|
+
else:
|
|
232
|
+
if current_line == "":
|
|
233
|
+
current_line = word
|
|
234
|
+
else:
|
|
235
|
+
current_line += " " + word
|
|
236
|
+
if current_line.strip():
|
|
237
|
+
lines.append(("fg:ansibrightblack", current_line))
|
|
238
|
+
lines.append(("", "\n"))
|
|
239
|
+
|
|
240
|
+
lines.append(("", "\n"))
|
|
241
|
+
|
|
242
|
+
# Current status
|
|
243
|
+
lines.append(("bold", " Status: "))
|
|
244
|
+
if is_current:
|
|
245
|
+
lines.append(("fg:ansigreen bold", "✓ Currently Active"))
|
|
246
|
+
else:
|
|
247
|
+
lines.append(("fg:ansibrightblack", "Not active"))
|
|
248
|
+
lines.append(("", "\n"))
|
|
249
|
+
|
|
250
|
+
return lines
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
async def interactive_agent_picker() -> Optional[str]:
|
|
254
|
+
"""Show interactive terminal UI to select an agent.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Agent name to switch to, or None if cancelled.
|
|
258
|
+
"""
|
|
259
|
+
entries = _get_agent_entries()
|
|
260
|
+
current_agent = get_current_agent()
|
|
261
|
+
current_agent_name = current_agent.name if current_agent else ""
|
|
262
|
+
|
|
263
|
+
if not entries:
|
|
264
|
+
from code_puppy.messaging import emit_info
|
|
265
|
+
|
|
266
|
+
emit_info("No agents found.")
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
# State
|
|
270
|
+
selected_idx = [0] # Current selection (global index)
|
|
271
|
+
current_page = [0] # Current page
|
|
272
|
+
result = [None] # Selected agent name
|
|
273
|
+
|
|
274
|
+
total_pages = (len(entries) + PAGE_SIZE - 1) // PAGE_SIZE
|
|
275
|
+
|
|
276
|
+
def get_current_entry() -> Optional[Tuple[str, str, str]]:
|
|
277
|
+
if 0 <= selected_idx[0] < len(entries):
|
|
278
|
+
return entries[selected_idx[0]]
|
|
279
|
+
return None
|
|
280
|
+
|
|
281
|
+
# Build UI
|
|
282
|
+
menu_control = FormattedTextControl(text="")
|
|
283
|
+
preview_control = FormattedTextControl(text="")
|
|
284
|
+
|
|
285
|
+
def update_display():
|
|
286
|
+
"""Update both panels."""
|
|
287
|
+
menu_control.text = _render_menu_panel(
|
|
288
|
+
entries, current_page[0], selected_idx[0], current_agent_name
|
|
289
|
+
)
|
|
290
|
+
preview_control.text = _render_preview_panel(
|
|
291
|
+
get_current_entry(), current_agent_name
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
menu_window = Window(
|
|
295
|
+
content=menu_control, wrap_lines=False, width=Dimension(weight=35)
|
|
296
|
+
)
|
|
297
|
+
preview_window = Window(
|
|
298
|
+
content=preview_control, wrap_lines=False, width=Dimension(weight=65)
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
menu_frame = Frame(menu_window, width=Dimension(weight=35), title="Agents")
|
|
302
|
+
preview_frame = Frame(preview_window, width=Dimension(weight=65), title="Preview")
|
|
303
|
+
|
|
304
|
+
root_container = VSplit(
|
|
305
|
+
[
|
|
306
|
+
menu_frame,
|
|
307
|
+
preview_frame,
|
|
308
|
+
]
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Key bindings
|
|
312
|
+
kb = KeyBindings()
|
|
313
|
+
|
|
314
|
+
@kb.add("up")
|
|
315
|
+
def _(event):
|
|
316
|
+
if selected_idx[0] > 0:
|
|
317
|
+
selected_idx[0] -= 1
|
|
318
|
+
# Update page if needed
|
|
319
|
+
current_page[0] = selected_idx[0] // PAGE_SIZE
|
|
320
|
+
update_display()
|
|
321
|
+
|
|
322
|
+
@kb.add("down")
|
|
323
|
+
def _(event):
|
|
324
|
+
if selected_idx[0] < len(entries) - 1:
|
|
325
|
+
selected_idx[0] += 1
|
|
326
|
+
# Update page if needed
|
|
327
|
+
current_page[0] = selected_idx[0] // PAGE_SIZE
|
|
328
|
+
update_display()
|
|
329
|
+
|
|
330
|
+
@kb.add("left")
|
|
331
|
+
def _(event):
|
|
332
|
+
if current_page[0] > 0:
|
|
333
|
+
current_page[0] -= 1
|
|
334
|
+
selected_idx[0] = current_page[0] * PAGE_SIZE
|
|
335
|
+
update_display()
|
|
336
|
+
|
|
337
|
+
@kb.add("right")
|
|
338
|
+
def _(event):
|
|
339
|
+
if current_page[0] < total_pages - 1:
|
|
340
|
+
current_page[0] += 1
|
|
341
|
+
selected_idx[0] = current_page[0] * PAGE_SIZE
|
|
342
|
+
update_display()
|
|
343
|
+
|
|
344
|
+
@kb.add("enter")
|
|
345
|
+
def _(event):
|
|
346
|
+
entry = get_current_entry()
|
|
347
|
+
if entry:
|
|
348
|
+
result[0] = entry[0] # Store agent name
|
|
349
|
+
event.app.exit()
|
|
350
|
+
|
|
351
|
+
@kb.add("c-c")
|
|
352
|
+
def _(event):
|
|
353
|
+
result[0] = None
|
|
354
|
+
event.app.exit()
|
|
355
|
+
|
|
356
|
+
layout = Layout(root_container)
|
|
357
|
+
app = Application(
|
|
358
|
+
layout=layout,
|
|
359
|
+
key_bindings=kb,
|
|
360
|
+
full_screen=False,
|
|
361
|
+
mouse_support=False,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
set_awaiting_user_input(True)
|
|
365
|
+
|
|
366
|
+
# Enter alternate screen buffer once for entire session
|
|
367
|
+
sys.stdout.write("\033[?1049h") # Enter alternate buffer
|
|
368
|
+
sys.stdout.write("\033[2J\033[H") # Clear and home
|
|
369
|
+
sys.stdout.flush()
|
|
370
|
+
time.sleep(0.05)
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
# Initial display
|
|
374
|
+
update_display()
|
|
375
|
+
|
|
376
|
+
# Clear the current buffer
|
|
377
|
+
sys.stdout.write("\033[2J\033[H")
|
|
378
|
+
sys.stdout.flush()
|
|
379
|
+
|
|
380
|
+
# Run application
|
|
381
|
+
await app.run_async()
|
|
382
|
+
|
|
383
|
+
finally:
|
|
384
|
+
# Exit alternate screen buffer once at end
|
|
385
|
+
sys.stdout.write("\033[?1049l") # Exit alternate buffer
|
|
386
|
+
sys.stdout.flush()
|
|
387
|
+
# Reset awaiting input flag
|
|
388
|
+
set_awaiting_user_input(False)
|
|
389
|
+
|
|
390
|
+
# Clear exit message
|
|
391
|
+
from code_puppy.messaging import emit_info
|
|
392
|
+
|
|
393
|
+
emit_info("✓ Exited agent picker")
|
|
394
|
+
|
|
395
|
+
return result[0]
|