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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. code_puppy/__init__.py +7 -1
  2. code_puppy/agents/__init__.py +2 -0
  3. code_puppy/agents/agent_c_reviewer.py +59 -6
  4. code_puppy/agents/agent_code_puppy.py +7 -1
  5. code_puppy/agents/agent_code_reviewer.py +12 -2
  6. code_puppy/agents/agent_cpp_reviewer.py +73 -6
  7. code_puppy/agents/agent_creator_agent.py +45 -4
  8. code_puppy/agents/agent_golang_reviewer.py +92 -3
  9. code_puppy/agents/agent_javascript_reviewer.py +101 -8
  10. code_puppy/agents/agent_manager.py +81 -4
  11. code_puppy/agents/agent_pack_leader.py +383 -0
  12. code_puppy/agents/agent_planning.py +163 -0
  13. code_puppy/agents/agent_python_programmer.py +165 -0
  14. code_puppy/agents/agent_python_reviewer.py +28 -6
  15. code_puppy/agents/agent_qa_expert.py +98 -6
  16. code_puppy/agents/agent_qa_kitten.py +12 -7
  17. code_puppy/agents/agent_security_auditor.py +113 -3
  18. code_puppy/agents/agent_terminal_qa.py +323 -0
  19. code_puppy/agents/agent_typescript_reviewer.py +106 -7
  20. code_puppy/agents/base_agent.py +802 -176
  21. code_puppy/agents/event_stream_handler.py +350 -0
  22. code_puppy/agents/pack/__init__.py +34 -0
  23. code_puppy/agents/pack/bloodhound.py +304 -0
  24. code_puppy/agents/pack/husky.py +321 -0
  25. code_puppy/agents/pack/retriever.py +393 -0
  26. code_puppy/agents/pack/shepherd.py +348 -0
  27. code_puppy/agents/pack/terrier.py +287 -0
  28. code_puppy/agents/pack/watchdog.py +367 -0
  29. code_puppy/agents/prompt_reviewer.py +145 -0
  30. code_puppy/agents/subagent_stream_handler.py +276 -0
  31. code_puppy/api/__init__.py +13 -0
  32. code_puppy/api/app.py +169 -0
  33. code_puppy/api/main.py +21 -0
  34. code_puppy/api/pty_manager.py +446 -0
  35. code_puppy/api/routers/__init__.py +12 -0
  36. code_puppy/api/routers/agents.py +36 -0
  37. code_puppy/api/routers/commands.py +217 -0
  38. code_puppy/api/routers/config.py +74 -0
  39. code_puppy/api/routers/sessions.py +232 -0
  40. code_puppy/api/templates/terminal.html +361 -0
  41. code_puppy/api/websocket.py +154 -0
  42. code_puppy/callbacks.py +142 -4
  43. code_puppy/chatgpt_codex_client.py +283 -0
  44. code_puppy/claude_cache_client.py +586 -0
  45. code_puppy/cli_runner.py +916 -0
  46. code_puppy/command_line/add_model_menu.py +1079 -0
  47. code_puppy/command_line/agent_menu.py +395 -0
  48. code_puppy/command_line/attachments.py +10 -5
  49. code_puppy/command_line/autosave_menu.py +605 -0
  50. code_puppy/command_line/clipboard.py +527 -0
  51. code_puppy/command_line/colors_menu.py +520 -0
  52. code_puppy/command_line/command_handler.py +176 -738
  53. code_puppy/command_line/command_registry.py +150 -0
  54. code_puppy/command_line/config_commands.py +715 -0
  55. code_puppy/command_line/core_commands.py +792 -0
  56. code_puppy/command_line/diff_menu.py +863 -0
  57. code_puppy/command_line/load_context_completion.py +15 -22
  58. code_puppy/command_line/mcp/base.py +0 -3
  59. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  60. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  61. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  62. code_puppy/command_line/mcp/edit_command.py +148 -0
  63. code_puppy/command_line/mcp/handler.py +9 -4
  64. code_puppy/command_line/mcp/help_command.py +6 -5
  65. code_puppy/command_line/mcp/install_command.py +15 -26
  66. code_puppy/command_line/mcp/install_menu.py +685 -0
  67. code_puppy/command_line/mcp/list_command.py +2 -2
  68. code_puppy/command_line/mcp/logs_command.py +174 -65
  69. code_puppy/command_line/mcp/remove_command.py +2 -2
  70. code_puppy/command_line/mcp/restart_command.py +12 -4
  71. code_puppy/command_line/mcp/search_command.py +16 -10
  72. code_puppy/command_line/mcp/start_all_command.py +18 -6
  73. code_puppy/command_line/mcp/start_command.py +47 -25
  74. code_puppy/command_line/mcp/status_command.py +4 -5
  75. code_puppy/command_line/mcp/stop_all_command.py +7 -1
  76. code_puppy/command_line/mcp/stop_command.py +8 -4
  77. code_puppy/command_line/mcp/test_command.py +2 -2
  78. code_puppy/command_line/mcp/wizard_utils.py +20 -16
  79. code_puppy/command_line/mcp_completion.py +174 -0
  80. code_puppy/command_line/model_picker_completion.py +75 -25
  81. code_puppy/command_line/model_settings_menu.py +884 -0
  82. code_puppy/command_line/motd.py +14 -8
  83. code_puppy/command_line/onboarding_slides.py +179 -0
  84. code_puppy/command_line/onboarding_wizard.py +340 -0
  85. code_puppy/command_line/pin_command_completion.py +329 -0
  86. code_puppy/command_line/prompt_toolkit_completion.py +463 -63
  87. code_puppy/command_line/session_commands.py +296 -0
  88. code_puppy/command_line/utils.py +54 -0
  89. code_puppy/config.py +898 -112
  90. code_puppy/error_logging.py +118 -0
  91. code_puppy/gemini_code_assist.py +385 -0
  92. code_puppy/gemini_model.py +602 -0
  93. code_puppy/http_utils.py +210 -148
  94. code_puppy/keymap.py +128 -0
  95. code_puppy/main.py +5 -698
  96. code_puppy/mcp_/__init__.py +17 -0
  97. code_puppy/mcp_/async_lifecycle.py +35 -4
  98. code_puppy/mcp_/blocking_startup.py +70 -43
  99. code_puppy/mcp_/captured_stdio_server.py +2 -2
  100. code_puppy/mcp_/config_wizard.py +4 -4
  101. code_puppy/mcp_/dashboard.py +15 -6
  102. code_puppy/mcp_/managed_server.py +65 -38
  103. code_puppy/mcp_/manager.py +146 -52
  104. code_puppy/mcp_/mcp_logs.py +224 -0
  105. code_puppy/mcp_/registry.py +6 -6
  106. code_puppy/mcp_/server_registry_catalog.py +24 -5
  107. code_puppy/messaging/__init__.py +199 -2
  108. code_puppy/messaging/bus.py +610 -0
  109. code_puppy/messaging/commands.py +167 -0
  110. code_puppy/messaging/markdown_patches.py +57 -0
  111. code_puppy/messaging/message_queue.py +17 -48
  112. code_puppy/messaging/messages.py +500 -0
  113. code_puppy/messaging/queue_console.py +1 -24
  114. code_puppy/messaging/renderers.py +43 -146
  115. code_puppy/messaging/rich_renderer.py +1027 -0
  116. code_puppy/messaging/spinner/__init__.py +21 -5
  117. code_puppy/messaging/spinner/console_spinner.py +86 -51
  118. code_puppy/messaging/subagent_console.py +461 -0
  119. code_puppy/model_factory.py +634 -83
  120. code_puppy/model_utils.py +167 -0
  121. code_puppy/models.json +66 -68
  122. code_puppy/models_dev_api.json +1 -0
  123. code_puppy/models_dev_parser.py +592 -0
  124. code_puppy/plugins/__init__.py +164 -10
  125. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  126. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  127. code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
  128. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  129. code_puppy/plugins/antigravity_oauth/constants.py +136 -0
  130. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  131. code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
  132. code_puppy/plugins/antigravity_oauth/storage.py +271 -0
  133. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  134. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  135. code_puppy/plugins/antigravity_oauth/transport.py +767 -0
  136. code_puppy/plugins/antigravity_oauth/utils.py +169 -0
  137. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  138. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  139. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
  140. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
  141. code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
  142. code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
  143. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  144. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  145. code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
  146. code_puppy/plugins/claude_code_oauth/config.py +50 -0
  147. code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
  148. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  149. code_puppy/plugins/claude_code_oauth/utils.py +518 -0
  150. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  151. code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
  152. code_puppy/plugins/example_custom_command/README.md +280 -0
  153. code_puppy/plugins/example_custom_command/register_callbacks.py +2 -2
  154. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  155. code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
  156. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  157. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  158. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  159. code_puppy/plugins/oauth_puppy_html.py +228 -0
  160. code_puppy/plugins/shell_safety/__init__.py +6 -0
  161. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  162. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  163. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  164. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  165. code_puppy/prompts/codex_system_prompt.md +310 -0
  166. code_puppy/pydantic_patches.py +131 -0
  167. code_puppy/reopenable_async_client.py +8 -8
  168. code_puppy/round_robin_model.py +9 -12
  169. code_puppy/session_storage.py +2 -1
  170. code_puppy/status_display.py +21 -4
  171. code_puppy/summarization_agent.py +41 -13
  172. code_puppy/terminal_utils.py +418 -0
  173. code_puppy/tools/__init__.py +37 -1
  174. code_puppy/tools/agent_tools.py +536 -52
  175. code_puppy/tools/browser/__init__.py +37 -0
  176. code_puppy/tools/browser/browser_control.py +19 -23
  177. code_puppy/tools/browser/browser_interactions.py +41 -48
  178. code_puppy/tools/browser/browser_locators.py +36 -38
  179. code_puppy/tools/browser/browser_manager.py +316 -0
  180. code_puppy/tools/browser/browser_navigation.py +16 -16
  181. code_puppy/tools/browser/browser_screenshot.py +79 -143
  182. code_puppy/tools/browser/browser_scripts.py +32 -42
  183. code_puppy/tools/browser/browser_workflows.py +44 -27
  184. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  185. code_puppy/tools/browser/terminal_command_tools.py +521 -0
  186. code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
  187. code_puppy/tools/browser/terminal_tools.py +525 -0
  188. code_puppy/tools/command_runner.py +930 -147
  189. code_puppy/tools/common.py +1113 -5
  190. code_puppy/tools/display.py +84 -0
  191. code_puppy/tools/file_modifications.py +288 -89
  192. code_puppy/tools/file_operations.py +226 -154
  193. code_puppy/tools/subagent_context.py +158 -0
  194. code_puppy/uvx_detection.py +242 -0
  195. code_puppy/version_checker.py +30 -11
  196. code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
  197. code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
  198. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/METADATA +149 -75
  199. code_puppy-0.0.366.dist-info/RECORD +217 -0
  200. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
  201. code_puppy/command_line/mcp/add_command.py +0 -183
  202. code_puppy/messaging/spinner/textual_spinner.py +0 -106
  203. code_puppy/tools/browser/camoufox_manager.py +0 -216
  204. code_puppy/tools/browser/vqa_agent.py +0 -70
  205. code_puppy/tui/__init__.py +0 -10
  206. code_puppy/tui/app.py +0 -1105
  207. code_puppy/tui/components/__init__.py +0 -21
  208. code_puppy/tui/components/chat_view.py +0 -551
  209. code_puppy/tui/components/command_history_modal.py +0 -218
  210. code_puppy/tui/components/copy_button.py +0 -139
  211. code_puppy/tui/components/custom_widgets.py +0 -63
  212. code_puppy/tui/components/human_input_modal.py +0 -175
  213. code_puppy/tui/components/input_area.py +0 -167
  214. code_puppy/tui/components/sidebar.py +0 -309
  215. code_puppy/tui/components/status_bar.py +0 -185
  216. code_puppy/tui/messages.py +0 -27
  217. code_puppy/tui/models/__init__.py +0 -8
  218. code_puppy/tui/models/chat_message.py +0 -25
  219. code_puppy/tui/models/command_history.py +0 -89
  220. code_puppy/tui/models/enums.py +0 -24
  221. code_puppy/tui/screens/__init__.py +0 -17
  222. code_puppy/tui/screens/autosave_picker.py +0 -175
  223. code_puppy/tui/screens/help.py +0 -130
  224. code_puppy/tui/screens/mcp_install_wizard.py +0 -803
  225. code_puppy/tui/screens/settings.py +0 -306
  226. code_puppy/tui/screens/tools.py +0 -74
  227. code_puppy/tui_state.py +0 -55
  228. code_puppy-0.0.214.data/data/code_puppy/models.json +0 -112
  229. code_puppy-0.0.214.dist-info/RECORD +0 -131
  230. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +0 -0
  231. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
@@ -1,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
 
@@ -39,21 +40,21 @@ class SearchCommand(MCPCommandBase):
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,6 +6,8 @@ import logging
6
6
  import time
7
7
  from typing import List, Optional
8
8
 
9
+ from rich.text import Text
10
+
9
11
  from code_puppy.mcp_.managed_server import ServerState
10
12
  from code_puppy.messaging import emit_info
11
13
 
@@ -67,20 +69,23 @@ class StartAllCommand(MCPCommandBase):
67
69
  if success:
68
70
  started_count += 1
69
71
  emit_info(
70
- f" [green]✓ Started: {server_name}[/green]",
72
+ Text.from_markup(f" [green]✓ Started: {server_name}[/green]"),
71
73
  message_group=group_id,
72
74
  )
73
75
  else:
74
76
  failed_count += 1
75
77
  emit_info(
76
- 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,
77
80
  )
78
81
 
79
82
  # Summary
80
83
  emit_info("", message_group=group_id)
81
84
  if started_count > 0:
82
85
  emit_info(
83
- f"[green]Started {started_count} server(s)[/green]",
86
+ Text.from_markup(
87
+ f"[green]Started {started_count} server(s)[/green]"
88
+ ),
84
89
  message_group=group_id,
85
90
  )
86
91
  if already_running > 0:
@@ -90,7 +95,9 @@ class StartAllCommand(MCPCommandBase):
90
95
  )
91
96
  if failed_count > 0:
92
97
  emit_info(
93
- 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
+ ),
94
101
  message_group=group_id,
95
102
  )
96
103
 
@@ -109,8 +116,12 @@ class StartAllCommand(MCPCommandBase):
109
116
  try:
110
117
  agent = get_current_agent()
111
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()
112
121
  emit_info(
113
- "[dim]Agent reloaded with updated servers[/dim]",
122
+ Text.from_markup(
123
+ "[dim]Agent reloaded with updated servers[/dim]"
124
+ ),
114
125
  message_group=group_id,
115
126
  )
116
127
  except Exception as e:
@@ -119,5 +130,6 @@ class StartAllCommand(MCPCommandBase):
119
130
  except Exception as e:
120
131
  logger.error(f"Error starting all servers: {e}")
121
132
  emit_info(
122
- 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,
123
135
  )
@@ -3,10 +3,11 @@ 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
9
+
10
+ from code_puppy.messaging import emit_error, emit_info, emit_success
10
11
 
11
12
  from ...agents import get_current_agent
12
13
  from .base import MCPCommandBase
@@ -21,6 +22,7 @@ class StartCommand(MCPCommandBase):
21
22
  Command handler for starting MCP servers.
22
23
 
23
24
  Starts a specific MCP server by name and reloads the agent.
25
+ The server subprocess starts asynchronously in the background.
24
26
  """
25
27
 
26
28
  def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
@@ -36,7 +38,7 @@ class StartCommand(MCPCommandBase):
36
38
 
37
39
  if not args:
38
40
  emit_info(
39
- "[yellow]Usage: /mcp start <server_name>[/yellow]",
41
+ Text.from_markup("[yellow]Usage: /mcp start <server_name>[/yellow]"),
40
42
  message_group=group_id,
41
43
  )
42
44
  return
@@ -47,49 +49,69 @@ class StartCommand(MCPCommandBase):
47
49
  # Find server by name
48
50
  server_id = find_server_id_by_name(self.manager, server_name)
49
51
  if not server_id:
50
- emit_info(
51
- f"[red]Server '{server_name}' not found[/red]",
52
+ emit_error(
53
+ f"Server '{server_name}' not found",
52
54
  message_group=group_id,
53
55
  )
54
56
  suggest_similar_servers(self.manager, server_name, group_id=group_id)
55
57
  return
56
58
 
57
- # 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)
58
73
  success = self.manager.start_server_sync(server_id)
59
74
 
60
75
  if success:
61
- # This and subsequent messages will auto-group with the first message
62
- emit_info(
63
- f"[green]✓ Started server: {server_name}[/green]",
64
- message_group=group_id,
65
- )
66
-
67
- # Give async tasks a moment to complete
68
- try:
69
- import asyncio
70
-
71
- asyncio.get_running_loop() # Check if in async context
72
- # If we're in async context, wait a bit for server to start
73
- time.sleep(0.5) # Small delay to let async tasks progress
74
- except RuntimeError:
75
- 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
+ )
76
94
 
77
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)
78
98
  try:
79
99
  agent = get_current_agent()
80
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()
81
103
  emit_info(
82
- "[dim]Agent reloaded with updated servers[/dim]",
104
+ "Agent reloaded with updated servers",
83
105
  message_group=group_id,
84
106
  )
85
107
  except Exception as e:
86
108
  logger.warning(f"Could not reload agent: {e}")
87
109
  else:
88
- emit_info(
89
- f"[red]✗ Failed to start server: {server_name}[/red]",
110
+ emit_error(
111
+ f"Failed to start server: {server_name}",
90
112
  message_group=group_id,
91
113
  )
92
114
 
93
115
  except Exception as e:
94
116
  logger.error(f"Error starting server '{server_name}': {e}")
95
- 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)
@@ -7,9 +7,10 @@ from datetime import datetime
7
7
  from typing import List, Optional
8
8
 
9
9
  from rich.panel import Panel
10
+ from rich.text import Text
10
11
 
11
12
  from code_puppy.mcp_.managed_server import ServerState
12
- from code_puppy.messaging import emit_info
13
+ from code_puppy.messaging import emit_error, emit_info
13
14
 
14
15
  from .base import MCPCommandBase
15
16
  from .list_command import ListCommand
@@ -158,7 +159,7 @@ class StatusCommand(MCPCommandBase):
158
159
  status_lines.append(f"[bold]Metadata:[/bold] {len(metadata)} keys")
159
160
 
160
161
  # Create and show the panel
161
- panel_content = "\n".join(status_lines)
162
+ panel_content = Text.from_markup("\n".join(status_lines))
162
163
  panel = Panel(
163
164
  panel_content, title=f"🔌 {server_name} Status", border_style="cyan"
164
165
  )
@@ -180,6 +181,4 @@ class StatusCommand(MCPCommandBase):
180
181
  logger.error(
181
182
  f"Error getting detailed status for server '{server_name}': {e}"
182
183
  )
183
- emit_info(
184
- f"[red]Error getting server status: {e}[/red]", message_group=group_id
185
- )
184
+ emit_error(f"Error getting server status: {e}", message_group=group_id)
@@ -6,6 +6,8 @@ import logging
6
6
  import time
7
7
  from typing import List, Optional
8
8
 
9
+ from rich.text import Text
10
+
9
11
  from code_puppy.mcp_.managed_server import ServerState
10
12
  from code_puppy.messaging import emit_info
11
13
 
@@ -94,8 +96,12 @@ class StopAllCommand(MCPCommandBase):
94
96
  try:
95
97
  agent = get_current_agent()
96
98
  agent.reload_code_generation_agent()
99
+ # Update MCP tool cache immediately so token counts reflect the change
100
+ agent.update_mcp_tool_cache_sync()
97
101
  emit_info(
98
- "[dim]Agent reloaded with updated servers[/dim]",
102
+ Text.from_markup(
103
+ "[dim]Agent reloaded with updated servers[/dim]"
104
+ ),
99
105
  message_group=group_id,
100
106
  )
101
107
  except Exception as e: