refactor: flatten CLI directory structure - remove 'box in a box'
BEFORE: /opt/aitbc/cli/ ├── aitbc_cli/ # Python package (box in a box) │ ├── commands/ │ ├── main.py │ └── ... ├── setup.py AFTER: /opt/aitbc/cli/ # Flat structure ├── commands/ # Direct access ├── main.py # Direct access ├── auth/ ├── config/ ├── core/ ├── models/ ├── utils/ ├── plugins.py └── setup.py CHANGES MADE: - Moved all files from aitbc_cli/ to cli/ root - Fixed all relative imports (from . to absolute imports) - Updated setup.py entry point: aitbc_cli.main → main - Added CLI directory to Python path in entry script - Simplified deployment.py to remove dependency on deleted core.deployment - Fixed import paths in all command files - Recreated virtual environment with new structure BENEFITS: - Eliminated 'box in a box' nesting - Simpler directory structure - Direct access to all modules - Cleaner imports - Easier maintenance and development - CLI works with both 'python main.py' and 'aitbc' commands
This commit is contained in:
503
cli/commands/plugin_registry.py
Executable file
503
cli/commands/plugin_registry.py
Executable file
@@ -0,0 +1,503 @@
|
||||
"""
|
||||
Plugin Registry CLI Commands for AITBC
|
||||
Commands for managing plugin registration, versioning, and discovery
|
||||
"""
|
||||
|
||||
import click
|
||||
import json
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
@click.group()
|
||||
def plugin_registry():
|
||||
"""Plugin registry management commands"""
|
||||
pass
|
||||
|
||||
@plugin_registry.command()
|
||||
@click.option('--plugin-id', help='Plugin ID to register')
|
||||
@click.option('--name', required=True, help='Plugin name')
|
||||
@click.option('--version', required=True, help='Plugin version')
|
||||
@click.option('--description', required=True, help='Plugin description')
|
||||
@click.option('--author', required=True, help='Plugin author')
|
||||
@click.option('--category', required=True, help='Plugin category')
|
||||
@click.option('--tags', help='Plugin tags (comma-separated)')
|
||||
@click.option('--repository', help='Source repository URL')
|
||||
@click.option('--homepage', help='Plugin homepage URL')
|
||||
@click.option('--license', help='Plugin license')
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def register(plugin_id, name, version, description, author, category, tags, repository, homepage, license, test_mode):
|
||||
"""Register a new plugin in the registry"""
|
||||
try:
|
||||
if not plugin_id:
|
||||
plugin_id = name.lower().replace(' ', '-').replace('_', '-')
|
||||
|
||||
# Create plugin registration data
|
||||
plugin_data = {
|
||||
"plugin_id": plugin_id,
|
||||
"name": name,
|
||||
"version": version,
|
||||
"description": description,
|
||||
"author": author,
|
||||
"category": category,
|
||||
"tags": tags.split(',') if tags else [],
|
||||
"repository": repository,
|
||||
"homepage": homepage,
|
||||
"license": license,
|
||||
"status": "active",
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"updated_at": datetime.utcnow().isoformat(),
|
||||
"downloads": 0,
|
||||
"rating": 0.0,
|
||||
"reviews_count": 0
|
||||
}
|
||||
|
||||
if test_mode:
|
||||
# Mock registration for testing
|
||||
plugin_data["registration_id"] = f"reg_{int(datetime.utcnow().timestamp())}"
|
||||
plugin_data["status"] = "registered"
|
||||
click.echo(f"✅ Plugin registered successfully (test mode)")
|
||||
click.echo(f"📋 Plugin ID: {plugin_data['plugin_id']}")
|
||||
click.echo(f"📦 Version: {plugin_data['version']}")
|
||||
click.echo(f"📝 Description: {plugin_data['description']}")
|
||||
return
|
||||
|
||||
# Send to registry service
|
||||
config = get_config()
|
||||
response = requests.post(
|
||||
f"{config.coordinator_url}/api/v1/plugins/register",
|
||||
json=plugin_data,
|
||||
headers={"Authorization": f"Bearer {config.api_key}"},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
result = response.json()
|
||||
click.echo(f"✅ Plugin registered successfully")
|
||||
click.echo(f"📋 Plugin ID: {result['plugin_id']}")
|
||||
click.echo(f"📦 Version: {result['version']}")
|
||||
click.echo(f"📝 Description: {result['description']}")
|
||||
else:
|
||||
click.echo(f"❌ Registration failed: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error registering plugin: {str(e)}", err=True)
|
||||
|
||||
@plugin_registry.command()
|
||||
@click.option('--plugin-id', help='Specific plugin ID (optional)')
|
||||
@click.option('--category', help='Filter by category')
|
||||
@click.option('--author', help='Filter by author')
|
||||
@click.option('--status', help='Filter by status')
|
||||
@click.option('--limit', type=int, default=20, help='Number of results to return')
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def list(plugin_id, category, author, status, limit, test_mode):
|
||||
"""List registered plugins"""
|
||||
try:
|
||||
if test_mode:
|
||||
# Mock data for testing
|
||||
mock_plugins = [
|
||||
{
|
||||
"plugin_id": "trading-bot",
|
||||
"name": "Advanced Trading Bot",
|
||||
"version": "1.0.0",
|
||||
"description": "Automated trading bot with advanced algorithms",
|
||||
"author": "AITBC Team",
|
||||
"category": "trading",
|
||||
"tags": ["trading", "automation", "bot"],
|
||||
"status": "active",
|
||||
"downloads": 1250,
|
||||
"rating": 4.5,
|
||||
"reviews_count": 42
|
||||
},
|
||||
{
|
||||
"plugin_id": "oracle-feed",
|
||||
"name": "Oracle Price Feed",
|
||||
"version": "2.1.0",
|
||||
"description": "Real-time price oracle integration",
|
||||
"author": "Oracle Developer",
|
||||
"category": "oracle",
|
||||
"tags": ["oracle", "price", "feed"],
|
||||
"status": "active",
|
||||
"downloads": 890,
|
||||
"rating": 4.8,
|
||||
"reviews_count": 28
|
||||
}
|
||||
]
|
||||
|
||||
click.echo("📋 Registered Plugins:")
|
||||
click.echo("=" * 60)
|
||||
|
||||
for plugin in mock_plugins[:limit]:
|
||||
click.echo(f"📦 {plugin['name']} (v{plugin['version']})")
|
||||
click.echo(f" 🆔 ID: {plugin['plugin_id']}")
|
||||
click.echo(f" 👤 Author: {plugin['author']}")
|
||||
click.echo(f" 📂 Category: {plugin['category']}")
|
||||
click.echo(f" ⭐ Rating: {plugin['rating']}/5.0 ({plugin['reviews_count']} reviews)")
|
||||
click.echo(f" 📥 Downloads: {plugin['downloads']}")
|
||||
click.echo(f" 📝 {plugin['description'][:60]}...")
|
||||
click.echo("")
|
||||
|
||||
return
|
||||
|
||||
# Fetch from registry service
|
||||
config = get_config()
|
||||
params = {
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
if plugin_id:
|
||||
params["plugin_id"] = plugin_id
|
||||
if category:
|
||||
params["category"] = category
|
||||
if author:
|
||||
params["author"] = author
|
||||
if status:
|
||||
params["status"] = status
|
||||
|
||||
response = requests.get(
|
||||
f"{config.coordinator_url}/api/v1/plugins",
|
||||
params=params,
|
||||
headers={"Authorization": f"Bearer {config.api_key}"},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
plugins = result.get("plugins", [])
|
||||
|
||||
click.echo("📋 Registered Plugins:")
|
||||
click.echo("=" * 60)
|
||||
|
||||
for plugin in plugins:
|
||||
click.echo(f"📦 {plugin['name']} (v{plugin['version']})")
|
||||
click.echo(f" 🆔 ID: {plugin['plugin_id']}")
|
||||
click.echo(f" 👤 Author: {plugin['author']}")
|
||||
click.echo(f" 📂 Category: {plugin['category']}")
|
||||
click.echo(f" ⭐ Rating: {plugin.get('rating', 0)}/5.0 ({plugin.get('reviews_count', 0)} reviews)")
|
||||
click.echo(f" 📥 Downloads: {plugin.get('downloads', 0)}")
|
||||
click.echo(f" 📝 {plugin['description'][:60]}...")
|
||||
click.echo("")
|
||||
else:
|
||||
click.echo(f"❌ Failed to list plugins: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error listing plugins: {str(e)}", err=True)
|
||||
|
||||
@plugin_registry.command()
|
||||
@click.argument('plugin_id')
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def info(plugin_id, test_mode):
|
||||
"""Get detailed information about a specific plugin"""
|
||||
try:
|
||||
if test_mode:
|
||||
# Mock data for testing
|
||||
mock_plugin = {
|
||||
"plugin_id": plugin_id,
|
||||
"name": "Advanced Trading Bot",
|
||||
"version": "1.0.0",
|
||||
"description": "Automated trading bot with advanced algorithms and machine learning capabilities",
|
||||
"author": "AITBC Team",
|
||||
"category": "trading",
|
||||
"tags": ["trading", "automation", "bot", "ml"],
|
||||
"repository": "https://github.com/aitbc/trading-bot",
|
||||
"homepage": "https://aitbc.dev/plugins/trading-bot",
|
||||
"license": "MIT",
|
||||
"status": "active",
|
||||
"created_at": "2024-01-15T10:30:00Z",
|
||||
"updated_at": "2024-03-01T14:20:00Z",
|
||||
"downloads": 1250,
|
||||
"rating": 4.5,
|
||||
"reviews_count": 42,
|
||||
"dependencies": ["exchange-integration", "oracle-feed"],
|
||||
"security_scan": {
|
||||
"status": "passed",
|
||||
"scan_date": "2024-03-01T14:20:00Z",
|
||||
"vulnerabilities": 0
|
||||
},
|
||||
"performance_metrics": {
|
||||
"cpu_usage": 2.5,
|
||||
"memory_usage": 512,
|
||||
"response_time_ms": 45
|
||||
}
|
||||
}
|
||||
|
||||
click.echo(f"📦 Plugin Information: {mock_plugin['name']}")
|
||||
click.echo("=" * 60)
|
||||
click.echo(f"🆔 Plugin ID: {mock_plugin['plugin_id']}")
|
||||
click.echo(f"📦 Version: {mock_plugin['version']}")
|
||||
click.echo(f"👤 Author: {mock_plugin['author']}")
|
||||
click.echo(f"📂 Category: {mock_plugin['category']}")
|
||||
click.echo(f"🏷️ Tags: {', '.join(mock_plugin['tags'])}")
|
||||
click.echo(f"📄 License: {mock_plugin['license']}")
|
||||
click.echo(f"📊 Status: {mock_plugin['status']}")
|
||||
click.echo(f"⭐ Rating: {mock_plugin['rating']}/5.0 ({mock_plugin['reviews_count']} reviews)")
|
||||
click.echo(f"📥 Downloads: {mock_plugin['downloads']}")
|
||||
click.echo(f"📅 Created: {mock_plugin['created_at']}")
|
||||
click.echo(f"🔄 Updated: {mock_plugin['updated_at']}")
|
||||
click.echo("")
|
||||
click.echo("📝 Description:")
|
||||
click.echo(f" {mock_plugin['description']}")
|
||||
click.echo("")
|
||||
click.echo("🔗 Links:")
|
||||
click.echo(f" 📦 Repository: {mock_plugin['repository']}")
|
||||
click.echo(f" 🌐 Homepage: {mock_plugin['homepage']}")
|
||||
click.echo("")
|
||||
click.echo("🔒 Security Scan:")
|
||||
click.echo(f" Status: {mock_plugin['security_scan']['status']}")
|
||||
click.echo(f" Scan Date: {mock_plugin['security_scan']['scan_date']}")
|
||||
click.echo(f" Vulnerabilities: {mock_plugin['security_scan']['vulnerabilities']}")
|
||||
click.echo("")
|
||||
click.echo("⚡ Performance Metrics:")
|
||||
click.echo(f" CPU Usage: {mock_plugin['performance_metrics']['cpu_usage']}%")
|
||||
click.echo(f" Memory Usage: {mock_plugin['performance_metrics']['memory_usage']}MB")
|
||||
click.echo(f" Response Time: {mock_plugin['performance_metrics']['response_time_ms']}ms")
|
||||
return
|
||||
|
||||
# Fetch from registry service
|
||||
config = get_config()
|
||||
response = requests.get(
|
||||
f"{config.coordinator_url}/api/v1/plugins/{plugin_id}",
|
||||
headers={"Authorization": f"Bearer {config.api_key}"},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
plugin = response.json()
|
||||
|
||||
click.echo(f"📦 Plugin Information: {plugin['name']}")
|
||||
click.echo("=" * 60)
|
||||
click.echo(f"🆔 Plugin ID: {plugin['plugin_id']}")
|
||||
click.echo(f"📦 Version: {plugin['version']}")
|
||||
click.echo(f"👤 Author: {plugin['author']}")
|
||||
click.echo(f"📂 Category: {plugin['category']}")
|
||||
click.echo(f"🏷️ Tags: {', '.join(plugin.get('tags', []))}")
|
||||
click.echo(f"📄 License: {plugin.get('license', 'N/A')}")
|
||||
click.echo(f"📊 Status: {plugin['status']}")
|
||||
click.echo(f"⭐ Rating: {plugin.get('rating', 0)}/5.0 ({plugin.get('reviews_count', 0)} reviews)")
|
||||
click.echo(f"📥 Downloads: {plugin.get('downloads', 0)}")
|
||||
click.echo(f"📅 Created: {plugin['created_at']}")
|
||||
click.echo(f"🔄 Updated: {plugin['updated_at']}")
|
||||
click.echo("")
|
||||
click.echo("📝 Description:")
|
||||
click.echo(f" {plugin['description']}")
|
||||
click.echo("")
|
||||
if plugin.get('repository'):
|
||||
click.echo("🔗 Links:")
|
||||
click.echo(f" 📦 Repository: {plugin['repository']}")
|
||||
if plugin.get('homepage'):
|
||||
click.echo(f" 🌐 Homepage: {plugin['homepage']}")
|
||||
else:
|
||||
click.echo(f"❌ Plugin not found: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error getting plugin info: {str(e)}", err=True)
|
||||
|
||||
@plugin_registry.command()
|
||||
@click.argument('plugin_id')
|
||||
@click.option('--version', required=True, help='New version number')
|
||||
@click.option('--changelog', required=True, help='Version changelog')
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def update_version(plugin_id, version, changelog, test_mode):
|
||||
"""Update plugin version"""
|
||||
try:
|
||||
update_data = {
|
||||
"version": version,
|
||||
"changelog": changelog,
|
||||
"updated_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
if test_mode:
|
||||
click.echo(f"✅ Plugin version updated (test mode)")
|
||||
click.echo(f"📦 Plugin ID: {plugin_id}")
|
||||
click.echo(f"📦 New Version: {version}")
|
||||
click.echo(f"📝 Changelog: {changelog}")
|
||||
return
|
||||
|
||||
# Send to registry service
|
||||
config = get_config()
|
||||
response = requests.put(
|
||||
f"{config.coordinator_url}/api/v1/plugins/{plugin_id}/version",
|
||||
json=update_data,
|
||||
headers={"Authorization": f"Bearer {config.api_key}"},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
click.echo(f"✅ Plugin version updated successfully")
|
||||
click.echo(f"📦 Plugin ID: {result['plugin_id']}")
|
||||
click.echo(f"📦 New Version: {result['version']}")
|
||||
click.echo(f"📝 Changelog: {changelog}")
|
||||
else:
|
||||
click.echo(f"❌ Version update failed: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error updating plugin version: {str(e)}", err=True)
|
||||
|
||||
@plugin_registry.command()
|
||||
@click.option('--query', help='Search query')
|
||||
@click.option('--category', help='Filter by category')
|
||||
@click.option('--tags', help='Filter by tags (comma-separated)')
|
||||
@click.option('--limit', type=int, default=10, help='Number of results')
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def search(query, category, tags, limit, test_mode):
|
||||
"""Search for plugins"""
|
||||
try:
|
||||
search_params = {
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
if query:
|
||||
search_params["query"] = query
|
||||
if category:
|
||||
search_params["category"] = category
|
||||
if tags:
|
||||
search_params["tags"] = tags.split(',')
|
||||
|
||||
if test_mode:
|
||||
# Mock search results
|
||||
mock_results = [
|
||||
{
|
||||
"plugin_id": "trading-bot",
|
||||
"name": "Advanced Trading Bot",
|
||||
"version": "1.0.0",
|
||||
"description": "Automated trading bot with advanced algorithms",
|
||||
"relevance_score": 0.95
|
||||
},
|
||||
{
|
||||
"plugin_id": "oracle-feed",
|
||||
"name": "Oracle Price Feed",
|
||||
"version": "2.1.0",
|
||||
"description": "Real-time price oracle integration",
|
||||
"relevance_score": 0.87
|
||||
}
|
||||
]
|
||||
|
||||
click.echo(f"🔍 Search Results for '{query or 'all'}':")
|
||||
click.echo("=" * 60)
|
||||
|
||||
for result in mock_results:
|
||||
click.echo(f"📦 {result['name']} (v{result['version']})")
|
||||
click.echo(f" 🆔 ID: {result['plugin_id']}")
|
||||
click.echo(f" 📝 {result['description'][:60]}...")
|
||||
click.echo(f" 📊 Relevance: {result['relevance_score']:.2f}")
|
||||
click.echo("")
|
||||
|
||||
return
|
||||
|
||||
# Search in registry service
|
||||
config = get_config()
|
||||
response = requests.get(
|
||||
f"{config.coordinator_url}/api/v1/plugins/search",
|
||||
params=search_params,
|
||||
headers={"Authorization": f"Bearer {config.api_key}"},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
plugins = result.get("plugins", [])
|
||||
|
||||
click.echo(f"🔍 Search Results for '{query or 'all'}':")
|
||||
click.echo("=" * 60)
|
||||
|
||||
for plugin in plugins:
|
||||
click.echo(f"📦 {plugin['name']} (v{plugin['version']})")
|
||||
click.echo(f" 🆔 ID: {plugin['plugin_id']}")
|
||||
click.echo(f" 📝 {plugin['description'][:60]}...")
|
||||
click.echo(f" 📊 Relevance: {plugin.get('relevance_score', 0):.2f}")
|
||||
click.echo("")
|
||||
else:
|
||||
click.echo(f"❌ Search failed: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error searching plugins: {str(e)}", err=True)
|
||||
|
||||
@plugin_registry.command()
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def status(test_mode):
|
||||
"""Get plugin registry status"""
|
||||
try:
|
||||
if test_mode:
|
||||
# Mock status data
|
||||
status_data = {
|
||||
"total_plugins": 156,
|
||||
"active_plugins": 142,
|
||||
"pending_plugins": 8,
|
||||
"inactive_plugins": 6,
|
||||
"total_downloads": 45678,
|
||||
"categories": {
|
||||
"trading": 45,
|
||||
"oracle": 32,
|
||||
"security": 28,
|
||||
"analytics": 25,
|
||||
"utility": 26
|
||||
},
|
||||
"recent_registrations": 12,
|
||||
"security_scans": {
|
||||
"passed": 148,
|
||||
"failed": 3,
|
||||
"pending": 5
|
||||
}
|
||||
}
|
||||
|
||||
click.echo("📊 Plugin Registry Status:")
|
||||
click.echo("=" * 40)
|
||||
click.echo(f"📦 Total Plugins: {status_data['total_plugins']}")
|
||||
click.echo(f"✅ Active Plugins: {status_data['active_plugins']}")
|
||||
click.echo(f"⏳ Pending Plugins: {status_data['pending_plugins']}")
|
||||
click.echo(f"❌ Inactive Plugins: {status_data['inactive_plugins']}")
|
||||
click.echo(f"📥 Total Downloads: {status_data['total_downloads']}")
|
||||
click.echo("")
|
||||
click.echo("📂 Categories:")
|
||||
for category, count in status_data['categories'].items():
|
||||
click.echo(f" {category}: {count}")
|
||||
click.echo("")
|
||||
click.echo("🔒 Security Scans:")
|
||||
click.echo(f" ✅ Passed: {status_data['security_scans']['passed']}")
|
||||
click.echo(f" ❌ Failed: {status_data['security_scans']['failed']}")
|
||||
click.echo(f" ⏳ Pending: {status_data['security_scans']['pending']}")
|
||||
return
|
||||
|
||||
# Get status from registry service
|
||||
config = get_config()
|
||||
response = requests.get(
|
||||
f"{config.coordinator_url}/api/v1/plugins/status",
|
||||
headers={"Authorization": f"Bearer {config.api_key}"},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
status = response.json()
|
||||
|
||||
click.echo("📊 Plugin Registry Status:")
|
||||
click.echo("=" * 40)
|
||||
click.echo(f"📦 Total Plugins: {status.get('total_plugins', 0)}")
|
||||
click.echo(f"✅ Active Plugins: {status.get('active_plugins', 0)}")
|
||||
click.echo(f"⏳ Pending Plugins: {status.get('pending_plugins', 0)}")
|
||||
click.echo(f"❌ Inactive Plugins: {status.get('inactive_plugins', 0)}")
|
||||
click.echo(f"📥 Total Downloads: {status.get('total_downloads', 0)}")
|
||||
click.echo(f"📈 Recent Registrations: {status.get('recent_registrations', 0)}")
|
||||
else:
|
||||
click.echo(f"❌ Failed to get status: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error getting status: {str(e)}", err=True)
|
||||
|
||||
# Helper function to get config
|
||||
def get_config():
|
||||
"""Get CLI configuration"""
|
||||
try:
|
||||
from config import get_config
|
||||
return get_config()
|
||||
except ImportError:
|
||||
# Fallback for testing
|
||||
from types import SimpleNamespace
|
||||
return SimpleNamespace(
|
||||
coordinator_url="http://localhost:8013",
|
||||
api_key="test-api-key"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
plugin_registry()
|
||||
Reference in New Issue
Block a user