diff --git a/apps/marketplace-service/src/marketplace_service/main.py b/apps/marketplace-service/src/marketplace_service/main.py index 2b7b3376..52332906 100644 --- a/apps/marketplace-service/src/marketplace_service/main.py +++ b/apps/marketplace-service/src/marketplace_service/main.py @@ -246,6 +246,49 @@ async def get_analytics( return await svc.get_analytics(period_type=period_type) +@app.get("/v1/marketplace/plugins") +async def get_plugins( + type: str | None = None, + svc: MarketplaceService = Depends(get_marketplace_service), +): + """Get marketplace plugins""" + try: + logger.info(f"GET /v1/marketplace/plugins called with type={type}") + # Return mock plugin data for now + plugins = [ + { + "id": "plugin_001", + "name": "cli_enhancer", + "type": "cli", + "author": "AITBC Team", + "description": "Enhances CLI with additional commands", + "version": "1.0.0" + }, + { + "id": "plugin_002", + "name": "web_dashboard", + "type": "web", + "author": "AITBC Team", + "description": "Web dashboard plugin for monitoring", + "version": "1.2.0" + }, + { + "id": "plugin_003", + "name": "security_scanner", + "type": "blockchain", + "author": "Security Team", + "description": "Security scanning plugin for blockchain", + "version": "0.9.0" + } + ] + if type: + plugins = [p for p in plugins if p.get("type") == type] + return {"plugins": plugins} + except Exception as e: + logger.error(f"Error in GET /v1/marketplace/plugins: {type(e).__name__}: {str(e)}") + raise + + @app.post("/v1/transactions") async def submit_transaction(transaction_data: dict, session: AsyncSession = Depends(get_session_dep)): """Submit marketplace transaction""" diff --git a/cli/core/plugins.py b/cli/core/plugins.py index d227d265..38a0fbfb 100755 --- a/cli/core/plugins.py +++ b/cli/core/plugins.py @@ -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"])) diff --git a/cli/handlers/market.py b/cli/handlers/market.py index 470b5626..e53dc238 100644 --- a/cli/handlers/market.py +++ b/cli/handlers/market.py @@ -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 diff --git a/cli/parsers/market.py b/cli/parsers/market.py index 5d115b48..5cca60f8 100644 --- a/cli/parsers/market.py +++ b/cli/parsers/market.py @@ -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)