claude-mpm 5.4.55__py3-none-any.whl → 5.4.85__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 +1 -1
- claude_mpm/agents/CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md +405 -0
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +63 -241
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +109 -1925
- claude_mpm/agents/PM_INSTRUCTIONS.md +36 -9
- claude_mpm/cli/__init__.py +5 -1
- claude_mpm/cli/commands/agents.py +2 -4
- claude_mpm/cli/commands/agents_reconcile.py +197 -0
- claude_mpm/cli/commands/configure.py +620 -21
- claude_mpm/cli/commands/skills.py +166 -14
- claude_mpm/cli/executor.py +1 -0
- claude_mpm/cli/interactive/__init__.py +10 -0
- claude_mpm/cli/interactive/agent_wizard.py +30 -50
- claude_mpm/cli/interactive/questionary_styles.py +65 -0
- claude_mpm/cli/interactive/skill_selector.py +481 -0
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/startup.py +223 -388
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +2 -2
- claude_mpm/core/interactive_session.py +7 -7
- claude_mpm/core/output_style_manager.py +21 -13
- claude_mpm/core/unified_config.py +50 -8
- claude_mpm/core/unified_paths.py +30 -13
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +8 -0
- claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
- claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
- claude_mpm/services/agents/sources/git_source_sync_service.py +7 -4
- claude_mpm/services/agents/startup_sync.py +5 -2
- claude_mpm/services/pm_skills_deployer.py +4 -0
- claude_mpm/services/skills/git_skill_source_manager.py +24 -8
- claude_mpm/services/skills/selective_skill_deployer.py +82 -83
- claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
- claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
- claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
- claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
- claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
- claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
- claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
- claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
- claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
- claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
- claude_mpm/skills/bundled/pm/pm-bug-reporting/pm-bug-reporting.md +248 -0
- claude_mpm/skills/bundled/pm/pm-delegation-patterns/SKILL.md +167 -0
- claude_mpm/skills/bundled/pm/pm-git-file-tracking/SKILL.md +113 -0
- claude_mpm/skills/bundled/pm/pm-pr-workflow/SKILL.md +124 -0
- claude_mpm/skills/bundled/pm/pm-teaching-mode/SKILL.md +657 -0
- claude_mpm/skills/bundled/pm/pm-ticketing-integration/SKILL.md +154 -0
- claude_mpm/skills/bundled/pm/pm-verification-protocols/SKILL.md +198 -0
- claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
- claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
- claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
- claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
- claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
- claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
- claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
- claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
- claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
- claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
- claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
- claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
- claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
- claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
- claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
- claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
- claude_mpm/utils/agent_dependency_loader.py +103 -4
- claude_mpm/utils/robust_installer.py +45 -24
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/METADATA +47 -23
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/RECORD +159 -47
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/top_level.txt +0 -0
|
@@ -26,6 +26,7 @@ from rich.prompt import Confirm, Prompt
|
|
|
26
26
|
from rich.text import Text
|
|
27
27
|
|
|
28
28
|
from ...core.config import Config
|
|
29
|
+
from ...core.unified_config import UnifiedConfig
|
|
29
30
|
from ...services.agents.agent_recommendation_service import AgentRecommendationService
|
|
30
31
|
from ...services.version_service import VersionService
|
|
31
32
|
from ...utils.agent_filters import apply_all_filters, get_deployed_agent_ids
|
|
@@ -79,6 +80,7 @@ class ConfigureCommand(BaseCommand):
|
|
|
79
80
|
self._template_editor = None # Lazy-initialized
|
|
80
81
|
self._startup_manager = None # Lazy-initialized
|
|
81
82
|
self._recommendation_service = None # Lazy-initialized
|
|
83
|
+
self._unified_config = None # Lazy-initialized
|
|
82
84
|
|
|
83
85
|
def validate_args(self, args) -> Optional[str]:
|
|
84
86
|
"""Validate command arguments."""
|
|
@@ -162,6 +164,18 @@ class ConfigureCommand(BaseCommand):
|
|
|
162
164
|
self._recommendation_service = AgentRecommendationService()
|
|
163
165
|
return self._recommendation_service
|
|
164
166
|
|
|
167
|
+
@property
|
|
168
|
+
def unified_config(self) -> UnifiedConfig:
|
|
169
|
+
"""Lazy-initialize unified config."""
|
|
170
|
+
if self._unified_config is None:
|
|
171
|
+
try:
|
|
172
|
+
self._unified_config = UnifiedConfig()
|
|
173
|
+
except Exception as e:
|
|
174
|
+
self.logger.warning(f"Failed to load unified config: {e}")
|
|
175
|
+
# Fallback to default config
|
|
176
|
+
self._unified_config = UnifiedConfig()
|
|
177
|
+
return self._unified_config
|
|
178
|
+
|
|
165
179
|
def run(self, args) -> CommandResult:
|
|
166
180
|
"""Execute the configure command."""
|
|
167
181
|
# Set configuration scope
|
|
@@ -656,7 +670,7 @@ class ConfigureCommand(BaseCommand):
|
|
|
656
670
|
self.behavior_manager.manage_behaviors()
|
|
657
671
|
|
|
658
672
|
def _manage_skills(self) -> None:
|
|
659
|
-
"""Skills management interface."""
|
|
673
|
+
"""Skills management interface with questionary checkbox selection."""
|
|
660
674
|
from ...cli.interactive.skills_wizard import SkillsWizard
|
|
661
675
|
from ...skills.skill_manager import get_manager
|
|
662
676
|
|
|
@@ -667,22 +681,22 @@ class ConfigureCommand(BaseCommand):
|
|
|
667
681
|
self.console.clear()
|
|
668
682
|
self._display_header()
|
|
669
683
|
|
|
670
|
-
self.console.print("\n[bold]Skills Management
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
self.console.print("
|
|
674
|
-
self.console.print(" [
|
|
675
|
-
self.console.print(" [
|
|
684
|
+
self.console.print("\n[bold]Skills Management[/bold]")
|
|
685
|
+
|
|
686
|
+
# Show action options
|
|
687
|
+
self.console.print("\n[bold]Actions:[/bold]")
|
|
688
|
+
self.console.print(" [1] Install/Uninstall skills")
|
|
689
|
+
self.console.print(" [2] Configure skills for agents")
|
|
690
|
+
self.console.print(" [3] View current skill mappings")
|
|
691
|
+
self.console.print(" [4] Auto-link skills to agents")
|
|
692
|
+
self.console.print(" [b] Back to main menu")
|
|
676
693
|
self.console.print()
|
|
677
694
|
|
|
678
695
|
choice = Prompt.ask("[bold blue]Select an option[/bold blue]", default="b")
|
|
679
696
|
|
|
680
697
|
if choice == "1":
|
|
681
|
-
#
|
|
682
|
-
self.
|
|
683
|
-
self._display_header()
|
|
684
|
-
wizard.list_available_skills()
|
|
685
|
-
Prompt.ask("\nPress Enter to continue")
|
|
698
|
+
# Install/Uninstall skills with category-based selection
|
|
699
|
+
self._manage_skill_installation()
|
|
686
700
|
|
|
687
701
|
elif choice == "2":
|
|
688
702
|
# Configure skills interactively
|
|
@@ -805,6 +819,548 @@ class ConfigureCommand(BaseCommand):
|
|
|
805
819
|
self.console.print("[red]Invalid choice. Please try again.[/red]")
|
|
806
820
|
Prompt.ask("\nPress Enter to continue")
|
|
807
821
|
|
|
822
|
+
def _detect_skill_patterns(self, skills: list[dict]) -> dict[str, list[dict]]:
|
|
823
|
+
"""Group skills by detected common prefixes.
|
|
824
|
+
|
|
825
|
+
Args:
|
|
826
|
+
skills: List of skill dictionaries
|
|
827
|
+
|
|
828
|
+
Returns:
|
|
829
|
+
Dict mapping pattern prefix to list of skills.
|
|
830
|
+
Skills without pattern match go under "" (empty string) key.
|
|
831
|
+
"""
|
|
832
|
+
from collections import defaultdict
|
|
833
|
+
|
|
834
|
+
# Count prefix occurrences (try 1-segment and 2-segment prefixes)
|
|
835
|
+
prefix_counts = defaultdict(list)
|
|
836
|
+
|
|
837
|
+
for skill in skills:
|
|
838
|
+
skill_id = skill.get("name", skill.get("skill_id", ""))
|
|
839
|
+
|
|
840
|
+
# Try to extract prefixes (split by hyphen)
|
|
841
|
+
parts = skill_id.split("-")
|
|
842
|
+
|
|
843
|
+
if len(parts) >= 2:
|
|
844
|
+
# Try 2-segment prefix first (e.g., "toolchains-universal")
|
|
845
|
+
two_seg_prefix = f"{parts[0]}-{parts[1]}"
|
|
846
|
+
prefix_counts[two_seg_prefix].append(skill)
|
|
847
|
+
|
|
848
|
+
# Also try 1-segment prefix (e.g., "digitalocean")
|
|
849
|
+
one_seg_prefix = parts[0]
|
|
850
|
+
if one_seg_prefix != two_seg_prefix:
|
|
851
|
+
prefix_counts[one_seg_prefix].append(skill)
|
|
852
|
+
|
|
853
|
+
# Build pattern groups (require at least 2 skills per pattern)
|
|
854
|
+
pattern_groups = defaultdict(list)
|
|
855
|
+
used_skills = set()
|
|
856
|
+
|
|
857
|
+
# Prefer longer (more specific) prefixes
|
|
858
|
+
sorted_prefixes = sorted(prefix_counts.keys(), key=lambda x: (-len(x), x))
|
|
859
|
+
|
|
860
|
+
for prefix in sorted_prefixes:
|
|
861
|
+
matching_skills = prefix_counts[prefix]
|
|
862
|
+
|
|
863
|
+
# Only create a pattern group if we have 2+ skills and they're not already grouped
|
|
864
|
+
available_skills = [s for s in matching_skills if id(s) not in used_skills]
|
|
865
|
+
|
|
866
|
+
if len(available_skills) >= 2:
|
|
867
|
+
pattern_groups[prefix] = available_skills
|
|
868
|
+
used_skills.update(id(s) for s in available_skills)
|
|
869
|
+
|
|
870
|
+
# Add ungrouped skills to "" (Other) group
|
|
871
|
+
for skill in skills:
|
|
872
|
+
if id(skill) not in used_skills:
|
|
873
|
+
pattern_groups[""].append(skill)
|
|
874
|
+
|
|
875
|
+
return dict(pattern_groups)
|
|
876
|
+
|
|
877
|
+
def _get_pattern_icon(self, prefix: str) -> str:
|
|
878
|
+
"""Get icon for a pattern prefix.
|
|
879
|
+
|
|
880
|
+
Args:
|
|
881
|
+
prefix: Pattern prefix (e.g., "digitalocean", "vercel")
|
|
882
|
+
|
|
883
|
+
Returns:
|
|
884
|
+
Emoji icon for the pattern
|
|
885
|
+
"""
|
|
886
|
+
pattern_icons = {
|
|
887
|
+
"digitalocean": "🌊",
|
|
888
|
+
"aws": "☁️",
|
|
889
|
+
"github": "🐙",
|
|
890
|
+
"google": "🔍",
|
|
891
|
+
"vercel": "▲",
|
|
892
|
+
"netlify": "🦋",
|
|
893
|
+
"universal-testing": "🧪",
|
|
894
|
+
"universal-debugging": "🐛",
|
|
895
|
+
"universal-security": "🔒",
|
|
896
|
+
"toolchains-python": "🐍",
|
|
897
|
+
"toolchains-typescript": "📘",
|
|
898
|
+
"toolchains-javascript": "📒",
|
|
899
|
+
}
|
|
900
|
+
return pattern_icons.get(prefix, "📦")
|
|
901
|
+
|
|
902
|
+
def _manage_skill_installation(self) -> None:
|
|
903
|
+
"""Manage skill installation with category-based questionary checkbox selection."""
|
|
904
|
+
import questionary
|
|
905
|
+
|
|
906
|
+
# Get all skills
|
|
907
|
+
all_skills = self._get_all_skills_from_git()
|
|
908
|
+
if not all_skills:
|
|
909
|
+
self.console.print(
|
|
910
|
+
"[yellow]No skills available. Try syncing skills first.[/yellow]"
|
|
911
|
+
)
|
|
912
|
+
Prompt.ask("\nPress Enter to continue")
|
|
913
|
+
return
|
|
914
|
+
|
|
915
|
+
# Get deployed skills
|
|
916
|
+
deployed = self._get_deployed_skill_ids()
|
|
917
|
+
|
|
918
|
+
# Group by category
|
|
919
|
+
grouped = {}
|
|
920
|
+
for skill in all_skills:
|
|
921
|
+
# Try to get category from tags or use toolchain
|
|
922
|
+
category = None
|
|
923
|
+
tags = skill.get("tags", [])
|
|
924
|
+
|
|
925
|
+
# Look for category tag
|
|
926
|
+
for tag in tags:
|
|
927
|
+
if tag in [
|
|
928
|
+
"universal",
|
|
929
|
+
"python",
|
|
930
|
+
"typescript",
|
|
931
|
+
"javascript",
|
|
932
|
+
"go",
|
|
933
|
+
"rust",
|
|
934
|
+
]:
|
|
935
|
+
category = tag
|
|
936
|
+
break
|
|
937
|
+
|
|
938
|
+
# Fallback to toolchain or universal
|
|
939
|
+
if not category:
|
|
940
|
+
category = skill.get("toolchain", "universal")
|
|
941
|
+
|
|
942
|
+
if category not in grouped:
|
|
943
|
+
grouped[category] = []
|
|
944
|
+
grouped[category].append(skill)
|
|
945
|
+
|
|
946
|
+
# Category icons
|
|
947
|
+
icons = {
|
|
948
|
+
"universal": "🌐",
|
|
949
|
+
"python": "🐍",
|
|
950
|
+
"typescript": "📘",
|
|
951
|
+
"javascript": "📒",
|
|
952
|
+
"go": "🔷",
|
|
953
|
+
"rust": "⚙️",
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
# Sort categories: universal first, then alphabetically
|
|
957
|
+
categories = sorted(grouped.keys(), key=lambda x: (x != "universal", x))
|
|
958
|
+
|
|
959
|
+
while True:
|
|
960
|
+
# Show category selection first
|
|
961
|
+
self.console.clear()
|
|
962
|
+
self._display_header()
|
|
963
|
+
self.console.print("\n[bold cyan]Skills Management[/bold cyan]")
|
|
964
|
+
self.console.print(
|
|
965
|
+
f"[dim]{len(all_skills)} skills available, {len(deployed)} installed[/dim]\n"
|
|
966
|
+
)
|
|
967
|
+
|
|
968
|
+
cat_choices = [
|
|
969
|
+
Choice(
|
|
970
|
+
title=f"{icons.get(cat, '📦')} {cat.title()} ({len(grouped[cat])} skills)",
|
|
971
|
+
value=cat,
|
|
972
|
+
)
|
|
973
|
+
for cat in categories
|
|
974
|
+
]
|
|
975
|
+
cat_choices.append(Choice(title="← Back to main menu", value="back"))
|
|
976
|
+
|
|
977
|
+
selected_cat = questionary.select(
|
|
978
|
+
"Select a category:", choices=cat_choices, style=self.QUESTIONARY_STYLE
|
|
979
|
+
).ask()
|
|
980
|
+
|
|
981
|
+
if selected_cat is None or selected_cat == "back":
|
|
982
|
+
return
|
|
983
|
+
|
|
984
|
+
# Show skills in category with checkbox selection
|
|
985
|
+
category_skills = grouped[selected_cat]
|
|
986
|
+
|
|
987
|
+
# Detect pattern groups within category
|
|
988
|
+
pattern_groups = self._detect_skill_patterns(category_skills)
|
|
989
|
+
|
|
990
|
+
# Build choices with pattern grouping and installation status
|
|
991
|
+
skill_choices = []
|
|
992
|
+
|
|
993
|
+
# Track which skills belong to which group for expansion later
|
|
994
|
+
group_to_skills = {}
|
|
995
|
+
|
|
996
|
+
# Sort pattern groups: "" (Other) last, rest alphabetically
|
|
997
|
+
sorted_patterns = sorted(pattern_groups.keys(), key=lambda x: (x == "", x))
|
|
998
|
+
|
|
999
|
+
for pattern in sorted_patterns:
|
|
1000
|
+
pattern_skills = pattern_groups[pattern]
|
|
1001
|
+
|
|
1002
|
+
# Skip empty groups
|
|
1003
|
+
if not pattern_skills:
|
|
1004
|
+
continue
|
|
1005
|
+
|
|
1006
|
+
# Collect skill IDs in this group
|
|
1007
|
+
skill_ids_in_group = []
|
|
1008
|
+
for skill in pattern_skills:
|
|
1009
|
+
skill_id = skill.get("name", skill.get("skill_id", "unknown"))
|
|
1010
|
+
skill_ids_in_group.append(skill_id)
|
|
1011
|
+
|
|
1012
|
+
# Check if all skills in group are installed
|
|
1013
|
+
all_installed = all(
|
|
1014
|
+
skill.get(
|
|
1015
|
+
"deployment_name", skill.get("name", skill.get("skill_id"))
|
|
1016
|
+
)
|
|
1017
|
+
in deployed
|
|
1018
|
+
or skill.get("name", skill.get("skill_id")) in deployed
|
|
1019
|
+
for skill in pattern_skills
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
# Add pattern group header as selectable choice
|
|
1023
|
+
if pattern:
|
|
1024
|
+
# Named pattern group
|
|
1025
|
+
pattern_icon = self._get_pattern_icon(pattern)
|
|
1026
|
+
skill_count = len(pattern_skills)
|
|
1027
|
+
group_key = f"__group__:{pattern}"
|
|
1028
|
+
group_to_skills[group_key] = skill_ids_in_group
|
|
1029
|
+
|
|
1030
|
+
skill_choices.append(
|
|
1031
|
+
Choice(
|
|
1032
|
+
title=f"{pattern_icon} {pattern} ({skill_count} skills) [Select All]",
|
|
1033
|
+
value=group_key,
|
|
1034
|
+
checked=all_installed,
|
|
1035
|
+
)
|
|
1036
|
+
)
|
|
1037
|
+
elif pattern_skills:
|
|
1038
|
+
# "Other" group - only show if there are skills
|
|
1039
|
+
group_key = "__group__:Other"
|
|
1040
|
+
group_to_skills[group_key] = skill_ids_in_group
|
|
1041
|
+
|
|
1042
|
+
skill_choices.append(
|
|
1043
|
+
Choice(
|
|
1044
|
+
title=f"📦 Other ({len(pattern_skills)} skills) [Select All]",
|
|
1045
|
+
value=group_key,
|
|
1046
|
+
checked=all_installed,
|
|
1047
|
+
)
|
|
1048
|
+
)
|
|
1049
|
+
|
|
1050
|
+
# Add skills in this pattern group
|
|
1051
|
+
for skill in sorted(pattern_skills, key=lambda x: x.get("name", "")):
|
|
1052
|
+
skill_id = skill.get("name", skill.get("skill_id", "unknown"))
|
|
1053
|
+
deploy_name = skill.get("deployment_name", skill_id)
|
|
1054
|
+
description = skill.get("description", "")[:50]
|
|
1055
|
+
|
|
1056
|
+
# Check if installed
|
|
1057
|
+
is_installed = deploy_name in deployed or skill_id in deployed
|
|
1058
|
+
|
|
1059
|
+
# Add indentation for pattern-grouped skills (all skills are indented)
|
|
1060
|
+
skill_choices.append(
|
|
1061
|
+
Choice(
|
|
1062
|
+
title=f" {skill_id} - {description}",
|
|
1063
|
+
value=skill_id,
|
|
1064
|
+
checked=is_installed,
|
|
1065
|
+
)
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
# Add spacing between pattern groups (not after last group)
|
|
1069
|
+
if pattern != sorted_patterns[-1]:
|
|
1070
|
+
skill_choices.append(Separator())
|
|
1071
|
+
|
|
1072
|
+
self.console.clear()
|
|
1073
|
+
self._display_header()
|
|
1074
|
+
self.console.print(
|
|
1075
|
+
f"\n{icons.get(selected_cat, '📦')} [bold]{selected_cat.title()}[/bold]"
|
|
1076
|
+
)
|
|
1077
|
+
self.console.print(
|
|
1078
|
+
"[dim]Use spacebar to toggle individual skills or entire groups, enter to confirm[/dim]\n"
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
selected = questionary.checkbox(
|
|
1082
|
+
"Select skills to install:",
|
|
1083
|
+
choices=skill_choices,
|
|
1084
|
+
style=self.QUESTIONARY_STYLE,
|
|
1085
|
+
).ask()
|
|
1086
|
+
|
|
1087
|
+
if selected is None:
|
|
1088
|
+
continue # User cancelled, go back to category selection
|
|
1089
|
+
|
|
1090
|
+
# Process group selections - expand to individual skills
|
|
1091
|
+
selected_set = set()
|
|
1092
|
+
for item in selected:
|
|
1093
|
+
if item.startswith("__group__:"):
|
|
1094
|
+
# Expand group selection to all skills in that group
|
|
1095
|
+
selected_set.update(group_to_skills[item])
|
|
1096
|
+
else:
|
|
1097
|
+
# Individual skill selection
|
|
1098
|
+
selected_set.add(item)
|
|
1099
|
+
|
|
1100
|
+
current_in_cat = set()
|
|
1101
|
+
|
|
1102
|
+
# Find currently installed skills in this category
|
|
1103
|
+
for skill in category_skills:
|
|
1104
|
+
skill_id = skill.get("name", skill.get("skill_id", "unknown"))
|
|
1105
|
+
deploy_name = skill.get("deployment_name", skill_id)
|
|
1106
|
+
if deploy_name in deployed or skill_id in deployed:
|
|
1107
|
+
current_in_cat.add(skill_id)
|
|
1108
|
+
|
|
1109
|
+
# Install newly selected
|
|
1110
|
+
to_install = selected_set - current_in_cat
|
|
1111
|
+
for skill_id in to_install:
|
|
1112
|
+
skill = next(
|
|
1113
|
+
(
|
|
1114
|
+
s
|
|
1115
|
+
for s in category_skills
|
|
1116
|
+
if s.get("name") == skill_id or s.get("skill_id") == skill_id
|
|
1117
|
+
),
|
|
1118
|
+
None,
|
|
1119
|
+
)
|
|
1120
|
+
if skill:
|
|
1121
|
+
self._install_skill_from_dict(skill)
|
|
1122
|
+
self.console.print(f"[green]✓ Installed {skill_id}[/green]")
|
|
1123
|
+
|
|
1124
|
+
# Uninstall deselected
|
|
1125
|
+
to_uninstall = current_in_cat - selected_set
|
|
1126
|
+
for skill_id in to_uninstall:
|
|
1127
|
+
# Find the skill to get deployment_name
|
|
1128
|
+
skill = next(
|
|
1129
|
+
(
|
|
1130
|
+
s
|
|
1131
|
+
for s in category_skills
|
|
1132
|
+
if s.get("name") == skill_id or s.get("skill_id") == skill_id
|
|
1133
|
+
),
|
|
1134
|
+
None,
|
|
1135
|
+
)
|
|
1136
|
+
if skill:
|
|
1137
|
+
deploy_name = skill.get("deployment_name", skill_id)
|
|
1138
|
+
# Use the name that's actually in deployed set
|
|
1139
|
+
name_to_uninstall = (
|
|
1140
|
+
deploy_name if deploy_name in deployed else skill_id
|
|
1141
|
+
)
|
|
1142
|
+
self._uninstall_skill_by_name(name_to_uninstall)
|
|
1143
|
+
self.console.print(f"[yellow]✗ Uninstalled {skill_id}[/yellow]")
|
|
1144
|
+
|
|
1145
|
+
# Update deployed set for next iteration
|
|
1146
|
+
deployed = self._get_deployed_skill_ids()
|
|
1147
|
+
|
|
1148
|
+
# Show completion message
|
|
1149
|
+
if to_install or to_uninstall:
|
|
1150
|
+
Prompt.ask("\nPress Enter to continue")
|
|
1151
|
+
|
|
1152
|
+
def _get_all_skills_from_git(self) -> list:
|
|
1153
|
+
"""Get all skills from Git-based skill manager.
|
|
1154
|
+
|
|
1155
|
+
Returns:
|
|
1156
|
+
List of skill dicts with full metadata from GitSkillSourceManager.
|
|
1157
|
+
"""
|
|
1158
|
+
from ...config.skill_sources import SkillSourceConfiguration
|
|
1159
|
+
from ...services.skills.git_skill_source_manager import GitSkillSourceManager
|
|
1160
|
+
|
|
1161
|
+
try:
|
|
1162
|
+
config = SkillSourceConfiguration()
|
|
1163
|
+
manager = GitSkillSourceManager(config)
|
|
1164
|
+
return manager.get_all_skills()
|
|
1165
|
+
except Exception as e:
|
|
1166
|
+
self.console.print(
|
|
1167
|
+
f"[yellow]Warning: Could not load Git skills: {e}[/yellow]"
|
|
1168
|
+
)
|
|
1169
|
+
return []
|
|
1170
|
+
|
|
1171
|
+
def _display_skills_table_grouped(self) -> None:
|
|
1172
|
+
"""Display skills in a table grouped by category, like agents."""
|
|
1173
|
+
from rich import box
|
|
1174
|
+
from rich.table import Table
|
|
1175
|
+
|
|
1176
|
+
# Get all skills from Git manager
|
|
1177
|
+
all_skills = self._get_all_skills_from_git()
|
|
1178
|
+
deployed_ids = self._get_deployed_skill_ids()
|
|
1179
|
+
|
|
1180
|
+
if not all_skills:
|
|
1181
|
+
self.console.print(
|
|
1182
|
+
"[yellow]No skills available. Try syncing skills first.[/yellow]"
|
|
1183
|
+
)
|
|
1184
|
+
return
|
|
1185
|
+
|
|
1186
|
+
# Group skills by category/toolchain
|
|
1187
|
+
grouped = {}
|
|
1188
|
+
for skill in all_skills:
|
|
1189
|
+
# Try to get category from tags or use toolchain
|
|
1190
|
+
category = None
|
|
1191
|
+
tags = skill.get("tags", [])
|
|
1192
|
+
|
|
1193
|
+
# Look for category tag
|
|
1194
|
+
for tag in tags:
|
|
1195
|
+
if tag in [
|
|
1196
|
+
"universal",
|
|
1197
|
+
"python",
|
|
1198
|
+
"typescript",
|
|
1199
|
+
"javascript",
|
|
1200
|
+
"go",
|
|
1201
|
+
"rust",
|
|
1202
|
+
]:
|
|
1203
|
+
category = tag
|
|
1204
|
+
break
|
|
1205
|
+
|
|
1206
|
+
# Fallback to toolchain or universal
|
|
1207
|
+
if not category:
|
|
1208
|
+
category = skill.get("toolchain", "universal")
|
|
1209
|
+
|
|
1210
|
+
if category not in grouped:
|
|
1211
|
+
grouped[category] = []
|
|
1212
|
+
grouped[category].append(skill)
|
|
1213
|
+
|
|
1214
|
+
# Sort categories: universal first, then alphabetically
|
|
1215
|
+
categories = sorted(grouped.keys(), key=lambda x: (x != "universal", x))
|
|
1216
|
+
|
|
1217
|
+
# Track global skill number across all categories
|
|
1218
|
+
skill_counter = 0
|
|
1219
|
+
|
|
1220
|
+
for category in categories:
|
|
1221
|
+
category_skills = grouped[category]
|
|
1222
|
+
|
|
1223
|
+
# Category header with icon
|
|
1224
|
+
icons = {
|
|
1225
|
+
"universal": "🌐",
|
|
1226
|
+
"python": "🐍",
|
|
1227
|
+
"typescript": "📘",
|
|
1228
|
+
"javascript": "📒",
|
|
1229
|
+
"go": "🔷",
|
|
1230
|
+
"rust": "⚙️",
|
|
1231
|
+
}
|
|
1232
|
+
icon = icons.get(category, "📦")
|
|
1233
|
+
self.console.print(
|
|
1234
|
+
f"\n{icon} [bold cyan]{category.title()}[/bold cyan] ({len(category_skills)} skills)"
|
|
1235
|
+
)
|
|
1236
|
+
|
|
1237
|
+
# Create table for this category
|
|
1238
|
+
table = Table(show_header=True, header_style="bold", box=box.SIMPLE)
|
|
1239
|
+
table.add_column("#", style="dim", width=4)
|
|
1240
|
+
table.add_column("Skill ID", style="cyan", width=35)
|
|
1241
|
+
table.add_column("Description", style="white", width=45)
|
|
1242
|
+
table.add_column("Status", style="green", width=12)
|
|
1243
|
+
|
|
1244
|
+
for skill in sorted(category_skills, key=lambda x: x.get("name", "")):
|
|
1245
|
+
skill_counter += 1
|
|
1246
|
+
skill_id = skill.get("name", skill.get("skill_id", "unknown"))
|
|
1247
|
+
# Use deployment_name for matching if available
|
|
1248
|
+
deploy_name = skill.get("deployment_name", skill_id)
|
|
1249
|
+
description = skill.get("description", "")[:45]
|
|
1250
|
+
|
|
1251
|
+
# Check if installed - handle both deployment_name and skill_id
|
|
1252
|
+
is_installed = deploy_name in deployed_ids or skill_id in deployed_ids
|
|
1253
|
+
status = "[green]✓ Installed[/green]" if is_installed else "Available"
|
|
1254
|
+
|
|
1255
|
+
table.add_row(str(skill_counter), skill_id, description, status)
|
|
1256
|
+
|
|
1257
|
+
self.console.print(table)
|
|
1258
|
+
|
|
1259
|
+
# Summary
|
|
1260
|
+
total = len(all_skills)
|
|
1261
|
+
installed = sum(
|
|
1262
|
+
1
|
|
1263
|
+
for s in all_skills
|
|
1264
|
+
if s.get("deployment_name", s.get("name", "")) in deployed_ids
|
|
1265
|
+
or s.get("name", "") in deployed_ids
|
|
1266
|
+
)
|
|
1267
|
+
self.console.print(
|
|
1268
|
+
f"\n[dim]Showing {total} skills ({installed} installed)[/dim]"
|
|
1269
|
+
)
|
|
1270
|
+
|
|
1271
|
+
def _get_deployed_skill_ids(self) -> set:
|
|
1272
|
+
"""Get set of deployed skill IDs from .claude/skills/ directory.
|
|
1273
|
+
|
|
1274
|
+
Returns:
|
|
1275
|
+
Set of skill directory names and common variations for matching.
|
|
1276
|
+
"""
|
|
1277
|
+
from pathlib import Path
|
|
1278
|
+
|
|
1279
|
+
skills_dir = Path.cwd() / ".claude" / "skills"
|
|
1280
|
+
if not skills_dir.exists():
|
|
1281
|
+
return set()
|
|
1282
|
+
|
|
1283
|
+
# Each deployed skill is a directory in .claude/skills/
|
|
1284
|
+
deployed_ids = set()
|
|
1285
|
+
for skill_dir in skills_dir.iterdir():
|
|
1286
|
+
if skill_dir.is_dir() and not skill_dir.name.startswith("."):
|
|
1287
|
+
# Add both the directory name and common variations
|
|
1288
|
+
deployed_ids.add(skill_dir.name)
|
|
1289
|
+
# Also add without prefix for matching (e.g., universal-testing -> testing)
|
|
1290
|
+
if skill_dir.name.startswith("universal-"):
|
|
1291
|
+
deployed_ids.add(skill_dir.name.replace("universal-", "", 1))
|
|
1292
|
+
|
|
1293
|
+
return deployed_ids
|
|
1294
|
+
|
|
1295
|
+
def _install_skill(self, skill) -> None:
|
|
1296
|
+
"""Install a skill to .claude/skills/ directory."""
|
|
1297
|
+
import shutil
|
|
1298
|
+
from pathlib import Path
|
|
1299
|
+
|
|
1300
|
+
# Target directory
|
|
1301
|
+
target_dir = Path.cwd() / ".claude" / "skills" / skill.skill_id
|
|
1302
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
1303
|
+
|
|
1304
|
+
# Copy skill file(s)
|
|
1305
|
+
if skill.path.is_file():
|
|
1306
|
+
# Single file skill - copy to skill.md in target directory
|
|
1307
|
+
shutil.copy2(skill.path, target_dir / "skill.md")
|
|
1308
|
+
elif skill.path.is_dir():
|
|
1309
|
+
# Directory-based skill - copy all contents
|
|
1310
|
+
for item in skill.path.iterdir():
|
|
1311
|
+
if item.is_file():
|
|
1312
|
+
shutil.copy2(item, target_dir / item.name)
|
|
1313
|
+
elif item.is_dir():
|
|
1314
|
+
shutil.copytree(item, target_dir / item.name, dirs_exist_ok=True)
|
|
1315
|
+
|
|
1316
|
+
def _uninstall_skill(self, skill) -> None:
|
|
1317
|
+
"""Uninstall a skill from .claude/skills/ directory."""
|
|
1318
|
+
import shutil
|
|
1319
|
+
from pathlib import Path
|
|
1320
|
+
|
|
1321
|
+
target_dir = Path.cwd() / ".claude" / "skills" / skill.skill_id
|
|
1322
|
+
if target_dir.exists():
|
|
1323
|
+
shutil.rmtree(target_dir)
|
|
1324
|
+
|
|
1325
|
+
def _install_skill_from_dict(self, skill_dict: dict) -> None:
|
|
1326
|
+
"""Install a skill from Git skill dict to .claude/skills/ directory.
|
|
1327
|
+
|
|
1328
|
+
Args:
|
|
1329
|
+
skill_dict: Skill metadata dict from GitSkillSourceManager.get_all_skills()
|
|
1330
|
+
"""
|
|
1331
|
+
from pathlib import Path
|
|
1332
|
+
|
|
1333
|
+
skill_id = skill_dict.get("name", skill_dict.get("skill_id", "unknown"))
|
|
1334
|
+
content = skill_dict.get("content", "")
|
|
1335
|
+
|
|
1336
|
+
if not content:
|
|
1337
|
+
self.console.print(
|
|
1338
|
+
f"[yellow]Warning: Skill '{skill_id}' has no content[/yellow]"
|
|
1339
|
+
)
|
|
1340
|
+
return
|
|
1341
|
+
|
|
1342
|
+
# Target directory using deployment_name if available
|
|
1343
|
+
deploy_name = skill_dict.get("deployment_name", skill_id)
|
|
1344
|
+
target_dir = Path.cwd() / ".claude" / "skills" / deploy_name
|
|
1345
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
1346
|
+
|
|
1347
|
+
# Write skill content to skill.md
|
|
1348
|
+
skill_file = target_dir / "skill.md"
|
|
1349
|
+
skill_file.write_text(content, encoding="utf-8")
|
|
1350
|
+
|
|
1351
|
+
def _uninstall_skill_by_name(self, skill_name: str) -> None:
|
|
1352
|
+
"""Uninstall a skill by name from .claude/skills/ directory.
|
|
1353
|
+
|
|
1354
|
+
Args:
|
|
1355
|
+
skill_name: Name of skill directory to remove
|
|
1356
|
+
"""
|
|
1357
|
+
import shutil
|
|
1358
|
+
from pathlib import Path
|
|
1359
|
+
|
|
1360
|
+
target_dir = Path.cwd() / ".claude" / "skills" / skill_name
|
|
1361
|
+
if target_dir.exists():
|
|
1362
|
+
shutil.rmtree(target_dir)
|
|
1363
|
+
|
|
808
1364
|
def _display_behavior_files(self) -> None:
|
|
809
1365
|
"""Display current behavior files."""
|
|
810
1366
|
self.behavior_manager.display_behavior_files()
|
|
@@ -1437,18 +1993,20 @@ class ConfigureCommand(BaseCommand):
|
|
|
1437
1993
|
|
|
1438
1994
|
# Add inline control: Select/Deselect all from this collection
|
|
1439
1995
|
if all_selected:
|
|
1996
|
+
deselect_value = f"__DESELECT_ALL_{collection_id}__"
|
|
1440
1997
|
choices.append(
|
|
1441
1998
|
Choice(
|
|
1442
|
-
f" [Deselect all from {collection_id}]",
|
|
1443
|
-
value=
|
|
1999
|
+
f" [Deselect all from {collection_id}]", # nosec B608
|
|
2000
|
+
value=deselect_value,
|
|
1444
2001
|
checked=False,
|
|
1445
2002
|
)
|
|
1446
2003
|
)
|
|
1447
2004
|
else:
|
|
2005
|
+
select_value = f"__SELECT_ALL_{collection_id}__"
|
|
1448
2006
|
choices.append(
|
|
1449
2007
|
Choice(
|
|
1450
|
-
f" [Select all from {collection_id}]",
|
|
1451
|
-
value=
|
|
2008
|
+
f" [Select all from {collection_id}]", # nosec B608
|
|
2009
|
+
value=select_value,
|
|
1452
2010
|
checked=False,
|
|
1453
2011
|
)
|
|
1454
2012
|
)
|
|
@@ -1518,18 +2076,32 @@ class ConfigureCommand(BaseCommand):
|
|
|
1518
2076
|
)
|
|
1519
2077
|
display_name = self._format_display_name(raw_display_name)
|
|
1520
2078
|
|
|
1521
|
-
# Check if agent is
|
|
2079
|
+
# Check if agent is required (cannot be unchecked)
|
|
2080
|
+
required_agents = set(self.unified_config.agents.required)
|
|
2081
|
+
is_required = (
|
|
2082
|
+
agent_leaf_name in required_agents
|
|
2083
|
+
or agent_id in required_agents
|
|
2084
|
+
)
|
|
1522
2085
|
|
|
1523
|
-
# Format choice text
|
|
1524
|
-
|
|
2086
|
+
# Format choice text with [Required] indicator
|
|
2087
|
+
if is_required:
|
|
2088
|
+
choice_text = f" {display_name} [Required]"
|
|
2089
|
+
else:
|
|
2090
|
+
choice_text = f" {display_name}"
|
|
1525
2091
|
|
|
1526
|
-
|
|
2092
|
+
# Required agents are always selected
|
|
2093
|
+
is_selected = is_required or agent_id in current_selection
|
|
2094
|
+
|
|
2095
|
+
# Add to current selection if required
|
|
2096
|
+
if is_required:
|
|
2097
|
+
current_selection.add(agent_id)
|
|
1527
2098
|
|
|
1528
2099
|
choices.append(
|
|
1529
2100
|
Choice(
|
|
1530
2101
|
title=choice_text,
|
|
1531
2102
|
value=agent_id, # Use agent_id for value
|
|
1532
2103
|
checked=is_selected,
|
|
2104
|
+
disabled=is_required, # Disable checkbox for required agents
|
|
1533
2105
|
)
|
|
1534
2106
|
)
|
|
1535
2107
|
|
|
@@ -1538,6 +2110,7 @@ class ConfigureCommand(BaseCommand):
|
|
|
1538
2110
|
self.console.print(
|
|
1539
2111
|
"[dim][ ] Unchecked = Available (check to install)[/dim]"
|
|
1540
2112
|
)
|
|
2113
|
+
self.console.print("[dim][Required] = Core agents (always installed)[/dim]")
|
|
1541
2114
|
self.console.print(
|
|
1542
2115
|
"[dim]Use arrow keys to navigate, space to toggle, Enter to apply[/dim]\n"
|
|
1543
2116
|
)
|
|
@@ -1624,12 +2197,37 @@ class ConfigureCommand(BaseCommand):
|
|
|
1624
2197
|
|
|
1625
2198
|
# No controls selected - use the individual selections as final
|
|
1626
2199
|
final_selection = set(selected_values)
|
|
2200
|
+
|
|
2201
|
+
# Ensure required agents are always in the final selection
|
|
2202
|
+
required_agents = set(self.unified_config.agents.required)
|
|
2203
|
+
for agent in agents:
|
|
2204
|
+
agent_id = getattr(agent, "agent_id", agent.name)
|
|
2205
|
+
agent_leaf_name = agent_id.split("/")[-1]
|
|
2206
|
+
if agent_leaf_name in required_agents or agent_id in required_agents:
|
|
2207
|
+
final_selection.add(agent_id)
|
|
2208
|
+
|
|
1627
2209
|
break
|
|
1628
2210
|
|
|
1629
2211
|
# Determine changes
|
|
1630
2212
|
to_deploy = final_selection - deployed_full_paths
|
|
1631
2213
|
to_remove = deployed_full_paths - final_selection
|
|
1632
2214
|
|
|
2215
|
+
# Prevent removal of required agents
|
|
2216
|
+
required_agents = set(self.unified_config.agents.required)
|
|
2217
|
+
to_remove_filtered = set()
|
|
2218
|
+
for agent_id in to_remove:
|
|
2219
|
+
agent_leaf_name = agent_id.split("/")[-1]
|
|
2220
|
+
if (
|
|
2221
|
+
agent_leaf_name not in required_agents
|
|
2222
|
+
and agent_id not in required_agents
|
|
2223
|
+
):
|
|
2224
|
+
to_remove_filtered.add(agent_id)
|
|
2225
|
+
else:
|
|
2226
|
+
self.console.print(
|
|
2227
|
+
f"[yellow]⚠ Cannot remove required agent: {agent_id}[/yellow]"
|
|
2228
|
+
)
|
|
2229
|
+
to_remove = to_remove_filtered
|
|
2230
|
+
|
|
1633
2231
|
if not to_deploy and not to_remove:
|
|
1634
2232
|
self.console.print("[yellow]No changes needed[/yellow]")
|
|
1635
2233
|
Prompt.ask("\nPress Enter to continue")
|
|
@@ -2329,7 +2927,8 @@ class ConfigureCommand(BaseCommand):
|
|
|
2329
2927
|
f" Detection Quality: [{'green' if summary.get('detection_quality') == 'high' else 'yellow'}]{summary.get('detection_quality', 'unknown')}[/]"
|
|
2330
2928
|
)
|
|
2331
2929
|
self.console.print()
|
|
2332
|
-
except Exception:
|
|
2930
|
+
except Exception: # nosec B110 - Suppress broad except for failed safety check
|
|
2931
|
+
# Silent failure on safety check - non-critical feature
|
|
2333
2932
|
pass
|
|
2334
2933
|
|
|
2335
2934
|
# Build mapping: agent_id -> AgentConfig
|