diff --git a/Dockerfile b/Dockerfile
index 27aefb4f469b3165f6ec9de3fb806239c7a10f87..47168adeea1a56ce6a49ae43b942b13a8ddbb6b5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -30,9 +30,12 @@ ADD . /km3buu
 
 RUN source /opt/rh/devtoolset-10/enable && \
     cd /km3buu && \
+    pip3 install --upgrade pip && \
     pip3 install setuptools-scm && \
     pip3 install pytest-runner && \
-    pip3 install -e .
+    pip3 install -e . && \
+    pip3 install -e ".[dev]" && \
+    pip3 install -e ".[extras]"
 
 RUN cd /km3buu/externals/km3net-dataformat/ && \
     make
diff --git a/Makefile b/Makefile
index d537c66ba2da0d3999ef47f934f94a27e963bf70..b0117ee32c37734dc31dc38c8767b6eb3a1eb45a 100644
--- a/Makefile
+++ b/Makefile
@@ -42,6 +42,7 @@ install:
 
 install-dev:
 	pip install -e ".[dev]"
+	pip install -e ".[extras]"	
 
 test:
 	python -m pytest --junitxml=./reports/junit.xml -o junit_suite_name=$(PKGNAME) $(PKGNAME)
diff --git a/requirements-dev.txt b/requirements/dev.txt
similarity index 100%
rename from requirements-dev.txt
rename to requirements/dev.txt
diff --git a/requirements/extras.txt b/requirements/extras.txt
new file mode 100644
index 0000000000000000000000000000000000000000..599852a59c4790a40fcb7d8a6a4142405762cc8d
--- /dev/null
+++ b/requirements/extras.txt
@@ -0,0 +1,3 @@
+docopt
+type-docopt
+tqdm
diff --git a/requirements.txt b/requirements/install.txt
similarity index 100%
rename from requirements.txt
rename to requirements/install.txt
diff --git a/scripts/runner.py b/scripts/runner.py
new file mode 100755
index 0000000000000000000000000000000000000000..f758563a8c9bf35d4a5f6e81dda63e1089892a89
--- /dev/null
+++ b/scripts/runner.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+# coding=utf-8
+# Filename: runner.py
+# Author: Johannes Schumann <jschumann@km3net.de>
+"""
+
+Usage:
+    runner.py fixed --energy=ENERGY --events=EVENTS (--CC|--NC) (--electron|--muon|--tau) --target-a=TARGETA --target-z=TARGETZ (--sphere=RADIUS | --can) [--outdir=OUTDIR] [--km3netfile=OUTFILE]
+    runner.py range --energy-min=ENERGYMIN --energy-max=ENERGYMAX --events=EVENTS (--CC|--NC) (--electron|--muon|--tau) --target-a=TARGETA --target-z=TARGETZ (--sphere=RADIUS | --can) [--flux=FLUXFILE] [--outdir=OUTDIR] [--km3netfile=OUTFILE]
+    runner.py (-h | --help)
+
+Options:
+    -h --help   Show this screen.
+    --energy=ENERGY                 Neutrino energy [type: float]                   
+    --energy-min=ENERGYMIN          Neutrino energy [type: float]                   
+    --energy-max=ENERGYMAX          Neutrino energy [type: float]                   
+    --events=EVENTS                 Number of simulated events [type: int]          
+    --target-a=TARGETA              Target nucleons [type: int]                     
+    --target-z=TARGETZ              Target protons [type: int]                      
+    --sphere=RADIUS                 Radius of the sphere volume in metres [type: float]
+    --can                           Use CAN with std. dimensions
+    --flux=FLUXFILE                 Flux definition [type: path]
+    --outdir=OUTDIR                 Output directory [type: path]
+    (--CC | --NC)                   Interaction type
+    (--electron | --muon | --tau)   Neutrino flavor
+"""
+from type_docopt import docopt
+from pathlib import Path
+from tempfile import TemporaryDirectory
+from os.path import join
+
+from km3buu.jobcard import generate_neutrino_jobcard
+from km3buu.ctrl import run_jobcard
+from km3buu.geometry import CanVolume, SphericalVolume
+from km3buu.output import GiBUUOutput, write_detector_file
+
+
+def main():
+    args = docopt(__doc__, types={'path': Path})
+
+    events = args["--events"]
+    energy = args["--energy"] if args["fixed"] else (args["--energy-min"],
+                                                     args["--energy-max"])
+    interaction = "CC" if args["--CC"] else "NC"
+    flavour = "electron" if args["--electron"] else (
+        "muon" if args["--muon"] else "tau")
+    target = (args["--target-z"], args["--target-a"])
+
+    jc = generate_neutrino_jobcard(events,
+                                   interaction,
+                                   flavour,
+                                   energy,
+                                   target,
+                                   fluxfile=args["--flux"])
+
+    outdir = args["--outdir"] if args["--outdir"] else TemporaryDirectory()
+    outdirname = outdir if args["--outdir"] else outdir.name
+
+    run_jobcard(jc, outdirname)
+
+    fobj = GiBUUOutput(outdir)
+
+    volume = SphericalVolume(
+        args["--sphere"]) if args["--sphere"] else CanVolume()
+
+    if args["fixed"]:
+        descriptor = "{0}_{1}_{2}GeV_A{3}Z{4}".format(flavour, interaction,
+                                                      energy, target[0],
+                                                      target[1])
+    else:
+        descriptor = "{0}_{1}_{2}-{3}GeV_A{4}Z{5}".format(
+            flavour, interaction, energy[0], energy[1], target[0], target[1])
+
+    if args["--km3netfile"]:
+        outfilename = args["--km3netfile"]
+    else:
+        outfilename = "km3buu_" + descriptor + ".root"
+        if args["--outdir"]:
+            outfilename = join(args["--outdir"], outfilename)
+
+    write_detector_file(fobj, geometry=volume, ofile=outfilename)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/setup.py b/setup.py
index e7c6f7346c1fc9555b83fbf9a46ebe3d6f4c19e4..02c36a6f4082e4141c9fec52cf86cb2fffa29c55 100644
--- a/setup.py
+++ b/setup.py
@@ -15,11 +15,11 @@ DESCRIPTION = 'GiBUU tools for KM3NeT'
 __author__ = 'Johannes Schumann'
 __email__ = 'jschumann@km3net.de'
 
-with open('requirements.txt') as fobj:
-    REQUIREMENTS = [l.strip() for l in fobj.readlines()]
 
-with open('requirements-dev.txt') as fobj:
-    DEV_REQUIREMENTS = [l.strip() for l in fobj.readlines()]
+def read_requirements(kind):
+    with open(os.path.join('requirements', kind + '.txt')) as fobj:
+        requirements = [l.strip() for l in fobj.readlines()]
+    return requirements
 
 setup(
     name=PACKAGE_NAME,
@@ -35,8 +35,11 @@ setup(
         'write_to': '{}/version.txt'.format(PACKAGE_NAME),
         'tag_regex': r'^(?P<prefix>v)?(?P<version>[^\+]+)(?P<suffix>.*)?$',
     },
-    install_requires=REQUIREMENTS,
-    extras_require={'dev': DEV_REQUIREMENTS},
+    install_requires=read_requirements("install"),
+    extras_require={
+        kind: read_requirements(kind)
+        for kind in ["dev", "extras"]
+    },
     python_requires='>=3.0',
     entry_points={'console_scripts': ['km3buu=km3buu.cmd:main']},
     classifiers=[