diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7cd35099200f5e2c0e63b6606e2b899d0be8ef5c..dc83a81d2b421de89432d5d80756b977f740ce29 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,7 +8,6 @@ before_script:
   - apk add curl
   - export TAG=${CI_COMMIT_TAG:-latest}
 
-
 build:
   stage: build
   services:
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100755
index 0000000000000000000000000000000000000000..36af219892fda8ea669cd4b6725cd7b892231967
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+npx lint-staged
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000000000000000000000000000000000000..154871ed58add16f47f8227661d7c5d1331fa10a
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,3 @@
+default
+dind
+plugins
\ No newline at end of file
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000000000000000000000000000000000000..236324e86690b8e32efaf624fc0d9f93bdc2a626
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,8 @@
+{
+  "tabWidth": 2,
+  "semi": true,
+  "trailingComma": "all",
+  "singleQuote": true,
+  "printWidth": 120,
+  "useTabs": false
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e6de0c635810f0efd00cd72376139fc2da306484..0e47e974d6434faaf7c800988ec5b4de66acaedd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,23 +1,26 @@
-
 [1.4] 24/02/2022
-* chore: remove java plugin
-* feat: prefix path instead of suffix for hosts
+
+- chore: remove java plugin
+- feat: prefix path instead of suffix for hosts
 
 [1.3] 14/02/2022
-* upgrade java plugin to 0.21.0
-* support of git for student
+
+- upgrade java plugin to 0.21.0
+- support of git for student
 
 [1.2] 27/11/2020
-* Image supports in briefing
-* Set theia-full instead of theia-java (https://git.e-biz.fr/deadlock-public/deadlock-theia/-/issues/2)
+
+- Image supports in briefing
+- Set theia-full instead of theia-java (https://git.e-biz.fr/deadlock-public/deadlock-theia/-/issues/2)
 
 [1.1] 27/11/2020
-* preStop hook ok
-* GitLens integration
-* Keep view on memory
+
+- preStop hook ok
+- GitLens integration
+- Keep view on memory
 
 [1.0] 02/10/2020
-* git integration
-* recorder-command
-* docs/ support
 
+- git integration
+- recorder-command
+- docs/ support
diff --git a/README.md b/README.md
index 0efce8a5520780a55d73ec3cbdbb3ce03eeccaa3..428481eb4b0d30b949cf41d17331633ffbd087fc 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,18 @@
-
 Deadlock Theia projet basé sur notre propre image de Theia https://git.e-biz.fr/deadlock-public/theia.
 Plusieurs éléments ajoutés à l'image de base :
 
 1. Deadlock plugins (deadlock-plugins/)
 2. Outil d'enregistrment, qui permet d'enregistrer le code quand un utilisateur exécute du code (deadlock(plugins/recorder))
-3. Gestion du CORS (*.deadlock.io) dans `server.js`
+3. Gestion du CORS (\*.deadlock.io) dans `server.js`
+
+Deux images de `deadlock-theia` sont build ici, _code_ et _kube_.
 
-Deux images de `deadlock-theia` sont build ici, *code* et *kube*.
+## Setup
+
+Afin d'installer les éléments requis pour le développement, vous pouvez lancer le script `./setup.sh`.
 
 ## Construire les plugins :
+
 `./build-plugins.sh`
 
 Tous les plugins qui se trouvent dans `deadlock-public` vont être construit en `.vsix` puis placés
@@ -16,19 +20,23 @@ dans `plugins/` (ex: deadlock-plugins/deadlock-extension).
 Il est aussi possible d'ajouter directement des plugins `.vsix` en ajoutant le fichier dans `plugins/`
 
 ## Recorder :
+
 Le recorder permet de sauvegarder régulièrement le code de l'utilisateur.
 Pour se faire il écoute les commandes exécutées par l'utilisateur et si une contient `java|npm|yarn`
 alors un snapshot du code est réalisé.
 
 ### Build
+
 `./build-recorder.sh`
 
 ## Construire l'image Deadlock Theia avec le recorder et les plugins
+
 `./build.sh $TAG (code|kube)`
 
 # Requirements:
+
 1. NodeJS > ^14.X
 2. Vscode ou Vscodium
 3. Docker
 
-Ce projet contient le minimum pour construire une image Docker Theia avec Blueprint (https://theia-ide.org/docs/composing_applications/) 
\ No newline at end of file
+Ce projet contient le minimum pour construire une image Docker Theia avec Blueprint (https://theia-ide.org/docs/composing_applications/)
diff --git a/deadlock-plugins/README.md b/deadlock-plugins/README.md
index 9d3f1309c92306a79c6551a4b30cd9944eadfb97..73b6ded32c1c777f9127a8b4d9099a9f034bbec4 100644
--- a/deadlock-plugins/README.md
+++ b/deadlock-plugins/README.md
@@ -1,7 +1,6 @@
-
 Contient une liste de dossier des extensions à construire pour ajouter à Theia pendant la phase de build.
 Chaque dossier d'extension doit contenir un fichier `install.sh` et `build.sh` qui doivent contenir
 respectivement la façon d'installer les dépendances et les executions pour construire le plugin,
 c'est à dire créer un fichier `vsix` à la fin.
 
-Chaque fichier build.sh sera appelé avec comme premier argument le tag en cours.
\ No newline at end of file
+Chaque fichier build.sh sera appelé avec comme premier argument le tag en cours.
diff --git a/deadlock-plugins/deadlock-extension/.eslintrc.js b/deadlock-plugins/deadlock-extension/.eslintrc.js
index cd15cf93f5de58e6be674beb72643e473d6bdb8d..2cac4952287f4b6d28a6b22b9a39044d31270618 100644
--- a/deadlock-plugins/deadlock-extension/.eslintrc.js
+++ b/deadlock-plugins/deadlock-extension/.eslintrc.js
@@ -1,21 +1,16 @@
 /**@type {import('eslint').Linter.Config} */
 // eslint-disable-next-line no-undef
 module.exports = {
-	root: true,
-	parser: '@typescript-eslint/parser',
-	plugins: [
-		'@typescript-eslint',
-	],
-	extends: [
-		'eslint:recommended',
-		'plugin:@typescript-eslint/recommended',
-	],
-	rules: {
-		'semi': [2, 'always'],
-		'quotes': [2, 'single', 'avoid-escape'],
-		'@typescript-eslint/no-unused-vars': 0,
-		'@typescript-eslint/no-explicit-any': 0,
-		'@typescript-eslint/explicit-module-boundary-types': 0,
-		'@typescript-eslint/no-non-null-assertion': 0,
-	}
-};
\ No newline at end of file
+  root: true,
+  parser: '@typescript-eslint/parser',
+  plugins: ['@typescript-eslint'],
+  extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
+  rules: {
+    semi: [2, 'always'],
+    quotes: [2, 'single' | 'backtick', 'avoid-escape'],
+    '@typescript-eslint/no-unused-vars': 0,
+    '@typescript-eslint/no-explicit-any': 0,
+    '@typescript-eslint/explicit-module-boundary-types': 0,
+    '@typescript-eslint/no-non-null-assertion': 0,
+  },
+};
diff --git a/deadlock-plugins/deadlock-extension/.vscode/extensions.json b/deadlock-plugins/deadlock-extension/.vscode/extensions.json
index af515502dfd158060279cdec8c0bb7e54c09af47..326dae3cd5b9cae9464cbdc6390d11927043f728 100644
--- a/deadlock-plugins/deadlock-extension/.vscode/extensions.json
+++ b/deadlock-plugins/deadlock-extension/.vscode/extensions.json
@@ -1,9 +1,7 @@
 {
-	// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
-	// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
+  // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
+  // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
 
-	// List of extensions which should be recommended for users of this workspace.
-	"recommendations": [
-		"dbaeumer.vscode-eslint"
-	]
-}
\ No newline at end of file
+  // List of extensions which should be recommended for users of this workspace.
+  "recommendations": ["dbaeumer.vscode-eslint"]
+}
diff --git a/deadlock-plugins/deadlock-extension/.vscode/launch.json b/deadlock-plugins/deadlock-extension/.vscode/launch.json
index 461f3da0dba050b10029a046cd1c0a70058cc7eb..4c4a9c78b100a81bc070816bab70bbcf326ee2c1 100644
--- a/deadlock-plugins/deadlock-extension/.vscode/launch.json
+++ b/deadlock-plugins/deadlock-extension/.vscode/launch.json
@@ -3,16 +3,16 @@
 // Hover to view descriptions of existing attributes.
 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
 {
-	"version": "0.2.0",
-	"configurations": [
-		{
-			"name": "Run Extension",
-			"type": "extensionHost",
-			"request": "launch",
-			"runtimeExecutable": "${execPath}",
-			"args": ["--extensionDevelopmentPath=${workspaceRoot}"],
-			"outFiles": ["${workspaceFolder}/out/**/*.js"],
-			"preLaunchTask": "npm: watch"
-		}
-	]
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "Run Extension",
+      "type": "extensionHost",
+      "request": "launch",
+      "runtimeExecutable": "${execPath}",
+      "args": ["--extensionDevelopmentPath=${workspaceRoot}"],
+      "outFiles": ["${workspaceFolder}/out/**/*.js"],
+      "preLaunchTask": "npm: watch"
+    }
+  ]
 }
diff --git a/deadlock-plugins/deadlock-extension/.vscode/settings.json b/deadlock-plugins/deadlock-extension/.vscode/settings.json
index 8d047dada4aa529a4c752ee2c0ed4f265f819426..194c9b6b6c9840561b2552671bdaa26be65366ba 100644
--- a/deadlock-plugins/deadlock-extension/.vscode/settings.json
+++ b/deadlock-plugins/deadlock-extension/.vscode/settings.json
@@ -1,3 +1,3 @@
 {
-    "editor.insertSpaces": false
-}
\ No newline at end of file
+  "editor.insertSpaces": false
+}
diff --git a/deadlock-plugins/deadlock-extension/.vscode/tasks.json b/deadlock-plugins/deadlock-extension/.vscode/tasks.json
index 3b17e53b62cc6ffaacd997adeb1915422fb6858f..078ff7e01ef583efd188933e6f5af582ec9d81bc 100644
--- a/deadlock-plugins/deadlock-extension/.vscode/tasks.json
+++ b/deadlock-plugins/deadlock-extension/.vscode/tasks.json
@@ -1,20 +1,20 @@
 // See https://go.microsoft.com/fwlink/?LinkId=733558
 // for the documentation about the tasks.json format
 {
-	"version": "2.0.0",
-	"tasks": [
-		{
-			"type": "npm",
-			"script": "watch",
-			"problemMatcher": "$tsc-watch",
-			"isBackground": true,
-			"presentation": {
-				"reveal": "never"
-			},
-			"group": {
-				"kind": "build",
-				"isDefault": true
-			}
-		}
-	]
+  "version": "2.0.0",
+  "tasks": [
+    {
+      "type": "npm",
+      "script": "watch",
+      "problemMatcher": "$tsc-watch",
+      "isBackground": true,
+      "presentation": {
+        "reveal": "never"
+      },
+      "group": {
+        "kind": "build",
+        "isDefault": true
+      }
+    }
+  ]
 }
diff --git a/deadlock-plugins/deadlock-extension/.vscodeignore b/deadlock-plugins/deadlock-extension/.vscodeignore
index 7d2b7710829b2c915b12a84f06439e8a6af2e2a4..d626c52f3c583b1db5e9a70958ac2f34ac37a757 100644
--- a/deadlock-plugins/deadlock-extension/.vscodeignore
+++ b/deadlock-plugins/deadlock-extension/.vscodeignore
@@ -1,8 +1,6 @@
 .vscode
-node_modules
 out/
 !out/main.js
 src/
 dev/
-tsconfig.json
-webpack.config.js
\ No newline at end of file
+tsconfig.json
\ No newline at end of file
diff --git a/deadlock-plugins/deadlock-extension/package-lock.json b/deadlock-plugins/deadlock-extension/package-lock.json
index b333a7a44e20b089514ec4236f37dd0fd07631f8..b295b7667c31cb60e1d9f701b2cefc4ce3da03cb 100644
--- a/deadlock-plugins/deadlock-extension/package-lock.json
+++ b/deadlock-plugins/deadlock-extension/package-lock.json
@@ -8,19 +8,27 @@
       "name": "deadlock-coding",
       "version": "0.0.2",
       "dependencies": {
+        "@vscode/webview-ui-toolkit": "^1.0.0",
         "async": "^3.2.2",
+        "crypto-js": "^4.1.1",
         "date-fns": "^2.27.0",
+        "inversify": "^6.0.1",
         "marked": "^4.0.6",
+        "node-fetch": "^3.2.3",
+        "reflect-metadata": "^0.1.13",
         "simple-git": "^2.48.0"
       },
       "devDependencies": {
+        "@types/crypto-js": "^4.1.1",
         "@types/marked": "^4.0.1",
         "@types/node": "^12.20.47",
+        "@types/node-fetch": "^2.6.1",
         "@types/vscode": "^1.51.0",
         "@typescript-eslint/eslint-plugin": "^3.10.1",
         "@typescript-eslint/parser": "^3.10.1",
         "esbuild": "^0.14.2",
         "eslint": "^7.32.0",
+        "prettier": "2.6.2",
         "terser-webpack-plugin": "^5.2.5",
         "ts-node": "^9.1.1",
         "typescript": "^3.9.10",
@@ -29,7 +37,7 @@
         "webpack-cli": "^4.9.1"
       },
       "engines": {
-        "vscode": "^1.51.0"
+        "vscode": "^1.63.0"
       }
     },
     "node_modules/@babel/code-frame": {
@@ -149,6 +157,48 @@
       "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
       "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="
     },
+    "node_modules/@microsoft/fast-element": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.9.0.tgz",
+      "integrity": "sha512-VxDwoLEEQRRxXfLOB5Llzha9WWvjVJHZFHNE1mn2s1QV5IAkOhrYrhFe9vzv50eI6CqazChDyHDy1km3+rn0wg=="
+    },
+    "node_modules/@microsoft/fast-foundation": {
+      "version": "2.41.1",
+      "resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.41.1.tgz",
+      "integrity": "sha512-q3t43CVsYrLeBbmiY6+GvV0/uUqnz7anmagD8qMBpHIpC6XpEERGBNbyC6AxQrRoBrYBFFhBJRTPn7vCqi4iPA==",
+      "dependencies": {
+        "@microsoft/fast-element": "^1.9.0",
+        "@microsoft/fast-web-utilities": "^5.2.0",
+        "tabbable": "^5.2.0",
+        "tslib": "^1.13.0"
+      }
+    },
+    "node_modules/@microsoft/fast-react-wrapper": {
+      "version": "0.1.48",
+      "resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.1.48.tgz",
+      "integrity": "sha512-9NvEjru9Kn5ZKjomAMX6v+eF0DR+eDkxKDwDfi+Wb73kTbrNzcnmlwd4diN15ygH97kldgj2+lpvI4CKLQQWLg==",
+      "dependencies": {
+        "@microsoft/fast-element": "^1.9.0",
+        "@microsoft/fast-foundation": "^2.41.1"
+      },
+      "peerDependencies": {
+        "react": ">=16.9.0"
+      }
+    },
+    "node_modules/@microsoft/fast-web-utilities": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/@microsoft/fast-web-utilities/-/fast-web-utilities-5.2.0.tgz",
+      "integrity": "sha512-Ri5FR4MotdAPKeFLo/yhZu6/xMVo/H9OXcGaC/qiQo+U0zY8cLPCViGmHYYgxpCYviS4VpgqPL5dslTE6wg9Yg==",
+      "dependencies": {
+        "exenv-es6": "^1.1.1"
+      }
+    },
+    "node_modules/@types/crypto-js": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz",
+      "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==",
+      "dev": true
+    },
     "node_modules/@types/eslint": {
       "version": "8.2.1",
       "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.1.tgz",
@@ -199,6 +249,16 @@
       "integrity": "sha512-BzcaRsnFuznzOItW1WpQrDHM7plAa7GIDMZ6b5pnMbkqEtM/6WCOhvZar39oeMQP79gwvFUWjjptE7/KGcNqFg==",
       "dev": true
     },
+    "node_modules/@types/node-fetch": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz",
+      "integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*",
+        "form-data": "^3.0.0"
+      }
+    },
     "node_modules/@types/vscode": {
       "version": "1.62.0",
       "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.62.0.tgz",
@@ -343,6 +403,19 @@
         "url": "https://opencollective.com/typescript-eslint"
       }
     },
+    "node_modules/@vscode/webview-ui-toolkit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.0.0.tgz",
+      "integrity": "sha512-/qaHYZXqvIKkao54b7bLzyNH8BC+X4rBmTUx1MvcIiCjqRMxml0BCpqJhnDpfrCb0IOxXRO8cAy1eB5ayzQfBA==",
+      "dependencies": {
+        "@microsoft/fast-element": "^1.6.2",
+        "@microsoft/fast-foundation": "^2.38.0",
+        "@microsoft/fast-react-wrapper": "^0.1.18"
+      },
+      "peerDependencies": {
+        "react": ">=16.9.0"
+      }
+    },
     "node_modules/@webassemblyjs/ast": {
       "version": "1.11.1",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
@@ -538,9 +611,9 @@
       "dev": true
     },
     "node_modules/acorn": {
-      "version": "7.4.1",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
-      "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
+      "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
       "dev": true,
       "bin": {
         "acorn": "bin/acorn"
@@ -667,6 +740,12 @@
       "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz",
       "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g=="
     },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+      "dev": true
+    },
     "node_modules/azure-devops-node-api": {
       "version": "11.1.0",
       "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.1.0.tgz",
@@ -1010,6 +1089,18 @@
       "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
       "dev": true
     },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dev": true,
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/commander": {
       "version": "2.20.3",
       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
@@ -1054,6 +1145,11 @@
         "node": ">= 8"
       }
     },
+    "node_modules/crypto-js": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
+      "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
+    },
     "node_modules/css-select": {
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz",
@@ -1082,6 +1178,14 @@
         "url": "https://github.com/sponsors/fb55"
       }
     },
+    "node_modules/data-uri-to-buffer": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
+      "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
+      "engines": {
+        "node": ">= 12"
+      }
+    },
     "node_modules/date-fns": {
       "version": "2.27.0",
       "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.27.0.tgz",
@@ -1137,6 +1241,15 @@
       "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
       "dev": true
     },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/delegates": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@@ -1333,84 +1446,6 @@
         "esbuild-windows-arm64": "0.14.2"
       }
     },
-    "node_modules/esbuild-android-arm64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.2.tgz",
-      "integrity": "sha512-hEixaKMN3XXCkoe+0WcexO4CcBVU5DCSUT+7P8JZiWZCbAjSkc9b6Yz2X5DSfQmRCtI/cQRU6TfMYrMQ5NBfdw==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "android"
-      ]
-    },
-    "node_modules/esbuild-darwin-64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.2.tgz",
-      "integrity": "sha512-Uq8t0cbJQkxkQdbUfOl2wZqZ/AtLZjvJulR1HHnc96UgyzG9YlCLSDMiqjM+NANEy7/zzvwKJsy3iNC9wwqLJA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/esbuild-darwin-arm64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.2.tgz",
-      "integrity": "sha512-619MSa17sr7YCIrUj88KzQu2ESA4jKYtIYfLU/smX6qNgxQt3Y/gzM4s6sgJ4fPQzirvmXgcHv1ZNQAs/Xh48A==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/esbuild-freebsd-64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.2.tgz",
-      "integrity": "sha512-aP6FE/ZsChZpUV6F3HE3x1Pz0paoYXycJ7oLt06g0G9dhJKknPawXCqQg/WMyD+ldCEZfo7F1kavenPdIT/SGQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "freebsd"
-      ]
-    },
-    "node_modules/esbuild-freebsd-arm64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.2.tgz",
-      "integrity": "sha512-LSm98WTb1QIhyS83+Po0KTpZNdd2XpVpI9ua5rLWqKWbKeNRFwOsjeiuwBaRNc+O32s9oC2ZMefETxHBV6VNkQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "freebsd"
-      ]
-    },
-    "node_modules/esbuild-linux-32": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.2.tgz",
-      "integrity": "sha512-8VxnNEyeUbiGflTKcuVc5JEPTqXfsx2O6ABwUbfS1Hp26lYPRPC7pKQK5Dxa0MBejGc50jy7YZae3EGQUQ8EkQ==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
     "node_modules/esbuild-linux-64": {
       "version": "0.14.2",
       "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.2.tgz",
@@ -1424,136 +1459,6 @@
         "linux"
       ]
     },
-    "node_modules/esbuild-linux-arm": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.2.tgz",
-      "integrity": "sha512-PaylahvMHhH8YMfJPMKEqi64qA0Su+d4FNfHKvlKes/2dUe4QxgbwXT9oLVgy8iJdcFMrO7By4R8fS8S0p8aVQ==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/esbuild-linux-arm64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.2.tgz",
-      "integrity": "sha512-RlIVp0RwJrdtasDF1vTFueLYZ8WuFzxoQ1OoRFZOTyJHCGCNgh7xJIC34gd7B7+RT0CzLBB4LcM5n0LS+hIoww==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/esbuild-linux-mips64le": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.2.tgz",
-      "integrity": "sha512-Fdwrq2roFnO5oetIiUQQueZ3+5soCxBSJswg3MvYaXDomj47BN6oAWMZgLrFh1oVrtWrxSDLCJBenYdbm2s+qQ==",
-      "cpu": [
-        "mips64el"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/esbuild-linux-ppc64le": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.2.tgz",
-      "integrity": "sha512-vxptskw8JfCDD9QqpRO0XnsM1osuWeRjPaXX1TwdveLogYsbdFtcuiuK/4FxGiNMUr1ojtnCS2rMPbY8puc5NA==",
-      "cpu": [
-        "ppc64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/esbuild-netbsd-64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.2.tgz",
-      "integrity": "sha512-I8+LzYK5iSNpspS9eCV9sW67Rj8FgMHimGri4mKiGAmN0pNfx+hFX146rYtzGtewuxKtTsPywWteHx+hPRLDsw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "netbsd"
-      ]
-    },
-    "node_modules/esbuild-openbsd-64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.2.tgz",
-      "integrity": "sha512-120HgMe9elidWUvM2E6mMf0csrGwx8sYDqUIJugyMy1oHm+/nT08bTAVXuwYG/rkMIqsEO9AlMxuYnwR6En/3Q==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "openbsd"
-      ]
-    },
-    "node_modules/esbuild-sunos-64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.2.tgz",
-      "integrity": "sha512-Q3xcf9Uyfra9UuCFxoLixVvdigo0daZaKJ97TL2KNA4bxRUPK18wwGUk3AxvgDQZpRmg82w9PnkaNYo7a+24ow==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "sunos"
-      ]
-    },
-    "node_modules/esbuild-windows-32": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.2.tgz",
-      "integrity": "sha512-TW7O49tPsrq+N1sW8mb3m24j/iDGa4xzAZH4wHWwoIzgtZAYPKC0hpIhufRRG/LA30bdMChO9pjJZ5mtcybtBQ==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/esbuild-windows-64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.2.tgz",
-      "integrity": "sha512-Rym6ViMNmi1E2QuQMWy0AFAfdY0wGwZD73BnzlsQBX5hZBuy/L+Speh7ucUZ16gwsrMM9v86icZUDrSN/lNBKg==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/esbuild-windows-arm64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.2.tgz",
-      "integrity": "sha512-ZrLbhr0vX5Em/P1faMnHucjVVWPS+m3tktAtz93WkMZLmbRJevhiW1y4CbulBd2z0MEdXZ6emDa1zFHq5O5bSA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
     "node_modules/escalade": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -1692,6 +1597,18 @@
         "node": "^10.12.0 || >=12.0.0"
       }
     },
+    "node_modules/espree/node_modules/acorn": {
+      "version": "7.4.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+      "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+      "dev": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/esprima": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
@@ -1797,6 +1714,11 @@
         "url": "https://github.com/sindresorhus/execa?sponsor=1"
       }
     },
+    "node_modules/exenv-es6": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/exenv-es6/-/exenv-es6-1.1.1.tgz",
+      "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ=="
+    },
     "node_modules/expand-template": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
@@ -1839,6 +1761,28 @@
         "pend": "~1.2.0"
       }
     },
+    "node_modules/fetch-blob": {
+      "version": "3.1.5",
+      "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz",
+      "integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/jimmywarting"
+        },
+        {
+          "type": "paypal",
+          "url": "https://paypal.me/jimmywarting"
+        }
+      ],
+      "dependencies": {
+        "node-domexception": "^1.0.0",
+        "web-streams-polyfill": "^3.0.3"
+      },
+      "engines": {
+        "node": "^12.20 || >= 14.13"
+      }
+    },
     "node_modules/file-entry-cache": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -1883,6 +1827,31 @@
       "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
       "dev": true
     },
+    "node_modules/form-data": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
+      "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+      "dev": true,
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/formdata-polyfill": {
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+      "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+      "dependencies": {
+        "fetch-blob": "^3.1.2"
+      },
+      "engines": {
+        "node": ">=12.20.0"
+      }
+    },
     "node_modules/fs-constants": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -2241,6 +2210,11 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/inversify": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/inversify/-/inversify-6.0.1.tgz",
+      "integrity": "sha512-B3ex30927698TJENHR++8FfEaJGqoWOgI6ZY5Ht/nLUsFCwHn6akbwtnUAPCgUepAnTpe2qHxhDNjoKLyz6rgQ=="
+    },
     "node_modules/is-core-module": {
       "version": "2.8.0",
       "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz",
@@ -2369,8 +2343,7 @@
     "node_modules/js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
-      "dev": true
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
     },
     "node_modules/js-yaml": {
       "version": "3.14.1",
@@ -2493,6 +2466,18 @@
       "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
       "dev": true
     },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "peer": true,
+      "dependencies": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      },
+      "bin": {
+        "loose-envify": "cli.js"
+      }
+    },
     "node_modules/lru-cache": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -2687,6 +2672,41 @@
       "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==",
       "dev": true
     },
+    "node_modules/node-domexception": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+      "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/jimmywarting"
+        },
+        {
+          "type": "github",
+          "url": "https://paypal.me/jimmywarting"
+        }
+      ],
+      "engines": {
+        "node": ">=10.5.0"
+      }
+    },
+    "node_modules/node-fetch": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.3.tgz",
+      "integrity": "sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA==",
+      "dependencies": {
+        "data-uri-to-buffer": "^4.0.0",
+        "fetch-blob": "^3.1.4",
+        "formdata-polyfill": "^4.0.10"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/node-fetch"
+      }
+    },
     "node_modules/node-releases": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
@@ -2971,6 +2991,21 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/prettier": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
+      "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
+      "dev": true,
+      "bin": {
+        "prettier": "bin-prettier.js"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      },
+      "funding": {
+        "url": "https://github.com/prettier/prettier?sponsor=1"
+      }
+    },
     "node_modules/process-nextick-args": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -3053,6 +3088,18 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/react": {
+      "version": "18.0.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-18.0.0.tgz",
+      "integrity": "sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A==",
+      "peer": true,
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/read": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
@@ -3098,6 +3145,11 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/reflect-metadata": {
+      "version": "0.1.13",
+      "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
+      "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
+    },
     "node_modules/regexpp": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
@@ -3498,6 +3550,11 @@
         "node": ">=4"
       }
     },
+    "node_modules/tabbable": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.2.1.tgz",
+      "integrity": "sha512-40pEZ2mhjaZzK0BnI+QGNjJO8UYx9pP5v7BGe17SORTO0OEuuaAwQTkAp8whcZvqon44wKFOikD+Al11K3JICQ=="
+    },
     "node_modules/table": {
       "version": "6.7.5",
       "resolved": "https://registry.npmjs.org/table/-/table-6.7.5.tgz",
@@ -3702,8 +3759,7 @@
     "node_modules/tslib": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-      "dev": true
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
     },
     "node_modules/tsutils": {
       "version": "3.21.0",
@@ -3916,6 +3972,14 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/web-streams-polyfill": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
+      "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
     "node_modules/webpack": {
       "version": "5.65.0",
       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.65.0.tgz",
@@ -4037,18 +4101,6 @@
         "node": ">=10.13.0"
       }
     },
-    "node_modules/webpack/node_modules/acorn": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz",
-      "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==",
-      "dev": true,
-      "bin": {
-        "acorn": "bin/acorn"
-      },
-      "engines": {
-        "node": ">=0.4.0"
-      }
-    },
     "node_modules/which": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -4250,6 +4302,45 @@
       "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
       "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="
     },
+    "@microsoft/fast-element": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.9.0.tgz",
+      "integrity": "sha512-VxDwoLEEQRRxXfLOB5Llzha9WWvjVJHZFHNE1mn2s1QV5IAkOhrYrhFe9vzv50eI6CqazChDyHDy1km3+rn0wg=="
+    },
+    "@microsoft/fast-foundation": {
+      "version": "2.41.1",
+      "resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.41.1.tgz",
+      "integrity": "sha512-q3t43CVsYrLeBbmiY6+GvV0/uUqnz7anmagD8qMBpHIpC6XpEERGBNbyC6AxQrRoBrYBFFhBJRTPn7vCqi4iPA==",
+      "requires": {
+        "@microsoft/fast-element": "^1.9.0",
+        "@microsoft/fast-web-utilities": "^5.2.0",
+        "tabbable": "^5.2.0",
+        "tslib": "^1.13.0"
+      }
+    },
+    "@microsoft/fast-react-wrapper": {
+      "version": "0.1.48",
+      "resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.1.48.tgz",
+      "integrity": "sha512-9NvEjru9Kn5ZKjomAMX6v+eF0DR+eDkxKDwDfi+Wb73kTbrNzcnmlwd4diN15ygH97kldgj2+lpvI4CKLQQWLg==",
+      "requires": {
+        "@microsoft/fast-element": "^1.9.0",
+        "@microsoft/fast-foundation": "^2.41.1"
+      }
+    },
+    "@microsoft/fast-web-utilities": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/@microsoft/fast-web-utilities/-/fast-web-utilities-5.2.0.tgz",
+      "integrity": "sha512-Ri5FR4MotdAPKeFLo/yhZu6/xMVo/H9OXcGaC/qiQo+U0zY8cLPCViGmHYYgxpCYviS4VpgqPL5dslTE6wg9Yg==",
+      "requires": {
+        "exenv-es6": "^1.1.1"
+      }
+    },
+    "@types/crypto-js": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz",
+      "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==",
+      "dev": true
+    },
     "@types/eslint": {
       "version": "8.2.1",
       "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.1.tgz",
@@ -4300,6 +4391,16 @@
       "integrity": "sha512-BzcaRsnFuznzOItW1WpQrDHM7plAa7GIDMZ6b5pnMbkqEtM/6WCOhvZar39oeMQP79gwvFUWjjptE7/KGcNqFg==",
       "dev": true
     },
+    "@types/node-fetch": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz",
+      "integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*",
+        "form-data": "^3.0.0"
+      }
+    },
     "@types/vscode": {
       "version": "1.62.0",
       "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.62.0.tgz",
@@ -4377,6 +4478,16 @@
         "eslint-visitor-keys": "^1.1.0"
       }
     },
+    "@vscode/webview-ui-toolkit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.0.0.tgz",
+      "integrity": "sha512-/qaHYZXqvIKkao54b7bLzyNH8BC+X4rBmTUx1MvcIiCjqRMxml0BCpqJhnDpfrCb0IOxXRO8cAy1eB5ayzQfBA==",
+      "requires": {
+        "@microsoft/fast-element": "^1.6.2",
+        "@microsoft/fast-foundation": "^2.38.0",
+        "@microsoft/fast-react-wrapper": "^0.1.18"
+      }
+    },
     "@webassemblyjs/ast": {
       "version": "1.11.1",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
@@ -4559,9 +4670,9 @@
       "dev": true
     },
     "acorn": {
-      "version": "7.4.1",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
-      "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
+      "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
       "dev": true
     },
     "acorn-import-assertions": {
@@ -4660,6 +4771,12 @@
       "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz",
       "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g=="
     },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+      "dev": true
+    },
     "azure-devops-node-api": {
       "version": "11.1.0",
       "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.1.0.tgz",
@@ -4916,6 +5033,15 @@
       "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
       "dev": true
     },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dev": true,
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
     "commander": {
       "version": "2.20.3",
       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
@@ -4957,6 +5083,11 @@
         "which": "^2.0.1"
       }
     },
+    "crypto-js": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
+      "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
+    },
     "css-select": {
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz",
@@ -4976,6 +5107,11 @@
       "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==",
       "dev": true
     },
+    "data-uri-to-buffer": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
+      "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA=="
+    },
     "date-fns": {
       "version": "2.27.0",
       "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.27.0.tgz",
@@ -5010,6 +5146,12 @@
       "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
       "dev": true
     },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+      "dev": true
+    },
     "delegates": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@@ -5157,48 +5299,6 @@
         "esbuild-windows-arm64": "0.14.2"
       }
     },
-    "esbuild-android-arm64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.2.tgz",
-      "integrity": "sha512-hEixaKMN3XXCkoe+0WcexO4CcBVU5DCSUT+7P8JZiWZCbAjSkc9b6Yz2X5DSfQmRCtI/cQRU6TfMYrMQ5NBfdw==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-darwin-64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.2.tgz",
-      "integrity": "sha512-Uq8t0cbJQkxkQdbUfOl2wZqZ/AtLZjvJulR1HHnc96UgyzG9YlCLSDMiqjM+NANEy7/zzvwKJsy3iNC9wwqLJA==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-darwin-arm64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.2.tgz",
-      "integrity": "sha512-619MSa17sr7YCIrUj88KzQu2ESA4jKYtIYfLU/smX6qNgxQt3Y/gzM4s6sgJ4fPQzirvmXgcHv1ZNQAs/Xh48A==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-freebsd-64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.2.tgz",
-      "integrity": "sha512-aP6FE/ZsChZpUV6F3HE3x1Pz0paoYXycJ7oLt06g0G9dhJKknPawXCqQg/WMyD+ldCEZfo7F1kavenPdIT/SGQ==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-freebsd-arm64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.2.tgz",
-      "integrity": "sha512-LSm98WTb1QIhyS83+Po0KTpZNdd2XpVpI9ua5rLWqKWbKeNRFwOsjeiuwBaRNc+O32s9oC2ZMefETxHBV6VNkQ==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-linux-32": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.2.tgz",
-      "integrity": "sha512-8VxnNEyeUbiGflTKcuVc5JEPTqXfsx2O6ABwUbfS1Hp26lYPRPC7pKQK5Dxa0MBejGc50jy7YZae3EGQUQ8EkQ==",
-      "dev": true,
-      "optional": true
-    },
     "esbuild-linux-64": {
       "version": "0.14.2",
       "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.2.tgz",
@@ -5206,76 +5306,6 @@
       "dev": true,
       "optional": true
     },
-    "esbuild-linux-arm": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.2.tgz",
-      "integrity": "sha512-PaylahvMHhH8YMfJPMKEqi64qA0Su+d4FNfHKvlKes/2dUe4QxgbwXT9oLVgy8iJdcFMrO7By4R8fS8S0p8aVQ==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-linux-arm64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.2.tgz",
-      "integrity": "sha512-RlIVp0RwJrdtasDF1vTFueLYZ8WuFzxoQ1OoRFZOTyJHCGCNgh7xJIC34gd7B7+RT0CzLBB4LcM5n0LS+hIoww==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-linux-mips64le": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.2.tgz",
-      "integrity": "sha512-Fdwrq2roFnO5oetIiUQQueZ3+5soCxBSJswg3MvYaXDomj47BN6oAWMZgLrFh1oVrtWrxSDLCJBenYdbm2s+qQ==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-linux-ppc64le": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.2.tgz",
-      "integrity": "sha512-vxptskw8JfCDD9QqpRO0XnsM1osuWeRjPaXX1TwdveLogYsbdFtcuiuK/4FxGiNMUr1ojtnCS2rMPbY8puc5NA==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-netbsd-64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.2.tgz",
-      "integrity": "sha512-I8+LzYK5iSNpspS9eCV9sW67Rj8FgMHimGri4mKiGAmN0pNfx+hFX146rYtzGtewuxKtTsPywWteHx+hPRLDsw==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-openbsd-64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.2.tgz",
-      "integrity": "sha512-120HgMe9elidWUvM2E6mMf0csrGwx8sYDqUIJugyMy1oHm+/nT08bTAVXuwYG/rkMIqsEO9AlMxuYnwR6En/3Q==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-sunos-64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.2.tgz",
-      "integrity": "sha512-Q3xcf9Uyfra9UuCFxoLixVvdigo0daZaKJ97TL2KNA4bxRUPK18wwGUk3AxvgDQZpRmg82w9PnkaNYo7a+24ow==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-windows-32": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.2.tgz",
-      "integrity": "sha512-TW7O49tPsrq+N1sW8mb3m24j/iDGa4xzAZH4wHWwoIzgtZAYPKC0hpIhufRRG/LA30bdMChO9pjJZ5mtcybtBQ==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-windows-64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.2.tgz",
-      "integrity": "sha512-Rym6ViMNmi1E2QuQMWy0AFAfdY0wGwZD73BnzlsQBX5hZBuy/L+Speh7ucUZ16gwsrMM9v86icZUDrSN/lNBKg==",
-      "dev": true,
-      "optional": true
-    },
-    "esbuild-windows-arm64": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.2.tgz",
-      "integrity": "sha512-ZrLbhr0vX5Em/P1faMnHucjVVWPS+m3tktAtz93WkMZLmbRJevhiW1y4CbulBd2z0MEdXZ6emDa1zFHq5O5bSA==",
-      "dev": true,
-      "optional": true
-    },
     "escalade": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -5378,6 +5408,14 @@
         "acorn": "^7.4.0",
         "acorn-jsx": "^5.3.1",
         "eslint-visitor-keys": "^1.3.0"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "7.4.1",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+          "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+          "dev": true
+        }
       }
     },
     "esprima": {
@@ -5455,6 +5493,11 @@
         "strip-final-newline": "^2.0.0"
       }
     },
+    "exenv-es6": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/exenv-es6/-/exenv-es6-1.1.1.tgz",
+      "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ=="
+    },
     "expand-template": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
@@ -5494,6 +5537,15 @@
         "pend": "~1.2.0"
       }
     },
+    "fetch-blob": {
+      "version": "3.1.5",
+      "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz",
+      "integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==",
+      "requires": {
+        "node-domexception": "^1.0.0",
+        "web-streams-polyfill": "^3.0.3"
+      }
+    },
     "file-entry-cache": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -5529,6 +5581,25 @@
       "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
       "dev": true
     },
+    "form-data": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
+      "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+      "dev": true,
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      }
+    },
+    "formdata-polyfill": {
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+      "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+      "requires": {
+        "fetch-blob": "^3.1.2"
+      }
+    },
     "fs-constants": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -5793,6 +5864,11 @@
       "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
       "dev": true
     },
+    "inversify": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/inversify/-/inversify-6.0.1.tgz",
+      "integrity": "sha512-B3ex30927698TJENHR++8FfEaJGqoWOgI6ZY5Ht/nLUsFCwHn6akbwtnUAPCgUepAnTpe2qHxhDNjoKLyz6rgQ=="
+    },
     "is-core-module": {
       "version": "2.8.0",
       "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz",
@@ -5887,8 +5963,7 @@
     "js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
-      "dev": true
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
     },
     "js-yaml": {
       "version": "3.14.1",
@@ -5992,6 +6067,15 @@
       "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
       "dev": true
     },
+    "loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "peer": true,
+      "requires": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      }
+    },
     "lru-cache": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -6151,6 +6235,21 @@
       "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==",
       "dev": true
     },
+    "node-domexception": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+      "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
+    },
+    "node-fetch": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.3.tgz",
+      "integrity": "sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA==",
+      "requires": {
+        "data-uri-to-buffer": "^4.0.0",
+        "fetch-blob": "^3.1.4",
+        "formdata-polyfill": "^4.0.10"
+      }
+    },
     "node-releases": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
@@ -6374,6 +6473,12 @@
       "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
       "dev": true
     },
+    "prettier": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
+      "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
+      "dev": true
+    },
     "process-nextick-args": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -6440,6 +6545,15 @@
         }
       }
     },
+    "react": {
+      "version": "18.0.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-18.0.0.tgz",
+      "integrity": "sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A==",
+      "peer": true,
+      "requires": {
+        "loose-envify": "^1.1.0"
+      }
+    },
     "read": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
@@ -6481,6 +6595,11 @@
         "resolve": "^1.9.0"
       }
     },
+    "reflect-metadata": {
+      "version": "0.1.13",
+      "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
+      "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
+    },
     "regexpp": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
@@ -6767,6 +6886,11 @@
         "has-flag": "^3.0.0"
       }
     },
+    "tabbable": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.2.1.tgz",
+      "integrity": "sha512-40pEZ2mhjaZzK0BnI+QGNjJO8UYx9pP5v7BGe17SORTO0OEuuaAwQTkAp8whcZvqon44wKFOikD+Al11K3JICQ=="
+    },
     "table": {
       "version": "6.7.5",
       "resolved": "https://registry.npmjs.org/table/-/table-6.7.5.tgz",
@@ -6908,8 +7032,7 @@
     "tslib": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-      "dev": true
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
     },
     "tsutils": {
       "version": "3.21.0",
@@ -7075,6 +7198,11 @@
         "graceful-fs": "^4.1.2"
       }
     },
+    "web-streams-polyfill": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
+      "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q=="
+    },
     "webpack": {
       "version": "5.65.0",
       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.65.0.tgz",
@@ -7105,14 +7233,6 @@
         "terser-webpack-plugin": "^5.1.3",
         "watchpack": "^2.3.1",
         "webpack-sources": "^3.2.2"
-      },
-      "dependencies": {
-        "acorn": {
-          "version": "8.6.0",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz",
-          "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==",
-          "dev": true
-        }
       }
     },
     "webpack-cli": {
diff --git a/deadlock-plugins/deadlock-extension/package.json b/deadlock-plugins/deadlock-extension/package.json
index 984302eae3d6e7e54653d9265aaea3b6f79a98e6..69aa8ecd31ee4a426c3aee800ade330a3baf4327 100644
--- a/deadlock-plugins/deadlock-extension/package.json
+++ b/deadlock-plugins/deadlock-extension/package.json
@@ -4,7 +4,7 @@
   "version": "0.0.2",
   "publisher": "Deadlock",
   "engines": {
-    "vscode": "^1.51.0"
+    "vscode": "^1.66.0"
   },
   "categories": [
     "Other"
@@ -27,6 +27,21 @@
         "command": "deadlock.openBriefing",
         "title": "Open Briefing",
         "category": "Deadlock Coding"
+      },
+      {
+        "command": "deadlock.openQuickSetup",
+        "title": "Open Deadlock quick setup page",
+        "category": "Deadlock Coding"
+      },
+      {
+        "command": "deadlock.chooseMissionWorkdir",
+        "title": "Choose mission workdir",
+        "category": "Deadlock Coding"
+      },
+      {
+        "command": "deadlock.clear",
+        "title": "Clear cache",
+        "category": "Deadlock Coding"
       }
     ],
     "viewsContainers": {
@@ -45,11 +60,27 @@
           "name": "Explorer",
           "icon": "media/dep.svg",
           "contextualTitle": "Deadlock"
+        },
+        {
+          "id": "help",
+          "name": "Help",
+          "visibility": "collapsed"
         }
       ]
     },
+    "viewsWelcome": [
+      {
+        "view": "help",
+        "contents": "[Quick Setup](command:deadlock.openQuickSetup)\n"
+      }
+    ],
     "menus": {
-      "view/title": [],
+      "view/title": [
+        {
+          "command": "deadlock.chooseMissionWorkdir",
+          "when": "view == deadlockPanel"
+        }
+      ],
       "view/item/context": []
     }
   },
@@ -65,19 +96,27 @@
     "watch": "tsc -w -p ./"
   },
   "dependencies": {
+    "@vscode/webview-ui-toolkit": "^1.0.0",
     "async": "^3.2.2",
+    "crypto-js": "^4.1.1",
     "date-fns": "^2.27.0",
+    "inversify": "^6.0.1",
     "marked": "^4.0.6",
+    "node-fetch": "^3.2.3",
+    "reflect-metadata": "^0.1.13",
     "simple-git": "^2.48.0"
   },
   "devDependencies": {
+    "@types/crypto-js": "^4.1.1",
     "@types/marked": "^4.0.1",
     "@types/node": "^12.20.47",
+    "@types/node-fetch": "^2.6.1",
     "@types/vscode": "^1.51.0",
     "@typescript-eslint/eslint-plugin": "^3.10.1",
     "@typescript-eslint/parser": "^3.10.1",
     "esbuild": "^0.14.2",
     "eslint": "^7.32.0",
+    "prettier": "2.6.2",
     "terser-webpack-plugin": "^5.2.5",
     "ts-node": "^9.1.1",
     "typescript": "^3.9.10",
diff --git a/deadlock-plugins/deadlock-extension/resources/js/gettingStartedView.js b/deadlock-plugins/deadlock-extension/resources/js/gettingStartedView.js
new file mode 100644
index 0000000000000000000000000000000000000000..363a55f54e7adbcc2c6d4ca27ec46a730a2e4b81
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/resources/js/gettingStartedView.js
@@ -0,0 +1,14 @@
+// This file is used by gettingStarted via a <script> tag
+const vscode = acquireVsCodeApi();
+
+function launchChooseMissionWorkdirAction() {
+  vscode.postMessage({
+    command: 'launchChooseMissionWorkdirAction',
+  });
+}
+
+function openAuthenticationPageAction() {
+  vscode.postMessage({
+    command: 'openAuthenticationPageAction',
+  });
+}
diff --git a/deadlock-plugins/deadlock-extension/resources/styles/gettingStartedView.css b/deadlock-plugins/deadlock-extension/resources/styles/gettingStartedView.css
new file mode 100644
index 0000000000000000000000000000000000000000..5ae4f6fcf2fff5cd84531b5ab0e3f00fa46cf26f
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/resources/styles/gettingStartedView.css
@@ -0,0 +1,34 @@
+/* This file is used by gettingStarted via a <style> tag */
+
+.deadlock-getting-started-card-container {
+  display: flex;
+  justify-content: center;
+  flex-direction: column;
+  align-items: center;
+}
+.deadlock-getting-started-card-container .deadlock-getting-started-card {
+  display: flex;
+  align-items: start;
+  min-height: 5em;
+  margin: 0.5em;
+  width: 50vw;
+  min-width: 20vw;
+}
+
+.deadlock-getting-started-card-container .deadlock-getting-started-card .card-checkbox {
+  width: 15%;
+}
+
+.deadlock-getting-started-card-container .deadlock-getting-started-card .card-body {
+  width: 85%;
+}
+
+.deadlock-getting-started-card-container .deadlock-getting-started-card .card-body .card-title {
+  font-weight: bold;
+  font-size: 1.5em;
+  margin-bottom: 0.2em;
+}
+
+.deadlock-getting-started-card-container .deadlock-getting-started-card .card-body .card-description {
+  margin-bottom: 0.5em;
+}
diff --git a/deadlock-plugins/deadlock-extension/src/config.prod.ts b/deadlock-plugins/deadlock-extension/src/config.prod.ts
new file mode 100644
index 0000000000000000000000000000000000000000..46ee6889446bcf153f9619125c3ab2bd4d753d65
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/config.prod.ts
@@ -0,0 +1,5 @@
+export const KEYCLOAK_DEVICE_AUTH_URL =
+  'https://auth.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/auth/device';
+export const KEYCLOAK_TOKEN_CREATE_URL = 'https://auth.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/token';
+export const KEYCLOAK_USER_INFO_URL = 'https://auth.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo';
+export const REJECT_UNAUTHORIZED = true;
diff --git a/deadlock-plugins/deadlock-extension/src/config.staging.ts b/deadlock-plugins/deadlock-extension/src/config.staging.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e8ddb5e90e01601adc2b4f79ee07d241551c535b
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/config.staging.ts
@@ -0,0 +1,7 @@
+export const KEYCLOAK_DEVICE_AUTH_URL =
+  'https://auth.staging.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/auth/device';
+export const KEYCLOAK_TOKEN_CREATE_URL =
+  'https://auth.staging.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/token';
+export const KEYCLOAK_USER_INFO_URL =
+  'https://auth.staging.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo';
+export const REJECT_UNAUTHORIZED = true;
diff --git a/deadlock-plugins/deadlock-extension/src/config.ts b/deadlock-plugins/deadlock-extension/src/config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a53b3ab5bba69c5e9d7ed0ef82f7662a8227933f
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/config.ts
@@ -0,0 +1,7 @@
+export const KEYCLOAK_DEVICE_AUTH_URL =
+  'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/auth/device';
+export const KEYCLOAK_TOKEN_CREATE_URL =
+  'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/token';
+export const KEYCLOAK_USER_INFO_URL =
+  'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo';
+export const REJECT_UNAUTHORIZED = false;
diff --git a/deadlock-plugins/deadlock-extension/src/core/commandHandler.ts b/deadlock-plugins/deadlock-extension/src/core/commandHandler.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9dcdc452f031924a31a33349fd1ce8566ffedee0
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/core/commandHandler.ts
@@ -0,0 +1,26 @@
+import { commands } from 'vscode';
+import { Command } from '../theia/command';
+import Controller from './controller';
+import ExtensionStore from './extensionStore';
+
+export class CommandHandler {
+  private extensionStore: ExtensionStore;
+  constructor(private controller: Controller) {
+    this.extensionStore = ExtensionStore.getInstance();
+    this.initCommandHandler();
+  }
+
+  initCommandHandler() {
+    commands.registerCommand(
+      CHOOSE_MISSION_WORKDIR_COMMAND.cmd,
+      this.controller.chooseMissionWorkdir.bind(this.controller),
+    );
+    commands.registerCommand(AUTHENTICATE_COMMAND.cmd, this.controller.authenticate.bind(this.controller));
+    commands.registerCommand(CLEAR_COMMAND.cmd, this.controller.clear.bind(this.controller));
+  }
+}
+
+export const CHOOSE_MISSION_WORKDIR_COMMAND = new Command('Choose mission workdir', 'deadlock.chooseMissionWorkdir');
+export const AUTHENTICATE_COMMAND = new Command('Authenticate', 'deadlock.authenticate');
+export const CLEAR_COMMAND = new Command('Clear', 'deadlock.clear');
+export const OPEN_URL_IN_BROWSER_COMMAND = new Command('Open url in browser', 'vscode.open');
diff --git a/deadlock-plugins/deadlock-extension/src/core/config.ts b/deadlock-plugins/deadlock-extension/src/core/config.ts
index ff72a36003b87cf4bceb70f15eb013e319a29219..629d89ddb3183a72bfab6d8bb97bd6e15fc91049 100644
--- a/deadlock-plugins/deadlock-extension/src/core/config.ts
+++ b/deadlock-plugins/deadlock-extension/src/core/config.ts
@@ -7,41 +7,22 @@ const onContainer = homeDir.includes('theia') || homeDir.includes('root');
 
 const deadlockExtensionPath = path.join(homeDir, 'deadlock-extension');
 
-export const PROJECT_SRC_PATH = onContainer
-  ? '/project'
-  : path.join(homeDir, 'deadlock-extension', '/project');
+export const PROJECT_SRC_PATH = onContainer ? '/project' : path.join(homeDir, 'deadlock-extension', '/project');
 
 export const PROJECT_THEIA_PATH = onContainer
   ? path.join('/home/project')
   : path.join(deadlockExtensionPath, 'project-theia');
 
-export const DOCS_PATH = path.join(
-  path.join(onContainer ? '/home/theia' : deadlockExtensionPath),
-  'docs'
-);
+export const DOCS_PATH = path.join(path.join(onContainer ? '/home/theia' : deadlockExtensionPath), 'docs');
 
-export const CONFIG_PATH = onContainer
-  ? '/home/config/'
-  : path.join(deadlockExtensionPath, 'config');
+export const CONFIG_PATH = onContainer ? '/home/config/' : path.join(deadlockExtensionPath, 'config');
 
-export const USER_CHALLENGE_PATH = path.join(
-  CONFIG_PATH,
-  'user-challenge.json'
-);
+export const USER_CHALLENGE_PATH = path.join(CONFIG_PATH, 'user-challenge.json');
 
 export const BRIEFING_FILE_NAME = 'briefing.md';
 
-export const ENV_FILE_PATH = path.join(
-  PROJECT_THEIA_PATH,
-  '/.env'
-);
+export const ENV_FILE_PATH = path.join(PROJECT_THEIA_PATH, '/.env');
 
-export const BASHRC_PATH = path.join(
-  homeDir,
-  '/.bashrc'
-);
+export const BASHRC_PATH = path.join(homeDir, '/.bashrc');
 
-export const SERVICES_PATHS_PATH = path.join(
-  PROJECT_THEIA_PATH,
-  '/paths.json'
-);
+export const SERVICES_PATHS_PATH = path.join(PROJECT_THEIA_PATH, '/paths.json');
diff --git a/deadlock-plugins/deadlock-extension/src/core/controller.ts b/deadlock-plugins/deadlock-extension/src/core/controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7d7d3bb15fec7d1d56978bac11aa52d28b86716f
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/core/controller.ts
@@ -0,0 +1,109 @@
+import * as vscode from 'vscode';
+import { KEYCLOAK_DEVICE_AUTH_URL, KEYCLOAK_TOKEN_CREATE_URL, KEYCLOAK_USER_INFO_URL } from '../config';
+import { OPEN_QUICK_SETUP_COMMAND } from '../theia/command';
+import BriefingView from '../view/briefingView';
+import QuickSetupView from '../view/quickSetupView';
+import { CHOOSE_MISSION_WORKDIR_COMMAND, CommandHandler, OPEN_URL_IN_BROWSER_COMMAND } from './commandHandler';
+import ExtensionStore from './extensionStore';
+import KeycloakOAuth2DeviceFlowConnection from './keycloakOAuth2DeviceFlowConnection';
+
+export default class Controller {
+  public connection: KeycloakOAuth2DeviceFlowConnection;
+  private commandHandler: CommandHandler;
+  private briefingView: BriefingView;
+  private quickSetupView: QuickSetupView;
+  private extensionStore: ExtensionStore;
+
+  constructor(private context: vscode.ExtensionContext) {
+    this.extensionStore = ExtensionStore.getInstance(context);
+    this.briefingView = new BriefingView();
+    this.quickSetupView = new QuickSetupView(context.extensionUri);
+    this.commandHandler = new CommandHandler(this);
+    this.connection = new KeycloakOAuth2DeviceFlowConnection(
+      KEYCLOAK_DEVICE_AUTH_URL,
+      KEYCLOAK_TOKEN_CREATE_URL,
+      KEYCLOAK_USER_INFO_URL,
+    );
+
+    this.init();
+  }
+  private async init() {
+    const that = this;
+    vscode.window.registerUriHandler({
+      handleUri(uri: vscode.Uri) {
+        const queryParams: URLSearchParams = new URLSearchParams(uri.query);
+        const action: string | null = queryParams.get('action');
+
+        switch (action) {
+          case 'open-challenge':
+            that.launchMission(queryParams.get('missionId'));
+            break;
+
+          default:
+            vscode.window.showInformationMessage('Aucune action trouvée!');
+        }
+      },
+    });
+
+    const exensionStorage = ExtensionStore.getInstance();
+    this.quickSetupView.isAlreadyConnected = !!(await exensionStorage.getAccessToken());
+  }
+  async chooseMissionWorkdir() {
+    const actualMissionWorkDir = this.extensionStore.getMissionWorkdir();
+
+    const folderUri = await vscode.window.showOpenDialog({
+      defaultUri: actualMissionWorkDir ? vscode.Uri.file(actualMissionWorkDir) : undefined,
+      canSelectFolders: true,
+      canSelectFiles: false,
+      title: 'Choisis le dossier qui contiendra tes missions',
+    });
+
+    if (!folderUri) {
+      if (this.extensionStore.getMissionWorkdir()) {
+        return;
+      }
+      this.chooseMissionWorkdir();
+    } else {
+      this.extensionStore.setMissionWorkdir(folderUri[0].path);
+    }
+  }
+  public async clear() {
+    const exensionStorage = ExtensionStore.getInstance();
+    await exensionStorage.clear();
+    this.quickSetupView.isAlreadyConnected = false;
+  }
+
+  public async authenticate() {
+    // WARN generate a new device code every time student clicks on log in button. Should I keep it ?\
+    // The answer might be 'yes' because when the student is already authenticated, the log in button should be disabled.
+    await this.connection.registerDevice();
+    const tokens = await this.connection.getToken({ openLink: Controller.openBrowserWithUrl });
+    await this.extensionStore.setAccessToken(tokens.accessToken);
+    await this.extensionStore.setRefreshToken(tokens.refreshToken);
+    this.quickSetupView.isAlreadyConnected = true;
+  }
+  public static openBrowserWithUrl(url: string) {
+    vscode.commands.executeCommand(OPEN_URL_IN_BROWSER_COMMAND.cmd, vscode.Uri.parse(url));
+  }
+
+  public async launchMission(missionId: string | null) {
+    if (missionId) {
+      vscode.window.showInformationMessage(`vous lancez la mission ${missionId}`);
+      const hadMissionWorkdir = this.extensionStore.getMissionWorkdir() !== undefined;
+      if (!hadMissionWorkdir) {
+        await vscode.commands.executeCommand(CHOOSE_MISSION_WORKDIR_COMMAND.cmd);
+      }
+
+      const hadBeenConnected = (await this.extensionStore.getAccessToken()) !== undefined;
+
+      if (!hadBeenConnected) {
+        this.authenticate();
+        vscode.window.showInformationMessage('Nouvelle connexion validée');
+      } else {
+        vscode.window.showInformationMessage('Déjà connecté: session récupérée');
+      }
+
+      vscode.commands.executeCommand(OPEN_QUICK_SETUP_COMMAND.cmd);
+    }
+  }
+}
diff --git a/deadlock-plugins/deadlock-extension/src/core/extensionStore.ts b/deadlock-plugins/deadlock-extension/src/core/extensionStore.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5d4497c98e99a9929c8e88368d2c8a445bb602cc
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/core/extensionStore.ts
@@ -0,0 +1,82 @@
+import { ExtensionContext, Memento, SecretStorage, window } from 'vscode';
+import { log } from '../recorder/utils';
+
+export type GlobalStorageType = Memento & { setKeysForSync(keys: readonly string[]): void };
+
+export default class ExtensionStore {
+  private static instance: ExtensionStore;
+
+  private globalStorage: GlobalStorageType;
+  private secretStorage: SecretStorage;
+
+  private constructor(context: ExtensionContext) {
+    this.globalStorage = context.globalState;
+    this.secretStorage = context.secrets;
+  }
+
+  public async clear() {
+    if (this.globalStorage.keys()) {
+      for (let key of this.globalStorage.keys()) {
+        this.globalStorage.update(key, undefined);
+      }
+    }
+    if (await this.secretStorage.get(StoreKey.AccessTokenKey)) this.secretStorage.delete(StoreKey.AccessTokenKey);
+    if (await this.secretStorage.get(StoreKey.RefreshTokenKey)) this.secretStorage.delete(StoreKey.RefreshTokenKey);
+  }
+
+  public static getInstance(context?: ExtensionContext): ExtensionStore {
+    if (!ExtensionStore.instance) {
+      if (!context) throw new Error('ExtensionStore should be initiate with a storage first time');
+      ExtensionStore.instance = new ExtensionStore(context);
+    }
+
+    return ExtensionStore.instance;
+  }
+
+  getMissionWorkdir(): string | undefined {
+    return this.globalStorage.get<string>(StoreKey.MissionWorkdirKey);
+  }
+
+  setMissionWorkdir(path: string) {
+    this.globalStorage.update(StoreKey.MissionWorkdirKey, path);
+    window.showInformationMessage(`Nouveau dossier de stockage des missions: ${path}`);
+  }
+
+  public async getAccessToken() {
+    return this.readSecret(StoreKey.AccessTokenKey);
+  }
+
+  public async getRefreshToken() {
+    return this.readSecret(StoreKey.RefreshTokenKey);
+  }
+
+  public async setAccessToken(accessToken: string) {
+    if (!accessToken) {
+      log('Attempt to store undefined access token');
+      return;
+    }
+    return this.storeSecret(StoreKey.AccessTokenKey, accessToken);
+  }
+
+  public async setRefreshToken(refreshToken: string) {
+    if (!refreshToken) {
+      log('Attempt to store undefined refresh token');
+      return;
+    }
+    return this.storeSecret(StoreKey.RefreshTokenKey, refreshToken);
+  }
+
+  private async storeSecret(key: StoreKey, value: string) {
+    this.secretStorage.store(key, value);
+  }
+
+  private async readSecret(key: StoreKey) {
+    return this.secretStorage.get(key);
+  }
+}
+
+enum StoreKey {
+  MissionWorkdirKey = 'mission-workdir-key',
+  AccessTokenKey = 'access-token-key',
+  RefreshTokenKey = 'refresh-token-key',
+}
diff --git a/deadlock-plugins/deadlock-extension/src/core/gitMission.ts b/deadlock-plugins/deadlock-extension/src/core/gitMission.ts
index aa0f9d2989970ae9729b4cac877d9fa4301fe4fe..9e739afc67856fa5f0f13e735acde4d5e2f4554a 100644
--- a/deadlock-plugins/deadlock-extension/src/core/gitMission.ts
+++ b/deadlock-plugins/deadlock-extension/src/core/gitMission.ts
@@ -1,4 +1,5 @@
 import simpleGit, { SimpleGit, SimpleGitOptions } from 'simple-git';
+import { error } from '../recorder/utils';
 import { PROJECT_SRC_PATH } from './config';
 import UserConfig from './userConfig';
 
@@ -9,103 +10,106 @@ const DEFAULT_REMOTE = 'origin';
 const DEFAULT_BRANCH = 'master';
 
 export default class GitMission {
+  private git: SimpleGit;
 
-    private git: SimpleGit;
+  constructor(private userConfig: UserConfig) {
+    const options: Partial<SimpleGitOptions> = {
+      baseDir: PROJECT_SRC_PATH,
+      binary: 'git',
+      maxConcurrentProcesses: 2,
+    };
 
-    constructor(private userConfig: UserConfig) {
-        const options: Partial<SimpleGitOptions> = {
-            baseDir: PROJECT_SRC_PATH,
-            binary: 'git',
-            maxConcurrentProcesses: 2,
-        };
+    this.git = simpleGit(options);
+  }
 
-        this.git = simpleGit(options);
-    }
-
-    async setupSshAgent() {
-        const { err, stderr, stdout } = await exec(`eval "$(ssh-agent -s)" && ssh-keyscan -p ${this.userConfig.getGiteaSshPort()} -H ${this.userConfig.getGiteaHost()} >> ~/.ssh/known_hosts`);
+  async setupSshAgent() {
+    const { err, stderr, stdout } = await exec(
+      `eval "$(ssh-agent -s)" && ssh-keyscan -p ${this.userConfig.getGiteaSshPort()} -H ${this.userConfig.getGiteaHost()} >> ~/.ssh/known_hosts`,
+    );
 
-        console.log(stdout);
-
-        if (err) {
-            console.error(stderr);
-            throw new Error(err);
-        }
+    console.log(stdout);
 
+    if (err) {
+      console.error(stderr);
+      throw new Error(err);
     }
-
-    get author() {
-
-        return this.userConfig.getUsername();
+  }
+
+  get author() {
+    return this.userConfig.getUsername();
+  }
+
+  async init() {
+    try {
+      console.log('Setup ssh agent');
+      await this.setupSshAgent();
+
+      console.log('Init Git mission..');
+
+      const remote = await this.readRemote();
+      if (remote === DEFAULT_REMOTE) {
+        return Promise.resolve(this);
+      }
+
+      const remotePath = this.getRemotePath();
+
+      await this.git.init();
+      await this.git.addRemote(DEFAULT_REMOTE, remotePath);
+      await this.git.addConfig('user.email', this.userConfig.getCurrentUserDetails().email, false, 'system');
+      await this.git.addConfig(
+        'user.name',
+        `${this.userConfig.getCurrentUserDetails().lastName} ${this.userConfig.getCurrentUserDetails().firstName}`,
+        false,
+        'system',
+      );
+
+      return Promise.resolve(this);
+    } catch (e) {
+      console.error(e);
+      return Promise.reject(e);
     }
+  }
 
-    async init() {
-        try {
-
-            console.log('Setup ssh agent');
-            await this.setupSshAgent();
-
-            console.log('Init Git mission..');
+  private getRemotePath() {
+    return `ssh://git@${this.userConfig.getGiteaHost()}:${this.userConfig.getGiteaSshPort()}/${this.userConfig.getRemoteGitUsername()}/${this.userConfig.getMissionId()}`;
+  }
 
-            const remote = await this.readRemote();
-            if (remote === DEFAULT_REMOTE) {
-                return Promise.resolve(this);
-            }
-
-            const remotePath = this.getRemotePath();
-
-            await this.git.init();
-            await this.git.addRemote(DEFAULT_REMOTE, remotePath);
-            await this.git.addConfig('user.email', this.userConfig.getCurrentUserDetails().email, false, "system");
-            await this.git.addConfig('user.name', `${this.userConfig.getCurrentUserDetails().lastName} ${this.userConfig.getCurrentUserDetails().firstName}`, false, "system");
-
-            return Promise.resolve(this);
-        } catch (e) {
-            console.error(e);
-            return Promise.reject(e);
-        }
+  async readRemote() {
+    try {
+      return ((await this.git.remote([])) || '').replace(/(\r\n|\n|\r)/gm, '');
+    } catch (e) {
+      error(e);
     }
-
-    private getRemotePath() {
-        return `ssh://git@${this.userConfig.getGiteaHost()}:${this.userConfig.getGiteaSshPort()}/${this.userConfig.getRemoteGitUsername()}/${this.userConfig.getMissionId()}`;
-    }
-
-    async readRemote() {
-        try {
-            return (await this.git.remote([]) || '').replace(/(\r\n|\n|\r)/gm, '');
-        } catch (e) {
-            // ignore it, maybe the git repo does not exist
-        }
-        return '';
-    }
-
-    fetch() {
-        return this.git.fetch(DEFAULT_REMOTE);
-    }
-
-    pull() {
-        return this.git.pull(DEFAULT_REMOTE, DEFAULT_BRANCH, { '--rebase': 'true' });
-    }
-
-    addAll() {
-        return this.git.add('.');
-    }
-
-    commit(message) {
-        return this.git.commit(message);
-    }
-
-    push() {
-        return this.git.push(DEFAULT_REMOTE, DEFAULT_BRANCH);
-    }
-
-    async isRemoteRepoExist() {
-        try {
-            const remotes = await this.git.listRemote();
-            return remotes.length !== 0;
-        } catch {
-            // error, Gitea throws Gitea: Unauthorized when not found
-            return false;
-        }
+    return '';
+  }
+
+  fetch() {
+    return this.git.fetch(DEFAULT_REMOTE);
+  }
+
+  pull() {
+    return this.git.pull(DEFAULT_REMOTE, DEFAULT_BRANCH, { '--rebase': 'true' });
+  }
+
+  addAll() {
+    return this.git.add('.');
+  }
+
+  commit(message) {
+    return this.git.commit(message);
+  }
+
+  push() {
+    return this.git.push(DEFAULT_REMOTE, DEFAULT_BRANCH);
+  }
+
+  async isRemoteRepoExist() {
+    try {
+      const remotes = await this.git.listRemote();
+      return remotes.length !== 0;
+    } catch {
+      // error, Gitea throws Gitea: Unauthorized when not found
+      return false;
     }
-}
\ No newline at end of file
+  }
+}
diff --git a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.test.ts b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..006d71388862f23244deef583bda1675242eb2e8
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.test.ts
@@ -0,0 +1,56 @@
+import { error, log } from '../recorder/utils';
+import KeycloakOAuth2DeviceFlowConnection from './keycloakOAuth2DeviceFlowConnection';
+
+/**
+ * Maybe temporary file ?\
+ * This class shows how to use KeycloakOAuth2DeviceFlowConnection.\
+ * It is currently based on dev.deadlock instance\
+ * but there are only some urls to change for further testings.
+ *
+ */
+export default class KeycloakOAuth2DeviceFlowConnectionTest {
+  public connection: KeycloakOAuth2DeviceFlowConnection;
+  constructor() {
+    log(' --- KeycloakOAuth2DeviceFlowConnectionTest --- ');
+    this.connection = new KeycloakOAuth2DeviceFlowConnection(
+      'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/auth/device',
+      'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/token',
+      'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo',
+    );
+  }
+
+  /**
+   * Simple working examples
+   */
+  public async run() {
+    const openLinkPlaceholder = (link: string) => {
+      log(`click here: ${link}`);
+    };
+
+    let tokens = await this.connection.getToken({ openLink: openLinkPlaceholder });
+    log('tokens', tokens);
+    if (!tokens?.refreshToken) {
+      log("refresh token doesn't exist");
+      return;
+    }
+
+    let refreshedTokens = await this.connection.getToken({
+      refreshToken: tokens.refreshToken,
+      openLink: openLinkPlaceholder,
+    });
+    log('refreshed tokens', refreshedTokens);
+
+    try {
+      log(`'12345' is a valid token ? ${await this.connection.tokenIsValid('12345')};`);
+      log(`'' is a valid token ? ${await this.connection.tokenIsValid('')};`);
+      log(`${tokens.accessToken} is a valid token ? ${await this.connection.tokenIsValid(tokens.accessToken)};`);
+      log(
+        `${refreshedTokens.accessToken} is a valid token ? ${await this.connection.tokenIsValid(
+          refreshedTokens.accessToken,
+        )};`,
+      );
+    } catch (e) {
+      error(e);
+    }
+  }
+}
diff --git a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e0fbb38bf3c481e4ddafe1262b2d0b40c281981e
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts
@@ -0,0 +1,266 @@
+import * as https from 'https';
+import fetch, { Response } from 'node-fetch';
+import { REJECT_UNAUTHORIZED } from '../config';
+import { HttpStatusCode } from '../customTypings/HttpStatusCode';
+import { TokenFetchErrorCode } from '../customTypings/KeycloakAPITypes';
+import { error as err, log } from '../recorder/utils';
+
+process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = REJECT_UNAUTHORIZED ? '1' : '0';
+
+export default class KeycloakOAuth2DeviceFlowConnection {
+  private waitDuration: WaitDuration;
+  private accessToken: string;
+  private refreshToken: string;
+  private deviceAuthorizationRequestResponse: DeviceAuthorizationRequestResponse;
+
+  constructor(private deviceUrl: string, private tokenUrl: string, private userInfoUrl: string) {
+    /*
+     * Arbitrary durations to wait in seconds \
+     * Waiting time between requests should be increasing
+     * if Keycloak api reponds with error code `slow_down` \
+     * otherwise, it will continue to respond with this error code. \
+     * The minimal waiting duration has been chosen to not be too short: \
+     * Keycloak may saturate and crash due to too many requests.
+     */
+    this.waitDuration = new WaitDuration([5_000, 5_000, 5_000, 10_000, 10_000, 10_000, 30_000, 30_000, 100_000]);
+    this.accessToken = '';
+    this.refreshToken = '';
+    this.deviceAuthorizationRequestResponse = {};
+  }
+
+  /**
+   * @param accessToken
+   * @returns Promise
+   */
+  public async tokenIsValid(accessToken: string) {
+    if (!accessToken) {
+      return Promise.resolve(false);
+    }
+    const tokenValidationRequestResponse: Response = await fetch(this.userInfoUrl, {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/x-www-form-urlencoded',
+        Authorization: `Bearer ${accessToken}`,
+      },
+      body: '',
+      agent: new https.Agent({ rejectUnauthorized: REJECT_UNAUTHORIZED }),
+    });
+    const tokenValidationRequestResponseCode = tokenValidationRequestResponse.status;
+    switch (tokenValidationRequestResponseCode) {
+      case HttpStatusCode.OK: {
+        return Promise.resolve(true);
+      }
+      case HttpStatusCode.BAD_REQUEST: {
+        const badRequestResponse = (await tokenValidationRequestResponse.json()) as FailedAuthenticationResponse;
+        err(`${badRequestResponse.error!}: ${badRequestResponse.error_description}`);
+        throw new Error(`${badRequestResponse.error}: ${badRequestResponse.error_description}`);
+      }
+      case HttpStatusCode.UNAUTHORIZED: {
+        return Promise.resolve(false);
+      }
+      default: {
+        err(`Unhandled HTTP status: ${tokenValidationRequestResponseCode}`);
+        throw new Error(`Unhandled HTTP status: ${tokenValidationRequestResponseCode}`);
+      }
+    }
+  }
+
+  public async getToken(args: { refreshToken?: string; openLink: (link: string) => void }) {
+    const { refreshToken, openLink } = args;
+    if (!!refreshToken) {
+      await this.createUserAuthentication({
+        url: this.tokenUrl,
+        body: (() => {
+          const params = new URLSearchParams();
+          params.append('response_type', 'token');
+          params.append('client_id', 'deadlock-desktop');
+          params.append('grant_type', 'refresh_token');
+          params.append('refresh_token', refreshToken);
+          return params.toString();
+        })(),
+      });
+      return Promise.resolve({ accessToken: this.accessToken, refreshToken: this.refreshToken });
+    }
+    if (!this.deviceIsRegistered()) {
+      await this.registerDevice();
+    }
+    try {
+      openLink(this.deviceAuthorizationRequestResponse.verification_uri_complete!);
+      await this.createUserAuthentication({
+        url: this.tokenUrl,
+        body: (() => {
+          const params = new URLSearchParams();
+          params.append('response_type', 'token');
+          params.append('device_code', this.deviceAuthorizationRequestResponse.device_code ?? '');
+          params.append('grant_type', 'urn:ietf:params:oauth:grant-type:device_code');
+          params.append('client_id', 'deadlock-desktop');
+          return params.toString();
+        })(),
+      });
+      return Promise.resolve({ accessToken: this.accessToken, refreshToken: this.refreshToken });
+    } catch (error) {
+      return Promise.reject(error);
+    }
+  }
+
+  private deviceIsRegistered(): boolean {
+    return !!this.deviceAuthorizationRequestResponse.device_code;
+  }
+
+  public async registerDevice() {
+    log('Device not registered. Registering ...');
+    const deviceAuthorizationRequestResponse: Response = await this.createDeviceAuthorization({
+      url: this.deviceUrl,
+      body: (() => {
+        const params = new URLSearchParams();
+        params.append('client_id', 'deadlock-desktop');
+        return params.toString();
+      })(),
+    });
+    this.deviceAuthorizationRequestResponse =
+      (await deviceAuthorizationRequestResponse.json()) as DeviceAuthorizationRequestResponse;
+  }
+
+  private async createDeviceAuthorization(args: { url: string; body: string }): Promise<Response> {
+    const { url, body } = args;
+    log(`[POST] ${url} \n ${body}`);
+    return fetch(url, {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/x-www-form-urlencoded',
+      },
+      body: body,
+      agent: new https.Agent({ rejectUnauthorized: REJECT_UNAUTHORIZED }),
+    });
+  }
+
+  /**
+   *
+   * @param args API URL endpoint to ask for a new token & request form parameters
+   * @throw Error containing Keycloak API `error_code`
+   */
+  private async createUserAuthentication(args: { url: string; body: string }) {
+    const { url, body } = args;
+    let userAuthenticationRequestResponseCode = HttpStatusCode.I_AM_A_TEAPOT;
+    while (userAuthenticationRequestResponseCode !== HttpStatusCode.OK) {
+      log(`[POST] ${url} \n ${body}`);
+      const userAuthenticationRequestResponse: Response = await fetch(url, {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/x-www-form-urlencoded',
+        },
+        body: body,
+        agent: new https.Agent({ rejectUnauthorized: REJECT_UNAUTHORIZED }),
+      });
+      userAuthenticationRequestResponseCode = userAuthenticationRequestResponse.status;
+      switch (userAuthenticationRequestResponseCode) {
+        case HttpStatusCode.BAD_REQUEST: {
+          await this.onUserAuthenticationBadRequest(userAuthenticationRequestResponse);
+          break;
+        }
+
+        case HttpStatusCode.OK: {
+          await this.onUserAuthenticationSuccess(userAuthenticationRequestResponse);
+          break;
+        }
+
+        default: {
+          err(`Unhandled HTTP status: ${userAuthenticationRequestResponseCode}`);
+          throw new Error(`Unhandled HTTP status: ${userAuthenticationRequestResponseCode}`);
+        }
+      }
+    }
+  }
+
+  private async onUserAuthenticationBadRequest(userAuthenticationRequestResponse: Response) {
+    const badRequestResponse = (await userAuthenticationRequestResponse.json()) as FailedAuthenticationResponse;
+    const errorCode = TokenFetchErrorCode[badRequestResponse.error!];
+    switch (errorCode) {
+      case TokenFetchErrorCode.invalid_client:
+      case TokenFetchErrorCode.invalid_grant:
+      case TokenFetchErrorCode.unsupported_grant_type: {
+        err(`${badRequestResponse.error!}: ${badRequestResponse.error_description}`);
+        throw new Error('createUserAuthentication: ' + errorCode);
+      }
+      case TokenFetchErrorCode.authorization_pending: {
+        await sleep(this.waitDuration.getCurrentDuration());
+        break;
+      }
+      case TokenFetchErrorCode.slow_down: {
+        this.waitDuration.increase();
+        await sleep(this.waitDuration.getCurrentDuration());
+        break;
+      }
+      default: {
+        err(`${badRequestResponse.error!}: ${badRequestResponse.error_description}`);
+        throw new Error(
+          `Unhandled error code [ ${badRequestResponse.error!}: ${badRequestResponse.error_description} ]`,
+        );
+      }
+    }
+  }
+
+  private async onUserAuthenticationSuccess(userAuthenticationRequestResponse: Response) {
+    const successRequestResponse = (await userAuthenticationRequestResponse.json()) as SuccessfulAuthenticationResponse;
+    this.accessToken = successRequestResponse.access_token ?? '';
+    this.refreshToken = successRequestResponse.refresh_token ?? '';
+  }
+}
+
+/**
+ * KEEP the SAME case \
+ * to respect keycloak API return
+ */
+interface DeviceAuthorizationRequestResponse {
+  device_code?: string;
+  user_code?: string;
+  verification_uri?: string;
+  verification_uri_complete?: string;
+  expires_in?: number;
+  interval?: number;
+}
+
+/**
+ * KEEP the SAME case \
+ * to respect keycloak API return
+ */
+interface SuccessfulAuthenticationResponse {
+  access_token?: string;
+  expires_in?: number;
+  'not-before-policy'?: number;
+  refresh_expires_in: number;
+  refresh_token?: string;
+  scope?: string;
+  session_state?: string;
+  token_type?: 'Bearer' | string;
+}
+
+interface FailedAuthenticationResponse {
+  error?: string;
+  error_description?: string;
+}
+
+function sleep(ms: number) {
+  return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+class WaitDuration {
+  private durations: Uint16Array;
+  private index: number;
+  constructor(durations: number[]) {
+    this.durations = new Uint16Array(durations);
+    this.durations.sort();
+    this.index = 0;
+  }
+  public getCurrentDuration(): number {
+    if (this.index >= this.durations.length - 1) {
+      throw new Error(`Index out of bounds. Current index: ${this.index} | Max val: ${this.durations.length - 1}`);
+    }
+    return this.durations[this.index];
+  }
+  public increase(): void {
+    if (this.index < this.durations.length - 1) {
+      this.index += 1;
+    }
+  }
+}
diff --git a/deadlock-plugins/deadlock-extension/src/core/metadataProvider.ts b/deadlock-plugins/deadlock-extension/src/core/metadataProvider.ts
index 22d50dda5700a4075c72853b8eaa9cc8740c037a..a598545b60abeb4628fb4f5e15949b5dcd391b18 100644
--- a/deadlock-plugins/deadlock-extension/src/core/metadataProvider.ts
+++ b/deadlock-plugins/deadlock-extension/src/core/metadataProvider.ts
@@ -5,65 +5,64 @@ import { error as err, log } from '../recorder/utils';
 import { BASHRC_PATH, ENV_FILE_PATH } from './config';
 
 export default class MetadataProvider {
+  public static loadPathsToJson(filePath: string) {
+    const portToPath = userConfig.getPaths();
+    const serviceNameToPath = this.getServicePathFromPortPath(portToPath);
+    const jsonValue = Object.fromEntries(serviceNameToPath);
+    const fileContent = JSON.stringify(jsonValue, null, 4) + '\n';
+    fs.writeFileSync(filePath, fileContent, { flag: 'w+' });
+  }
 
-    public static loadPathsToJson(filePath: string) {
-        const portToPath = userConfig.getPaths();
-        const serviceNameToPath = this.getServicePathFromPortPath(portToPath);
-        const jsonValue = Object.fromEntries(serviceNameToPath);
-        const fileContent = JSON.stringify(jsonValue, null, 4) + '\n';
-        fs.writeFileSync(filePath, fileContent, { flag: 'w+'});
+  public static loadPathsToEnvVariables(): void {
+    const portToPath = userConfig.getPaths();
+    const serviceNameToPath = this.getServicePathFromPortPath(portToPath);
+    let fileContent = '';
+    for (const [serviceName, servicePath] of serviceNameToPath.entries()) {
+      fileContent += `${serviceName}=${servicePath} \n`;
     }
-
-    public static loadPathsToEnvVariables(): void {
-        const portToPath = userConfig.getPaths();
-        const serviceNameToPath = this.getServicePathFromPortPath(portToPath);
-        let fileContent = '';
-        for (const [serviceName, servicePath] of serviceNameToPath.entries()) {
-            fileContent += `${serviceName}=${servicePath} \n`;
-        }
-        fs.writeFileSync(ENV_FILE_PATH, fileContent, { flag: 'w+'});
-        const bashrcAppendContent = `
+    fs.writeFileSync(ENV_FILE_PATH, fileContent, { flag: 'w+' });
+    const bashrcAppendContent = `
 export $(cat ${ENV_FILE_PATH} | xargs -L 1)
 `;
-        fs.writeFileSync(BASHRC_PATH, bashrcAppendContent, { flag: 'a+'});
-        exec(`. ${BASHRC_PATH}`, execCallback);
-    }
+    fs.writeFileSync(BASHRC_PATH, bashrcAppendContent, { flag: 'a+' });
+    exec(`. ${BASHRC_PATH}`, execCallback);
+  }
 
-    /**
-     * two formats to handle: (key="front", value="3001") | (key="8080": value="HXW3fnKwUULrfKS1-cdb")\
-     * expected result: (key="front", value="localhost:3001") | (key="cdb", value="HXW3fnKwUULrfKS1-cdb")
-     * 
-     */
-    private static getServicePathFromPortPath(portToPath: Map<number, string>): Map<string, string> {
-        const serviceToPath = new Map<string, string>();
-        for (const [port, path] of Object.entries(portToPath)) {
-
-            let key = '';
-            let value = '';
-            if (isNumeric(port)) {
-                key = path.split('-').slice(-1);
-                value = `${path}.${userConfig.getHost()}`;
-            } else {
-                key = port;
-                value = `localhost:${path}`;
-            }
-            serviceToPath.set(key, value);
-        }
-        return serviceToPath;
+  /**
+   * two formats to handle: (key="front", value="3001") | (key="8080": value="HXW3fnKwUULrfKS1-cdb")\
+   * expected result: (key="front", value="localhost:3001") | (key="cdb", value="HXW3fnKwUULrfKS1-cdb")
+   *
+   */
+  private static getServicePathFromPortPath(portToPath: Map<number, string>): Map<string, string> {
+    const serviceToPath = new Map<string, string>();
+    for (const [port, path] of Object.entries(portToPath)) {
+      let key = '';
+      let value = '';
+      if (isNumeric(port)) {
+        key = path.split('-').slice(-1);
+        value = `${path}.${userConfig.getHost()}`;
+      } else {
+        key = port;
+        value = `localhost:${path}`;
+      }
+      serviceToPath.set(key, value);
     }
-
+    return serviceToPath;
+  }
 }
 
-function isNumeric(val: string) { return /^\d+$/.test(val); }
+function isNumeric(val: string) {
+  return /^\d+$/.test(val);
+}
 
 function execCallback(error: ExecException | null, stdout: string, stderr: string) {
-    if (error) {
-        err(`error: ${error.message}`);
-        return;
-    }
-    if (stderr) {
-        err(`stderr: ${stderr}`);
-        return;
-    }
-    log(stdout);
+  if (error) {
+    err(`error: ${error.message}`);
+    return;
+  }
+  if (stderr) {
+    err(`stderr: ${stderr}`);
+    return;
+  }
+  log(stdout);
 }
diff --git a/deadlock-plugins/deadlock-extension/src/core/userConfig.ts b/deadlock-plugins/deadlock-extension/src/core/userConfig.ts
index 21f1d3eac3c171ec402a976e662d530222fd8c59..46295519cf5a44668fabe677467787ecbfd7276e 100644
--- a/deadlock-plugins/deadlock-extension/src/core/userConfig.ts
+++ b/deadlock-plugins/deadlock-extension/src/core/userConfig.ts
@@ -1,4 +1,3 @@
-
 /**
  * Example:
  * {
@@ -19,75 +18,75 @@
 import { error } from '../recorder/utils';
 
 export interface UserDetails {
-    firstName: string;
-    lastName: string;
-    avatarUrl: string;
-    email: string;
+  firstName: string;
+  lastName: string;
+  avatarUrl: string;
+  email: string;
 }
 
 export default abstract class UserConfig {
-    private userConfigJson: any | undefined;
+  private userConfigJson: any | undefined;
 
-    getPaths(): Map<number, string> {
-        return this.userConfigJson?.paths;
-    }
+  getPaths(): Map<number, string> {
+    return this.userConfigJson?.paths;
+  }
 
-    getHost(): string {
-        return this.userConfigJson?.host;
-    }
+  getHost(): string {
+    return this.userConfigJson?.host;
+  }
 
-    getToken(): string {
-        return this.userConfigJson?.token;
-    }
+  getToken(): string {
+    return this.userConfigJson?.token;
+  }
 
-    getGiteaHost(): string {
-        return this.userConfigJson?.giteaHost;
-    }
+  getGiteaHost(): string {
+    return this.userConfigJson?.giteaHost;
+  }
 
-    getGiteaSshPort(): number {
-        return this.userConfigJson?.giteaSshPort;
-    }
+  getGiteaSshPort(): number {
+    return this.userConfigJson?.giteaSshPort;
+  }
 
-    getUsername(): string {
-        return this.userConfigJson?.username;
-    }
+  getUsername(): string {
+    return this.userConfigJson?.username;
+  }
 
-    getRemoteGitUsername(): string {
-        return this.userConfigJson?.remoteGitUsername;
-    }
+  getRemoteGitUsername(): string {
+    return this.userConfigJson?.remoteGitUsername;
+  }
 
-    getMissionId(): string {
-        return this.userConfigJson?.missionId;
-    }
+  getMissionId(): string {
+    return this.userConfigJson?.missionId;
+  }
 
-    getEmail(): string {
-        return this.userConfigJson?.email;
-    }
+  getEmail(): string {
+    return this.userConfigJson?.email;
+  }
 
-    isProfessor(): boolean {
-        return this.userConfigJson?.professor;
-    }
+  isProfessor(): boolean {
+    return this.userConfigJson?.professor;
+  }
 
-    getCurrentUserDetails(): UserDetails {
-        return this.userConfigJson?.currentUserDetails;
-    }
+  getCurrentUserDetails(): UserDetails {
+    return this.userConfigJson?.currentUserDetails;
+  }
 
-    getRemoteUserDetails(): UserDetails {
-        return this.userConfigJson?.remoteUserDetails;
-    }
+  getRemoteUserDetails(): UserDetails {
+    return this.userConfigJson?.remoteUserDetails;
+  }
 
-    abstract loadText(): Promise<string>;
+  abstract loadText(): Promise<string>;
 
-    public async init() {
-        try {
-            const userConfig = await this.loadText();
+  public async init() {
+    try {
+      const userConfig = await this.loadText();
 
-            this.userConfigJson = JSON.parse(userConfig);
-            return Promise.resolve();
-        } catch (e) {
-            error('Cannot load userConfig');
-            error(e);
-            return Promise.reject();
-        }
+      this.userConfigJson = JSON.parse(userConfig);
+      return Promise.resolve();
+    } catch (e) {
+      error('Cannot load userConfig');
+      error(e);
+      return Promise.reject();
     }
-}
\ No newline at end of file
+  }
+}
diff --git a/deadlock-plugins/deadlock-extension/src/customTypings/HttpStatusCode.ts b/deadlock-plugins/deadlock-extension/src/customTypings/HttpStatusCode.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e5bb61ccb9a971188808ff21d9693ae63679e851
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/customTypings/HttpStatusCode.ts
@@ -0,0 +1,379 @@
+/**
+ * {@link https://gist.github.com/scokmen/f813c904ef79022e84ab2409574d1b45 Source code link}\
+ * Hypertext Transfer Protocol (HTTP) response status codes.
+ * @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes}
+ */
+export enum HttpStatusCode {
+  /**
+   * The server has received the request headers and the client should proceed to send the request body
+   * (in the case of a request for which a body needs to be sent; for example, a POST request).
+   * Sending a large request body to a server after a request has been rejected for inappropriate headers would be inefficient.
+   * To have a server check the request's headers, a client must send Expect: 100-continue as a header in its initial request
+   * and receive a 100 Continue status code in response before sending the body. The response 417 Expectation Failed indicates the request should not be continued.
+   */
+  CONTINUE = 100,
+
+  /**
+   * The requester has asked the server to switch protocols and the server has agreed to do so.
+   */
+  SWITCHING_PROTOCOLS = 101,
+
+  /**
+   * A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request.
+   * This code indicates that the server has received and is processing the request, but no response is available yet.
+   * This prevents the client from timing out and assuming the request was lost.
+   */
+  PROCESSING = 102,
+
+  /**
+   * Standard response for successful HTTP requests.
+   * The actual response will depend on the request method used.
+   * In a GET request, the response will contain an entity corresponding to the requested resource.
+   * In a POST request, the response will contain an entity describing or containing the result of the action.
+   */
+  OK = 200,
+
+  /**
+   * The request has been fulfilled, resulting in the creation of a new resource.
+   */
+  CREATED = 201,
+
+  /**
+   * The request has been accepted for processing, but the processing has not been completed.
+   * The request might or might not be eventually acted upon, and may be disallowed when processing occurs.
+   */
+  ACCEPTED = 202,
+
+  /**
+   * SINCE HTTP/1.1
+   * The server is a transforming proxy that received a 200 OK from its origin,
+   * but is returning a modified version of the origin's response.
+   */
+  NON_AUTHORITATIVE_INFORMATION = 203,
+
+  /**
+   * The server successfully processed the request and is not returning any content.
+   */
+  NO_CONTENT = 204,
+
+  /**
+   * The server successfully processed the request, but is not returning any content.
+   * Unlike a 204 response, this response requires that the requester reset the document view.
+   */
+  RESET_CONTENT = 205,
+
+  /**
+   * The server is delivering only part of the resource (byte serving) due to a range header sent by the client.
+   * The range header is used by HTTP clients to enable resuming of interrupted downloads,
+   * or split a download into multiple simultaneous streams.
+   */
+  PARTIAL_CONTENT = 206,
+
+  /**
+   * The message body that follows is an XML message and can contain a number of separate response codes,
+   * depending on how many sub-requests were made.
+   */
+  MULTI_STATUS = 207,
+
+  /**
+   * The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response,
+   * and are not being included again.
+   */
+  ALREADY_REPORTED = 208,
+
+  /**
+   * The server has fulfilled a request for the resource,
+   * and the response is a representation of the result of one or more instance-manipulations applied to the current instance.
+   */
+  IM_USED = 226,
+
+  /**
+   * Indicates multiple options for the resource from which the client may choose (via agent-driven content negotiation).
+   * For example, this code could be used to present multiple video format options,
+   * to list files with different filename extensions, or to suggest word-sense disambiguation.
+   */
+  MULTIPLE_CHOICES = 300,
+
+  /**
+   * This and all future requests should be directed to the given URI.
+   */
+  MOVED_PERMANENTLY = 301,
+
+  /**
+   * This is an example of industry practice contradicting the standard.
+   * The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect
+   * (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302
+   * with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307
+   * to distinguish between the two behaviours. However, some Web applications and frameworks
+   * use the 302 status code as if it were the 303.
+   */
+  FOUND = 302,
+
+  /**
+   * SINCE HTTP/1.1
+   * The response to the request can be found under another URI using a GET method.
+   * When received in response to a POST (or PUT/DELETE), the client should presume that
+   * the server has received the data and should issue a redirect with a separate GET message.
+   */
+  SEE_OTHER = 303,
+
+  /**
+   * Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.
+   * In such case, there is no need to retransmit the resource since the client still has a previously-downloaded copy.
+   */
+  NOT_MODIFIED = 304,
+
+  /**
+   * SINCE HTTP/1.1
+   * The requested resource is available only through a proxy, the address for which is provided in the response.
+   * Many HTTP clients (such as Mozilla and Internet Explorer) do not correctly handle responses with this status code, primarily for security reasons.
+   */
+  USE_PROXY = 305,
+
+  /**
+   * No longer used. Originally meant "Subsequent requests should use the specified proxy."
+   */
+  SWITCH_PROXY = 306,
+
+  /**
+   * SINCE HTTP/1.1
+   * In this case, the request should be repeated with another URI; however, future requests should still use the original URI.
+   * In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request.
+   * For example, a POST request should be repeated using another POST request.
+   */
+  TEMPORARY_REDIRECT = 307,
+
+  /**
+   * The request and all future requests should be repeated using another URI.
+   * 307 and 308 parallel the behaviors of 302 and 301, but do not allow the HTTP method to change.
+   * So, for example, submitting a form to a permanently redirected resource may continue smoothly.
+   */
+  PERMANENT_REDIRECT = 308,
+
+  /**
+   * The server cannot or will not process the request due to an apparent client error
+   * (e.g., malformed request syntax, too large size, invalid request message framing, or deceptive request routing).
+   */
+  BAD_REQUEST = 400,
+
+  /**
+   * Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet
+   * been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the
+   * requested resource. See Basic access authentication and Digest access authentication. 401 semantically means
+   * "unauthenticated",i.e. the user does not have the necessary credentials.
+   */
+  UNAUTHORIZED = 401,
+
+  /**
+   * Reserved for future use. The original intention was that this code might be used as part of some form of digital
+   * cash or micro payment scheme, but that has not happened, and this code is not usually used.
+   * Google Developers API uses this status if a particular developer has exceeded the daily limit on requests.
+   */
+  PAYMENT_REQUIRED = 402,
+
+  /**
+   * The request was valid, but the server is refusing action.
+   * The user might not have the necessary permissions for a resource.
+   */
+  FORBIDDEN = 403,
+
+  /**
+   * The requested resource could not be found but may be available in the future.
+   * Subsequent requests by the client are permissible.
+   */
+  NOT_FOUND = 404,
+
+  /**
+   * A request method is not supported for the requested resource;
+   * for example, a GET request on a form that requires data to be presented via POST, or a PUT request on a read-only resource.
+   */
+  METHOD_NOT_ALLOWED = 405,
+
+  /**
+   * The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.
+   */
+  NOT_ACCEPTABLE = 406,
+
+  /**
+   * The client must first authenticate itself with the proxy.
+   */
+  PROXY_AUTHENTICATION_REQUIRED = 407,
+
+  /**
+   * The server timed out waiting for the request.
+   * According to HTTP specifications:
+   * "The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time."
+   */
+  REQUEST_TIMEOUT = 408,
+
+  /**
+   * Indicates that the request could not be processed because of conflict in the request,
+   * such as an edit conflict between multiple simultaneous updates.
+   */
+  CONFLICT = 409,
+
+  /**
+   * Indicates that the resource requested is no longer available and will not be available again.
+   * This should be used when a resource has been intentionally removed and the resource should be purged.
+   * Upon receiving a 410 status code, the client should not request the resource in the future.
+   * Clients such as search engines should remove the resource from their indices.
+   * Most use cases do not require clients and search engines to purge the resource, and a "404 Not Found" may be used instead.
+   */
+  GONE = 410,
+
+  /**
+   * The request did not specify the length of its content, which is required by the requested resource.
+   */
+  LENGTH_REQUIRED = 411,
+
+  /**
+   * The server does not meet one of the preconditions that the requester put on the request.
+   */
+  PRECONDITION_FAILED = 412,
+
+  /**
+   * The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".
+   */
+  PAYLOAD_TOO_LARGE = 413,
+
+  /**
+   * The URI provided was too long for the server to process. Often the result of too much data being encoded as a query-string of a GET request,
+   * in which case it should be converted to a POST request.
+   * Called "Request-URI Too Long" previously.
+   */
+  URI_TOO_LONG = 414,
+
+  /**
+   * The request entity has a media type which the server or resource does not support.
+   * For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format.
+   */
+  UNSUPPORTED_MEDIA_TYPE = 415,
+
+  /**
+   * The client has asked for a portion of the file (byte serving), but the server cannot supply that portion.
+   * For example, if the client asked for a part of the file that lies beyond the end of the file.
+   * Called "Requested Range Not Satisfiable" previously.
+   */
+  RANGE_NOT_SATISFIABLE = 416,
+
+  /**
+   * The server cannot meet the requirements of the Expect request-header field.
+   */
+  EXPECTATION_FAILED = 417,
+
+  /**
+   * This code was defined in 1998 as one of the traditional IETF April Fools' jokes, in RFC 2324, Hyper Text Coffee Pot Control Protocol,
+   * and is not expected to be implemented by actual HTTP servers. The RFC specifies this code should be returned by
+   * teapots requested to brew coffee. This HTTP status is used as an Easter egg in some websites, including Google.com.
+   */
+  I_AM_A_TEAPOT = 418,
+
+  /**
+   * The request was directed at a server that is not able to produce a response (for example because a connection reuse).
+   */
+  MISDIRECTED_REQUEST = 421,
+
+  /**
+   * The request was well-formed but was unable to be followed due to semantic errors.
+   */
+  UNPROCESSABLE_ENTITY = 422,
+
+  /**
+   * The resource that is being accessed is locked.
+   */
+  LOCKED = 423,
+
+  /**
+   * The request failed due to failure of a previous request (e.g., a PROPPATCH).
+   */
+  FAILED_DEPENDENCY = 424,
+
+  /**
+   * The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field.
+   */
+  UPGRADE_REQUIRED = 426,
+
+  /**
+   * The origin server requires the request to be conditional.
+   * Intended to prevent "the 'lost update' problem, where a client
+   * GETs a resource's state, modifies it, and PUTs it back to the server,
+   * when meanwhile a third party has modified the state on the server, leading to a conflict."
+   */
+  PRECONDITION_REQUIRED = 428,
+
+  /**
+   * The user has sent too many requests in a given amount of time. Intended for use with rate-limiting schemes.
+   */
+  TOO_MANY_REQUESTS = 429,
+
+  /**
+   * The server is unwilling to process the request because either an individual header field,
+   * or all the header fields collectively, are too large.
+   */
+  REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
+
+  /**
+   * A server operator has received a legal demand to deny access to a resource or to a set of resources
+   * that includes the requested resource. The code 451 was chosen as a reference to the novel Fahrenheit 451.
+   */
+  UNAVAILABLE_FOR_LEGAL_REASONS = 451,
+
+  /**
+   * A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.
+   */
+  INTERNAL_SERVER_ERROR = 500,
+
+  /**
+   * The server either does not recognize the request method, or it lacks the ability to fulfill the request.
+   * Usually this implies future availability (e.g., a new feature of a web-service API).
+   */
+  NOT_IMPLEMENTED = 501,
+
+  /**
+   * The server was acting as a gateway or proxy and received an invalid response from the upstream server.
+   */
+  BAD_GATEWAY = 502,
+
+  /**
+   * The server is currently unavailable (because it is overloaded or down for maintenance).
+   * Generally, this is a temporary state.
+   */
+  SERVICE_UNAVAILABLE = 503,
+
+  /**
+   * The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.
+   */
+  GATEWAY_TIMEOUT = 504,
+
+  /**
+   * The server does not support the HTTP protocol version used in the request
+   */
+  HTTP_VERSION_NOT_SUPPORTED = 505,
+
+  /**
+   * Transparent content negotiation for the request results in a circular reference.
+   */
+  VARIANT_ALSO_NEGOTIATES = 506,
+
+  /**
+   * The server is unable to store the representation needed to complete the request.
+   */
+  INSUFFICIENT_STORAGE = 507,
+
+  /**
+   * The server detected an infinite loop while processing the request.
+   */
+  LOOP_DETECTED = 508,
+
+  /**
+   * Further extensions to the request are required for the server to fulfill it.
+   */
+  NOT_EXTENDED = 510,
+
+  /**
+   * The client needs to authenticate to gain network access.
+   * Intended for use by intercepting proxies used to control access to the network (e.g., "captive portals" used
+   * to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot).
+   */
+  NETWORK_AUTHENTICATION_REQUIRED = 511,
+}
diff --git a/deadlock-plugins/deadlock-extension/src/customTypings/KeycloakAPITypes.ts b/deadlock-plugins/deadlock-extension/src/customTypings/KeycloakAPITypes.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d33bc3989e1792c3a7a6a3dc5b0eea3d68cd87b1
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/customTypings/KeycloakAPITypes.ts
@@ -0,0 +1,13 @@
+//
+/**
+ * KEEP the SAME case \
+ * `TokenFetchErrorCode['slow_down'] === TokenFetchErrorCode.slow_down; // === true` \
+ * `TokenFetchErrorCode['slow_down'] === TokenFetchErrorCode.SLOW_DOWN; // === false`
+ */
+export enum TokenFetchErrorCode {
+  authorization_pending = 'authorization_pending',
+  invalid_grant = 'invalid_grant',
+  unsupported_grant_type = 'unsupported_grant_type',
+  invalid_client = 'invalid_client',
+  slow_down = 'slow_down',
+}
diff --git a/deadlock-plugins/deadlock-extension/src/extension.ts b/deadlock-plugins/deadlock-extension/src/extension.ts
index aa7bc8dc7214bd709f53c830a23678059995602d..506ad15ae186c4a443ddda1651c5a02ff2cb1bce 100644
--- a/deadlock-plugins/deadlock-extension/src/extension.ts
+++ b/deadlock-plugins/deadlock-extension/src/extension.ts
@@ -1,38 +1,29 @@
 import * as vscode from 'vscode';
 import { SERVICES_PATHS_PATH } from './core/config';
+import Controller from './core/controller';
 import MetadataProvider from './core/metadataProvider';
 import { error } from './recorder/utils';
-import { OPEN_BRIEFING_COMMAND } from './theia/command';
 import { DepNodeProvider } from './theia/deadlockPanel';
 import UserConfigTheia from './theia/userConfigTheia';
-import BriefingView from './view/briefingView';
-
-export function initViews() {
-  new BriefingView();
-}
 
 export const userConfig = new UserConfigTheia();
 
 export async function activate(context: vscode.ExtensionContext) {
   vscode.window.showInformationMessage('Bienvenue sur Deadlock!');
 
+  const controller = new Controller(context);
+
+  const workspaceFolders = vscode.workspace.workspaceFolders?.toString() ?? '';
+  if (!workspaceFolders) vscode.window.showInformationMessage('Pas de répertoires ouverts');
+  const deadlockPanelProvider = new DepNodeProvider(workspaceFolders);
+  vscode.window.registerTreeDataProvider('deadlockPanel', deadlockPanelProvider);
+
   try {
     await userConfig.init();
   } catch (e) {
     error('Cannot init userConfig');
   }
 
-  initViews();
-
-  await vscode.commands.executeCommand(OPEN_BRIEFING_COMMAND.cmd);
-
-  // @ts-ignore
-  const deadlockPanelProvider = new DepNodeProvider(vscode.workspace.rootPath);
-  vscode.window.registerTreeDataProvider(
-    'deadlockPanel',
-    deadlockPanelProvider
-  );
-
   MetadataProvider.loadPathsToEnvVariables();
   MetadataProvider.loadPathsToJson(`${SERVICES_PATHS_PATH}`);
 }
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/README.md b/deadlock-plugins/deadlock-extension/src/recorder/README.md
index acb5a763e59ad8b9c7b30752f4bdf3ab95ac3c27..0b5bd9b0e9b22c9486284b70f2398406bbd2a03e 100644
--- a/deadlock-plugins/deadlock-extension/src/recorder/README.md
+++ b/deadlock-plugins/deadlock-extension/src/recorder/README.md
@@ -2,4 +2,4 @@
 
 ```
 ts-node recorder.ts
-```
\ No newline at end of file
+```
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/command-recorder.ts b/deadlock-plugins/deadlock-extension/src/recorder/command-recorder.ts
index ca941693d5f0ec9953d36082d96fda2fb3641beb..17865f4da95b95c0572a67cbb59456bb0fb82657 100644
--- a/deadlock-plugins/deadlock-extension/src/recorder/command-recorder.ts
+++ b/deadlock-plugins/deadlock-extension/src/recorder/command-recorder.ts
@@ -33,9 +33,7 @@ export default class CommandRecorder {
     if (!this.commandsInProgress.has(pid)) {
       this.commandsInProgress.set(pid, new Command(pid, command));
       try {
-        this.queue.push(
-          async () => await commitAndPushCode(this.gitMission, CommitFrom.Run)
-        );
+        this.queue.push(async () => await commitAndPushCode(this.gitMission, CommitFrom.Run));
       } catch (e) {
         console.error('Cannot send user code to git');
         console.error(e);
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/index.ts b/deadlock-plugins/deadlock-extension/src/recorder/index.ts
index 9a4b4f0b698f84b48d8fab62249474d77747e4bd..51f4a3a41cd3a2804c1ac81a2e2b5f5c15efbb72 100644
--- a/deadlock-plugins/deadlock-extension/src/recorder/index.ts
+++ b/deadlock-plugins/deadlock-extension/src/recorder/index.ts
@@ -5,64 +5,62 @@ import { PROJECT_SRC_PATH, PROJECT_THEIA_PATH } from '../core/config';
 import { copyProjectSources, clearFilesExceptGit, log, error, renameTempToUserGitFiles } from './utils';
 import UserConfig from '../core/userConfig';
 
-
 export default class Recorder {
+  async setupProject(userConfig: UserConfig, gitMission?: GitMission) {
+    log('Setup user project..');
 
-    async setupProject(userConfig: UserConfig, gitMission?: GitMission) {
-        log('Setup user project..');
+    if (!userConfig.isProfessor()) {
+      await copyProjectSources(PROJECT_SRC_PATH, PROJECT_THEIA_PATH, ['.git/']);
 
-        if (!userConfig.isProfessor()) {
-            await copyProjectSources(PROJECT_SRC_PATH, PROJECT_THEIA_PATH, ['.git/']);
-            
-            if (gitMission) {
-                renameTempToUserGitFiles(PROJECT_THEIA_PATH, gitMission.author);
+      if (gitMission) {
+        renameTempToUserGitFiles(PROJECT_THEIA_PATH, gitMission.author);
 
-                log('Starting CommandRecorder..');
-                new CommandRecorder(gitMission).run();
-            } else {
-                error('Cannot start command recorder, gitMission not found');
-            }
-        } else {
-            await copyProjectSources(PROJECT_SRC_PATH, PROJECT_THEIA_PATH);
-        }
+        log('Starting CommandRecorder..');
+        new CommandRecorder(gitMission).run();
+      } else {
+        error('Cannot start command recorder, gitMission not found');
+      }
+    } else {
+      await copyProjectSources(PROJECT_SRC_PATH, PROJECT_THEIA_PATH);
     }
+  }
 
-    async setupFromRemoteRepo(gitMission: GitMission) {
-        log('Check if remote repo exist');
-        const isRemoteRepoExist = await gitMission.isRemoteRepoExist();
-        if (isRemoteRepoExist) {
-            // rm all except git directory pull remote code and setup
-            log('Cleaning files to pull repo');
+  async setupFromRemoteRepo(gitMission: GitMission) {
+    log('Check if remote repo exist');
+    const isRemoteRepoExist = await gitMission.isRemoteRepoExist();
+    if (isRemoteRepoExist) {
+      // rm all except git directory pull remote code and setup
+      log('Cleaning files to pull repo');
 
-            await clearFilesExceptGit(PROJECT_SRC_PATH);
+      await clearFilesExceptGit(PROJECT_SRC_PATH);
 
-            log('Pulling user repo');
-            await gitMission.pull();
-        }
+      log('Pulling user repo');
+      await gitMission.pull();
     }
+  }
 
-    async run() {
-        const userConfig = new UserConfigNode();
+  async run() {
+    const userConfig = new UserConfigNode();
 
-        let gitMission;
+    let gitMission;
 
-        try {
-            log('Init User config');
-            await userConfig.init();
-            log('Init GitMission');
-            gitMission = await new GitMission(userConfig).init();
-            await this.setupFromRemoteRepo(gitMission);
-        } catch (e) {
-            error('Cannot setup user repo.');
-            error(e);
-        }
-        try {
-            await this.setupProject(userConfig, gitMission);
-        } catch (e) {
-            error('Error while setup project sources');
-            error(e);
-        }
+    try {
+      log('Init User config');
+      await userConfig.init();
+      log('Init GitMission');
+      gitMission = await new GitMission(userConfig).init();
+      await this.setupFromRemoteRepo(gitMission);
+    } catch (e) {
+      error('Cannot setup user repo.');
+      error(e);
+    }
+    try {
+      await this.setupProject(userConfig, gitMission);
+    } catch (e) {
+      error('Error while setup project sources');
+      error(e);
     }
+  }
 }
 
-new Recorder().run();
\ No newline at end of file
+new Recorder().run();
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/preStop.ts b/deadlock-plugins/deadlock-extension/src/recorder/preStop.ts
index 98b321368ba4311d3caebcb4789c1e21dfe0b555..e3154055e67b1b1a0d13521cc58a7c7891aac31e 100644
--- a/deadlock-plugins/deadlock-extension/src/recorder/preStop.ts
+++ b/deadlock-plugins/deadlock-extension/src/recorder/preStop.ts
@@ -1,10 +1,10 @@
 import UserConfigNode from './userConfigNode';
-import GitMission from '../core/gitMission'
-import { PROJECT_SRC_PATH, PROJECT_THEIA_PATH } from '../core/config'
+import GitMission from '../core/gitMission';
+import { PROJECT_SRC_PATH, PROJECT_THEIA_PATH } from '../core/config';
 
-import { log, error, commitAndPushCode, CommitFrom } from "./utils";
-const util = require("util");
-const exec = util.promisify(require("child_process").exec);
+import { log, error, commitAndPushCode, CommitFrom } from './utils';
+const util = require('util');
+const exec = util.promisify(require('child_process').exec);
 
 async function containsDiff() {
   try {
@@ -16,10 +16,7 @@ async function containsDiff() {
     // when status code is > 0
     if (result.code === 1) {
       const stdout = result.stdout;
-      if (
-        stdout.indexOf("Files ") !== -1 ||
-        stdout.indexOf("Only in /home/project/") !== -1
-      ) {
+      if (stdout.indexOf('Files ') !== -1 || stdout.indexOf('Only in /home/project/') !== -1) {
         // if user created new file or added a directory
         return true;
       }
@@ -33,20 +30,20 @@ async function containsDiff() {
 
 (async () => {
   try {
-    log("Container will die");
+    log('Container will die');
 
     const userConfig = new UserConfigNode();
     await userConfig.init();
 
     if (!userConfig.isProfessor()) {
-      log("Save user code..");
+      log('Save user code..');
       const gitMission = await new GitMission(userConfig).init();
       if (await containsDiff()) {
         await commitAndPushCode(gitMission, CommitFrom.Auto);
       }
     }
   } catch (e) {
-    error("Cannot push user code at the end..");
+    error('Cannot push user code at the end..');
     error(e);
   }
 })();
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/userConfigNode.ts b/deadlock-plugins/deadlock-extension/src/recorder/userConfigNode.ts
index ce449c84d90c606a7c1551cfe2eb3e891788cc62..3b977cae4be6285c3233aa2ee74e814dd3f20c9f 100644
--- a/deadlock-plugins/deadlock-extension/src/recorder/userConfigNode.ts
+++ b/deadlock-plugins/deadlock-extension/src/recorder/userConfigNode.ts
@@ -2,13 +2,11 @@ import UserConfig from '../core/userConfig';
 import { USER_CHALLENGE_PATH } from '../core/config';
 
 const fs = require('fs');
- 
 
 export default class UserConfigNode extends UserConfig {
-    async loadText(): Promise<string> {
-        const text = fs.readFileSync(USER_CHALLENGE_PATH, 'utf8');
+  async loadText(): Promise<string> {
+    const text = fs.readFileSync(USER_CHALLENGE_PATH, 'utf8');
 
-        return Promise.resolve(text);
-    }
-
-}
\ No newline at end of file
+    return Promise.resolve(text);
+  }
+}
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/utils.ts b/deadlock-plugins/deadlock-extension/src/recorder/utils.ts
index 74331fbc5a781394c296e116e28a58efa7ad56a1..cc271f3f93a3efba58d1b43ba020581c0f1f417e 100644
--- a/deadlock-plugins/deadlock-extension/src/recorder/utils.ts
+++ b/deadlock-plugins/deadlock-extension/src/recorder/utils.ts
@@ -1,7 +1,7 @@
 import GitMission from '../core/gitMission';
 import { PROJECT_SRC_PATH, PROJECT_THEIA_PATH } from '../core/config';
 import { format } from 'date-fns';
-import { execSync } from "child_process";
+import { execSync } from 'child_process';
 import { existsSync, renameSync, copyFileSync, PathLike } from 'fs';
 
 const util = require('util');
@@ -13,136 +13,119 @@ const Path = require('path');
 
 const PREFIX = '[DEADLOCK-RECORDER]';
 
-export const log = (message: any, args?: any[]) => {
-    if (args) {
-        console.log(`${PREFIX} ${message}`, args);
-    } else {
-        console.log(`${PREFIX} ${message}`);
-    }
+export const log = (message: any, ...args: any[]) => {
+  if (args) {
+    console.log(`${PREFIX} ${message}`, ...args);
+  } else {
+    console.log(`${PREFIX} ${message}`);
+  }
 };
 
-export const error = (message: any, args?: any[]) => {
-    if (args) {
-        console.error(`${PREFIX} ${message}`, args);
-    } else {
-        console.error(`${PREFIX} ${message}`);
-    }
+export const error = (message: any, ...args: any[]) => {
+  if (args) {
+    console.error(`${PREFIX} ${message}`, ...args);
+  } else {
+    console.error(`${PREFIX} ${message}`);
+  }
 };
 
-export async function copyProjectSources(
-    srcPath: string,
-    theiaPath: string,
-    excludes: Array<string> = [])
-{
-    const excludeCmd = excludes.map((current) => `--exclude ${current}`).join(' ');
-    return exec(`rsync -a ${srcPath}/ ${theiaPath} ${excludeCmd} && chown -R theia:theia /home/project`);
+export async function copyProjectSources(srcPath: string, theiaPath: string, excludes: Array<string> = []) {
+  const excludeCmd = excludes.map((current) => `--exclude ${current}`).join(' ');
+  return exec(`rsync -a ${srcPath}/ ${theiaPath} ${excludeCmd} && chown -R theia:theia /home/project`);
 }
 
 export async function pathContainsFiles(path: string) {
-    return exec(`ls -1 ${path} | wc -l`);
+  return exec(`ls -1 ${path} | wc -l`);
 }
 
 export async function clearFilesExceptGit(path) {
-    readdirSync(path).forEach(async (file) => {
-        if (file !== '.git') {
-            const curPath = Path.join(path, file);
-            if (fs.lstatSync(curPath).isDirectory()) {
-                fs.rmdirSync(curPath, { recursive: true });
-            } else {
-                await unlink(curPath);
-            }
-        }
-    });
+  readdirSync(path).forEach(async (file) => {
+    if (file !== '.git') {
+      const curPath = Path.join(path, file);
+      if (fs.lstatSync(curPath).isDirectory()) {
+        fs.rmdirSync(curPath, { recursive: true });
+      } else {
+        await unlink(curPath);
+      }
+    }
+  });
 }
 
 function renameIfExistsSync(srcPath: PathLike, destPath: PathLike) {
-
-    if (existsSync(srcPath)) {
-        renameSync(srcPath, destPath);
-    } else {
-        log(`Renaming : No ${srcPath} found`);
-    }
+  if (existsSync(srcPath)) {
+    renameSync(srcPath, destPath);
+  } else {
+    log(`Renaming : No ${srcPath} found`);
+  }
 }
 
 function copyFileIfExistsSync(srcPath: PathLike, destPath: PathLike) {
-
-    if (existsSync(srcPath)) {
-        copyFileSync(srcPath, destPath);
-    } else {
-        log(`Copying file: No ${srcPath} found`);
-    }
+  if (existsSync(srcPath)) {
+    copyFileSync(srcPath, destPath);
+  } else {
+    log(`Copying file: No ${srcPath} found`);
+  }
 }
 
 function copyFolderIfExistsSync(srcPath: PathLike, destPath: PathLike) {
-
-    if (existsSync(srcPath)) {
-        execSync(`rsync -r ${srcPath}/* ${destPath}`);
-    } else {
-        log(`Copying folder: No ${srcPath} found`);
-    }
+  if (existsSync(srcPath)) {
+    execSync(`rsync -r ${srcPath}/* ${destPath}`);
+  } else {
+    log(`Copying folder: No ${srcPath} found`);
+  }
 }
 
-
 /**
- * 
+ *
  * Rename temporary git files back to actual git files.
- * 
+ *
  * @param path Path of the folder containing user's temporary git project files.
  * @param userId Identifier used to create the temporary files name
  */
 export function renameTempToUserGitFiles(path: string, userId: string) {
-    
-    renameIfExistsSync(
-        Path.join(path,`user-git-${userId}`),
-        Path.join(path,'.git')
-    );
-    renameIfExistsSync(
-        Path.join(path, `user-gitignore-${userId}`),
-        Path.join(path, '.gitignore')
-    );
+  renameIfExistsSync(Path.join(path, `user-git-${userId}`), Path.join(path, '.git'));
+  renameIfExistsSync(Path.join(path, `user-gitignore-${userId}`), Path.join(path, '.gitignore'));
 }
 
 /**
- * 
+ *
  * Copy user's git project files and paste them with renamed filenames so that
  * they can be saved by the recorder.
- * 
+ *
  * @param srcPath Path of the folder containing user's git project files to be saved.
  * @param destPath Path where the user's git project files should be pasted.
  * @param userId Identifier used to create the temporary files name
  */
 function copyGitUserFiles(srcPath: string, destPath: string, userId: string) {
-
-    copyFolderIfExistsSync(
-        Path.join(srcPath,'.git'),
-        Path.join(destPath,`user-git-${userId}`)
-    );
-    copyFileIfExistsSync(
-        Path.join(srcPath, '.gitignore'),
-        Path.join(destPath, `user-gitignore-${userId}`)
-    );
+  copyFolderIfExistsSync(Path.join(srcPath, '.git'), Path.join(destPath, `user-git-${userId}`));
+  copyFileIfExistsSync(Path.join(srcPath, '.gitignore'), Path.join(destPath, `user-gitignore-${userId}`));
 }
 
 export enum CommitFrom {
-    Run = 'Run',
-    Auto = 'Auto'
+  Run = 'Run',
+  Auto = 'Auto',
 }
 
 export async function commitAndPushCode(gitMission: GitMission, from: CommitFrom) {
-    try {
-        log('Commit & push');
-        await clearFilesExceptGit(PROJECT_SRC_PATH);
-
-        copyGitUserFiles(PROJECT_THEIA_PATH, PROJECT_SRC_PATH, gitMission.author);
-        execSync(`rsync -r --exclude .git --exclude npm --exclude target ${PROJECT_THEIA_PATH}/* ${PROJECT_SRC_PATH} && cp ${Path.join(__dirname, '.gitignore')} ${PROJECT_SRC_PATH} && chown -R root:root /project`);
-
-        const currentDate = format(new Date(), "HH'H'mm'_'dd/LL/y");
-        await gitMission.addAll();
-        await gitMission.commit(currentDate);
-        await gitMission.push();
-        log('Commit & push done.');
-    } catch (e) {
-        error(`[${e.status}] cannot commitAndPush code: ${e.stderr}`);
-        error(e.message);
-    }
+  try {
+    log('Commit & push');
+    await clearFilesExceptGit(PROJECT_SRC_PATH);
+
+    copyGitUserFiles(PROJECT_THEIA_PATH, PROJECT_SRC_PATH, gitMission.author);
+    execSync(
+      `rsync -r --exclude .git --exclude npm --exclude target ${PROJECT_THEIA_PATH}/* ${PROJECT_SRC_PATH} && cp ${Path.join(
+        __dirname,
+        '.gitignore',
+      )} ${PROJECT_SRC_PATH} && chown -R root:root /project`,
+    );
+
+    const currentDate = format(new Date(), "HH'H'mm'_'dd/LL/y");
+    await gitMission.addAll();
+    await gitMission.commit(currentDate);
+    await gitMission.push();
+    log('Commit & push done.');
+  } catch (e) {
+    error(`[${e.status}] cannot commitAndPush code: ${e.stderr}`);
+    error(e.message);
+  }
 }
diff --git a/deadlock-plugins/deadlock-extension/src/theia/command.ts b/deadlock-plugins/deadlock-extension/src/theia/command.ts
index 72ec485011d6183ba5f8a86ea1d01c2bd242c603..53638bd941e7cc69623287b76f5670be8e78cb2c 100644
--- a/deadlock-plugins/deadlock-extension/src/theia/command.ts
+++ b/deadlock-plugins/deadlock-extension/src/theia/command.ts
@@ -1,21 +1,19 @@
-
 import { Command as VscodeComand } from 'vscode';
 export class Command {
-	constructor(private _title: string, private _command: string) {
-	}
-
-	get title() {
-		return this._title;
-	}
+  constructor(private _title: string, private command: string) {}
 
-	get cmd() {
-		return this._command;
-	}
+  get title() {
+    return this._title;
+  }
 
-	asVsCodeCommand(): VscodeComand {
-		return { title: this.title, command: this.cmd }
+  get cmd() {
+    return this.command;
+  }
 
-	}
+  asVsCodeCommand(): VscodeComand {
+    return { title: this.title, command: this.cmd };
+  }
 }
 
-export const OPEN_BRIEFING_COMMAND = new Command('Open Briefing', 'deadlock.openBriefing');
\ No newline at end of file
+export const OPEN_BRIEFING_COMMAND = new Command('Open Briefing', 'deadlock.openBriefing');
+export const OPEN_QUICK_SETUP_COMMAND = new Command('Open Deadlock quick setup page', 'deadlock.openQuickSetup');
diff --git a/deadlock-plugins/deadlock-extension/src/theia/deadlockPanel.ts b/deadlock-plugins/deadlock-extension/src/theia/deadlockPanel.ts
index d4a5f752125a191492db1ed4d86d7dc62fbc920c..bf68f8424c822157b6e3d543d1afc09ec5702dcb 100644
--- a/deadlock-plugins/deadlock-extension/src/theia/deadlockPanel.ts
+++ b/deadlock-plugins/deadlock-extension/src/theia/deadlockPanel.ts
@@ -1,75 +1,66 @@
-import * as vscode from 'vscode';
 import * as path from 'path';
+import * as vscode from 'vscode';
 import { TreeItemCollapsibleState } from 'vscode';
 import { OPEN_BRIEFING_COMMAND } from './command';
 
 const DOCUMENTATION_LABEL = 'Documentation';
 
 export class DepNodeProvider implements vscode.TreeDataProvider<Action> {
-
-	private _onDidChangeTreeData: vscode.EventEmitter<Action | undefined | void> = new vscode.EventEmitter<Action | undefined | void>();
-	// @ts-ignore
-	readonly onDidChangeTreeData: vscode.Event<Action | undefined | void> = this._onDidChangeTreeData.event;
-
-	constructor(private workspaceRoot: string) {
-	}
-
-	refresh(): void {
-		this._onDidChangeTreeData.fire();
-	}
-
-	private root(): Action[] {
-		return [
-			new Action(DOCUMENTATION_LABEL, TreeItemCollapsibleState.Collapsed, 'folder'),
-		];
-	}
-
-	getTreeItem(element: Action): vscode.TreeItem {
-		return element;
-	}
-
-	getChildren(element?: Action): Thenable<Action[]> {
-		if (!element) {
-			return Promise.resolve(this.root());
-		} else if (element.label === DOCUMENTATION_LABEL) {
-			return Promise.resolve([
-				new Action(
-					'Briefing',
-					TreeItemCollapsibleState.None,
-					'document',
-					OPEN_BRIEFING_COMMAND.asVsCodeCommand()),
-			]);
-		}
-
-		return Promise.resolve([]);
-	}
-
+  private _onDidChangeTreeData: vscode.EventEmitter<Action | undefined | void> = new vscode.EventEmitter<
+    Action | undefined | void
+  >();
+  // @ts-ignore
+  readonly onDidChangeTreeData: vscode.Event<Action | undefined | void> = this._onDidChangeTreeData.event;
+
+  constructor(private workspaceRoot: string) {}
+
+  refresh(): void {
+    this._onDidChangeTreeData.fire();
+  }
+
+  private root(): Action[] {
+    return [new Action(DOCUMENTATION_LABEL, TreeItemCollapsibleState.Collapsed, 'folder')];
+  }
+
+  getTreeItem(element: Action): vscode.TreeItem {
+    return element;
+  }
+
+  getChildren(element?: Action): Thenable<Action[]> {
+    if (!element) {
+      return Promise.resolve(this.root());
+    } else if (element.label === DOCUMENTATION_LABEL) {
+      return Promise.resolve([
+        new Action('Briefing', TreeItemCollapsibleState.None, 'document', OPEN_BRIEFING_COMMAND.asVsCodeCommand()),
+      ]);
+    }
+
+    return Promise.resolve([]);
+  }
 }
 
 export class Action extends vscode.TreeItem {
-
-	constructor(
-		public readonly label: string,
-		public readonly collapsibleState: vscode.TreeItemCollapsibleState,
-		public readonly iconName: string,
-		public readonly command?: vscode.Command
-	) {
-		super(label, collapsibleState);
-	}
-
-	get tooltip(): string {
-		return this.label;
-	}
-
-	get description(): string {
-		return '';
-	}
-
-	iconPath = {
-		light: path.join(__filename, '..', '..', 'resources', 'light', `${this.iconName}.svg`),
-		dark: path.join(__filename, '..', '..', 'resources', 'dark', `${this.iconName}.svg`)
-	};
-
-	contextValue = 'dependency';
-
-}
\ No newline at end of file
+  constructor(
+    public readonly label: string,
+    public readonly collapsibleState: vscode.TreeItemCollapsibleState,
+    public readonly iconName: string,
+    public readonly command?: vscode.Command,
+  ) {
+    super(label, collapsibleState);
+  }
+
+  get tooltip(): string {
+    return this.label;
+  }
+
+  get description(): string {
+    return '';
+  }
+
+  iconPath = {
+    light: path.join(__filename, '..', '..', 'resources', 'light', `${this.iconName}.svg`),
+    dark: path.join(__filename, '..', '..', 'resources', 'dark', `${this.iconName}.svg`),
+  };
+
+  contextValue = 'dependency';
+}
diff --git a/deadlock-plugins/deadlock-extension/src/theia/userConfigTheia.ts b/deadlock-plugins/deadlock-extension/src/theia/userConfigTheia.ts
index d4c07f85bd14faec6ca81f7be080883663e090e3..387a808962c71036b199aac2b2e63ce5d078eeee 100644
--- a/deadlock-plugins/deadlock-extension/src/theia/userConfigTheia.ts
+++ b/deadlock-plugins/deadlock-extension/src/theia/userConfigTheia.ts
@@ -4,9 +4,8 @@ import * as vscode from 'vscode';
 import { USER_CHALLENGE_PATH } from '../core/config';
 
 export default class UserConfigTheia extends UserConfig {
-    async loadText(): Promise<string> {
-        const textDocument = await vscode.workspace.openTextDocument(vscode.Uri.parse(USER_CHALLENGE_PATH));
-        return Promise.resolve(textDocument.getText());
-    }
-
-}
\ No newline at end of file
+  async loadText(): Promise<string> {
+    const textDocument = await vscode.workspace.openTextDocument(vscode.Uri.parse(USER_CHALLENGE_PATH));
+    return Promise.resolve(textDocument.getText());
+  }
+}
diff --git a/deadlock-plugins/deadlock-extension/src/view/briefingView.ts b/deadlock-plugins/deadlock-extension/src/view/briefingView.ts
index 21a6ae7727d106c1595a71c3c7f6d5f8f12d2074..c041518b04192392f591fdc2b69c12ccc983fc11 100644
--- a/deadlock-plugins/deadlock-extension/src/view/briefingView.ts
+++ b/deadlock-plugins/deadlock-extension/src/view/briefingView.ts
@@ -28,15 +28,13 @@ export default class BriefingView extends WebviewBase {
   }
 
   setupImages(text: string) {
-    const imageRegex = /\!\[(.*?)\]\(\b(image:)(.*?\))?/g
+    const imageRegex = /\!\[(.*?)\]\(\b(image:)(.*?\))?/g;
 
     return text.replace(imageRegex, (match, title, imagePrefix, img) => {
       const fileName = img.replace(')', '');
       const extension = fileName.split('.');
 
-      return `![${title}](data:image/${
-        extension[extension.length - 1]
-      };base64,${this.toBase64(fileName)})`;
+      return `![${title}](data:image/${extension[extension.length - 1]};base64,${this.toBase64(fileName)})`;
     });
   }
 
@@ -46,25 +44,20 @@ export default class BriefingView extends WebviewBase {
         this.loadingBriefing = true;
         clearInterval(intervalId);
 
-        vscode.workspace
-          .openTextDocument(
-            vscode.Uri.parse(path.join(DOCS_PATH, BRIEFING_FILE_NAME))
-          )
-          .then(
-            (document) => {
-              try {
-                this.briefingContent = this.setupImages(document.getText());
-              } catch (e) {
-                console.error(e);
-                this.briefingContent = 'Error while parsing your briefing.';
-              }
-              this.show();
-            },
-            (error) => {
-              console.error('Cannot load briefing', error);
-              this.errorLoadingBriefing = true;
+        vscode.workspace.openTextDocument(vscode.Uri.parse(path.join(DOCS_PATH, BRIEFING_FILE_NAME))).then(
+          (document) => {
+            try {
+              this.briefingContent = this.setupImages(document.getText());
+            } catch (e) {
+              console.error(e);
+              this.briefingContent = 'Error while parsing your briefing.';
             }
-          );
+          },
+          (error) => {
+            console.error('Cannot load briefing', error);
+            this.errorLoadingBriefing = true;
+          },
+        );
       }
     }, 500);
   }
@@ -77,9 +70,9 @@ export default class BriefingView extends WebviewBase {
         <h2>Professeur</h2>
         Bonjour ${userConfig.getCurrentUserDetails().lastName} ${
         userConfig.getCurrentUserDetails().firstName
-      } vous êtes actuellement entrain de relire le code de <b>${
-        userConfig.getRemoteUserDetails().lastName
-      } ${userConfig.getRemoteUserDetails().firstName}</b>.<br />
+      } vous êtes actuellement entrain de relire le code de <b>${userConfig.getRemoteUserDetails().lastName} ${
+        userConfig.getRemoteUserDetails().firstName
+      }</b>.<br />
         Vous pouvez consulter chaque exécution de son code dans l'onglet à gauche de GitLens. Chaque commit sur <b>master</b> représente une exécution.
         <br />
       `;
diff --git a/deadlock-plugins/deadlock-extension/src/view/quickSetupView.ts b/deadlock-plugins/deadlock-extension/src/view/quickSetupView.ts
new file mode 100644
index 0000000000000000000000000000000000000000..daeb16529db561a4389c29535b84496c16cac449
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/view/quickSetupView.ts
@@ -0,0 +1,135 @@
+import * as vscode from 'vscode';
+import { AUTHENTICATE_COMMAND, CHOOSE_MISSION_WORKDIR_COMMAND } from '../core/commandHandler';
+import ExtensionStore from '../core/extensionStore';
+import { log } from '../recorder/utils';
+import { OPEN_QUICK_SETUP_COMMAND } from '../theia/command';
+import { WebviewBase } from './webviewBase';
+
+export const QUICK_SETUP_ID = 'quickSetup';
+
+export default class QuickSetupView extends WebviewBase {
+  private extensionUri: vscode.Uri;
+  private extensionStore: ExtensionStore;
+  private _isAlreadyConnected: boolean;
+
+  constructor(extensionUri: vscode.Uri) {
+    super(QUICK_SETUP_ID, 'QuickSetup', OPEN_QUICK_SETUP_COMMAND);
+    this.extensionUri = extensionUri;
+    this.extensionStore = ExtensionStore.getInstance();
+    this._isAlreadyConnected = false;
+  }
+
+  set isAlreadyConnected(newVal: boolean) {
+    if (this._isAlreadyConnected !== newVal) {
+      this._isAlreadyConnected = newVal;
+      this.reload();
+    }
+  }
+
+  render(): string {
+    return `
+      <head>
+        ${this.renderHeaderHtml()}
+      </head>
+      <body>
+        ${this.renderHtmlBody()}
+      </body>
+    `;
+  }
+
+  renderHtmlBody() {
+    const hadMissionWorkdir = this.extensionStore.getMissionWorkdir() !== undefined;
+
+    return `
+      <h1>Quick Setup</h1>
+      <div class="deadlock-getting-started-card-container">
+
+        ${this.renderCardHtml(
+          'Connexion à Deadlock',
+          "Tu as besoin d'être connecté à Deadlock pour continuer.",
+          { name: 'Se connecter', onClickFunctionName: 'openAuthenticationPageAction' },
+          this._isAlreadyConnected,
+          this._isAlreadyConnected,
+        )}
+        ${this.renderCardHtml(
+          'Dossier contenant tes exercices',
+          'Choisis le dossier qui contiendra tous les exercices Deadlock.',
+          {
+            name: 'Choisir un dossier',
+            onClickFunctionName: 'launchChooseMissionWorkdirAction',
+          },
+          hadMissionWorkdir,
+        )}
+      </div>
+      
+      
+      `;
+  }
+
+  renderCardHtml(
+    title: string,
+    description: string,
+    button: { name: string; onClickFunctionName: string },
+    isChecked: boolean,
+    isDisabled?: boolean,
+    callbackArgs?: string,
+  ) {
+    return `
+        <div class="deadlock-getting-started-card">
+          <vscode-checkbox ${isChecked ? 'checked' : ''} readonly> </vscode-checkbox>
+          <div class="card-body">
+            <div class="card-title">
+              ${title}
+            </div>
+            <div class="card-description">
+              ${description}
+            </div>
+            <vscode-button ${isDisabled ? 'disabled' : ''} onclick="${button.onClickFunctionName}(${callbackArgs})">${
+      button.name
+    }</vscode-button>
+          </div>
+        </div>
+      
+      `;
+  }
+
+  renderHeaderHtml() {
+    const toolkitUri = this.getExternalRessourcePath(this.extensionUri, [
+      'node_modules',
+      '@vscode',
+      'webview-ui-toolkit',
+      'dist',
+      'toolkit.js',
+    ]);
+
+    const customStyle = this.getExternalRessourcePath(this.extensionUri, [
+      'resources',
+      'styles',
+      'gettingStartedView.css',
+    ]);
+
+    const customScript = this.getExternalRessourcePath(this.extensionUri, ['resources', 'js', 'gettingStartedView.js']);
+
+    return `
+      <meta charset="UTF-8">
+      <script type="module" src="${toolkitUri}"></script>
+      <script type="text/javascript" src="${customScript}"></script>
+      <link href="${customStyle}" rel="stylesheet" />  
+    `;
+  }
+
+  onMessageReceive(message: any): void {
+    switch (message.command) {
+      case 'launchChooseMissionWorkdirAction':
+        vscode.commands.executeCommand(CHOOSE_MISSION_WORKDIR_COMMAND.cmd).then(() => this.reload());
+        return;
+      case 'openAuthenticationPageAction':
+        vscode.commands.executeCommand(AUTHENTICATE_COMMAND.cmd);
+        return;
+    }
+  }
+
+  load(): void {
+    log('Loading GettingStartedView');
+  }
+}
diff --git a/deadlock-plugins/deadlock-extension/src/view/view.ts b/deadlock-plugins/deadlock-extension/src/view/view.ts
index c2387c802709dbbca6fe070b5ba3bd89096c39a5..43791cdbadd2625dd8be886001b18eb52e4aa7e6 100644
--- a/deadlock-plugins/deadlock-extension/src/view/view.ts
+++ b/deadlock-plugins/deadlock-extension/src/view/view.ts
@@ -1,10 +1,8 @@
-import * as path from 'path';
 import * as vscode from 'vscode';
 
 function getNonce() {
   let text = '';
-  const possible =
-    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+  const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
   for (let i = 0; i < 32; i++) {
     text += possible.charAt(Math.floor(Math.random() * possible.length));
   }
@@ -15,7 +13,6 @@ function getNonce() {
  * @deprecated use #webviewBase instead of
  */
 export default abstract class View {
-
   private static views: Map<string, View> = new Map();
 
   public currentWebviewPanel: vscode.WebviewPanel | undefined;
@@ -26,7 +23,7 @@ export default abstract class View {
     id: string,
     private readonly context: vscode.ExtensionContext,
     public readonly panelName: string,
-    public readonly title: string
+    public readonly title: string,
   ) {
     this.isRegisteredOnWebviewPanelSerializer = false;
 
@@ -51,14 +48,9 @@ export default abstract class View {
   }
 
   private init(currentWebviewPanel) {
-
     // Listen for when the panel is disposed
     // This happens when the user closes the panel or when the panel is closed programatically
-    currentWebviewPanel.onDidDispose(
-      () => this.dispose(),
-      null,
-      this._disposables
-    );
+    currentWebviewPanel.onDidDispose(() => this.dispose(), null, this._disposables);
 
     // Update the content based on view changes
     currentWebviewPanel.onDidChangeViewState(
@@ -68,7 +60,7 @@ export default abstract class View {
         }
       },
       null,
-      this._disposables
+      this._disposables,
     );
 
     // Handle messages from the webview
@@ -81,16 +73,13 @@ export default abstract class View {
         }
       },
       null,
-      this._disposables
+      this._disposables,
     );
 
     if (vscode.window.registerWebviewPanelSerializer && !this.isRegisteredOnWebviewPanelSerializer) {
       // Make sure we register a serializer in activation event
       vscode.window.registerWebviewPanelSerializer(this.panelName, {
-        async deserializeWebviewPanel(
-          webviewPanel: vscode.WebviewPanel,
-          state: any
-        ) {
+        async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel, state: any) {
           //   View.revive(webviewPanel, extensionPath);
         },
       });
@@ -99,9 +88,7 @@ export default abstract class View {
   }
 
   public createOrShow() {
-    const column = vscode.window.activeTextEditor
-      ? vscode.window.activeTextEditor.viewColumn
-      : undefined;
+    const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
 
     // If we already have a panel, show it.
     if (this.currentWebviewPanel) {
@@ -121,9 +108,9 @@ export default abstract class View {
         // And restrict the webview to only loading content from our extension's `media` directory.
         // for now we don't need to include the media folder, because React bundle is not used
         // localResourceRoots: [
-          // vscode.Uri.file(path.join(this.extensionPath, 'media')),
+        // vscode.Uri.file(path.join(this.extensionPath, 'media')),
         // ],
-      }
+      },
     );
     // update first time to force render of webviewPanel
     this.update();
@@ -165,16 +152,16 @@ export default abstract class View {
     // /!\ EXPERIMENTAL /!\
     // Local path to main script run in the webview
     // const scriptPathOnDisk = vscode.Uri.file(
-      // path.join(this.extensionPath, 'media', 'static/bundle.js')
+    // path.join(this.extensionPath, 'media', 'static/bundle.js')
     // );
     // const cssPathOnDisk = vscode.Uri.file(
-      // path.join(this.extensionPath, 'media', 'static/bundle.css')
+    // path.join(this.extensionPath, 'media', 'static/bundle.css')
     // );
 
     // And the uri we use to load this script in the webview
     // const scriptUri = webview.asWebviewUri(scriptPathOnDisk);
     // const cssUri = webview.asWebviewUri(cssPathOnDisk);
-		// append in the HTML head <link href="${cssUri}" rel="stylesheet"></head>
+    // append in the HTML head <link href="${cssUri}" rel="stylesheet"></head>
 
     // Use a nonce to whitelist which scripts can be run
     // const nonce = getNonce();
@@ -186,20 +173,20 @@ export default abstract class View {
       content = this.render();
     } catch (e) {
       console.error('Erreur lors du render', e);
-      content = 'Impossible d\'afficher le rendu, nous sommes désolés';
+      content = "Impossible d'afficher le rendu, nous sommes désolés";
     }
 
     return `<!DOCTYPE html><html lang="en">
-		<head>
-		  <meta charset="UTF-8">
-		  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-		  <title>Deadlock</title>
-		</head>
-		<body>
-			<div>
-				${content}
-			</div>
-		</body></html>`;
+    <head>
+      <meta charset="UTF-8">
+      <meta name="viewport" content="width=device-width, initial-scale=1.0">
+      <title>Deadlock</title>
+    </head>
+    <body>
+      <div>
+        ${content}
+      </div>
+    </body></html>`;
   }
 
   /**
diff --git a/deadlock-plugins/deadlock-extension/src/view/webviewBase.ts b/deadlock-plugins/deadlock-extension/src/view/webviewBase.ts
index 14d582f41acaefef13194e2da8c2ef9a68205703..1639cb5634d43b3209661865ad8e2bb0a3dac4ee 100644
--- a/deadlock-plugins/deadlock-extension/src/view/webviewBase.ts
+++ b/deadlock-plugins/deadlock-extension/src/view/webviewBase.ts
@@ -1,14 +1,15 @@
-"use strict";
+'use strict';
 import {
   commands,
   Disposable,
+  Uri,
   ViewColumn,
   Webview,
   WebviewPanel,
   WebviewPanelOnDidChangeViewStateEvent,
   window,
-} from "vscode";
-import { Command } from "../theia/command";
+} from 'vscode';
+import { Command } from '../theia/command';
 
 const emptyCommands: Disposable[] = [
   {
@@ -18,18 +19,24 @@ const emptyCommands: Disposable[] = [
   },
 ];
 
+export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) {
+  return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList));
+}
+
 export abstract class WebviewBase implements Disposable {
   protected disposable: Disposable;
-  private _disposablePanel: Disposable | undefined;
-  private _panel: WebviewPanel | undefined;
+  private disposablePanel: Disposable | undefined;
+  protected panel: WebviewPanel | undefined;
 
-  constructor(private id: string, private title:string, command: Command, private readonly _column?: ViewColumn) {
-    this.disposable = Disposable.from(
-      commands.registerCommand(command.cmd, this.onShowCommand, this)
-    );
+  constructor(private id: string, private title: string, command: Command, private readonly _column?: ViewColumn) {
+    this.disposable = Disposable.from(commands.registerCommand(command.cmd, this.onShowCommand, this));
     this.load();
   }
 
+  getExternalRessourcePath(extensionUri: Uri, pathList: string[]) {
+    return getUri(this.panel!.webview, extensionUri, pathList);
+  }
+
   registerCommands(): Disposable[] {
     return emptyCommands;
   }
@@ -40,7 +47,7 @@ export abstract class WebviewBase implements Disposable {
 
   dispose() {
     this.disposable.dispose();
-    this._disposablePanel?.dispose();
+    this.disposablePanel?.dispose();
   }
 
   protected onShowCommand() {
@@ -48,42 +55,42 @@ export abstract class WebviewBase implements Disposable {
   }
 
   private onPanelDisposed() {
-    this._disposablePanel?.dispose();
-    this._panel = undefined;
+    this.disposablePanel?.dispose();
+    this.panel = undefined;
   }
 
   private onViewStateChanged(e: WebviewPanelOnDidChangeViewStateEvent) {
     console.log(
       `Webview(${this.id}).onViewStateChanged`,
-      `active=${e.webviewPanel.active}, visible=${e.webviewPanel.visible}`
+      `active=${e.webviewPanel.active}, visible=${e.webviewPanel.visible}`,
     );
   }
 
   get visible() {
-    return this._panel?.visible ?? false;
+    return this.panel?.visible ?? false;
   }
 
   hide() {
-    this._panel?.dispose();
+    this.panel?.dispose();
   }
 
   setTitle(title: string) {
-    if (this._panel == null) return;
+    if (this.panel == null) return;
 
-    this._panel.title = title;
+    this.panel.title = title;
   }
 
   onMessageReceive(message) {
     switch (message.command) {
-      case "alert":
+      case 'alert':
         window.showErrorMessage(message.text);
         return;
     }
   }
 
   async show(column: ViewColumn = ViewColumn.Beside): Promise<void> {
-    if (this._panel == null) {
-      this._panel = window.createWebviewPanel(
+    if (this.panel == null) {
+      this.panel = window.createWebviewPanel(
         this.id,
         this.title,
         { viewColumn: column, preserveFocus: false },
@@ -92,26 +99,36 @@ export abstract class WebviewBase implements Disposable {
           enableFindWidget: true,
           enableCommandUris: true,
           enableScripts: true,
-        }
+        },
       );
 
-      this._disposablePanel = Disposable.from(
-        this._panel,
-        this._panel.onDidDispose(this.onPanelDisposed, this),
-        this._panel.onDidChangeViewState(this.onViewStateChanged, this),
-        this._panel.webview.onDidReceiveMessage(this.onMessageReceive, this),
-        ...this.registerCommands()
+      this.disposablePanel = Disposable.from(
+        this.panel,
+        this.panel.onDidDispose(this.onPanelDisposed, this),
+        this.panel.onDidChangeViewState(this.onViewStateChanged, this),
+        this.panel.webview.onDidReceiveMessage(this.onMessageReceive, this),
+        ...this.registerCommands(),
       );
 
-      this._panel.webview.html = await this.getHtml(this._panel.webview);
+      this.panel.webview.html = await this.getHtml(this.panel.webview);
     } else {
-      const html = await this.getHtml(this._panel.webview);
+      const html = await this.getHtml(this.panel.webview);
 
       // Reset the html to get the webview to reload
-      this._panel.webview.html = "";
-      this._panel.webview.html = html;
+      this.panel.webview.html = '';
+      this.panel.webview.html = html;
 
-      this._panel.reveal(this._panel.viewColumn ?? ViewColumn.Active, false);
+      this.panel.reveal(this.panel.viewColumn ?? ViewColumn.Active, false);
+    }
+  }
+
+  async reload() {
+    if (this.panel) {
+      const html = await this.getHtml(this.panel.webview);
+
+      // Reset the html to get the webview to reload
+      this.panel.webview.html = '';
+      this.panel.webview.html = html;
     }
   }
 
@@ -125,7 +142,6 @@ export abstract class WebviewBase implements Disposable {
   abstract load(): void;
 
   private async getHtml(webview: Webview): Promise<string> {
-	  return this.render();
+    return this.render();
   }
-
 }
diff --git a/deadlock-plugins/deadlock-extension/tsconfig.json b/deadlock-plugins/deadlock-extension/tsconfig.json
index 45ea4863817c56fa21e1f40ba11c6c29935fb8a0..adaa4e495b1fd3b88482adf9b1a89fb97262fbdf 100644
--- a/deadlock-plugins/deadlock-extension/tsconfig.json
+++ b/deadlock-plugins/deadlock-extension/tsconfig.json
@@ -1,14 +1,18 @@
 {
-	"compilerOptions": {
-		"module": "commonjs",
-		"target": "es2019",
-		"lib": ["ES2019"],
-		"outDir": "out",
-		"sourceMap": false,
-		"strict": true,
-		"rootDir": "src",
-		"noImplicitAny": false,
-		"allowJs": true,
-	},
-	"exclude": ["node_modules", ".vscode-test", "front", "out", "media"]
+  "compilerOptions": {
+    "module": "commonjs",
+    "target": "es2019",
+    "lib": ["ES2019"],
+    "outDir": "out",
+    "sourceMap": false,
+    "strict": true,
+    "rootDir": "src",
+    "noImplicitAny": false,
+    "allowJs": true,
+    "types": ["reflect-metadata"],
+    "moduleResolution": "node",
+    "experimentalDecorators": true,
+    "emitDecoratorMetadata": true
+  },
+  "exclude": ["node_modules", ".vscode-test", "front", "out", "media", "resources"]
 }
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..392bf32cda135bf2b4ca4bc13b692b889436e486
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1705 @@
+{
+  "name": "deadlock-theia",
+  "version": "1.0.0",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "deadlock-theia",
+      "version": "1.0.0",
+      "devDependencies": {
+        "husky": "^7.0.4",
+        "lint-staged": "^12.3.7",
+        "prettier": "2.6.2"
+      }
+    },
+    "node_modules/aggregate-error": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+      "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+      "dev": true,
+      "dependencies": {
+        "clean-stack": "^2.0.0",
+        "indent-string": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ansi-escapes": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+      "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+      "dev": true,
+      "dependencies": {
+        "type-fest": "^0.21.3"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "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==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz",
+      "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/astral-regex": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+      "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dev": true,
+      "dependencies": {
+        "fill-range": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/clean-stack": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+      "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/cli-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+      "dev": true,
+      "dependencies": {
+        "restore-cursor": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cli-truncate": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz",
+      "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==",
+      "dev": true,
+      "dependencies": {
+        "slice-ansi": "^5.0.0",
+        "string-width": "^5.0.0"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "node_modules/colorette": {
+      "version": "2.0.16",
+      "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
+      "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
+      "dev": true
+    },
+    "node_modules/commander": {
+      "version": "8.3.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+      "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+      "dev": true,
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/cross-spawn": {
+      "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"
+      }
+    },
+    "node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/eastasianwidth": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+      "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+      "dev": true
+    },
+    "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==",
+      "dev": true
+    },
+    "node_modules/execa": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+      "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+      "dev": true,
+      "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/fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dev": true,
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "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==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "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==",
+      "dev": true,
+      "engines": {
+        "node": ">=10.17.0"
+      }
+    },
+    "node_modules/husky": {
+      "version": "7.0.4",
+      "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz",
+      "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==",
+      "dev": true,
+      "bin": {
+        "husky": "lib/bin.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/typicode"
+      }
+    },
+    "node_modules/indent-string": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+      "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-fullwidth-code-point": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
+      "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/is-stream": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+      "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "node_modules/lilconfig": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz",
+      "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/lint-staged": {
+      "version": "12.3.7",
+      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.3.7.tgz",
+      "integrity": "sha512-/S4D726e2GIsDVWIk1XGvheCaDm1SJRQp8efamZFWJxQMVEbOwSysp7xb49Oo73KYCdy97mIWinhlxcoNqIfIQ==",
+      "dev": true,
+      "dependencies": {
+        "cli-truncate": "^3.1.0",
+        "colorette": "^2.0.16",
+        "commander": "^8.3.0",
+        "debug": "^4.3.3",
+        "execa": "^5.1.1",
+        "lilconfig": "2.0.4",
+        "listr2": "^4.0.1",
+        "micromatch": "^4.0.4",
+        "normalize-path": "^3.0.0",
+        "object-inspect": "^1.12.0",
+        "pidtree": "^0.5.0",
+        "string-argv": "^0.3.1",
+        "supports-color": "^9.2.1",
+        "yaml": "^1.10.2"
+      },
+      "bin": {
+        "lint-staged": "bin/lint-staged.js"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/lint-staged"
+      }
+    },
+    "node_modules/listr2": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz",
+      "integrity": "sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==",
+      "dev": true,
+      "dependencies": {
+        "cli-truncate": "^2.1.0",
+        "colorette": "^2.0.16",
+        "log-update": "^4.0.0",
+        "p-map": "^4.0.0",
+        "rfdc": "^1.3.0",
+        "rxjs": "^7.5.5",
+        "through": "^2.3.8",
+        "wrap-ansi": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "peerDependencies": {
+        "enquirer": ">= 2.3.0 < 3"
+      },
+      "peerDependenciesMeta": {
+        "enquirer": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/listr2/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/listr2/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/listr2/node_modules/cli-truncate": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz",
+      "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
+      "dev": true,
+      "dependencies": {
+        "slice-ansi": "^3.0.0",
+        "string-width": "^4.2.0"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/listr2/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true
+    },
+    "node_modules/listr2/node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/listr2/node_modules/slice-ansi": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
+      "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "astral-regex": "^2.0.0",
+        "is-fullwidth-code-point": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/listr2/node_modules/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==",
+      "dev": true,
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/listr2/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/log-update": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz",
+      "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==",
+      "dev": true,
+      "dependencies": {
+        "ansi-escapes": "^4.3.0",
+        "cli-cursor": "^3.1.0",
+        "slice-ansi": "^4.0.0",
+        "wrap-ansi": "^6.2.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/log-update/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/log-update/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/log-update/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true
+    },
+    "node_modules/log-update/node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/log-update/node_modules/slice-ansi": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+      "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "astral-regex": "^2.0.0",
+        "is-fullwidth-code-point": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+      }
+    },
+    "node_modules/log-update/node_modules/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==",
+      "dev": true,
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/log-update/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/log-update/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==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "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==",
+      "dev": true
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+      "dev": true,
+      "dependencies": {
+        "braces": "^3.0.2",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "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==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "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==",
+      "dev": true,
+      "dependencies": {
+        "path-key": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.12.0",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
+      "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/onetime": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+      "dev": true,
+      "dependencies": {
+        "mimic-fn": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/p-map": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+      "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+      "dev": true,
+      "dependencies": {
+        "aggregate-error": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/path-key": {
+      "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"
+      }
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/pidtree": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.5.0.tgz",
+      "integrity": "sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==",
+      "dev": true,
+      "bin": {
+        "pidtree": "bin/pidtree.js"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/prettier": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
+      "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
+      "dev": true,
+      "bin": {
+        "prettier": "bin-prettier.js"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      },
+      "funding": {
+        "url": "https://github.com/prettier/prettier?sponsor=1"
+      }
+    },
+    "node_modules/restore-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+      "dev": true,
+      "dependencies": {
+        "onetime": "^5.1.0",
+        "signal-exit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/rfdc": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
+      "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==",
+      "dev": true
+    },
+    "node_modules/rxjs": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
+      "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==",
+      "dev": true,
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/shebang-command": {
+      "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"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "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/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==",
+      "dev": true
+    },
+    "node_modules/slice-ansi": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
+      "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^6.0.0",
+        "is-fullwidth-code-point": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+      }
+    },
+    "node_modules/string-argv": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz",
+      "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.6.19"
+      }
+    },
+    "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==",
+      "dev": true,
+      "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/strip-ansi": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
+      "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+      }
+    },
+    "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==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.2.tgz",
+      "integrity": "sha512-XC6g/Kgux+rJXmwokjm9ECpD6k/smUoS5LKlUCcsYr4IY3rW0XyAympon2RmxGrlnZURMpg5T18gWDP9CsHXFA==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/supports-color?sponsor=1"
+      }
+    },
+    "node_modules/through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "dev": true
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+      "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
+      "dev": true
+    },
+    "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==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "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,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "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/wrap-ansi/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/wrap-ansi/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/wrap-ansi/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true
+    },
+    "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/wrap-ansi/node_modules/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==",
+      "dev": true,
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/wrap-ansi/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/yaml": {
+      "version": "1.10.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+      "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 6"
+      }
+    }
+  },
+  "dependencies": {
+    "aggregate-error": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+      "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+      "dev": true,
+      "requires": {
+        "clean-stack": "^2.0.0",
+        "indent-string": "^4.0.0"
+      }
+    },
+    "ansi-escapes": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+      "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+      "dev": true,
+      "requires": {
+        "type-fest": "^0.21.3"
+      }
+    },
+    "ansi-regex": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+      "dev": true
+    },
+    "ansi-styles": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz",
+      "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==",
+      "dev": true
+    },
+    "astral-regex": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+      "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+      "dev": true
+    },
+    "braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dev": true,
+      "requires": {
+        "fill-range": "^7.0.1"
+      }
+    },
+    "clean-stack": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+      "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+      "dev": true
+    },
+    "cli-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+      "dev": true,
+      "requires": {
+        "restore-cursor": "^3.1.0"
+      }
+    },
+    "cli-truncate": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz",
+      "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==",
+      "dev": true,
+      "requires": {
+        "slice-ansi": "^5.0.0",
+        "string-width": "^5.0.0"
+      }
+    },
+    "color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "requires": {
+        "color-name": "~1.1.4"
+      }
+    },
+    "color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "colorette": {
+      "version": "2.0.16",
+      "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
+      "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
+      "dev": true
+    },
+    "commander": {
+      "version": "8.3.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+      "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+      "dev": true
+    },
+    "cross-spawn": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+      "dev": true,
+      "requires": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      }
+    },
+    "debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "requires": {
+        "ms": "2.1.2"
+      }
+    },
+    "eastasianwidth": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+      "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+      "dev": true
+    },
+    "emoji-regex": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+      "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+      "dev": true
+    },
+    "execa": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+      "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+      "dev": true,
+      "requires": {
+        "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"
+      }
+    },
+    "fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dev": true,
+      "requires": {
+        "to-regex-range": "^5.0.1"
+      }
+    },
+    "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==",
+      "dev": true
+    },
+    "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==",
+      "dev": true
+    },
+    "husky": {
+      "version": "7.0.4",
+      "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz",
+      "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==",
+      "dev": true
+    },
+    "indent-string": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+      "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+      "dev": true
+    },
+    "is-fullwidth-code-point": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
+      "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
+      "dev": true
+    },
+    "is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true
+    },
+    "is-stream": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+      "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+      "dev": true
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "lilconfig": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz",
+      "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==",
+      "dev": true
+    },
+    "lint-staged": {
+      "version": "12.3.7",
+      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.3.7.tgz",
+      "integrity": "sha512-/S4D726e2GIsDVWIk1XGvheCaDm1SJRQp8efamZFWJxQMVEbOwSysp7xb49Oo73KYCdy97mIWinhlxcoNqIfIQ==",
+      "dev": true,
+      "requires": {
+        "cli-truncate": "^3.1.0",
+        "colorette": "^2.0.16",
+        "commander": "^8.3.0",
+        "debug": "^4.3.3",
+        "execa": "^5.1.1",
+        "lilconfig": "2.0.4",
+        "listr2": "^4.0.1",
+        "micromatch": "^4.0.4",
+        "normalize-path": "^3.0.0",
+        "object-inspect": "^1.12.0",
+        "pidtree": "^0.5.0",
+        "string-argv": "^0.3.1",
+        "supports-color": "^9.2.1",
+        "yaml": "^1.10.2"
+      }
+    },
+    "listr2": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz",
+      "integrity": "sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==",
+      "dev": true,
+      "requires": {
+        "cli-truncate": "^2.1.0",
+        "colorette": "^2.0.16",
+        "log-update": "^4.0.0",
+        "p-map": "^4.0.0",
+        "rfdc": "^1.3.0",
+        "rxjs": "^7.5.5",
+        "through": "^2.3.8",
+        "wrap-ansi": "^7.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+          "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "dev": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "cli-truncate": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz",
+          "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
+          "dev": true,
+          "requires": {
+            "slice-ansi": "^3.0.0",
+            "string-width": "^4.2.0"
+          }
+        },
+        "emoji-regex": {
+          "version": "8.0.0",
+          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+          "dev": true
+        },
+        "slice-ansi": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
+          "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^4.0.0",
+            "astral-regex": "^2.0.0",
+            "is-fullwidth-code-point": "^3.0.0"
+          }
+        },
+        "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==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^8.0.0",
+            "is-fullwidth-code-point": "^3.0.0",
+            "strip-ansi": "^6.0.1"
+          }
+        },
+        "strip-ansi": {
+          "version": "6.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+          "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^5.0.1"
+          }
+        }
+      }
+    },
+    "log-update": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz",
+      "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==",
+      "dev": true,
+      "requires": {
+        "ansi-escapes": "^4.3.0",
+        "cli-cursor": "^3.1.0",
+        "slice-ansi": "^4.0.0",
+        "wrap-ansi": "^6.2.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+          "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "dev": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "emoji-regex": {
+          "version": "8.0.0",
+          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+          "dev": true
+        },
+        "slice-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+          "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^4.0.0",
+            "astral-regex": "^2.0.0",
+            "is-fullwidth-code-point": "^3.0.0"
+          }
+        },
+        "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==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^8.0.0",
+            "is-fullwidth-code-point": "^3.0.0",
+            "strip-ansi": "^6.0.1"
+          }
+        },
+        "strip-ansi": {
+          "version": "6.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+          "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^5.0.1"
+          }
+        },
+        "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==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^4.0.0",
+            "string-width": "^4.1.0",
+            "strip-ansi": "^6.0.0"
+          }
+        }
+      }
+    },
+    "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==",
+      "dev": true
+    },
+    "micromatch": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+      "dev": true,
+      "requires": {
+        "braces": "^3.0.2",
+        "picomatch": "^2.3.1"
+      }
+    },
+    "mimic-fn": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "dev": true
+    },
+    "ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true
+    },
+    "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==",
+      "dev": true,
+      "requires": {
+        "path-key": "^3.0.0"
+      }
+    },
+    "object-inspect": {
+      "version": "1.12.0",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
+      "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==",
+      "dev": true
+    },
+    "onetime": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+      "dev": true,
+      "requires": {
+        "mimic-fn": "^2.1.0"
+      }
+    },
+    "p-map": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+      "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+      "dev": true,
+      "requires": {
+        "aggregate-error": "^3.0.0"
+      }
+    },
+    "path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true
+    },
+    "picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true
+    },
+    "pidtree": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.5.0.tgz",
+      "integrity": "sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==",
+      "dev": true
+    },
+    "prettier": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
+      "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
+      "dev": true
+    },
+    "restore-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+      "dev": true,
+      "requires": {
+        "onetime": "^5.1.0",
+        "signal-exit": "^3.0.2"
+      }
+    },
+    "rfdc": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
+      "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==",
+      "dev": true
+    },
+    "rxjs": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
+      "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==",
+      "dev": true,
+      "requires": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "shebang-command": {
+      "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,
+      "requires": {
+        "shebang-regex": "^3.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true
+    },
+    "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==",
+      "dev": true
+    },
+    "slice-ansi": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
+      "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^6.0.0",
+        "is-fullwidth-code-point": "^4.0.0"
+      }
+    },
+    "string-argv": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz",
+      "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==",
+      "dev": true
+    },
+    "string-width": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+      "dev": true,
+      "requires": {
+        "eastasianwidth": "^0.2.0",
+        "emoji-regex": "^9.2.2",
+        "strip-ansi": "^7.0.1"
+      }
+    },
+    "strip-ansi": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
+      "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^6.0.1"
+      }
+    },
+    "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==",
+      "dev": true
+    },
+    "supports-color": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.2.tgz",
+      "integrity": "sha512-XC6g/Kgux+rJXmwokjm9ECpD6k/smUoS5LKlUCcsYr4IY3rW0XyAympon2RmxGrlnZURMpg5T18gWDP9CsHXFA==",
+      "dev": true
+    },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "dev": true
+    },
+    "to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "requires": {
+        "is-number": "^7.0.0"
+      }
+    },
+    "tslib": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+      "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
+      "dev": true
+    },
+    "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==",
+      "dev": true
+    },
+    "which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "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,
+      "requires": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+          "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "dev": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "emoji-regex": {
+          "version": "8.0.0",
+          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+          "dev": true
+        },
+        "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==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^8.0.0",
+            "is-fullwidth-code-point": "^3.0.0",
+            "strip-ansi": "^6.0.1"
+          }
+        },
+        "strip-ansi": {
+          "version": "6.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+          "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^5.0.1"
+          }
+        }
+      }
+    },
+    "yaml": {
+      "version": "1.10.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+      "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+      "dev": true
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..784d63424ab0720db5c3080ad5c7da5e34770300
--- /dev/null
+++ b/package.json
@@ -0,0 +1,24 @@
+{
+  "name": "deadlock-theia",
+  "version": "1.0.0",
+  "description": "Deadlock Theia projet basé sur notre propre image de Theia https://git.e-biz.fr/deadlock-public/theia.",
+  "scripts": {
+    "prepare": "husky install"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git@git.e-biz.fr:deadlock-public/deadlock-theia.git"
+  },
+  "author": "Takima",
+  "devDependencies": {
+    "husky": "^7.0.4",
+    "lint-staged": "^12.3.7",
+    "prettier": "2.6.2"
+  },
+  "lint-staged": {
+    "*.{ts,md,js,json}": [
+      "prettier --write --ignore-unknown",
+      "git add"
+    ]
+  }
+}
diff --git a/server.js b/server.js
index b553783e30565b8c16184bc9dd3fe31584b0e112..00cc0611f7b3a6daf4060cd0acc11449fb63b357 100644
--- a/server.js
+++ b/server.js
@@ -5,13 +5,12 @@
  * then you will find the content within /home/theia/src-gen/backend/server.js in the container
  */
 
-
 // @ts-check
 require('reflect-metadata');
 
 // Patch electron version if missing, see https://github.com/eclipse-theia/theia/pull/7361#pullrequestreview-377065146
 if (typeof process.versions.electron === 'undefined' && typeof process.env.THEIA_ELECTRON_VERSION === 'string') {
-    process.versions.electron = process.env.THEIA_ELECTRON_VERSION;
+  process.versions.electron = process.env.THEIA_ELECTRON_VERSION;
 }
 
 const path = require('path');
@@ -29,58 +28,96 @@ container.load(loggerBackendModule);
 
 //CORS middleware
 const allowCrossDomain = function (req, res, next) {
-    if (req.headers.origin && req.headers.origin.endsWith('.deadlock.io')) {
-        res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
-        res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type');
-        res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
-    }
-    next();
+  if (req.headers.origin && req.headers.origin.endsWith('.deadlock.io')) {
+    res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
+    res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type');
+    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
+  }
+  next();
 };
 
 function load(raw) {
-    return Promise.resolve(raw.default).then(module =>
-        container.load(module)
-    )
+  return Promise.resolve(raw.default).then((module) => container.load(module));
 }
 
 function start(port, host, argv) {
-    if (argv === undefined) {
-        argv = process.argv;
-    }
+  if (argv === undefined) {
+    argv = process.argv;
+  }
 
-    const cliManager = container.get(CliManager);
-    return cliManager.initializeCli(argv).then(function () {
-        const application = container.get(BackendApplication);
-        application.use(allowCrossDomain);
-        application.use(express.static(path.join(__dirname, '../../lib')));
-        application.use(express.static(path.join(__dirname, '../../lib/index.html')));
-        return application.start(port, host);
-    });
+  const cliManager = container.get(CliManager);
+  return cliManager.initializeCli(argv).then(function () {
+    const application = container.get(BackendApplication);
+    application.use(allowCrossDomain);
+    application.use(express.static(path.join(__dirname, '../../lib')));
+    application.use(express.static(path.join(__dirname, '../../lib/index.html')));
+    return application.start(port, host);
+  });
 }
 
-module.exports = (port, host, argv) => Promise.resolve()
-    .then(function () { return Promise.resolve(require('@theia/core/lib/node/i18n/i18n-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/core/lib/node/hosting/backend-hosting-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/filesystem/lib/node/filesystem-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/filesystem/lib/node/download/file-download-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/workspace/lib/node/workspace-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/process/lib/common/process-common-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/process/lib/node/process-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/file-search/lib/node/file-search-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/git/lib/node/git-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/git/lib/node/env/git-env-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/terminal/lib/node/terminal-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/task/lib/node/task-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/debug/lib/node/debug-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/search-in-workspace/lib/node/search-in-workspace-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/plugin-ext/lib/plugin-ext-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/plugin-ext-vscode/lib/node/plugin-vscode-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/mini-browser/lib/node/mini-browser-backend-module')).then(load) })
-    .then(function () { return Promise.resolve(require('@theia/vsx-registry/lib/node/vsx-registry-backend-module')).then(load) })
-    .then(() => start(port, host, argv)).catch(reason => {
-        console.error('Failed to start the backend application.');
-        if (reason) {
-            console.error(reason);
-        }
-        throw reason;
+module.exports = (port, host, argv) =>
+  Promise.resolve()
+    .then(function () {
+      return Promise.resolve(require('@theia/core/lib/node/i18n/i18n-backend-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/core/lib/node/hosting/backend-hosting-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/filesystem/lib/node/filesystem-backend-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/filesystem/lib/node/download/file-download-backend-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/workspace/lib/node/workspace-backend-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/process/lib/common/process-common-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/process/lib/node/process-backend-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/file-search/lib/node/file-search-backend-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/git/lib/node/git-backend-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/git/lib/node/env/git-env-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/terminal/lib/node/terminal-backend-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/task/lib/node/task-backend-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/debug/lib/node/debug-backend-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/search-in-workspace/lib/node/search-in-workspace-backend-module')).then(
+        load,
+      );
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/plugin-ext/lib/plugin-ext-backend-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/plugin-ext-vscode/lib/node/plugin-vscode-backend-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/mini-browser/lib/node/mini-browser-backend-module')).then(load);
+    })
+    .then(function () {
+      return Promise.resolve(require('@theia/vsx-registry/lib/node/vsx-registry-backend-module')).then(load);
+    })
+    .then(() => start(port, host, argv))
+    .catch((reason) => {
+      console.error('Failed to start the backend application.');
+      if (reason) {
+        console.error(reason);
+      }
+      throw reason;
     });
diff --git a/setup.sh b/setup.sh
new file mode 100755
index 0000000000000000000000000000000000000000..2ebb8e0d4157f9d08fc87605c996fc2daf1e1085
--- /dev/null
+++ b/setup.sh
@@ -0,0 +1,2 @@
+npm install
+npm install --prefix ./deadlock-plugins/deadlock-extension
\ No newline at end of file