code-puppy 0.0.302__py3-none-any.whl → 0.0.323__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 (65) hide show
  1. code_puppy/agents/base_agent.py +373 -46
  2. code_puppy/chatgpt_codex_client.py +283 -0
  3. code_puppy/cli_runner.py +795 -0
  4. code_puppy/command_line/add_model_menu.py +8 -1
  5. code_puppy/command_line/autosave_menu.py +266 -35
  6. code_puppy/command_line/colors_menu.py +515 -0
  7. code_puppy/command_line/command_handler.py +8 -2
  8. code_puppy/command_line/config_commands.py +59 -10
  9. code_puppy/command_line/core_commands.py +19 -7
  10. code_puppy/command_line/mcp/edit_command.py +3 -1
  11. code_puppy/command_line/mcp/handler.py +7 -2
  12. code_puppy/command_line/mcp/install_command.py +8 -3
  13. code_puppy/command_line/mcp/logs_command.py +173 -64
  14. code_puppy/command_line/mcp/restart_command.py +7 -2
  15. code_puppy/command_line/mcp/search_command.py +10 -4
  16. code_puppy/command_line/mcp/start_all_command.py +16 -6
  17. code_puppy/command_line/mcp/start_command.py +3 -1
  18. code_puppy/command_line/mcp/status_command.py +2 -1
  19. code_puppy/command_line/mcp/stop_all_command.py +5 -1
  20. code_puppy/command_line/mcp/stop_command.py +3 -1
  21. code_puppy/command_line/mcp/wizard_utils.py +10 -4
  22. code_puppy/command_line/model_settings_menu.py +53 -7
  23. code_puppy/command_line/prompt_toolkit_completion.py +16 -2
  24. code_puppy/command_line/session_commands.py +11 -4
  25. code_puppy/config.py +103 -15
  26. code_puppy/keymap.py +8 -2
  27. code_puppy/main.py +5 -828
  28. code_puppy/mcp_/__init__.py +17 -0
  29. code_puppy/mcp_/blocking_startup.py +61 -32
  30. code_puppy/mcp_/config_wizard.py +5 -1
  31. code_puppy/mcp_/managed_server.py +23 -3
  32. code_puppy/mcp_/manager.py +65 -0
  33. code_puppy/mcp_/mcp_logs.py +224 -0
  34. code_puppy/messaging/__init__.py +20 -4
  35. code_puppy/messaging/bus.py +64 -0
  36. code_puppy/messaging/markdown_patches.py +57 -0
  37. code_puppy/messaging/messages.py +16 -0
  38. code_puppy/messaging/renderers.py +21 -9
  39. code_puppy/messaging/rich_renderer.py +113 -67
  40. code_puppy/messaging/spinner/console_spinner.py +34 -0
  41. code_puppy/model_factory.py +185 -30
  42. code_puppy/model_utils.py +57 -48
  43. code_puppy/models.json +19 -5
  44. code_puppy/plugins/chatgpt_oauth/config.py +5 -1
  45. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +5 -6
  46. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +3 -3
  47. code_puppy/plugins/chatgpt_oauth/test_plugin.py +26 -11
  48. code_puppy/plugins/chatgpt_oauth/utils.py +180 -65
  49. code_puppy/plugins/claude_code_oauth/register_callbacks.py +28 -0
  50. code_puppy/plugins/claude_code_oauth/utils.py +1 -0
  51. code_puppy/plugins/shell_safety/agent_shell_safety.py +1 -118
  52. code_puppy/plugins/shell_safety/register_callbacks.py +44 -3
  53. code_puppy/prompts/codex_system_prompt.md +310 -0
  54. code_puppy/pydantic_patches.py +131 -0
  55. code_puppy/terminal_utils.py +126 -0
  56. code_puppy/tools/agent_tools.py +34 -9
  57. code_puppy/tools/command_runner.py +361 -32
  58. code_puppy/tools/file_operations.py +33 -45
  59. {code_puppy-0.0.302.data → code_puppy-0.0.323.data}/data/code_puppy/models.json +19 -5
  60. {code_puppy-0.0.302.dist-info → code_puppy-0.0.323.dist-info}/METADATA +1 -1
  61. {code_puppy-0.0.302.dist-info → code_puppy-0.0.323.dist-info}/RECORD +65 -57
  62. {code_puppy-0.0.302.data → code_puppy-0.0.323.data}/data/code_puppy/models_dev_api.json +0 -0
  63. {code_puppy-0.0.302.dist-info → code_puppy-0.0.323.dist-info}/WHEEL +0 -0
  64. {code_puppy-0.0.302.dist-info → code_puppy-0.0.323.dist-info}/entry_points.txt +0 -0
  65. {code_puppy-0.0.302.dist-info → code_puppy-0.0.323.dist-info}/licenses/LICENSE +0 -0
@@ -145,6 +145,8 @@ def handle_exit_command(command: str) -> bool:
145
145
  )
146
146
  def handle_agent_command(command: str) -> bool:
147
147
  """Handle agent switching."""
148
+ from rich.text import Text
149
+
148
150
  from code_puppy.agents import (
149
151
  get_agent_descriptions,
150
152
  get_available_agents,
@@ -201,7 +203,9 @@ def handle_agent_command(command: str) -> bool:
201
203
  )
202
204
  emit_info(f"{new_agent.description}", message_group=group_id)
203
205
  emit_info(
204
- f"[dim]Auto-save session rotated to: {new_session_id}[/dim]",
206
+ Text.from_markup(
207
+ f"[dim]Auto-save session rotated to: {new_session_id}[/dim]"
208
+ ),
205
209
  message_group=group_id,
206
210
  )
207
211
  else:
@@ -224,15 +228,19 @@ def handle_agent_command(command: str) -> bool:
224
228
  group_id = str(uuid.uuid4())
225
229
 
226
230
  emit_info(
227
- f"[bold green]Current Agent:[/bold green] {current_agent.display_name}",
231
+ Text.from_markup(
232
+ f"[bold green]Current Agent:[/bold green] {current_agent.display_name}"
233
+ ),
228
234
  message_group=group_id,
229
235
  )
230
236
  emit_info(
231
- f"[dim]{current_agent.description}[/dim]\n", message_group=group_id
237
+ Text.from_markup(f"[dim]{current_agent.description}[/dim]\n"),
238
+ message_group=group_id,
232
239
  )
233
240
 
234
241
  emit_info(
235
- "[bold magenta]Available Agents:[/bold magenta]", message_group=group_id
242
+ Text.from_markup("[bold magenta]Available Agents:[/bold magenta]"),
243
+ message_group=group_id,
236
244
  )
237
245
  for name, display_name in available_agents.items():
238
246
  description = descriptions.get(name, "No description")
@@ -240,13 +248,15 @@ def handle_agent_command(command: str) -> bool:
240
248
  " [green]← current[/green]" if name == current_agent.name else ""
241
249
  )
242
250
  emit_info(
243
- f" [cyan]{name:<12}[/cyan] {display_name}{current_marker}",
251
+ Text.from_markup(
252
+ f" [cyan]{name:<12}[/cyan] {display_name}{current_marker}"
253
+ ),
244
254
  message_group=group_id,
245
255
  )
246
256
  emit_info(f" {description}", message_group=group_id)
247
257
 
248
258
  emit_info(
249
- "\n[yellow]Usage:[/yellow] /agent <agent-name>",
259
+ Text.from_markup("\n[yellow]Usage:[/yellow] /agent <agent-name>"),
250
260
  message_group=group_id,
251
261
  )
252
262
  return True
@@ -292,7 +302,9 @@ def handle_agent_command(command: str) -> bool:
292
302
  )
293
303
  emit_info(f"{new_agent.description}", message_group=group_id)
294
304
  emit_info(
295
- f"[dim]Auto-save session rotated to: {new_session_id}[/dim]",
305
+ Text.from_markup(
306
+ f"[dim]Auto-save session rotated to: {new_session_id}[/dim]"
307
+ ),
296
308
  message_group=group_id,
297
309
  )
298
310
  return True
@@ -8,6 +8,8 @@ import logging
8
8
  import os
9
9
  from typing import List, Optional
10
10
 
11
+ from rich.text import Text
12
+
11
13
  from code_puppy.config import MCP_SERVERS_FILE
12
14
  from code_puppy.messaging import emit_error, emit_info, emit_warning
13
15
 
@@ -39,7 +41,7 @@ class EditCommand(MCPCommandBase):
39
41
  # Need a server name
40
42
  if not args:
41
43
  emit_info(
42
- "[yellow]Usage: /mcp edit <server_name>[/yellow]",
44
+ Text.from_markup("[yellow]Usage: /mcp edit <server_name>[/yellow]"),
43
45
  message_group=group_id,
44
46
  )
45
47
  emit_info(
@@ -8,6 +8,8 @@ to their respective command modules.
8
8
  import logging
9
9
  import shlex
10
10
 
11
+ from rich.text import Text
12
+
11
13
  from code_puppy.messaging import emit_info
12
14
 
13
15
  from .add_command import AddCommand
@@ -103,7 +105,8 @@ class MCPCommandHandler(MCPCommandBase):
103
105
  args = shlex.split(args_str)
104
106
  except ValueError as e:
105
107
  emit_info(
106
- f"[red]Invalid command syntax: {e}[/red]", message_group=group_id
108
+ Text.from_markup(f"[red]Invalid command syntax: {e}[/red]"),
109
+ message_group=group_id,
107
110
  )
108
111
  return True
109
112
 
@@ -121,7 +124,9 @@ class MCPCommandHandler(MCPCommandBase):
121
124
  return True
122
125
  else:
123
126
  emit_info(
124
- f"[yellow]Unknown MCP subcommand: {subcommand}[/yellow]",
127
+ Text.from_markup(
128
+ f"[yellow]Unknown MCP subcommand: {subcommand}[/yellow]"
129
+ ),
125
130
  message_group=group_id,
126
131
  )
127
132
  emit_info(
@@ -5,6 +5,8 @@ MCP Install Command - Installs pre-configured MCP servers from the registry.
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_error, emit_info
9
11
 
10
12
  from .base import MCPCommandBase
@@ -150,7 +152,9 @@ class InstallCommand(MCPCommandBase):
150
152
  required_env_vars = selected_server.get_environment_vars()
151
153
  if required_env_vars:
152
154
  emit_info(
153
- "\n[yellow]Required Environment Variables:[/yellow]",
155
+ Text.from_markup(
156
+ "\n[yellow]Required Environment Variables:[/yellow]"
157
+ ),
154
158
  message_group=group_id,
155
159
  )
156
160
  for var in required_env_vars:
@@ -160,7 +164,7 @@ class InstallCommand(MCPCommandBase):
160
164
  current_value = os.environ.get(var, "")
161
165
  if current_value:
162
166
  emit_info(
163
- f" {var}: [green]Already set[/green]",
167
+ Text.from_markup(f" {var}: [green]Already set[/green]"),
164
168
  message_group=group_id,
165
169
  )
166
170
  env_vars[var] = current_value
@@ -173,7 +177,8 @@ class InstallCommand(MCPCommandBase):
173
177
  required_cmd_args = selected_server.get_command_line_args()
174
178
  if required_cmd_args:
175
179
  emit_info(
176
- "\n[yellow]Command Line Arguments:[/yellow]", message_group=group_id
180
+ Text.from_markup("\n[yellow]Command Line Arguments:[/yellow]"),
181
+ message_group=group_id,
177
182
  )
178
183
  for arg_config in required_cmd_args:
179
184
  name = arg_config.get("name", "")
@@ -1,14 +1,21 @@
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.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
+ )
12
19
  from code_puppy.messaging import emit_error, emit_info
13
20
 
14
21
  from .base import MCPCommandBase
@@ -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", [])
80
-
81
- if not recent_events:
82
- emit_info(
83
- f"No recent events for server: {server_name}",
84
- message_group=group_id,
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)"
85
169
  )
86
- return
87
170
 
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")
171
+ # Format log content with syntax highlighting
172
+ log_content = "\n".join(log_lines)
173
+
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
+ )
93
182
 
94
- # Take only the requested number of events
95
- events_to_show = (
96
- recent_events[-limit:] if len(recent_events) > limit else recent_events
183
+ panel = Panel(
184
+ syntax,
185
+ title=header,
186
+ subtitle=f"Log file: {get_log_file_path(server_name)}",
187
+ border_style="dim",
97
188
  )
98
189
 
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 "-"
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:
194
+ emit_info(
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
+ ),
199
+ message_group=group_id,
121
200
  )
122
- emit_info(table, message_group=group_id)
123
201
 
124
202
  except Exception as e:
125
203
  logger.error(f"Error getting logs for server '{server_name}': {e}")
126
204
  emit_error(f"Error getting logs: {e}", message_group=group_id)
205
+
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)
216
+
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,
221
+ )
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
+ )
232
+
233
+ except Exception as e:
234
+ logger.error(f"Error clearing logs for server '{server_name}': {e}")
235
+ emit_error(f"Error clearing logs: {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
@@ -72,7 +74,9 @@ class RestartCommand(MCPCommandBase):
72
74
  # Update MCP tool cache immediately so token counts reflect the change
73
75
  agent.update_mcp_tool_cache_sync()
74
76
  emit_info(
75
- "[dim]Agent reloaded with updated servers[/dim]",
77
+ Text.from_markup(
78
+ "[dim]Agent reloaded with updated servers[/dim]"
79
+ ),
76
80
  message_group=group_id,
77
81
  )
78
82
  except Exception as e:
@@ -91,5 +95,6 @@ class RestartCommand(MCPCommandBase):
91
95
  except Exception as e:
92
96
  logger.error(f"Error restarting server '{server_name}': {e}")
93
97
  emit_info(
94
- 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,
95
100
  )
@@ -6,6 +6,7 @@ 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
11
  from code_puppy.messaging import emit_info, emit_system_message, emit_warning
11
12
 
@@ -99,19 +100,24 @@ class SearchCommand(MCPCommandBase):
99
100
  emit_system_message(table, message_group=group_id)
100
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
 
@@ -112,7 +119,9 @@ class StartAllCommand(MCPCommandBase):
112
119
  # Update MCP tool cache immediately so token counts reflect the change
113
120
  agent.update_mcp_tool_cache_sync()
114
121
  emit_info(
115
- "[dim]Agent reloaded with updated servers[/dim]",
122
+ Text.from_markup(
123
+ "[dim]Agent reloaded with updated servers[/dim]"
124
+ ),
116
125
  message_group=group_id,
117
126
  )
118
127
  except Exception as e:
@@ -121,5 +130,6 @@ class StartAllCommand(MCPCommandBase):
121
130
  except Exception as e:
122
131
  logger.error(f"Error starting all servers: {e}")
123
132
  emit_info(
124
- 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,
125
135
  )
@@ -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.messaging import emit_error, emit_info, emit_success
10
12
 
11
13
  from ...agents import get_current_agent
@@ -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
@@ -7,6 +7,7 @@ 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
13
  from code_puppy.messaging import emit_error, emit_info
@@ -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
  )
@@ -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
 
@@ -97,7 +99,9 @@ class StopAllCommand(MCPCommandBase):
97
99
  # Update MCP tool cache immediately so token counts reflect the change
98
100
  agent.update_mcp_tool_cache_sync()
99
101
  emit_info(
100
- "[dim]Agent reloaded with updated servers[/dim]",
102
+ Text.from_markup(
103
+ "[dim]Agent reloaded with updated servers[/dim]"
104
+ ),
101
105
  message_group=group_id,
102
106
  )
103
107
  except Exception as e:
@@ -5,6 +5,8 @@ MCP Stop Command - Stops 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_error, emit_info
9
11
 
10
12
  from ...agents import get_current_agent
@@ -35,7 +37,7 @@ class StopCommand(MCPCommandBase):
35
37
 
36
38
  if not args:
37
39
  emit_info(
38
- "[yellow]Usage: /mcp stop <server_name>[/yellow]",
40
+ Text.from_markup("[yellow]Usage: /mcp stop <server_name>[/yellow]"),
39
41
  message_group=group_id,
40
42
  )
41
43
  return