Based on claude-code-tools TmuxCLIController, this refactor: - Added DockerTmuxController class for robust tmux session management - Implements send_keys() with configurable delay_enter - Implements capture_pane() for output retrieval - Implements wait_for_prompt() for pattern-based completion detection - Implements wait_for_idle() for content-hash-based idle detection - Implements wait_for_shell_prompt() for shell prompt detection Also includes workflow improvements: - Pre-task git snapshot before agent execution - Post-task commit protocol in agent guidelines Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
261 lines
8.1 KiB
Python
261 lines
8.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Plugin CLI Integration - Command handlers for plugin marketplace operations
|
|
|
|
Provides CLI commands for:
|
|
- luzia plugins list
|
|
- luzia plugins <name>
|
|
- luzia plugins skills
|
|
- luzia plugins find <task>
|
|
- luzia plugins export
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Dict, Any, Optional, List
|
|
|
|
from plugin_marketplace import get_marketplace_registry
|
|
from plugin_skill_loader import get_plugin_skill_loader
|
|
from dispatcher_plugin_integration import get_dispatcher_bridge
|
|
from plugin_kg_integration import export_plugins_to_kg
|
|
|
|
|
|
class PluginCLI:
|
|
"""CLI handler for plugin marketplace operations"""
|
|
|
|
def __init__(self):
|
|
self.registry = get_marketplace_registry()
|
|
self.skill_loader = get_plugin_skill_loader()
|
|
self.bridge = get_dispatcher_bridge(self.registry)
|
|
|
|
def handle_plugins_command(self, args: List[str]) -> int:
|
|
"""
|
|
Handle: luzia plugins <subcommand> [args...]
|
|
|
|
Subcommands:
|
|
- list: List all available plugins
|
|
- <name>: Show plugin details
|
|
- skills: List all plugin skills
|
|
- find <task>: Find plugins for a task
|
|
- export: Export all plugin data
|
|
- stats: Show statistics
|
|
"""
|
|
if not args:
|
|
return self.show_plugins_help()
|
|
|
|
subcommand = args[0]
|
|
|
|
if subcommand == "list":
|
|
return self.cmd_list_plugins(args[1:])
|
|
elif subcommand == "skills":
|
|
return self.cmd_list_skills(args[1:])
|
|
elif subcommand == "find":
|
|
return self.cmd_find_plugins(args[1:])
|
|
elif subcommand == "export":
|
|
return self.cmd_export_plugins(args[1:])
|
|
elif subcommand == "stats":
|
|
return self.cmd_show_stats(args[1:])
|
|
elif subcommand == "help":
|
|
return self.show_plugins_help()
|
|
else:
|
|
# Try as plugin name for details
|
|
return self.cmd_show_plugin(subcommand)
|
|
|
|
def show_plugins_help(self) -> int:
|
|
"""Show plugin CLI help"""
|
|
help_text = """
|
|
Plugin Marketplace Commands:
|
|
|
|
luzia plugins list List all plugins
|
|
luzia plugins <name> Show plugin details
|
|
luzia plugins skills List all plugin skills
|
|
luzia plugins find "<task>" Find plugins for a task
|
|
luzia plugins export Export plugin data to files
|
|
luzia plugins stats Show statistics
|
|
luzia plugins help Show this help
|
|
|
|
Examples:
|
|
luzia plugins list
|
|
luzia plugins code-simplifier
|
|
luzia plugins find "review code for security"
|
|
luzia plugins export
|
|
|
|
"""
|
|
print(help_text)
|
|
return 0
|
|
|
|
def cmd_list_plugins(self, args: List[str]) -> int:
|
|
"""Handle: luzia plugins list"""
|
|
plugins = self.registry.list_plugins()
|
|
|
|
if not plugins:
|
|
print("No plugins available")
|
|
return 0
|
|
|
|
print(f"\n{'Name':<30} {'Vendor':<15} {'Trust':<10} {'Capabilities'}")
|
|
print("-" * 75)
|
|
|
|
for plugin in plugins:
|
|
cap_count = len(plugin.capabilities)
|
|
print(f"{plugin.name:<30} {plugin.vendor:<15} {plugin.trust_level:<10} {cap_count}")
|
|
|
|
print(f"\nTotal: {len(plugins)} plugins\n")
|
|
return 0
|
|
|
|
def cmd_show_plugin(self, plugin_id: str) -> int:
|
|
"""Show plugin details"""
|
|
plugin = self.registry.get_plugin(plugin_id)
|
|
|
|
if not plugin:
|
|
# Try to find by name
|
|
plugins = self.registry.list_plugins()
|
|
for p in plugins:
|
|
if p.name.lower() == plugin_id.lower() or p.id.lower() == plugin_id.lower():
|
|
plugin = p
|
|
break
|
|
|
|
if not plugin:
|
|
print(f"Plugin not found: {plugin_id}")
|
|
return 1
|
|
|
|
output = {
|
|
'id': plugin.id,
|
|
'name': plugin.name,
|
|
'description': plugin.description,
|
|
'vendor': plugin.vendor,
|
|
'version': plugin.version,
|
|
'url': plugin.url,
|
|
'trust_level': plugin.trust_level,
|
|
'capabilities': [
|
|
{
|
|
'name': c.name,
|
|
'description': c.description,
|
|
'category': c.category,
|
|
'tags': c.tags
|
|
}
|
|
for c in plugin.capabilities
|
|
]
|
|
}
|
|
|
|
print(json.dumps(output, indent=2))
|
|
return 0
|
|
|
|
def cmd_list_skills(self, args: List[str]) -> int:
|
|
"""Handle: luzia plugins skills"""
|
|
if not self.skill_loader.skills:
|
|
self.skill_loader.generate_skills_from_plugins()
|
|
|
|
skills = self.skill_loader.list_skills()
|
|
|
|
if not skills:
|
|
print("No skills available")
|
|
return 0
|
|
|
|
print(f"\n{'Skill ID':<40} {'Category':<20} {'Trust':<10}")
|
|
print("-" * 75)
|
|
|
|
for skill in skills:
|
|
print(f"{skill.skill_id:<40} {skill.category:<20} {skill.trust_level:<10}")
|
|
|
|
print(f"\nTotal: {len(skills)} skills\n")
|
|
return 0
|
|
|
|
def cmd_find_plugins(self, args: List[str]) -> int:
|
|
"""Handle: luzia plugins find <task>"""
|
|
if not args:
|
|
print("Usage: luzia plugins find '<task description>'")
|
|
return 1
|
|
|
|
task = " ".join(args)
|
|
matched_skills = self.skill_loader.find_skills_for_task(task, min_relevance=0.3)
|
|
|
|
if not matched_skills:
|
|
print(f"No matching skills found for: {task}\n")
|
|
return 0
|
|
|
|
output = {
|
|
'query': task,
|
|
'matched_skills': matched_skills,
|
|
'count': len(matched_skills)
|
|
}
|
|
|
|
print(json.dumps(output, indent=2))
|
|
return 0
|
|
|
|
def cmd_export_plugins(self, args: List[str]) -> int:
|
|
"""Handle: luzia plugins export"""
|
|
output_dir = None
|
|
if args and args[0].startswith('--output='):
|
|
output_dir = Path(args[0].split('=')[1])
|
|
|
|
saved_files = export_plugins_to_kg(export_dir=output_dir)
|
|
|
|
output = {
|
|
'action': 'export_plugins',
|
|
'status': 'success',
|
|
'files': {k: str(v) for k, v in saved_files.items()},
|
|
'count': len(saved_files)
|
|
}
|
|
|
|
print(json.dumps(output, indent=2))
|
|
return 0
|
|
|
|
def cmd_show_stats(self, args: List[str]) -> int:
|
|
"""Handle: luzia plugins stats"""
|
|
if not self.skill_loader.skills:
|
|
self.skill_loader.generate_skills_from_plugins()
|
|
|
|
stats = {
|
|
'total_plugins': len(self.registry.plugins),
|
|
'total_skills': len(self.skill_loader.skills),
|
|
'categories': list(self.skill_loader.category_index.keys()),
|
|
'category_counts': {
|
|
cat: len(skill_ids)
|
|
for cat, skill_ids in self.skill_loader.category_index.items()
|
|
},
|
|
'keywords': len(self.skill_loader.skill_index),
|
|
'trust_distribution': self._get_trust_distribution()
|
|
}
|
|
|
|
print(json.dumps(stats, indent=2))
|
|
return 0
|
|
|
|
def _get_trust_distribution(self) -> Dict[str, int]:
|
|
"""Get trust level distribution"""
|
|
distribution: Dict[str, int] = {}
|
|
for plugin in self.registry.list_plugins():
|
|
trust = plugin.trust_level
|
|
distribution[trust] = distribution.get(trust, 0) + 1
|
|
return distribution
|
|
|
|
def cmd_dispatch_with_plugins(self, task: str, project: str = "test",
|
|
job_id: str = "job-test") -> Dict[str, Any]:
|
|
"""
|
|
Example: Dispatch a task with plugin context
|
|
|
|
Returns dispatch result with plugin recommendations
|
|
"""
|
|
dispatch_result = self.bridge.dispatch_with_plugin_context(task, project, job_id)
|
|
return dispatch_result
|
|
|
|
|
|
# Integration functions for main CLI
|
|
def match_plugins_command(args: list) -> Optional[list]:
|
|
"""Match 'luzia plugins' command"""
|
|
if args and args[0] == "plugins":
|
|
return args[1:]
|
|
return None
|
|
|
|
|
|
def route_plugins_command(config: dict, args: list, kwargs: dict) -> int:
|
|
"""Route to plugin CLI handler"""
|
|
cli = PluginCLI()
|
|
return cli.handle_plugins_command(args)
|
|
|
|
|
|
# Convenience functions
|
|
def get_plugin_cli() -> PluginCLI:
|
|
"""Get plugin CLI instance"""
|
|
return PluginCLI()
|