3df609b
diff --git a/module_build_service/manage.py b/module_build_service/manage.py
3df609b
index 925b5031..1215bdba 100755
3df609b
--- a/module_build_service/manage.py
3df609b
+++ b/module_build_service/manage.py
3df609b
@@ -5,10 +5,9 @@
3df609b
-from functools import wraps
3df609b
+import click
3df609b
 import getpass
3df609b
 import logging
3df609b
 import os
3df609b
 import sys
3df609b
-import textwrap
3df609b
 
3df609b
 import flask_migrate
3df609b
-from flask_script import Manager, prompt_bool
3df609b
+from flask.cli import FlaskGroup
3df609b
 from werkzeug.datastructures import FileStorage
3df609b
@@ -21,74 +20,34 @@ from module_build_service.builder.MockModuleBuilder import (
3df609b
 from module_build_service.common.errors import StreamAmbigous, StreamNotXyz
3df609b
-from module_build_service.common.logger import level_flags
3df609b
 from module_build_service.common.utils import load_mmd_file, import_mmd
3df609b
 import module_build_service.scheduler.consumer
3df609b
 from module_build_service.scheduler.db_session import db_session
3df609b
 import module_build_service.scheduler.local
3df609b
 from module_build_service.web.submit import submit_module_build_from_yaml
3df609b
 
3df609b
-
3df609b
-def create_app(debug=False, verbose=False, quiet=False):
3df609b
-    # logging (intended for flask-script, see manage.py)
3df609b
-    log = logging.getLogger(__name__)
3df609b
-    if debug:
3df609b
-        log.setLevel(level_flags["debug"])
3df609b
-    elif verbose:
3df609b
-        log.setLevel(level_flags["verbose"])
3df609b
-    elif quiet:
3df609b
-        log.setLevel(level_flags["quiet"])
3df609b
-
3df609b
-    return app
3df609b
-
3df609b
-
3df609b
-manager = Manager(create_app)
3df609b
-help_args = ("-?", "--help")
3df609b
-manager.help_args = help_args
3df609b
-migrations_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)),
3df609b
-                              'migrations')
3df609b
+migrations_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "migrations")
3df609b
 migrate = flask_migrate.Migrate(app, db, directory=migrations_dir)
3df609b
-manager.add_command("db", flask_migrate.MigrateCommand)
3df609b
-manager.add_option("-d", "--debug", dest="debug", action="store_true")
3df609b
-manager.add_option("-v", "--verbose", dest="verbose", action="store_true")
3df609b
-manager.add_option("-q", "--quiet", dest="quiet", action="store_true")
3df609b
 
3df609b
 
3df609b
-def console_script_help(f):
3df609b
-    @wraps(f)
3df609b
-    def wrapped(*args, **kwargs):
3df609b
-        import sys
3df609b
+@click.group(cls=FlaskGroup, create_app=lambda *args, **kwargs: app)
3df609b
+def cli():
3df609b
+    """MBS manager"""
3df609b
 
3df609b
-        if any([arg in help_args for arg in sys.argv[1:]]):
3df609b
-            command = os.path.basename(sys.argv[0])
3df609b
-            print(textwrap.dedent(
3df609b
-                """\
3df609b
-                    {0}
3df609b
-
3df609b
-                    Usage: {0} [{1}]
3df609b
-
3df609b
-                    See also:
3df609b
-                    mbs-manager(1)
3df609b
-                """).strip().format(command, "|".join(help_args))
3df609b
-            )
3df609b
-            sys.exit(2)
3df609b
-        r = f(*args, **kwargs)
3df609b
-        return r
3df609b
 
3df609b
-    return wrapped
3df609b
-
3df609b
-
3df609b
-@console_script_help
3df609b
-@manager.command
3df609b
+@cli.command("upgradedb")
3df609b
 def upgradedb():
3df609b
     """ Upgrades the database schema to the latest revision
3df609b
     """
3df609b
     app.config["SERVER_NAME"] = "localhost"
3df609b
-    # TODO: configurable?
3df609b
-    migrations_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "migrations")
3df609b
     with app.app_context():
3df609b
         flask_migrate.upgrade(directory=migrations_dir)
3df609b
 
3df609b
 
3df609b
-@manager.command
3df609b
+def upgradedb_entrypoint():
3df609b
+    """Entrypoint for command mbs-upgradedb"""
3df609b
+    upgradedb()
3df609b
+
3df609b
+
3df609b
+@cli.command("cleardb")
3df609b
 def cleardb():
3df609b
     """ Clears the database
3df609b
     """
3df609b
@@ -96,8 +53,8 @@ def cleardb():
3df609b
     models.ComponentBuild.query.delete()
3df609b
 
3df609b
 
3df609b
-@console_script_help
3df609b
-@manager.command
3df609b
+@cli.command("import_module")
3df609b
+@click.argument("mmd_file", type=click.Path(exists=True))
3df609b
 def import_module(mmd_file):
3df609b
     """ Imports the module from mmd_file
3df609b
     """
3df609b
@@ -117,41 +74,56 @@ def import_module(mmd_file):
3df609b
     return collected
3df609b
 
3df609b
 
3df609b
-@manager.option("--stream", action="store", dest="stream")
3df609b
-@manager.option("--file", action="store", dest="yaml_file")
3df609b
-@manager.option("--srpm", action="append", default=[], dest="srpms", metavar="SRPM")
3df609b
-@manager.option("--skiptests", action="store_true", dest="skiptests")
3df609b
-@manager.option("--offline", action="store_true", dest="offline")
3df609b
-@manager.option(
3df609b
-    '--buildrequires', action='append', metavar='name:stream',
3df609b
-    dest='buildrequires', default=[],
3df609b
-    help='Buildrequires to override in the form of "name:stream"'
3df609b
-)
3df609b
-@manager.option(
3df609b
-    '--requires', action='append', metavar='name:stream',
3df609b
-    dest='requires', default=[],
3df609b
-    help='Requires to override in the form of "name:stream"'
3df609b
-)
3df609b
-@manager.option("-d", "--debug", action="store_true", dest="log_debug")
3df609b
-@manager.option("-l", "--add-local-build", action="append", default=None, dest="local_build_nsvs")
3df609b
-@manager.option("-s", "--set-stream", action="append", default=[], dest="default_streams")
3df609b
-@manager.option(
3df609b
-    "-r", "--platform-repo-file", action="append", default=[], dest="platform_repofiles"
3df609b
+@cli.command("build_module_locally")
3df609b
+@click.option("--stream", metavar="STREAM")
3df609b
+@click.option(
3df609b
+    "--file", "yaml_file",
3df609b
+    metavar="FILE",
3df609b
+    required=True,
3df609b
+    type=click.Path(exists=True),
3df609b
+)
3df609b
+@click.option("--srpm", "srpms", metavar="SRPM", multiple=True)
3df609b
+@click.option("--skiptests", is_flag=True)
3df609b
+@click.option("--offline", is_flag=True)
3df609b
+@click.option(
3df609b
+    '--buildrequires', "buildrequires", multiple=True,
3df609b
+    metavar='name:stream', default=[],
3df609b
+    help='Buildrequires to override in the form of "name:stream"'
3df609b
+)
3df609b
+@click.option(
3df609b
+    '--requires', "requires", multiple=True,
3df609b
+    metavar='name:stream', default=[],
3df609b
+    help='Requires to override in the form of "name:stream"'
3df609b
+)
3df609b
+@click.option("-d", "--debug", "log_debug", is_flag=True)
3df609b
+@click.option(
3df609b
+    "-l", "--add-local-build", "local_build_nsvs",
3df609b
+    metavar="NSV", multiple=True
3df609b
+)
3df609b
+@click.option(
3df609b
+    "-s", "--set-stream", "default_streams",
3df609b
+    metavar="STREAM", multiple=True
3df609b
 )
3df609b
-@manager.option("-p", "--platform-id", action="store", default=None, dest="platform_id")
3df609b
+@click.option(
3df609b
+    "-r", "--platform-repo-file", "platform_repofiles",
3df609b
+    metavar="FILE",
3df609b
+    type=click.Path(exists=True),
3df609b
+    multiple=True
3df609b
+)
3df609b
+@click.option("-p", "--platform-id", metavar="PLATFORM_ID")
3df609b
 def build_module_locally(
3df609b
-    local_build_nsvs=None,
3df609b
+    stream=None,
3df609b
     yaml_file=None,
3df609b
     srpms=None,
3df609b
-    stream=None,
3df609b
     skiptests=False,
3df609b
-    default_streams=None,
3df609b
     offline=False,
3df609b
+    log_debug=False,
3df609b
+    local_build_nsvs=None,
3df609b
+    default_streams=None,
3df609b
     platform_repofiles=None,
3df609b
     platform_id=None,
3df609b
     requires=None,
3df609b
     buildrequires=None,
3df609b
-    log_debug=False,
3df609b
 ):
3df609b
     """ Performs local module build using Mock
3df609b
     """
3df609b
@@ -233,14 +193,11 @@ def build_module_locally(
3df609b
         sys.exit(1)
3df609b
 
3df609b
 
3df609b
-@manager.option(
3df609b
-    "identifier",
3df609b
-    metavar="NAME:STREAM[:VERSION[:CONTEXT]]",
3df609b
-    help="Identifier for selecting module builds to retire",
3df609b
-)
3df609b
-@manager.option(
3df609b
+@cli.command("retire")
3df609b
+@click.argument("identifier", metavar="NAME:STREAM[:VERSION[:CONTEXT]]")
3df609b
+@click.option(
3df609b
     "--confirm",
3df609b
-    action="store_true",
3df609b
+    is_flag=True,
3df609b
     default=False,
3df609b
     help="Perform retire operation without prompting",
3df609b
 )
3df609b
@@ -273,7 +244,8 @@ def retire(identifier, confirm=False):
3df609b
         logging.info("\t%s", ":".join((build.name, build.stream, build.version, build.context)))
3df609b
 
3df609b
     # Prompt for confirmation
3df609b
-    is_confirmed = confirm or prompt_bool("Retire {} module builds?".format(len(module_builds)))
3df609b
+    confirm_msg = "Retire {} module builds?".format(len(module_builds))
3df609b
+    is_confirmed = confirm or click.confirm(confirm_msg, abort=False)
3df609b
     if not is_confirmed:
3df609b
         logging.info("Module builds were NOT retired.")
3df609b
         return
3df609b
@@ -288,8 +260,10 @@ def retire(identifier, confirm=False):
3df609b
     logging.info("Module builds retired.")
3df609b
 
3df609b
 
3df609b
-@console_script_help
3df609b
-@manager.command
3df609b
+@cli.command("run")
3df609b
+@click.option("-h", "--host", metavar="HOST", help="Bind to this host.")
3df609b
+@click.option("-p", "--port", metavar="PORT", help="Bind to this port along with --host.")
3df609b
+@click.option("-d", "--debug", is_flag=True, default=False, help="Run frontend in debug mode.")
3df609b
 def run(host=None, port=None, debug=None):
3df609b
     """ Runs the Flask app, locally. Intended for dev instances, should not be used for production.
3df609b
     """
3df609b
@@ -302,9 +276,5 @@ def run(host=None, port=None, debug=None):
3df609b
     app.run(host=host, port=port, debug=debug)
3df609b
 
3df609b
 
3df609b
-def manager_wrapper():
3df609b
-    manager.run()
3df609b
-
3df609b
-
3df609b
 if __name__ == "__main__":
3df609b
-    manager_wrapper()
3df609b
+    cli()
3df609b
diff --git a/requirements.txt b/requirements.txt
3df609b
index 9ea96f50..17ea4df0 100644
3df609b
--- a/requirements.txt
3df609b
+++ b/requirements.txt
3df609b
@@ -1,10 +1,10 @@
3df609b
+click
3df609b
 distro
3df609b
 dogpile.cache
3df609b
 enum34
3df609b
 fedmsg
3df609b
 Flask
3df609b
 Flask-Migrate
3df609b
-Flask-Script
3df609b
 Flask-SQLAlchemy
3df609b
 funcsigs # Python2 only
3df609b
 futures # Python 2 only
3df609b
diff --git a/setup.py b/setup.py
3df609b
index 7eac415c..09cd3b23 100644
3df609b
--- a/setup.py
3df609b
+++ b/setup.py
3df609b
@@ -39,9 +39,9 @@ setup(
3df609b
     dependency_links=deps_links,
3df609b
     entry_points={
3df609b
         "console_scripts": [
3df609b
-            "mbs-upgradedb = module_build_service.manage:upgradedb",
3df609b
+            "mbs-upgradedb = module_build_service.manage:upgradedb_entrypoint",
3df609b
             "mbs-frontend = module_build_service.manage:run",
3df609b
-            "mbs-manager = module_build_service.manage:manager_wrapper",
3df609b
+            "mbs-manager = module_build_service.manage:cli",
3df609b
         ],
3df609b
         "moksha.consumer": "mbsconsumer = module_build_service.scheduler.consumer:MBSConsumer",
3df609b
         "mbs.messaging_backends": [
3df609b
diff --git a/tests/test_manage.py b/tests/test_manage.py
3df609b
index 6d3026c8..44ab8638 100644
3df609b
--- a/tests/test_manage.py
3df609b
+++ b/tests/test_manage.py
3df609b
@@ -5,10 +5,9 @@ from __future__ import absolute_import
3df609b
 from mock import patch
3df609b
 import pytest
3df609b
 
3df609b
-from module_build_service import app
3df609b
+from module_build_service import app, manage as mbs_manager
3df609b
 from module_build_service.common import models
3df609b
 from module_build_service.common.models import BUILD_STATES, ModuleBuild
3df609b
-from module_build_service.manage import manager_wrapper, retire
3df609b
 from module_build_service.scheduler.db_session import db_session
3df609b
 from module_build_service.web.utils import deps_to_dict
3df609b
 from tests import clean_database, staged_data_filename
3df609b
@@ -30,10 +29,12 @@ class TestMBSManage:
3df609b
     )
3df609b
     def test_retire_identifier_validation(self, identifier, is_valid):
3df609b
         if is_valid:
3df609b
-            retire(identifier)
3df609b
+            with pytest.raises(SystemExit) as exc_info:
3df609b
+                mbs_manager.cli(["retire", identifier])
3df609b
+                assert 0 == exc_info
3df609b
         else:
3df609b
             with pytest.raises(ValueError):
3df609b
-                retire(identifier)
3df609b
+                mbs_manager.cli(["retire", identifier])
3df609b
 
3df609b
     @pytest.mark.parametrize(
3df609b
         ("overrides", "identifier", "changed_count"),
3df609b
@@ -47,9 +48,9 @@ class TestMBSManage:
3df609b
             ({"context": "pickme"}, "spam:eggs:ham", 2),
3df609b
         ),
3df609b
     )
3df609b
-    @patch("module_build_service.manage.prompt_bool")
3df609b
-    def test_retire_build(self, prompt_bool, overrides, identifier, changed_count):
3df609b
-        prompt_bool.return_value = True
3df609b
+    @patch("click.confirm")
3df609b
+    def test_retire_build(self, confirm, overrides, identifier, changed_count):
3df609b
+        confirm.return_value = True
3df609b
 
3df609b
         module_builds = (
3df609b
             db_session.query(ModuleBuild)
3df609b
@@ -71,7 +72,10 @@ class TestMBSManage:
3df609b
 
3df609b
         db_session.commit()
3df609b
 
3df609b
-        retire(identifier)
3df609b
+        with pytest.raises(SystemExit) as exc_info:
3df609b
+            mbs_manager.cli(["retire", identifier])
3df609b
+            assert 0 == exc_info.value
3df609b
+
3df609b
         retired_module_builds = (
3df609b
             db_session.query(ModuleBuild)
3df609b
             .filter_by(state=BUILD_STATES["garbage"])
3df609b
@@ -93,11 +97,11 @@ class TestMBSManage:
3df609b
             (False, True, True)
3df609b
         ),
3df609b
     )
3df609b
-    @patch("module_build_service.manage.prompt_bool")
3df609b
+    @patch("click.confirm")
3df609b
     def test_retire_build_confirm_prompt(
3df609b
-        self, prompt_bool, confirm_prompt, confirm_arg, confirm_expected
3df609b
+        self, confirm, confirm_prompt, confirm_arg, confirm_expected
3df609b
     ):
3df609b
-        prompt_bool.return_value = confirm_prompt
3df609b
+        confirm.return_value = confirm_prompt
3df609b
 
3df609b
         module_builds = db_session.query(ModuleBuild).filter_by(state=BUILD_STATES["ready"]).all()
3df609b
         # Verify our assumption of the amount of ModuleBuilds in database
3df609b
@@ -106,15 +110,17 @@ class TestMBSManage:
3df609b
         for x, build in enumerate(module_builds):
3df609b
             build.name = "spam" + str(x) if x > 0 else "spam"
3df609b
             build.stream = "eggs"
3df609b
-
3df609b
         db_session.commit()
3df609b
 
3df609b
-        retire("spam:eggs", confirm_arg)
3df609b
+        cmd = ["retire", "spam:eggs"] + (["--confirm"] if confirm_arg else [])
3df609b
+        with pytest.raises(SystemExit) as exc_info:
3df609b
+            mbs_manager.cli(cmd)
3df609b
+            assert 0 == exc_info.value
3df609b
+
3df609b
+        expected_changed_count = 1 if confirm_expected else 0
3df609b
         retired_module_builds = (
3df609b
             db_session.query(ModuleBuild).filter_by(state=BUILD_STATES["garbage"]).all()
3df609b
         )
3df609b
-
3df609b
-        expected_changed_count = 1 if confirm_expected else 0
3df609b
         assert len(retired_module_builds) == expected_changed_count
3df609b
 
3df609b
 
3df609b
@@ -156,7 +162,7 @@ class TestCommandBuildModuleLocally:
3df609b
         original_db_uri = app.config["SQLALCHEMY_DATABASE_URI"]
3df609b
         try:
3df609b
             with patch("sys.argv", new=cli_cmd):
3df609b
-                manager_wrapper()
3df609b
+                mbs_manager.cli()
3df609b
         finally:
3df609b
             app.config["SQLALCHEMY_DATABASE_URI"] = original_db_uri
3df609b
 
3df609b