Add missing plugin CLI commands and REST API endpoint
Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Integration Tests / test-service-integration (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
CLI Tests / test-cli (push) Has been cancelled

CLI:
- Added 'aitbc plugin create' - Create new plugin skeleton
- Added 'aitbc plugin package' - Package plugin for distribution
- Added 'aitbc market list-plugin' - List marketplace plugins

Marketplace service:
- Added GET /v1/marketplace/plugins endpoint
- Returns mock plugin data (cli_enhancer, web_dashboard, security_scanner)

Fixes scenario doc references to non-existent plugin commands
This commit is contained in:
aitbc
2026-05-15 00:24:29 +02:00
parent 398c14ed96
commit c4a2afee37
4 changed files with 198 additions and 0 deletions

View File

@@ -154,6 +154,122 @@ def uninstall(ctx, name: str):
output({"name": name, "status": "uninstalled"}, ctx.obj.get('output_format', 'table'))
@plugin.command()
@click.argument("name")
@click.option("--type", default="cli", help="Plugin type (cli, web, blockchain, ai)")
@click.option("--description", default="", help="Plugin description")
@click.option("--author", default="", help="Plugin author")
@click.pass_context
def create(ctx, name: str, type: str, description: str, author: str):
"""Create a new plugin skeleton"""
from .utils import output, success
plugin_dir = get_plugin_dir()
plugin_file = plugin_dir / f"{name}.py"
if plugin_file.exists():
from .utils import error
error(f"Plugin '{name}' already exists")
return
# Create plugin skeleton
template = f'''"""{name} - {description}"""
import click
@click.group()
def plugin_command():
"""{name} plugin commands"""
pass
@plugin_command.command()
def hello():
"""Hello from {name} plugin"""
click.echo("Hello from {name} plugin!")
'''
with open(plugin_file, "w") as f:
f.write(template)
# Update manifest
manifest_file = plugin_dir / "plugins.json"
manifest = {"plugins": []}
if manifest_file.exists():
with open(manifest_file) as f:
manifest = json.load(f)
manifest["plugins"].append({
"name": name,
"file": f"{name}.py",
"description": description,
"author": author,
"type": type,
"enabled": True
})
with open(manifest_file, "w") as f:
json.dump(manifest, f, indent=2)
success(f"Plugin '{name}' created")
output({"name": name, "file": str(plugin_file), "type": type}, ctx.obj.get('output_format', 'table'))
@plugin.command()
@click.argument("name")
@click.option("--output", default=".", help="Output directory")
@click.pass_context
def package(ctx, name: str, output: str):
"""Package a plugin for distribution"""
from .utils import output, success, error
import shutil
from pathlib import Path
import tarfile
plugin_dir = get_plugin_dir()
manifest_file = plugin_dir / "plugins.json"
if not manifest_file.exists():
error(f"Plugin '{name}' not found")
return
with open(manifest_file) as f:
manifest = json.load(f)
plugin_entry = next((p for p in manifest["plugins"] if p["name"] == name), None)
if not plugin_entry:
error(f"Plugin '{name}' not found")
return
plugin_file = plugin_dir / plugin_entry["file"]
if not plugin_file.exists():
error(f"Plugin file '{plugin_entry['file']}' not found")
return
# Create package
output_dir = Path(output)
output_dir.mkdir(parents=True, exist_ok=True)
package_file = output_dir / f"{name}.tar.gz"
with tarfile.open(package_file, "w:gz") as tar:
tar.add(plugin_file, arcname=plugin_file.name)
# Add metadata
metadata = json.dumps({
"name": name,
"description": plugin_entry.get("description", ""),
"author": plugin_entry.get("author", ""),
"type": plugin_entry.get("type", "cli"),
"version": "1.0.0"
})
metadata_file = output_dir / "metadata.json"
with open(metadata_file, "w") as f:
f.write(metadata)
tar.add(metadata_file, arcname="metadata.json")
metadata_file.unlink()
success(f"Plugin '{name}' packaged to {package_file}")
output({"name": name, "package": str(package_file)}, ctx.obj.get('output_format', 'table'))
@plugin.command()
@click.argument("name")
@click.argument("state", type=click.Choice(["enable", "disable"]))

View File

@@ -381,3 +381,37 @@ def handle_market_orders(args, default_coordinator_url, output_format, render_ma
except Exception as e:
print(f"Error getting orders: {e}")
return
def handle_market_list_plugins(args, default_coordinator_url, output_format, render_mapping):
"""Handle marketplace plugin listing command."""
marketplace_url = _marketplace_url(args, default_coordinator_url)
print(f"Getting marketplace plugins from {marketplace_url}...")
try:
response = requests.get(f"{marketplace_url}/v1/marketplace/plugins", timeout=10)
if response.status_code == 200:
plugins = response.json()
if output_format(args) == "json":
print(json.dumps(plugins, indent=2))
return
if isinstance(plugins, dict):
plugins = plugins.get("plugins", [])
print("Available marketplace plugins:")
if not plugins:
print(" No plugins found")
return
for plugin in plugins:
print(f" - ID: {plugin.get('id', 'N/A')}")
print(f" Name: {plugin.get('name', 'N/A')}")
print(f" Type: {plugin.get('type', 'N/A')}")
print(f" Author: {plugin.get('author', 'N/A')}")
print(f" Description: {plugin.get('description', 'N/A')}")
print(f" Version: {plugin.get('version', 'N/A')}")
else:
print(f"Query failed: {response.status_code}")
print(f"Error: {response.text}")
return
except Exception as e:
print(f"Error getting plugins: {e}")
return

View File

@@ -104,3 +104,8 @@ def register(subparsers: argparse._SubParsersAction, ctx: ParserContext) -> None
market_orders_parser.add_argument("--rpc-url", default=ctx.default_rpc_url)
market_orders_parser.add_argument("--marketplace-url")
market_orders_parser.set_defaults(handler=ctx.handle_market_orders)
market_plugins_parser = market_subparsers.add_parser("list-plugin", help="List marketplace plugins")
market_plugins_parser.add_argument("--coordinator-url", default=ctx.default_coordinator_url)
market_plugins_parser.add_argument("--marketplace-url")
market_plugins_parser.set_defaults(handler=ctx.handle_market_list_plugins)