diff --git a/.gitignore b/.gitignore
index bbbd4e1fed3e6666ccd721036b1b7a6edfe9cb3a..6bccaaee4d029027cf3491ecbff30df8b3054aad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@
 .pdm-python
 
 #   build artifacts
+dist
 *.tar.gz
 *.whl
 *.egg-info
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000000000000000000000000000000000000..62a5456a9347c8768b5305270b1e850ff5b26c8a
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1 @@
+include src/pystencilssfg/cmake/*.cmake
\ No newline at end of file
diff --git a/cmake/FindPystencilsSfg.cmake b/cmake/FindPystencilsSfg.cmake
index 27eb1c01bdb06a7cf730d229f423c64f0dc68a6d..bdc5132d42335afe8cbce49872ddf5d6c0b51b48 100644
--- a/cmake/FindPystencilsSfg.cmake
+++ b/cmake/FindPystencilsSfg.cmake
@@ -1,5 +1,4 @@
 set( PystencilsSfg_FOUND OFF CACHE BOOL "pystencils source file generator found" )
-set( PystencilsSfg_CONFIGURATOR_SCRIPT "" CACHE STRING "Configurator script for the pystencils source file generator" )
 
 mark_as_advanced( PystencilsSfg_FOUND )
 
diff --git a/pyproject.toml b/pyproject.toml
index e8ce266e1c0ec9c2b2e02a0deab988926ecc88b4..4e241ff535ccbeb055f7d6621854852d1aa4b62f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,6 +13,9 @@ readme = "README.md"
 license = {text = "noneyet"}
 dynamic = ["version"]
 
+[project.scripts]
+sfg-cli = "pystencilssfg.cli:cli_main"
+
 [build-system]
 requires = ["setuptools>=61", "versioneer>=0.29"]
 build-backend = "setuptools.build_meta"
diff --git a/src/pystencilssfg/__main__.py b/src/pystencilssfg/__main__.py
index 3c793c464fd8762c21bbcf932e81496d09019a84..ce41802d0392490b67433f3429ac38a90d3373f2 100644
--- a/src/pystencilssfg/__main__.py
+++ b/src/pystencilssfg/__main__.py
@@ -1,80 +1,4 @@
-import sys
-from os import path
 
-from argparse import ArgumentParser
-
-from .configuration import (
-    SfgConfigException, SfgConfigSource,
-    add_config_args_to_parser, config_from_parser_args, merge_configurations
-)
-
-
-def main():
-    parser = ArgumentParser("pystencilssfg",
-                            description="pystencilssfg command-line utility")
-
-    subparsers = parser.add_subparsers(required=True, title="Subcommands")
-
-    version_parser = subparsers.add_parser(
-        "version", help="Print version and exit.")
-    version_parser.set_defaults(func=version)
-
-    outfiles_parser = subparsers.add_parser(
-        "list-files", help="List files produced by given codegen script.")
-
-    outfiles_parser.set_defaults(func=list_files)
-    outfiles_parser.add_argument(
-        "--format", type=str, choices=("human", "cmake"), default="human")
-    outfiles_parser.add_argument("codegen_script", type=str)
-
-    add_config_args_to_parser(outfiles_parser)
-
-    args = parser.parse_args()
-    args.func(args)
-
-
-def version(args, argv):
-    from . import __version__
-    print(__version__)
-    exit(0)
-
-
-def list_files(args):
-    try:
-        project_config, cmdline_config = config_from_parser_args(args)
-    except SfgConfigException as exc:
-        abort_with_config_exception(exc)
-
-    config = merge_configurations(project_config, cmdline_config, None)
-
-    scriptdir, scriptname = path.split(args.codegen_script)
-    basename = path.splitext(scriptname)[0]
-
-    from .emitters.cpu.basic_cpu import BasicCpuEmitter
-
-    emitter = BasicCpuEmitter(basename, config)
-
-    match args.format:
-        case "human": print(" ".join(emitter.output_files))
-        case "cmake": print(";".join(emitter.output_files), end="")
-
-    exit(0)
-
-
-def abort_with_config_exception(exception: SfgConfigException):
-    def eprint(*args, **kwargs):
-        print(*args, file=sys.stderr, **kwargs)
-
-    match exception.config_source:
-        case SfgConfigSource.PROJECT:
-            eprint(
-                f"Invalid project configuration: {exception.message}\nCheck your configurator script.")
-        case SfgConfigSource.COMMANDLINE:
-            eprint(
-                f"Invalid configuration on command line: {exception.message}")
-        case _: assert False, "(Theoretically) unreachable code. Contact the developers."
-
-    exit(1)
-
-
-main()
+if __name__ == "__main__":
+    from .cli import cli_main
+    cli_main("python -m pystencilssfg")
diff --git a/src/pystencilssfg/cli.py b/src/pystencilssfg/cli.py
new file mode 100644
index 0000000000000000000000000000000000000000..458c1fe6ae75649bbde9df46ebf88a3d7cf26455
--- /dev/null
+++ b/src/pystencilssfg/cli.py
@@ -0,0 +1,79 @@
+import sys
+from os import path
+
+from argparse import ArgumentParser
+
+from .configuration import (
+    SfgConfigException, SfgConfigSource,
+    add_config_args_to_parser, config_from_parser_args, merge_configurations
+)
+
+
+def cli_main(program='sfg-cli'):
+    parser = ArgumentParser(program,
+                            description="pystencilssfg command-line utility")
+
+    subparsers = parser.add_subparsers(required=True, title="Subcommands")
+
+    version_parser = subparsers.add_parser(
+        "version", help="Print version and exit.")
+    version_parser.set_defaults(func=version)
+
+    outfiles_parser = subparsers.add_parser(
+        "list-files", help="List files produced by given codegen script.")
+
+    outfiles_parser.set_defaults(func=list_files)
+    outfiles_parser.add_argument(
+        "--format", type=str, choices=("human", "cmake"), default="human")
+    outfiles_parser.add_argument("codegen_script", type=str)
+
+    add_config_args_to_parser(outfiles_parser)
+
+    args = parser.parse_args()
+    args.func(args)
+
+    exit(-1)  # should never happen
+
+
+def version(args):
+    from . import __version__
+    print(__version__)
+    exit(0)
+
+
+def list_files(args):
+    try:
+        project_config, cmdline_config = config_from_parser_args(args)
+    except SfgConfigException as exc:
+        abort_with_config_exception(exc)
+
+    config = merge_configurations(project_config, cmdline_config, None)
+
+    scriptdir, scriptname = path.split(args.codegen_script)
+    basename = path.splitext(scriptname)[0]
+
+    from .emitters.cpu.basic_cpu import BasicCpuEmitter
+
+    emitter = BasicCpuEmitter(basename, config)
+
+    match args.format:
+        case "human": print(" ".join(emitter.output_files))
+        case "cmake": print(";".join(emitter.output_files), end="")
+
+    exit(0)
+
+
+def abort_with_config_exception(exception: SfgConfigException):
+    def eprint(*args, **kwargs):
+        print(*args, file=sys.stderr, **kwargs)
+
+    match exception.config_source:
+        case SfgConfigSource.PROJECT:
+            eprint(
+                f"Invalid project configuration: {exception.message}\nCheck your configurator script.")
+        case SfgConfigSource.COMMANDLINE:
+            eprint(
+                f"Invalid configuration on command line: {exception.message}")
+        case _: assert False, "(Theoretically) unreachable code. Contact the developers."
+
+    exit(1)
diff --git a/src/pystencilssfg/cmake/PystencilsSfgFunctions.cmake b/src/pystencilssfg/cmake/PystencilsSfgFunctions.cmake
index 0817bf2b0c8096bcacc7328c670df84233e43153..3af2a29c75ebc7f4e1bf7a367bfb7e03573b6a7f 100644
--- a/src/pystencilssfg/cmake/PystencilsSfgFunctions.cmake
+++ b/src/pystencilssfg/cmake/PystencilsSfgFunctions.cmake
@@ -1,9 +1,11 @@
 
-set(PSSFG_CONFIGURATOR_SCRIPT "" CACHE FILEPATH "Configurator script for the pystencils Source File Generator" )
+set(PystencilsSfg_CONFIGURATOR_SCRIPT "" CACHE FILEPATH "Configurator script for the pystencils Source File Generator" )
+set(PystencilsSfg_GENERATED_SOURCES_DIR "${CMAKE_BINARY_DIR}/sfg_sources" CACHE PATH "Output directory for genenerated sources" )
 
-set(PSSFG_GENERATED_SOURCES_DIR "${CMAKE_BINARY_DIR}/pystencils_generated_sources")
-file(MAKE_DIRECTORY "${PSSFG_GENERATED_SOURCES_DIR}")
-include_directories(${PSSFG_GENERATED_SOURCES_DIR})
+mark_as_advanced(PystencilsSfg_GENERATED_SOURCES_DIR)
+
+file(MAKE_DIRECTORY "${PystencilsSfg_GENERATED_SOURCES_DIR}")
+include_directories(${PystencilsSfg_GENERATED_SOURCES_DIR})
 
 
 function(_pssfg_add_gen_source target script)
@@ -13,7 +15,7 @@ function(_pssfg_add_gen_source target script)
 
     cmake_parse_arguments(_pssfg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
-    set(generatedSourcesDir ${PSSFG_GENERATED_SOURCES_DIR}/gen/${target})
+    set(generatedSourcesDir ${PystencilsSfg_GENERATED_SOURCES_DIR}/${target})
     get_filename_component(basename ${script} NAME_WLE)
     cmake_path(ABSOLUTE_PATH script OUTPUT_VARIABLE scriptAbsolute)
 
@@ -46,16 +48,14 @@ function(pystencilssfg_generate_target_sources TARGET)
     set(multiValueArgs SCRIPTS DEPENDS FILE_EXTENSIONS)
     cmake_parse_arguments(_pssfg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
-    set(generatedSourcesDir ${PSSFG_GENERATED_SOURCES_DIR}/gen/${_pssfg_TARGET})
-
     set(generatorArgs)
 
     if(_pssfg_HEADER_ONLY)
         list(APPEND generatorArgs "--sfg-header-only")
     endif()
 
-    if(NOT (PSSFG_CONFIGURATOR_SCRIPT STREQUAL ""))
-        list(APPEND generatorArgs "--sfg-configurator='${_pssfg_CONFIGURATOR_SCRIPT}'")
+    if(NOT (PystencilsSfg_CONFIGURATOR_SCRIPT STREQUAL ""))
+        list(APPEND generatorArgs "--sfg-configurator='${_PystencilsSfg_CONFIGURATOR_SCRIPT}'")
     endif()
 
     if(DEFINED _pssfg_FILE_EXTENSIONS)