Repository: stateful/vscode-runme
Pull request #291 (2023-03-10 22:30:31)

Use gRPC health check for runme server
Detect whether gRPC server exists using `grpc/grpc` healthcheck rather than using the parser client directly. This is more in line with gRPC standard practice and gives us more options down the line.


Patch:
diff --git a/package-lock.json b/package-lock.json
index 540667b5..7c1898f8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,17 +1,17 @@
 {
   "name": "runme",
-  "version": "1.0.2",
+  "version": "1.0.3",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "runme",
-      "version": "1.0.2",
+      "version": "1.0.3",
       "hasInstallScript": true,
       "license": "Apache-2.0",
       "dependencies": {
+        "@buf/grpc_grpc.community_timostamm-protobuf-ts": "^2.8.2-20230310150327-f6c75491ea13.1",
         "@buf/stateful_runme.community_timostamm-protobuf-ts": "^2.8.2-20230307185721-114a366533aa.1",
-        "@grpc/grpc-js": "^1.8.4",
         "@protobuf-ts/grpc-transport": "^2.8.2",
         "@protobuf-ts/runtime": "^2.8.2",
         "@protobuf-ts/runtime-rpc": "^2.8.2",
@@ -1730,6 +1730,91 @@
       "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
       "dev": true
     },
+    "node_modules/@buf/cncf_xds.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20230106150209-c313df85559e.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/cncf_xds.community_timostamm-protobuf-ts/2.8.2-20230106150209-c313df85559e.1/tarball",
+      "dependencies": {
+        "@buf/envoyproxy_protoc-gen-validate.community_timostamm-protobuf-ts": "2.8.2-20221025150516-6607b10f00ed.1",
+        "@buf/googleapis_googleapis.community_timostamm-protobuf-ts": "2.8.2-20221214150216-75b4300737fb.1"
+      },
+      "peerDependencies": {
+        "@protobuf-ts/runtime": "^2.8.2",
+        "@protobuf-ts/runtime-rpc": "^2.8.2"
+      }
+    },
+    "node_modules/@buf/envoyproxy_envoy.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20230310150312-30eb1ed13cd9.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/envoyproxy_envoy.community_timostamm-protobuf-ts/2.8.2-20230310150312-30eb1ed13cd9.1/tarball",
+      "dependencies": {
+        "@buf/cncf_xds.community_timostamm-protobuf-ts": "2.8.2-20230106150209-c313df85559e.1",
+        "@buf/envoyproxy_protoc-gen-validate.community_timostamm-protobuf-ts": "2.8.2-20221025150516-6607b10f00ed.1",
+        "@buf/googleapis_googleapis.community_timostamm-protobuf-ts": "2.8.2-20221214150216-75b4300737fb.1",
+        "@buf/opencensus_opencensus.community_timostamm-protobuf-ts": "2.8.2-20220922204435-bc2645b08553.1",
+        "@buf/opentelemetry_opentelemetry.community_timostamm-protobuf-ts": "2.8.2-20230127150227-43554dfbbfbd.1",
+        "@buf/prometheus_client-model.community_timostamm-protobuf-ts": "2.8.2-20220112145104-1d56a02d481a.1"
+      },
+      "peerDependencies": {
+        "@protobuf-ts/runtime": "^2.8.2",
+        "@protobuf-ts/runtime-rpc": "^2.8.2"
+      }
+    },
+    "node_modules/@buf/envoyproxy_protoc-gen-validate.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20221025150516-6607b10f00ed.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/envoyproxy_protoc-gen-validate.community_timostamm-protobuf-ts/2.8.2-20221025150516-6607b10f00ed.1/tarball",
+      "peerDependencies": {
+        "@protobuf-ts/runtime": "^2.8.2",
+        "@protobuf-ts/runtime-rpc": "^2.8.2"
+      }
+    },
+    "node_modules/@buf/googleapis_googleapis.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20221214150216-75b4300737fb.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/googleapis_googleapis.community_timostamm-protobuf-ts/2.8.2-20221214150216-75b4300737fb.1/tarball",
+      "peerDependencies": {
+        "@protobuf-ts/runtime": "^2.8.2",
+        "@protobuf-ts/runtime-rpc": "^2.8.2"
+      }
+    },
+    "node_modules/@buf/grpc_grpc.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20230310150327-f6c75491ea13.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/grpc_grpc.community_timostamm-protobuf-ts/2.8.2-20230310150327-f6c75491ea13.1/tarball",
+      "dependencies": {
+        "@buf/cncf_xds.community_timostamm-protobuf-ts": "2.8.2-20230106150209-c313df85559e.1",
+        "@buf/envoyproxy_envoy.community_timostamm-protobuf-ts": "2.8.2-20230310150312-30eb1ed13cd9.1",
+        "@buf/envoyproxy_protoc-gen-validate.community_timostamm-protobuf-ts": "2.8.2-20221025150516-6607b10f00ed.1",
+        "@buf/googleapis_googleapis.community_timostamm-protobuf-ts": "2.8.2-20221214150216-75b4300737fb.1",
+        "@buf/opencensus_opencensus.community_timostamm-protobuf-ts": "2.8.2-20220922204435-bc2645b08553.1",
+        "@buf/opentelemetry_opentelemetry.community_timostamm-protobuf-ts": "2.8.2-20230127150227-43554dfbbfbd.1",
+        "@buf/prometheus_client-model.community_timostamm-protobuf-ts": "2.8.2-20220112145104-1d56a02d481a.1"
+      },
+      "peerDependencies": {
+        "@protobuf-ts/runtime": "^2.8.2",
+        "@protobuf-ts/runtime-rpc": "^2.8.2"
+      }
+    },
+    "node_modules/@buf/opencensus_opencensus.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20220922204435-bc2645b08553.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/opencensus_opencensus.community_timostamm-protobuf-ts/2.8.2-20220922204435-bc2645b08553.1/tarball",
+      "peerDependencies": {
+        "@protobuf-ts/runtime": "^2.8.2",
+        "@protobuf-ts/runtime-rpc": "^2.8.2"
+      }
+    },
+    "node_modules/@buf/opentelemetry_opentelemetry.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20230127150227-43554dfbbfbd.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/opentelemetry_opentelemetry.community_timostamm-protobuf-ts/2.8.2-20230127150227-43554dfbbfbd.1/tarball",
+      "peerDependencies": {
+        "@protobuf-ts/runtime": "^2.8.2",
+        "@protobuf-ts/runtime-rpc": "^2.8.2"
+      }
+    },
+    "node_modules/@buf/prometheus_client-model.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20220112145104-1d56a02d481a.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/prometheus_client-model.community_timostamm-protobuf-ts/2.8.2-20220112145104-1d56a02d481a.1/tarball",
+      "peerDependencies": {
+        "@protobuf-ts/runtime": "^2.8.2",
+        "@protobuf-ts/runtime-rpc": "^2.8.2"
+      }
+    },
     "node_modules/@buf/stateful_runme.community_timostamm-protobuf-ts": {
       "version": "2.8.2-20230307185721-114a366533aa.1",
       "resolved": "https://buf.build/gen/npm/v1/@buf/stateful_runme.community_timostamm-protobuf-ts/2.8.2-20230307185721-114a366533aa.1/tarball",
@@ -2386,6 +2471,7 @@
       "version": "1.8.13",
       "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.13.tgz",
       "integrity": "sha512-iY3jsdfbc0ARoCLFvbvUB8optgyb0r1XLPb142u+QtgBcKJYkCIFt3Fd/881KqjLYWjsBJF57N3b8Eop9NDfUA==",
+      "peer": true,
       "dependencies": {
         "@grpc/proto-loader": "^0.7.0",
         "@types/node": ">=12.12.47"
@@ -2398,6 +2484,7 @@
       "version": "0.7.5",
       "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.5.tgz",
       "integrity": "sha512-mfcTuMbFowq1wh/Rn5KQl6qb95M21Prej3bewD9dUQMurYGVckGO/Pbe2Ocwto6sD05b/mxZLspvqwx60xO2Rg==",
+      "peer": true,
       "dependencies": {
         "@types/long": "^4.0.1",
         "lodash.camelcase": "^4.3.0",
@@ -2416,6 +2503,7 @@
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
       "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "peer": true,
       "dependencies": {
         "color-convert": "^2.0.1"
       },
@@ -2430,6 +2518,7 @@
       "version": "7.0.4",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
       "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+      "peer": true,
       "dependencies": {
         "string-width": "^4.2.0",
         "strip-ansi": "^6.0.0",
@@ -2440,6 +2529,7 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
       "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "peer": true,
       "dependencies": {
         "color-name": "~1.1.4"
       },
@@ -2450,17 +2540,20 @@
     "node_modules/@grpc/proto-loader/node_modules/color-name": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "peer": true
     },
     "node_modules/@grpc/proto-loader/node_modules/emoji-regex": {
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "peer": true
     },
     "node_modules/@grpc/proto-loader/node_modules/is-fullwidth-code-point": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
       "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "peer": true,
       "engines": {
         "node": ">=8"
       }
@@ -2469,6 +2562,7 @@
       "version": "4.2.3",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "peer": true,
       "dependencies": {
         "emoji-regex": "^8.0.0",
         "is-fullwidth-code-point": "^3.0.0",
@@ -2482,6 +2576,7 @@
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
       "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "peer": true,
       "dependencies": {
         "ansi-styles": "^4.0.0",
         "string-width": "^4.1.0",
@@ -2498,6 +2593,7 @@
       "version": "16.2.0",
       "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
       "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+      "peer": true,
       "dependencies": {
         "cliui": "^7.0.2",
         "escalade": "^3.1.1",
@@ -3156,27 +3252,32 @@
     "node_modules/@protobufjs/aspromise": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
-      "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
+      "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+      "peer": true
     },
     "node_modules/@protobufjs/base64": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
-      "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
+      "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+      "peer": true
     },
     "node_modules/@protobufjs/codegen": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
-      "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
+      "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+      "peer": true
     },
     "node_modules/@protobufjs/eventemitter": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
-      "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
+      "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+      "peer": true
     },
     "node_modules/@protobufjs/fetch": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
       "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+      "peer": true,
       "dependencies": {
         "@protobufjs/aspromise": "^1.1.1",
         "@protobufjs/inquire": "^1.1.0"
@@ -3185,27 +3286,32 @@
     "node_modules/@protobufjs/float": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
-      "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
+      "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+      "peer": true
     },
     "node_modules/@protobufjs/inquire": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
-      "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
+      "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
+      "peer": true
     },
     "node_modules/@protobufjs/path": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
-      "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
+      "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+      "peer": true
     },
     "node_modules/@protobufjs/pool": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
-      "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
+      "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+      "peer": true
     },
     "node_modules/@protobufjs/utf8": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
-      "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
+      "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
+      "peer": true
     },
     "node_modules/@remix-run/dev": {
       "name": "@vercel/remix-run-dev",
@@ -4075,7 +4181,8 @@
     "node_modules/@types/long": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
-      "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
+      "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
+      "peer": true
     },
     "node_modules/@types/lru-cache": {
       "version": "4.1.1",
@@ -5866,6 +5973,7 @@
       "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.6.2.tgz",
       "integrity": "sha512-vmvau1rGqyWkmVLOeWtdw7Q1rE12BJDaGp//G9ZOVMEmpTLqjuof+wB9eCXjVvpxyOcl4GWiz7xpTnXUPwoydQ==",
       "dev": true,
+      "optional": true,
       "dependencies": {
         "@wdio/logger": "8.1.0",
         "@wdio/types": "8.4.0",
@@ -5885,6 +5993,7 @@
       "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.6.1.tgz",
       "integrity": "sha512-F/ITsHOG6Q1RxZaQuGcuti/noqxWmgkYQmRM8XdqZe0gSy4VdflGfc6TKCnfcn2oV3VPAX2HiORdTZnEGvzH2g==",
       "dev": true,
+      "optional": true,
       "dependencies": {
         "@wdio/logger": "8.1.0",
         "@wdio/types": "8.4.0",
@@ -5900,6 +6009,7 @@
       "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.0.tgz",
       "integrity": "sha512-EAZejC7JvnQINayvB/7BJbpZpNOJ8Lrw2OZNEvQxe0vaLn1SuwMcfV7/MNaX8L/T0wmptBFI4YMtDvSBxYDc7w==",
       "dev": true,
+      "optional": true,
       "dependencies": {
         "fs.realpath": "^1.0.0",
         "minimatch": "^7.4.1",
@@ -5918,6 +6028,7 @@
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.2.tgz",
       "integrity": "sha512-xy4q7wou3vUoC9k1xGTXc+awNdGaGVHtFUaey8tiX4H1QRc04DZ/rmDFwNm2EBsuYEhAZ6SgMmYf3InGY6OauA==",
       "dev": true,
+      "optional": true,
       "dependencies": {
         "brace-expansion": "^2.0.1"
       },
@@ -5933,6 +6044,7 @@
       "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz",
       "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==",
       "dev": true,
+      "optional": true,
       "engines": {
         "node": ">=8"
       }
@@ -5942,6 +6054,7 @@
       "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.6.3.tgz",
       "integrity": "sha512-JnoBsPx4puebxgrkdwYEYYDdYzRXjckDVS/FEhEnU6MbEX2me328VwdEIP0Q74igZaaFoRGdihfXmGgP+pUHkw==",
       "dev": true,
+      "optional": true,
       "engines": {
         "node": "^16.13 || >=18"
       },
@@ -6359,13 +6472,15 @@
       "version": "8.5.7",
       "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.5.7.tgz",
       "integrity": "sha512-ymdXSRqHugEptLdjLnvX7m7TY6cvae1B1yiFJVpaKwj88s4PaCUs7aISexC0ES9s6z8ZWRjZo3mrKPlwZ/IKCw==",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "node_modules/@wdio/repl": {
       "version": "8.1.0",
       "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.1.0.tgz",
       "integrity": "sha512-96G4TzbYnRf95+GURo15FYt6iTYq85nbWU6YQedLRAV15RfSp4foKTbAnq++bKKMALNL6gdzTc+HGhQr3Q0sQg==",
       "dev": true,
+      "optional": true,
       "dependencies": {
         "@types/node": "^18.0.0"
       },
@@ -6667,6 +6782,7 @@
       "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.4.0.tgz",
       "integrity": "sha512-1eA0D0jS8Ttg67zB2gsZJFUcHcRz4VRjLTjxdLKh70+ZfB1+YZr9tScLgQjc+qsjsK1wKSzOz03uZiidwNnN9g==",
       "dev": true,
+      "optional": true,
       "dependencies": {
         "@types/node": "^18.0.0"
       },
@@ -9624,6 +9740,7 @@
       "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-4.3.0.tgz",
       "integrity": "sha512-if3ZYdkD2dClhnXR5reKtG98cwyaRT1NeugQoAPTTfsOpV9kqyeiBF9Qa5RHjemb3KzD5ulqygv6ED3t5j9eJw==",
       "dev": true,
+      "optional": true,
       "engines": {
         "node": ">=12.4.0"
       }
@@ -15764,7 +15881,8 @@
     "node_modules/long": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
-      "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+      "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
+      "peer": true
     },
     "node_modules/longest-streak": {
       "version": "3.1.0",
@@ -18768,6 +18886,7 @@
       "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.2.tgz",
       "integrity": "sha512-++PrQIjrom+bFDPpfmqXfAGSQs40116JRrqqyf53dymUMvvb5d/LMRyicRoF1AUKoXVS1/IgJXlEgcpr4gTF3Q==",
       "hasInstallScript": true,
+      "peer": true,
       "dependencies": {
         "@protobufjs/aspromise": "^1.1.2",
         "@protobufjs/base64": "^1.1.2",
@@ -18789,7 +18908,8 @@
     "node_modules/protobufjs/node_modules/long": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
-      "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A=="
+      "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==",
+      "peer": true
     },
     "node_modules/proxy-addr": {
       "version": "2.0.7",
@@ -22951,6 +23071,7 @@
       "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.6.2.tgz",
       "integrity": "sha512-jgnu/hB7O8hrjCZm6zW77QhBEdwteQF+liWvjZcflQXUmS/YLdS/UAfCBQzt1CIUHfbGrlxHshwFm+ywjT9XJw==",
       "dev": true,
+      "optional": true,
       "dependencies": {
         "@types/node": "^18.0.0",
         "@types/ws": "^8.5.3",
@@ -22973,6 +23094,7 @@
       "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.6.1.tgz",
       "integrity": "sha512-F/ITsHOG6Q1RxZaQuGcuti/noqxWmgkYQmRM8XdqZe0gSy4VdflGfc6TKCnfcn2oV3VPAX2HiORdTZnEGvzH2g==",
       "dev": true,
+      "optional": true,
       "dependencies": {
         "@wdio/logger": "8.1.0",
         "@wdio/types": "8.4.0",
@@ -22988,6 +23110,7 @@
       "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
       "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
       "dev": true,
+      "optional": true,
       "engines": {
         "node": ">=10.0.0"
       },
@@ -25453,6 +25576,64 @@
       "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
       "dev": true
     },
+    "@buf/cncf_xds.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20230106150209-c313df85559e.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/cncf_xds.community_timostamm-protobuf-ts/2.8.2-20230106150209-c313df85559e.1/tarball",
+      "requires": {
+        "@buf/envoyproxy_protoc-gen-validate.community_timostamm-protobuf-ts": "2.8.2-20221025150516-6607b10f00ed.1",
+        "@buf/googleapis_googleapis.community_timostamm-protobuf-ts": "2.8.2-20221214150216-75b4300737fb.1"
+      }
+    },
+    "@buf/envoyproxy_envoy.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20230310150312-30eb1ed13cd9.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/envoyproxy_envoy.community_timostamm-protobuf-ts/2.8.2-20230310150312-30eb1ed13cd9.1/tarball",
+      "requires": {
+        "@buf/cncf_xds.community_timostamm-protobuf-ts": "2.8.2-20230106150209-c313df85559e.1",
+        "@buf/envoyproxy_protoc-gen-validate.community_timostamm-protobuf-ts": "2.8.2-20221025150516-6607b10f00ed.1",
+        "@buf/googleapis_googleapis.community_timostamm-protobuf-ts": "2.8.2-20221214150216-75b4300737fb.1",
+        "@buf/opencensus_opencensus.community_timostamm-protobuf-ts": "2.8.2-20220922204435-bc2645b08553.1",
+        "@buf/opentelemetry_opentelemetry.community_timostamm-protobuf-ts": "2.8.2-20230127150227-43554dfbbfbd.1",
+        "@buf/prometheus_client-model.community_timostamm-protobuf-ts": "2.8.2-20220112145104-1d56a02d481a.1"
+      }
+    },
+    "@buf/envoyproxy_protoc-gen-validate.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20221025150516-6607b10f00ed.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/envoyproxy_protoc-gen-validate.community_timostamm-protobuf-ts/2.8.2-20221025150516-6607b10f00ed.1/tarball",
+      "requires": {}
+    },
+    "@buf/googleapis_googleapis.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20221214150216-75b4300737fb.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/googleapis_googleapis.community_timostamm-protobuf-ts/2.8.2-20221214150216-75b4300737fb.1/tarball",
+      "requires": {}
+    },
+    "@buf/grpc_grpc.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20230310150327-f6c75491ea13.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/grpc_grpc.community_timostamm-protobuf-ts/2.8.2-20230310150327-f6c75491ea13.1/tarball",
+      "requires": {
+        "@buf/cncf_xds.community_timostamm-protobuf-ts": "2.8.2-20230106150209-c313df85559e.1",
+        "@buf/envoyproxy_envoy.community_timostamm-protobuf-ts": "2.8.2-20230310150312-30eb1ed13cd9.1",
+        "@buf/envoyproxy_protoc-gen-validate.community_timostamm-protobuf-ts": "2.8.2-20221025150516-6607b10f00ed.1",
+        "@buf/googleapis_googleapis.community_timostamm-protobuf-ts": "2.8.2-20221214150216-75b4300737fb.1",
+        "@buf/opencensus_opencensus.community_timostamm-protobuf-ts": "2.8.2-20220922204435-bc2645b08553.1",
+        "@buf/opentelemetry_opentelemetry.community_timostamm-protobuf-ts": "2.8.2-20230127150227-43554dfbbfbd.1",
+        "@buf/prometheus_client-model.community_timostamm-protobuf-ts": "2.8.2-20220112145104-1d56a02d481a.1"
+      }
+    },
+    "@buf/opencensus_opencensus.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20220922204435-bc2645b08553.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/opencensus_opencensus.community_timostamm-protobuf-ts/2.8.2-20220922204435-bc2645b08553.1/tarball",
+      "requires": {}
+    },
+    "@buf/opentelemetry_opentelemetry.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20230127150227-43554dfbbfbd.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/opentelemetry_opentelemetry.community_timostamm-protobuf-ts/2.8.2-20230127150227-43554dfbbfbd.1/tarball",
+      "requires": {}
+    },
+    "@buf/prometheus_client-model.community_timostamm-protobuf-ts": {
+      "version": "2.8.2-20220112145104-1d56a02d481a.1",
+      "resolved": "https://buf.build/gen/npm/v1/@buf/prometheus_client-model.community_timostamm-protobuf-ts/2.8.2-20220112145104-1d56a02d481a.1/tarball",
+      "requires": {}
+    },
     "@buf/stateful_runme.community_timostamm-protobuf-ts": {
       "version": "2.8.2-20230307185721-114a366533aa.1",
       "resolved": "https://buf.build/gen/npm/v1/@buf/stateful_runme.community_timostamm-protobuf-ts/2.8.2-20230307185721-114a366533aa.1/tarball",
@@ -25857,6 +26038,7 @@
       "version": "1.8.13",
       "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.13.tgz",
       "integrity": "sha512-iY3jsdfbc0ARoCLFvbvUB8optgyb0r1XLPb142u+QtgBcKJYkCIFt3Fd/881KqjLYWjsBJF57N3b8Eop9NDfUA==",
+      "peer": true,
       "requires": {
         "@grpc/proto-loader": "^0.7.0",
         "@types/node": ">=12.12.47"
@@ -25866,6 +26048,7 @@
       "version": "0.7.5",
       "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.5.tgz",
       "integrity": "sha512-mfcTuMbFowq1wh/Rn5KQl6qb95M21Prej3bewD9dUQMurYGVckGO/Pbe2Ocwto6sD05b/mxZLspvqwx60xO2Rg==",
+      "peer": true,
       "requires": {
         "@types/long": "^4.0.1",
         "lodash.camelcase": "^4.3.0",
@@ -25878,6 +26061,7 @@
           "version": "4.3.0",
           "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
           "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "peer": true,
           "requires": {
             "color-convert": "^2.0.1"
           }
@@ -25886,6 +26070,7 @@
           "version": "7.0.4",
           "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
           "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+          "peer": true,
           "requires": {
             "string-width": "^4.2.0",
             "strip-ansi": "^6.0.0",
@@ -25896,6 +26081,7 @@
           "version": "2.0.1",
           "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
           "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "peer": true,
           "requires": {
             "color-name": "~1.1.4"
           }
@@ -25903,22 +26089,26 @@
         "color-name": {
           "version": "1.1.4",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+          "peer": true
         },
         "emoji-regex": {
           "version": "8.0.0",
           "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+          "peer": true
         },
         "is-fullwidth-code-point": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+          "peer": true
         },
         "string-width": {
           "version": "4.2.3",
           "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
           "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+          "peer": true,
           "requires": {
             "emoji-regex": "^8.0.0",
             "is-fullwidth-code-point": "^3.0.0",
@@ -25929,6 +26119,7 @@
           "version": "7.0.0",
           "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
           "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+          "peer": true,
           "requires": {
             "ansi-styles": "^4.0.0",
             "string-width": "^4.1.0",
@@ -25939,6 +26130,7 @@
           "version": "16.2.0",
           "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
           "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+          "peer": true,
           "requires": {
             "cliui": "^7.0.2",
             "escalade": "^3.1.1",
@@ -26473,27 +26665,32 @@
     "@protobufjs/aspromise": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
-      "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
+      "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+      "peer": true
     },
     "@protobufjs/base64": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
-      "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
+      "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+      "peer": true
     },
     "@protobufjs/codegen": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
-      "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
+      "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+      "peer": true
     },
     "@protobufjs/eventemitter": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
-      "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
+      "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+      "peer": true
     },
     "@protobufjs/fetch": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
       "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+      "peer": true,
       "requires": {
         "@protobufjs/aspromise": "^1.1.1",
         "@protobufjs/inquire": "^1.1.0"
@@ -26502,27 +26699,32 @@
     "@protobufjs/float": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
-      "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
+      "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+      "peer": true
     },
     "@protobufjs/inquire": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
-      "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
+      "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
+      "peer": true
     },
     "@protobufjs/path": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
-      "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
+      "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+      "peer": true
     },
     "@protobufjs/pool": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
-      "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
+      "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+      "peer": true
     },
     "@protobufjs/utf8": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
-      "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
+      "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
+      "peer": true
     },
     "@remix-run/dev": {
       "version": "npm:@vercel/remix-run-dev@1.14.2",
@@ -27193,7 +27395,8 @@
     "@types/long": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
-      "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
+      "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
+      "peer": true
     },
     "@types/lru-cache": {
       "version": "4.1.1",
@@ -28487,6 +28690,7 @@
       "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.6.2.tgz",
       "integrity": "sha512-vmvau1rGqyWkmVLOeWtdw7Q1rE12BJDaGp//G9ZOVMEmpTLqjuof+wB9eCXjVvpxyOcl4GWiz7xpTnXUPwoydQ==",
       "dev": true,
+      "optional": true,
       "requires": {
         "@wdio/logger": "8.1.0",
         "@wdio/types": "8.4.0",
@@ -28503,6 +28707,7 @@
           "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.6.1.tgz",
           "integrity": "sha512-F/ITsHOG6Q1RxZaQuGcuti/noqxWmgkYQmRM8XdqZe0gSy4VdflGfc6TKCnfcn2oV3VPAX2HiORdTZnEGvzH2g==",
           "dev": true,
+          "optional": true,
           "requires": {
             "@wdio/logger": "8.1.0",
             "@wdio/types": "8.4.0",
@@ -28515,6 +28720,7 @@
           "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.0.tgz",
           "integrity": "sha512-EAZejC7JvnQINayvB/7BJbpZpNOJ8Lrw2OZNEvQxe0vaLn1SuwMcfV7/MNaX8L/T0wmptBFI4YMtDvSBxYDc7w==",
           "dev": true,
+          "optional": true,
           "requires": {
             "fs.realpath": "^1.0.0",
             "minimatch": "^7.4.1",
@@ -28527,6 +28733,7 @@
           "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.2.tgz",
           "integrity": "sha512-xy4q7wou3vUoC9k1xGTXc+awNdGaGVHtFUaey8tiX4H1QRc04DZ/rmDFwNm2EBsuYEhAZ6SgMmYf3InGY6OauA==",
           "dev": true,
+          "optional": true,
           "requires": {
             "brace-expansion": "^2.0.1"
           }
@@ -28535,7 +28742,8 @@
           "version": "4.2.5",
           "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz",
           "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==",
-          "dev": true
+          "dev": true,
+          "optional": true
         }
       }
     },
@@ -28544,6 +28752,7 @@
       "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.6.3.tgz",
       "integrity": "sha512-JnoBsPx4puebxgrkdwYEYYDdYzRXjckDVS/FEhEnU6MbEX2me328VwdEIP0Q74igZaaFoRGdihfXmGgP+pUHkw==",
       "dev": true,
+      "optional": true,
       "requires": {
         "expect-webdriverio": "^4.0.1",
         "webdriverio": "8.6.3"
@@ -28853,13 +29062,15 @@
       "version": "8.5.7",
       "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.5.7.tgz",
       "integrity": "sha512-ymdXSRqHugEptLdjLnvX7m7TY6cvae1B1yiFJVpaKwj88s4PaCUs7aISexC0ES9s6z8ZWRjZo3mrKPlwZ/IKCw==",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "@wdio/repl": {
       "version": "8.1.0",
       "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.1.0.tgz",
       "integrity": "sha512-96G4TzbYnRf95+GURo15FYt6iTYq85nbWU6YQedLRAV15RfSp4foKTbAnq++bKKMALNL6gdzTc+HGhQr3Q0sQg==",
       "dev": true,
+      "optional": true,
       "requires": {
         "@types/node": "^18.0.0"
       }
@@ -29081,6 +29292,7 @@
       "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.4.0.tgz",
       "integrity": "sha512-1eA0D0jS8Ttg67zB2gsZJFUcHcRz4VRjLTjxdLKh70+ZfB1+YZr9tScLgQjc+qsjsK1wKSzOz03uZiidwNnN9g==",
       "dev": true,
+      "optional": true,
       "requires": {
         "@types/node": "^18.0.0"
       }
@@ -31342,7 +31554,8 @@
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-4.3.0.tgz",
       "integrity": "sha512-if3ZYdkD2dClhnXR5reKtG98cwyaRT1NeugQoAPTTfsOpV9kqyeiBF9Qa5RHjemb3KzD5ulqygv6ED3t5j9eJw==",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "defaults": {
       "version": "1.0.4",
@@ -35927,7 +36140,8 @@
     "long": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
-      "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+      "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
+      "peer": true
     },
     "longest-streak": {
       "version": "3.1.0",
@@ -38038,6 +38252,7 @@
       "version": "7.2.2",
       "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.2.tgz",
       "integrity": "sha512-++PrQIjrom+bFDPpfmqXfAGSQs40116JRrqqyf53dymUMvvb5d/LMRyicRoF1AUKoXVS1/IgJXlEgcpr4gTF3Q==",
+      "peer": true,
       "requires": {
         "@protobufjs/aspromise": "^1.1.2",
         "@protobufjs/base64": "^1.1.2",
@@ -38056,7 +38271,8 @@
         "long": {
           "version": "5.2.1",
           "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
-          "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A=="
+          "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==",
+          "peer": true
         }
       }
     },
@@ -41002,6 +41218,7 @@
       "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.6.2.tgz",
       "integrity": "sha512-jgnu/hB7O8hrjCZm6zW77QhBEdwteQF+liWvjZcflQXUmS/YLdS/UAfCBQzt1CIUHfbGrlxHshwFm+ywjT9XJw==",
       "dev": true,
+      "optional": true,
       "requires": {
         "@types/node": "^18.0.0",
         "@types/ws": "^8.5.3",
@@ -41021,6 +41238,7 @@
           "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.6.1.tgz",
           "integrity": "sha512-F/ITsHOG6Q1RxZaQuGcuti/noqxWmgkYQmRM8XdqZe0gSy4VdflGfc6TKCnfcn2oV3VPAX2HiORdTZnEGvzH2g==",
           "dev": true,
+          "optional": true,
           "requires": {
             "@wdio/logger": "8.1.0",
             "@wdio/types": "8.4.0",
@@ -41033,6 +41251,7 @@
           "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
           "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
           "dev": true,
+          "optional": true,
           "requires": {}
         }
       }
diff --git a/package.json b/package.json
index b4e76982..3fd41c54 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "runme",
-  "version": "1.0.2",
+  "version": "1.0.3",
   "publisher": "stateful",
   "displayName": "Runme",
   "description": "Run your README.md as an interactive notebook",
@@ -52,6 +52,7 @@
   "activationEvents": [
     "onNotebook:runme",
     "onRenderer:runme-renderer",
+    "onCommand:runme.welcome",
     "onStartupFinished"
   ],
   "main": "./out/extension.js",
@@ -82,6 +83,11 @@
         "category": "Runme",
         "title": "Reset Environment Variables for current VS Code session"
       },
+      {
+        "command": "runme.surveyWinDefaultShell",
+        "category": "Runme",
+        "title": "Prompt to participate in Windows Default Shell survey"
+      },
       {
         "command": "runme.openAsRunmeNotebook",
         "category": "Runme",
@@ -113,6 +119,18 @@
         "category": "Runme",
         "title": "Expand all items",
         "icon": "$(search-expand-results)"
+      },
+      {
+        "command": "runme.welcome",
+        "title": "Welcome",
+        "category": "Runme",
+        "description": "Open the welcome page for the Runme extension."
+      },
+      {
+        "command": "runme.try",
+        "title": "Try Runme",
+        "category": "Runme",
+        "description": "Walk through some interactive notebook examples."
       }
     ],
     "menus": {
@@ -472,7 +490,7 @@
   },
   "dependencies": {
     "@buf/stateful_runme.community_timostamm-protobuf-ts": "^2.8.2-20230307185721-114a366533aa.1",
-    "@grpc/grpc-js": "^1.8.4",
+    "@buf/grpc_grpc.community_timostamm-protobuf-ts": "^2.8.2-20230310150327-f6c75491ea13.1",
     "@protobuf-ts/grpc-transport": "^2.8.2",
     "@protobuf-ts/runtime": "^2.8.2",
     "@protobuf-ts/runtime-rpc": "^2.8.2",
diff --git a/src/client/components/terminal.ts b/src/client/components/terminal.ts
index 0d8eea93..ae544a16 100644
--- a/src/client/components/terminal.ts
+++ b/src/client/components/terminal.ts
@@ -9,12 +9,7 @@ import { Unicode11Addon } from 'xterm-addon-unicode11'
 import { ClientMessages } from '../../constants'
 import { getContext } from '../utils'
 import { onClientMessage, postClientMessage } from '../../utils/messaging'
-
-
-interface IWindowSize {
-  width: number
-  height: number
-}
+import { stripANSI } from '../../utils/ansi'
 
 const vscodeCSS = (...identifiers: string[]) => `--vscode-${identifiers.join('-')}`
 const terminalCSS = (id: string) => vscodeCSS('terminal', id)
@@ -219,13 +214,41 @@ export class TerminalView extends LitElement {
         z-index: 2;
         position: relative;
     }
+
+    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;
+    }
+
+    .button-group {
+      display: flex;
+      flex-direction: row;
+      justify-content: end;
+    }
+
+    section {
+      display: flex;
+      flex-direction: column;
+      gap: 5px;
+    }
   `
 
   protected disposables: Disposable[] = []
   protected terminal?: XTermJS
   protected fitAddon?: FitAddon
   protected serializer?: SerializeAddon
-  protected windowSize: IWindowSize
 
   @property({ type: String })
   uuid?: string
@@ -244,10 +267,6 @@ export class TerminalView extends LitElement {
 
   constructor() {
     super()
-    this.windowSize = {
-      height: window.innerHeight,
-      width: window.innerWidth
-    }
   }
 
   connectedCallback(): void {
@@ -274,9 +293,9 @@ export class TerminalView extends LitElement {
     }
 
     this.fitAddon = new FitAddon()
+    this.fitAddon.activate(this.terminal!)
     this.serializer = new SerializeAddon()
     const unicode11Addon = new Unicode11Addon()
-    this.terminal.loadAddon(this.fitAddon)
     this.terminal.loadAddon(this.serializer)
     this.terminal.loadAddon(unicode11Addon)
     this.terminal.unicode.activeVersion = '11'
@@ -284,7 +303,7 @@ export class TerminalView extends LitElement {
 
     const ctx = getContext()
 
-    window.addEventListener('resize', this.#onResizeWindow)
+    window.addEventListener('resize', this.#onResizeWindow.bind(this))
 
     this.disposables.push(
       onClientMessage(ctx, (e) => {
@@ -319,8 +338,13 @@ export class TerminalView extends LitElement {
 
   protected firstUpdated(props: PropertyValues): void {
     super.firstUpdated(props)
-    const terminalContainer = this.#getTerminalElement()
-    this.terminal!.open(terminalContainer as HTMLElement)
+    const terminalContainer = this.#getTerminalElement() as HTMLElement
+
+    window.addEventListener('focus', () => {
+      this.terminal?.focus()
+    })
+
+    this.terminal!.open(terminalContainer)
     this.terminal!.focus()
     this.fitAddon?.fit()
     this.#updateTerminalTheme()
@@ -356,21 +380,43 @@ export class TerminalView extends LitElement {
   }
 
   #onResizeWindow(): void {
-    const { innerWidth } = window
-    // Prevent adjusting the terminal size if the width remains the same
-    if (this.windowSize.width !== innerWidth) {
-      this.windowSize.width = innerWidth
-      this.fitAddon?.fit()
-    }
+    this.fitAddon?.fit()
   }
 
   // Render the UI as a function of component state
   render() {
-    return html`<div id="terminal"></div>`
+    return html`<section>
+      <div id="terminal"></div>
+      <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>
+      </div>
+    </section>`
   }
 
   dispose() {
     this.disposables.forEach(({ dispose }) => dispose())
   }
 
+  #copy() {
+    const ctx = getContext()
+    if (!ctx.postMessage) { return }
+
+    const content = stripANSI(this.serializer?.serialize({ excludeModes: true, excludeAltBuffer: true }) ?? '')
+
+    return navigator.clipboard.writeText(content).then(
+      () => postClientMessage(ctx, ClientMessages.infoMessage, 'Copied result content to clipboard!'),
+      (err) => postClientMessage(ctx, ClientMessages.infoMessage, `'Failed to copy to clipboard: ${err.message}!'`),
+    )
+  }
 }
diff --git a/src/extension/commands/index.ts b/src/extension/commands/index.ts
index f8ac7947..0cc3e01e 100644
--- a/src/extension/commands/index.ts
+++ b/src/extension/commands/index.ts
@@ -3,8 +3,9 @@ import os from 'node:os'
 
 import {
   NotebookCell, Uri, window, env, NotebookDocument, TextDocument, ViewColumn,
-  workspace, NotebookData, commands, NotebookCellData, NotebookCellKind
+  workspace, NotebookData, commands, NotebookCellData, NotebookCellKind, ExtensionContext
 } from 'vscode'
+import { v4 as uuidv4 } from 'uuid'
 
 import { getBinaryPath, getTLSDir, getTLSEnabled, isNotebookTerminalEnabledForCell } from '../../utils/configuration'
 import { Kernel } from '../kernel'
@@ -137,3 +138,29 @@ export async function createNewRunmeNotebook () {
   )
   await commands.executeCommand('vscode.openWith', newNotebook.uri, Kernel.type)
 }
+
+export async function welcome () {
+  commands.executeCommand(
+    'workbench.action.openWalkthrough',
+    'stateful.runme#runme.welcome',
+    false
+  )
+}
+
+export async function tryIt (context: ExtensionContext) {
+  try {
+    const fileContent = await workspace.fs.readFile(
+      Uri.file(path.join(__dirname, '..', 'walkthroughs', 'welcome.md'))
+    )
+
+    const projectUri = Uri.joinPath(context.globalStorageUri, uuidv4())
+    await workspace.fs.createDirectory(projectUri)
+    const enc = new TextEncoder()
+    const newNotebookUri = Uri.joinPath(projectUri, 'Welcome to Runme.md')
+    await workspace.fs.writeFile(newNotebookUri, enc.encode(fileContent.toString()))
+    await commands.executeCommand('vscode.openWith', newNotebookUri, Kernel.type)
+  } catch (err) {
+    const localMarkdown = Uri.joinPath(Uri.file(context.extensionPath), 'walkthroughs', 'welcome.md')
+    return commands.executeCommand('vscode.openWith', localMarkdown, Kernel.type)
+  }
+}
diff --git a/src/extension/executors/runner.ts b/src/extension/executors/runner.ts
index 7ce4813e..9c2a3701 100644
--- a/src/extension/executors/runner.ts
+++ b/src/extension/executors/runner.ts
@@ -57,8 +57,8 @@ export async function executeRunner(
     RUNME_ID
   }
 
-  const commands = await parseCommandSeq(cellText)
-  if (!commands) { return false }
+  const commands = await parseCommandSeq(cellText, (s) => s.split('\n'))
+  if(!commands) { return false }
 
   if (commands.length === 0) {
     commands.push('')
diff --git a/src/extension/executors/utils.ts b/src/extension/executors/utils.ts
index a58dfc73..d9da2d14 100644
--- a/src/extension/executors/utils.ts
+++ b/src/extension/executors/utils.ts
@@ -1,13 +1,12 @@
 import cp from 'node:child_process'
 import path from 'node:path'
-import os from 'node:os'
 
 import { NotebookCellOutput, NotebookCellExecution, NotebookCellOutputItem, window } from 'vscode'
 
 import { ENV_STORE } from '../constants'
 import { OutputType } from '../../constants'
 import type { CellOutputPayload } from '../../types'
-import { replaceOutput, getCmdSeq } from '../utils'
+import { replaceOutput } from '../utils'
 
 const ENV_VAR_REGEXP = /(\$\w+)/g
 /**
@@ -60,7 +59,8 @@ export async function promptUserForVariable(
 }
 
 export function getCommandExportExtractMatches(
-  rawText: string
+  rawText: string,
+  supportsDirect = true
 ): CommandExportExtractMatch[] {
   const test = new RegExp(EXPORT_EXTRACT_REGEX)
 
@@ -85,8 +85,10 @@ export function getCommandExportExtractMatches(
       value = placeHolder.slice(2, -1)
     } else if (!placeHolder.includes('\n')) {
       matchType = 'prompt'
-    } else {
+    } else if (supportsDirect) {
       matchType = 'direct'
+    } else {
+      continue
     }
 
     result.push({
@@ -191,9 +193,12 @@ export function getShellPath(execKey?: string): string|undefined {
  * Returns `undefined` when a user cancels on prompt
  */
 export async function parseCommandSeq(
-  cellText: string
+  cellText: string,
+  parseBlock?: (block: string) => string[]
 ): Promise<string[]|undefined> {
-  const exportMatches = getCommandExportExtractMatches(cellText)
+  parseBlock ??= (s) => s ? s.split('\n') : []
+
+  const exportMatches = getCommandExportExtractMatches(cellText, false)
 
   type CommandBlock =
     |{
@@ -243,9 +248,5 @@ export async function parseCommandSeq(
   parsedCommandBlocks.push({ type: 'block', content: cellText.slice(offset) })
 
   return parsedCommandBlocks
-    .flatMap(({ type, content }) => type === 'block' ? getCmdSeq(content) : [content])
-}
-
-export function isWindows(): boolean {
-  return os.platform().startsWith('win')
+    .flatMap(({ type, content }) => (type === 'block' && parseBlock?.(content)) || (content ? [content] : []))
 }
diff --git a/src/extension/extension.ts b/src/extension/extension.ts
index 64f90087..77363a86 100644
--- a/src/extension/extension.ts
+++ b/src/extension/extension.ts
@@ -17,7 +17,9 @@ import {
   openAsRunmeNotebook,
   openSplitViewAsMarkdownText,
   stopBackgroundTask,
-  createNewRunmeNotebook
+  createNewRunmeNotebook,
+  welcome,
+  tryIt
 } from './commands'
 import { WasmSerializer, GrpcSerializer } from './serializer'
 import { RunmeLauncherProvider } from './provider/launcher'
@@ -25,6 +27,7 @@ import { RunmeUriHandler } from './handler/uri'
 import { BOOTFILE } from './constants'
 import { GrpcRunner, IRunner } from './runner'
 import { CliProvider } from './provider/cli'
+import * as survey from './survey'
 
 export class RunmeExtension {
   async initialize(context: ExtensionContext) {
@@ -62,12 +65,14 @@ export class RunmeExtension {
 
     const treeViewer = new RunmeLauncherProvider(getDefaultWorkspace())
     const uriHandler = new RunmeUriHandler(context)
+    const winSurvey = new survey.WinDefaultShell(context)
 
     context.subscriptions.push(
       kernel,
       serializer,
       server,
       treeViewer,
+      winSurvey,
       workspace.registerNotebookSerializer(Kernel.type, serializer, {
         transientOutputs: true,
         transientCellMetadata: {
@@ -94,6 +99,8 @@ export class RunmeExtension {
       RunmeExtension.registerCommand('runme.openSplitViewAsMarkdownText', openSplitViewAsMarkdownText),
       RunmeExtension.registerCommand('runme.openAsRunmeNotebook', openAsRunmeNotebook),
       RunmeExtension.registerCommand('runme.new', createNewRunmeNotebook),
+      RunmeExtension.registerCommand('runme.welcome', welcome),
+      RunmeExtension.registerCommand('runme.try', () => tryIt(context)),
       RunmeExtension.registerCommand('runme.openRunmeFile', RunmeLauncherProvider.openFile),
       RunmeExtension.registerCommand('runme.keybinding.m', () => { }),
       RunmeExtension.registerCommand('runme.keybinding.y', () => { }),
@@ -104,8 +111,8 @@ export class RunmeExtension {
        * tree viewer items
        */
       window.registerTreeDataProvider('runme.launcher', treeViewer),
-      commands.registerCommand('runme.collapseTreeView', treeViewer.collapseAll.bind(treeViewer)),
-      commands.registerCommand('runme.expandTreeView', treeViewer.expandAll.bind(treeViewer)),
+      RunmeExtension.registerCommand('runme.collapseTreeView', treeViewer.collapseAll.bind(treeViewer)),
+      RunmeExtension.registerCommand('runme.expandTreeView', treeViewer.expandAll.bind(treeViewer)),
 
       /**
        * Uri handler
diff --git a/src/extension/grpc/client.ts b/src/extension/grpc/client.ts
index a1622872..63713978 100644
--- a/src/extension/grpc/client.ts
+++ b/src/extension/grpc/client.ts
@@ -2,10 +2,11 @@
 import { ParserServiceClient } from '@buf/stateful_runme.community_timostamm-protobuf-ts/runme/parser/v1/parser_pb.client'
 // eslint-disable-next-line max-len
 import { RunnerServiceClient } from '@buf/stateful_runme.community_timostamm-protobuf-ts/runme/runner/v1/runner_pb.client'
+import { HealthClient } from '@buf/grpc_grpc.community_timostamm-protobuf-ts/grpc/health/v1/health_pb.client'
 import { GrpcTransport } from '@protobuf-ts/grpc-transport'
 
 function initParserClient(transport: GrpcTransport): ParserServiceClient {
   return new ParserServiceClient(transport)
 }
 
-export { ParserServiceClient, RunnerServiceClient, initParserClient }
+export { ParserServiceClient, RunnerServiceClient, initParserClient, HealthClient }
diff --git a/src/extension/grpc/healthTypes.ts b/src/extension/grpc/healthTypes.ts
new file mode 100644
index 00000000..e9bcca34
--- /dev/null
+++ b/src/extension/grpc/healthTypes.ts
@@ -0,0 +1,1 @@
+export * from '@buf/grpc_grpc.community_timostamm-protobuf-ts/grpc/health/v1/health_pb'
diff --git a/src/extension/kernel.ts b/src/extension/kernel.ts
index e68d47d9..48dcff74 100644
--- a/src/extension/kernel.ts
+++ b/src/extension/kernel.ts
@@ -21,11 +21,10 @@ import { API } from '../utils/deno/api'
 
 import executor, { type IEnvironmentManager, ENV_STORE_MANAGER } from './executors'
 import { DENO_ACCESS_TOKEN_KEY } from './constants'
-import { resetEnv, getKey, getAnnotations, hashDocumentUri, processEnviron } from './utils'
+import { resetEnv, getKey, getAnnotations, hashDocumentUri, processEnviron, isWindows } from './utils'
 import './wasm/wasm_exec.js'
 import { IRunner, IRunnerEnvironment } from './runner'
 import { executeRunner } from './executors/runner'
-import { isWindows } from './executors/utils'
 import { ITerminalState, NotebookTerminalType, XTermState } from './terminal/terminalState'
 
 enum ConfirmationItems {
diff --git a/src/extension/provider/launcher.ts b/src/extension/provider/launcher.ts
index bd8d4f14..17b9b89a 100644
--- a/src/extension/provider/launcher.ts
+++ b/src/extension/provider/launcher.ts
@@ -184,7 +184,8 @@ export class RunmeLauncherProvider implements TreeDataProvider<RunmeFile>, Dispo
     const rootFolder = basename(this.workspaceRoot || '')
     const info = basename(file.path)
     const folderPath = dirname(file.path)
-    const folderName = folderPath.replace(resolve(this.workspaceRoot || '', '..'), '') + nameTweaker || rootFolder
+    let folderName = folderPath.replace(resolve(this.workspaceRoot || '', '..'), '') + nameTweaker || rootFolder
+    folderName = folderName.startsWith('/') ? folderName.substring(1, folderName.length) : folderName
     return { info, folderPath, folderName }
   }
 
diff --git a/src/extension/server/runmeServer.ts b/src/extension/server/runmeServer.ts
index d92b06db..b0e1ff1f 100644
--- a/src/extension/server/runmeServer.ts
+++ b/src/extension/server/runmeServer.ts
@@ -6,11 +6,14 @@ import { ChannelCredentials } from '@grpc/grpc-js'
 import { GrpcTransport } from '@protobuf-ts/grpc-transport'
 import { Disposable, Uri, EventEmitter } from 'vscode'
 
+import {
+  HealthCheckRequest,
+  HealthCheckResponse_ServingStatus
+} from '../grpc/healthTypes'
 import { SERVER_ADDRESS } from '../../constants'
 import { enableServerLogs, getBinaryPath, getPortNumber, getTLSDir, getTLSEnabled } from '../../utils/configuration'
-import { initParserClient } from '../grpc/client'
-import { DeserializeRequest } from '../grpc/serializerTypes'
 import { isPortAvailable } from '../utils'
+import { HealthClient } from '../grpc/client'
 
 import RunmeServerError from './runmeServerError'
 
@@ -76,18 +79,22 @@ class RunmeServer implements Disposable {
     }
 
     async isRunning(): Promise<boolean> {
-      const client = initParserClient(await this.transport())
+      const client = new HealthClient(await this.transport())
+
       try {
-        const deserialRequest = DeserializeRequest.create({ source: Buffer.from('## Server running', 'utf-8') })
-        const request = client.deserialize(deserialRequest)
-        const status = await request.status
-        return status.code === 'OK'
+        const { response } = await client.check(HealthCheckRequest.create())
+
+        if (response.status === HealthCheckResponse_ServingStatus.SERVING) {
+          return true
+        }
       } catch (err: any) {
         if (err?.code === 'UNAVAILABLE') {
           return false
         }
         throw err
       }
+
+      return false
     }
 
     address() {
diff --git a/src/extension/survey.ts b/src/extension/survey.ts
new file mode 100644
index 00000000..ee9ead70
--- /dev/null
+++ b/src/extension/survey.ts
@@ -0,0 +1,150 @@
+import path from 'node:path'
+import { mkdirSync, readFileSync, unlinkSync } from 'node:fs'
+
+import {
+  Disposable,
+  NotebookDocument,
+  workspace,
+  window,
+  ExtensionContext,
+  Task,
+  TaskScope,
+  ShellExecution,
+  TaskRevealKind,
+  TaskPanelKind,
+  tasks,
+  Uri,
+  commands,
+} from 'vscode'
+import { TelemetryReporter } from 'vscode-telemetry'
+
+import { Kernel } from './kernel'
+import { isWindows } from './utils'
+
+export class WinDefaultShell implements Disposable {
+  static readonly #id: string = 'runme.surveyWinDefaultShell'
+  readonly #tempDir: Uri
+  readonly #context: ExtensionContext
+  readonly #disposables: Disposable[] = []
+
+  constructor(context: ExtensionContext) {
+    commands.registerCommand(WinDefaultShell.#id, this.#prompt.bind(this))
+
+    this.#tempDir = context.globalStorageUri
+    this.#context = context
+
+    // Only prompt on Windows
+    if (!isWindows()) {
+      return
+    }
+
+    this.#disposables.push(
+      workspace.onDidOpenNotebookDocument(this.#handleOpenNotebook.bind(this))
+    )
+  }
+
+  async #handleOpenNotebook({ notebookType }: NotebookDocument) {
+    if (
+      notebookType !== Kernel.type ||
+      this.#context.globalState.get<boolean>(
+        WinDefaultShell.#id,
+        false
+      )
+    ) {
+      return
+    }
+
+    await new Promise<void>(resolve => setTimeout(resolve, 2000))
+    await commands.executeCommand(WinDefaultShell.#id, false)
+  }
+
+  async #prompt(runDirect = true) {
+    // reset done when run from command palette
+    if (runDirect) {
+      await this.#undo()
+    }
+
+    const option = await window.showInformationMessage(
+      'Please help us improve Runme on Windows: Click OK to share what default shell you are using.',
+      'OK',
+      'Don\'t ask again',
+      'Dismiss'
+    )
+    if (option === 'Dismiss') {
+      return
+    } else if (option !== 'OK') {
+      await this.#done()
+      return
+    }
+
+    mkdirSync(this.#tempDir.fsPath, { recursive: true })
+
+    const name = 'Runme Windows Shell'
+    const tmpfile = path.join(this.#tempDir.fsPath, 'defaultShell')
+    try {
+      unlinkSync(tmpfile)
+    } catch (err) {
+      if (err instanceof Error) {
+        console.log(err.message)
+      }
+    }
+    // eslint-disable-next-line max-len
+    const cmdline = `echo $SHELL > "${tmpfile}"; echo $PSVersionTable | Out-File -Encoding utf8 "${tmpfile}"`
+    const taskExecution = new Task(
+      { type: 'shell', name },
+      TaskScope.Workspace,
+      name,
+      'exec',
+      new ShellExecution(cmdline)
+    )
+
+    taskExecution.isBackground = true
+    taskExecution.presentationOptions = {
+      focus: false,
+      reveal: TaskRevealKind.Never,
+      panel: TaskPanelKind.Dedicated
+    }
+
+    const exitCode = await new Promise<number>((resolve) => {
+      tasks.executeTask(taskExecution).then((execution) => {
+        this.#disposables.push(tasks.onDidEndTaskProcess((e) => {
+          const taskId = (e.execution as any)['_id']
+          const executionId = (execution as any)['_id']
+
+          if (
+            taskId !== executionId ||
+            typeof e.exitCode === 'undefined'
+          ) {
+            return
+          }
+
+          // non-zero exit code does not mean failure
+          resolve(e.exitCode)
+        }))
+      })
+    })
+
+    try {
+      const output = readFileSync(tmpfile, { encoding: 'utf-8' }).trim()
+      TelemetryReporter.sendTelemetryEvent('survey.WinDefaultShell', { output, exitCode: exitCode.toString() })
+      await this.#done()
+      unlinkSync(tmpfile)
+    } catch (err) {
+      if (err instanceof Error) {
+        console.log(err.message)
+      }
+    }
+  }
+
+  async #undo() {
+    await this.#context.globalState.update(WinDefaultShell.#id, false)
+  }
+
+  async #done() {
+    await this.#context.globalState.update(WinDefaultShell.#id, true)
+  }
+
+  dispose() {
+    this.#disposables.forEach(d => d.dispose())
+  }
+}
diff --git a/src/extension/utils.ts b/src/extension/utils.ts
index d5e277a0..6719b58a 100644
--- a/src/extension/utils.ts
+++ b/src/extension/utils.ts
@@ -1,6 +1,7 @@
 import path from 'node:path'
 import util from 'node:util'
 import cp from 'node:child_process'
+import os from 'node:os'
 
 import vscode, {
   FileType,
@@ -319,3 +320,7 @@ export async function isPortAvailable(port: number): Promise<boolean> {
 export function processEnviron(): string[] {
   return Object.entries(process.env).map(([k, v]) => `${k}=${v || ''}`)
 }
+
+export function isWindows(): boolean {
+  return os.platform().startsWith('win')
+}
diff --git a/src/utils/ansi.ts b/src/utils/ansi.ts
new file mode 100644
index 00000000..bb1c3f3a
--- /dev/null
+++ b/src/utils/ansi.ts
@@ -0,0 +1,6 @@
+export function stripANSI(src: string): string {
+  return src.replace(
+    /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
+    '',
+  )
+}
diff --git a/src/utils/configuration.ts b/src/utils/configuration.ts
index f0b96f97..78e04d4a 100644
--- a/src/utils/configuration.ts
+++ b/src/utils/configuration.ts
@@ -5,8 +5,7 @@ import { NotebookCell, Uri, workspace } from 'vscode'
 import { z } from 'zod'
 import { v4 as uuidv4 } from 'uuid'
 
-import { isWindows } from '../extension/executors/utils'
-import { getAnnotations } from '../extension/utils'
+import { getAnnotations, isWindows } from '../extension/utils'
 
 const SERVER_SECTION_NAME = 'runme.server'
 const TERMINAL_SECTION_NAME= 'runme.terminal'
@@ -19,7 +18,7 @@ const configurationSchema = {
         port: z
             .number()
             .positive()
-            .min(8080)
+            .min(7070)
             .max(9090)
             .default(7863),
         binaryPath: z
diff --git a/walkthroughs/0-intro.md b/walkthroughs/0-intro.md
index 0c4088db..2f6cfce8 100644
--- a/walkthroughs/0-intro.md
+++ b/walkthroughs/0-intro.md
@@ -4,6 +4,11 @@ Thank you for interest in Runme 💟!
 
 Runme strives to provide a great experience right out of the box. Please continue to learn how to get the most out of our docs via Runme. Please don't be surprised if Runme's docs prompt you to use Runme. Learning by using is very effective.
 
+<div>
+  <img width="15" src="https://runme.dev/img/logo.svg" />
+  <a href="command:runme.try">Give Runme a try</a>
+</div>
+
 A well-written README can significantly impact time to get up and running on your projects, reduce onboarding time, and interactively document common development and operations workflows (runbooks). On the contrary, a poorly written README can negatively impact the visibility of your project, even if the underyling code of your software is in excellent condition. For new contributors, it can quickly become a very frustrating experience.
 
 The Runme team believes that even if you have textually excellent README.md, encouraging your users to experience it through the lens of Runme, your docs will be more reliable, less susceptible to bit-rot, and a lot more humanly ergnomic. All of which leeds to happier and more productive developers.
@@ -31,7 +36,7 @@ Gaps in developer experience such as:
 - Docs that have fallen behind undetected
 - The vault lines of commonplace tools were designed for machines, not humans.
 
-![Readme as Notebook and Markdown side-by-side](https://www.runme.dev/assets/images/README_side_by_side-e67bbc4db8e183d9193f1fcccd9b302b.png)
+![Readme as Notebook and Markdown side-by-side](https://github.com/stateful/runme.dev/raw/63f857ba8f4f8cfd824099c80c14ffc405802ea4/static/img/sidebyside.png)
 
 Runme’s purpose is to enable developers to achieve a functional local development environment quickly, starting with copy&paste, click to run commands, and many more features. While README.md is the most well-known, there are several other markdown files often found in a repo that you may encounter, including; BUILD.md, CONTRIBUTING.md, and many more. Runme supports most .md and .mdx files!
 
diff --git a/walkthroughs/1-annotations.md b/walkthroughs/1-annotations.md
index e67a3717..292c4906 100644
--- a/walkthroughs/1-annotations.md
+++ b/walkthroughs/1-annotations.md
@@ -8,11 +8,11 @@ See the complete list of cell configuration options [in the docs](https://runme.
 
 You want to enable the `background` setting if notebook execution will continue indefinitely on a single command.
 
-![Readme background task status bar](https://runme.dev/assets/images/long-running-process-28a244ba20337b531c85fd31e7a669ad.png)
+![Readme background task status bar](https://github.com/stateful/runme.dev/raw/63f857ba8f4f8cfd824099c80c14ffc405802ea4/static/img/long-running-process.png)
 
 It is very common to use file-watcher enabled compilers/bundlers (`npm start dev`, `watchexec`... etc) in the background during development. For any cell containing an instance of these commands be sure to tick the `"background"` cell setting. It prevents execution from permanently blocking the notebook UX. Once ticked notice the "Background Task" label shows up in the cell status bar.
 
-![Readme background task status bar](https://runme.dev/assets/images/background-task-process-fb519197e23bcf3411d4ce405ae93082.png)
+![Readme background task status bar](https://github.com/stateful/runme.dev/raw/63f857ba8f4f8cfd824099c80c14ffc405802ea4/static/img/background-task-process.png)
 
 **Default:** `false`
 **Example:**
@@ -25,7 +25,7 @@ It is very common to use file-watcher enabled compilers/bundlers (`npm start dev
 
 If a cell's commands do not require any input from a reader it might be a good fit to include the cell's output inside the notebook. This is useful if the resulting output could be useful as input in a downstream cell. This is what `interactive=false` is for which defaults to true.
 
-![Readme interactive task status bar](https://runme.dev/assets/images/interactive-execution-46d0d65215954b7ac770448a5394051d.png)
+![Readme interactive task status bar](https://github.com/stateful/runme.dev/raw/63f857ba8f4f8cfd824099c80c14ffc405802ea4/static/img/interactive-execution.png)
 
 **Default:** `true`
 **Example:**
@@ -52,7 +52,7 @@ Check the docs on [runme.dev](https://runme.dev/docs/annotations) for more
 
 JSON, text, images, etc. Not all cells’ output is plain text. Using the `mimeType` specifier it is possible to specify the expected output's type. Notebooks have a variety of renderers that will display them human friendly. The MIME type defaults to text/plain.
 
-![Readme mimeType task status bar](https://runme.dev/assets/images/human-centric-output-b9290d4818822a18e4e45700efa7592a.png)
+![Readme mimeType task status bar](https://github.com/stateful/runme.dev/raw/63f857ba8f4f8cfd824099c80c14ffc405802ea4/static/img/human-centric-output.png)
 
 
 ## Supported MIME types
@@ -75,4 +75,4 @@ Runme supports the standard VS Code MIME types alongside custom Runme MIME types
 - text/x-javascript
 - text/x-html
 - text/x-rust
-- text/x-LANGUAGE_ID for any other built-in or installed languages.
\ No newline at end of file
+- text/x-LANGUAGE_ID for any other built-in or installed languages.
diff --git a/walkthroughs/3-features.md b/walkthroughs/3-features.md
index 5c23b1a1..440d8b6e 100644
--- a/walkthroughs/3-features.md
+++ b/walkthroughs/3-features.md
@@ -4,11 +4,11 @@
 
 To run a command, simply click the run button (may require your mouse over the command in some themes). You will notice that this turns into a stop button for hung or long-running commands, which can be used to kill the terminal process.
 
-![Readme run a command](https://runme.dev/assets/images/run-a-command-f37f5462befc66c5721c19dfe4336352.png)
+![Readme run a command](https://github.com/stateful/runme.dev/raw/63f857ba8f4f8cfd824099c80c14ffc405802ea4/static/img/run-a-command.png)
 
 A succeeding exit code will be indicated with the small green checkbox seen below:
 
-![Readme run a command result](https://runme.dev/assets/images/check-mark-success-d2f5bcb93690d780969dda1a3b710034.png)
+![Readme run a command result](https://github.com/stateful/runme.dev/raw/63f857ba8f4f8cfd824099c80c14ffc405802ea4/static/img/check-mark-success.png)
 
 You can also open the terminal that did the execution by clicking the “Open Terminal” button as shown above with its PID.
 
@@ -17,18 +17,18 @@ You can also open the terminal that did the execution by clicking the “Open Te
 
 Outside of literally running commands, Runme offers the ability to quickly copy commands (with the click of a button) out of a markdown file to paste into your terminal.
 
-![Readme run a command result](https://runme.dev/assets/images/feature-copy-d4563fd523f32ecc468254750d3aafe4.png)
+![Readme run a command result](https://github.com/stateful/runme.dev/raw/63f857ba8f4f8cfd824099c80c14ffc405802ea4/static/img/feature-copy.png)
 
 
 ## Run all commands
 
 To run all the commands in the notebook in the order they are found, you can click the “Run All” button.
 
-![Readme run all commands](https://runme.dev/assets/images/run-all-877ed54c3b7bd63b837c8e5a7cfd1235.png)
+![Readme run all commands](https://github.com/stateful/runme.dev/raw/63f857ba8f4f8cfd824099c80c14ffc405802ea4/static/img/run-all.png)
 
 To be extra safe, you will be prompted before each step to confirm your intentions unless you select “Skip Prompt and run all”.
 
-![Readme run all commands confirm step](https://runme.dev/assets/images/confirm-run-all-def58028aa9410f8abdcf9c0e79d37c9.png)
+![Readme run all commands confirm step](https://github.com/stateful/runme.dev/raw/63f857ba8f4f8cfd824099c80c14ffc405802ea4/static/img/confirm-run-all.png)
 
 ## Split view of markdown and notebook
 
@@ -36,11 +36,11 @@ It’s easy to get from notebook to markdown and vice versa.
 
 ### Open the notebook version
 
-![Readme run all commands confirm step](https://runme.dev/assets/images/split-view-ba9635ba52ccc77021f35e23da587bdb.png)
+![Readme run all commands confirm step](https://github.com/stateful/runme.dev/raw/63f857ba8f4f8cfd824099c80c14ffc405802ea4/static/img/split-view.png)
 
 ### Open the markdown version
 
-![Readme open md version](https://runme.dev/assets/images/markdown-version-4a58ba676a1a96571a7188c643bcab6d.png)
+![Readme open md version](https://github.com/stateful/runme.dev/raw/63f857ba8f4f8cfd824099c80c14ffc405802ea4/static/img/markdown-version.png)
 
 ## Summary
 
@@ -51,4 +51,4 @@ It’s easy to get from notebook to markdown and vice versa.
 - Capture non-interactive output directly inside the respective notebook's cell output
 - Control whether or not a cell's terminal should remain open after successful execution
 - Use language identifiers in fenced code blocks to forgo language detection and higher reliability
-- Set a cell's output MIME type to render images, JSON, or any other format in human-compatible ways
\ No newline at end of file
+- Set a cell's output MIME type to render images, JSON, or any other format in human-compatible ways
diff --git a/walkthroughs/welcome.md b/walkthroughs/welcome.md
new file mode 100644
index 00000000..fc808e36
--- /dev/null
+++ b/walkthroughs/welcome.md
@@ -0,0 +1,27 @@
+# ![Runme](https://runme.dev/img/logo.svg "Runme") Welcome to Runme
+
+Runme is primary a VS Code extension that provides developers with the ability to navigate workflows center around code repositories by making Readme markdown files interactive and smart.
+
+It allows you to run Shell scripts within a Notebook interface, give it a try:
+
+```sh { interactive=false }
+echo "Hello Runme!"
+```
+
+Awesome! 🎉
+
+It also persist your session state, so all environment variables stay around, e.g.:
+
+```sh
+export RUNME_MESSAGE="<input message here>"
+```
+
+now run:
+
+```sh { interactive=false }
+echo $RUNME_MESSAGE
+```
+
+Nice! 👌
+
+There is a lot more of that. Checkout the [Runme docs](https://runme.dev) and checkout all features that this extension can help you with. Also, make sure to join our [Discord](https://discord.gg/BQm8zRCBUY) in case you have any questions or like to provide feedback!


Test patch:
diff --git a/tests/extension/commands/index.test.ts b/tests/extension/commands/index.test.ts
index c090683b..56e84a64 100644
--- a/tests/extension/commands/index.test.ts
+++ b/tests/extension/commands/index.test.ts
@@ -22,7 +22,9 @@ import {
   openAsRunmeNotebook,
   openSplitViewAsMarkdownText,
   stopBackgroundTask,
-  createNewRunmeNotebook
+  createNewRunmeNotebook,
+  welcome,
+  tryIt
 } from '../../../src/extension/commands'
 import { getTerminalByCell, getAnnotations, replaceOutput } from '../../../src/extension/utils'
 import { getBinaryPath, isNotebookTerminalEnabledForCell } from '../../../src/utils/configuration'
@@ -148,3 +150,29 @@ test('createNewRunmeNotebook', async () => {
   expect(NotebookCellData).toBeCalledTimes(3)
   expect(commands.executeCommand).toBeCalledWith('vscode.openWith', expect.any(String), 'runme')
 })
+
+test('welcome command', async () => {
+  await welcome()
+  expect(commands.executeCommand).toBeCalledWith(
+    'workbench.action.openWalkthrough',
+    'stateful.runme#runme.welcome',
+    false
+  )
+})
+
+test('tryIt command', async () => {
+  await tryIt({ globalStorageUri: { fsPath: '/foo/bar' } } as any)
+  expect(
+    vi.mocked(workspace.fs.createDirectory).mock.calls[0][0].path.startsWith('/foo/bar')
+  ).toBe(true)
+  expect(
+    vi.mocked(workspace.fs.writeFile).mock.calls[0][0].path.endsWith('Welcome to Runme.md')
+  ).toBe(true)
+  expect(commands.executeCommand).toBeCalledWith('vscode.openWith', expect.any(Object), 'runme')
+})
+
+test('tryIt command in failure case', async () => {
+  vi.mocked(workspace.fs.createDirectory).mockRejectedValue('ups')
+  await tryIt({ extensionPath: '/foo/bar' } as any)
+  expect(commands.executeCommand).toBeCalledWith('vscode.openWith', expect.any(Object), 'runme')
+})
diff --git a/tests/extension/executors/utils.test.ts b/tests/extension/executors/utils.test.ts
index 189744d7..3752f473 100644
--- a/tests/extension/executors/utils.test.ts
+++ b/tests/extension/executors/utils.test.ts
@@ -5,6 +5,7 @@ import { expect, vi, test, suite, beforeEach } from 'vitest'
 
 import { ENV_STORE } from '../../../src/extension/constants'
 import { retrieveShellCommand, parseCommandSeq } from '../../../src/extension/executors/utils'
+import { getCmdSeq } from '../../../src/extension/utils'
 
 const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
 
@@ -208,25 +209,71 @@ suite('parseCommandSeq', () => {
     expect(res?.[0]).toBe('export TEST="test value"')
   })
 
+  test('single line export with cancelled prompt', async () => {
+    vi.mocked(window.showInputBox).mockImplementationOnce(async () => undefined)
+
+    const res = await parseCommandSeq([
+      'export TEST="<placeholder>"'
+    ].join('\n'))
+
+    expect(res).toBe(undefined)
+  })
+
   test('multiline export', async () => {
-    const exportLine = [
+    const exportLines = [
       'export TEST="I',
       'am',
       'doing',
       'well!"'
-    ].join('\n')
+    ]
 
-    const res = await parseCommandSeq(exportLine)
+    const res = await parseCommandSeq(exportLines.join('\n'))
 
     expect(res).toBeTruthy
-    expect(res).toHaveLength(1)
-    expect(res?.[0]).toBe(exportLine)
+    expect(res).toHaveLength(4)
+    expect(res).toStrictEqual(exportLines)
   })
 
   test('exports between normal command sequences', async () => {
     vi.mocked(window.showInputBox).mockImplementationOnce(async () => 'test value')
 
-    const cmd = [
+    const cmdLines = [
+      'echo "Hello!"',
+      'echo "Hi!"',
+      'export TEST="<placeholder>"',
+      'echo $TEST',
+      'export TEST_MULTILINE="This',
+      'is',
+      'a',
+      'multiline',
+      'env!"',
+      'echo $TEST_MULTILINE'
+    ]
+
+    const res = await parseCommandSeq(cmdLines.join('\n'))
+
+    expect(res).toBeTruthy()
+    expect(res).toStrictEqual([
+      'echo "Hello!"',
+      'echo "Hi!"',
+      'export TEST="test value"',
+      '',
+      'echo $TEST',
+      ...[
+        'export TEST_MULTILINE="This',
+        'is',
+        'a',
+        'multiline',
+        'env!"',
+      ],
+      'echo $TEST_MULTILINE'
+    ])
+  })
+
+  test('exports between normal command sequences with getCmdSeq', async () => {
+    vi.mocked(window.showInputBox).mockImplementationOnce(async () => 'test value')
+
+    const cmdLines = [
       'echo "Hello!"',
       'echo "Hi!"',
       'export TEST="<placeholder>"',
@@ -237,9 +284,9 @@ suite('parseCommandSeq', () => {
       'multiline',
       'env!"',
       'echo $TEST_MULTILINE'
-    ].join('\n')
+    ]
 
-    const res = await parseCommandSeq(cmd)
+    const res = await parseCommandSeq(cmdLines.join('\n'), getCmdSeq)
 
     expect(res).toBeTruthy()
     expect(res).toStrictEqual([
@@ -247,13 +294,13 @@ suite('parseCommandSeq', () => {
       'echo "Hi!"',
       'export TEST="test value"',
       'echo $TEST',
-      [
+      ...[
         'export TEST_MULTILINE="This',
         'is',
         'a',
         'multiline',
         'env!"',
-      ].join('\n'),
+      ],
       'echo $TEST_MULTILINE'
     ])
   })
diff --git a/tests/extension/extension.test.ts b/tests/extension/extension.test.ts
index bdd82849..47aaf576 100644
--- a/tests/extension/extension.test.ts
+++ b/tests/extension/extension.test.ts
@@ -1,5 +1,8 @@
 import { test, expect, vi } from 'vitest'
 import { notebooks, workspace, commands, window, Uri } from 'vscode'
+import {
+  HealthCheckResponse_ServingStatus
+} from '@buf/grpc_grpc.community_timostamm-protobuf-ts/grpc/health/v1/health_pb'
 
 import { RunmeExtension } from '../../src/extension/extension'
 import RunmeServer from '../../src/extension/server/runmeServer'
@@ -23,6 +26,15 @@ vi.mock('../../src/extension/grpc/client', () => {
         return { status: { code: 'OK' } }
       })
     })),
+    HealthClient: class {
+      async check() {
+        return {
+          response: {
+            status: HealthCheckResponse_ServingStatus.SERVING
+          }
+        }
+      }
+    },
   })
 })
 
@@ -42,7 +54,7 @@ test('initializes all providers', async () => {
   await ext.initialize(context)
   expect(notebooks.registerNotebookCellStatusBarItemProvider).toBeCalledTimes(6)
   expect(workspace.registerNotebookSerializer).toBeCalledTimes(1)
-  expect(commands.registerCommand).toBeCalledTimes(14)
+  expect(commands.registerCommand).toBeCalledTimes(17)
   expect(window.registerTreeDataProvider).toBeCalledTimes(1)
   expect(window.registerUriHandler).toBeCalledTimes(1)
 
diff --git a/tests/extension/provider/launcher.test.ts b/tests/extension/provider/launcher.test.ts
index d8fd9cf3..fed6a7c8 100644
--- a/tests/extension/provider/launcher.test.ts
+++ b/tests/extension/provider/launcher.test.ts
@@ -101,7 +101,7 @@ describe('Runme Notebooks', () => {
     expect(workspace.createFileSystemWatcher).toBeCalledWith('**/*.md', false, true, false)
     handler.mock.calls[0][0]({ path: '/foo/bar' }, true)
     expect([...launchProvider['filesTree'].entries()])
-      .toEqual([['/foo ', { files: ['bar'], folderPath: '/foo' }]])
+      .toEqual([['foo ', { files: ['bar'], folderPath: '/foo' }]])
       handler.mock.calls[1][0]({ path: '/foo/bar' })
     expect(launchProvider.refresh).toBeCalledTimes(1)
   })
diff --git a/tests/extension/server/runmeServer.test.ts b/tests/extension/server/runmeServer.test.ts
index 1a6013b7..75c5033f 100644
--- a/tests/extension/server/runmeServer.test.ts
+++ b/tests/extension/server/runmeServer.test.ts
@@ -2,15 +2,30 @@ import fs from 'node:fs/promises'
 
 import { suite, test, expect, vi, beforeEach } from 'vitest'
 import { Uri, workspace } from 'vscode'
+import {
+  HealthCheckResponse_ServingStatus
+} from '@buf/grpc_grpc.community_timostamm-protobuf-ts/grpc/health/v1/health_pb'
 
 // eslint-disable-next-line import/order
 import { isPortAvailable } from '../../../src/extension/utils'
 
+import Server, { IServerConfig } from '../../../src/extension/server/runmeServer'
+import RunmeServerError from '../../../src/extension/server/runmeServerError'
+import { testCertPEM, testPrivKeyPEM } from '../../testTLSCert'
+
+const healthCheck = vi.fn()
+
 vi.mock('vscode')
-vi.mock('../../../src/extension/grpc/client', () => ({ initParserClient: vi.fn() }))
+vi.mock('../../../src/extension/grpc/client', () => ({
+  initParserClient: vi.fn(),
+  HealthClient: class {
+    check = healthCheck
+  }
+}))
 
 vi.mock('../../../src/extension/utils', () => ({
   isPortAvailable: vi.fn(async () => true),
+  isWindows: vi.fn().mockReturnValue(false)
 }))
 
 vi.mock('node:fs/promises', async () => ({
@@ -37,9 +52,28 @@ vi.mock('node:child_process', async () => ({
   spawn: vi.fn(),
 }))
 
-import Server from '../../../src/extension/server/runmeServer'
-import RunmeServerError from '../../../src/extension/server/runmeServerError'
-import { testCertPEM, testPrivKeyPEM } from '../../testTLSCert'
+suite('health check', () => {
+  test('passes if serving', async () => {
+    const server = createServer()
+    healthCheck.mockResolvedValueOnce({ response: { status: HealthCheckResponse_ServingStatus.SERVING } })
+
+    expect(await server['isRunning']()).toBe(true)
+  })
+
+  test('fails if not serving', async () => {
+    const server = createServer()
+    healthCheck.mockResolvedValueOnce({ response: { status: HealthCheckResponse_ServingStatus.NOT_SERVING } })
+
+    expect(await server['isRunning']()).toBe(false)
+  })
+
+  test('fails if error', async () => {
+    const server = createServer()
+    healthCheck.mockRejectedValueOnce({ code: 'UNAVAILABLE' })
+
+    expect(await server['isRunning']()).toBe(false)
+  })
+})
 
 suite('Runme server spawn process', () => {
     const configValues: Record<string, any> = {
@@ -84,14 +118,11 @@ suite('Runme server spawn process', () => {
     test('Should try 2 times before failing', async () => {
       configValues.enableTLS = true
 
-      const server = new Server(
-        Uri.file('/Users/user/.vscode/extension/stateful.runme'),
-        {
-          retryOnFailure: true,
-          maxNumberOfIntents: 2,
-        },
-        false
-      )
+      const server = createServer({
+        retryOnFailure: true,
+        maxNumberOfIntents: 2,
+      })
+
       const serverLaunchSpy = vi.spyOn(server, 'launch')
       await expect(server.launch()).rejects.toBeInstanceOf(RunmeServerError)
       expect(serverLaunchSpy).toBeCalledTimes(3)
@@ -100,14 +131,10 @@ suite('Runme server spawn process', () => {
     test('Should increment until port is available', async () => {
       configValues.enableTLS = true
 
-      const server = new Server(
-        Uri.file('/Users/user/.vscode/extension/stateful.runme'),
-        {
+      const server = createServer({
           retryOnFailure: true,
           maxNumberOfIntents: 2,
-        },
-        false
-      )
+      })
 
       vi.mocked(fs.access).mockResolvedValueOnce()
       vi.mocked(fs.stat).mockResolvedValueOnce({
@@ -125,18 +152,14 @@ suite('Runme server spawn process', () => {
 suite('Runme server accept connections', () => {
     let server: Server
     beforeEach(() => {
-        server = new Server(
-          Uri.file('/Users/user/.vscode/extension/stateful.runme'),
-          {
-            retryOnFailure: false,
-            maxNumberOfIntents: 2,
-            acceptsConnection: {
-              intents: 4,
-              interval: 1,
-            }
-          },
-          false
-        )
+      server = createServer({
+        retryOnFailure: false,
+        maxNumberOfIntents: 2,
+        acceptsConnection: {
+          intents: 4,
+          interval: 1,
+        }
+      })
     })
 
     test('Should wait until server accepts connection', async () => {
@@ -160,24 +183,16 @@ suite('Runme server accept connections', () => {
     })
 })
 
-test('Runme server clears server disposables', () => {
-  const server = new Server(
+function createServer(
+  config: IServerConfig = {
+    retryOnFailure: true,
+    maxNumberOfIntents: 2,
+  },
+  externalServer = false
+) {
+  return new Server(
     Uri.file('/Users/user/.vscode/extension/stateful.runme'),
-    {
-      retryOnFailure: false,
-      maxNumberOfIntents: 2,
-      acceptsConnection: {
-        intents: 4,
-        interval: 1,
-      }
-    },
-    false
+    config,
+    externalServer
   )
-
-  const dispose = vi.fn()
-
-  server['registerServerDisposable']({ dispose })
-  server['clearServerDisposables']()
-
-  expect(dispose).toHaveBeenCalledTimes(1)
-})
+}
diff --git a/vitest.conf.ts b/vitest.conf.ts
index 187d6d24..b87c1dd8 100644
--- a/vitest.conf.ts
+++ b/vitest.conf.ts
@@ -12,7 +12,7 @@ export default defineConfig({
       enabled: true,
       exclude: ['**/build/**', '**/__fixtures__/**', '**/*.test.ts'],
       statements: 43,
-      branches: 88.4,
+      branches: 88.3,
       functions: 32.6,
       lines: 43
     }