claude-mpm 5.0.2__py3-none-any.whl → 5.1.9__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (76) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +1176 -909
  4. claude_mpm/agents/base_agent_loader.py +10 -35
  5. claude_mpm/agents/frontmatter_validator.py +68 -0
  6. claude_mpm/agents/templates/circuit-breakers.md +293 -44
  7. claude_mpm/cli/__init__.py +0 -1
  8. claude_mpm/cli/commands/__init__.py +2 -0
  9. claude_mpm/cli/commands/agent_state_manager.py +64 -11
  10. claude_mpm/cli/commands/agents.py +446 -25
  11. claude_mpm/cli/commands/auto_configure.py +535 -233
  12. claude_mpm/cli/commands/configure.py +545 -89
  13. claude_mpm/cli/commands/postmortem.py +401 -0
  14. claude_mpm/cli/commands/run.py +1 -39
  15. claude_mpm/cli/commands/skills.py +322 -19
  16. claude_mpm/cli/interactive/agent_wizard.py +302 -195
  17. claude_mpm/cli/parsers/agents_parser.py +137 -0
  18. claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
  19. claude_mpm/cli/parsers/base_parser.py +4 -0
  20. claude_mpm/cli/parsers/skills_parser.py +7 -0
  21. claude_mpm/cli/startup.py +73 -32
  22. claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
  23. claude_mpm/commands/mpm-agents-list.md +2 -2
  24. claude_mpm/commands/mpm-config-view.md +2 -2
  25. claude_mpm/commands/mpm-help.md +3 -0
  26. claude_mpm/commands/mpm-postmortem.md +123 -0
  27. claude_mpm/commands/mpm-session-resume.md +2 -2
  28. claude_mpm/commands/mpm-ticket-organize.md +2 -2
  29. claude_mpm/commands/mpm-ticket-view.md +2 -2
  30. claude_mpm/config/agent_presets.py +312 -82
  31. claude_mpm/config/skill_presets.py +392 -0
  32. claude_mpm/constants.py +1 -0
  33. claude_mpm/core/claude_runner.py +2 -25
  34. claude_mpm/core/framework/loaders/file_loader.py +54 -101
  35. claude_mpm/core/interactive_session.py +19 -5
  36. claude_mpm/core/oneshot_session.py +16 -4
  37. claude_mpm/core/output_style_manager.py +173 -43
  38. claude_mpm/core/protocols/__init__.py +23 -0
  39. claude_mpm/core/protocols/runner_protocol.py +103 -0
  40. claude_mpm/core/protocols/session_protocol.py +131 -0
  41. claude_mpm/core/shared/singleton_manager.py +11 -4
  42. claude_mpm/core/system_context.py +38 -0
  43. claude_mpm/core/unified_agent_registry.py +129 -1
  44. claude_mpm/core/unified_config.py +22 -0
  45. claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
  46. claude_mpm/models/agent_definition.py +7 -0
  47. claude_mpm/services/agents/cache_git_manager.py +621 -0
  48. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
  49. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +195 -1
  50. claude_mpm/services/agents/sources/git_source_sync_service.py +37 -5
  51. claude_mpm/services/analysis/__init__.py +25 -0
  52. claude_mpm/services/analysis/postmortem_reporter.py +474 -0
  53. claude_mpm/services/analysis/postmortem_service.py +765 -0
  54. claude_mpm/services/command_deployment_service.py +108 -5
  55. claude_mpm/services/core/base.py +7 -2
  56. claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
  57. claude_mpm/services/git/git_operations_service.py +8 -8
  58. claude_mpm/services/mcp_config_manager.py +75 -145
  59. claude_mpm/services/mcp_gateway/core/process_pool.py +22 -16
  60. claude_mpm/services/mcp_service_verifier.py +6 -3
  61. claude_mpm/services/monitor/daemon.py +28 -8
  62. claude_mpm/services/monitor/daemon_manager.py +96 -19
  63. claude_mpm/services/project/project_organizer.py +4 -0
  64. claude_mpm/services/runner_configuration_service.py +16 -3
  65. claude_mpm/services/session_management_service.py +16 -4
  66. claude_mpm/utils/agent_filters.py +288 -0
  67. claude_mpm/utils/gitignore.py +3 -0
  68. claude_mpm/utils/migration.py +372 -0
  69. claude_mpm/utils/progress.py +5 -1
  70. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/METADATA +69 -8
  71. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/RECORD +76 -62
  72. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  73. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/WHEEL +0 -0
  74. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/entry_points.txt +0 -0
  75. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/licenses/LICENSE +0 -0
  76. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/top_level.txt +0 -0
@@ -6,18 +6,33 @@ local agents with user-friendly prompts, intelligent defaults, and validation.
6
6
 
7
7
  import json
8
8
  import re
9
+ import shutil
9
10
  import sys
10
11
  from pathlib import Path
11
12
  from typing import Any, Dict, List, Optional, Tuple
12
13
 
14
+ import questionary
15
+ from questionary import Style
16
+
13
17
  from claude_mpm.core.logging_config import get_logger
14
18
  from claude_mpm.services.agents.local_template_manager import (
15
19
  LocalAgentTemplate,
16
20
  LocalAgentTemplateManager,
17
21
  )
22
+ from claude_mpm.utils.agent_filters import apply_all_filters
18
23
 
19
24
  logger = get_logger(__name__)
20
25
 
26
+ # Questionary style matching Rich cyan theme (consistent with configure.py)
27
+ QUESTIONARY_STYLE = Style(
28
+ [
29
+ ("selected", "fg:cyan bold"),
30
+ ("pointer", "fg:cyan bold"),
31
+ ("highlighted", "fg:cyan"),
32
+ ("question", "fg:cyan bold"),
33
+ ]
34
+ )
35
+
21
36
 
22
37
  class AgentWizard:
23
38
  """
@@ -47,6 +62,50 @@ class AgentWizard:
47
62
  self.source_manager = None
48
63
  self.discovery_enabled = False
49
64
 
65
+ @staticmethod
66
+ def _calculate_column_widths(
67
+ terminal_width: int, columns: Dict[str, int]
68
+ ) -> Dict[str, int]:
69
+ """Calculate dynamic column widths based on terminal size.
70
+
71
+ Args:
72
+ terminal_width: Current terminal width in characters
73
+ columns: Dict mapping column names to minimum widths
74
+
75
+ Returns:
76
+ Dict mapping column names to calculated widths
77
+
78
+ Design:
79
+ - Ensures minimum widths are respected
80
+ - Distributes extra space proportionally
81
+ - Handles narrow terminals gracefully (minimum 80 chars)
82
+ """
83
+ # Ensure minimum terminal width
84
+ min_terminal_width = 80
85
+ terminal_width = max(terminal_width, min_terminal_width)
86
+
87
+ # Calculate total minimum width needed
88
+ total_min_width = sum(columns.values())
89
+
90
+ # Account for spacing between columns
91
+ overhead = len(columns) + 1
92
+ available_width = terminal_width - overhead
93
+
94
+ # If we have extra space, distribute proportionally
95
+ if available_width > total_min_width:
96
+ extra_space = available_width - total_min_width
97
+ total_weight = sum(columns.values())
98
+
99
+ result = {}
100
+ for col_name, min_width in columns.items():
101
+ # Distribute extra space based on minimum width proportion
102
+ proportion = min_width / total_weight
103
+ extra = int(extra_space * proportion)
104
+ result[col_name] = min_width + extra
105
+ return result
106
+ # Terminal too narrow, use minimum widths
107
+ return columns.copy()
108
+
50
109
  def run_interactive_create(self) -> Tuple[bool, str]:
51
110
  """Run interactive agent creation wizard.
52
111
 
@@ -206,7 +265,10 @@ class AgentWizard:
206
265
  if deployed_file.exists() or deployed_file_alt.exists():
207
266
  agent_data["deployed"] = True
208
267
 
209
- return list(agents.values())
268
+ # Filter BASE_AGENT from all agent lists (1M-502 Phase 1)
269
+ # BASE_AGENT is a build tool, not a deployable agent
270
+ agent_list = list(agents.values())
271
+ return apply_all_filters(agent_list, filter_base=True, filter_deployed=False)
210
272
 
211
273
  def run_interactive_manage(self) -> Tuple[bool, str]:
212
274
  """Run interactive agent management menu.
@@ -242,50 +304,94 @@ class AgentWizard:
242
304
  print("❌ Invalid choice. Please try again.")
243
305
  continue
244
306
 
245
- # Show existing agents in a table
307
+ # Show existing agents in a table with dynamic widths
246
308
  print(f"\n📋 Found {len(all_agents)} agent(s):\n")
309
+
310
+ # Calculate dynamic column widths based on terminal size
311
+ terminal_width = shutil.get_terminal_size().columns
312
+ min_widths = {
313
+ "#": 4,
314
+ "Agent ID": 30,
315
+ "Name": 20,
316
+ "Source": 15,
317
+ "Status": 10,
318
+ }
319
+ widths = self._calculate_column_widths(terminal_width, min_widths)
320
+
321
+ # Print header with dynamic widths
247
322
  print(
248
- f"{'#':<4} {'Agent ID':<40} {'Name':<25} {'Source':<20} {'Status':<10}"
323
+ f"{'#':<{widths['#']}} "
324
+ f"{'Agent ID':<{widths['Agent ID']}} "
325
+ f"{'Name':<{widths['Name']}} "
326
+ f"{'Source':<{widths['Source']}} "
327
+ f"{'Status':<{widths['Status']}}"
249
328
  )
250
- print("-" * 105)
329
+ separator_width = sum(widths.values()) + len(widths) - 1
330
+ print("-" * separator_width)
251
331
 
252
332
  for i, agent in enumerate(all_agents, 1):
253
333
  agent_id = agent["agent_id"]
254
- name = (
255
- agent["name"][:24] if len(agent["name"]) > 24 else agent["name"]
256
- )
334
+ # Truncate to fit dynamic width
335
+ if len(agent_id) > widths["Agent ID"]:
336
+ agent_id = agent_id[: widths["Agent ID"] - 1] + "…"
337
+
338
+ name = agent["name"]
339
+ if len(name) > widths["Name"]:
340
+ name = name[: widths["Name"] - 1] + "…"
341
+
257
342
  source_label = (
258
- f"[{agent['source_type']}] {agent['source_identifier']}"[:19]
343
+ f"[{agent['source_type']}] {agent['source_identifier']}"
259
344
  )
345
+ if len(source_label) > widths["Source"]:
346
+ source_label = source_label[: widths["Source"] - 1] + "…"
347
+
260
348
  status = "✓ Deployed" if agent["deployed"] else "Available"
261
349
 
262
350
  print(
263
- f"{i:<4} {agent_id:<40} {name:<25} {source_label:<20} {status:<10}"
351
+ f"{i:<{widths['#']}} "
352
+ f"{agent_id:<{widths['Agent ID']}} "
353
+ f"{name:<{widths['Name']}} "
354
+ f"{source_label:<{widths['Source']}} "
355
+ f"{status:<{widths['Status']}}"
264
356
  )
265
357
 
266
- # Enhanced menu options
267
- print(f"\n{len(all_agents) + 1}. Deploy agent")
268
- print(f"{len(all_agents) + 2}. Create new agent")
269
- print(f"{len(all_agents) + 3}. Delete agent(s)")
270
- print(f"{len(all_agents) + 4}. Import agents")
271
- print(f"{len(all_agents) + 5}. Export all agents")
358
+ # Build menu choices with arrow-key navigation
359
+ menu_choices = []
360
+
361
+ # Add agent viewing options (1-N)
362
+ for i, agent in enumerate(all_agents, 1):
363
+ menu_choices.append(f"{i}. View agent: {agent['agent_id']}")
364
+
365
+ # Add action options
366
+ menu_choices.append(f"{len(all_agents) + 1}. Deploy agent")
367
+ menu_choices.append(f"{len(all_agents) + 2}. Create new agent")
368
+ menu_choices.append(f"{len(all_agents) + 3}. Delete agent(s)")
369
+ menu_choices.append(f"{len(all_agents) + 4}. Import agents")
370
+ menu_choices.append(f"{len(all_agents) + 5}. Export all agents")
371
+
272
372
  if self.discovery_enabled:
273
- print(f"{len(all_agents) + 6}. Browse & filter agents")
274
- print(f"{len(all_agents) + 7}. Deploy preset")
275
- print(f"{len(all_agents) + 8}. Manage agent sources")
276
- print(f"{len(all_agents) + 9}. Exit")
277
- max_choice = len(all_agents) + 9
373
+ menu_choices.append(
374
+ f"{len(all_agents) + 6}. Browse & filter agents"
375
+ )
376
+ menu_choices.append(f"{len(all_agents) + 7}. Deploy preset")
377
+ menu_choices.append(f"{len(all_agents) + 8}. Manage agent sources")
378
+ menu_choices.append(f"{len(all_agents) + 9}. Exit")
379
+ exit_num = len(all_agents) + 9
278
380
  else:
279
- print(f"{len(all_agents) + 6}. Exit")
280
- max_choice = len(all_agents) + 6
381
+ menu_choices.append(f"{len(all_agents) + 6}. Exit")
382
+ exit_num = len(all_agents) + 6
281
383
 
282
- choice = input(f"\nSelect option [1-{max_choice}]: ").strip()
384
+ choice = questionary.select(
385
+ "Agent Management Menu:",
386
+ choices=menu_choices,
387
+ style=QUESTIONARY_STYLE,
388
+ ).ask()
283
389
 
284
- try:
285
- choice_num = int(choice)
286
- except ValueError:
287
- print("❌ Invalid choice. Please enter a number.")
288
- continue
390
+ if not choice: # User pressed Esc
391
+ return True, "Management menu exited"
392
+
393
+ # Parse choice number from "N. Description" format
394
+ choice_num = int(choice.split(".")[0])
289
395
 
290
396
  if 1 <= choice_num <= len(all_agents):
291
397
  # View agent details
@@ -324,9 +430,7 @@ class AgentWizard:
324
430
  elif choice_num == len(all_agents) + 8 and self.discovery_enabled:
325
431
  self._manage_sources_interactive()
326
432
  continue
327
- elif (choice_num == len(all_agents) + 9 and self.discovery_enabled) or (
328
- choice_num == len(all_agents) + 6 and not self.discovery_enabled
329
- ):
433
+ elif choice_num == exit_num:
330
434
  return True, "Management menu exited"
331
435
  else:
332
436
  print("❌ Invalid choice. Please try again.")
@@ -1073,8 +1177,11 @@ class AgentWizard:
1073
1177
  Args:
1074
1178
  available_agents: List of all available agents
1075
1179
  """
1076
- # Filter to non-deployed agents
1077
- deployable = [a for a in available_agents if not a["deployed"]]
1180
+ # Filter to non-deployed agents using improved detection (1M-502 Phase 1)
1181
+ # This checks both .claude-mpm/agents/ and .claude/agents/
1182
+ deployable = apply_all_filters(
1183
+ available_agents, filter_base=True, filter_deployed=True
1184
+ )
1078
1185
 
1079
1186
  if not deployable:
1080
1187
  print("\n✅ All agents are already deployed!")
@@ -1086,110 +1193,100 @@ class AgentWizard:
1086
1193
  print("=" * 60)
1087
1194
  print(f"\n{len(deployable)} agent(s) available to deploy:\n")
1088
1195
 
1089
- for i, agent in enumerate(deployable, 1):
1090
- print(f" {i}. {agent['agent_id']}")
1091
- print(
1092
- f" {agent['description'][:60]}{'...' if len(agent['description']) > 60 else ''}"
1093
- )
1094
-
1095
- choice = input("\nEnter agent number (or 'c' to cancel): ").strip()
1096
- if choice.lower() == "c":
1097
- return
1098
-
1099
- try:
1100
- idx = int(choice) - 1
1101
- if idx < 0 or idx >= len(deployable):
1102
- print("❌ Invalid selection")
1103
- input("\nPress Enter to continue...")
1104
- return
1105
-
1106
- agent = deployable[idx]
1196
+ # Build agent selection choices with arrow-key navigation
1197
+ agent_choices = [
1198
+ f"{i}. {agent['agent_id']} - {agent['description'][:60]}{'...' if len(agent['description']) > 60 else ''}"
1199
+ for i, agent in enumerate(deployable, 1)
1200
+ ]
1107
1201
 
1108
- # Deploy agent using deployment service
1109
- print(f"\n🚀 Deploying {agent['agent_id']}...")
1202
+ choice = questionary.select(
1203
+ "Select agent to deploy:", choices=agent_choices, style=QUESTIONARY_STYLE
1204
+ ).ask()
1110
1205
 
1111
- try:
1112
- # Use SingleAgentDeployer for deployment
1113
- from claude_mpm.services.agents.deployment.agent_template_builder import (
1114
- AgentTemplateBuilder,
1115
- )
1116
- from claude_mpm.services.agents.deployment.agent_version_manager import (
1117
- AgentVersionManager,
1118
- )
1119
- from claude_mpm.services.agents.deployment.deployment_results_manager import (
1120
- DeploymentResultsManager,
1121
- )
1122
- from claude_mpm.services.agents.deployment.single_agent_deployer import (
1123
- SingleAgentDeployer,
1124
- )
1206
+ if not choice: # User pressed Esc
1207
+ return
1125
1208
 
1126
- # Initialize deployment services
1127
- template_builder = AgentTemplateBuilder()
1128
- version_manager = AgentVersionManager()
1129
- results_manager = DeploymentResultsManager(self.logger)
1130
- deployer = SingleAgentDeployer(
1131
- template_builder=template_builder,
1132
- version_manager=version_manager,
1133
- results_manager=results_manager,
1134
- logger=self.logger,
1135
- )
1209
+ # Parse agent index from "N. agent_id - description" format
1210
+ idx = int(choice.split(".")[0]) - 1
1211
+ agent = deployable[idx]
1136
1212
 
1137
- # Prepare deployment parameters
1138
- template_path = Path(agent["path"])
1139
- target_dir = Path.cwd() / ".claude" / "agents"
1213
+ # Deploy agent using deployment service
1214
+ print(f"\n🚀 Deploying {agent['agent_id']}...")
1140
1215
 
1141
- # Find base_agent.json in multiple possible locations
1142
- base_agent_candidates = [
1143
- Path.home()
1144
- / ".claude-mpm"
1145
- / "agents"
1146
- / "templates"
1147
- / "base_agent.json",
1148
- Path.home() / ".claude-mpm" / "cache" / "base_agent.json",
1149
- Path(__file__).parent.parent.parent
1150
- / "agents"
1151
- / "templates"
1152
- / "base_agent.json",
1153
- ]
1154
- base_agent_path = None
1155
- for candidate in base_agent_candidates:
1156
- if candidate.exists():
1157
- base_agent_path = candidate
1158
- break
1159
-
1160
- if not base_agent_path:
1161
- base_agent_path = base_agent_candidates[
1162
- 0
1163
- ] # Use default even if not exists
1216
+ try:
1217
+ # Use SingleAgentDeployer for deployment
1218
+ from claude_mpm.services.agents.deployment.agent_template_builder import (
1219
+ AgentTemplateBuilder,
1220
+ )
1221
+ from claude_mpm.services.agents.deployment.agent_version_manager import (
1222
+ AgentVersionManager,
1223
+ )
1224
+ from claude_mpm.services.agents.deployment.deployment_results_manager import (
1225
+ DeploymentResultsManager,
1226
+ )
1227
+ from claude_mpm.services.agents.deployment.single_agent_deployer import (
1228
+ SingleAgentDeployer,
1229
+ )
1164
1230
 
1165
- # Deploy the agent
1166
- success = deployer.deploy_agent(
1167
- agent_name=agent["agent_id"],
1168
- templates_dir=template_path.parent,
1169
- target_dir=target_dir,
1170
- base_agent_path=base_agent_path,
1171
- force_rebuild=True,
1172
- working_directory=Path.cwd(),
1173
- )
1231
+ # Initialize deployment services
1232
+ template_builder = AgentTemplateBuilder()
1233
+ version_manager = AgentVersionManager()
1234
+ results_manager = DeploymentResultsManager(self.logger)
1235
+ deployer = SingleAgentDeployer(
1236
+ template_builder=template_builder,
1237
+ version_manager=version_manager,
1238
+ results_manager=results_manager,
1239
+ logger=self.logger,
1240
+ )
1174
1241
 
1175
- if success:
1176
- print(f"\n✅ Successfully deployed {agent['agent_id']}")
1177
- else:
1178
- print(f"\n❌ Failed to deploy {agent['agent_id']}")
1242
+ # Prepare deployment parameters
1243
+ template_path = Path(agent["path"])
1244
+ target_dir = Path.cwd() / ".claude" / "agents"
1245
+
1246
+ # Find base_agent.json in multiple possible locations
1247
+ base_agent_candidates = [
1248
+ Path.home()
1249
+ / ".claude-mpm"
1250
+ / "agents"
1251
+ / "templates"
1252
+ / "base_agent.json",
1253
+ Path.home() / ".claude-mpm" / "cache" / "base_agent.json",
1254
+ Path(__file__).parent.parent.parent
1255
+ / "agents"
1256
+ / "templates"
1257
+ / "base_agent.json",
1258
+ ]
1259
+ base_agent_path = None
1260
+ for candidate in base_agent_candidates:
1261
+ if candidate.exists():
1262
+ base_agent_path = candidate
1263
+ break
1179
1264
 
1180
- except Exception as e:
1181
- self.logger.error(f"Deployment failed: {e}", exc_info=True)
1182
- print(f"\n❌ Deployment error: {e}")
1265
+ if not base_agent_path:
1266
+ base_agent_path = base_agent_candidates[
1267
+ 0
1268
+ ] # Use default even if not exists
1269
+
1270
+ # Deploy the agent
1271
+ success = deployer.deploy_agent(
1272
+ agent_name=agent["agent_id"],
1273
+ templates_dir=template_path.parent,
1274
+ target_dir=target_dir,
1275
+ base_agent_path=base_agent_path,
1276
+ force_rebuild=True,
1277
+ working_directory=Path.cwd(),
1278
+ )
1183
1279
 
1184
- input("\nPress Enter to continue...")
1280
+ if success:
1281
+ print(f"\n✅ Successfully deployed {agent['agent_id']}")
1282
+ else:
1283
+ print(f"\n❌ Failed to deploy {agent['agent_id']}")
1185
1284
 
1186
- except ValueError:
1187
- print("❌ Invalid selection")
1188
- input("\nPress Enter to continue...")
1189
1285
  except Exception as e:
1190
- self.logger.error(f"Deployment error: {e}", exc_info=True)
1191
- print(f"\n❌ Error: {e}")
1192
- input("\nPress Enter to continue...")
1286
+ self.logger.error(f"Deployment failed: {e}", exc_info=True)
1287
+ print(f"\n❌ Deployment error: {e}")
1288
+
1289
+ input("\nPress Enter to continue...")
1193
1290
 
1194
1291
  def _browse_agents_interactive(self):
1195
1292
  """Interactive agent browsing with filters."""
@@ -1203,24 +1300,37 @@ class AgentWizard:
1203
1300
  print("🔍 Browse & Filter Agents")
1204
1301
  print("=" * 60)
1205
1302
 
1206
- # Show filter menu
1303
+ # Show filter menu with arrow-key navigation
1207
1304
  print("\n[bold]Filter by:[/bold]")
1208
- print(" [1] Category (engineer/backend, qa, ops, etc.)")
1209
- print(" [2] Language (python, typescript, rust, etc.)")
1210
- print(" [3] Framework (react, nextjs, flask, etc.)")
1211
- print(" [4] Show all agents")
1212
- print(" [b] Back to main menu")
1213
1305
 
1214
- choice = input("\nSelect filter option: ").strip()
1306
+ filter_choices = [
1307
+ "1. Category (engineer/backend, qa, ops, etc.)",
1308
+ "2. Language (python, typescript, rust, etc.)",
1309
+ "3. Framework (react, nextjs, flask, etc.)",
1310
+ "4. Show all agents",
1311
+ "← Back to main menu",
1312
+ ]
1313
+
1314
+ choice = questionary.select(
1315
+ "Browse & Filter Agents:",
1316
+ choices=filter_choices,
1317
+ style=QUESTIONARY_STYLE,
1318
+ ).ask()
1319
+
1320
+ if not choice or "Back" in choice:
1321
+ break
1215
1322
 
1216
- if choice == "b":
1323
+ # Parse choice number if it starts with a digit
1324
+ if choice[0].isdigit():
1325
+ choice_num = choice.split(".")[0]
1326
+ else:
1217
1327
  break
1218
1328
 
1219
1329
  filtered_agents = []
1220
1330
  filter_description = ""
1221
1331
 
1222
- if choice == "1":
1223
- # Category filtering
1332
+ if choice_num == "1":
1333
+ # Category filtering with arrow-key navigation
1224
1334
  categories = [
1225
1335
  "engineer/backend",
1226
1336
  "engineer/frontend",
@@ -1229,26 +1339,26 @@ class AgentWizard:
1229
1339
  "documentation",
1230
1340
  "universal",
1231
1341
  ]
1232
- print("\n[bold]Available categories:[/bold]")
1233
- for idx, cat in enumerate(categories, 1):
1234
- print(f" {idx}. {cat}")
1235
1342
 
1236
- cat_choice = input("\nSelect category number: ").strip()
1237
- try:
1238
- category = categories[int(cat_choice) - 1]
1239
- all_agents = self._merge_agent_sources()
1240
- filtered_agents = [
1241
- a
1242
- for a in all_agents
1243
- if a.get("category", "").startswith(category)
1244
- ]
1245
- filter_description = f"Category: {category}"
1246
- except (ValueError, IndexError):
1247
- print("❌ Invalid selection")
1248
- input("\nPress Enter to continue...")
1343
+ cat_choices = [f"{idx}. {cat}" for idx, cat in enumerate(categories, 1)]
1344
+
1345
+ cat_choice = questionary.select(
1346
+ "Select category:", choices=cat_choices, style=QUESTIONARY_STYLE
1347
+ ).ask()
1348
+
1349
+ if not cat_choice: # User pressed Esc
1249
1350
  continue
1250
1351
 
1251
- elif choice == "2":
1352
+ # Parse category from "N. category" format
1353
+ cat_idx = int(cat_choice.split(".")[0]) - 1
1354
+ category = categories[cat_idx]
1355
+ all_agents = self._merge_agent_sources()
1356
+ filtered_agents = [
1357
+ a for a in all_agents if a.get("category", "").startswith(category)
1358
+ ]
1359
+ filter_description = f"Category: {category}"
1360
+
1361
+ elif choice_num == "2":
1252
1362
  # Language filtering (using AUTO-DEPLOY-INDEX if available)
1253
1363
  language = input(
1254
1364
  "\nEnter language (python, typescript, rust, go, etc.): "
@@ -1294,7 +1404,7 @@ class AgentWizard:
1294
1404
  input("\nPress Enter to continue...")
1295
1405
  continue
1296
1406
 
1297
- elif choice == "3":
1407
+ elif choice_num == "3":
1298
1408
  # Framework filtering
1299
1409
  framework = input(
1300
1410
  "\nEnter framework (react, nextjs, flask, django, etc.): "
@@ -1337,7 +1447,7 @@ class AgentWizard:
1337
1447
  input("\nPress Enter to continue...")
1338
1448
  continue
1339
1449
 
1340
- elif choice == "4":
1450
+ elif choice_num == "4":
1341
1451
  # Show all agents
1342
1452
  filtered_agents = self._merge_agent_sources()
1343
1453
  filter_description = "All agents"
@@ -1407,26 +1517,28 @@ class AgentWizard:
1407
1517
  input("\nPress Enter to continue...")
1408
1518
  return
1409
1519
 
1410
- agent_num = input(
1411
- f"\nEnter agent number to deploy (1-{len(agents)}) or 'c' to cancel: "
1412
- ).strip()
1413
- if agent_num.lower() == "c":
1414
- return
1520
+ # Build agent selection choices
1521
+ agent_choices = [
1522
+ f"{i}. {agent['agent_id']}" for i, agent in enumerate(agents, 1)
1523
+ ]
1415
1524
 
1416
- try:
1417
- idx = int(agent_num) - 1
1418
- if idx < 0 or idx >= len(agents):
1419
- print("❌ Invalid agent number")
1420
- input("\nPress Enter to continue...")
1421
- return
1525
+ agent_choice = questionary.select(
1526
+ "Select agent to deploy:", choices=agent_choices, style=QUESTIONARY_STYLE
1527
+ ).ask()
1422
1528
 
1423
- agent = agents[idx]
1529
+ if not agent_choice: # User pressed Esc
1530
+ return
1424
1531
 
1425
- if agent.get("deployed"):
1426
- print(f"\n[yellow]{agent['agent_id']} is already deployed[/yellow]")
1427
- else:
1428
- print(f"\n🚀 Deploying {agent['agent_id']}...")
1532
+ # Parse agent index from "N. agent_id" format
1533
+ idx = int(agent_choice.split(".")[0]) - 1
1534
+ agent = agents[idx]
1429
1535
 
1536
+ if agent.get("deployed"):
1537
+ print(f"\n[yellow]{agent['agent_id']} is already deployed[/yellow]")
1538
+ else:
1539
+ print(f"\n🚀 Deploying {agent['agent_id']}...")
1540
+
1541
+ try:
1430
1542
  from claude_mpm.services.agents.deployment.agent_template_builder import (
1431
1543
  AgentTemplateBuilder,
1432
1544
  )
@@ -1494,14 +1606,11 @@ class AgentWizard:
1494
1606
  else:
1495
1607
  print(f"[red]✗ Failed to deploy {agent['agent_id']}[/red]")
1496
1608
 
1497
- input("\nPress Enter to continue...")
1498
- except ValueError:
1499
- print("❌ Invalid agent number")
1500
- input("\nPress Enter to continue...")
1501
- except Exception as e:
1502
- self.logger.error(f"Deployment error: {e}", exc_info=True)
1503
- print(f"❌ Deployment error: {e}")
1504
- input("\nPress Enter to continue...")
1609
+ except Exception as e:
1610
+ self.logger.error(f"Deployment error: {e}", exc_info=True)
1611
+ print(f"❌ Deployment error: {e}")
1612
+
1613
+ input("\nPress Enter to continue...")
1505
1614
 
1506
1615
  def _view_from_filtered_list(self, agents: List[Dict[str, Any]]):
1507
1616
  """View details of an agent from filtered list.
@@ -1514,24 +1623,22 @@ class AgentWizard:
1514
1623
  input("\nPress Enter to continue...")
1515
1624
  return
1516
1625
 
1517
- agent_num = input(
1518
- f"\nEnter agent number to view (1-{len(agents)}) or 'c' to cancel: "
1519
- ).strip()
1520
- if agent_num.lower() == "c":
1521
- return
1626
+ # Build agent selection choices
1627
+ agent_choices = [
1628
+ f"{i}. {agent['agent_id']}" for i, agent in enumerate(agents, 1)
1629
+ ]
1522
1630
 
1523
- try:
1524
- idx = int(agent_num) - 1
1525
- if idx < 0 or idx >= len(agents):
1526
- print("❌ Invalid agent number")
1527
- input("\nPress Enter to continue...")
1528
- return
1631
+ agent_choice = questionary.select(
1632
+ "Select agent to view:", choices=agent_choices, style=QUESTIONARY_STYLE
1633
+ ).ask()
1529
1634
 
1530
- agent = agents[idx]
1531
- self._show_agent_details(agent)
1532
- except ValueError:
1533
- print("❌ Invalid agent number")
1534
- input("\nPress Enter to continue...")
1635
+ if not agent_choice: # User pressed Esc
1636
+ return
1637
+
1638
+ # Parse agent index from "N. agent_id" format
1639
+ idx = int(agent_choice.split(".")[0]) - 1
1640
+ agent = agents[idx]
1641
+ self._show_agent_details(agent)
1535
1642
 
1536
1643
  def _deploy_preset_interactive(self):
1537
1644
  """Interactive preset deployment with preview and confirmation."""