claude-mpm 4.7.7__py3-none-any.whl → 4.7.8__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 CHANGED
@@ -1 +1 @@
1
- 4.7.7
1
+ 4.7.8
@@ -60,6 +60,8 @@ class SimpleAgentManager:
60
60
  import logging
61
61
 
62
62
  self.logger = logging.getLogger(__name__)
63
+ # Track pending changes for batch operations
64
+ self.deferred_changes: Dict[str, bool] = {}
63
65
 
64
66
  def _load_states(self):
65
67
  """Load agent states from file."""
@@ -85,6 +87,33 @@ class SimpleAgentManager:
85
87
  self.states[agent_name]["enabled"] = enabled
86
88
  self._save_states()
87
89
 
90
+ def set_agent_enabled_deferred(self, agent_name: str, enabled: bool) -> None:
91
+ """Queue agent state change without saving."""
92
+ self.deferred_changes[agent_name] = enabled
93
+
94
+ def commit_deferred_changes(self) -> None:
95
+ """Save all deferred changes at once."""
96
+ for agent_name, enabled in self.deferred_changes.items():
97
+ if agent_name not in self.states:
98
+ self.states[agent_name] = {}
99
+ self.states[agent_name]["enabled"] = enabled
100
+ self._save_states()
101
+ self.deferred_changes.clear()
102
+
103
+ def discard_deferred_changes(self) -> None:
104
+ """Discard all pending changes."""
105
+ self.deferred_changes.clear()
106
+
107
+ def get_pending_state(self, agent_name: str) -> bool:
108
+ """Get agent state including pending changes."""
109
+ if agent_name in self.deferred_changes:
110
+ return self.deferred_changes[agent_name]
111
+ return self.states.get(agent_name, {}).get("enabled", True)
112
+
113
+ def has_pending_changes(self) -> bool:
114
+ """Check if there are unsaved changes."""
115
+ return len(self.deferred_changes) > 0
116
+
88
117
  def discover_agents(self) -> List[AgentConfig]:
89
118
  """Discover available agents from template JSON files."""
90
119
  agents = []
@@ -296,6 +325,33 @@ class ConfigureCommand(BaseCommand):
296
325
  self._switch_scope()
297
326
  elif choice == "6":
298
327
  self._show_version_info_interactive()
328
+ elif choice == "l":
329
+ # Check for pending agent changes
330
+ if self.agent_manager and self.agent_manager.has_pending_changes():
331
+ should_save = Confirm.ask(
332
+ "[yellow]You have unsaved agent changes. Save them before launching?[/yellow]",
333
+ default=True,
334
+ )
335
+ if should_save:
336
+ self.agent_manager.commit_deferred_changes()
337
+ self.console.print("[green]✓ Agent changes saved[/green]")
338
+ else:
339
+ self.agent_manager.discard_deferred_changes()
340
+ self.console.print(
341
+ "[yellow]⚠ Agent changes discarded[/yellow]"
342
+ )
343
+
344
+ # Save all configuration
345
+ self.console.print("\n[cyan]Saving configuration...[/cyan]")
346
+ if self._save_all_configuration():
347
+ # Launch Claude MPM (this will replace the process if successful)
348
+ self._launch_claude_mpm()
349
+ # If execvp fails, we'll return here and break
350
+ break
351
+ self.console.print(
352
+ "[red]✗ Failed to save configuration. Not launching.[/red]"
353
+ )
354
+ Prompt.ask("\nPress Enter to continue")
299
355
  elif choice == "q":
300
356
  self.console.print(
301
357
  "\n[green]Configuration complete. Goodbye![/green]"
@@ -356,16 +412,17 @@ class ConfigureCommand(BaseCommand):
356
412
  ),
357
413
  ("5", "Switch Scope", f"Current: {self.current_scope}"),
358
414
  ("6", "Version Info", "Display MPM and Claude versions"),
359
- ("q", "Quit", "Exit configuration interface"),
415
+ ("l", "Save & Launch", "Save all changes and start Claude MPM"),
416
+ ("q", "Quit", "Exit without launching"),
360
417
  ]
361
418
 
362
419
  table = Table(show_header=False, box=None, padding=(0, 2))
363
- table.add_column("Key", style="cyan", width=3)
364
- table.add_column("Option", style="bold white", width=20)
365
- table.add_column("Description", style="dim")
420
+ table.add_column("Key", style="cyan bold", width=4) # Bolder shortcuts
421
+ table.add_column("Option", style="bold white", width=24) # Wider for titles
422
+ table.add_column("Description", style="white") # Better contrast
366
423
 
367
424
  for key, option, desc in menu_items:
368
- table.add_row(f"[{key}]", option, desc)
425
+ table.add_row(f"\\[{key}]", option, desc)
369
426
 
370
427
  menu_panel = Panel(
371
428
  table, title="[bold]Main Menu[/bold]", box=ROUNDED, style="green"
@@ -392,15 +449,10 @@ class ConfigureCommand(BaseCommand):
392
449
  self.console.print("\n[bold]Agent Management Options:[/bold]")
393
450
 
394
451
  # Use Text objects to properly display shortcuts with styling
395
- text_e = Text(" ")
396
- text_e.append("[e]", style="cyan bold")
397
- text_e.append(" Enable an agent")
398
- self.console.print(text_e)
399
-
400
- text_d = Text(" ")
401
- text_d.append("[d]", style="cyan bold")
402
- text_d.append(" Disable an agent")
403
- self.console.print(text_d)
452
+ text_t = Text(" ")
453
+ text_t.append("[t]", style="cyan bold")
454
+ text_t.append(" Toggle agents (enable/disable multiple)")
455
+ self.console.print(text_t)
404
456
 
405
457
  text_c = Text(" ")
406
458
  text_c.append("[c]", style="cyan bold")
@@ -428,10 +480,8 @@ class ConfigureCommand(BaseCommand):
428
480
 
429
481
  if choice == "b":
430
482
  break
431
- if choice == "e":
432
- self._enable_agent_interactive(agents)
433
- elif choice == "d":
434
- self._disable_agent_interactive(agents)
483
+ if choice == "t":
484
+ self._toggle_agents_interactive(agents)
435
485
  elif choice == "c":
436
486
  self._customize_agent_template(agents)
437
487
  elif choice == "v":
@@ -494,55 +544,127 @@ class ConfigureCommand(BaseCommand):
494
544
 
495
545
  self.console.print(table)
496
546
 
497
- def _enable_agent_interactive(self, agents: List[AgentConfig]) -> None:
498
- """Interactive agent enabling."""
499
- agent_id = Prompt.ask("Enter agent ID to enable (or 'all' for all agents)")
547
+ def _display_agents_with_pending_states(self, agents: List[AgentConfig]) -> None:
548
+ """Display agents table with pending state indicators."""
549
+ has_pending = self.agent_manager.has_pending_changes()
550
+ pending_count = len(self.agent_manager.deferred_changes) if has_pending else 0
500
551
 
501
- if agent_id.lower() == "all":
502
- if Confirm.ask("[yellow]Enable ALL agents?[/yellow]"):
503
- for agent in agents:
504
- self.agent_manager.set_agent_enabled(agent.name, True)
505
- self.console.print("[green]All agents enabled successfully![/green]")
506
- else:
507
- try:
508
- idx = int(agent_id) - 1
509
- if 0 <= idx < len(agents):
510
- agent = agents[idx]
511
- self.agent_manager.set_agent_enabled(agent.name, True)
512
- self.console.print(
513
- f"[green]Agent '{agent.name}' enabled successfully![/green]"
514
- )
515
- else:
516
- self.console.print("[red]Invalid agent ID.[/red]")
517
- except ValueError:
518
- self.console.print("[red]Invalid input. Please enter a number.[/red]")
552
+ title = f"Available Agents ({len(agents)} total)"
553
+ if has_pending:
554
+ title += f" [yellow]({pending_count} change{'s' if pending_count != 1 else ''} pending)[/yellow]"
519
555
 
520
- Prompt.ask("Press Enter to continue")
556
+ table = Table(title=title, box=ROUNDED, show_lines=True, expand=True)
557
+ table.add_column("ID", justify="right", style="cyan", width=5)
558
+ table.add_column("Name", style="bold", width=22)
559
+ table.add_column("Status", width=20)
560
+ table.add_column("Description", style="bold cyan", width=45)
521
561
 
522
- def _disable_agent_interactive(self, agents: List[AgentConfig]) -> None:
523
- """Interactive agent disabling."""
524
- agent_id = Prompt.ask("Enter agent ID to disable (or 'all' for all agents)")
562
+ for idx, agent in enumerate(agents, 1):
563
+ current_state = self.agent_manager.is_agent_enabled(agent.name)
564
+ pending_state = self.agent_manager.get_pending_state(agent.name)
525
565
 
526
- if agent_id.lower() == "all":
527
- if Confirm.ask("[yellow]Disable ALL agents?[/yellow]"):
528
- for agent in agents:
529
- self.agent_manager.set_agent_enabled(agent.name, False)
530
- self.console.print("[green]All agents disabled successfully![/green]")
531
- else:
532
- try:
533
- idx = int(agent_id) - 1
534
- if 0 <= idx < len(agents):
535
- agent = agents[idx]
536
- self.agent_manager.set_agent_enabled(agent.name, False)
537
- self.console.print(
538
- f"[green]Agent '{agent.name}' disabled successfully![/green]"
539
- )
566
+ # Show pending status with arrow
567
+ if current_state != pending_state:
568
+ if pending_state:
569
+ status = "[yellow]✗ Disabled → ✓ Enabled[/yellow]"
540
570
  else:
541
- self.console.print("[red]Invalid agent ID.[/red]")
542
- except ValueError:
543
- self.console.print("[red]Invalid input. Please enter a number.[/red]")
571
+ status = "[yellow] Enabled → ✗ Disabled[/yellow]"
572
+ else:
573
+ status = (
574
+ "[green]✓ Enabled[/green]"
575
+ if current_state
576
+ else "[dim]✗ Disabled[/dim]"
577
+ )
544
578
 
545
- Prompt.ask("Press Enter to continue")
579
+ desc_display = Text()
580
+ desc_display.append(
581
+ (
582
+ agent.description[:42] + "..."
583
+ if len(agent.description) > 42
584
+ else agent.description
585
+ ),
586
+ style="cyan",
587
+ )
588
+
589
+ table.add_row(str(idx), agent.name, status, desc_display)
590
+
591
+ self.console.print(table)
592
+
593
+ def _toggle_agents_interactive(self, agents: List[AgentConfig]) -> None:
594
+ """Interactive multi-agent enable/disable with batch save."""
595
+
596
+ # Initialize pending states from current states
597
+ for agent in agents:
598
+ current_state = self.agent_manager.is_agent_enabled(agent.name)
599
+ self.agent_manager.set_agent_enabled_deferred(agent.name, current_state)
600
+
601
+ while True:
602
+ # Display table with pending states
603
+ self._display_agents_with_pending_states(agents)
604
+
605
+ # Show menu
606
+ self.console.print("\n[bold]Toggle Agent Status:[/bold]")
607
+ text_toggle = Text(" ")
608
+ text_toggle.append("[t]", style="cyan bold")
609
+ text_toggle.append(" Enter agent IDs to toggle (e.g., '1,3,5' or '1-4')")
610
+ self.console.print(text_toggle)
611
+
612
+ text_all = Text(" ")
613
+ text_all.append("[a]", style="cyan bold")
614
+ text_all.append(" Enable all agents")
615
+ self.console.print(text_all)
616
+
617
+ text_none = Text(" ")
618
+ text_none.append("[n]", style="cyan bold")
619
+ text_none.append(" Disable all agents")
620
+ self.console.print(text_none)
621
+
622
+ text_save = Text(" ")
623
+ text_save.append("[s]", style="green bold")
624
+ text_save.append(" Save changes and return")
625
+ self.console.print(text_save)
626
+
627
+ text_cancel = Text(" ")
628
+ text_cancel.append("[c]", style="yellow bold")
629
+ text_cancel.append(" Cancel (discard changes)")
630
+ self.console.print(text_cancel)
631
+
632
+ choice = (
633
+ Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="s")
634
+ .strip()
635
+ .lower()
636
+ )
637
+
638
+ if choice == "s":
639
+ if self.agent_manager.has_pending_changes():
640
+ self.agent_manager.commit_deferred_changes()
641
+ self.console.print("[green]✓ Changes saved successfully![/green]")
642
+ else:
643
+ self.console.print("[yellow]No changes to save.[/yellow]")
644
+ Prompt.ask("Press Enter to continue")
645
+ break
646
+ if choice == "c":
647
+ self.agent_manager.discard_deferred_changes()
648
+ self.console.print("[yellow]Changes discarded.[/yellow]")
649
+ Prompt.ask("Press Enter to continue")
650
+ break
651
+ if choice == "a":
652
+ for agent in agents:
653
+ self.agent_manager.set_agent_enabled_deferred(agent.name, True)
654
+ elif choice == "n":
655
+ for agent in agents:
656
+ self.agent_manager.set_agent_enabled_deferred(agent.name, False)
657
+ elif choice == "t" or choice.replace(",", "").replace("-", "").isdigit():
658
+ selected_ids = self._parse_id_selection(
659
+ choice if choice != "t" else Prompt.ask("Enter IDs"), len(agents)
660
+ )
661
+ for idx in selected_ids:
662
+ if 1 <= idx <= len(agents):
663
+ agent = agents[idx - 1]
664
+ current = self.agent_manager.get_pending_state(agent.name)
665
+ self.agent_manager.set_agent_enabled_deferred(
666
+ agent.name, not current
667
+ )
546
668
 
547
669
  def _customize_agent_template(self, agents: List[AgentConfig]) -> None:
548
670
  """Customize agent JSON template."""
@@ -1707,6 +1829,68 @@ class ConfigureCommand(BaseCommand):
1707
1829
  Prompt.ask("Press Enter to continue")
1708
1830
  return False
1709
1831
 
1832
+ def _save_all_configuration(self) -> bool:
1833
+ """Save all configuration changes across all contexts.
1834
+
1835
+ Returns:
1836
+ bool: True if all saves successful, False otherwise
1837
+ """
1838
+ try:
1839
+ # 1. Save any pending agent changes
1840
+ if self.agent_manager and self.agent_manager.has_pending_changes():
1841
+ self.agent_manager.commit_deferred_changes()
1842
+ self.console.print("[green]✓ Agent changes saved[/green]")
1843
+
1844
+ # 2. Save configuration file
1845
+ config = Config()
1846
+
1847
+ # Determine config file path based on scope
1848
+ if self.current_scope == "project":
1849
+ config_file = self.project_dir / ".claude-mpm" / "configuration.yaml"
1850
+ else:
1851
+ config_file = Path.home() / ".claude-mpm" / "configuration.yaml"
1852
+
1853
+ config_file.parent.mkdir(parents=True, exist_ok=True)
1854
+
1855
+ # Save with suppressed logging to avoid duplicate messages
1856
+ import logging
1857
+
1858
+ root_logger = logging.getLogger("claude_mpm")
1859
+ original_level = root_logger.level
1860
+ root_logger.setLevel(logging.WARNING)
1861
+
1862
+ try:
1863
+ config.save(config_file, format="yaml")
1864
+ finally:
1865
+ root_logger.setLevel(original_level)
1866
+
1867
+ self.console.print(f"[green]✓ Configuration saved to {config_file}[/green]")
1868
+ return True
1869
+
1870
+ except Exception as e:
1871
+ self.console.print(f"[red]✗ Error saving configuration: {e}[/red]")
1872
+ import traceback
1873
+
1874
+ traceback.print_exc()
1875
+ return False
1876
+
1877
+ def _launch_claude_mpm(self) -> None:
1878
+ """Launch Claude MPM run command, replacing current process."""
1879
+ self.console.print("\n[bold cyan]═══ Launching Claude MPM ═══[/bold cyan]\n")
1880
+
1881
+ try:
1882
+ # Use execvp to replace the current process with claude-mpm run
1883
+ # This ensures a clean transition from configurator to Claude MPM
1884
+ os.execvp("claude-mpm", ["claude-mpm", "run"])
1885
+ except Exception as e:
1886
+ self.console.print(
1887
+ f"[yellow]⚠ Could not launch Claude MPM automatically: {e}[/yellow]"
1888
+ )
1889
+ self.console.print(
1890
+ "[cyan]→ Please run 'claude-mpm run' manually to start.[/cyan]"
1891
+ )
1892
+ Prompt.ask("\nPress Enter to exit")
1893
+
1710
1894
  def _switch_scope(self) -> None:
1711
1895
  """Switch between project and user scope."""
1712
1896
  self.current_scope = "user" if self.current_scope == "project" else "project"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 4.7.7
3
+ Version: 4.7.8
4
4
  Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
5
5
  Author-email: Bob Matsuoka <bob@matsuoka.com>
6
6
  Maintainer: Claude MPM Team
@@ -1,5 +1,5 @@
1
1
  claude_mpm/BUILD_NUMBER,sha256=9JfxhnDtr-8l3kCP2U5TVXSErptHoga8m7XA8zqgGOc,4
2
- claude_mpm/VERSION,sha256=HB85LgMagRltrQjQ-DOxsxPXgZAlAkVxXen4zNqgCSE,6
2
+ claude_mpm/VERSION,sha256=XWuLX-eNJrJGfyBd1GUrzseop7w6skHL0X8KHS__eFE,6
3
3
  claude_mpm/__init__.py,sha256=UCw6j9e_tZQ3kJtTqmdfNv7MHyw9nD1jkj80WurwM2g,2064
4
4
  claude_mpm/__main__.py,sha256=Ro5UBWBoQaSAIoSqWAr7zkbLyvi4sSy28WShqAhKJG0,723
5
5
  claude_mpm/constants.py,sha256=cChN3myrAcF3jC-6DvHnBFTEnwlDk-TAsIXPvUZr_yw,5953
@@ -79,7 +79,7 @@ claude_mpm/cli/commands/analyze_code.py,sha256=yWZpG0aL4XlhcthtvbUqnFSlnvvseWO3V
79
79
  claude_mpm/cli/commands/cleanup.py,sha256=TukZoPVfAFSF4ICfKCQUibczDE73EJP8nbEbfuT8GhE,19768
80
80
  claude_mpm/cli/commands/cleanup_orphaned_agents.py,sha256=JR8crvgrz7Sa6d-SI-gKywok5S9rwc_DzDVk_h85sVs,4467
81
81
  claude_mpm/cli/commands/config.py,sha256=Yfi8WO-10_MYz2QipFw-yEzVvHKNQ6iSQXeyW5J85Cg,18559
82
- claude_mpm/cli/commands/configure.py,sha256=TgWcPzCzIYKpMgcLMaDz4VbXmobeMoDW0oE5tWqyo0E,84906
82
+ claude_mpm/cli/commands/configure.py,sha256=rj8IEAyI-TgRAnTxtOJ8AL2xGhP33gOte8z8WzKhsG4,92942
83
83
  claude_mpm/cli/commands/dashboard.py,sha256=4jPTmTl97DRNNJlYREWeE1iDdkct1uL-vv24MZn9fj4,11403
84
84
  claude_mpm/cli/commands/debug.py,sha256=YCfJ3aYf6hOCvLW_grdfINdEqI4RXVS28VJ7tkZBFS8,47115
85
85
  claude_mpm/cli/commands/doctor.py,sha256=nNKLZG3Qv_UsHNgrmetrWKgS7Pe2Jn5vq5aXyl60wKQ,7310
@@ -786,9 +786,9 @@ claude_mpm/utils/subprocess_utils.py,sha256=D0izRT8anjiUb_JG72zlJR_JAw1cDkb7kalN
786
786
  claude_mpm/validation/__init__.py,sha256=YZhwE3mhit-lslvRLuwfX82xJ_k4haZeKmh4IWaVwtk,156
787
787
  claude_mpm/validation/agent_validator.py,sha256=GprtAvu80VyMXcKGsK_VhYiXWA6BjKHv7O6HKx0AB9w,20917
788
788
  claude_mpm/validation/frontmatter_validator.py,sha256=YpJlYNNYcV8u6hIOi3_jaRsDnzhbcQpjCBE6eyBKaFY,7076
789
- claude_mpm-4.7.7.dist-info/licenses/LICENSE,sha256=lpaivOlPuBZW1ds05uQLJJswy8Rp_HMNieJEbFlqvLk,1072
790
- claude_mpm-4.7.7.dist-info/METADATA,sha256=xVqJzb3mt7CPS-7pxe3uOhh7FeqkBkwociB-EruqX0s,17517
791
- claude_mpm-4.7.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
792
- claude_mpm-4.7.7.dist-info/entry_points.txt,sha256=Vlw3GNi-OtTpKSrez04iNrPmxNxYDpIWxmJCxiZ5Tx8,526
793
- claude_mpm-4.7.7.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
794
- claude_mpm-4.7.7.dist-info/RECORD,,
789
+ claude_mpm-4.7.8.dist-info/licenses/LICENSE,sha256=lpaivOlPuBZW1ds05uQLJJswy8Rp_HMNieJEbFlqvLk,1072
790
+ claude_mpm-4.7.8.dist-info/METADATA,sha256=0zNQ_GqGZeqiE8cxEiJK7LQNZ6gfeuJyvsVrunOlrEE,17517
791
+ claude_mpm-4.7.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
792
+ claude_mpm-4.7.8.dist-info/entry_points.txt,sha256=Vlw3GNi-OtTpKSrez04iNrPmxNxYDpIWxmJCxiZ5Tx8,526
793
+ claude_mpm-4.7.8.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
794
+ claude_mpm-4.7.8.dist-info/RECORD,,