diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8d102705..8c08a065 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 0.2.3 (unreleased)
+### Features / Enhancement
+- **CLI**. Allow user to use command-line interface to execute Crawlab programs.
+
# 0.2.2 (2019-05-30)
### Features / Enhancement
- **Automatic Extract Fields**: Automatically extracting data fields in list pages for configurable spider.
diff --git a/README-zh.md b/README-zh.md
index 88d83abc..94530fef 100644
--- a/README-zh.md
+++ b/README-zh.md
@@ -1,7 +1,7 @@
# Crawlab

-
+
diff --git a/README.md b/README.md
index 9bffb6ed..29433857 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Crawlab

-
+
diff --git a/crawlab/__init__.py b/crawlab/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/crawlab/bin/run_flower.py b/crawlab/flower.py
similarity index 100%
rename from crawlab/bin/run_flower.py
rename to crawlab/flower.py
diff --git a/crawlab/manage.py b/crawlab/manage.py
new file mode 100644
index 00000000..4e2eb613
--- /dev/null
+++ b/crawlab/manage.py
@@ -0,0 +1,74 @@
+import argparse
+import os
+import subprocess
+from multiprocessing import Process
+import sys
+
+BASE_DIR = os.path.dirname(__file__)
+
+APP_DESC = """
+Crawlab CLI tool.
+"""
+ACTION_LIST = [
+ 'serve',
+ 'app',
+ 'worker',
+ 'flower',
+ 'frontend',
+]
+if len(sys.argv) == 1:
+ sys.argv.append('--help')
+parser = argparse.ArgumentParser()
+parser.add_argument('action', type=str)
+# parser.add_argument('-q', '--quality', type=int, default=0,
+# help="download video quality : 1 for the standard-definition; 3 for the super-definition")
+args = parser.parse_args()
+
+
+def run_app():
+ p = subprocess.Popen([sys.executable, os.path.join(BASE_DIR, 'app.py')])
+ p.communicate()
+
+
+def run_flower():
+ p = subprocess.Popen([sys.executable, os.path.join(BASE_DIR, 'flower.py')])
+ p.communicate()
+
+
+def run_worker():
+ p = subprocess.Popen([sys.executable, os.path.join(BASE_DIR, 'worker.py')])
+ p.communicate()
+
+
+def run_frontend():
+ p = subprocess.Popen(['npm', 'run', 'serve'],
+ cwd=os.path.abspath(os.path.join(BASE_DIR, '..', 'frontend')))
+ p.communicate()
+
+
+def main():
+ p_app = Process(target=run_app)
+ p_flower = Process(target=run_flower)
+ p_worker = Process(target=run_worker)
+ p_frontend = Process(target=run_frontend)
+ if args.action == 'serve':
+ p_app.start()
+ p_flower.start()
+ p_worker.start()
+ p_frontend.start()
+ elif args.action == 'app':
+ p_app.start()
+ p_flower.start()
+ elif args.action == 'worker':
+ p_app.start()
+ p_worker.start()
+ elif args.action == 'flower':
+ p_flower.start()
+ elif args.action == 'frontend':
+ p_frontend.start()
+ else:
+ print(f'Invalid action: {args.action}')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/crawlab/bin/run_worker.py b/crawlab/worker.py
similarity index 100%
rename from crawlab/bin/run_worker.py
rename to crawlab/worker.py
diff --git a/frontend/package.json b/frontend/package.json
index 701463a3..e751c9f9 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "crawlab",
- "version": "0.2.1",
+ "version": "0.2.3",
"private": true,
"scripts": {
"serve": "cross-env NODE_ENV=development vue-cli-service serve --ip=0.0.0.0",