claude-mpm 4.5.6__py3-none-any.whl → 4.5.11__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +20 -5
- claude_mpm/agents/BASE_OPS.md +10 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +28 -4
- claude_mpm/agents/agent_loader.py +19 -2
- claude_mpm/agents/base_agent_loader.py +5 -5
- claude_mpm/agents/templates/agent-manager.json +3 -3
- claude_mpm/agents/templates/agentic-coder-optimizer.json +3 -3
- claude_mpm/agents/templates/api_qa.json +1 -1
- claude_mpm/agents/templates/clerk-ops.json +3 -3
- claude_mpm/agents/templates/code_analyzer.json +3 -3
- claude_mpm/agents/templates/dart_engineer.json +294 -0
- claude_mpm/agents/templates/data_engineer.json +3 -3
- claude_mpm/agents/templates/documentation.json +2 -2
- claude_mpm/agents/templates/engineer.json +2 -2
- claude_mpm/agents/templates/gcp_ops_agent.json +2 -2
- claude_mpm/agents/templates/imagemagick.json +1 -1
- claude_mpm/agents/templates/local_ops_agent.json +363 -49
- claude_mpm/agents/templates/memory_manager.json +2 -2
- claude_mpm/agents/templates/nextjs_engineer.json +2 -2
- claude_mpm/agents/templates/ops.json +2 -2
- claude_mpm/agents/templates/php-engineer.json +1 -1
- claude_mpm/agents/templates/project_organizer.json +1 -1
- claude_mpm/agents/templates/prompt-engineer.json +6 -4
- claude_mpm/agents/templates/python_engineer.json +2 -2
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/react_engineer.json +3 -3
- claude_mpm/agents/templates/refactoring_engineer.json +3 -3
- claude_mpm/agents/templates/research.json +2 -2
- claude_mpm/agents/templates/security.json +2 -2
- claude_mpm/agents/templates/ticketing.json +2 -2
- claude_mpm/agents/templates/typescript_engineer.json +2 -2
- claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
- claude_mpm/agents/templates/version_control.json +2 -2
- claude_mpm/agents/templates/web_qa.json +6 -6
- claude_mpm/agents/templates/web_ui.json +3 -3
- claude_mpm/cli/__init__.py +49 -19
- claude_mpm/cli/commands/configure.py +591 -7
- claude_mpm/cli/parsers/configure_parser.py +5 -0
- claude_mpm/core/__init__.py +53 -17
- claude_mpm/core/config.py +1 -1
- claude_mpm/core/log_manager.py +7 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +16 -11
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +9 -11
- claude_mpm/services/__init__.py +140 -156
- claude_mpm/services/agents/deployment/deployment_config_loader.py +21 -0
- claude_mpm/services/agents/loading/base_agent_manager.py +12 -2
- claude_mpm/services/async_session_logger.py +112 -96
- claude_mpm/services/claude_session_logger.py +63 -61
- claude_mpm/services/mcp_config_manager.py +328 -38
- claude_mpm/services/mcp_gateway/__init__.py +98 -94
- claude_mpm/services/monitor/event_emitter.py +1 -1
- claude_mpm/services/orphan_detection.py +791 -0
- claude_mpm/services/project_port_allocator.py +601 -0
- claude_mpm/services/response_tracker.py +17 -6
- claude_mpm/services/session_manager.py +176 -0
- {claude_mpm-4.5.6.dist-info → claude_mpm-4.5.11.dist-info}/METADATA +1 -1
- {claude_mpm-4.5.6.dist-info → claude_mpm-4.5.11.dist-info}/RECORD +62 -58
- {claude_mpm-4.5.6.dist-info → claude_mpm-4.5.11.dist-info}/WHEEL +0 -0
- {claude_mpm-4.5.6.dist-info → claude_mpm-4.5.11.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.5.6.dist-info → claude_mpm-4.5.11.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.5.6.dist-info → claude_mpm-4.5.11.dist-info}/top_level.txt +0 -0
@@ -26,6 +26,8 @@ from rich.syntax import Syntax
|
|
26
26
|
from rich.table import Table
|
27
27
|
from rich.text import Text
|
28
28
|
|
29
|
+
from ...core.config import Config
|
30
|
+
from ...services.mcp_config_manager import MCPConfigManager
|
29
31
|
from ...services.version_service import VersionService
|
30
32
|
from ...utils.console import console as default_console
|
31
33
|
from ..shared import BaseCommand, CommandResult
|
@@ -54,6 +56,10 @@ class SimpleAgentManager:
|
|
54
56
|
self.templates_dir = (
|
55
57
|
Path(__file__).parent.parent.parent / "agents" / "templates"
|
56
58
|
)
|
59
|
+
# Add logger for error reporting
|
60
|
+
import logging
|
61
|
+
|
62
|
+
self.logger = logging.getLogger(__name__)
|
57
63
|
|
58
64
|
def _load_states(self):
|
59
65
|
"""Load agent states from file."""
|
@@ -115,6 +121,9 @@ class SimpleAgentManager:
|
|
115
121
|
# Extract capabilities/tools as dependencies for display
|
116
122
|
capabilities = template_data.get("capabilities", {})
|
117
123
|
tools = capabilities.get("tools", [])
|
124
|
+
# Ensure tools is a list before slicing
|
125
|
+
if not isinstance(tools, list):
|
126
|
+
tools = []
|
118
127
|
# Show first few tools as "dependencies" for UI purposes
|
119
128
|
display_tools = tools[:3] if len(tools) > 3 else tools
|
120
129
|
|
@@ -133,14 +142,24 @@ class SimpleAgentManager:
|
|
133
142
|
)
|
134
143
|
)
|
135
144
|
|
136
|
-
except (json.JSONDecodeError, KeyError):
|
137
|
-
#
|
145
|
+
except (json.JSONDecodeError, KeyError) as e:
|
146
|
+
# Log malformed templates but continue
|
147
|
+
self.logger.debug(
|
148
|
+
f"Skipping malformed template {template_file.name}: {e}"
|
149
|
+
)
|
150
|
+
continue
|
151
|
+
except Exception as e:
|
152
|
+
# Log unexpected errors but continue processing other templates
|
153
|
+
self.logger.debug(
|
154
|
+
f"Error processing template {template_file.name}: {e}"
|
155
|
+
)
|
138
156
|
continue
|
139
157
|
|
140
158
|
except Exception as e:
|
141
|
-
# If there's
|
159
|
+
# If there's a catastrophic error reading templates directory
|
160
|
+
self.logger.error(f"Failed to read templates directory: {e}")
|
142
161
|
return [
|
143
|
-
AgentConfig("engineer", f"Error
|
162
|
+
AgentConfig("engineer", f"Error accessing templates: {e!s}", []),
|
144
163
|
AgentConfig("research", "Research agent", []),
|
145
164
|
]
|
146
165
|
|
@@ -174,6 +193,7 @@ class ConfigureCommand(BaseCommand):
|
|
174
193
|
getattr(args, "agents", False),
|
175
194
|
getattr(args, "templates", False),
|
176
195
|
getattr(args, "behaviors", False),
|
196
|
+
getattr(args, "startup", False),
|
177
197
|
getattr(args, "version_info", False),
|
178
198
|
]
|
179
199
|
if sum(nav_options) > 1:
|
@@ -242,6 +262,9 @@ class ConfigureCommand(BaseCommand):
|
|
242
262
|
if getattr(args, "behaviors", False):
|
243
263
|
return self._run_behavior_management()
|
244
264
|
|
265
|
+
if getattr(args, "startup", False):
|
266
|
+
return self._run_startup_configuration()
|
267
|
+
|
245
268
|
# Launch interactive TUI
|
246
269
|
return self._run_interactive_tui(args)
|
247
270
|
|
@@ -263,8 +286,15 @@ class ConfigureCommand(BaseCommand):
|
|
263
286
|
elif choice == "3":
|
264
287
|
self._manage_behaviors()
|
265
288
|
elif choice == "4":
|
266
|
-
|
289
|
+
# If user saves and wants to proceed to startup, exit the configurator
|
290
|
+
if self._manage_startup_configuration():
|
291
|
+
self.console.print(
|
292
|
+
"\n[green]Configuration saved. Exiting configurator...[/green]"
|
293
|
+
)
|
294
|
+
break
|
267
295
|
elif choice == "5":
|
296
|
+
self._switch_scope()
|
297
|
+
elif choice == "6":
|
268
298
|
self._show_version_info_interactive()
|
269
299
|
elif choice == "q":
|
270
300
|
self.console.print(
|
@@ -287,10 +317,14 @@ class ConfigureCommand(BaseCommand):
|
|
287
317
|
"""Display the TUI header."""
|
288
318
|
self.console.clear()
|
289
319
|
|
320
|
+
# Get version for display
|
321
|
+
from claude_mpm import __version__
|
322
|
+
|
290
323
|
# Create header panel
|
291
324
|
header_text = Text()
|
292
325
|
header_text.append("Claude MPM ", style="bold cyan")
|
293
326
|
header_text.append("Configuration Interface", style="bold white")
|
327
|
+
header_text.append(f"\nv{__version__}", style="dim cyan")
|
294
328
|
|
295
329
|
scope_text = Text(f"Scope: {self.current_scope.upper()}", style="yellow")
|
296
330
|
dir_text = Text(f"Directory: {self.project_dir}", style="dim")
|
@@ -315,8 +349,13 @@ class ConfigureCommand(BaseCommand):
|
|
315
349
|
("1", "Agent Management", "Enable/disable agents and customize settings"),
|
316
350
|
("2", "Template Editing", "Edit agent JSON templates"),
|
317
351
|
("3", "Behavior Files", "Manage identity and workflow configurations"),
|
318
|
-
(
|
319
|
-
|
352
|
+
(
|
353
|
+
"4",
|
354
|
+
"Startup Configuration",
|
355
|
+
"Configure MCP services and agents to start",
|
356
|
+
),
|
357
|
+
("5", "Switch Scope", f"Current: {self.current_scope}"),
|
358
|
+
("6", "Version Info", "Display MPM and Claude versions"),
|
320
359
|
("q", "Quit", "Exit configuration interface"),
|
321
360
|
]
|
322
361
|
|
@@ -979,6 +1018,537 @@ class ConfigureCommand(BaseCommand):
|
|
979
1018
|
self.console.print("[yellow]Behavior file export - Coming soon![/yellow]")
|
980
1019
|
Prompt.ask("Press Enter to continue")
|
981
1020
|
|
1021
|
+
def _manage_startup_configuration(self) -> bool:
|
1022
|
+
"""Manage startup configuration for MCP services and agents.
|
1023
|
+
|
1024
|
+
Returns:
|
1025
|
+
bool: True if user saved and wants to proceed to startup, False otherwise
|
1026
|
+
"""
|
1027
|
+
# Temporarily suppress INFO logging during Config initialization
|
1028
|
+
import logging
|
1029
|
+
|
1030
|
+
root_logger = logging.getLogger("claude_mpm")
|
1031
|
+
original_level = root_logger.level
|
1032
|
+
root_logger.setLevel(logging.WARNING)
|
1033
|
+
|
1034
|
+
try:
|
1035
|
+
# Load current configuration ONCE at the start
|
1036
|
+
config = Config()
|
1037
|
+
startup_config = self._load_startup_configuration(config)
|
1038
|
+
finally:
|
1039
|
+
# Restore original logging level
|
1040
|
+
root_logger.setLevel(original_level)
|
1041
|
+
|
1042
|
+
proceed_to_startup = False
|
1043
|
+
while True:
|
1044
|
+
self.console.clear()
|
1045
|
+
self._display_header()
|
1046
|
+
|
1047
|
+
self.console.print("[bold]Startup Configuration Management[/bold]\n")
|
1048
|
+
self.console.print(
|
1049
|
+
"[dim]Configure which MCP services, hook services, and system agents "
|
1050
|
+
"are enabled when Claude MPM starts.[/dim]\n"
|
1051
|
+
)
|
1052
|
+
|
1053
|
+
# Display current configuration (using in-memory state)
|
1054
|
+
self._display_startup_configuration(startup_config)
|
1055
|
+
|
1056
|
+
# Show menu options
|
1057
|
+
self.console.print("\n[bold]Options:[/bold]")
|
1058
|
+
self.console.print(" [cyan]1[/cyan] - Configure MCP Services")
|
1059
|
+
self.console.print(" [cyan]2[/cyan] - Configure Hook Services")
|
1060
|
+
self.console.print(" [cyan]3[/cyan] - Configure System Agents")
|
1061
|
+
self.console.print(" [cyan]4[/cyan] - Enable All")
|
1062
|
+
self.console.print(" [cyan]5[/cyan] - Disable All")
|
1063
|
+
self.console.print(" [cyan]6[/cyan] - Reset to Defaults")
|
1064
|
+
self.console.print(
|
1065
|
+
" [cyan]s[/cyan] - Save configuration and start claude-mpm"
|
1066
|
+
)
|
1067
|
+
self.console.print(" [cyan]b[/cyan] - Cancel and return without saving")
|
1068
|
+
self.console.print()
|
1069
|
+
|
1070
|
+
choice = Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="s")
|
1071
|
+
|
1072
|
+
if choice == "b":
|
1073
|
+
break
|
1074
|
+
if choice == "1":
|
1075
|
+
self._configure_mcp_services(startup_config, config)
|
1076
|
+
elif choice == "2":
|
1077
|
+
self._configure_hook_services(startup_config, config)
|
1078
|
+
elif choice == "3":
|
1079
|
+
self._configure_system_agents(startup_config, config)
|
1080
|
+
elif choice == "4":
|
1081
|
+
self._enable_all_services(startup_config, config)
|
1082
|
+
elif choice == "5":
|
1083
|
+
self._disable_all_services(startup_config, config)
|
1084
|
+
elif choice == "6":
|
1085
|
+
self._reset_to_defaults(startup_config, config)
|
1086
|
+
elif choice == "s":
|
1087
|
+
# Save and exit if successful
|
1088
|
+
if self._save_startup_configuration(startup_config, config):
|
1089
|
+
proceed_to_startup = True
|
1090
|
+
break
|
1091
|
+
else:
|
1092
|
+
self.console.print("[red]Invalid choice.[/red]")
|
1093
|
+
Prompt.ask("Press Enter to continue")
|
1094
|
+
|
1095
|
+
return proceed_to_startup
|
1096
|
+
|
1097
|
+
def _load_startup_configuration(self, config: Config) -> Dict:
|
1098
|
+
"""Load current startup configuration from config."""
|
1099
|
+
startup_config = config.get("startup", {})
|
1100
|
+
|
1101
|
+
# Ensure all required sections exist
|
1102
|
+
if "enabled_mcp_services" not in startup_config:
|
1103
|
+
# Get available MCP services from MCPConfigManager
|
1104
|
+
mcp_manager = MCPConfigManager()
|
1105
|
+
available_services = list(mcp_manager.STATIC_MCP_CONFIGS.keys())
|
1106
|
+
startup_config["enabled_mcp_services"] = available_services.copy()
|
1107
|
+
|
1108
|
+
if "enabled_hook_services" not in startup_config:
|
1109
|
+
# Default hook services (health-monitor enabled by default)
|
1110
|
+
startup_config["enabled_hook_services"] = [
|
1111
|
+
"monitor",
|
1112
|
+
"dashboard",
|
1113
|
+
"response-logger",
|
1114
|
+
"health-monitor",
|
1115
|
+
]
|
1116
|
+
|
1117
|
+
if "disabled_agents" not in startup_config:
|
1118
|
+
# NEW LOGIC: Track DISABLED agents instead of enabled
|
1119
|
+
# By default, NO agents are disabled (all agents enabled)
|
1120
|
+
startup_config["disabled_agents"] = []
|
1121
|
+
|
1122
|
+
return startup_config
|
1123
|
+
|
1124
|
+
def _display_startup_configuration(self, startup_config: Dict) -> None:
|
1125
|
+
"""Display current startup configuration in a table."""
|
1126
|
+
table = Table(
|
1127
|
+
title="Current Startup Configuration", box=ROUNDED, show_lines=True
|
1128
|
+
)
|
1129
|
+
|
1130
|
+
table.add_column("Category", style="cyan", width=20)
|
1131
|
+
table.add_column("Enabled Services", style="white", width=50)
|
1132
|
+
table.add_column("Count", style="dim", width=10)
|
1133
|
+
|
1134
|
+
# MCP Services
|
1135
|
+
mcp_services = startup_config.get("enabled_mcp_services", [])
|
1136
|
+
mcp_display = ", ".join(mcp_services[:3]) + (
|
1137
|
+
"..." if len(mcp_services) > 3 else ""
|
1138
|
+
)
|
1139
|
+
table.add_row(
|
1140
|
+
"MCP Services",
|
1141
|
+
mcp_display if mcp_services else "[dim]None[/dim]",
|
1142
|
+
str(len(mcp_services)),
|
1143
|
+
)
|
1144
|
+
|
1145
|
+
# Hook Services
|
1146
|
+
hook_services = startup_config.get("enabled_hook_services", [])
|
1147
|
+
hook_display = ", ".join(hook_services[:3]) + (
|
1148
|
+
"..." if len(hook_services) > 3 else ""
|
1149
|
+
)
|
1150
|
+
table.add_row(
|
1151
|
+
"Hook Services",
|
1152
|
+
hook_display if hook_services else "[dim]None[/dim]",
|
1153
|
+
str(len(hook_services)),
|
1154
|
+
)
|
1155
|
+
|
1156
|
+
# System Agents - show count of ENABLED agents (total - disabled)
|
1157
|
+
all_agents = self.agent_manager.discover_agents() if self.agent_manager else []
|
1158
|
+
disabled_agents = startup_config.get("disabled_agents", [])
|
1159
|
+
enabled_count = len(all_agents) - len(disabled_agents)
|
1160
|
+
|
1161
|
+
# Show first few enabled agent names
|
1162
|
+
enabled_names = [a.name for a in all_agents if a.name not in disabled_agents]
|
1163
|
+
agent_display = ", ".join(enabled_names[:3]) + (
|
1164
|
+
"..." if len(enabled_names) > 3 else ""
|
1165
|
+
)
|
1166
|
+
table.add_row(
|
1167
|
+
"System Agents",
|
1168
|
+
agent_display if enabled_names else "[dim]All Disabled[/dim]",
|
1169
|
+
f"{enabled_count}/{len(all_agents)}",
|
1170
|
+
)
|
1171
|
+
|
1172
|
+
self.console.print(table)
|
1173
|
+
|
1174
|
+
def _configure_mcp_services(self, startup_config: Dict, config: Config) -> None:
|
1175
|
+
"""Configure which MCP services to enable at startup."""
|
1176
|
+
self.console.clear()
|
1177
|
+
self._display_header()
|
1178
|
+
self.console.print("[bold]Configure MCP Services[/bold]\n")
|
1179
|
+
|
1180
|
+
# Get available MCP services
|
1181
|
+
mcp_manager = MCPConfigManager()
|
1182
|
+
available_services = list(mcp_manager.STATIC_MCP_CONFIGS.keys())
|
1183
|
+
enabled_services = set(startup_config.get("enabled_mcp_services", []))
|
1184
|
+
|
1185
|
+
# Display services with checkboxes
|
1186
|
+
table = Table(box=ROUNDED, show_lines=True)
|
1187
|
+
table.add_column("ID", style="dim", width=5)
|
1188
|
+
table.add_column("Service", style="cyan", width=25)
|
1189
|
+
table.add_column("Status", width=15)
|
1190
|
+
table.add_column("Description", style="white", width=45)
|
1191
|
+
|
1192
|
+
service_descriptions = {
|
1193
|
+
"kuzu-memory": "Graph-based memory system for agents",
|
1194
|
+
"mcp-ticketer": "Ticket and issue tracking integration",
|
1195
|
+
"mcp-browser": "Browser automation and web scraping",
|
1196
|
+
"mcp-vector-search": "Semantic code search capabilities",
|
1197
|
+
}
|
1198
|
+
|
1199
|
+
for idx, service in enumerate(available_services, 1):
|
1200
|
+
status = (
|
1201
|
+
"[green]✓ Enabled[/green]"
|
1202
|
+
if service in enabled_services
|
1203
|
+
else "[red]✗ Disabled[/red]"
|
1204
|
+
)
|
1205
|
+
description = service_descriptions.get(service, "MCP service")
|
1206
|
+
table.add_row(str(idx), service, status, description)
|
1207
|
+
|
1208
|
+
self.console.print(table)
|
1209
|
+
self.console.print("\n[bold]Commands:[/bold]")
|
1210
|
+
self.console.print(" Enter service IDs to toggle (e.g., '1,3' or '1-4')")
|
1211
|
+
self.console.print(" [cyan][a][/cyan] Enable all")
|
1212
|
+
self.console.print(" [cyan][n][/cyan] Disable all")
|
1213
|
+
self.console.print(" [cyan][b][/cyan] Back to previous menu")
|
1214
|
+
self.console.print()
|
1215
|
+
|
1216
|
+
choice = Prompt.ask("[bold cyan]Toggle services[/bold cyan]", default="b")
|
1217
|
+
|
1218
|
+
if choice == "b":
|
1219
|
+
return
|
1220
|
+
if choice == "a":
|
1221
|
+
startup_config["enabled_mcp_services"] = available_services.copy()
|
1222
|
+
self.console.print("[green]All MCP services enabled![/green]")
|
1223
|
+
elif choice == "n":
|
1224
|
+
startup_config["enabled_mcp_services"] = []
|
1225
|
+
self.console.print("[green]All MCP services disabled![/green]")
|
1226
|
+
else:
|
1227
|
+
# Parse service IDs
|
1228
|
+
try:
|
1229
|
+
selected_ids = self._parse_id_selection(choice, len(available_services))
|
1230
|
+
for idx in selected_ids:
|
1231
|
+
service = available_services[idx - 1]
|
1232
|
+
if service in enabled_services:
|
1233
|
+
enabled_services.remove(service)
|
1234
|
+
self.console.print(f"[red]Disabled {service}[/red]")
|
1235
|
+
else:
|
1236
|
+
enabled_services.add(service)
|
1237
|
+
self.console.print(f"[green]Enabled {service}[/green]")
|
1238
|
+
startup_config["enabled_mcp_services"] = list(enabled_services)
|
1239
|
+
except (ValueError, IndexError) as e:
|
1240
|
+
self.console.print(f"[red]Invalid selection: {e}[/red]")
|
1241
|
+
|
1242
|
+
Prompt.ask("Press Enter to continue")
|
1243
|
+
|
1244
|
+
def _configure_hook_services(self, startup_config: Dict, config: Config) -> None:
|
1245
|
+
"""Configure which hook services to enable at startup."""
|
1246
|
+
self.console.clear()
|
1247
|
+
self._display_header()
|
1248
|
+
self.console.print("[bold]Configure Hook Services[/bold]\n")
|
1249
|
+
|
1250
|
+
# Available hook services
|
1251
|
+
available_services = [
|
1252
|
+
("monitor", "Real-time event monitoring server (SocketIO)"),
|
1253
|
+
("dashboard", "Web-based dashboard interface"),
|
1254
|
+
("response-logger", "Agent response logging"),
|
1255
|
+
("health-monitor", "Service health and recovery monitoring"),
|
1256
|
+
]
|
1257
|
+
|
1258
|
+
enabled_services = set(startup_config.get("enabled_hook_services", []))
|
1259
|
+
|
1260
|
+
# Display services with checkboxes
|
1261
|
+
table = Table(box=ROUNDED, show_lines=True)
|
1262
|
+
table.add_column("ID", style="dim", width=5)
|
1263
|
+
table.add_column("Service", style="cyan", width=25)
|
1264
|
+
table.add_column("Status", width=15)
|
1265
|
+
table.add_column("Description", style="white", width=45)
|
1266
|
+
|
1267
|
+
for idx, (service, description) in enumerate(available_services, 1):
|
1268
|
+
status = (
|
1269
|
+
"[green]✓ Enabled[/green]"
|
1270
|
+
if service in enabled_services
|
1271
|
+
else "[red]✗ Disabled[/red]"
|
1272
|
+
)
|
1273
|
+
table.add_row(str(idx), service, status, description)
|
1274
|
+
|
1275
|
+
self.console.print(table)
|
1276
|
+
self.console.print("\n[bold]Commands:[/bold]")
|
1277
|
+
self.console.print(" Enter service IDs to toggle (e.g., '1,3' or '1-4')")
|
1278
|
+
self.console.print(" [cyan][a][/cyan] Enable all")
|
1279
|
+
self.console.print(" [cyan][n][/cyan] Disable all")
|
1280
|
+
self.console.print(" [cyan][b][/cyan] Back to previous menu")
|
1281
|
+
self.console.print()
|
1282
|
+
|
1283
|
+
choice = Prompt.ask("[bold cyan]Toggle services[/bold cyan]", default="b")
|
1284
|
+
|
1285
|
+
if choice == "b":
|
1286
|
+
return
|
1287
|
+
if choice == "a":
|
1288
|
+
startup_config["enabled_hook_services"] = [s[0] for s in available_services]
|
1289
|
+
self.console.print("[green]All hook services enabled![/green]")
|
1290
|
+
elif choice == "n":
|
1291
|
+
startup_config["enabled_hook_services"] = []
|
1292
|
+
self.console.print("[green]All hook services disabled![/green]")
|
1293
|
+
else:
|
1294
|
+
# Parse service IDs
|
1295
|
+
try:
|
1296
|
+
selected_ids = self._parse_id_selection(choice, len(available_services))
|
1297
|
+
for idx in selected_ids:
|
1298
|
+
service = available_services[idx - 1][0]
|
1299
|
+
if service in enabled_services:
|
1300
|
+
enabled_services.remove(service)
|
1301
|
+
self.console.print(f"[red]Disabled {service}[/red]")
|
1302
|
+
else:
|
1303
|
+
enabled_services.add(service)
|
1304
|
+
self.console.print(f"[green]Enabled {service}[/green]")
|
1305
|
+
startup_config["enabled_hook_services"] = list(enabled_services)
|
1306
|
+
except (ValueError, IndexError) as e:
|
1307
|
+
self.console.print(f"[red]Invalid selection: {e}[/red]")
|
1308
|
+
|
1309
|
+
Prompt.ask("Press Enter to continue")
|
1310
|
+
|
1311
|
+
def _configure_system_agents(self, startup_config: Dict, config: Config) -> None:
|
1312
|
+
"""Configure which system agents to deploy at startup.
|
1313
|
+
|
1314
|
+
NEW LOGIC: Uses disabled_agents list. All agents from templates are enabled by default.
|
1315
|
+
"""
|
1316
|
+
while True:
|
1317
|
+
self.console.clear()
|
1318
|
+
self._display_header()
|
1319
|
+
self.console.print("[bold]Configure System Agents[/bold]\n")
|
1320
|
+
self.console.print(
|
1321
|
+
"[dim]All agents discovered from templates are enabled by default. "
|
1322
|
+
"Mark agents as disabled to prevent deployment.[/dim]\n"
|
1323
|
+
)
|
1324
|
+
|
1325
|
+
# Discover available agents from template files
|
1326
|
+
agents = self.agent_manager.discover_agents()
|
1327
|
+
disabled_agents = set(startup_config.get("disabled_agents", []))
|
1328
|
+
|
1329
|
+
# Display agents with checkboxes
|
1330
|
+
table = Table(box=ROUNDED, show_lines=True)
|
1331
|
+
table.add_column("ID", style="dim", width=5)
|
1332
|
+
table.add_column("Agent", style="cyan", width=25)
|
1333
|
+
table.add_column("Status", width=15)
|
1334
|
+
table.add_column("Description", style="white", width=45)
|
1335
|
+
|
1336
|
+
for idx, agent in enumerate(agents, 1):
|
1337
|
+
# Agent is ENABLED if NOT in disabled list
|
1338
|
+
is_enabled = agent.name not in disabled_agents
|
1339
|
+
status = (
|
1340
|
+
"[green]✓ Enabled[/green]"
|
1341
|
+
if is_enabled
|
1342
|
+
else "[red]✗ Disabled[/red]"
|
1343
|
+
)
|
1344
|
+
desc_display = (
|
1345
|
+
agent.description[:42] + "..."
|
1346
|
+
if len(agent.description) > 42
|
1347
|
+
else agent.description
|
1348
|
+
)
|
1349
|
+
table.add_row(str(idx), agent.name, status, desc_display)
|
1350
|
+
|
1351
|
+
self.console.print(table)
|
1352
|
+
self.console.print("\n[bold]Commands:[/bold]")
|
1353
|
+
self.console.print(" Enter agent IDs to toggle (e.g., '1,3' or '1-4')")
|
1354
|
+
self.console.print(" [cyan]a[/cyan] - Enable all (clear disabled list)")
|
1355
|
+
self.console.print(" [cyan]n[/cyan] - Disable all")
|
1356
|
+
self.console.print(" [cyan]b[/cyan] - Back to previous menu")
|
1357
|
+
self.console.print()
|
1358
|
+
|
1359
|
+
choice = Prompt.ask("[bold cyan]Select option[/bold cyan]", default="b")
|
1360
|
+
|
1361
|
+
if choice == "b":
|
1362
|
+
return
|
1363
|
+
if choice == "a":
|
1364
|
+
# Enable all = empty disabled list
|
1365
|
+
startup_config["disabled_agents"] = []
|
1366
|
+
self.console.print("[green]All agents enabled![/green]")
|
1367
|
+
Prompt.ask("Press Enter to continue")
|
1368
|
+
elif choice == "n":
|
1369
|
+
# Disable all = all agents in disabled list
|
1370
|
+
startup_config["disabled_agents"] = [agent.name for agent in agents]
|
1371
|
+
self.console.print("[green]All agents disabled![/green]")
|
1372
|
+
Prompt.ask("Press Enter to continue")
|
1373
|
+
else:
|
1374
|
+
# Parse agent IDs
|
1375
|
+
try:
|
1376
|
+
selected_ids = self._parse_id_selection(choice, len(agents))
|
1377
|
+
for idx in selected_ids:
|
1378
|
+
agent = agents[idx - 1]
|
1379
|
+
if agent.name in disabled_agents:
|
1380
|
+
# Currently disabled, enable it (remove from disabled list)
|
1381
|
+
disabled_agents.remove(agent.name)
|
1382
|
+
self.console.print(f"[green]Enabled {agent.name}[/green]")
|
1383
|
+
else:
|
1384
|
+
# Currently enabled, disable it (add to disabled list)
|
1385
|
+
disabled_agents.add(agent.name)
|
1386
|
+
self.console.print(f"[red]Disabled {agent.name}[/red]")
|
1387
|
+
startup_config["disabled_agents"] = list(disabled_agents)
|
1388
|
+
# Refresh the display to show updated status immediately
|
1389
|
+
except (ValueError, IndexError) as e:
|
1390
|
+
self.console.print(f"[red]Invalid selection: {e}[/red]")
|
1391
|
+
Prompt.ask("Press Enter to continue")
|
1392
|
+
|
1393
|
+
def _parse_id_selection(self, selection: str, max_id: int) -> List[int]:
|
1394
|
+
"""Parse ID selection string (e.g., '1,3,5' or '1-4')."""
|
1395
|
+
ids = set()
|
1396
|
+
parts = selection.split(",")
|
1397
|
+
|
1398
|
+
for part in parts:
|
1399
|
+
part = part.strip()
|
1400
|
+
if "-" in part:
|
1401
|
+
# Range selection
|
1402
|
+
start, end = part.split("-")
|
1403
|
+
start_id = int(start.strip())
|
1404
|
+
end_id = int(end.strip())
|
1405
|
+
if start_id < 1 or end_id > max_id or start_id > end_id:
|
1406
|
+
raise ValueError(f"Invalid range: {part}")
|
1407
|
+
ids.update(range(start_id, end_id + 1))
|
1408
|
+
else:
|
1409
|
+
# Single ID
|
1410
|
+
id_num = int(part)
|
1411
|
+
if id_num < 1 or id_num > max_id:
|
1412
|
+
raise ValueError(f"Invalid ID: {id_num}")
|
1413
|
+
ids.add(id_num)
|
1414
|
+
|
1415
|
+
return sorted(ids)
|
1416
|
+
|
1417
|
+
def _enable_all_services(self, startup_config: Dict, config: Config) -> None:
|
1418
|
+
"""Enable all services and agents."""
|
1419
|
+
if Confirm.ask("[yellow]Enable ALL services and agents?[/yellow]"):
|
1420
|
+
# Enable all MCP services
|
1421
|
+
mcp_manager = MCPConfigManager()
|
1422
|
+
startup_config["enabled_mcp_services"] = list(
|
1423
|
+
mcp_manager.STATIC_MCP_CONFIGS.keys()
|
1424
|
+
)
|
1425
|
+
|
1426
|
+
# Enable all hook services
|
1427
|
+
startup_config["enabled_hook_services"] = [
|
1428
|
+
"monitor",
|
1429
|
+
"dashboard",
|
1430
|
+
"response-logger",
|
1431
|
+
"health-monitor",
|
1432
|
+
]
|
1433
|
+
|
1434
|
+
# Enable all agents (empty disabled list)
|
1435
|
+
startup_config["disabled_agents"] = []
|
1436
|
+
|
1437
|
+
self.console.print("[green]All services and agents enabled![/green]")
|
1438
|
+
Prompt.ask("Press Enter to continue")
|
1439
|
+
|
1440
|
+
def _disable_all_services(self, startup_config: Dict, config: Config) -> None:
|
1441
|
+
"""Disable all services and agents."""
|
1442
|
+
if Confirm.ask("[yellow]Disable ALL services and agents?[/yellow]"):
|
1443
|
+
startup_config["enabled_mcp_services"] = []
|
1444
|
+
startup_config["enabled_hook_services"] = []
|
1445
|
+
# Disable all agents = add all to disabled list
|
1446
|
+
agents = self.agent_manager.discover_agents()
|
1447
|
+
startup_config["disabled_agents"] = [agent.name for agent in agents]
|
1448
|
+
|
1449
|
+
self.console.print("[green]All services and agents disabled![/green]")
|
1450
|
+
self.console.print(
|
1451
|
+
"[yellow]Note: You may need to enable at least some services for Claude MPM to function properly.[/yellow]"
|
1452
|
+
)
|
1453
|
+
Prompt.ask("Press Enter to continue")
|
1454
|
+
|
1455
|
+
def _reset_to_defaults(self, startup_config: Dict, config: Config) -> None:
|
1456
|
+
"""Reset startup configuration to defaults."""
|
1457
|
+
if Confirm.ask("[yellow]Reset startup configuration to defaults?[/yellow]"):
|
1458
|
+
# Reset to default values
|
1459
|
+
mcp_manager = MCPConfigManager()
|
1460
|
+
startup_config["enabled_mcp_services"] = list(
|
1461
|
+
mcp_manager.STATIC_MCP_CONFIGS.keys()
|
1462
|
+
)
|
1463
|
+
startup_config["enabled_hook_services"] = [
|
1464
|
+
"monitor",
|
1465
|
+
"dashboard",
|
1466
|
+
"response-logger",
|
1467
|
+
"health-monitor",
|
1468
|
+
]
|
1469
|
+
# Default: All agents enabled (empty disabled list)
|
1470
|
+
startup_config["disabled_agents"] = []
|
1471
|
+
|
1472
|
+
self.console.print(
|
1473
|
+
"[green]Startup configuration reset to defaults![/green]"
|
1474
|
+
)
|
1475
|
+
Prompt.ask("Press Enter to continue")
|
1476
|
+
|
1477
|
+
def _save_startup_configuration(self, startup_config: Dict, config: Config) -> bool:
|
1478
|
+
"""Save startup configuration to config file and return whether to proceed to startup.
|
1479
|
+
|
1480
|
+
Returns:
|
1481
|
+
bool: True if should proceed to startup, False to continue in menu
|
1482
|
+
"""
|
1483
|
+
try:
|
1484
|
+
# Update the startup configuration
|
1485
|
+
config.set("startup", startup_config)
|
1486
|
+
|
1487
|
+
# IMPORTANT: Also update agent_deployment.disabled_agents so the deployment
|
1488
|
+
# system actually uses the configured disabled agents list
|
1489
|
+
config.set(
|
1490
|
+
"agent_deployment.disabled_agents",
|
1491
|
+
startup_config.get("disabled_agents", []),
|
1492
|
+
)
|
1493
|
+
|
1494
|
+
# Determine config file path
|
1495
|
+
if self.current_scope == "project":
|
1496
|
+
config_file = self.project_dir / ".claude-mpm" / "configuration.yaml"
|
1497
|
+
else:
|
1498
|
+
config_file = Path.home() / ".claude-mpm" / "configuration.yaml"
|
1499
|
+
|
1500
|
+
# Ensure directory exists
|
1501
|
+
config_file.parent.mkdir(parents=True, exist_ok=True)
|
1502
|
+
|
1503
|
+
# Temporarily suppress INFO logging to avoid duplicate save messages
|
1504
|
+
import logging
|
1505
|
+
|
1506
|
+
root_logger = logging.getLogger("claude_mpm")
|
1507
|
+
original_level = root_logger.level
|
1508
|
+
root_logger.setLevel(logging.WARNING)
|
1509
|
+
|
1510
|
+
try:
|
1511
|
+
# Save configuration (this will log at INFO level which we've suppressed)
|
1512
|
+
config.save(config_file, format="yaml")
|
1513
|
+
finally:
|
1514
|
+
# Restore original logging level
|
1515
|
+
root_logger.setLevel(original_level)
|
1516
|
+
|
1517
|
+
self.console.print(
|
1518
|
+
f"[green]✓ Startup configuration saved to {config_file}[/green]"
|
1519
|
+
)
|
1520
|
+
self.console.print(
|
1521
|
+
"\n[cyan]Applying configuration and launching Claude MPM...[/cyan]\n"
|
1522
|
+
)
|
1523
|
+
|
1524
|
+
# Launch claude-mpm run command to get full startup cycle
|
1525
|
+
# This ensures:
|
1526
|
+
# 1. Configuration is loaded
|
1527
|
+
# 2. Enabled agents are deployed
|
1528
|
+
# 3. Disabled agents are removed from .claude/agents/
|
1529
|
+
# 4. MCP services and hooks are started
|
1530
|
+
try:
|
1531
|
+
# Use execvp to replace the current process with claude-mpm run
|
1532
|
+
# This ensures a clean transition from configurator to Claude MPM
|
1533
|
+
os.execvp("claude-mpm", ["claude-mpm", "run"])
|
1534
|
+
except Exception as e:
|
1535
|
+
self.console.print(
|
1536
|
+
f"[yellow]Could not launch Claude MPM automatically: {e}[/yellow]"
|
1537
|
+
)
|
1538
|
+
self.console.print(
|
1539
|
+
"[cyan]Please run 'claude-mpm' manually to start.[/cyan]"
|
1540
|
+
)
|
1541
|
+
Prompt.ask("Press Enter to continue")
|
1542
|
+
return True
|
1543
|
+
|
1544
|
+
# This line will never be reached if execvp succeeds
|
1545
|
+
return True
|
1546
|
+
|
1547
|
+
except Exception as e:
|
1548
|
+
self.console.print(f"[red]Error saving configuration: {e}[/red]")
|
1549
|
+
Prompt.ask("Press Enter to continue")
|
1550
|
+
return False
|
1551
|
+
|
982
1552
|
def _switch_scope(self) -> None:
|
983
1553
|
"""Switch between project and user scope."""
|
984
1554
|
self.current_scope = "user" if self.current_scope == "project" else "project"
|
@@ -1377,6 +1947,20 @@ Directory: {self.project_dir}
|
|
1377
1947
|
except Exception as e:
|
1378
1948
|
return CommandResult.error_result(f"Behavior management failed: {e}")
|
1379
1949
|
|
1950
|
+
def _run_startup_configuration(self) -> CommandResult:
|
1951
|
+
"""Jump directly to startup configuration."""
|
1952
|
+
try:
|
1953
|
+
proceed = self._manage_startup_configuration()
|
1954
|
+
if proceed:
|
1955
|
+
return CommandResult.success_result(
|
1956
|
+
"Startup configuration saved, proceeding to startup"
|
1957
|
+
)
|
1958
|
+
return CommandResult.success_result("Startup configuration completed")
|
1959
|
+
except KeyboardInterrupt:
|
1960
|
+
return CommandResult.success_result("Startup configuration cancelled")
|
1961
|
+
except Exception as e:
|
1962
|
+
return CommandResult.error_result(f"Startup configuration failed: {e}")
|
1963
|
+
|
1380
1964
|
|
1381
1965
|
def manage_configure(args) -> int:
|
1382
1966
|
"""Main entry point for configuration management command.
|