code-puppy 0.0.135__py3-none-any.whl → 0.0.137__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 (61) hide show
  1. code_puppy/agent.py +15 -17
  2. code_puppy/agents/agent_manager.py +320 -9
  3. code_puppy/agents/base_agent.py +58 -2
  4. code_puppy/agents/runtime_manager.py +68 -42
  5. code_puppy/command_line/command_handler.py +82 -33
  6. code_puppy/command_line/mcp/__init__.py +10 -0
  7. code_puppy/command_line/mcp/add_command.py +183 -0
  8. code_puppy/command_line/mcp/base.py +35 -0
  9. code_puppy/command_line/mcp/handler.py +133 -0
  10. code_puppy/command_line/mcp/help_command.py +146 -0
  11. code_puppy/command_line/mcp/install_command.py +176 -0
  12. code_puppy/command_line/mcp/list_command.py +94 -0
  13. code_puppy/command_line/mcp/logs_command.py +126 -0
  14. code_puppy/command_line/mcp/remove_command.py +82 -0
  15. code_puppy/command_line/mcp/restart_command.py +92 -0
  16. code_puppy/command_line/mcp/search_command.py +117 -0
  17. code_puppy/command_line/mcp/start_all_command.py +126 -0
  18. code_puppy/command_line/mcp/start_command.py +98 -0
  19. code_puppy/command_line/mcp/status_command.py +185 -0
  20. code_puppy/command_line/mcp/stop_all_command.py +109 -0
  21. code_puppy/command_line/mcp/stop_command.py +79 -0
  22. code_puppy/command_line/mcp/test_command.py +107 -0
  23. code_puppy/command_line/mcp/utils.py +129 -0
  24. code_puppy/command_line/mcp/wizard_utils.py +259 -0
  25. code_puppy/command_line/model_picker_completion.py +21 -4
  26. code_puppy/command_line/prompt_toolkit_completion.py +9 -0
  27. code_puppy/config.py +5 -5
  28. code_puppy/main.py +23 -17
  29. code_puppy/mcp/__init__.py +42 -16
  30. code_puppy/mcp/async_lifecycle.py +51 -49
  31. code_puppy/mcp/blocking_startup.py +125 -113
  32. code_puppy/mcp/captured_stdio_server.py +63 -70
  33. code_puppy/mcp/circuit_breaker.py +63 -47
  34. code_puppy/mcp/config_wizard.py +169 -136
  35. code_puppy/mcp/dashboard.py +79 -71
  36. code_puppy/mcp/error_isolation.py +147 -100
  37. code_puppy/mcp/examples/retry_example.py +55 -42
  38. code_puppy/mcp/health_monitor.py +152 -141
  39. code_puppy/mcp/managed_server.py +100 -93
  40. code_puppy/mcp/manager.py +168 -156
  41. code_puppy/mcp/registry.py +148 -110
  42. code_puppy/mcp/retry_manager.py +63 -61
  43. code_puppy/mcp/server_registry_catalog.py +271 -225
  44. code_puppy/mcp/status_tracker.py +80 -80
  45. code_puppy/mcp/system_tools.py +47 -52
  46. code_puppy/messaging/message_queue.py +20 -13
  47. code_puppy/messaging/renderers.py +30 -15
  48. code_puppy/state_management.py +103 -0
  49. code_puppy/tui/app.py +64 -7
  50. code_puppy/tui/components/chat_view.py +3 -3
  51. code_puppy/tui/components/human_input_modal.py +12 -8
  52. code_puppy/tui/screens/__init__.py +2 -2
  53. code_puppy/tui/screens/mcp_install_wizard.py +208 -179
  54. code_puppy/tui/tests/test_agent_command.py +3 -3
  55. {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/METADATA +1 -1
  56. {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/RECORD +60 -42
  57. code_puppy/command_line/mcp_commands.py +0 -1789
  58. {code_puppy-0.0.135.data → code_puppy-0.0.137.data}/data/code_puppy/models.json +0 -0
  59. {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/WHEEL +0 -0
  60. {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/entry_points.txt +0 -0
  61. {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,133 @@
1
+ """
2
+ MCP Command Handler - Main router for MCP server management commands.
3
+
4
+ This module provides the MCPCommandHandler class that routes MCP commands
5
+ to their respective command modules.
6
+ """
7
+
8
+ import logging
9
+ import shlex
10
+
11
+ from code_puppy.messaging import emit_info
12
+
13
+ from .add_command import AddCommand
14
+ from .base import MCPCommandBase
15
+ from .help_command import HelpCommand
16
+ from .install_command import InstallCommand
17
+
18
+ # Import all command modules
19
+ from .list_command import ListCommand
20
+ from .logs_command import LogsCommand
21
+ from .remove_command import RemoveCommand
22
+ from .restart_command import RestartCommand
23
+ from .search_command import SearchCommand
24
+ from .start_all_command import StartAllCommand
25
+ from .start_command import StartCommand
26
+ from .status_command import StatusCommand
27
+ from .stop_all_command import StopAllCommand
28
+ from .stop_command import StopCommand
29
+ from .test_command import TestCommand
30
+
31
+ # Configure logging
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ class MCPCommandHandler(MCPCommandBase):
36
+ """
37
+ Main command handler for MCP server management operations.
38
+
39
+ Routes MCP commands to their respective command modules.
40
+ Each command is implemented in its own module for better maintainability.
41
+
42
+ Example usage:
43
+ handler = MCPCommandHandler()
44
+ handler.handle_mcp_command("/mcp list")
45
+ handler.handle_mcp_command("/mcp start filesystem")
46
+ handler.handle_mcp_command("/mcp status filesystem")
47
+ """
48
+
49
+ def __init__(self):
50
+ """Initialize the MCP command handler."""
51
+ super().__init__()
52
+
53
+ # Initialize command handlers
54
+ self._commands = {
55
+ "list": ListCommand(),
56
+ "start": StartCommand(),
57
+ "start-all": StartAllCommand(),
58
+ "stop": StopCommand(),
59
+ "stop-all": StopAllCommand(),
60
+ "restart": RestartCommand(),
61
+ "status": StatusCommand(),
62
+ "test": TestCommand(),
63
+ "add": AddCommand(),
64
+ "remove": RemoveCommand(),
65
+ "logs": LogsCommand(),
66
+ "search": SearchCommand(),
67
+ "install": InstallCommand(),
68
+ "help": HelpCommand(),
69
+ }
70
+
71
+ logger.info("MCPCommandHandler initialized with all command modules")
72
+
73
+ def handle_mcp_command(self, command: str) -> bool:
74
+ """
75
+ Handle MCP commands and route to appropriate handler.
76
+
77
+ Args:
78
+ command: The full command string (e.g., "/mcp list", "/mcp start server")
79
+
80
+ Returns:
81
+ True if command was handled successfully, False otherwise
82
+ """
83
+ group_id = self.generate_group_id()
84
+
85
+ try:
86
+ # Remove /mcp prefix and parse arguments
87
+ command = command.strip()
88
+ if not command.startswith("/mcp"):
89
+ return False
90
+
91
+ # Remove the /mcp prefix
92
+ args_str = command[4:].strip()
93
+
94
+ # If no subcommand, show status dashboard
95
+ if not args_str:
96
+ self._commands["list"].execute([], group_id=group_id)
97
+ return True
98
+
99
+ # Parse arguments using shlex for proper handling of quoted strings
100
+ try:
101
+ args = shlex.split(args_str)
102
+ except ValueError as e:
103
+ emit_info(
104
+ f"[red]Invalid command syntax: {e}[/red]", message_group=group_id
105
+ )
106
+ return True
107
+
108
+ if not args:
109
+ self._commands["list"].execute([], group_id=group_id)
110
+ return True
111
+
112
+ subcommand = args[0].lower()
113
+ sub_args = args[1:] if len(args) > 1 else []
114
+
115
+ # Route to appropriate command handler
116
+ command_handler = self._commands.get(subcommand)
117
+ if command_handler:
118
+ command_handler.execute(sub_args, group_id=group_id)
119
+ return True
120
+ else:
121
+ emit_info(
122
+ f"[yellow]Unknown MCP subcommand: {subcommand}[/yellow]",
123
+ message_group=group_id,
124
+ )
125
+ emit_info(
126
+ "Type '/mcp help' for available commands", message_group=group_id
127
+ )
128
+ return True
129
+
130
+ except Exception as e:
131
+ logger.error(f"Error handling MCP command '{command}': {e}")
132
+ emit_info(f"Error executing MCP command: {e}", message_group=group_id)
133
+ return True
@@ -0,0 +1,146 @@
1
+ """
2
+ MCP Help Command - Shows help for all MCP commands.
3
+ """
4
+
5
+ import logging
6
+ from typing import List, Optional
7
+
8
+ from rich.text import Text
9
+
10
+ from code_puppy.messaging import emit_info
11
+
12
+ from .base import MCPCommandBase
13
+
14
+ # Configure logging
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class HelpCommand(MCPCommandBase):
19
+ """
20
+ Command handler for showing MCP command help.
21
+
22
+ Displays comprehensive help information for all available MCP commands.
23
+ """
24
+
25
+ def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
26
+ """
27
+ Show help for MCP commands.
28
+
29
+ Args:
30
+ args: Command arguments (unused)
31
+ group_id: Optional message group ID for grouping related messages
32
+ """
33
+ if group_id is None:
34
+ group_id = self.generate_group_id()
35
+
36
+ try:
37
+ # Build help text programmatically to avoid markup conflicts
38
+ help_lines = []
39
+
40
+ # Title
41
+ help_lines.append(
42
+ Text("MCP Server Management Commands", style="bold magenta")
43
+ )
44
+ help_lines.append(Text(""))
45
+
46
+ # Registry Commands
47
+ help_lines.append(Text("Registry Commands:", style="bold cyan"))
48
+ help_lines.append(
49
+ Text("/mcp search", style="cyan")
50
+ + Text(" [query] Search 30+ pre-configured servers")
51
+ )
52
+ help_lines.append(
53
+ Text("/mcp install", style="cyan")
54
+ + Text(" <id> Install server from registry")
55
+ )
56
+ help_lines.append(Text(""))
57
+
58
+ # Core Commands
59
+ help_lines.append(Text("Core Commands:", style="bold cyan"))
60
+ help_lines.append(
61
+ Text("/mcp", style="cyan")
62
+ + Text(" Show server status dashboard")
63
+ )
64
+ help_lines.append(
65
+ Text("/mcp list", style="cyan")
66
+ + Text(" List all registered servers")
67
+ )
68
+ help_lines.append(
69
+ Text("/mcp start", style="cyan")
70
+ + Text(" <name> Start a specific server")
71
+ )
72
+ help_lines.append(
73
+ Text("/mcp start-all", style="cyan")
74
+ + Text(" Start all servers")
75
+ )
76
+ help_lines.append(
77
+ Text("/mcp stop", style="cyan")
78
+ + Text(" <name> Stop a specific server")
79
+ )
80
+ help_lines.append(
81
+ Text("/mcp stop-all", style="cyan")
82
+ + Text(" [group_id] Stop all running servers")
83
+ )
84
+ help_lines.append(
85
+ Text("/mcp restart", style="cyan")
86
+ + Text(" <name> Restart a specific server")
87
+ )
88
+ help_lines.append(Text(""))
89
+
90
+ # Management Commands
91
+ help_lines.append(Text("Management Commands:", style="bold cyan"))
92
+ help_lines.append(
93
+ Text("/mcp status", style="cyan")
94
+ + Text(" [name] Show detailed status (all servers or specific)")
95
+ )
96
+ help_lines.append(
97
+ Text("/mcp test", style="cyan")
98
+ + Text(" <name> Test connectivity to a server")
99
+ )
100
+ help_lines.append(
101
+ Text("/mcp logs", style="cyan")
102
+ + Text(" <name> [limit] Show recent events (default limit: 10)")
103
+ )
104
+ help_lines.append(
105
+ Text("/mcp add", style="cyan")
106
+ + Text(" [json] Add new server (JSON or wizard)")
107
+ )
108
+ help_lines.append(
109
+ Text("/mcp remove", style="cyan")
110
+ + Text(" <name> Remove/disable a server")
111
+ )
112
+ help_lines.append(
113
+ Text("/mcp help", style="cyan")
114
+ + Text(" Show this help message")
115
+ )
116
+ help_lines.append(Text(""))
117
+
118
+ # Status Indicators
119
+ help_lines.append(Text("Status Indicators:", style="bold"))
120
+ help_lines.append(
121
+ Text("✓ Running ✗ Stopped ⚠ Error ⏸ Quarantined ⭐ Popular")
122
+ )
123
+ help_lines.append(Text(""))
124
+
125
+ # Examples
126
+ help_lines.append(Text("Examples:", style="bold"))
127
+ examples_text = """/mcp search database # Find database servers
128
+ /mcp install postgres # Install PostgreSQL server
129
+ /mcp start filesystem # Start a specific server
130
+ /mcp start-all # Start all servers at once
131
+ /mcp stop-all # Stop all running servers
132
+ /mcp add {"name": "test", "type": "stdio", "command": "echo"}"""
133
+ help_lines.append(Text(examples_text, style="dim"))
134
+
135
+ # Combine all lines
136
+ final_text = Text()
137
+ for i, line in enumerate(help_lines):
138
+ if i > 0:
139
+ final_text.append("\n")
140
+ final_text.append_text(line)
141
+
142
+ emit_info(final_text, message_group=group_id)
143
+
144
+ except Exception as e:
145
+ logger.error(f"Error showing help: {e}")
146
+ emit_info(f"[red]Error showing help: {e}[/red]", message_group=group_id)
@@ -0,0 +1,176 @@
1
+ """
2
+ MCP Install Command - Installs pre-configured MCP servers from the registry.
3
+ """
4
+
5
+ import logging
6
+ from typing import List, Optional
7
+
8
+ from code_puppy.messaging import emit_info
9
+ from code_puppy.state_management import is_tui_mode
10
+
11
+ from .base import MCPCommandBase
12
+ from .wizard_utils import run_interactive_install_wizard
13
+
14
+ # Configure logging
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class InstallCommand(MCPCommandBase):
19
+ """
20
+ Command handler for installing MCP servers from registry.
21
+
22
+ Installs pre-configured MCP servers with optional interactive wizard.
23
+ """
24
+
25
+ def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
26
+ """
27
+ Install a pre-configured MCP server from the registry.
28
+
29
+ Args:
30
+ args: Server ID and optional custom name
31
+ group_id: Optional message group ID for grouping related messages
32
+ """
33
+ if group_id is None:
34
+ group_id = self.generate_group_id()
35
+
36
+ try:
37
+ # If in TUI mode, show message to use Ctrl+T
38
+ if is_tui_mode():
39
+ emit_info(
40
+ "In TUI mode, use Ctrl+T to open the MCP Install Wizard",
41
+ message_group=group_id,
42
+ )
43
+ return
44
+
45
+ # In interactive mode, use the comprehensive installer
46
+ if not args:
47
+ # No args - launch interactive wizard
48
+ success = run_interactive_install_wizard(self.manager, group_id)
49
+ if success:
50
+ try:
51
+ from code_puppy.agent import reload_mcp_servers
52
+
53
+ reload_mcp_servers()
54
+ except ImportError:
55
+ pass
56
+ return
57
+
58
+ # Has args - install directly from catalog
59
+ server_id = args[0]
60
+ success = self._install_from_catalog(server_id, group_id)
61
+ if success:
62
+ try:
63
+ from code_puppy.agent import reload_mcp_servers
64
+
65
+ reload_mcp_servers()
66
+ except ImportError:
67
+ pass
68
+ return
69
+
70
+ except ImportError:
71
+ emit_info("Server registry not available", message_group=group_id)
72
+ except Exception as e:
73
+ logger.error(f"Error installing server: {e}")
74
+ emit_info(f"Installation failed: {e}", message_group=group_id)
75
+
76
+ def _install_from_catalog(self, server_name_or_id: str, group_id: str) -> bool:
77
+ """Install a server directly from the catalog by name or ID."""
78
+ try:
79
+ from code_puppy.mcp.server_registry_catalog import catalog
80
+ from code_puppy.messaging import emit_prompt
81
+
82
+ from .utils import find_server_id_by_name
83
+ from .wizard_utils import install_server_from_catalog
84
+
85
+ # Try to find server by ID first, then by name/search
86
+ selected_server = catalog.get_by_id(server_name_or_id)
87
+
88
+ if not selected_server:
89
+ # Try searching by name
90
+ results = catalog.search(server_name_or_id)
91
+ if not results:
92
+ emit_info(
93
+ f"❌ No server found matching '{server_name_or_id}'",
94
+ message_group=group_id,
95
+ )
96
+ emit_info(
97
+ "Try '/mcp install' to browse available servers",
98
+ message_group=group_id,
99
+ )
100
+ return False
101
+ elif len(results) == 1:
102
+ selected_server = results[0]
103
+ else:
104
+ # Multiple matches, show them
105
+ emit_info(
106
+ f"🔍 Multiple servers found matching '{server_name_or_id}':",
107
+ message_group=group_id,
108
+ )
109
+ for i, server in enumerate(results[:5]):
110
+ indicators = []
111
+ if server.verified:
112
+ indicators.append("✓")
113
+ if server.popular:
114
+ indicators.append("⭐")
115
+
116
+ indicator_str = ""
117
+ if indicators:
118
+ indicator_str = " " + "".join(indicators)
119
+
120
+ emit_info(
121
+ f" {i + 1}. {server.display_name}{indicator_str}",
122
+ message_group=group_id,
123
+ )
124
+ emit_info(f" ID: {server.id}", message_group=group_id)
125
+
126
+ emit_info(
127
+ "Please use the exact server ID: '/mcp install <server_id>'",
128
+ message_group=group_id,
129
+ )
130
+ return False
131
+
132
+ # Show what we're installing
133
+ emit_info(
134
+ f"📦 Installing: {selected_server.display_name}", message_group=group_id
135
+ )
136
+ description = (
137
+ selected_server.description
138
+ if selected_server.description
139
+ else "No description available"
140
+ )
141
+ emit_info(f"Description: {description}", message_group=group_id)
142
+ emit_info("", message_group=group_id)
143
+
144
+ # Get custom name (default to server name)
145
+ server_name = emit_prompt(
146
+ f"Enter custom name for this server [{selected_server.name}]: "
147
+ ).strip()
148
+ if not server_name:
149
+ server_name = selected_server.name
150
+
151
+ # Check if name already exists
152
+ existing_server = find_server_id_by_name(self.manager, server_name)
153
+ if existing_server:
154
+ override = emit_prompt(
155
+ f"Server '{server_name}' already exists. Override it? [y/N]: "
156
+ )
157
+ if not override.lower().startswith("y"):
158
+ emit_info("Installation cancelled", message_group=group_id)
159
+ return False
160
+
161
+ # Install with default configuration (simplified)
162
+ env_vars = {}
163
+ cmd_args = {}
164
+
165
+ # Install the server
166
+ return install_server_from_catalog(
167
+ self.manager, selected_server, server_name, env_vars, cmd_args, group_id
168
+ )
169
+
170
+ except ImportError:
171
+ emit_info("Server catalog not available", message_group=group_id)
172
+ return False
173
+ except Exception as e:
174
+ logger.error(f"Error installing from catalog: {e}")
175
+ emit_info(f"[red]Installation error: {e}[/red]", message_group=group_id)
176
+ return False
@@ -0,0 +1,94 @@
1
+ """
2
+ MCP List Command - Lists all registered MCP servers in a formatted table.
3
+ """
4
+
5
+ import logging
6
+ from typing import List, Optional
7
+
8
+ from rich.table import Table
9
+ from rich.text import Text
10
+
11
+ from code_puppy.mcp.managed_server import ServerState
12
+ from code_puppy.messaging import emit_info
13
+
14
+ from .base import MCPCommandBase
15
+ from .utils import format_state_indicator, format_uptime
16
+
17
+ # Configure logging
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class ListCommand(MCPCommandBase):
22
+ """
23
+ Command handler for listing MCP servers.
24
+
25
+ Displays all registered MCP servers in a formatted table with status information.
26
+ """
27
+
28
+ def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
29
+ """
30
+ List all registered MCP servers in a formatted table.
31
+
32
+ Args:
33
+ args: Command arguments (unused for list command)
34
+ group_id: Optional message group ID for grouping related messages
35
+ """
36
+ if group_id is None:
37
+ group_id = self.generate_group_id()
38
+
39
+ try:
40
+ servers = self.manager.list_servers()
41
+
42
+ if not servers:
43
+ emit_info("No MCP servers registered", message_group=group_id)
44
+ return
45
+
46
+ # Create table for server list
47
+ table = Table(title="🔌 MCP Server Status Dashboard")
48
+ table.add_column("Name", style="cyan", no_wrap=True)
49
+ table.add_column("Type", style="dim", no_wrap=True)
50
+ table.add_column("State", justify="center")
51
+ table.add_column("Enabled", justify="center")
52
+ table.add_column("Uptime", style="dim")
53
+ table.add_column("Status", style="dim")
54
+
55
+ for server in servers:
56
+ # Format state with appropriate color and icon
57
+ state_display = format_state_indicator(server.state)
58
+
59
+ # Format enabled status
60
+ enabled_display = "✓" if server.enabled else "✗"
61
+ enabled_style = "green" if server.enabled else "red"
62
+
63
+ # Format uptime
64
+ uptime_display = format_uptime(server.uptime_seconds)
65
+
66
+ # Format status message
67
+ status_display = server.error_message or "OK"
68
+ if server.quarantined:
69
+ status_display = "Quarantined"
70
+
71
+ table.add_row(
72
+ server.name,
73
+ server.type.upper(),
74
+ state_display,
75
+ Text(enabled_display, style=enabled_style),
76
+ uptime_display,
77
+ status_display,
78
+ )
79
+
80
+ emit_info(table, message_group=group_id)
81
+
82
+ # Show summary
83
+ total = len(servers)
84
+ running = sum(
85
+ 1 for s in servers if s.state == ServerState.RUNNING and s.enabled
86
+ )
87
+ emit_info(
88
+ f"\n📊 Summary: {running}/{total} servers running",
89
+ message_group=group_id,
90
+ )
91
+
92
+ except Exception as e:
93
+ logger.error(f"Error listing MCP servers: {e}")
94
+ emit_info(f"[red]Error listing servers: {e}[/red]", message_group=group_id)
@@ -0,0 +1,126 @@
1
+ """
2
+ MCP Logs Command - Shows recent events/logs for a server.
3
+ """
4
+
5
+ import logging
6
+ from datetime import datetime
7
+ from typing import List, Optional
8
+
9
+ from rich.table import Table
10
+ from rich.text import Text
11
+
12
+ from code_puppy.messaging import emit_info
13
+
14
+ from .base import MCPCommandBase
15
+ from .utils import find_server_id_by_name, suggest_similar_servers
16
+
17
+ # Configure logging
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class LogsCommand(MCPCommandBase):
22
+ """
23
+ Command handler for showing MCP server logs.
24
+
25
+ Shows recent events/logs for a specific MCP server with configurable limit.
26
+ """
27
+
28
+ def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
29
+ """
30
+ Show recent events/logs for a server.
31
+
32
+ Args:
33
+ args: Command arguments, expects [server_name] and optional [limit]
34
+ group_id: Optional message group ID for grouping related messages
35
+ """
36
+ if group_id is None:
37
+ group_id = self.generate_group_id()
38
+
39
+ if not args:
40
+ emit_info("Usage: /mcp logs <server_name> [limit]", message_group=group_id)
41
+ return
42
+
43
+ server_name = args[0]
44
+ limit = 10 # Default limit
45
+
46
+ if len(args) > 1:
47
+ try:
48
+ limit = int(args[1])
49
+ if limit <= 0 or limit > 100:
50
+ emit_info(
51
+ "Limit must be between 1 and 100, using default: 10",
52
+ message_group=group_id,
53
+ )
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
+
61
+ try:
62
+ # Find server by name
63
+ server_id = find_server_id_by_name(self.manager, server_name)
64
+ 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
68
+
69
+ # Get server status which includes recent events
70
+ status = self.manager.get_server_status(server_id)
71
+
72
+ if not status.get("exists", True):
73
+ emit_info(
74
+ f"Server '{server_name}' status not available",
75
+ message_group=group_id,
76
+ )
77
+ return
78
+
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,
85
+ )
86
+ return
87
+
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")
93
+
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
+ )
98
+
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 "-"
121
+ )
122
+ emit_info(table, message_group=group_id)
123
+
124
+ 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)