diff --git a/.gitignore b/.gitignore
index bbcf40c9751719662c0912bd3f5b52dcc6e3e6ac..5d889f542ad1b6de7093a00bd316a0e421d049f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,12 @@ Wiki/.idea
 
 NodeApp/src/config/Version.ts
 
+dojo_bash_completion.sh
+dojo.fish
+
+sonarlint.xml
+sonarlint/
+
 ############################ MacOS
 # General
 .DS_Store
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e17ff30b0b8a260162f2ed9fb69367afd430ea7..77f3bb11f7650411f94096fd60de74b9795514fc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,10 +18,29 @@
 -->
 
 
-## 3.5.0 (???)
+## 4.0.0 (???)
 
 ### ✨ Feature
-- Link a commit of an exercise as a corrige of an assignment
+- Add some commands to generate files that will be used for terminal completion (bash, fish and zsh)
+- Add `dojo upgrade` command that automatic upgrade the CLI to the latest version (or the latest dev version)
+- Add features related to corrige (commentary, commit specific link / update, delete link)
+
+### 🀏 Minor change
+- Do not add spaces in directory name of repository's clone
+
+### 🎨 Interface
+- Move from `session` command to `auth` (for retro-compatibility, `session` command is still available, but hidden)
+
+### πŸ”¨ Internal / Developers
+- SonarQube integration
+- Migration to GitBreaker library for all Gitlab API calls
+- Dependencies update
+
+
+## 3.5.0 (2024-02-21)
+
+### ✨ Feature
+- Link a commit of an exercise as a correction of an assignment
 
 
 ## 3.4.2 (2024-01-23)
diff --git a/NodeApp/.env.vault b/NodeApp/.env.vault
index 103f5eb46269011024d6b042d99253f10872150a..85a3476c23075cd04d21417852b32513acc4bca5 100644
--- a/NodeApp/.env.vault
+++ b/NodeApp/.env.vault
@@ -1,14 +1,14 @@
 #/-------------------.env.vault---------------------/
 #/         cloud-agnostic vaulting standard         /
-#/   [how it works](https://dotenv.org/env-vault)   /
+#/   [how it works](https://dotenvx.com/env-vault)  /
 #/--------------------------------------------------/
 
 # development
-DOTENV_VAULT_DEVELOPMENT="xWZfuXiO1nWw4qDf/vY90xPpLhb7l2cyOYK7v7K2WPOjlSuJBL74nWHaphDeslFUpJYiIjNe2QOn7z5nqn6DWw4PqYkjEPTjYGLfXmig3QoQtehYyYDErFH9RYRzCSE34NZzk2GP+F6vNGKJ0fA+ua4sTPfzPuhuoSC6Oz6TXsUwmDz7PpsrRDSsf1Y8e2GRHUiyYYb5W5lLKtFn2wCRNMU41nHgOsZip4/gmoz/6wD3vpJptLnMXMWUO7EZ87cyWXKZxVHCP19WmW1Fncdb4bo297VL1vH4b2yYcGwtgBAlsrMHhEzJH3QcXZucDdvIsN5A39CuIreQPM5szUonUJZIXcwXga8cmvuioszNO4zvrnG+BpMxOmLxa5XYYjUepfuSdP260zeMcLnAg0Mbylg6PlcaoA8fjKHiJZeAt2KMH5aL5dMLi5VMlleF2Wq0XbklZwyflfxzjULYJ6XNnrUJNea63Su1vzj7gPTA05eAfYnlKlNF8zc76qlY/G5Wigz0InOim3omo1JrLB4uBWCSkJvM6dxnB15DJfHjWHSJETI0desojXremJ11cnqabH6miC/hKH6A9YOEax4zdMIrRTMFa24xFuHjHqbTe/1/G6URW1kKKmyi9r8hNBKEyAYq1PYnf2BiGBYRj5KMybkYwqB+C0xLucAXYIXgOamw91vuN3SVpuLQ2adz7KLiZbe5xiXZALXhzXj08j195A7sZUoz5yWfqZcVE9o0EkGzX6xAGqVADg1mbgw9dRbm+Ux9u0/qAMzpTbK93lRtC94ZBKenGtJc+Sdc6rYmUKj/+3mmIGYGQjX6VAV2ApoC+yJxyG1WBy/Ae0BeoGtuevuCbYjM55KHWy5cFh3s71I3eJVfcJxf7+bi3VQudOOLE8ZhK42Oa6tY/Aqgr2oyjcfBvA+B0R/wJBy+ZIOKtZgtcH9mIl8Srw3FgGEPch/F4r3ndooM1TuDD/yVyFK0crstir8iD+nMExj9ll8fi2TmcAQ5CC0/0QteCEb9qeVCwa0mO0xIk8IVxC9hVlrInk+yw1A4uZLYwe9ppIjlSGf0zLqIJPiOY8mPTpCucgrYV+BhybIhUKGIdCbXu+JR/LZ7babQAUXfeA86kkKhcNc2HVEpx8ms7kvju3d06rTMdxCz/isEpmzVobm0ZEzXzBe+VBk71YP6liVDrGO0YXPz/ew000fZOyhcZbEryCjZO9WjES5w3uPiTo6MGLmuKfjRDgrJQuQ05wYrDdH7YnXKY0jrzSibSEkuywdXjVgq4YgTMnLKtsc8AIVlB/CzI4Xq8w5gPPz0kZVo/xmThziwjkk0TIlRKpFjlito/QBKquZ38YmXhkOBbSGpCsEOtQfThtbnArKhojhQLcSOruAwIE8wQBJDr1yW+/WtqeWi80E9C/QI91zMKNM/DsD/JSZmkZAXwyV0R7/IVoZlX2JQgTGxD0VjGI1Tsr3mGfU2zUOBif9Ff6tcYhWBfUrVIv1InTa905tpZ/1DObYE9a3+stFERtGgKICdq801fSwKjvXq4ijFgJEwD0sGAj6E52X995qTxftyMcCkByWmTiTltZfHeNOYBRODhl2y15rHvsBh2XjERiuN1NyVUhytinHCqx6+OMRvQ0TOLplSuR0XEBL9v3Iz3zvkJ6K6t12FXD5TzjlIKUfUVgHUHcZVZo1HuGxvuS4Ymo5qAJzgv8g+yZfEml+AMDkZfBCP6KO9f8wi6X+X4Z54dDDxrXNyyBwmv58a9GMWyFDiQNTH8caCs4T2h2uzB56yJkjL2DDMeZtw9WL5mFQ0kUxeaO7J1iLsccbJ58yRHsowHY4Y/wSNaeNEHKNCRFyIEC7nHSQawqmZZXmCX1Dgs83V5sLM5pBjHnWQ2xI="
+DOTENV_VAULT_DEVELOPMENT="eDm9ilaSSUuX59BforLz1zax3Nw7IMihh9fNLg2U3KiN2ackcOfM/1Zy2wcexUvgdmG6klSJeCrCn6cfi2EC1r9vNLvCxUWCv0mVW1GAC9DXAgCmmYZywIiCRgZCLbuYjAAEfy/psIXIDYz1fazcxAR8PUF2/1OF33ISv5aSUkpxzu4EkidCHNsmn3YJ19U10Wy9ZeXlt+d/nfFHrzHzKhvXXoftDNxJBkq7aqFvjeqhyHvO/lgdd7AM869rTBLgxpRyYUsR7Gtzgdu9H93sCbOIo1dJzQ/UWMrh64tRC/yFc/2O37Kvc4sKwl8XF78l39t+ZPIJdDOEdYue/n6ZZwLGFEcyJQGywrJ9KOmI2JR1TbAT5wxCTKNOCt8gSJMwO9DQU1kxsFSX0+MS4yqgOkgXl9hoLxeGncXu5Q8HoLaoi+q7mRa/PvIljP2d4yXQHyAOyhxpb54+S+G1zGv6R38PT11PZs3+nRS2O+8hIA/zy8Rvear0JGEkP/edOzgznFBCDz+0Cudh4tk3LwhdZ7MtfPgb3gqYNK9BMUKnxruEmjRuTxc9Q1Hbx+3/jwtyyx9uNx2uSAX3MY7vPnaUWr2AuPQHuy9O/I+arapfymHmQBzGLuVuT5fj8CioJxiHBUk0Fks3L1VZInO03I0NdM8b9cuKiKYMEJwJ4rU7HfUqcqbcSCydCCBI9SRKCGfWRsA9LX0KH1igExHfi6myqj2XTnOUgWmiafMusegaQTRP3V8E2hV7tAfbvRR9aIubdTQuTRXq76iR/b8BJbQ65JM87dU3XVu3AMhUAhql2OK4wQ6P47SV7OnhdRd0FNRb0GjKntbQ8vcLzwURMrSxuF+YDIHxXWQrmRR+s/x9xL3FirWmF+IcjBRgthg37oT1KQBmmBwGyoM+7IUfGPdil0qokFOPAw9jEAW7DB2y4cmBnwAd1xRvcBe/u3MNa+dwiW31FLteJczd5QsYA5fIg1e8JiAhx45R3epi89q80b2Yx1FoOyguch3YY9eT5FslUNFCsZbn3zZnOpC/lkyS1sPZ2NZDscathXE8FoeTkt7V0jDDtAYSGEMAE7hIdFO9iv8GpYEen+J4oljRaWa8Lq3F4IlzjRMJdz3ZWHR2/fyqbIP9iJZNtCx9KihXetZkuCi8BHRPqpY6j0IwTC43FeGBCJehBcMjDWgJAHtcjRr44J1nzAMJReUQb7hhGHWcrJGKktKy8RF2zKcFMiIaQNA5TaQ6Xfn62WD19cyxZqNn1HsR38XELRKDzRujmnTyX+zDRNawJqEjo6NoaEXlwum3HLG25LEqnXFnq2XHR55MZA8iNVr2ECbVy2pQrHUQ/MjABov76JQlkwptuhOtw/z6G8hsirmijKTsC/Bqj/MADIr9zddr1Hd/StSSX6Iv6ngapDZOA2JEybfCLjy9Aa+3+/JwQh3jB1MUaNoYfg8UEponW0EE+YCg93kVLJE+P8SToq5Q7PxOBPNGz/jBERzF7zIAw0hFmNawRamedI5VYOx6x+4UQR99I/D5//9aF/rYE7mTIFOiAbdm16CIknqBOCo1DT0j89n32QQY+V3pC9uUHGha2MvTkwzRM2iH9RP6JAY8P1qsfZRfLcG+dulV9R1UUcGwEl0cbQTCGEW625e6C9LjSH6P9/htFnjDLh75bxdPwLD2LT+u5hvBSO16b4DNSUrt6QIecIE86yfLBUJ7h8geq+UlVYKNcL/vUZQUP1Y0Rd0sBstMr85j+jVllahFITOgxDIbSRZDNLq9Xe1CXUJ7vmMXZTuUCwwb93CNRlwPqdCzcpmgykUkLl30HmwkUHiMD89zQUit/8zSXYEfK0a7A1NBcifcdo35njE89f7TdIf48ey/f4JUMLB/50j5sOu25m640488sjtceGrVcwHIJ3DfnRf626Dpgvhy096p5dxBlv7RLN9By83EhUF0wtgBOrQ5QMd56HS1ezIR6DS27n9JITJY0/G+X+BioDFJD3KlRYjPUQy0MrBU3JYeMGhZzhQ7eBQa1+GIgxkJsDCvUQ=="
 
 # production
-DOTENV_VAULT_PRODUCTION="geUVFVOsvd0vRIIfeu1i6pKoAr3/2nwB63oO4gxe/ug3GnFBEh+Rf8I7DwM4qcD10PBqaMV7tJiYVIBqAUviHrQSGEJw5aK4Le4ndXf/1yPpsRf6YdMNfL/oU5I2aMMXlCCGTylbgon67Vrw2SgmZbCD1vkr9wmdJqATANcNSfNbJL+mf790rZyUXYC4piUIfdGDnjJpeSTozfpZeTh3yEp0pRbjRsU6NlXIPH/mDVFJkWMT31jZJ1kyePu6b1qWWh2uZRwWaaOOTclpKGnLRaT/N3Z+M1OJbB1Kjp0uUoLIz4VytTGcYRjS8wbfzBnl4wg9HSPR49UKShX5SNtQ9ov/7vmRpYH59SNuwV55S6Bv5pgOUVjBgdl+4tzXuh7Z8FD2867a2k7vXBpXoOBgi6yujlSmEoul3XMa5a3zyWHB+kz0GwEtlyZyAFR5xXyIWdURkXw4RRLLhHaI5y6LGKPCFHyFPdEUX4uRroFB261+ft5IT51ZwTujeymC6qkC2KlsrpViyySTjE4LynLzitIsLP5I7PiZ6tH227BfzcwmUdHlSuiHIQMWPKl2oFLyhEkfWgUxCRUfUxYMgCRjPkrXf7zPhvhgbtOOTBLlZsMBQxFoM7kXECzhnnyqp6JVQxfOMAGPTcgLQP3HAqVLVfAYzRpfoTROxuoZ6nuikIdR8rO4BAasH2/6Z0hWEGKUMsnhDHb8l3xDNB3IAH00y+t1WM0Yre49s3RrtTz+I8K3nZ1hCCT6UqfDxRkefltXngdd47qNU49FpWS2MKitUapLvlqTccAxYrCUmAkl/giDMNrRaLFbWSeiA8tyOEhdp8eqVz5ygLalkecdZTr3x3qwFbLam++MqLph1JXeFWzBoekLyJUrtWEYNBXb/g+eQlfHKEbd0AbbYsSaqVZz9R8hjqcryq/O+pMETx7F8DV7l8oR1xMaFeDBc+rJPLXka5plc91mdstU3E0FgviScUvaE1dclEuPGW3M/1MXR+oxIZ81dl4j1uUYcSUueKv7QsOS8jFuJhQQXzNyBrFtJCb8jV8V9/0vXKUN+Zj9Bfdn0IuUxXN8VfaoSNBOA2fTssEAPSvCBOJPSQsL7YsSye/1koMCifYy+c2RDSDxxD72bfsij9U1srjJgGJAqRI2WetUjH9HgqESJ+oPQouKhHwEinnsNVSweoLLN0nD02RSjm3iIeCy/5R0B/23yALZ4YWTxdS4kJs22mue0cUyGY6on3eCmwF5d+7H9/r737sbLYODBLn6eArXvHAvIS72mtvveTiviXhzU8c0ntu562zpDDMTn8useCWHgsEukRm2eumpMiXSMCiFSotV4T+GFNSAkm3J6mGAzZFp1QOgZHnImjrERsLaKRweQblNT2ZYxaZNbCNMlrj248EIIIbSPH+/4wgUyl5tbhnyPYO58Z0V1kLOvofuzIIVXGoeScHaTDEMlx1ZmN9Mtl0CuYNIyzvzxRee2j2EUu6ivLwOArR1SDZILd540y3ONOMObAVZhJCB1vUqzTdBrz5BRxy1knwhhQRbQQHcVVp+zqxSVtRrUWgAzHzCP655ilDqXJwkV0Sy/naBr5I8Hjs0euVLj0Qq7XlcyIHtch8Lcm+rfJiDo0bYJlHfgoeRrvum0N/w+qoEVbuGpzNztGO7ISbhxeitLoP/cu7tDYxLVXuzXYCVYn2MJwjeLtlytZy74jQoKiDRLt2Zy32JSbg8hR1utaFkN8BA9OmAhbXTmdsJgleyWxlQDl3YeLaN4+L1rLrgpmmyTIBcM6oNhO9GTstjvzrwXKPHb6CmzEwFkyzBPS36bN1glcZHF5auiybAJkMMlKHMbupA7244Gw=="
+DOTENV_VAULT_PRODUCTION="wpkU0o0laCd2mANVdd+8ji6Q8mF1lczuMTNOxZhc+s/ISsa95JvSdUONbjn0tyaGUsXO7XH+YDZcTFP/RZr5ZDDki0YFhKgZbnwWHAXfYcU+U5+y6nT3ONMf1cApnS+iZuNLc3WgU13IT4ShW9zIv9aLibservDhVBXInYW+BPGi/KMUhh1iSTbJXPU/w6A+eLmjXQQnaY4F+eVfIk9H9175n5NxmeZNqkPmocqP8ZR21Z6FvugYaNGAC0Q3Rz1Y3oS76UUeIYqPdwPTO5AJN/3DY9f/9J2iSRpW4hp12KKl/kaZVRSV2Je++whIvdwQASyQ0vpw4wl+YdTT+M7voa7hX6NpS6KnHBGrm+C+rdXmEzZY7rvzREMWvIUlqP1GQrG4CjVOeNXejAo6on/ZNe+zBCYabmdOY/ZtdECl17mfVDPuvaJhfKbLHGA2Hs9v+03xfvxBBghB9lDigz1YM/EdrjhbKnbPPpKFz0JgabBZIv5e91zVnGGne1T6opzRujiJnFI+w0S9wvYX8i4QdFimM9R8MX6LTkWBjzFGYXFS+4jdUsAwc0vZXn1zjBImdzVUVMdnibXKw4MoKxS2ddrmoXkpTV6IIIsKdetfhax65WA6GoVDl1pQVgxIa9Vwkgolu/7EF/RKuCHhclpJA2xvvm8UtqfgMFvaTlcj+nhb256s/vBS62eqHpQTmO+mU3+gfTrbz3bKV+LNmge5OqedHIQusqxKewCdZND67uMKa3MYgeQG3UdPlIYw0Twgvme7LPopj5Prs/6pc7YKH9yelDNc+NR2Huvf3ooRb4lEVl3VrMhsiPl+E+RLGR1jv6yJ3aTlZv/4kyxTWn5aDSz6AF+aiZph/ZLQaDBcN5VDxHq9jrFOrxoSvcp1IP7MI3IvD3lPVCQXVl/HOzfnQWF8PcSxcr96Td57KhUPxyvyUy01b0qYLDEM9FgVe2YytgRLIh2sxv3n7fbYhDReD2jnGBMNCAE/mg2Gc+muTiqTs5RPpvgtZVAnhvLnz/o2Pgc7gI30zluoCK/ZkkCZOF/+ukL+I2adyd2+/LJrX+hjyvxcdDHP1LcGhO/xsPBj7fT49/j87aF8K4M5RYEpXvh+fmBqCd4uPd8LBbffa2dNrxJgc0DRbS8lUoN8FUVe680lnZ8PZW49bGC9qeIIuH1uBr2DWo4/ufoHyDUKp7x4LxJYGY/7Qn2S/gBDG3CnomC8clYO5ud3UYJWqEecvqiminPFj2BaNzEgNuiIq4wW+KrkQK0Q2HKY5Qh8ryh16SxIsMqfaKNj74DFlEjf/C94IJw2IPzDFTBq1mas3odGZ76pwD0ryI6jGta2My4zW5Ld8NhHTJGNXZ/tqYIAVscdKvvXzpY8xkDBiRHm21AmjWRsO9S2GatBtEXVKQ0+YFCcfPl3754jfSt7KBz5HrRNPAjWPV36MBH5XlbcASfIRpjhD+Ks6N3bLEx3u7KEQum1nwZZM9Hk/3IHBoqndVRgHqGO4BNYumzUuWArN+JUVuRXCloPiFGYxZaoipG3RJE55eLvNdjsUMq1nM+zkdxs2gu2mqj1W8zFCvWdrAVEvrO91fzAmlVLs/2Tj1nj5T91Cxrz5yu1Qwb/sqd3NQBeETnTRWYydp88WtUvt8mmSfSkKw49txABlLzUQXRgMprd5SjG/yDDoxv/w0S/wa4gqhH+lITbmVqIpiD+9fmlO9QG1x9glpihuQi4/uxoiHnHJC1+a3XnGfsgztaVHXfk+vjA+WAI3xBOiMLx+5IjeR9QhP5dJaEqVsOEeziIdpt0DZErl1zd0vgq77pIe3qpqNj5qcbkFwWOy5ZTriedZ1nrOse48jTreqSTH2QWJ9Mp7OYxChl0WHIjNa0TwjfDvRPF0eFu3n6wbheX058jgyXBjbCuFSkKydGRbPLIWf9S7gOSqybBClQeDdUjaA5v+7tj/VNRcdUEATrZGrdyt3AV6BKuu+OOnc7KYQwNvDq0jfJSHaKWAJi44XT5mwCyvP5BAQVaH43QVy3DvZnpLie0"
 
 # test
-DOTENV_VAULT_TEST="NjWlOUWT5HZM4whZTpS/sqrHLsWiJKV6xSQcrU2Pm4m8csvh/PJ9iQgyME+eltgHAFAdGZIrwZadFws7kCHQSQxikYq1hz513wlLhhVqk9QGuHUdM352OtsoySuHYS7SwwKog8MAsLQuJpIjmvJaCFvQ+Q15KjbHGjXgQghXELel87IEy0+lxibd1JUMfHNPzyA4EXZe3LGSVNgIfNRZWcE1vWQ+UAhDxCA1M5Y9yEQO+TB2FHyEZaFgSHC+uLgybK5SbFwyDxPY8RzLBbHtlAB5Ez2NnLWkj6wLLRNZW75wa7TserUaArVyBy8nBPqtDGpMCpMXubVHxKzFDxUHqgbhn/eVDaD7FWhVYT0PWijOpLKjtASTebEj24ea46NuLZAjMxIhb2Vr5s06PU7wmACNIi2ajdMFD+MSwKyRIvWlhcw+q2jUcZYcnqUM2F/AKp3qdSY3wLbnYr6F+dEYqXVk1A6pw+JC33eBdGVFPtskuxttg+//VfEuZOKSC7uLPHywQ8ldoljCqFLUt/+oKuhE8gzeNkX7Eb+O1scK9J4SHD2veVSafUZqBNewDjRnixGt8SMMv82XNqHbfpS7GqQKBNz5koO9wOJVvdDwSfGYY1e+RFreTUFsZtLUi6noai9+mQcwSe/cjsM77qewpfYSF+mdp4noKc3mlZTwkoVxF2zNO80uIvvkXmWraC0zqS5A+C05ppZZrjbcMN/CcKHSc1ERJaeWmbfWjjrsZlPwRtiOxLZu4Mohxl6CrZkln57lyryfhJNzjjV2/CyGhgb2VuolZp5s/hckCuSmGH7O4Uk4wiD8WaIA3j6c2YtRJ6/bWnXRVmLK+NBn50N6C0iEzixEl4nPyb8rfvI8ueDiXase28vPmXMwY8OU1uGn0L+K9M/ySSK0whxu6yLG75mv1ZLs3qQ+Z1HxUN6ZTYTNQmTAw2W2l12ZB2yZgR7Qi+uGMuI6zzLSrl7Ru+S7SIGV4PUyOc6QAtuKFx1XPNgj0ZA8Kfqx9iMnj1aHtDS1GsONuzSHOsUzW8vr1YMGEG4knEspRbIzlC7NVP4aChO/Mo4WmjZAu+oZaXxyyNXEwgxmWO4AArrsdgVCulWarr4Y1dV4639ATz81QjtjV+UieluJsAuCxqBz4p8Kw+/2k4EUBiu9FaO4pw9CDE1y2d/y9H1I+8l2NU11mPvqnNzXZgLJOLK4qV3tOFPheEK0de0WzTt4PZsneFSQbZNWmlbJDluEn53VheVfJOcUXq9KEWKwKDnzn+1dYUyWuAW6y7uXnx2ECWz1xM9DzaIwUcjv8/62NdspsSHNag8RxZ/KzHffH6ePoxpfhAogfaRjlgX+P1fpjxujY2/1LmdnH3/0kHyFuU+D/2IQC1cJMGpU7mD754JXfGS/g2d4ISVFEOTfDEv7ZwC8gRrkIMrUnk4xR4WYE3MSewZzWAQ1NcgdYrDpcSA8/wPUXnNcaP6GHZKzPoYq9SbEdOlVNkExOH+AueKGWtPm0VqprhwFOsveSsdwwAF79CKIzk1XJFCyLD+3aX7+ZvAAn/3734ggO+0tjBCy+N2pZ8+4UDABjSHA3d/UD3dNr78kzZY67xhkUu6fQSpS4nFezGgek9YpDTR8ukUbTiyovqtTmgXeb5HDBH5sZ/kaoEFgI+kskWb1u7Mk+5iZoVmPZXw2DFs7Ng2iQ+cS+KOPID534AmJ+mt8PrM92hVBlXmc96hrP4XqhIqQAImb6t9GQoDxdA8hsCUB4LQf8gKuZx4DDtrSronheYCFBLaNFNnixhFI1sVdK55VnS4/6zma6g7YRw=="
+DOTENV_VAULT_TEST="CYDEOpA8aIlJs245eFTFon8fcioqqLopkdOco1dRb5g70jfXSvgOJQ5GYuFFGOPfZkv0Q5yTHvABPiUzMP8RS0m2x+88BKpfd2TS/9ojTq/UASfOJH0/OaR2Pfj72nKqKN3PuAE6Gf0GQsjzypa25B8z0b3eRbaV/wvCxSbV+Y2F4kkw/+mm9TtYtG3oWFHyBzcAD/zGyFcXqmRDi2tIw0IHEWzD4emIS1rJDK/8Y85xILSrALAtfyPr+a2tvSNapg8kH1dv+BG5DIZBz/8xERxJQNO68altEkpsDEoue23rGEReP3gcIfF0E7/y55PEXkPUQl2RQNXIPW9DdMa0tb94ToHiOgMmOxo6oW+liIShD6ERTZIIzcA4yD+jEEFP739evtfouEHw5SeGVp++EjWN4PwB9qmow6mNgDDQyFolxzlbRvfsWQEvTGe0urXzthE+0THbHHX128XUS3NwVWLLTZEECZRvz9ZeiuAPadKoqCdydDLg5s+ufajlmw0fFJVhMQc+AP533ttlHC2vT4xVjjN59Z2yE94kap3i4HtyGqKMq7bFJWAiLT4B615xrg9zZUarlE1qM7ZqXsGpyU3kyAm+/wf4/s2hGYl8uK/ff5rmXU4X+9gobmuV77H07iuWUJESbg3dl1NQK0X4y1zA9EWcU3OB7LFrwi3ydzgi84Aq/tvOnhxn0n1PRvMwqsX4qVj3JCynUSAM3RA8ukO9RL7CcMjNnNvHi58UhLUJVCJLyZ4MS3Hcx+GdnpQ92KQvIEUFtwcLX9SaEQSRBdRJ+Knxq711E2pJrcv1uHMi7fJbaBuPsnP/ntkcWrxqmp7EqcHl29bGzOVs9js0sjlBpnW9RjvJ0AIug3ze2JzG1qoT42jSWjVVNiVTAGGPOXIFqrzx5eaTYt2bE0oWNlQCY+0bJAPqtpBJWX2sxnnuKOtYjOYXMx7uz92WJDdtmzvXZ5ymOOs0Ye7RxxBvZUSEyBws1MCP39VuOJcMidUfQGLMhiNv7EkVaPudwYQ1pD8Fvav/oQQmRNOiIo5UrbC7dZ9fd/RrS+65mDhCxHj7nni7O959reDd1y0xo+zKmD64JChJVTWN2/YyhFdqieGL8Lmtz3Z9rkigch3q4HnpVfFJmCcBGjP/KenftIW2AWU9LMHUwwEggLTU4XyO5AhhwFzzWaYWrUqxbVxhS0gsTYw/2lm+PWXFMBI7FQgax2Y9sp/eMeKGjgkykmyftqhHfmet3C7ALzdbygpkERkq9JjXQdigtmgSP1lFxxf8LkmSdWYQqHNvrTeXDJyQdL3m2m9o0zUMBnDjtd64LnslWjUBPk7uLARd3cKIK4lPGP66+jgvMJKoGJTjFcD67xko++M+/2R/ip919uTxJ+/y1mtJXFnSL/FA4jl2UsU3YaiOISO726Zv1l4AuoKfRq6x1KlfJKemh2MQVdXwXIqvqfvOIHz6/P3Eie4cwt00WDV1GbVQgR4FEAT6uQxxMaSXTGJwi4g48TprsKN+zWmmCXzAF/Zmpy9O8GNrW9of8WY6hGe9ZpMhANhTSOIH2ey3Zenkl4ZoYKZ9ywbK6db0pSFPr9SXdzzPVOeriQzQ57ij0YX7YIZJA3YROqMRRQ+xnQTnxQlxRGTJPd5abU01KVNWy9GVu/Win7wQuWg0QeDWEIS5ga3tNb4txtWzyFam9RHVPA76YjOUIgL9NMrHq4t5iE3HAr1QU4pMZ5BEeFA/nqY7kO8rNGPYrwe41cd5RcwBajPxyGH0HGxT8ovd3U2vN6fpje6NuB4e3Uz8Go2CP4U5x/b29MoR245iPjtzhn0Q/AhIMr5MVEN8Zt8DcLBtxo2E11KQMHqXuadsqT/mS65PlshVeqgITl34IIV/tf8flC143FLQNlNECqgHgE9GLEJvuTKSHLFhDC6pHstGLEyPe8gtMikN4DlyqdZ/pqV4hhNG1E+jeL3x"
 
diff --git a/NodeApp/.eslintignore b/NodeApp/.eslintignore
deleted file mode 100644
index ecded56c0bd829718e46ffea13b1e3b6791c794e..0000000000000000000000000000000000000000
--- a/NodeApp/.eslintignore
+++ /dev/null
@@ -1,3 +0,0 @@
-dist
-node_modules
-.gitlab-ci
\ No newline at end of file
diff --git a/NodeApp/.eslintrc.json b/NodeApp/.eslintrc.json
deleted file mode 100644
index be8c02d07111eec0c73437a65326e56150fda24d..0000000000000000000000000000000000000000
--- a/NodeApp/.eslintrc.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-    "root"   : true,
-    "parser" : "@typescript-eslint/parser",
-    "plugins": [
-        "@typescript-eslint"
-    ],
-    "extends": [
-        "eslint:recommended",
-        "plugin:@typescript-eslint/recommended"
-    ]
-}
\ No newline at end of file
diff --git a/NodeApp/.gitlab-ci/00_vars.yml b/NodeApp/.gitlab-ci/00_vars.yml
index 642e2de4b7e2ad37e4cdb90bab91aa7676f34cf3..bab6ab50ebf7df743d8421beb74dc41759f55b29 100644
--- a/NodeApp/.gitlab-ci/00_vars.yml
+++ b/NodeApp/.gitlab-ci/00_vars.yml
@@ -30,7 +30,7 @@ variables:
     BIN_FOLDER_WINDOWS_ARM64: $BIN_FOLDER_WINDOWS/arm64
     BIN_FOLDER_WINDOWS_X64: $BIN_FOLDER_WINDOWS/x64
 
-    VERSION_FILE: $ARTIFACTS_FOLDER/VERSION
+    CONFIG_FILE: $ARTIFACTS_FOLDER/CONFIG.env
     VERSION_DEV_SUFFIX: '-dev'
     VERSION_TEST_SUFFIX: '-test'
 
diff --git a/NodeApp/.gitlab-ci/01_functions.yml b/NodeApp/.gitlab-ci/01_functions.yml
index 231424194f00183d07f31b1aec69137e3efbe791..d2ad20f0e8fcd9c2126745be25f597e2df01f5f2 100644
--- a/NodeApp/.gitlab-ci/01_functions.yml
+++ b/NodeApp/.gitlab-ci/01_functions.yml
@@ -88,9 +88,13 @@
         - |
             if [[ $CI_COMMIT_REF_PROTECTED == "true" || $IS_TEST == true ]]; then
                 echo "Decrypt production env vars"
-                sed -i -r "s/(DOTENV_KEY[ ]*:[ ]*[\'\"\`])[^'\"\`]*([\'\"\`])([ ]*\,)?//g" src/app.ts
-                sed -i -r "s/,[\ \n]*\}/\}/g" src/app.ts
-                npx dotenv-vault local decrypt "${DOTENV_PROD_KEY}" > .env
+                sed -i -r "s/(DOTENV_KEY[ ]*:[ ]*[\'\"\`])[^'\"\`]*([\'\"\`])([ ]*\,)?//g" src/init.ts
+                sed -i -r "s/,[\ \n]*\}/\}/g" src/init.ts
+            
+                echo "DOTENV_KEY_PRODUCTION=\"${DOTENV_PROD_KEY}\"" > .env.keys
+                npx dotenvx decrypt
+                mv .env.production .env
+                rm .env.keys
             fi
 
         # Build
diff --git a/NodeApp/.gitlab-ci/02_templates.yml b/NodeApp/.gitlab-ci/02_templates.yml
index 41ca52ff2ee29ca7b5adc9f7483c279d35c21b81..6db2a93051bae63eef1536b96840ee9b16416ca1 100644
--- a/NodeApp/.gitlab-ci/02_templates.yml
+++ b/NodeApp/.gitlab-ci/02_templates.yml
@@ -53,10 +53,10 @@
         - rm -Rf ${PKG_BUILD_FOLDER_NAME}
     artifacts:
         paths:
-            - $ARTIFACTS_FOLDER/*
+            - $BIN_FOLDER_DEBIAN_PKG_AND_SIGN/*
         expire_in: 10 mins
     rules:
-        -   if: '$CI_COMMIT_REF_PROTECTED == "true"'
+        -   if: $CI_COMMIT_REF_PROTECTED == "true"
 
 
 .pkg_and_sign:macos:
@@ -66,7 +66,7 @@
     dependencies:
         - build:version
     script:
-        - VERSION=$(cat $VERSION_FILE)
+        - source $CONFIG_FILE
         - !reference [ .get_version_dependent_vars, script ]
 
         - security unlock-keychain -p $SIGN_KEYCHAIN_PASSWORD $SIGN_LOGIN_KEYCHAIN_PATH
@@ -87,7 +87,7 @@
         - rm -Rf ${BIN_NAME_BASE}_pkg
     artifacts:
         paths:
-            - $ARTIFACTS_FOLDER/*
+            - $BIN_FOLDER_MACOS_PKG_AND_SIGN/*
         expire_in: 10 mins
     rules:
-        -   if: '$CI_COMMIT_REF_PROTECTED == "true"'
\ No newline at end of file
+        -   if: $CI_COMMIT_REF_PROTECTED == "true"
\ No newline at end of file
diff --git a/NodeApp/.gitlab-ci/04_stageCodeQuality.yml b/NodeApp/.gitlab-ci/04_stageCodeQuality.yml
index 34da3c81104d02864ec17037861763c402d4fcf2..4742d1e1751730bf864712b27bcbd20fbec4b407 100644
--- a/NodeApp/.gitlab-ci/04_stageCodeQuality.yml
+++ b/NodeApp/.gitlab-ci/04_stageCodeQuality.yml
@@ -9,4 +9,32 @@ code_quality:lint:
         - npm install
         - npm run lint
     rules:
-        -   if: '$CI_COMMIT_TAG =~ "/^$/"'
\ No newline at end of file
+        -   if: $CI_COMMIT_TAG
+            when: never
+        -   if: $CI_PIPELINE_SOURCE == "merge_request_event"
+            when: manual
+        -   when: on_success
+
+
+code_quality:sonarqube:
+    stage: code_quality
+    tags:
+        - code_quality
+    image:
+        name: leadrien/isc-sonar-scanner-cli
+        entrypoint: [ "" ]
+    variables:
+        SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
+        GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
+    cache:
+        key: "${CI_JOB_NAME}"
+        paths:
+            - .sonar/cache
+    script:
+        - sonar-scanner
+    rules:
+        -   if: $CI_COMMIT_TAG
+            when: never
+        -   if: $CI_PIPELINE_SOURCE == "merge_request_event"
+            when: manual
+        -   when: on_success
diff --git a/NodeApp/.gitlab-ci/05_stageTest.yml b/NodeApp/.gitlab-ci/05_stageTest.yml
index 914033e3daf27044c12958f49ffb58bd6a093e08..9a70f95723f890ff766498cb96557381c5fc94a8 100644
--- a/NodeApp/.gitlab-ci/05_stageTest.yml
+++ b/NodeApp/.gitlab-ci/05_stageTest.yml
@@ -12,4 +12,12 @@ test:build:
         # Build
         - !reference [ .build_cli, script ]
     rules:
-        -   if: '$CI_COMMIT_TAG =~ "/^$/" && $CI_COMMIT_REF_PROTECTED != "true" && $CI_COMMIT_BRANCH != "test"'
+        -   if: $CI_COMMIT_TAG
+            when: never
+        -   if: $CI_COMMIT_REF_PROTECTED == "true"
+            when: never
+        -   if: $CI_COMMIT_BRANCH == "test"
+            when: never
+        -   if: $CI_PIPELINE_SOURCE == "merge_request_event"
+            when: manual
+        -   when: on_success
diff --git a/NodeApp/.gitlab-ci/06_stageBuild.yml b/NodeApp/.gitlab-ci/06_stageBuild.yml
index f1aeaa2e13cd5e49b54436ee79fcf96cc86d0cc8..45260652484bee1faf9c912a238659115286e05f 100644
--- a/NodeApp/.gitlab-ci/06_stageBuild.yml
+++ b/NodeApp/.gitlab-ci/06_stageBuild.yml
@@ -10,7 +10,7 @@ build:version:
         - !reference [ .get_version_dependent_vars, script ]
 
         - mkdir -p $ARTIFACTS_FOLDER
-        - echo $VERSION > $VERSION_FILE
+        - echo "VERSION=${VERSION}" > $CONFIG_FILE
 
         # Build
         - !reference [ .build_cli, script ]
@@ -20,4 +20,5 @@ build:version:
             - $ARTIFACTS_FOLDER/*
         expire_in: 10 mins
     rules:
-        -   if: '$CI_COMMIT_REF_PROTECTED == "true" || $CI_COMMIT_BRANCH == "test"'
+        -   if: $CI_COMMIT_REF_PROTECTED == "true"
+        -   if: $CI_COMMIT_BRANCH == "test"
diff --git a/NodeApp/.gitlab-ci/07_stagePkgAndSign.yml b/NodeApp/.gitlab-ci/07_stagePkgAndSign.yml
index 64032c58e7903c16aba5c9564f0b1ae57ee7df3c..ef8c4b8d9507ddcf4b5992f69af6b5b9feb0a5a0 100644
--- a/NodeApp/.gitlab-ci/07_stagePkgAndSign.yml
+++ b/NodeApp/.gitlab-ci/07_stagePkgAndSign.yml
@@ -1,43 +1,53 @@
 pkg_and_sign:debian:
     parallel:
         matrix:
-            -   ARCH: [ "arm64", "amd64" ]
+            -   ARCH: "arm64"
+                BIN_FOLDER_DEBIAN_PKG_AND_SIGN: $BIN_FOLDER_LINUX_ARM64
+            -   ARCH: "amd64"
+                BIN_FOLDER_DEBIAN_PKG_AND_SIGN: $BIN_FOLDER_LINUX_X64
     before_script:
         - !reference [ .global_before_script, script ]
-        - BIN_FOLDER_DEBIAN_PKG_AND_SIGN=$([[ $ARCH == "arm64" ]] && echo ${BIN_FOLDER_LINUX_ARM64} || echo ${BIN_FOLDER_LINUX_X64})
     extends: .pkg_and_sign:debian
 
 
-pkg_and_sign:debian-test:
+pkg_and_sign:debian:test:
     parallel:
         matrix:
-            -   ARCH: [ "arm64", "amd64" ]
+            -   ARCH: "arm64"
+                BIN_FOLDER_DEBIAN_PKG_AND_SIGN: $BIN_FOLDER_LINUX_ARM64
+            -   ARCH: "amd64"
+                BIN_FOLDER_DEBIAN_PKG_AND_SIGN: $BIN_FOLDER_LINUX_X64
     before_script:
         - !reference [ .global_before_script, script ]
-        - BIN_FOLDER_DEBIAN_PKG_AND_SIGN=$([[ $ARCH == "arm64" ]] && echo ${BIN_FOLDER_LINUX_ARM64} || echo ${BIN_FOLDER_LINUX_X64})
     extends: .pkg_and_sign:debian
     artifacts:
         expire_in: 2 weeks
     rules:
-        -   if: '$CI_COMMIT_BRANCH == "test"'
+        -   if: $CI_COMMIT_BRANCH == "test"
 
 
 pkg_and_sign:macos:
     parallel:
         matrix:
-            -   BIN_FOLDER_MACOS_PKG_AND_SIGN: [ "$BIN_FOLDER_MACOS_ARM64", "$BIN_FOLDER_MACOS_X64" ]
+            -   ARCH: "arm64"
+                BIN_FOLDER_MACOS_PKG_AND_SIGN: $BIN_FOLDER_MACOS_ARM64
+            -   ARCH: "amd64"
+                BIN_FOLDER_MACOS_PKG_AND_SIGN: $BIN_FOLDER_MACOS_X64
     extends: .pkg_and_sign:macos
 
 
-pkg_and_sign:macos-test:
+pkg_and_sign:macos:test:
     parallel:
         matrix:
-            -   BIN_FOLDER_MACOS_PKG_AND_SIGN: [ "$BIN_FOLDER_MACOS_ARM64", "$BIN_FOLDER_MACOS_X64" ]
+            -   ARCH: "arm64"
+                BIN_FOLDER_MACOS_PKG_AND_SIGN: $BIN_FOLDER_MACOS_ARM64
+            -   ARCH: "amd64"
+                BIN_FOLDER_MACOS_PKG_AND_SIGN: $BIN_FOLDER_MACOS_X64
     extends: .pkg_and_sign:macos
     artifacts:
         expire_in: 2 weeks
     rules:
-        -   if: '$CI_COMMIT_BRANCH == "test"'
+        -   if: $CI_COMMIT_BRANCH == "test"
 
 
 
diff --git a/NodeApp/.gitlab-ci/08_stageClean.yml b/NodeApp/.gitlab-ci/08_stageClean.yml
index 0885aa18bbe5e7eac2a63f41b34e384aa8930317..8954bd7224bd9bc358e147ab60393f53d509a590 100644
--- a/NodeApp/.gitlab-ci/08_stageClean.yml
+++ b/NodeApp/.gitlab-ci/08_stageClean.yml
@@ -8,7 +8,7 @@ clean:release:
         - !reference [ .get_version_dependent_vars, script ]
         - !reference [ .clean_release, script ]
     rules:
-        -   if: '$CI_COMMIT_REF_PROTECTED == "true"'
+        -   if: $CI_COMMIT_REF_PROTECTED == "true"
 
 
 clean:packages:
@@ -21,10 +21,10 @@ clean:packages:
         - !reference [ .get_version_dependent_vars, script ]
         - !reference [ .clean_packages, script ]
     rules:
-        -   if: '$CI_COMMIT_REF_PROTECTED == "true"'
+        -   if: $CI_COMMIT_REF_PROTECTED == "true"
 
 
-clean:dev:release:
+clean:release:dev:
     stage: clean
     tags:
         - gitlab_clean
@@ -35,10 +35,10 @@ clean:dev:release:
         - !reference [ .get_version_dependent_vars, script ]
         - !reference [ .clean_release, script ]
     rules:
-        -   if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
+        -   if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
 
 
-clean:dev:packages:
+clean:packages:dev:
     stage: clean
     tags:
         - gitlab_clean
@@ -49,10 +49,10 @@ clean:dev:packages:
         - !reference [ .get_version_dependent_vars, script ]
         - !reference [ .clean_packages, script ]
     rules:
-        -   if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
+        -   if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
 
 
-clean:latest:release:
+clean:release:latest:
     stage: clean
     tags:
         - gitlab_clean
@@ -61,10 +61,10 @@ clean:latest:release:
         - VERSION="Latest"
         - !reference [ .clean_release, script ]
     rules:
-        -   if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
+        -   if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
 
 
-clean:pre-alpha:release:
+clean:release:pre-alpha:
     stage: clean
     tags:
         - gitlab_clean
@@ -73,4 +73,6 @@ clean:pre-alpha:release:
         - VERSION="Pre-alpha"
         - !reference [ .clean_release, script ]
     rules:
-        -   if: '$CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
\ No newline at end of file
+        -   if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+            when: never
+        -   if: $CI_COMMIT_REF_PROTECTED == "true"
\ No newline at end of file
diff --git a/NodeApp/.gitlab-ci/09_stageUpload.yml b/NodeApp/.gitlab-ci/09_stageUpload.yml
index 19b77df97d314f5f40fd8eacd133914249d16626..3cdbf6ffca1d5330163852675cbe4427ff53cf71 100644
--- a/NodeApp/.gitlab-ci/09_stageUpload.yml
+++ b/NodeApp/.gitlab-ci/09_stageUpload.yml
@@ -3,6 +3,7 @@ upload:packages:
     tags:
         - gitlab_package
     dependencies:
+        - build:version
         - pkg_and_sign:macos
         - pkg_and_sign:debian
     image: registry.gitlab.com/gitlab-ci-utils/curl-jq:latest
@@ -27,10 +28,10 @@ upload:packages:
         - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${BIN_FILE_WINDOWS_ARM64} "${PACKAGE_URL_WINDOWS_ARM64_BIN}";'
         - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${BIN_FILE_WINDOWS_X64} "${PACKAGE_URL_WINDOWS_X64_BIN}";'
     rules:
-        -   if: '$CI_COMMIT_REF_PROTECTED == "true"'
+        -   if: $CI_COMMIT_REF_PROTECTED == "true"
 
 
-upload:packages:wiki:
+upload:packages:doc:wiki:
     stage: upload
     tags:
         - gitlab_package
@@ -51,4 +52,4 @@ upload:packages:wiki:
         # Send package
         - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${WIKI_ARCHIVE_PATH} "${PACKAGE_URL_WIKI}";'
     rules:
-        -   if: '$CI_COMMIT_REF_PROTECTED == "true"'
\ No newline at end of file
+        -   if: $CI_COMMIT_REF_PROTECTED == "true"
\ No newline at end of file
diff --git a/NodeApp/.gitlab-ci/10_stageRelease.yml b/NodeApp/.gitlab-ci/10_stageRelease.yml
index d66bb5ae0e4b88cae9fdd47bb2d3905e32208fd9..e48306feedb7b625fc12e9a6f0a61d446d87d02f 100644
--- a/NodeApp/.gitlab-ci/10_stageRelease.yml
+++ b/NodeApp/.gitlab-ci/10_stageRelease.yml
@@ -1,4 +1,4 @@
-release:wiki:
+release:doc:wiki:
     stage: release
     tags:
         - release
@@ -44,7 +44,7 @@ release:wiki:
         # Push the change back to the master branch of the wiki
         - git push origin "HEAD:main"
     rules:
-        -   if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
+        -   if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
 
 
 release:gitlab:
@@ -58,10 +58,10 @@ release:gitlab:
         - RELEASE_NAME=$VERSION
         - !reference [ .release_gitlab, script ]
     rules:
-        -   if: '$CI_COMMIT_REF_PROTECTED == "true"'
+        -   if: $CI_COMMIT_REF_PROTECTED == "true"
 
 
-release:latest:gitlab:
+release:gitlab:latest:
     stage: release
     tags:
         - release
@@ -72,10 +72,10 @@ release:latest:gitlab:
         - RELEASE_NAME="Latest"
         - !reference [ .release_gitlab, script ]
     rules:
-        -   if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
+        -   if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
 
 
-release:pre-alpha:gitlab:
+release:gitlab:pre-alpha:
     stage: release
     tags:
         - release
@@ -86,4 +86,6 @@ release:pre-alpha:gitlab:
         - RELEASE_NAME="Pre-alpha"
         - !reference [ .release_gitlab, script ]
     rules:
-        -   if: '$CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
\ No newline at end of file
+        -   if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+            when: never
+        -   if: $CI_COMMIT_REF_PROTECTED == "true"
\ No newline at end of file
diff --git a/NodeApp/.idea/.gitignore b/NodeApp/.idea/.gitignore
index 13566b81b018ad684f3a35fee301741b2734c8f4..a9d7db9c0a81b2db47ca92e4e180b30090b27632 100644
--- a/NodeApp/.idea/.gitignore
+++ b/NodeApp/.idea/.gitignore
@@ -6,3 +6,5 @@
 # Datasource local storage ignored files
 /dataSources/
 /dataSources.local.xml
+# GitHub Copilot persisted chat sessions
+/copilot/chatSessions
diff --git a/NodeApp/.idea/DojoCLI.iml b/NodeApp/.idea/DojoCLI.iml
index d47865a1c8d932c244bdb1f09d5eb3295d5dc715..dd6c9d867f2ee5279c298de21610d6340db23dd7 100644
--- a/NodeApp/.idea/DojoCLI.iml
+++ b/NodeApp/.idea/DojoCLI.iml
@@ -12,4 +12,7 @@
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
   </component>
+  <component name="SonarLintModuleSettings">
+    <option name="uniqueId" value="7e31b6fd-1f86-4d26-8c50-c9ab6530f54e" />
+  </component>
 </module>
\ No newline at end of file
diff --git a/NodeApp/.idea/jetbrainsConfiguration b/NodeApp/.idea/jetbrainsConfiguration
index ffc5d65f9f0f0e825688177425e526131aa84631..ef5c7bd49a57bc28db77bad797de4980133d6523 160000
--- a/NodeApp/.idea/jetbrainsConfiguration
+++ b/NodeApp/.idea/jetbrainsConfiguration
@@ -1 +1 @@
-Subproject commit ffc5d65f9f0f0e825688177425e526131aa84631
+Subproject commit ef5c7bd49a57bc28db77bad797de4980133d6523
diff --git a/NodeApp/.idea/jsLinters/eslint.xml b/NodeApp/.idea/jsLinters/eslint.xml
index 541945bb0819b8ff4a3dae9431632ebd10e6f98b..8f07b941341104616456519be3dc0a7cd019c07d 100644
--- a/NodeApp/.idea/jsLinters/eslint.xml
+++ b/NodeApp/.idea/jsLinters/eslint.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="EslintConfiguration">
+    <custom-configuration-file used="false" path="./eslint.config.js" />
     <option name="fix-on-save" value="true" />
   </component>
 </project>
\ No newline at end of file
diff --git a/NodeApp/eslint.config.mjs b/NodeApp/eslint.config.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..ca4042203d54482e9b783dc1c2e32a82e47fdbb6
--- /dev/null
+++ b/NodeApp/eslint.config.mjs
@@ -0,0 +1,25 @@
+// @ts-check
+// @formatter:off
+
+import eslint from '@eslint/js';
+import tseslint from 'typescript-eslint';
+
+
+export default tseslint.config({
+                                   ignores: [ 'dist/*', 'node_modules/*', '.gitlab-ci.yml', 'eslint.config.mjs' ]
+                               }, eslint.configs.recommended, ...tseslint.configs.recommendedTypeChecked, {
+                                   languageOptions: {
+                                       parserOptions: {
+                                           project: true, tsconfigRootDir: import.meta.dirname
+                                       }
+                                   }
+                               }, {
+                                   plugins: {
+                                       '@typescript-eslint': tseslint.plugin
+                                   }, rules: {
+                                       '@typescript-eslint/no-unsafe-assignment': 'off',
+                                       '@typescript-eslint/no-unsafe-member-access': 'off',
+                                       '@typescript-eslint/require-await': 'off',
+                                       '@typescript-eslint/restrict-template-expressions': 'off',
+                                   }
+                               });
\ No newline at end of file
diff --git a/NodeApp/package-lock.json b/NodeApp/package-lock.json
index f971eb6179b3ab26a8212b4d1feca408ddb586fc..0f20b7049e6a6ebd54eda68af77b3bc6f38030a9 100644
--- a/NodeApp/package-lock.json
+++ b/NodeApp/package-lock.json
@@ -1,22 +1,25 @@
 {
     "name": "dojo_cli",
-    "version": "3.5.0",
+    "version": "4.0.0",
     "lockfileVersion": 3,
     "requires": true,
     "packages": {
         "": {
             "name": "dojo_cli",
-            "version": "3.5.0",
+            "version": "4.0.0",
             "license": "AGPLv3",
             "dependencies": {
-                "@gitbeaker/rest": "^39.34.3",
+                "@dotenvx/dotenvx": "^0.34.0",
+                "@eslint/js": "^9.0.0",
+                "@gitbeaker/core": "^40.0.3",
+                "@gitbeaker/requester-utils": "^40.0.3",
+                "@gitbeaker/rest": "^40.0.3",
                 "appdata-path": "^1.0.0",
-                "axios": "^1.6.5",
+                "axios": "^1.6.8",
                 "boxen": "^5.1.2",
                 "chalk": "^4.1.2",
-                "commander": "^11.1.0",
-                "dotenv": "^16.3.1",
-                "dotenv-expand": "^10.0.0",
+                "commander": "^12.0.0",
+                "form-data": "^4.0.0",
                 "fs-extra": "^11.2.0",
                 "http-status-codes": "^2.3.0",
                 "inquirer": "^8.2.6",
@@ -24,12 +27,13 @@
                 "jsonwebtoken": "^8.5.1",
                 "open": "^8.4.2",
                 "ora": "^5.4.1",
-                "semver": "^7.5.4",
-                "tar-stream": "^3.1.6",
-                "winston": "^3.11.0",
-                "yaml": "^2.3.4",
-                "zod": "^3.22.4",
-                "zod-validation-error": "^3.0.0"
+                "semver": "^7.6.0",
+                "tar-stream": "^3.1.7",
+                "winston": "^3.13.0",
+                "winston-transport": "^4.7.0",
+                "yaml": "^2.4.1",
+                "zod": "^3.22.5",
+                "zod-validation-error": "^3.1.0"
             },
             "bin": {
                 "dojo": "dist/app.js"
@@ -38,17 +42,19 @@
                 "@types/fs-extra": "^11.0.4",
                 "@types/inquirer": "^8.2.10",
                 "@types/jsonwebtoken": "^8.5.9",
-                "@types/node": "^18.19.2",
-                "@types/semver": "^7.5.6",
+                "@types/node": "^18.19.31",
+                "@types/semver": "^7.5.8",
                 "@types/tar-stream": "^3.1.3",
-                "@typescript-eslint/eslint-plugin": "^6.18.1",
-                "@typescript-eslint/parser": "^6.18.1",
-                "dotenv-vault": "^1.25.0",
+                "@typescript-eslint/eslint-plugin": "^7.7.0",
+                "@typescript-eslint/parser": "^7.7.0",
+                "dotenv-vault": "^1.26.1",
+                "eslint": "^8.57.0",
                 "genversion": "^3.2.0",
                 "pkg": "^5.8.1",
                 "tiny-typed-emitter": "^2.1.0",
-                "ts-node": "^10.9.2",
-                "typescript": "^5.3.3"
+                "tsx": "^4.7.2",
+                "typescript": "^5.4.5",
+                "typescript-eslint": "^7.7.0"
             }
         },
         "node_modules/@aashutoshrathi/word-wrap": {
@@ -56,7 +62,6 @@
             "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
             "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
             "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">=0.10.0"
             }
@@ -76,9 +81,9 @@
             }
         },
         "node_modules/@babel/helper-string-parser": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
-            "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
+            "version": "7.24.1",
+            "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
+            "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==",
             "dev": true,
             "engines": {
                 "node": ">=6.9.0"
@@ -159,6 +164,415 @@
                 "kuler": "^2.0.0"
             }
         },
+        "node_modules/@dotenvx/dotenvx": {
+            "version": "0.34.0",
+            "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-0.34.0.tgz",
+            "integrity": "sha512-0PiQUTGicI9M+pl9/GJpmzwa6E/MU0SI+K8JLTlplPIfeRv471Nd8qPBdBujCBH4kPt5maXvV5hCtdq+gV0pJw==",
+            "dependencies": {
+                "@inquirer/confirm": "^2.0.17",
+                "arch": "^2.1.1",
+                "chalk": "^4.1.2",
+                "commander": "^11.1.0",
+                "conf": "^10.2.0",
+                "dotenv": "^16.4.5",
+                "dotenv-expand": "^11.0.6",
+                "execa": "^5.1.1",
+                "glob": "^10.3.10",
+                "ignore": "^5.3.0",
+                "is-wsl": "^2.1.1",
+                "object-treeify": "1.1.33",
+                "open": "^8.4.2",
+                "ora": "^5.4.1",
+                "semver": "^7.3.4",
+                "undici": "^5.28.3",
+                "which": "^4.0.0",
+                "winston": "^3.11.0",
+                "xxhashjs": "^0.2.2"
+            },
+            "bin": {
+                "dotenvx": "src/cli/dotenvx.js",
+                "git-dotenvx": "src/cli/dotenvx.js"
+            },
+            "funding": {
+                "url": "https://dotenvx.com"
+            }
+        },
+        "node_modules/@dotenvx/dotenvx/node_modules/commander": {
+            "version": "11.1.0",
+            "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+            "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+            "engines": {
+                "node": ">=16"
+            }
+        },
+        "node_modules/@esbuild/aix-ppc64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
+            "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
+            "cpu": [
+                "ppc64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "aix"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/android-arm": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
+            "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
+            "cpu": [
+                "arm"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "android"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/android-arm64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
+            "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "android"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/android-x64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
+            "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "android"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/darwin-arm64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
+            "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "darwin"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/darwin-x64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
+            "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "darwin"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/freebsd-arm64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
+            "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "freebsd"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/freebsd-x64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
+            "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "freebsd"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-arm": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
+            "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
+            "cpu": [
+                "arm"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-arm64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
+            "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-ia32": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
+            "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
+            "cpu": [
+                "ia32"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-loong64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
+            "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
+            "cpu": [
+                "loong64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-mips64el": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
+            "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
+            "cpu": [
+                "mips64el"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-ppc64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
+            "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
+            "cpu": [
+                "ppc64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-riscv64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
+            "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
+            "cpu": [
+                "riscv64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-s390x": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
+            "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
+            "cpu": [
+                "s390x"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-x64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
+            "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/netbsd-x64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
+            "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "netbsd"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/openbsd-x64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
+            "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "openbsd"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/sunos-x64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
+            "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "sunos"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/win32-arm64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
+            "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "win32"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/win32-ia32": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
+            "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
+            "cpu": [
+                "ia32"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "win32"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/win32-x64": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
+            "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "optional": true,
+            "os": [
+                "win32"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
         "node_modules/@eslint-community/eslint-utils": {
             "version": "4.4.0",
             "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -188,7 +602,6 @@
             "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
             "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "ajv": "^6.12.4",
                 "debug": "^4.3.2",
@@ -207,19 +620,33 @@
                 "url": "https://opencollective.com/eslint"
             }
         },
+        "node_modules/@eslint/eslintrc/node_modules/ajv": {
+            "version": "6.12.6",
+            "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+            "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+            "dev": true,
+            "dependencies": {
+                "fast-deep-equal": "^3.1.1",
+                "fast-json-stable-stringify": "^2.0.0",
+                "json-schema-traverse": "^0.4.1",
+                "uri-js": "^4.2.2"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/epoberezkin"
+            }
+        },
         "node_modules/@eslint/eslintrc/node_modules/argparse": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
             "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
-            "dev": true,
-            "peer": true
+            "dev": true
         },
         "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
             "version": "1.1.11",
             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
             "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -230,7 +657,6 @@
             "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
             "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "argparse": "^2.0.1"
             },
@@ -238,12 +664,17 @@
                 "js-yaml": "bin/js-yaml.js"
             }
         },
+        "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+            "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+            "dev": true
+        },
         "node_modules/@eslint/eslintrc/node_modules/minimatch": {
             "version": "3.1.2",
             "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
             "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "brace-expansion": "^1.1.7"
             },
@@ -252,21 +683,27 @@
             }
         },
         "node_modules/@eslint/js": {
-            "version": "8.56.0",
-            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
-            "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
-            "dev": true,
-            "peer": true,
+            "version": "9.0.0",
+            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.0.0.tgz",
+            "integrity": "sha512-RThY/MnKrhubF6+s1JflwUjPEsnCEmYCWwqa/aRISKWNXGZ9epUwft4bUMM35SdKF9xvBrLydAM1RDHd1Z//ZQ==",
             "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+            }
+        },
+        "node_modules/@fastify/busboy": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
+            "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
+            "engines": {
+                "node": ">=14"
             }
         },
         "node_modules/@gitbeaker/core": {
-            "version": "39.34.3",
-            "resolved": "https://registry.npmjs.org/@gitbeaker/core/-/core-39.34.3.tgz",
-            "integrity": "sha512-/3qBXme2MjO38QU2F/MYGon9a4wHKrgtwNzdHHdjpbYJ2/wOGNgbEWSZcibcFkiWVgAjbPXdYqC5sY8hcwGO1w==",
+            "version": "40.0.3",
+            "resolved": "https://registry.npmjs.org/@gitbeaker/core/-/core-40.0.3.tgz",
+            "integrity": "sha512-MzeY4oCtoa9zmPIkQIdC2KU8cGmHIXwnAi0L6jjjouqjy6kcA4BydZf8W5Xsj27Rw5iiyhfj8YC1/O3CgrzvCQ==",
             "dependencies": {
-                "@gitbeaker/requester-utils": "^39.34.3",
+                "@gitbeaker/requester-utils": "^40.0.3",
                 "qs": "^6.11.2",
                 "xcase": "^2.0.1"
             },
@@ -275,9 +712,9 @@
             }
         },
         "node_modules/@gitbeaker/requester-utils": {
-            "version": "39.34.3",
-            "resolved": "https://registry.npmjs.org/@gitbeaker/requester-utils/-/requester-utils-39.34.3.tgz",
-            "integrity": "sha512-nMnTkTo4UixHPwPYsYIjp8UdKrmSw3TjvRESexliAeNNq4/LVeyVUyRqBUa1ZI8MXt1nPPnPX3wh8s7rqlm7uA==",
+            "version": "40.0.3",
+            "resolved": "https://registry.npmjs.org/@gitbeaker/requester-utils/-/requester-utils-40.0.3.tgz",
+            "integrity": "sha512-L8JpuMIsvXTHfu/2wXzkc5QyfQJSWg4XyEPStHq1ig5SAcbxxqbBoe8ed27eUXLah+PcGrPInMK4cCMxhQm41g==",
             "dependencies": {
                 "picomatch-browser": "^2.2.6",
                 "qs": "^6.11.2",
@@ -289,12 +726,12 @@
             }
         },
         "node_modules/@gitbeaker/rest": {
-            "version": "39.34.3",
-            "resolved": "https://registry.npmjs.org/@gitbeaker/rest/-/rest-39.34.3.tgz",
-            "integrity": "sha512-SuceThS6WhJtqNNcKmW8j0yUU7aXA4k5a29OWcd6bn7peQ3MXlIpbfvLLRnmuUaYUuxHLnUzZhAfuxaNf4DVtQ==",
+            "version": "40.0.3",
+            "resolved": "https://registry.npmjs.org/@gitbeaker/rest/-/rest-40.0.3.tgz",
+            "integrity": "sha512-ihaA0GX3yCo4oUWbISkcjFMIw+WxDAC9L+bEYq2irz4wpv/0EpAU/0jKjggPzY4cGWL9VAyPhew77VeACv4YWw==",
             "dependencies": {
-                "@gitbeaker/core": "^39.34.3",
-                "@gitbeaker/requester-utils": "^39.34.3"
+                "@gitbeaker/core": "^40.0.3",
+                "@gitbeaker/requester-utils": "^40.0.3"
             },
             "engines": {
                 "node": ">=18.0.0"
@@ -305,7 +742,6 @@
             "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
             "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "@humanwhocodes/object-schema": "^2.0.2",
                 "debug": "^4.3.1",
@@ -320,7 +756,6 @@
             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
             "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -331,44 +766,183 @@
             "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
             "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
             "dev": true,
-            "peer": true,
             "dependencies": {
-                "brace-expansion": "^1.1.7"
+                "brace-expansion": "^1.1.7"
+            },
+            "engines": {
+                "node": "*"
+            }
+        },
+        "node_modules/@humanwhocodes/module-importer": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+            "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+            "dev": true,
+            "engines": {
+                "node": ">=12.22"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/nzakas"
+            }
+        },
+        "node_modules/@humanwhocodes/object-schema": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+            "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+            "dev": true
+        },
+        "node_modules/@inquirer/confirm": {
+            "version": "2.0.17",
+            "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-2.0.17.tgz",
+            "integrity": "sha512-EqzhGryzmGpy2aJf6LxJVhndxYmFs+m8cxXzf8nejb1DE3sabf6mUgBcp4J0jAUEiAcYzqmkqRr7LPFh/WdnXA==",
+            "dependencies": {
+                "@inquirer/core": "^6.0.0",
+                "@inquirer/type": "^1.1.6",
+                "chalk": "^4.1.2"
+            },
+            "engines": {
+                "node": ">=14.18.0"
+            }
+        },
+        "node_modules/@inquirer/core": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-6.0.0.tgz",
+            "integrity": "sha512-fKi63Khkisgda3ohnskNf5uZJj+zXOaBvOllHsOkdsXRA/ubQLJQrZchFFi57NKbZzkTunXiBMdvWOv71alonw==",
+            "dependencies": {
+                "@inquirer/type": "^1.1.6",
+                "@types/mute-stream": "^0.0.4",
+                "@types/node": "^20.10.7",
+                "@types/wrap-ansi": "^3.0.0",
+                "ansi-escapes": "^4.3.2",
+                "chalk": "^4.1.2",
+                "cli-spinners": "^2.9.2",
+                "cli-width": "^4.1.0",
+                "figures": "^3.2.0",
+                "mute-stream": "^1.0.0",
+                "run-async": "^3.0.0",
+                "signal-exit": "^4.1.0",
+                "strip-ansi": "^6.0.1",
+                "wrap-ansi": "^6.2.0"
+            },
+            "engines": {
+                "node": ">=14.18.0"
+            }
+        },
+        "node_modules/@inquirer/core/node_modules/@types/node": {
+            "version": "20.12.7",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
+            "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
+            "dependencies": {
+                "undici-types": "~5.26.4"
+            }
+        },
+        "node_modules/@inquirer/type": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.3.0.tgz",
+            "integrity": "sha512-RW4Zf6RCTnInRaOZuRHTqAUl+v6VJuQGglir7nW2BkT3OXOphMhkIFhvFRjorBx2l0VwtC/M4No8vYR65TdN9Q==",
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/@isaacs/cliui": {
+            "version": "8.0.2",
+            "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+            "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+            "dependencies": {
+                "string-width": "^5.1.2",
+                "string-width-cjs": "npm:string-width@^4.2.0",
+                "strip-ansi": "^7.0.1",
+                "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+                "wrap-ansi": "^8.1.0",
+                "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+            "version": "6.0.1",
+            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+            "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+            "engines": {
+                "node": ">=12"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+            }
+        },
+        "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
+            "version": "6.2.1",
+            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+            "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+            "engines": {
+                "node": ">=12"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+            }
+        },
+        "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
+            "version": "9.2.2",
+            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+            "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
+        },
+        "node_modules/@isaacs/cliui/node_modules/string-width": {
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+            "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+            "dependencies": {
+                "eastasianwidth": "^0.2.0",
+                "emoji-regex": "^9.2.2",
+                "strip-ansi": "^7.0.1"
+            },
+            "engines": {
+                "node": ">=12"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+            "version": "7.1.0",
+            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+            "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+            "dependencies": {
+                "ansi-regex": "^6.0.1"
             },
             "engines": {
-                "node": "*"
+                "node": ">=12"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/strip-ansi?sponsor=1"
             }
         },
-        "node_modules/@humanwhocodes/module-importer": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
-            "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
-            "dev": true,
-            "peer": true,
+        "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
+            "version": "8.1.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+            "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+            "dependencies": {
+                "ansi-styles": "^6.1.0",
+                "string-width": "^5.0.1",
+                "strip-ansi": "^7.0.1"
+            },
             "engines": {
-                "node": ">=12.22"
+                "node": ">=12"
             },
             "funding": {
-                "type": "github",
-                "url": "https://github.com/sponsors/nzakas"
+                "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
             }
         },
-        "node_modules/@humanwhocodes/object-schema": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
-            "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
-            "dev": true,
-            "peer": true
-        },
         "node_modules/@jridgewell/gen-mapping": {
-            "version": "0.3.3",
-            "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
-            "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+            "version": "0.3.5",
+            "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+            "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
             "dev": true,
             "dependencies": {
-                "@jridgewell/set-array": "^1.0.1",
+                "@jridgewell/set-array": "^1.2.1",
                 "@jridgewell/sourcemap-codec": "^1.4.10",
-                "@jridgewell/trace-mapping": "^0.3.9"
+                "@jridgewell/trace-mapping": "^0.3.24"
             },
             "engines": {
                 "node": ">=6.0.0"
@@ -384,9 +958,9 @@
             }
         },
         "node_modules/@jridgewell/set-array": {
-            "version": "1.1.2",
-            "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
-            "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+            "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
             "dev": true,
             "engines": {
                 "node": ">=6.0.0"
@@ -399,9 +973,9 @@
             "dev": true
         },
         "node_modules/@jridgewell/trace-mapping": {
-            "version": "0.3.22",
-            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
-            "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
+            "version": "0.3.25",
+            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+            "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
             "dev": true,
             "dependencies": {
                 "@jridgewell/resolve-uri": "^3.1.0",
@@ -512,6 +1086,23 @@
                 "url": "https://github.com/chalk/supports-color?sponsor=1"
             }
         },
+        "node_modules/@oclif/core/node_modules/wrap-ansi": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^4.0.0",
+                "string-width": "^4.1.0",
+                "strip-ansi": "^6.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+            }
+        },
         "node_modules/@oclif/linewrap": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/@oclif/linewrap/-/linewrap-1.0.0.tgz",
@@ -531,9 +1122,9 @@
             }
         },
         "node_modules/@oclif/plugin-help/node_modules/@oclif/core": {
-            "version": "2.15.0",
-            "resolved": "https://registry.npmjs.org/@oclif/core/-/core-2.15.0.tgz",
-            "integrity": "sha512-fNEMG5DzJHhYmI3MgpByTvltBOMyFcnRIUMxbiz2ai8rhaYgaTHMG3Q38HcosfIvtw9nCjxpcQtC8MN8QtVCcA==",
+            "version": "2.16.0",
+            "resolved": "https://registry.npmjs.org/@oclif/core/-/core-2.16.0.tgz",
+            "integrity": "sha512-dL6atBH0zCZl1A1IXCKJgLPrM/wR7K+Wi401E/IvqsK8m2iCHW+0TEOGrans/cuN3oTW+uxIyJFHJ8Im0k4qBw==",
             "dev": true,
             "dependencies": {
                 "@types/cli-progress": "^3.11.0",
@@ -584,6 +1175,23 @@
                 "url": "https://github.com/chalk/supports-color?sponsor=1"
             }
         },
+        "node_modules/@oclif/plugin-help/node_modules/wrap-ansi": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^4.0.0",
+                "string-width": "^4.1.0",
+                "strip-ansi": "^6.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+            }
+        },
         "node_modules/@oclif/plugin-not-found": {
             "version": "2.4.3",
             "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-2.4.3.tgz",
@@ -599,9 +1207,9 @@
             }
         },
         "node_modules/@oclif/plugin-not-found/node_modules/@oclif/core": {
-            "version": "2.15.0",
-            "resolved": "https://registry.npmjs.org/@oclif/core/-/core-2.15.0.tgz",
-            "integrity": "sha512-fNEMG5DzJHhYmI3MgpByTvltBOMyFcnRIUMxbiz2ai8rhaYgaTHMG3Q38HcosfIvtw9nCjxpcQtC8MN8QtVCcA==",
+            "version": "2.16.0",
+            "resolved": "https://registry.npmjs.org/@oclif/core/-/core-2.16.0.tgz",
+            "integrity": "sha512-dL6atBH0zCZl1A1IXCKJgLPrM/wR7K+Wi401E/IvqsK8m2iCHW+0TEOGrans/cuN3oTW+uxIyJFHJ8Im0k4qBw==",
             "dev": true,
             "dependencies": {
                 "@types/cli-progress": "^3.11.0",
@@ -652,6 +1260,23 @@
                 "url": "https://github.com/chalk/supports-color?sponsor=1"
             }
         },
+        "node_modules/@oclif/plugin-not-found/node_modules/wrap-ansi": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^4.0.0",
+                "string-width": "^4.1.0",
+                "strip-ansi": "^6.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+            }
+        },
         "node_modules/@oclif/plugin-update": {
             "version": "3.2.4",
             "resolved": "https://registry.npmjs.org/@oclif/plugin-update/-/plugin-update-3.2.4.tgz",
@@ -676,9 +1301,9 @@
             }
         },
         "node_modules/@oclif/plugin-update/node_modules/@oclif/core": {
-            "version": "2.15.0",
-            "resolved": "https://registry.npmjs.org/@oclif/core/-/core-2.15.0.tgz",
-            "integrity": "sha512-fNEMG5DzJHhYmI3MgpByTvltBOMyFcnRIUMxbiz2ai8rhaYgaTHMG3Q38HcosfIvtw9nCjxpcQtC8MN8QtVCcA==",
+            "version": "2.16.0",
+            "resolved": "https://registry.npmjs.org/@oclif/core/-/core-2.16.0.tgz",
+            "integrity": "sha512-dL6atBH0zCZl1A1IXCKJgLPrM/wR7K+Wi401E/IvqsK8m2iCHW+0TEOGrans/cuN3oTW+uxIyJFHJ8Im0k4qBw==",
             "dev": true,
             "dependencies": {
                 "@types/cli-progress": "^3.11.0",
@@ -744,6 +1369,23 @@
                 "url": "https://github.com/chalk/supports-color?sponsor=1"
             }
         },
+        "node_modules/@oclif/plugin-update/node_modules/wrap-ansi": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^4.0.0",
+                "string-width": "^4.1.0",
+                "strip-ansi": "^6.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+            }
+        },
         "node_modules/@oclif/plugin-warn-if-update-available": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/@oclif/plugin-warn-if-update-available/-/plugin-warn-if-update-available-2.1.1.tgz",
@@ -762,9 +1404,9 @@
             }
         },
         "node_modules/@oclif/plugin-warn-if-update-available/node_modules/@oclif/core": {
-            "version": "2.15.0",
-            "resolved": "https://registry.npmjs.org/@oclif/core/-/core-2.15.0.tgz",
-            "integrity": "sha512-fNEMG5DzJHhYmI3MgpByTvltBOMyFcnRIUMxbiz2ai8rhaYgaTHMG3Q38HcosfIvtw9nCjxpcQtC8MN8QtVCcA==",
+            "version": "2.16.0",
+            "resolved": "https://registry.npmjs.org/@oclif/core/-/core-2.16.0.tgz",
+            "integrity": "sha512-dL6atBH0zCZl1A1IXCKJgLPrM/wR7K+Wi401E/IvqsK8m2iCHW+0TEOGrans/cuN3oTW+uxIyJFHJ8Im0k4qBw==",
             "dev": true,
             "dependencies": {
                 "@types/cli-progress": "^3.11.0",
@@ -815,6 +1457,23 @@
                 "url": "https://github.com/chalk/supports-color?sponsor=1"
             }
         },
+        "node_modules/@oclif/plugin-warn-if-update-available/node_modules/wrap-ansi": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^4.0.0",
+                "string-width": "^4.1.0",
+                "strip-ansi": "^6.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+            }
+        },
         "node_modules/@oclif/screen": {
             "version": "3.0.8",
             "resolved": "https://registry.npmjs.org/@oclif/screen/-/screen-3.0.8.tgz",
@@ -825,10 +1484,19 @@
                 "node": ">=12.0.0"
             }
         },
+        "node_modules/@pkgjs/parseargs": {
+            "version": "0.11.0",
+            "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+            "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+            "optional": true,
+            "engines": {
+                "node": ">=14"
+            }
+        },
         "node_modules/@tsconfig/node10": {
-            "version": "1.0.9",
-            "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
-            "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+            "version": "1.0.11",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
+            "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
             "dev": true
         },
         "node_modules/@tsconfig/node12": {
@@ -902,19 +1570,26 @@
                 "@types/node": "*"
             }
         },
+        "node_modules/@types/mute-stream": {
+            "version": "0.0.4",
+            "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz",
+            "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==",
+            "dependencies": {
+                "@types/node": "*"
+            }
+        },
         "node_modules/@types/node": {
-            "version": "18.19.17",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.17.tgz",
-            "integrity": "sha512-SzyGKgwPzuWp2SHhlpXKzCX0pIOfcI4V2eF37nNBJOhwlegQ83omtVQ1XxZpDE06V/d6AQvfQdPfnw0tRC//Ng==",
-            "dev": true,
+            "version": "18.19.31",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz",
+            "integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==",
             "dependencies": {
                 "undici-types": "~5.26.4"
             }
         },
         "node_modules/@types/semver": {
-            "version": "7.5.7",
-            "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz",
-            "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==",
+            "version": "7.5.8",
+            "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
+            "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
             "dev": true
         },
         "node_modules/@types/tar-stream": {
@@ -940,34 +1615,39 @@
             "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
             "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="
         },
+        "node_modules/@types/wrap-ansi": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz",
+            "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g=="
+        },
         "node_modules/@typescript-eslint/eslint-plugin": {
-            "version": "6.21.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
-            "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==",
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.0.tgz",
+            "integrity": "sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==",
             "dev": true,
             "dependencies": {
-                "@eslint-community/regexpp": "^4.5.1",
-                "@typescript-eslint/scope-manager": "6.21.0",
-                "@typescript-eslint/type-utils": "6.21.0",
-                "@typescript-eslint/utils": "6.21.0",
-                "@typescript-eslint/visitor-keys": "6.21.0",
+                "@eslint-community/regexpp": "^4.10.0",
+                "@typescript-eslint/scope-manager": "7.7.0",
+                "@typescript-eslint/type-utils": "7.7.0",
+                "@typescript-eslint/utils": "7.7.0",
+                "@typescript-eslint/visitor-keys": "7.7.0",
                 "debug": "^4.3.4",
                 "graphemer": "^1.4.0",
-                "ignore": "^5.2.4",
+                "ignore": "^5.3.1",
                 "natural-compare": "^1.4.0",
-                "semver": "^7.5.4",
-                "ts-api-utils": "^1.0.1"
+                "semver": "^7.6.0",
+                "ts-api-utils": "^1.3.0"
             },
             "engines": {
-                "node": "^16.0.0 || >=18.0.0"
+                "node": "^18.18.0 || >=20.0.0"
             },
             "funding": {
                 "type": "opencollective",
                 "url": "https://opencollective.com/typescript-eslint"
             },
             "peerDependencies": {
-                "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
-                "eslint": "^7.0.0 || ^8.0.0"
+                "@typescript-eslint/parser": "^7.0.0",
+                "eslint": "^8.56.0"
             },
             "peerDependenciesMeta": {
                 "typescript": {
@@ -976,26 +1656,26 @@
             }
         },
         "node_modules/@typescript-eslint/parser": {
-            "version": "6.21.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
-            "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.0.tgz",
+            "integrity": "sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/scope-manager": "6.21.0",
-                "@typescript-eslint/types": "6.21.0",
-                "@typescript-eslint/typescript-estree": "6.21.0",
-                "@typescript-eslint/visitor-keys": "6.21.0",
+                "@typescript-eslint/scope-manager": "7.7.0",
+                "@typescript-eslint/types": "7.7.0",
+                "@typescript-eslint/typescript-estree": "7.7.0",
+                "@typescript-eslint/visitor-keys": "7.7.0",
                 "debug": "^4.3.4"
             },
             "engines": {
-                "node": "^16.0.0 || >=18.0.0"
+                "node": "^18.18.0 || >=20.0.0"
             },
             "funding": {
                 "type": "opencollective",
                 "url": "https://opencollective.com/typescript-eslint"
             },
             "peerDependencies": {
-                "eslint": "^7.0.0 || ^8.0.0"
+                "eslint": "^8.56.0"
             },
             "peerDependenciesMeta": {
                 "typescript": {
@@ -1004,16 +1684,16 @@
             }
         },
         "node_modules/@typescript-eslint/scope-manager": {
-            "version": "6.21.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
-            "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.0.tgz",
+            "integrity": "sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/types": "6.21.0",
-                "@typescript-eslint/visitor-keys": "6.21.0"
+                "@typescript-eslint/types": "7.7.0",
+                "@typescript-eslint/visitor-keys": "7.7.0"
             },
             "engines": {
-                "node": "^16.0.0 || >=18.0.0"
+                "node": "^18.18.0 || >=20.0.0"
             },
             "funding": {
                 "type": "opencollective",
@@ -1021,25 +1701,25 @@
             }
         },
         "node_modules/@typescript-eslint/type-utils": {
-            "version": "6.21.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz",
-            "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==",
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.0.tgz",
+            "integrity": "sha512-bOp3ejoRYrhAlnT/bozNQi3nio9tIgv3U5C0mVDdZC7cpcQEDZXvq8inrHYghLVwuNABRqrMW5tzAv88Vy77Sg==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/typescript-estree": "6.21.0",
-                "@typescript-eslint/utils": "6.21.0",
+                "@typescript-eslint/typescript-estree": "7.7.0",
+                "@typescript-eslint/utils": "7.7.0",
                 "debug": "^4.3.4",
-                "ts-api-utils": "^1.0.1"
+                "ts-api-utils": "^1.3.0"
             },
             "engines": {
-                "node": "^16.0.0 || >=18.0.0"
+                "node": "^18.18.0 || >=20.0.0"
             },
             "funding": {
                 "type": "opencollective",
                 "url": "https://opencollective.com/typescript-eslint"
             },
             "peerDependencies": {
-                "eslint": "^7.0.0 || ^8.0.0"
+                "eslint": "^8.56.0"
             },
             "peerDependenciesMeta": {
                 "typescript": {
@@ -1048,12 +1728,12 @@
             }
         },
         "node_modules/@typescript-eslint/types": {
-            "version": "6.21.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
-            "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.0.tgz",
+            "integrity": "sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==",
             "dev": true,
             "engines": {
-                "node": "^16.0.0 || >=18.0.0"
+                "node": "^18.18.0 || >=20.0.0"
             },
             "funding": {
                 "type": "opencollective",
@@ -1061,22 +1741,22 @@
             }
         },
         "node_modules/@typescript-eslint/typescript-estree": {
-            "version": "6.21.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
-            "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.0.tgz",
+            "integrity": "sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/types": "6.21.0",
-                "@typescript-eslint/visitor-keys": "6.21.0",
+                "@typescript-eslint/types": "7.7.0",
+                "@typescript-eslint/visitor-keys": "7.7.0",
                 "debug": "^4.3.4",
                 "globby": "^11.1.0",
                 "is-glob": "^4.0.3",
-                "minimatch": "9.0.3",
-                "semver": "^7.5.4",
-                "ts-api-utils": "^1.0.1"
+                "minimatch": "^9.0.4",
+                "semver": "^7.6.0",
+                "ts-api-utils": "^1.3.0"
             },
             "engines": {
-                "node": "^16.0.0 || >=18.0.0"
+                "node": "^18.18.0 || >=20.0.0"
             },
             "funding": {
                 "type": "opencollective",
@@ -1089,41 +1769,41 @@
             }
         },
         "node_modules/@typescript-eslint/utils": {
-            "version": "6.21.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
-            "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.0.tgz",
+            "integrity": "sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==",
             "dev": true,
             "dependencies": {
                 "@eslint-community/eslint-utils": "^4.4.0",
-                "@types/json-schema": "^7.0.12",
-                "@types/semver": "^7.5.0",
-                "@typescript-eslint/scope-manager": "6.21.0",
-                "@typescript-eslint/types": "6.21.0",
-                "@typescript-eslint/typescript-estree": "6.21.0",
-                "semver": "^7.5.4"
+                "@types/json-schema": "^7.0.15",
+                "@types/semver": "^7.5.8",
+                "@typescript-eslint/scope-manager": "7.7.0",
+                "@typescript-eslint/types": "7.7.0",
+                "@typescript-eslint/typescript-estree": "7.7.0",
+                "semver": "^7.6.0"
             },
             "engines": {
-                "node": "^16.0.0 || >=18.0.0"
+                "node": "^18.18.0 || >=20.0.0"
             },
             "funding": {
                 "type": "opencollective",
                 "url": "https://opencollective.com/typescript-eslint"
             },
             "peerDependencies": {
-                "eslint": "^7.0.0 || ^8.0.0"
+                "eslint": "^8.56.0"
             }
         },
         "node_modules/@typescript-eslint/visitor-keys": {
-            "version": "6.21.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
-            "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.0.tgz",
+            "integrity": "sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/types": "6.21.0",
-                "eslint-visitor-keys": "^3.4.1"
+                "@typescript-eslint/types": "7.7.0",
+                "eslint-visitor-keys": "^3.4.3"
             },
             "engines": {
-                "node": "^16.0.0 || >=18.0.0"
+                "node": "^18.18.0 || >=20.0.0"
             },
             "funding": {
                 "type": "opencollective",
@@ -1134,8 +1814,7 @@
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
             "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
-            "dev": true,
-            "peer": true
+            "dev": true
         },
         "node_modules/acorn": {
             "version": "8.11.3",
@@ -1154,7 +1833,6 @@
             "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
             "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
             "dev": true,
-            "peer": true,
             "peerDependencies": {
                 "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
             }
@@ -1181,15 +1859,13 @@
             }
         },
         "node_modules/ajv": {
-            "version": "6.12.6",
-            "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
-            "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
-            "dev": true,
-            "peer": true,
+            "version": "8.12.0",
+            "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+            "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
             "dependencies": {
                 "fast-deep-equal": "^3.1.1",
-                "fast-json-stable-stringify": "^2.0.0",
-                "json-schema-traverse": "^0.4.1",
+                "json-schema-traverse": "^1.0.0",
+                "require-from-string": "^2.0.2",
                 "uri-js": "^4.2.2"
             },
             "funding": {
@@ -1197,6 +1873,22 @@
                 "url": "https://github.com/sponsors/epoberezkin"
             }
         },
+        "node_modules/ajv-formats": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+            "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+            "dependencies": {
+                "ajv": "^8.0.0"
+            },
+            "peerDependencies": {
+                "ajv": "^8.0.0"
+            },
+            "peerDependenciesMeta": {
+                "ajv": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/ansi-align": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
@@ -1219,17 +1911,6 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
-        "node_modules/ansi-escapes/node_modules/type-fest": {
-            "version": "0.21.3",
-            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
-            "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
         "node_modules/ansi-regex": {
             "version": "5.0.1",
             "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -1263,6 +1944,25 @@
             "resolved": "https://registry.npmjs.org/appdata-path/-/appdata-path-1.0.0.tgz",
             "integrity": "sha512-ZbH3ezXfnT/YE3NdqduIt4lBV+H0ybvA2Qx3K76gIjQvh8gROpDFdDLpx6B1QJtW7zxisCbpTlCLhKqoR8cDBw=="
         },
+        "node_modules/arch": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
+            "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==",
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/feross"
+                },
+                {
+                    "type": "patreon",
+                    "url": "https://www.patreon.com/feross"
+                },
+                {
+                    "type": "consulting",
+                    "url": "https://feross.org/support"
+                }
+            ]
+        },
         "node_modules/arg": {
             "version": "4.1.3",
             "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
@@ -1315,12 +2015,20 @@
                 "node": ">= 4.0.0"
             }
         },
+        "node_modules/atomically": {
+            "version": "1.7.0",
+            "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz",
+            "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==",
+            "engines": {
+                "node": ">=10.12.0"
+            }
+        },
         "node_modules/axios": {
-            "version": "1.6.7",
-            "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
-            "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+            "version": "1.6.8",
+            "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
+            "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
             "dependencies": {
-                "follow-redirects": "^1.15.4",
+                "follow-redirects": "^1.15.6",
                 "form-data": "^4.0.0",
                 "proxy-from-env": "^1.1.0"
             }
@@ -1333,13 +2041,12 @@
         "node_modules/balanced-match": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
-            "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
-            "dev": true
+            "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
         },
         "node_modules/bare-events": {
-            "version": "2.2.0",
-            "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.0.tgz",
-            "integrity": "sha512-Yyyqff4PIFfSuthCZqLlPISTWHmnQxoPuAvkmgzsJEmG3CesdIv6Xweayl0JkCZJSB2yYIdJyEz97tpxNhgjbg==",
+            "version": "2.2.2",
+            "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz",
+            "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==",
             "optional": true
         },
         "node_modules/base64-js": {
@@ -1392,11 +2099,37 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/boxen/node_modules/type-fest": {
+            "version": "0.20.2",
+            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+            "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/boxen/node_modules/wrap-ansi": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+            "dependencies": {
+                "ansi-styles": "^4.0.0",
+                "string-width": "^4.1.0",
+                "strip-ansi": "^6.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+            }
+        },
         "node_modules/brace-expansion": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
             "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
-            "dev": true,
             "dependencies": {
                 "balanced-match": "^1.0.0"
             }
@@ -1473,7 +2206,6 @@
             "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
             "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
             "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">=6"
             }
@@ -1589,11 +2321,11 @@
             }
         },
         "node_modules/cli-width": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
-            "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
+            "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
             "engines": {
-                "node": ">= 10"
+                "node": ">= 12"
             }
         },
         "node_modules/cliui": {
@@ -1607,6 +2339,23 @@
                 "wrap-ansi": "^7.0.0"
             }
         },
+        "node_modules/cliui/node_modules/wrap-ansi": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^4.0.0",
+                "string-width": "^4.1.0",
+                "strip-ansi": "^6.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+            }
+        },
         "node_modules/clone": {
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
@@ -1683,11 +2432,11 @@
             }
         },
         "node_modules/commander": {
-            "version": "11.1.0",
-            "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
-            "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+            "version": "12.0.0",
+            "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
+            "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==",
             "engines": {
-                "node": ">=16"
+                "node": ">=18"
             }
         },
         "node_modules/concat-map": {
@@ -1696,6 +2445,29 @@
             "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
             "dev": true
         },
+        "node_modules/conf": {
+            "version": "10.2.0",
+            "resolved": "https://registry.npmjs.org/conf/-/conf-10.2.0.tgz",
+            "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==",
+            "dependencies": {
+                "ajv": "^8.6.3",
+                "ajv-formats": "^2.1.1",
+                "atomically": "^1.7.0",
+                "debounce-fn": "^4.0.0",
+                "dot-prop": "^6.0.1",
+                "env-paths": "^2.2.1",
+                "json-schema-typed": "^7.0.3",
+                "onetime": "^5.1.2",
+                "pkg-up": "^3.1.0",
+                "semver": "^7.3.5"
+            },
+            "engines": {
+                "node": ">=12"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/content-type": {
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
@@ -1721,14 +2493,51 @@
             "version": "7.0.3",
             "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
             "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
-            "dev": true,
             "dependencies": {
                 "path-key": "^3.1.0",
                 "shebang-command": "^2.0.0",
                 "which": "^2.0.1"
             },
-            "engines": {
-                "node": ">= 8"
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/cross-spawn/node_modules/isexe": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+            "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+        },
+        "node_modules/cross-spawn/node_modules/which": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+            "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+            "dependencies": {
+                "isexe": "^2.0.0"
+            },
+            "bin": {
+                "node-which": "bin/node-which"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/cuint": {
+            "version": "0.2.2",
+            "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
+            "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw=="
+        },
+        "node_modules/debounce-fn": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz",
+            "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==",
+            "dependencies": {
+                "mimic-fn": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
             }
         },
         "node_modules/debug": {
@@ -1776,8 +2585,7 @@
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
             "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
-            "dev": true,
-            "peer": true
+            "dev": true
         },
         "node_modules/defaults": {
             "version": "1.0.4",
@@ -1823,9 +2631,9 @@
             }
         },
         "node_modules/detect-libc": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
-            "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
+            "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
             "dev": true,
             "engines": {
                 "node": ">=8"
@@ -1857,7 +2665,6 @@
             "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
             "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "esutils": "^2.0.2"
             },
@@ -1865,6 +2672,20 @@
                 "node": ">=6.0.0"
             }
         },
+        "node_modules/dot-prop": {
+            "version": "6.0.1",
+            "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
+            "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
+            "dependencies": {
+                "is-obj": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/dotenv": {
             "version": "16.4.5",
             "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
@@ -1877,17 +2698,23 @@
             }
         },
         "node_modules/dotenv-expand": {
-            "version": "10.0.0",
-            "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
-            "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==",
+            "version": "11.0.6",
+            "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz",
+            "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==",
+            "dependencies": {
+                "dotenv": "^16.4.4"
+            },
             "engines": {
                 "node": ">=12"
+            },
+            "funding": {
+                "url": "https://dotenvx.com"
             }
         },
         "node_modules/dotenv-vault": {
-            "version": "1.26.0",
-            "resolved": "https://registry.npmjs.org/dotenv-vault/-/dotenv-vault-1.26.0.tgz",
-            "integrity": "sha512-2PNnlprtOdFEG9+hAAZxXegcjlJVZMSy88arnRR4YjwU/PwkDbdtk1uzw/D88D5EZ0b84n7YVQ6RccRXmW/Qzg==",
+            "version": "1.26.1",
+            "resolved": "https://registry.npmjs.org/dotenv-vault/-/dotenv-vault-1.26.1.tgz",
+            "integrity": "sha512-v+RK6LXpJQWhaelTT2s0b5FQB0qziRBuGCrAgAeDHtgkDEA0NqF7OXYXsrnKTuCPnwBg0FmNJr4lZebCpJnrFA==",
             "dev": true,
             "dependencies": {
                 "@oclif/core": "^1",
@@ -1895,7 +2722,7 @@
                 "@oclif/plugin-not-found": "^2.3.34",
                 "@oclif/plugin-update": "^3.1.16",
                 "@oclif/plugin-warn-if-update-available": "^2.0.46",
-                "axios": "^0.27.2",
+                "axios": "^1.6.7",
                 "chalk": "^4.1.2",
                 "dotenv": "^16.3.1"
             },
@@ -1906,15 +2733,10 @@
                 "node": ">=16"
             }
         },
-        "node_modules/dotenv-vault/node_modules/axios": {
-            "version": "0.27.2",
-            "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
-            "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
-            "dev": true,
-            "dependencies": {
-                "follow-redirects": "^1.14.9",
-                "form-data": "^4.0.0"
-            }
+        "node_modules/eastasianwidth": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+            "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
         },
         "node_modules/ecdsa-sig-formatter": {
             "version": "1.0.11",
@@ -1925,9 +2747,9 @@
             }
         },
         "node_modules/ejs": {
-            "version": "3.1.9",
-            "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
-            "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
+            "version": "3.1.10",
+            "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+            "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
             "dev": true,
             "dependencies": {
                 "jake": "^10.8.5"
@@ -1958,6 +2780,14 @@
                 "once": "^1.4.0"
             }
         },
+        "node_modules/env-paths": {
+            "version": "2.2.1",
+            "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+            "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/error-ex": {
             "version": "1.3.2",
             "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -1986,6 +2816,44 @@
                 "node": ">= 0.4"
             }
         },
+        "node_modules/esbuild": {
+            "version": "0.19.12",
+            "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
+            "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
+            "dev": true,
+            "hasInstallScript": true,
+            "bin": {
+                "esbuild": "bin/esbuild"
+            },
+            "engines": {
+                "node": ">=12"
+            },
+            "optionalDependencies": {
+                "@esbuild/aix-ppc64": "0.19.12",
+                "@esbuild/android-arm": "0.19.12",
+                "@esbuild/android-arm64": "0.19.12",
+                "@esbuild/android-x64": "0.19.12",
+                "@esbuild/darwin-arm64": "0.19.12",
+                "@esbuild/darwin-x64": "0.19.12",
+                "@esbuild/freebsd-arm64": "0.19.12",
+                "@esbuild/freebsd-x64": "0.19.12",
+                "@esbuild/linux-arm": "0.19.12",
+                "@esbuild/linux-arm64": "0.19.12",
+                "@esbuild/linux-ia32": "0.19.12",
+                "@esbuild/linux-loong64": "0.19.12",
+                "@esbuild/linux-mips64el": "0.19.12",
+                "@esbuild/linux-ppc64": "0.19.12",
+                "@esbuild/linux-riscv64": "0.19.12",
+                "@esbuild/linux-s390x": "0.19.12",
+                "@esbuild/linux-x64": "0.19.12",
+                "@esbuild/netbsd-x64": "0.19.12",
+                "@esbuild/openbsd-x64": "0.19.12",
+                "@esbuild/sunos-x64": "0.19.12",
+                "@esbuild/win32-arm64": "0.19.12",
+                "@esbuild/win32-ia32": "0.19.12",
+                "@esbuild/win32-x64": "0.19.12"
+            }
+        },
         "node_modules/escalade": {
             "version": "3.1.2",
             "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
@@ -2008,17 +2876,16 @@
             }
         },
         "node_modules/eslint": {
-            "version": "8.56.0",
-            "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
-            "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
+            "version": "8.57.0",
+            "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
+            "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "@eslint-community/eslint-utils": "^4.2.0",
                 "@eslint-community/regexpp": "^4.6.1",
                 "@eslint/eslintrc": "^2.1.4",
-                "@eslint/js": "8.56.0",
-                "@humanwhocodes/config-array": "^0.11.13",
+                "@eslint/js": "8.57.0",
+                "@humanwhocodes/config-array": "^0.11.14",
                 "@humanwhocodes/module-importer": "^1.0.1",
                 "@nodelib/fs.walk": "^1.2.8",
                 "@ungap/structured-clone": "^1.2.0",
@@ -2068,7 +2935,6 @@
             "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
             "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "esrecurse": "^4.3.0",
                 "estraverse": "^5.2.0"
@@ -2092,19 +2958,42 @@
                 "url": "https://opencollective.com/eslint"
             }
         },
+        "node_modules/eslint/node_modules/@eslint/js": {
+            "version": "8.57.0",
+            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
+            "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+            "dev": true,
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            }
+        },
+        "node_modules/eslint/node_modules/ajv": {
+            "version": "6.12.6",
+            "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+            "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+            "dev": true,
+            "dependencies": {
+                "fast-deep-equal": "^3.1.1",
+                "fast-json-stable-stringify": "^2.0.0",
+                "json-schema-traverse": "^0.4.1",
+                "uri-js": "^4.2.2"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/epoberezkin"
+            }
+        },
         "node_modules/eslint/node_modules/argparse": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
             "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
-            "dev": true,
-            "peer": true
+            "dev": true
         },
         "node_modules/eslint/node_modules/brace-expansion": {
             "version": "1.1.11",
             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
             "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -2115,7 +3004,6 @@
             "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
             "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "argparse": "^2.0.1"
             },
@@ -2123,12 +3011,17 @@
                 "js-yaml": "bin/js-yaml.js"
             }
         },
+        "node_modules/eslint/node_modules/json-schema-traverse": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+            "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+            "dev": true
+        },
         "node_modules/eslint/node_modules/minimatch": {
             "version": "3.1.2",
             "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
             "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "brace-expansion": "^1.1.7"
             },
@@ -2141,7 +3034,6 @@
             "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
             "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "acorn": "^8.9.0",
                 "acorn-jsx": "^5.3.2",
@@ -2172,7 +3064,6 @@
             "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
             "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "estraverse": "^5.1.0"
             },
@@ -2185,7 +3076,6 @@
             "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
             "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "estraverse": "^5.2.0"
             },
@@ -2198,7 +3088,6 @@
             "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
             "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
             "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">=4.0"
             }
@@ -2208,11 +3097,37 @@
             "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
             "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
             "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/execa": {
+            "version": "5.1.1",
+            "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+            "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+            "dependencies": {
+                "cross-spawn": "^7.0.3",
+                "get-stream": "^6.0.0",
+                "human-signals": "^2.1.0",
+                "is-stream": "^2.0.0",
+                "merge-stream": "^2.0.0",
+                "npm-run-path": "^4.0.1",
+                "onetime": "^5.1.2",
+                "signal-exit": "^3.0.3",
+                "strip-final-newline": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sindresorhus/execa?sponsor=1"
+            }
+        },
+        "node_modules/execa/node_modules/signal-exit": {
+            "version": "3.0.7",
+            "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+            "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+        },
         "node_modules/expand-template": {
             "version": "2.0.3",
             "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
@@ -2238,9 +3153,7 @@
         "node_modules/fast-deep-equal": {
             "version": "3.1.3",
             "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-            "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-            "dev": true,
-            "peer": true
+            "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
         },
         "node_modules/fast-fifo": {
             "version": "1.3.2",
@@ -2279,8 +3192,7 @@
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
             "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
-            "dev": true,
-            "peer": true
+            "dev": true
         },
         "node_modules/fast-levenshtein": {
             "version": "3.0.0",
@@ -2341,7 +3253,6 @@
             "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
             "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "flat-cache": "^3.0.4"
             },
@@ -2405,7 +3316,6 @@
             "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
             "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "locate-path": "^6.0.0",
                 "path-exists": "^4.0.0"
@@ -2422,7 +3332,6 @@
             "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
             "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "flatted": "^3.2.9",
                 "keyv": "^4.5.3",
@@ -2436,8 +3345,7 @@
             "version": "3.3.1",
             "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
             "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
-            "dev": true,
-            "peer": true
+            "dev": true
         },
         "node_modules/fn.name": {
             "version": "1.1.0",
@@ -2445,9 +3353,9 @@
             "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
         },
         "node_modules/follow-redirects": {
-            "version": "1.15.5",
-            "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
-            "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
+            "version": "1.15.6",
+            "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+            "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
             "funding": [
                 {
                     "type": "individual",
@@ -2463,6 +3371,21 @@
                 }
             }
         },
+        "node_modules/foreground-child": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+            "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+            "dependencies": {
+                "cross-spawn": "^7.0.0",
+                "signal-exit": "^4.0.1"
+            },
+            "engines": {
+                "node": ">=14"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
         "node_modules/form-data": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@@ -2539,8 +3462,21 @@
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
             "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+            "dev": true
+        },
+        "node_modules/fsevents": {
+            "version": "2.3.3",
+            "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+            "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
             "dev": true,
-            "peer": true
+            "hasInstallScript": true,
+            "optional": true,
+            "os": [
+                "darwin"
+            ],
+            "engines": {
+                "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+            }
         },
         "node_modules/function-bind": {
             "version": "1.1.2",
@@ -2612,6 +3548,29 @@
                 "node": ">=8.0.0"
             }
         },
+        "node_modules/get-stream": {
+            "version": "6.0.1",
+            "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+            "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/get-tsconfig": {
+            "version": "4.7.3",
+            "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz",
+            "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==",
+            "dev": true,
+            "dependencies": {
+                "resolve-pkg-maps": "^1.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+            }
+        },
         "node_modules/github-from-package": {
             "version": "0.0.0",
             "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
@@ -2619,21 +3578,21 @@
             "dev": true
         },
         "node_modules/glob": {
-            "version": "7.2.3",
-            "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
-            "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
-            "dev": true,
-            "peer": true,
+            "version": "10.3.12",
+            "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
+            "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
             "dependencies": {
-                "fs.realpath": "^1.0.0",
-                "inflight": "^1.0.4",
-                "inherits": "2",
-                "minimatch": "^3.1.1",
-                "once": "^1.3.0",
-                "path-is-absolute": "^1.0.0"
+                "foreground-child": "^3.1.0",
+                "jackspeak": "^2.3.6",
+                "minimatch": "^9.0.1",
+                "minipass": "^7.0.4",
+                "path-scurry": "^1.10.2"
+            },
+            "bin": {
+                "glob": "dist/esm/bin.mjs"
             },
             "engines": {
-                "node": "*"
+                "node": ">=16 || 14 >=14.17"
             },
             "funding": {
                 "url": "https://github.com/sponsors/isaacs"
@@ -2644,7 +3603,6 @@
             "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
             "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "is-glob": "^4.0.3"
             },
@@ -2652,36 +3610,11 @@
                 "node": ">=10.13.0"
             }
         },
-        "node_modules/glob/node_modules/brace-expansion": {
-            "version": "1.1.11",
-            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-            "dev": true,
-            "peer": true,
-            "dependencies": {
-                "balanced-match": "^1.0.0",
-                "concat-map": "0.0.1"
-            }
-        },
-        "node_modules/glob/node_modules/minimatch": {
-            "version": "3.1.2",
-            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
-            "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
-            "dev": true,
-            "peer": true,
-            "dependencies": {
-                "brace-expansion": "^1.1.7"
-            },
-            "engines": {
-                "node": "*"
-            }
-        },
         "node_modules/globals": {
             "version": "13.24.0",
             "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
             "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "type-fest": "^0.20.2"
             },
@@ -2692,6 +3625,18 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/globals/node_modules/type-fest": {
+            "version": "0.20.2",
+            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+            "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/globby": {
             "version": "11.1.0",
             "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
@@ -2785,9 +3730,9 @@
             }
         },
         "node_modules/hasown": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
-            "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+            "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
             "dependencies": {
                 "function-bind": "^1.1.2"
             },
@@ -2830,6 +3775,14 @@
                 "node": ">= 6"
             }
         },
+        "node_modules/human-signals": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+            "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+            "engines": {
+                "node": ">=10.17.0"
+            }
+        },
         "node_modules/hyperlinker": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/hyperlinker/-/hyperlinker-1.0.0.tgz",
@@ -2873,7 +3826,6 @@
             "version": "5.3.1",
             "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
             "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
-            "dev": true,
             "engines": {
                 "node": ">= 4"
             }
@@ -2883,7 +3835,6 @@
             "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
             "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "parent-module": "^1.0.0",
                 "resolve-from": "^4.0.0"
@@ -2900,7 +3851,6 @@
             "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
             "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
             "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">=0.8.19"
             }
@@ -2919,7 +3869,6 @@
             "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
             "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "once": "^1.3.0",
                 "wrappy": "1"
@@ -2961,17 +3910,25 @@
                 "node": ">=12.0.0"
             }
         },
-        "node_modules/inquirer/node_modules/wrap-ansi": {
-            "version": "6.2.0",
-            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
-            "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
-            "dependencies": {
-                "ansi-styles": "^4.0.0",
-                "string-width": "^4.1.0",
-                "strip-ansi": "^6.0.0"
-            },
+        "node_modules/inquirer/node_modules/cli-width": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
+            "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
             "engines": {
-                "node": ">=8"
+                "node": ">= 10"
+            }
+        },
+        "node_modules/inquirer/node_modules/mute-stream": {
+            "version": "0.0.8",
+            "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
+            "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
+        },
+        "node_modules/inquirer/node_modules/run-async": {
+            "version": "2.4.1",
+            "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
+            "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
+            "engines": {
+                "node": ">=0.12.0"
             }
         },
         "node_modules/into-stream": {
@@ -3068,12 +4025,19 @@
                 "node": ">=0.12.0"
             }
         },
+        "node_modules/is-obj": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
+            "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/is-path-inside": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
             "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
             "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">=8"
             }
@@ -3127,10 +4091,29 @@
             "dev": true
         },
         "node_modules/isexe": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-            "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
-            "dev": true
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
+            "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
+            "engines": {
+                "node": ">=16"
+            }
+        },
+        "node_modules/jackspeak": {
+            "version": "2.3.6",
+            "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
+            "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
+            "dependencies": {
+                "@isaacs/cliui": "^8.0.2"
+            },
+            "engines": {
+                "node": ">=14"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            },
+            "optionalDependencies": {
+                "@pkgjs/parseargs": "^0.11.0"
+            }
         },
         "node_modules/jake": {
             "version": "10.8.7",
@@ -3201,8 +4184,7 @@
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
             "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
-            "dev": true,
-            "peer": true
+            "dev": true
         },
         "node_modules/json-parse-better-errors": {
             "version": "1.0.2",
@@ -3211,18 +4193,20 @@
             "dev": true
         },
         "node_modules/json-schema-traverse": {
-            "version": "0.4.1",
-            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
-            "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
-            "dev": true,
-            "peer": true
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+            "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+        },
+        "node_modules/json-schema-typed": {
+            "version": "7.0.3",
+            "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz",
+            "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A=="
         },
         "node_modules/json-stable-stringify-without-jsonify": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
             "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
-            "dev": true,
-            "peer": true
+            "dev": true
         },
         "node_modules/json5": {
             "version": "2.2.3",
@@ -3299,7 +4283,6 @@
             "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
             "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "json-buffer": "3.0.1"
             }
@@ -3314,7 +4297,6 @@
             "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
             "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "prelude-ls": "^1.2.1",
                 "type-check": "~0.4.0"
@@ -3328,7 +4310,6 @@
             "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
             "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "p-locate": "^5.0.0"
             },
@@ -3384,8 +4365,7 @@
             "version": "4.6.2",
             "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
             "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
-            "dev": true,
-            "peer": true
+            "dev": true
         },
         "node_modules/lodash.once": {
             "version": "4.1.1",
@@ -3461,14 +4441,11 @@
             }
         },
         "node_modules/lru-cache": {
-            "version": "6.0.0",
-            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-            "dependencies": {
-                "yallist": "^4.0.0"
-            },
+            "version": "10.2.0",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
+            "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
             "engines": {
-                "node": ">=10"
+                "node": "14 || >=16.14"
             }
         },
         "node_modules/make-error": {
@@ -3477,6 +4454,11 @@
             "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
             "dev": true
         },
+        "node_modules/merge-stream": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+            "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
+        },
         "node_modules/merge2": {
             "version": "1.4.1",
             "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -3519,11 +4501,11 @@
             }
         },
         "node_modules/mimic-fn": {
-            "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
-            "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
+            "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
             "engines": {
-                "node": ">=6"
+                "node": ">=8"
             }
         },
         "node_modules/mimic-response": {
@@ -3539,10 +4521,9 @@
             }
         },
         "node_modules/minimatch": {
-            "version": "9.0.3",
-            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
-            "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
-            "dev": true,
+            "version": "9.0.4",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
+            "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
             "dependencies": {
                 "brace-expansion": "^2.0.1"
             },
@@ -3562,6 +4543,14 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/minipass": {
+            "version": "7.0.4",
+            "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+            "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
+            "engines": {
+                "node": ">=16 || 14 >=14.17"
+            }
+        },
         "node_modules/mkdirp-classic": {
             "version": "0.5.3",
             "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
@@ -3598,9 +4587,12 @@
             }
         },
         "node_modules/mute-stream": {
-            "version": "0.0.8",
-            "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
-            "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
+            "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==",
+            "engines": {
+                "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+            }
         },
         "node_modules/napi-build-utils": {
             "version": "1.0.2",
@@ -3624,9 +4616,9 @@
             }
         },
         "node_modules/node-abi": {
-            "version": "3.55.0",
-            "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.55.0.tgz",
-            "integrity": "sha512-uPEjtyh2tFEvWYt4Jw7McOD5FPcHkcxm/tHZc5PWaDB3JYq0rGFUbgaAK+CT5pYpQddBfsZVWI08OwoRfdfbcQ==",
+            "version": "3.59.0",
+            "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.59.0.tgz",
+            "integrity": "sha512-HyyfzvTLCE8b1SX2nWimlra8cibEsypcSu/Az4SXMhWhtuctkwAX7qsEYNjUOIoYtPV884oN3wtYTN+iZKBtvw==",
             "dev": true,
             "dependencies": {
                 "semver": "^7.3.5"
@@ -3655,6 +4647,17 @@
                 }
             }
         },
+        "node_modules/npm-run-path": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+            "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+            "dependencies": {
+                "path-key": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/object-inspect": {
             "version": "1.13.1",
             "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -3667,7 +4670,6 @@
             "version": "1.1.33",
             "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz",
             "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==",
-            "dev": true,
             "engines": {
                 "node": ">= 10"
             }
@@ -3703,6 +4705,14 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/onetime/node_modules/mimic-fn": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+            "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/open": {
             "version": "8.4.2",
             "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
@@ -3724,7 +4734,6 @@
             "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
             "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "@aashutoshrathi/word-wrap": "^1.2.3",
                 "deep-is": "^0.1.3",
@@ -3741,8 +4750,7 @@
             "version": "2.0.6",
             "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
             "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
-            "dev": true,
-            "peer": true
+            "dev": true
         },
         "node_modules/ora": {
             "version": "5.4.1",
@@ -3788,7 +4796,6 @@
             "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
             "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "yocto-queue": "^0.1.0"
             },
@@ -3804,7 +4811,6 @@
             "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
             "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "p-limit": "^3.0.2"
             },
@@ -3815,12 +4821,19 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/p-try": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+            "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/parent-module": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
             "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "callsites": "^3.0.0"
             },
@@ -3865,7 +4878,6 @@
             "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
             "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
             "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">=8"
             }
@@ -3875,7 +4887,6 @@
             "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
             "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
             "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">=0.10.0"
             }
@@ -3884,7 +4895,6 @@
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
             "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
-            "dev": true,
             "engines": {
                 "node": ">=8"
             }
@@ -3904,6 +4914,21 @@
                 "node": ">= 0.8.0"
             }
         },
+        "node_modules/path-scurry": {
+            "version": "1.10.2",
+            "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
+            "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
+            "dependencies": {
+                "lru-cache": "^10.2.0",
+                "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+            },
+            "engines": {
+                "node": ">=16 || 14 >=14.17"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
         "node_modules/path-type": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -4003,6 +5028,73 @@
                 "node": ">=10"
             }
         },
+        "node_modules/pkg-up": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
+            "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
+            "dependencies": {
+                "find-up": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/pkg-up/node_modules/find-up": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+            "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+            "dependencies": {
+                "locate-path": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/pkg-up/node_modules/locate-path": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+            "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+            "dependencies": {
+                "p-locate": "^3.0.0",
+                "path-exists": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/pkg-up/node_modules/p-limit": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+            "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+            "dependencies": {
+                "p-try": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/pkg-up/node_modules/p-locate": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+            "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+            "dependencies": {
+                "p-limit": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/pkg-up/node_modules/path-exists": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+            "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+            "engines": {
+                "node": ">=4"
+            }
+        },
         "node_modules/pkg/node_modules/fs-extra": {
             "version": "9.1.0",
             "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@@ -4049,7 +5141,6 @@
             "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
             "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
             "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">= 0.8.0"
             }
@@ -4088,18 +5179,16 @@
             "version": "2.3.1",
             "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
             "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
-            "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">=6"
             }
         },
         "node_modules/qs": {
-            "version": "6.11.2",
-            "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
-            "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
+            "version": "6.12.1",
+            "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz",
+            "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==",
             "dependencies": {
-                "side-channel": "^1.0.4"
+                "side-channel": "^1.0.6"
             },
             "engines": {
                 "node": ">=0.6"
@@ -4193,6 +5282,14 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/require-from-string": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+            "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/resolve": {
             "version": "1.22.8",
             "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -4215,11 +5312,19 @@
             "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
             "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
             "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">=4"
             }
         },
+        "node_modules/resolve-pkg-maps": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+            "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+            "dev": true,
+            "funding": {
+                "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+            }
+        },
         "node_modules/resolve/node_modules/is-core-module": {
             "version": "2.13.1",
             "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
@@ -4244,6 +5349,11 @@
                 "node": ">=8"
             }
         },
+        "node_modules/restore-cursor/node_modules/signal-exit": {
+            "version": "3.0.7",
+            "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+            "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+        },
         "node_modules/reusify": {
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -4259,7 +5369,6 @@
             "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
             "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "glob": "^7.1.3"
             },
@@ -4270,10 +5379,52 @@
                 "url": "https://github.com/sponsors/isaacs"
             }
         },
+        "node_modules/rimraf/node_modules/brace-expansion": {
+            "version": "1.1.11",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "dev": true,
+            "dependencies": {
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
+            }
+        },
+        "node_modules/rimraf/node_modules/glob": {
+            "version": "7.2.3",
+            "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+            "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+            "dev": true,
+            "dependencies": {
+                "fs.realpath": "^1.0.0",
+                "inflight": "^1.0.4",
+                "inherits": "2",
+                "minimatch": "^3.1.1",
+                "once": "^1.3.0",
+                "path-is-absolute": "^1.0.0"
+            },
+            "engines": {
+                "node": "*"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
+        "node_modules/rimraf/node_modules/minimatch": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+            "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+            "dev": true,
+            "dependencies": {
+                "brace-expansion": "^1.1.7"
+            },
+            "engines": {
+                "node": "*"
+            }
+        },
         "node_modules/run-async": {
-            "version": "2.4.1",
-            "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
-            "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz",
+            "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==",
             "engines": {
                 "node": ">=0.12.0"
             }
@@ -4355,17 +5506,28 @@
                 "node": ">=10"
             }
         },
+        "node_modules/semver/node_modules/lru-cache": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+            "dependencies": {
+                "yallist": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
         "node_modules/set-function-length": {
-            "version": "1.2.1",
-            "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
-            "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
+            "version": "1.2.2",
+            "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+            "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
             "dependencies": {
-                "define-data-property": "^1.1.2",
+                "define-data-property": "^1.1.4",
                 "es-errors": "^1.3.0",
                 "function-bind": "^1.1.2",
-                "get-intrinsic": "^1.2.3",
+                "get-intrinsic": "^1.2.4",
                 "gopd": "^1.0.1",
-                "has-property-descriptors": "^1.0.1"
+                "has-property-descriptors": "^1.0.2"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -4375,7 +5537,6 @@
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
             "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
-            "dev": true,
             "dependencies": {
                 "shebang-regex": "^3.0.0"
             },
@@ -4387,17 +5548,16 @@
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
             "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
-            "dev": true,
             "engines": {
                 "node": ">=8"
             }
         },
         "node_modules/side-channel": {
-            "version": "1.0.5",
-            "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
-            "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+            "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
             "dependencies": {
-                "call-bind": "^1.0.6",
+                "call-bind": "^1.0.7",
                 "es-errors": "^1.3.0",
                 "get-intrinsic": "^1.2.4",
                 "object-inspect": "^1.13.1"
@@ -4410,9 +5570,15 @@
             }
         },
         "node_modules/signal-exit": {
-            "version": "3.0.7",
-            "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
-            "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+            "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+            "engines": {
+                "node": ">=14"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
         },
         "node_modules/simple-concat": {
             "version": "1.0.1",
@@ -4584,6 +5750,20 @@
                 "node": ">=8"
             }
         },
+        "node_modules/string-width-cjs": {
+            "name": "string-width",
+            "version": "4.2.3",
+            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+            "dependencies": {
+                "emoji-regex": "^8.0.0",
+                "is-fullwidth-code-point": "^3.0.0",
+                "strip-ansi": "^6.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/strip-ansi": {
             "version": "6.0.1",
             "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -4595,12 +5775,31 @@
                 "node": ">=8"
             }
         },
+        "node_modules/strip-ansi-cjs": {
+            "name": "strip-ansi",
+            "version": "6.0.1",
+            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+            "dependencies": {
+                "ansi-regex": "^5.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/strip-final-newline": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+            "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/strip-json-comments": {
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
             "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
             "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">=8"
             },
@@ -4691,8 +5890,7 @@
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
             "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
-            "dev": true,
-            "peer": true
+            "dev": true
         },
         "node_modules/through": {
             "version": "2.3.8",
@@ -4752,9 +5950,9 @@
             }
         },
         "node_modules/ts-api-utils": {
-            "version": "1.2.1",
-            "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz",
-            "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
+            "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
             "dev": true,
             "engines": {
                 "node": ">=16"
@@ -4811,6 +6009,25 @@
             "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
             "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
         },
+        "node_modules/tsx": {
+            "version": "4.7.2",
+            "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.2.tgz",
+            "integrity": "sha512-BCNd4kz6fz12fyrgCTEdZHGJ9fWTGeUzXmQysh0RVocDY3h4frk05ZNCXSy4kIenF7y/QnrdiVpTsyNRn6vlAw==",
+            "dev": true,
+            "dependencies": {
+                "esbuild": "~0.19.10",
+                "get-tsconfig": "^4.7.2"
+            },
+            "bin": {
+                "tsx": "dist/cli.mjs"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            },
+            "optionalDependencies": {
+                "fsevents": "~2.3.3"
+            }
+        },
         "node_modules/tunnel-agent": {
             "version": "0.6.0",
             "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -4828,7 +6045,6 @@
             "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
             "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "prelude-ls": "^1.2.1"
             },
@@ -4837,9 +6053,9 @@
             }
         },
         "node_modules/type-fest": {
-            "version": "0.20.2",
-            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
-            "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+            "version": "0.21.3",
+            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+            "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
             "engines": {
                 "node": ">=10"
             },
@@ -4848,9 +6064,9 @@
             }
         },
         "node_modules/typescript": {
-            "version": "5.3.3",
-            "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
-            "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
+            "version": "5.4.5",
+            "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
+            "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
             "dev": true,
             "bin": {
                 "tsc": "bin/tsc",
@@ -4860,11 +6076,47 @@
                 "node": ">=14.17"
             }
         },
+        "node_modules/typescript-eslint": {
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.7.0.tgz",
+            "integrity": "sha512-wZZ+7mTQJCn4mGAvzdERtL4vwKGM/mF9cMSMeKUllz3Hgbd1Mdd5L60Q+nJmCio9RB4OyMMr0EX4Ry2Q7jiAyw==",
+            "dev": true,
+            "dependencies": {
+                "@typescript-eslint/eslint-plugin": "7.7.0",
+                "@typescript-eslint/parser": "7.7.0",
+                "@typescript-eslint/utils": "7.7.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || >=20.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "eslint": "^8.56.0"
+            },
+            "peerDependenciesMeta": {
+                "typescript": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/undici": {
+            "version": "5.28.4",
+            "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
+            "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
+            "dependencies": {
+                "@fastify/busboy": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=14.0"
+            }
+        },
         "node_modules/undici-types": {
             "version": "5.26.5",
             "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
-            "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
-            "dev": true
+            "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
         },
         "node_modules/universalify": {
             "version": "2.0.1",
@@ -4878,8 +6130,6 @@
             "version": "4.4.1",
             "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
             "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
-            "dev": true,
-            "peer": true,
             "dependencies": {
                 "punycode": "^2.1.0"
             }
@@ -4920,18 +6170,17 @@
             }
         },
         "node_modules/which": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
-            "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
-            "dev": true,
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
+            "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
             "dependencies": {
-                "isexe": "^2.0.0"
+                "isexe": "^3.1.1"
             },
             "bin": {
-                "node-which": "bin/node-which"
+                "node-which": "bin/which.js"
             },
             "engines": {
-                "node": ">= 8"
+                "node": "^16.13.0 || >=18.0.0"
             }
         },
         "node_modules/widest-line": {
@@ -4946,9 +6195,9 @@
             }
         },
         "node_modules/winston": {
-            "version": "3.11.0",
-            "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz",
-            "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==",
+            "version": "3.13.0",
+            "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz",
+            "integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==",
             "dependencies": {
                 "@colors/colors": "^1.6.0",
                 "@dabh/diagnostics": "^2.0.2",
@@ -4960,7 +6209,7 @@
                 "safe-stable-stringify": "^2.3.1",
                 "stack-trace": "0.0.x",
                 "triple-beam": "^1.3.0",
-                "winston-transport": "^4.5.0"
+                "winston-transport": "^4.7.0"
             },
             "engines": {
                 "node": ">= 12.0.0"
@@ -4986,6 +6235,20 @@
             "dev": true
         },
         "node_modules/wrap-ansi": {
+            "version": "6.2.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+            "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+            "dependencies": {
+                "ansi-styles": "^4.0.0",
+                "string-width": "^4.1.0",
+                "strip-ansi": "^6.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/wrap-ansi-cjs": {
+            "name": "wrap-ansi",
             "version": "7.0.0",
             "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
             "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
@@ -5012,6 +6275,14 @@
             "resolved": "https://registry.npmjs.org/xcase/-/xcase-2.0.1.tgz",
             "integrity": "sha512-UmFXIPU+9Eg3E9m/728Bii0lAIuoc+6nbrNUKaRPJOFp91ih44qqGlWtxMB6kXFrRD6po+86ksHM5XHCfk6iPw=="
         },
+        "node_modules/xxhashjs": {
+            "version": "0.2.2",
+            "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz",
+            "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==",
+            "dependencies": {
+                "cuint": "^0.2.2"
+            }
+        },
         "node_modules/y18n": {
             "version": "5.0.8",
             "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -5027,9 +6298,12 @@
             "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
         },
         "node_modules/yaml": {
-            "version": "2.3.4",
-            "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
-            "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
+            "version": "2.4.1",
+            "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz",
+            "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==",
+            "bin": {
+                "yaml": "bin.mjs"
+            },
             "engines": {
                 "node": ">= 14"
             }
@@ -5075,7 +6349,6 @@
             "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
             "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
             "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">=10"
             },
@@ -5084,17 +6357,17 @@
             }
         },
         "node_modules/zod": {
-            "version": "3.22.4",
-            "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
-            "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==",
+            "version": "3.22.5",
+            "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.5.tgz",
+            "integrity": "sha512-HqnGsCdVZ2xc0qWPLdO25WnseXThh0kEYKIdV5F/hTHO75hNZFp8thxSeHhiPrHZKrFTo1SOgkAj9po5bexZlw==",
             "funding": {
                 "url": "https://github.com/sponsors/colinhacks"
             }
         },
         "node_modules/zod-validation-error": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.0.2.tgz",
-            "integrity": "sha512-21xGaDmnU7lJZ4J63n5GXWqi+rTzGy3gDHbuZ1jP6xrK/DEQGyOqs/xW7eH96tIfCOYm+ecCuT0bfajBRKEVUw==",
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.1.0.tgz",
+            "integrity": "sha512-zujS6HqJjMZCsvjfbnRs7WI3PXN39ovTcY1n8a+KTm4kOH0ZXYsNiJkH1odZf4xZKMkBDL7M2rmQ913FCS1p9w==",
             "engines": {
                 "node": ">=18.0.0"
             },
diff --git a/NodeApp/package.json b/NodeApp/package.json
index cea130b65abd9cad9e60bdc1f1afc9c6ccdc1a94..f323aef3ea21fd956c3fcc13eb55ea005fc29a3e 100644
--- a/NodeApp/package.json
+++ b/NodeApp/package.json
@@ -1,16 +1,16 @@
 {
-    "name"           : "dojo_cli",
-    "description"    : "CLI of the Dojo project",
-    "version"        : "3.5.0",
-    "license"        : "AGPLv3",
-    "author"         : "MichaΓ«l Minelli <dojo@minelli.me>",
-    "main"           : "dist/app.js",
-    "bin"            : {
+    "name": "dojo_cli",
+    "description": "CLI of the Dojo project",
+    "version": "4.0.0",
+    "license": "AGPLv3",
+    "author": "MichaΓ«l Minelli <dojo@minelli.me>",
+    "main": "dist/app.js",
+    "bin": {
         "dojo": "./dist/app.js"
     },
-    "pkg"            : {
+    "pkg": {
         "scripts": [],
-        "assets" : [
+        "assets": [
             "node_modules/axios/dist/node/axios.cjs",
             ".env",
             "assets/**/*"
@@ -24,51 +24,57 @@
             "node18-win-x86"
         ]
     },
-    "scripts"        : {
-        "dotenv:build": "npx dotenv-vault local build",
-        "lint"        : "npx eslint .",
-        "genversion"  : "npx genversion -s -e src/config/Version.ts",
-        "build"       : "npm run genversion; npx tsc",
-        "start:dev"   : "npm run genversion; npm run lint; npx ts-node src/app.ts",
-        "test"        : "echo \"Error: no test specified\" && exit 1"
+    "scripts": {
+        "dotenv:build": "npx dotenvx encrypt",
+        "lint": "npx eslint .",
+        "genversion": "npx genversion -s -e src/config/Version.ts",
+        "build": "npm run genversion; npx tsc",
+        "start:dev": "npm run genversion; npm run lint; tsc --noEmit && npx tsx dist/app.js",
+        "test": "echo \"Error: no test specified\" && exit 1"
     },
-    "dependencies"   : {
-        "@gitbeaker/rest"     : "^39.34.3",
-        "appdata-path"        : "^1.0.0",
-        "axios"               : "^1.6.5",
-        "boxen"               : "^5.1.2",
-        "chalk"               : "^4.1.2",
-        "commander"           : "^11.1.0",
-        "dotenv"              : "^16.3.1",
-        "dotenv-expand"       : "^10.0.0",
-        "fs-extra"            : "^11.2.0",
-        "http-status-codes"   : "^2.3.0",
-        "inquirer"            : "^8.2.6",
-        "json5"               : "^2.2.3",
-        "jsonwebtoken"        : "^8.5.1",
-        "open"                : "^8.4.2",
-        "ora"                 : "^5.4.1",
-        "semver"              : "^7.5.4",
-        "tar-stream"          : "^3.1.6",
-        "winston"             : "^3.11.0",
-        "yaml"                : "^2.3.4",
-        "zod"                 : "^3.22.4",
-        "zod-validation-error": "^3.0.0"
+    "dependencies": {
+        "@dotenvx/dotenvx": "^0.34.0",
+        "@eslint/js": "^9.0.0",
+        "@gitbeaker/core": "^40.0.3",
+        "@gitbeaker/requester-utils": "^40.0.3",
+        "@gitbeaker/rest": "^40.0.3",
+        "appdata-path": "^1.0.0",
+        "axios": "^1.6.8",
+        "boxen": "^5.1.2",
+        "chalk": "^4.1.2",
+        "commander": "^12.0.0",
+        "form-data": "^4.0.0",
+        "fs-extra": "^11.2.0",
+        "http-status-codes": "^2.3.0",
+        "inquirer": "^8.2.6",
+        "json5": "^2.2.3",
+        "jsonwebtoken": "^8.5.1",
+        "open": "^8.4.2",
+        "ora": "^5.4.1",
+        "semver": "^7.6.0",
+        "tar-stream": "^3.1.7",
+        "winston": "^3.13.0",
+        "winston-transport": "^4.7.0",
+        "yaml": "^2.4.1",
+        "zod": "^3.22.5",
+        "zod-validation-error": "^3.1.0"
     },
     "devDependencies": {
-        "@types/fs-extra"                 : "^11.0.4",
-        "@types/inquirer"                 : "^8.2.10",
-        "@types/jsonwebtoken"             : "^8.5.9",
-        "@types/node"                     : "^18.19.2",
-        "@types/semver"                   : "^7.5.6",
-        "@types/tar-stream"               : "^3.1.3",
-        "@typescript-eslint/eslint-plugin": "^6.18.1",
-        "@typescript-eslint/parser"       : "^6.18.1",
-        "dotenv-vault"                    : "^1.25.0",
-        "genversion"                      : "^3.2.0",
-        "pkg"                             : "^5.8.1",
-        "tiny-typed-emitter"              : "^2.1.0",
-        "ts-node"                         : "^10.9.2",
-        "typescript"                      : "^5.3.3"
+        "@types/fs-extra": "^11.0.4",
+        "@types/inquirer": "^8.2.10",
+        "@types/jsonwebtoken": "^8.5.9",
+        "@types/node": "^18.19.31",
+        "@types/semver": "^7.5.8",
+        "@types/tar-stream": "^3.1.3",
+        "@typescript-eslint/eslint-plugin": "^7.7.0",
+        "@typescript-eslint/parser": "^7.7.0",
+        "dotenv-vault": "^1.26.1",
+        "eslint": "^8.57.0",
+        "genversion": "^3.2.0",
+        "pkg": "^5.8.1",
+        "tiny-typed-emitter": "^2.1.0",
+        "tsx": "^4.7.2",
+        "typescript": "^5.4.5",
+        "typescript-eslint": "^7.7.0"
     }
 }
diff --git a/NodeApp/src/app.ts b/NodeApp/src/app.ts
index be01e40ef06b23244940be8b6e228a38344a74d3..3228486f8e0531c4a9867dbb315ff1cfc21bbdc2 100644
--- a/NodeApp/src/app.ts
+++ b/NodeApp/src/app.ts
@@ -1,23 +1,10 @@
-// Read from the .env file
-// ATTENTION : These lines MUST be the first of this file (except for the path import)
-import path = require('node:path');
-import myEnv = require('dotenv');
-import dotenvExpand = require('dotenv-expand');
-
-
-dotenvExpand.expand(myEnv.config({
-                                     path      : path.join(__dirname, '../.env'),
-                                     DOTENV_KEY: 'dotenv://:key_fc323d8e0a02349342f1c6a119bb38495958ce3a43a87d19a3f674b7e2896dcb@dotenv.local/vault/.env.vault?environment=development'
-                                 }));
-
-require('./shared/helpers/TypeScriptExtensions'); // ATTENTION : This line MUST be the second of this file
-
-
-import CommanderApp from './commander/CommanderApp';
-import HttpManager  from './managers/HttpManager';
+// ATTENTION : This line MUST be the first of this file
+import './init.js';
+import CommanderApp from './commander/CommanderApp.js';
+import HttpManager  from './managers/HttpManager.js';
 
 
 HttpManager.registerAxiosInterceptor();
 
 
-new CommanderApp();
\ No newline at end of file
+(new CommanderApp()).parse();
\ No newline at end of file
diff --git a/NodeApp/src/commander/CommanderApp.ts b/NodeApp/src/commander/CommanderApp.ts
index f32b3d79c7495f13c06da4940f20b27ab0feec0f..7cb5952408f98e02b73517ca53746c41dc13c3ab 100644
--- a/NodeApp/src/commander/CommanderApp.ts
+++ b/NodeApp/src/commander/CommanderApp.ts
@@ -1,38 +1,61 @@
 import { Command, Option } from 'commander';
-import SessionCommand      from './session/SessionCommand';
-import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig';
-import AssignmentCommand   from './assignment/AssignmentCommand';
-import ExerciseCommand     from './exercise/ExerciseCommand';
-import SharedConfig        from '../shared/config/SharedConfig';
+import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig.js';
+import AssignmentCommand   from './assignment/AssignmentCommand.js';
+import ExerciseCommand     from './exercise/ExerciseCommand.js';
+import SharedConfig        from '../shared/config/SharedConfig.js';
 import boxen               from 'boxen';
-import { stateConfigFile } from '../config/ConfigFiles';
-import semver              from 'semver/preload';
-import { version }         from '../config/Version';
-import Config              from '../config/Config';
+import { stateConfigFile } from '../config/ConfigFiles.js';
+import semver              from 'semver/preload.js';
+import { version }         from '../config/Version.js';
+import Config              from '../config/Config.js';
+import CompletionCommand   from './completion/CompletionCommand.js';
+import AuthCommand         from './auth/AuthCommand.js';
+import SessionCommand      from './auth/SessionCommand.js';
+import UpgradeCommand      from './UpgradeCommand.js';
+import TextStyle           from '../types/TextStyle.js';
 
 
 class CommanderApp {
-    program: Command = new Command();
+    public program: Command = new Command();
+
+    private readonly commandHookDisabled: Array<string> = [ 'completion' ];
+
+    private hasToExecuteHook(actionCommand: Command): boolean {
+        if ( actionCommand.parent == null ) {
+            return true;
+        } else {
+            if ( this.commandHookDisabled.includes(actionCommand.name()) ) {
+                return false;
+            }
+
+            return this.hasToExecuteHook(actionCommand.parent);
+        }
+    }
 
     constructor() {
         this.program
-        .name('dojo')
-        .description('CLI of the Dojo application')
-        .version('{{VERSION}}')
-        .showHelpAfterError()
-        .configureHelp({
-                           showGlobalOptions: true,
-                           sortOptions      : true,
-                           sortSubcommands  : true
-                       })
-        .option('-H, --host <string>', 'override the Dojo API endpoint', ClientsSharedConfig.apiURL)
-        .option('-I, --interactive', 'show interactive interface when available', Config.interactiveMode)
-        .addOption(new Option('--debug').hideHelp())
-        .hook('preAction', () => {
-            this.warnDevelopmentVersion();
-        }).hook('postAction', () => {
-            this.informNewVersion();
-        });
+            .name('dojo')
+            .description('CLI of the Dojo application')
+            .version('{{VERSION}}')
+            .showHelpAfterError()
+            .configureHelp({
+                               showGlobalOptions: true,
+                               sortOptions      : true,
+                               sortSubcommands  : true
+                           })
+            .option('-H, --host <string>', 'override the Dojo API endpoint', ClientsSharedConfig.apiURL)
+            .option('-I, --interactive', 'show interactive interface when available', Config.interactiveMode)
+            .addOption(new Option('--debug').hideHelp())
+            .hook('preAction', (_thisCommand: Command, actionCommand: Command) => {
+                if ( this.hasToExecuteHook(actionCommand) ) {
+                    this.warnDevelopmentVersion();
+                }
+            })
+            .hook('postAction', (_thisCommand: Command, actionCommand: Command) => {
+                if ( this.hasToExecuteHook(actionCommand) ) {
+                    this.informNewVersion();
+                }
+            });
 
         this.program.on('option:host', () => {
             ClientsSharedConfig.apiURL = this.program.opts().host;
@@ -47,15 +70,17 @@ class CommanderApp {
         });
 
         this.registerCommands();
+    }
 
+    public parse() {
         this.program.parse();
     }
 
     private warnDevelopmentVersion() {
         if ( !SharedConfig.production ) {
             console.log(boxen(`This is a development (unstable) version of the DojoCLI.
-If you want to use the stable version, please install the package from the following url: 
-https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
+If you want to use the stable version, please execute this command: 
+${ TextStyle.CODE(' dojo upgrade ') }`, {
                 title         : 'Warning',
                 titleAlignment: 'center',
                 borderColor   : 'red',
@@ -71,28 +96,30 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
         if ( SharedConfig.production ) {
             const latestDojoCliVersion = stateConfigFile.getParam('latestDojoCliVersion') as string | null || '0.0.0';
             const latestDojoCliVersionNotification = stateConfigFile.getParam('latestDojoCliVersionNotification') as number | null || 0;
-            if ( semver.lt(version, latestDojoCliVersion) ) {
-                if ( (new Date()).getTime() - latestDojoCliVersionNotification >= Config.versionUpdateInformationPeriodHours * 60 * 60 * 1000 ) {
-                    console.log(boxen(`The ${ latestDojoCliVersion } version of the DojoCLI is available: 
-https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
-                        title         : 'Information',
-                        titleAlignment: 'center',
-                        borderColor   : 'blue',
-                        borderStyle   : 'bold',
-                        margin        : 1,
-                        padding       : 1,
-                        textAlignment : 'left'
-                    }));
-                    stateConfigFile.setParam('latestDojoCliVersionNotification', (new Date()).getTime());
-                }
+            if ( semver.lt(version as string, latestDojoCliVersion) && (new Date()).getTime() - latestDojoCliVersionNotification >= Config.versionUpdateInformationPeriodHours * 60 * 60 * 1000 ) {
+                console.log(boxen(`The ${ latestDojoCliVersion } version of the DojoCLI is available.
+You can upgrade the DojoCLI by executing this command: 
+${ TextStyle.CODE(' dojo upgrade ') }`, {
+                    title         : 'Information',
+                    titleAlignment: 'center',
+                    borderColor   : 'blue',
+                    borderStyle   : 'bold',
+                    margin        : 1,
+                    padding       : 1,
+                    textAlignment : 'left'
+                }));
+                stateConfigFile.setParam('latestDojoCliVersionNotification', (new Date()).getTime());
             }
         }
     }
 
     private registerCommands() {
+        new AuthCommand().registerOnCommand(this.program);
         SessionCommand.registerOnCommand(this.program);
         AssignmentCommand.registerOnCommand(this.program);
         ExerciseCommand.registerOnCommand(this.program);
+        CompletionCommand.registerOnCommand(this.program);
+        UpgradeCommand.registerOnCommand(this.program);
     }
 }
 
diff --git a/NodeApp/src/commander/CommanderCommand.ts b/NodeApp/src/commander/CommanderCommand.ts
index b5fd5e7d363c8d9d8d16e6022b8529397685a4af..4a34ad5c3e5ecc33c26d5fb245ad42e18c911421 100644
--- a/NodeApp/src/commander/CommanderCommand.ts
+++ b/NodeApp/src/commander/CommanderCommand.ts
@@ -1,12 +1,16 @@
-import { Command } from 'commander';
+import { Command, CommandOptions } from 'commander';
 
 
 abstract class CommanderCommand {
     protected abstract commandName: string;
+    protected aliasNames: Array<string> = [];
+
+    protected options: CommandOptions = {};
+
     command: Command = new Command();
 
     registerOnCommand(parent: Command) {
-        this.command = parent.command(this.commandName);
+        this.command = parent.command(this.commandName, this.options).aliases(this.aliasNames);
 
         this.defineCommand();
         this.defineSubCommands();
@@ -14,7 +18,12 @@ abstract class CommanderCommand {
 
     protected abstract defineCommand(): void;
 
-    protected defineSubCommands() {}
+    protected defineSubCommands() {
+        /*
+        * No action
+        * Override this method only if you need to define subcommands
+        * */
+    }
 
     protected abstract commandAction(...args: Array<unknown>): Promise<void>;
 }
diff --git a/NodeApp/src/commander/UpgradeCommand.ts b/NodeApp/src/commander/UpgradeCommand.ts
new file mode 100644
index 0000000000000000000000000000000000000000..18a96ab9716fb49cdb63286c643e3d65f533d79a
--- /dev/null
+++ b/NodeApp/src/commander/UpgradeCommand.ts
@@ -0,0 +1,63 @@
+import CommanderCommand from './CommanderCommand.js';
+import ora              from 'ora';
+import Config           from '../config/Config.js';
+import TextStyle        from '../types/TextStyle.js';
+import os               from 'os';
+import { spawn }        from 'child_process';
+
+
+class UpgradeCommand extends CommanderCommand {
+    protected commandName: string = 'upgrade';
+
+    protected defineCommand() {
+        this.command.description('upgrade the DojoCLI.')
+            .option('--pre-alpha', 'upgrade to the latest pre-alpha (dev) version. Attention it\'s an unstable version without support.')
+            .option('-o, --instructions', 'only display upgrade instructions.')
+            .action(this.commandAction.bind(this));
+    }
+
+    private displayInstructions(upgradeCommand: string, preAlpha: boolean) {
+        upgradeCommand = TextStyle.CODE(` ${ upgradeCommand } `);
+        
+        console.log(TextStyle.BLOCK(`You can upgrade DojoCLI on ${ os.platform() === 'darwin' ? 'macOS' : 'Linux' }:`));
+        ora().start().info(`By executing this command: ${ upgradeCommand }`);
+        console.log('or');
+        ora().start().info(`By downloading the release from: ${ preAlpha ? Config.gitlab.cliPreAlphaReleasePage : Config.gitlab.cliReleasePage }`);
+    }
+
+    private upgrade(upgradeCommand: string) {
+        const upgradeSpinner = ora().start('DojoCLI upgrade is in progress. Please wait...');
+
+        const upgradeProcess = spawn(upgradeCommand, {
+            shell: true
+        });
+
+        upgradeProcess.on('exit', code => {
+            code === null || code !== 0 ? upgradeSpinner.fail('DojoCLI upgrade failed... Please try again manually.') : upgradeSpinner.succeed('DojoCLI upgrade is successful.');
+        });
+    }
+
+    protected async commandAction(options: { preAlpha: boolean, instructions: boolean }): Promise<void> {
+        if ( os.platform() === 'win32' ) {
+            if ( !options.instructions ) {
+                ora().start().warn('Automatic upgrade is not supported on Windows. \n');
+            }
+
+            console.log(TextStyle.BLOCK('You can upgrade DojoCLI on Windows:'));
+            ora().start().info(`By downloading the release from: ${ options.preAlpha ? Config.gitlab.cliPreAlphaReleasePage : Config.gitlab.cliReleasePage }`);
+
+            return;
+        }
+
+        const upgradeCommand = `curl -L "https://dojo.isc-hesge.ch/installer.sh" | sh /dev/stdin${ options.preAlpha ? ' installer=pre-alpha' : '' }`;
+
+        if ( options.instructions ) {
+            this.displayInstructions(upgradeCommand, options.preAlpha);
+        } else {
+            this.upgrade(upgradeCommand);
+        }
+    }
+}
+
+
+export default new UpgradeCommand();
\ No newline at end of file
diff --git a/NodeApp/src/commander/assignment/AssignmentCommand.ts b/NodeApp/src/commander/assignment/AssignmentCommand.ts
index b54c2cb00fcd0622f818ed9c621b823d9b7699e4..8d36acc5581688a025db51d1bad31052875c77d8 100644
--- a/NodeApp/src/commander/assignment/AssignmentCommand.ts
+++ b/NodeApp/src/commander/assignment/AssignmentCommand.ts
@@ -1,10 +1,10 @@
-import CommanderCommand            from '../CommanderCommand';
-import AssignmentCreateCommand     from './subcommands/AssignmentCreateCommand';
-import AssignmentPublishCommand    from './subcommands/AssignmentPublishCommand';
-import AssignmentUnpublishCommand  from './subcommands/AssignmentUnpublishCommand';
-import AssignmentCheckCommand      from './subcommands/AssignmentCheckCommand';
-import AssignmentRunCommand        from './subcommands/AssignmentRunCommand';
-import AssignmentCorrectionCommand from './subcommands/correction/AssignmentCorrectionCommand';
+import CommanderCommand            from '../CommanderCommand.js';
+import AssignmentCreateCommand     from './subcommands/AssignmentCreateCommand.js';
+import AssignmentPublishCommand    from './subcommands/AssignmentPublishCommand.js';
+import AssignmentUnpublishCommand  from './subcommands/AssignmentUnpublishCommand.js';
+import AssignmentCheckCommand      from './subcommands/AssignmentCheckCommand.js';
+import AssignmentRunCommand        from './subcommands/AssignmentRunCommand.js';
+import AssignmentCorrectionCommand from './subcommands/correction/AssignmentCorrectionCommand.js';
 
 
 class AssignmentCommand extends CommanderCommand {
@@ -12,7 +12,7 @@ class AssignmentCommand extends CommanderCommand {
 
     protected defineCommand() {
         this.command
-        .description('manage an assignment');
+            .description('manage an assignment');
     }
 
     protected defineSubCommands() {
@@ -24,7 +24,9 @@ class AssignmentCommand extends CommanderCommand {
         AssignmentCorrectionCommand.registerOnCommand(this.command);
     }
 
-    protected async commandAction(): Promise<void> { }
+    protected async commandAction(): Promise<void> {
+        // No action
+    }
 }
 
 
diff --git a/NodeApp/src/commander/assignment/subcommands/AssignmentCheckCommand.ts b/NodeApp/src/commander/assignment/subcommands/AssignmentCheckCommand.ts
index b818c738ad25d31d38e2c848066018c50ad991ef..04993b5b6b56c8d2c31450a06cbe227d9368ef5b 100644
--- a/NodeApp/src/commander/assignment/subcommands/AssignmentCheckCommand.ts
+++ b/NodeApp/src/commander/assignment/subcommands/AssignmentCheckCommand.ts
@@ -1,28 +1,78 @@
-import CommanderCommand              from '../../CommanderCommand';
-import Config                        from '../../../config/Config';
+import CommanderCommand              from '../../CommanderCommand.js';
+import Config                        from '../../../config/Config.js';
 import ora                           from 'ora';
 import chalk                         from 'chalk';
-import AssignmentValidator           from '../../../sharedByClients/helpers/Dojo/AssignmentValidator';
-import ClientsSharedAssignmentHelper from '../../../sharedByClients/helpers/Dojo/ClientsSharedAssignmentHelper';
-import { Option }                    from 'commander';
-import SharedConfig                  from '../../../shared/config/SharedConfig';
+import AssignmentValidator           from '../../../sharedByClients/helpers/Dojo/AssignmentValidator.js';
+import ClientsSharedAssignmentHelper from '../../../sharedByClients/helpers/Dojo/ClientsSharedAssignmentHelper.js';
+import SharedConfig                  from '../../../shared/config/SharedConfig.js';
+import GlobalHelper                  from '../../../helpers/GlobalHelper.js';
 
 
 class AssignmentCheckCommand extends CommanderCommand {
     protected commandName: string = 'check';
+    protected currentSpinner: ora.Ora = ora();
+    private verbose: boolean = false;
+    private superVerbose: boolean = false;
+    private buildPhase: boolean | undefined = undefined;
 
     protected defineCommand() {
-        this.command
-        .description('locally run a check of an assignment')
-        .option('-p, --path <value>', 'assignment path', Config.folders.defaultLocalExercise)
-        .option('-v, --verbose', 'verbose mode - display principal container output in live')
-        .addOption(new Option('-w, --super-verbose', 'verbose mode - display all docker compose logs (build included) in live').conflicts('verbose'))
-        .addOption(new Option('--verbose-ssj2').hideHelp().implies({ superVerbose: true }))
-        .action(this.commandAction.bind(this));
+        GlobalHelper.runCommandDefinition(this.command)
+            .description('locally run a check of an assignment')
+            .action(this.commandAction.bind(this));
+    }
+
+    private logsEvent(log: string, _error: boolean, displayable: boolean, _currentStep: string, currentSubStep: string) {
+        for ( const line of log.split('\n') ) {
+            if ( displayable && this.buildPhase === undefined && line.startsWith('#') ) {
+                this.buildPhase = true;
+            }
+
+            if ( currentSubStep === 'COMPOSE_RUN' && this.buildPhase === true && line !== '' && !line.startsWith('#') ) {
+                this.buildPhase = false;
+            }
+
+            if ( SharedConfig.debug || (displayable && (this.superVerbose || this.buildPhase === false)) ) {
+                console.log(line);
+            }
+        }
+    }
+
+    private subStepEvent(name: string, message: string) {
+        this.currentSpinner = ora({
+                                      text  : message,
+                                      indent: 4
+                                  }).start();
+
+        if ( this.verbose && name === 'COMPOSE_RUN' ) {
+            this.currentSpinner.info();
+        }
+    }
+
+    private endSubStepEvent(stepName: string, message: string, error: boolean) {
+        if ( error ) {
+            if ( this.verbose && stepName === 'COMPOSE_RUN' ) {
+                ora({
+                        text  : message,
+                        indent: 4
+                    }).start().fail();
+            } else {
+                this.currentSpinner.fail(message);
+            }
+        } else {
+            if ( this.verbose && stepName === 'COMPOSE_RUN' ) {
+                ora({
+                        text  : message,
+                        indent: 4
+                    }).start().succeed();
+            } else {
+                this.currentSpinner.succeed(message);
+            }
+        }
     }
 
     protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
-        const verbose: boolean = options.verbose || options.superVerbose || SharedConfig.debug;
+        this.superVerbose = options.superVerbose;
+        this.verbose = options.verbose || options.superVerbose || SharedConfig.debug;
 
         const localExercisePath: string = options.path ?? Config.folders.defaultLocalExercise;
 
@@ -30,69 +80,19 @@ class AssignmentCheckCommand extends CommanderCommand {
 
         try {
             await new Promise<void>((resolve, reject) => {
-                let spinner: ora.Ora;
-
-                if ( verbose ) {
-                    let buildPhase: boolean | undefined = undefined;
-                    assignmentValidator.events.on('logs', (log: string, error: boolean, displayable: boolean, _currentStep: string, currentSubStep: string) => {
-                        for ( const line of log.split('\n') ) {
-                            if ( displayable && buildPhase == undefined && line.startsWith('#') ) {
-                                buildPhase = true;
-                            }
-
-                            if ( currentSubStep == 'COMPOSE_RUN' && buildPhase === true && line != '' && !line.startsWith('#') ) {
-                                buildPhase = false;
-                            }
-
-                            if ( SharedConfig.debug || (displayable && (options.superVerbose || buildPhase === false)) ) {
-                                console.log(line);
-                            }
-                        }
-                    });
+                if ( this.verbose ) {
+                    assignmentValidator.events.on('logs', this.logsEvent.bind(this));
                 }
 
-                assignmentValidator.events.on('step', (name: string, message: string) => {
-                    console.log(chalk.cyan(message));
-                });
+                assignmentValidator.events.on('step', (_name: string, message: string) => console.log(chalk.cyan(message)));
 
-                assignmentValidator.events.on('subStep', (name: string, message: string) => {
-                    spinner = ora({
-                                      text  : message,
-                                      indent: 4
-                                  }).start();
+                assignmentValidator.events.on('subStep', this.subStepEvent.bind(this));
+
+                assignmentValidator.events.on('endSubStep', this.endSubStepEvent.bind(this));
+
+                assignmentValidator.events.on('finished', (success: boolean) => success ? resolve() : reject());
 
-                    if ( verbose && name == 'COMPOSE_RUN' ) {
-                        spinner.info();
-                    }
-                });
-
-                assignmentValidator.events.on('endSubStep', (stepName: string, message: string, error: boolean) => {
-                    if ( error ) {
-                        if ( verbose && stepName == 'COMPOSE_RUN' ) {
-                            ora({
-                                    text  : message,
-                                    indent: 4
-                                }).start().fail();
-                        } else {
-                            spinner.fail(message);
-                        }
-                    } else {
-                        if ( verbose && stepName == 'COMPOSE_RUN' ) {
-                            ora({
-                                    text  : message,
-                                    indent: 4
-                                }).start().succeed();
-                        } else {
-                            spinner.succeed(message);
-                        }
-                    }
-                });
-
-                assignmentValidator.events.on('finished', (success: boolean) => {
-                    success ? resolve() : reject();
-                });
-
-                assignmentValidator.run(true);
+                assignmentValidator.run();
             });
         } catch ( error ) { /* empty */ }
 
diff --git a/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts b/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts
index 3429b7518bb8694b892f2037f1ed138b163b28e9..922c126c5d7205de5de12136b80fcc980686ddcc 100644
--- a/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts
+++ b/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts
@@ -1,98 +1,100 @@
-import CommanderCommand   from '../../CommanderCommand';
-import chalk              from 'chalk';
+import CommanderCommand   from '../../CommanderCommand.js';
 import ora                from 'ora';
-import AccessesHelper     from '../../../helpers/AccessesHelper';
-import Assignment         from '../../../sharedByClients/models/Assignment';
-import GitlabUser         from '../../../shared/types/Gitlab/GitlabUser';
-import GitlabManager      from '../../../managers/GitlabManager';
-import DojoBackendManager from '../../../managers/DojoBackendManager';
-import Toolbox            from '../../../shared/helpers/Toolbox';
+import AccessesHelper     from '../../../helpers/AccessesHelper.js';
+import Assignment         from '../../../sharedByClients/models/Assignment.js';
+import DojoBackendManager from '../../../managers/DojoBackendManager.js';
+import Toolbox            from '../../../shared/helpers/Toolbox.js';
+import * as Gitlab        from '@gitbeaker/rest';
+import TextStyle          from '../../../types/TextStyle.js';
+import GitlabManager      from '../../../managers/GitlabManager.js';
+
+
+type CommandOptions = { name: string, template?: string, members_id?: Array<number>, members_username?: Array<string>, clone?: string | boolean }
 
 
 class AssignmentCreateCommand extends CommanderCommand {
     protected commandName: string = 'create';
 
+    private members!: Array<Gitlab.UserSchema> | undefined;
+    private templateIdOrNamespace: string | null = null;
+    private assignment!: Assignment;
+
     protected defineCommand() {
         this.command
-        .description('create a new repository for an assignment')
-        .requiredOption('-n, --name <name>', 'name of the assignment')
-        .option('-i, --members_id <ids...>', 'list of gitlab members ids (teaching staff) to add to the repository')
-        .option('-u, --members_username <usernames...>', 'list of gitlab members username (teaching staff) to add to the repository')
-        .option('-t, --template <string>', 'id or url of the template (http/s and ssh urls are possible)')
-        .option('-c, --clone [string]', 'automatically clone the repository (SSH required) in the specified directory (this will create a subdirectory with the assignment name)')
-        .action(this.commandAction.bind(this));
+            .description('create a new repository for an assignment')
+            .requiredOption('-n, --name <name>', 'name of the assignment')
+            .option('-i, --members_id <ids...>', 'list of gitlab members ids (teaching staff) to add to the repository')
+            .option('-u, --members_username <usernames...>', 'list of gitlab members username (teaching staff) to add to the repository')
+            .option('-t, --template <string>', 'id or url of the template (http/s and ssh urls are possible)')
+            .option('-c, --clone [string]', 'automatically clone the repository (SSH required) in the specified directory (this will create a subdirectory with the assignment name)')
+            .action(this.commandAction.bind(this));
     }
 
-    protected async commandAction(options: { name: string, template?: string, members_id?: Array<number>, members_username?: Array<string>, clone?: string | boolean }): Promise<void> {
-        let members!: Array<GitlabUser> | false;
-        let templateIdOrNamespace: string | null = null;
-        let assignment!: Assignment;
+    private async dataRetrieval(options: CommandOptions) {
+        console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
 
-        // Check access and retrieve data
-        {
-            console.log(chalk.cyan('Please wait while we verify and retrieve data...'));
+        if ( !await AccessesHelper.checkTeachingStaff() ) {
+            throw new Error();
+        }
 
-            if ( !await AccessesHelper.checkTeachingStaff() ) {
-                return;
-            }
+        this.members = await GitlabManager.fetchMembers(options);
+        if ( !this.members ) {
+            throw new Error();
+        }
+
+        const assignmentGetSpinner: ora.Ora = ora('Checking assignment name availability').start();
+        if ( await DojoBackendManager.getAssignment(options.name) ) {
+            assignmentGetSpinner.fail(`Assignment name "${ options.name }" is already taken. Please choose another one.`);
+            throw new Error();
+        }
+        assignmentGetSpinner.succeed(`Assignment name "${ options.name }" is available`);
 
-            members = await GitlabManager.fetchMembers(options);
-            if ( !members ) {
-                return;
+        if ( options.template ) {
+            this.templateIdOrNamespace = options.template;
+
+            if ( Number.isNaN(Number(this.templateIdOrNamespace)) ) {
+                this.templateIdOrNamespace = Toolbox.urlToPath(this.templateIdOrNamespace);
             }
 
-            const assignmentGetSpinner: ora.Ora = ora('Checking assignment name availability').start();
-            if ( await DojoBackendManager.getAssignment(options.name) ) {
-                assignmentGetSpinner.fail(`Assignment name "${ options.name }" is already taken. Please choose another one.`);
-                return;
+            if ( !await DojoBackendManager.checkTemplateAccess(this.templateIdOrNamespace) ) {
+                throw new Error();
             }
-            assignmentGetSpinner.succeed(`Assignment name "${ options.name }" is available`);
+        }
+    }
 
-            if ( options.template ) {
-                templateIdOrNamespace = options.template;
+    private async createAssignment(options: CommandOptions) {
+        console.log(TextStyle.BLOCK('Please wait while we are creating the assignment (approximately 10 seconds)...'));
 
-                if ( Number.isNaN(Number(templateIdOrNamespace)) ) {
-                    templateIdOrNamespace = Toolbox.urlToPath(templateIdOrNamespace as string);
-                }
+        this.assignment = await DojoBackendManager.createAssignment(options.name, this.members!, this.templateIdOrNamespace);
 
-                if ( !await DojoBackendManager.checkTemplateAccess(encodeURIComponent(templateIdOrNamespace as string)) ) {
-                    return;
-                }
-            }
-        }
+        const oraInfo = (message: string) => {
+            ora({
+                    text  : message,
+                    indent: 4
+                }).start().info();
+        };
 
-        // Create the assignment
-        {
-            console.log(chalk.cyan('Please wait while we are creating the assignment (approximately 10 seconds)...'));
-
-            try {
-                assignment = await DojoBackendManager.createAssignment(options.name, members, templateIdOrNamespace);
-
-                const oraInfo = (message: string) => {
-                    ora({
-                            text  : message,
-                            indent: 4
-                        }).start().info();
-                };
-
-                oraInfo(`${ chalk.magenta('Name:') } ${ assignment.name }`);
-                oraInfo(`${ chalk.magenta('Web URL:') } ${ assignment.gitlabCreationInfo.web_url }`);
-                oraInfo(`${ chalk.magenta('HTTP Repo:') } ${ assignment.gitlabCreationInfo.http_url_to_repo }`);
-                oraInfo(`${ chalk.magenta('SSH Repo:') } ${ assignment.gitlabCreationInfo.ssh_url_to_repo }`);
-            } catch ( error ) {
-                return;
-            }
-        }
+        oraInfo(`${ TextStyle.LIST_ITEM_NAME('Name:') } ${ this.assignment.name }`);
+        oraInfo(`${ TextStyle.LIST_ITEM_NAME('Web URL:') } ${ this.assignment.gitlabCreationInfo.web_url }`);
+        oraInfo(`${ TextStyle.LIST_ITEM_NAME('HTTP Repo:') } ${ this.assignment.gitlabCreationInfo.http_url_to_repo }`);
+        oraInfo(`${ TextStyle.LIST_ITEM_NAME('SSH Repo:') } ${ this.assignment.gitlabCreationInfo.ssh_url_to_repo }`);
+    }
 
-        // Clone the repository
-        {
-            if ( options.clone ) {
-                console.log(chalk.cyan('Please wait while we are cloning the repository...'));
+    private async cloneRepository(options: CommandOptions) {
+        if ( options.clone ) {
+            console.log(TextStyle.BLOCK('Please wait while we are cloning the repository...'));
 
-                await GitlabManager.cloneRepository(options.clone, assignment.gitlabCreationInfo.ssh_url_to_repo, undefined, true, 0);
-            }
+            await GitlabManager.cloneRepository(options.clone, this.assignment.gitlabCreationInfo.ssh_url_to_repo, undefined, true, 0);
         }
     }
+
+    protected async commandAction(options: CommandOptions): Promise<void> {
+        try {
+            await this.dataRetrieval(options);
+            await this.createAssignment(options);
+            await this.cloneRepository(options);
+        } catch ( e ) { /* Do nothing */ }
+    }
 }
 
 
diff --git a/NodeApp/src/commander/assignment/subcommands/AssignmentPublishCommand.ts b/NodeApp/src/commander/assignment/subcommands/AssignmentPublishCommand.ts
index 6816aa312932ce01df561c2c1cab1a3f366a1a77..8188c3bf016ca3154a625e26ad0abf06a4747385 100644
--- a/NodeApp/src/commander/assignment/subcommands/AssignmentPublishCommand.ts
+++ b/NodeApp/src/commander/assignment/subcommands/AssignmentPublishCommand.ts
@@ -1,4 +1,4 @@
-import AssignmentPublishUnpublishCommandBase from './AssignmentPublishUnpublishCommandBase';
+import AssignmentPublishUnpublishCommandBase from './AssignmentPublishUnpublishCommandBase.js';
 
 
 class AssignmentPublishCommand extends AssignmentPublishUnpublishCommandBase {
diff --git a/NodeApp/src/commander/assignment/subcommands/AssignmentPublishUnpublishCommandBase.ts b/NodeApp/src/commander/assignment/subcommands/AssignmentPublishUnpublishCommandBase.ts
index 3c7c1d86e5c365d9b12e23d28487f8f5a3edcf67..ab16340d89000fa0f72c894789feddda679b6620 100644
--- a/NodeApp/src/commander/assignment/subcommands/AssignmentPublishUnpublishCommandBase.ts
+++ b/NodeApp/src/commander/assignment/subcommands/AssignmentPublishUnpublishCommandBase.ts
@@ -1,11 +1,11 @@
-import CommanderCommand       from '../../CommanderCommand';
+import CommanderCommand       from '../../CommanderCommand.js';
 import inquirer               from 'inquirer';
-import chalk                  from 'chalk';
-import SessionManager         from '../../../managers/SessionManager';
+import SessionManager         from '../../../managers/SessionManager.js';
 import ora                    from 'ora';
-import DojoBackendManager     from '../../../managers/DojoBackendManager';
-import Assignment             from '../../../sharedByClients/models/Assignment';
-import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper';
+import DojoBackendManager     from '../../../managers/DojoBackendManager.js';
+import Assignment             from '../../../sharedByClients/models/Assignment.js';
+import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper.js';
+import TextStyle              from '../../../types/TextStyle.js';
 
 
 abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand {
@@ -13,10 +13,10 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand {
 
     protected defineCommand() {
         this.command
-        .description(`${ this.publish ? 'publish' : 'unpublish' } an assignment`)
-        .argument('<name or url>', 'name or url (http/s or ssh) of the assignment')
-        .option('-f, --force', 'don\'t ask for confirmation')
-        .action(this.commandAction.bind(this));
+            .description(`${ this.publish ? 'publish' : 'unpublish' } an assignment`)
+            .argument('<name or url>', 'name or url (http/s or ssh) of the assignment')
+            .option('-f, --force', 'don\'t ask for confirmation')
+            .action(this.commandAction.bind(this));
     }
 
     protected async commandAction(assignmentNameOrUrl: string, options: { force: boolean }): Promise<void> {
@@ -35,7 +35,7 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand {
         let assignment!: Assignment | undefined;
 
         {
-            console.log(chalk.cyan('Please wait while we verify and retrieve data...'));
+            console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
 
             if ( !await SessionManager.testSession(true, null) ) {
                 return;
@@ -82,7 +82,7 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand {
         }
 
         {
-            console.log(chalk.cyan(`Please wait while we ${ this.publish ? 'publish' : 'unpublish' } the assignment...`));
+            console.log(TextStyle.BLOCK(`Please wait while we ${ this.publish ? 'publish' : 'unpublish' } the assignment...`));
 
             try {
                 await DojoBackendManager.changeAssignmentPublishedStatus(assignment, this.publish);
diff --git a/NodeApp/src/commander/assignment/subcommands/AssignmentRunCommand.ts b/NodeApp/src/commander/assignment/subcommands/AssignmentRunCommand.ts
index 9a7489f2e9db1792682554f343ebc0b1b62fa2ee..e20719e732deab560676af102fba23406d93ffae 100644
--- a/NodeApp/src/commander/assignment/subcommands/AssignmentRunCommand.ts
+++ b/NodeApp/src/commander/assignment/subcommands/AssignmentRunCommand.ts
@@ -1,25 +1,19 @@
-import CommanderCommand  from '../../CommanderCommand';
-import Config            from '../../../config/Config';
-import { Option }        from 'commander';
-import ExerciseRunHelper from '../../../helpers/Dojo/ExerciseRunHelper';
+import CommanderCommand  from '../../CommanderCommand.js';
+import ExerciseRunHelper from '../../../helpers/Dojo/ExerciseRunHelper.js';
+import GlobalHelper      from '../../../helpers/GlobalHelper.js';
 
 
 class AssignmentRunCommand extends CommanderCommand {
     protected commandName: string = 'run';
 
     protected defineCommand() {
-        // This command is synced with the "exercise run" command
-        this.command
-        .description('locally run the assignment as an exercise')
-        .option('-p, --path <value>', 'exercise path', Config.folders.defaultLocalExercise)
-        .option('-v, --verbose', 'verbose mode - display principal container output in live')
-        .addOption(new Option('-w, --super-verbose', 'verbose mode - display all docker compose logs (build included) in live').conflicts('verbose'))
-        .addOption(new Option('--verbose-ssj2').hideHelp().implies({ superVerbose: true }))
-        .action(this.commandAction.bind(this));
+        GlobalHelper.runCommandDefinition(this.command)
+            .description('locally run the assignment as an exercise')
+            .action(this.commandAction.bind(this));
     }
 
     protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
-        await ExerciseRunHelper.run(options);
+        await (new ExerciseRunHelper(options)).run();
     }
 }
 
diff --git a/NodeApp/src/commander/assignment/subcommands/AssignmentUnpublishCommand.ts b/NodeApp/src/commander/assignment/subcommands/AssignmentUnpublishCommand.ts
index e329c48dc0cd673b8e730f65939cea71a0859057..6aa5e9f416fb8d84b3ef8a99b6961e6e039483b1 100644
--- a/NodeApp/src/commander/assignment/subcommands/AssignmentUnpublishCommand.ts
+++ b/NodeApp/src/commander/assignment/subcommands/AssignmentUnpublishCommand.ts
@@ -1,4 +1,4 @@
-import AssignmentPublishUnpublishCommandBase from './AssignmentPublishUnpublishCommandBase';
+import AssignmentPublishUnpublishCommandBase from './AssignmentPublishUnpublishCommandBase.js';
 
 
 class AssignmentUnpublishCommand extends AssignmentPublishUnpublishCommandBase {
diff --git a/NodeApp/src/commander/assignment/subcommands/correction/AssignmentCorrectionCommand.ts b/NodeApp/src/commander/assignment/subcommands/correction/AssignmentCorrectionCommand.ts
index 3f30a994e7fdcd0a773401db5daa36abba861579..1ff39af42304fd623e842705375f9b445f1c2461 100644
--- a/NodeApp/src/commander/assignment/subcommands/correction/AssignmentCorrectionCommand.ts
+++ b/NodeApp/src/commander/assignment/subcommands/correction/AssignmentCorrectionCommand.ts
@@ -1,6 +1,6 @@
-import CommanderCommand                  from '../../../CommanderCommand';
-import AssignmentCorrectionLinkCommand   from './subcommands/AssignmentCorrectionLinkCommand';
-import AssignmentCorrectionUpdateCommand from './subcommands/AssignmentCorrectionUpdateCommand';
+import CommanderCommand                  from '../../../CommanderCommand.js';
+import AssignmentCorrectionLinkCommand   from './subcommands/AssignmentCorrectionLinkCommand.js';
+import AssignmentCorrectionUpdateCommand from './subcommands/AssignmentCorrectionUpdateCommand.js';
 
 
 class AssignmentCorrectionCommand extends CommanderCommand {
@@ -8,7 +8,7 @@ class AssignmentCorrectionCommand extends CommanderCommand {
 
     protected defineCommand() {
         this.command
-        .description('manage corrections of an assignment');
+            .description('manage corrections of an assignment');
     }
 
     protected defineSubCommands() {
@@ -16,7 +16,9 @@ class AssignmentCorrectionCommand extends CommanderCommand {
         AssignmentCorrectionUpdateCommand.registerOnCommand(this.command);
     }
 
-    protected async commandAction(): Promise<void> { }
+    protected async commandAction(): Promise<void> {
+        // No action
+    }
 }
 
 
diff --git a/NodeApp/src/commander/assignment/subcommands/correction/subcommands/AssignmentCorrectionLinkCommand.ts b/NodeApp/src/commander/assignment/subcommands/correction/subcommands/AssignmentCorrectionLinkCommand.ts
index 5d173206038be1262127faf618b74cb874022ffa..431e192acf09f92c8d5ab470cfe1b5be79a3ec74 100644
--- a/NodeApp/src/commander/assignment/subcommands/correction/subcommands/AssignmentCorrectionLinkCommand.ts
+++ b/NodeApp/src/commander/assignment/subcommands/correction/subcommands/AssignmentCorrectionLinkCommand.ts
@@ -1,4 +1,4 @@
-import AssignmentCorrectionLinkUpdateCommand from './AssignmentCorrectionLinkUpdateCommand';
+import AssignmentCorrectionLinkUpdateCommand from './AssignmentCorrectionLinkUpdateCommand.js';
 
 
 class AssignmentCorrectionLinkCommand extends AssignmentCorrectionLinkUpdateCommand {
diff --git a/NodeApp/src/commander/assignment/subcommands/correction/subcommands/AssignmentCorrectionLinkUpdateCommand.ts b/NodeApp/src/commander/assignment/subcommands/correction/subcommands/AssignmentCorrectionLinkUpdateCommand.ts
index cd7b4f8897678e3e43fe2bd6973a76f0ac61db94..230e0202ee9f69357574658b9a3b4a21a00297ca 100644
--- a/NodeApp/src/commander/assignment/subcommands/correction/subcommands/AssignmentCorrectionLinkUpdateCommand.ts
+++ b/NodeApp/src/commander/assignment/subcommands/correction/subcommands/AssignmentCorrectionLinkUpdateCommand.ts
@@ -1,9 +1,9 @@
-import CommanderCommand   from '../../../../CommanderCommand';
-import chalk              from 'chalk';
+import CommanderCommand   from '../../../../CommanderCommand.js';
 import ora                from 'ora';
-import DojoBackendManager from '../../../../../managers/DojoBackendManager';
-import SessionManager     from '../../../../../managers/SessionManager';
-import Assignment         from '../../../../../sharedByClients/models/Assignment';
+import DojoBackendManager from '../../../../../managers/DojoBackendManager.js';
+import SessionManager     from '../../../../../managers/SessionManager.js';
+import Assignment         from '../../../../../sharedByClients/models/Assignment.js';
+import TextStyle          from '../../../../../types/TextStyle.js';
 
 
 abstract class AssignmentCorrectionLinkUpdateCommand extends CommanderCommand {
@@ -11,10 +11,10 @@ abstract class AssignmentCorrectionLinkUpdateCommand extends CommanderCommand {
 
     protected defineCommand() {
         this.command
-        .description(this.isUpdate ? 'update a correction of an assignment' : 'link an exercise repo as a correction for an assignment')
-        .argument('<string>', 'id or url of the exercise that is the correction')
-        .requiredOption('-a, --assignment <string>', 'id or url of the assignment of the correction')
-        .action(this.commandAction.bind(this));
+            .description(this.isUpdate ? 'update a correction of an assignment' : 'link an exercise repo as a correction for an assignment')
+            .argument('<string>', 'id or url of the exercise that is the correction')
+            .requiredOption('-a, --assignment <string>', 'id or url of the assignment of the correction')
+            .action(this.commandAction.bind(this));
     }
 
     protected async commandAction(exerciseIdOrUrl: string, options: { assignment: string }): Promise<void> {
@@ -22,7 +22,7 @@ abstract class AssignmentCorrectionLinkUpdateCommand extends CommanderCommand {
 
         // Check access
         {
-            console.log(chalk.cyan('Please wait while we check access...'));
+            console.log(TextStyle.BLOCK('Please wait while we check access...'));
 
             const assignmentGetSpinner: ora.Ora = ora('Checking if assignment exists').start();
             assignment = await DojoBackendManager.getAssignment(options.assignment);
@@ -51,7 +51,7 @@ abstract class AssignmentCorrectionLinkUpdateCommand extends CommanderCommand {
 
         // Link the exercise
         {
-            console.log(chalk.cyan('Please wait while we link the exercise...'));
+            console.log(TextStyle.BLOCK('Please wait while we link the exercise...'));
 
             await DojoBackendManager.linkUpdateCorrection(exerciseIdOrUrl, assignment, this.isUpdate);
         }
diff --git a/NodeApp/src/commander/assignment/subcommands/correction/subcommands/AssignmentCorrectionUpdateCommand.ts b/NodeApp/src/commander/assignment/subcommands/correction/subcommands/AssignmentCorrectionUpdateCommand.ts
index 88a9f48dfdce248137c857f945116e0be51d171e..b38aec1078bd160b48f351a85f8a0ccd2cd22006 100644
--- a/NodeApp/src/commander/assignment/subcommands/correction/subcommands/AssignmentCorrectionUpdateCommand.ts
+++ b/NodeApp/src/commander/assignment/subcommands/correction/subcommands/AssignmentCorrectionUpdateCommand.ts
@@ -1,4 +1,4 @@
-import AssignmentCorrectionLinkUpdateCommand from './AssignmentCorrectionLinkUpdateCommand';
+import AssignmentCorrectionLinkUpdateCommand from './AssignmentCorrectionLinkUpdateCommand.js';
 
 
 class AssignmentCorrectionUpdateCommand extends AssignmentCorrectionLinkUpdateCommand {
diff --git a/NodeApp/src/commander/auth/AuthCommand.ts b/NodeApp/src/commander/auth/AuthCommand.ts
new file mode 100644
index 0000000000000000000000000000000000000000..299e94f0dc32a8d791c1ade018d720041d6ce2eb
--- /dev/null
+++ b/NodeApp/src/commander/auth/AuthCommand.ts
@@ -0,0 +1,27 @@
+import CommanderCommand     from '../CommanderCommand.js';
+import SessionTestCommand   from './subcommands/AuthTestCommand.js';
+import SessionLoginCommand  from './subcommands/AuthLoginCommand.js';
+import SessionLogoutCommand from './subcommands/AuthLogoutCommand.js';
+
+
+class AuthCommand extends CommanderCommand {
+    protected commandName: string = 'auth';
+
+    protected defineCommand() {
+        this.command
+            .description('manage Dojo and Gitlab sessions');
+    }
+
+    protected defineSubCommands() {
+        SessionLoginCommand.registerOnCommand(this.command);
+        SessionLogoutCommand.registerOnCommand(this.command);
+        SessionTestCommand.registerOnCommand(this.command);
+    }
+
+    protected async commandAction(): Promise<void> {
+        // No action
+    }
+}
+
+
+export default AuthCommand;
\ No newline at end of file
diff --git a/NodeApp/src/commander/auth/SessionCommand.ts b/NodeApp/src/commander/auth/SessionCommand.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9185fa59f3d5801cb12b144937f22d6a59863a29
--- /dev/null
+++ b/NodeApp/src/commander/auth/SessionCommand.ts
@@ -0,0 +1,13 @@
+import AuthCommand        from './AuthCommand.js';
+import { CommandOptions } from 'commander';
+
+
+class SessionCommand extends AuthCommand {
+    protected commandName: string = 'session';
+    protected options: CommandOptions = {
+        hidden: true
+    };
+}
+
+
+export default new SessionCommand();
\ No newline at end of file
diff --git a/NodeApp/src/commander/auth/subcommands/AuthLoginCommand.ts b/NodeApp/src/commander/auth/subcommands/AuthLoginCommand.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a2340464503790fa74dd76762086dfacb612a084
--- /dev/null
+++ b/NodeApp/src/commander/auth/subcommands/AuthLoginCommand.ts
@@ -0,0 +1,25 @@
+import CommanderCommand from '../../CommanderCommand.js';
+import SessionManager   from '../../../managers/SessionManager.js';
+import TextStyle        from '../../../types/TextStyle.js';
+
+
+class AuthLoginCommand extends CommanderCommand {
+    protected commandName: string = 'login';
+
+    protected defineCommand() {
+        this.command
+            .description('login to Dojo')
+            .option('-c, --cli', 'proceed to the login in headless mode (do not try to open web browser).')
+            .action(this.commandAction.bind(this));
+    }
+
+    protected async commandAction(options: { cli: boolean }): Promise<void> {
+        try {
+            console.log(TextStyle.BLOCK('Please wait while we login you into Dojo...'));
+            await SessionManager.login(options.cli);
+        } catch ( error ) { /* empty */ }
+    }
+}
+
+
+export default new AuthLoginCommand();
\ No newline at end of file
diff --git a/NodeApp/src/commander/session/subcommands/SessionLogoutCommand.ts b/NodeApp/src/commander/auth/subcommands/AuthLogoutCommand.ts
similarity index 72%
rename from NodeApp/src/commander/session/subcommands/SessionLogoutCommand.ts
rename to NodeApp/src/commander/auth/subcommands/AuthLogoutCommand.ts
index e71d882d4935b8b3f8cc1b4f248a2a3d9ae6ab33..4cc88bb149a2b9a7f87a4d38d5a664f3e6171995 100644
--- a/NodeApp/src/commander/session/subcommands/SessionLogoutCommand.ts
+++ b/NodeApp/src/commander/auth/subcommands/AuthLogoutCommand.ts
@@ -1,17 +1,17 @@
-import CommanderCommand from '../../CommanderCommand';
+import CommanderCommand from '../../CommanderCommand.js';
 import inquirer         from 'inquirer';
 import ora              from 'ora';
-import SessionManager   from '../../../managers/SessionManager';
+import SessionManager   from '../../../managers/SessionManager.js';
 
 
-class SessionLogoutCommand extends CommanderCommand {
+class AuthLogoutCommand extends CommanderCommand {
     protected commandName: string = 'logout';
 
     protected defineCommand() {
         this.command
-        .description('logout of Dojo')
-        .option('-f, --force', 'attempt to logout without prompting for confirmation')
-        .action(this.commandAction.bind(this));
+            .description('logout of Dojo')
+            .option('-f, --force', 'attempt to logout without prompting for confirmation')
+            .action(this.commandAction.bind(this));
     }
 
     protected async commandAction(options: { force: boolean }): Promise<void> {
@@ -35,4 +35,4 @@ class SessionLogoutCommand extends CommanderCommand {
 }
 
 
-export default new SessionLogoutCommand();
\ No newline at end of file
+export default new AuthLogoutCommand();
\ No newline at end of file
diff --git a/NodeApp/src/commander/auth/subcommands/AuthTestCommand.ts b/NodeApp/src/commander/auth/subcommands/AuthTestCommand.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e2e9d67ef39b3eeab6189f28272ae9abee1ccc3c
--- /dev/null
+++ b/NodeApp/src/commander/auth/subcommands/AuthTestCommand.ts
@@ -0,0 +1,22 @@
+import CommanderCommand from '../../CommanderCommand.js';
+import SessionManager   from '../../../managers/SessionManager.js';
+import GitlabManager    from '../../../managers/GitlabManager.js';
+
+
+class AuthTestCommand extends CommanderCommand {
+    protected commandName: string = 'test';
+
+    protected defineCommand() {
+        this.command
+            .description('test availability of registered Dojo API and Gitlab API sessions')
+            .action(this.commandAction.bind(this));
+    }
+
+    protected async commandAction(): Promise<void> {
+        await SessionManager.testSession();
+        await GitlabManager.testToken();
+    }
+}
+
+
+export default new AuthTestCommand();
\ No newline at end of file
diff --git a/NodeApp/src/commander/completion/CompletionCommand.ts b/NodeApp/src/commander/completion/CompletionCommand.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e3cddf9a4c0e7d074e4615ab6d90c5c889c2f1f0
--- /dev/null
+++ b/NodeApp/src/commander/completion/CompletionCommand.ts
@@ -0,0 +1,27 @@
+import CommanderCommand              from '../CommanderCommand.js';
+import CompletionCreateUpdateCommand from './subcommands/CompletionCreateUpdateCommand.js';
+import CompletionGetCommand          from './subcommands/CompletionGetCommand.js';
+import CompletionScriptCommand       from './subcommands/CompletionScriptCommand.js';
+
+
+class CompletionCommand extends CommanderCommand {
+    protected commandName: string = 'completion';
+
+    protected defineCommand() {
+        this.command
+            .description('generate completions for bash, fish, or zsh');
+    }
+
+    protected defineSubCommands() {
+        CompletionCreateUpdateCommand.registerOnCommand(this.command);
+        CompletionGetCommand.registerOnCommand(this.command);
+        CompletionScriptCommand.registerOnCommand(this.command);
+    }
+
+    protected async commandAction(): Promise<void> {
+        // No action
+    }
+}
+
+
+export default new CompletionCommand();
\ No newline at end of file
diff --git a/NodeApp/src/commander/completion/subcommands/CompletionCreateUpdateCommand.ts b/NodeApp/src/commander/completion/subcommands/CompletionCreateUpdateCommand.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f1b6fa26577b474b91044b50440e3b0b742429e9
--- /dev/null
+++ b/NodeApp/src/commander/completion/subcommands/CompletionCreateUpdateCommand.ts
@@ -0,0 +1,101 @@
+import CommanderCommand                                                 from '../../CommanderCommand.js';
+import { Option }                                                       from 'commander';
+import { generateFishCompletion, getRoot, tryRenameFile, updateRcFile } from '../../../helpers/AutoCompletionHelper.js';
+import os, { homedir }                                                  from 'os';
+import path                                                             from 'path';
+import ora                                                              from 'ora';
+import fs                                                               from 'fs-extra';
+import TextStyle                                                        from '../../../types/TextStyle.js';
+
+
+class CompletionCreateUpdateCommand extends CommanderCommand {
+    protected commandName: string = 'create';
+    protected aliasNames: Array<string> = [ 'update' ];
+
+
+    protected defineCommand() {
+        this.command.description('generate shell completion')
+            .addOption(new Option('-s, --shell <shell>', 'shell type').choices([ 'bash', 'zsh', 'fish' ]).makeOptionMandatory(true))
+            .addOption(new Option('-f, --file <filename>', '(only for fish shell)').implies({ shell: 'fish' }))
+            .addOption(new Option('-y, --force', 'don\'t ask for file overwrite confirmation (only for fish shell)').implies({ shell: 'fish' }))
+            .action(this.commandAction.bind(this));
+    }
+
+    private bash() {
+        const completionCommand = `
+# Added by DojoCLI
+eval "$(dojo completion script bash)"
+`;
+        updateRcFile('bash', path.join(os.homedir(), '.bashrc'), completionCommand);
+    }
+
+    private zsh() {
+        const completionCommand = `
+# Added by DojoCLI
+source <(dojo completion script zsh)
+`;
+        updateRcFile('zsh', path.join(homedir(), '.zshrc'), completionCommand);
+    }
+
+    /* The completion command must do the following:
+       - if a file is provided:
+            - if force is not enabled:
+                - check if the file exists:
+                    - if it exists, prompt the user that it will be erased
+                        - if ok is given write the file and prompt that a backup has been created
+            - else create the file containing the completion
+        - else
+            - if force is not enabled:
+                - check if the default file exists:
+                    - if it exists, prompt the user that it will be erased:
+                        - if ok is given write the file and prompt that a backup has been created
+            - else
+                - create the file containing the completion
+    */
+    private async fish(options: { file: string, force: boolean }) {
+        const filePath = path.resolve(options.file ?? path.join(os.homedir(), '.config/fish/completions/dojo.fish'));
+        const showInstructions = !!options.file;
+        if ( !(await tryRenameFile(filePath, options.force)) ) { // means renaming was interrupted
+            return;
+        }
+
+        const spinner: ora.Ora = ora(`Writing fish completion in ${ filePath }...`).start();
+
+        try {
+            fs.mkdirsSync(path.dirname(filePath));
+
+            fs.writeFileSync(filePath, generateFishCompletion(getRoot(this.command)));
+
+            spinner.succeed(`Fish completion successfully written in ${ filePath }.`);
+            if ( showInstructions ) {
+                const cpCommand = ` cp -i ${ filePath } ~/.config/fish/completions  # interactive cp to avoid accidents `;
+                console.log(`
+The easiest way to install the completion is to copy the ${ TextStyle.CODE(filePath) } into the ${ TextStyle.CODE('~/.config/fish/completions') } directory.
+
+${ TextStyle.CODE(cpCommand) }`);
+            }
+        } catch ( error ) {
+            spinner.fail(`Fish completion error: ${ error }.`);
+        }
+    }
+
+    protected async commandAction(options: { shell: 'bash' | 'zsh' | 'fish', file: string, force: boolean }): Promise<void> {
+        switch ( options.shell ) {
+            case 'bash':
+                this.bash();
+                break;
+            case 'zsh':
+                this.zsh();
+                break;
+            case 'fish':
+                await this.fish(options);
+                break;
+            default:
+                console.error('Unsupported shell.');
+                break;
+        }
+    }
+}
+
+
+export default new CompletionCreateUpdateCommand();
\ No newline at end of file
diff --git a/NodeApp/src/commander/completion/subcommands/CompletionGetCommand.ts b/NodeApp/src/commander/completion/subcommands/CompletionGetCommand.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bf207f42f40dcada44bba31a4db1d756f3a17313
--- /dev/null
+++ b/NodeApp/src/commander/completion/subcommands/CompletionGetCommand.ts
@@ -0,0 +1,69 @@
+import CommanderCommand                    from '../../CommanderCommand.js';
+import { Command, CommandOptions, Option } from 'commander';
+import * as AutoCompletionHelper           from '../../../helpers/AutoCompletionHelper.js';
+
+
+type CompletionProposal = { name: string, description: string };
+
+
+class CompletionGetCommand extends CommanderCommand {
+    protected commandName: string = 'get';
+    protected options: CommandOptions = {
+        hidden: true
+    };
+
+    protected defineCommand() {
+        this.command.description('generate completion options for a given command')
+            .addOption(new Option('-s, --shell <shell>', 'shell completion result format').choices([ 'bash', 'zsh' ]))
+            .argument('<commands...>', 'command chain to complete')
+            .action(this.commandAction.bind(this));
+    }
+
+    private completion(commandsChain: Array<string>, displayFunction: (completionProposals: Array<CompletionProposal>) => void) {
+        const command = AutoCompletionHelper.getCommandFromChain(AutoCompletionHelper.getRoot(this.command), commandsChain.slice(1));
+
+        if ( command ) {
+            const commands = command.commands.filter(cmd => !(cmd as Command & { _hidden: boolean })._hidden);
+            const options = command.options.filter(option => !option.hidden);
+
+            displayFunction([ ...commands.flatMap(cmd => [ {
+                name       : cmd.name(),
+                description: cmd.description()
+            }, ...cmd.aliases().map(alias => ({
+                name       : alias,
+                description: cmd.description()
+            })) ]), ...options.flatMap(option => [ {
+                name       : option.long,
+                description: option.description
+            }, {
+                name       : option.short,
+                description: option.description
+            } ]) ].filter(proposal => proposal.name) as Array<CompletionProposal>);
+        }
+    }
+
+    private bashCompletion(commandsChain: Array<string>) {
+        this.completion(commandsChain, (completionProposals: Array<CompletionProposal>) => console.log(completionProposals.map(proposal => proposal.name).join(' ')));
+    }
+
+    private zshCompletion(commandsChain: Array<string>) {
+        this.completion(commandsChain, (completionProposals: Array<CompletionProposal>) => completionProposals.forEach(proposal => console.log(`${ proposal.name }:${ proposal.description }`)));
+    }
+
+    protected async commandAction(commandsChain: Array<string>, options: { shell: 'bash' | 'zsh' }): Promise<void> {
+        switch ( options.shell ) {
+            case 'bash':
+                this.bashCompletion(commandsChain);
+                break;
+            case 'zsh':
+                this.zshCompletion(commandsChain);
+                break;
+            default:
+                console.error('Unsupported shell completion format');
+                break;
+        }
+    }
+}
+
+
+export default new CompletionGetCommand();
\ No newline at end of file
diff --git a/NodeApp/src/commander/completion/subcommands/CompletionScriptCommand.ts b/NodeApp/src/commander/completion/subcommands/CompletionScriptCommand.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9a51fc2dd2c119c10a22652c5252cc4cdc200a8b
--- /dev/null
+++ b/NodeApp/src/commander/completion/subcommands/CompletionScriptCommand.ts
@@ -0,0 +1,75 @@
+import CommanderCommand from '../../CommanderCommand.js';
+import { Argument }     from 'commander';
+
+
+class CompletionScriptCommand extends CommanderCommand {
+    protected commandName: string = 'script';
+
+    protected defineCommand() {
+        this.command.description('generate shell script completion')
+            .addArgument(new Argument('<shell>', 'shell completion format').choices([ 'bash', 'zsh' ]))
+            .action(this.commandAction.bind(this));
+    }
+
+    private bashCompletionScript() {
+        console.log(`
+#/usr/bin/env bash
+###-begin-dojo-completions-###
+#
+# dojo command completion script for bash
+#
+# Installation: dojo completion bash
+#
+function _dojo_completions()
+{
+    latest="\${COMP_WORDS[$COMP_CWORD]}"
+    words=$(dojo completion get --shell bash \${COMP_WORDS[@]})
+
+    COMPREPLY=($(compgen -W "$words" -- $latest))
+    return 0
+}
+complete -F _dojo_completions dojo
+###-end-dojo-completions-###
+        `);
+    }
+
+    private zshCompletionScript() {
+        console.log(`
+#compdef dojo
+###-begin-dojo-completions-###
+#
+# dojo command completion script for zsh
+#
+# Installation: dojo completion zsh
+#
+_dojo_completions()
+{
+  local reply
+  local si=$IFS
+  IFS=$'
+' reply=($(dojo completion get --shell zsh \${words[@]}))
+  IFS=$si
+  _describe 'values' reply
+}
+compdef _dojo_completions dojo
+###-end-dojo-completions-###
+        `);
+    }
+
+    protected async commandAction(shell: 'bash' | 'zsh'): Promise<void> {
+        switch ( shell ) {
+            case 'bash':
+                this.bashCompletionScript();
+                break;
+            case 'zsh':
+                this.zshCompletionScript();
+                break;
+            default:
+                console.error('Unsupported shell completion format');
+                break;
+        }
+    }
+}
+
+
+export default new CompletionScriptCommand();
\ No newline at end of file
diff --git a/NodeApp/src/commander/exercise/ExerciseCommand.ts b/NodeApp/src/commander/exercise/ExerciseCommand.ts
index 2a93b7a40c66845444f75d2453f731ab8fa6925f..8089092754831b94bf8b9fba3e07e2fcd92a657e 100644
--- a/NodeApp/src/commander/exercise/ExerciseCommand.ts
+++ b/NodeApp/src/commander/exercise/ExerciseCommand.ts
@@ -1,7 +1,7 @@
-import CommanderCommand          from '../CommanderCommand';
-import ExerciseCreateCommand     from './subcommands/ExerciseCreateCommand';
-import ExerciseRunCommand        from './subcommands/ExerciseRunCommand';
-import ExerciseCorrectionCommand from './subcommands/ExerciseCorrectionCommand';
+import CommanderCommand          from '../CommanderCommand.js';
+import ExerciseCreateCommand     from './subcommands/ExerciseCreateCommand.js';
+import ExerciseRunCommand        from './subcommands/ExerciseRunCommand.js';
+import ExerciseCorrectionCommand from './subcommands/ExerciseCorrectionCommand.js';
 
 
 class ExerciseCommand extends CommanderCommand {
@@ -9,7 +9,7 @@ class ExerciseCommand extends CommanderCommand {
 
     protected defineCommand() {
         this.command
-        .description('manage an exercise');
+            .description('manage an exercise');
     }
 
     protected defineSubCommands() {
@@ -18,7 +18,9 @@ class ExerciseCommand extends CommanderCommand {
         ExerciseCorrectionCommand.registerOnCommand(this.command);
     }
 
-    protected async commandAction(): Promise<void> { }
+    protected async commandAction(): Promise<void> {
+        // No action
+    }
 }
 
 
diff --git a/NodeApp/src/commander/exercise/subcommands/ExerciseCorrectionCommand.ts b/NodeApp/src/commander/exercise/subcommands/ExerciseCorrectionCommand.ts
index 0704ebfa5598e601f2575f80aa7fa8e24c221f02..6dfddd1a4eed2f39a8ae2def689536d4b9275bdf 100644
--- a/NodeApp/src/commander/exercise/subcommands/ExerciseCorrectionCommand.ts
+++ b/NodeApp/src/commander/exercise/subcommands/ExerciseCorrectionCommand.ts
@@ -1,11 +1,12 @@
-import CommanderCommand   from '../../CommanderCommand';
+import CommanderCommand   from '../../CommanderCommand.js';
 import ora                from 'ora';
-import DojoBackendManager from '../../../managers/DojoBackendManager';
-import Config             from '../../../config/Config';
-import Assignment         from '../../../sharedByClients/models/Assignment';
+import DojoBackendManager from '../../../managers/DojoBackendManager.js';
+import Config             from '../../../config/Config.js';
+import Assignment         from '../../../sharedByClients/models/Assignment.js';
 import inquirer           from 'inquirer';
 import open               from 'open';
 import chalk              from 'chalk';
+import TextStyle          from '../../../types/TextStyle.js';
 
 
 type CorrectionResume = { name: string, value: string }
@@ -16,9 +17,9 @@ class ExerciseCorrectionCommand extends CommanderCommand {
 
     protected defineCommand() {
         this.command
-        .description('link an exercise repo as a correction for an assignment')
-        .requiredOption('-a, --assignment <string>', 'id or url of the assignment of the correction')
-        .action(this.commandAction.bind(this));
+            .description('link an exercise repo as a correction for an assignment')
+            .requiredOption('-a, --assignment <string>', 'id or url of the assignment of the correction')
+            .action(this.commandAction.bind(this));
     }
 
     protected async commandAction(options: { assignment: string }): Promise<void> {
@@ -33,7 +34,6 @@ class ExerciseCorrectionCommand extends CommanderCommand {
             Config.interactiveMode ? await this.showCorrectionsInteractive(assignment, assignmentGetSpinner) : this.showCorrections(assignment, assignmentGetSpinner);
         } else {
             assignmentGetSpinner.fail(`The assignment doesn't have any corrections yet`);
-            return;
         }
     }
 
@@ -41,7 +41,7 @@ class ExerciseCorrectionCommand extends CommanderCommand {
         return assignment.corrections.map(correction => {
             return {
                 name : correction.name.replace(correction.assignmentName, '').split('-')[2].trim(),
-                value: correction.correctionCommit!.web_url?.replace('/commit/', '/tree/') ?? ''
+                value: correction.correctionCommit?.web_url?.replace('/commit/', '/tree/') ?? ''
             };
         });
     }
@@ -66,9 +66,9 @@ class ExerciseCorrectionCommand extends CommanderCommand {
                                                                  default: false
                                                              })).correctionUrl;
 
-        console.log(chalk.green(correctionUrl));
+        console.log(TextStyle.URL(correctionUrl));
 
-        open(correctionUrl).then();
+        await open(correctionUrl);
     }
 }
 
diff --git a/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts b/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts
index 00215301ae2cac6352adb41a152f64fcceeea91f..b82e7c597b4fc64e94f3929132db5d4b8e488c06 100644
--- a/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts
+++ b/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts
@@ -1,101 +1,104 @@
-import CommanderCommand   from '../../CommanderCommand';
-import chalk              from 'chalk';
-import GitlabManager      from '../../../managers/GitlabManager';
-import GitlabUser         from '../../../shared/types/Gitlab/GitlabUser';
+import CommanderCommand   from '../../CommanderCommand.js';
 import ora                from 'ora';
-import DojoBackendManager from '../../../managers/DojoBackendManager';
-import AccessesHelper     from '../../../helpers/AccessesHelper';
-import Assignment         from '../../../sharedByClients/models/Assignment';
-import Exercise           from '../../../sharedByClients/models/Exercise';
+import DojoBackendManager from '../../../managers/DojoBackendManager.js';
+import AccessesHelper     from '../../../helpers/AccessesHelper.js';
+import Assignment         from '../../../sharedByClients/models/Assignment.js';
+import Exercise           from '../../../sharedByClients/models/Exercise.js';
+import * as Gitlab        from '@gitbeaker/rest';
+import TextStyle          from '../../../types/TextStyle.js';
+import GitlabManager      from '../../../managers/GitlabManager.js';
+
+
+type CommandOptions = { assignment: string, members_id?: Array<number>, members_username?: Array<string>, clone?: string | boolean }
 
 
 class ExerciseCreateCommand extends CommanderCommand {
     protected commandName: string = 'create';
 
+    private members!: Array<Gitlab.UserSchema> | undefined;
+    private assignment!: Assignment | undefined;
+    private exercise!: Exercise;
+
     protected defineCommand() {
         this.command
-        .description('create a new exercise from an assignment')
-        .requiredOption('-a, --assignment <value>', 'assignment source (Dojo assignment ID, Dojo assignment name or Gitlab assignment URL)')
-        .option('-i, --members_id <ids...>', 'list of gitlab members ids (group\'s student) to add to the repository')
-        .option('-u, --members_username <usernames...>', 'list of gitlab members username (group\'s student) to add to the repository')
-        .option('-c, --clone [string]', 'automatically clone the repository (SSH required) in the specified directory (this will create a subdirectory with the assignment name)')
-        .action(this.commandAction.bind(this));
+            .description('create a new exercise from an assignment')
+            .requiredOption('-a, --assignment <value>', 'assignment source (Dojo assignment ID, Dojo assignment name or Gitlab assignment URL)')
+            .option('-i, --members_id <ids...>', 'list of gitlab members ids (group\'s student) to add to the repository')
+            .option('-u, --members_username <usernames...>', 'list of gitlab members username (group\'s student) to add to the repository')
+            .option('-c, --clone [string]', 'automatically clone the repository (SSH required) in the specified directory (this will create a subdirectory with the assignment name)')
+            .action(this.commandAction.bind(this));
     }
 
-    protected async commandAction(options: { assignment: string, members_id?: Array<number>, members_username?: Array<string>, clone?: string | boolean }): Promise<void> {
-        let members!: Array<GitlabUser> | false;
-        let assignment!: Assignment | undefined;
-        let exercise!: Exercise;
-
-        // Check access and retrieve data
-        {
-            console.log(chalk.cyan('Please wait while we verify and retrieve data...'));
-
-            if ( !await AccessesHelper.checkStudent() ) {
-                return;
-            }
-
-            members = await GitlabManager.fetchMembers(options);
-            if ( !members ) {
-                return;
-            }
-
-            ora('Checking assignment:').start().info();
-            const assignmentGetSpinner: ora.Ora = ora({
-                                                          text  : 'Checking if assignment exists',
-                                                          indent: 4
-                                                      }).start();
-            assignment = await DojoBackendManager.getAssignment(options.assignment);
-            if ( !assignment ) {
-                assignmentGetSpinner.fail(`Assignment "${ options.assignment }" doesn't exists`);
-                return;
-            }
-            assignmentGetSpinner.succeed(`Assignment "${ options.assignment }" exists`);
-
-            const assignmentPublishedSpinner: ora.Ora = ora({
-                                                                text  : 'Checking if assignment is published',
-                                                                indent: 4
-                                                            }).start();
-            if ( !assignment.published ) {
-                assignmentPublishedSpinner.fail(`Assignment "${ assignment.name }" isn't published`);
-                return;
-            }
-            assignmentPublishedSpinner.succeed(`Assignment "${ assignment.name }" is published`);
+    private async dataRetrieval(options: CommandOptions) {
+        console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
+
+        if ( !await AccessesHelper.checkStudent() ) {
+            throw new Error();
         }
 
-        //Create the exercise
-        {
-            console.log(chalk.cyan('Please wait while we are creating the exercise (approximately 10 seconds)...'));
-
-            try {
-                exercise = await DojoBackendManager.createExercise((assignment as Assignment).name, members);
-
-                const oraInfo = (message: string) => {
-                    ora({
-                            text  : message,
-                            indent: 4
-                        }).start().info();
-                };
-
-                oraInfo(`${ chalk.magenta('Id:') } ${ exercise.id }`);
-                oraInfo(`${ chalk.magenta('Name:') } ${ exercise.name }`);
-                oraInfo(`${ chalk.magenta('Web URL:') } ${ exercise.gitlabCreationInfo.web_url }`);
-                oraInfo(`${ chalk.magenta('HTTP Repo:') } ${ exercise.gitlabCreationInfo.http_url_to_repo }`);
-                oraInfo(`${ chalk.magenta('SSH Repo:') } ${ exercise.gitlabCreationInfo.ssh_url_to_repo }`);
-            } catch ( error ) {
-                return;
-            }
+        this.members = await GitlabManager.fetchMembers(options);
+        if ( !this.members ) {
+            throw new Error();
         }
 
-        // Clone the repository
-        {
-            if ( options.clone ) {
-                console.log(chalk.cyan('Please wait while we are cloning the repository...'));
+        ora('Checking assignment:').start().info();
+        const assignmentGetSpinner: ora.Ora = ora({
+                                                      text  : 'Checking if assignment exists',
+                                                      indent: 4
+                                                  }).start();
+        this.assignment = await DojoBackendManager.getAssignment(options.assignment);
+        if ( !this.assignment ) {
+            assignmentGetSpinner.fail(`Assignment "${ options.assignment }" doesn't exists`);
+            throw new Error();
+        }
+        assignmentGetSpinner.succeed(`Assignment "${ options.assignment }" exists`);
+
+        const assignmentPublishedSpinner: ora.Ora = ora({
+                                                            text  : 'Checking if assignment is published',
+                                                            indent: 4
+                                                        }).start();
+        if ( !this.assignment.published ) {
+            assignmentPublishedSpinner.fail(`Assignment "${ this.assignment.name }" isn't published`);
+            throw new Error();
+        }
+        assignmentPublishedSpinner.succeed(`Assignment "${ this.assignment.name }" is published`);
+    }
+
+    private async createExercise() {
+        console.log(TextStyle.BLOCK('Please wait while we are creating the exercise (approximately 10 seconds)...'));
 
-                await GitlabManager.cloneRepository(options.clone, exercise.gitlabCreationInfo.ssh_url_to_repo, `DojoExercise - ${ exercise.assignmentName }`, true, 0);
-            }
+        this.exercise = await DojoBackendManager.createExercise(this.assignment!.name, this.members!);
+
+        const oraInfo = (message: string) => {
+            ora({
+                    text  : message,
+                    indent: 4
+                }).start().info();
+        };
+
+        oraInfo(`${ TextStyle.LIST_ITEM_NAME('Id:') } ${ this.exercise.id }`);
+        oraInfo(`${ TextStyle.LIST_ITEM_NAME('Name:') } ${ this.exercise.name }`);
+        oraInfo(`${ TextStyle.LIST_ITEM_NAME('Web URL:') } ${ this.exercise.gitlabCreationInfo.web_url }`);
+        oraInfo(`${ TextStyle.LIST_ITEM_NAME('HTTP Repo:') } ${ this.exercise.gitlabCreationInfo.http_url_to_repo }`);
+        oraInfo(`${ TextStyle.LIST_ITEM_NAME('SSH Repo:') } ${ this.exercise.gitlabCreationInfo.ssh_url_to_repo }`);
+    }
+
+    private async cloneRepository(options: CommandOptions) {
+        if ( options.clone ) {
+            console.log(TextStyle.BLOCK('Please wait while we are cloning the repository...'));
+
+            await GitlabManager.cloneRepository(options.clone, this.exercise.gitlabCreationInfo.ssh_url_to_repo, `DojoExercise_${ this.exercise.assignmentName }`, true, 0);
         }
     }
+
+
+    protected async commandAction(options: CommandOptions): Promise<void> {
+        try {
+            await this.dataRetrieval(options);
+            await this.createExercise();
+            await this.cloneRepository(options);
+        } catch ( e ) { /* Do nothing */ }
+    }
 }
 
 
diff --git a/NodeApp/src/commander/exercise/subcommands/ExerciseRunCommand.ts b/NodeApp/src/commander/exercise/subcommands/ExerciseRunCommand.ts
index c4d6b2195d767955bafb319875993b8733e77176..061c9cbc65808d819927ab1c5aefad2e11c4f84a 100644
--- a/NodeApp/src/commander/exercise/subcommands/ExerciseRunCommand.ts
+++ b/NodeApp/src/commander/exercise/subcommands/ExerciseRunCommand.ts
@@ -1,25 +1,19 @@
-import CommanderCommand  from '../../CommanderCommand';
-import Config            from '../../../config/Config';
-import ExerciseRunHelper from '../../../helpers/Dojo/ExerciseRunHelper';
-import { Option }        from 'commander';
+import CommanderCommand  from '../../CommanderCommand.js';
+import ExerciseRunHelper from '../../../helpers/Dojo/ExerciseRunHelper.js';
+import GlobalHelper      from '../../../helpers/GlobalHelper.js';
 
 
 class ExerciseRunCommand extends CommanderCommand {
     protected commandName: string = 'run';
 
     protected defineCommand() {
-        // This command is synced with the "assignment run" command
-        this.command
-        .description('locally run an exercise')
-        .option('-p, --path <value>', 'exercise path', Config.folders.defaultLocalExercise)
-        .option('-v, --verbose', 'verbose mode - display principal container output in live')
-        .addOption(new Option('-w, --super-verbose', 'verbose mode - display all docker compose logs (build included) in live').conflicts('verbose'))
-        .addOption(new Option('--verbose-ssj2').hideHelp().implies({ superVerbose: true }))
-        .action(this.commandAction.bind(this));
+        GlobalHelper.runCommandDefinition(this.command)
+            .description('locally run an exercise')
+            .action(this.commandAction.bind(this));
     }
 
     protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
-        await ExerciseRunHelper.run(options);
+        await (new ExerciseRunHelper(options)).run();
     }
 }
 
diff --git a/NodeApp/src/commander/session/SessionCommand.ts b/NodeApp/src/commander/session/SessionCommand.ts
deleted file mode 100644
index 6dff026f2eae88717b4ca3bebe53f2db8025e749..0000000000000000000000000000000000000000
--- a/NodeApp/src/commander/session/SessionCommand.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import CommanderCommand     from '../CommanderCommand';
-import SessionTestCommand   from './subcommands/SessionTestCommand';
-import SessionLoginCommand  from './subcommands/SessionLoginCommand';
-import SessionLogoutCommand from './subcommands/SessionLogoutCommand';
-
-
-class SessionCommand extends CommanderCommand {
-    protected commandName: string = 'session';
-
-    protected defineCommand() {
-        this.command
-        .description('manage Dojo and Gitlab sessions');
-    }
-
-    protected defineSubCommands() {
-        SessionLoginCommand.registerOnCommand(this.command);
-        SessionLogoutCommand.registerOnCommand(this.command);
-        SessionTestCommand.registerOnCommand(this.command);
-    }
-
-    protected async commandAction(): Promise<void> { }
-}
-
-
-export default new SessionCommand();
\ No newline at end of file
diff --git a/NodeApp/src/commander/session/subcommands/SessionLoginCommand.ts b/NodeApp/src/commander/session/subcommands/SessionLoginCommand.ts
deleted file mode 100644
index d9061e261f67f35104d1836d75cc78adea55dbc1..0000000000000000000000000000000000000000
--- a/NodeApp/src/commander/session/subcommands/SessionLoginCommand.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import chalk            from 'chalk';
-import CommanderCommand from '../../CommanderCommand';
-import SessionManager   from '../../../managers/SessionManager';
-
-
-class SessionLoginCommand extends CommanderCommand {
-    protected commandName: string = 'login';
-
-    protected defineCommand() {
-        this.command
-        .description('login to Dojo')
-        .option('-c, --cli', 'proceed to the login in headless mode (do not try to open web browser).')
-        .action(this.commandAction.bind(this));
-    }
-
-    protected async commandAction(options: { cli: boolean }): Promise<void> {
-        try {
-            console.log(chalk.cyan('Please wait while we login you into Dojo...'));
-            await SessionManager.login(options.cli);
-        } catch ( error ) { /* empty */ }
-    }
-}
-
-
-export default new SessionLoginCommand();
\ No newline at end of file
diff --git a/NodeApp/src/commander/session/subcommands/SessionTestCommand.ts b/NodeApp/src/commander/session/subcommands/SessionTestCommand.ts
deleted file mode 100644
index 57c1bf00b1ea126c17fddea14714d06effdbea40..0000000000000000000000000000000000000000
--- a/NodeApp/src/commander/session/subcommands/SessionTestCommand.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import CommanderCommand from '../../CommanderCommand';
-import SessionManager   from '../../../managers/SessionManager';
-import GitlabManager    from '../../../managers/GitlabManager';
-
-
-class SessionTestCommand extends CommanderCommand {
-    protected commandName: string = 'test';
-
-    protected defineCommand() {
-        this.command
-        .description('test availability of registered Dojo API and Gitlab API sessions')
-        .action(this.commandAction.bind(this));
-    }
-
-    protected async commandAction(): Promise<void> {
-        await SessionManager.testSession();
-        await GitlabManager.testToken();
-    }
-}
-
-
-export default new SessionTestCommand();
\ No newline at end of file
diff --git a/NodeApp/src/config/Config.ts b/NodeApp/src/config/Config.ts
index 375f0cf22034eb73d958b8d711efe8fa9af9eb72..615a36942e83d11b0e63d3de6a89e8ff74944d5c 100644
--- a/NodeApp/src/config/Config.ts
+++ b/NodeApp/src/config/Config.ts
@@ -10,6 +10,7 @@ class Config {
 
     public readonly gitlab: {
         cliReleasePage: string
+        cliPreAlphaReleasePage: string
     };
 
     public readonly login: {
@@ -42,7 +43,8 @@ class Config {
         this.versionUpdateInformationPeriodHours = Number(process.env.VERSION_UPDATE_INFORMATION_PERIOD_HOURS || 24);
 
         this.gitlab = {
-            cliReleasePage: process.env.GITLAB_CLI_RELEASE_PAGE || ''
+            cliReleasePage        : process.env.GITLAB_CLI_RELEASE_PAGE || '',
+            cliPreAlphaReleasePage: process.env.GITLAB_CLI_PREALPHA_RELEASE_PAGE || ''
         };
 
         this.login = {
diff --git a/NodeApp/src/config/ConfigFiles.ts b/NodeApp/src/config/ConfigFiles.ts
index 3bce1b773c92e24ee6f80f5ae2a03c3aa1ad6794..9744e260e423d853a0fc25d81e106bb191e40a22 100644
--- a/NodeApp/src/config/ConfigFiles.ts
+++ b/NodeApp/src/config/ConfigFiles.ts
@@ -1,5 +1,5 @@
-import LocalConfigFile from './LocalConfigFile';
-import Config          from './Config';
+import LocalConfigFile from './LocalConfigFile.js';
+import Config          from './Config.js';
 
 
 const sessionConfigFile = new LocalConfigFile(Config.localConfig.sessionFile);
diff --git a/NodeApp/src/config/LocalConfigFile.ts b/NodeApp/src/config/LocalConfigFile.ts
index ecc0530b3ce4144a99a10f1d9ec87fcb11ab99c3..29f62a6343defe952125d6e52b546fbd7f5e7057 100644
--- a/NodeApp/src/config/LocalConfigFile.ts
+++ b/NodeApp/src/config/LocalConfigFile.ts
@@ -1,10 +1,14 @@
 import * as fs from 'fs';
-import Config  from './Config';
+import Config  from './Config.js';
 import JSON5   from 'json5';
 
 
 class LocalConfigFile {
-    constructor(private filename: string) {
+    private readonly filename: string;
+
+    constructor(filename: string) {
+        this.filename = filename;
+
         this.loadConfig();
     }
 
@@ -28,7 +32,7 @@ class LocalConfigFile {
         }
     }
 
-    getParam(key: string): unknown | null {
+    getParam(key: string): unknown {
         const value = key in this._config ? this._config[key] : null;
         if ( value === null ) {
             return null;
diff --git a/NodeApp/src/helpers/AccessesHelper.ts b/NodeApp/src/helpers/AccessesHelper.ts
index c9ff5e1bdaa823ca011eba7fa057e1c0b949a74e..1be0777e85227aae8e950cb6974e5df2866e72ba 100644
--- a/NodeApp/src/helpers/AccessesHelper.ts
+++ b/NodeApp/src/helpers/AccessesHelper.ts
@@ -1,5 +1,5 @@
-import SessionManager from '../managers/SessionManager';
-import GitlabManager  from '../managers/GitlabManager';
+import SessionManager from '../managers/SessionManager.js';
+import GitlabManager  from '../managers/GitlabManager.js';
 
 
 class AccessesHelper {
diff --git a/NodeApp/src/helpers/AutoCompletionHelper.ts b/NodeApp/src/helpers/AutoCompletionHelper.ts
new file mode 100644
index 0000000000000000000000000000000000000000..122be3bc783ed231386135b1a747cc45a67dc3d8
--- /dev/null
+++ b/NodeApp/src/helpers/AutoCompletionHelper.ts
@@ -0,0 +1,367 @@
+import { Command }                from 'commander';
+import { existsSync, renameSync } from 'fs';
+import ora                        from 'ora';
+import TextStyle                  from '../types/TextStyle.js';
+import inquirer                   from 'inquirer';
+import fs                         from 'fs-extra';
+
+
+function renameFile(filename: string, showWarning: boolean) {
+    const oldFilename = `${ filename }.old`;
+    const spinner: ora.Ora = ora(`Renaming ${ TextStyle.CODE(filename) } in ${ TextStyle.CODE(oldFilename) } ...`).start();
+    try {
+        renameSync(filename, oldFilename);
+        spinner.succeed(`Renaming success: ${ TextStyle.CODE(filename) } in ${ TextStyle.CODE(oldFilename) }`);
+
+        if ( showWarning ) {
+            console.log(`${ TextStyle.WARNING('Warning:') } Your ${ TextStyle.CODE(filename) } was renamed ${ TextStyle.CODE(oldFilename) }. If this was not intended please revert this change.`);
+        }
+    } catch ( error ) {
+        spinner.fail(`Renaming failed: ${ error }.`);
+    }
+}
+
+async function askConfirmation(msg: string): Promise<boolean> {
+    return (await inquirer.prompt({
+                                      name   : 'confirm',
+                                      message: msg,
+                                      type   : 'confirm',
+                                      default: false
+                                  })).confirm as boolean;
+}
+
+// Returns false, when the renaming is interrupted
+export async function tryRenameFile(path: string, force: boolean): Promise<boolean> {
+    const fileExists = existsSync(path);
+    if ( fileExists && force ) {
+        renameFile(path, false);
+    } else if ( fileExists ) {
+        const confirm = (await askConfirmation(`${ TextStyle.CODE(path) } in ${ TextStyle.CODE(path + '.old') }. Are you sure?`));
+        if ( confirm ) {
+            renameFile(path, true);
+        } else {
+            console.log(`${ TextStyle.BLOCK('Completion generation interrupted.') }`);
+            return false;
+        }
+    }
+    return true;
+}
+
+const fishFunction = `
+function __fish_dojo_using_commands
+    set cmd (commandline -opc)
+    set num_cmd (count $cmd)
+    if [ $num_cmd -eq $argv[1] ]
+        for i in (seq 1 (math $num_cmd))
+            if [ $argv[(math $i+1)] != $cmd[$i] ]
+                return 1
+            end
+        end
+        return 0
+    end
+    return 1
+end
+
+complete -f -c dojo
+`;
+
+function isHidden(cmd: Command): boolean {
+    return (cmd as Command & { _hidden: boolean })._hidden;
+}
+
+function isLeaf(cmd: Command): boolean {
+    return cmd.commands.length === 0;
+}
+
+function flatten(cmd: Command): Array<Command> {
+    if ( isLeaf(cmd) ) {
+        return [ cmd ];
+    } else {
+        return cmd.commands
+            .filter(c => !isHidden(c))
+            .map(child => flatten(child))
+            .reduce((acc, subCmd) => acc.concat(subCmd), [ cmd ]);
+    }
+}
+
+// Computes the maximum number of commands until a leaf is reached
+function computeDepth(cmd: Command | undefined): number {
+    if ( cmd === undefined ) {
+        return 0;
+    } else {
+        return 1 + cmd.commands.filter(c => !isHidden(c)).map(subCmd => computeDepth(subCmd)).reduce((acc, depth) => depth > acc ? depth : acc, 0);
+    }
+}
+
+// Computes the maximum number of commands until the root is reached
+function computeHeight(cmd: Command | null): number {
+    let height = 0;
+    let tmp = cmd;
+    while ( tmp !== null ) {
+        tmp = tmp.parent;
+        height += 1;
+    }
+    return height;
+}
+
+// Computes the maximum number of commands until the root is reached
+export function getRoot(cmd: Command): Command {
+    if ( cmd.parent == null ) {
+        return cmd;
+    } else {
+        return getRoot(cmd.parent);
+    }
+}
+
+function getOptions(cmd: Command): string {
+    // we remove <args>, [command], and , from option lines
+    return cmd.options.filter(opt => !opt.hidden).map(opt => opt.flags.replace(/<.*?>/, '').replace(/\[.*?]/, '').replace(',', '').trimEnd()).join(' ');
+}
+
+function commandsAndOptionsToString(cmd: Command): string {
+    return cmd.commands.filter(c => !isHidden(c)).map(c => c.name()).join(' ').concat(' ' + getOptions(cmd)).trim().concat(' --help -h').trim();
+}
+
+function addLine(identLevel: number, pattern: string): string {
+    return `${ '    '.repeat(identLevel) }${ pattern }\n`;
+}
+
+export function getCommandFromChain(currentCmd: Command, chain: Array<string>): Command | null {
+    if ( chain.length === 0 ) {
+        return currentCmd;
+    } else {
+        const subCmd = currentCmd.commands.find(c => c.name() === chain[0]);
+        if ( subCmd === undefined ) {
+            return currentCmd;
+        } else {
+            return getCommandFromChain(subCmd, chain.slice(1));
+        }
+    }
+}
+
+function generateBashSubCommands(cmd: Command, current: number, maxDepth: number, ident: number): string {
+    if ( current === maxDepth ) {
+        return addLine(ident, `case "\${COMP_WORDS[$COMP_CWORD - ${ maxDepth - current + 1 }]}" in`) + addLine(ident + 1, `${ cmd.name() })`) + addLine(ident + 2, `words="${ commandsAndOptionsToString(cmd) }"`) + addLine(ident + 1, ';;') + addLine(ident + 1, '*)') + addLine(ident + 1, ';;') + addLine(ident, 'esac');
+    } else {
+        let data = addLine(ident, `case "\${COMP_WORDS[$COMP_CWORD - ${ maxDepth - current + 1 }]}" in`) + addLine(ident + 1, `${ cmd.name() })`);
+        cmd.commands.filter(c => !isHidden(c)).forEach(subCmd => {
+            data += generateBashSubCommands(subCmd, current + 1, maxDepth, ident + 2);
+        });
+        data += addLine(ident + 1, ';;') + addLine(ident + 1, '*)') + addLine(ident + 1, ';;') + addLine(ident, 'esac');
+        return data;
+    }
+}
+
+export function generateBashCompletion(root: Command): string {
+    const depth = computeDepth(root);
+    let data = addLine(0, '#/usr/bin/env bash\nfunction _dojo_completions()') + addLine(0, '{') + addLine(1, 'latest="${COMP_WORDS[$COMP_CWORD]}"');
+    for ( let i = 1 ; i <= depth ; i++ ) {
+        data += addLine(1, `${ i === 1 ? 'if' : 'elif' } [ $COMP_CWORD -eq ${ depth - i + 1 } ]`) + addLine(1, 'then');
+        data += generateBashSubCommands(root, i, depth, 2);
+    }
+    data += addLine(1, 'fi') + addLine(1, 'COMPREPLY=($(compgen -W "$words" -- $latest))') + addLine(1, 'return 0') + addLine(0, '}') + addLine(0, 'complete -F _dojo_completions dojo');
+
+    return data;
+}
+
+const prefix = 'complete -f -c dojo -n \'__fish_dojo_using_commands';
+
+function generateCommandChain(cmd: Command | null): string {
+    let data = '';
+    while ( cmd !== null ) {
+        data = cmd.name().concat(` ${ data }`);
+        cmd = cmd.parent;
+    }
+    return data.trimEnd();
+}
+
+function hasOptions(cmd: Command): boolean {
+    return cmd.options.length > 0;
+}
+
+function optionsToString(cmd: Command): string {
+    return cmd.options.filter(opt => !opt.hidden).map(opt => `${ prefix } ${ computeHeight(cmd) } ${ generateCommandChain(cmd) }' -a "${ opt.short ?? '' } ${ opt.long ?? '' }" -d "${ opt.description }"`).join('\n').concat('\n');
+}
+
+export function generateFishCompletion(root: Command): string {
+    const commands = flatten(root);
+
+    return fishFunction.concat(// add completions for options
+                               commands.filter(c => !isHidden(c)).filter(cmd => hasOptions(cmd)).map(cmd => optionsToString(cmd)).filter(str => str !== '').join('')).concat(// add completions for commands and subcommands
+                                                                                                                                                                             commands.filter(c => !isHidden(c)).filter(cmd => !isLeaf(cmd)).map(cmd => cmd.commands.filter(c => !isHidden(c)).map(subCmd => `${ prefix } ${ computeHeight(cmd) } ${ generateCommandChain(cmd) }' -a ${ subCmd.name() } -d "${ subCmd.description() }"`).join('\n').concat('\n')).join(''));
+}
+
+export function updateRcFile(shellType: 'bash' | 'zsh', filePath: string, completionCommand: string) {
+    const spinner: ora.Ora = ora(`Modifying ${ filePath } ...`).start();
+    if ( fs.existsSync(filePath) ) {
+        const data = fs.readFileSync(filePath);
+        let updated = false;
+        try {
+            if ( !data.includes(completionCommand) ) {
+                fs.appendFileSync(filePath, completionCommand);
+                updated = true;
+            }
+        } catch {
+            spinner.fail(`Error appending in ${ filePath }`);
+            return;
+        }
+
+        spinner.succeed(updated ? `${ shellType } updated. Please restart your shell session.` : `${ shellType } already up to date.`);
+    } else {
+        try {
+            fs.writeFileSync(filePath, completionCommand);
+            spinner.succeed(`${ shellType } written. Please restart your shell session.`);
+        } catch ( error ) {
+            spinner.fail(`Error writing in ${ filePath }`);
+        }
+    }
+}
+
+
+// The following code should create a bash completion automatically from the Commander
+// CLI library. The file should look something like that (it looks at the time
+// this comment is written).
+
+// #/usr/bin/env bash
+// function _dojo_completions()
+// {
+//     latest="${COMP_WORDS[$COMP_CWORD]}"
+//     if [ $COMP_CWORD -eq 3 ]
+//     then
+//         case "${COMP_WORDS[$COMP_CWORD - 3]}" in
+//             dojo)
+//                 case "${COMP_WORDS[$COMP_CWORD - 2]}" in
+//                     session)
+//                         case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//                             login)
+//                                 words="-c --cli --help -h"
+//                             ;;
+//                             *)
+//                             ;;
+//                         esac
+//                         case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//                             logout)
+//                                 words="-f --force --help -h"
+//                             ;;
+//                             *)
+//                             ;;
+//                         esac
+//                         case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//                             test)
+//                                 words="--help -h"
+//                             ;;
+//                             *)
+//                             ;;
+//                         esac
+//                     ;;
+//                     *)
+//                     ;;
+//                 esac
+//                 case "${COMP_WORDS[$COMP_CWORD - 2]}" in
+//                     assignment)
+//                         case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//                             create)
+//                                 words="-n --name -i --members_id -u --members_username -t --template -c --clone --help -h"
+//                             ;;
+//                             *)
+//                             ;;
+//                         esac
+//                         case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//                             check)
+//                                 words="-p --path -v --verbose -w --super-verbose --help -h"
+//                             ;;
+//                             *)
+//                             ;;
+//                         esac
+//                         case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//                             run)
+//                                 words="-p --path -v --verbose -w --super-verbose --help -h"
+//                             ;;
+//                             *)
+//                             ;;
+//                         esac
+//                         case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//                             publish)
+//                                 words="-f --force --help -h"
+//                             ;;
+//                             *)
+//                             ;;
+//                         esac
+//                         case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//                             unpublish)
+//                                 words="-f --force --help -h"
+//                             ;;
+//                             *)
+//                             ;;
+//                         esac
+//                     ;;
+//                     *)
+//                     ;;
+//                 esac
+//                 case "${COMP_WORDS[$COMP_CWORD - 2]}" in
+//                     exercise)
+//                         case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//                             create)
+//                                 words="-a --assignment -i --members_id -u --members_username -c --clone --help -h"
+//                             ;;
+//                             *)
+//                             ;;
+//                         esac
+//                         case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//                             run)
+//                                 words="-p --path -v --verbose -w --super-verbose --help -h"
+//                             ;;
+//                             *)
+//                             ;;
+//                         esac
+//                     ;;
+//                     *)
+//                     ;;
+//                 esac
+//             ;;
+//             *)
+//             ;;
+//         esac
+//     elif [ $COMP_CWORD -eq 2 ]
+//     then
+//         case "${COMP_WORDS[$COMP_CWORD - 2]}" in
+//             dojo)
+//                 case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//                     session)
+//                         words="login logout test --help -h"
+//                     ;;
+//                     *)
+//                     ;;
+//                 esac
+//                 case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//                     assignment)
+//                         words="create check run publish unpublish --help -h"
+//                     ;;
+//                     *)
+//                     ;;
+//                 esac
+//                 case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//                     exercise)
+//                         words="create run --help -h"
+//                     ;;
+//                     *)
+//                     ;;
+//                 esac
+//             ;;
+//             *)
+//             ;;
+//         esac
+//     elif [ $COMP_CWORD -eq 1 ]
+//     then
+//         case "${COMP_WORDS[$COMP_CWORD - 1]}" in
+//             dojo)
+//                 words="session assignment exercise -V --version -H --host --help -h"
+//             ;;
+//             *)
+//             ;;
+//         esac
+//     fi
+//     COMPREPLY=($(compgen -W "$words" -- $latest))
+//     return 0
+// }
+// complete -F _dojo_completions dojo
diff --git a/NodeApp/src/helpers/Dojo/ExerciseRunHelper.ts b/NodeApp/src/helpers/Dojo/ExerciseRunHelper.ts
index 2db4ba26242ff5fbea45c80b82bbfaff1b27dfa7..235bc74664d12d874fe945f5582f652852c30568 100644
--- a/NodeApp/src/helpers/Dojo/ExerciseRunHelper.ts
+++ b/NodeApp/src/helpers/Dojo/ExerciseRunHelper.ts
@@ -1,19 +1,20 @@
 import ora                                  from 'ora';
 import chalk                                from 'chalk';
-import Config                               from '../../config/Config';
-import AssignmentFile                       from '../../shared/types/Dojo/AssignmentFile';
-import ExerciseDockerCompose                from '../../sharedByClients/helpers/Dojo/ExerciseDockerCompose';
-import ExerciseResultsSanitizerAndValidator from '../../sharedByClients/helpers/Dojo/ExerciseResultsSanitizerAndValidator';
+import Config                               from '../../config/Config.js';
+import AssignmentFile                       from '../../shared/types/Dojo/AssignmentFile.js';
+import ExerciseDockerCompose                from '../../sharedByClients/helpers/Dojo/ExerciseDockerCompose.js';
+import ExerciseResultsSanitizerAndValidator from '../../sharedByClients/helpers/Dojo/ExerciseResultsSanitizerAndValidator.js';
 import fs                                   from 'node:fs';
-import ClientsSharedConfig                  from '../../sharedByClients/config/ClientsSharedConfig';
-import SharedAssignmentHelper               from '../../shared/helpers/Dojo/SharedAssignmentHelper';
+import ClientsSharedConfig                  from '../../sharedByClients/config/ClientsSharedConfig.js';
+import SharedAssignmentHelper               from '../../shared/helpers/Dojo/SharedAssignmentHelper.js';
 import path                                 from 'path';
-import ExerciseCheckerError                 from '../../shared/types/Dojo/ExerciseCheckerError';
-import ClientsSharedExerciseHelper          from '../../sharedByClients/helpers/Dojo/ClientsSharedExerciseHelper';
+import ExerciseCheckerError                 from '../../shared/types/Dojo/ExerciseCheckerError.js';
+import ClientsSharedExerciseHelper          from '../../sharedByClients/helpers/Dojo/ClientsSharedExerciseHelper.js';
 import os                                   from 'os';
 import util                                 from 'util';
 import { exec }                             from 'child_process';
-import SharedConfig                         from '../../shared/config/SharedConfig';
+import SharedConfig                         from '../../shared/config/SharedConfig.js';
+import TextStyle                            from '../../types/TextStyle.js';
 
 
 const execAsync = util.promisify(exec);
@@ -30,6 +31,25 @@ class ExerciseRunHelper {
 
     private readonly fileComposeLogs: string = path.join(this.folderResultsDojo, `dockerComposeLogs.txt`);
 
+    private readonly options: { path: string, verbose: boolean, superVerbose: boolean };
+    private readonly verbose: boolean;
+    private readonly localExercisePath: string;
+
+    private assignmentFile!: AssignmentFile;
+    private exerciseDockerCompose!: ExerciseDockerCompose;
+    private exerciseResultsValidation!: ExerciseResultsSanitizerAndValidator;
+
+    private haveResultsVolume!: boolean;
+
+    private exerciseRunSpinner!: ora.Ora;
+    private buildPhase: boolean | undefined = undefined;
+
+    constructor(options: { path: string, verbose: boolean, superVerbose: boolean }) {
+        this.options = options;
+        this.verbose = options.verbose || options.superVerbose || SharedConfig.debug;
+        this.localExercisePath = options.path ?? Config.folders.defaultLocalExercise;
+    }
+
     private displayExecutionLogs() {
         ora({
                 text  : `${ chalk.magenta('Execution logs folder:') } ${ this.folderResultsVolume }`,
@@ -37,231 +57,239 @@ class ExerciseRunHelper {
             }).start().info();
     }
 
-    async run(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
-        const verbose: boolean = options.verbose || options.superVerbose || SharedConfig.debug;
+    /**
+     * Step 1: Check requirements (if it's an exercise folder and if Docker daemon is running)
+     * @private
+     */
+    private async checkRequirements() {
+        console.log(TextStyle.BLOCK('Please wait while we are checking and creating dependencies...'));
 
-        const localExercisePath: string = options.path ?? Config.folders.defaultLocalExercise;
+        // Create result temp folder
+        fs.mkdirSync(this.folderResultsVolume, { recursive: true });
+        fs.mkdirSync(this.folderResultsDojo, { recursive: true });
+        fs.mkdirSync(this.folderResultsExercise, { recursive: true });
 
-        let assignmentFile: AssignmentFile;
-        let exerciseDockerCompose: ExerciseDockerCompose;
-        let exerciseResultsValidation: ExerciseResultsSanitizerAndValidator;
 
-        let haveResultsVolume: boolean;
+        ora({
+                text  : `Checking exercise content:`,
+                indent: 4
+            }).start().info();
 
-        // Step 1: Check requirements (if it's an exercise folder and if Docker daemon is running)
+        // Exercise folder
         {
-            console.log(chalk.cyan('Please wait while we are checking and creating dependencies...'));
+            const spinner: ora.Ora = ora({
+                                             text  : `Checking exercise folder`,
+                                             indent: 8
+                                         }).start();
 
-            // Create result temp folder
-            fs.mkdirSync(this.folderResultsVolume, { recursive: true });
-            fs.mkdirSync(this.folderResultsDojo, { recursive: true });
-            fs.mkdirSync(this.folderResultsExercise, { recursive: true });
+            const files = fs.readdirSync(this.options.path);
+            const missingFiles = Config.exercise.neededFiles.map((file: string): [ string, boolean ] => [ file, files.includes(file) ]).filter((file: [ string, boolean ]) => !file[1]);
 
+            if ( missingFiles.length > 0 ) {
+                spinner.fail(`The exercise folder is missing the following files: ${ missingFiles.map((file: [ string, boolean ]) => file[0]).join(', ') }`);
+                throw new Error();
+            }
 
-            ora({
-                    text  : `Checking exercise content:`,
-                    indent: 4
-                }).start().info();
+            spinner.succeed(`The exercise folder contains all the needed files`);
+        }
+
+        // dojo_assignment.json validity
+        {
+            const spinner: ora.Ora = ora({
+                                             text  : `Checking ${ ClientsSharedConfig.assignment.filename } file`,
+                                             indent: 8
+                                         }).start();
+
+            const validationResults = SharedAssignmentHelper.validateDescriptionFile(path.join(this.options.path, ClientsSharedConfig.assignment.filename));
+            if ( !validationResults.isValid ) {
+                spinner.fail(`The ${ ClientsSharedConfig.assignment.filename } file is invalid: ${ validationResults.error }`);
+                throw new Error();
+            } else {
+                this.assignmentFile = validationResults.content!;
+            }
 
-            // Exercise folder
-            {
-                const spinner: ora.Ora = ora({
-                                                 text  : `Checking exercise folder`,
-                                                 indent: 8
-                                             }).start();
+            this.haveResultsVolume = this.assignmentFile.result.volume !== undefined;
 
-                const files = fs.readdirSync(options.path);
-                const missingFiles = Config.exercise.neededFiles.map((file: string): [ string, boolean ] => [ file, files.includes(file) ]).filter((file: [ string, boolean ]) => !file[1]);
+            spinner.succeed(`The ${ ClientsSharedConfig.assignment.filename } file is valid`);
+        }
 
-                if ( missingFiles.length > 0 ) {
-                    spinner.fail(`The exercise folder is missing the following files: ${ missingFiles.map((file: [ string, boolean ]) => file[0]).join(', ') }`);
-                    return;
-                }
+        // Docker daemon
+        {
+            const spinner: ora.Ora = ora({
+                                             text  : `Checking Docker daemon`,
+                                             indent: 4
+                                         }).start();
 
-                spinner.succeed(`The exercise folder contains all the needed files`);
+            try {
+                await execAsync(`docker ps`);
+            } catch ( error ) {
+                spinner.fail(`The Docker daemon is not running`);
+                throw new Error();
             }
 
-            // dojo_assignment.json validity
-            {
-                const spinner: ora.Ora = ora({
-                                                 text  : `Checking ${ ClientsSharedConfig.assignment.filename } file`,
-                                                 indent: 8
-                                             }).start();
-
-                const validationResults = SharedAssignmentHelper.validateDescriptionFile(path.join(options.path, ClientsSharedConfig.assignment.filename));
-                if ( !validationResults.isValid ) {
-                    spinner.fail(`The ${ ClientsSharedConfig.assignment.filename } file is invalid: ${ validationResults.error }`);
-                    return;
-                } else {
-                    assignmentFile = validationResults.content!;
-                }
+            spinner.succeed(`The Docker daemon is running`);
+        }
+    }
 
-                haveResultsVolume = assignmentFile.result.volume !== undefined;
+    private logsEvent(log: string, _error: boolean, displayable: boolean, currentStep: string) {
+        for ( const line of log.split('\n') ) {
+            if ( displayable && this.buildPhase === undefined && line.startsWith('#') ) {
+                this.buildPhase = true;
+            }
 
-                spinner.succeed(`The ${ ClientsSharedConfig.assignment.filename } file is valid`);
+            if ( currentStep === 'COMPOSE_RUN' && this.buildPhase === true && line !== '' && !line.startsWith('#') ) {
+                this.buildPhase = false;
             }
 
-            // Docker daemon
-            {
-                const spinner: ora.Ora = ora({
-                                                 text  : `Checking Docker daemon`,
-                                                 indent: 4
-                                             }).start();
-
-                try {
-                    await execAsync(`docker ps`);
-                } catch ( error ) {
-                    spinner.fail(`The Docker daemon is not running`);
-                    return;
-                }
+            if ( SharedConfig.debug || (displayable && (this.options.superVerbose || this.buildPhase === false)) ) {
+                console.log(line);
+            }
+        }
+    }
+
+    private stepEvent(name: string, message: string) {
+        this.exerciseRunSpinner = ora({
+                                          text  : message,
+                                          indent: 4
+                                      }).start();
+
+        if ( this.verbose && name === 'COMPOSE_RUN' ) {
+            this.exerciseRunSpinner.info();
+        }
+    }
 
-                spinner.succeed(`The Docker daemon is running`);
+    private endStepEvent(stepName: string, message: string, error: boolean) {
+        if ( error ) {
+            if ( this.verbose && stepName === 'COMPOSE_RUN' ) {
+                ora({
+                        text  : message,
+                        indent: 4
+                    }).start().fail();
+            } else {
+                this.exerciseRunSpinner.fail(message);
             }
+        } else if ( this.verbose && stepName === 'COMPOSE_RUN' ) {
+            ora({
+                    text  : message,
+                    indent: 4
+                }).start().succeed();
+        } else {
+            this.exerciseRunSpinner.succeed(message);
         }
 
+    }
 
-        // Step 2: Run docker-compose file
-        {
-            console.log(chalk.cyan('Please wait while we are running the exercise...'));
+    /**
+     * Step 2: Run docker-compose file
+     * @private
+     */
+    private async runDockerCompose() {
+        console.log(TextStyle.BLOCK('Please wait while we are running the exercise...'));
 
-            let composeFileOverride: string[] = [];
-            const composeOverridePath: string = path.join(localExercisePath, 'docker-compose-override.yml');
-            if ( haveResultsVolume ) {
-                const composeOverride = fs.readFileSync(path.join(__dirname, '../../../assets/docker-compose-override.yml'), 'utf8').replace('{{VOLUME_NAME}}', assignmentFile.result.volume!).replace('{{MOUNT_PATH}}', this.folderResultsExercise);
-                fs.writeFileSync(composeOverridePath, composeOverride);
+        let composeFileOverride: string[] = [];
+        const composeOverridePath: string = path.join(this.localExercisePath, 'docker-compose-override.yml');
+        if ( this.haveResultsVolume ) {
+            const composeOverride = fs.readFileSync(path.join(__dirname, '../../../assets/docker-compose-override.yml'), 'utf8').replace('{{VOLUME_NAME}}', this.assignmentFile.result.volume!).replace('{{MOUNT_PATH}}', this.folderResultsExercise);
+            fs.writeFileSync(composeOverridePath, composeOverride);
 
-                composeFileOverride = [ composeOverridePath ];
-            }
+            composeFileOverride = [ composeOverridePath ];
+        }
 
-            exerciseDockerCompose = new ExerciseDockerCompose(this.projectName, assignmentFile, localExercisePath, composeFileOverride);
+        this.exerciseDockerCompose = new ExerciseDockerCompose(this.projectName, this.assignmentFile, this.localExercisePath, composeFileOverride);
 
-            try {
-                await new Promise<void>((resolve, reject) => {
-                    let spinner: ora.Ora;
-
-                    if ( verbose ) {
-                        let buildPhase: boolean | undefined = undefined;
-                        exerciseDockerCompose.events.on('logs', (log: string, _error: boolean, displayable: boolean, currentStep: string) => {
-                            for ( const line of log.split('\n') ) {
-                                if ( displayable && buildPhase == undefined && line.startsWith('#') ) {
-                                    buildPhase = true;
-                                }
-
-                                if ( currentStep == 'COMPOSE_RUN' && buildPhase === true && line != '' && !line.startsWith('#') ) {
-                                    buildPhase = false;
-                                }
-
-                                if ( SharedConfig.debug || (displayable && (options.superVerbose || buildPhase === false)) ) {
-                                    console.log(line);
-                                }
-                            }
-                        });
-                    }
+        try {
+            await new Promise<void>((resolve, reject) => {
+                if ( this.verbose ) {
+                    this.exerciseDockerCompose.events.on('logs', this.logsEvent.bind(this));
+                }
 
-                    exerciseDockerCompose.events.on('step', (name: string, message: string) => {
-                        spinner = ora({
-                                          text  : message,
-                                          indent: 4
-                                      }).start();
+                this.exerciseDockerCompose.events.on('step', this.stepEvent.bind(this));
 
-                        if ( verbose && name == 'COMPOSE_RUN' ) {
-                            spinner.info();
-                        }
-                    });
-
-                    exerciseDockerCompose.events.on('endStep', (stepName: string, message: string, error: boolean) => {
-                        if ( error ) {
-                            if ( verbose && stepName == 'COMPOSE_RUN' ) {
-                                ora({
-                                        text  : message,
-                                        indent: 4
-                                    }).start().fail();
-                            } else {
-                                spinner.fail(message);
-                            }
-                        } else {
-                            if ( verbose && stepName == 'COMPOSE_RUN' ) {
-                                ora({
-                                        text  : message,
-                                        indent: 4
-                                    }).start().succeed();
-                            } else {
-                                spinner.succeed(message);
-                            }
-                        }
-                    });
+                this.exerciseDockerCompose.events.on('endStep', this.endStepEvent.bind(this));
 
-                    exerciseDockerCompose.events.on('finished', (success: boolean) => {
-                        success ? resolve() : reject();
-                    });
+                this.exerciseDockerCompose.events.on('finished', (success: boolean) => success ? resolve() : reject());
 
-                    exerciseDockerCompose.run(true);
-                });
-            } catch ( error ) { /* empty */ }
+                this.exerciseDockerCompose.run(true);
+            });
+        } catch ( error ) { /* empty */ }
 
-            fs.rmSync(composeOverridePath, { force: true });
-            fs.writeFileSync(this.fileComposeLogs, exerciseDockerCompose.allLogs);
+        fs.rmSync(composeOverridePath, { force: true });
+        fs.writeFileSync(this.fileComposeLogs, this.exerciseDockerCompose.allLogs);
 
-            if ( !exerciseDockerCompose.success ) {
-                this.displayExecutionLogs();
-                return;
-            }
+        if ( !this.exerciseDockerCompose.success ) {
+            this.displayExecutionLogs();
+            throw new Error();
         }
+    }
 
+    /**
+     * Step 3: Get results
+     * @private
+     */
+    private async getResults() {
+        console.log(TextStyle.BLOCK('Please wait while we are checking the results...'));
 
-        // Step 3: Get results
-        {
-            console.log(chalk.cyan('Please wait while we are checking the results...'));
+        this.exerciseResultsValidation = new ExerciseResultsSanitizerAndValidator(this.folderResultsDojo, this.folderResultsExercise, this.exerciseDockerCompose.exitCode);
 
-            exerciseResultsValidation = new ExerciseResultsSanitizerAndValidator(this.folderResultsDojo, this.folderResultsExercise, exerciseDockerCompose.exitCode);
+        try {
+            await new Promise<void>((resolve, reject) => {
+                let spinner: ora.Ora;
 
-            try {
-                await new Promise<void>((resolve, reject) => {
-                    let spinner: ora.Ora;
+                this.exerciseResultsValidation.events.on('step', (_name: string, message: string) => {
+                    spinner = ora({
+                                      text  : message,
+                                      indent: 4
+                                  }).start();
+                });
 
-                    exerciseResultsValidation.events.on('step', (_name: string, message: string) => {
-                        spinner = ora({
-                                          text  : message,
-                                          indent: 4
-                                      }).start();
-                    });
-
-                    exerciseResultsValidation.events.on('endStep', (stepName: string, message: string, error: boolean) => {
-                        if ( error ) {
-                            if ( stepName == 'CHECK_SIZE' ) {
-                                spinner.warn(message);
-                            } else {
-                                spinner.fail(message);
-                            }
+                this.exerciseResultsValidation.events.on('endStep', (stepName: string, message: string, error: boolean) => {
+                    if ( error ) {
+                        if ( stepName === 'CHECK_SIZE' ) {
+                            spinner.warn(message);
                         } else {
-                            spinner.succeed(message);
+                            spinner.fail(message);
                         }
-                    });
-
-                    exerciseResultsValidation.events.on('finished', (success: boolean, exitCode: number) => {
-                        success || exitCode == ExerciseCheckerError.EXERCISE_RESULTS_FOLDER_TOO_BIG ? resolve() : reject();
-                    });
+                    } else {
+                        spinner.succeed(message);
+                    }
+                });
 
-                    exerciseResultsValidation.run();
+                this.exerciseResultsValidation.events.on('finished', (success: boolean, exitCode: number) => {
+                    success || exitCode === ExerciseCheckerError.EXERCISE_RESULTS_FOLDER_TOO_BIG.valueOf() ? resolve() : reject();
                 });
-            } catch ( error ) {
-                this.displayExecutionLogs();
-                return;
-            }
+
+                this.exerciseResultsValidation.run();
+            });
+        } catch ( error ) {
+            this.displayExecutionLogs();
+            throw new Error();
         }
+    }
 
+    /**
+     * Step 4: Display results + Volume location
+     * @private
+     */
+    private displayResults() {
+        const info = chalk.magenta.bold.italic;
+        ClientsSharedExerciseHelper.displayExecutionResults(this.exerciseResultsValidation.exerciseResults, this.exerciseDockerCompose.exitCode, {
+            INFO   : info,
+            SUCCESS: chalk.green,
+            FAILURE: chalk.red
+        }, `\n\n${ info('Execution results folder: ') }${ this.folderResultsVolume }`);
+    }
 
-        // Step 4: Display results + Volume location
-        {
-            const info = chalk.magenta.bold.italic;
-            ClientsSharedExerciseHelper.displayExecutionResults(exerciseResultsValidation.exerciseResults!, exerciseDockerCompose.exitCode, {
-                INFO   : info,
-                SUCCESS: chalk.green,
-                FAILURE: chalk.red
-            }, `\n\n${ info('Execution results folder: ') }${ this.folderResultsVolume }`);
+    async run(): Promise<void> {
+        try {
+            await this.checkRequirements();
+            await this.runDockerCompose();
+            await this.getResults();
+            this.displayResults();
+        } catch ( error ) {
+            return;
         }
     }
 }
 
 
-export default new ExerciseRunHelper();
+export default ExerciseRunHelper;
diff --git a/NodeApp/src/helpers/GlobalHelper.ts b/NodeApp/src/helpers/GlobalHelper.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d14735a8160fde93be2a725420d3532361c05872
--- /dev/null
+++ b/NodeApp/src/helpers/GlobalHelper.ts
@@ -0,0 +1,26 @@
+import { Command, Option } from 'commander';
+import Config              from '../config/Config.js';
+import SessionManager      from '../managers/SessionManager.js';
+
+
+class GlobalHelper {
+    public runCommandDefinition(command: Command) {
+        command
+            .option('-p, --path <value>', 'assignment path', Config.folders.defaultLocalExercise)
+            .option('-v, --verbose', 'verbose mode - display principal container output in live')
+            .addOption(new Option('-w, --super-verbose', 'verbose mode - display all docker compose logs (build included) in live').conflicts('verbose'))
+            .addOption(new Option('--verbose-ssj2').hideHelp().implies({ superVerbose: true }));
+
+        return command;
+    }
+
+
+    public readonly refreshGitlabTokenFunction = async () => {
+        await SessionManager.refreshTokens();
+
+        return SessionManager.gitlabCredentials.accessToken ?? '';
+    };
+}
+
+
+export default new GlobalHelper();
\ No newline at end of file
diff --git a/NodeApp/src/init.ts b/NodeApp/src/init.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d89ce1264824df5db55df6a164cc4d9e35787a0f
--- /dev/null
+++ b/NodeApp/src/init.ts
@@ -0,0 +1,10 @@
+import path         from 'node:path';
+import dotenv       from 'dotenv';
+import dotenvExpand from 'dotenv-expand';
+import './shared/helpers/TypeScriptExtensions.js';
+
+
+dotenvExpand.expand(dotenv.config({
+                                      path      : path.join(__dirname, '../.env'),
+                                      DOTENV_KEY: 'dotenv://:key_fc323d8e0a02349342f1c6a119bb38495958ce3a43a87d19a3f674b7e2896dcb@dotenv.local/vault/.env.vault?environment=development'
+                                  }));
diff --git a/NodeApp/src/managers/DojoBackendManager.ts b/NodeApp/src/managers/DojoBackendManager.ts
index fcd749df9a44d151c202498cb8d5a8dfe5f2df2a..c16b7388e4fede536e61f97fe0e2ba57e410e02b 100644
--- a/NodeApp/src/managers/DojoBackendManager.ts
+++ b/NodeApp/src/managers/DojoBackendManager.ts
@@ -1,26 +1,80 @@
 import axios, { AxiosError } from 'axios';
 import ora                   from 'ora';
-import ApiRoute              from '../sharedByClients/types/Dojo/ApiRoute';
-import { StatusCodes }       from 'http-status-codes';
-import GitlabUser            from '../shared/types/Gitlab/GitlabUser';
-import ClientsSharedConfig   from '../sharedByClients/config/ClientsSharedConfig';
-import Assignment            from '../sharedByClients/models/Assignment';
-import DojoBackendResponse   from '../shared/types/Dojo/DojoBackendResponse';
-import Exercise              from '../sharedByClients/models/Exercise';
-import GitlabToken           from '../shared/types/Gitlab/GitlabToken';
-import User                  from '../sharedByClients/models/User';
-import DojoStatusCode        from '../shared/types/Dojo/DojoStatusCode';
+import ApiRoute              from '../sharedByClients/types/Dojo/ApiRoute.js';
+import ClientsSharedConfig   from '../sharedByClients/config/ClientsSharedConfig.js';
+import Assignment            from '../sharedByClients/models/Assignment.js';
+import DojoBackendResponse   from '../shared/types/Dojo/DojoBackendResponse.js';
+import Exercise              from '../sharedByClients/models/Exercise.js';
+import GitlabToken           from '../shared/types/Gitlab/GitlabToken.js';
+import User                  from '../sharedByClients/models/User.js';
+import DojoStatusCode        from '../shared/types/Dojo/DojoStatusCode.js';
+import * as Gitlab           from '@gitbeaker/rest';
+import DojoBackendHelper     from '../sharedByClients/helpers/Dojo/DojoBackendHelper.js';
+import GitlabPipelineStatus  from '../shared/types/Gitlab/GitlabPipelineStatus.js';
 
 
 class DojoBackendManager {
-    public getApiUrl(route: ApiRoute): string {
-        return `${ ClientsSharedConfig.apiURL }${ route }`;
+    private handleApiError(error: unknown, spinner: ora.Ora, verbose: boolean, defaultErrorMessage?: string, otherErrorHandler?: (error: AxiosError, spinner: ora.Ora, verbose: boolean) => void) {
+        if ( verbose ) {
+            if ( error instanceof AxiosError ) {
+                switch ( error.response?.data?.code ) {
+                    case DojoStatusCode.ASSIGNMENT_EXERCISE_NOT_RELATED:
+                        spinner.fail(`The exercise does not belong to the assignment.`);
+                        break;
+                    case DojoStatusCode.ASSIGNMENT_PUBLISH_NO_PIPELINE:
+                        spinner.fail(`No pipeline found for this assignment.`);
+                        break;
+                    case DojoStatusCode.ASSIGNMENT_PUBLISH_PIPELINE_FAILED:
+                        spinner.fail((error.response?.data?.message as string | undefined) ?? `Last pipeline status is not "${ GitlabPipelineStatus.SUCCESS }".`);
+                        break;
+                    case DojoStatusCode.EXERCISE_CORRECTION_ALREADY_EXIST:
+                        spinner.fail(`This exercise is already labelled as a correction. If you want to update it, please use the update command.`);
+                        break;
+                    case DojoStatusCode.EXERCISE_CORRECTION_NOT_EXIST:
+                        spinner.fail(`The exercise is not labelled as a correction so it's not possible to update it.`);
+                        break;
+                    case DojoStatusCode.ASSIGNMENT_NAME_CONFLICT:
+                        spinner.fail(`Assignment creation error: The assignment name already exists. Please choose another name.`);
+                        break;
+                    case DojoStatusCode.ASSIGNMENT_CREATION_GITLAB_ERROR:
+                        spinner.fail(`Assignment creation error: An unknown error occurred while creating the assignment on GitLab (internal error message: ${ error.response?.data?.description ?? 'unknown error' }). Please try again later or contact an administrator.`);
+                        break;
+                    case DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR:
+                        spinner.fail(`Assignment creation error: An unknown error occurred while creating the assignment (internal error message: ${ error.response?.data?.description ?? 'unknown error' }). Please try again later or contact an administrator.`);
+                        break;
+                    case DojoStatusCode.MAX_EXERCISE_PER_ASSIGNMENT_REACHED:
+                        spinner.fail(`The following users have reached the maximum number of exercise of this assignment : ${ ((error.response.data as DojoBackendResponse<Array<Gitlab.UserSchema>>).data).map(user => user.name).join(', ') }.`);
+                        break;
+                    case DojoStatusCode.EXERCISE_CREATION_INTERNAL_ERROR:
+                        spinner.fail(`Exercise creation error: An unknown error occurred while creating the exercise (internal error message: ${ error.response?.data?.description ?? 'unknown error' }). Please try again later or contact an administrator.`);
+                        break;
+                    case DojoStatusCode.EXERCISE_CREATION_GITLAB_ERROR:
+                        spinner.fail(`Exercise creation error: An unknown error occurred while creating the exercise on GitLab (internal error message: ${ error.response?.data?.description ?? 'unknown error' }). Please try again later or contact an administrator.`);
+                        break;
+                    case DojoStatusCode.GITLAB_TEMPLATE_NOT_FOUND:
+                        spinner.fail(`Template not found or access denied. Please check the template ID or url. Also, please check that the template have public/internal visibility or that your and Dojo account (${ ClientsSharedConfig.gitlab.dojoAccount.username }) have at least reporter role to the template (if private).`);
+                        break;
+                    case DojoStatusCode.GITLAB_TEMPLATE_ACCESS_UNAUTHORIZED:
+                        spinner.fail(`Please check that the template have public/internal visibility or that your and Dojo account (${ ClientsSharedConfig.gitlab.dojoAccount.username }) have at least reporter role to the template (if private).`);
+                        break;
+                    default:
+                        if ( otherErrorHandler ) {
+                            otherErrorHandler(error, spinner, verbose);
+                        } else {
+                            spinner.fail(defaultErrorMessage ?? 'Unknown error');
+                        }
+                        break;
+                }
+            } else {
+                spinner.fail(defaultErrorMessage ?? 'Unknown error');
+            }
+        }
     }
 
 
     public async login(gitlabTokens: GitlabToken): Promise<User | undefined> {
         try {
-            return (await axios.post<DojoBackendResponse<User>>(this.getApiUrl(ApiRoute.LOGIN), {
+            return (await axios.post<DojoBackendResponse<User>>(DojoBackendHelper.getApiUrl(ApiRoute.LOGIN), {
                 accessToken : gitlabTokens.access_token,
                 refreshToken: gitlabTokens.refresh_token
             })).data.data;
@@ -31,7 +85,7 @@ class DojoBackendManager {
 
 
     public async refreshTokens(refreshToken: string): Promise<GitlabToken> {
-        return (await axios.post<DojoBackendResponse<GitlabToken>>(this.getApiUrl(ApiRoute.REFRESH_TOKENS), {
+        return (await axios.post<DojoBackendResponse<GitlabToken>>(DojoBackendHelper.getApiUrl(ApiRoute.REFRESH_TOKENS), {
             refreshToken: refreshToken
         })).data.data;
     }
@@ -39,7 +93,7 @@ class DojoBackendManager {
 
     public async getAssignment(nameOrUrl: string): Promise<Assignment | undefined> {
         try {
-            return (await axios.get<DojoBackendResponse<Assignment>>(this.getApiUrl(ApiRoute.ASSIGNMENT_GET).replace('{{nameOrUrl}}', encodeURIComponent(nameOrUrl)))).data.data;
+            return (await axios.get<DojoBackendResponse<Assignment>>(DojoBackendHelper.getApiUrl(ApiRoute.ASSIGNMENT_GET, { assignmentNameOrUrl: nameOrUrl }))).data.data;
         } catch ( error ) {
             return undefined;
         }
@@ -54,7 +108,7 @@ class DojoBackendManager {
         }
 
         try {
-            await axios.get(this.getApiUrl(ApiRoute.GITLAB_CHECK_TEMPLATE_ACCESS).replace('{{id}}', idOrNamespace));
+            await axios.get(DojoBackendHelper.getApiUrl(ApiRoute.GITLAB_CHECK_TEMPLATE_ACCESS, { gitlabProjectId: idOrNamespace }));
 
             if ( verbose ) {
                 spinner.succeed('Template access granted');
@@ -62,27 +116,13 @@ class DojoBackendManager {
 
             return true;
         } catch ( error ) {
-            if ( verbose ) {
-                if ( error instanceof AxiosError ) {
-                    if ( error.response ) {
-                        if ( error.response.status === StatusCodes.NOT_FOUND ) {
-                            spinner.fail(`Template not found or access denied. Please check the template ID or url. Also, please check that the template have public/internal visibility or that your and Dojo account (${ ClientsSharedConfig.gitlab.dojoAccount.username }) have at least reporter role to the template (if private).`);
-                        } else if ( error.response.status === StatusCodes.UNAUTHORIZED ) {
-                            spinner.fail(`Please check that the template have public/internal visibility or that your and Dojo account (${ ClientsSharedConfig.gitlab.dojoAccount.username }) have at least reporter role to the template (if private).`);
-                        } else {
-                            spinner.fail(`Template error: ${ error.response.statusText }`);
-                        }
-                    }
-                } else {
-                    spinner.fail(`Template error: ${ error }`);
-                }
-            }
+            this.handleApiError(error, spinner, verbose, `Template error: ${ error }`);
 
             return false;
         }
     }
 
-    public async createAssignment(name: string, members: Array<GitlabUser>, templateIdOrNamespace: string | null, verbose: boolean = true): Promise<Assignment> {
+    public async createAssignment(name: string, members: Array<Gitlab.UserSchema>, templateIdOrNamespace: string | null, verbose: boolean = true): Promise<Assignment> {
         const spinner: ora.Ora = ora('Creating assignment...');
 
         if ( verbose ) {
@@ -90,10 +130,10 @@ class DojoBackendManager {
         }
 
         try {
-            const response = await axios.post<DojoBackendResponse<Assignment>>(this.getApiUrl(ApiRoute.ASSIGNMENT_CREATE), Object.assign({
-                                                                                                                                             name   : name,
-                                                                                                                                             members: JSON.stringify(members)
-                                                                                                                                         }, templateIdOrNamespace ? { template: templateIdOrNamespace } : {}));
+            const response = await axios.post<DojoBackendResponse<Assignment>>(DojoBackendHelper.getApiUrl(ApiRoute.ASSIGNMENT_CREATE), Object.assign({
+                                                                                                                                                          name   : name,
+                                                                                                                                                          members: JSON.stringify(members)
+                                                                                                                                                      }, templateIdOrNamespace ? { template: templateIdOrNamespace } : {}));
 
             if ( verbose ) {
                 spinner.succeed(`Assignment successfully created`);
@@ -101,29 +141,13 @@ class DojoBackendManager {
 
             return response.data.data;
         } catch ( error ) {
-            if ( verbose ) {
-                if ( error instanceof AxiosError ) {
-                    if ( error.response ) {
-                        if ( error.response.status === StatusCodes.CONFLICT ) {
-                            spinner.fail(`The assignment name is already used. Please choose another one.`);
-                        } else {
-                            if ( (error.response.data as DojoBackendResponse<unknown>).code === DojoStatusCode.ASSIGNMENT_CREATION_GITLAB_ERROR ) {
-                                spinner.fail(`Assignment creation error: An unknown error occurred while creating the assignment on Gitlab. Please try again later or contact an administrator.`);
-                            } else {
-                                spinner.fail(`Assignment creation error: An unknown error occurred while creating the assignment on Dojo server. Please try again later or contact an administrator.`);
-                            }
-                        }
-                    }
-                } else {
-                    spinner.fail(`Assignment creation error: unknown error`);
-                }
-            }
+            this.handleApiError(error, spinner, verbose, `Assignment creation error: unknown error`);
 
             throw error;
         }
     }
 
-    public async createExercise(assignmentName: string, members: Array<GitlabUser>, verbose: boolean = true): Promise<Exercise> {
+    public async createExercise(assignmentName: string, members: Array<Gitlab.UserSchema>, verbose: boolean = true): Promise<Exercise> {
         const spinner: ora.Ora = ora('Creating exercise...');
 
         if ( verbose ) {
@@ -131,7 +155,7 @@ class DojoBackendManager {
         }
 
         try {
-            const response = await axios.post<DojoBackendResponse<Exercise>>(this.getApiUrl(ApiRoute.EXERCISE_CREATE).replace('{{nameOrUrl}}', encodeURIComponent(assignmentName)), { members: JSON.stringify(members) });
+            const response = await axios.post<DojoBackendResponse<Exercise>>(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_CREATE, { assignmentNameOrUrl: assignmentName }), { members: JSON.stringify(members) });
 
             if ( verbose ) {
                 spinner.succeed(`Exercise successfully created`);
@@ -139,27 +163,7 @@ class DojoBackendManager {
 
             return response.data.data;
         } catch ( error ) {
-            if ( verbose ) {
-                if ( error instanceof AxiosError ) {
-                    if ( error.response ) {
-                        if ( error.response.status === StatusCodes.INSUFFICIENT_SPACE_ON_RESOURCE ) {
-                            if ( error.response.data && (error.response.data as DojoBackendResponse<Array<GitlabUser>>).code === DojoStatusCode.MAX_EXERCISE_PER_ASSIGNMENT_REACHED ) {
-                                spinner.fail(`The following users have reached the maximum number of exercise of this assignment : ${ ((error.response.data as DojoBackendResponse<Array<GitlabUser>>).data as Array<GitlabUser>).map(user => user.name).join(', ') }.`);
-                            } else {
-                                spinner.fail(`You've already reached the max number of exercise of this assignment.`);
-                            }
-                        } else {
-                            if ( (error.response.data as DojoBackendResponse<unknown>).code === DojoStatusCode.EXERCISE_CREATION_GITLAB_ERROR ) {
-                                spinner.fail(`Exercise creation error: An unknown error occurred while creating the exercise on Gitlab. Please try again later or contact an administrator.`);
-                            } else {
-                                spinner.fail(`Exercise creation error: An unknown error occurred while creating the exercise on Dojo server. Please try again later or contact an administrator.`);
-                            }
-                        }
-                    }
-                } else {
-                    spinner.fail(`Exercise creation error: unknown error`);
-                }
-            }
+            this.handleApiError(error, spinner, verbose, `Exercise creation error: unknown error`);
 
             throw error;
         }
@@ -173,7 +177,7 @@ class DojoBackendManager {
         }
 
         try {
-            await axios.patch<DojoBackendResponse<null>>(this.getApiUrl(publish ? ApiRoute.ASSIGNMENT_PUBLISH : ApiRoute.ASSIGNMENT_UNPUBLISH).replace('{{nameOrUrl}}', encodeURIComponent(assignment.name)), {});
+            await axios.patch<DojoBackendResponse<null>>(DojoBackendHelper.getApiUrl(publish ? ApiRoute.ASSIGNMENT_PUBLISH : ApiRoute.ASSIGNMENT_UNPUBLISH, { assignmentNameOrUrl: assignment.name }), {});
 
             if ( verbose ) {
                 spinner.succeed(`Assignment ${ assignment.name } successfully ${ publish ? 'published' : 'unpublished' }`);
@@ -181,13 +185,7 @@ class DojoBackendManager {
 
             return;
         } catch ( error ) {
-            if ( verbose ) {
-                if ( error instanceof AxiosError && error.response ) {
-                    spinner.fail(`Assignment visibility change error: ${ error.response.statusText }`);
-                } else {
-                    spinner.fail(`Assignment visibility change error: unknown error`);
-                }
-            }
+            this.handleApiError(error, spinner, verbose, `Assignment visibility change error: ${ error }`);
 
             throw error;
         }
@@ -201,12 +199,15 @@ class DojoBackendManager {
         }
 
         try {
-            const axiosFunction = isUpdate ? axios.patch : axios.post;
+            const axiosFunction = isUpdate ? axios.patch.bind(axios) : axios.post.bind(axios);
             const route = isUpdate ? ApiRoute.ASSIGNMENT_CORRECTION_UPDATE : ApiRoute.ASSIGNMENT_CORRECTION_LINK;
 
-            await axiosFunction(this.getApiUrl(route).replace('{{assignmentNameOrUrl}}', encodeURIComponent(assignment.name)).replace('{{exerciseIdOrUrl}}', encodeURIComponent(exerciseIdOrUrl)), {
-                exerciseIdOrUrl: exerciseIdOrUrl
-            });
+            await axiosFunction(DojoBackendHelper.getApiUrl(route, {
+                assignmentNameOrUrl: assignment.name,
+                exerciseIdOrUrl    : exerciseIdOrUrl
+            }), {
+                                    exerciseIdOrUrl: exerciseIdOrUrl
+                                });
 
             if ( verbose ) {
                 spinner.succeed(`Correction ${ isUpdate ? 'updated' : 'linked' }`);
@@ -214,23 +215,7 @@ class DojoBackendManager {
 
             return true;
         } catch ( error ) {
-            if ( verbose ) {
-                if ( error instanceof AxiosError ) {
-                    if ( error.response?.data ) {
-                        if ( error.response.data.code === DojoStatusCode.ASSIGNMENT_EXERCISE_NOT_RELATED ) {
-                            spinner.fail(`The exercise does not belong to the assignment.`);
-                        } else if ( error.response.data.code === DojoStatusCode.EXERCISE_CORRECTION_ALREADY_EXIST ) {
-                            spinner.fail(`This exercise is already labelled as a correction. If you want to update it, please use the update command.`);
-                        } else if ( error.response.data.code === DojoStatusCode.EXERCISE_CORRECTION_NOT_EXIST ) {
-                            spinner.fail(`The exercise is not labelled as a correction so it's not possible to update it.`);
-                        }
-                    } else {
-                        spinner.fail(`Correction ${ isUpdate ? 'update' : 'link' } error: ${ error.response?.statusText }`);
-                    }
-                } else {
-                    spinner.fail(`Correction ${ isUpdate ? 'update' : 'link' } error: ${ error }`);
-                }
-            }
+            this.handleApiError(error, spinner, verbose, `Correction ${ isUpdate ? 'update' : 'link' } error: ${ error }`);
 
             return false;
         }
diff --git a/NodeApp/src/managers/GitlabManager.ts b/NodeApp/src/managers/GitlabManager.ts
index 356d9e26d61f1e55a591be9e6c811271c9ef8323..0e6c92e364f91e6cfe1be20508acd20dd2aee73b 100644
--- a/NodeApp/src/managers/GitlabManager.ts
+++ b/NodeApp/src/managers/GitlabManager.ts
@@ -1,16 +1,18 @@
-import axios            from 'axios';
-import ora              from 'ora';
-import GitlabUser       from '../shared/types/Gitlab/GitlabUser';
-import GitlabRoute      from '../shared/types/Gitlab/GitlabRoute';
-import SharedConfig     from '../shared/config/SharedConfig';
-import GitlabRepository from '../shared/types/Gitlab/GitlabRepository';
-import fs               from 'fs-extra';
-import { spawn }        from 'child_process';
-
-
-class GitlabManager {
-    private getApiUrl(route: GitlabRoute): string {
-        return `${ SharedConfig.gitlab.apiURL }${ route }`;
+import ora                                       from 'ora';
+import fs                                        from 'fs-extra';
+import { spawn }                                 from 'child_process';
+import { NotificationSettingSchema, UserSchema } from '@gitbeaker/rest';
+import * as GitlabCore                           from '@gitbeaker/core';
+import SharedGitlabManager                       from '../shared/managers/SharedGitlabManager.js';
+import GlobalHelper                              from '../helpers/GlobalHelper.js';
+
+
+type getGitlabUser = (param: number | string) => Promise<UserSchema | undefined>
+
+
+class GitlabManager extends SharedGitlabManager {
+    constructor() {
+        super('', GlobalHelper.refreshGitlabTokenFunction.bind(GlobalHelper));
     }
 
     public async testToken(verbose: boolean = true): Promise<[ boolean, boolean ]> {
@@ -35,7 +37,7 @@ class GitlabManager {
             }
 
             try {
-                notificationSettings = (await this.getNotificationSettings()).data as NotificationSettings;
+                notificationSettings = await this.getNotificationSettings();
 
                 result[0] = true;
 
@@ -63,7 +65,7 @@ class GitlabManager {
 
             try {
                 const oldSettings = notificationSettings;
-                const newSettings = { level: someLevelTypes[someLevelTypes[0] == oldSettings.level ? 1 : 0] };
+                const newSettings = { level: someLevelTypes[someLevelTypes[0] === oldSettings.level ? 1 : 0] };
 
                 await this.setNotificationSettings(newSettings);
                 await this.setNotificationSettings(oldSettings);
@@ -83,15 +85,15 @@ class GitlabManager {
         return result;
     }
 
-    public getNotificationSettings() {
-        return axios.get(this.getApiUrl(GitlabRoute.NOTIFICATION_SETTINGS));
+    public getNotificationSettings(): Promise<NotificationSettingSchema> {
+        return this.executeGitlabRequest(() => this.api.NotificationSettings.show());
     }
 
-    public setNotificationSettings(newSettings: Record<string, string>) {
-        return axios.put(this.getApiUrl(GitlabRoute.NOTIFICATION_SETTINGS), { params: new URLSearchParams(newSettings) });
+    public setNotificationSettings(newSettings: GitlabCore.EditNotificationSettingsOptions) {
+        return this.executeGitlabRequest(() => this.api.NotificationSettings.edit(newSettings));
     }
 
-    private async getGitlabUsers(paramsToSearch: Array<string | number>, paramName: string, verbose: boolean = false, verboseIndent: number = 0): Promise<Array<GitlabUser | undefined>> {
+    private async getGitlabUsers(paramsToSearch: Array<string | number>, searchFunction: getGitlabUser, verbose: boolean = false, verboseIndent: number = 0): Promise<Array<UserSchema | undefined>> {
         try {
             return await Promise.all(paramsToSearch.map(async param => {
                 const spinner: ora.Ora = ora({
@@ -101,21 +103,20 @@ class GitlabManager {
                 if ( verbose ) {
                     spinner.start();
                 }
-                const params: { [key: string]: unknown } = {};
-                params[paramName] = param;
-                const user = await axios.get<Array<GitlabUser>>(this.getApiUrl(GitlabRoute.USERS_GET), { params: params });
 
-                if ( user.data[0] ) {
-                    const gitlabUser = user.data[0];
+                const user = await searchFunction(param);
 
+                if ( user ) {
                     if ( verbose ) {
-                        spinner.succeed(`${ gitlabUser.username } (${ gitlabUser.id })`);
+                        spinner.succeed(`${ user.username } (${ user.id })`);
                     }
-                    return gitlabUser;
+                    return user;
                 } else {
                     if ( verbose ) {
                         spinner.fail(`${ param }`);
                     }
+
+                    return undefined;
                 }
             }));
         } catch ( e ) {
@@ -123,30 +124,26 @@ class GitlabManager {
         }
     }
 
-    public async getUsersById(ids: Array<number>, verbose: boolean = false, verboseIndent: number = 0): Promise<Array<GitlabUser | undefined>> {
-        return await this.getGitlabUsers(ids, 'id', verbose, verboseIndent);
+    public async getUsersById(ids: Array<number>, verbose: boolean = false, verboseIndent: number = 0): Promise<Array<UserSchema | undefined>> {
+        return this.getGitlabUsers(ids, this.getUserById.bind(this) as getGitlabUser, verbose, verboseIndent);
     }
 
-    public async getUsersByUsername(usernames: Array<string>, verbose: boolean = false, verboseIndent: number = 0): Promise<Array<GitlabUser | undefined>> {
-        return await this.getGitlabUsers(usernames, 'search', verbose, verboseIndent);
+    public async getUsersByUsername(usernames: Array<string>, verbose: boolean = false, verboseIndent: number = 0): Promise<Array<UserSchema | undefined>> {
+        return this.getGitlabUsers(usernames, this.getUserByUsername.bind(this) as getGitlabUser, verbose, verboseIndent);
     }
 
-    public async getRepository(repoId: number): Promise<GitlabRepository> {
-        return await axios.get(this.getApiUrl(GitlabRoute.REPOSITORY_GET).replace('{{id}}', repoId.toString()));
-    }
-
-    public async fetchMembers(options: { members_id?: Array<number>, members_username?: Array<string> }): Promise<Array<GitlabUser> | false> {
+    public async fetchMembers(options: { members_id?: Array<number>, members_username?: Array<string> }): Promise<Array<UserSchema> | undefined> {
         if ( options.members_id || options.members_username ) {
             ora('Checking Gitlab members:').start().info();
         }
 
-        let members: Array<GitlabUser> = [];
+        let members: Array<UserSchema> = [];
 
-        async function getMembers<T>(context: unknown, functionName: string, paramsToSearch: Array<T>): Promise<boolean> {
-            const result = await ((context as { [functionName: string]: (arg: Array<T>, verbose: boolean, verboseIndent: number) => Promise<Array<GitlabUser | undefined>> })[functionName])(paramsToSearch, true, 8);
+        async function isUsersExists<T>(context: unknown, functionName: string, paramsToSearch: Array<T>): Promise<boolean> {
+            const users = await ((context as { [functionName: string]: (arg: Array<T>, verbose: boolean, verboseIndent: number) => Promise<Array<UserSchema | undefined>> })[functionName])(paramsToSearch, true, 8);
 
-            if ( result.every(user => user) ) {
-                members = members.concat(result as Array<GitlabUser>);
+            if ( users.every(user => user) ) {
+                members = members.concat(users as Array<UserSchema>);
                 return true;
             } else {
                 return false;
@@ -161,7 +158,7 @@ class GitlabManager {
                     indent: 4
                 }).start().info();
 
-            result = await getMembers(this, 'getUsersById', options.members_id);
+            result = await isUsersExists(this, 'getUsersById', options.members_id);
         }
 
         if ( options.members_username ) {
@@ -170,16 +167,10 @@ class GitlabManager {
                     indent: 4
                 }).start().info();
 
-            result = result && await getMembers(this, 'getUsersByUsername', options.members_username);
+            result = result && await isUsersExists(this, 'getUsersByUsername', options.members_username);
         }
 
-        if ( !result ) {
-            return false;
-        }
-
-        members = members.removeObjectDuplicates(gitlabUser => gitlabUser.id);
-
-        return members;
+        return result ? members.removeObjectDuplicates(gitlabUser => gitlabUser.id) : undefined;
     }
 
     public async cloneRepository(clonePath: string | boolean, repositorySshUrl: string, folderName?: string, verbose: boolean = false, verboseIndent: number = 0) {
@@ -200,13 +191,13 @@ class GitlabManager {
 
         try {
             await new Promise<void>((resolve, reject) => {
-                const gitClone = spawn(`git clone ${ repositorySshUrl } "${ folderName ?? '' }"`, {
+                const gitClone = spawn(`git clone ${ repositorySshUrl } "${ folderName?.replace(' ', '_') ?? '' }"`, {
                     cwd  : path,
                     shell: true
                 });
 
-                gitClone.on('exit', (code) => {
-                    code !== null && code == 0 ? resolve() : reject();
+                gitClone.on('exit', code => {
+                    code !== null && code === 0 ? resolve() : reject();
                 });
             });
 
diff --git a/NodeApp/src/managers/HttpManager.ts b/NodeApp/src/managers/HttpManager.ts
index 6c75099ca1b1d5bc86f33336502b7d29cad4be93..760f19c1f1f0b4d12089bd6f9badd84e68eb424d 100644
--- a/NodeApp/src/managers/HttpManager.ts
+++ b/NodeApp/src/managers/HttpManager.ts
@@ -1,15 +1,14 @@
 import axios, { AxiosError, AxiosRequestHeaders } from 'axios';
-import SessionManager                             from './SessionManager';
+import SessionManager                             from './SessionManager.js';
 import FormData                                   from 'form-data';
 import { StatusCodes }                            from 'http-status-codes';
-import ClientsSharedConfig                        from '../sharedByClients/config/ClientsSharedConfig';
-import { version }                                from '../config/Version';
-import DojoBackendResponse                        from '../shared/types/Dojo/DojoBackendResponse';
-import DojoStatusCode                             from '../shared/types/Dojo/DojoStatusCode';
+import ClientsSharedConfig                        from '../sharedByClients/config/ClientsSharedConfig.js';
+import { version }                                from '../config/Version.js';
+import DojoBackendResponse                        from '../shared/types/Dojo/DojoBackendResponse.js';
+import DojoStatusCode                             from '../shared/types/Dojo/DojoStatusCode.js';
 import boxen                                      from 'boxen';
-import Config                                     from '../config/Config';
-import SharedConfig                               from '../shared/config/SharedConfig';
-import { stateConfigFile }                        from '../config/ConfigFiles';
+import { stateConfigFile }                        from '../config/ConfigFiles.js';
+import TextStyle                                  from '../types/TextStyle.js';
 
 
 class HttpManager {
@@ -21,16 +20,17 @@ class HttpManager {
     }
 
     private registerRequestInterceptor() {
-        axios.interceptors.request.use((config) => {
-
+        axios.interceptors.request.use(config => {
             if ( config.data instanceof FormData ) {
-                config.headers = { ...config.headers, ...(config.data as FormData).getHeaders() } as AxiosRequestHeaders;
+                config.headers = { ...config.headers, ...config.data.getHeaders() } as AxiosRequestHeaders;
             }
 
             if ( config.url && (config.url.indexOf(ClientsSharedConfig.apiURL) !== -1) ) {
+
+
                 config.headers['Accept-Encoding'] = 'gzip';
 
-                if ( config.data && Object.keys(config.data).length > 0 ) {
+                if ( config.data && Object.keys(config.data as { [key: string]: unknown }).length > 0 ) {
                     config.headers['Content-Type'] = 'multipart/form-data';
                 }
 
@@ -42,10 +42,6 @@ class HttpManager {
                 config.headers['client-version'] = version;
             }
 
-            if ( SessionManager.gitlabCredentials.accessToken && config.url && config.url.indexOf(SharedConfig.gitlab.apiURL) !== -1 ) {
-                config.headers.Authorization = `Bearer ${ SessionManager.gitlabCredentials.accessToken }`;
-            }
-
             return config;
         });
     }
@@ -63,8 +59,47 @@ class HttpManager {
         process.exit(1);
     }
 
+    private apiMethodNotAllowed(error: AxiosError, isFromApi: boolean) {
+        if ( error.response?.status === StatusCodes.METHOD_NOT_ALLOWED && isFromApi && error.response.data ) {
+            const data: DojoBackendResponse<void> = error.response.data as DojoBackendResponse<void>;
+
+            switch ( data.code ) {
+                case DojoStatusCode.CLIENT_NOT_SUPPORTED.valueOf():
+                    this.requestError('Client not recognized by the server. Please contact the administrator.');
+                    break;
+                case DojoStatusCode.CLIENT_VERSION_NOT_SUPPORTED.valueOf():
+                    this.requestError(`CLI version not anymore supported by the server. Please update the CLI by executing this command:\n${ TextStyle.CODE(' dojo upgrade ') }`);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private apiAuthorizationError(error: AxiosError, isFromApi: boolean) {
+        if ( this.handleAuthorizationCommandErrors && isFromApi && error.response ) {
+            const errorCustomCode = (error.response.data as DojoBackendResponse<unknown> | undefined)?.code ?? error.response.status;
+
+            if ( errorCustomCode === error.response.status ) {
+                switch ( error.response.status ) {
+                    case StatusCodes.UNAUTHORIZED.valueOf():
+                        this.requestError('Session expired or does not exist. Please login again.');
+                        break;
+                    case StatusCodes.FORBIDDEN.valueOf():
+                        this.requestError('Forbidden access.');
+                        break;
+                    default:
+                        this.requestError('Unknown error.');
+                        break;
+                }
+            }
+        } else {
+            this.handleAuthorizationCommandErrors = true;
+        }
+    }
+
     private registerResponseInterceptor() {
-        axios.interceptors.response.use((response) => {
+        axios.interceptors.response.use(response => {
             if ( response.data && response.data.sessionToken ) {
                 SessionManager.apiToken = response.data.sessionToken;
             }
@@ -80,61 +115,12 @@ class HttpManager {
             }
 
             return response;
-        }, async (error) => {
-            if ( error.response ) {
-                const originalConfig = error.config;
-
-                const isFromApi = error.response.config.url && error.response.config.url.indexOf(ClientsSharedConfig.apiURL) !== -1;
-                const isFromGitlab = error.response.config.url && error.response.config.url.indexOf(SharedConfig.gitlab.URL) !== -1;
-
-                // Try to refresh the Gitlab tokens if the request have failed with a 401 error
-                if ( error.response.status === StatusCodes.UNAUTHORIZED && isFromGitlab && !originalConfig._retry ) {
-                    originalConfig._retry = true;
-
-                    try {
-                        await SessionManager.refreshTokens();
-
-                        return axios(originalConfig);
-                    } catch ( error: unknown ) {
-                        if ( error instanceof AxiosError ) {
-                            if ( error.response && error.response.data ) {
-                                return Promise.reject(error.response.data);
-                            }
-                        }
-
-                        return Promise.reject(error);
-                    }
-                }
-
-                if ( error.response.status === StatusCodes.METHOD_NOT_ALLOWED && isFromApi && error.response.data ) {
-                    const data: DojoBackendResponse<void> = error.response.data;
-
-                    switch ( data.code ) {
-                        case DojoStatusCode.CLIENT_NOT_SUPPORTED:
-                            this.requestError('Client not recognized by the server. Please contact the administrator.');
-                            break;
-                        case DojoStatusCode.CLIENT_VERSION_NOT_SUPPORTED:
-                            this.requestError(`CLI version not anymore supported by the server. Please update the CLI.\nYou can download the latest stable version on this page:\n${ Config.gitlab.cliReleasePage }`);
-                            break;
-                        default:
-                            break;
-                    }
-                }
+        }, async error => {
+            if ( error instanceof AxiosError && error.response ) {
+                const isFromApi = error.response.config.url !== undefined && error.response.config.url.indexOf(ClientsSharedConfig.apiURL) !== -1;
 
-                if ( this.handleAuthorizationCommandErrors ) {
-                    if ( isFromApi ) {
-                        switch ( error.response.status ) {
-                            case StatusCodes.UNAUTHORIZED:
-                                this.requestError('Session expired or does not exist. Please login again.');
-                                break;
-                            case StatusCodes.FORBIDDEN:
-                                this.requestError('Forbidden access.');
-                                break;
-                        }
-                    }
-                } else {
-                    this.handleAuthorizationCommandErrors = true;
-                }
+                this.apiMethodNotAllowed(error, isFromApi);
+                this.apiAuthorizationError(error, isFromApi);
             } else {
                 this.requestError('Error connecting to the server. Please check your internet connection. If the problem persists, please contact the administrator.');
             }
diff --git a/NodeApp/src/managers/SessionManager.ts b/NodeApp/src/managers/SessionManager.ts
index b5175f8598955b32b9a1d2a4b0404e716e422926..2b4a3da164b0419c08adac66ae5fe61a8b587f44 100644
--- a/NodeApp/src/managers/SessionManager.ts
+++ b/NodeApp/src/managers/SessionManager.ts
@@ -1,36 +1,38 @@
-import * as jwt                  from 'jsonwebtoken';
-import User                      from '../sharedByClients/models/User';
-import LocalConfigKeys           from '../types/LocalConfigKeys';
-import axios, { HttpStatusCode } from 'axios';
-import HttpManager               from './HttpManager';
-import ora                       from 'ora';
-import Permissions               from '../types/Permissions';
-import ApiRoute                  from '../sharedByClients/types/Dojo/ApiRoute';
-import DojoBackendManager        from './DojoBackendManager';
-import Config                    from '../config/Config';
-import ClientsSharedConfig       from '../sharedByClients/config/ClientsSharedConfig';
-import DojoGitlabCredentials     from '../sharedByClients/types/Dojo/DojoGitlabCredentials';
-import * as http                 from 'http';
-import EventEmitter              from 'events';
-import SharedConfig              from '../shared/config/SharedConfig';
-import chalk                     from 'chalk';
-import inquirer                  from 'inquirer';
-import SharedGitlabManager       from '../shared/managers/SharedGitlabManager';
-import GitlabManager             from './GitlabManager';
-import GitlabToken               from '../shared/types/Gitlab/GitlabToken';
-import open                      from 'open';
-import { sessionConfigFile }     from '../config/ConfigFiles';
+import * as jwt              from 'jsonwebtoken';
+import User                  from '../sharedByClients/models/User.js';
+import LocalConfigKeys       from '../types/LocalConfigKeys.js';
+import axios                 from 'axios';
+import HttpManager           from './HttpManager.js';
+import ora                   from 'ora';
+import Permissions           from '../types/Permissions.js';
+import ApiRoute              from '../sharedByClients/types/Dojo/ApiRoute.js';
+import DojoBackendManager    from './DojoBackendManager.js';
+import Config                from '../config/Config.js';
+import ClientsSharedConfig   from '../sharedByClients/config/ClientsSharedConfig.js';
+import DojoGitlabCredentials from '../sharedByClients/types/Dojo/DojoGitlabCredentials.js';
+import * as http             from 'http';
+import EventEmitter          from 'events';
+import SharedConfig          from '../shared/config/SharedConfig.js';
+import chalk                 from 'chalk';
+import inquirer              from 'inquirer';
+import GitlabToken           from '../shared/types/Gitlab/GitlabToken.js';
+import open                  from 'open';
+import { sessionConfigFile } from '../config/ConfigFiles.js';
+import TextStyle             from '../types/TextStyle.js';
+import DojoBackendHelper     from '../sharedByClients/helpers/Dojo/DojoBackendHelper.js';
+import GitlabManager         from './GitlabManager.js';
+import { StatusCodes }       from 'http-status-codes';
 
 
 class LoginServer {
     readonly events: EventEmitter = new EventEmitter();
-    private server: http.Server;
+    private readonly server: http.Server;
 
     constructor() {
         this.server = http.createServer((req, res) => {
             const sendError = (error: string) => {
                 this.events.emit('error', error);
-                res.writeHead(HttpStatusCode.InternalServerError, { 'Content-Type': 'text/html' });
+                res.writeHead(StatusCodes.INTERNAL_SERVER_ERROR, { 'Content-Type': 'text/html' });
                 res.write(`<html lang="en"><body><h1 style="color: red">DojoCLI login error</h1><h3>Please look at your CLI for more informations.</h3></body></html>`);
                 res.end();
             };
@@ -38,19 +40,16 @@ class LoginServer {
             if ( req.url?.match(Config.login.server.route) ) {
                 const urlParts = req.url.split('=');
                 if ( urlParts.length > 0 ) {
-                    res.writeHead(HttpStatusCode.Ok, { 'Content-Type': 'text/html' });
+                    res.writeHead(StatusCodes.OK, { 'Content-Type': 'text/html' });
                     res.write(`<html lang="en"><body><h1 style="color: green">DojoCLI login successful</h1><h3>You can close this window.</h3></body></html>`);
                     res.end();
-                    
+
                     this.events.emit('code', urlParts[1]);
                     return;
                 }
 
                 sendError(`Incorrect call => ${ req.url }`);
-                return;
             }
-
-            //sendError(`Unknown route call => ${ req.url }`);
         });
     }
 
@@ -117,23 +116,25 @@ class SessionManager {
 
     set gitlabCredentials(credentials: DojoGitlabCredentials) {
         sessionConfigFile.setParam(LocalConfigKeys.GITLAB, credentials);
-    }
 
-    constructor() { }
+        if ( credentials.accessToken ) {
+            GitlabManager.setToken(credentials.accessToken);
+        }
+    }
 
     private async getGitlabCodeFromHeadlessEnvironment(): Promise<string> {
         const indent: string = '    ';
         console.log(`${ indent }Please open the following URL in your web browser and accept to give the requested permissions to Dojo:`);
-        console.log(chalk.blue(`${ indent }${ Config.login.gitlab.url.code }`));
+        console.log(TextStyle.URL(`${ indent }${ Config.login.gitlab.url.code }`));
         console.log(`${ indent }Then, copy the code at the end of the redirected url and paste it bellow.`);
-        console.log(`${ indent }Example of url (here the code is 123456): ${ chalk.blue(`${ SharedConfig.login.gitlab.url.redirect }?code=`) }${ chalk.green('123456') }`);
+        console.log(`${ indent }Example of url (here the code is 123456): ${ TextStyle.URL(SharedConfig.login.gitlab.url.redirect + '?code=') }${ chalk.green('123456') }`);
         return (await inquirer.prompt({
                                           type   : 'password',
                                           name   : 'code',
                                           message: `${ chalk.green('?') } Please paste the Gitlab code here`,
                                           mask   : '*',
                                           prefix : '   '
-                                      })).code;
+                                      })).code as string;
     }
 
     private getGitlabCodeFromGraphicEnvironment(): Promise<string> {
@@ -155,7 +156,7 @@ class SessionManager {
                                          text  : `Waiting for user to authorize the application in his web browser. If the browser does not open automatically, please go to : ${ Config.login.gitlab.url.code }`,
                                          indent: 4
                                      }).start();
-                open(Config.login.gitlab.url.code).then();
+                void open(Config.login.gitlab.url.code).then();
             });
             loginServer.events.on('error', (error: string) => {
                 currentSpinner.fail(`Login server error: ${ error }`);
@@ -208,7 +209,7 @@ class SessionManager {
                                         }).start();
         let gitlabTokens: GitlabToken;
         try {
-            gitlabTokens = await SharedGitlabManager.getTokens(gitlabCode);
+            gitlabTokens = await GitlabManager.getTokens(gitlabCode);
             this.gitlabCredentials = {
                 refreshToken: gitlabTokens.refresh_token,
                 accessToken : gitlabTokens.access_token
@@ -219,7 +220,7 @@ class SessionManager {
             throw error;
         }
 
-        const isGitlabTokensValid = (await GitlabManager.testToken()).every((value) => value);
+        const isGitlabTokensValid = (await GitlabManager.testToken()).every(value => value);
         if ( !isGitlabTokensValid ) {
             throw new Error('Gitlab tokens are invalid');
         }
@@ -258,25 +259,25 @@ class SessionManager {
         };
     }
 
-    checkPermissions(verbose: boolean = true, indent: number = 8, checkPermissions: Array<string> | null = []): Permissions {
-        const hasPermission = (permissionPredicate: () => boolean, verboseText: string): boolean => {
-            const isAllowed: boolean = this.profile !== undefined && permissionPredicate();
+    private hasPermission(permissionPredicate: () => boolean, verbose: boolean, verboseText: string, indent: number): boolean {
+        const isAllowed: boolean = this.profile !== undefined && permissionPredicate();
 
-            if ( verbose ) {
-                const spinner: ora.Ora = ora({
-                                                 text  : verboseText,
-                                                 indent: indent
-                                             }).start();
-                isAllowed ? spinner.succeed() : spinner.fail();
-            }
+        if ( verbose ) {
+            const spinner: ora.Ora = ora({
+                                             text  : verboseText,
+                                             indent: indent
+                                         }).start();
+            isAllowed ? spinner.succeed() : spinner.fail();
+        }
 
-            return isAllowed;
-        };
+        return isAllowed;
+    }
 
+    checkPermissions(verbose: boolean = true, indent: number = 8, checkPermissions: Array<string> | null = []): Permissions {
         return {
-            student      : checkPermissions && (checkPermissions.length == 0 || checkPermissions.includes('student')) ? hasPermission(() => true, 'Student permissions') : false,
-            teachingStaff: checkPermissions && (checkPermissions.length == 0 || checkPermissions.includes('teachingStaff')) ? hasPermission(() => this.profile?.isTeachingStaff ?? false, 'Teaching staff permissions') : false,
-            admin        : checkPermissions && (checkPermissions.length == 0 || checkPermissions.includes('admin')) ? hasPermission(() => this.profile?.isAdmin ?? false, 'Admin permissions') : false
+            student      : checkPermissions && (checkPermissions.length === 0 || checkPermissions.includes('student')) ? this.hasPermission(() => true, verbose, 'Student permissions', indent) : false,
+            teachingStaff: checkPermissions && (checkPermissions.length === 0 || checkPermissions.includes('teachingStaff')) ? this.hasPermission(() => this.profile?.isTeachingStaff ?? false, verbose, 'Teaching staff permissions', indent) : false,
+            admin        : checkPermissions && (checkPermissions.length === 0 || checkPermissions.includes('admin')) ? this.hasPermission(() => this.profile?.isAdmin ?? false, verbose, 'Admin permissions', indent) : false
         };
     }
 
@@ -297,7 +298,7 @@ class SessionManager {
         }
 
         try {
-            await axios.get(DojoBackendManager.getApiUrl(ApiRoute.TEST_SESSION), {});
+            await axios.get(DojoBackendHelper.getApiUrl(ApiRoute.TEST_SESSION), {});
 
             if ( verbose ) {
                 spinner.succeed(`The session is valid`);
@@ -310,6 +311,13 @@ class SessionManager {
             return false;
         }
 
+        if ( checkPermissions && checkPermissions.length === 0 ) {
+            ora({
+                    text  : `Here is your permissions:`,
+                    indent: 4
+                }).start().info();
+        }
+
         return this.checkPermissions(verbose, 8, checkPermissions);
     }
 }
diff --git a/NodeApp/src/shared b/NodeApp/src/shared
index 9e3f29d2f313ef96944a199da0db39f1827c496a..771f8cd079b39ec4050c5ece024dc4d70f342529 160000
--- a/NodeApp/src/shared
+++ b/NodeApp/src/shared
@@ -1 +1 @@
-Subproject commit 9e3f29d2f313ef96944a199da0db39f1827c496a
+Subproject commit 771f8cd079b39ec4050c5ece024dc4d70f342529
diff --git a/NodeApp/src/sharedByClients b/NodeApp/src/sharedByClients
index 4efff1c5127c6f84104016d7041d0cf281d981f8..8514d5ef589a8aa34e4d2260c618781d81368c22 160000
--- a/NodeApp/src/sharedByClients
+++ b/NodeApp/src/sharedByClients
@@ -1 +1 @@
-Subproject commit 4efff1c5127c6f84104016d7041d0cf281d981f8
+Subproject commit 8514d5ef589a8aa34e4d2260c618781d81368c22
diff --git a/NodeApp/src/types/TextStyle.ts b/NodeApp/src/types/TextStyle.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e3f82ea36ea530bd395a1282dc3100f370d5a7e3
--- /dev/null
+++ b/NodeApp/src/types/TextStyle.ts
@@ -0,0 +1,13 @@
+import chalk from 'chalk';
+
+
+class TextStyle {
+    public readonly BLOCK = chalk.cyan;
+    public readonly CODE = chalk.bgHex('F7F7F7').grey.italic;
+    public readonly LIST_ITEM_NAME = chalk.magenta;
+    public readonly URL = chalk.blue.underline;
+    public readonly WARNING = chalk.red;
+}
+
+
+export default new TextStyle();
\ No newline at end of file
diff --git a/NodeApp/tsconfig.json b/NodeApp/tsconfig.json
index 6dd8b7ef56110c1be2c0ce6d891878d406ba1acf..bdd2d43b6302727fc871a6dfccac57243b628b7f 100644
--- a/NodeApp/tsconfig.json
+++ b/NodeApp/tsconfig.json
@@ -1,16 +1,26 @@
 {
     "compilerOptions": {
-        "rootDir"         : "src",
-        "outDir"          : "dist",
-        "strict"          : true,
-        "target"          : "ES2022",
-        "module"          : "commonjs",
-        "sourceMap"       : true,
-        "esModuleInterop" : true,
-        "moduleResolution": "node",
-        "noImplicitAny"   : true
+        "rootDir"        : "src",
+        "outDir"         : "dist",
+        "strict"         : true,
+        "target"         : "ES2022",
+        "module"         : "commonjs",
+        "sourceMap"      : true,
+        "noImplicitAny"  : true,
+        "esModuleInterop": true,
+        "lib"            : [
+            "ES2022",
+            "DOM"
+        ],
+        "types"          : [
+            "node"
+        ]
     },
     "exclude"        : [
         "node_modules"
+    ],
+    "include"        : [
+        "src/**/*.ts",
+        "eslint.config.mjs"
     ]
 }
\ No newline at end of file
diff --git a/README.md b/README.md
index 025b53d8ede4c01f2a1815af55a990e87633cf5b..d4426ceedfbd3e32fcea61c72fee93ad8475de71 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
-# DojoCLI
+# Documentation of `The Dojo CLI` utility
 
-More informations about the DojoCLI can be found in the [wiki](https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/wikis/home).
\ No newline at end of file
+All documentations are available on the [Dojo documentation website](https://www.hepiapp.ch/) : https://www.hepiapp.ch/.
\ No newline at end of file
diff --git a/Wiki/0-Dojo-presentation.md b/Wiki/0-Dojo-presentation.md
deleted file mode 100644
index 6b57243843561093f1e74debe2fcfc426fb7ba16..0000000000000000000000000000000000000000
--- a/Wiki/0-Dojo-presentation.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# Dojo: a platform to practice programming
-
-The dojo platform is an online tool built to help practice programming by
-allowing users to propose assignments and perform them as exercises.
-
-The tool is very flexible and allows for proposing exercises for any language
-and does not impose any limitation on a framework to be heavily relying
-on Docker and Gitlab. These tools used in combination allow for automatic
-correction of assignments in order to give immediate feedback to users
-performing exercises. Solved exercises can then be shared among the community
-of users such that they can inspire other users or give hints on ways to solve
-a given exercise.
-
-The two major concepts of the platform are the **assignments** and the **exercises**.
-
-The principal way to interact with the Dojo platform is currently the `dojo` CLI.
-
-## The assignment
-
-An assignment is written by a user that wants to propose an exercise. It is typically composed of a written description of the work to be performed,
-and tests that must be passed once the exercise is successfully performed (and some configuration files for the infrastructure of the tests
-such as docker files). At its core, an assignment is
-nothing else than a git repository that can be forked in the form of an exercise and modified using standard git commands.
-For a more detailed description please see the [CLI documentation](home).
-An assignment can be proposed by any user.
-
-In the future a dependency tree of assignments can be created, as well as tagging for filtering purposes.
-
-## The exercise
-
-An exercise is an instance of an assignment which the learner will modify in order to make it pass the automatic tests.
-It can be run locally on any user's machine using the dojo CLI. When the exercise is completed
-it is pushed on the dojo where the CI/CD tools of Gitlab can evaluate it automatically and
-notify the dojo platform of the result. The exercises can then be shared with other users
-in order to propose a wide variety of solutions and can be a base for discussion among users
-and with teachers.
-For a more detailed description please see the [CLI documentation](home).
\ No newline at end of file
diff --git a/Wiki/Development/1-How-to-setup-your-development-environment.md b/Wiki/Development/1-How-to-setup-your-development-environment.md
deleted file mode 100644
index 47a876164e87e7f6a17827ed854a0c1484f5e40a..0000000000000000000000000000000000000000
--- a/Wiki/Development/1-How-to-setup-your-development-environment.md
+++ /dev/null
@@ -1,94 +0,0 @@
-# How to setup your development environment
-
-## Introduction
-
-This tutorial describes how to setup your development environment for building the Dojo CLI by detailing the prerequisites and dependencies needed.
-
-## Technologies
-
-The cli is built using [NodeJS](https://nodejs.org/en/) and [NPM](https://www.npmjs.com/).
-
-The programming language used is [Typescript](https://www.typescriptlang.org/) v5.
-
-
-## Prerequisites
-
-In order to build the cli you will need the following tools:
-- [NodeJS](https://nodejs.org/en/) (version 18 or higher)
-- [NPM](https://www.npmjs.com/) (version 10 or higher)
-
-Install NodeJS and NPM by following the instructions on the [official website](https://nodejs.org/en/download/package-manager).
-Or via Node Version Manager (NVM) by following the instructions on the [official website](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
-
-
-## Dependencies
-
-The CLI is packaged using [pkg](https://www.npmjs.com/package/pkg). 
-This means that all the dependencies are bundled in the final binary.
-
-Here are the main dependencies used by the cli (you don't need to install them manually or globally on your system):
-- [Axios](https://www.npmjs.com/package/axios): a promise-based HTTP client for the browser and Node.js. It is
-used to make HTTP(S) requests to the Dojo backend and Gitlab.
-- [Boxen](https://www.npmjs.com/package/boxen): used to display messages in a box in the terminal.
-- [Chalk](https://www.npmjs.com/package/chalk): used to add colors to the messages in the terminal.
-- [Commander.js](https://www.npmjs.com/package/commander): a library to write command-line interfaces.
-- [Dotenv](https://www.npmjs.com/package/dotenv): used to load environment variables from a .env file.
-- [Dotenv-vault](https://www.npmjs.com/package/dotenv-vault): a CLI to sync .env files across machines, 
-environments, and team members.
-- [Inquirer](https://www.npmjs.com/package/inquirer): used to ask perform to the interactive command line user interfaces.
-- [JsonWebToken](https://www.npmjs.com/package/jsonwebtoken): used to generate and validate [JSON Web Tokens](https://jwt.io/).
-- [ora](https://www.npmjs.com/package/ora): used to display elegantly in the terminal.
-- [zod](https://www.npmjs.com/package/zod): a TypeScript-first schema validation with static type inference. Used
-in the projet to validate json files created by the user.
-
-
-## Installation
-
-First of all, you need to clone the repository:
-```bash
-$ git clone --recurse-submodule ssh://git@ssh.hesge.ch:10572/dojo_project/projects/ui/dojocli.git
-```
-
-Then, you need to move to the project's directory:
-```bash
-$ cd NodeApp
-```
-
-To install the dependencies listed above you can use the following command in the base directory of the project:
-```bash
-$ npm install
-```
-
-
-## Environment variables
-
-Environment variables are used to store sensitive information such as API keys, passwords, etc.
-They are also used to store configuration information that can be changed without modifying the code.
-
-You can decrypt env var stored in the `.env.vault` file with the following commands in the project's main folder:
-```bash
-> npx dotenv-vault local keys
- environment DOTENV_KEY
- ─────────── ─────────────────────────────────────────────────────────────────────────
- development dotenv://:key_1234@dotenv.local/vault/.env.vault?environment=development
-
-Set DOTENV_KEY on your server
-
-> npx dotenv-vault local decrypt dotenv://:key_1234@dotenv.local/vault/.env.vault?environment=development > .env.development
-```
-
-**The `.env.keys` file have to be requested to the project maintainer: [MichaΓ«l Minelli](mailto:dojo@minelli.me).**
-
-
-## Run the cli
-
-To run the cli (in dev mode) you can use the following command in the base directory of the project:
-```bash
-$ npm run start:dev -- COMMAND
-```
-Where `COMMAND` is the command you want to run.
-
-For example, if you want to test the exercise creation command you can use the following command:
-```bash
-$ npm run start:dev -- exercise create -a "Technique de compilation - TP" --members_username michael.minelli
-```
\ No newline at end of file
diff --git a/Wiki/Development/2-How-to-add-a-new-command.md b/Wiki/Development/2-How-to-add-a-new-command.md
deleted file mode 100644
index 8548351bc0d7d45d5f0f8dcb4d8ad48fa7d6dabb..0000000000000000000000000000000000000000
--- a/Wiki/Development/2-How-to-add-a-new-command.md
+++ /dev/null
@@ -1,402 +0,0 @@
-# How to add a new command to the Dojo cli
-
-## Introduction
-
-This tutorial describes how to add a new command to the Dojo cli. For that we take an existing command and describe
-his implementation. This command allow a member of the teaching staff of an assignment to publish it.
-
-The command is named `publish` and is a subcommand of the `assignment` command. Here is the command structure:
-```bash
-dojo assignment publish [options] <assignment_name_or_url>
-```
-
-This tutorial is linked to another one which explains how to add a new route to the Dojo backend.
-The documentation for this is available on the [Dojo backend wiki](https://gitedu.hesge.ch/dojo/backend/-/wikis/Development/1-How-to-add-a-new-route).
-
-For the rest of this tutorial we will assume that we are in the `NodeApp` folder :
-```bash
-cd NodeApp
-```
-
-
-## Prerequisites
-
-All the prerequisites are described in
-[How to setup your development environment](1-How-to-setup-your-development-environment) tutorial.
-
-
-## Commands files structure
-
-The commands files are located in the `src/commander` folder. All command files are named with the following pattern:
-`commandPathCommand.ts` where `commandPath` has to be replaced by the path of the command (command, subcommand,
-subsubcommand, etc.).
-
-For each command there are two choices:
-1. If it's a command that will contains subcommands, you will need to create a folder with the name of the command.
-   - In this folder you will need to create a file with the name of the command.
-   - A subfolder named `subcommands` will be needed to store the subcommands files or folders.
-2. If it's a command that will not contains subcommands, you will need to create a file with the name of the command.
-
-### Apply to our use case
-In our case we will create a command that will be a subcommand of the `assignment` command, so its file path will be :
-`src/commander/assignment/subcommands/AssignmentPublishCommand.ts`.
-
-
-## Command class inheritance
-
-All commands must inherit from the `CommanderCommand` abstract class. This class is located in the
-`src/commander/CommanderCommand.ts` file.
-
-When you inherit from this class you will need to implement the following methods and properties :
-- `commandName: string`: This property will be used to define the name of the (sub)command.
-- `defineCommand(): void`: This method will be used to define the command itself.
-- `commandAction(...args: Array<unknown>): Promise<void>`: This method will be used to define the action of the command.
-  You will need to link the command to the action by writing this code in the defineCommand method:
-```typescript
-this.command.action(this.commandAction.bind(this));
-```
-
-Optionally, you can implement the `defineSubCommands(): void` method if you want to declare some subcommands.
-Here is an exemple of implementation:
-```typescript
-protected defineSubCommands() {
-  SessionLoginCommand.registerOnCommand(this.command);
-  SessionLogoutCommand.registerOnCommand(this.command);
-  SessionTestCommand.registerOnCommand(this.command);
-}
-```
-
-### Apply to our use case
-
-Now, the `src/commander/assignment/subcommands/AssignmentPublishCommand.ts` file will look like this:
-```typescript
-import CommanderCommand from '../../CommanderCommand';
-
-class AssignmentPublishCommand extends CommanderCommand {
-  protected commandName: string = 'publish';
-
-  protected defineCommand() {
-  ...
-  }
-
-  protected async commandAction(assignmentNameOrUrl: string, options: { force: boolean }): Promise<void> {
-  ...
-  }
-}
-
-export default new AssignmentPublishCommand();
-```
-
-
-## Define the command
-
-In the `defineCommand()` method you will need to define the command itself. To do this you will need to use the
-Commander.js library. The documentation is available [here](https://github.com/tj/commander.js).
-
-### Apply to our use case
-
-We want to define the `publish` command that take the name of the assignment (or his url) as an argument and have a
-`--force` option that will allow the user to publish the assignment without having to confirm the action.
-
-Here is the code to add in the `defineCommand()` method:
-```typescript
-protected defineCommand() {
-  this.command
-    .description('publish an assignment')
-    .argument('<name or url>', 'name or url (http/s or ssh) of the assignment')
-    .option('-f, --force', 'don\'t ask for confirmation')
-    .action(this.commandAction.bind(this));
-}
-```
-
-
-## Define the action
-
-To define the action we must adapt the `commandAction()` method by adding arguments and options defined in the command
-definition.
-
-And then we will need to implement the action of the command.
-
-### Apply to our use case
-
-In our case we will need to adapt the `commandAction()` method like this:
-```typescript
-protected async commandAction(assignmentNameOrUrl: string, options: { force: boolean }): Promise<void> {
-  ...
-}
-```
-
-For the implementation we will split our code into several steps:
-1. Options parsing and confirmation asking
-2. Checking / Retrieving data
-   1. Test the session validity
-   2. Retrieve the assignment and check if it exists
-   3. Check if the user is in the teaching staff of the assignment
-   4. Check if the assignment is publishable by getting the last pipeline status
-3. Publishing the assignment
-
-#### 1. Options parsing and confirmation asking
-
-First we need to parse the options and check if the `--force` option is present. If it's not present we ask
-the user to confirm the action.
-
-```typescript
-if ( !options.force ) {
-  options.force = (await inquirer.prompt({
-    type   : 'confirm',
-    name   : 'force',
-    message: 'Are you sure you want to publish this assignment?'
-  })).force;
-}
-
-if ( !options.force ) {
-  return;
-}
-```
-
-#### 2. Checking / Retrieving data
-
-##### 2.1. Test the session validity
-
-We call the SessionManager singleton that contain a function to test the session validity.
-It checks only the Dojo backend session validity and not the Gitlab one.
-
-```typescript
-if ( !await SessionManager.testSession(true, null) ) {
-  return;
-}
-```
-
-##### 2.2. Retrieve the assignment and check if it exists
-
-We call the DojoBackendManager singleton that contains a function to retrieve an assignment by his name or his url.
-This function make a request to the Dojo backend to retrieve the assignment.
-
-```typescript
-assignment = await DojoBackendManager.getAssignment(assignmentNameOrUrl);
-if ( !assignment ) {
-  return;
-}
-```
-
-##### 2.3. Check if the user is in the teaching staff of the assignment
-
-We check if the user is in the teaching staff of the assignment by verifying if his id is in the staff array.
-
-```typescript
-if ( !assignment.staff.some(staff => staff.id === SessionManager.profile?.id) ) {
-  return;
-}
-```
-
-##### 2.4. Check if the assignment is publishable by getting the last pipeline status
-
-We call the SharedAssignmentHelper singleton that contains a function to check if the assignment is publishable
-(an assignment is publishable only if the last pipeline status is `success`).
-This function make a request to the Gitlab API to retrieve the last pipeline of the assignment and return an object
-with the informations of it including a `isPublishable` state that is true only if the status of the pipeline is
-`success`.
-
-```typescript
-const isPublishable = await SharedAssignmentHelper.isPublishable(assignment.gitlabId);
-if ( !isPublishable.isPublishable ) {
-  return;
-}
-```
-
-#### 3. Publishing the assignment
-
-Finally, we will call the DojoBackendManager singleton that contain a function to publish an assignment as will be described hereafter.
-
-First of all, we need to implement the route on the backend. For that, please refer to the linked tutorial on the
-[Dojo backend API wiki](https://gitedu.hesge.ch/dojo/backend/-/wikis/Development/1-How-to-add-a-new-route).
-
-Then, we need to create a function that will call the newly created route. This function needs to know the route
-that we have previously created. We will store this route in the `src/sharedByClients/types/Dojo/ApiRoutes.ts` file.
-
-```typescript
-export enum ApiRoute {
-  ...
-  ASSIGNMENT_PUBLISH = '/assignment/{{nameOrUrl}}/publish',
-  ASSIGNMENT_UNPUBLISH = '/assignment/{{nameOrUrl}}/unpublish',
-  ...
-}
-```
-
-Then, we can create the function that will be located in the backend manager singleton stored in the
-`src/managers/DojoBackendManager.ts` file.
-
-_Note:_ We use the `axios` library to make the request to the backend. We do not need to fill authorization headers
-because the `axios interceptors` defined in the `src/managers/HttpManager.ts` file will do it for us.
-
-```typescript
-public async changeAssignmentPublishedStatus(assignment: Assignment, publish: boolean, verbose: boolean = true) {
-  try {
-     await axios.patch<DojoBackendResponse<null>>(this.getApiUrl(publish ? ApiRoute.ASSIGNMENT_PUBLISH : ApiRoute.ASSIGNMENT_UNPUBLISH).replace('{{nameOrUrl}}', encodeURIComponent(assignment.name)), {});
-  } catch ( error ) {
-     ...
-     throw error;
-  }
-}
-```
-
-Finally, we go back to the command file and we call the function to publish the assignment.
-
-```typescript
-try {
-  await DojoBackendManager.changeAssignmentPublishedStatus(assignment, true);
-} catch ( error ) {
-  return;
-}
-```
-
-
-## Use case: final code
-
-In the examples above we have seen all the different steps to implement our `publish` subcommand.
-For a better readability all the UI code (done with ora library) have been removed from the snippets.
-
-Here is the final code of the `src/commander/assignment/subcommands/AssignmentPublishCommand.ts` file with integration
-of ora calls for the interface.
-
-### AssignmentPublishCommand.ts
-```typescript
-import CommanderCommand     from '../../CommanderCommand';
-import inquirer          from 'inquirer';
-import Assignment         from '../../../sharedByClients/models/Assignment';
-import chalk            from 'chalk';
-import SessionManager      from '../../../managers/SessionManager';
-import ora              from 'ora';
-import DojoBackendManager    from '../../../managers/DojoBackendManager';
-import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper';
-
-
-class AssignmentPublishCommand extends CommanderCommand {
-  protected commandName: string = 'publish';
-
-  protected defineCommand() {
-    this.command
-      .description('publish an assignment')
-      .argument('<name or url>', 'name or url (http/s or ssh) of the assignment')
-      .option('-f, --force', 'don\'t ask for confirmation')
-      .action(this.commandAction.bind(this));
-  }
-
-  protected async commandAction(assignmentNameOrUrl: string, options: { force: boolean }): Promise<void> {
-    if ( !options.force ) {
-      options.force = (await inquirer.prompt({
-        type  : 'confirm',
-        name  : 'force',
-        message: 'Are you sure you want to publish this assignment?'
-      })).force;
-    }
-
-    if ( !options.force ) {
-      return;
-    }
-
-    let assignment!: Assignment | undefined;
-
-    {
-      console.log(chalk.cyan('Please wait while we verify and retrieve data...'));
-
-      if ( !await SessionManager.testSession(true, null) ) {
-        return;
-      }
-
-      ora('Checking assignment:').start().info();
-      ora({
-        text  : assignmentNameOrUrl,
-        indent: 4
-      }).start().info();
-      const assignmentGetSpinner: ora.Ora = ora({
-        text  : 'Checking if assignment exists',
-        indent: 8
-      }).start();
-      assignment = await DojoBackendManager.getAssignment(assignmentNameOrUrl);
-      if ( !assignment ) {
-        assignmentGetSpinner.fail(`The assignment doesn't exists`);
-        return;
-      }
-      assignmentGetSpinner.succeed(`The assignment exists`);
-
-
-      const assignmentCheckAccessSpinner: ora.Ora = ora({
-        text  : 'Checking accesses',
-        indent: 8
-      }).start();
-      if ( !assignment.staff.some(staff => staff.id === SessionManager.profile?.id) ) {
-        assignmentCheckAccessSpinner.fail(`You are not in the staff of this assignment`);
-        return;
-      }
-      assignmentCheckAccessSpinner.succeed(`You are in the staff of this assignment`);
-
-
-      const assignmentIsPublishable: ora.Ora = ora({
-        text  : 'Checking if the assignment is publishable',
-        indent: 8
-      }).start();
-      const isPublishable = await SharedAssignmentHelper.isPublishable(assignment.gitlabId);
-      if ( !isPublishable.isPublishable ) {
-        assignmentIsPublishable.fail(`The assignment is not publishable: ${ isPublishable.status?.message }`);
-        return;
-      }
-      assignmentIsPublishable.succeed(`The assignment is publishable`);
-    }
-
-    {
-      console.log(chalk.cyan(`Please wait while we publish the assignment...`));
-
-      try {
-        await DojoBackendManager.changeAssignmentPublishedStatus(assignment, true);
-      } catch ( error ) {
-        return;
-      }
-    }
-  }
-}
-
-
-export default new AssignmentPublishCommand();
-```
-
-### ApiRoutes.ts
-```typescript
-export enum ApiRoute {
-  ...
-  ASSIGNMENT_PUBLISH = '/assignment/{{nameOrUrl}}/publish',
-  ASSIGNMENT_UNPUBLISH = '/assignment/{{nameOrUrl}}/unpublish',
-  ...
-}
-```
-
-### DojoBackendManager.ts
-```typescript
-public async changeAssignmentPublishedStatus(assignment: Assignment, publish: boolean, verbose: boolean = true) {
-  const spinner: ora.Ora = ora('Changing published status...');
-
-  if ( verbose ) {
-    spinner.start();
-  }
-
-  try {
-    await axios.patch<DojoBackendResponse<null>>(this.getApiUrl(publish ? ApiRoute.ASSIGNMENT_PUBLISH : ApiRoute.ASSIGNMENT_UNPUBLISH).replace('{{nameOrUrl}}', encodeURIComponent(assignment.name)), {});
-
-    if ( verbose ) {
-      spinner.succeed(`Assignment ${ assignment.name } successfully ${ publish ? 'published' : 'unpublished' }`);
-    }
-
-    return;
-  } catch ( error ) {
-    if ( verbose ) {
-      if ( error instanceof AxiosError && error.response ) {
-        spinner.fail(`Assignment visibility change error: ${ error.response.statusText }`);
-      } else {
-        spinner.fail(`Assignment visibility change error: unknown error`);
-      }
-    }
-
-    throw error;
-  }
-}
-```
\ No newline at end of file
diff --git a/Wiki/Tutorials/0-Exercise-perform.md b/Wiki/Tutorials/0-Exercise-perform.md
deleted file mode 100644
index e2ca1b442ba52137db823f1bd62b38b6a31ab1c0..0000000000000000000000000000000000000000
--- a/Wiki/Tutorials/0-Exercise-perform.md
+++ /dev/null
@@ -1,154 +0,0 @@
-# How to perform an exercise
-
-In this tutorial we quickly explain the workflow for performing an exercise by a student (or a professor).
-The exercise is based on the `c_hello_world` assignment created in [assignment creation](1-Assignment-creation) tutorial.
-
-## Exercise "creation"
-
-To perform an exercise the student must first create the exercise. Under the hood, this operation consist in making 
-a fork of a published assignment in the student's namespace.
-
-This is performed by the following `dojo` command:
-```bash
-$ dojo exercise create --assignment c_hello_world
-```
-```console
-Please wait while we verify and retrieve data...
-β„Ή Checking Dojo session: 
-    βœ” The session is valid
-        βœ” Student permissions
-β„Ή Checking Gitlab token: 
-    βœ” Read access
-    βœ” Write access
-β„Ή Checking assignment:
-    βœ” Assignment "c_hello_world" exists
-    βœ” Assignment "c_hello_world" is published
-Please wait while we are creating the exercise...
-βœ” Exercise successfully created
-    β„Ή Id: 8d3f53a0-0d32-4455-a251-e1a1c5a97c6a
-    β„Ή Name: DojoEx - c_hello_world - orestis.malaspin - 1
-    β„Ή Web URL: https://gitedu.hesge.ch/dojo/exercise/dojo-ex_c_hello_world_8d3f53a0-0d32-4455-a251-e1a1c5a97c6a
-    β„Ή HTTP Repo: https://gitedu.hesge.ch/dojo/exercise/dojo-ex_c_hello_world_8d3f53a0-0d32-4455-a251-e1a1c5a97c6a.git
-    β„Ή SSH Repo: ssh://git@ssh.hesge.ch:10572/dojo/exercise/dojo-ex_c_hello_world_8d3f53a0-0d32-4455-a251-e1a1c5a97c6a.git
-```
-
-## To perform the exercise
-
-The exercise is nothing else than a git repository so the workflow is pretty straightforward.
-
-1. Clone the repository (see the repo link above)
-```bash
-$ git clone ssh://git@ssh.hesge.ch:10572/dojo/exercise/dojo-ex_c_hello_world_8d3f53a0-0d32-4455-a251-e1a1c5a97c6a.git
-```
-```console
-Cloning into 'dojo-ex_c_hello_world_8d3f53a0-0d32-4455-a251-e1a1c5a97c6a'...
-remote: Enumerating objects: 33, done.
-remote: Counting objects: 100% (33/33), done.
-remote: Compressing objects: 100% (26/26), done.
-remote: Total 33 (delta 6), reused 16 (delta 3), pack-reused 0
-Receiving objects: 100% (33/33), 7.32 KiB | 7.32 MiB/s, done.
-Resolving deltas: 100% (6/6), done.
-```
-2. Read the `README.md` file to understand what is expected by the teacher.
-3. Modify/create the appropriate code.
-4. (Optional but recommended) Execute the pipeline locally
-```bash
-$ dojo exercise run
-```
-```console
-Please wait while we are checking and creating dependencies...
-    β„Ή Checking exercise content:
-        βœ” The exercise folder contains all the needed files
-        βœ” The dojo_assignment.json file is valid
-    βœ” The Docker deamon is running
-Please wait while we are running the exercise...
-    βœ” Docker Compose file run successfully
-    βœ” Linked services logs acquired
-    βœ” Containers stopped and removed
-Please wait while we are checking the results...
-    βœ” Results file found
-    βœ” Results file is valid
-    βœ” Results folder size is in bounds
-
-   ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Results ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
-   ┃                                                                                                    ┃
-   ┃   Global result : ❌ Failure                                                                       ┃
-   ┃                                                                                                    ┃
-   ┃   Execution exit code : 0                                                                          ┃
-   ┃                                                                                                    ┃
-   ┃   Execution results folder : /home/student/DojoExecutions/dojo_execLogs_2023-08-30T18_58_05_764Z   ┃
-   ┃                                                                                                    ┃
-   ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
-```
-Currently the exercise is a failure which is sad but expected since there was no modification in any file.
-One can see that we have access to a `results` folder in `/home/student/DojoExecutions/dojo_execLogs_2023-08-30T18_58_05_764Z`.
-This directory contains the output of the pipeline execution
-```console
-/home/student/DojoExecutions/dojo_execLogs_2023-08-30T18_58_05_764Z
-β”œβ”€β”€ Dojo
-β”‚   β”œβ”€β”€ dockerComposeLogs.txt
-β”‚   └── results.json
-└── Exercise
-    └── diff_output.txt
-
-2 directories, 3 files
-```
-In particular one can find the output the teacher wanted us (students) to see in the `Exercise` folder.
-It contains any output file that can be used for debugging for example. In this case it contains
-```console
-< ./hello_world
-< (null)
-\ No newline at end of file
----
-> Hello world!
-\ No newline at end of file
-```
-One can see that there is an `Hello world!` expected and that no `Hello world!` was provided by our program.
-One can also see the complete
-logs of the `docker compose` command in `Dojo/dockerComposeLogs.txt` which may (or may not)
-provide additional informations.
-
-In order to complete this assignment we have to modify the `src/function.c` file such that it becomes
-```c
-#include "function.h"
-
-char *hello_world()
-{
-    return "Hello world!";
-}
-```
-Rerunning the pipeline now yields
-```bash
-$ dojo exercise run
-```
-```console
-Please wait while we are checking and creating dependencies...
-    β„Ή Checking exercise content:
-        βœ” The exercise folder contains all the needed files
-        βœ” The dojo_assignment.json file is valid
-    βœ” The Docker deamon is running
-Please wait while we are running the exercise...
-    βœ” Docker Compose file run successfully
-    βœ” Linked services logs acquired
-    βœ” Containers stopped and removed
-Please wait while we are checking the results...
-    βœ” Results file found
-    βœ” Results file is valid
-    βœ” Results folder size is in bounds
-
-   ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Results ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
-   ┃                                                                                                    ┃
-   ┃   Global result : βœ… Success                                                                       ┃
-   ┃                                                                                                    ┃
-   ┃   Execution exit code : 0                                                                          ┃
-   ┃                                                                                                    ┃
-   ┃   Execution results folder : /home/student/DojoExecutions/dojo_execLogs_2023-08-30T19_20_25_104Z   ┃
-   ┃                                                                                                    ┃
-   ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
-```
-5. Now that we are happy with the state of our exercise, we can commit and push our modifications and
-the exercise will be run through the Gitlab CI pipeline and one can see the "official" result
-of our exercise.
-![The Gitlab CI pipeline succeeded!](pipeline.png)
-It is not necessary to have a successful pipeline to commit and push the code. We can do it at any time
-which is particularly useful when doing long assignments that take many iterations to finish.
diff --git a/Wiki/Tutorials/1-Assignment-creation.md b/Wiki/Tutorials/1-Assignment-creation.md
deleted file mode 100644
index 848d3b9e3f9be09a460bc3b69e7a55b47ad320d7..0000000000000000000000000000000000000000
--- a/Wiki/Tutorials/1-Assignment-creation.md
+++ /dev/null
@@ -1,469 +0,0 @@
-# How to create an assignment
-
-We will describe how to create a very simple assignment with dojo.
-
-Here we will build a `Hello world!` in the C programming language. We will describe how one must
-modify the default template to have an exercise.
-
-The exercise will be to fill a function to return the `Hello world!` string.
-The output of the function will be printed on the standard output.
-The structure will be provided to the students as well as a very simple `Makefile`
-to compile and run the code.
-
-The success or failure of this assignment will be tested by comparing the output
-of our program with an the expected output (the famous `Hello world!`).
-
-To build this exercise we need to perform several steps that will be described below in great details.
-
-## Empty assignment creation
-
-First create a new assignment with the command
-```bash
-$ dojo assignment create --name c_hello_world
-```
-```console
-Please wait while we verify and retrieve data...
-β„Ή Checking Dojo session: 
-    βœ” The session is valid
-        βœ” Teaching staff permissions
-β„Ή Checking Gitlab token: 
-    βœ” Read access
-    βœ” Write access
-βœ” Assignment name "c_hello_world" is available
-Please wait while we are creating the assignment...
-βœ” Assignment successfully created
-    β„Ή Name: c_hello_world
-    β„Ή Web URL: https://gitedu.hesge.ch/dojo/assignment/c_hello_world
-    β„Ή HTTP Repo: https://gitedu.hesge.ch/dojo/assignment/c_hello_world.git
-    β„Ή SSH Repo: ssh://git@ssh.hesge.ch:10572/dojo/assignment/c_hello_world.git
-```
-
-## Clone the assignment locally
-
-The assignment is nothing more than a git repository. We just clone it and start
-modifying it
-```bash
-$ git clone ssh://git@ssh.hesge.ch:10572/dojo/assignment/c_hello_world.git
-```
-```console
-Cloning into 'c_hello_world'...
-remote: Enumerating objects: 18, done.
-remote: Counting objects: 100% (18/18), done.
-remote: Compressing objects: 100% (12/12), done.
-remote: Total 18 (delta 3), reused 18 (delta 3), pack-reused 0
-Receiving objects: 100% (18/18), 4.37 KiB | 4.37 MiB/s, done.
-Resolving deltas: 100% (3/3), done.
-```
-The `c_hello_world` assignment has now the following structure
-```bash
-$ tree c_hello_world/
-```
-```console
-c_hello_world/
-β”œβ”€β”€ docker-compose.yml
-β”œβ”€β”€ Dockerfile
-β”œβ”€β”€ dojo.assignment
-└── README.md
-```
-
-## Build the development environment
-
-In order to execute a C program we need a working compiler and `make`. In order to
-produce a special result file to be parsed by the `dojo` tool to produce nicely formatted output
-we will use `jq`, a command-line json creation/edition/reding tool. Our `Dockerfile`
-contains
-```dockerfile
-FROM ubuntu:latest
-RUN apt update && apt install gcc make jq -y
-COPY . /
-ENTRYPOINT ["./run.sh"]
-```
-We see that we start by installing the required packages on top of the latest Ubuntu image.
-We then proceed to copy the complete assignment into the docker container, finall we run
-a yet to be created `run.sh` script.
-
-We can test our `Dockerfile` by running the command
-```bash
-$ docker compose run --build hello_world
-```
-```console
-[+] Building 20.5s (8/8) FINISHED                                 
- => [hello_world internal] load build definition from Dockerfile
- => => transferring dockerfile: 134B
- => [hello_world internal] load .dockerignore
- => => transferring context: 2B
- => [hello_world internal] load metadata for docker.io/library/ubuntu:latest
- => CACHED [hello_world 1/3] FROM docker.io/library/ubuntu:latest@sha256:ec050c32e4a6085b423d36ecd025c0d3ff00c38ab93a3d71a460ff1c44fa6d77
- => [hello_world internal] load build context
- => => transferring context: 39.87kB
- => [hello_world 2/3] RUN apt update && apt install gcc make jq -y
- => [hello_world 3/3] COPY . /
- => [hello_world] exporting to image
- => => exporting layers
- => => writing image sha256:4b561113c7123da08206a2cf2642cb4f331670fe44350646437eaa78e44aff3a
- => => naming to docker.io/library/c_hello_world-hello_world
-ERRO[0021] error waiting for container:                                   
-Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "./run.sh": stat ./run.sh: no such file or directory: unknown
-```
-We see an error at the end of the command which is perfectly normal. We did not create the `run.sh` file et (let's leave that for later).
-
-We then need to focus on the `docker-compose.yml` file which will take care of all the hard work
-needed if complex workflows are required. Here it is as simple as possible and should contain
-```yml
-services:
-    hello_world:
-        container_name: hello_world
-        build:
-            context: ./
-            dockerfile: Dockerfile
-        volumes:
-            - hello_world_volume:/result # <hello_world_volume> must be the same as below but
-                                         # the name may be arbitrary. The volume is optional but 
-                                         # you can provide it for show details of the execution like
-                                         # tests passed or not or simply any log file you think that 
-                                         # can be useful for the students. If it's present, this volume 
-                                         # must be present in the dojo_assignment.json file under the field
-                                         # "result": {
-                                         #   "volume": "hello_world_volume", 
-                                         #    ...
-                                         # }
-volumes:
-    hello_world_volume:
-```
-In this file, we see the definition of a `hello_world_volume` this is an arbitrary name and can be changed but it
-must be coherent in this file and in the `dojo_assignment.json` file (more on this later configuration file in [The dojo configuration file](#the-dojo-configuration-file)).
-This (optional) volume is responsible of mounting `/result/` directory which will contain all
-the output generated by our assignment (again the name can be changed). In particular
-in this director he will be required to create a `dojo_assignment.json` file
-that contains at least if the assignment was successfully performed (more on that at the end of the [Creating the assignment files](#creating-the-assignment-files) section).
-
-## The dojo configuration file
-
-The `dojo_assignment.json` file contains the general configuration of the assignment. 
-The configuration parameters are:
-- the `dojoAssignmentVersion` which define the version of the dojo assignment file (actually only version 1 is available),
-- the `version` of you assignment,
-- the `immutable` field which are files or directories that will be overwritten when the compilation pipeline is run (even if the student
-modifies these files the modifications will not be taken into account). Each immutable is defined by:
-  - `path` **(required)**: the path of the immutable file or directory,
-  - `description` _(optional)_: provides a description of the immutable for the students or Dojo interface,
-  - `isDirectory` _(optional)_: `true` if the immutable is a directory, `false` otherwise (default),
-- the `results` which provide information to the Dojo for finding results of the execution. The *result* field is defined by:
-  - `container` **(required)**: the name of the service in the docker compose file that will be run (his dependencies will be run automatically). In our case it is `hello_world`,
-  - `volume` _(optional)_: this field (`hello_world_volume`) corresponds to the `volumes` field in the `docker-compose.yml` file.
-
-In its default form `dojo_assignment.json` file contains
-```json
-{
-  "dojoAssignmentVersion": 1,
-  "version": 1,
-  "immutable": [
-    {
-      "description": "Dockerfile of the unique container",
-      "path": "Dockerfile",
-      "isDirectory": false
-    }
-  ],
-  "result": {
-    "container": "hello_world",
-    "volume": "hello_world_volume"
-  }
-}
-```
-
-This file will be completed in [The immutable files](#the-immutable-files) section with the files of our assignment that will be
-created in the [next section](#creating-the-assignment-files).
-
-## Creating the assignment files
-
-For this assignment we will create the following files with the following content:
-
-- `src/Makefile`
-```makefile
-CC:=gcc
-CFLAGS:=-Wall -Wextra -Wpedantic -fsanitize=address  -Werror -g
-LDFLAGS:=-fsanitize=address
-
-hello_world: hello_world.o function.o
-	gcc -o $@ $^ $(LDFLAGS)
-
-hello_world.o: function.h
-
-function.o: function.h
-
-run: hello_world
-	./hello_world
-
-clean: 
-	rm -f *.o hello_world
-```
-- `src/hello_world.c`
-```c
-#include <stdio.h>
-#include <stdlib.h>
-#include "function.h"
-
-int main()
-{
-    printf("%s", hello_world());
-    return EXIT_SUCCESS;
-}
-```
-- `src/function.h`
-```c
-#ifndef _FUNCTION_H_
-#define _FUNCTION_H_
-
-char *hello_world();
-
-#endif
-```
-- `src/function.c`
-```c
-#include "function.h"
-
-char *hello_world()
-{
-    // TODO: Replace 0 here to return "Hello world!"
-    return 0;
-}
-```
-- `src/expected_output.txt`
-```
-Hello world!
-```
-
-These files will be used to create an executable that will be run by the custom execution script, `run.sh`, see the `Dockerfile` in
-the [Build the environment](#build-the-development-environment) section.
-
-All these files have arbitrary names and it's completely up to the teacher to make a coherent exercise.
-
-The only missing file is `run.sh` (do not forget to make it executable, `chmod +x run.sh`) that contains the following code
-```bash
-#!/bin/bash
-
-echo "Starting tests."
-
-GLOBAL_SUCCESS=false
-
-make -C src clean -s
-make -C src -s
-if [ $? -eq 0 ]; then
-    make run -C src > src/output.txt -s
-    if [ $? -eq 0 ]; then
-        diff --color src/output.txt src/expected_output.txt > result/diff_output.txt
-        if [ $? -ne 0 ]; then
-            echo "Output is wrong:";
-            cat result/diff_output.txt
-            exit(1);
-        else
-            echo "All tests were a complete success"
-            GLOBAL_SUCCESS=true
-            exit(0);
-        fi
-        
-    else
-        echo "Execution failed";
-        exit(2);
-    fi
-else
-    echo "Compilation failed."
-    exit(3);
-fi
-```
-We use the exit code to say to Dojo if the exercise was a success (exit code `0`) or a failure (all other exit codes).
-Here one can see the creation of two different files that are located in the `result` directory:
-- `result/results.json`
-- `result/diff_output.txt`
-The `results.json` is mandatory to be created and must at least contain the
-`success` field must be `true` or `false` and determines whether the
-assignment is a success or a failure. The other files present in the `result` folder
-can be retrieved by the students.
-
-In the result directory you can provide the optional `result.json` file that can contain more details about the tests:
-```
-successfulTests?: number;
-failedTests?: number;
-
-successfulTestsList?: Array<string>;
-failedTestsList?: Array<string>;
-```
-- the `successfulTests` which provide the number of successfully passed tests,
-- the `failedTests` which provide the number of failed tests,
-- the `successfulTestsList` which provide the list (of string) of successfully passed tests,
-- the `failedTestsList` which provide the list (of string) of failed tests,
-
-To test if everything works according to plan, one can again use the command
-```bash
-$ docker compose run --build hello_world
-```
-```console
-[+] Building 1.8s (8/8) FINISHED                                                                                    
- => [hello_world internal] load build definition from Dockerfile
- => => transferring dockerfile: 134B
- => [hello_world internal] load .dockerignore
- => => transferring context: 2B
- => [hello_world internal] load metadata for docker.io/library/ubuntu:latest
- => [hello_world 1/3] FROM docker.io/library/ubuntu:latest@sha256:ec050c32e4a6085b423d36ecd025c0d3ff00c38ab93
- => [hello_world internal] load build context
- => => transferring context: 3.23kB
- => CACHED [hello_world 2/3] RUN apt update && apt install gcc make jq -y
- => [hello_world 3/3] COPY . /
- => [hello_world] exporting to image
- => => exporting layers
- => => writing image sha256:5499aa3aa0b2f2021584dbf46b4b05ab83dc0a5ad4d6c81a3466c56673c3f562
- => => naming to docker.io/library/c_hello_world-hello_world
-Starting tests.
-Output is wrong:
-1c1
-< (null)
-\ No newline at end of file
----
-> Hello world!
-\ No newline at end of file
-```
-where we see that the execution fails. This will be improved in the future and the actual execution as expected to be
-run by the students will be added.
-
-## The immutable files
-
-There is only one file that should be modified by the students in this example: the `function.c` file. Therefore we can safely
-add all the created files in the `src` except `src/function.c` which is precisely
-the file that the student must modify, as well as the `run.sh` file. The `dojo_assignment.json` file becomes
-```json
-{
-  "dojoAssignmentVersion": 1,
-  "version": 1,
-  "immutable": [
-    {
-      "description": "Dockerfile of the unique container",
-      "path": "Dockerfile",
-      "isDirectory": false
-    },
-    {
-      "description": "The entry point of the Dockerfile",
-      "path": "run.sh",
-      "isDirectory": false
-    },
-    {
-      "description": "The makefile for compilation/execution purposes",
-      "path": "src/Makefile",
-      "isDirectory": false
-    },
-    {
-      "description": "Entry point of the code",
-      "path": "src/hello_world.c",
-      "isDirectory": false
-    },
-    {
-      "description": "The header file fot the program's assignment",
-      "path": "src/function.h",
-      "isDirectory": false
-    },
-    {
-      "description": "The expected output file for comparing with the actual output",
-      "path": "src/expected_output.txt",
-      "isDirectory": false
-    }
-  ],
-  "result": {
-    "container": "hello_world",
-    "volume": "hello_world_volume"
-  }
-}
-```
-
-## The `README.md` file
-
-The `README.md` file is used to provide informations on the assignment and the way the teacher wants students to accomplish
-the assignment and its content is completely free. It is recommended to use the `Markdown` syntax as the
-file extension suggests.
-
-In this assignment the `README.md` file reads
-```markdown
-# Hello world!
-
-C'est le premier vrai exercice jamais créé sur le Dojo!
-
-Il sert de tutoriel pour crΓ©er un exercice simple en C.
-
-## But
-
-Le but est de faire afficher "Hello world!" Γ  notre programme en C.
-
-Pour ce faire, il faut modifier le fichier `src/function.c` sous la ligne
-annotΓ©e avec `TODO`. Bonne chance!
-```
-
-## Validate the assignment
-
-Now that the assignment is ready, we must validate it.
-You can do it first locally with the command
-```bash
-$ dojo assignment check
-```
-```console
-Please wait while we are checking requirements...
-    βœ” Docker daemon is running
-    βœ” All required files exists
-Please wait while we are validating dojo_assignment.json file...
-    βœ” dojo_assignment.json file schema is valid
-    βœ” Immutable files are valid
-Please wait while we are validating docker compose file...
-    βœ” Docker compose file structure is valid
-    βœ” Docker compose file content is valid
-Please wait while we are validating dockerfiles...
-    βœ” Docker compose file content is valid
-Please wait while we are running the assignment...
-    βœ” Docker Compose file run successfully
-    βœ” Linked services logs acquired
-    βœ” Containers stopped and removed
-
-   ┏━━━━━━━━━━━━━━━━━ Results ━━━━━━━━━━━━━━━━━┓
-   ┃                                           ┃
-   ┃   Global result : βœ… Success              ┃
-   ┃                                           ┃
-   ┃   The assignment is ready to be pushed.   ┃
-   ┃                                           ┃
-   ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
-```
-
-## Publish the work
-
-Now that the assignment is ready, we must publish it. First add/commit/push all the
-files needed for your exercise. In this case, it should be:
-```console
-c_hello_world/
-β”œβ”€β”€ docker-compose.yml
-β”œβ”€β”€ Dockerfile
-β”œβ”€β”€ dojo_assignment.json
-β”œβ”€β”€ README.md
-β”œβ”€β”€ run.sh
-└── src
-    β”œβ”€β”€ expected_output.txt
-    β”œβ”€β”€ function.c
-    β”œβ”€β”€ function.h
-    β”œβ”€β”€ hello_world.c
-    └── Makefile
-```
-Then one must *publish* the assignment for the students to be able to perform to get the exercise.
-A pipeline will be automatically run to check that the assignment is valid (the same as the previous state).
-
-Wait for the successfully completion of this pipeline and then you are ready to publish the assignment with the command:
-```bash
-$ dojo assignment publish c_hello_world
-```
-```console
-? Are you sure you want to publish this assignment? Yes
-Please wait while we verify and retrieve data...
-β„Ή Checking Dojo session: 
-    βœ” The session is valid
-β„Ή Checking assignment:
-    β„Ή c_hello_world
-        βœ” The assignment exists
-        βœ” You are in the staff of this assignment
-Please wait while we publish the assignment...
-βœ” Assignment c_hello_world successfully published
-```
-
-The assignment is now ready to be performed by students!
diff --git a/Wiki/UserDocumentation/0-Installation.md b/Wiki/UserDocumentation/0-Installation.md
deleted file mode 100644
index da20cbbb4580d83e64ec251bdef712c5cddecb32..0000000000000000000000000000000000000000
--- a/Wiki/UserDocumentation/0-Installation.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Installation of the Dojo CLI
-
-1. Download the latest stable version (without "-dev" suffix) from the releases: <https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases>
-
-![releases](../figures/releases.png)
-
-1. Download the executable corresponding to your OS and architecture.
-   - ℹ️ For these OS you can use specific packaged release that install the binary at the right place (so you can ignore the next point).
-      - **Debian / Ubuntu** : You can use the `deb` package named `Debian / Ubuntu (YOUR_ARCH) package`.
-      - **macOS**: You can use the `pkg` version named `macOS (YOUR_ARCH) package`.
-
-2. Put it in your path. For
-   - For exemple:
-       - ℹ️ **Linux**: `$HOME/.local/bin`
-       - ℹ️ **macOS**: `/usr/local/bin`
-
-3. Verify your installation is working correctly by calling the `dojo` CLI.
-```bash
-dojo
-```
-```console
-Usage: dojo [options] [command]
-
-CLI of the Dojo application
-
-Options:
-  -h, --help           display help for command
-  -H, --host <string>  override the Dojo API endpoint (default: "https://rdps.hesge.ch/dojo/api")
-  -V, --version        output the version number
-
-Commands:
-  assignment           manage an assignment
-  exercise             manage an exercise
-  help [command]       display help for command
-  session              manage Dojo and Gitlab sessions
-```
-
-As you can see calling the `dojo` command shows the help menu.
\ No newline at end of file
diff --git a/Wiki/UserDocumentation/1-Authentification.md b/Wiki/UserDocumentation/1-Authentification.md
deleted file mode 100644
index 5c7a45ce9c045efd9f6b94900ea9eb1da838654d..0000000000000000000000000000000000000000
--- a/Wiki/UserDocumentation/1-Authentification.md
+++ /dev/null
@@ -1,54 +0,0 @@
-# Authentication to the Dojo
-
-The authentication is done by the login command:
-```bash
-dojo session login
-```
-```console
-Please wait while we login you into Dojo...
-β„Ή Login with Gitlab (https://gitedu.hesge.ch/):
-    βœ” Login server started
-    β ‡ Waiting for user to authorize the application in his web browser
-```
-At this time the cli opens a web browser to the Gitlab login page. Once you are logged in you will be asked to authorize the application to access your account. Click on the `Authorize` button.
-
-![Authorize](../figures/GitlabLogin.png)
-
-**Note:** If you want to use the cli in a headless environment you can use the `--cli` option. In this case you will be provided with a link to copy/paste in your browser.
-
-Once, the authorization is done, the cli will retrieve the Gitlab token and will try to login to the Dojo backend. If everything is ok you will see the following output (the permissions section may differ depending on your account):
-```console
-Please wait while we login you into Dojo...
-β„Ή Login with Gitlab (https://gitedu.hesge.ch/):
-    βœ” Login server started
-    βœ” Login code received
-    βœ” Login server stopped
-    βœ” Gitlab tokens retrieved
-β„Ή Checking Gitlab token:
-    βœ” Read access
-    βœ” Write access
-β„Ή Login to Dojo backend:
-    βœ” Logged in
-β„Ή Checking Dojo session:
-    βœ” The session is valid
-        βœ” Student permissions
-        βœ” Teaching staff permissions
-        βœ” Admin permissions
-```
-
-
-## Test if it is working
-```bash
-dojo session test
-```
-```console
-β„Ή Checking Dojo session: 
-    βœ” The session is valid
-        βœ” Teaching staff permissions
-        βœ” Student permissions
-β„Ή Checking Gitlab token: 
-    βœ” Read access
-    βœ” Write access
-```
-
-Good news. You can use the Dojo!
diff --git a/Wiki/UserDocumentation/2-Assignment-creation.md b/Wiki/UserDocumentation/2-Assignment-creation.md
deleted file mode 100644
index 76d65404f7d920ce8312ebc5f39483ef5eb71624..0000000000000000000000000000000000000000
--- a/Wiki/UserDocumentation/2-Assignment-creation.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# Assignment creation
-
-The assignment is created by a member of the teaching staff and is the basis for students to perform their exercise.
-
-1. To create an assignment
-```bash
-dojo assignment create --name <unique_name>
-```
-```console
-Please wait while we verify and retrieve data...
-β„Ή Checking Dojo session: 
-    βœ” The session is valid
-        βœ” Teaching staff permissions
-β„Ή Checking Gitlab token: 
-    βœ” Read access
-    βœ” Write access
-βœ” Assignment name "unique_name" is available
-Please wait while we are creating the assignment...
-βœ” Assignment successfully created
-    β„Ή Name: unique_name
-    β„Ή Web URL: https://gitedu.hesge.ch/dojo/assignment/unique_name
-    β„Ή HTTP Repo: https://gitedu.hesge.ch/dojo/assignment/unique_name.git
-    β„Ή SSH Repo: ssh://git@ssh.hesge.ch:10572/dojo/assignment/unique_name.git
-```
-where `<unique_name>` is the name of the assignment. By default only the creator of the exercise is added to
-the assignment (which is just a git repository with some configuration files in it).
-There are several other options that can be provided:
-```bash
-dojo assignment create --name <unique_name> --template <url>
-```
-where `<url>` is the url of the template repository one may want to use as a basis for the assignment-
-```bash
-dojo assignment create --name <unique_name> --members_username <usernames>
-```
-where `<usernames>` is a list of gitedu usernames that will be given the same permissions as the
-user creating the assignment.
-
-As usual one can show the help menu by typing
-```bash
-dojo assignment create --help
-```
-Or you may just enter (`--help` is implicit in most commands)
-```bash
-dojo assignment create
-```
-2. Clone the repository of the assignment that was just created:
-```bash
-git clone ssh://git@ssh.hesge.ch:10572/dojo/assignment/unique_name.git
-```
-3. Modify the `unique_name` assignment as you want (modify the Dockerfile, docker-compose.yml files, add a readme, source code, compilation tools, etc.). Commit and push our work (soonβ„’ more details will be provided on how to create assignment).
-4. Once the assignment is done and validated by the pipeline it must be published to be available to students:
-```bash
-dojo assignment publish unique_name
-```
-```console
-? Are you sure you want to publish this assignment? Yes
-Please wait while we verify and retrieve data...
-β„Ή Checking Dojo session: 
-    βœ” The session is valid
-β„Ή Checking assignment:
-    β„Ή unique_name
-        βœ” The assignment exists
-        βœ” You are in the staff of this assignment
-Please wait while we publish the assignment...
-βœ” Assignment unique_name successfully published
-```
\ No newline at end of file
diff --git a/Wiki/UserDocumentation/3-Exercise-creation.md b/Wiki/UserDocumentation/3-Exercise-creation.md
deleted file mode 100644
index 29817fb37e5441833e1c0da61b9f41557d6d0c2e..0000000000000000000000000000000000000000
--- a/Wiki/UserDocumentation/3-Exercise-creation.md
+++ /dev/null
@@ -1,74 +0,0 @@
-# Exercise creation
-
-The exercise is an instance of a **published assignment** to be performed by students (or group of students).
-
-1. Create an exercise
-```bash
-dojo exercise create --assignment unique_name                   
-```
-```console
-Please wait while we verify and retrieve data...
-β„Ή Checking Dojo session: 
-    βœ” The session is valid
-        βœ” Student permissions
-β„Ή Checking Gitlab token: 
-    βœ” Read access
-    βœ” Write access
-β„Ή Checking assignment:
-    βœ” Assignment "unique_name" exists
-    βœ” Assignment "unique_name" is published
-Please wait while we are creating the exercise...
-βœ” Exercise successfully created
-    β„Ή Id: some-long-hash
-    β„Ή Name: DojoEx - unique_name - your.name
-    β„Ή Web URL: https://gitedu.hesge.ch/dojo/exercise/dojo-ex_unique_name_some-long-hash
-    β„Ή HTTP Repo: https://gitedu.hesge.ch/dojo/exercise/dojo-ex_unique_name_some-long-hash.git
-    β„Ή SSH Repo: ssh://git@ssh.hesge.ch:10572/dojo/exercise/dojo-ex_unique_name_some-long-hash.git
-```
-**Tips**: You cas use the --members_username or --members_id options to add other students of the group to the exercise.
-2. Make changes and try solving the exercise.
-3. The complete tests can be run with the following command.
-```bash
-dojo exercise run
-```
-```console
-Please wait while we are checking and creating dependencies...
-    β„Ή Checking exercise content:
-        βœ” The exercise folder contains all the needed files
-        βœ” The dojo_assignment.json file is valid
-    βœ” The Docker deamon is running
-Please wait while we are running the exercise...
-    βœ” Docker Compose file run successfully
-    βœ” Linked services logs acquired
-    βœ” Containers stopped and removed
-Please wait while we are checking the results...
-    βœ” Results file found
-    βœ” Results file is valid
-    βœ” Results folder size is in bounds
-
-   ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Results ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
-   ┃                                                                                                    ┃
-   ┃   Global result : βœ… Success                                                                       ┃
-   ┃                                                                                                    ┃
-   ┃   Execution exit code : 0                                                                          ┃
-   ┃                                                                                                    ┃
-   ┃   Tests passed : 3                                                                                 ┃
-   ┃   Tests failed : 1                                                                                 ┃
-   ┃                                                                                                    ┃
-   ┃   Tests :                                                                                          ┃
-   ┃   - βœ… ListeOrdonnee                                                                               ┃
-   ┃   - βœ… ListeVide                                                                                   ┃
-   ┃   - βœ… ListeOrdreInverse                                                                           ┃
-   ┃   - ❌ ListeRandom                                                                                 ┃
-   ┃                                                                                                    ┃
-   ┃   Execution results folder : /home/username/DojoExecutions/dojo_execLogs_2023-08-21T21_33_38_684Z  ┃
-   ┃                                                                                                    ┃
-   ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
-```
-This command runs the exercise in the current directory or in the path provided by the `--path` option.
-
-4. Once confident enough in the solution, one can commit/push the solution. 
-This triggers the CI/CD pipeline on gitedu. In the CI/CD all immutable files are overwritten and 
-the docker-compose is executed (only logs from the current container are given) then other logs 
-are retrieved from the other images that may be used. The error code of the docker container is retrieved. 
-
diff --git a/Wiki/figures/GitlabLogin.png b/Wiki/figures/GitlabLogin.png
deleted file mode 100644
index bfa99cde6a3fbe64055f0b1f6c9aa39b9f8528fa..0000000000000000000000000000000000000000
Binary files a/Wiki/figures/GitlabLogin.png and /dev/null differ
diff --git a/Wiki/figures/releases.png b/Wiki/figures/releases.png
deleted file mode 100644
index 8950a930ee305be9aeb50317502a57d90ce2d853..0000000000000000000000000000000000000000
Binary files a/Wiki/figures/releases.png and /dev/null differ
diff --git a/Wiki/home.md b/Wiki/home.md
index df06c272b3e13cb87c280810470168ba50f5cba9..d4426ceedfbd3e32fcea61c72fee93ad8475de71 100644
--- a/Wiki/home.md
+++ b/Wiki/home.md
@@ -1,32 +1,3 @@
-# Documentation of the `dojo` CLI utility
+# Documentation of `The Dojo CLI` utility
 
-In this wiki you will find the documentation related to the `dojo` CLI.
-
-## Dojo Project
-The dojo platform is an online tool built to help practice programming by allowing users to propose assignments and perform them as exercises.
-
-The two major concepts of the platform are the **assignments** (provided by teaching staff) and the **exercises** (performed by students).
-
-More details here : [Dojo detailed presentation](0-Dojo-presentation)
-
-## User documentation
-
-* [Installation of the CLI](UserDocumentation/0-Installation)
-* [Authentification](UserDocumentation/1-Authentification)
-* [Assignment creation](UserDocumentation/2-Assignment-creation)
-* [Exercice creation](UserDocumentation/3-Exercise-creation)
-
-## Tutorials / Exemples
-
-### Students / Everyone
-* [How to perform an exercise](Tutorials/0-Exercise-perform)
-
-### Teaching staff
-* [How to create and publish an assignment](Tutorials/1-Assignment-creation)
-
-
-## Development / Contribution
-
-* [How to contribute]() - Available soon
-* [How to setup your development environment](Development/1-How-to-setup-your-development-environment)
-* [How to add a new command](Development/2-How-to-add-a-new-command)
\ No newline at end of file
+All documentations are available on the [Dojo documentation website](https://www.hepiapp.ch/) : https://www.hepiapp.ch/.
\ No newline at end of file
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 0000000000000000000000000000000000000000..975a41c930b77e5fa39c31e9e5d5376a9d6fb397
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,3 @@
+sonar.projectKey=DojoCLI
+sonar.qualitygate.wait=true
+