codepp 0.0.437__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 (288) hide show
  1. code_puppy/__init__.py +10 -0
  2. code_puppy/__main__.py +10 -0
  3. code_puppy/agents/__init__.py +31 -0
  4. code_puppy/agents/agent_c_reviewer.py +155 -0
  5. code_puppy/agents/agent_code_puppy.py +117 -0
  6. code_puppy/agents/agent_code_reviewer.py +90 -0
  7. code_puppy/agents/agent_cpp_reviewer.py +132 -0
  8. code_puppy/agents/agent_creator_agent.py +638 -0
  9. code_puppy/agents/agent_golang_reviewer.py +151 -0
  10. code_puppy/agents/agent_helios.py +124 -0
  11. code_puppy/agents/agent_javascript_reviewer.py +160 -0
  12. code_puppy/agents/agent_manager.py +742 -0
  13. code_puppy/agents/agent_pack_leader.py +385 -0
  14. code_puppy/agents/agent_planning.py +165 -0
  15. code_puppy/agents/agent_python_programmer.py +169 -0
  16. code_puppy/agents/agent_python_reviewer.py +90 -0
  17. code_puppy/agents/agent_qa_expert.py +163 -0
  18. code_puppy/agents/agent_qa_kitten.py +208 -0
  19. code_puppy/agents/agent_scheduler.py +121 -0
  20. code_puppy/agents/agent_security_auditor.py +181 -0
  21. code_puppy/agents/agent_terminal_qa.py +323 -0
  22. code_puppy/agents/agent_typescript_reviewer.py +166 -0
  23. code_puppy/agents/base_agent.py +2156 -0
  24. code_puppy/agents/event_stream_handler.py +348 -0
  25. code_puppy/agents/json_agent.py +202 -0
  26. code_puppy/agents/pack/__init__.py +34 -0
  27. code_puppy/agents/pack/bloodhound.py +304 -0
  28. code_puppy/agents/pack/husky.py +327 -0
  29. code_puppy/agents/pack/retriever.py +393 -0
  30. code_puppy/agents/pack/shepherd.py +348 -0
  31. code_puppy/agents/pack/terrier.py +287 -0
  32. code_puppy/agents/pack/watchdog.py +367 -0
  33. code_puppy/agents/prompt_reviewer.py +145 -0
  34. code_puppy/agents/subagent_stream_handler.py +276 -0
  35. code_puppy/api/__init__.py +13 -0
  36. code_puppy/api/app.py +169 -0
  37. code_puppy/api/main.py +21 -0
  38. code_puppy/api/pty_manager.py +453 -0
  39. code_puppy/api/routers/__init__.py +12 -0
  40. code_puppy/api/routers/agents.py +36 -0
  41. code_puppy/api/routers/commands.py +217 -0
  42. code_puppy/api/routers/config.py +75 -0
  43. code_puppy/api/routers/sessions.py +234 -0
  44. code_puppy/api/templates/terminal.html +361 -0
  45. code_puppy/api/websocket.py +154 -0
  46. code_puppy/callbacks.py +692 -0
  47. code_puppy/chatgpt_codex_client.py +338 -0
  48. code_puppy/claude_cache_client.py +672 -0
  49. code_puppy/cli_runner.py +1073 -0
  50. code_puppy/command_line/__init__.py +1 -0
  51. code_puppy/command_line/add_model_menu.py +1092 -0
  52. code_puppy/command_line/agent_menu.py +662 -0
  53. code_puppy/command_line/attachments.py +395 -0
  54. code_puppy/command_line/autosave_menu.py +704 -0
  55. code_puppy/command_line/clipboard.py +527 -0
  56. code_puppy/command_line/colors_menu.py +532 -0
  57. code_puppy/command_line/command_handler.py +293 -0
  58. code_puppy/command_line/command_registry.py +150 -0
  59. code_puppy/command_line/config_commands.py +719 -0
  60. code_puppy/command_line/core_commands.py +867 -0
  61. code_puppy/command_line/diff_menu.py +865 -0
  62. code_puppy/command_line/file_path_completion.py +73 -0
  63. code_puppy/command_line/load_context_completion.py +52 -0
  64. code_puppy/command_line/mcp/__init__.py +10 -0
  65. code_puppy/command_line/mcp/base.py +32 -0
  66. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  67. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  68. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  69. code_puppy/command_line/mcp/edit_command.py +148 -0
  70. code_puppy/command_line/mcp/handler.py +138 -0
  71. code_puppy/command_line/mcp/help_command.py +147 -0
  72. code_puppy/command_line/mcp/install_command.py +214 -0
  73. code_puppy/command_line/mcp/install_menu.py +705 -0
  74. code_puppy/command_line/mcp/list_command.py +94 -0
  75. code_puppy/command_line/mcp/logs_command.py +235 -0
  76. code_puppy/command_line/mcp/remove_command.py +82 -0
  77. code_puppy/command_line/mcp/restart_command.py +100 -0
  78. code_puppy/command_line/mcp/search_command.py +123 -0
  79. code_puppy/command_line/mcp/start_all_command.py +135 -0
  80. code_puppy/command_line/mcp/start_command.py +117 -0
  81. code_puppy/command_line/mcp/status_command.py +184 -0
  82. code_puppy/command_line/mcp/stop_all_command.py +112 -0
  83. code_puppy/command_line/mcp/stop_command.py +80 -0
  84. code_puppy/command_line/mcp/test_command.py +107 -0
  85. code_puppy/command_line/mcp/utils.py +129 -0
  86. code_puppy/command_line/mcp/wizard_utils.py +334 -0
  87. code_puppy/command_line/mcp_completion.py +174 -0
  88. code_puppy/command_line/model_picker_completion.py +197 -0
  89. code_puppy/command_line/model_settings_menu.py +932 -0
  90. code_puppy/command_line/motd.py +96 -0
  91. code_puppy/command_line/onboarding_slides.py +179 -0
  92. code_puppy/command_line/onboarding_wizard.py +342 -0
  93. code_puppy/command_line/pin_command_completion.py +329 -0
  94. code_puppy/command_line/prompt_toolkit_completion.py +846 -0
  95. code_puppy/command_line/session_commands.py +302 -0
  96. code_puppy/command_line/shell_passthrough.py +145 -0
  97. code_puppy/command_line/skills_completion.py +160 -0
  98. code_puppy/command_line/uc_menu.py +893 -0
  99. code_puppy/command_line/utils.py +93 -0
  100. code_puppy/command_line/wiggum_state.py +78 -0
  101. code_puppy/config.py +1770 -0
  102. code_puppy/error_logging.py +134 -0
  103. code_puppy/gemini_code_assist.py +385 -0
  104. code_puppy/gemini_model.py +754 -0
  105. code_puppy/hook_engine/README.md +105 -0
  106. code_puppy/hook_engine/__init__.py +21 -0
  107. code_puppy/hook_engine/aliases.py +155 -0
  108. code_puppy/hook_engine/engine.py +221 -0
  109. code_puppy/hook_engine/executor.py +296 -0
  110. code_puppy/hook_engine/matcher.py +156 -0
  111. code_puppy/hook_engine/models.py +240 -0
  112. code_puppy/hook_engine/registry.py +106 -0
  113. code_puppy/hook_engine/validator.py +144 -0
  114. code_puppy/http_utils.py +361 -0
  115. code_puppy/keymap.py +128 -0
  116. code_puppy/main.py +10 -0
  117. code_puppy/mcp_/__init__.py +66 -0
  118. code_puppy/mcp_/async_lifecycle.py +286 -0
  119. code_puppy/mcp_/blocking_startup.py +469 -0
  120. code_puppy/mcp_/captured_stdio_server.py +275 -0
  121. code_puppy/mcp_/circuit_breaker.py +290 -0
  122. code_puppy/mcp_/config_wizard.py +507 -0
  123. code_puppy/mcp_/dashboard.py +308 -0
  124. code_puppy/mcp_/error_isolation.py +407 -0
  125. code_puppy/mcp_/examples/retry_example.py +226 -0
  126. code_puppy/mcp_/health_monitor.py +589 -0
  127. code_puppy/mcp_/managed_server.py +428 -0
  128. code_puppy/mcp_/manager.py +807 -0
  129. code_puppy/mcp_/mcp_logs.py +224 -0
  130. code_puppy/mcp_/registry.py +451 -0
  131. code_puppy/mcp_/retry_manager.py +337 -0
  132. code_puppy/mcp_/server_registry_catalog.py +1126 -0
  133. code_puppy/mcp_/status_tracker.py +355 -0
  134. code_puppy/mcp_/system_tools.py +209 -0
  135. code_puppy/mcp_prompts/__init__.py +1 -0
  136. code_puppy/mcp_prompts/hook_creator.py +103 -0
  137. code_puppy/messaging/__init__.py +255 -0
  138. code_puppy/messaging/bus.py +613 -0
  139. code_puppy/messaging/commands.py +167 -0
  140. code_puppy/messaging/markdown_patches.py +57 -0
  141. code_puppy/messaging/message_queue.py +361 -0
  142. code_puppy/messaging/messages.py +569 -0
  143. code_puppy/messaging/queue_console.py +271 -0
  144. code_puppy/messaging/renderers.py +311 -0
  145. code_puppy/messaging/rich_renderer.py +1158 -0
  146. code_puppy/messaging/spinner/__init__.py +83 -0
  147. code_puppy/messaging/spinner/console_spinner.py +240 -0
  148. code_puppy/messaging/spinner/spinner_base.py +95 -0
  149. code_puppy/messaging/subagent_console.py +460 -0
  150. code_puppy/model_factory.py +848 -0
  151. code_puppy/model_switching.py +63 -0
  152. code_puppy/model_utils.py +168 -0
  153. code_puppy/models.json +174 -0
  154. code_puppy/models_dev_api.json +1 -0
  155. code_puppy/models_dev_parser.py +592 -0
  156. code_puppy/plugins/__init__.py +186 -0
  157. code_puppy/plugins/agent_skills/__init__.py +22 -0
  158. code_puppy/plugins/agent_skills/config.py +175 -0
  159. code_puppy/plugins/agent_skills/discovery.py +136 -0
  160. code_puppy/plugins/agent_skills/downloader.py +392 -0
  161. code_puppy/plugins/agent_skills/installer.py +22 -0
  162. code_puppy/plugins/agent_skills/metadata.py +219 -0
  163. code_puppy/plugins/agent_skills/prompt_builder.py +60 -0
  164. code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
  165. code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
  166. code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
  167. code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
  168. code_puppy/plugins/agent_skills/skills_menu.py +781 -0
  169. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  170. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  171. code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
  172. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  173. code_puppy/plugins/antigravity_oauth/constants.py +133 -0
  174. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  175. code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
  176. code_puppy/plugins/antigravity_oauth/storage.py +288 -0
  177. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  178. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  179. code_puppy/plugins/antigravity_oauth/transport.py +863 -0
  180. code_puppy/plugins/antigravity_oauth/utils.py +168 -0
  181. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  182. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  183. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +329 -0
  184. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
  185. code_puppy/plugins/chatgpt_oauth/test_plugin.py +301 -0
  186. code_puppy/plugins/chatgpt_oauth/utils.py +523 -0
  187. code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
  188. code_puppy/plugins/claude_code_hooks/config.py +137 -0
  189. code_puppy/plugins/claude_code_hooks/register_callbacks.py +175 -0
  190. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  191. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  192. code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  193. code_puppy/plugins/claude_code_oauth/config.py +52 -0
  194. code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
  195. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  196. code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
  197. code_puppy/plugins/claude_code_oauth/utils.py +640 -0
  198. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  199. code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
  200. code_puppy/plugins/example_custom_command/README.md +280 -0
  201. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  202. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  203. code_puppy/plugins/file_permission_handler/register_callbacks.py +470 -0
  204. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  205. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  206. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  207. code_puppy/plugins/hook_creator/__init__.py +1 -0
  208. code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
  209. code_puppy/plugins/hook_manager/__init__.py +1 -0
  210. code_puppy/plugins/hook_manager/config.py +290 -0
  211. code_puppy/plugins/hook_manager/hooks_menu.py +564 -0
  212. code_puppy/plugins/hook_manager/register_callbacks.py +227 -0
  213. code_puppy/plugins/oauth_puppy_html.py +228 -0
  214. code_puppy/plugins/scheduler/__init__.py +1 -0
  215. code_puppy/plugins/scheduler/register_callbacks.py +88 -0
  216. code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
  217. code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
  218. code_puppy/plugins/shell_safety/__init__.py +6 -0
  219. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  220. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  221. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  222. code_puppy/plugins/synthetic_status/__init__.py +1 -0
  223. code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
  224. code_puppy/plugins/synthetic_status/status_api.py +147 -0
  225. code_puppy/plugins/universal_constructor/__init__.py +13 -0
  226. code_puppy/plugins/universal_constructor/models.py +138 -0
  227. code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  228. code_puppy/plugins/universal_constructor/registry.py +302 -0
  229. code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  230. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  231. code_puppy/pydantic_patches.py +356 -0
  232. code_puppy/reopenable_async_client.py +232 -0
  233. code_puppy/round_robin_model.py +150 -0
  234. code_puppy/scheduler/__init__.py +41 -0
  235. code_puppy/scheduler/__main__.py +9 -0
  236. code_puppy/scheduler/cli.py +118 -0
  237. code_puppy/scheduler/config.py +126 -0
  238. code_puppy/scheduler/daemon.py +280 -0
  239. code_puppy/scheduler/executor.py +155 -0
  240. code_puppy/scheduler/platform.py +19 -0
  241. code_puppy/scheduler/platform_unix.py +22 -0
  242. code_puppy/scheduler/platform_win.py +32 -0
  243. code_puppy/session_storage.py +338 -0
  244. code_puppy/status_display.py +257 -0
  245. code_puppy/summarization_agent.py +176 -0
  246. code_puppy/terminal_utils.py +418 -0
  247. code_puppy/tools/__init__.py +501 -0
  248. code_puppy/tools/agent_tools.py +603 -0
  249. code_puppy/tools/ask_user_question/__init__.py +26 -0
  250. code_puppy/tools/ask_user_question/constants.py +73 -0
  251. code_puppy/tools/ask_user_question/demo_tui.py +55 -0
  252. code_puppy/tools/ask_user_question/handler.py +232 -0
  253. code_puppy/tools/ask_user_question/models.py +304 -0
  254. code_puppy/tools/ask_user_question/registration.py +26 -0
  255. code_puppy/tools/ask_user_question/renderers.py +309 -0
  256. code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
  257. code_puppy/tools/ask_user_question/theme.py +155 -0
  258. code_puppy/tools/ask_user_question/tui_loop.py +423 -0
  259. code_puppy/tools/browser/__init__.py +37 -0
  260. code_puppy/tools/browser/browser_control.py +289 -0
  261. code_puppy/tools/browser/browser_interactions.py +545 -0
  262. code_puppy/tools/browser/browser_locators.py +640 -0
  263. code_puppy/tools/browser/browser_manager.py +378 -0
  264. code_puppy/tools/browser/browser_navigation.py +251 -0
  265. code_puppy/tools/browser/browser_screenshot.py +179 -0
  266. code_puppy/tools/browser/browser_scripts.py +462 -0
  267. code_puppy/tools/browser/browser_workflows.py +221 -0
  268. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  269. code_puppy/tools/browser/terminal_command_tools.py +534 -0
  270. code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
  271. code_puppy/tools/browser/terminal_tools.py +525 -0
  272. code_puppy/tools/command_runner.py +1346 -0
  273. code_puppy/tools/common.py +1409 -0
  274. code_puppy/tools/display.py +84 -0
  275. code_puppy/tools/file_modifications.py +886 -0
  276. code_puppy/tools/file_operations.py +802 -0
  277. code_puppy/tools/scheduler_tools.py +412 -0
  278. code_puppy/tools/skills_tools.py +244 -0
  279. code_puppy/tools/subagent_context.py +158 -0
  280. code_puppy/tools/tools_content.py +51 -0
  281. code_puppy/tools/universal_constructor.py +889 -0
  282. code_puppy/uvx_detection.py +242 -0
  283. code_puppy/version_checker.py +82 -0
  284. codepp-0.0.437.dist-info/METADATA +766 -0
  285. codepp-0.0.437.dist-info/RECORD +288 -0
  286. codepp-0.0.437.dist-info/WHEEL +4 -0
  287. codepp-0.0.437.dist-info/entry_points.txt +3 -0
  288. codepp-0.0.437.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,107 @@
1
+ """
2
+ MCP Test Command - Tests connectivity to a specific MCP server.
3
+ """
4
+
5
+ import logging
6
+ from typing import List, Optional
7
+
8
+ from code_puppy.messaging import emit_error, emit_info
9
+
10
+ from .base import MCPCommandBase
11
+ from .utils import find_server_id_by_name, suggest_similar_servers
12
+
13
+ # Configure logging
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class TestCommand(MCPCommandBase):
18
+ """
19
+ Command handler for testing MCP server connectivity.
20
+
21
+ Tests connectivity and basic functionality of a specific MCP server.
22
+ """
23
+
24
+ def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
25
+ """
26
+ Test connectivity to a specific MCP server.
27
+
28
+ Args:
29
+ args: Command arguments, expects [server_name]
30
+ group_id: Optional message group ID for grouping related messages
31
+ """
32
+ if group_id is None:
33
+ group_id = self.generate_group_id()
34
+
35
+ if not args:
36
+ emit_info("Usage: /mcp test <server_name>", message_group=group_id)
37
+ return
38
+
39
+ server_name = args[0]
40
+
41
+ try:
42
+ # Find server by name
43
+ server_id = find_server_id_by_name(self.manager, server_name)
44
+ if not server_id:
45
+ emit_info(f"Server '{server_name}' not found", message_group=group_id)
46
+ suggest_similar_servers(self.manager, server_name, group_id=group_id)
47
+ return
48
+
49
+ # Get managed server
50
+ managed_server = self.manager.get_server(server_id)
51
+ if not managed_server:
52
+ emit_info(
53
+ f"Server '{server_name}' not accessible", message_group=group_id
54
+ )
55
+ return
56
+
57
+ emit_info(
58
+ f"🔍 Testing connectivity to server: {server_name}",
59
+ message_group=group_id,
60
+ )
61
+
62
+ # Basic connectivity test - try to get the pydantic server
63
+ try:
64
+ managed_server.get_pydantic_server() # Test server instantiation
65
+ emit_info(
66
+ "✓ Server instance created successfully", message_group=group_id
67
+ )
68
+
69
+ # Try to get server info if available
70
+ emit_info(
71
+ f" • Server type: {managed_server.config.type}",
72
+ message_group=group_id,
73
+ )
74
+ emit_info(
75
+ f" • Server enabled: {managed_server.is_enabled()}",
76
+ message_group=group_id,
77
+ )
78
+ emit_info(
79
+ f" • Server quarantined: {managed_server.is_quarantined()}",
80
+ message_group=group_id,
81
+ )
82
+
83
+ if not managed_server.is_enabled():
84
+ emit_info(
85
+ " • Server is disabled - enable it with '/mcp start'",
86
+ message_group=group_id,
87
+ )
88
+
89
+ if managed_server.is_quarantined():
90
+ emit_info(
91
+ " • Server is quarantined - may have recent errors",
92
+ message_group=group_id,
93
+ )
94
+
95
+ emit_info(
96
+ f"✓ Connectivity test passed for: {server_name}",
97
+ message_group=group_id,
98
+ )
99
+
100
+ except Exception as test_error:
101
+ emit_info(
102
+ f"✗ Connectivity test failed: {test_error}", message_group=group_id
103
+ )
104
+
105
+ except Exception as e:
106
+ logger.error(f"Error testing server '{server_name}': {e}")
107
+ emit_error(f"Error testing server: {e}", message_group=group_id)
@@ -0,0 +1,129 @@
1
+ """
2
+ MCP Command Utilities - Shared helper functions for MCP command handlers.
3
+
4
+ Provides common utility functions used across multiple MCP command modules.
5
+ """
6
+
7
+ from typing import Optional
8
+
9
+ from rich.text import Text
10
+
11
+ from code_puppy.mcp_.managed_server import ServerState
12
+
13
+
14
+ def format_state_indicator(state: ServerState) -> Text:
15
+ """
16
+ Format a server state with appropriate color and icon.
17
+
18
+ Args:
19
+ state: Server state to format
20
+
21
+ Returns:
22
+ Rich Text object with colored state indicator
23
+ """
24
+ state_map = {
25
+ ServerState.RUNNING: ("✓ Run", "green"),
26
+ ServerState.STOPPED: ("✗ Stop", "red"),
27
+ ServerState.STARTING: ("↗ Start", "yellow"),
28
+ ServerState.STOPPING: ("↙ Stop", "yellow"),
29
+ ServerState.ERROR: ("⚠ Err", "red"),
30
+ ServerState.QUARANTINED: ("⏸ Quar", "yellow"),
31
+ }
32
+
33
+ display, color = state_map.get(state, ("? Unk", "dim"))
34
+ return Text(display, style=color)
35
+
36
+
37
+ def format_uptime(uptime_seconds: Optional[float]) -> str:
38
+ """
39
+ Format uptime in a human-readable format.
40
+
41
+ Args:
42
+ uptime_seconds: Uptime in seconds, or None
43
+
44
+ Returns:
45
+ Formatted uptime string
46
+ """
47
+ if uptime_seconds is None or uptime_seconds <= 0:
48
+ return "-"
49
+
50
+ # Convert to readable format
51
+ if uptime_seconds < 60:
52
+ return f"{int(uptime_seconds)}s"
53
+ elif uptime_seconds < 3600:
54
+ minutes = int(uptime_seconds // 60)
55
+ seconds = int(uptime_seconds % 60)
56
+ return f"{minutes}m {seconds}s"
57
+ else:
58
+ hours = int(uptime_seconds // 3600)
59
+ minutes = int((uptime_seconds % 3600) // 60)
60
+ return f"{hours}h {minutes}m"
61
+
62
+
63
+ def find_server_id_by_name(manager, server_name: str) -> Optional[str]:
64
+ """
65
+ Find a server ID by its name.
66
+
67
+ Args:
68
+ manager: MCP manager instance
69
+ server_name: Name of the server to find
70
+
71
+ Returns:
72
+ Server ID if found, None otherwise
73
+ """
74
+ import logging
75
+
76
+ logger = logging.getLogger(__name__)
77
+
78
+ try:
79
+ servers = manager.list_servers()
80
+ for server in servers:
81
+ if server.name.lower() == server_name.lower():
82
+ return server.id
83
+ return None
84
+ except Exception as e:
85
+ logger.error(f"Error finding server by name '{server_name}': {e}")
86
+ return None
87
+
88
+
89
+ def suggest_similar_servers(
90
+ manager, server_name: str, group_id: Optional[str] = None
91
+ ) -> None:
92
+ """
93
+ Suggest similar server names when a server is not found.
94
+
95
+ Args:
96
+ manager: MCP manager instance
97
+ server_name: The server name that was not found
98
+ group_id: Optional message group ID for grouping related messages
99
+ """
100
+ import logging
101
+
102
+ from code_puppy.messaging import emit_info
103
+
104
+ logger = logging.getLogger(__name__)
105
+
106
+ try:
107
+ servers = manager.list_servers()
108
+ if not servers:
109
+ emit_info("No servers are registered", message_group=group_id)
110
+ return
111
+
112
+ # Simple suggestion based on partial matching
113
+ suggestions = []
114
+ server_name_lower = server_name.lower()
115
+
116
+ for server in servers:
117
+ if server_name_lower in server.name.lower():
118
+ suggestions.append(server.name)
119
+
120
+ if suggestions:
121
+ emit_info(f"Did you mean: {', '.join(suggestions)}", message_group=group_id)
122
+ else:
123
+ server_names = [s.name for s in servers]
124
+ emit_info(
125
+ f"Available servers: {', '.join(server_names)}", message_group=group_id
126
+ )
127
+
128
+ except Exception as e:
129
+ logger.error(f"Error suggesting similar servers: {e}")
@@ -0,0 +1,334 @@
1
+ """
2
+ MCP Interactive Wizard Utilities - Shared interactive installation wizard functions.
3
+
4
+ Provides interactive functionality for installing and configuring MCP servers.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any, Dict, Optional
9
+
10
+ from rich.text import Text
11
+
12
+ from code_puppy.messaging import emit_error, emit_info, emit_prompt
13
+
14
+ # Configure logging
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ def run_interactive_install_wizard(manager, group_id: str) -> bool:
19
+ """
20
+ Run the interactive MCP server installation wizard.
21
+
22
+ Args:
23
+ manager: MCP manager instance
24
+ group_id: Message group ID for grouping related messages
25
+
26
+ Returns:
27
+ True if installation was successful, False otherwise
28
+ """
29
+ try:
30
+ # Show welcome message
31
+ emit_info("🚀 MCP Server Installation Wizard", message_group=group_id)
32
+ emit_info(
33
+ "This wizard will help you install pre-configured MCP servers",
34
+ message_group=group_id,
35
+ )
36
+ emit_info("", message_group=group_id)
37
+
38
+ # Let user select a server
39
+ selected_server = interactive_server_selection(group_id)
40
+ if not selected_server:
41
+ return False
42
+
43
+ # Get custom name
44
+ server_name = interactive_get_server_name(selected_server, group_id)
45
+ if not server_name:
46
+ return False
47
+
48
+ # Collect environment variables and command line arguments
49
+ env_vars = {}
50
+ cmd_args = {}
51
+
52
+ # Get environment variables
53
+ required_env_vars = selected_server.get_environment_vars()
54
+ if required_env_vars:
55
+ emit_info(
56
+ Text.from_markup("\n[yellow]Required Environment Variables:[/yellow]"),
57
+ message_group=group_id,
58
+ )
59
+ for var in required_env_vars:
60
+ # Check if already set in environment
61
+ import os
62
+
63
+ current_value = os.environ.get(var, "")
64
+ if current_value:
65
+ emit_info(
66
+ Text.from_markup(f" {var}: [green]Already set[/green]"),
67
+ message_group=group_id,
68
+ )
69
+ env_vars[var] = current_value
70
+ else:
71
+ value = emit_prompt(f" Enter value for {var}: ").strip()
72
+ if value:
73
+ env_vars[var] = value
74
+
75
+ # Get command line arguments
76
+ required_cmd_args = selected_server.get_command_line_args()
77
+ if required_cmd_args:
78
+ emit_info(
79
+ Text.from_markup("\n[yellow]Command Line Arguments:[/yellow]"),
80
+ message_group=group_id,
81
+ )
82
+ for arg_config in required_cmd_args:
83
+ name = arg_config.get("name", "")
84
+ prompt = arg_config.get("prompt", name)
85
+ default = arg_config.get("default", "")
86
+ required = arg_config.get("required", True)
87
+
88
+ # If required or has default, prompt user
89
+ if required or default:
90
+ arg_prompt = f" {prompt}"
91
+ if default:
92
+ arg_prompt += f" [{default}]"
93
+ if not required:
94
+ arg_prompt += " (optional)"
95
+
96
+ value = emit_prompt(f"{arg_prompt}: ").strip()
97
+ if value:
98
+ cmd_args[name] = value
99
+ elif default:
100
+ cmd_args[name] = default
101
+
102
+ # Configure the server
103
+ return interactive_configure_server(
104
+ manager, selected_server, server_name, group_id, env_vars, cmd_args
105
+ )
106
+
107
+ except ImportError:
108
+ emit_error("Server catalog not available", message_group=group_id)
109
+ return False
110
+ except Exception as e:
111
+ logger.error(f"Error in interactive wizard: {e}")
112
+ emit_error(f"Wizard error: {e}", message_group=group_id)
113
+ return False
114
+
115
+
116
+ def interactive_server_selection(group_id: str):
117
+ """
118
+ Interactive server selection from catalog.
119
+
120
+ Returns selected server or None if cancelled.
121
+ """
122
+ # This is a simplified version - the full implementation would have
123
+ # category browsing, search, etc. For now, we'll just show popular servers
124
+ try:
125
+ from code_puppy.mcp_.server_registry_catalog import catalog
126
+
127
+ servers = catalog.get_popular(10)
128
+ if not servers:
129
+ emit_info("No servers available in catalog", message_group=group_id)
130
+ return None
131
+
132
+ emit_info("Popular MCP Servers:", message_group=group_id)
133
+ for i, server in enumerate(servers, 1):
134
+ indicators = []
135
+ if server.verified:
136
+ indicators.append("✓")
137
+ if server.popular:
138
+ indicators.append("⭐")
139
+
140
+ indicator_str = ""
141
+ if indicators:
142
+ indicator_str = " " + "".join(indicators)
143
+
144
+ emit_info(
145
+ f"{i:2}. {server.display_name}{indicator_str}", message_group=group_id
146
+ )
147
+ emit_info(f" {server.description[:80]}...", message_group=group_id)
148
+
149
+ choice = emit_prompt(
150
+ "Enter number (1-{}) or 'q' to quit: ".format(len(servers))
151
+ )
152
+
153
+ if choice.lower() == "q":
154
+ return None
155
+
156
+ try:
157
+ index = int(choice) - 1
158
+ if 0 <= index < len(servers):
159
+ return servers[index]
160
+ else:
161
+ emit_error("Invalid selection", message_group=group_id)
162
+ return None
163
+ except ValueError:
164
+ emit_error("Invalid input", message_group=group_id)
165
+ return None
166
+
167
+ except Exception as e:
168
+ logger.error(f"Error in server selection: {e}")
169
+ return None
170
+
171
+
172
+ def interactive_get_server_name(selected_server, group_id: str) -> Optional[str]:
173
+ """
174
+ Get custom server name from user.
175
+
176
+ Returns server name or None if cancelled.
177
+ """
178
+ default_name = selected_server.name
179
+ server_name = emit_prompt(f"Enter name for this server [{default_name}]: ").strip()
180
+
181
+ if not server_name:
182
+ server_name = default_name
183
+
184
+ return server_name
185
+
186
+
187
+ def interactive_configure_server(
188
+ manager,
189
+ selected_server,
190
+ server_name: str,
191
+ group_id: str,
192
+ env_vars: Dict[str, Any],
193
+ cmd_args: Dict[str, Any],
194
+ ) -> bool:
195
+ """
196
+ Configure and install the selected server.
197
+
198
+ Returns True if successful, False otherwise.
199
+ """
200
+ try:
201
+ # Check if server already exists
202
+ from .utils import find_server_id_by_name
203
+
204
+ existing_server = find_server_id_by_name(manager, server_name)
205
+ if existing_server:
206
+ override = emit_prompt(
207
+ f"Server '{server_name}' already exists. Override? [y/N]: "
208
+ )
209
+ if not override.lower().startswith("y"):
210
+ emit_info("Installation cancelled", message_group=group_id)
211
+ return False
212
+
213
+ # Show confirmation
214
+ emit_info(f"Installing: {selected_server.display_name}", message_group=group_id)
215
+ emit_info(f"Name: {server_name}", message_group=group_id)
216
+
217
+ if env_vars:
218
+ emit_info("Environment Variables:", message_group=group_id)
219
+ for var, _value in env_vars.items():
220
+ emit_info(f" {var}: ***", message_group=group_id)
221
+
222
+ if cmd_args:
223
+ emit_info("Command Line Arguments:", message_group=group_id)
224
+ for arg, value in cmd_args.items():
225
+ emit_info(f" {arg}: {value}", message_group=group_id)
226
+
227
+ confirm = emit_prompt("Proceed with installation? [Y/n]: ")
228
+ if confirm.lower().startswith("n"):
229
+ emit_info("Installation cancelled", message_group=group_id)
230
+ return False
231
+
232
+ # Install the server (simplified version)
233
+ return install_server_from_catalog(
234
+ manager, selected_server, server_name, env_vars, cmd_args, group_id
235
+ )
236
+
237
+ except Exception as e:
238
+ logger.error(f"Error configuring server: {e}")
239
+ emit_error(f"Configuration error: {e}", message_group=group_id)
240
+ return False
241
+
242
+
243
+ def install_server_from_catalog(
244
+ manager,
245
+ selected_server,
246
+ server_name: str,
247
+ env_vars: Dict[str, Any],
248
+ cmd_args: Dict[str, Any],
249
+ group_id: str,
250
+ ) -> bool:
251
+ """
252
+ Install a server from the catalog with the given configuration.
253
+
254
+ Returns True if successful, False otherwise.
255
+ """
256
+ try:
257
+ import json
258
+ import os
259
+
260
+ from code_puppy.config import MCP_SERVERS_FILE
261
+ from code_puppy.mcp_.managed_server import ServerConfig
262
+
263
+ # Set environment variables in the current environment
264
+ for var, value in env_vars.items():
265
+ os.environ[var] = value
266
+
267
+ # Get server config with command line argument overrides
268
+ config_dict = selected_server.to_server_config(server_name, **cmd_args)
269
+
270
+ # Update the config with actual environment variable values
271
+ if "env" in config_dict:
272
+ for env_key, env_value in config_dict["env"].items():
273
+ # If it's a placeholder like $GITHUB_TOKEN, replace with actual value
274
+ if env_value.startswith("$"):
275
+ var_name = env_value[1:] # Remove the $
276
+ if var_name in env_vars:
277
+ config_dict["env"][env_key] = env_vars[var_name]
278
+
279
+ # Create ServerConfig
280
+ server_config = ServerConfig(
281
+ id=server_name,
282
+ name=server_name,
283
+ type=selected_server.type,
284
+ enabled=True,
285
+ config=config_dict,
286
+ )
287
+
288
+ # Register with manager
289
+ server_id = manager.register_server(server_config)
290
+
291
+ if not server_id:
292
+ emit_info(
293
+ "Failed to register server with manager",
294
+ message_group=group_id,
295
+ )
296
+ return False
297
+
298
+ # Save to mcp_servers.json for persistence
299
+ if os.path.exists(MCP_SERVERS_FILE):
300
+ with open(MCP_SERVERS_FILE, "r") as f:
301
+ data = json.load(f)
302
+ servers = data.get("mcp_servers", {})
303
+ else:
304
+ servers = {}
305
+ data = {"mcp_servers": servers}
306
+
307
+ # Add new server
308
+ # Copy the config dict and add type before saving
309
+ save_config = config_dict.copy()
310
+ save_config["type"] = selected_server.type
311
+ servers[server_name] = save_config
312
+
313
+ # Save back
314
+ os.makedirs(os.path.dirname(MCP_SERVERS_FILE), exist_ok=True)
315
+ with open(MCP_SERVERS_FILE, "w") as f:
316
+ json.dump(data, f, indent=2)
317
+
318
+ emit_info(
319
+ Text.from_markup(
320
+ f"[green]✓ Successfully installed server: {server_name}[/green]"
321
+ ),
322
+ message_group=group_id,
323
+ )
324
+ emit_info(
325
+ "Use '/mcp start {}' to start the server".format(server_name),
326
+ message_group=group_id,
327
+ )
328
+
329
+ return True
330
+
331
+ except Exception as e:
332
+ logger.error(f"Error installing server: {e}")
333
+ emit_error(f"Installation failed: {e}", message_group=group_id)
334
+ return False