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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +1176 -909
- claude_mpm/agents/base_agent_loader.py +10 -35
- claude_mpm/agents/frontmatter_validator.py +68 -0
- claude_mpm/agents/templates/circuit-breakers.md +293 -44
- claude_mpm/cli/__init__.py +0 -1
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/agent_state_manager.py +64 -11
- claude_mpm/cli/commands/agents.py +446 -25
- claude_mpm/cli/commands/auto_configure.py +535 -233
- claude_mpm/cli/commands/configure.py +545 -89
- claude_mpm/cli/commands/postmortem.py +401 -0
- claude_mpm/cli/commands/run.py +1 -39
- claude_mpm/cli/commands/skills.py +322 -19
- claude_mpm/cli/interactive/agent_wizard.py +302 -195
- claude_mpm/cli/parsers/agents_parser.py +137 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
- claude_mpm/cli/parsers/base_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +7 -0
- claude_mpm/cli/startup.py +73 -32
- claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
- claude_mpm/commands/mpm-agents-list.md +2 -2
- claude_mpm/commands/mpm-config-view.md +2 -2
- claude_mpm/commands/mpm-help.md +3 -0
- claude_mpm/commands/mpm-postmortem.md +123 -0
- claude_mpm/commands/mpm-session-resume.md +2 -2
- claude_mpm/commands/mpm-ticket-organize.md +2 -2
- claude_mpm/commands/mpm-ticket-view.md +2 -2
- claude_mpm/config/agent_presets.py +312 -82
- claude_mpm/config/skill_presets.py +392 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +2 -25
- claude_mpm/core/framework/loaders/file_loader.py +54 -101
- claude_mpm/core/interactive_session.py +19 -5
- claude_mpm/core/oneshot_session.py +16 -4
- claude_mpm/core/output_style_manager.py +173 -43
- claude_mpm/core/protocols/__init__.py +23 -0
- claude_mpm/core/protocols/runner_protocol.py +103 -0
- claude_mpm/core/protocols/session_protocol.py +131 -0
- claude_mpm/core/shared/singleton_manager.py +11 -4
- claude_mpm/core/system_context.py +38 -0
- claude_mpm/core/unified_agent_registry.py +129 -1
- claude_mpm/core/unified_config.py +22 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
- claude_mpm/models/agent_definition.py +7 -0
- claude_mpm/services/agents/cache_git_manager.py +621 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +195 -1
- claude_mpm/services/agents/sources/git_source_sync_service.py +37 -5
- claude_mpm/services/analysis/__init__.py +25 -0
- claude_mpm/services/analysis/postmortem_reporter.py +474 -0
- claude_mpm/services/analysis/postmortem_service.py +765 -0
- claude_mpm/services/command_deployment_service.py +108 -5
- claude_mpm/services/core/base.py +7 -2
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
- claude_mpm/services/git/git_operations_service.py +8 -8
- claude_mpm/services/mcp_config_manager.py +75 -145
- claude_mpm/services/mcp_gateway/core/process_pool.py +22 -16
- claude_mpm/services/mcp_service_verifier.py +6 -3
- claude_mpm/services/monitor/daemon.py +28 -8
- claude_mpm/services/monitor/daemon_manager.py +96 -19
- claude_mpm/services/project/project_organizer.py +4 -0
- claude_mpm/services/runner_configuration_service.py +16 -3
- claude_mpm/services/session_management_service.py +16 -4
- claude_mpm/utils/agent_filters.py +288 -0
- claude_mpm/utils/gitignore.py +3 -0
- claude_mpm/utils/migration.py +372 -0
- claude_mpm/utils/progress.py +5 -1
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/METADATA +69 -8
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/RECORD +76 -62
- /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/WHEEL +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/top_level.txt +0 -0
|
@@ -12,10 +12,13 @@ DESIGN DECISIONS:
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
import json
|
|
15
|
+
import shutil
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
from typing import Dict, List, Optional
|
|
17
18
|
|
|
18
19
|
import questionary
|
|
20
|
+
import questionary.constants
|
|
21
|
+
import questionary.prompts.common # For checkbox symbol customization
|
|
19
22
|
from questionary import Style
|
|
20
23
|
from rich.console import Console
|
|
21
24
|
from rich.prompt import Confirm, Prompt
|
|
@@ -23,6 +26,7 @@ from rich.text import Text
|
|
|
23
26
|
|
|
24
27
|
from ...core.config import Config
|
|
25
28
|
from ...services.version_service import VersionService
|
|
29
|
+
from ...utils.agent_filters import apply_all_filters, get_deployed_agent_ids
|
|
26
30
|
from ...utils.console import console as default_console
|
|
27
31
|
from ..shared import BaseCommand, CommandResult
|
|
28
32
|
from .agent_state_manager import SimpleAgentManager
|
|
@@ -43,13 +47,18 @@ from .configure_validators import (
|
|
|
43
47
|
class ConfigureCommand(BaseCommand):
|
|
44
48
|
"""Interactive configuration management command."""
|
|
45
49
|
|
|
46
|
-
# Questionary style
|
|
50
|
+
# Questionary style optimized for dark terminals (WCAG AAA compliant)
|
|
47
51
|
QUESTIONARY_STYLE = Style(
|
|
48
52
|
[
|
|
49
|
-
("selected", "fg
|
|
50
|
-
("pointer", "fg
|
|
51
|
-
("highlighted", "fg
|
|
52
|
-
("question", "fg
|
|
53
|
+
("selected", "fg:#e0e0e0 bold"), # Light gray - excellent readability
|
|
54
|
+
("pointer", "fg:#ffd700 bold"), # Gold/yellow - highly visible pointer
|
|
55
|
+
("highlighted", "fg:#e0e0e0"), # Light gray - clear hover state
|
|
56
|
+
("question", "fg:#e0e0e0 bold"), # Light gray bold - prominent questions
|
|
57
|
+
("checkbox", "fg:#00ff00"), # Green - for checked boxes
|
|
58
|
+
(
|
|
59
|
+
"checkbox-selected",
|
|
60
|
+
"fg:#00ff00 bold",
|
|
61
|
+
), # Green bold - for checked selected boxes
|
|
53
62
|
]
|
|
54
63
|
)
|
|
55
64
|
|
|
@@ -296,23 +305,33 @@ class ConfigureCommand(BaseCommand):
|
|
|
296
305
|
return self.navigation.show_main_menu()
|
|
297
306
|
|
|
298
307
|
def _manage_agents(self) -> None:
|
|
299
|
-
"""Enhanced agent management with remote agent discovery and
|
|
308
|
+
"""Enhanced agent management with remote agent discovery and installation."""
|
|
300
309
|
while True:
|
|
301
310
|
self.console.clear()
|
|
302
311
|
self.navigation.display_header()
|
|
303
312
|
self.console.print("\n[bold blue]═══ Agent Management ═══[/bold blue]\n")
|
|
304
313
|
|
|
305
314
|
# Step 1: Show configured sources
|
|
306
|
-
self.console.print("[bold
|
|
315
|
+
self.console.print("[bold white]═══ Agent Sources ═══[/bold white]\n")
|
|
307
316
|
|
|
308
317
|
sources = self._get_configured_sources()
|
|
309
318
|
if sources:
|
|
310
319
|
from rich.table import Table
|
|
311
320
|
|
|
312
|
-
sources_table = Table(show_header=True, header_style="bold
|
|
313
|
-
sources_table.add_column(
|
|
314
|
-
|
|
315
|
-
|
|
321
|
+
sources_table = Table(show_header=True, header_style="bold white")
|
|
322
|
+
sources_table.add_column(
|
|
323
|
+
"Source",
|
|
324
|
+
style="bright_yellow",
|
|
325
|
+
width=40,
|
|
326
|
+
no_wrap=True,
|
|
327
|
+
overflow="ellipsis",
|
|
328
|
+
)
|
|
329
|
+
sources_table.add_column(
|
|
330
|
+
"Status", style="green", width=15, no_wrap=True
|
|
331
|
+
)
|
|
332
|
+
sources_table.add_column(
|
|
333
|
+
"Agents", style="yellow", width=10, no_wrap=True
|
|
334
|
+
)
|
|
316
335
|
|
|
317
336
|
for source in sources:
|
|
318
337
|
status = "✓ Active" if source.get("enabled", True) else "Disabled"
|
|
@@ -329,19 +348,29 @@ class ConfigureCommand(BaseCommand):
|
|
|
329
348
|
)
|
|
330
349
|
|
|
331
350
|
# Step 2: Discover and display available agents
|
|
332
|
-
self.console.print("\n[bold
|
|
351
|
+
self.console.print("\n[bold white]═══ Available Agents ═══[/bold white]\n")
|
|
333
352
|
|
|
334
353
|
try:
|
|
335
354
|
# Discover agents (includes both local and remote)
|
|
336
355
|
agents = self.agent_manager.discover_agents(include_remote=True)
|
|
337
356
|
|
|
357
|
+
# Set deployment status on each agent for display
|
|
358
|
+
deployed_ids = get_deployed_agent_ids()
|
|
359
|
+
for agent in agents:
|
|
360
|
+
# Extract leaf name for comparison
|
|
361
|
+
agent_leaf_name = agent.name.split("/")[-1]
|
|
362
|
+
agent.is_deployed = agent_leaf_name in deployed_ids
|
|
363
|
+
|
|
364
|
+
# Filter BASE_AGENT from display (1M-502 Phase 1)
|
|
365
|
+
agents = self._filter_agent_configs(agents, filter_deployed=False)
|
|
366
|
+
|
|
338
367
|
if not agents:
|
|
339
368
|
self.console.print("[yellow]No agents found[/yellow]")
|
|
340
369
|
self.console.print(
|
|
341
370
|
"[dim]Configure sources with 'claude-mpm agent-source add'[/dim]\n"
|
|
342
371
|
)
|
|
343
372
|
else:
|
|
344
|
-
# Display agents in a table
|
|
373
|
+
# Display agents in a table (already filtered at line 339)
|
|
345
374
|
self._display_agents_with_source_info(agents)
|
|
346
375
|
|
|
347
376
|
except Exception as e:
|
|
@@ -350,13 +379,14 @@ class ConfigureCommand(BaseCommand):
|
|
|
350
379
|
|
|
351
380
|
# Step 3: Menu options with arrow-key navigation
|
|
352
381
|
self.console.print()
|
|
382
|
+
self.logger.debug("About to show agent management menu")
|
|
353
383
|
try:
|
|
354
384
|
choice = questionary.select(
|
|
355
385
|
"Agent Management:",
|
|
356
386
|
choices=[
|
|
357
387
|
"Manage sources (add/remove repositories)",
|
|
358
|
-
"
|
|
359
|
-
"
|
|
388
|
+
"Select Agents",
|
|
389
|
+
"Install preset (predefined sets)",
|
|
360
390
|
"Remove agents",
|
|
361
391
|
"View agent details",
|
|
362
392
|
"Toggle agents (legacy enable/disable)",
|
|
@@ -374,9 +404,10 @@ class ConfigureCommand(BaseCommand):
|
|
|
374
404
|
# Map selection to action
|
|
375
405
|
if choice == "Manage sources (add/remove repositories)":
|
|
376
406
|
self._manage_sources()
|
|
377
|
-
elif choice == "
|
|
407
|
+
elif choice == "Select Agents":
|
|
408
|
+
self.logger.debug("User selected 'Select Agents' from menu")
|
|
378
409
|
self._deploy_agents_individual(agents_var)
|
|
379
|
-
elif choice == "
|
|
410
|
+
elif choice == "Install preset (predefined sets)":
|
|
380
411
|
self._deploy_agents_preset()
|
|
381
412
|
elif choice == "Remove agents":
|
|
382
413
|
self._remove_agents(agents_var)
|
|
@@ -388,6 +419,26 @@ class ConfigureCommand(BaseCommand):
|
|
|
388
419
|
except KeyboardInterrupt:
|
|
389
420
|
self.console.print("\n[yellow]Operation cancelled[/yellow]")
|
|
390
421
|
break
|
|
422
|
+
except Exception as e:
|
|
423
|
+
# Handle questionary menu failure
|
|
424
|
+
import sys
|
|
425
|
+
|
|
426
|
+
self.logger.error(f"Agent management menu failed: {e}", exc_info=True)
|
|
427
|
+
self.console.print("[red]Error: Interactive menu failed[/red]")
|
|
428
|
+
self.console.print(f"[dim]Reason: {e}[/dim]")
|
|
429
|
+
if not sys.stdin.isatty():
|
|
430
|
+
self.console.print(
|
|
431
|
+
"[dim]Interactive terminal required for this operation[/dim]"
|
|
432
|
+
)
|
|
433
|
+
self.console.print("[dim]Use command-line options instead:[/dim]")
|
|
434
|
+
self.console.print(
|
|
435
|
+
"[dim] claude-mpm configure --list-agents[/dim]"
|
|
436
|
+
)
|
|
437
|
+
self.console.print(
|
|
438
|
+
"[dim] claude-mpm configure --enable-agent <id>[/dim]"
|
|
439
|
+
)
|
|
440
|
+
Prompt.ask("\nPress Enter to continue")
|
|
441
|
+
break
|
|
391
442
|
|
|
392
443
|
def _display_agents_table(self, agents: List[AgentConfig]) -> None:
|
|
393
444
|
"""Display a table of available agents."""
|
|
@@ -560,6 +611,8 @@ class ConfigureCommand(BaseCommand):
|
|
|
560
611
|
|
|
561
612
|
# Get list of enabled agents
|
|
562
613
|
agents = self.agent_manager.discover_agents()
|
|
614
|
+
# Filter BASE_AGENT from all agent operations (1M-502 Phase 1)
|
|
615
|
+
agents = self._filter_agent_configs(agents, filter_deployed=False)
|
|
563
616
|
enabled_agents = [
|
|
564
617
|
a.name
|
|
565
618
|
for a in agents
|
|
@@ -603,9 +656,9 @@ class ConfigureCommand(BaseCommand):
|
|
|
603
656
|
else:
|
|
604
657
|
from rich.table import Table
|
|
605
658
|
|
|
606
|
-
table = Table(show_header=True, header_style="bold
|
|
607
|
-
table.add_column("Agent", style="
|
|
608
|
-
table.add_column("Skills", style="green")
|
|
659
|
+
table = Table(show_header=True, header_style="bold white")
|
|
660
|
+
table.add_column("Agent", style="white", no_wrap=True)
|
|
661
|
+
table.add_column("Skills", style="green", no_wrap=True)
|
|
609
662
|
|
|
610
663
|
for agent_id, skills in mappings.items():
|
|
611
664
|
skills_str = (
|
|
@@ -626,6 +679,8 @@ class ConfigureCommand(BaseCommand):
|
|
|
626
679
|
|
|
627
680
|
# Get enabled agents
|
|
628
681
|
agents = self.agent_manager.discover_agents()
|
|
682
|
+
# Filter BASE_AGENT from all agent operations (1M-502 Phase 1)
|
|
683
|
+
agents = self._filter_agent_configs(agents, filter_deployed=False)
|
|
629
684
|
enabled_agents = [
|
|
630
685
|
a.name
|
|
631
686
|
for a in agents
|
|
@@ -757,6 +812,8 @@ class ConfigureCommand(BaseCommand):
|
|
|
757
812
|
def _list_agents_non_interactive(self) -> CommandResult:
|
|
758
813
|
"""List agents in non-interactive mode."""
|
|
759
814
|
agents = self.agent_manager.discover_agents()
|
|
815
|
+
# Filter BASE_AGENT from all agent lists (1M-502 Phase 1)
|
|
816
|
+
agents = self._filter_agent_configs(agents, filter_deployed=False)
|
|
760
817
|
|
|
761
818
|
data = []
|
|
762
819
|
for agent in agents:
|
|
@@ -900,41 +957,184 @@ class ConfigureCommand(BaseCommand):
|
|
|
900
957
|
self.logger.warning(f"Failed to get configured sources: {e}")
|
|
901
958
|
return []
|
|
902
959
|
|
|
960
|
+
def _filter_agent_configs(
|
|
961
|
+
self, agents: List[AgentConfig], filter_deployed: bool = False
|
|
962
|
+
) -> List[AgentConfig]:
|
|
963
|
+
"""Filter AgentConfig objects using agent_filters utilities.
|
|
964
|
+
|
|
965
|
+
Converts AgentConfig objects to dictionaries for filtering,
|
|
966
|
+
then back to AgentConfig. Always filters BASE_AGENT.
|
|
967
|
+
Optionally filters deployed agents.
|
|
968
|
+
|
|
969
|
+
Args:
|
|
970
|
+
agents: List of AgentConfig objects
|
|
971
|
+
filter_deployed: Whether to filter out deployed agents (default: False)
|
|
972
|
+
|
|
973
|
+
Returns:
|
|
974
|
+
Filtered list of AgentConfig objects
|
|
975
|
+
"""
|
|
976
|
+
# Convert AgentConfig to dict format for filtering
|
|
977
|
+
agent_dicts = []
|
|
978
|
+
for agent in agents:
|
|
979
|
+
agent_dicts.append(
|
|
980
|
+
{
|
|
981
|
+
"agent_id": agent.name,
|
|
982
|
+
"name": agent.name,
|
|
983
|
+
"description": agent.description,
|
|
984
|
+
"deployed": getattr(agent, "is_deployed", False),
|
|
985
|
+
}
|
|
986
|
+
)
|
|
987
|
+
|
|
988
|
+
# Apply filters (always filter BASE_AGENT)
|
|
989
|
+
filtered_dicts = apply_all_filters(
|
|
990
|
+
agent_dicts, filter_base=True, filter_deployed=filter_deployed
|
|
991
|
+
)
|
|
992
|
+
|
|
993
|
+
# Convert back to AgentConfig objects
|
|
994
|
+
filtered_names = {d["agent_id"] for d in filtered_dicts}
|
|
995
|
+
return [a for a in agents if a.name in filtered_names]
|
|
996
|
+
|
|
997
|
+
@staticmethod
|
|
998
|
+
def _calculate_column_widths(
|
|
999
|
+
terminal_width: int, columns: Dict[str, int]
|
|
1000
|
+
) -> Dict[str, int]:
|
|
1001
|
+
"""Calculate dynamic column widths based on terminal size.
|
|
1002
|
+
|
|
1003
|
+
Args:
|
|
1004
|
+
terminal_width: Current terminal width in characters
|
|
1005
|
+
columns: Dict mapping column names to minimum widths
|
|
1006
|
+
|
|
1007
|
+
Returns:
|
|
1008
|
+
Dict mapping column names to calculated widths
|
|
1009
|
+
|
|
1010
|
+
Design:
|
|
1011
|
+
- Ensures minimum widths are respected
|
|
1012
|
+
- Distributes extra space proportionally
|
|
1013
|
+
- Handles narrow terminals gracefully (minimum 80 chars)
|
|
1014
|
+
"""
|
|
1015
|
+
# Ensure minimum terminal width
|
|
1016
|
+
min_terminal_width = 80
|
|
1017
|
+
terminal_width = max(terminal_width, min_terminal_width)
|
|
1018
|
+
|
|
1019
|
+
# Calculate total minimum width needed
|
|
1020
|
+
total_min_width = sum(columns.values())
|
|
1021
|
+
|
|
1022
|
+
# Account for table borders and padding (2 chars per column + 2 for edges)
|
|
1023
|
+
overhead = (len(columns) * 2) + 2
|
|
1024
|
+
available_width = terminal_width - overhead
|
|
1025
|
+
|
|
1026
|
+
# If we have extra space, distribute proportionally
|
|
1027
|
+
if available_width > total_min_width:
|
|
1028
|
+
extra_space = available_width - total_min_width
|
|
1029
|
+
total_weight = sum(columns.values())
|
|
1030
|
+
|
|
1031
|
+
result = {}
|
|
1032
|
+
for col_name, min_width in columns.items():
|
|
1033
|
+
# Distribute extra space based on minimum width proportion
|
|
1034
|
+
proportion = min_width / total_weight
|
|
1035
|
+
extra = int(extra_space * proportion)
|
|
1036
|
+
result[col_name] = min_width + extra
|
|
1037
|
+
return result
|
|
1038
|
+
# Terminal too narrow, use minimum widths
|
|
1039
|
+
return columns.copy()
|
|
1040
|
+
|
|
903
1041
|
def _display_agents_with_source_info(self, agents: List[AgentConfig]) -> None:
|
|
904
|
-
"""Display agents table with source information and
|
|
1042
|
+
"""Display agents table with source information and installation status."""
|
|
905
1043
|
from rich.table import Table
|
|
906
1044
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1045
|
+
# Get terminal width and calculate dynamic column widths
|
|
1046
|
+
terminal_width = shutil.get_terminal_size().columns
|
|
1047
|
+
min_widths = {
|
|
1048
|
+
"#": 4,
|
|
1049
|
+
"Agent ID": 30,
|
|
1050
|
+
"Name": 20,
|
|
1051
|
+
"Source": 15,
|
|
1052
|
+
"Status": 10,
|
|
1053
|
+
}
|
|
1054
|
+
widths = self._calculate_column_widths(terminal_width, min_widths)
|
|
1055
|
+
|
|
1056
|
+
agents_table = Table(show_header=True, header_style="bold white")
|
|
1057
|
+
agents_table.add_column("#", style="dim", width=widths["#"], no_wrap=True)
|
|
1058
|
+
agents_table.add_column(
|
|
1059
|
+
"Agent ID",
|
|
1060
|
+
style="white",
|
|
1061
|
+
width=widths["Agent ID"],
|
|
1062
|
+
no_wrap=True,
|
|
1063
|
+
overflow="ellipsis",
|
|
1064
|
+
)
|
|
1065
|
+
agents_table.add_column(
|
|
1066
|
+
"Name",
|
|
1067
|
+
style="white",
|
|
1068
|
+
width=widths["Name"],
|
|
1069
|
+
no_wrap=True,
|
|
1070
|
+
overflow="ellipsis",
|
|
1071
|
+
)
|
|
1072
|
+
agents_table.add_column(
|
|
1073
|
+
"Source",
|
|
1074
|
+
style="bright_yellow",
|
|
1075
|
+
width=widths["Source"],
|
|
1076
|
+
no_wrap=True,
|
|
1077
|
+
)
|
|
1078
|
+
agents_table.add_column(
|
|
1079
|
+
"Status", style="white", width=widths["Status"], no_wrap=True
|
|
1080
|
+
)
|
|
913
1081
|
|
|
914
1082
|
for idx, agent in enumerate(agents, 1):
|
|
915
|
-
# Determine source
|
|
1083
|
+
# Determine source with repo name
|
|
916
1084
|
source_type = getattr(agent, "source_type", "local")
|
|
917
|
-
source_label = "Remote" if source_type == "remote" else "Local"
|
|
918
1085
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1086
|
+
if source_type == "remote":
|
|
1087
|
+
# Get repo name from agent metadata
|
|
1088
|
+
source_dict = getattr(agent, "source_dict", {})
|
|
1089
|
+
repo_url = source_dict.get("source", "")
|
|
1090
|
+
|
|
1091
|
+
# Extract repo name from URL
|
|
1092
|
+
if (
|
|
1093
|
+
"bobmatnyc/claude-mpm" in repo_url
|
|
1094
|
+
or "claude-mpm" in repo_url.lower()
|
|
1095
|
+
):
|
|
1096
|
+
source_label = "MPM Agents"
|
|
1097
|
+
elif "/" in repo_url:
|
|
1098
|
+
# Extract last part of org/repo
|
|
1099
|
+
parts = repo_url.rstrip("/").split("/")
|
|
1100
|
+
if len(parts) >= 2:
|
|
1101
|
+
source_label = f"{parts[-2]}/{parts[-1]}"
|
|
1102
|
+
else:
|
|
1103
|
+
source_label = "Community"
|
|
1104
|
+
else:
|
|
1105
|
+
source_label = "Community"
|
|
1106
|
+
else:
|
|
1107
|
+
source_label = "Local"
|
|
1108
|
+
|
|
1109
|
+
# Determine installation status (removed symbols for cleaner look)
|
|
1110
|
+
is_installed = getattr(agent, "is_deployed", False)
|
|
1111
|
+
if is_installed:
|
|
1112
|
+
status = "[green]Installed[/green]"
|
|
1113
|
+
else:
|
|
1114
|
+
status = "Available"
|
|
922
1115
|
|
|
923
1116
|
# Get display name (for remote agents, use display_name instead of agent_id)
|
|
924
1117
|
display_name = getattr(agent, "display_name", agent.name)
|
|
925
|
-
|
|
926
|
-
display_name = display_name[:20] + "..."
|
|
1118
|
+
# Let overflow="ellipsis" handle truncation automatically
|
|
927
1119
|
|
|
928
1120
|
agents_table.add_row(
|
|
929
1121
|
str(idx), agent.name, display_name, source_label, status
|
|
930
1122
|
)
|
|
931
1123
|
|
|
932
1124
|
self.console.print(agents_table)
|
|
933
|
-
|
|
1125
|
+
|
|
1126
|
+
# Show installed vs available count
|
|
1127
|
+
installed_count = sum(1 for a in agents if getattr(a, "is_deployed", False))
|
|
1128
|
+
available_count = len(agents) - installed_count
|
|
1129
|
+
self.console.print(
|
|
1130
|
+
f"\n[green]✓ {installed_count} installed[/green] | "
|
|
1131
|
+
f"[dim]{available_count} available[/dim] | "
|
|
1132
|
+
f"[dim]Total: {len(agents)}[/dim]"
|
|
1133
|
+
)
|
|
934
1134
|
|
|
935
1135
|
def _manage_sources(self) -> None:
|
|
936
1136
|
"""Interactive source management."""
|
|
937
|
-
self.console.print("\n[bold
|
|
1137
|
+
self.console.print("\n[bold white]═══ Manage Agent Sources ═══[/bold white]\n")
|
|
938
1138
|
self.console.print(
|
|
939
1139
|
"[dim]Use 'claude-mpm agent-source' command to add/remove sources[/dim]"
|
|
940
1140
|
)
|
|
@@ -945,43 +1145,297 @@ class ConfigureCommand(BaseCommand):
|
|
|
945
1145
|
Prompt.ask("\nPress Enter to continue")
|
|
946
1146
|
|
|
947
1147
|
def _deploy_agents_individual(self, agents: List[AgentConfig]) -> None:
|
|
948
|
-
"""
|
|
1148
|
+
"""Manage agent installation state (unified install/remove interface)."""
|
|
949
1149
|
if not agents:
|
|
950
|
-
self.console.print("[yellow]No agents available
|
|
1150
|
+
self.console.print("[yellow]No agents available[/yellow]")
|
|
951
1151
|
Prompt.ask("\nPress Enter to continue")
|
|
952
1152
|
return
|
|
953
1153
|
|
|
954
|
-
#
|
|
955
|
-
|
|
1154
|
+
# Get ALL agents (filter BASE_AGENT but keep deployed agents visible)
|
|
1155
|
+
from claude_mpm.utils.agent_filters import (
|
|
1156
|
+
filter_base_agents,
|
|
1157
|
+
get_deployed_agent_ids,
|
|
1158
|
+
)
|
|
956
1159
|
|
|
957
|
-
|
|
958
|
-
|
|
1160
|
+
# Filter BASE_AGENT but keep deployed agents visible
|
|
1161
|
+
all_agents = filter_base_agents(
|
|
1162
|
+
[
|
|
1163
|
+
{
|
|
1164
|
+
"agent_id": a.name,
|
|
1165
|
+
"name": a.name,
|
|
1166
|
+
"description": a.description,
|
|
1167
|
+
"deployed": getattr(a, "is_deployed", False),
|
|
1168
|
+
}
|
|
1169
|
+
for a in agents
|
|
1170
|
+
]
|
|
1171
|
+
)
|
|
1172
|
+
|
|
1173
|
+
# Get deployed agent IDs (original state - for calculating final changes)
|
|
1174
|
+
# NOTE: deployed_ids contains LEAF NAMES (e.g., "python-engineer")
|
|
1175
|
+
deployed_ids = get_deployed_agent_ids()
|
|
1176
|
+
|
|
1177
|
+
if not all_agents:
|
|
1178
|
+
self.console.print("[yellow]No agents available[/yellow]")
|
|
959
1179
|
Prompt.ask("\nPress Enter to continue")
|
|
960
1180
|
return
|
|
961
1181
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1182
|
+
# Build mapping: leaf name -> full path for deployed agents
|
|
1183
|
+
# This allows comparing deployed_ids (leaf names) with agent.name (full paths)
|
|
1184
|
+
deployed_full_paths = set()
|
|
1185
|
+
for agent in agents:
|
|
1186
|
+
agent_leaf_name = agent.name.split("/")[-1]
|
|
1187
|
+
if agent_leaf_name in deployed_ids:
|
|
1188
|
+
deployed_full_paths.add(agent.name)
|
|
966
1189
|
|
|
967
|
-
selection
|
|
968
|
-
|
|
969
|
-
return
|
|
1190
|
+
# Track current selection state (starts with deployed full paths, updated after each iteration)
|
|
1191
|
+
current_selection = deployed_full_paths.copy()
|
|
970
1192
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1193
|
+
# Loop to allow adjusting selection
|
|
1194
|
+
while True:
|
|
1195
|
+
# Build checkbox choices with pre-selection based on current_selection
|
|
1196
|
+
agent_choices = []
|
|
1197
|
+
agent_map = {} # For lookup after selection
|
|
1198
|
+
|
|
1199
|
+
for agent in agents:
|
|
1200
|
+
if agent.name in {a["agent_id"] for a in all_agents}:
|
|
1201
|
+
display_name = getattr(agent, "display_name", agent.name)
|
|
1202
|
+
|
|
1203
|
+
# Pre-check based on current_selection (full paths)
|
|
1204
|
+
# current_selection contains full paths like "engineer/backend/python-engineer"
|
|
1205
|
+
is_selected = agent.name in current_selection
|
|
1206
|
+
|
|
1207
|
+
# Simple format: "agent/path - Display Name"
|
|
1208
|
+
# Checkbox state (checked/unchecked) indicates installed status
|
|
1209
|
+
choice_text = f"{agent.name}"
|
|
1210
|
+
if display_name and display_name != agent.name:
|
|
1211
|
+
choice_text += f" - {display_name}"
|
|
1212
|
+
|
|
1213
|
+
# Create choice with checked based on current_selection
|
|
1214
|
+
choice = questionary.Choice(
|
|
1215
|
+
title=choice_text, value=agent.name, checked=is_selected
|
|
1216
|
+
)
|
|
1217
|
+
|
|
1218
|
+
agent_choices.append(choice)
|
|
1219
|
+
agent_map[agent.name] = agent
|
|
1220
|
+
|
|
1221
|
+
# Multi-select with pre-selection
|
|
1222
|
+
self.console.print("\n[bold cyan]Manage Agent Installation[/bold cyan]")
|
|
1223
|
+
self.console.print("[dim][✓] Checked = Installed (uncheck to remove)[/dim]")
|
|
1224
|
+
self.console.print(
|
|
1225
|
+
"[dim][ ] Unchecked = Available (check to install)[/dim]"
|
|
1226
|
+
)
|
|
1227
|
+
self.console.print(
|
|
1228
|
+
"[dim]Use arrow keys to navigate, space to toggle, "
|
|
1229
|
+
"Enter to apply changes[/dim]\n"
|
|
1230
|
+
)
|
|
1231
|
+
|
|
1232
|
+
# Monkey-patch questionary symbols for better visibility
|
|
1233
|
+
# Must patch common module directly since it imports constants at load time
|
|
1234
|
+
questionary.prompts.common.INDICATOR_SELECTED = "[✓]"
|
|
1235
|
+
questionary.prompts.common.INDICATOR_UNSELECTED = "[ ]"
|
|
1236
|
+
|
|
1237
|
+
# Pre-selection via checked=True on Choice objects
|
|
1238
|
+
self.logger.debug(
|
|
1239
|
+
"About to show checkbox selection with %d agents", len(agent_choices)
|
|
1240
|
+
)
|
|
1241
|
+
|
|
1242
|
+
try:
|
|
1243
|
+
selected_agent_ids = questionary.checkbox(
|
|
1244
|
+
"Agents:", choices=agent_choices, style=self.QUESTIONARY_STYLE
|
|
1245
|
+
).ask()
|
|
1246
|
+
except Exception as e:
|
|
1247
|
+
# Handle questionary failure (non-TTY, broken pipe, keyboard interrupt, etc.)
|
|
1248
|
+
import sys
|
|
1249
|
+
|
|
1250
|
+
self.logger.error(f"Questionary checkbox failed: {e}", exc_info=True)
|
|
1251
|
+
self.console.print(
|
|
1252
|
+
"[red]Error: Could not display interactive menu[/red]"
|
|
1253
|
+
)
|
|
1254
|
+
self.console.print(f"[dim]Reason: {e}[/dim]")
|
|
1255
|
+
if not sys.stdin.isatty():
|
|
1256
|
+
self.console.print("[dim]Interactive terminal required. Use:[/dim]")
|
|
1257
|
+
self.console.print(
|
|
1258
|
+
"[dim] --list-agents to see available agents[/dim]"
|
|
1259
|
+
)
|
|
1260
|
+
self.console.print(
|
|
1261
|
+
"[dim] --enable-agent/--disable-agent for scripting[/dim]"
|
|
1262
|
+
)
|
|
1263
|
+
else:
|
|
1264
|
+
self.console.print(
|
|
1265
|
+
"[dim]This might be a terminal compatibility issue.[/dim]"
|
|
1266
|
+
)
|
|
978
1267
|
Prompt.ask("\nPress Enter to continue")
|
|
979
|
-
|
|
980
|
-
|
|
1268
|
+
return
|
|
1269
|
+
|
|
1270
|
+
# Handle Esc OR non-interactive terminal
|
|
1271
|
+
if selected_agent_ids is None:
|
|
1272
|
+
# Check if we're in a non-interactive environment
|
|
1273
|
+
import sys
|
|
1274
|
+
|
|
1275
|
+
if not sys.stdin.isatty():
|
|
1276
|
+
self.console.print(
|
|
1277
|
+
"[red]Error: Interactive terminal required for agent selection[/red]"
|
|
1278
|
+
)
|
|
1279
|
+
self.console.print(
|
|
1280
|
+
"[dim]Use --list-agents to see available agents[/dim]"
|
|
1281
|
+
)
|
|
1282
|
+
self.console.print(
|
|
1283
|
+
"[dim]Use --enable-agent/--disable-agent for non-interactive mode[/dim]"
|
|
1284
|
+
)
|
|
1285
|
+
else:
|
|
1286
|
+
self.console.print("[yellow]No changes made[/yellow]")
|
|
1287
|
+
Prompt.ask("\nPress Enter to continue")
|
|
1288
|
+
return
|
|
1289
|
+
|
|
1290
|
+
# Update current_selection based on user's choices (full paths)
|
|
1291
|
+
current_selection = set(selected_agent_ids)
|
|
1292
|
+
|
|
1293
|
+
# Determine actions based on ORIGINAL deployed state
|
|
1294
|
+
# Compare full paths to full paths (deployed_full_paths was built from deployed_ids)
|
|
1295
|
+
to_deploy = (
|
|
1296
|
+
current_selection - deployed_full_paths
|
|
1297
|
+
) # Selected but not originally deployed
|
|
1298
|
+
to_remove = (
|
|
1299
|
+
deployed_full_paths - current_selection
|
|
1300
|
+
) # Originally deployed but not selected
|
|
1301
|
+
|
|
1302
|
+
if not to_deploy and not to_remove:
|
|
1303
|
+
self.console.print(
|
|
1304
|
+
"[yellow]No changes needed - all selected agents are already installed[/yellow]"
|
|
1305
|
+
)
|
|
1306
|
+
Prompt.ask("\nPress Enter to continue")
|
|
1307
|
+
return
|
|
1308
|
+
|
|
1309
|
+
# Show what will happen
|
|
1310
|
+
self.console.print("\n[bold]Changes to apply:[/bold]")
|
|
1311
|
+
if to_deploy:
|
|
1312
|
+
self.console.print(f"[green]Install {len(to_deploy)} agent(s)[/green]")
|
|
1313
|
+
for agent_id in to_deploy:
|
|
1314
|
+
self.console.print(f" + {agent_id}")
|
|
1315
|
+
if to_remove:
|
|
1316
|
+
self.console.print(f"[red]Remove {len(to_remove)} agent(s)[/red]")
|
|
1317
|
+
for agent_id in to_remove:
|
|
1318
|
+
self.console.print(f" - {agent_id}")
|
|
1319
|
+
|
|
1320
|
+
# Ask user to confirm, adjust, or cancel
|
|
1321
|
+
action = questionary.select(
|
|
1322
|
+
"\nWhat would you like to do?",
|
|
1323
|
+
choices=[
|
|
1324
|
+
questionary.Choice("Apply these changes", value="apply"),
|
|
1325
|
+
questionary.Choice("Adjust selection", value="adjust"),
|
|
1326
|
+
questionary.Choice("Cancel", value="cancel"),
|
|
1327
|
+
],
|
|
1328
|
+
default="apply",
|
|
1329
|
+
style=self.QUESTIONARY_STYLE,
|
|
1330
|
+
).ask()
|
|
1331
|
+
|
|
1332
|
+
if action == "cancel":
|
|
1333
|
+
self.console.print("[yellow]Changes cancelled[/yellow]")
|
|
1334
|
+
Prompt.ask("\nPress Enter to continue")
|
|
1335
|
+
return
|
|
1336
|
+
if action == "adjust":
|
|
1337
|
+
# current_selection is already updated, loop will use it
|
|
1338
|
+
continue
|
|
1339
|
+
|
|
1340
|
+
# Execute changes
|
|
1341
|
+
deploy_success = 0
|
|
1342
|
+
deploy_fail = 0
|
|
1343
|
+
remove_success = 0
|
|
1344
|
+
remove_fail = 0
|
|
1345
|
+
|
|
1346
|
+
# Install new agents
|
|
1347
|
+
for agent_id in to_deploy:
|
|
1348
|
+
agent = agent_map.get(agent_id)
|
|
1349
|
+
if agent and self._deploy_single_agent(agent, show_feedback=False):
|
|
1350
|
+
deploy_success += 1
|
|
1351
|
+
self.console.print(f"[green]✓ Installed: {agent_id}[/green]")
|
|
1352
|
+
else:
|
|
1353
|
+
deploy_fail += 1
|
|
1354
|
+
self.console.print(f"[red]✗ Failed to install: {agent_id}[/red]")
|
|
1355
|
+
|
|
1356
|
+
# Remove agents
|
|
1357
|
+
for agent_id in to_remove:
|
|
1358
|
+
try:
|
|
1359
|
+
import json
|
|
1360
|
+
from pathlib import Path
|
|
1361
|
+
|
|
1362
|
+
# Remove from project, legacy, and user locations
|
|
1363
|
+
project_path = (
|
|
1364
|
+
Path.cwd() / ".claude-mpm" / "agents" / f"{agent_id}.md"
|
|
1365
|
+
)
|
|
1366
|
+
legacy_path = Path.cwd() / ".claude" / "agents" / f"{agent_id}.md"
|
|
1367
|
+
user_path = Path.home() / ".claude" / "agents" / f"{agent_id}.md"
|
|
1368
|
+
|
|
1369
|
+
removed = False
|
|
1370
|
+
for path in [project_path, legacy_path, user_path]:
|
|
1371
|
+
if path.exists():
|
|
1372
|
+
path.unlink()
|
|
1373
|
+
removed = True
|
|
1374
|
+
|
|
1375
|
+
# Also remove from virtual deployment state
|
|
1376
|
+
deployment_state_paths = [
|
|
1377
|
+
Path.cwd() / ".claude" / "agents" / ".mpm_deployment_state",
|
|
1378
|
+
Path.home() / ".claude" / "agents" / ".mpm_deployment_state",
|
|
1379
|
+
]
|
|
1380
|
+
|
|
1381
|
+
for state_path in deployment_state_paths:
|
|
1382
|
+
if state_path.exists():
|
|
1383
|
+
try:
|
|
1384
|
+
with state_path.open() as f:
|
|
1385
|
+
state = json.load(f)
|
|
1386
|
+
|
|
1387
|
+
# Remove agent from deployment state
|
|
1388
|
+
agents = state.get("last_check_results", {}).get(
|
|
1389
|
+
"agents", {}
|
|
1390
|
+
)
|
|
1391
|
+
if agent_id in agents:
|
|
1392
|
+
del agents[agent_id]
|
|
1393
|
+
removed = True
|
|
1394
|
+
|
|
1395
|
+
# Save updated state
|
|
1396
|
+
with state_path.open("w") as f:
|
|
1397
|
+
json.dump(state, f, indent=2)
|
|
1398
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
1399
|
+
# Log but don't fail - physical removal still counts
|
|
1400
|
+
self.logger.debug(
|
|
1401
|
+
f"Failed to update deployment state at {state_path}: {e}"
|
|
1402
|
+
)
|
|
1403
|
+
|
|
1404
|
+
if removed:
|
|
1405
|
+
remove_success += 1
|
|
1406
|
+
self.console.print(f"[green]✓ Removed: {agent_id}[/green]")
|
|
1407
|
+
else:
|
|
1408
|
+
remove_fail += 1
|
|
1409
|
+
self.console.print(f"[yellow]⚠ Not found: {agent_id}[/yellow]")
|
|
1410
|
+
except Exception as e:
|
|
1411
|
+
remove_fail += 1
|
|
1412
|
+
self.console.print(f"[red]✗ Failed to remove {agent_id}: {e}[/red]")
|
|
1413
|
+
|
|
1414
|
+
# Show summary
|
|
1415
|
+
self.console.print()
|
|
1416
|
+
if deploy_success > 0:
|
|
1417
|
+
self.console.print(
|
|
1418
|
+
f"[green]✓ Installed {deploy_success} agent(s)[/green]"
|
|
1419
|
+
)
|
|
1420
|
+
if deploy_fail > 0:
|
|
1421
|
+
self.console.print(
|
|
1422
|
+
f"[red]✗ Failed to install {deploy_fail} agent(s)[/red]"
|
|
1423
|
+
)
|
|
1424
|
+
if remove_success > 0:
|
|
1425
|
+
self.console.print(
|
|
1426
|
+
f"[green]✓ Removed {remove_success} agent(s)[/green]"
|
|
1427
|
+
)
|
|
1428
|
+
if remove_fail > 0:
|
|
1429
|
+
self.console.print(
|
|
1430
|
+
f"[red]✗ Failed to remove {remove_fail} agent(s)[/red]"
|
|
1431
|
+
)
|
|
1432
|
+
|
|
981
1433
|
Prompt.ask("\nPress Enter to continue")
|
|
1434
|
+
# Exit the loop after successful execution
|
|
1435
|
+
break
|
|
982
1436
|
|
|
983
1437
|
def _deploy_agents_preset(self) -> None:
|
|
984
|
-
"""
|
|
1438
|
+
"""Install agents using preset configuration."""
|
|
985
1439
|
try:
|
|
986
1440
|
from claude_mpm.services.agents.agent_preset_service import (
|
|
987
1441
|
AgentPresetService,
|
|
@@ -998,9 +1452,9 @@ class ConfigureCommand(BaseCommand):
|
|
|
998
1452
|
Prompt.ask("\nPress Enter to continue")
|
|
999
1453
|
return
|
|
1000
1454
|
|
|
1001
|
-
self.console.print("\n[bold
|
|
1455
|
+
self.console.print("\n[bold white]═══ Available Presets ═══[/bold white]\n")
|
|
1002
1456
|
for idx, preset in enumerate(presets, 1):
|
|
1003
|
-
self.console.print(f" {idx}. [
|
|
1457
|
+
self.console.print(f" {idx}. [white]{preset['name']}[/white]")
|
|
1004
1458
|
self.console.print(f" {preset['description']}")
|
|
1005
1459
|
self.console.print(f" [dim]Agents: {len(preset['agents'])}[/dim]\n")
|
|
1006
1460
|
|
|
@@ -1024,14 +1478,14 @@ class ConfigureCommand(BaseCommand):
|
|
|
1024
1478
|
Prompt.ask("\nPress Enter to continue")
|
|
1025
1479
|
return
|
|
1026
1480
|
|
|
1027
|
-
# Confirm
|
|
1481
|
+
# Confirm installation
|
|
1028
1482
|
self.console.print(
|
|
1029
1483
|
f"\n[bold]Preset '{preset_name}' includes {len(resolution['agents'])} agents[/bold]"
|
|
1030
1484
|
)
|
|
1031
|
-
if Confirm.ask("
|
|
1032
|
-
|
|
1485
|
+
if Confirm.ask("Install all agents?", default=True):
|
|
1486
|
+
installed = 0
|
|
1033
1487
|
for agent in resolution["agents"]:
|
|
1034
|
-
# Convert dict to AgentConfig-like object for
|
|
1488
|
+
# Convert dict to AgentConfig-like object for installation
|
|
1035
1489
|
agent_config = AgentConfig(
|
|
1036
1490
|
name=agent.get("agent_id", "unknown"),
|
|
1037
1491
|
description=agent.get("metadata", {}).get(
|
|
@@ -1043,10 +1497,10 @@ class ConfigureCommand(BaseCommand):
|
|
|
1043
1497
|
agent_config.full_agent_id = agent.get("agent_id", "unknown")
|
|
1044
1498
|
|
|
1045
1499
|
if self._deploy_single_agent(agent_config, show_feedback=False):
|
|
1046
|
-
|
|
1500
|
+
installed += 1
|
|
1047
1501
|
|
|
1048
1502
|
self.console.print(
|
|
1049
|
-
f"\n[green]✓
|
|
1503
|
+
f"\n[green]✓ Installed {installed}/{len(resolution['agents'])} agents[/green]"
|
|
1050
1504
|
)
|
|
1051
1505
|
|
|
1052
1506
|
Prompt.ask("\nPress Enter to continue")
|
|
@@ -1055,14 +1509,14 @@ class ConfigureCommand(BaseCommand):
|
|
|
1055
1509
|
Prompt.ask("\nPress Enter to continue")
|
|
1056
1510
|
|
|
1057
1511
|
except Exception as e:
|
|
1058
|
-
self.console.print(f"[red]Error
|
|
1059
|
-
self.logger.error(f"Preset
|
|
1512
|
+
self.console.print(f"[red]Error installing preset: {e}[/red]")
|
|
1513
|
+
self.logger.error(f"Preset installation failed: {e}", exc_info=True)
|
|
1060
1514
|
Prompt.ask("\nPress Enter to continue")
|
|
1061
1515
|
|
|
1062
1516
|
def _deploy_single_agent(
|
|
1063
1517
|
self, agent: AgentConfig, show_feedback: bool = True
|
|
1064
1518
|
) -> bool:
|
|
1065
|
-
"""
|
|
1519
|
+
"""Install a single agent to the appropriate location."""
|
|
1066
1520
|
try:
|
|
1067
1521
|
# Check if this is a remote agent with source_dict
|
|
1068
1522
|
source_dict = getattr(agent, "source_dict", None)
|
|
@@ -1090,7 +1544,9 @@ class ConfigureCommand(BaseCommand):
|
|
|
1090
1544
|
target_file = target_dir / target_name
|
|
1091
1545
|
|
|
1092
1546
|
if show_feedback:
|
|
1093
|
-
self.console.print(
|
|
1547
|
+
self.console.print(
|
|
1548
|
+
f"\n[white]Installing {full_agent_id}...[/white]"
|
|
1549
|
+
)
|
|
1094
1550
|
|
|
1095
1551
|
# Copy the agent file
|
|
1096
1552
|
import shutil
|
|
@@ -1099,38 +1555,38 @@ class ConfigureCommand(BaseCommand):
|
|
|
1099
1555
|
|
|
1100
1556
|
if show_feedback:
|
|
1101
1557
|
self.console.print(
|
|
1102
|
-
f"[green]✓ Successfully
|
|
1558
|
+
f"[green]✓ Successfully installed {full_agent_id} to {target_file}[/green]"
|
|
1103
1559
|
)
|
|
1104
1560
|
Prompt.ask("\nPress Enter to continue")
|
|
1105
1561
|
|
|
1106
1562
|
return True
|
|
1107
|
-
# Legacy local template
|
|
1563
|
+
# Legacy local template installation (not implemented here)
|
|
1108
1564
|
if show_feedback:
|
|
1109
1565
|
self.console.print(
|
|
1110
|
-
"[yellow]Local template
|
|
1566
|
+
"[yellow]Local template installation not yet implemented[/yellow]"
|
|
1111
1567
|
)
|
|
1112
1568
|
Prompt.ask("\nPress Enter to continue")
|
|
1113
1569
|
return False
|
|
1114
1570
|
|
|
1115
1571
|
except Exception as e:
|
|
1116
1572
|
if show_feedback:
|
|
1117
|
-
self.console.print(f"[red]Error
|
|
1118
|
-
self.logger.error(f"Agent
|
|
1573
|
+
self.console.print(f"[red]Error installing agent: {e}[/red]")
|
|
1574
|
+
self.logger.error(f"Agent installation failed: {e}", exc_info=True)
|
|
1119
1575
|
Prompt.ask("\nPress Enter to continue")
|
|
1120
1576
|
return False
|
|
1121
1577
|
|
|
1122
1578
|
def _remove_agents(self, agents: List[AgentConfig]) -> None:
|
|
1123
|
-
"""Remove
|
|
1124
|
-
# Filter to
|
|
1125
|
-
|
|
1579
|
+
"""Remove installed agents."""
|
|
1580
|
+
# Filter to installed agents only
|
|
1581
|
+
installed = [a for a in agents if getattr(a, "is_deployed", False)]
|
|
1126
1582
|
|
|
1127
|
-
if not
|
|
1128
|
-
self.console.print("[yellow]No agents are currently
|
|
1583
|
+
if not installed:
|
|
1584
|
+
self.console.print("[yellow]No agents are currently installed[/yellow]")
|
|
1129
1585
|
Prompt.ask("\nPress Enter to continue")
|
|
1130
1586
|
return
|
|
1131
1587
|
|
|
1132
|
-
self.console.print(f"\n[bold]
|
|
1133
|
-
for idx, agent in enumerate(
|
|
1588
|
+
self.console.print(f"\n[bold]Installed agents ({len(installed)}):[/bold]")
|
|
1589
|
+
for idx, agent in enumerate(installed, 1):
|
|
1134
1590
|
display_name = getattr(agent, "display_name", agent.name)
|
|
1135
1591
|
self.console.print(f" {idx}. {agent.name} - {display_name}")
|
|
1136
1592
|
|
|
@@ -1140,8 +1596,8 @@ class ConfigureCommand(BaseCommand):
|
|
|
1140
1596
|
|
|
1141
1597
|
try:
|
|
1142
1598
|
idx = int(selection) - 1
|
|
1143
|
-
if 0 <= idx < len(
|
|
1144
|
-
agent =
|
|
1599
|
+
if 0 <= idx < len(installed):
|
|
1600
|
+
agent = installed[idx]
|
|
1145
1601
|
full_agent_id = getattr(agent, "full_agent_id", agent.name)
|
|
1146
1602
|
|
|
1147
1603
|
# Determine possible file names (hierarchical and leaf)
|
|
@@ -1207,7 +1663,7 @@ class ConfigureCommand(BaseCommand):
|
|
|
1207
1663
|
agent = agents[idx]
|
|
1208
1664
|
|
|
1209
1665
|
self.console.clear()
|
|
1210
|
-
self.console.print("\n[bold
|
|
1666
|
+
self.console.print("\n[bold white]═══ Agent Details ═══[/bold white]\n")
|
|
1211
1667
|
|
|
1212
1668
|
# Basic info
|
|
1213
1669
|
self.console.print(f"[bold]ID:[/bold] {agent.name}")
|
|
@@ -1229,9 +1685,9 @@ class ConfigureCommand(BaseCommand):
|
|
|
1229
1685
|
self.console.print(f"[bold]Source:[/bold] {source}")
|
|
1230
1686
|
self.console.print(f"[bold]Version:[/bold] {version[:16]}...")
|
|
1231
1687
|
|
|
1232
|
-
#
|
|
1233
|
-
|
|
1234
|
-
status = "
|
|
1688
|
+
# Installation status
|
|
1689
|
+
is_installed = getattr(agent, "is_deployed", False)
|
|
1690
|
+
status = "Installed" if is_installed else "Available"
|
|
1235
1691
|
self.console.print(f"[bold]Status:[/bold] {status}")
|
|
1236
1692
|
|
|
1237
1693
|
Prompt.ask("\nPress Enter to continue")
|