"""Plugin management CLI (SPEC ยง11.1). python -m app.plugins.cli list python -m app.plugins.cli install python -m app.plugins.cli uninstall [--drop-data] Builtin (core) plugins refuse uninstall. """ from __future__ import annotations import argparse from app.plugins import loader, migrate def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser( prog="python -m app.plugins.cli", description="Manage webmail plugins" ) sub = parser.add_subparsers(dest="cmd", required=True) sub.add_parser("list", help="list discovered plugins") p_install = sub.add_parser("install", help="apply a plugin's migrations") p_install.add_argument("name") p_uninstall = sub.add_parser("uninstall", help="remove a plugin (refuses builtin)") p_uninstall.add_argument("name") p_uninstall.add_argument( "--drop-data", action="store_true", help="also drop the plugin's tables" ) args = parser.parse_args(argv) if args.cmd == "list": for plugin in loader.get_plugins(): kind = "builtin" if plugin.builtin else "removable" migr = "migrations" if plugin.migrations else "no-migrations" print(f"{plugin.name}\t{plugin.version}\t{kind}\t{migr}") return 0 if args.cmd == "install": migrate.install(args.name) print(f"installed {args.name}") return 0 if args.cmd == "uninstall": try: migrate.uninstall(args.name, drop_data=args.drop_data) except loader.PluginRemovalError as exc: print(f"error: {exc}") return 1 suffix = " (data dropped)" if args.drop_data else " (data kept)" print(f"uninstalled {args.name}{suffix}") return 0 return 1 if __name__ == "__main__": # pragma: no cover raise SystemExit(main())