diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0283ef292f0b04c4e94ab11266a720bcd135f261..34b7886a5f60ad1ae80e7cbe9e183f28a051c6b0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -23,8 +23,7 @@ lint:
   - apt-get update -qq && apt-get -qq -y install make python3-venv
   - python3 -m venv /var/venv/
   - source /var/venv/bin/activate
-  - pip install -r requirements.txt
-  - pip install -r requirements-dev.txt
+  - pip install '.[dev]'
   - make --no-print-directory lint
 
 slide_gen:
diff --git a/Makefile b/Makefile
index bd8ed5dc05d684f36f1924b08597082110b47377..20bfaeac2f307e9e6333fa1119837ee617738ad0 100644
--- a/Makefile
+++ b/Makefile
@@ -1,29 +1,35 @@
-EDITOR = geany
+VBIN=${VIRTUAL_ENV}/bin
 
 help: # Print help on Makefile
 	@grep '^[^.#]\+:\s\+.*#' Makefile | \
-	sed "s/\(.\+\):\s*\(.*\) #\s*\(.*\)/`printf "\033[93m"`\1`printf "\033[0m"`	\3/" | \
+	sed "s/\(.\+\):\s*\(.*\) #\s*\(.*\)/`printf "\e[1;33;4;40m"`\1`printf "\033[0m"`	\3/" | \
 	expand -t20
 
+clean: # Remove files not tracked in source control
+	rm -rf .pytest_cache htmlcov __pycache__
+	find . -type f -name "*.orig" -delete
+	find . -type f -name "*.pyc" -delete
+	find . -type d -empty -delete
+
+create_git_hook: # Create pre-commit hook
+	echo "make --no-print-directory --quiet lint" > .git/hooks/pre-commit && chmod u+x .git/hooks/pre-commit
+
+format: # Format the code and lint it
+	${VBIN}/black src/geostrapy/*.py tests/*.py
+	.git/hooks/pre-commit
+
 open_all: # Open all projects files
-	test -n ${VIRTUAL_ENV} && ${EDITOR} ${VIRTUAL_ENV}/bin/activate
-	${EDITOR} .gitignore .gitlab-ci.yml Makefile README.md requirements.txt requirements-dev.txt setup.cfg
+	test -f ${VBIN}/activate && ${EDITOR} ${VBIN}/activate
+	${EDITOR} .gitignore .gitlab-ci.yml Makefile pyproject.toml README.md
 	${EDITOR} .git/hooks/p*-commit
-	${EDITOR} cli/*.py
+	${EDITOR} src/geostrapy/*.py
 	${EDITOR} docs/*.md
 
 pre_commit: # Run the pre-commit hook
 	.git/hooks/pre-commit
 
-clean: # Remove files not tracked in source control
-	rm -rf htmlcov .pytest_cache
-	find . -type f -name "*.orig" -delete
-	find . -type f -name "*.pyc" -delete
-	find . -type d -name "__pycache__" -delete
-	find . -type d -empty -delete
-
 lint: # Lint code
-	${VIRTUAL_ENV}/bin/black --check --quiet cli/*.py
-	${VIRTUAL_ENV}/bin/flake8 --config=setup.cfg
-	${VIRTUAL_ENV}/bin/pydocstyle
-	${VIRTUAL_ENV}/bin/pylint --rcfile=setup.cfg cli/*.py
+	${VBIN}/black --quiet --check src/geostrapy/*.py && echo "✅ black" || echo "🚨 black"
+	${VBIN}/pflake8 --config=pyproject.toml && echo "✅ pflake8"  || echo "🚨 pflake8"
+	${VBIN}/pydocstyle && echo "✅ pydocstyle" || echo "🚨 pydocstyle"
+	${VBIN}/pylint --rcfile=pyproject.toml src/geostrapy/*.py && echo "✅ pylint"  || echo "🚨 pylint"
diff --git a/README.md b/README.md
index b5697a9891b6b04e7ec66e3897b01d285eb0f65e..dd485ea42912e23ac740fee3a916a63e7c75e565 100644
--- a/README.md
+++ b/README.md
@@ -35,8 +35,8 @@ These are imagined with _[Strava](https://developers.strava.com/docs/reference/)
 git clone git@lab.frogg.it:fcode/geostrapy.git && cd geostrapy
 /<path>/<to>/python3.9 -m venv ~/.venvs/geostrapy
 source ~/.venvs/geostrapy/bin/activate
-pip install -r requirements.txt
-python cli/run.py --help
+pip install .
+geostrapy
 ```
 
 
@@ -47,7 +47,7 @@ python cli/run.py --help
 - Built with `Python 3.9`
 - Code linting with [`flake8`](https://pypi.org/project/flake8/), [`pylint`](https://pypi.org/project/pylint), [`black`](https://pypi.org/project/black) & [pydocstyle](https://pypi.org/project/pydocstyle/).
 - Install development tools:
-    * `pip install -r requirements-dev.txt`
+    * `pip install -e '.[dev]'`
 - A `Makefile` with tools : run `make help` to have a look
 - Use the `pre-commit` git hook, [CI will run it](.gitlab-ci.yaml)
     * `echo "make --no-print-directory lint" > .git/hooks/pre-commit`
@@ -56,7 +56,7 @@ python cli/run.py --help
 
 ### 📌 Dependences
 
-Details in [`requirements.txt`](requirements.txt) & [`requirements-dev.txt`](requirements-dev.txt)
+Details in [`pyproject.toml`](pyproject.toml)
 
 - [`gpxpy`](https://github.com/tkrajina/gpxpy/#readme) : GPX file manipulation
 - [`strava-cli`](https://github.com/bwilczynski/strava-cli#readme) : Use Strava's API on command line
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..f89643e6423a15969b0fbda2adf318e33272dde1
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,45 @@
+[build-system]
+requires = ["flit_core >=3.4,<4", "pip >=21.3"]
+build-backend = "flit_core.buildapi"
+
+[project]
+authors = [{name = "freezed", email = "git-expect-noreply@freezed.me"}]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "Intended Audience :: End Users/Desktop",
+    "License :: OSI Approved :: GNU Affero General Public License v3",
+    "Natural Language :: English",
+    "Operating System :: POSIX :: Linux",
+    "Programming Language :: Python :: 3.9",
+]
+dependencies = [
+    "click==8.0.4",
+    "gpxpy==1.5.0",
+    "strava-cli==0.6.1",
+]
+dynamic = ["version", "description"]
+license = {"file" = "LICENSE.md"}
+name = "geostrapy"
+readme = "README.md"
+requires-python = ">=3.9"
+
+[project.urls]
+Documentation = "https://lab.frogg.it/freezed/geostrapy/README.md"
+Home = "https://lab.frogg.it/freezed/geostrapy"
+Source = "https://lab.frogg.it/freezed/geostrapy"
+
+[project.optional-dependencies]
+dev = [
+    "black",
+    "flake8",
+    "pydocstyle",
+    "pylint",
+    "pyproject-flake8",
+    "toml",
+]
+
+[project.scripts]
+geostrapy = "geostrapy.__main__:main"
+
+[tool.flake8]
+max-line-length = 90
diff --git a/requirements-dev.txt b/requirements-dev.txt
deleted file mode 100644
index 583e3e50cef67a60e1b22bccfd2e7b962375e668..0000000000000000000000000000000000000000
--- a/requirements-dev.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-black
-flake8
-pydocstyle
-pylint
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index ac074f1865a97d2611069edf677a363b0700c9e9..0000000000000000000000000000000000000000
--- a/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-click==8.0.4
-gpxpy==1.5.0
-strava-cli==0.6.1
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 6abd81160309f014cedac2ea7a2cd9a36906ddcc..0000000000000000000000000000000000000000
--- a/setup.cfg
+++ /dev/null
@@ -1,6 +0,0 @@
-[flake8]
-max-line-length = 90
-
-[pylint]
-output-format = colorized
-score = n
diff --git a/src/geostrapy/__init__.py b/src/geostrapy/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7f7a45af86f45ff4093b15d52dfb7754c0b8a3fd
--- /dev/null
+++ b/src/geostrapy/__init__.py
@@ -0,0 +1,6 @@
+# Licence: GNU AGPL v3: http://www.gnu.org/licenses/
+# This file is part of `geostrapy`
+
+"""Geostrapy."""
+
+__version__ = "0.2.0"
diff --git a/cli/main.py b/src/geostrapy/__main__.py
similarity index 69%
rename from cli/main.py
rename to src/geostrapy/__main__.py
index c1e713c0c33779746f2fc21bb205edeee934a82c..c3d201f69f2b552ef4e51a555f1f01d83a78495f 100644
--- a/cli/main.py
+++ b/src/geostrapy/__main__.py
@@ -13,18 +13,18 @@ from strava.commands import (
     set_config,
 )
 
-from commands import post_create
+from .commands import post_create
 
 
 @click.group()
-def cli():
+def main():
     """Launch the CLI."""
 
 
-cli.add_command(login)
-cli.add_command(logout)
-cli.add_command(post_create)
-cli.add_command(set_config)
+main.add_command(login)
+main.add_command(logout)
+main.add_command(post_create)
+main.add_command(set_config)
 
 if __name__ == "__main__":
-    cli()
+    main()
diff --git a/cli/api.py b/src/geostrapy/api.py
similarity index 100%
rename from cli/api.py
rename to src/geostrapy/api.py
diff --git a/cli/commands.py b/src/geostrapy/commands.py
similarity index 98%
rename from cli/commands.py
rename to src/geostrapy/commands.py
index 59e26b57be021cb9408f33bd2a55a3387c4f79f6..4e44af9c209a50dcb5e34b0af51dfb21cf0bddd4 100644
--- a/cli/commands.py
+++ b/src/geostrapy/commands.py
@@ -20,7 +20,7 @@ from strava.formatters import (
     apply_formatters,
 )
 
-from api import post_activity
+from .api import post_activity
 
 _ACTIVITY_COLUMNS = ("key", "value")
 
diff --git a/cli/gpx.py b/src/geostrapy/gpx.py
similarity index 100%
rename from cli/gpx.py
rename to src/geostrapy/gpx.py