claude-mpm 5.0.2__py3-none-any.whl → 5.4.3__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 (184) 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 +1218 -905
  4. claude_mpm/agents/agent_loader.py +10 -17
  5. claude_mpm/agents/base_agent_loader.py +10 -35
  6. claude_mpm/agents/frontmatter_validator.py +68 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +431 -45
  8. claude_mpm/cli/__init__.py +0 -1
  9. claude_mpm/cli/commands/__init__.py +2 -0
  10. claude_mpm/cli/commands/agent_state_manager.py +67 -23
  11. claude_mpm/cli/commands/agents.py +446 -25
  12. claude_mpm/cli/commands/auto_configure.py +535 -233
  13. claude_mpm/cli/commands/configure.py +1500 -147
  14. claude_mpm/cli/commands/configure_agent_display.py +13 -6
  15. claude_mpm/cli/commands/mpm_init/core.py +158 -1
  16. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  17. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  18. claude_mpm/cli/commands/postmortem.py +401 -0
  19. claude_mpm/cli/commands/run.py +1 -39
  20. claude_mpm/cli/commands/skills.py +322 -19
  21. claude_mpm/cli/commands/summarize.py +413 -0
  22. claude_mpm/cli/executor.py +8 -0
  23. claude_mpm/cli/interactive/agent_wizard.py +302 -195
  24. claude_mpm/cli/parsers/agents_parser.py +137 -0
  25. claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
  26. claude_mpm/cli/parsers/base_parser.py +9 -0
  27. claude_mpm/cli/parsers/skills_parser.py +7 -0
  28. claude_mpm/cli/startup.py +133 -85
  29. claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
  30. claude_mpm/commands/mpm-agents-list.md +2 -2
  31. claude_mpm/commands/mpm-config-view.md +2 -2
  32. claude_mpm/commands/mpm-help.md +3 -0
  33. claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
  34. claude_mpm/commands/mpm-postmortem.md +123 -0
  35. claude_mpm/commands/mpm-session-resume.md +2 -2
  36. claude_mpm/commands/mpm-ticket-view.md +2 -2
  37. claude_mpm/config/agent_presets.py +312 -82
  38. claude_mpm/config/agent_sources.py +27 -0
  39. claude_mpm/config/skill_presets.py +392 -0
  40. claude_mpm/constants.py +1 -0
  41. claude_mpm/core/claude_runner.py +2 -25
  42. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  43. claude_mpm/core/framework/loaders/file_loader.py +54 -101
  44. claude_mpm/core/interactive_session.py +19 -5
  45. claude_mpm/core/oneshot_session.py +16 -4
  46. claude_mpm/core/output_style_manager.py +173 -43
  47. claude_mpm/core/protocols/__init__.py +23 -0
  48. claude_mpm/core/protocols/runner_protocol.py +103 -0
  49. claude_mpm/core/protocols/session_protocol.py +131 -0
  50. claude_mpm/core/shared/singleton_manager.py +11 -4
  51. claude_mpm/core/socketio_pool.py +3 -3
  52. claude_mpm/core/system_context.py +38 -0
  53. claude_mpm/core/unified_agent_registry.py +134 -16
  54. claude_mpm/core/unified_config.py +22 -0
  55. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  56. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
  57. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  58. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  59. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  60. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  61. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  62. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  63. claude_mpm/hooks/claude_hooks/event_handlers.py +35 -2
  64. claude_mpm/hooks/claude_hooks/hook_handler.py +4 -0
  65. claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
  66. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  67. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  68. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  69. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  70. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  71. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  72. claude_mpm/models/agent_definition.py +7 -0
  73. claude_mpm/scripts/launch_monitor.py +93 -13
  74. claude_mpm/services/agents/agent_recommendation_service.py +279 -0
  75. claude_mpm/services/agents/cache_git_manager.py +621 -0
  76. claude_mpm/services/agents/deployment/agent_template_builder.py +3 -2
  77. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
  78. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +518 -55
  79. claude_mpm/services/agents/git_source_manager.py +20 -0
  80. claude_mpm/services/agents/sources/git_source_sync_service.py +45 -6
  81. claude_mpm/services/agents/toolchain_detector.py +6 -5
  82. claude_mpm/services/analysis/__init__.py +35 -0
  83. claude_mpm/services/analysis/clone_detector.py +1030 -0
  84. claude_mpm/services/analysis/postmortem_reporter.py +474 -0
  85. claude_mpm/services/analysis/postmortem_service.py +765 -0
  86. claude_mpm/services/command_deployment_service.py +106 -5
  87. claude_mpm/services/core/base.py +7 -2
  88. claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
  89. claude_mpm/services/event_bus/config.py +3 -1
  90. claude_mpm/services/git/git_operations_service.py +8 -8
  91. claude_mpm/services/mcp_config_manager.py +75 -145
  92. claude_mpm/services/mcp_service_verifier.py +6 -3
  93. claude_mpm/services/monitor/daemon.py +37 -10
  94. claude_mpm/services/monitor/daemon_manager.py +134 -21
  95. claude_mpm/services/monitor/server.py +225 -19
  96. claude_mpm/services/project/project_organizer.py +4 -0
  97. claude_mpm/services/runner_configuration_service.py +16 -3
  98. claude_mpm/services/session_management_service.py +16 -4
  99. claude_mpm/services/socketio/event_normalizer.py +15 -1
  100. claude_mpm/services/socketio/server/core.py +160 -21
  101. claude_mpm/services/version_control/git_operations.py +103 -0
  102. claude_mpm/utils/agent_filters.py +261 -0
  103. claude_mpm/utils/gitignore.py +3 -0
  104. claude_mpm/utils/migration.py +372 -0
  105. claude_mpm/utils/progress.py +5 -1
  106. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/METADATA +69 -84
  107. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/RECORD +112 -153
  108. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/entry_points.txt +0 -2
  109. claude_mpm/dashboard/analysis_runner.py +0 -455
  110. claude_mpm/dashboard/index.html +0 -13
  111. claude_mpm/dashboard/open_dashboard.py +0 -66
  112. claude_mpm/dashboard/static/css/activity.css +0 -1958
  113. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  114. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  115. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  116. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  117. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  118. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  119. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  120. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  121. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  122. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  123. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  124. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  125. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  126. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  127. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  128. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  129. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  130. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  131. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  132. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  133. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  134. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  135. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  136. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  137. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  138. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  139. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  140. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  141. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  142. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  143. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  144. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  145. claude_mpm/dashboard/templates/code_simple.html +0 -153
  146. claude_mpm/dashboard/templates/index.html +0 -606
  147. claude_mpm/dashboard/test_dashboard.html +0 -372
  148. claude_mpm/scripts/mcp_server.py +0 -75
  149. claude_mpm/scripts/mcp_wrapper.py +0 -39
  150. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  151. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  152. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  153. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  154. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  155. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  156. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  157. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  158. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  159. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  160. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -971
  161. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  162. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  163. claude_mpm/services/mcp_gateway/main.py +0 -589
  164. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  165. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  166. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  167. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  168. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  169. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  170. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  171. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  172. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  173. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  174. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  175. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  176. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  177. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  178. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  179. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  180. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  181. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  182. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/WHEEL +0 -0
  183. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/licenses/LICENSE +0 -0
  184. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/top_level.txt +0 -0
@@ -59,12 +59,15 @@ class AgentDisplay:
59
59
  Shows:
60
60
  - Agent ID (for selection)
61
61
  - Name and description
62
- - Enable/disable status
62
+ - Installed/Available status
63
63
  - Model or tools information
64
64
 
65
65
  Args:
66
66
  agents: List of agent configurations to display
67
67
  """
68
+
69
+ from ...utils.agent_filters import get_deployed_agent_ids
70
+
68
71
  table = Table(
69
72
  title=f"Available Agents ({len(agents)} total)",
70
73
  box=ROUNDED,
@@ -77,12 +80,16 @@ class AgentDisplay:
77
80
  table.add_column("Description", style="bold", width=45)
78
81
  table.add_column("Model/Tools", style="dim", width=20)
79
82
 
83
+ # Get deployed agent IDs
84
+ deployed_ids = get_deployed_agent_ids()
85
+
80
86
  for idx, agent in enumerate(agents, 1):
81
- # Check if agent is enabled
82
- is_enabled = self.agent_manager.is_agent_enabled(agent.name)
83
- status = (
84
- "[green]✓ Enabled[/green]" if is_enabled else "[red]✗ Disabled[/red]"
85
- )
87
+ # Check if agent is deployed to .claude/agents/
88
+ agent_leaf_name = agent.name.split("/")[-1]
89
+ is_deployed = agent_leaf_name in deployed_ids
90
+
91
+ # Show "Installed" for deployed agents, "Available" otherwise
92
+ status = "[green]Installed[/green]" if is_deployed else "Available"
86
93
 
87
94
  # Format tools/dependencies - show first 2 tools
88
95
  tools_display = ""
@@ -24,6 +24,7 @@ from claude_mpm.utils.display_helper import DisplayHelper
24
24
 
25
25
  # Import from sibling modules in the mpm_init package
26
26
  from . import display, git_activity, modes, prompts
27
+ from .knowledge_extractor import ProjectKnowledgeExtractor
27
28
 
28
29
  logger = get_logger(__name__)
29
30
 
@@ -34,6 +35,7 @@ class MPMInitCommand:
34
35
  def __init__(self, project_path: Optional[Path] = None):
35
36
  """Initialize the MPM-Init command."""
36
37
  self.project_path = project_path or Path.cwd()
38
+ self.claude_mpm_dir = self.project_path / ".claude-mpm"
37
39
  self.claude_mpm_script = self._find_claude_mpm_script()
38
40
  self.console = Console()
39
41
 
@@ -452,6 +454,10 @@ class MPMInitCommand:
452
454
  self.project_path, project_type, framework, ast_analysis
453
455
  )
454
456
 
457
+ def _is_initialized(self) -> bool:
458
+ """Check if project is already initialized with .claude-mpm directory."""
459
+ return self.claude_mpm_dir.exists()
460
+
455
461
  def _build_update_prompt(
456
462
  self,
457
463
  project_type: Optional[str],
@@ -459,10 +465,65 @@ class MPMInitCommand:
459
465
  ast_analysis: bool,
460
466
  preserve_custom: bool,
461
467
  ) -> str:
462
- """Build prompt for update mode."""
468
+ """Build prompt for update mode with optional knowledge extraction."""
463
469
  # Get existing content analysis
464
470
  doc_analysis = self.doc_manager.analyze_existing_content()
465
471
 
472
+ # Check if project is initialized (.claude-mpm exists)
473
+ # If so, use enhanced update mode with knowledge extraction
474
+ if self._is_initialized():
475
+ self.console.print(
476
+ "[cyan]✓ Detected initialized project - activating enhanced update mode[/cyan]"
477
+ )
478
+
479
+ # Extract knowledge from all sources
480
+ extractor = ProjectKnowledgeExtractor(self.project_path)
481
+
482
+ self.console.print("[cyan]✓ Analyzing git history (last 90 days)...[/cyan]")
483
+ git_insights = extractor.extract_from_git(days=90)
484
+
485
+ if git_insights.get("available"):
486
+ arch_count = len(git_insights.get("architectural_decisions", []))
487
+ workflow_count = len(git_insights.get("workflow_patterns", []))
488
+ self.console.print(f" - Found {arch_count} architectural decisions")
489
+ self.console.print(f" - Detected {workflow_count} workflow patterns")
490
+
491
+ self.console.print("[cyan]✓ Analyzing session logs...[/cyan]")
492
+ log_insights = extractor.extract_from_logs()
493
+
494
+ if log_insights.get("available"):
495
+ learning_count = len(log_insights.get("learnings", []))
496
+ self.console.print(f" - Found {learning_count} learning entries")
497
+
498
+ self.console.print("[cyan]✓ Analyzing memory files...[/cyan]")
499
+ memory_insights = extractor.extract_from_memory()
500
+
501
+ if memory_insights.get("available"):
502
+ total_insights = (
503
+ len(memory_insights.get("architectural_knowledge", []))
504
+ + len(memory_insights.get("implementation_guidelines", []))
505
+ + len(memory_insights.get("common_mistakes", []))
506
+ + len(memory_insights.get("technical_context", []))
507
+ )
508
+ self.console.print(f" - Found {total_insights} accumulated insights")
509
+
510
+ self.console.print(
511
+ "[green]✓ Knowledge extraction complete - building enhanced prompt[/green]\n"
512
+ )
513
+
514
+ # Build enhanced prompt with extracted knowledge
515
+ return prompts.build_enhanced_update_prompt(
516
+ self.project_path,
517
+ doc_analysis,
518
+ git_insights,
519
+ log_insights,
520
+ memory_insights,
521
+ project_type,
522
+ framework,
523
+ ast_analysis,
524
+ preserve_custom,
525
+ )
526
+ # Standard update mode (no .claude-mpm directory)
466
527
  return prompts.build_update_prompt(
467
528
  self.project_path,
468
529
  doc_analysis,
@@ -548,6 +609,11 @@ class MPMInitCommand:
548
609
  self.archive_manager,
549
610
  )
550
611
 
612
+ # Optimize CLAUDE.md with prompt-engineer if it exists
613
+ claude_md = self.project_path / "CLAUDE.md"
614
+ if claude_md.exists():
615
+ self._optimize_claude_md_with_prompt_engineer()
616
+
551
617
  def _display_results(self, result: Dict, verbose: bool):
552
618
  """Display initialization results."""
553
619
  display.display_results(self.display, self.console, result, verbose)
@@ -569,5 +635,96 @@ class MPMInitCommand:
569
635
  """Build structured Research agent delegation prompt from git analysis."""
570
636
  return prompts.build_research_context_prompt(git_analysis, days)
571
637
 
638
+ def _optimize_claude_md_with_prompt_engineer(self) -> None:
639
+ """Optimize CLAUDE.md with prompt-engineer for conciseness and clarity."""
640
+ claude_md_path = self.project_path / "CLAUDE.md"
641
+ if not claude_md_path.exists():
642
+ return
643
+
644
+ try:
645
+ # Read current content
646
+ original_content = claude_md_path.read_text()
647
+ original_tokens = self._estimate_tokens(original_content)
648
+
649
+ # Create backup
650
+ backup_path = self.archive_manager.auto_archive_before_update(
651
+ claude_md_path, update_reason="Before prompt-engineer optimization"
652
+ )
653
+
654
+ if not backup_path:
655
+ logger.warning("Could not create backup before optimization")
656
+ self.console.print(
657
+ "[yellow]⚠️ Skipping optimization - backup failed[/yellow]"
658
+ )
659
+ return
660
+
661
+ self.console.print(
662
+ "\n[cyan]✓ Optimizing CLAUDE.md with prompt-engineer...[/cyan]"
663
+ )
664
+ self.console.print(f" - Original: {original_tokens:,} tokens (estimated)")
665
+ self.console.print(f" - Backup created: {backup_path}")
666
+
667
+ # Build optimization prompt
668
+ prompt = prompts.build_prompt_engineer_optimization_prompt(
669
+ original_content, original_tokens
670
+ )
671
+
672
+ # Run optimization through subprocess
673
+ with Progress(
674
+ SpinnerColumn(),
675
+ TextColumn("[progress.description]{task.description}"),
676
+ console=self.console,
677
+ ) as progress:
678
+ task = progress.add_task(
679
+ "[cyan]Running prompt-engineer optimization...", total=None
680
+ )
681
+
682
+ result = self._run_initialization(
683
+ prompt, verbose=False, update_mode=True
684
+ )
685
+
686
+ progress.update(task, description="[green]✓ Optimization complete")
687
+
688
+ # Check if optimization succeeded
689
+ if result.get("status") == OperationResult.SUCCESS:
690
+ # Read optimized content
691
+ optimized_content = claude_md_path.read_text()
692
+ optimized_tokens = self._estimate_tokens(optimized_content)
693
+
694
+ # Calculate reduction
695
+ token_reduction = original_tokens - optimized_tokens
696
+ reduction_percent = (
697
+ (token_reduction / original_tokens * 100)
698
+ if original_tokens > 0
699
+ else 0
700
+ )
701
+
702
+ self.console.print(
703
+ f" - Optimized: {optimized_tokens:,} tokens ({reduction_percent:.1f}% reduction)"
704
+ )
705
+ self.console.print("[green]✓ CLAUDE.md optimization complete[/green]\n")
706
+ # Restore from backup on failure
707
+ elif backup_path and backup_path.exists():
708
+ import shutil
709
+
710
+ shutil.copy2(backup_path, claude_md_path)
711
+ self.console.print(
712
+ "[yellow]⚠️ Optimization failed - restored from backup[/yellow]\n"
713
+ )
714
+
715
+ except Exception as e:
716
+ logger.error(f"Failed to optimize CLAUDE.md: {e}")
717
+ self.console.print(
718
+ f"[yellow]⚠️ Could not optimize CLAUDE.md: {e}[/yellow]\n"
719
+ )
720
+
721
+ def _estimate_tokens(self, text: str) -> int:
722
+ """Estimate token count for text (rough approximation).
723
+
724
+ Uses a simple heuristic: ~4 characters per token for English text.
725
+ This is a rough estimate but sufficient for displaying progress.
726
+ """
727
+ return len(text) // 4
728
+
572
729
 
573
730
  __all__ = ["MPMInitCommand"]