diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a021856d44d200d16f7ce37638fe7e1811295630
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,21 @@
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+  configuration: docs/source/conf.py
+  fail_on_warning: false
+
+# Optionally set the version of Python and requirements required to build your docs
+python:
+  version: 3.8
+  install:
+    - requirements: docs/requirements.txt
+    - method: pip
+      path: .
+      extra_requirements:
+        - docs
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8c3d97346d10554acfffb13cc0867b8609e11852
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1 @@
+iCalibrationDB @ git+https://xcalgitlab:${GITHUB_TOKEN}@git.xfel.eu/gitlab/detectors/cal_db_interactive.git@2.0.9
\ No newline at end of file
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 693a632c479a8138ec0597535dd386458686d595..5112b0010d5a26c2fbe9d20ac57d4604006997dc 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -13,6 +13,8 @@
 # All configuration values have a default; values that are commented out
 # serve to show the default.
 
+import glob
+
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
@@ -20,6 +22,23 @@
 # import os
 # import sys
 # sys.path.insert(0, os.path.abspath('.'))
+import os
+import shutil
+import sys
+import textwrap
+from datetime import datetime
+from subprocess import Popen, check_output
+from textwrap import dedent, indent
+from uuid import uuid4
+
+import nbformat
+import tabulate
+from dateutil.parser import parse
+from lxml import etree
+from nbconvert import RSTExporter
+
+# generate the list of available notebooks
+from xfel_calibrate import notebooks
 
 # -- General configuration ------------------------------------------------
 
@@ -39,11 +58,6 @@ extensions = [
     'sphinx.ext.viewcode',
 ]
 
-import glob
-import os
-import sys
-from subprocess import Popen
-
 sys.path.append(os.path.abspath("../pycalibration/"))
 p = Popen(["./makeAllDocs.sh"])
 p.communicate()
@@ -374,18 +388,7 @@ except:
     check_call(["wget", pandoc_url])
     check_call(["dpkg", "-i", pandoc_pack])
 
-import os
-from subprocess import check_output
-from textwrap import dedent, indent
-
-import nbformat
-from nbconvert import RSTExporter
-
-# generate the list of available notebooks
-from xfel_calibrate import notebooks
-
 rst_exporter = RSTExporter()
-
 with open("available_notebooks.rst", "w") as f:
     f.write(dedent("""
             .. _available_notebooks:
@@ -407,7 +410,8 @@ with open("available_notebooks.rst", "w") as f:
 
         for caltype in sorted(values.keys()):
             data = values[caltype]
-
+            if data.get("notebook", None) is None:
+                continue
             nbpath = os.path.abspath("{}/../../../{}".format(__file__, data["notebook"]))
             with open(nbpath, "r") as nf:
                 nb = nbformat.read(nf, as_version=4)
@@ -440,16 +444,7 @@ with open("available_notebooks.rst", "w") as f:
             f.write("\n\n")
 
 # add test results
-test_artefact_dir = os.path.realpath("../../tests/artefacts")
-
-import shutil
-import textwrap
-from datetime import datetime
-from uuid import uuid4
-
-import tabulate
-from dateutil.parser import parse
-from lxml import etree
+test_artefact_dir = os.path.realpath("../../tests/legacy/artefacts")
 
 
 def xml_to_rst_report(xml, git_tag, reports=[]):
@@ -575,7 +570,6 @@ Contents:
 """
 if not os.path.exists("./test_rsts"):
     os.makedirs("./test_rsts")
-
 with open("test_results.rst", "w") as f:
     f.write(header)
     for commit, modtime in sorted_dir(test_artefact_dir):
@@ -610,5 +604,6 @@ with open("test_results.rst", "w") as f:
                     fr.write(rst)
             f.write("   test_rsts/{}\n".format(commit))
 
+
 def setup(app):
     app.add_stylesheet('css/test_decorators.css')
diff --git a/notebooks/test/test-cli.ipynb b/notebooks/test/test-cli.ipynb
index 4367676b193dee076662a4142870f031d3988d83..daacfb22315d6a9edadd4991c31937751e721e74 100644
--- a/notebooks/test/test-cli.ipynb
+++ b/notebooks/test/test-cli.ipynb
@@ -75,9 +75,24 @@
   }
  ],
  "metadata": {
-  "language_info": {},
-  "orig_nbformat": 3
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.12"
+  }
  },
  "nbformat": 4,
- "nbformat_minor": 0
-}
\ No newline at end of file
+ "nbformat_minor": 4
+}
diff --git a/setup.py b/setup.py
index c0ed5ebc223ee07ee783be93fc4d61be0529ec3c..926dde6e6b04e67518b2151e5e596e04fd54f4c7 100644
--- a/setup.py
+++ b/setup.py
@@ -1,11 +1,12 @@
+import sys
 from distutils.command.build import build
 from subprocess import check_output
 
 import numpy
 from Cython.Build import cythonize
 from Cython.Distutils import build_ext
-from setuptools.extension import Extension
 from setuptools import find_packages, setup
+from setuptools.extension import Extension
 
 from src.xfel_calibrate.notebooks import notebooks
 
@@ -40,43 +41,10 @@ for ctypes in notebooks.values():
 
 data_files = list(filter(None, data_files))  # Get rid of `None` entries
 
-setup(
-    name="European XFEL Offline Calibration",
-    version="1.0",
-    author="Steffen Hauf",
-    author_email="steffen.hauf@xfel.eu",
-    maintainer="EuXFEL Calibration Team",
-    url="",
-    description="",
-    long_description="",
-    long_description_content_type="text/markdown",
-    # TODO: find licence, assuming this will be open sourced eventually
-    license="(c) European XFEL GmbH 2018",
-    packages=find_packages("src"),
-    package_dir={"": "src"},
-    package_data={
-        "xfel_calibrate": [
-            "bin/*.sh",
-            "titlepage.tmpl",
-            "xfel.pdf",
-        ]
-        + data_files
-    },
-    entry_points={
-        "console_scripts": [
-            "xfel-calibrate = xfel_calibrate.calibrate:run",
-        ],
-    },
-    cmdclass={
-        "build": PreInstallCommand,
-        "build_ext": build_ext,
-    },
-    ext_modules=cythonize(ext_modules, language_level=3),
-    install_requires=[
-        "iCalibrationDB @ git+ssh://git@git.xfel.eu:10022/detectors/cal_db_interactive.git@2.0.9",  # noqa
-        "XFELDetectorAnalysis @ git+ssh://git@git.xfel.eu:10022/karaboDevices/pyDetLib.git@2.7.0",  # noqa
+install_requires = [
         "Cython==0.29.21",
         "Jinja2==2.11.2",
+        "markupsafe==2.0.1",
         "astcheck==0.2.5",
         "astsearch==0.2.0",
         "cfelpyutils==1.0.1",
@@ -121,7 +89,47 @@ setup(
         "sphinx==1.8.5",
         "tabulate==0.8.6",
         "traitlets==4.3.3",
-    ],
+]
+
+if "readthedocs.org" not in sys.executable:
+    install_requires += [
+        "iCalibrationDB @ git+ssh://git@git.xfel.eu:10022/detectors/cal_db_interactive.git@2.0.9",  # noqa
+        "XFELDetectorAnalysis @ git+ssh://git@git.xfel.eu:10022/karaboDevices/pyDetLib.git@2.7.0",  # noqa
+    ]
+
+setup(
+    name="European XFEL Offline Calibration",
+    version="1.0",
+    author="Steffen Hauf",
+    author_email="steffen.hauf@xfel.eu",
+    maintainer="EuXFEL Calibration Team",
+    url="",
+    description="",
+    long_description="",
+    long_description_content_type="text/markdown",
+    # TODO: find licence, assuming this will be open sourced eventually
+    license="(c) European XFEL GmbH 2018",
+    packages=find_packages("src"),
+    package_dir={"": "src"},
+    package_data={
+        "xfel_calibrate": [
+            "bin/*.sh",
+            "titlepage.tmpl",
+            "xfel.pdf",
+        ]
+        + data_files
+    },
+    entry_points={
+        "console_scripts": [
+            "xfel-calibrate = xfel_calibrate.calibrate:run",
+        ],
+    },
+    cmdclass={
+        "build": PreInstallCommand,
+        "build_ext": build_ext,
+    },
+    ext_modules=cythonize(ext_modules, language_level=3),
+    install_requires=install_requires,
     extras_require={
         "docs": [
             "nbsphinx",
diff --git a/src/xfel_calibrate/calibrate.py b/src/xfel_calibrate/calibrate.py
index 19f46fdef2644cd8e831c31fb1d94d873c82a1f4..b0cc47655858d49218bd484da032f1426d376932 100755
--- a/src/xfel_calibrate/calibrate.py
+++ b/src/xfel_calibrate/calibrate.py
@@ -558,7 +558,8 @@ def make_par_table(parms, run_tmp_path: str):
 def run(argv=None):
     """ Run a calibration task with parser arguments """
     # Ensure files are opened as UTF-8 by default, regardless of environment.
-    locale.setlocale(locale.LC_CTYPE, ('en_US', 'UTF-8'))
+    if "readthedocs.org" not in sys.executable:
+        locale.setlocale(locale.LC_CTYPE, ('en_US', 'UTF-8'))
 
     if argv is None:
         argv = sys.argv
diff --git a/src/xfel_calibrate/nb_args.py b/src/xfel_calibrate/nb_args.py
index bdf4a7d5e7ab7bb7e5ce62c9011ffa38ceed274f..e768d9f63a39b59865c28f4c5b0d6b39582edeaa 100644
--- a/src/xfel_calibrate/nb_args.py
+++ b/src/xfel_calibrate/nb_args.py
@@ -317,7 +317,8 @@ def extend_params(nb, extend_func_name, argv):
 
     # Make a temporary parser that won't exit if it sees -h or --help
     pre_parser = make_initial_parser(add_help=False)
-    add_args_from_nb(nb, pre_parser, no_required=True)
+    params = extract_parameters(nb, lang='python')
+    add_args_from_nb(params, pre_parser, no_required=True)
     known, _ = pre_parser.parse_known_args(argv[1:])
     args = deconsolize_args(vars(known))
 
@@ -328,7 +329,7 @@ def extend_params(nb, extend_func_name, argv):
 
     extension = f(*[args[p] for p in sig.parameters])
     fcc = first_code_cell(nb)
-    fcc["source"] += "\n" + extension
+    fcc["source"] += "\n" + extension if extension else "\n"
 
 
 @dataclass