claude-mpm 5.4.74__py3-none-any.whl → 5.4.75__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.
@@ -656,25 +656,22 @@ class ConfigureCommand(BaseCommand):
656
656
  self.behavior_manager.manage_behaviors()
657
657
 
658
658
  def _manage_skills(self) -> None:
659
- """Skills management interface with table display."""
659
+ """Skills management interface with questionary checkbox selection."""
660
660
  from ...cli.interactive.skills_wizard import SkillsWizard
661
- from ...skills.registry import get_registry
662
661
  from ...skills.skill_manager import get_manager
663
662
 
664
663
  wizard = SkillsWizard()
665
664
  manager = get_manager()
666
- registry = get_registry()
667
665
 
668
666
  while True:
669
667
  self.console.clear()
670
668
  self._display_header()
671
669
 
672
- # Display skills table
673
- self._display_skills_table(registry)
670
+ self.console.print("\n[bold]Skills Management[/bold]")
674
671
 
675
672
  # Show action options
676
673
  self.console.print("\n[bold]Actions:[/bold]")
677
- self.console.print(" [1] Toggle skill installation")
674
+ self.console.print(" [1] Install/Uninstall skills")
678
675
  self.console.print(" [2] Configure skills for agents")
679
676
  self.console.print(" [3] View current skill mappings")
680
677
  self.console.print(" [4] Auto-link skills to agents")
@@ -684,57 +681,8 @@ class ConfigureCommand(BaseCommand):
684
681
  choice = Prompt.ask("[bold blue]Select an option[/bold blue]", default="b")
685
682
 
686
683
  if choice == "1":
687
- # Toggle skill installation
688
- self.console.clear()
689
- self._display_header()
690
- self._display_skills_table(registry)
691
-
692
- skill_num = Prompt.ask(
693
- "\n[bold blue]Enter skill number to toggle (or 'b' to go back)[/bold blue]",
694
- default="b",
695
- )
696
-
697
- if skill_num == "b":
698
- continue
699
-
700
- try:
701
- skill_idx = int(skill_num) - 1
702
- all_skills = self._get_all_skills_sorted(registry)
703
-
704
- if 0 <= skill_idx < len(all_skills):
705
- skill = all_skills[skill_idx]
706
- deployed_ids = self._get_deployed_skill_ids()
707
-
708
- if skill.skill_id in deployed_ids:
709
- # Uninstall
710
- confirm = Confirm.ask(
711
- f"\n[yellow]Uninstall skill '{skill.name}'?[/yellow]",
712
- default=False,
713
- )
714
- if confirm:
715
- self._uninstall_skill(skill)
716
- self.console.print(
717
- f"\n[green]✓ Skill '{skill.name}' uninstalled[/green]"
718
- )
719
- else:
720
- # Install
721
- confirm = Confirm.ask(
722
- f"\n[cyan]Install skill '{skill.name}'?[/cyan]",
723
- default=True,
724
- )
725
- if confirm:
726
- self._install_skill(skill)
727
- self.console.print(
728
- f"\n[green]✓ Skill '{skill.name}' installed[/green]"
729
- )
730
- else:
731
- self.console.print("[red]Invalid skill number[/red]")
732
- except ValueError:
733
- self.console.print(
734
- "[red]Invalid input. Please enter a number.[/red]"
735
- )
736
-
737
- Prompt.ask("\nPress Enter to continue")
684
+ # Install/Uninstall skills with category-based selection
685
+ self._manage_skill_installation()
738
686
 
739
687
  elif choice == "2":
740
688
  # Configure skills interactively
@@ -857,72 +805,304 @@ class ConfigureCommand(BaseCommand):
857
805
  self.console.print("[red]Invalid choice. Please try again.[/red]")
858
806
  Prompt.ask("\nPress Enter to continue")
859
807
 
860
- def _display_skills_table(self, registry) -> None:
861
- """Display skills in a table format like agents."""
862
- from rich import box
863
- from rich.table import Table
808
+ def _manage_skill_installation(self) -> None:
809
+ """Manage skill installation with category-based questionary checkbox selection."""
810
+ import questionary
864
811
 
865
- # Get all skills and deployed skill IDs
866
- all_skills = self._get_all_skills_sorted(registry)
867
- deployed_ids = self._get_deployed_skill_ids()
812
+ # Get all skills
813
+ all_skills = self._get_all_skills_from_git()
814
+ if not all_skills:
815
+ self.console.print(
816
+ "[yellow]No skills available. Try syncing skills first.[/yellow]"
817
+ )
818
+ Prompt.ask("\nPress Enter to continue")
819
+ return
868
820
 
869
- # Create table with same styling as agents table
870
- table = Table(show_header=True, header_style="bold cyan", box=box.ROUNDED)
871
- table.add_column("#", style="bright_black", width=6)
872
- table.add_column("Skill ID", style="bright_black", overflow="ellipsis")
873
- table.add_column("Name", style="bright_cyan", overflow="ellipsis")
874
- table.add_column("Source", style="bright_yellow")
875
- table.add_column("Status", style="bright_black")
876
-
877
- # Populate table
878
- for i, skill in enumerate(all_skills, 1):
879
- # Determine status
880
- if skill.skill_id in deployed_ids:
881
- status = "[green]Installed[/green]"
882
- else:
883
- status = "Available"
821
+ # Get deployed skills
822
+ deployed = self._get_deployed_skill_ids()
823
+
824
+ # Group by category
825
+ grouped = {}
826
+ for skill in all_skills:
827
+ # Try to get category from tags or use toolchain
828
+ category = None
829
+ tags = skill.get("tags", [])
830
+
831
+ # Look for category tag
832
+ for tag in tags:
833
+ if tag in [
834
+ "universal",
835
+ "python",
836
+ "typescript",
837
+ "javascript",
838
+ "go",
839
+ "rust",
840
+ ]:
841
+ category = tag
842
+ break
884
843
 
885
- # Determine source label
886
- if skill.source == "bundled":
887
- source = "MPM Skills"
888
- elif skill.source == "user":
889
- source = "User Skills"
890
- elif skill.source == "project":
891
- source = "Project Skills"
892
- else:
893
- source = skill.source.title()
844
+ # Fallback to toolchain or universal
845
+ if not category:
846
+ category = skill.get("toolchain", "universal")
847
+
848
+ if category not in grouped:
849
+ grouped[category] = []
850
+ grouped[category].append(skill)
851
+
852
+ # Category icons
853
+ icons = {
854
+ "universal": "🌐",
855
+ "python": "🐍",
856
+ "typescript": "📘",
857
+ "javascript": "📒",
858
+ "go": "🔷",
859
+ "rust": "⚙️",
860
+ }
894
861
 
895
- # Get display name (fallback to skill_id with formatting)
896
- name = skill.name or skill.skill_id.replace("-", " ").title()
862
+ # Sort categories: universal first, then alphabetically
863
+ categories = sorted(grouped.keys(), key=lambda x: (x != "universal", x))
897
864
 
898
- table.add_row(str(i), skill.skill_id, name, source, status)
865
+ while True:
866
+ # Show category selection first
867
+ self.console.clear()
868
+ self._display_header()
869
+ self.console.print("\n[bold cyan]Skills Management[/bold cyan]")
870
+ self.console.print(
871
+ f"[dim]{len(all_skills)} skills available, {len(deployed)} installed[/dim]\n"
872
+ )
899
873
 
900
- self.console.print(table)
874
+ cat_choices = [
875
+ Choice(
876
+ title=f"{icons.get(cat, '📦')} {cat.title()} ({len(grouped[cat])} skills)",
877
+ value=cat,
878
+ )
879
+ for cat in categories
880
+ ]
881
+ cat_choices.append(Choice(title="← Back to main menu", value="back"))
901
882
 
902
- # Show summary
903
- installed_count = len([s for s in all_skills if s.skill_id in deployed_ids])
904
- self.console.print(
905
- f"\nShowing {len(all_skills)} skills ({installed_count} installed)"
906
- )
883
+ selected_cat = questionary.select(
884
+ "Select a category:", choices=cat_choices, style=self.QUESTIONARY_STYLE
885
+ ).ask()
886
+
887
+ if selected_cat is None or selected_cat == "back":
888
+ return
907
889
 
908
- def _get_all_skills_sorted(self, registry):
909
- """Get all skills from registry, sorted by source and name."""
910
- # Get skills from all sources
911
- bundled = registry.list_skills(source="bundled")
912
- user = registry.list_skills(source="user")
913
- project = registry.list_skills(source="project")
890
+ # Show skills in category with checkbox selection
891
+ category_skills = grouped[selected_cat]
914
892
 
915
- # Combine and sort: bundled first, then user, then project
916
- # Within each group, sort by name
917
- all_skills = []
918
- all_skills.extend(sorted(bundled, key=lambda s: s.name.lower()))
919
- all_skills.extend(sorted(user, key=lambda s: s.name.lower()))
920
- all_skills.extend(sorted(project, key=lambda s: s.name.lower()))
893
+ # Build choices with current installation status
894
+ skill_choices = []
895
+ for skill in sorted(category_skills, key=lambda x: x.get("name", "")):
896
+ skill_id = skill.get("name", skill.get("skill_id", "unknown"))
897
+ deploy_name = skill.get("deployment_name", skill_id)
898
+ description = skill.get("description", "")[:50]
921
899
 
922
- return all_skills
900
+ # Check if installed
901
+ is_installed = deploy_name in deployed or skill_id in deployed
902
+
903
+ skill_choices.append(
904
+ Choice(
905
+ title=f"{skill_id} - {description}",
906
+ value=skill_id,
907
+ checked=is_installed,
908
+ )
909
+ )
910
+
911
+ self.console.clear()
912
+ self._display_header()
913
+ self.console.print(
914
+ f"\n{icons.get(selected_cat, '📦')} [bold]{selected_cat.title()}[/bold]"
915
+ )
916
+ self.console.print("[dim]Use spacebar to toggle, enter to confirm[/dim]\n")
917
+
918
+ selected = questionary.checkbox(
919
+ "Select skills to install:",
920
+ choices=skill_choices,
921
+ style=self.QUESTIONARY_STYLE,
922
+ ).ask()
923
+
924
+ if selected is None:
925
+ continue # User cancelled, go back to category selection
926
+
927
+ # Process changes
928
+ selected_set = set(selected)
929
+ current_in_cat = set()
930
+
931
+ # Find currently installed skills in this category
932
+ for skill in category_skills:
933
+ skill_id = skill.get("name", skill.get("skill_id", "unknown"))
934
+ deploy_name = skill.get("deployment_name", skill_id)
935
+ if deploy_name in deployed or skill_id in deployed:
936
+ current_in_cat.add(skill_id)
937
+
938
+ # Install newly selected
939
+ to_install = selected_set - current_in_cat
940
+ for skill_id in to_install:
941
+ skill = next(
942
+ (
943
+ s
944
+ for s in category_skills
945
+ if s.get("name") == skill_id or s.get("skill_id") == skill_id
946
+ ),
947
+ None,
948
+ )
949
+ if skill:
950
+ self._install_skill_from_dict(skill)
951
+ self.console.print(f"[green]✓ Installed {skill_id}[/green]")
952
+
953
+ # Uninstall deselected
954
+ to_uninstall = current_in_cat - selected_set
955
+ for skill_id in to_uninstall:
956
+ # Find the skill to get deployment_name
957
+ skill = next(
958
+ (
959
+ s
960
+ for s in category_skills
961
+ if s.get("name") == skill_id or s.get("skill_id") == skill_id
962
+ ),
963
+ None,
964
+ )
965
+ if skill:
966
+ deploy_name = skill.get("deployment_name", skill_id)
967
+ # Use the name that's actually in deployed set
968
+ name_to_uninstall = (
969
+ deploy_name if deploy_name in deployed else skill_id
970
+ )
971
+ self._uninstall_skill_by_name(name_to_uninstall)
972
+ self.console.print(f"[yellow]✗ Uninstalled {skill_id}[/yellow]")
973
+
974
+ # Update deployed set for next iteration
975
+ deployed = self._get_deployed_skill_ids()
976
+
977
+ # Show completion message
978
+ if to_install or to_uninstall:
979
+ Prompt.ask("\nPress Enter to continue")
980
+
981
+ def _get_all_skills_from_git(self) -> list:
982
+ """Get all skills from Git-based skill manager.
983
+
984
+ Returns:
985
+ List of skill dicts with full metadata from GitSkillSourceManager.
986
+ """
987
+ from ...config.skill_sources import SkillSourceConfiguration
988
+ from ...services.skills.git_skill_source_manager import GitSkillSourceManager
989
+
990
+ try:
991
+ config = SkillSourceConfiguration()
992
+ manager = GitSkillSourceManager(config)
993
+ return manager.get_all_skills()
994
+ except Exception as e:
995
+ self.console.print(
996
+ f"[yellow]Warning: Could not load Git skills: {e}[/yellow]"
997
+ )
998
+ return []
999
+
1000
+ def _display_skills_table_grouped(self) -> None:
1001
+ """Display skills in a table grouped by category, like agents."""
1002
+ from rich import box
1003
+ from rich.table import Table
1004
+
1005
+ # Get all skills from Git manager
1006
+ all_skills = self._get_all_skills_from_git()
1007
+ deployed_ids = self._get_deployed_skill_ids()
1008
+
1009
+ if not all_skills:
1010
+ self.console.print(
1011
+ "[yellow]No skills available. Try syncing skills first.[/yellow]"
1012
+ )
1013
+ return
1014
+
1015
+ # Group skills by category/toolchain
1016
+ grouped = {}
1017
+ for skill in all_skills:
1018
+ # Try to get category from tags or use toolchain
1019
+ category = None
1020
+ tags = skill.get("tags", [])
1021
+
1022
+ # Look for category tag
1023
+ for tag in tags:
1024
+ if tag in [
1025
+ "universal",
1026
+ "python",
1027
+ "typescript",
1028
+ "javascript",
1029
+ "go",
1030
+ "rust",
1031
+ ]:
1032
+ category = tag
1033
+ break
1034
+
1035
+ # Fallback to toolchain or universal
1036
+ if not category:
1037
+ category = skill.get("toolchain", "universal")
1038
+
1039
+ if category not in grouped:
1040
+ grouped[category] = []
1041
+ grouped[category].append(skill)
1042
+
1043
+ # Sort categories: universal first, then alphabetically
1044
+ categories = sorted(grouped.keys(), key=lambda x: (x != "universal", x))
1045
+
1046
+ # Track global skill number across all categories
1047
+ skill_counter = 0
1048
+
1049
+ for category in categories:
1050
+ category_skills = grouped[category]
1051
+
1052
+ # Category header with icon
1053
+ icons = {
1054
+ "universal": "🌐",
1055
+ "python": "🐍",
1056
+ "typescript": "📘",
1057
+ "javascript": "📒",
1058
+ "go": "🔷",
1059
+ "rust": "⚙️",
1060
+ }
1061
+ icon = icons.get(category, "📦")
1062
+ self.console.print(
1063
+ f"\n{icon} [bold cyan]{category.title()}[/bold cyan] ({len(category_skills)} skills)"
1064
+ )
1065
+
1066
+ # Create table for this category
1067
+ table = Table(show_header=True, header_style="bold", box=box.SIMPLE)
1068
+ table.add_column("#", style="dim", width=4)
1069
+ table.add_column("Skill ID", style="cyan", width=35)
1070
+ table.add_column("Description", style="white", width=45)
1071
+ table.add_column("Status", style="green", width=12)
1072
+
1073
+ for skill in sorted(category_skills, key=lambda x: x.get("name", "")):
1074
+ skill_counter += 1
1075
+ skill_id = skill.get("name", skill.get("skill_id", "unknown"))
1076
+ # Use deployment_name for matching if available
1077
+ deploy_name = skill.get("deployment_name", skill_id)
1078
+ description = skill.get("description", "")[:45]
1079
+
1080
+ # Check if installed - handle both deployment_name and skill_id
1081
+ is_installed = deploy_name in deployed_ids or skill_id in deployed_ids
1082
+ status = "[green]✓ Installed[/green]" if is_installed else "Available"
1083
+
1084
+ table.add_row(str(skill_counter), skill_id, description, status)
1085
+
1086
+ self.console.print(table)
1087
+
1088
+ # Summary
1089
+ total = len(all_skills)
1090
+ installed = sum(
1091
+ 1
1092
+ for s in all_skills
1093
+ if s.get("deployment_name", s.get("name", "")) in deployed_ids
1094
+ or s.get("name", "") in deployed_ids
1095
+ )
1096
+ self.console.print(
1097
+ f"\n[dim]Showing {total} skills ({installed} installed)[/dim]"
1098
+ )
923
1099
 
924
1100
  def _get_deployed_skill_ids(self) -> set:
925
- """Get set of deployed skill IDs from .claude/skills/ directory."""
1101
+ """Get set of deployed skill IDs from .claude/skills/ directory.
1102
+
1103
+ Returns:
1104
+ Set of skill directory names and common variations for matching.
1105
+ """
926
1106
  from pathlib import Path
927
1107
 
928
1108
  skills_dir = Path.cwd() / ".claude" / "skills"
@@ -933,7 +1113,11 @@ class ConfigureCommand(BaseCommand):
933
1113
  deployed_ids = set()
934
1114
  for skill_dir in skills_dir.iterdir():
935
1115
  if skill_dir.is_dir() and not skill_dir.name.startswith("."):
1116
+ # Add both the directory name and common variations
936
1117
  deployed_ids.add(skill_dir.name)
1118
+ # Also add without prefix for matching (e.g., universal-testing -> testing)
1119
+ if skill_dir.name.startswith("universal-"):
1120
+ deployed_ids.add(skill_dir.name.replace("universal-", "", 1))
937
1121
 
938
1122
  return deployed_ids
939
1123
 
@@ -967,6 +1151,45 @@ class ConfigureCommand(BaseCommand):
967
1151
  if target_dir.exists():
968
1152
  shutil.rmtree(target_dir)
969
1153
 
1154
+ def _install_skill_from_dict(self, skill_dict: dict) -> None:
1155
+ """Install a skill from Git skill dict to .claude/skills/ directory.
1156
+
1157
+ Args:
1158
+ skill_dict: Skill metadata dict from GitSkillSourceManager.get_all_skills()
1159
+ """
1160
+ from pathlib import Path
1161
+
1162
+ skill_id = skill_dict.get("name", skill_dict.get("skill_id", "unknown"))
1163
+ content = skill_dict.get("content", "")
1164
+
1165
+ if not content:
1166
+ self.console.print(
1167
+ f"[yellow]Warning: Skill '{skill_id}' has no content[/yellow]"
1168
+ )
1169
+ return
1170
+
1171
+ # Target directory using deployment_name if available
1172
+ deploy_name = skill_dict.get("deployment_name", skill_id)
1173
+ target_dir = Path.cwd() / ".claude" / "skills" / deploy_name
1174
+ target_dir.mkdir(parents=True, exist_ok=True)
1175
+
1176
+ # Write skill content to skill.md
1177
+ skill_file = target_dir / "skill.md"
1178
+ skill_file.write_text(content, encoding="utf-8")
1179
+
1180
+ def _uninstall_skill_by_name(self, skill_name: str) -> None:
1181
+ """Uninstall a skill by name from .claude/skills/ directory.
1182
+
1183
+ Args:
1184
+ skill_name: Name of skill directory to remove
1185
+ """
1186
+ import shutil
1187
+ from pathlib import Path
1188
+
1189
+ target_dir = Path.cwd() / ".claude" / "skills" / skill_name
1190
+ if target_dir.exists():
1191
+ shutil.rmtree(target_dir)
1192
+
970
1193
  def _display_behavior_files(self) -> None:
971
1194
  """Display current behavior files."""
972
1195
  self.behavior_manager.display_behavior_files()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 5.4.74
3
+ Version: 5.4.75
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
@@ -60,7 +60,7 @@ claude_mpm/cli/commands/auto_configure.py,sha256=0Suzil6O0SBNeHUCwHOkt2q7gfuXRTy
60
60
  claude_mpm/cli/commands/cleanup.py,sha256=RQikOGLuLFWXzjeoHArdr5FA4Pf7tSK9w2NXL4vCrok,19769
61
61
  claude_mpm/cli/commands/cleanup_orphaned_agents.py,sha256=JR8crvgrz7Sa6d-SI-gKywok5S9rwc_DzDVk_h85sVs,4467
62
62
  claude_mpm/cli/commands/config.py,sha256=2M9VUPYcQkBUCIyyB-v1qTL3xYvao9YI2l_JGBUDauA,23374
63
- claude_mpm/cli/commands/configure.py,sha256=EB3G9mJ2thGg4oBvMl1KoBFBA3qh2iGaFZxClgSWS0Q,119990
63
+ claude_mpm/cli/commands/configure.py,sha256=yav4ipRbZ5tc1tjBHkU8Pf6Ls4FtGirqZmN94H0WPic,127811
64
64
  claude_mpm/cli/commands/configure_agent_display.py,sha256=oSvUhR861o_Pyqmop4ACAQNjwL02-Rf6TMqFvmQNh24,10575
65
65
  claude_mpm/cli/commands/configure_behavior_manager.py,sha256=_tfpcKW0KgMGO52q6IHFXL3W5xwjC8-q2_KpIvHVuoI,6827
66
66
  claude_mpm/cli/commands/configure_hook_manager.py,sha256=1X5brU6cgKRRF-2lQYA0aiKD7ZjTClqNHUSWuayktEw,9205
@@ -993,10 +993,10 @@ claude_mpm/utils/subprocess_utils.py,sha256=D0izRT8anjiUb_JG72zlJR_JAw1cDkb7kalN
993
993
  claude_mpm/validation/__init__.py,sha256=YZhwE3mhit-lslvRLuwfX82xJ_k4haZeKmh4IWaVwtk,156
994
994
  claude_mpm/validation/agent_validator.py,sha256=GprtAvu80VyMXcKGsK_VhYiXWA6BjKHv7O6HKx0AB9w,20917
995
995
  claude_mpm/validation/frontmatter_validator.py,sha256=YpJlYNNYcV8u6hIOi3_jaRsDnzhbcQpjCBE6eyBKaFY,7076
996
- claude_mpm-5.4.74.dist-info/licenses/LICENSE,sha256=ca3y_Rk4aPrbF6f62z8Ht5MJM9OAvbGlHvEDcj9vUQ4,3867
997
- claude_mpm-5.4.74.dist-info/licenses/LICENSE-FAQ.md,sha256=TxfEkXVCK98RzDOer09puc7JVCP_q_bN4dHtZKHCMcM,5104
998
- claude_mpm-5.4.74.dist-info/METADATA,sha256=nh2EhPg2C9U9MGIo3T-M0Z5hU6F6oBNUkgqk47yM3sU,38503
999
- claude_mpm-5.4.74.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1000
- claude_mpm-5.4.74.dist-info/entry_points.txt,sha256=n-Uk4vwHPpuvu-g_I7-GHORzTnN_m6iyOsoLveKKD0E,228
1001
- claude_mpm-5.4.74.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
1002
- claude_mpm-5.4.74.dist-info/RECORD,,
996
+ claude_mpm-5.4.75.dist-info/licenses/LICENSE,sha256=ca3y_Rk4aPrbF6f62z8Ht5MJM9OAvbGlHvEDcj9vUQ4,3867
997
+ claude_mpm-5.4.75.dist-info/licenses/LICENSE-FAQ.md,sha256=TxfEkXVCK98RzDOer09puc7JVCP_q_bN4dHtZKHCMcM,5104
998
+ claude_mpm-5.4.75.dist-info/METADATA,sha256=64ZleLArD4nY_HFCsrqzXn1K2RzAscpaNflWxTNRWOg,38503
999
+ claude_mpm-5.4.75.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1000
+ claude_mpm-5.4.75.dist-info/entry_points.txt,sha256=n-Uk4vwHPpuvu-g_I7-GHORzTnN_m6iyOsoLveKKD0E,228
1001
+ claude_mpm-5.4.75.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
1002
+ claude_mpm-5.4.75.dist-info/RECORD,,