Repository: stateful/vscode-runme
Pull request #595 (2023-06-09 18:40:13)

Use prettier
Outfits the repo to use prettier.

Also enables auto-save by default in `settings.json`, I hope this is not controversial.


Patch:
diff --git a/.eslintrc.json b/.eslintrc.json
index 9b10de8e..699eee0e 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,7 +1,8 @@
 {
   "root": true,
   "extends": [
-    "plugin:import/typescript"
+    "plugin:import/typescript",
+    "prettier"
   ],
   "parser": "@typescript-eslint/parser",
   "parserOptions": {
@@ -23,22 +24,11 @@
     "@typescript-eslint/no-unused-vars": "error",
     "@typescript-eslint/semi": ["error", "never"],
     "@typescript-eslint/naming-convention": "off",
-    "@typescript-eslint/member-delimiter-style": ["error", {
-      "multiline": {
-        "delimiter": "none",
-        "requireLast": true
-      },
-      "singleline": {
-        "delimiter": "comma",
-        "requireLast": false
-      },
-      "multilineDetection": "brackets"
-    }],
     "no-unused-vars": "off",
     "curly": "warn",
     "eqeqeq": "warn",
     "no-throw-literal": "warn",
-    "quotes": [2, "single"],
+    "quotes": ["error", "single", { "avoidEscape": true }],
     "semi": [
       "error",
       "never"
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..47a400fa
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,19 @@
+/**/*.*
+!src/**/*.ts
+!src/**/*.tsx
+!src/**/*.js
+!src/**/*.jsx
+
+out
+node_modules
+.vscode-test/
+*.vsix
+wasm
+.DS_Store
+coverage
+.wdio-vscode-service
+examples/fresh/deno.lock
+tests/e2e/logs
+tests/e2e/screenshots
+bin
+vscode-runme-CHANGELOG.txt
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 00000000..75a894a2
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,5 @@
+{
+  "semi": false,
+  "singleQuote": true,
+  "printWidth": 100
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 4dab8159..cf810461 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -8,5 +8,18 @@
   },
   // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
   "typescript.tsc.autoDetect": "off",
-  "marquee.widgets.npm-stats.packageNames": ["runme"]
+  "marquee.widgets.npm-stats.packageNames": ["runme"],
+  "editor.formatOnSave": true,
+  "[typescript]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[typescriptreact]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[javascript]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[javascriptreact]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  }
 }
diff --git a/README.md b/README.md
index 46ddab4f..9af5fb3e 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
 # [Runme](http://runme.dev) for [Visual Studio Code](https://code.visualstudio.com/) [![Tests](https://github.com/stateful/vscode-runme/actions/workflows/test.yml/badge.svg)](https://github.com/stateful/vscode-runme/actions/workflows/test.yml)
 
-This VS Code extension transparently opens markdown files (i.e. README.md) as runnable VS Code notebooks (with the click of a `play` button). Annotating markdown code blocks repo maintainers can enhance the interactive runbook-like experience of their markdown notebooks while they will continue to render properly in markdown viewers.
+This VS Code extension transparently opens markdown files (i.e. README.md) as runnable VS Code notebooks (with the click of a `play` button). Annotating markdown code blocks repo maintainers can enhance the interactive runbook-like experience of their markdown notebooks while they will continue to render properly in markdown viewers. Runme is open source and licensed under APL2.0.
 
 > 💡 If you don't want the runme notebook, you can always right click on the .md file, click `Open With...` and select the text editor. To make your choice permanent pick "Configure default editor for...".
 
-![Text and notebook view side-by-side](https://runme.dev/img/intro.gif)
+![Text and notebook view side-by-side](https://runme.dev/img/short.gif)
 
 ## Documentation
 
@@ -12,14 +12,8 @@ Please visit Runme's documentation to learn about its purpose, all its features,
 
 - [https://docs.runme.dev/](https://docs.runme.dev/)
 
-This project is open source licensed under APL2.0 and its code can be found in the following repos:
-
-- [stateful/vscode-runme](https://github.com/stateful/vscode-runme)
-- [stateful/runme](https://github.com/stateful/runme)
-- [stateful/docs.runme.dev](https://github.com/stateful/docs.runme.dev)
-
 Feel free to submit any issues you may have via the
-[issue tracker](https://github.com/stateful/vscode-runme/issues) or [tell us about it on Discord](https://discord.gg/stateful).
+[issue tracker](https://github.com/stateful/vscode-runme/issues) or [tell us about it on Discord](https://discord.gg/runme).
 
 # Installation
 
@@ -51,7 +45,7 @@ Check the complete list of configuration options at https://docs.runme.dev/confi
 
 Runme is in rapid development. Please be aware of following known limitations:
 
-- Only shell is currently supported on macOS, Linux, and Windows ([WSL]([url](https://learn.microsoft.com/en-us/windows/wsl/))). PowerShell is not supported yet
+- Only shell is currently supported on macOS, Linux, and Windows ([WSL](<[url](https://learn.microsoft.com/en-us/windows/wsl/)>)). PowerShell is not supported yet
 - Be aware of edge cases. Let us know when you hit any snags. We appreciate it!
 
 We would love to hear feedback, appreciate your patience, as Runme continutes to harden. Get in touch please!
diff --git a/__mocks__/vscode-telemetry.ts b/__mocks__/vscode-telemetry.ts
index 70ef8d25..3fdee089 100644
--- a/__mocks__/vscode-telemetry.ts
+++ b/__mocks__/vscode-telemetry.ts
@@ -1,5 +1,6 @@
 import { vi } from 'vitest'
 
 export const TelemetryReporter = {
-  sendTelemetryEvent: vi.fn()
+  sendTelemetryEvent: vi.fn(),
+  sendTelemetryErrorEvent: vi.fn()
 }
diff --git a/__mocks__/vscode.ts b/__mocks__/vscode.ts
index 0846fd90..acaf5fdc 100644
--- a/__mocks__/vscode.ts
+++ b/__mocks__/vscode.ts
@@ -232,6 +232,7 @@ export class EventEmitter<T> {
 
 export const languages = {
   registerCodeLensProvider: vi.fn(),
+  getLanguages: vi.fn().mockResolvedValue([]),
 }
 
 export class Position {
diff --git a/examples/README.md b/examples/README.md
index f3b7c23f..f4af078f 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -151,3 +151,19 @@ echo Rows: \$(tput lines)
 echo Columns: \$(tput cols)
 "
 ```
+
+## Inspect JSON files
+
+With [`antonmedv/fx`](https://github.com/antonmedv/fx) you can inspect JSON files interactively in Runme notebooks, e.g.:
+
+```sh
+curl "https://api.marquee.activecove.com/getWeather?lat=52&lon=10" | fx
+```
+
+## YAML
+
+```yaml
+config:
+  netsed:
+    para: true
+```
diff --git a/package-lock.json b/package-lock.json
index 373e1485..093d466d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,15 +1,16 @@
 {
   "name": "runme",
-  "version": "1.5.2-edge.1",
+  "version": "1.5.4-edge.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "runme",
-      "version": "1.5.2-edge.1",
+      "version": "1.5.4-edge.0",
       "hasInstallScript": true,
       "license": "Apache-2.0",
       "dependencies": {
+        "@apollo/client": "^3.7.15",
         "@buf/grpc_grpc.community_timostamm-protobuf-ts": "^2.9.0-20230505202852-f6db652602f5.1",
         "@buf/stateful_runme.community_timostamm-protobuf-ts": "^2.9.0-20230522162609-549daeeacece.1",
         "@protobuf-ts/grpc-transport": "^2.9.0",
@@ -20,11 +21,14 @@
         "@vscode/vscode-languagedetection": "^1.0.22",
         "@vscode/webview-ui-toolkit": "^1.2.2",
         "clipboardy": "^3.0.0",
+        "clsx": "^1.2.1",
+        "cross-fetch": "^3.1.6",
         "detect-port": "^1.5.1",
         "dotenv": "^16.0.3",
         "filenamify": "^6.0.0",
         "get-port": "^6.1.2",
         "got": "^12.6.0",
+        "graphql": "^16.6.0",
         "lit": "^2.7.4",
         "octokit": "^2.0.14",
         "preact": "^10.14.1",
@@ -63,6 +67,7 @@
         "c8": "^7.13.0",
         "clipboardy": "^3.0.0",
         "eslint": "^8.40.0",
+        "eslint-config-prettier": "^8.8.0",
         "eslint-import-resolver-typescript": "^3.5.5",
         "eslint-plugin-import": "^2.27.5",
         "eslint-plugin-unicorn": "^47.0.0",
@@ -71,6 +76,7 @@
         "fork-ts-checker-webpack-plugin": "^8.0.0",
         "husky": "^8.0.3",
         "npm-run-all": "^4.1.5",
+        "prettier": "2.8.8",
         "runme": "^2.3.5",
         "semver": "^7.5.1",
         "tar-fs": "^2.1.1",
@@ -116,6 +122,52 @@
         "node": ">=6.0.0"
       }
     },
+    "node_modules/@apollo/client": {
+      "version": "3.7.15",
+      "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.7.15.tgz",
+      "integrity": "sha512-pLScjo4GAQRWKWyEg2J3FlQr9fbUAuADT0EI2+JlLf2BjaU9I7WUaZVD9w+0qNPE8BZqs53MRQq0OCm1QCW+eg==",
+      "dependencies": {
+        "@graphql-typed-document-node/core": "^3.1.1",
+        "@wry/context": "^0.7.0",
+        "@wry/equality": "^0.5.0",
+        "@wry/trie": "^0.4.0",
+        "graphql-tag": "^2.12.6",
+        "hoist-non-react-statics": "^3.3.2",
+        "optimism": "^0.16.2",
+        "prop-types": "^15.7.2",
+        "response-iterator": "^0.2.6",
+        "symbol-observable": "^4.0.0",
+        "ts-invariant": "^0.10.3",
+        "tslib": "^2.3.0",
+        "zen-observable-ts": "^1.2.5"
+      },
+      "peerDependencies": {
+        "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0",
+        "graphql-ws": "^5.5.5",
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+        "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
+        "subscriptions-transport-ws": "^0.9.0 || ^0.11.0"
+      },
+      "peerDependenciesMeta": {
+        "graphql-ws": {
+          "optional": true
+        },
+        "react": {
+          "optional": true
+        },
+        "react-dom": {
+          "optional": true
+        },
+        "subscriptions-transport-ws": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@apollo/client/node_modules/tslib": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+      "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+    },
     "node_modules/@babel/code-frame": {
       "version": "7.21.4",
       "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
@@ -2584,6 +2636,14 @@
         "node": ">=10"
       }
     },
+    "node_modules/@graphql-typed-document-node/core": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz",
+      "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==",
+      "peerDependencies": {
+        "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
+      }
+    },
     "node_modules/@grpc/grpc-js": {
       "version": "1.8.14",
       "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.14.tgz",
@@ -2997,6 +3057,25 @@
         "node-pre-gyp": "bin/node-pre-gyp"
       }
     },
+    "node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": {
+      "version": "2.6.11",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
+      "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@microsoft/1ds-core-js": {
       "version": "3.2.11",
       "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-3.2.11.tgz",
@@ -3467,6 +3546,25 @@
         "node": ">= 14"
       }
     },
+    "node_modules/@octokit/request/node_modules/node-fetch": {
+      "version": "2.6.11",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
+      "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@octokit/rest": {
       "version": "19.0.11",
       "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.11.tgz",
@@ -4072,6 +4170,25 @@
       "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
       "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
     },
+    "node_modules/@remix-run/dev/node_modules/node-fetch": {
+      "version": "2.6.11",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
+      "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@remix-run/dev/node_modules/npm-run-path": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@@ -4097,6 +4214,20 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/@remix-run/dev/node_modules/prettier": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
+      "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
+      "bin": {
+        "prettier": "bin-prettier.js"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      },
+      "funding": {
+        "url": "https://github.com/prettier/prettier?sponsor=1"
+      }
+    },
     "node_modules/@remix-run/dev/node_modules/strip-final-newline": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -4537,9 +4668,9 @@
       "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
     },
     "node_modules/@types/node": {
-      "version": "20.2.5",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz",
-      "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ=="
+      "version": "20.3.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz",
+      "integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ=="
     },
     "node_modules/@types/node-fetch": {
       "version": "2.6.3",
@@ -4752,14 +4883,14 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "5.59.7",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.7.tgz",
-      "integrity": "sha512-VhpsIEuq/8i5SF+mPg9jSdIwgMBBp0z9XqjiEay+81PYLJuroN+ET1hM5IhkiYMJd9MkTz8iJLt7aaGAgzWUbQ==",
+      "version": "5.59.9",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.9.tgz",
+      "integrity": "sha512-FsPkRvBtcLQ/eVK1ivDiNYBjn3TGJdXy2fhXX+rc7czWl4ARwnpArwbihSOHI2Peg9WbtGHrbThfBUkZZGTtvQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "5.59.7",
-        "@typescript-eslint/types": "5.59.7",
-        "@typescript-eslint/typescript-estree": "5.59.7",
+        "@typescript-eslint/scope-manager": "5.59.9",
+        "@typescript-eslint/types": "5.59.9",
+        "@typescript-eslint/typescript-estree": "5.59.9",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -4778,14 +4909,33 @@
         }
       }
     },
-    "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
+    "node_modules/@typescript-eslint/scope-manager": {
+      "version": "5.59.9",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.9.tgz",
+      "integrity": "sha512-8RA+E+w78z1+2dzvK/tGZ2cpGigBZ58VMEHDZtpE1v+LLjzrYGc8mMaTONSxKyEkz3IuXFM0IqYiGHlCsmlZxQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "5.59.9",
+        "@typescript-eslint/visitor-keys": "5.59.9"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils": {
       "version": "5.59.7",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.7.tgz",
-      "integrity": "sha512-FL6hkYWK9zBGdxT2wWEd2W8ocXMu3K94i3gvMrjXpx+koFYdYV7KprKfirpgY34vTGzEPPuKoERpP8kD5h7vZQ==",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.7.tgz",
+      "integrity": "sha512-ozuz/GILuYG7osdY5O5yg0QxXUAEoI4Go3Do5xeu+ERH9PorHBPSdvD3Tjp2NN2bNLh1NJQSsQu2TPu/Ly+HaQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.59.7",
-        "@typescript-eslint/visitor-keys": "5.59.7"
+        "@typescript-eslint/typescript-estree": "5.59.7",
+        "@typescript-eslint/utils": "5.59.7",
+        "debug": "^4.3.4",
+        "tsutils": "^3.21.0"
       },
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -4793,9 +4943,17 @@
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
       }
     },
-    "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
+    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
       "version": "5.59.7",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.7.tgz",
       "integrity": "sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==",
@@ -4808,7 +4966,7 @@
         "url": "https://opencollective.com/typescript-eslint"
       }
     },
-    "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
+    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
       "version": "5.59.7",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.7.tgz",
       "integrity": "sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ==",
@@ -4835,7 +4993,7 @@
         }
       }
     },
-    "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
+    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
       "version": "5.59.7",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz",
       "integrity": "sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==",
@@ -4852,37 +5010,10 @@
         "url": "https://opencollective.com/typescript-eslint"
       }
     },
-    "node_modules/@typescript-eslint/type-utils": {
-      "version": "5.59.7",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.7.tgz",
-      "integrity": "sha512-ozuz/GILuYG7osdY5O5yg0QxXUAEoI4Go3Do5xeu+ERH9PorHBPSdvD3Tjp2NN2bNLh1NJQSsQu2TPu/Ly+HaQ==",
-      "dev": true,
-      "dependencies": {
-        "@typescript-eslint/typescript-estree": "5.59.7",
-        "@typescript-eslint/utils": "5.59.7",
-        "debug": "^4.3.4",
-        "tsutils": "^3.21.0"
-      },
-      "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      },
-      "peerDependencies": {
-        "eslint": "*"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
-      "version": "5.59.7",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.7.tgz",
-      "integrity": "sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==",
+    "node_modules/@typescript-eslint/types": {
+      "version": "5.59.9",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.9.tgz",
+      "integrity": "sha512-uW8H5NRgTVneSVTfiCVffBb8AbwWSKg7qcA4Ot3JI3MPCJGsB4Db4BhvAODIIYE5mNj7Q+VJkK7JxmRhk2Lyjw==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -4892,14 +5023,14 @@
         "url": "https://opencollective.com/typescript-eslint"
       }
     },
-    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
-      "version": "5.59.7",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.7.tgz",
-      "integrity": "sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ==",
+    "node_modules/@typescript-eslint/typescript-estree": {
+      "version": "5.59.9",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.9.tgz",
+      "integrity": "sha512-pmM0/VQ7kUhd1QyIxgS+aRvMgw+ZljB3eDb+jYyp6d2bC0mQWLzUDF+DLwCTkQ3tlNyVsvZRXjFyV0LkU/aXjA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.59.7",
-        "@typescript-eslint/visitor-keys": "5.59.7",
+        "@typescript-eslint/types": "5.59.9",
+        "@typescript-eslint/visitor-keys": "5.59.9",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -4919,23 +5050,6 @@
         }
       }
     },
-    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
-      "version": "5.59.7",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz",
-      "integrity": "sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==",
-      "dev": true,
-      "dependencies": {
-        "@typescript-eslint/types": "5.59.7",
-        "eslint-visitor-keys": "^3.3.0"
-      },
-      "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      }
-    },
     "node_modules/@typescript-eslint/utils": {
       "version": "5.59.7",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.7.tgz",
@@ -5036,6 +5150,23 @@
         "url": "https://opencollective.com/typescript-eslint"
       }
     },
+    "node_modules/@typescript-eslint/visitor-keys": {
+      "version": "5.59.9",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.9.tgz",
+      "integrity": "sha512-bT7s0td97KMaLwpEBckbzj/YohnvXtqbe2XgqNvTl6RJVakY5mvENOTPvw5u66nljfZxthESpDozs86U+oLY8Q==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "5.59.9",
+        "eslint-visitor-keys": "^3.3.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
     "node_modules/@vanilla-extract/babel-plugin-debug-ids": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/@vanilla-extract/babel-plugin-debug-ids/-/babel-plugin-debug-ids-1.0.3.tgz",
@@ -5207,6 +5338,22 @@
         "node": ">= 14"
       }
     },
+    "node_modules/@vercel/client/node_modules/@zeit/fetch": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/@zeit/fetch/-/fetch-5.2.0.tgz",
+      "integrity": "sha512-S4TFaT210j/lgzHjaxN2SiaMxXGeoB4hghNlhlvSqwTFSDNIqyJIDKFZQy+r3NKJOQU+RB5yGKJt8LS1pPHUrw==",
+      "dependencies": {
+        "@types/async-retry": "1.2.1",
+        "@zeit/fetch-cached-dns": "1.2.0",
+        "@zeit/fetch-retry": "^4.1.0",
+        "agentkeepalive": "3.4.1",
+        "debug": "3.1.0"
+      },
+      "peerDependencies": {
+        "@types/node-fetch": "2",
+        "node-fetch": "2"
+      }
+    },
     "node_modules/@vercel/client/node_modules/bl": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
@@ -5216,6 +5363,19 @@
         "safe-buffer": "^5.1.1"
       }
     },
+    "node_modules/@vercel/client/node_modules/debug": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+      "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/@vercel/client/node_modules/debug/node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+    },
     "node_modules/@vercel/client/node_modules/ignore": {
       "version": "4.0.6",
       "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@@ -5968,6 +6128,115 @@
         "url": "https://github.com/chalk/chalk?sponsor=1"
       }
     },
+    "node_modules/@wdio/cli/node_modules/devtools": {
+      "version": "8.10.7",
+      "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.10.7.tgz",
+      "integrity": "sha512-picJDxsjpaOW7gnQjcQVGDXvxuP1RZ4VNJpvJ+qiy86Gf3l4FGLKYkpJJFAMsIugL0XPs89bIVhjbtIv5NGL1w==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "^20.1.0",
+        "@wdio/config": "8.10.7",
+        "@wdio/logger": "8.10.6",
+        "@wdio/protocols": "8.10.2",
+        "@wdio/types": "8.10.4",
+        "@wdio/utils": "8.10.7",
+        "chrome-launcher": "^0.15.0",
+        "edge-paths": "^3.0.5",
+        "import-meta-resolve": "^3.0.0",
+        "puppeteer-core": "20.3.0",
+        "query-selector-shadow-dom": "^1.0.0",
+        "ua-parser-js": "^1.0.1",
+        "uuid": "^9.0.0",
+        "which": "^3.0.0"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
+    "node_modules/@wdio/cli/node_modules/devtools-protocol": {
+      "version": "0.0.1149535",
+      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1149535.tgz",
+      "integrity": "sha512-vpM8tGaYz2nrN9n8rvUEhQCgU05ocejO5WIJySsftEHxUahQ/fWuNyPxXuQNBEmaISYyMZkxCunhjtSEyBl/Dg==",
+      "dev": true
+    },
+    "node_modules/@wdio/cli/node_modules/is-plain-obj": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+      "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@wdio/cli/node_modules/minimatch": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
+      "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@wdio/cli/node_modules/webdriverio": {
+      "version": "8.10.7",
+      "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.10.7.tgz",
+      "integrity": "sha512-TkkPE3zBxdLRdcsNLqHct2OARnfMYB9/A0ri4sccmc3C3dVFiW99NAstN88nzD1SYzXAbxALRuITVd5oswqqhg==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "^20.1.0",
+        "@wdio/config": "8.10.7",
+        "@wdio/logger": "8.10.6",
+        "@wdio/protocols": "8.10.2",
+        "@wdio/repl": "8.10.1",
+        "@wdio/types": "8.10.4",
+        "@wdio/utils": "8.10.7",
+        "archiver": "^5.0.0",
+        "aria-query": "^5.0.0",
+        "css-shorthand-properties": "^1.1.1",
+        "css-value": "^0.0.1",
+        "devtools": "8.10.7",
+        "devtools-protocol": "^0.0.1149535",
+        "grapheme-splitter": "^1.0.2",
+        "import-meta-resolve": "^3.0.0",
+        "is-plain-obj": "^4.1.0",
+        "lodash.clonedeep": "^4.5.0",
+        "lodash.zip": "^4.2.0",
+        "minimatch": "^9.0.0",
+        "puppeteer-core": "20.3.0",
+        "query-selector-shadow-dom": "^1.0.0",
+        "resq": "^1.9.1",
+        "rgb2hex": "0.2.5",
+        "serialize-error": "^8.0.0",
+        "webdriver": "8.10.7"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
+    "node_modules/@wdio/cli/node_modules/which": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+      "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
+      "dev": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/which.js"
+      },
+      "engines": {
+        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+      }
+    },
     "node_modules/@wdio/config": {
       "version": "8.10.7",
       "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.10.7.tgz",
@@ -6074,6 +6343,121 @@
         "webdriverio": "8.10.7"
       }
     },
+    "node_modules/@wdio/globals/node_modules/devtools": {
+      "version": "8.10.7",
+      "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.10.7.tgz",
+      "integrity": "sha512-picJDxsjpaOW7gnQjcQVGDXvxuP1RZ4VNJpvJ+qiy86Gf3l4FGLKYkpJJFAMsIugL0XPs89bIVhjbtIv5NGL1w==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "@types/node": "^20.1.0",
+        "@wdio/config": "8.10.7",
+        "@wdio/logger": "8.10.6",
+        "@wdio/protocols": "8.10.2",
+        "@wdio/types": "8.10.4",
+        "@wdio/utils": "8.10.7",
+        "chrome-launcher": "^0.15.0",
+        "edge-paths": "^3.0.5",
+        "import-meta-resolve": "^3.0.0",
+        "puppeteer-core": "20.3.0",
+        "query-selector-shadow-dom": "^1.0.0",
+        "ua-parser-js": "^1.0.1",
+        "uuid": "^9.0.0",
+        "which": "^3.0.0"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
+    "node_modules/@wdio/globals/node_modules/devtools-protocol": {
+      "version": "0.0.1149535",
+      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1149535.tgz",
+      "integrity": "sha512-vpM8tGaYz2nrN9n8rvUEhQCgU05ocejO5WIJySsftEHxUahQ/fWuNyPxXuQNBEmaISYyMZkxCunhjtSEyBl/Dg==",
+      "dev": true,
+      "optional": true
+    },
+    "node_modules/@wdio/globals/node_modules/is-plain-obj": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+      "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+      "dev": true,
+      "optional": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@wdio/globals/node_modules/minimatch": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
+      "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@wdio/globals/node_modules/webdriverio": {
+      "version": "8.10.7",
+      "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.10.7.tgz",
+      "integrity": "sha512-TkkPE3zBxdLRdcsNLqHct2OARnfMYB9/A0ri4sccmc3C3dVFiW99NAstN88nzD1SYzXAbxALRuITVd5oswqqhg==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "@types/node": "^20.1.0",
+        "@wdio/config": "8.10.7",
+        "@wdio/logger": "8.10.6",
+        "@wdio/protocols": "8.10.2",
+        "@wdio/repl": "8.10.1",
+        "@wdio/types": "8.10.4",
+        "@wdio/utils": "8.10.7",
+        "archiver": "^5.0.0",
+        "aria-query": "^5.0.0",
+        "css-shorthand-properties": "^1.1.1",
+        "css-value": "^0.0.1",
+        "devtools": "8.10.7",
+        "devtools-protocol": "^0.0.1149535",
+        "grapheme-splitter": "^1.0.2",
+        "import-meta-resolve": "^3.0.0",
+        "is-plain-obj": "^4.1.0",
+        "lodash.clonedeep": "^4.5.0",
+        "lodash.zip": "^4.2.0",
+        "minimatch": "^9.0.0",
+        "puppeteer-core": "20.3.0",
+        "query-selector-shadow-dom": "^1.0.0",
+        "resq": "^1.9.1",
+        "rgb2hex": "0.2.5",
+        "serialize-error": "^8.0.0",
+        "webdriver": "8.10.7"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
+    "node_modules/@wdio/globals/node_modules/which": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+      "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/which.js"
+      },
+      "engines": {
+        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+      }
+    },
     "node_modules/@wdio/local-runner": {
       "version": "8.10.7",
       "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.10.7.tgz",
@@ -6183,13 +6567,13 @@
       }
     },
     "node_modules/@wdio/reporter": {
-      "version": "8.10.6",
-      "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.10.6.tgz",
-      "integrity": "sha512-dJFzhO4bZJLNgk+V4NYuZcugDjW3vJzL0gmThWwLAxfui5dXKJeVBL96ZnvBLcHSAK4rVx1+KdCI3M26rpOS8w==",
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.11.0.tgz",
+      "integrity": "sha512-eNQxzaTzouyCGRT8ljSzdOf5oJnQxgiKwOs9Cw/j4r5eqd5JQs+GCFP6tFVNjGtPpVIbfsfTb7yI+xmvjNRXhw==",
       "dev": true,
       "dependencies": {
         "@types/node": "^20.1.0",
-        "@wdio/logger": "8.10.6",
+        "@wdio/logger": "8.11.0",
         "@wdio/types": "8.10.4",
         "diff": "^5.0.0",
         "object-inspect": "^1.12.0",
@@ -6199,6 +6583,60 @@
         "node": "^16.13 || >=18"
       }
     },
+    "node_modules/@wdio/reporter/node_modules/@wdio/logger": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.11.0.tgz",
+      "integrity": "sha512-IsuKSaYi7NKEdgA57h8muzlN/MVp1dQG+V4C//7g4m03YJUnNQLvDhJzLjdeNTfvZy61U7foQSyt+3ktNzZkXA==",
+      "dev": true,
+      "dependencies": {
+        "chalk": "^5.1.2",
+        "loglevel": "^1.6.0",
+        "loglevel-plugin-prefix": "^0.8.4",
+        "strip-ansi": "^7.1.0"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
+    "node_modules/@wdio/reporter/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/@wdio/reporter/node_modules/chalk": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
+      "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.17.0 || ^14.13 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/@wdio/reporter/node_modules/strip-ansi": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+      }
+    },
     "node_modules/@wdio/runner": {
       "version": "8.10.7",
       "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.10.7.tgz",
@@ -6221,13 +6659,122 @@
         "node": "^16.13 || >=18"
       }
     },
+    "node_modules/@wdio/runner/node_modules/devtools": {
+      "version": "8.10.7",
+      "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.10.7.tgz",
+      "integrity": "sha512-picJDxsjpaOW7gnQjcQVGDXvxuP1RZ4VNJpvJ+qiy86Gf3l4FGLKYkpJJFAMsIugL0XPs89bIVhjbtIv5NGL1w==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "^20.1.0",
+        "@wdio/config": "8.10.7",
+        "@wdio/logger": "8.10.6",
+        "@wdio/protocols": "8.10.2",
+        "@wdio/types": "8.10.4",
+        "@wdio/utils": "8.10.7",
+        "chrome-launcher": "^0.15.0",
+        "edge-paths": "^3.0.5",
+        "import-meta-resolve": "^3.0.0",
+        "puppeteer-core": "20.3.0",
+        "query-selector-shadow-dom": "^1.0.0",
+        "ua-parser-js": "^1.0.1",
+        "uuid": "^9.0.0",
+        "which": "^3.0.0"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
+    "node_modules/@wdio/runner/node_modules/devtools-protocol": {
+      "version": "0.0.1149535",
+      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1149535.tgz",
+      "integrity": "sha512-vpM8tGaYz2nrN9n8rvUEhQCgU05ocejO5WIJySsftEHxUahQ/fWuNyPxXuQNBEmaISYyMZkxCunhjtSEyBl/Dg==",
+      "dev": true
+    },
+    "node_modules/@wdio/runner/node_modules/is-plain-obj": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+      "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@wdio/runner/node_modules/minimatch": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
+      "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@wdio/runner/node_modules/webdriverio": {
+      "version": "8.10.7",
+      "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.10.7.tgz",
+      "integrity": "sha512-TkkPE3zBxdLRdcsNLqHct2OARnfMYB9/A0ri4sccmc3C3dVFiW99NAstN88nzD1SYzXAbxALRuITVd5oswqqhg==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "^20.1.0",
+        "@wdio/config": "8.10.7",
+        "@wdio/logger": "8.10.6",
+        "@wdio/protocols": "8.10.2",
+        "@wdio/repl": "8.10.1",
+        "@wdio/types": "8.10.4",
+        "@wdio/utils": "8.10.7",
+        "archiver": "^5.0.0",
+        "aria-query": "^5.0.0",
+        "css-shorthand-properties": "^1.1.1",
+        "css-value": "^0.0.1",
+        "devtools": "8.10.7",
+        "devtools-protocol": "^0.0.1149535",
+        "grapheme-splitter": "^1.0.2",
+        "import-meta-resolve": "^3.0.0",
+        "is-plain-obj": "^4.1.0",
+        "lodash.clonedeep": "^4.5.0",
+        "lodash.zip": "^4.2.0",
+        "minimatch": "^9.0.0",
+        "puppeteer-core": "20.3.0",
+        "query-selector-shadow-dom": "^1.0.0",
+        "resq": "^1.9.1",
+        "rgb2hex": "0.2.5",
+        "serialize-error": "^8.0.0",
+        "webdriver": "8.10.7"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
+    "node_modules/@wdio/runner/node_modules/which": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+      "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
+      "dev": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/which.js"
+      },
+      "engines": {
+        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+      }
+    },
     "node_modules/@wdio/spec-reporter": {
-      "version": "8.10.6",
-      "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.10.6.tgz",
-      "integrity": "sha512-CbdyAY063THUqYnhj+7yr7WLjFYxG++d1AuGdJvYn8CL1/5Wjr3E+EGyofIZs4GtYAJsXH69kNtRO7lwDClvcQ==",
+      "version": "8.11.2",
+      "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.11.2.tgz",
+      "integrity": "sha512-ylhxICgen4eEMXgGBEAN6lYs/UnQ4WUYy2ld/4978vRObXy7pGINz96pIPZHAMn01ENU0+gWr8edwawfZyrqLw==",
       "dev": true,
       "dependencies": {
-        "@wdio/reporter": "8.10.6",
+        "@wdio/reporter": "8.11.0",
         "@wdio/types": "8.10.4",
         "chalk": "^5.1.2",
         "easy-table": "^1.2.0",
@@ -6428,9 +6975,9 @@
       }
     },
     "node_modules/@webpack-cli/configtest": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.0.tgz",
-      "integrity": "sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w==",
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz",
+      "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==",
       "dev": true,
       "engines": {
         "node": ">=14.15.0"
@@ -6441,9 +6988,9 @@
       }
     },
     "node_modules/@webpack-cli/info": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz",
-      "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==",
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz",
+      "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==",
       "dev": true,
       "engines": {
         "node": ">=14.15.0"
@@ -6454,9 +7001,9 @@
       }
     },
     "node_modules/@webpack-cli/serve": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.4.tgz",
-      "integrity": "sha512-0xRgjgDLdz6G7+vvDLlaRpFatJaJ69uTalZLRSMX5B3VUrDmXcrVA3+6fXXQgmYz7bY9AAgs348XQdmtLsK41A==",
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz",
+      "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==",
       "dev": true,
       "engines": {
         "node": ">=14.15.0"
@@ -6471,6 +7018,54 @@
         }
       }
     },
+    "node_modules/@wry/context": {
+      "version": "0.7.3",
+      "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.3.tgz",
+      "integrity": "sha512-Nl8WTesHp89RF803Se9X3IiHjdmLBrIvPMaJkl+rKVJAYyPsz1TEUbu89943HpvujtSJgDUx9W4vZw3K1Mr3sA==",
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/@wry/context/node_modules/tslib": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+      "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+    },
+    "node_modules/@wry/equality": {
+      "version": "0.5.6",
+      "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.6.tgz",
+      "integrity": "sha512-D46sfMTngaYlrH+OspKf8mIJETntFnf6Hsjb0V41jAXJ7Bx2kB8Rv8RCUujuVWYttFtHkUNp7g+FwxNQAr6mXA==",
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/@wry/equality/node_modules/tslib": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+      "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+    },
+    "node_modules/@wry/trie": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.4.3.tgz",
+      "integrity": "sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w==",
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/@wry/trie/node_modules/tslib": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+      "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+    },
     "node_modules/@xtuc/ieee754": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -6500,22 +7095,6 @@
       "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
       "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ=="
     },
-    "node_modules/@zeit/fetch": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/@zeit/fetch/-/fetch-5.2.0.tgz",
-      "integrity": "sha512-S4TFaT210j/lgzHjaxN2SiaMxXGeoB4hghNlhlvSqwTFSDNIqyJIDKFZQy+r3NKJOQU+RB5yGKJt8LS1pPHUrw==",
-      "dependencies": {
-        "@types/async-retry": "1.2.1",
-        "@zeit/fetch-cached-dns": "1.2.0",
-        "@zeit/fetch-retry": "^4.1.0",
-        "agentkeepalive": "3.4.1",
-        "debug": "3.1.0"
-      },
-      "peerDependencies": {
-        "@types/node-fetch": "2",
-        "node-fetch": "2"
-      }
-    },
     "node_modules/@zeit/fetch-cached-dns": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@zeit/fetch-cached-dns/-/fetch-cached-dns-1.2.0.tgz",
@@ -6563,19 +7142,6 @@
         "node": ">= 4"
       }
     },
-    "node_modules/@zeit/fetch/node_modules/debug": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-      "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-      "dependencies": {
-        "ms": "2.0.0"
-      }
-    },
-    "node_modules/@zeit/fetch/node_modules/ms": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-    },
     "node_modules/abbrev": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -8163,6 +8729,18 @@
         "node": ">=16"
       }
     },
+    "node_modules/chromium-bidi": {
+      "version": "0.4.9",
+      "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz",
+      "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==",
+      "dev": true,
+      "dependencies": {
+        "mitt": "3.0.0"
+      },
+      "peerDependencies": {
+        "devtools-protocol": "*"
+      }
+    },
     "node_modules/ci-info": {
       "version": "3.8.0",
       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
@@ -8444,6 +9022,14 @@
         "node": ">=4"
       }
     },
+    "node_modules/clsx": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
+      "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/code-block-writer": {
       "version": "10.1.1",
       "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-10.1.1.tgz",
@@ -8673,6 +9259,33 @@
       "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
       "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
     },
+    "node_modules/cross-fetch": {
+      "version": "3.1.6",
+      "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz",
+      "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==",
+      "dependencies": {
+        "node-fetch": "^2.6.11"
+      }
+    },
+    "node_modules/cross-fetch/node_modules/node-fetch": {
+      "version": "2.6.11",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
+      "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -9366,17 +9979,17 @@
       }
     },
     "node_modules/devtools": {
-      "version": "8.10.7",
-      "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.10.7.tgz",
-      "integrity": "sha512-picJDxsjpaOW7gnQjcQVGDXvxuP1RZ4VNJpvJ+qiy86Gf3l4FGLKYkpJJFAMsIugL0XPs89bIVhjbtIv5NGL1w==",
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.11.0.tgz",
+      "integrity": "sha512-j1wXFQyjswJ6doAV1+h4Bxl8+Oeb8SMpWTpBVa0DurGsxfft8sU2OhDlMo5tx/zbX82X5sGyJDMnKHqBJ2XRvQ==",
       "dev": true,
       "dependencies": {
         "@types/node": "^20.1.0",
-        "@wdio/config": "8.10.7",
-        "@wdio/logger": "8.10.6",
-        "@wdio/protocols": "8.10.2",
+        "@wdio/config": "8.11.0",
+        "@wdio/logger": "8.11.0",
+        "@wdio/protocols": "8.11.0",
         "@wdio/types": "8.10.4",
-        "@wdio/utils": "8.10.7",
+        "@wdio/utils": "8.11.0",
         "chrome-launcher": "^0.15.0",
         "edge-paths": "^3.0.5",
         "import-meta-resolve": "^3.0.0",
@@ -9391,11 +10004,179 @@
       }
     },
     "node_modules/devtools-protocol": {
-      "version": "0.0.1149535",
-      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1149535.tgz",
-      "integrity": "sha512-vpM8tGaYz2nrN9n8rvUEhQCgU05ocejO5WIJySsftEHxUahQ/fWuNyPxXuQNBEmaISYyMZkxCunhjtSEyBl/Dg==",
+      "version": "0.0.1152884",
+      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1152884.tgz",
+      "integrity": "sha512-9eP6OmCoU1cWArpXLuzyZQcBJ2PkINOh8Nwx8W5i8u6NDigDE5/mPlLLBAfshwn5YVvIz6ZQ9jbs0PZvKGccdQ==",
+      "dev": true
+    },
+    "node_modules/devtools/node_modules/@wdio/config": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.11.0.tgz",
+      "integrity": "sha512-nBQXsXbPCjddtI/3rAK5yFs3eD3f0T3lZMivweTkLLR7GKBxGjiFoBjXtfqUrHJYa+2uwfXrwxo6y+dA6fVbuw==",
+      "dev": true,
+      "dependencies": {
+        "@wdio/logger": "8.11.0",
+        "@wdio/types": "8.10.4",
+        "@wdio/utils": "8.11.0",
+        "decamelize": "^6.0.0",
+        "deepmerge-ts": "^5.0.0",
+        "glob": "^10.2.2",
+        "import-meta-resolve": "^3.0.0",
+        "read-pkg-up": "^9.1.0"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
+    "node_modules/devtools/node_modules/@wdio/logger": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.11.0.tgz",
+      "integrity": "sha512-IsuKSaYi7NKEdgA57h8muzlN/MVp1dQG+V4C//7g4m03YJUnNQLvDhJzLjdeNTfvZy61U7foQSyt+3ktNzZkXA==",
+      "dev": true,
+      "dependencies": {
+        "chalk": "^5.1.2",
+        "loglevel": "^1.6.0",
+        "loglevel-plugin-prefix": "^0.8.4",
+        "strip-ansi": "^7.1.0"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
+    "node_modules/devtools/node_modules/@wdio/protocols": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.11.0.tgz",
+      "integrity": "sha512-eXTMYt/XoaX53H/Q2qmsn1uWthIC5aSTGtX9YyXD/AkagG2hXeX3lLmzNWBaSIvKR+vWXRYbg3Y/7IvL2s25Wg==",
       "dev": true
     },
+    "node_modules/devtools/node_modules/@wdio/utils": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.11.0.tgz",
+      "integrity": "sha512-XBl1zalk5UPu8QKZ7LZIA82Ad363fpNHZHP5uI5OxUFnk4ZPWgY9eCWpeD+4f9a0DS0w2Dro15E4PORNX84pIw==",
+      "dev": true,
+      "dependencies": {
+        "@wdio/logger": "8.11.0",
+        "@wdio/types": "8.10.4",
+        "import-meta-resolve": "^3.0.0",
+        "p-iteration": "^1.1.8"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
+    "node_modules/devtools/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/devtools/node_modules/chalk": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
+      "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.17.0 || ^14.13 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/devtools/node_modules/foreground-child": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+      "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+      "dev": true,
+      "dependencies": {
+        "cross-spawn": "^7.0.0",
+        "signal-exit": "^4.0.1"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/devtools/node_modules/glob": {
+      "version": "10.2.7",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz",
+      "integrity": "sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==",
+      "dev": true,
+      "dependencies": {
+        "foreground-child": "^3.1.0",
+        "jackspeak": "^2.0.3",
+        "minimatch": "^9.0.1",
+        "minipass": "^5.0.0 || ^6.0.2",
+        "path-scurry": "^1.7.0"
+      },
+      "bin": {
+        "glob": "dist/cjs/src/bin.js"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/devtools/node_modules/minimatch": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
+      "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/devtools/node_modules/minipass": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz",
+      "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==",
+      "dev": true,
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      }
+    },
+    "node_modules/devtools/node_modules/signal-exit": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz",
+      "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/devtools/node_modules/strip-ansi": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+      }
+    },
     "node_modules/devtools/node_modules/which": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
@@ -9507,11 +10288,14 @@
       }
     },
     "node_modules/dotenv": {
-      "version": "16.0.3",
-      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
-      "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
+      "version": "16.1.4",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz",
+      "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==",
       "engines": {
         "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/motdotla/dotenv?sponsor=1"
       }
     },
     "node_modules/download": {
@@ -10702,6 +11486,18 @@
         "url": "https://opencollective.com/eslint"
       }
     },
+    "node_modules/eslint-config-prettier": {
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz",
+      "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==",
+      "dev": true,
+      "bin": {
+        "eslint-config-prettier": "bin/cli.js"
+      },
+      "peerDependencies": {
+        "eslint": ">=7.0.0"
+      }
+    },
     "node_modules/eslint-import-resolver-node": {
       "version": "0.3.7",
       "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz",
@@ -11513,9 +12309,9 @@
       }
     },
     "node_modules/expect-webdriverio": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.2.3.tgz",
-      "integrity": "sha512-B5y5NYVUWHQGbMiBzH8UBHcGTHhwu13o4/uUZEpc1wmJk2JxWP7fNpmpkUyiW5yygb2Sp0/02ds4b/xw6sBR/A==",
+      "version": "4.2.6",
+      "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.2.6.tgz",
+      "integrity": "sha512-/7wImWgef6ooZSoivXq6jkCDE1vvDC42WcM7Iyguspah/h384nprUdpQPLIUkkvTCorSQdYtRftNvHJjEpVUmA==",
       "dev": true,
       "dependencies": {
         "expect": "^29.5.0",
@@ -11880,7 +12676,6 @@
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
       "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
-      "dev": true,
       "funding": [
         {
           "type": "github",
@@ -12306,7 +13101,6 @@
       "version": "4.0.10",
       "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
       "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
-      "dev": true,
       "dependencies": {
         "fetch-blob": "^3.1.2"
       },
@@ -12931,6 +13725,33 @@
       "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
       "dev": true
     },
+    "node_modules/graphql": {
+      "version": "16.6.0",
+      "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz",
+      "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==",
+      "engines": {
+        "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
+      }
+    },
+    "node_modules/graphql-tag": {
+      "version": "2.12.6",
+      "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz",
+      "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
+      }
+    },
+    "node_modules/graphql-tag/node_modules/tslib": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+      "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+    },
     "node_modules/gunzip-maybe": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz",
@@ -13133,6 +13954,19 @@
         "he": "bin/he"
       }
     },
+    "node_modules/hoist-non-react-statics": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+      "dependencies": {
+        "react-is": "^16.7.0"
+      }
+    },
+    "node_modules/hoist-non-react-statics/node_modules/react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+    },
     "node_modules/hosted-git-info": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@@ -15058,9 +15892,9 @@
       "dev": true
     },
     "node_modules/lit": {
-      "version": "2.7.4",
-      "resolved": "https://registry.npmjs.org/lit/-/lit-2.7.4.tgz",
-      "integrity": "sha512-cgD7xrZoYr21mbrkZIuIrj98YTMw/snJPg52deWVV4A8icLyNHI3bF70xsJeAgwTuiq5Kkd+ZR8gybSJDCPB7g==",
+      "version": "2.7.5",
+      "resolved": "https://registry.npmjs.org/lit/-/lit-2.7.5.tgz",
+      "integrity": "sha512-i/cH7Ye6nBDUASMnfwcictBnsTN91+aBjXoTHF2xARghXScKxpD4F4WYI+VLXg9lqbMinDfvoI7VnZXjyHgdfQ==",
       "dependencies": {
         "@lit/reactive-element": "^1.6.0",
         "lit-element": "^3.3.0",
@@ -15351,7 +16185,6 @@
       "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"
       },
@@ -16901,7 +17734,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
       "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
-      "dev": true,
       "funding": [
         {
           "type": "github",
@@ -16917,22 +17749,28 @@
       }
     },
     "node_modules/node-fetch": {
-      "version": "2.6.11",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
-      "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz",
+      "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==",
       "dependencies": {
-        "whatwg-url": "^5.0.0"
+        "data-uri-to-buffer": "^4.0.0",
+        "fetch-blob": "^3.1.4",
+        "formdata-polyfill": "^4.0.10"
       },
       "engines": {
-        "node": "4.x || >=6.0.0"
-      },
-      "peerDependencies": {
-        "encoding": "^0.1.0"
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
       },
-      "peerDependenciesMeta": {
-        "encoding": {
-          "optional": true
-        }
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/node-fetch"
+      }
+    },
+    "node_modules/node-fetch/node_modules/data-uri-to-buffer": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+      "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+      "engines": {
+        "node": ">= 12"
       }
     },
     "node_modules/node-gyp-build": {
@@ -17332,6 +18170,31 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/optimism": {
+      "version": "0.16.2",
+      "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.16.2.tgz",
+      "integrity": "sha512-zWNbgWj+3vLEjZNIh/okkY2EUfX+vB9TJopzIZwT1xxaMqC5hRLLraePod4c5n4He08xuXNH+zhKFFCu390wiQ==",
+      "dependencies": {
+        "@wry/context": "^0.7.0",
+        "@wry/trie": "^0.3.0"
+      }
+    },
+    "node_modules/optimism/node_modules/@wry/trie": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.3.2.tgz",
+      "integrity": "sha512-yRTyhWSls2OY/pYLfwff867r8ekooZ4UI+/gxot5Wj8EFwSf2rG+n+Mo/6LoLQm1TKA4GRj2+LCpbfS937dClQ==",
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/optimism/node_modules/tslib": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+      "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+    },
     "node_modules/optionator": {
       "version": "0.9.1",
       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -18268,9 +19131,10 @@
       }
     },
     "node_modules/prettier": {
-      "version": "2.7.1",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
-      "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
+      "version": "2.8.8",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
+      "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
+      "dev": true,
       "bin": {
         "prettier": "bin-prettier.js"
       },
@@ -18366,6 +19230,21 @@
       "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
       "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="
     },
+    "node_modules/prop-types": {
+      "version": "15.8.1",
+      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+      "dependencies": {
+        "loose-envify": "^1.4.0",
+        "object-assign": "^4.1.1",
+        "react-is": "^16.13.1"
+      }
+    },
+    "node_modules/prop-types/node_modules/react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+    },
     "node_modules/property-information": {
       "version": "6.2.0",
       "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz",
@@ -18507,27 +19386,6 @@
         }
       }
     },
-    "node_modules/puppeteer-core/node_modules/chromium-bidi": {
-      "version": "0.4.9",
-      "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz",
-      "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==",
-      "dev": true,
-      "dependencies": {
-        "mitt": "3.0.0"
-      },
-      "peerDependencies": {
-        "devtools-protocol": "*"
-      }
-    },
-    "node_modules/puppeteer-core/node_modules/cross-fetch": {
-      "version": "3.1.6",
-      "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz",
-      "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==",
-      "dev": true,
-      "dependencies": {
-        "node-fetch": "^2.6.11"
-      }
-    },
     "node_modules/puppeteer-core/node_modules/devtools-protocol": {
       "version": "0.0.1120988",
       "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz",
@@ -19283,6 +20141,14 @@
         "node": ">=4"
       }
     },
+    "node_modules/response-iterator": {
+      "version": "0.2.6",
+      "resolved": "https://registry.npmjs.org/response-iterator/-/response-iterator-0.2.6.tgz",
+      "integrity": "sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw==",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/responselike": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz",
@@ -19567,33 +20433,6 @@
         "runme2": "bin/runme2.js"
       }
     },
-    "node_modules/runme/node_modules/data-uri-to-buffer": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
-      "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
-      "dev": true,
-      "engines": {
-        "node": ">= 12"
-      }
-    },
-    "node_modules/runme/node_modules/node-fetch": {
-      "version": "3.3.1",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz",
-      "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==",
-      "dev": true,
-      "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/rxjs": {
       "version": "7.8.1",
       "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
@@ -20555,6 +21394,14 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/symbol-observable": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
+      "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==",
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
     "node_modules/synckit": {
       "version": "0.8.5",
       "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
@@ -21063,6 +21910,22 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/ts-invariant": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz",
+      "integrity": "sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ts-invariant/node_modules/tslib": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+      "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+    },
     "node_modules/ts-loader": {
       "version": "9.4.3",
       "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.3.tgz",
@@ -21332,15 +22195,15 @@
       }
     },
     "node_modules/typescript": {
-      "version": "5.0.4",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
-      "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
+      "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
       },
       "engines": {
-        "node": ">=12.20"
+        "node": ">=14.17"
       }
     },
     "node_modules/ua-parser-js": {
@@ -21940,9 +22803,9 @@
       }
     },
     "node_modules/vite": {
-      "version": "4.3.7",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.7.tgz",
-      "integrity": "sha512-MTIFpbIm9v7Hh5b0wSBgkcWzSBz7SAa6K/cBTwS4kUiQJfQLFlZZRJRQgqunCVzhTPCk674tW+0Qaqh3Q00dBg==",
+      "version": "4.3.9",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
+      "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
       "dependencies": {
         "esbuild": "^0.17.5",
         "postcss": "^8.4.23",
@@ -22717,7 +23580,6 @@
       "version": "3.2.1",
       "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
       "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
-      "dev": true,
       "engines": {
         "node": ">= 8"
       }
@@ -22755,24 +23617,24 @@
       }
     },
     "node_modules/webdriverio": {
-      "version": "8.10.7",
-      "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.10.7.tgz",
-      "integrity": "sha512-TkkPE3zBxdLRdcsNLqHct2OARnfMYB9/A0ri4sccmc3C3dVFiW99NAstN88nzD1SYzXAbxALRuITVd5oswqqhg==",
+      "version": "8.11.2",
+      "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.11.2.tgz",
+      "integrity": "sha512-e/9WkdNTfWeoaSo2UzK0Giec/nQX3i7U9J8esimhozH/EpwSqIaEJ2pRRlxRVafEhe2OBG1QDhnLnDjdCC5Hxg==",
       "dev": true,
       "dependencies": {
         "@types/node": "^20.1.0",
-        "@wdio/config": "8.10.7",
-        "@wdio/logger": "8.10.6",
-        "@wdio/protocols": "8.10.2",
+        "@wdio/config": "8.11.0",
+        "@wdio/logger": "8.11.0",
+        "@wdio/protocols": "8.11.0",
         "@wdio/repl": "8.10.1",
         "@wdio/types": "8.10.4",
-        "@wdio/utils": "8.10.7",
+        "@wdio/utils": "8.11.0",
         "archiver": "^5.0.0",
         "aria-query": "^5.0.0",
         "css-shorthand-properties": "^1.1.1",
         "css-value": "^0.0.1",
-        "devtools": "8.10.7",
-        "devtools-protocol": "^0.0.1149535",
+        "devtools": "8.11.0",
+        "devtools-protocol": "^0.0.1152884",
         "grapheme-splitter": "^1.0.2",
         "import-meta-resolve": "^3.0.0",
         "is-plain-obj": "^4.1.0",
@@ -22784,12 +23646,129 @@
         "resq": "^1.9.1",
         "rgb2hex": "0.2.5",
         "serialize-error": "^8.0.0",
-        "webdriver": "8.10.7"
+        "webdriver": "8.11.1"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
+    "node_modules/webdriverio/node_modules/@wdio/config": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.11.0.tgz",
+      "integrity": "sha512-nBQXsXbPCjddtI/3rAK5yFs3eD3f0T3lZMivweTkLLR7GKBxGjiFoBjXtfqUrHJYa+2uwfXrwxo6y+dA6fVbuw==",
+      "dev": true,
+      "dependencies": {
+        "@wdio/logger": "8.11.0",
+        "@wdio/types": "8.10.4",
+        "@wdio/utils": "8.11.0",
+        "decamelize": "^6.0.0",
+        "deepmerge-ts": "^5.0.0",
+        "glob": "^10.2.2",
+        "import-meta-resolve": "^3.0.0",
+        "read-pkg-up": "^9.1.0"
       },
       "engines": {
         "node": "^16.13 || >=18"
       }
     },
+    "node_modules/webdriverio/node_modules/@wdio/logger": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.11.0.tgz",
+      "integrity": "sha512-IsuKSaYi7NKEdgA57h8muzlN/MVp1dQG+V4C//7g4m03YJUnNQLvDhJzLjdeNTfvZy61U7foQSyt+3ktNzZkXA==",
+      "dev": true,
+      "dependencies": {
+        "chalk": "^5.1.2",
+        "loglevel": "^1.6.0",
+        "loglevel-plugin-prefix": "^0.8.4",
+        "strip-ansi": "^7.1.0"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
+    "node_modules/webdriverio/node_modules/@wdio/protocols": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.11.0.tgz",
+      "integrity": "sha512-eXTMYt/XoaX53H/Q2qmsn1uWthIC5aSTGtX9YyXD/AkagG2hXeX3lLmzNWBaSIvKR+vWXRYbg3Y/7IvL2s25Wg==",
+      "dev": true
+    },
+    "node_modules/webdriverio/node_modules/@wdio/utils": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.11.0.tgz",
+      "integrity": "sha512-XBl1zalk5UPu8QKZ7LZIA82Ad363fpNHZHP5uI5OxUFnk4ZPWgY9eCWpeD+4f9a0DS0w2Dro15E4PORNX84pIw==",
+      "dev": true,
+      "dependencies": {
+        "@wdio/logger": "8.11.0",
+        "@wdio/types": "8.10.4",
+        "import-meta-resolve": "^3.0.0",
+        "p-iteration": "^1.1.8"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
+    "node_modules/webdriverio/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/webdriverio/node_modules/chalk": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
+      "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.17.0 || ^14.13 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/webdriverio/node_modules/foreground-child": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+      "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+      "dev": true,
+      "dependencies": {
+        "cross-spawn": "^7.0.0",
+        "signal-exit": "^4.0.1"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/webdriverio/node_modules/glob": {
+      "version": "10.2.7",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz",
+      "integrity": "sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==",
+      "dev": true,
+      "dependencies": {
+        "foreground-child": "^3.1.0",
+        "jackspeak": "^2.0.3",
+        "minimatch": "^9.0.1",
+        "minipass": "^5.0.0 || ^6.0.2",
+        "path-scurry": "^1.7.0"
+      },
+      "bin": {
+        "glob": "dist/cjs/src/bin.js"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/webdriverio/node_modules/is-plain-obj": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
@@ -22817,6 +23796,64 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/webdriverio/node_modules/minipass": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz",
+      "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==",
+      "dev": true,
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      }
+    },
+    "node_modules/webdriverio/node_modules/signal-exit": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz",
+      "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/webdriverio/node_modules/strip-ansi": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+      }
+    },
+    "node_modules/webdriverio/node_modules/webdriver": {
+      "version": "8.11.1",
+      "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.11.1.tgz",
+      "integrity": "sha512-hSpUZYzUA65t4DDtKujCHUX6hpFTUleb7lWMcf5xjPz8sxWrK9R8NIw7pXt/GU6PVS331nGAaYkzoXrqz2VB8w==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "^20.1.0",
+        "@types/ws": "^8.5.3",
+        "@wdio/config": "8.11.0",
+        "@wdio/logger": "8.11.0",
+        "@wdio/protocols": "8.11.0",
+        "@wdio/types": "8.10.4",
+        "@wdio/utils": "8.11.0",
+        "deepmerge-ts": "^5.0.0",
+        "got": "^ 12.6.1",
+        "ky": "^0.33.0",
+        "ws": "^8.8.0"
+      },
+      "engines": {
+        "node": "^16.13 || >=18"
+      }
+    },
     "node_modules/webidl-conversions": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -22870,15 +23907,15 @@
       }
     },
     "node_modules/webpack-cli": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.1.tgz",
-      "integrity": "sha512-OLJwVMoXnXYH2ncNGU8gxVpUtm3ybvdioiTvHgUyBuyMLKiVvWy+QObzBsMtp5pH7qQoEuWgeEUQ/sU3ZJFzAw==",
+      "version": "5.1.4",
+      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz",
+      "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
       "dev": true,
       "dependencies": {
         "@discoveryjs/json-ext": "^0.5.0",
-        "@webpack-cli/configtest": "^2.1.0",
-        "@webpack-cli/info": "^2.0.1",
-        "@webpack-cli/serve": "^2.0.4",
+        "@webpack-cli/configtest": "^2.1.1",
+        "@webpack-cli/info": "^2.0.2",
+        "@webpack-cli/serve": "^2.0.5",
         "colorette": "^2.0.14",
         "commander": "^10.0.1",
         "cross-spawn": "^7.0.3",
@@ -23896,6 +24933,19 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/zen-observable": {
+      "version": "0.8.15",
+      "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
+      "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="
+    },
+    "node_modules/zen-observable-ts": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz",
+      "integrity": "sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==",
+      "dependencies": {
+        "zen-observable": "0.8.15"
+      }
+    },
     "node_modules/zip-stream": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz",
@@ -23953,6 +25003,33 @@
         "@jridgewell/trace-mapping": "^0.3.9"
       }
     },
+    "@apollo/client": {
+      "version": "3.7.15",
+      "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.7.15.tgz",
+      "integrity": "sha512-pLScjo4GAQRWKWyEg2J3FlQr9fbUAuADT0EI2+JlLf2BjaU9I7WUaZVD9w+0qNPE8BZqs53MRQq0OCm1QCW+eg==",
+      "requires": {
+        "@graphql-typed-document-node/core": "^3.1.1",
+        "@wry/context": "^0.7.0",
+        "@wry/equality": "^0.5.0",
+        "@wry/trie": "^0.4.0",
+        "graphql-tag": "^2.12.6",
+        "hoist-non-react-statics": "^3.3.2",
+        "optimism": "^0.16.2",
+        "prop-types": "^15.7.2",
+        "response-iterator": "^0.2.6",
+        "symbol-observable": "^4.0.0",
+        "ts-invariant": "^0.10.3",
+        "tslib": "^2.3.0",
+        "zen-observable-ts": "^1.2.5"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.5.3",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+          "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+        }
+      }
+    },
     "@babel/code-frame": {
       "version": "7.21.4",
       "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
@@ -25586,6 +26663,12 @@
       "integrity": "sha512-leOyCx+xnmioBSPqdkFBi1drkdM+Nm5+MfgffRcdkcVVUjFuAlxqEJ7jkYeXyHLvL9/l7ejPGooE1TPAo7qmmA==",
       "dev": true
     },
+    "@graphql-typed-document-node/core": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz",
+      "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==",
+      "requires": {}
+    },
     "@grpc/grpc-js": {
       "version": "1.8.14",
       "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.14.tgz",
@@ -25903,6 +26986,16 @@
         "rimraf": "^3.0.2",
         "semver": "^7.3.5",
         "tar": "^6.1.11"
+      },
+      "dependencies": {
+        "node-fetch": {
+          "version": "2.6.11",
+          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
+          "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
+          "requires": {
+            "whatwg-url": "^5.0.0"
+          }
+        }
       }
     },
     "@microsoft/1ds-core-js": {
@@ -26265,6 +27358,16 @@
         "is-plain-object": "^5.0.0",
         "node-fetch": "^2.6.7",
         "universal-user-agent": "^6.0.0"
+      },
+      "dependencies": {
+        "node-fetch": {
+          "version": "2.6.11",
+          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
+          "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
+          "requires": {
+            "whatwg-url": "^5.0.0"
+          }
+        }
       }
     },
     "@octokit/request-error": {
@@ -26743,6 +27846,14 @@
           "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
           "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
         },
+        "node-fetch": {
+          "version": "2.6.11",
+          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
+          "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
+          "requires": {
+            "whatwg-url": "^5.0.0"
+          }
+        },
         "npm-run-path": {
           "version": "4.0.1",
           "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@@ -26759,6 +27870,11 @@
             "mimic-fn": "^2.1.0"
           }
         },
+        "prettier": {
+          "version": "2.7.1",
+          "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
+          "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g=="
+        },
         "strip-final-newline": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -27141,9 +28257,9 @@
       "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
     },
     "@types/node": {
-      "version": "20.2.5",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz",
-      "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ=="
+      "version": "20.3.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz",
+      "integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ=="
     },
     "@types/node-fetch": {
       "version": "2.6.3",
@@ -27321,58 +28437,25 @@
       }
     },
     "@typescript-eslint/parser": {
-      "version": "5.59.7",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.7.tgz",
-      "integrity": "sha512-VhpsIEuq/8i5SF+mPg9jSdIwgMBBp0z9XqjiEay+81PYLJuroN+ET1hM5IhkiYMJd9MkTz8iJLt7aaGAgzWUbQ==",
+      "version": "5.59.9",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.9.tgz",
+      "integrity": "sha512-FsPkRvBtcLQ/eVK1ivDiNYBjn3TGJdXy2fhXX+rc7czWl4ARwnpArwbihSOHI2Peg9WbtGHrbThfBUkZZGTtvQ==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/scope-manager": "5.59.7",
-        "@typescript-eslint/types": "5.59.7",
-        "@typescript-eslint/typescript-estree": "5.59.7",
+        "@typescript-eslint/scope-manager": "5.59.9",
+        "@typescript-eslint/types": "5.59.9",
+        "@typescript-eslint/typescript-estree": "5.59.9",
         "debug": "^4.3.4"
-      },
-      "dependencies": {
-        "@typescript-eslint/scope-manager": {
-          "version": "5.59.7",
-          "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.7.tgz",
-          "integrity": "sha512-FL6hkYWK9zBGdxT2wWEd2W8ocXMu3K94i3gvMrjXpx+koFYdYV7KprKfirpgY34vTGzEPPuKoERpP8kD5h7vZQ==",
-          "dev": true,
-          "requires": {
-            "@typescript-eslint/types": "5.59.7",
-            "@typescript-eslint/visitor-keys": "5.59.7"
-          }
-        },
-        "@typescript-eslint/types": {
-          "version": "5.59.7",
-          "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.7.tgz",
-          "integrity": "sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==",
-          "dev": true
-        },
-        "@typescript-eslint/typescript-estree": {
-          "version": "5.59.7",
-          "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.7.tgz",
-          "integrity": "sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ==",
-          "dev": true,
-          "requires": {
-            "@typescript-eslint/types": "5.59.7",
-            "@typescript-eslint/visitor-keys": "5.59.7",
-            "debug": "^4.3.4",
-            "globby": "^11.1.0",
-            "is-glob": "^4.0.3",
-            "semver": "^7.3.7",
-            "tsutils": "^3.21.0"
-          }
-        },
-        "@typescript-eslint/visitor-keys": {
-          "version": "5.59.7",
-          "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz",
-          "integrity": "sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==",
-          "dev": true,
-          "requires": {
-            "@typescript-eslint/types": "5.59.7",
-            "eslint-visitor-keys": "^3.3.0"
-          }
-        }
+      }
+    },
+    "@typescript-eslint/scope-manager": {
+      "version": "5.59.9",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.9.tgz",
+      "integrity": "sha512-8RA+E+w78z1+2dzvK/tGZ2cpGigBZ58VMEHDZtpE1v+LLjzrYGc8mMaTONSxKyEkz3IuXFM0IqYiGHlCsmlZxQ==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/types": "5.59.9",
+        "@typescript-eslint/visitor-keys": "5.59.9"
       }
     },
     "@typescript-eslint/type-utils": {
@@ -27420,6 +28503,27 @@
         }
       }
     },
+    "@typescript-eslint/types": {
+      "version": "5.59.9",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.9.tgz",
+      "integrity": "sha512-uW8H5NRgTVneSVTfiCVffBb8AbwWSKg7qcA4Ot3JI3MPCJGsB4Db4BhvAODIIYE5mNj7Q+VJkK7JxmRhk2Lyjw==",
+      "dev": true
+    },
+    "@typescript-eslint/typescript-estree": {
+      "version": "5.59.9",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.9.tgz",
+      "integrity": "sha512-pmM0/VQ7kUhd1QyIxgS+aRvMgw+ZljB3eDb+jYyp6d2bC0mQWLzUDF+DLwCTkQ3tlNyVsvZRXjFyV0LkU/aXjA==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/types": "5.59.9",
+        "@typescript-eslint/visitor-keys": "5.59.9",
+        "debug": "^4.3.4",
+        "globby": "^11.1.0",
+        "is-glob": "^4.0.3",
+        "semver": "^7.3.7",
+        "tsutils": "^3.21.0"
+      }
+    },
     "@typescript-eslint/utils": {
       "version": "5.59.7",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.7.tgz",
@@ -27479,6 +28583,16 @@
         }
       }
     },
+    "@typescript-eslint/visitor-keys": {
+      "version": "5.59.9",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.9.tgz",
+      "integrity": "sha512-bT7s0td97KMaLwpEBckbzj/YohnvXtqbe2XgqNvTl6RJVakY5mvENOTPvw5u66nljfZxthESpDozs86U+oLY8Q==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/types": "5.59.9",
+        "eslint-visitor-keys": "^3.3.0"
+      }
+    },
     "@vanilla-extract/babel-plugin-debug-ids": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/@vanilla-extract/babel-plugin-debug-ids/-/babel-plugin-debug-ids-1.0.3.tgz",
@@ -27626,6 +28740,18 @@
         "tar-fs": "1.16.3"
       },
       "dependencies": {
+        "@zeit/fetch": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/@zeit/fetch/-/fetch-5.2.0.tgz",
+          "integrity": "sha512-S4TFaT210j/lgzHjaxN2SiaMxXGeoB4hghNlhlvSqwTFSDNIqyJIDKFZQy+r3NKJOQU+RB5yGKJt8LS1pPHUrw==",
+          "requires": {
+            "@types/async-retry": "1.2.1",
+            "@zeit/fetch-cached-dns": "1.2.0",
+            "@zeit/fetch-retry": "^4.1.0",
+            "agentkeepalive": "3.4.1",
+            "debug": "3.1.0"
+          }
+        },
         "bl": {
           "version": "1.2.3",
           "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
@@ -27635,6 +28761,21 @@
             "safe-buffer": "^5.1.1"
           }
         },
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "requires": {
+            "ms": "2.0.0"
+          },
+          "dependencies": {
+            "ms": {
+              "version": "2.0.0",
+              "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+              "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+            }
+          }
+        },
         "ignore": {
           "version": "4.0.6",
           "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@@ -28249,6 +29390,91 @@
           "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
           "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
           "dev": true
+        },
+        "devtools": {
+          "version": "8.10.7",
+          "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.10.7.tgz",
+          "integrity": "sha512-picJDxsjpaOW7gnQjcQVGDXvxuP1RZ4VNJpvJ+qiy86Gf3l4FGLKYkpJJFAMsIugL0XPs89bIVhjbtIv5NGL1w==",
+          "dev": true,
+          "requires": {
+            "@types/node": "^20.1.0",
+            "@wdio/config": "8.10.7",
+            "@wdio/logger": "8.10.6",
+            "@wdio/protocols": "8.10.2",
+            "@wdio/types": "8.10.4",
+            "@wdio/utils": "8.10.7",
+            "chrome-launcher": "^0.15.0",
+            "edge-paths": "^3.0.5",
+            "import-meta-resolve": "^3.0.0",
+            "puppeteer-core": "20.3.0",
+            "query-selector-shadow-dom": "^1.0.0",
+            "ua-parser-js": "^1.0.1",
+            "uuid": "^9.0.0",
+            "which": "^3.0.0"
+          }
+        },
+        "devtools-protocol": {
+          "version": "0.0.1149535",
+          "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1149535.tgz",
+          "integrity": "sha512-vpM8tGaYz2nrN9n8rvUEhQCgU05ocejO5WIJySsftEHxUahQ/fWuNyPxXuQNBEmaISYyMZkxCunhjtSEyBl/Dg==",
+          "dev": true
+        },
+        "is-plain-obj": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+          "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+          "dev": true
+        },
+        "minimatch": {
+          "version": "9.0.1",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
+          "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
+          "dev": true,
+          "requires": {
+            "brace-expansion": "^2.0.1"
+          }
+        },
+        "webdriverio": {
+          "version": "8.10.7",
+          "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.10.7.tgz",
+          "integrity": "sha512-TkkPE3zBxdLRdcsNLqHct2OARnfMYB9/A0ri4sccmc3C3dVFiW99NAstN88nzD1SYzXAbxALRuITVd5oswqqhg==",
+          "dev": true,
+          "requires": {
+            "@types/node": "^20.1.0",
+            "@wdio/config": "8.10.7",
+            "@wdio/logger": "8.10.6",
+            "@wdio/protocols": "8.10.2",
+            "@wdio/repl": "8.10.1",
+            "@wdio/types": "8.10.4",
+            "@wdio/utils": "8.10.7",
+            "archiver": "^5.0.0",
+            "aria-query": "^5.0.0",
+            "css-shorthand-properties": "^1.1.1",
+            "css-value": "^0.0.1",
+            "devtools": "8.10.7",
+            "devtools-protocol": "^0.0.1149535",
+            "grapheme-splitter": "^1.0.2",
+            "import-meta-resolve": "^3.0.0",
+            "is-plain-obj": "^4.1.0",
+            "lodash.clonedeep": "^4.5.0",
+            "lodash.zip": "^4.2.0",
+            "minimatch": "^9.0.0",
+            "puppeteer-core": "20.3.0",
+            "query-selector-shadow-dom": "^1.0.0",
+            "resq": "^1.9.1",
+            "rgb2hex": "0.2.5",
+            "serialize-error": "^8.0.0",
+            "webdriver": "8.10.7"
+          }
+        },
+        "which": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+          "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
+          "dev": true,
+          "requires": {
+            "isexe": "^2.0.0"
+          }
         }
       }
     },
@@ -28322,6 +29548,99 @@
       "requires": {
         "expect-webdriverio": "^4.0.1",
         "webdriverio": "8.10.7"
+      },
+      "dependencies": {
+        "devtools": {
+          "version": "8.10.7",
+          "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.10.7.tgz",
+          "integrity": "sha512-picJDxsjpaOW7gnQjcQVGDXvxuP1RZ4VNJpvJ+qiy86Gf3l4FGLKYkpJJFAMsIugL0XPs89bIVhjbtIv5NGL1w==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "@types/node": "^20.1.0",
+            "@wdio/config": "8.10.7",
+            "@wdio/logger": "8.10.6",
+            "@wdio/protocols": "8.10.2",
+            "@wdio/types": "8.10.4",
+            "@wdio/utils": "8.10.7",
+            "chrome-launcher": "^0.15.0",
+            "edge-paths": "^3.0.5",
+            "import-meta-resolve": "^3.0.0",
+            "puppeteer-core": "20.3.0",
+            "query-selector-shadow-dom": "^1.0.0",
+            "ua-parser-js": "^1.0.1",
+            "uuid": "^9.0.0",
+            "which": "^3.0.0"
+          }
+        },
+        "devtools-protocol": {
+          "version": "0.0.1149535",
+          "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1149535.tgz",
+          "integrity": "sha512-vpM8tGaYz2nrN9n8rvUEhQCgU05ocejO5WIJySsftEHxUahQ/fWuNyPxXuQNBEmaISYyMZkxCunhjtSEyBl/Dg==",
+          "dev": true,
+          "optional": true
+        },
+        "is-plain-obj": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+          "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+          "dev": true,
+          "optional": true
+        },
+        "minimatch": {
+          "version": "9.0.1",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
+          "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "brace-expansion": "^2.0.1"
+          }
+        },
+        "webdriverio": {
+          "version": "8.10.7",
+          "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.10.7.tgz",
+          "integrity": "sha512-TkkPE3zBxdLRdcsNLqHct2OARnfMYB9/A0ri4sccmc3C3dVFiW99NAstN88nzD1SYzXAbxALRuITVd5oswqqhg==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "@types/node": "^20.1.0",
+            "@wdio/config": "8.10.7",
+            "@wdio/logger": "8.10.6",
+            "@wdio/protocols": "8.10.2",
+            "@wdio/repl": "8.10.1",
+            "@wdio/types": "8.10.4",
+            "@wdio/utils": "8.10.7",
+            "archiver": "^5.0.0",
+            "aria-query": "^5.0.0",
+            "css-shorthand-properties": "^1.1.1",
+            "css-value": "^0.0.1",
+            "devtools": "8.10.7",
+            "devtools-protocol": "^0.0.1149535",
+            "grapheme-splitter": "^1.0.2",
+            "import-meta-resolve": "^3.0.0",
+            "is-plain-obj": "^4.1.0",
+            "lodash.clonedeep": "^4.5.0",
+            "lodash.zip": "^4.2.0",
+            "minimatch": "^9.0.0",
+            "puppeteer-core": "20.3.0",
+            "query-selector-shadow-dom": "^1.0.0",
+            "resq": "^1.9.1",
+            "rgb2hex": "0.2.5",
+            "serialize-error": "^8.0.0",
+            "webdriver": "8.10.7"
+          }
+        },
+        "which": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+          "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "isexe": "^2.0.0"
+          }
+        }
       }
     },
     "@wdio/local-runner": {
@@ -28405,17 +29724,52 @@
       }
     },
     "@wdio/reporter": {
-      "version": "8.10.6",
-      "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.10.6.tgz",
-      "integrity": "sha512-dJFzhO4bZJLNgk+V4NYuZcugDjW3vJzL0gmThWwLAxfui5dXKJeVBL96ZnvBLcHSAK4rVx1+KdCI3M26rpOS8w==",
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.11.0.tgz",
+      "integrity": "sha512-eNQxzaTzouyCGRT8ljSzdOf5oJnQxgiKwOs9Cw/j4r5eqd5JQs+GCFP6tFVNjGtPpVIbfsfTb7yI+xmvjNRXhw==",
       "dev": true,
       "requires": {
         "@types/node": "^20.1.0",
-        "@wdio/logger": "8.10.6",
+        "@wdio/logger": "8.11.0",
         "@wdio/types": "8.10.4",
         "diff": "^5.0.0",
         "object-inspect": "^1.12.0",
         "supports-color": "9.3.1"
+      },
+      "dependencies": {
+        "@wdio/logger": {
+          "version": "8.11.0",
+          "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.11.0.tgz",
+          "integrity": "sha512-IsuKSaYi7NKEdgA57h8muzlN/MVp1dQG+V4C//7g4m03YJUnNQLvDhJzLjdeNTfvZy61U7foQSyt+3ktNzZkXA==",
+          "dev": true,
+          "requires": {
+            "chalk": "^5.1.2",
+            "loglevel": "^1.6.0",
+            "loglevel-plugin-prefix": "^0.8.4",
+            "strip-ansi": "^7.1.0"
+          }
+        },
+        "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
+        },
+        "chalk": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
+          "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "7.1.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+          "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^6.0.1"
+          }
+        }
       }
     },
     "@wdio/runner": {
@@ -28435,15 +29789,102 @@
         "gaze": "^1.1.2",
         "webdriver": "8.10.7",
         "webdriverio": "8.10.7"
+      },
+      "dependencies": {
+        "devtools": {
+          "version": "8.10.7",
+          "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.10.7.tgz",
+          "integrity": "sha512-picJDxsjpaOW7gnQjcQVGDXvxuP1RZ4VNJpvJ+qiy86Gf3l4FGLKYkpJJFAMsIugL0XPs89bIVhjbtIv5NGL1w==",
+          "dev": true,
+          "requires": {
+            "@types/node": "^20.1.0",
+            "@wdio/config": "8.10.7",
+            "@wdio/logger": "8.10.6",
+            "@wdio/protocols": "8.10.2",
+            "@wdio/types": "8.10.4",
+            "@wdio/utils": "8.10.7",
+            "chrome-launcher": "^0.15.0",
+            "edge-paths": "^3.0.5",
+            "import-meta-resolve": "^3.0.0",
+            "puppeteer-core": "20.3.0",
+            "query-selector-shadow-dom": "^1.0.0",
+            "ua-parser-js": "^1.0.1",
+            "uuid": "^9.0.0",
+            "which": "^3.0.0"
+          }
+        },
+        "devtools-protocol": {
+          "version": "0.0.1149535",
+          "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1149535.tgz",
+          "integrity": "sha512-vpM8tGaYz2nrN9n8rvUEhQCgU05ocejO5WIJySsftEHxUahQ/fWuNyPxXuQNBEmaISYyMZkxCunhjtSEyBl/Dg==",
+          "dev": true
+        },
+        "is-plain-obj": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+          "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+          "dev": true
+        },
+        "minimatch": {
+          "version": "9.0.1",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
+          "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
+          "dev": true,
+          "requires": {
+            "brace-expansion": "^2.0.1"
+          }
+        },
+        "webdriverio": {
+          "version": "8.10.7",
+          "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.10.7.tgz",
+          "integrity": "sha512-TkkPE3zBxdLRdcsNLqHct2OARnfMYB9/A0ri4sccmc3C3dVFiW99NAstN88nzD1SYzXAbxALRuITVd5oswqqhg==",
+          "dev": true,
+          "requires": {
+            "@types/node": "^20.1.0",
+            "@wdio/config": "8.10.7",
+            "@wdio/logger": "8.10.6",
+            "@wdio/protocols": "8.10.2",
+            "@wdio/repl": "8.10.1",
+            "@wdio/types": "8.10.4",
+            "@wdio/utils": "8.10.7",
+            "archiver": "^5.0.0",
+            "aria-query": "^5.0.0",
+            "css-shorthand-properties": "^1.1.1",
+            "css-value": "^0.0.1",
+            "devtools": "8.10.7",
+            "devtools-protocol": "^0.0.1149535",
+            "grapheme-splitter": "^1.0.2",
+            "import-meta-resolve": "^3.0.0",
+            "is-plain-obj": "^4.1.0",
+            "lodash.clonedeep": "^4.5.0",
+            "lodash.zip": "^4.2.0",
+            "minimatch": "^9.0.0",
+            "puppeteer-core": "20.3.0",
+            "query-selector-shadow-dom": "^1.0.0",
+            "resq": "^1.9.1",
+            "rgb2hex": "0.2.5",
+            "serialize-error": "^8.0.0",
+            "webdriver": "8.10.7"
+          }
+        },
+        "which": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+          "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
+          "dev": true,
+          "requires": {
+            "isexe": "^2.0.0"
+          }
+        }
       }
     },
     "@wdio/spec-reporter": {
-      "version": "8.10.6",
-      "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.10.6.tgz",
-      "integrity": "sha512-CbdyAY063THUqYnhj+7yr7WLjFYxG++d1AuGdJvYn8CL1/5Wjr3E+EGyofIZs4GtYAJsXH69kNtRO7lwDClvcQ==",
+      "version": "8.11.2",
+      "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.11.2.tgz",
+      "integrity": "sha512-ylhxICgen4eEMXgGBEAN6lYs/UnQ4WUYy2ld/4978vRObXy7pGINz96pIPZHAMn01ENU0+gWr8edwawfZyrqLw==",
       "dev": true,
       "requires": {
-        "@wdio/reporter": "8.10.6",
+        "@wdio/reporter": "8.11.0",
         "@wdio/types": "8.10.4",
         "chalk": "^5.1.2",
         "easy-table": "^1.2.0",
@@ -28631,26 +30072,71 @@
       }
     },
     "@webpack-cli/configtest": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.0.tgz",
-      "integrity": "sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w==",
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz",
+      "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==",
       "dev": true,
       "requires": {}
     },
     "@webpack-cli/info": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz",
-      "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==",
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz",
+      "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==",
       "dev": true,
       "requires": {}
     },
     "@webpack-cli/serve": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.4.tgz",
-      "integrity": "sha512-0xRgjgDLdz6G7+vvDLlaRpFatJaJ69uTalZLRSMX5B3VUrDmXcrVA3+6fXXQgmYz7bY9AAgs348XQdmtLsK41A==",
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz",
+      "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==",
       "dev": true,
       "requires": {}
     },
+    "@wry/context": {
+      "version": "0.7.3",
+      "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.3.tgz",
+      "integrity": "sha512-Nl8WTesHp89RF803Se9X3IiHjdmLBrIvPMaJkl+rKVJAYyPsz1TEUbu89943HpvujtSJgDUx9W4vZw3K1Mr3sA==",
+      "requires": {
+        "tslib": "^2.3.0"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.5.3",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+          "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+        }
+      }
+    },
+    "@wry/equality": {
+      "version": "0.5.6",
+      "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.6.tgz",
+      "integrity": "sha512-D46sfMTngaYlrH+OspKf8mIJETntFnf6Hsjb0V41jAXJ7Bx2kB8Rv8RCUujuVWYttFtHkUNp7g+FwxNQAr6mXA==",
+      "requires": {
+        "tslib": "^2.3.0"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.5.3",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+          "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+        }
+      }
+    },
+    "@wry/trie": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.4.3.tgz",
+      "integrity": "sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w==",
+      "requires": {
+        "tslib": "^2.3.0"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.5.3",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+          "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+        }
+      }
+    },
     "@xtuc/ieee754": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -28682,33 +30168,6 @@
         }
       }
     },
-    "@zeit/fetch": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/@zeit/fetch/-/fetch-5.2.0.tgz",
-      "integrity": "sha512-S4TFaT210j/lgzHjaxN2SiaMxXGeoB4hghNlhlvSqwTFSDNIqyJIDKFZQy+r3NKJOQU+RB5yGKJt8LS1pPHUrw==",
-      "requires": {
-        "@types/async-retry": "1.2.1",
-        "@zeit/fetch-cached-dns": "1.2.0",
-        "@zeit/fetch-retry": "^4.1.0",
-        "agentkeepalive": "3.4.1",
-        "debug": "3.1.0"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-          "requires": {
-            "ms": "2.0.0"
-          }
-        },
-        "ms": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-        }
-      }
-    },
     "@zeit/fetch-cached-dns": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@zeit/fetch-cached-dns/-/fetch-cached-dns-1.2.0.tgz",
@@ -29926,6 +31385,15 @@
         "tcp-port-used": "^1.0.1"
       }
     },
+    "chromium-bidi": {
+      "version": "0.4.9",
+      "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz",
+      "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==",
+      "dev": true,
+      "requires": {
+        "mitt": "3.0.0"
+      }
+    },
     "ci-info": {
       "version": "3.8.0",
       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
@@ -30122,6 +31590,11 @@
         }
       }
     },
+    "clsx": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
+      "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
+    },
     "code-block-writer": {
       "version": "10.1.1",
       "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-10.1.1.tgz",
@@ -30303,6 +31776,24 @@
       "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
       "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
     },
+    "cross-fetch": {
+      "version": "3.1.6",
+      "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz",
+      "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==",
+      "requires": {
+        "node-fetch": "^2.6.11"
+      },
+      "dependencies": {
+        "node-fetch": {
+          "version": "2.6.11",
+          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
+          "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
+          "requires": {
+            "whatwg-url": "^5.0.0"
+          }
+        }
+      }
+    },
     "cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -30823,17 +32314,17 @@
       }
     },
     "devtools": {
-      "version": "8.10.7",
-      "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.10.7.tgz",
-      "integrity": "sha512-picJDxsjpaOW7gnQjcQVGDXvxuP1RZ4VNJpvJ+qiy86Gf3l4FGLKYkpJJFAMsIugL0XPs89bIVhjbtIv5NGL1w==",
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.11.0.tgz",
+      "integrity": "sha512-j1wXFQyjswJ6doAV1+h4Bxl8+Oeb8SMpWTpBVa0DurGsxfft8sU2OhDlMo5tx/zbX82X5sGyJDMnKHqBJ2XRvQ==",
       "dev": true,
       "requires": {
         "@types/node": "^20.1.0",
-        "@wdio/config": "8.10.7",
-        "@wdio/logger": "8.10.6",
-        "@wdio/protocols": "8.10.2",
+        "@wdio/config": "8.11.0",
+        "@wdio/logger": "8.11.0",
+        "@wdio/protocols": "8.11.0",
         "@wdio/types": "8.10.4",
-        "@wdio/utils": "8.10.7",
+        "@wdio/utils": "8.11.0",
         "chrome-launcher": "^0.15.0",
         "edge-paths": "^3.0.5",
         "import-meta-resolve": "^3.0.0",
@@ -30844,6 +32335,117 @@
         "which": "^3.0.0"
       },
       "dependencies": {
+        "@wdio/config": {
+          "version": "8.11.0",
+          "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.11.0.tgz",
+          "integrity": "sha512-nBQXsXbPCjddtI/3rAK5yFs3eD3f0T3lZMivweTkLLR7GKBxGjiFoBjXtfqUrHJYa+2uwfXrwxo6y+dA6fVbuw==",
+          "dev": true,
+          "requires": {
+            "@wdio/logger": "8.11.0",
+            "@wdio/types": "8.10.4",
+            "@wdio/utils": "8.11.0",
+            "decamelize": "^6.0.0",
+            "deepmerge-ts": "^5.0.0",
+            "glob": "^10.2.2",
+            "import-meta-resolve": "^3.0.0",
+            "read-pkg-up": "^9.1.0"
+          }
+        },
+        "@wdio/logger": {
+          "version": "8.11.0",
+          "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.11.0.tgz",
+          "integrity": "sha512-IsuKSaYi7NKEdgA57h8muzlN/MVp1dQG+V4C//7g4m03YJUnNQLvDhJzLjdeNTfvZy61U7foQSyt+3ktNzZkXA==",
+          "dev": true,
+          "requires": {
+            "chalk": "^5.1.2",
+            "loglevel": "^1.6.0",
+            "loglevel-plugin-prefix": "^0.8.4",
+            "strip-ansi": "^7.1.0"
+          }
+        },
+        "@wdio/protocols": {
+          "version": "8.11.0",
+          "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.11.0.tgz",
+          "integrity": "sha512-eXTMYt/XoaX53H/Q2qmsn1uWthIC5aSTGtX9YyXD/AkagG2hXeX3lLmzNWBaSIvKR+vWXRYbg3Y/7IvL2s25Wg==",
+          "dev": true
+        },
+        "@wdio/utils": {
+          "version": "8.11.0",
+          "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.11.0.tgz",
+          "integrity": "sha512-XBl1zalk5UPu8QKZ7LZIA82Ad363fpNHZHP5uI5OxUFnk4ZPWgY9eCWpeD+4f9a0DS0w2Dro15E4PORNX84pIw==",
+          "dev": true,
+          "requires": {
+            "@wdio/logger": "8.11.0",
+            "@wdio/types": "8.10.4",
+            "import-meta-resolve": "^3.0.0",
+            "p-iteration": "^1.1.8"
+          }
+        },
+        "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
+        },
+        "chalk": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
+          "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
+          "dev": true
+        },
+        "foreground-child": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+          "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+          "dev": true,
+          "requires": {
+            "cross-spawn": "^7.0.0",
+            "signal-exit": "^4.0.1"
+          }
+        },
+        "glob": {
+          "version": "10.2.7",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz",
+          "integrity": "sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==",
+          "dev": true,
+          "requires": {
+            "foreground-child": "^3.1.0",
+            "jackspeak": "^2.0.3",
+            "minimatch": "^9.0.1",
+            "minipass": "^5.0.0 || ^6.0.2",
+            "path-scurry": "^1.7.0"
+          }
+        },
+        "minimatch": {
+          "version": "9.0.1",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
+          "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
+          "dev": true,
+          "requires": {
+            "brace-expansion": "^2.0.1"
+          }
+        },
+        "minipass": {
+          "version": "6.0.2",
+          "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz",
+          "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==",
+          "dev": true
+        },
+        "signal-exit": {
+          "version": "4.0.2",
+          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz",
+          "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "7.1.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+          "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^6.0.1"
+          }
+        },
         "which": {
           "version": "3.0.1",
           "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
@@ -30856,9 +32458,9 @@
       }
     },
     "devtools-protocol": {
-      "version": "0.0.1149535",
-      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1149535.tgz",
-      "integrity": "sha512-vpM8tGaYz2nrN9n8rvUEhQCgU05ocejO5WIJySsftEHxUahQ/fWuNyPxXuQNBEmaISYyMZkxCunhjtSEyBl/Dg==",
+      "version": "0.0.1152884",
+      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1152884.tgz",
+      "integrity": "sha512-9eP6OmCoU1cWArpXLuzyZQcBJ2PkINOh8Nwx8W5i8u6NDigDE5/mPlLLBAfshwn5YVvIz6ZQ9jbs0PZvKGccdQ==",
       "dev": true
     },
     "diff": {
@@ -30927,9 +32529,9 @@
       }
     },
     "dotenv": {
-      "version": "16.0.3",
-      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
-      "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="
+      "version": "16.1.4",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz",
+      "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw=="
     },
     "download": {
       "version": "8.0.0",
@@ -31884,6 +33486,13 @@
         }
       }
     },
+    "eslint-config-prettier": {
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz",
+      "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==",
+      "dev": true,
+      "requires": {}
+    },
     "eslint-import-resolver-node": {
       "version": "0.3.7",
       "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz",
@@ -32373,9 +33982,9 @@
       }
     },
     "expect-webdriverio": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.2.3.tgz",
-      "integrity": "sha512-B5y5NYVUWHQGbMiBzH8UBHcGTHhwu13o4/uUZEpc1wmJk2JxWP7fNpmpkUyiW5yygb2Sp0/02ds4b/xw6sBR/A==",
+      "version": "4.2.6",
+      "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.2.6.tgz",
+      "integrity": "sha512-/7wImWgef6ooZSoivXq6jkCDE1vvDC42WcM7Iyguspah/h384nprUdpQPLIUkkvTCorSQdYtRftNvHJjEpVUmA==",
       "dev": true,
       "requires": {
         "@wdio/globals": "^8.8.8",
@@ -32688,7 +34297,6 @@
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
       "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
-      "dev": true,
       "requires": {
         "node-domexception": "^1.0.0",
         "web-streams-polyfill": "^3.0.3"
@@ -32993,7 +34601,6 @@
       "version": "4.0.10",
       "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
       "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
-      "dev": true,
       "requires": {
         "fetch-blob": "^3.1.2"
       }
@@ -33474,6 +35081,26 @@
       "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
       "dev": true
     },
+    "graphql": {
+      "version": "16.6.0",
+      "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz",
+      "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw=="
+    },
+    "graphql-tag": {
+      "version": "2.12.6",
+      "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz",
+      "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==",
+      "requires": {
+        "tslib": "^2.1.0"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.5.3",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+          "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+        }
+      }
+    },
     "gunzip-maybe": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz",
@@ -33620,6 +35247,21 @@
       "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
       "dev": true
     },
+    "hoist-non-react-statics": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+      "requires": {
+        "react-is": "^16.7.0"
+      },
+      "dependencies": {
+        "react-is": {
+          "version": "16.13.1",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+          "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+        }
+      }
+    },
     "hosted-git-info": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@@ -35053,9 +36695,9 @@
       "dev": true
     },
     "lit": {
-      "version": "2.7.4",
-      "resolved": "https://registry.npmjs.org/lit/-/lit-2.7.4.tgz",
-      "integrity": "sha512-cgD7xrZoYr21mbrkZIuIrj98YTMw/snJPg52deWVV4A8icLyNHI3bF70xsJeAgwTuiq5Kkd+ZR8gybSJDCPB7g==",
+      "version": "2.7.5",
+      "resolved": "https://registry.npmjs.org/lit/-/lit-2.7.5.tgz",
+      "integrity": "sha512-i/cH7Ye6nBDUASMnfwcictBnsTN91+aBjXoTHF2xARghXScKxpD4F4WYI+VLXg9lqbMinDfvoI7VnZXjyHgdfQ==",
       "requires": {
         "@lit/reactive-element": "^1.6.0",
         "lit-element": "^3.3.0",
@@ -35285,7 +36927,6 @@
       "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"
       }
@@ -36358,15 +37999,23 @@
     "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==",
-      "dev": true
+      "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
     },
     "node-fetch": {
-      "version": "2.6.11",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
-      "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz",
+      "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==",
       "requires": {
-        "whatwg-url": "^5.0.0"
+        "data-uri-to-buffer": "^4.0.0",
+        "fetch-blob": "^3.1.4",
+        "formdata-polyfill": "^4.0.10"
+      },
+      "dependencies": {
+        "data-uri-to-buffer": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+          "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="
+        }
       }
     },
     "node-gyp-build": {
@@ -36660,6 +38309,30 @@
         "is-wsl": "^2.2.0"
       }
     },
+    "optimism": {
+      "version": "0.16.2",
+      "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.16.2.tgz",
+      "integrity": "sha512-zWNbgWj+3vLEjZNIh/okkY2EUfX+vB9TJopzIZwT1xxaMqC5hRLLraePod4c5n4He08xuXNH+zhKFFCu390wiQ==",
+      "requires": {
+        "@wry/context": "^0.7.0",
+        "@wry/trie": "^0.3.0"
+      },
+      "dependencies": {
+        "@wry/trie": {
+          "version": "0.3.2",
+          "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.3.2.tgz",
+          "integrity": "sha512-yRTyhWSls2OY/pYLfwff867r8ekooZ4UI+/gxot5Wj8EFwSf2rG+n+Mo/6LoLQm1TKA4GRj2+LCpbfS937dClQ==",
+          "requires": {
+            "tslib": "^2.3.0"
+          }
+        },
+        "tslib": {
+          "version": "2.5.3",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+          "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+        }
+      }
+    },
     "optionator": {
       "version": "0.9.1",
       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -37325,9 +38998,10 @@
       "dev": true
     },
     "prettier": {
-      "version": "2.7.1",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
-      "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g=="
+      "version": "2.8.8",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
+      "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
+      "dev": true
     },
     "pretty-bytes": {
       "version": "5.6.0",
@@ -37389,6 +39063,23 @@
       "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
       "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="
     },
+    "prop-types": {
+      "version": "15.8.1",
+      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+      "requires": {
+        "loose-envify": "^1.4.0",
+        "object-assign": "^4.1.1",
+        "react-is": "^16.13.1"
+      },
+      "dependencies": {
+        "react-is": {
+          "version": "16.13.1",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+          "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+        }
+      }
+    },
     "property-information": {
       "version": "6.2.0",
       "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz",
@@ -37506,24 +39197,6 @@
         "ws": "8.13.0"
       },
       "dependencies": {
-        "chromium-bidi": {
-          "version": "0.4.9",
-          "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz",
-          "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==",
-          "dev": true,
-          "requires": {
-            "mitt": "3.0.0"
-          }
-        },
-        "cross-fetch": {
-          "version": "3.1.6",
-          "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz",
-          "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==",
-          "dev": true,
-          "requires": {
-            "node-fetch": "^2.6.11"
-          }
-        },
         "devtools-protocol": {
           "version": "0.0.1120988",
           "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz",
@@ -38090,6 +39763,11 @@
       "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
       "dev": true
     },
+    "response-iterator": {
+      "version": "0.2.6",
+      "resolved": "https://registry.npmjs.org/response-iterator/-/response-iterator-0.2.6.tgz",
+      "integrity": "sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw=="
+    },
     "responselike": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz",
@@ -38279,25 +39957,6 @@
         "tar-fs": "^2.1.1",
         "unzipper": "^0.10.11",
         "wait-on": "^7.0.1"
-      },
-      "dependencies": {
-        "data-uri-to-buffer": {
-          "version": "4.0.1",
-          "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
-          "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
-          "dev": true
-        },
-        "node-fetch": {
-          "version": "3.3.1",
-          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz",
-          "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==",
-          "dev": true,
-          "requires": {
-            "data-uri-to-buffer": "^4.0.0",
-            "fetch-blob": "^3.1.4",
-            "formdata-polyfill": "^4.0.10"
-          }
-        }
       }
     },
     "rxjs": {
@@ -39036,6 +40695,11 @@
       "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
       "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
     },
+    "symbol-observable": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
+      "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ=="
+    },
     "synckit": {
       "version": "0.8.5",
       "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
@@ -39435,6 +41099,21 @@
       "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz",
       "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g=="
     },
+    "ts-invariant": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz",
+      "integrity": "sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==",
+      "requires": {
+        "tslib": "^2.1.0"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.5.3",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+          "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+        }
+      }
+    },
     "ts-loader": {
       "version": "9.4.3",
       "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.3.tgz",
@@ -39628,9 +41307,9 @@
       }
     },
     "typescript": {
-      "version": "5.0.4",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
-      "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw=="
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
+      "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw=="
     },
     "ua-parser-js": {
       "version": "1.0.35",
@@ -40074,9 +41753,9 @@
       }
     },
     "vite": {
-      "version": "4.3.7",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.7.tgz",
-      "integrity": "sha512-MTIFpbIm9v7Hh5b0wSBgkcWzSBz7SAa6K/cBTwS4kUiQJfQLFlZZRJRQgqunCVzhTPCk674tW+0Qaqh3Q00dBg==",
+      "version": "4.3.9",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
+      "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
       "requires": {
         "esbuild": "^0.17.5",
         "fsevents": "~2.3.2",
@@ -40488,8 +42167,7 @@
     "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==",
-      "dev": true
+      "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q=="
     },
     "web-vitals": {
       "version": "0.2.4",
@@ -40521,24 +42199,24 @@
       }
     },
     "webdriverio": {
-      "version": "8.10.7",
-      "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.10.7.tgz",
-      "integrity": "sha512-TkkPE3zBxdLRdcsNLqHct2OARnfMYB9/A0ri4sccmc3C3dVFiW99NAstN88nzD1SYzXAbxALRuITVd5oswqqhg==",
+      "version": "8.11.2",
+      "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.11.2.tgz",
+      "integrity": "sha512-e/9WkdNTfWeoaSo2UzK0Giec/nQX3i7U9J8esimhozH/EpwSqIaEJ2pRRlxRVafEhe2OBG1QDhnLnDjdCC5Hxg==",
       "dev": true,
       "requires": {
         "@types/node": "^20.1.0",
-        "@wdio/config": "8.10.7",
-        "@wdio/logger": "8.10.6",
-        "@wdio/protocols": "8.10.2",
+        "@wdio/config": "8.11.0",
+        "@wdio/logger": "8.11.0",
+        "@wdio/protocols": "8.11.0",
         "@wdio/repl": "8.10.1",
         "@wdio/types": "8.10.4",
-        "@wdio/utils": "8.10.7",
+        "@wdio/utils": "8.11.0",
         "archiver": "^5.0.0",
         "aria-query": "^5.0.0",
         "css-shorthand-properties": "^1.1.1",
         "css-value": "^0.0.1",
-        "devtools": "8.10.7",
-        "devtools-protocol": "^0.0.1149535",
+        "devtools": "8.11.0",
+        "devtools-protocol": "^0.0.1152884",
         "grapheme-splitter": "^1.0.2",
         "import-meta-resolve": "^3.0.0",
         "is-plain-obj": "^4.1.0",
@@ -40550,9 +42228,90 @@
         "resq": "^1.9.1",
         "rgb2hex": "0.2.5",
         "serialize-error": "^8.0.0",
-        "webdriver": "8.10.7"
+        "webdriver": "8.11.1"
       },
       "dependencies": {
+        "@wdio/config": {
+          "version": "8.11.0",
+          "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.11.0.tgz",
+          "integrity": "sha512-nBQXsXbPCjddtI/3rAK5yFs3eD3f0T3lZMivweTkLLR7GKBxGjiFoBjXtfqUrHJYa+2uwfXrwxo6y+dA6fVbuw==",
+          "dev": true,
+          "requires": {
+            "@wdio/logger": "8.11.0",
+            "@wdio/types": "8.10.4",
+            "@wdio/utils": "8.11.0",
+            "decamelize": "^6.0.0",
+            "deepmerge-ts": "^5.0.0",
+            "glob": "^10.2.2",
+            "import-meta-resolve": "^3.0.0",
+            "read-pkg-up": "^9.1.0"
+          }
+        },
+        "@wdio/logger": {
+          "version": "8.11.0",
+          "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.11.0.tgz",
+          "integrity": "sha512-IsuKSaYi7NKEdgA57h8muzlN/MVp1dQG+V4C//7g4m03YJUnNQLvDhJzLjdeNTfvZy61U7foQSyt+3ktNzZkXA==",
+          "dev": true,
+          "requires": {
+            "chalk": "^5.1.2",
+            "loglevel": "^1.6.0",
+            "loglevel-plugin-prefix": "^0.8.4",
+            "strip-ansi": "^7.1.0"
+          }
+        },
+        "@wdio/protocols": {
+          "version": "8.11.0",
+          "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.11.0.tgz",
+          "integrity": "sha512-eXTMYt/XoaX53H/Q2qmsn1uWthIC5aSTGtX9YyXD/AkagG2hXeX3lLmzNWBaSIvKR+vWXRYbg3Y/7IvL2s25Wg==",
+          "dev": true
+        },
+        "@wdio/utils": {
+          "version": "8.11.0",
+          "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.11.0.tgz",
+          "integrity": "sha512-XBl1zalk5UPu8QKZ7LZIA82Ad363fpNHZHP5uI5OxUFnk4ZPWgY9eCWpeD+4f9a0DS0w2Dro15E4PORNX84pIw==",
+          "dev": true,
+          "requires": {
+            "@wdio/logger": "8.11.0",
+            "@wdio/types": "8.10.4",
+            "import-meta-resolve": "^3.0.0",
+            "p-iteration": "^1.1.8"
+          }
+        },
+        "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
+        },
+        "chalk": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
+          "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
+          "dev": true
+        },
+        "foreground-child": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+          "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+          "dev": true,
+          "requires": {
+            "cross-spawn": "^7.0.0",
+            "signal-exit": "^4.0.1"
+          }
+        },
+        "glob": {
+          "version": "10.2.7",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz",
+          "integrity": "sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==",
+          "dev": true,
+          "requires": {
+            "foreground-child": "^3.1.0",
+            "jackspeak": "^2.0.3",
+            "minimatch": "^9.0.1",
+            "minipass": "^5.0.0 || ^6.0.2",
+            "path-scurry": "^1.7.0"
+          }
+        },
         "is-plain-obj": {
           "version": "4.1.0",
           "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
@@ -40567,6 +42326,46 @@
           "requires": {
             "brace-expansion": "^2.0.1"
           }
+        },
+        "minipass": {
+          "version": "6.0.2",
+          "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz",
+          "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==",
+          "dev": true
+        },
+        "signal-exit": {
+          "version": "4.0.2",
+          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz",
+          "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "7.1.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+          "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^6.0.1"
+          }
+        },
+        "webdriver": {
+          "version": "8.11.1",
+          "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.11.1.tgz",
+          "integrity": "sha512-hSpUZYzUA65t4DDtKujCHUX6hpFTUleb7lWMcf5xjPz8sxWrK9R8NIw7pXt/GU6PVS331nGAaYkzoXrqz2VB8w==",
+          "dev": true,
+          "requires": {
+            "@types/node": "^20.1.0",
+            "@types/ws": "^8.5.3",
+            "@wdio/config": "8.11.0",
+            "@wdio/logger": "8.11.0",
+            "@wdio/protocols": "8.11.0",
+            "@wdio/types": "8.10.4",
+            "@wdio/utils": "8.11.0",
+            "deepmerge-ts": "^5.0.0",
+            "got": "^ 12.6.1",
+            "ky": "^0.33.0",
+            "ws": "^8.8.0"
+          }
         }
       }
     },
@@ -40608,15 +42407,15 @@
       }
     },
     "webpack-cli": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.1.tgz",
-      "integrity": "sha512-OLJwVMoXnXYH2ncNGU8gxVpUtm3ybvdioiTvHgUyBuyMLKiVvWy+QObzBsMtp5pH7qQoEuWgeEUQ/sU3ZJFzAw==",
+      "version": "5.1.4",
+      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz",
+      "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
       "dev": true,
       "requires": {
         "@discoveryjs/json-ext": "^0.5.0",
-        "@webpack-cli/configtest": "^2.1.0",
-        "@webpack-cli/info": "^2.0.1",
-        "@webpack-cli/serve": "^2.0.4",
+        "@webpack-cli/configtest": "^2.1.1",
+        "@webpack-cli/info": "^2.0.2",
+        "@webpack-cli/serve": "^2.0.5",
         "colorette": "^2.0.14",
         "commander": "^10.0.1",
         "cross-spawn": "^7.0.3",
@@ -41355,6 +43154,19 @@
       "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
       "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
     },
+    "zen-observable": {
+      "version": "0.8.15",
+      "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
+      "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="
+    },
+    "zen-observable-ts": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz",
+      "integrity": "sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==",
+      "requires": {
+        "zen-observable": "0.8.15"
+      }
+    },
     "zip-stream": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz",
diff --git a/package.json b/package.json
index 355eb4ce..f2997957 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "runme",
-  "version": "1.5.2-edge.1",
+  "version": "1.5.4-edge.0",
   "publisher": "stateful",
   "displayName": "Runme",
   "description": "Execute commands inside your runbooks, docs, and READMEs.",
@@ -166,6 +166,11 @@
         "category": "Runme",
         "title": "Run cells with the same Category",
         "icon": "$(group-by-ref-type)"
+      },
+      {
+        "command": "runme.addToRecommendedExtensions",
+        "title": "Add Runme to recommended extensions",
+        "category": "Runme"
       }
     ],
     "menus": {
@@ -313,6 +318,11 @@
             "default": true,
             "markdownDescription": "If enabled, Runme's save restriction on non checked-in files will be lifted."
           },
+          "runme.flags.startFile": {
+            "type": "string",
+            "scope": "window",
+            "markdownDescription": "Relative path to a file within the workspace to open within a Runme notebook view, when VS Code starts (e.g. `./CONTRIBUTING.md`)."
+          },
           "runme.experiments.grpcSerializer": {
             "type": "boolean",
             "scope": "machine",
@@ -425,6 +435,21 @@
             "type": "boolean",
             "default": true,
             "markdownDescription": "Load environment files from workspace into all scripts"
+          },
+          "runme.cli.useIntegratedRunme": {
+            "type": "boolean",
+            "default": false,
+            "markdownDescription": "Whether to use full path to integrated runme executable when running CLI commands. This is mostly useful for development purposes."
+          },
+          "runme.app.apiUrl": {
+            "type": "string",
+            "default": "",
+            "markdownDescription": "Runme App API URL"
+          },
+          "runme.app.enableShare": {
+            "type": "boolean",
+            "default": false,
+            "markdownDescription": "Enable share button in notebook terminal"
           }
         }
       }
@@ -543,6 +568,8 @@
     "test": "run-s test:*",
     "test:lint": "eslint src tests --ext ts",
     "test:lint:fix": "eslint src tests --ext ts --fix",
+    "test:format": "prettier --check .",
+    "test:format:fix": "prettier --write .",
     "test:unit": "vitest -c ./vitest.conf.ts",
     "test:e2e": "wdio run ./tests/e2e/wdio.conf.ts",
     "pretest:e2e": "npm run download:binary",
@@ -571,6 +598,7 @@
     "c8": "^7.13.0",
     "clipboardy": "^3.0.0",
     "eslint": "^8.40.0",
+    "eslint-config-prettier": "^8.8.0",
     "eslint-import-resolver-typescript": "^3.5.5",
     "eslint-plugin-import": "^2.27.5",
     "eslint-plugin-unicorn": "^47.0.0",
@@ -579,6 +607,7 @@
     "fork-ts-checker-webpack-plugin": "^8.0.0",
     "husky": "^8.0.3",
     "npm-run-all": "^4.1.5",
+    "prettier": "2.8.8",
     "runme": "^2.3.5",
     "semver": "^7.5.1",
     "tar-fs": "^2.1.1",
@@ -594,6 +623,7 @@
     "webpack-cli": "^5.1.1"
   },
   "dependencies": {
+    "@apollo/client": "^3.7.15",
     "@buf/grpc_grpc.community_timostamm-protobuf-ts": "^2.9.0-20230505202852-f6db652602f5.1",
     "@buf/stateful_runme.community_timostamm-protobuf-ts": "^2.9.0-20230522162609-549daeeacece.1",
     "@protobuf-ts/grpc-transport": "^2.9.0",
@@ -604,11 +634,14 @@
     "@vscode/vscode-languagedetection": "^1.0.22",
     "@vscode/webview-ui-toolkit": "^1.2.2",
     "clipboardy": "^3.0.0",
+    "clsx": "^1.2.1",
+    "cross-fetch": "^3.1.6",
     "detect-port": "^1.5.1",
     "dotenv": "^16.0.3",
     "filenamify": "^6.0.0",
     "get-port": "^6.1.2",
     "got": "^12.6.0",
+    "graphql": "^16.6.0",
     "lit": "^2.7.4",
     "octokit": "^2.0.14",
     "preact": "^10.14.1",
diff --git a/src/client/components/copyButton.ts b/src/client/components/copyButton.ts
new file mode 100644
index 00000000..d907e8c5
--- /dev/null
+++ b/src/client/components/copyButton.ts
@@ -0,0 +1,47 @@
+import { LitElement, css, html } from 'lit'
+import { customElement, property } from 'lit/decorators.js'
+
+import { CopyIcon } from './icons/copy'
+
+@customElement('copy-button')
+export class CopyButton extends LitElement {
+
+  @property({ type: String })
+  copyText: string = 'Copy'
+  /* eslint-disable */
+  static styles = css`
+    vscode-button {
+        background: transparent;
+        color: #ccc;
+        transform: scale(.9);
+      }
+      vscode-button:hover {
+        background: var(--button-secondary-background);
+      }
+      vscode-button:focus {
+        outline: #007fd4 1px solid;
+      }
+      .icon {
+        width: 13px;
+        margin: 0 5px 0 -5px;
+        padding: 0;
+      }
+    `
+
+  private onCopy(e: Event) {
+    if (e.defaultPrevented) {
+      e.preventDefault()
+    }
+    const event = new CustomEvent('onCopy')
+    this.dispatchEvent(event)
+  }
+
+  render() {
+    return html`
+        <vscode-button appearance="secondary" @click=${this.onCopy}>
+        ${CopyIcon}
+        ${this.copyText}
+        </vscode-button>
+        `
+  }
+}
\ No newline at end of file
diff --git a/src/client/components/icons/copy.ts b/src/client/components/icons/copy.ts
new file mode 100644
index 00000000..9866016b
--- /dev/null
+++ b/src/client/components/icons/copy.ts
@@ -0,0 +1,26 @@
+import { html } from 'lit'
+
+export const CopyIcon = html`
+<svg
+    class="icon"
+    width="16"
+    height="16"
+    viewBox="0 0 16 16"
+    xmlns="http://www.w3.org/2000/svg"
+    fill="currentColor">
+    <path
+        fill-rule="evenodd"
+        clip-rule="evenodd"
+        d="M4 4l1-1h5.414L14 6.586V14l-1 1H5l-1-1V4zm9 3l-3-3H5v10h8V7z"
+    />
+    <path
+        fill-rule="evenodd"
+        clip-rule="evenodd"
+        d="M3 1L2 2v10l1 1V2h6.414l-1-1H3z"
+    />
+</svg>
+`
+
+export default {
+    CopyIcon
+}
\ No newline at end of file
diff --git a/src/client/components/icons/index.ts b/src/client/components/icons/index.ts
new file mode 100644
index 00000000..41352a2b
--- /dev/null
+++ b/src/client/components/icons/index.ts
@@ -0,0 +1,7 @@
+import ShareIcon from './share'
+import { CopyIcon } from './copy'
+
+export default {
+    ShareIcon,
+    CopyIcon
+}
\ No newline at end of file
diff --git a/src/client/components/icons/share.ts b/src/client/components/icons/share.ts
new file mode 100644
index 00000000..4f27f763
--- /dev/null
+++ b/src/client/components/icons/share.ts
@@ -0,0 +1,23 @@
+import { html } from 'lit'
+
+/* eslint-disable max-len */
+export const ShareIcon = html`
+<svg
+class="icon"
+width="16"
+height="16"
+viewBox="0 0 16 16"
+fill="currentColor"
+xmlns="http://www.w3.org/2000/svg"
+>
+<path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.9564 6H12.0063C12.8003 6 13.5617 6.31607 14.1231 6.87868C14.6845 7.44129 14.9999 8.20435 14.9999 9C14.9999 9.79565 14.6845 10.5587 14.1231 11.1213C13.5617 11.6839 12.8003 12 12.0063 12H10.0106V11H12.0063C12.5356 11 13.0432 10.7893 13.4175 10.4142C13.7918 10.0391 14.002 9.53044 14.002 9C14.002 8.46957 13.7918 7.96086 13.4175 7.58579C13.0432 7.21072 12.5356 7 12.0063 7H11.0923L10.9686 6.143C10.8937 5.60541 10.6455 5.10711 10.2617 4.72407C9.87792 4.34103 9.37968 4.09427 8.84295 4.02143C8.30621 3.94859 7.76044 4.05365 7.28883 4.3206C6.81723 4.58754 6.44567 5.00173 6.23082 5.5L5.89754 6.262L5.08929 6.073C4.90378 6.02699 4.71361 6.0025 4.52251 6C3.8609 6 3.22639 6.2634 2.75856 6.73224C2.29073 7.20108 2.02791 7.83696 2.02791 8.5C2.02791 9.16304 2.29073 9.79893 2.75856 10.2678C3.22639 10.7366 3.8609 11 4.52251 11H7.01712V12H4.52251C4.02742 12.0043 3.53708 11.903 3.08401 11.7029C2.63095 11.5028 2.22551 11.2084 1.8946 10.8394C1.56369 10.4703 1.31487 10.0349 1.16465 9.56211C1.01442 9.08932 0.966218 8.58992 1.02324 8.09704C1.08026 7.60416 1.24121 7.12906 1.49539 6.70326C1.74958 6.27745 2.0912 5.91068 2.4976 5.62727C2.904 5.34385 3.36588 5.15027 3.85261 5.05937C4.33935 4.96847 4.8398 4.98232 5.32079 5.1C5.62405 4.40501 6.14506 3.82799 6.8049 3.45635C7.46474 3.08472 8.22745 2.9387 8.97752 3.04044C9.72759 3.14217 10.4241 3.4861 10.9617 4.02014C11.4992 4.55418 11.8484 5.24923 11.9564 6ZM10.2928 9.85348L8.97879 8.53944L8.97879 13.9749H7.98492L7.98493 8.57568L6.7071 9.85347L5.99999 9.14636L8.14643 6.99998H8.85354L10.9999 9.14637L10.2928 9.85348Z"
+    fill="#C5C5C5"
+/>
+</svg>`
+
+export default {
+    ShareIcon
+}
\ No newline at end of file
diff --git a/src/client/components/terminal.ts b/src/client/components/terminal/index.ts
similarity index 77%
rename from src/client/components/terminal.ts
rename to src/client/components/terminal/index.ts
index 3613302c..8f64a703 100644
--- a/src/client/components/terminal.ts
+++ b/src/client/components/terminal/index.ts
@@ -5,24 +5,42 @@ import { ITheme, Terminal as XTermJS } from 'xterm'
 import { SerializeAddon } from 'xterm-addon-serialize'
 import { Unicode11Addon } from 'xterm-addon-unicode11'
 import { WebLinksAddon } from 'xterm-addon-web-links'
+import { when } from 'lit/directives/when.js'
 
-import { FitAddon, type ITerminalDimensions } from '../fitAddon'
-import { ClientMessages, RENDERERS, OutputType } from '../../constants'
-import { closeOutput, getContext } from '../utils'
-import { onClientMessage, postClientMessage } from '../../utils/messaging'
-import { stripANSI } from '../../utils/ansi'
+import { FitAddon, type ITerminalDimensions } from '../../fitAddon'
+import { ClientMessages, RENDERERS, OutputType } from '../../../constants'
+import { closeOutput, getContext } from '../../utils'
+import { onClientMessage, postClientMessage } from '../../../utils/messaging'
+import { stripANSI } from '../../../utils/ansi'
+import { APIMethod } from '../../../types'
 
-import './closeCellButton'
+import '../closeCellButton'
+import '../copyButton'
+import './share'
 
 interface IWindowSize {
   width: number
   height: number
 }
 
+enum MessageOptions {
+  OpenLink = 'Open link',
+  CopyToClipboard = 'Copy to clipboard',
+  Cancel = 'Cancel'
+}
+
 const vscodeCSS = (...identifiers: string[]) => `--vscode-${identifiers.join('-')}`
 const terminalCSS = (id: string) => vscodeCSS('terminal', id)
 const toAnsi = (id: string) => `ansi${id.charAt(0).toUpperCase() + id.slice(1)}`
-const LISTEN_TO_EVENTS = ['terminal:', 'theme:']
+const LISTEN_TO_EVENTS = [
+  'terminal:',
+  'theme:',
+  ClientMessages.cloudApiRequest,
+  ClientMessages.cloudApiResponse,
+  ClientMessages.onOptionsMessage,
+  ClientMessages.optionsMessage,
+  ClientMessages.onCopyTextToClipboard
+]
 
 const ANSI_COLORS = [
   'black',
@@ -46,6 +64,11 @@ const ANSI_COLORS = [
 
 @customElement(RENDERERS.TerminalView)
 export class TerminalView extends LitElement {
+
+  protected copyText = 'Copy'
+  protected shareText = 'Share'
+
+
   static styles = css`
     .xterm {
       cursor: text;
@@ -292,6 +315,15 @@ export class TerminalView extends LitElement {
   @property({ type: Number })
   lastLine?: number // TODO: Get the last line of the terminal and store it.
 
+  @property()
+  isCloudApiLoading: boolean = false
+
+  @property()
+  shareUrl?: string
+
+  @property({ type: Boolean })
+  enableShareButton: boolean = false
+
   constructor() {
     super()
     this.windowSize = {
@@ -318,7 +350,7 @@ export class TerminalView extends LitElement {
       convertEol: true,
       allowProposedApi: true,
       fontFamily: this.terminalFontFamily,
-      drawBoldTextInBrightColors: false,
+      drawBoldTextInBrightColors: false
     })
 
     if (this.initialContent) {
@@ -340,7 +372,7 @@ export class TerminalView extends LitElement {
     window.addEventListener('resize', this.#onResizeWindow.bind(this))
 
     this.disposables.push(
-      onClientMessage(ctx, (e) => {
+      onClientMessage(ctx, async (e) => {
         if (!LISTEN_TO_EVENTS.some(event => e.type.startsWith(event))) { return }
 
         switch (e.type) {
@@ -355,6 +387,37 @@ export class TerminalView extends LitElement {
               this.terminal!.write(data)
             }
           } break
+          case ClientMessages.cloudApiResponse: {
+            if (e.output.uuid !== this.uuid) { return }
+            this.isCloudApiLoading = false
+            if (e.output.hasErrors) {
+              return postClientMessage(ctx, ClientMessages.errorMessage, e.output.data)
+            }
+            const { data: { createCellExecution: { htmlUrl } } } = e.output.data
+            this.shareUrl = htmlUrl
+            await this.#displayShareDialog()
+          } break
+
+          case ClientMessages.onOptionsMessage: {
+            if (e.output.uuid !== this.uuid) { return }
+            const answer = e.output.option
+            this.isCloudApiLoading = false
+            switch (answer) {
+              case MessageOptions.OpenLink: {
+                return postClientMessage(ctx, ClientMessages.openExternalLink, this.shareUrl!)
+              }
+              case MessageOptions.CopyToClipboard: {
+                return postClientMessage(ctx, ClientMessages.copyTextToClipboard, {
+                  uuid: this.uuid!,
+                  text: this.shareUrl!
+                })
+              }
+            }
+          } break
+          case ClientMessages.onCopyTextToClipboard: {
+            if (e.output.uuid !== this.uuid) { return }
+            return postClientMessage(ctx, ClientMessages.infoMessage, 'Link copied!')
+          }
         }
       }),
       this.terminal.onData((data) => postClientMessage(ctx, ClientMessages.terminalStdin, {
@@ -518,6 +581,37 @@ export class TerminalView extends LitElement {
     })
   }
 
+  async #displayShareDialog(): Promise<boolean | void> {
+    const ctx = getContext()
+    if (!ctx.postMessage || !this.shareUrl) { return }
+    return postClientMessage(ctx, ClientMessages.optionsMessage, {
+      title: 'Share link created',
+      options: Object.values(MessageOptions),
+      uuid: this.uuid!
+    })
+  }
+
+  async #shareCellOutput(): Promise<boolean | void | undefined> {
+    const ctx = getContext()
+    if (!ctx.postMessage) { return }
+    try {
+      this.isCloudApiLoading = true
+      const contentWithAnsi = this.serializer?.serialize({ excludeModes: true, excludeAltBuffer: true }) ?? ''
+      const terminalContents = new TextEncoder().encode(contentWithAnsi)
+      await postClientMessage(ctx, ClientMessages.cloudApiRequest, {
+        data: {
+          stdout: JSON.stringify([...terminalContents], null, 2)
+        },
+        uuid: this.uuid!,
+        method: APIMethod.CreateCellExecution
+      })
+    } catch (error) {
+      this.isCloudApiLoading = false
+      postClientMessage(ctx, ClientMessages.infoMessage, `Failed to share output: ${(error as any).message}`)
+    }
+
+  }
+
   #onWebLinkClick(event: MouseEvent, uri: string): void {
     postClientMessage(getContext(), ClientMessages.openLink, uri)
   }
@@ -533,18 +627,16 @@ export class TerminalView extends LitElement {
         })
       }}"></close-cell-button>
       <div class="button-group">
-        <vscode-button appearance="secondary" @click="${this.#copy.bind(this)}">
-          <svg
-            class="icon" width="16" height="16" viewBox="0 0 16 16"
-            xmlns="http://www.w3.org/2000/svg" fill="currentColor"
-          >
-            <path fill-rule="evenodd" clip-rule="evenodd"
-              d="M4 4l1-1h5.414L14 6.586V14l-1 1H5l-1-1V4zm9 3l-3-3H5v10h8V7z"/>
-            <path fill-rule="evenodd" clip-rule="evenodd"
-              d="M3 1L2 2v10l1 1V2h6.414l-1-1H3z"/>
-          </svg>
-          Copy
-        </vscode-button>
+      <copy-button copyText="${this.copyText}" @onCopy="${async () => {
+        return this.#copy()
+      }}"></copy-button>
+      ${when(this.enableShareButton, () =>
+        html`
+        <share-cell 
+          ?disabled=${this.isCloudApiLoading}
+          shareText="${this.isCloudApiLoading ? 'Generating link ...' : this.shareText}"
+          @onShare="${this.#shareCellOutput}">
+        </share-cell>`, () => html``)}
       </div>
     </section>`
   }
@@ -556,11 +648,12 @@ export class TerminalView extends LitElement {
   #copy() {
     const ctx = getContext()
     if (!ctx.postMessage) { return }
-
     const content = stripANSI(this.serializer?.serialize({ excludeModes: true, excludeAltBuffer: true }) ?? '')
-
-    return navigator.clipboard.writeText(content).catch(
-      (err) => postClientMessage(ctx, ClientMessages.infoMessage, `'Failed to copy to clipboard: ${err.message}!'`),
+    return navigator.clipboard.writeText(content).then(() => {
+      this.copyText = 'Copied!'
+      this.requestUpdate()
+    }).catch(
+      (err) => postClientMessage(ctx, ClientMessages.infoMessage, `Failed to copy to clipboard: ${err.message}!`),
     )
   }
 }
diff --git a/src/client/components/terminal/share.ts b/src/client/components/terminal/share.ts
new file mode 100644
index 00000000..68da3144
--- /dev/null
+++ b/src/client/components/terminal/share.ts
@@ -0,0 +1,58 @@
+import { LitElement, css, html } from 'lit'
+import { customElement, property } from 'lit/decorators.js'
+import { when } from 'lit/directives/when.js'
+
+import { ShareIcon } from '../icons/share'
+
+@customElement('share-cell')
+export class ShareCell extends LitElement {
+
+  @property({ type: String })
+  shareText: string = 'Copy'
+
+  @property({ type: Boolean, reflect: true })
+  disabled: boolean = false
+
+  /* eslint-disable */
+  static styles = css`
+    vscode-button {
+        background: transparent;
+        color: #ccc;
+        transform: scale(.9);
+      }
+      vscode-button:hover {
+        background: var(--button-secondary-background);
+      }
+      vscode-button:focus {
+        outline: #007fd4 1px solid;
+      }
+      .icon {
+        width: 13px;
+        margin: 0 5px 0 -5px;
+        padding: 0;
+      }
+    `
+
+  private onShareClick(e: Event) {
+    if (e.defaultPrevented) {
+      e.preventDefault()
+    }
+    const event = new CustomEvent('onShare')
+    this.dispatchEvent(event)
+  }
+
+  render() {
+    return when(this.disabled, () => html`
+    <vscode-button disabled appearance="secondary" @click=${this.onShareClick}>
+      ${ShareIcon}
+      ${this.shareText}
+    </vscode-button>
+    `, () => html`
+    <vscode-button appearance="secondary" @click=${this.onShareClick}>
+      ${ShareIcon}
+      ${this.shareText}
+    </vscode-button>
+    `)
+
+  }
+}
\ No newline at end of file
diff --git a/src/client/index.ts b/src/client/index.ts
index 5c93f8b7..13776038 100644
--- a/src/client/index.ts
+++ b/src/client/index.ts
@@ -70,7 +70,9 @@ export const activate: ActivationFunction = (context: RendererContext<void>) =>
           terminalElement.setAttribute('uuid', payload.output['runme.dev/uuid'])
           terminalElement.setAttribute('terminalFontFamily', payload.output.terminalFontFamily)
           terminalElement.setAttribute('terminalFontSize', payload.output.terminalFontSize.toString())
-
+          if (payload.output.enableShareButton) {
+            terminalElement.setAttribute('enableShareButton', payload.output.enableShareButton.toString())
+          }
           if (payload.output.initialRows !== undefined) {
             terminalElement.setAttribute('initialRows', payload.output.initialRows.toString())
           }
diff --git a/src/client/utils.ts b/src/client/utils.ts
index b37aefcf..4482faa3 100644
--- a/src/client/utils.ts
+++ b/src/client/utils.ts
@@ -5,21 +5,21 @@ import { ClientMessages, OutputType } from '../constants'
 
 let context: RendererContext<void> | undefined
 
-export function getContext () {
+export function getContext() {
   if (!context) {
     throw new Error('Renderer context not defined')
   }
   return context
 }
 
-export function setContext (c: RendererContext<void>) {
+export function setContext(c: RendererContext<void>) {
   context = c
 }
 
-export function closeOutput({ uuid, outputType}: { uuid: string, outputType: OutputType}) {
+export function closeOutput({ uuid, outputType }: { uuid: string, outputType: OutputType }) {
   const ctx = getContext()
   ctx.postMessage && postClientMessage(ctx, ClientMessages.closeCellOutput, {
-      uuid,
-      outputType
+    uuid,
+    outputType
   })
 }
\ No newline at end of file
diff --git a/src/constants.ts b/src/constants.ts
index 0d22227d..ca37daf6 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -24,6 +24,13 @@ export enum ClientMessages {
   setState = 'common:setState',
   getState = 'common:getState',
   onGetState = 'common:onGetState',
+  cloudApiRequest = 'common:cloudApiRequest',
+  cloudApiResponse = 'common:cloudApiResponse',
+  optionsMessage = 'common:optionsMessage',
+  openExternalLink = 'common:openExternalLink',
+  onOptionsMessage = 'common:onOptionsMessage',
+  copyTextToClipboard = 'common:copyTextToClipboard',
+  onCopyTextToClipboard = 'common:onCopyTextToClipboard',
   denoUpdate = 'deno:deploymentUpdate',
   denoPromote = 'deno:promoteDeployment',
   vercelProd = 'vercel:promotePreview',
@@ -99,12 +106,26 @@ export const LANGUAGES = new Map([
   ['YAML', 'yaml', undefined],
 ].map(([, source, dest]) => [source, dest]))
 
+/**
+ * Map from vscode language id to how it should be represented in markdown
+ *
+ * For example, the vscode language id for all bash-like shells is
+ * "shellscript," so this object maps "shellscript" -> "sh"
+ */
+export const VSCODE_LANGUAGEID_MAP: Record<string, string | undefined> = {
+  'shellscript': 'sh',
+  'javascriptreact': 'jsx',
+  'typescriptreact': 'tsx',
+}
+
+export const DEFAULT_LANGUAGEID = 'shellscript'
+
 export const SERVER_ADDRESS = 'localhost'
 export const SERVER_PORT = 7863
 
 export const DEFAULT_PROMPT_ENV = true
 
-export enum RENDERERS  {
+export enum RENDERERS {
   VercelOutput = 'vercel-output',
   DenoOutput = 'deno-output',
   ShellOutput = 'shell-output',
@@ -119,3 +140,910 @@ export enum AuthenticationProviders {
 }
 export const NOTEBOOK_AVAILABLE_CATEGORIES = 'notebookAvailableCategories'
 export const NOTEBOOK_HAS_CATEGORIES = 'notebookHasCategories'
+
+/**
+ * https://gist.github.com/ppisarczyk/43962d06686722d26d176fad46879d41?permalink_comment_id=3949999#gistcomment-3949999
+ */
+export const LANGUAGE_PREFIXES = {
+  abap: 'ABAP',
+  asc: 'Public Key',
+  ash: 'AGS Script',
+  ampl: 'AMPL',
+  mod: 'XML',
+  g4: 'ANTLR',
+  apib: 'API Blueprint',
+  apl: 'APL',
+  dyalog: 'APL',
+  asp: 'ASP',
+  asax: 'ASP',
+  ascx: 'ASP',
+  ashx: 'ASP',
+  asmx: 'ASP',
+  aspx: 'ASP',
+  axd: 'ASP',
+  dats: 'ATS',
+  hats: 'ATS',
+  sats: 'ATS',
+  as: 'ActionScript',
+  adb: 'Ada',
+  ada: 'Ada',
+  ads: 'Ada',
+  agda: 'Agda',
+  als: 'Alloy',
+  apacheconf: 'ApacheConf',
+  vhost: 'Nginx',
+  cls: 'Visual Basic',
+  applescript: 'AppleScript',
+  scpt: 'AppleScript',
+  arc: 'Arc',
+  ino: 'Arduino',
+  asciidoc: 'AsciiDoc',
+  adoc: 'AsciiDoc',
+  aj: 'AspectJ',
+  asm: 'Assembly',
+  a51: 'Assembly',
+  inc: 'SourcePawn',
+  nasm: 'Assembly',
+  aug: 'Augeas',
+  ahk: 'AutoHotkey',
+  ahkl: 'AutoHotkey',
+  au3: 'AutoIt',
+  awk: 'Awk',
+  auk: 'Awk',
+  gawk: 'Awk',
+  mawk: 'Awk',
+  nawk: 'Awk',
+  bat: 'Batchfile',
+  cmd: 'Batchfile',
+  befunge: 'Befunge',
+  bison: 'Bison',
+  bb: 'BlitzBasic',
+  decls: 'BlitzBasic',
+  bmx: 'BlitzMax',
+  bsv: 'Bluespec',
+  boo: 'Boo',
+  b: 'Limbo',
+  bf: 'HyPhy',
+  brs: 'Brightscript',
+  bro: 'Bro',
+  c: 'C',
+  cats: 'C',
+  h: 'Objective-C',
+  idc: 'C',
+  w: 'C',
+  cs: 'Smalltalk',
+  cake: 'CoffeeScript',
+  cshtml: 'C#',
+  csx: 'C#',
+  cpp: 'C++',
+  'c++': 'C++',
+  cc: 'C++',
+  cp: 'Component Pascal',
+  cxx: 'C++',
+  'h++': 'C++',
+  hh: 'Hack',
+  hpp: 'C++',
+  hxx: 'C++',
+  inl: 'C++',
+  ipp: 'C++',
+  tcc: 'C++',
+  tpp: 'C++',
+  'c-objdump': 'C-ObjDump',
+  chs: 'C2hs Haskell',
+  clp: 'CLIPS',
+  cmake: 'CMake',
+  'cmake.in': 'CMake',
+  cob: 'COBOL',
+  cbl: 'COBOL',
+  ccp: 'COBOL',
+  cobol: 'COBOL',
+  cpy: 'COBOL',
+  css: 'CSS',
+  csv: 'CSV',
+  capnp: 'Cap\'n Proto',
+  mss: 'CartoCSS',
+  ceylon: 'Ceylon',
+  chpl: 'Chapel',
+  ch: 'xBase',
+  ck: 'ChucK',
+  cirru: 'Cirru',
+  clw: 'Clarion',
+  icl: 'Clean',
+  dcl: 'Clean',
+  click: 'Click',
+  clj: 'Clojure',
+  boot: 'Clojure',
+  cl2: 'Clojure',
+  cljc: 'Clojure',
+  cljs: 'Clojure',
+  'cljs.hl': 'Clojure',
+  cljscm: 'Clojure',
+  cljx: 'Clojure',
+  hic: 'Clojure',
+  coffee: 'CoffeeScript',
+  _coffee: 'CoffeeScript',
+  cjsx: 'CoffeeScript',
+  cson: 'CoffeeScript',
+  iced: 'CoffeeScript',
+  cfm: 'ColdFusion',
+  cfml: 'ColdFusion',
+  cfc: 'ColdFusion CFC',
+  lisp: 'NewLisp',
+  asd: 'Common Lisp',
+  cl: 'OpenCL',
+  l: 'PicoLisp',
+  lsp: 'NewLisp',
+  ny: 'Common Lisp',
+  podsl: 'Common Lisp',
+  sexp: 'Common Lisp',
+  cps: 'Component Pascal',
+  coq: 'Coq',
+  v: 'Verilog',
+  cppobjdump: 'Cpp-ObjDump',
+  'c++-objdump': 'Cpp-ObjDump',
+  'c++objdump': 'Cpp-ObjDump',
+  'cpp-objdump': 'Cpp-ObjDump',
+  'cxx-objdump': 'Cpp-ObjDump',
+  creole: 'Creole',
+  cr: 'Crystal',
+  feature: 'Cucumber',
+  cu: 'Cuda',
+  cuh: 'Cuda',
+  cy: 'Cycript',
+  pyx: 'Cython',
+  pxd: 'Cython',
+  pxi: 'Cython',
+  d: 'Makefile',
+  di: 'D',
+  'd-objdump': 'D-ObjDump',
+  com: 'DIGITAL Command Language',
+  dm: 'DM',
+  zone: 'DNS Zone',
+  arpa: 'DNS Zone',
+  darcspatch: 'Darcs Patch',
+  dpatch: 'Darcs Patch',
+  dart: 'Dart',
+  diff: 'Diff',
+  patch: 'Diff',
+  dockerfile: 'Dockerfile',
+  djs: 'Dogescript',
+  dylan: 'Dylan',
+  dyl: 'Dylan',
+  intr: 'Dylan',
+  lid: 'Dylan',
+  E: 'E',
+  ecl: 'ECLiPSe',
+  eclxml: 'ECL',
+  sch: 'KiCad',
+  brd: 'KiCad',
+  epj: 'Ecere Projects',
+  e: 'Eiffel',
+  ex: 'Elixir',
+  exs: 'Elixir',
+  elm: 'Elm',
+  el: 'Emacs Lisp',
+  emacs: 'Emacs Lisp',
+  'emacs.desktop': 'Emacs Lisp',
+  em: 'EmberScript',
+  emberscript: 'EmberScript',
+  erl: 'Erlang',
+  es: 'JavaScript',
+  escript: 'Erlang',
+  hrl: 'Erlang',
+  xrl: 'Erlang',
+  yrl: 'Erlang',
+  fs: 'GLSL',
+  fsi: 'F#',
+  fsx: 'F#',
+  fx: 'HLSL',
+  flux: 'FLUX',
+  f90: 'FORTRAN',
+  f: 'Forth',
+  f03: 'FORTRAN',
+  f08: 'FORTRAN',
+  f77: 'FORTRAN',
+  f95: 'FORTRAN',
+  for: 'Forth',
+  fpp: 'FORTRAN',
+  factor: 'Factor',
+  fy: 'Fancy',
+  fancypack: 'Fancy',
+  fan: 'Fantom',
+  'eam.fs': 'Formatted',
+  fth: 'Forth',
+  '4th': 'Forth',
+  forth: 'Forth',
+  fr: 'Text',
+  frt: 'Forth',
+  ftl: 'FreeMarker',
+  g: 'GAP',
+  gco: 'G-code',
+  gcode: 'G-code',
+  gms: 'GAMS',
+  gap: 'GAP',
+  gd: 'GDScript',
+  gi: 'GAP',
+  tst: 'Scilab',
+  s: 'GAS',
+  ms: 'MAXScript',
+  glsl: 'GLSL',
+  fp: 'GLSL',
+  frag: 'JavaScript',
+  frg: 'GLSL',
+  fsh: 'GLSL',
+  fshader: 'GLSL',
+  geo: 'GLSL',
+  geom: 'GLSL',
+  glslv: 'GLSL',
+  gshader: 'GLSL',
+  shader: 'GLSL',
+  vert: 'GLSL',
+  vrx: 'GLSL',
+  vsh: 'GLSL',
+  vshader: 'GLSL',
+  gml: 'XML',
+  kid: 'Genshi',
+  ebuild: 'Gentoo Ebuild',
+  eclass: 'Gentoo Eclass',
+  po: 'Gettext Catalog',
+  pot: 'Gettext Catalog',
+  glf: 'Glyph',
+  gp: 'Gnuplot',
+  gnu: 'Gnuplot',
+  gnuplot: 'Gnuplot',
+  plot: 'Gnuplot',
+  plt: 'Gnuplot',
+  go: 'Go',
+  golo: 'Golo',
+  gs: 'JavaScript',
+  gst: 'Gosu',
+  gsx: 'Gosu',
+  vark: 'Gosu',
+  grace: 'Grace',
+  gradle: 'Gradle',
+  gf: 'Grammatical Framework',
+  graphql: 'GraphQL',
+  dot: 'Graphviz (DOT)',
+  gv: 'Graphviz (DOT)',
+  man: 'Groff',
+  '1in': 'Groff',
+  '1m': 'Groff',
+  '1x': 'Groff',
+  '3in': 'Groff',
+  '3m': 'Groff',
+  '3qt': 'Groff',
+  '3x': 'Groff',
+  me: 'Groff',
+  n: 'Nemerle',
+  rno: 'Groff',
+  roff: 'Groff',
+  groovy: 'Groovy',
+  grt: 'Groovy',
+  gtpl: 'Groovy',
+  gvy: 'Groovy',
+  gsp: 'Groovy Server Pages',
+  hcl: 'HCL',
+  tf: 'HCL',
+  hlsl: 'HLSL',
+  fxh: 'HLSL',
+  hlsli: 'HLSL',
+  html: 'HTML',
+  htm: 'HTML',
+  'html.hl': 'HTML',
+  st: 'Smalltalk',
+  xht: 'HTML',
+  xhtml: 'HTML',
+  mustache: 'HTML+Django',
+  jinja: 'HTML+Django',
+  eex: 'HTML+EEX',
+  erb: 'HTML+ERB',
+  'erb.deface': 'HTML+ERB',
+  phtml: 'HTML+PHP',
+  http: 'HTTP',
+  php: 'PHP',
+  haml: 'Haml',
+  'haml.deface': 'Haml',
+  handlebars: 'Handlebars',
+  hbs: 'Handlebars',
+  hb: 'Harbour',
+  hs: 'Haskell',
+  hsc: 'Haskell',
+  hx: 'Haxe',
+  hxsl: 'Haxe',
+  hy: 'Hy',
+  pro: 'QMake',
+  dlm: 'IDL',
+  ipf: 'IGOR Pro',
+  ini: 'INI',
+  cfg: 'INI',
+  prefs: 'INI',
+  properties: 'INI',
+  irclog: 'IRC log',
+  weechatlog: 'IRC log',
+  idr: 'Idris',
+  lidr: 'Idris',
+  ni: 'Inform 7',
+  i7x: 'Inform 7',
+  iss: 'Inno Setup',
+  io: 'Io',
+  ik: 'Ioke',
+  thy: 'Isabelle',
+  ijs: 'J',
+  flex: 'JFlex',
+  jflex: 'JFlex',
+  json: 'JSON',
+  geojson: 'JSON',
+  lock: 'JSON',
+  topojson: 'JSON',
+  json5: 'JSON5',
+  jsonld: 'JSONLD',
+  jq: 'JSONiq',
+  jsx: 'JSX',
+  jade: 'Jade',
+  j: 'Objective-J',
+  java: 'Java',
+  jsp: 'Java Server Pages',
+  js: 'JavaScript',
+  _js: 'JavaScript',
+  bones: 'JavaScript',
+  es6: 'JavaScript',
+  jake: 'JavaScript',
+  jsb: 'JavaScript',
+  jscad: 'JavaScript',
+  jsfl: 'JavaScript',
+  jsm: 'JavaScript',
+  jss: 'JavaScript',
+  njs: 'JavaScript',
+  pac: 'JavaScript',
+  sjs: 'JavaScript',
+  ssjs: 'JavaScript',
+  'sublime-build': 'JavaScript',
+  'sublime-commands': 'JavaScript',
+  'sublime-completions': 'JavaScript',
+  'sublime-keymap': 'JavaScript',
+  'sublime-macro': 'JavaScript',
+  'sublime-menu': 'JavaScript',
+  'sublime-mousemap': 'JavaScript',
+  'sublime-project': 'JavaScript',
+  'sublime-settings': 'JavaScript',
+  'sublime-theme': 'JavaScript',
+  'sublime-workspace': 'JavaScript',
+  sublime_metrics: 'JavaScript',
+  sublime_session: 'JavaScript',
+  xsjs: 'JavaScript',
+  xsjslib: 'JavaScript',
+  jl: 'Julia',
+  ipynb: 'Jupyter Notebook',
+  krl: 'KRL',
+  kicad_pcb: 'KiCad',
+  kit: 'Kit',
+  kt: 'Kotlin',
+  ktm: 'Kotlin',
+  kts: 'Kotlin',
+  lfe: 'LFE',
+  ll: 'LLVM',
+  lol: 'LOLCODE',
+  lsl: 'LSL',
+  lslp: 'LSL',
+  lvproj: 'LabVIEW',
+  lasso: 'Lasso',
+  las: 'Lasso',
+  lasso8: 'Lasso',
+  lasso9: 'Lasso',
+  ldml: 'Lasso',
+  latte: 'Latte',
+  lean: 'Lean',
+  hlean: 'Lean',
+  less: 'Less',
+  lex: 'Lex',
+  ly: 'LilyPond',
+  ily: 'LilyPond',
+  m: 'Objective-C',
+  ld: 'Linker Script',
+  lds: 'Linker Script',
+  liquid: 'Liquid',
+  lagda: 'Literate Agda',
+  litcoffee: 'Literate CoffeeScript',
+  lhs: 'Literate Haskell',
+  ls: 'LoomScript',
+  _ls: 'LiveScript',
+  xm: 'Logos',
+  x: 'Logos',
+  xi: 'Logos',
+  lgt: 'Logtalk',
+  logtalk: 'Logtalk',
+  lookml: 'LookML',
+  lua: 'Lua',
+  fcgi: 'Shell',
+  nse: 'Lua',
+  pd_lua: 'Lua',
+  rbxs: 'Lua',
+  wlua: 'Lua',
+  mumps: 'M',
+  m4: 'M4Sugar',
+  mcr: 'MAXScript',
+  mtml: 'MTML',
+  muf: 'MUF',
+  mak: 'Makefile',
+  mk: 'Makefile',
+  mkfile: 'Makefile',
+  mako: 'Mako',
+  mao: 'Mako',
+  md: 'Markdown',
+  markdown: 'Markdown',
+  mkd: 'Markdown',
+  mkdn: 'Markdown',
+  mkdown: 'Markdown',
+  ron: 'Markdown',
+  mask: 'Mask',
+  mathematica: 'Mathematica',
+  cdf: 'Mathematica',
+  ma: 'Mathematica',
+  mt: 'Mathematica',
+  nb: 'Text',
+  nbp: 'Mathematica',
+  wl: 'Mathematica',
+  wlt: 'Mathematica',
+  matlab: 'Matlab',
+  maxpat: 'Max',
+  maxhelp: 'Max',
+  maxproj: 'Max',
+  mxt: 'Max',
+  pat: 'Max',
+  mediawiki: 'MediaWiki',
+  wiki: 'MediaWiki',
+  moo: 'Moocode',
+  metal: 'Metal',
+  minid: 'MiniD',
+  druby: 'Mirah',
+  duby: 'Mirah',
+  mir: 'Mirah',
+  mirah: 'Mirah',
+  mo: 'Modelica',
+  mms: 'Module Management System',
+  mmk: 'Module Management System',
+  monkey: 'Monkey',
+  moon: 'MoonScript',
+  myt: 'Myghty',
+  ncl: 'Text',
+  nl: 'NewLisp',
+  nsi: 'NSIS',
+  nsh: 'NSIS',
+  axs: 'NetLinx',
+  axi: 'NetLinx',
+  'axs.erb': 'NetLinx+ERB',
+  'axi.erb': 'NetLinx+ERB',
+  nlogo: 'NetLogo',
+  nginxconf: 'Nginx',
+  nim: 'Nimrod',
+  nimrod: 'Nimrod',
+  ninja: 'Ninja',
+  nit: 'Nit',
+  nix: 'Nix',
+  nu: 'Nu',
+  numpy: 'NumPy',
+  numpyw: 'NumPy',
+  numsc: 'NumPy',
+  ml: 'OCaml',
+  eliom: 'OCaml',
+  eliomi: 'OCaml',
+  ml4: 'OCaml',
+  mli: 'OCaml',
+  mll: 'OCaml',
+  mly: 'OCaml',
+  objdump: 'ObjDump',
+  mm: 'XML',
+  sj: 'Objective-J',
+  omgrofl: 'Omgrofl',
+  opa: 'Opa',
+  opal: 'Opal',
+  opencl: 'OpenCL',
+  p: 'OpenEdge ABL',
+  scad: 'OpenSCAD',
+  org: 'Org',
+  ox: 'Ox',
+  oxh: 'Ox',
+  oxo: 'Ox',
+  oxygene: 'Oxygene',
+  oz: 'Oz',
+  pwn: 'PAWN',
+  aw: 'PHP',
+  ctp: 'PHP',
+  php3: 'PHP',
+  php4: 'PHP',
+  php5: 'PHP',
+  phps: 'PHP',
+  phpt: 'PHP',
+  pls: 'PLSQL',
+  pck: 'PLSQL',
+  pkb: 'PLSQL',
+  pks: 'PLSQL',
+  plb: 'PLSQL',
+  plsql: 'PLSQL',
+  sql: 'SQLPL',
+  pov: 'POV-Ray SDL',
+  pan: 'Pan',
+  psc: 'Papyrus',
+  parrot: 'Parrot',
+  pasm: 'Parrot Assembly',
+  pir: 'Parrot Internal Representation',
+  pas: 'Pascal',
+  dfm: 'Pascal',
+  dpr: 'Pascal',
+  lpr: 'Pascal',
+  pp: 'Puppet',
+  pl: 'Prolog',
+  al: 'Perl',
+  cgi: 'Shell',
+  perl: 'Perl',
+  ph: 'Perl',
+  plx: 'Perl',
+  pm: 'Perl6',
+  pod: 'Pod',
+  psgi: 'Perl',
+  t: 'Turing',
+  '6pl': 'Perl6',
+  '6pm': 'Perl6',
+  nqp: 'Perl6',
+  p6: 'Perl6',
+  p6l: 'Perl6',
+  p6m: 'Perl6',
+  pl6: 'Perl6',
+  pm6: 'Perl6',
+  pkl: 'Pickle',
+  pig: 'PigLatin',
+  pike: 'Pike',
+  pmod: 'Pike',
+  pogo: 'PogoScript',
+  pony: 'Pony',
+  ps: 'PostScript',
+  eps: 'PostScript',
+  ps1: 'PowerShell',
+  psd1: 'PowerShell',
+  psm1: 'PowerShell',
+  pde: 'Processing',
+  prolog: 'Prolog',
+  yap: 'Prolog',
+  spin: 'Propeller Spin',
+  proto: 'Protocol Buffer',
+  pub: 'Public Key',
+  pd: 'Pure Data',
+  pb: 'PureBasic',
+  pbi: 'PureBasic',
+  purs: 'PureScript',
+  py: 'Python',
+  bzl: 'Python',
+  gyp: 'Python',
+  lmi: 'Python',
+  pyde: 'Python',
+  pyp: 'Python',
+  pyt: 'Python',
+  pyw: 'Python',
+  rpy: 'Ren\'Py',
+  tac: 'Python',
+  wsgi: 'Python',
+  xpy: 'Python',
+  pytb: 'Python traceback',
+  qml: 'QML',
+  qbs: 'QML',
+  pri: 'QMake',
+  r: 'Rebol',
+  rd: 'R',
+  rsx: 'R',
+  raml: 'RAML',
+  rdoc: 'RDoc',
+  rbbas: 'REALbasic',
+  rbfrm: 'REALbasic',
+  rbmnu: 'REALbasic',
+  rbres: 'REALbasic',
+  rbtbar: 'REALbasic',
+  rbuistate: 'REALbasic',
+  rhtml: 'RHTML',
+  rmd: 'RMarkdown',
+  rkt: 'Racket',
+  rktd: 'Racket',
+  rktl: 'Racket',
+  scrbl: 'Racket',
+  rl: 'Ragel in Ruby Host',
+  raw: 'Raw token data',
+  reb: 'Rebol',
+  r2: 'Rebol',
+  r3: 'Rebol',
+  rebol: 'Rebol',
+  red: 'Red',
+  reds: 'Red',
+  cw: 'Redcode',
+  rs: 'Rust',
+  rsh: 'RenderScript',
+  robot: 'RobotFramework',
+  rg: 'Rouge',
+  rb: 'Ruby',
+  builder: 'Ruby',
+  gemspec: 'Ruby',
+  god: 'Ruby',
+  irbrc: 'Ruby',
+  jbuilder: 'Ruby',
+  mspec: 'Ruby',
+  pluginspec: 'XML',
+  podspec: 'Ruby',
+  rabl: 'Ruby',
+  rake: 'Ruby',
+  rbuild: 'Ruby',
+  rbw: 'Ruby',
+  rbx: 'Ruby',
+  ru: 'Ruby',
+  ruby: 'Ruby',
+  thor: 'Ruby',
+  watchr: 'Ruby',
+  'rs.in': 'Rust',
+  sas: 'SAS',
+  scss: 'SCSS',
+  smt2: 'SMT',
+  smt: 'SMT',
+  sparql: 'SPARQL',
+  rq: 'SPARQL',
+  sqf: 'SQF',
+  hqf: 'SQF',
+  cql: 'SQL',
+  ddl: 'SQL',
+  prc: 'SQL',
+  tab: 'SQL',
+  udf: 'SQL',
+  viw: 'SQL',
+  db2: 'SQLPL',
+  ston: 'STON',
+  svg: 'SVG',
+  sage: 'Sage',
+  sagews: 'Sage',
+  sls: 'Scheme',
+  sass: 'Sass',
+  scala: 'Scala',
+  sbt: 'Scala',
+  sc: 'SuperCollider',
+  scaml: 'Scaml',
+  scm: 'Scheme',
+  sld: 'Scheme',
+  sps: 'Scheme',
+  ss: 'Scheme',
+  sci: 'Scilab',
+  sce: 'Scilab',
+  self: 'Self',
+  sh: 'Shell',
+  bash: 'Shell',
+  bats: 'Shell',
+  command: 'Shell',
+  ksh: 'Shell',
+  'sh.in': 'Shell',
+  tmux: 'Shell',
+  tool: 'Shell',
+  zsh: 'Shell',
+  'sh-session': 'ShellSession',
+  shen: 'Shen',
+  sl: 'Slash',
+  slim: 'Slim',
+  smali: 'Smali',
+  tpl: 'Smarty',
+  sp: 'SourcePawn',
+  sma: 'SourcePawn',
+  nut: 'Squirrel',
+  stan: 'Stan',
+  ML: 'Standard ML',
+  fun: 'Standard ML',
+  sig: 'Standard ML',
+  sml: 'Standard ML',
+  do: 'Stata',
+  ado: 'Stata',
+  doh: 'Stata',
+  ihlp: 'Stata',
+  mata: 'Stata',
+  matah: 'Stata',
+  sthlp: 'Stata',
+  styl: 'Stylus',
+  scd: 'SuperCollider',
+  swift: 'Swift',
+  sv: 'SystemVerilog',
+  svh: 'SystemVerilog',
+  vh: 'SystemVerilog',
+  toml: 'TOML',
+  txl: 'TXL',
+  tcl: 'Tcl',
+  adp: 'Tcl',
+  tm: 'Tcl',
+  tcsh: 'Tcsh',
+  csh: 'Tcsh',
+  tex: 'TeX',
+  aux: 'TeX',
+  bbx: 'TeX',
+  bib: 'TeX',
+  cbx: 'TeX',
+  dtx: 'TeX',
+  ins: 'TeX',
+  lbx: 'TeX',
+  ltx: 'TeX',
+  mkii: 'TeX',
+  mkiv: 'TeX',
+  mkvi: 'TeX',
+  sty: 'TeX',
+  toc: 'TeX',
+  tea: 'Tea',
+  txt: 'Text',
+  no: 'Text',
+  textile: 'Textile',
+  thrift: 'Thrift',
+  tu: 'Turing',
+  ttl: 'Turtle',
+  twig: 'Twig',
+  ts: 'typescript',
+  tsx: 'typescriptreact',
+  upc: 'Unified Parallel C',
+  anim: 'Unity3D Asset',
+  asset: 'Unity3D Asset',
+  mat: 'Unity3D Asset',
+  meta: 'Unity3D Asset',
+  prefab: 'Unity3D Asset',
+  unity: 'Unity3D Asset',
+  uno: 'Uno',
+  uc: 'UnrealScript',
+  ur: 'UrWeb',
+  urs: 'UrWeb',
+  vcl: 'VCL',
+  vhdl: 'VHDL',
+  vhd: 'VHDL',
+  vhf: 'VHDL',
+  vhi: 'VHDL',
+  vho: 'VHDL',
+  vhs: 'VHDL',
+  vht: 'VHDL',
+  vhw: 'VHDL',
+  vala: 'Vala',
+  vapi: 'Vala',
+  veo: 'Verilog',
+  vim: 'VimL',
+  vb: 'Visual Basic',
+  bas: 'Visual Basic',
+  frm: 'Visual Basic',
+  frx: 'Visual Basic',
+  vba: 'Visual Basic',
+  vbhtml: 'Visual Basic',
+  vbs: 'Visual Basic',
+  volt: 'Volt',
+  vue: 'Vue',
+  owl: 'Web Ontology Language',
+  webidl: 'WebIDL',
+  x10: 'X10',
+  xc: 'XC',
+  xml: 'XML',
+  ant: 'XML',
+  axml: 'XML',
+  ccxml: 'XML',
+  clixml: 'XML',
+  cproject: 'XML',
+  csl: 'XML',
+  csproj: 'XML',
+  ct: 'XML',
+  dita: 'XML',
+  ditamap: 'XML',
+  ditaval: 'XML',
+  'dll.config': 'XML',
+  dotsettings: 'XML',
+  filters: 'XML',
+  fsproj: 'XML',
+  fxml: 'XML',
+  glade: 'XML',
+  grxml: 'XML',
+  iml: 'XML',
+  ivy: 'XML',
+  jelly: 'XML',
+  jsproj: 'XML',
+  kml: 'XML',
+  launch: 'XML',
+  mdpolicy: 'XML',
+  mxml: 'XML',
+  nproj: 'XML',
+  nuspec: 'XML',
+  odd: 'XML',
+  osm: 'XML',
+  plist: 'XML',
+  props: 'XML',
+  ps1xml: 'XML',
+  psc1: 'XML',
+  pt: 'XML',
+  rdf: 'XML',
+  rss: 'XML',
+  scxml: 'XML',
+  srdf: 'XML',
+  storyboard: 'XML',
+  stTheme: 'XML',
+  'sublime-snippet': 'XML',
+  targets: 'XML',
+  tmCommand: 'XML',
+  tml: 'XML',
+  tmLanguage: 'XML',
+  tmPreferences: 'XML',
+  tmSnippet: 'XML',
+  tmTheme: 'XML',
+  ui: 'XML',
+  urdf: 'XML',
+  ux: 'XML',
+  vbproj: 'XML',
+  vcxproj: 'XML',
+  vssettings: 'XML',
+  vxml: 'XML',
+  wsdl: 'XML',
+  wsf: 'XML',
+  wxi: 'XML',
+  wxl: 'XML',
+  wxs: 'XML',
+  x3d: 'XML',
+  xacro: 'XML',
+  xaml: 'XML',
+  xib: 'XML',
+  xlf: 'XML',
+  xliff: 'XML',
+  xmi: 'XML',
+  'xml.dist': 'XML',
+  xproj: 'XML',
+  xsd: 'XML',
+  xul: 'XML',
+  zcml: 'XML',
+  'xsp-config': 'XPages',
+  'xsp.metadata': 'XPages',
+  xpl: 'XProc',
+  xproc: 'XProc',
+  xquery: 'XQuery',
+  xq: 'XQuery',
+  xql: 'XQuery',
+  xqm: 'XQuery',
+  xqy: 'XQuery',
+  xs: 'XS',
+  xslt: 'XSLT',
+  xsl: 'XSLT',
+  xojo_code: 'Xojo',
+  xojo_menu: 'Xojo',
+  xojo_report: 'Xojo',
+  xojo_script: 'Xojo',
+  xojo_toolbar: 'Xojo',
+  xojo_window: 'Xojo',
+  xtend: 'Xtend',
+  yml: 'YAML',
+  reek: 'YAML',
+  rviz: 'YAML',
+  'sublime-syntax': 'YAML',
+  syntax: 'YAML',
+  yaml: 'YAML',
+  'yaml-tmlanguage': 'YAML',
+  yang: 'YANG',
+  y: 'Yacc',
+  yacc: 'Yacc',
+  yy: 'Yacc',
+  zep: 'Zephir',
+  zimpl: 'Zimpl',
+  zmpl: 'Zimpl',
+  zpl: 'Zimpl',
+  desktop: 'desktop',
+  'desktop.in': 'desktop',
+  ec: 'eC',
+  eh: 'eC',
+  edn: 'edn',
+  fish: 'fish',
+  mu: 'mupad',
+  nc: 'nesC',
+  ooc: 'ooc',
+  rst: 'reStructuredText',
+  rest: 'reStructuredText',
+  'rest.txt': 'reStructuredText',
+  'rst.txt': 'reStructuredText',
+  wisp: 'wisp',
+  prg: 'xBase',
+  prw: 'xBase'
+}
+
+export const SUPPORTED_FILE_EXTENSIONS = [
+  'sh', 'bash', 'ksh', 'zsh', 'fish', 'bat', 'cmd', 'pwsh',
+  ...Object.keys(LANGUAGE_PREFIXES),
+]
+
+export const EXTENSION_NAME = 'stateful.runme'
+export enum TELEMETRY_EVENTS {
+  RecommendExtension = 'runme.recommendExtension'
+}
\ No newline at end of file
diff --git a/src/extension/api/client.ts b/src/extension/api/client.ts
new file mode 100644
index 00000000..52e56c76
--- /dev/null
+++ b/src/extension/api/client.ts
@@ -0,0 +1,14 @@
+import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client'
+import fetch from 'cross-fetch'
+
+import { getRunmeApiUrl } from '../../utils/configuration'
+
+export function InitializeClient({ uri }: { uri: string } = { uri: '' }) {
+    const graphQlApiUrl = getRunmeApiUrl()
+    const client = new ApolloClient({
+        cache: new InMemoryCache(),
+        link: new HttpLink({ fetch, uri: uri || graphQlApiUrl })
+    })
+
+    return client
+}
\ No newline at end of file
diff --git a/src/extension/api/grapql.ts b/src/extension/api/grapql.ts
new file mode 100644
index 00000000..95e032c3
--- /dev/null
+++ b/src/extension/api/grapql.ts
@@ -0,0 +1,39 @@
+import { gql } from '@apollo/client'
+
+export interface CellExecutionMetadata {
+    mimeType: string
+    category: string
+    name: string
+}
+
+export interface CellExecutionRequest {
+    stdout: any
+    input: any
+    exitCode: number
+    pid: number
+    metadata: CellExecutionMetadata
+}
+
+export const createCellExecutionQuery = ({ input, exitCode, stdout, pid, metadata }: CellExecutionRequest) => {
+    return gql`mutation
+        {
+            createCellExecution(data: 
+                { 
+                    input: "${input}", 
+                    stdout: ${exitCode === 0 ? stdout : '[]'}, 
+                    stderr: ${exitCode !== 0 ? stdout : '[]'}, 
+                    pid: ${pid}, 
+                    exitCode: ${exitCode},
+                    metadata:
+                    { 
+                        mimeType: "${metadata.mimeType}",
+                        category: "${metadata.category || ''}",
+                        name: "${metadata.name}"
+                    }
+                })
+            {
+                id
+                htmlUrl
+            }
+        }`
+}
\ No newline at end of file
diff --git a/src/extension/cell.ts b/src/extension/cell.ts
index 96267aa4..55367867 100644
--- a/src/extension/cell.ts
+++ b/src/extension/cell.ts
@@ -20,7 +20,8 @@ import { Mutex } from '../utils/sync'
 import {
   getNotebookTerminalFontFamily,
   getNotebookTerminalFontSize,
-  getNotebookTerminalRows
+  getNotebookTerminalRows,
+  isRunmeApiEnabled
 } from '../utils/configuration'
 
 import { getAnnotations, replaceOutput, validateAnnotations } from './utils'
@@ -172,7 +173,8 @@ export class NotebookCellOutputManager {
               terminalFontFamily,
               terminalFontSize,
               content: terminalState.serialize(),
-              initialRows: getNotebookTerminalRows()
+              initialRows: getNotebookTerminalRows(),
+              enableShareButton: isRunmeApiEnabled()
             }
           }
 
@@ -266,7 +268,7 @@ export class NotebookCellOutputManager {
   private async newCellExecution(): Promise<NotebookCellExecution | undefined> {
     try {
       return this.controller.createNotebookCellExecution(this.cell)
-    } catch(e: any) {
+    } catch (e: any) {
       if (e.message.toString().includes('controller is NOT associated')) {
         await this.handleNotebookKernelSelection()
         return undefined
@@ -276,7 +278,7 @@ export class NotebookCellOutputManager {
     }
   }
 
-  async createNotebookCellExecution(): Promise<RunmeNotebookCellExecution|undefined> {
+  async createNotebookCellExecution(): Promise<RunmeNotebookCellExecution | undefined> {
     await this.onFinish
 
     return await this.withLock(async () => {
diff --git a/src/extension/commands/index.ts b/src/extension/commands/index.ts
index c2469222..ad568348 100644
--- a/src/extension/commands/index.ts
+++ b/src/extension/commands/index.ts
@@ -18,12 +18,22 @@ import {
 } from 'vscode'
 import { v4 as uuidv4 } from 'uuid'
 
-import { getTLSDir, getTLSEnabled, isNotebookTerminalEnabledForCell } from '../../utils/configuration'
+import {
+  getBinaryPath,
+  getCLIUseIntegratedRunme,
+  getTLSDir,
+  getTLSEnabled,
+  isNotebookTerminalEnabledForCell
+} from '../../utils/configuration'
 import { Kernel } from '../kernel'
 import { getAnnotations, getNotebookCategories, getTerminalByCell, openFileAsRunmeNotebook } from '../utils'
 import RunmeServer from '../server/runmeServer'
 import { GrpcRunnerEnvironment } from '../runner'
 import { NotebookToolbarCommand } from '../../types'
+import getLogger from '../logger'
+import { RecommendExtensionMessage } from '../messaging'
+
+const log = getLogger('Commands')
 
 function showWarningMessage() {
   return window.showWarningMessage('Couldn\'t find terminal! Was it already closed?')
@@ -116,12 +126,38 @@ export function runCLICommand(
   server: RunmeServer,
   kernel: Kernel
 ) {
-  return async function (cell: { metadata?: any, document: TextDocument }) {
+  return async function (cell: NotebookCell) {
+    if (cell.notebook.isDirty) {
+      const option = await window.showInformationMessage(
+        'You have unsaved changes. Save and run in CLI?',
+        'Save',
+        'Cancel'
+      )
+
+      if (option === 'Cancel' || !option) {
+        return
+      }
+
+      await cell.notebook.save()
+    }
+
     const cwd = path.dirname(cell.document.uri.fsPath)
 
+    const index = cell.notebook.getCells()
+      .filter(x => x.kind === NotebookCellKind.Code)
+      .indexOf(cell)
+
+    if (index < 0) {
+      window.showErrorMessage('Internal error identifying cell index')
+      log.error(`Failed getting code cell index for cell at index ${cell.index}`)
+
+      return
+    }
+
     const args = [
       `--chdir="${cwd}"`,
-      `--filename="${path.basename(cell.document.uri.fsPath)}"`
+      `--filename="${path.basename(cell.document.uri.fsPath)}"`,
+      `--index=${index}`
     ]
 
     const envs: Record<string, string> = {}
@@ -148,8 +184,10 @@ export function runCLICommand(
       env: envs,
     })
 
+    const runmeExec = getCLIUseIntegratedRunme() ? getBinaryPath(extensionBaseUri, process.platform).fsPath : 'runme'
+
     term.show(false)
-    term.sendText(`runme run ${annotations.name} ${args.join(' ')}`)
+    term.sendText(`${runmeExec} run ${args.join(' ')}`)
   }
 }
 
@@ -223,3 +261,9 @@ export async function authenticateWithGitHub() {
     window.showErrorMessage('Failed to authenticate with GitHub')
   }
 }
+
+export async function addToRecommendedExtensions(context: ExtensionContext) {
+  return new RecommendExtensionMessage(context, {
+    'runme.recommendExtension': true
+  }).display()
+}
\ No newline at end of file
diff --git a/src/extension/executors/runner.ts b/src/extension/executors/runner.ts
index 3f99fb08..52c46efa 100644
--- a/src/extension/executors/runner.ts
+++ b/src/extension/executors/runner.ts
@@ -53,7 +53,7 @@ export async function executeRunner(
   runningCell: TextDocument,
   messaging: NotebookRendererMessaging,
   cellUUID: string,
-  execKey: 'bash' | 'sh',
+  execKey: string,
   outputs: NotebookCellOutputManager,
   environment?: IRunnerEnvironment,
   environmentManager?: IEnvironmentManager
diff --git a/src/extension/executors/utils.ts b/src/extension/executors/utils.ts
index 4677db19..17437446 100644
--- a/src/extension/executors/utils.ts
+++ b/src/extension/executors/utils.ts
@@ -194,8 +194,8 @@ export async function retrieveShellCommand (exec: NotebookCellExecution, promptF
  * @param execKey Used as fallback in case `$SHELL` is not present
  */
 export function getSystemShellPath(): string|undefined
-export function getSystemShellPath(execKey: 'bash'|'sh'): string
-export function getSystemShellPath(execKey?: 'bash'|'sh'): string|undefined
+export function getSystemShellPath(execKey: string|undefined): string|undefined
+export function getSystemShellPath(execKey: string): string
 export function getSystemShellPath(execKey?: string): string|undefined {
   return process.env.SHELL ?? execKey
 }
@@ -203,7 +203,7 @@ export function getSystemShellPath(execKey?: string): string|undefined {
 export function getCellShellPath(
   cell: NotebookCell|NotebookCellData|Serializer.Cell,
   notebook: NotebookData|Serializer.Notebook|NotebookDocument,
-  execKey?: 'bash'|'sh'
+  execKey?: string
 ): string|undefined {
   const notebookMetadata = notebook.metadata as Serializer.Metadata|undefined
 
diff --git a/src/extension/extension.ts b/src/extension/extension.ts
index 96b04e69..72a6c917 100644
--- a/src/extension/extension.ts
+++ b/src/extension/extension.ts
@@ -1,4 +1,4 @@
-import { workspace, notebooks, commands, ExtensionContext, tasks, window, Uri } from 'vscode'
+import { workspace, notebooks, commands, ExtensionContext, tasks, window } from 'vscode'
 import { TelemetryReporter } from 'vscode-telemetry'
 
 import { Serializer } from '../types'
@@ -9,7 +9,7 @@ import RunmeServer from './server/runmeServer'
 import RunmeServerError from './server/runmeServerError'
 import { ToggleTerminalProvider, BackgroundTaskProvider, StopBackgroundTaskProvider } from './provider/background'
 import { CopyProvider } from './provider/copy'
-import { getDefaultWorkspace, resetEnv } from './utils'
+import { getDefaultWorkspace, resetEnv, bootFile } from './utils'
 import { AnnotationsProvider } from './provider/annotations'
 import { RunmeTaskProvider } from './provider/runmeTask'
 import {
@@ -26,12 +26,12 @@ import {
   openIntegratedTerminal,
   authenticateWithGitHub,
   displayCategoriesSelector,
-  runCellsByCategory
+  runCellsByCategory,
+  addToRecommendedExtensions
 } from './commands'
 import { WasmSerializer, GrpcSerializer } from './serializer'
 import { RunmeLauncherProvider } from './provider/launcher'
 import { RunmeUriHandler } from './handler/uri'
-import { BOOTFILE } from './constants'
 import { GrpcRunner, IRunner } from './runner'
 import { CliProvider } from './provider/cli'
 import * as survey from './survey'
@@ -154,19 +154,15 @@ export class RunmeExtension {
       /**
        * Uri handler
        */
-      window.registerUriHandler(uriHandler)
+      window.registerUriHandler(uriHandler),
+
+      /**
+       * Runme Message Display commands
+       */
+      RunmeExtension.registerCommand('runme.addToRecommendedExtensions', () => addToRecommendedExtensions(context))
     )
 
-    if (workspace.workspaceFolders?.length && workspace.workspaceFolders[0]) {
-      const startupFileUri = Uri.joinPath(workspace.workspaceFolders[0].uri, BOOTFILE)
-      const hasStartupFile = await workspace.fs.stat(startupFileUri).then(() => true, () => false)
-      if (hasStartupFile) {
-        const bootFile = new TextDecoder().decode(await workspace.fs.readFile(startupFileUri))
-        const bootFileUri = Uri.joinPath(workspace.workspaceFolders[0].uri, bootFile)
-        await workspace.fs.delete(startupFileUri)
-        await commands.executeCommand('vscode.openWith', bootFileUri, Kernel.type)
-      }
-    }
+    await bootFile()
   }
 
   static registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any) {
diff --git a/src/extension/kernel.ts b/src/extension/kernel.ts
index a0ce242f..249e5aad 100644
--- a/src/extension/kernel.ts
+++ b/src/extension/kernel.ts
@@ -13,11 +13,12 @@ import {
   env,
   Uri,
   commands,
+  languages,
 } from 'vscode'
 import { TelemetryReporter } from 'vscode-telemetry'
 
 import type { ClientMessage, Serializer } from '../types'
-import { ClientMessages, NOTEBOOK_HAS_CATEGORIES } from '../constants'
+import { ClientMessages, DEFAULT_LANGUAGEID, NOTEBOOK_HAS_CATEGORIES, SUPPORTED_FILE_EXTENSIONS } from '../constants'
 import { API } from '../utils/deno/api'
 import { postClientMessage } from '../utils/messaging'
 
@@ -32,6 +33,7 @@ import {
   processEnviron,
   isWindows,
   setNotebookCategories,
+  isShellLanguage,
 } from './utils'
 import './wasm/wasm_exec.js'
 import { IRunner, IRunnerEnvironment } from './runner'
@@ -41,6 +43,7 @@ import { NotebookCellManager, NotebookCellOutputManager, RunmeNotebookCellExecut
 import { handleCellOutputMessage } from './messages/cellOutput'
 import handleGitHubMessage from './messages/github'
 import { getNotebookCategories } from './utils'
+import { handleCloudApiMessage } from './messages/cloudApiRequest'
 
 enum ConfirmationItems {
   Yes = 'Yes',
@@ -79,11 +82,24 @@ export class Kernel implements Disposable {
     this.#experiments.set('grpcRunner', config.get<boolean>('grpcRunner', true))
     this.#experiments.set('grpcServer', config.get<boolean>('grpcServer', true))
 
-    this.#controller.supportedLanguages = Object.keys(executor)
     this.#controller.supportsExecutionOrder = false
     this.#controller.description = 'Run your Markdown'
     this.#controller.executeHandler = this._executeAll.bind(this)
 
+    languages.getLanguages().then(l => {
+      this.#controller.supportedLanguages = [
+        // TODO(mxs): smartly select default language depending on user shell
+        //            e.g., use powershell/bat for respective shells
+        DEFAULT_LANGUAGEID,
+        ...l.filter(x => x !== DEFAULT_LANGUAGEID),
+
+        // need to include file extensions since people often use file
+        // extension to tag code blocks
+        // TODO(mxs): should allow users to select their own
+        ...SUPPORTED_FILE_EXTENSIONS,
+      ]
+    })
+
     this.messaging.postMessage({ from: 'kernel' })
     this.#disposables.push(
       this.messaging.onDidReceiveMessage(this.#handleRendererMessage.bind(this)),
@@ -257,9 +273,9 @@ export class Kernel implements Disposable {
         return this._doExecuteCell(cell)
       }
     } else if (message.type === ClientMessages.infoMessage) {
-      return window.showInformationMessage(message.output as string)
+      return window.showInformationMessage(message.output as string,)
     } else if (message.type === ClientMessages.errorMessage) {
-      return window.showInformationMessage(message.output as string)
+      return window.showErrorMessage(message.output as string)
     } else if (message.type === ClientMessages.openLink) {
       return env.openExternal(Uri.parse(message.output))
     } else if (message.type === ClientMessages.closeCellOutput) {
@@ -291,7 +307,7 @@ export class Kernel implements Disposable {
       })
     } else if (message.type === ClientMessages.getState) {
       const cell = await getCellByUuId({ editor, uuid: message.output.uuid })
-      if(!cell) {
+      if (!cell) {
         return
       }
       const categories = await getNotebookCategories(this.context, cell.notebook.uri)
@@ -305,7 +321,7 @@ export class Kernel implements Disposable {
       })
     } else if (message.type === ClientMessages.setState) {
       const cell = await getCellByUuId({ editor, uuid: message.output.uuid })
-      if(!cell) {
+      if (!cell) {
         return
       }
       await setNotebookCategories(this.context, cell.notebook.uri, message.output.value)
@@ -318,6 +334,25 @@ export class Kernel implements Disposable {
         option: selectedOption,
         uuid: message.output.uuid
       })
+    } else if (message.type === ClientMessages.cloudApiRequest) {
+      return handleCloudApiMessage({
+        messaging: this.messaging,
+        message,
+        editor
+      })
+    } else if (message.type === ClientMessages.optionsMessage) {
+      const answer = await window.showInformationMessage(message.output.title, ...message.output.options)
+      return postClientMessage(this.messaging, ClientMessages.onOptionsMessage, {
+        option: answer,
+        uuid: message.output.uuid
+      })
+    } else if (message.type === ClientMessages.copyTextToClipboard) {
+      await env.clipboard.writeText(message.output.text)
+      return postClientMessage(this.messaging, ClientMessages.onCopyTextToClipboard, {
+        uuid: message.output.uuid
+      })
+    } else if (message.type === ClientMessages.openExternalLink) {
+      return env.openExternal(Uri.parse(message.output))
     } else if (
       message.type.startsWith('terminal:')
     ) {
@@ -392,7 +427,7 @@ export class Kernel implements Disposable {
     })
   }
 
-  public async createCellExecution(cell: NotebookCell): Promise<RunmeNotebookCellExecution|undefined> {
+  public async createCellExecution(cell: NotebookCell): Promise<RunmeNotebookCellExecution | undefined> {
     return await this.cellManager.createNotebookCellExecution(cell)
   }
 
@@ -432,7 +467,7 @@ export class Kernel implements Disposable {
       // TODO(mxs): support windows shells
       !isWindows()
     ) {
-      const runScript = (execKey: 'sh' | 'bash' = 'bash') => executeRunner(
+      const runScript = (execKey: string = 'sh') => executeRunner(
           this,
           this.context,
           this.runner!,
@@ -451,18 +486,22 @@ export class Kernel implements Disposable {
           return false
         })
 
-      if (execKey === 'bash' || execKey === 'sh') {
+      if (isShellLanguage(execKey)) {
         successfulCellExecution = await runScript(execKey)
-      } else {
-        successfulCellExecution = await executor[execKey].call(
+      } else if (execKey in executor) {
+        successfulCellExecution = await executor[execKey as keyof typeof executor].call(
           this, exec, runningCell, outputs, runScript, environmentManager
         )
+      } else {
+        window.showErrorMessage('Cell language is not executable')
+
+        successfulCellExecution = false
       }
     } else {
       /**
        * check if user is running experiment to execute shell via runme cli
        */
-      successfulCellExecution = await executor[execKey].call(this, exec, runningCell, outputs)
+      successfulCellExecution = await executor[execKey as keyof typeof executor]?.call(this, exec, runningCell, outputs)
     }
     TelemetryReporter.sendTelemetryEvent('cell.endExecute', { 'cell.success': successfulCellExecution?.toString() })
     runmeExec.end(successfulCellExecution)
@@ -515,4 +554,8 @@ export class Kernel implements Disposable {
       return ENV_STORE_MANAGER
     }
   }
+
+  getSupportedLanguages() {
+    return this.#controller.supportedLanguages
+  }
 }
diff --git a/src/extension/messages/cloudApiRequest/index.ts b/src/extension/messages/cloudApiRequest/index.ts
new file mode 100644
index 00000000..574ddcd2
--- /dev/null
+++ b/src/extension/messages/cloudApiRequest/index.ts
@@ -0,0 +1,24 @@
+import { NotebookEditor, NotebookRendererMessaging } from 'vscode'
+
+
+import { ClientMessages } from '../../../constants'
+import { APIMethod, ClientMessage } from '../../../types'
+
+import saveCellExecution from './saveCellExecution'
+
+
+export interface IApiMessage {
+    messaging: NotebookRendererMessaging
+    message: ClientMessage<ClientMessages>
+    editor: NotebookEditor
+}
+
+export async function handleCloudApiMessage({ messaging, message, editor }: IApiMessage): Promise<void | boolean> {
+    if (message.type !== ClientMessages.cloudApiRequest) {
+        throw new Error('Only API Request messages are supported!')
+    }
+    switch (message.output.method) {
+        case APIMethod.CreateCellExecution: return saveCellExecution({ messaging, message, editor })
+        default: throw new Error('Method not implemented')
+    }
+}
\ No newline at end of file
diff --git a/src/extension/messages/cloudApiRequest/saveCellExecution.ts b/src/extension/messages/cloudApiRequest/saveCellExecution.ts
new file mode 100644
index 00000000..2ac91e8d
--- /dev/null
+++ b/src/extension/messages/cloudApiRequest/saveCellExecution.ts
@@ -0,0 +1,53 @@
+import { ClientMessages } from '../../../constants'
+import { ClientMessage, IApiMessage } from '../../../types'
+import { InitializeClient } from '../../api/client'
+import { getCellByUuId } from '../../cell'
+import { getAnnotations, getTerminalByCell } from '../../utils'
+import { createCellExecutionQuery } from '../../api/grapql'
+import { postClientMessage } from '../../../utils/messaging'
+type APIRequestMessage = IApiMessage<ClientMessage<ClientMessages.cloudApiRequest>>
+
+export default async function saveCellExecution(requestMessage: APIRequestMessage): Promise<void | boolean> {
+    const { messaging, message, editor } = requestMessage
+
+    try {
+
+        const cell = await getCellByUuId({ editor, uuid: message.output.uuid })
+        if (!cell) {
+            throw new Error('Cell not found')
+        }
+        const terminal = getTerminalByCell(cell)
+        if (!terminal) {
+            throw new Error('Could not find an associated terminal')
+        }
+        const pid = await terminal.processId || 0
+        const runnerExitStatus = terminal.runnerSession?.hasExited()
+        const exitCode = runnerExitStatus ? (runnerExitStatus.type === 'exit' ? runnerExitStatus.code : -1) : 0
+        const annotations = getAnnotations(cell)
+        delete annotations['runme.dev/uuid']
+
+        const graphClient = InitializeClient()
+        const result = await graphClient.mutate({
+            mutation: createCellExecutionQuery({
+                ...message.output.data, exitCode, pid,
+                input: encodeURIComponent(cell.document.getText()),
+                metadata: {
+                    mimeType: annotations.mimeType,
+                    name: annotations.name,
+                    category: annotations.category
+                }
+            })
+        })
+
+        return postClientMessage(messaging, ClientMessages.cloudApiResponse, {
+            data: result,
+            uuid: message.output.uuid
+        })
+    } catch (error) {
+        return postClientMessage(messaging, ClientMessages.cloudApiResponse, {
+            data: (error as any).message,
+            uuid: message.output.uuid,
+            hasErrors: true
+        })
+    }
+} 
\ No newline at end of file
diff --git a/src/extension/messaging.ts b/src/extension/messaging.ts
new file mode 100644
index 00000000..c3099383
--- /dev/null
+++ b/src/extension/messaging.ts
@@ -0,0 +1,120 @@
+import { join } from 'node:path'
+
+import { Disposable, ExtensionContext, Uri, window, workspace } from 'vscode'
+import { TelemetryReporter } from 'vscode-telemetry'
+
+import { EXTENSION_NAME, TELEMETRY_EVENTS } from '../constants'
+
+import { fileOrDirectoryExists, getDefaultWorkspace, isMultiRootWorkspace } from './utils'
+
+export abstract class DisplayableMessage {
+    abstract dispose(): void
+    abstract display(): void
+    constructor(protected readonly context: ExtensionContext, readonly stateSettings?: Record<TELEMETRY_EVENTS, any>) {
+        this.context = context
+        this.stateSettings = stateSettings
+    }
+}
+
+export class MessagingBuilder implements Disposable {
+    /**
+     * Represents a collection of DisplayableMessages, useful to display window messages.
+     * @param messages Specify the displayable messages to show
+     */
+    constructor(private messages: DisplayableMessage[]) {
+        this.messages = messages
+    }
+
+    dispose() {
+        this.messages.forEach(d => d.dispose())
+    }
+
+    /**
+     * Display registered DisplayableMessages
+     */
+    activate() {
+        for (const message of this.messages) {
+            message.display()
+        }
+    }
+}
+
+
+export class RecommendExtensionMessage extends DisplayableMessage implements Disposable {
+    async display(): Promise<void> {
+        try {
+            const skipPrompSettings = this.stateSettings && this.stateSettings[TELEMETRY_EVENTS.RecommendExtension]
+            let promptUser = skipPrompSettings ||
+                this.context.globalState.get(TELEMETRY_EVENTS.RecommendExtension, true)
+            // Multi-root workspace not supported atm
+            const isMultiRoot = isMultiRootWorkspace()
+            if (isMultiRoot && skipPrompSettings) {
+                window.showInformationMessage('Multi-root workspace are not supported')
+                return
+            }
+            const workspaceRoot = getDefaultWorkspace()
+            if (!workspaceRoot || isMultiRoot) {
+                return
+            }
+            const extensionsFile = Uri.parse(join(workspaceRoot, '.vscode/extensions.json'))
+            const extensionfileOrDirectoryExists = await fileOrDirectoryExists(extensionsFile)
+            let extensionRecommendations: string[] = []
+            if (extensionfileOrDirectoryExists) {
+                const extensionDocument = await workspace.openTextDocument(extensionsFile)
+                const extensionContents = JSON.parse(extensionDocument.getText())
+                const { recommendations }: Record<string, string[]> = extensionContents
+                extensionRecommendations = recommendations
+                if (recommendations.includes(EXTENSION_NAME)) {
+                    promptUser = false
+                }
+            }
+
+            if (!promptUser) {
+                return
+            }
+
+            // eslint-disable-next-line max-len
+            const answer = await window.showInformationMessage(
+                'Would you like to add Runme to the recommended extensions?',
+                'Yes',
+                'No',
+                'Don\'t ask again'
+            )
+            if (answer !== 'Yes') {
+                TelemetryReporter.sendTelemetryEvent(TELEMETRY_EVENTS.RecommendExtension,
+                    {
+                        added: 'false',
+                        error: 'false'
+                    }
+                )
+                if (answer === 'Don\'t ask again') {
+                    await this.context.globalState.update(TELEMETRY_EVENTS.RecommendExtension, false)
+                }
+                return
+            }
+
+            const folderUri = Uri.parse(join(workspaceRoot, '.vscode'))
+            const dirExists = await fileOrDirectoryExists(folderUri)
+            if (!dirExists) {
+                await workspace.fs.createDirectory(folderUri)
+            }
+
+            const recommendations = extensionfileOrDirectoryExists
+                ? extensionRecommendations.push(EXTENSION_NAME) && extensionRecommendations
+                : [EXTENSION_NAME]
+            await workspace.fs.writeFile(extensionsFile, Buffer.from(JSON.stringify({
+                recommendations
+            }, null, 2)))
+
+            window.showInformationMessage('Runme added successfully to the recommended extensions')
+            TelemetryReporter.sendTelemetryEvent(TELEMETRY_EVENTS.RecommendExtension, { added: 'true', error: 'false' })
+
+        } catch (error) {
+            window.showErrorMessage('Failed to add Runme to the recommended extensions')
+            TelemetryReporter.sendTelemetryEvent(TELEMETRY_EVENTS.RecommendExtension, { added: 'false', error: 'true' })
+        }
+
+    }
+
+    dispose() { }
+}
\ No newline at end of file
diff --git a/src/extension/serializer.ts b/src/extension/serializer.ts
index b4ecbcd2..9496db19 100644
--- a/src/extension/serializer.ts
+++ b/src/extension/serializer.ts
@@ -20,6 +20,7 @@ import { v4 as uuidv4 } from 'uuid'
 import { GrpcTransport } from '@protobuf-ts/grpc-transport'
 
 import { Serializer } from '../types'
+import { VSCODE_LANGUAGEID_MAP } from '../constants'
 
 import { DeserializeRequest, SerializeRequest, Notebook } from './grpc/serializerTypes'
 import { initParserClient, ParserServiceClient } from './grpc/client'
@@ -48,7 +49,6 @@ export abstract class SerializerBase implements NotebookSerializer, Disposable {
       workspace.onDidChangeNotebookDocument(
         this.handleNotebookChanged.bind(this)
       ),
-      // TODO(mxs): https://github.com/stateful/vscode-runme/issues/566#issuecomment-1574128419
       // workspace.onDidSaveNotebookDocument(
       //   this.handleNotebookSaved.bind(this)
       // )
@@ -136,6 +136,18 @@ export abstract class SerializerBase implements NotebookSerializer, Disposable {
   ): Promise<Uint8Array> {
     await this.preSaveCheck()
 
+    const transformedCells = data.cells.map(cell => {
+      return {
+        ...cell,
+        languageId: VSCODE_LANGUAGEID_MAP[cell.languageId] ?? cell.languageId,
+      }
+    })
+
+    const metadata = data.metadata
+
+    data = new NotebookData(transformedCells)
+    data.metadata = metadata
+
     let encoded: Uint8Array
     try {
       encoded = await this.saveNotebook(data, token)
@@ -185,7 +197,9 @@ export abstract class SerializerBase implements NotebookSerializer, Disposable {
           ) {
             const norm = SerializerBase.normalize(elem.value)
             return this.languages.guess(norm, PLATFORM_OS).then((guessed) => {
-              elem.languageId = guessed
+              if (guessed) {
+                elem.languageId = guessed
+              }
               return elem
             })
           }
diff --git a/src/extension/utils.ts b/src/extension/utils.ts
index e0277f59..a88539a3 100644
--- a/src/extension/utils.ts
+++ b/src/extension/utils.ts
@@ -20,15 +20,14 @@ import { v5 as uuidv5 } from 'uuid'
 import getPort from 'get-port'
 import dotenv from 'dotenv'
 
-import { CellAnnotations, CellAnnotationsErrorResult, RunmeTerminal, Serializer } from '../types'
+import { CellAnnotations, CellAnnotationsErrorResult, RunmeTerminal, Serializer, ShellType } from '../types'
 import { SafeCellAnnotationsSchema, CellAnnotationsSchema } from '../schema'
 import { NOTEBOOK_AVAILABLE_CATEGORIES, SERVER_ADDRESS } from '../constants'
 import { getEnvLoadWorkspaceFiles, getEnvWorkspaceFileOrder, getPortNumber } from '../utils/configuration'
 
 import getLogger from './logger'
-import type executor from './executors'
 import { Kernel } from './kernel'
-import { ENV_STORE, DEFAULT_ENV } from './constants'
+import { ENV_STORE, DEFAULT_ENV, BOOTFILE } from './constants'
 
 declare var globalThis: any
 
@@ -133,17 +132,47 @@ export function isGitHubLink(runningCell: vscode.TextDocument) {
   return text.startsWith('https://github.com') && isWorkflowUrl
 }
 
-export function getKey(runningCell: vscode.TextDocument): keyof typeof executor {
+export function getKey(runningCell: vscode.TextDocument): string {
   if (isDenoScript(runningCell)) {
     return 'deno'
   }
+
   if (isGitHubLink(runningCell)) {
     return 'github'
   }
-  // if (text.startsWith('vercel ')) {
-  //   return 'vercel'
-  // }
-  return runningCell.languageId as keyof typeof executor
+
+  const { languageId } = runningCell
+
+  if (languageId === 'shellscript') {
+    return 'sh'
+  }
+
+  return languageId
+}
+
+export function isShellLanguage(languageId: string): ShellType | undefined {
+  switch (languageId.toLowerCase()) {
+    case 'sh':
+    case 'bash':
+    case 'zsh':
+    case 'ksh':
+    case 'shell':
+      return 'sh'
+
+    case 'bat':
+    case 'cmd':
+      return 'cmd'
+
+    case 'powershell':
+    case 'pwsh':
+      return 'powershell'
+
+    case 'fish':
+      return 'fish'
+
+    default:
+      return undefined
+  }
 }
 
 /**
@@ -446,9 +475,9 @@ export async function setNotebookCategories(context: ExtensionContext, uri: Uri,
 
 /**
  * Get the notebook cell categories from the global state
- * @param context 
- * @param uri 
- * @returns 
+ * @param context
+ * @param uri
+ * @returns
  */
 export async function getNotebookCategories(context: ExtensionContext, uri: Uri): Promise<string[]> {
   const notebooksCategories = context.globalState.get<Record<string, string[]>>(NOTEBOOK_AVAILABLE_CATEGORIES)
@@ -467,3 +496,54 @@ export function getNamespacedMid(namespace: string) {
   const ns = uuidv5(namespace, uuidv5.URL)
   return uuidv5(env.machineId, ns)
 }
+
+/**
+ * Opens a start-up file either defined within a `.runme_bootstrap` file or
+ * set as Runme configuration
+ */
+export async function bootFile() {
+  if (!workspace.workspaceFolders?.length || !workspace.workspaceFolders[0]) {
+    return
+  }
+
+  const startupFileUri = Uri.joinPath(workspace.workspaceFolders[0].uri, BOOTFILE)
+  const hasStartupFile = await workspace.fs.stat(startupFileUri).then(() => true, () => false)
+  if (hasStartupFile) {
+    const bootFile = new TextDecoder().decode(await workspace.fs.readFile(startupFileUri))
+    const bootFileUri = Uri.joinPath(workspace.workspaceFolders[0].uri, bootFile)
+    await workspace.fs.delete(startupFileUri)
+    log.info(`Open file defined in "${BOOTFILE}" file: ${bootFileUri.fsPath}`)
+    return commands.executeCommand('vscode.openWith', bootFileUri, Kernel.type)
+  }
+
+  /**
+   * if config is set, open file path directly
+   */
+  const config = workspace.getConfiguration('runme.flags')
+  const startupFilePath = config.get<string | undefined>('startFile')
+  if (!startupFilePath) {
+    return
+  }
+
+  const startupFileUriConfig = Uri.joinPath(workspace.workspaceFolders[0].uri, startupFilePath)
+  const hasStartupFileConfig = await workspace.fs.stat(startupFileUriConfig).then(() => true, () => false)
+  if (!hasStartupFileConfig) {
+    return
+  }
+
+  log.info(`Open file defined in "runme.flag.startFile" setting: ${startupFileUriConfig.fsPath}`)
+  return commands.executeCommand('vscode.openWith', startupFileUriConfig, Kernel.type)
+}
+
+export async function fileOrDirectoryExists(path: Uri): Promise<boolean> {
+  return workspace.fs.stat(path).then(
+    async (file) => {
+      return file.type === FileType.File || file.type === FileType.Directory
+    },
+    () => false
+  )
+}
+
+export function isMultiRootWorkspace(): boolean {
+  return (workspace.workspaceFolders && workspace.workspaceFolders.length > 1) || false
+}
diff --git a/src/types.ts b/src/types.ts
index 6e19b6cb..cbb50568 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,4 +1,13 @@
-import { NotebookCellKind, TaskDefinition, type Terminal, TerminalDimensions, Uri, ExtensionContext } from 'vscode'
+import {
+  NotebookCellKind,
+  TaskDefinition,
+  type Terminal,
+  TerminalDimensions,
+  Uri,
+  ExtensionContext,
+  NotebookRendererMessaging,
+  NotebookEditor
+} from 'vscode'
 import { z } from 'zod'
 
 import { OutputType, ClientMessages } from './constants'
@@ -97,6 +106,7 @@ interface Payload {
     terminalFontSize: number
     content?: string
     initialRows?: number
+    enableShareButton: boolean
   }
   [OutputType.github]?: GitHubState
 }
@@ -198,6 +208,34 @@ export interface ClientMessagePayload {
     value: string | string[]
     uuid: string
   }
+  [ClientMessages.cloudApiRequest]: {
+    data: any
+    uuid: string
+    hasErrors?: boolean
+    method: APIMethod
+  }
+  [ClientMessages.cloudApiResponse]: {
+    data: any
+    uuid: string
+    hasErrors?: boolean
+  }
+  [ClientMessages.optionsMessage]: {
+    title: string
+    uuid: string
+    options: any[]
+  }
+  [ClientMessages.onOptionsMessage]: {
+    uuid: string
+    option: string | undefined
+  }
+  [ClientMessages.openExternalLink]: string
+  [ClientMessages.copyTextToClipboard]: {
+    uuid: string
+    text: string
+  }
+  [ClientMessages.onCopyTextToClipboard]: {
+    uuid: string
+  }
 }
 
 export interface OutputItemsPayload {
@@ -248,3 +286,15 @@ export interface NotebookToolbarCommand {
     }
   }
 }
+
+export enum APIMethod {
+  CreateCellExecution = 'createCellExecution'
+}
+
+export interface IApiMessage<T extends ClientMessage<ClientMessages>> {
+  messaging: NotebookRendererMessaging
+  message: T
+  editor: NotebookEditor
+}
+
+export type ShellType = 'sh' | 'powershell' | 'cmd' | 'fish'
diff --git a/src/utils/configuration.ts b/src/utils/configuration.ts
index c8a16efa..c38e39ea 100644
--- a/src/utils/configuration.ts
+++ b/src/utils/configuration.ts
@@ -12,67 +12,77 @@ const SERVER_SECTION_NAME = 'runme.server'
 const TERMINAL_SECTION_NAME = 'runme.terminal'
 const CODELENS_SECTION_NAME = 'runme.codelens'
 const ENV_SECTION_NAME = 'runme.env'
+const CLI_SECTION_NAME = 'runme.cli'
+const APP_SECTION_NAME = 'runme.app'
 
 export const DEFAULT_TLS_DIR = path.join(os.tmpdir(), 'runme', uuidv4(), 'tls')
 const DEFAULT_WORKSPACE_FILE_ORDER = ['.env.local', '.env']
+const DEFAULT_RUNME_APP_API_URL = 'https://app.runme.dev/graphql'
 
 type NotebookTerminalValue = keyof typeof configurationSchema.notebookTerminal
 
 const configurationSchema = {
-    server: {
-        customAddress: z
-            .string()
-            .nonempty()
-            .optional(),
-        binaryPath: z
-            .string()
-            .optional(),
-        enableLogger: z
-            .boolean()
-            .default(false),
-        enableTLS: z
-            .boolean()
-            .default(true),
-        tlsDir: z
-            .string()
-            .nonempty()
-            .default(DEFAULT_TLS_DIR),
-    },
-    notebookTerminal: {
-        backgroundTask: z.boolean().default(true),
-        nonInteractive: z.boolean().default(false),
-        interactive: z.boolean().default(true),
-        fontSize: z.number().optional(),
-        fontFamily: z.string().optional(),
-        rows: z.number().int()
-    },
-    codelens: {
-      enable: z.boolean().default(true)
-    },
-    env: {
-      workspaceFileOrder: z.array(z.string()).default(DEFAULT_WORKSPACE_FILE_ORDER),
-      loadWorkspaceFiles: z.boolean().default(true),
-    }
+  server: {
+    customAddress: z
+      .string()
+      .nonempty()
+      .optional(),
+    binaryPath: z
+      .string()
+      .optional(),
+    enableLogger: z
+      .boolean()
+      .default(false),
+    enableTLS: z
+      .boolean()
+      .default(true),
+    tlsDir: z
+      .string()
+      .nonempty()
+      .default(DEFAULT_TLS_DIR),
+  },
+  notebookTerminal: {
+    backgroundTask: z.boolean().default(true),
+    nonInteractive: z.boolean().default(false),
+    interactive: z.boolean().default(true),
+    fontSize: z.number().optional(),
+    fontFamily: z.string().optional(),
+    rows: z.number().int()
+  },
+  codelens: {
+    enable: z.boolean().default(true)
+  },
+  env: {
+    workspaceFileOrder: z.array(z.string()).default(DEFAULT_WORKSPACE_FILE_ORDER),
+    loadWorkspaceFiles: z.boolean().default(true),
+  },
+  cli: {
+    useIntegratedRunme: z.boolean().default(false),
+  },
+  app: {
+    apiUrl: z.string().default(DEFAULT_RUNME_APP_API_URL),
+    enableShare: z.boolean().default(false)
+  }
 }
 
 const getServerConfigurationValue = <T>(configName: keyof typeof configurationSchema.server, defaultValue: T) => {
-    const configurationSection = workspace.getConfiguration(SERVER_SECTION_NAME)
-    const configurationValue = configurationSection.get<T>(configName)!
-    const parseResult = configurationSchema.server[configName].safeParse(configurationValue)
-    if (parseResult.success) {
-        return parseResult.data as T
-    }
-    return defaultValue
+  const configurationSection = workspace.getConfiguration(SERVER_SECTION_NAME)
+  const configurationValue = configurationSection.get<T>(configName)!
+  const parseResult = configurationSchema.server[configName].safeParse(configurationValue)
+  if (parseResult.success) {
+    return parseResult.data as T
+  }
+  return defaultValue
 }
 
 const getRunmeTerminalConfigurationValue = <T>(configName: NotebookTerminalValue, defaultValue: T) => {
-    const configurationSection = workspace.getConfiguration(TERMINAL_SECTION_NAME)
-    const configurationValue = configurationSection.get<T>(configName)!
-    const parseResult = configurationSchema.notebookTerminal[configName].safeParse(configurationValue)
-    if (parseResult.success) {
-        return parseResult.data as T
-    }
-return defaultValue
+  const configurationSection = workspace.getConfiguration(TERMINAL_SECTION_NAME)
+  const configurationValue = configurationSection.get<T>(configName)!
+  const parseResult = configurationSchema.notebookTerminal[configName].safeParse(configurationValue)
+  if (parseResult.success) {
+    return parseResult.data as T
+  }
+  return defaultValue
 }
 
 const getCodeLensConfigurationValue = <T>(configName: keyof typeof configurationSchema.codelens, defaultValue: T) => {
@@ -80,7 +90,7 @@ const getCodeLensConfigurationValue = <T>(configName: keyof typeof configuration
   const configurationValue = configurationSection.get<T>(configName)!
   const parseResult = configurationSchema.codelens[configName].safeParse(configurationValue)
   if (parseResult.success) {
-      return parseResult.data as T
+    return parseResult.data as T
   }
   return defaultValue
 }
@@ -90,7 +100,27 @@ const getEnvConfigurationValue = <T>(configName: keyof typeof configurationSchem
   const configurationValue = configurationSection.get<T>(configName)!
   const parseResult = configurationSchema.env[configName].safeParse(configurationValue)
   if (parseResult.success) {
-      return parseResult.data as T
+    return parseResult.data as T
+  }
+  return defaultValue
+}
+
+const getCloudConfigurationValue = <T>(configName: keyof typeof configurationSchema.app, defaultValue: T) => {
+  const configurationSection = workspace.getConfiguration(APP_SECTION_NAME)
+  const configurationValue = configurationSection.get<T>(configName)!
+  const parseResult = configurationSchema.app[configName].safeParse(configurationValue)
+  if (parseResult.success) {
+    return parseResult.data as T
+  }
+  return defaultValue
+}
+
+const getCLIConfigurationValue = <T>(configName: keyof typeof configurationSchema.cli, defaultValue: T) => {
+  const configurationSection = workspace.getConfiguration(CLI_SECTION_NAME)
+  const configurationValue = configurationSection.get<T>(configName)!
+  const parseResult = configurationSchema.cli[configName].safeParse(configurationValue)
+  if (parseResult.success) {
+    return parseResult.data as T
   }
   return defaultValue
 }
@@ -99,12 +129,12 @@ const getPortNumber = (): number => {
   return SERVER_PORT
 }
 
-const getCustomServerAddress = (): string|undefined => {
-  return getServerConfigurationValue<string|undefined>('customAddress', undefined)
+const getCustomServerAddress = (): string | undefined => {
+  return getServerConfigurationValue<string | undefined>('customAddress', undefined)
 }
 
 const getTLSEnabled = (): boolean => {
-  if(isWindows()) {
+  if (isWindows()) {
     // disable on windows until we figure out file permissions
     return false
   }
@@ -117,37 +147,37 @@ const getTLSDir = (): string => {
 }
 
 const getBinaryPath = (extensionBaseUri: Uri, platform: string): Uri => {
-    const userPath = getServerConfigurationValue<string | undefined>('binaryPath', undefined)
-
-    const isWin = platform.toLowerCase().startsWith('win')
-    const binName = isWin ? 'runme.exe' : 'runme'
-    const bundledPath = Uri.joinPath(extensionBaseUri, 'bin', binName)
-
-    if (userPath) {
-        if (path.isAbsolute(userPath)) {
-            return Uri.file(userPath)
-        } else if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) {
-            return Uri.joinPath(workspace.workspaceFolders[0].uri, userPath)
-        }
+  const userPath = getServerConfigurationValue<string | undefined>('binaryPath', undefined)
+
+  const isWin = platform.toLowerCase().startsWith('win')
+  const binName = isWin ? 'runme.exe' : 'runme'
+  const bundledPath = Uri.joinPath(extensionBaseUri, 'bin', binName)
+
+  if (userPath) {
+    if (path.isAbsolute(userPath)) {
+      return Uri.file(userPath)
+    } else if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) {
+      return Uri.joinPath(workspace.workspaceFolders[0].uri, userPath)
     }
+  }
 
-    return bundledPath
+  return bundledPath
 }
 
 const enableServerLogs = (): boolean => {
-    return getServerConfigurationValue<boolean>('enableLogger', false)
+  return getServerConfigurationValue<boolean>('enableLogger', false)
 }
 
 const isNotebookTerminalFeatureEnabled = (featureName: keyof typeof configurationSchema.notebookTerminal): boolean => {
-    return getRunmeTerminalConfigurationValue(featureName, false)
+  return getRunmeTerminalConfigurationValue(featureName, false)
 }
 
-const getNotebookTerminalFontSize = (): number|undefined => {
-  return getRunmeTerminalConfigurationValue<number|undefined>('fontSize', undefined)
+const getNotebookTerminalFontSize = (): number | undefined => {
+  return getRunmeTerminalConfigurationValue<number | undefined>('fontSize', undefined)
 }
 
-const getNotebookTerminalFontFamily = (): string|undefined => {
-  return getRunmeTerminalConfigurationValue<string|undefined>('fontFamily', undefined)
+const getNotebookTerminalFontFamily = (): string | undefined => {
+  return getRunmeTerminalConfigurationValue<string | undefined>('fontFamily', undefined)
 }
 
 const isNotebookTerminalEnabledForCell = (cell: NotebookCell): boolean => {
@@ -185,21 +215,36 @@ const getEnvLoadWorkspaceFiles = (): boolean => {
   return getEnvConfigurationValue('loadWorkspaceFiles', true)
 }
 
+const getCLIUseIntegratedRunme = (): boolean => {
+  return getCLIConfigurationValue('useIntegratedRunme', false)
+}
+
+const getRunmeApiUrl = (): string => {
+  return getCloudConfigurationValue('apiUrl', DEFAULT_RUNME_APP_API_URL)
+}
+
+const isRunmeApiEnabled = (): boolean => {
+  return getCloudConfigurationValue('enableShare', false)
+}
+
 export {
-    getPortNumber,
-    getBinaryPath,
-    enableServerLogs,
-    getServerConfigurationValue,
-    isNotebookTerminalFeatureEnabled,
-    isNotebookTerminalEnabledForCell,
-    getTLSEnabled,
-    getTLSDir,
-    getNotebookTerminalFontFamily,
-    getNotebookTerminalFontSize,
-    getNotebookTerminalRows,
-    getCodeLensEnabled,
-    registerExtensionEnvironmentVariables,
-    getCustomServerAddress,
-    getEnvWorkspaceFileOrder,
-    getEnvLoadWorkspaceFiles,
+  getPortNumber,
+  getBinaryPath,
+  enableServerLogs,
+  getServerConfigurationValue,
+  isNotebookTerminalFeatureEnabled,
+  isNotebookTerminalEnabledForCell,
+  getTLSEnabled,
+  getTLSDir,
+  getNotebookTerminalFontFamily,
+  getNotebookTerminalFontSize,
+  getNotebookTerminalRows,
+  getCodeLensEnabled,
+  registerExtensionEnvironmentVariables,
+  getCustomServerAddress,
+  getEnvWorkspaceFileOrder,
+  getEnvLoadWorkspaceFiles,
+  getCLIUseIntegratedRunme,
+  getRunmeApiUrl,
+  isRunmeApiEnabled,
 }


Test patch:
diff --git a/tests/extension/commands/index.test.ts b/tests/extension/commands/index.test.ts
index bcacc6d0..14cc5438 100644
--- a/tests/extension/commands/index.test.ts
+++ b/tests/extension/commands/index.test.ts
@@ -1,6 +1,6 @@
 import path from 'node:path'
 
-import { beforeEach, expect, test, vi } from 'vitest'
+import { beforeEach, expect, test, vi, suite } from 'vitest'
 import {
   window,
   env,
@@ -13,6 +13,7 @@ import {
   ViewColumn,
   NotebookCellData,
   Uri,
+  ExtensionContext,
 } from 'vscode'
 
 import {
@@ -25,10 +26,16 @@ import {
   createNewRunmeNotebook,
   welcome,
   tryIt,
-  openFileInRunme
+  openFileInRunme,
+  addToRecommendedExtensions
 } from '../../../src/extension/commands'
 import { getTerminalByCell, getAnnotations, openFileAsRunmeNotebook } from '../../../src/extension/utils'
-import { getBinaryPath, isNotebookTerminalEnabledForCell } from '../../../src/utils/configuration'
+import {
+  getBinaryPath,
+  getCLIUseIntegratedRunme,
+  isNotebookTerminalEnabledForCell
+} from '../../../src/utils/configuration'
+import { RecommendExtensionMessage } from '../../../src/extension/messaging'
 
 vi.mock('vscode', () => import(path.join(process.cwd(), '__mocks__', 'vscode')))
 vi.mock('vscode-telemetry')
@@ -41,6 +48,7 @@ vi.mock('../../../src/extension/utils', () => ({
 vi.mock('../../../src/utils/configuration', () => ({
   getBinaryPath: vi.fn(),
   isNotebookTerminalEnabledForCell: vi.fn(),
+  getCLIUseIntegratedRunme: vi.fn().mockReturnValue(false),
 }))
 vi.mock('../../../src/extension/provider/cli', () => ({
   CliProvider: {
@@ -65,7 +73,7 @@ beforeEach(() => {
 })
 
 test('openTerminal without notebook terminal', () => {
-  const func = toggleTerminal({ } as any, false)
+  const func = toggleTerminal({} as any, false)
 
   vi.mocked(isNotebookTerminalEnabledForCell).mockReturnValueOnce(false)
   vi.mocked(getAnnotations).mockReturnValueOnce(({ interactive: true } as any))
@@ -106,31 +114,106 @@ test('copyCellToClipboard', () => {
   expect(window.showInformationMessage).toBeCalledTimes(1)
 })
 
-test('runCLICommand', async () => {
-  const cell: any = {
-    metadata: { name: 'foobar' },
-    document: { uri: { fsPath: '/foo/bar/README.md' }}
-  }
-  vi.mocked(getAnnotations).mockReturnValueOnce({
-    name: 'foo-bar',
-  } as any)
-  vi.mocked(getBinaryPath).mockReturnValueOnce(Uri.file('/bin/runme'))
-  vi.mocked(window.showInformationMessage).mockResolvedValueOnce(false as any)
-
-  await runCLICommand({} as any, false, {} as any, {} as any)(cell)
-  expect(vi.mocked((terminal as any).sendText)).toHaveBeenCalledWith(
-    'runme run foo-bar --chdir="/foo/bar" --filename="README.md"'
-  )
+suite('runCliCommand', () => {
+  beforeEach(() => {
+    vi.mocked(getBinaryPath).mockClear()
+    vi.mocked(window.showInformationMessage).mockClear()
+    vi.mocked(getAnnotations).mockClear()
+    vi.mocked(terminal.sendText).mockClear()
+    vi.mocked(getCLIUseIntegratedRunme).mockClear()
+  })
+
+  test('runs command when not dirty, using index', async () => {
+    const cell: any = {
+      metadata: { name: 'foobar' },
+      document: { uri: { fsPath: '/foo/bar/README.md' } },
+      kind: 1
+    }
+
+    cell.notebook = {
+      getCells: () => [cell],
+      isDirty: false
+    }
+
+    // vi.mocked(getAnnotations).mockReturnValueOnce({
+    //   name: 'foo-bar',
+    // } as any)
+
+    // vi.mocked(getBinaryPath).mockReturnValueOnce(Uri.file('/bin/runme'))
+    // vi.mocked(window.showInformationMessage).mockResolvedValueOnce(false as any)
+
+    await runCLICommand({} as any, false, {} as any, {} as any)(cell)
+    expect(vi.mocked((terminal as any).sendText)).toHaveBeenCalledWith(
+      'runme run --chdir="/foo/bar" --filename="README.md" --index=0'
+    )
+  })
+
+  test('uses integrated runme if config set', async () => {
+    const cell: any = {
+      metadata: { name: 'foobar' },
+      document: { uri: { fsPath: '/foo/bar/README.md' } },
+      kind: 1
+    }
+
+    cell.notebook = {
+      getCells: () => [cell],
+      isDirty: false
+    }
+
+    vi.mocked(getBinaryPath).mockReturnValueOnce(Uri.file('/bin/runme'))
+    vi.mocked(getCLIUseIntegratedRunme).mockReturnValueOnce(true)
+
+    await runCLICommand({} as any, false, {} as any, {} as any)(cell)
+    expect(vi.mocked((terminal as any).sendText)).toHaveBeenCalledWith(
+      '/bin/runme run --chdir="/foo/bar" --filename="README.md" --index=0'
+    )
+  })
+
+  test('prompts user when dirty and fails if cancelled or closed', async () => {
+    const cell: any = {
+      metadata: { name: 'foobar' },
+      document: { uri: { fsPath: '/foo/bar/README.md' } },
+      kind: 1
+    }
+
+    cell.notebook = {
+      getCells: () => [cell],
+      isDirty: true,
+      save: vi.fn()
+    }
+
+    // Cancelled
+    vi.mocked(window.showInformationMessage).mockResolvedValueOnce('Cancel' as any)
+    await runCLICommand({} as any, false, {} as any, {} as any)(cell)
+    expect(vi.mocked(terminal.sendText)).not.toHaveBeenCalled()
+
+    vi.mocked(terminal.sendText).mockClear()
+
+    // Closed
+    vi.mocked(window.showInformationMessage).mockResolvedValueOnce(undefined as any)
+    await runCLICommand({} as any, false, {} as any, {} as any)(cell)
+
+    expect(vi.mocked(terminal.sendText)).not.toHaveBeenCalled()
+
+    vi.mocked(terminal.sendText).mockClear()
+
+    // Saved
+    vi.mocked(window.showInformationMessage).mockResolvedValueOnce('Save' as any)
+    await runCLICommand({} as any, false, {} as any, {} as any)(cell)
+
+    expect(vi.mocked(terminal.sendText)).toHaveBeenCalled()
+    expect(cell.notebook.save).toHaveBeenCalledOnce()
+  })
 })
 
 test('open markdown as Runme notebook', (file: NotebookDocument) => {
   openAsRunmeNotebook(file)
-  expect(window.showNotebookDocument).toBeCalledWith(file, { viewColumn: undefined})
+  expect(window.showNotebookDocument).toBeCalledWith(file, { viewColumn: undefined })
 })
 
 test('open Runme notebook in text editor', (file: TextDocument) => {
   openSplitViewAsMarkdownText(file)
-  expect(window.showTextDocument).toBeCalledWith(file, {viewColumn: ViewColumn.Beside})
+  expect(window.showTextDocument).toBeCalledWith(file, { viewColumn: ViewColumn.Beside })
 })
 
 test('stopBackgroundTask if terminal exists', () => {
@@ -184,7 +267,7 @@ beforeEach(() => {
 test('openFileInRunme command', async () => {
   vi.mocked(openFileAsRunmeNotebook).mockClear()
 
-  const uri = { } as any
+  const uri = {} as any
   await openFileInRunme(uri)
 
   expect(openFileAsRunmeNotebook).toHaveBeenCalledOnce()
@@ -194,11 +277,23 @@ test('openFileInRunme command', async () => {
 test('openFileInRunme command multiselect', async () => {
   vi.mocked(openFileAsRunmeNotebook).mockClear()
 
-  const uri = { } as any
-  const uri2 = { } as any
-  await openFileInRunme(uri, [ uri, uri2 ])
+  const uri = {} as any
+  const uri2 = {} as any
+  await openFileInRunme(uri, [uri, uri2])
 
   expect(openFileAsRunmeNotebook).toBeCalledTimes(2)
   expect(openFileAsRunmeNotebook).toBeCalledWith(uri, 0, [uri, uri2])
   expect(openFileAsRunmeNotebook).toBeCalledWith(uri2, 1, [uri, uri2])
 })
+
+test('addToRecommendedExtensions command', async () => {
+  const recommendExtensionSpy = vi.spyOn(RecommendExtensionMessage.prototype, 'display')
+  const contextMock: ExtensionContext = {
+    globalState: {
+      get: vi.fn().mockReturnValue(undefined),
+      update: vi.fn().mockResolvedValue({}),
+    }
+  } as any
+  await addToRecommendedExtensions(contextMock)
+  expect(recommendExtensionSpy).toHaveBeenCalledOnce()
+})
diff --git a/tests/extension/configuration.test.ts b/tests/extension/configuration.test.ts
index 58220926..f1b1ec93 100644
--- a/tests/extension/configuration.test.ts
+++ b/tests/extension/configuration.test.ts
@@ -12,7 +12,8 @@ import {
   DEFAULT_TLS_DIR,
   getNotebookTerminalFontFamily,
   getNotebookTerminalFontSize,
-  getCodeLensEnabled
+  getCodeLensEnabled,
+  getCLIUseIntegratedRunme
 } from '../../src/utils/configuration'
 import { SERVER_PORT } from '../../src/constants'
 
@@ -150,6 +151,10 @@ suite('Configuration', () => {
       ).toStrictEqual(true)
     })
 
+    test('getCLIUseIntegratexRunme should return false by default', () => {
+      expect(getCLIUseIntegratedRunme()).toStrictEqual(false)
+    })
+
     suite('posix', () => {
       beforeEach(() => {
         platformPathMocks(path.posix)
diff --git a/tests/extension/extension.test.ts b/tests/extension/extension.test.ts
index 33e7f8e4..596e5183 100644
--- a/tests/extension/extension.test.ts
+++ b/tests/extension/extension.test.ts
@@ -5,6 +5,7 @@ import {
 } from '@buf/grpc_grpc.community_timostamm-protobuf-ts/grpc/health/v1/health_pb'
 
 import { RunmeExtension } from '../../src/extension/extension'
+import { bootFile } from '../../src/extension/utils'
 import RunmeServer from '../../src/extension/server/runmeServer'
 import { testCertPEM, testPrivKeyPEM } from '../testTLSCert'
 
@@ -38,6 +39,15 @@ vi.mock('../../src/extension/grpc/client', () => {
   })
 })
 
+vi.mock('../../src/extension/utils', async () => ({
+  getDefaultWorkspace: vi.fn(),
+  resetEnv: vi.fn(),
+  initWasm: vi.fn(),
+  getNamespacedMid: vi.fn(),
+  isWindows: vi.fn().mockReturnValue(false),
+  bootFile: vi.fn().mockResolvedValue(undefined)
+}))
+
 vi.mock('../../src/extension/grpc/runnerTypes', () => ({}))
 
 test('initializes all providers', async () => {
@@ -63,11 +73,8 @@ test('initializes all providers', async () => {
   await ext.initialize(context)
   expect(notebooks.registerNotebookCellStatusBarItemProvider).toBeCalledTimes(6)
   expect(workspace.registerNotebookSerializer).toBeCalledTimes(1)
-  expect(commands.registerCommand).toBeCalledTimes(23)
+  expect(commands.registerCommand).toBeCalledTimes(24)
   expect(window.registerTreeDataProvider).toBeCalledTimes(1)
   expect(window.registerUriHandler).toBeCalledTimes(1)
-
-  expect(commands.executeCommand).toBeCalledWith('vscode.openWith', dummyFilePath, 'runme')
-  expect(workspace.fs.stat).toBeCalledWith(dummyFilePath)
-  expect(workspace.fs.delete).toBeCalledWith(dummyFilePath)
+  expect(bootFile).toBeCalledTimes(1)
 })
diff --git a/tests/extension/kernel.test.ts b/tests/extension/kernel.test.ts
index 6487c267..5d1dd403 100644
--- a/tests/extension/kernel.test.ts
+++ b/tests/extension/kernel.test.ts
@@ -1,5 +1,5 @@
-import { test, expect, vi, suite } from 'vitest'
-import { window, NotebookCell } from 'vscode'
+import { test, expect, vi, suite, beforeEach } from 'vitest'
+import { window, NotebookCell, workspace } from 'vscode'
 
 import { Kernel } from '../../src/extension/kernel'
 import { resetEnv } from '../../src/extension/utils'
@@ -8,12 +8,16 @@ import { TelemetryReporter } from '../../__mocks__/vscode-telemetry'
 
 vi.mock('vscode')
 vi.mock('vscode-telemetry')
-vi.mock('../../src/extension/utils', () => ({
-  resetEnv: vi.fn(),
-  getKey: vi.fn().mockReturnValue('foobar'),
-  getAnnotations: vi.fn((cell) => cell.metadata),
-  getNotebookCategories: vi.fn().mockResolvedValue([])
-}))
+vi.mock('../../src/extension/utils', async () => {
+  return ({
+    resetEnv: vi.fn(),
+    getKey: vi.fn(cell => cell.languageId),
+    getAnnotations: vi.fn((cell) => cell.metadata),
+    getNotebookCategories: vi.fn().mockResolvedValue([]),
+    isWindows: () => false,
+    isShellLanguage: () => false,
+  })
+})
 vi.mock('../../src/extension/executors/index.js', () => ({
   default: { foobar: vi.fn() },
   ENV_STORE_MANAGER: {}
@@ -167,28 +171,75 @@ suite('_executeAll', async () => {
 })
 
 
+suite('_doExecuteCell', () => {
+  beforeEach(() => {
+    vi.mocked(workspace.openTextDocument).mockReset()
+    vi.mocked(TelemetryReporter.sendTelemetryEvent).mockClear()
+  })
 
-test('_doExecuteCell', async () => {
-  const k = new Kernel({} as any)
+  test('calls proper executor if present', async () => {
+    const k = new Kernel({} as any)
+
+    k.createCellExecution = vi.fn().mockResolvedValue({
+      start: vi.fn(),
+      end: vi.fn(),
+      underlyingExecution: vi.fn(),
+    })
+    k.getCellOutputs = vi.fn().mockResolvedValue({})
+
+    vi.mocked(workspace.openTextDocument).mockResolvedValueOnce({
+      languageId: 'foobar'
+    } as any)
+
+    await k['_doExecuteCell']({
+      document: { uri: { fsPath: '/foo/bar' } },
+      metadata: { 'runme.dev/uuid': '849448b2-3c41-4323-920e-3098e71302ce' }
+    } as any)
+    // @ts-expect-error mocked out
+    expect(executors.foobar).toBeCalledTimes(1)
+    expect(TelemetryReporter.sendTelemetryEvent).toHaveBeenCalledWith(
+      'cell.startExecute'
+    )
+    expect(TelemetryReporter.sendTelemetryEvent).toHaveBeenCalledWith(
+      'cell.endExecute',
+      { 'cell.success': undefined }
+    )
+  })
+
+  test('shows error window if language is not supported', async () => {
+    const k = new Kernel({} as any)
+    k['runner'] = {} as any
+
+    k.createCellExecution = vi.fn().mockResolvedValue({
+      start: vi.fn(),
+      end: vi.fn(),
+      underlyingExecution: vi.fn(),
+    })
+    k.getCellOutputs = vi.fn().mockResolvedValue({})
+
+    vi.mocked(workspace.openTextDocument).mockResolvedValueOnce({
+      languageId: 'barfoo'
+    } as any)
+
+    await k['_doExecuteCell']({
+      document: { uri: { fsPath: '/foo/bar' } },
+      metadata: { 'runme.dev/uuid': '849448b2-3c41-4323-920e-3098e71302ce' }
+    } as any)
+
+    expect(TelemetryReporter.sendTelemetryEvent).toHaveBeenCalledWith(
+      'cell.startExecute'
+    )
+    expect(TelemetryReporter.sendTelemetryEvent).toHaveBeenCalledWith(
+      'cell.endExecute',
+      { 'cell.success': 'false' }
+    )
 
-  k.createCellExecution = vi.fn().mockResolvedValue({
-    start: vi.fn(),
-    end: vi.fn(),
-    underlyingExecution: vi.fn(),
+    expect(window.showErrorMessage).toHaveBeenCalledWith('Cell language is not executable')
   })
-  k.getCellOutputs = vi.fn().mockResolvedValue({})
-
-  await k['_doExecuteCell']({
-    document: { uri: { fsPath: '/foo/bar' } },
-    metadata: { 'runme.dev/uuid': '849448b2-3c41-4323-920e-3098e71302ce' }
-  } as any)
-  // @ts-expect-error mocked out
-  expect(executors.foobar).toBeCalledTimes(1)
-  expect(TelemetryReporter.sendTelemetryEvent).toHaveBeenCalledWith(
-    'cell.startExecute'
-  )
-  expect(TelemetryReporter.sendTelemetryEvent).toHaveBeenCalledWith(
-    'cell.endExecute',
-    { success: undefined }
-  )
+})
+
+test('supportedLanguages', async () => {
+  const k = new Kernel({} as any)
+
+  expect(k.getSupportedLanguages()![0]).toStrictEqual('shellscript')
 })
diff --git a/tests/extension/messaging.test.ts b/tests/extension/messaging.test.ts
new file mode 100644
index 00000000..3730080c
--- /dev/null
+++ b/tests/extension/messaging.test.ts
@@ -0,0 +1,208 @@
+import { expect, vi, test, suite, beforeEach } from 'vitest'
+import { Disposable, FileType, workspace, window, Uri, ExtensionContext } from 'vscode'
+import { TelemetryReporter } from 'vscode-telemetry'
+
+import { DisplayableMessage, MessagingBuilder, RecommendExtensionMessage } from '../../src/extension/messaging'
+
+vi.mock('vscode', async () => {
+    const vscode = await import('../../__mocks__/vscode')
+    return {
+        default: vscode,
+        ...vscode,
+        workspace: {
+            ...vscode.workspace,
+            openTextDocument: vi.fn().mockReturnValue({
+                getText: vi.fn().mockReturnValue(JSON.stringify({
+                    recommendations: ['stateful.runme']
+                }))
+            })
+        }
+    }
+})
+
+vi.mock('vscode')
+
+vi.mock('vscode-telemetry')
+
+vi.mock('../../src/extension/grpc/client', () => ({}))
+vi.mock('../../src/extension/grpc/runnerTypes', () => ({}))
+
+class MockMessage extends DisplayableMessage implements Disposable {
+    dispose(): void { }
+    display(): void { }
+}
+
+const contextMock: ExtensionContext = {
+    globalState: {
+        get: vi.fn().mockReturnValue(true),
+        update: vi.fn().mockResolvedValue({}),
+    }
+} as any
+
+suite('MessageBuilder', () => {
+
+    test('On Activate should display all displayable messages', () => {
+        const mockMessage = new MockMessage(contextMock)
+        const messageBuilder = new MessagingBuilder([mockMessage, mockMessage])
+        const mockMessageSpy = vi.spyOn(mockMessage, 'display')
+        messageBuilder.activate()
+        expect(mockMessageSpy).toHaveBeenCalledTimes(2)
+    })
+
+    test('On Dispose should dispose all displayable messages', () => {
+        const mockMessage = new MockMessage(contextMock)
+        const messageBuilder = new MessagingBuilder([mockMessage, mockMessage])
+        const mockMessageSpy = vi.spyOn(mockMessage, 'dispose')
+        messageBuilder.dispose()
+        expect(mockMessageSpy).toHaveBeenCalledTimes(2)
+    })
+})
+
+suite('RecommendExtensionMessage', () => {
+
+    beforeEach(() => {
+        vi.mocked(window.showInformationMessage).mockClear()
+        vi.mocked(workspace.fs.writeFile).mockClear()
+    })
+
+    test('It should not prompt the user to install the extension when already added', async () => {
+        const recommendExtension = new RecommendExtensionMessage(contextMock)
+        vi.mocked(workspace.fs.stat).mockResolvedValue({ type: FileType.File } as any)
+        await recommendExtension.display()
+        expect(window.showInformationMessage).toHaveBeenCalledTimes(0)
+        expect(workspace.fs.writeFile).toHaveBeenCalledTimes(0)
+    })
+
+    test('It should add the extension when selecting "Yes" option', async () => {
+        const recommendExtension = new RecommendExtensionMessage(contextMock)
+        vi.mocked(workspace.fs.stat).mockResolvedValue({ type: FileType.File } as any)
+        vi.mocked(workspace.openTextDocument as any).mockResolvedValue({
+            getText: vi.fn().mockReturnValue(JSON.stringify({
+                recommendations: ['microsoft.docker']
+            }))
+        })
+        vi.mocked(window.showInformationMessage).mockResolvedValueOnce('Yes' as any)
+        await recommendExtension.display()
+        expect(workspace.fs.writeFile).toBeCalledTimes(1)
+        expect(TelemetryReporter.sendTelemetryEvent)
+            .toBeCalledWith('runme.recommendExtension', { added: 'true', error: 'false' })
+        expect(window.showInformationMessage)
+            .toHaveBeenCalledWith('Runme added successfully to the recommended extensions')
+    })
+
+
+    test('It should create a .vscode folder when needed', async () => {
+        const recommendExtension = new RecommendExtensionMessage(contextMock)
+        vi.mocked(workspace.fs.stat).mockResolvedValue(false as any)
+        vi.mocked(workspace.openTextDocument as any).mockResolvedValue({
+            getText: vi.fn().mockReturnValue(JSON.stringify({
+                recommendations: ['microsoft.docker']
+            }))
+        })
+        vi.mocked(window.showInformationMessage).mockResolvedValueOnce('Yes' as any)
+        await recommendExtension.display()
+        expect(workspace.fs.createDirectory).toBeCalledTimes(1)
+        expect(workspace.fs.createDirectory).toHaveBeenCalledWith(Uri.parse('/runme/workspace/.vscode'))
+        expect(workspace.fs.writeFile).toBeCalledTimes(1)
+        expect(window.showInformationMessage)
+            .toHaveBeenCalledWith('Runme added successfully to the recommended extensions')
+        expect(TelemetryReporter.sendTelemetryEvent)
+            .toBeCalledWith('runme.recommendExtension', { added: 'true', error: 'false' })
+    })
+
+    test('It should create a .vscode/extensions.json file when needed', async () => {
+        const recommendExtension = new RecommendExtensionMessage(contextMock)
+        vi.mocked(workspace.fs.stat).mockImplementation(async (param: Uri) => {
+            return param.path === '/runme/workspace/.vscode/extensions.json'
+                ? { type: FileType.Unknown } :
+                { type: FileType.Directory } as any
+        })
+        vi.mocked(workspace.openTextDocument as any).mockResolvedValue({
+            getText: vi.fn().mockReturnValue(JSON.stringify({
+                recommendations: ['microsoft.docker']
+            }, null, 2))
+        })
+        vi.mocked(window.showInformationMessage).mockResolvedValueOnce('Yes' as any)
+        await recommendExtension.display()
+        const writeFileCalls = vi.mocked(workspace.fs.writeFile).mock.calls[0]
+        expect(workspace.fs.writeFile).toHaveBeenCalledOnce()
+        expect((writeFileCalls[0] as Uri).path).toStrictEqual('/runme/workspace/.vscode/extensions.json')
+        expect((writeFileCalls[1] as Buffer).toString('utf-8')).toStrictEqual(JSON.stringify({
+            recommendations: ['stateful.runme']
+        }, null, 2))
+        expect(window.showInformationMessage)
+            .toHaveBeenCalledWith('Runme added successfully to the recommended extensions')
+        expect(TelemetryReporter.sendTelemetryEvent)
+            .toBeCalledWith('runme.recommendExtension', { added: 'true', error: 'false' })
+    })
+
+    test('It should not add the extension when selecting "No" option', async () => {
+        const recommendExtension = new RecommendExtensionMessage(contextMock)
+        vi.mocked(workspace.fs.stat).mockResolvedValue({ type: FileType.File } as any)
+        vi.mocked(workspace.openTextDocument as any).mockResolvedValue({
+            getText: vi.fn().mockReturnValue(JSON.stringify({
+                recommendations: ['microsoft.docker']
+            }, null, 2))
+        })
+        vi.mocked(window.showInformationMessage).mockResolvedValueOnce('No' as any)
+        await recommendExtension.display()
+        expect(workspace.fs.writeFile).toBeCalledTimes(0)
+        expect(window.showInformationMessage).toBeCalledTimes(1)
+        expect(TelemetryReporter.sendTelemetryEvent)
+            .toBeCalledWith('runme.recommendExtension', { added: 'false', error: 'false' })
+    })
+
+    test('It should report on failure and display a message', async () => {
+        const recommendExtension = new RecommendExtensionMessage(contextMock)
+        vi.mocked(workspace.openTextDocument as any).mockResolvedValue({
+            getText: vi.fn().mockImplementation(new Error('Failure') as any)
+        })
+        await recommendExtension.display()
+        expect(window.showErrorMessage)
+            .toHaveBeenCalledWith('Failed to add Runme to the recommended extensions')
+        expect(TelemetryReporter.sendTelemetryEvent)
+            .toBeCalledWith('runme.recommendExtension', { added: 'false', error: 'true' })
+    })
+
+    test('It should not prompt when selecting don\'t ask again', async () => {
+        const recommendExtension = new RecommendExtensionMessage(contextMock)
+        vi.mocked(workspace.fs.stat).mockResolvedValue({ type: FileType.File } as any)
+        vi.mocked(workspace.openTextDocument as any).mockResolvedValue({
+            getText: vi.fn().mockReturnValue(JSON.stringify({
+                recommendations: ['microsoft.docker']
+            }, null, 2))
+        })
+        vi.mocked(window.showInformationMessage).mockResolvedValueOnce('Don\'t ask again' as any)
+        const spy = vi.spyOn(contextMock.globalState, 'update')
+        await recommendExtension.display()
+        expect(workspace.fs.writeFile).toBeCalledTimes(0)
+        expect(TelemetryReporter.sendTelemetryEvent)
+            .toBeCalledWith('runme.recommendExtension', { added: 'false', error: 'false' })
+        expect(spy).toHaveBeenCalledWith('runme.recommendExtension', false)
+    })
+
+    test('Should not prompt for multi-root workspaces when using the command palette', async () => {
+        const recommendExtension = new RecommendExtensionMessage(contextMock, {
+            'runme.recommendExtension': true
+        })
+        // @ts-expect-error
+        workspace.workspaceFolders = [
+            { uri: Uri.file('/Users/user/Projects/project1') },
+            { uri: Uri.file('/Users/user/Projects/project2') }
+        ]
+        await recommendExtension.display()
+        expect(window.showInformationMessage).toHaveBeenCalledWith('Multi-root workspace are not supported')
+    })
+
+    test('Should not prompt for multi-root workspaces', async () => {
+        const recommendExtension = new RecommendExtensionMessage(contextMock)
+        // @ts-expect-error
+        workspace.workspaceFolders = [
+            { uri: Uri.file('/Users/user/Projects/project1') },
+            { uri: Uri.file('/Users/user/Projects/project2') }
+        ]
+        await recommendExtension.display()
+        expect(window.showInformationMessage).toHaveBeenCalledTimes(0)
+        expect(workspace.fs.writeFile).toHaveBeenCalledTimes(0)
+    })
+})
\ No newline at end of file
diff --git a/tests/extension/serializer.test.ts b/tests/extension/serializer.test.ts
index 99b687d1..1e99114d 100644
--- a/tests/extension/serializer.test.ts
+++ b/tests/extension/serializer.test.ts
@@ -1,10 +1,11 @@
-import { NotebookDocument, NotebookEdit, window, workspace } from 'vscode'
+import { NotebookData, NotebookDocument, NotebookEdit, window, workspace } from 'vscode'
 import { expect, vi, it, describe, beforeEach } from 'vitest'
 
-import { WasmSerializer } from '../../src/extension/serializer'
+import { SerializerBase, WasmSerializer } from '../../src/extension/serializer'
 import { canEditFile } from '../../src/extension/utils'
 import type { Kernel } from '../../src/extension/kernel'
 import { EventEmitter, Uri } from '../../__mocks__/vscode'
+import { Serializer } from '../../src/types'
 
 globalThis.Go = vi.fn()
 globalThis.Runme = { serialize: vi.fn().mockResolvedValue('Hello World!') }
@@ -31,6 +32,15 @@ vi.mock('vscode', () => ({
       updateCellMetadata: (i: number, metadata: any) => ({ i, metadata, type: 'updateCellMetadata' }),
     },
     CancellationTokenSource: vi.fn(),
+    NotebookData: class {
+      constructor(public cells: any[]) { }
+    }
+}))
+
+vi.mock('../../src/extension/languages', () => ({
+  default: {
+    fromContext: vi.fn(),
+  },
 }))
 
 vi.mock('../../src/extension/utils', () => ({
@@ -47,6 +57,56 @@ describe('SerializerBase', () => {
     extensionUri: { fsPath: '/foo/bar' }
   }
 
+  it('serializeNotebook transforms languages', async () => {
+    const TestSerializer = class extends SerializerBase {
+      protected ready: Promise<void | Error> = Promise.resolve()
+
+      protected async saveNotebook(data: NotebookData): Promise<Uint8Array> {
+        return data as any
+      }
+
+      protected async reviveNotebook(content: Uint8Array): Promise<Serializer.Notebook> {
+        return content as any
+      }
+
+      protected async preSaveCheck() { }
+    }
+
+    const serializer = new TestSerializer({} as any, {} as any)
+
+    const processed = await serializer['serializeNotebook']({
+      cells: [
+        {
+          languageId: 'shellscript'
+        },
+        {
+          languageId: 'javascriptreact'
+        },
+        {
+          languageId: 'typescriptreact'
+        },
+        {
+          languageId: 'python',
+        }
+      ]
+    } as any, {} as any) as any
+
+    expect(processed.cells).toStrictEqual([
+      {
+        languageId: 'sh'
+      },
+      {
+        languageId: 'jsx'
+      },
+      {
+        languageId: 'tsx'
+      },
+      {
+        languageId: 'python',
+      }
+    ])
+  })
+
   describe('handleNotebookSaved', () => {
     const _onDidSaveNotebookDocument = new EventEmitter<NotebookDocument>()
 
@@ -134,7 +194,7 @@ describe('WasmSerializer', () => {
             const s = new WasmSerializer(context, newKernel())
             // @ts-ignore readonly
             s['ready'] = Promise.resolve()
-            expect(Buffer.from(await s.serializeNotebook({} as any, {} as any)))
+            expect(Buffer.from(await s.serializeNotebook({ cells: [] } as any, {} as any)))
                 .toEqual(Buffer.from('Hello World!'))
         })
     })
diff --git a/tests/extension/utils.test.ts b/tests/extension/utils.test.ts
index ec63d9e3..c606913f 100644
--- a/tests/extension/utils.test.ts
+++ b/tests/extension/utils.test.ts
@@ -23,7 +23,11 @@ import {
   validateAnnotations,
   setNotebookCategories,
   getNotebookCategories,
-  getNamespacedMid
+  getNamespacedMid,
+  bootFile,
+  isShellLanguage,
+  fileOrDirectoryExists,
+  isMultiRootWorkspace
 } from '../../src/extension/utils'
 import { ENV_STORE, DEFAULT_ENV } from '../../src/extension/constants'
 import { CellAnnotations } from '../../src/types'
@@ -78,6 +82,10 @@ beforeAll(() => {
   ENV_STORE.delete('PATH')
 })
 afterAll(() => { process.env.PATH = PATH })
+beforeEach(() => {
+  vi.mocked(workspace.getConfiguration).mockClear()
+  vi.mocked(commands.executeCommand).mockClear()
+})
 
 test('isInteractive', () => {
   expect(getAnnotations({ metadata: { interactive: 'true' } } as any).interactive).toBe(true)
@@ -92,16 +100,16 @@ test('getTerminalByCell', () => {
   } as any))
     .toBeTruthy()
 
-    expect(getTerminalByCell({
-      metadata: { 'runme.dev/uuid': v4() },
-      kind: 2,
-    } as any))
-      .toBeUndefined()
+  expect(getTerminalByCell({
+    metadata: { 'runme.dev/uuid': v4() },
+    kind: 2,
+  } as any))
+    .toBeUndefined()
 
-    expect(getTerminalByCell({
-      kind: 1,
-    } as any))
-      .toBeUndefined()
+  expect(getTerminalByCell({
+    kind: 1,
+  } as any))
+    .toBeUndefined()
 })
 
 test('resetEnv', () => {
@@ -116,10 +124,22 @@ test('getKey', () => {
     getText: vi.fn().mockReturnValue('foobar'),
     languageId: 'barfoo'
   } as any)).toBe('barfoo')
+
   expect(getKey({
     getText: vi.fn().mockReturnValue('deployctl deploy foobar'),
     languageId: 'something else'
   } as any)).toBe('deno')
+
+  expect(getKey({
+    getText: vi.fn().mockReturnValue(''),
+    languageId: 'shellscript'
+  } as any)).toBe('sh')
+})
+
+test('isShellLanguage', () => {
+  for (const shell of ['bash', 'sh', 'fish', 'ksh', 'zsh', 'shell', 'bat', 'cmd', 'powershell', 'pwsh']) {
+    expect(isShellLanguage(shell)).toBeTruthy()
+  }
 })
 
 suite('getCmdShellSeq', () => {
@@ -621,3 +641,73 @@ test('getNamespacedMid', () => {
     getNamespacedMid('1836968c0b13822b48750c44bbb356d8ae45bebbd8f990c63b2641092a23ba89')
   ).toBe('af4f113b-0bd0-5d95-ac4c-4d354a9cb84a')
 })
+
+suite('bootFile', () => {
+  test('should prefer boot file before settings', async () => {
+    await bootFile()
+    expect(workspace.getConfiguration).toBeCalledTimes(0)
+  })
+
+  test('should load file defined in settings', async () => {
+    vi.mocked(workspace.getConfiguration).mockReturnValue({
+      get: () => '/foo/Settings.md'
+    } as any)
+    vi.mocked(workspace.fs.stat).mockImplementation(
+      async (uri) => uri.path.endsWith('Settings.md')
+        ? true
+        : Promise.reject(new Error('not existing')) as any
+    )
+    await bootFile()
+    expect(commands.executeCommand).toBeCalledWith('vscode.openWith', expect.objectContaining({
+      path: '/foo/bar/foo/Settings.md'
+    }), 'runme')
+  })
+})
+
+suite('fileOrDirectoryExists', () => {
+  test('should return false when file does not exists', async () => {
+    vi.mocked(workspace.fs.stat).mockResolvedValue({ type: FileType.Unknown } as any)
+    const path = Uri.parse('.vscode-mango')
+    const exists = await fileOrDirectoryExists(path)
+    expect(exists).toBe(false)
+  })
+
+  test('should return true when file does exists', async () => {
+    vi.mocked(workspace.fs.stat).mockResolvedValue({ type: FileType.File } as any)
+    const path = Uri.parse('.vscode-mango')
+    const exists = await fileOrDirectoryExists(path)
+    expect(exists).toBe(true)
+  })
+
+  test('should return true when directory does exists', async () => {
+    vi.mocked(workspace.fs.stat).mockResolvedValue({ type: FileType.Directory } as any)
+    const path = Uri.parse('.vscode-mango')
+    const exists = await fileOrDirectoryExists(path)
+    expect(exists).toBe(true)
+  })
+})
+
+suite('isMultiRootWorkspace', () => {
+  test('should return true when there are multiple workspace folders', () => {
+    // @ts-expect-error
+    workspace.workspaceFolders = [
+      { uri: Uri.file('/Users/user/Projects/project1') },
+      { uri: Uri.file('/Users/user/Projects/project2') }
+    ]
+    expect(isMultiRootWorkspace()).toStrictEqual(true)
+  })
+
+  test('should return false when there is one workspace folder', () => {
+    // @ts-expect-error
+    workspace.workspaceFolders = [
+      { uri: Uri.file('/Users/user/Projects/project1') }
+    ]
+    expect(isMultiRootWorkspace()).toStrictEqual(false)
+  })
+
+  test('should return false when workspaceFolders are not defined', () => {
+    // @ts-expect-error
+    workspace.workspaceFolders = undefined
+    expect(isMultiRootWorkspace()).toStrictEqual(false)
+  })
+})