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.
Files changed (243) hide show
  1. code_puppy/__init__.py +7 -1
  2. code_puppy/agents/__init__.py +8 -8
  3. code_puppy/agents/agent_c_reviewer.py +155 -0
  4. code_puppy/agents/agent_code_puppy.py +9 -2
  5. code_puppy/agents/agent_code_reviewer.py +90 -0
  6. code_puppy/agents/agent_cpp_reviewer.py +132 -0
  7. code_puppy/agents/agent_creator_agent.py +48 -9
  8. code_puppy/agents/agent_golang_reviewer.py +151 -0
  9. code_puppy/agents/agent_javascript_reviewer.py +160 -0
  10. code_puppy/agents/agent_manager.py +146 -199
  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 +90 -0
  15. code_puppy/agents/agent_qa_expert.py +163 -0
  16. code_puppy/agents/agent_qa_kitten.py +208 -0
  17. code_puppy/agents/agent_security_auditor.py +181 -0
  18. code_puppy/agents/agent_terminal_qa.py +323 -0
  19. code_puppy/agents/agent_typescript_reviewer.py +166 -0
  20. code_puppy/agents/base_agent.py +1713 -1
  21. code_puppy/agents/event_stream_handler.py +350 -0
  22. code_puppy/agents/json_agent.py +12 -1
  23. code_puppy/agents/pack/__init__.py +34 -0
  24. code_puppy/agents/pack/bloodhound.py +304 -0
  25. code_puppy/agents/pack/husky.py +321 -0
  26. code_puppy/agents/pack/retriever.py +393 -0
  27. code_puppy/agents/pack/shepherd.py +348 -0
  28. code_puppy/agents/pack/terrier.py +287 -0
  29. code_puppy/agents/pack/watchdog.py +367 -0
  30. code_puppy/agents/prompt_reviewer.py +145 -0
  31. code_puppy/agents/subagent_stream_handler.py +276 -0
  32. code_puppy/api/__init__.py +13 -0
  33. code_puppy/api/app.py +169 -0
  34. code_puppy/api/main.py +21 -0
  35. code_puppy/api/pty_manager.py +446 -0
  36. code_puppy/api/routers/__init__.py +12 -0
  37. code_puppy/api/routers/agents.py +36 -0
  38. code_puppy/api/routers/commands.py +217 -0
  39. code_puppy/api/routers/config.py +74 -0
  40. code_puppy/api/routers/sessions.py +232 -0
  41. code_puppy/api/templates/terminal.html +361 -0
  42. code_puppy/api/websocket.py +154 -0
  43. code_puppy/callbacks.py +174 -4
  44. code_puppy/chatgpt_codex_client.py +283 -0
  45. code_puppy/claude_cache_client.py +586 -0
  46. code_puppy/cli_runner.py +916 -0
  47. code_puppy/command_line/add_model_menu.py +1079 -0
  48. code_puppy/command_line/agent_menu.py +395 -0
  49. code_puppy/command_line/attachments.py +395 -0
  50. code_puppy/command_line/autosave_menu.py +605 -0
  51. code_puppy/command_line/clipboard.py +527 -0
  52. code_puppy/command_line/colors_menu.py +520 -0
  53. code_puppy/command_line/command_handler.py +233 -627
  54. code_puppy/command_line/command_registry.py +150 -0
  55. code_puppy/command_line/config_commands.py +715 -0
  56. code_puppy/command_line/core_commands.py +792 -0
  57. code_puppy/command_line/diff_menu.py +863 -0
  58. code_puppy/command_line/load_context_completion.py +15 -22
  59. code_puppy/command_line/mcp/base.py +1 -4
  60. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  61. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  62. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  63. code_puppy/command_line/mcp/edit_command.py +148 -0
  64. code_puppy/command_line/mcp/handler.py +9 -4
  65. code_puppy/command_line/mcp/help_command.py +6 -5
  66. code_puppy/command_line/mcp/install_command.py +16 -27
  67. code_puppy/command_line/mcp/install_menu.py +685 -0
  68. code_puppy/command_line/mcp/list_command.py +3 -3
  69. code_puppy/command_line/mcp/logs_command.py +174 -65
  70. code_puppy/command_line/mcp/remove_command.py +2 -2
  71. code_puppy/command_line/mcp/restart_command.py +12 -4
  72. code_puppy/command_line/mcp/search_command.py +17 -11
  73. code_puppy/command_line/mcp/start_all_command.py +22 -13
  74. code_puppy/command_line/mcp/start_command.py +50 -31
  75. code_puppy/command_line/mcp/status_command.py +6 -7
  76. code_puppy/command_line/mcp/stop_all_command.py +11 -8
  77. code_puppy/command_line/mcp/stop_command.py +11 -10
  78. code_puppy/command_line/mcp/test_command.py +2 -2
  79. code_puppy/command_line/mcp/utils.py +1 -1
  80. code_puppy/command_line/mcp/wizard_utils.py +22 -18
  81. code_puppy/command_line/mcp_completion.py +174 -0
  82. code_puppy/command_line/model_picker_completion.py +89 -30
  83. code_puppy/command_line/model_settings_menu.py +884 -0
  84. code_puppy/command_line/motd.py +14 -8
  85. code_puppy/command_line/onboarding_slides.py +179 -0
  86. code_puppy/command_line/onboarding_wizard.py +340 -0
  87. code_puppy/command_line/pin_command_completion.py +329 -0
  88. code_puppy/command_line/prompt_toolkit_completion.py +626 -75
  89. code_puppy/command_line/session_commands.py +296 -0
  90. code_puppy/command_line/utils.py +54 -0
  91. code_puppy/config.py +1181 -51
  92. code_puppy/error_logging.py +118 -0
  93. code_puppy/gemini_code_assist.py +385 -0
  94. code_puppy/gemini_model.py +602 -0
  95. code_puppy/http_utils.py +220 -104
  96. code_puppy/keymap.py +128 -0
  97. code_puppy/main.py +5 -594
  98. code_puppy/{mcp → mcp_}/__init__.py +17 -0
  99. code_puppy/{mcp → mcp_}/async_lifecycle.py +35 -4
  100. code_puppy/{mcp → mcp_}/blocking_startup.py +70 -43
  101. code_puppy/{mcp → mcp_}/captured_stdio_server.py +2 -2
  102. code_puppy/{mcp → mcp_}/config_wizard.py +5 -5
  103. code_puppy/{mcp → mcp_}/dashboard.py +15 -6
  104. code_puppy/{mcp → mcp_}/examples/retry_example.py +4 -1
  105. code_puppy/{mcp → mcp_}/managed_server.py +66 -39
  106. code_puppy/{mcp → mcp_}/manager.py +146 -52
  107. code_puppy/mcp_/mcp_logs.py +224 -0
  108. code_puppy/{mcp → mcp_}/registry.py +6 -6
  109. code_puppy/{mcp → mcp_}/server_registry_catalog.py +25 -8
  110. code_puppy/messaging/__init__.py +199 -2
  111. code_puppy/messaging/bus.py +610 -0
  112. code_puppy/messaging/commands.py +167 -0
  113. code_puppy/messaging/markdown_patches.py +57 -0
  114. code_puppy/messaging/message_queue.py +17 -48
  115. code_puppy/messaging/messages.py +500 -0
  116. code_puppy/messaging/queue_console.py +1 -24
  117. code_puppy/messaging/renderers.py +43 -146
  118. code_puppy/messaging/rich_renderer.py +1027 -0
  119. code_puppy/messaging/spinner/__init__.py +33 -5
  120. code_puppy/messaging/spinner/console_spinner.py +92 -52
  121. code_puppy/messaging/spinner/spinner_base.py +29 -0
  122. code_puppy/messaging/subagent_console.py +461 -0
  123. code_puppy/model_factory.py +686 -80
  124. code_puppy/model_utils.py +167 -0
  125. code_puppy/models.json +86 -104
  126. code_puppy/models_dev_api.json +1 -0
  127. code_puppy/models_dev_parser.py +592 -0
  128. code_puppy/plugins/__init__.py +164 -10
  129. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  130. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  131. code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
  132. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  133. code_puppy/plugins/antigravity_oauth/constants.py +136 -0
  134. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  135. code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
  136. code_puppy/plugins/antigravity_oauth/storage.py +271 -0
  137. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  138. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  139. code_puppy/plugins/antigravity_oauth/transport.py +767 -0
  140. code_puppy/plugins/antigravity_oauth/utils.py +169 -0
  141. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  142. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  143. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
  144. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
  145. code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
  146. code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
  147. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  148. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  149. code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
  150. code_puppy/plugins/claude_code_oauth/config.py +50 -0
  151. code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
  152. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  153. code_puppy/plugins/claude_code_oauth/utils.py +518 -0
  154. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  155. code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
  156. code_puppy/plugins/example_custom_command/README.md +280 -0
  157. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  158. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  159. code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
  160. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  161. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  162. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  163. code_puppy/plugins/oauth_puppy_html.py +228 -0
  164. code_puppy/plugins/shell_safety/__init__.py +6 -0
  165. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  166. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  167. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  168. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  169. code_puppy/prompts/codex_system_prompt.md +310 -0
  170. code_puppy/pydantic_patches.py +131 -0
  171. code_puppy/reopenable_async_client.py +8 -8
  172. code_puppy/round_robin_model.py +10 -15
  173. code_puppy/session_storage.py +294 -0
  174. code_puppy/status_display.py +21 -4
  175. code_puppy/summarization_agent.py +52 -14
  176. code_puppy/terminal_utils.py +418 -0
  177. code_puppy/tools/__init__.py +139 -6
  178. code_puppy/tools/agent_tools.py +548 -49
  179. code_puppy/tools/browser/__init__.py +37 -0
  180. code_puppy/tools/browser/browser_control.py +289 -0
  181. code_puppy/tools/browser/browser_interactions.py +545 -0
  182. code_puppy/tools/browser/browser_locators.py +640 -0
  183. code_puppy/tools/browser/browser_manager.py +316 -0
  184. code_puppy/tools/browser/browser_navigation.py +251 -0
  185. code_puppy/tools/browser/browser_screenshot.py +179 -0
  186. code_puppy/tools/browser/browser_scripts.py +462 -0
  187. code_puppy/tools/browser/browser_workflows.py +221 -0
  188. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  189. code_puppy/tools/browser/terminal_command_tools.py +521 -0
  190. code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
  191. code_puppy/tools/browser/terminal_tools.py +525 -0
  192. code_puppy/tools/command_runner.py +941 -153
  193. code_puppy/tools/common.py +1146 -6
  194. code_puppy/tools/display.py +84 -0
  195. code_puppy/tools/file_modifications.py +288 -89
  196. code_puppy/tools/file_operations.py +352 -266
  197. code_puppy/tools/subagent_context.py +158 -0
  198. code_puppy/uvx_detection.py +242 -0
  199. code_puppy/version_checker.py +30 -11
  200. code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
  201. code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
  202. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/METADATA +184 -67
  203. code_puppy-0.0.366.dist-info/RECORD +217 -0
  204. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
  205. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +1 -0
  206. code_puppy/agent.py +0 -231
  207. code_puppy/agents/agent_orchestrator.json +0 -26
  208. code_puppy/agents/runtime_manager.py +0 -272
  209. code_puppy/command_line/mcp/add_command.py +0 -183
  210. code_puppy/command_line/meta_command_handler.py +0 -153
  211. code_puppy/message_history_processor.py +0 -490
  212. code_puppy/messaging/spinner/textual_spinner.py +0 -101
  213. code_puppy/state_management.py +0 -200
  214. code_puppy/tui/__init__.py +0 -10
  215. code_puppy/tui/app.py +0 -986
  216. code_puppy/tui/components/__init__.py +0 -21
  217. code_puppy/tui/components/chat_view.py +0 -550
  218. code_puppy/tui/components/command_history_modal.py +0 -218
  219. code_puppy/tui/components/copy_button.py +0 -139
  220. code_puppy/tui/components/custom_widgets.py +0 -63
  221. code_puppy/tui/components/human_input_modal.py +0 -175
  222. code_puppy/tui/components/input_area.py +0 -167
  223. code_puppy/tui/components/sidebar.py +0 -309
  224. code_puppy/tui/components/status_bar.py +0 -182
  225. code_puppy/tui/messages.py +0 -27
  226. code_puppy/tui/models/__init__.py +0 -8
  227. code_puppy/tui/models/chat_message.py +0 -25
  228. code_puppy/tui/models/command_history.py +0 -89
  229. code_puppy/tui/models/enums.py +0 -24
  230. code_puppy/tui/screens/__init__.py +0 -15
  231. code_puppy/tui/screens/help.py +0 -130
  232. code_puppy/tui/screens/mcp_install_wizard.py +0 -803
  233. code_puppy/tui/screens/settings.py +0 -290
  234. code_puppy/tui/screens/tools.py +0 -74
  235. code_puppy-0.0.169.data/data/code_puppy/models.json +0 -128
  236. code_puppy-0.0.169.dist-info/RECORD +0 -112
  237. /code_puppy/{mcp → mcp_}/circuit_breaker.py +0 -0
  238. /code_puppy/{mcp → mcp_}/error_isolation.py +0 -0
  239. /code_puppy/{mcp → mcp_}/health_monitor.py +0 -0
  240. /code_puppy/{mcp → mcp_}/retry_manager.py +0 -0
  241. /code_puppy/{mcp → mcp_}/status_tracker.py +0 -0
  242. /code_puppy/{mcp → mcp_}/system_tools.py +0 -0
  243. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
@@ -1,15 +1,22 @@
1
1
  """
2
- MCP Logs Command - Shows recent events/logs for a server.
2
+ MCP Logs Command - Shows server logs from persistent log files.
3
3
  """
4
4
 
5
5
  import logging
6
- from datetime import datetime
7
6
  from typing import List, Optional
8
7
 
9
- from rich.table import Table
8
+ from rich.panel import Panel
9
+ from rich.syntax import Syntax
10
10
  from rich.text import Text
11
11
 
12
- from code_puppy.messaging import emit_info
12
+ from code_puppy.mcp_.mcp_logs import (
13
+ clear_logs,
14
+ get_log_file_path,
15
+ get_log_stats,
16
+ list_servers_with_logs,
17
+ read_logs,
18
+ )
19
+ from code_puppy.messaging import emit_error, emit_info
13
20
 
14
21
  from .base import MCPCommandBase
15
22
  from .utils import find_server_id_by_name, suggest_similar_servers
@@ -22,105 +29,207 @@ class LogsCommand(MCPCommandBase):
22
29
  """
23
30
  Command handler for showing MCP server logs.
24
31
 
25
- Shows recent events/logs for a specific MCP server with configurable limit.
32
+ Shows logs from persistent log files stored in ~/.code_puppy/mcp_logs/.
26
33
  """
27
34
 
28
35
  def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
29
36
  """
30
- Show recent events/logs for a server.
37
+ Show logs for a server.
38
+
39
+ Usage:
40
+ /mcp logs - List servers with logs
41
+ /mcp logs <server_name> - Show last 50 lines
42
+ /mcp logs <server_name> 100 - Show last 100 lines
43
+ /mcp logs <server_name> all - Show all logs
44
+ /mcp logs <server_name> --clear - Clear logs for server
31
45
 
32
46
  Args:
33
- args: Command arguments, expects [server_name] and optional [limit]
47
+ args: Command arguments
34
48
  group_id: Optional message group ID for grouping related messages
35
49
  """
36
50
  if group_id is None:
37
51
  group_id = self.generate_group_id()
38
52
 
53
+ # No args - list servers with logs
39
54
  if not args:
40
- emit_info("Usage: /mcp logs <server_name> [limit]", message_group=group_id)
55
+ self._list_servers_with_logs(group_id)
41
56
  return
42
57
 
43
58
  server_name = args[0]
44
- limit = 10 # Default limit
59
+
60
+ # Check for --clear flag
61
+ if len(args) > 1 and args[1] == "--clear":
62
+ self._clear_logs(server_name, group_id)
63
+ return
64
+
65
+ # Determine number of lines
66
+ lines = 50 # Default
67
+ show_all = False
45
68
 
46
69
  if len(args) > 1:
47
- try:
48
- limit = int(args[1])
49
- if limit <= 0 or limit > 100:
70
+ if args[1].lower() == "all":
71
+ show_all = True
72
+ else:
73
+ try:
74
+ lines = int(args[1])
75
+ if lines <= 0:
76
+ emit_info(
77
+ "Lines must be positive, using default: 50",
78
+ message_group=group_id,
79
+ )
80
+ lines = 50
81
+ except ValueError:
50
82
  emit_info(
51
- "Limit must be between 1 and 100, using default: 10",
83
+ f"Invalid number '{args[1]}', using default: 50",
52
84
  message_group=group_id,
53
85
  )
54
- limit = 10
55
- except ValueError:
56
- emit_info(
57
- f"Invalid limit '{args[1]}', using default: 10",
58
- message_group=group_id,
59
- )
60
86
 
87
+ self._show_logs(server_name, lines if not show_all else None, group_id)
88
+
89
+ def _list_servers_with_logs(self, group_id: str) -> None:
90
+ """List all servers that have log files."""
91
+ servers = list_servers_with_logs()
92
+
93
+ if not servers:
94
+ emit_info(
95
+ "📋 No MCP server logs found.\n"
96
+ "Logs are created when servers are started.",
97
+ message_group=group_id,
98
+ )
99
+ return
100
+
101
+ lines = ["📋 **Servers with logs:**\n"]
102
+
103
+ for server in servers:
104
+ stats = get_log_stats(server)
105
+ size_kb = stats["total_size_bytes"] / 1024
106
+ size_str = (
107
+ f"{size_kb:.1f} KB" if size_kb < 1024 else f"{size_kb / 1024:.1f} MB"
108
+ )
109
+ rotated = (
110
+ f" (+{stats['rotated_count']} rotated)"
111
+ if stats["rotated_count"]
112
+ else ""
113
+ )
114
+ lines.append(
115
+ f" • **{server}** - {stats['line_count']} lines, {size_str}{rotated}"
116
+ )
117
+
118
+ lines.append("\n**Usage:** `/mcp logs <server_name> [lines|all]`")
119
+
120
+ emit_info("\n".join(lines), message_group=group_id)
121
+
122
+ def _show_logs(self, server_name: str, lines: Optional[int], group_id: str) -> None:
123
+ """
124
+ Show logs for a specific server.
125
+
126
+ Args:
127
+ server_name: Name of the server
128
+ lines: Number of lines to show, or None for all
129
+ group_id: Message group ID
130
+ """
61
131
  try:
62
- # Find server by name
132
+ # Verify server exists in manager
63
133
  server_id = find_server_id_by_name(self.manager, server_name)
64
134
  if not server_id:
65
- emit_info(f"Server '{server_name}' not found", message_group=group_id)
66
- suggest_similar_servers(self.manager, server_name, group_id=group_id)
67
- return
135
+ # Server not configured, but might have logs from before
136
+ stats = get_log_stats(server_name)
137
+ if not stats["exists"]:
138
+ emit_info(
139
+ f"Server '{server_name}' not found and has no logs.",
140
+ message_group=group_id,
141
+ )
142
+ suggest_similar_servers(
143
+ self.manager, server_name, group_id=group_id
144
+ )
145
+ return
68
146
 
69
- # Get server status which includes recent events
70
- status = self.manager.get_server_status(server_id)
147
+ # Read logs
148
+ log_lines = read_logs(server_name, lines=lines)
71
149
 
72
- if not status.get("exists", True):
150
+ if not log_lines:
73
151
  emit_info(
74
- f"Server '{server_name}' status not available",
152
+ f"📋 No logs found for server: **{server_name}**\n"
153
+ f"Log file: `{get_log_file_path(server_name)}`",
75
154
  message_group=group_id,
76
155
  )
77
156
  return
78
157
 
79
- recent_events = status.get("recent_events", [])
158
+ # Get stats for header
159
+ stats = get_log_stats(server_name)
160
+ total_lines = stats["line_count"]
161
+ showing = len(log_lines)
162
+
163
+ # Format header
164
+ if lines is None:
165
+ header = f"📋 Logs for {server_name} (all {total_lines} lines)"
166
+ else:
167
+ header = (
168
+ f"📋 Logs for {server_name} (last {showing} of {total_lines} lines)"
169
+ )
170
+
171
+ # Format log content with syntax highlighting
172
+ log_content = "\n".join(log_lines)
80
173
 
81
- if not recent_events:
174
+ # Create a panel with the logs
175
+ syntax = Syntax(
176
+ log_content,
177
+ "log",
178
+ theme="monokai",
179
+ word_wrap=True,
180
+ line_numbers=False,
181
+ )
182
+
183
+ panel = Panel(
184
+ syntax,
185
+ title=header,
186
+ subtitle=f"Log file: {get_log_file_path(server_name)}",
187
+ border_style="dim",
188
+ )
189
+
190
+ emit_info(panel, message_group=group_id)
191
+
192
+ # Show hint for more options
193
+ if lines is not None and showing < total_lines:
82
194
  emit_info(
83
- f"No recent events for server: {server_name}",
195
+ Text.from_markup(
196
+ f"[dim]💡 Use `/mcp logs {server_name} all` to see all logs, "
197
+ f"or `/mcp logs {server_name} <number>` for specific count[/dim]"
198
+ ),
84
199
  message_group=group_id,
85
200
  )
86
- return
87
201
 
88
- # Show events in a table
89
- table = Table(title=f"📋 Recent Events for {server_name} (last {limit})")
90
- table.add_column("Time", style="dim", no_wrap=True)
91
- table.add_column("Event", style="cyan")
92
- table.add_column("Details", style="dim")
202
+ except Exception as e:
203
+ logger.error(f"Error getting logs for server '{server_name}': {e}")
204
+ emit_error(f"Error getting logs: {e}", message_group=group_id)
93
205
 
94
- # Take only the requested number of events
95
- events_to_show = (
96
- recent_events[-limit:] if len(recent_events) > limit else recent_events
97
- )
206
+ def _clear_logs(self, server_name: str, group_id: str) -> None:
207
+ """
208
+ Clear logs for a specific server.
209
+
210
+ Args:
211
+ server_name: Name of the server
212
+ group_id: Message group ID
213
+ """
214
+ try:
215
+ stats = get_log_stats(server_name)
98
216
 
99
- for event in reversed(events_to_show): # Show newest first
100
- timestamp = datetime.fromisoformat(event["timestamp"])
101
- time_str = timestamp.strftime("%H:%M:%S")
102
- event_type = event["event_type"]
103
-
104
- # Format details
105
- details = event.get("details", {})
106
- details_str = details.get("message", "")
107
- if not details_str and "error" in details:
108
- details_str = str(details["error"])
109
-
110
- # Color code event types
111
- event_style = "cyan"
112
- if "error" in event_type.lower():
113
- event_style = "red"
114
- elif event_type in ["started", "enabled", "registered"]:
115
- event_style = "green"
116
- elif event_type in ["stopped", "disabled"]:
117
- event_style = "yellow"
118
-
119
- table.add_row(
120
- time_str, Text(event_type, style=event_style), details_str or "-"
217
+ if not stats["exists"] and stats["rotated_count"] == 0:
218
+ emit_info(
219
+ f"No logs to clear for server: {server_name}",
220
+ message_group=group_id,
121
221
  )
122
- emit_info(table, message_group=group_id)
222
+ return
223
+
224
+ # Clear the logs
225
+ clear_logs(server_name, include_rotated=True)
226
+
227
+ cleared_count = 1 + stats["rotated_count"]
228
+ emit_info(
229
+ f"🗑️ Cleared {cleared_count} log file(s) for **{server_name}**",
230
+ message_group=group_id,
231
+ )
123
232
 
124
233
  except Exception as e:
125
- logger.error(f"Error getting logs for server '{server_name}': {e}")
126
- emit_info(f"[red]Error getting logs: {e}[/red]", message_group=group_id)
234
+ logger.error(f"Error clearing logs for server '{server_name}': {e}")
235
+ emit_error(f"Error clearing logs: {e}", message_group=group_id)
@@ -7,7 +7,7 @@ import logging
7
7
  import os
8
8
  from typing import List, Optional
9
9
 
10
- from code_puppy.messaging import emit_info
10
+ from code_puppy.messaging import emit_error, emit_info
11
11
 
12
12
  from .base import MCPCommandBase
13
13
  from .utils import find_server_id_by_name, suggest_similar_servers
@@ -79,4 +79,4 @@ class RemoveCommand(MCPCommandBase):
79
79
 
80
80
  except Exception as e:
81
81
  logger.error(f"Error removing server '{server_name}': {e}")
82
- emit_info(f"[red]Error removing server: {e}[/red]", message_group=group_id)
82
+ emit_error(f"Error removing server: {e}", message_group=group_id)
@@ -5,6 +5,8 @@ MCP Restart Command - Restarts a specific MCP server.
5
5
  import logging
6
6
  from typing import List, Optional
7
7
 
8
+ from rich.text import Text
9
+
8
10
  from code_puppy.messaging import emit_info
9
11
 
10
12
  from .base import MCPCommandBase
@@ -65,11 +67,16 @@ class RestartCommand(MCPCommandBase):
65
67
 
66
68
  # Reload the agent to pick up the server changes
67
69
  try:
68
- from code_puppy.agent import get_code_generation_agent
70
+ from code_puppy.agents import get_current_agent
69
71
 
70
- get_code_generation_agent(force_reload=True)
72
+ agent = get_current_agent()
73
+ agent.reload_code_generation_agent()
74
+ # Update MCP tool cache immediately so token counts reflect the change
75
+ agent.update_mcp_tool_cache_sync()
71
76
  emit_info(
72
- "[dim]Agent reloaded with updated servers[/dim]",
77
+ Text.from_markup(
78
+ "[dim]Agent reloaded with updated servers[/dim]"
79
+ ),
73
80
  message_group=group_id,
74
81
  )
75
82
  except Exception as e:
@@ -88,5 +95,6 @@ class RestartCommand(MCPCommandBase):
88
95
  except Exception as e:
89
96
  logger.error(f"Error restarting server '{server_name}': {e}")
90
97
  emit_info(
91
- f"[red]Failed to restart server: {e}[/red]", message_group=group_id
98
+ Text.from_markup(f"[red]Failed to restart server: {e}[/red]"),
99
+ message_group=group_id,
92
100
  )
@@ -6,8 +6,9 @@ import logging
6
6
  from typing import List, Optional
7
7
 
8
8
  from rich.table import Table
9
+ from rich.text import Text
9
10
 
10
- from code_puppy.messaging import emit_info, emit_system_message
11
+ from code_puppy.messaging import emit_info, emit_system_message, emit_warning
11
12
 
12
13
  from .base import MCPCommandBase
13
14
 
@@ -34,26 +35,26 @@ class SearchCommand(MCPCommandBase):
34
35
  group_id = self.generate_group_id()
35
36
 
36
37
  try:
37
- from code_puppy.mcp.server_registry_catalog import catalog
38
+ from code_puppy.mcp_.server_registry_catalog import catalog
38
39
 
39
40
  if not args:
40
41
  # Show popular servers if no query
41
42
  emit_info(
42
- "[bold cyan]Popular MCP Servers:[/bold cyan]\n",
43
+ "Popular MCP Servers:\n",
43
44
  message_group=group_id,
44
45
  )
45
46
  servers = catalog.get_popular(15)
46
47
  else:
47
48
  query = " ".join(args)
48
49
  emit_info(
49
- f"[bold cyan]Searching for: {query}[/bold cyan]\n",
50
+ f"Searching for: {query}\n",
50
51
  message_group=group_id,
51
52
  )
52
53
  servers = catalog.search(query)
53
54
 
54
55
  if not servers:
55
- emit_info(
56
- "[yellow]No servers found matching your search[/yellow]",
56
+ emit_warning(
57
+ "No servers found matching your search",
57
58
  message_group=group_id,
58
59
  )
59
60
  emit_info(
@@ -97,21 +98,26 @@ class SearchCommand(MCPCommandBase):
97
98
 
98
99
  # The first message established the group, subsequent messages will auto-group
99
100
  emit_system_message(table, message_group=group_id)
100
- emit_info("\n[dim]✓ = Verified ⭐ = Popular[/dim]", message_group=group_id)
101
+ emit_info("\n✓ = Verified ⭐ = Popular", message_group=group_id)
101
102
  emit_info(
102
- "[yellow]To install:[/yellow] /mcp install <id>", message_group=group_id
103
+ Text.from_markup("[yellow]To install:[/yellow] /mcp install <id>"),
104
+ message_group=group_id,
103
105
  )
104
106
  emit_info(
105
- "[yellow]For details:[/yellow] /mcp search <specific-term>",
107
+ Text.from_markup(
108
+ "[yellow]For details:[/yellow] /mcp search <specific-term>"
109
+ ),
106
110
  message_group=group_id,
107
111
  )
108
112
 
109
113
  except ImportError:
110
114
  emit_info(
111
- "[red]Server registry not available[/red]", message_group=group_id
115
+ Text.from_markup("[red]Server registry not available[/red]"),
116
+ message_group=group_id,
112
117
  )
113
118
  except Exception as e:
114
119
  logger.error(f"Error searching server registry: {e}")
115
120
  emit_info(
116
- f"[red]Error searching servers: {e}[/red]", message_group=group_id
121
+ Text.from_markup(f"[red]Error searching servers: {e}[/red]"),
122
+ message_group=group_id,
117
123
  )
@@ -6,9 +6,12 @@ import logging
6
6
  import time
7
7
  from typing import List, Optional
8
8
 
9
- from code_puppy.mcp.managed_server import ServerState
9
+ from rich.text import Text
10
+
11
+ from code_puppy.mcp_.managed_server import ServerState
10
12
  from code_puppy.messaging import emit_info
11
13
 
14
+ from ...agents import get_current_agent
12
15
  from .base import MCPCommandBase
13
16
 
14
17
  # Configure logging
@@ -66,20 +69,23 @@ class StartAllCommand(MCPCommandBase):
66
69
  if success:
67
70
  started_count += 1
68
71
  emit_info(
69
- f" [green]✓ Started: {server_name}[/green]",
72
+ Text.from_markup(f" [green]✓ Started: {server_name}[/green]"),
70
73
  message_group=group_id,
71
74
  )
72
75
  else:
73
76
  failed_count += 1
74
77
  emit_info(
75
- f" [red]✗ Failed: {server_name}[/red]", message_group=group_id
78
+ Text.from_markup(f" [red]✗ Failed: {server_name}[/red]"),
79
+ message_group=group_id,
76
80
  )
77
81
 
78
82
  # Summary
79
83
  emit_info("", message_group=group_id)
80
84
  if started_count > 0:
81
85
  emit_info(
82
- f"[green]Started {started_count} server(s)[/green]",
86
+ Text.from_markup(
87
+ f"[green]Started {started_count} server(s)[/green]"
88
+ ),
83
89
  message_group=group_id,
84
90
  )
85
91
  if already_running > 0:
@@ -89,7 +95,9 @@ class StartAllCommand(MCPCommandBase):
89
95
  )
90
96
  if failed_count > 0:
91
97
  emit_info(
92
- f"[yellow]Failed to start {failed_count} server(s)[/yellow]",
98
+ Text.from_markup(
99
+ f"[yellow]Failed to start {failed_count} server(s)[/yellow]"
100
+ ),
93
101
  message_group=group_id,
94
102
  )
95
103
 
@@ -106,14 +114,14 @@ class StartAllCommand(MCPCommandBase):
106
114
  pass # No async loop, servers will start when agent uses them
107
115
 
108
116
  try:
109
- from code_puppy.agents.runtime_manager import (
110
- get_runtime_agent_manager,
111
- )
112
-
113
- manager = get_runtime_agent_manager()
114
- manager.reload_agent()
117
+ agent = get_current_agent()
118
+ agent.reload_code_generation_agent()
119
+ # Update MCP tool cache immediately so token counts reflect the change
120
+ agent.update_mcp_tool_cache_sync()
115
121
  emit_info(
116
- "[dim]Agent reloaded with updated servers[/dim]",
122
+ Text.from_markup(
123
+ "[dim]Agent reloaded with updated servers[/dim]"
124
+ ),
117
125
  message_group=group_id,
118
126
  )
119
127
  except Exception as e:
@@ -122,5 +130,6 @@ class StartAllCommand(MCPCommandBase):
122
130
  except Exception as e:
123
131
  logger.error(f"Error starting all servers: {e}")
124
132
  emit_info(
125
- f"[red]Failed to start servers: {e}[/red]", message_group=group_id
133
+ Text.from_markup(f"[red]Failed to start servers: {e}[/red]"),
134
+ message_group=group_id,
126
135
  )
@@ -3,11 +3,13 @@ MCP Start Command - Starts a specific MCP server.
3
3
  """
4
4
 
5
5
  import logging
6
- import time
7
6
  from typing import List, Optional
8
7
 
9
- from code_puppy.messaging import emit_info
8
+ from rich.text import Text
10
9
 
10
+ from code_puppy.messaging import emit_error, emit_info, emit_success
11
+
12
+ from ...agents import get_current_agent
11
13
  from .base import MCPCommandBase
12
14
  from .utils import find_server_id_by_name, suggest_similar_servers
13
15
 
@@ -20,6 +22,7 @@ class StartCommand(MCPCommandBase):
20
22
  Command handler for starting MCP servers.
21
23
 
22
24
  Starts a specific MCP server by name and reloads the agent.
25
+ The server subprocess starts asynchronously in the background.
23
26
  """
24
27
 
25
28
  def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
@@ -35,7 +38,7 @@ class StartCommand(MCPCommandBase):
35
38
 
36
39
  if not args:
37
40
  emit_info(
38
- "[yellow]Usage: /mcp start <server_name>[/yellow]",
41
+ Text.from_markup("[yellow]Usage: /mcp start <server_name>[/yellow]"),
39
42
  message_group=group_id,
40
43
  )
41
44
  return
@@ -46,53 +49,69 @@ class StartCommand(MCPCommandBase):
46
49
  # Find server by name
47
50
  server_id = find_server_id_by_name(self.manager, server_name)
48
51
  if not server_id:
49
- emit_info(
50
- f"[red]Server '{server_name}' not found[/red]",
52
+ emit_error(
53
+ f"Server '{server_name}' not found",
51
54
  message_group=group_id,
52
55
  )
53
56
  suggest_similar_servers(self.manager, server_name, group_id=group_id)
54
57
  return
55
58
 
56
- # Start the server (enable and start process)
59
+ # Get server info for better messaging (safely handle missing method)
60
+ server_type = "unknown"
61
+ try:
62
+ if hasattr(self.manager, "get_server_by_name"):
63
+ server_config = self.manager.get_server_by_name(server_name)
64
+ server_type = (
65
+ getattr(server_config, "type", "unknown")
66
+ if server_config
67
+ else "unknown"
68
+ )
69
+ except Exception:
70
+ pass # Default to unknown type if we can't determine it
71
+
72
+ # Start the server (schedules async start in background)
57
73
  success = self.manager.start_server_sync(server_id)
58
74
 
59
75
  if success:
60
- # This and subsequent messages will auto-group with the first message
61
- emit_info(
62
- f"[green]✓ Started server: {server_name}[/green]",
63
- message_group=group_id,
64
- )
65
-
66
- # Give async tasks a moment to complete
67
- try:
68
- import asyncio
69
-
70
- asyncio.get_running_loop() # Check if in async context
71
- # If we're in async context, wait a bit for server to start
72
- time.sleep(0.5) # Small delay to let async tasks progress
73
- except RuntimeError:
74
- pass # No async loop, server will start when agent uses it
76
+ if server_type == "stdio":
77
+ # Stdio servers start subprocess asynchronously
78
+ emit_success(
79
+ f"🚀 Starting server: {server_name} (subprocess starting in background)",
80
+ message_group=group_id,
81
+ )
82
+ emit_info(
83
+ Text.from_markup(
84
+ "[dim]Tip: Use /mcp status to check if the server is fully initialized[/dim]"
85
+ ),
86
+ message_group=group_id,
87
+ )
88
+ else:
89
+ # SSE/HTTP servers connect on first use
90
+ emit_success(
91
+ f"✅ Enabled server: {server_name}",
92
+ message_group=group_id,
93
+ )
75
94
 
76
95
  # Reload the agent to pick up the newly enabled server
96
+ # NOTE: We don't block or wait - the server will be ready
97
+ # when the next prompt runs (pydantic-ai handles connection)
77
98
  try:
78
- from code_puppy.agents.runtime_manager import (
79
- get_runtime_agent_manager,
80
- )
81
-
82
- manager = get_runtime_agent_manager()
83
- manager.reload_agent()
99
+ agent = get_current_agent()
100
+ agent.reload_code_generation_agent()
101
+ # Clear MCP tool cache - it will be repopulated on next run
102
+ agent.update_mcp_tool_cache_sync()
84
103
  emit_info(
85
- "[dim]Agent reloaded with updated servers[/dim]",
104
+ "Agent reloaded with updated servers",
86
105
  message_group=group_id,
87
106
  )
88
107
  except Exception as e:
89
108
  logger.warning(f"Could not reload agent: {e}")
90
109
  else:
91
- emit_info(
92
- f"[red]✗ Failed to start server: {server_name}[/red]",
110
+ emit_error(
111
+ f"Failed to start server: {server_name}",
93
112
  message_group=group_id,
94
113
  )
95
114
 
96
115
  except Exception as e:
97
116
  logger.error(f"Error starting server '{server_name}': {e}")
98
- emit_info(f"[red]Failed to start server: {e}[/red]", message_group=group_id)
117
+ emit_error(f"Failed to start server: {e}", message_group=group_id)