"""Flask CLI commands for plugin management.""" import click from flask import current_app from flask.cli import with_appcontext @click.group('plugin') def plugin_cli(): """Plugin management commands.""" pass @plugin_cli.command('list') @with_appcontext def list_plugins(): """List all available plugins.""" pm = current_app.extensions.get('plugin_manager') if not pm: click.echo(click.style("Plugin manager not initialized", fg='red')) return plugins = pm.discover_available() if not plugins: click.echo("No plugins found in plugins directory.") return # Format output click.echo("") click.echo(click.style("Available Plugins:", fg='cyan', bold=True)) click.echo("-" * 60) for p in plugins: if p['enabled']: status = click.style("[Enabled]", fg='green') elif p['installed']: status = click.style("[Disabled]", fg='yellow') else: status = click.style("[Available]", fg='white') click.echo(f" {p['name']:20} v{p['version']:10} {status}") if p['description']: click.echo(f" {p['description'][:55]}...") if p['dependencies']: deps = ', '.join(p['dependencies']) click.echo(f" Dependencies: {deps}") click.echo("") @plugin_cli.command('install') @click.argument('name') @click.option('--skip-migrations', is_flag=True, help='Skip database migrations') @with_appcontext def install_plugin(name: str, skip_migrations: bool): """ Install a plugin. Usage: flask plugin install printers """ pm = current_app.extensions.get('plugin_manager') if not pm: click.echo(click.style("Plugin manager not initialized", fg='red')) raise SystemExit(1) click.echo(f"Installing plugin: {name}") if pm.install_plugin(name, run_migrations=not skip_migrations): click.echo(click.style(f"Successfully installed {name}", fg='green')) else: click.echo(click.style(f"Failed to install {name}", fg='red')) raise SystemExit(1) @plugin_cli.command('uninstall') @click.argument('name') @click.option('--remove-data', is_flag=True, help='Remove plugin database tables') @click.confirmation_option(prompt='Are you sure you want to uninstall this plugin?') @with_appcontext def uninstall_plugin(name: str, remove_data: bool): """ Uninstall a plugin. Usage: flask plugin uninstall printers """ pm = current_app.extensions.get('plugin_manager') if not pm: click.echo(click.style("Plugin manager not initialized", fg='red')) raise SystemExit(1) click.echo(f"Uninstalling plugin: {name}") if pm.uninstall_plugin(name, remove_data=remove_data): click.echo(click.style(f"Successfully uninstalled {name}", fg='green')) else: click.echo(click.style(f"Failed to uninstall {name}", fg='red')) raise SystemExit(1) @plugin_cli.command('enable') @click.argument('name') @with_appcontext def enable_plugin(name: str): """Enable a disabled plugin.""" pm = current_app.extensions.get('plugin_manager') if not pm: click.echo(click.style("Plugin manager not initialized", fg='red')) raise SystemExit(1) if pm.enable_plugin(name): click.echo(click.style(f"Enabled {name}", fg='green')) else: click.echo(click.style(f"Failed to enable {name}", fg='red')) raise SystemExit(1) @plugin_cli.command('disable') @click.argument('name') @with_appcontext def disable_plugin(name: str): """Disable an enabled plugin.""" pm = current_app.extensions.get('plugin_manager') if not pm: click.echo(click.style("Plugin manager not initialized", fg='red')) raise SystemExit(1) if pm.disable_plugin(name): click.echo(click.style(f"Disabled {name}", fg='green')) else: click.echo(click.style(f"Failed to disable {name}", fg='red')) raise SystemExit(1) @plugin_cli.command('info') @click.argument('name') @with_appcontext def plugin_info(name: str): """Show detailed information about a plugin.""" pm = current_app.extensions.get('plugin_manager') if not pm: click.echo(click.style("Plugin manager not initialized", fg='red')) raise SystemExit(1) plugin_class = pm.loader.load_plugin_class(name) if not plugin_class: click.echo(click.style(f"Plugin {name} not found", fg='red')) raise SystemExit(1) try: temp = plugin_class() meta = temp.meta except Exception as e: click.echo(click.style(f"Error loading plugin: {e}", fg='red')) raise SystemExit(1) state = pm.registry.get(name) click.echo("") click.echo("=" * 50) click.echo(click.style(f"Plugin: {meta.name}", fg='cyan', bold=True)) click.echo("=" * 50) click.echo(f"Version: {meta.version}") click.echo(f"Description: {meta.description}") click.echo(f"Author: {meta.author or 'Unknown'}") click.echo(f"API Prefix: {meta.api_prefix}") click.echo(f"Dependencies: {', '.join(meta.dependencies) or 'None'}") click.echo(f"Core Version: {meta.core_version}") click.echo("") if state: status = click.style('Enabled', fg='green') if state.enabled else click.style('Disabled', fg='yellow') click.echo(f"Status: {status}") click.echo(f"Installed: {state.installed_at}") click.echo(f"Migrations: {len(state.migrations_applied)} applied") else: click.echo(f"Status: {click.style('Not installed', fg='white')}") click.echo("") @plugin_cli.command('migrate') @click.argument('name') @click.option('--revision', default='head', help='Target revision') @with_appcontext def migrate_plugin(name: str, revision: str): """Run migrations for a specific plugin.""" pm = current_app.extensions.get('plugin_manager') if not pm: click.echo(click.style("Plugin manager not initialized", fg='red')) raise SystemExit(1) if not pm.registry.is_installed(name): click.echo(click.style(f"Plugin {name} is not installed", fg='red')) raise SystemExit(1) click.echo(f"Running migrations for {name}...") if pm.migration_manager.run_plugin_migrations(name, revision): click.echo(click.style("Migrations completed", fg='green')) else: click.echo(click.style("Migration failed", fg='red')) raise SystemExit(1)